docs(change): restrict CR-OC-001C to present, protected DACLs (§10.2b) - #130
Merged
Merged
Conversation
Three hosted Windows CI runs established that a successful ReplaceFileW does
not preserve the required invariant on inheritance-enabled targets. A new
section 10.2b narrows the supported profile rather than weakening the
invariant.
CR-OC-001C now supports only targets whose pre-mutation DACL is present,
non-NULL, and protected from inheritance. Absent, NULL, and
inheritance-enabled DACLs are refused as metadata_precondition_failed with
outcome not_attempted, before temporary-file creation and before any
replacement attempt.
Unchanged: the exact owner, DACL-state, control-bit, revision, ACE-count,
ordered-ACE, and complete-ACE-byte comparison; the SE_DACL_AUTO_INHERITED
monotonic rule; and the mandatory, authoritative post-verification.
Unsupported targets are never normalized, repaired, protected automatically,
or attempted.
Discovery record:
30689442321 unprotected target differed in dacl_auto_inherited,
ace_count, ace_bytes_or_order
30690450931 paired protected control did not fail, but was
under-asserted -- recorded deliberately, because absence
from a failure list is not evidence
30691068391 strengthened protected control passed all fixture, result,
outcome, exact-byte, and exact-metadata assertions; the
unprotected control reproduced the same three differences
What that establishes is narrow: on the hosted runner the unprotected
profile violated the exact invariant while the present-and-protected profile
completed a verified replacement under the same comparison. It does not
prove inheritance recomputation is the underlying Windows mechanism, and the
environment (Windows Server 2025, build 10.0.26100, windows-2025-vs2026,
Python 3.12.10, NTFS) is bounded evidence, not a universal guarantee.
An effective-permissions rule was rejected: ACE order affects access
decisions, and common effective-rights helpers omit owner rights,
privileges, logon-session groups, resource-manager policy, and some
inherited-deny cases.
pre_dacl_nonempty was a diagnostic validity control, not a production
requirement; a present, non-NULL, protected DACL with zero ACEs remains
supported.
Section 10.2's carry-over bullet and section 11.2's merge language no longer
imply that ReplaceFileW alone establishes general preservation.
T21 is revised into four parts rather than adding a thirty-seventh
obligation, so obligations stay at 36 while mutants move from 30 to 31
(21 Windows, 10 Ubuntu). M31 is omitting the present-and-protected
precondition. Sections 21 and 25.7 add the corresponding acceptance
requirements.
Also corrects a pre-existing stale count: the section 25.5 heading said 34
test obligations while its table has listed 36 since the 10.1a amendment.
Documentation only. No code, tests, workflow, dependency, fixture, or
configuration changed. The production profile gate and M31 remain
outstanding work on draft PR #128.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
✅ Deploy Preview for poetic-quokka-0fd859 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
1. T20 part 3 still described an unrestricted healthy replacement -- "an ordinary target created by this process reaches replacement_verified" -- which under section 10.2b either permitted or required a healthy result without establishing the supported DACL profile. It now reads: a target created by this process, then placed into a present, non-NULL, protected DACL profile without changing its owner, reaches replacement_verified, and post owner equals pre owner. The section 10.2a change-log lineage entry carries the same qualifier, with a note that the original wording would be ambiguous under the amended contract. 2. Section 1's implementation status said "Proposed, not authorized", which has been false since implementation was explicitly authorized. It now records that implementation is authorized and in progress on draft PR #128; not merged, not accepted, not runtime-integrated; and not yet conformant with section 10.2b, since the supported-profile gate, revised fixtures, T21 evidence, and M31 remain pending. The adjacent authority and approval-gate bullets are adjusted so they no longer deny an authority that was in fact granted, while still recording that it came from a separate explicit approval bounded to the section 25.1 seven-path allowlist, and that it is not merge authority. 3. The gate order is now explicit: the supported-profile gate is evaluated immediately after validating the pre-mutation security snapshot and before the TokenOwner ownership gate, despite the order the two bullets are written in. That gives T21's absent, NULL, and unprotected cases a precise cessation point and avoids querying the token's default owner for a profile already known to be unsupported. Section 22 needed no change: its "not authorized" line is scoped to the requirements-era five-path list and is immediately followed by the existing annotation recording that section 25.1 supersedes it. The backlog needed no change: its section 10.2a summary does not enumerate T20's three parts and never carried the ambiguous phrase. Documentation only, within the same three-path boundary. No code, tests, workflow, dependency, fixture, or configuration changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lineage sentence said each amendment was "merged as a documentation-only change before the implementation was corrected to match". That is true of 10.1a and 10.2a but not of 10.2b, whose implementation correction has not happened yet -- as the very next status bullet correctly states. Replaced with future-neutral wording: implementation is corrected only after the corresponding documentation-only amendment merges, and the 10.2b implementation correction remains pending on draft PR #128. Documentation only, within the same three-path boundary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
coreytshaffer
marked this pull request as ready for review
August 1, 2026 08:58
coreytshaffer
added a commit
that referenced
this pull request
Aug 1, 2026
Implements the amendment merged as 5e41d6f (PR #130). Production (triage_core/mediated_executor.py): The supported-profile gate is evaluated immediately after the pre-mutation security snapshot validates and BEFORE the TokenOwner ownership gate, so an already-unsupported profile ceases without a needless token query. A DACL that is absent, NULL, or not protected from inheritance is refused as metadata_precondition_failed / not_attempted, before temporary-file creation and before ReplaceFileW. The executor only INSPECTS and REFUSES. It never protects, normalizes, repairs, or otherwise alters a target's DACL to make it eligible. ACE count deliberately does not participate: a present, non-NULL, protected DACL with zero ACEs is supported. Tests (tests/test_mediated_executor.py): Healthy Windows fixtures are placed into the supported profile by construction via workspace(protect=True) and prepare_supported_target(), which disable inheritance while copying the effective ACEs in as explicit entries and leave the owner untouched. Fixture preparation lives entirely in the tests. A fixture that fails to protect raises rather than skipping. T20 part 3 is now a supported-profile control asserting the pre-state is present and protected, and that post owner equals pre owner. T21 gains: a genuine unprotected NTFS target refusing before any mutation with the cessation point at capture_security, no temp/replace/write/delete, no token query, bytes intact, no artifacts, and the DACL still unprotected afterwards (the executor did not repair it); seam-injected absent and NULL states refusing identically; a present-protected zero-ACE DACL proving pre_dacl_nonempty never leaked into production policy; and the supported-profile control requiring ok, replacement_verified, exact proposed bytes, and the complete metadata invariant. T35W now confirms the supported profile before replacing. With a protected fixture the runner exhibits True->True rather than False->True -- both accepted under 10.1a, which is why that section refused to require the normalization. The two temporary diagnostic tests are converted into their contracted T21 homes rather than left as parallel controls, and T35W now shares the single difference classifier so the two cannot drift. Evidence, observed rather than carried forward: Windows focused 149 passed / 1 optional skip Windows mandatory 173 passed / 1 deselected / 0 skipped, gate PASS Ubuntu neutral 96 passed / 54 skipped, guards 24 passed Full repository 1664 passed / 6 skipped Mutants 31/31 killed (21 Windows, 10 Ubuntu), zero survivors All 31 anchors pre-validated as unique and CRLF-normalised before any cycle; byte-exact restoration and a clean 640-file manifest before and after every Ubuntu cycle; JUnit privacy scan reports zero SIDs, access masks, and SDDL. No dependency, pyproject.toml, runtime-integration, or eighth-path change. The six named modules remain unmodified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CR-OC-001C §10.2b — restrict execution to present, protected DACLs
Documentation only. Three paths, no code, tests, workflow, dependency, fixture, or configuration change:
Branched from
origin/mainat8e19cdf. Merge is not authorized yet.The change
CR-OC-001C now supports only targets whose pre-mutation DACL is present, non-NULL, and protected from inheritance (
SE_DACL_PROTECTEDset). Any absent, NULL, or inheritance-enabled DACL is refused asmetadata_precondition_failedwith outcomenot_attempted, before temporary-file creation and before any replacement attempt.This narrows the supported profile rather than weakening the invariant. Unchanged:
AclRevision, ACE-count, ACE-order, and complete-ACE-byte comparison of §10.1 — nothing is relaxed, made unordered, made partial, or replaced by an effective-permissions rule;SE_DACL_AUTO_INHERITEDmonotonic rule;Unsupported targets are not normalized, repaired, protected automatically, or attempted. The executor never modifies a security descriptor to make a target supportable — it refuses.
The amendment does not claim Microsoft guarantees exact preservation for all protected DACLs.
Discovery record
The middle run is recorded deliberately. The protected control's absence from the failure list was not evidence. As first written it failed only on a metadata label beyond
dacl_auto_inherited, so a refusal such astemp_creation_failedaccompanied by no metadata differences would also have passed it — and the recorded result property was unrecoverable, because the mandatory command failed before the gate or summary exposed it and no artifact is uploaded.The control was strengthened to require simultaneously: a protected and nonempty fixture,
reason_code == ok, outcomereplacement_verified, target bytes equal to the proposed bytes, and metadata differences of none ordacl_auto_inheritedalone.What run 30691068391 establishes, stated narrowly
What it does not establish
It does not prove that inheritance recomputation is the underlying Windows mechanism. The diagnostic reported field labels only; it did not establish that every new or changed ACE was inherited, nor that effective access was unchanged. Microsoft documents
ReplaceFileWas preserving the DACL and separately describes the operation as merging attribute and ACL information into the replacement file, without promising byte-identical ACE enumeration in every inheritance environment. The governance conclusion does not depend on which explanation is correct.The hosted environment is recorded as bounded evidence, not a universal Windows guarantee:
Why not an effective-permissions rule
Materially harder to prove safely than exact equality: ACE order affects access decisions, and common effective-rights helpers omit owner rights, privileges, logon-session groups, resource-manager policy, and some inherited-deny cases. Exact equality over a narrower profile is the conservative direction.
pre_dacl_nonemptyis not a production requirementIt was a diagnostic validity control — its only job was to prove the hosted success was not a trivial empty-ACL comparison. The executor does not require a nonempty DACL: a present, non-NULL, protected DACL with zero ACEs remains supported, and §10.1's three-state classification still distinguishes present-empty from NULL and absent. §10.2b says so explicitly so a later reading cannot promote it.
§11 carry-over language revised
§10.2's DACL carry-over bullet and §11.2's merge language no longer imply that
ReplaceFileWalone establishes general exact preservation. Both now state that documentation is trusted to choose the primitive, never to skip the check; that hosted evidence supports preservation only within the protected profile; and that post-verification remains the deciding evidence.Obligations and mutants
T21 is revised, not supplemented — obligations stay at 36. It now covers four parts: an unprotected target refuses before temporary-file creation or
ReplaceFileWwith original bytes unchanged and no artifacts remaining; seam-injected absent and NULL DACL states refuse identically; a present and protected target completes withok,replacement_verified, exact proposed bytes, and the full exact metadata invariant; and existing injected post-replacement differences still producemetadata_preservation_failed.M31 — omitting the present-and-protected precondition, or treating an inheritance-enabled target as supported. Killer: T21[W]'s unprotected-target test. Windows-scoped by necessity, since its killer asserts refusal against a genuine NTFS descriptor and a real mechanism call log.
Verified against the tables: §20 and §25.6 each list 31 rows; 19 Windows-only + 2 dual-venue (M13, M27) = 21 Windows-participating, 10 Ubuntu-only.
§21 and §25.7 acceptance now additionally require: every healthy replacement case runs within the supported profile; T35[W] confirms the supported pre-state before replacement; every other participating component is exactly preserved; absent, NULL, and unprotected profiles fail before mutation; and M31 is killed cleanly.
One pre-existing correction, flagged
The §25.5 heading read "34 test obligations" while its table has listed 36 since the §10.1a amendment. Corrected here. This was not part of the ruling — it is a stale count found inside an authorized path, and I am flagging it rather than folding it in silently.
Sections touched
§1 (amendment lineage), §10.2, §10.2b (new), §11.2, §19 T21 and T35, §20, §21, §25.5, §25.6, §25.7.
What this does not do
No production or test change belongs on this branch, and none is present. The implementation on draft PR #128 remains unmerged; the production profile gate and M31 remain outstanding work there. Merge authority for this amendment has not been granted.
🤖 Generated with Claude Code