Skip to content

fix(determinism): Tier 1 -- deterministic ops must be deterministic (#418) - #424

Merged
mlieberman85 merged 1 commit into
darnitdevorg:mainfrom
mlieberman85:fix-tier1-determinism
Sep 8, 2026
Merged

fix(determinism): Tier 1 -- deterministic ops must be deterministic (#418)#424
mlieberman85 merged 1 commit into
darnitdevorg:mainfrom
mlieberman85:fix-tier1-determinism

Conversation

@mlieberman85

Copy link
Copy Markdown
Contributor

Closes #418.

Summary

Same repo + same command should produce the same audit / remediation
output every run. Fixes the seven concrete Tier 1 gaps surfaced in the
determinism survey.

Fixes

Filesystem iteration (audit + context):

  • context/auto_detect.py:78 -- detect_ci_provider() sorts
    os.listdir before scanning for .yml / .yaml siblings.
  • context/auto_detect.py:309 -- detect_has_subprojects() sorts
    iterdir children (defensive; today's len() check is
    order-independent but future first-match / slicing changes stay
    safe by construction).
  • sieve/builtin_handlers.py:96 -- _walk_depth_limited sorts
    dirnames in-place so "first match wins" downstream is stable.
  • sieve/builtin_handlers.py:135, 449 -- glob.glob results sorted
    at both call sites; taking matches[0] is deterministic across
    filesystems that don't sort glob output by default.

Wall-clock injection in remediation templates:

  • remediation/executor.py::_get_template_context -- DATE dropped
    (no template referenced it; day-per-day drift cluttered PR diffs
    for identical inputs). YEAR kept (LICENSE templates use it;
    year-per-year cadence is slow enough to stay stable within a
    calendar year). New now_provider kwarg on RemediationExecutor
    lets tests inject a fixed clock.

List ordering in remediation templates:

  • remediation/executor.py -- list-valued context (e.g.,
    maintainers) is sorted() before " ".join, so upstream
    ordering drift (dict iteration, API-response order) doesn't drift
    rendered output.

Non-atomic writes:

  • sieve/builtin_handlers.py -- new _atomic_write_text helper
    (tempfile-then-rename in same directory, cleans tempfile on
    failure). file_create_handler and yaml_inject_handler now route
    through it; a crash mid-write can no longer leave a partial file.
    Same invariant FilesystemAuditCacheStore uses (feature 033).

Behavior change to call out

DATE is no longer available in remediation template context.
Repo-wide check confirmed no template (in the workspace) references it;
external plugins that did would need to move to YEAR or supply their
own via context. YEAR is unchanged.

Test plan

  • 10 new tests at tests/darnit/test_determinism_tier1.py cover
    each guarantee:
    • Atomic write leaves no partial file on os.replace failure.
    • file_create_handler leaves no partial file on failure.
    • _walk_depth_limited visits subdirs in sorted order.
    • file_exists glob first-match is lexicographically smallest.
    • RemediationExecutor.now_provider injection derives YEAR.
    • DATE field is absent from template context.
    • List-valued context sorted before join (identical output for
      different upstream orderings of the same set).
    • detect_ci_provider consumes os.listdir via sorted().
  • Full framework + baseline suite: 2808 passed, 13 skipped, 0
    failed.
  • ruff check clean on touched files.

Related

…arnitdevorg#418)

Same repo + same command should produce the same audit / remediation
output every run. Fixes the seven concrete Tier 1 gaps surfaced in the
determinism survey.

Filesystem iteration (audit + context):
- context/auto_detect.py:78 -- detect_ci_provider() now sorts
  os.listdir results before scanning for .yml / .yaml siblings.
- context/auto_detect.py:309 -- detect_has_subprojects() sorts
  iterdir children even though len() is order-independent today;
  future first-match/slicing changes stay safe by construction.
- sieve/builtin_handlers.py:96 -- _walk_depth_limited sorts dirnames
  in-place before yielding so "first match wins" downstream is stable.
- sieve/builtin_handlers.py:135, 449 -- glob.glob results sorted at
  both call sites; taking matches[0] is now deterministic across
  filesystems that don't sort glob output by default.

Wall-clock injection into remediation templates:
- remediation/executor.py:_get_template_context -- DATE dropped (no
  template referenced it, day-per-day drift cluttered PR diffs for
  identical inputs). YEAR kept (LICENSE templates use it; year-per-year
  cadence is slow enough to stay stable within a calendar year). New
  now_provider kwarg on RemediationExecutor lets tests inject a fixed
  clock so YEAR's derivation is deterministic under test.

List ordering in remediation templates:
- remediation/executor.py -- list-valued context (e.g., maintainers)
  is sorted before " ".join, so upstream ordering drift (dict
  iteration, API-response order) doesn't drift rendered output.

Non-atomic writes:
- sieve/builtin_handlers.py -- new _atomic_write_text helper
  (tempfile-then-rename in same directory, cleans tempfile on
  failure). file_create_handler:837 and yaml_inject_handler:983 now
  route through it; a crash mid-write can no longer leave a partial
  file. Same invariant FilesystemAuditCacheStore uses (feature 033).

Tests:
- tests/darnit/test_determinism_tier1.py -- 10 new tests covering each
  guarantee: atomic write with no partial file on os.replace failure,
  file_create no-partial-on-failure, _walk_depth_limited sorted
  visitation, file_exists glob first-match stability, now_provider
  injection, DATE-field absence, list-value sort determinism, and
  detect_ci_provider sorted-listdir consumption.

Full framework + baseline test suite: 2808 passed, 13 skipped, 0 failed.
@mlieberman85
mlieberman85 merged commit 513de1d into darnitdevorg:main Sep 8, 2026
7 checks passed
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Sep 8, 2026
User Story 2 of feature 036: environmental failures now surface at the
default log level. Previously they were DEBUG-only or silent, so an
operator whose audit was half-degraded by a broken token or an
unreachable MCP server had no way to notice without knowing to raise
verbosity first.

MCP handler (contract section 2.2): all nine exception paths now carry
an error_class. FR-006 names three; the remaining six are the ones the
FR's final clause delegates to the contract table.
  McpToolTimeout            -> timeout
  McpServerHandshakeFailed  -> network
  McpServerBinaryMissing    -> not_found
  McpServerVerificationFailed -> auth     (a trust relationship to fix)
  McpServerUnusable         -> network
  McpToolError              -> crashed    (ran, errored internally)
  McpToolResponseNotJson    -> crashed    (ran, output uninterpretable)
  UnknownMcpServer          -> not_found  (server never configured)
  bare Exception            -> crashed

The error_info tuple widened from (status, message) to
(status, message, error_class) to carry the classification to the
single construction site.

Orchestrator: a handler that raises now yields error_class="crashed"
and logs at WARN naming the control. A crashed handler and a genuine
ERROR verdict were previously indistinguishable in default logs.

Context auto-detect: _get_remote_url distinguishes two cases that were
both silent before. A non-zero exit is git's legitimate answer ("no
such remote") and stays quiet -- warning there would make every
single-remote repo noisy, which is how loud logging becomes ignored
logging. An exception (timeout / missing git / OSError) means we could
not consult git at all, and now warns with the matching error_class.

Also fixes a bug I introduced in feature 418 (PR darnitdevorg#424): inserting
_atomic_write_text directly after MCP_DEFAULT_TIMEOUT_SECONDS orphaned
that constant's attribute docstring, leaving it dangling as a bare
string expression after an unrelated function. Docstring reattached.

18 new tests (7 MCP mappings parameterized, orchestrator crash path,
3 auto-detect cases, happy-path no-WARN guard, dependency assertions).
Full suite: 2855 passed, 13 skipped. Baseline byte-identical.
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Sep 8, 2026
…US4)

Completes feature 036. Machine-readable surfaces plus a spec gap found
during the manual walkthrough.

Output surfaces:
- JSON: the full shape already passed error_class through (it
  serializes CheckResult verbatim); the summary shape stripped it.
  Extracted _compact_result to make FR-011 testable and to keep the
  field -- a summary that hides "we could not verify" would let a CI
  job report an unreachable network as a compliance failure.
- SARIF: properties["errorClass"], camelCase to match the sibling
  resolvingPassHandler / passHistory keys.
- Attestation predicate: additive next to authority, no schema version
  bump (feature 025 set that precedent). A signed attestation carrying
  a bare FAIL when the check never reached GitHub is exactly the
  misleading claim Principle II forbids.
- CLI text output: also annotated. The quickstart promised
  "FAIL [auth]" in terminal output, and only the MCP-side markdown
  formatter had been wired -- the doc was writing a check the code
  could not cash.

Spec gap found by the T032 walkthrough, worth calling out:

FR-009a says error_class comes from the RESOLVING pass. But when every
pass is inconclusive there IS no resolving pass, and that is the most
common shape for a degraded audit -- so a fully broken environment
reported a bare "manual verification required" with no hint the token
had expired. That defeats US1 entirely.

Added a scoped fallback: with no conclusion to supersede it, the most
recent environmental classification is the best available explanation.
This extends FR-009a's letter while serving its intent (a later
conclusion supersedes an earlier failure); it only fires where FR-009a
is silent.

Getting the fallback right took two attempts. "Last pass's value" read
plausibly but was wrong: nearly every baseline control ends with a
`manual` pass, an ask-a-human placeholder that always returns
INCONCLUSIVE and can never conclude anything. Treating that as "the
final attempt ran cleanly" wiped the real exec failure preceding it.
Corrected to last non-None, with a test for that exact shape.

Verified end to end against a live repo with an invalid GH_TOKEN:
26 results split 20 unannotated / 6 classified (1 auth, 4 network,
1 not_found). The auth case is correctly distinguished from the 404s,
and every genuine finding stays unannotated.

Also fixes a bug from feature 418 (PR darnitdevorg#424): inserting
_atomic_write_text after MCP_DEFAULT_TIMEOUT_SECONDS orphaned that
constant's attribute docstring.

Full suite: 2870 passed, 13 skipped. Baseline byte-identical.
validate_sync passes. ruff clean.
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Sep 8, 2026
User Story 2 of feature 036: environmental failures now surface at the
default log level. Previously they were DEBUG-only or silent, so an
operator whose audit was half-degraded by a broken token or an
unreachable MCP server had no way to notice without knowing to raise
verbosity first.

MCP handler (contract section 2.2): all nine exception paths now carry
an error_class. FR-006 names three; the remaining six are the ones the
FR's final clause delegates to the contract table.
  McpToolTimeout            -> timeout
  McpServerHandshakeFailed  -> network
  McpServerBinaryMissing    -> not_found
  McpServerVerificationFailed -> auth     (a trust relationship to fix)
  McpServerUnusable         -> network
  McpToolError              -> crashed    (ran, errored internally)
  McpToolResponseNotJson    -> crashed    (ran, output uninterpretable)
  UnknownMcpServer          -> not_found  (server never configured)
  bare Exception            -> crashed

The error_info tuple widened from (status, message) to
(status, message, error_class) to carry the classification to the
single construction site.

Orchestrator: a handler that raises now yields error_class="crashed"
and logs at WARN naming the control. A crashed handler and a genuine
ERROR verdict were previously indistinguishable in default logs.

Context auto-detect: _get_remote_url distinguishes two cases that were
both silent before. A non-zero exit is git's legitimate answer ("no
such remote") and stays quiet -- warning there would make every
single-remote repo noisy, which is how loud logging becomes ignored
logging. An exception (timeout / missing git / OSError) means we could
not consult git at all, and now warns with the matching error_class.

Also fixes a bug I introduced in feature 418 (PR darnitdevorg#424): inserting
_atomic_write_text directly after MCP_DEFAULT_TIMEOUT_SECONDS orphaned
that constant's attribute docstring, leaving it dangling as a bare
string expression after an unrelated function. Docstring reattached.

18 new tests (7 MCP mappings parameterized, orchestrator crash path,
3 auto-detect cases, happy-path no-WARN guard, dependency assertions).
Full suite: 2855 passed, 13 skipped. Baseline byte-identical.
mlieberman85 added a commit to mlieberman85/darnit that referenced this pull request Sep 8, 2026
…US4)

Completes feature 036. Machine-readable surfaces plus a spec gap found
during the manual walkthrough.

Output surfaces:
- JSON: the full shape already passed error_class through (it
  serializes CheckResult verbatim); the summary shape stripped it.
  Extracted _compact_result to make FR-011 testable and to keep the
  field -- a summary that hides "we could not verify" would let a CI
  job report an unreachable network as a compliance failure.
- SARIF: properties["errorClass"], camelCase to match the sibling
  resolvingPassHandler / passHistory keys.
- Attestation predicate: additive next to authority, no schema version
  bump (feature 025 set that precedent). A signed attestation carrying
  a bare FAIL when the check never reached GitHub is exactly the
  misleading claim Principle II forbids.
- CLI text output: also annotated. The quickstart promised
  "FAIL [auth]" in terminal output, and only the MCP-side markdown
  formatter had been wired -- the doc was writing a check the code
  could not cash.

Spec gap found by the T032 walkthrough, worth calling out:

FR-009a says error_class comes from the RESOLVING pass. But when every
pass is inconclusive there IS no resolving pass, and that is the most
common shape for a degraded audit -- so a fully broken environment
reported a bare "manual verification required" with no hint the token
had expired. That defeats US1 entirely.

Added a scoped fallback: with no conclusion to supersede it, the most
recent environmental classification is the best available explanation.
This extends FR-009a's letter while serving its intent (a later
conclusion supersedes an earlier failure); it only fires where FR-009a
is silent.

Getting the fallback right took two attempts. "Last pass's value" read
plausibly but was wrong: nearly every baseline control ends with a
`manual` pass, an ask-a-human placeholder that always returns
INCONCLUSIVE and can never conclude anything. Treating that as "the
final attempt ran cleanly" wiped the real exec failure preceding it.
Corrected to last non-None, with a test for that exact shape.

Verified end to end against a live repo with an invalid GH_TOKEN:
26 results split 20 unannotated / 6 classified (1 auth, 4 network,
1 not_found). The auth case is correctly distinguished from the 404s,
and every genuine finding stays unannotated.

Also fixes a bug from feature 418 (PR darnitdevorg#424): inserting
_atomic_write_text after MCP_DEFAULT_TIMEOUT_SECONDS orphaned that
constant's attribute docstring.

Full suite: 2870 passed, 13 skipped. Baseline byte-identical.
validate_sync passes. ruff clean.
Marc-cn pushed a commit that referenced this pull request Sep 9, 2026
…36) (#435)

* test(036): capture pre-feature output baseline for SC-002

Feature 036 SC-002 requires that an audit with zero environmental
failures produce byte-for-byte identical markdown / JSON / SARIF output
to the pre-feature implementation. Verifying that needs a baseline
captured from unmodified code -- goldens generated during
implementation would lock post-feature behavior instead of proving
pre-feature equivalence.

Captures two purely-deterministic controls (OSPS-DO-01.01 README
presence, OSPS-LE-03.01 LICENSE presence -- the same pair the parity
fixture corpus uses) against the mixed_repo fixture. Yields 1 PASS +
1 FAIL, so the baseline exercises both the success path and the
clean-failure path that must NOT carry an error_class once the feature
lands.

capture_baseline.py is committed alongside so the baseline is
regenerable and the scrubbing rules are reviewable.

Three fields are scrubbed as run- or machine-varying, none related to
this feature:
- fixture tempdir path (machine-dependent)
- format_results_markdown's "Generated At:" stamp (run-dependent).
  This is a real Tier 1 determinism gap -- markdown audit reports can
  never be byte-for-byte reproducible while the formatter embeds a
  wall-clock timestamp. Out of scope here; follow-up on #418.
- pass_history[].duration_ms (load-dependent; reads 0 for these fast
  checks in practice but nothing guarantees it)

Verified reproducible: two consecutive captures are byte-identical.

* feat(sieve): add error_class type and result fields (feature 036 phase 2)

Foundational half of feature 036. Adds the ErrorClass type and threads
the field through the three result objects, with the CEL post-step fix
that the field would otherwise be silently dropped by.

New module core/error_class.py exports two names:
- ErrorClass: Literal of the six v0 values (network, auth, timeout,
  rate_limit, not_found, crashed).
- ERROR_CLASSES: the same six as a frozenset.

Both are needed because typing.Literal is erased at runtime and
enforces nothing on its own -- the frozenset is what makes validation
possible (FR-002a). This mirrors core/authority.py's Literal+frozenset
pairing, but diverges in how the frozenset is used: authority.py backs
a fail-safe (is_terminal_authority returns False for unknowns, so an
unknown authority can never conclude a control), whereas an unknown
error_class has no safe default -- it is neither "the check failed" nor
"the check could not run" -- so this module's frozenset backs a raise.

Field additions, all additive:
- HandlerResult.error_class, plus a __post_init__ enforcing both
  contract invariants: unknown values are rejected (FR-002a), and
  error_class alongside status=PASS is rejected (a handler that could
  not complete cannot have produced a real pass).
- SieveResult.error_class, populated from the RESOLVING pass only per
  FR-009a. CONCLUDE_PASS is deliberately excluded from the threading:
  it fires only when handler_status is PASS, which HandlerResult makes
  unrepresentable alongside error_class, so there is provably nothing
  to carry.
- CheckResult["error_class"] as NotRequired[str], emitted from
  to_legacy_dict only when set. Typed str rather than ErrorClass
  because the TypedDict is the deserialization boundary.

The CEL fix is the subtle part. _apply_cel_expr builds brand-new
HandlerResult objects at three construction sites rather than mutating
the input, so any field not threaded explicitly is lost with no error
-- the verdict stays correct and only the failure cause disappears.
Feature 026 hit this identical bug with authority; the fix follows the
same shape. Tests were written first and confirmed failing on exactly
the three constructing branches (the five pass-through paths were
already safe), then passing after.

Full framework + baseline suite: 2816 passed, 13 skipped.
Baseline from 4d3d37f re-verified byte-identical: zero happy-path drift.

* feat(sieve): classify exec failures and surface error_class in markdown (036 US1)

User Story 1 of feature 036: an operator running an audit with an
expired token can now tell from the markdown report alone which
controls failed because the environment was broken and which failed
because the repository is genuinely non-compliant.

exec handler classification (contract section 2.1):
- subprocess.TimeoutExpired -> timeout
- FileNotFoundError -> not_found (the binary is absent from PATH)
- undeclared non-zero exit -> classified from stderr via
  _classify_exec_failure: GitHub rate-limit patterns first, then auth
  patterns, else network

Rate-limit is deliberately checked before auth because GitHub answers
403 for both rate limits and permission failures, and the rate-limit
body is the more specific signal.

Two paths deliberately carry NO error_class:
- pass_exit_codes (the check succeeded)
- declared fail_exit_codes (the check RAN and concluded non-compliance)

That second one is the invariant that keeps the feature honest. If a
declared failure code started producing an error_class, every real
finding would read as an infrastructure blip and the annotation would
become noise. There is an explicit test for it.

Pattern constants are GitHub-only for v0 per clarify Q4. The exec
handler sees only stdout/stderr/exit-code -- no response headers -- so
classification is substring matching against `gh` CLI stderr shape.
Other targets (git, curl, syft) fall through to network.

Environmental failures now log at WARN via _log_environmental_failure,
naming the control, the handler, and the error_class. Previously these
were silent or DEBUG-only, so a degraded audit was invisible unless
the operator knew to raise verbosity.

Markdown formatter annotates the control line with `[error_class]`
when present, leaving genuine findings unannotated.

21 new tests. Full suite: 2837 passed, 13 skipped.
Baseline from 4d3d37f re-verified byte-identical.

* feat(sieve): classify MCP, crash, and git failures at WARN (036 US2)

User Story 2 of feature 036: environmental failures now surface at the
default log level. Previously they were DEBUG-only or silent, so an
operator whose audit was half-degraded by a broken token or an
unreachable MCP server had no way to notice without knowing to raise
verbosity first.

MCP handler (contract section 2.2): all nine exception paths now carry
an error_class. FR-006 names three; the remaining six are the ones the
FR's final clause delegates to the contract table.
  McpToolTimeout            -> timeout
  McpServerHandshakeFailed  -> network
  McpServerBinaryMissing    -> not_found
  McpServerVerificationFailed -> auth     (a trust relationship to fix)
  McpServerUnusable         -> network
  McpToolError              -> crashed    (ran, errored internally)
  McpToolResponseNotJson    -> crashed    (ran, output uninterpretable)
  UnknownMcpServer          -> not_found  (server never configured)
  bare Exception            -> crashed

The error_info tuple widened from (status, message) to
(status, message, error_class) to carry the classification to the
single construction site.

Orchestrator: a handler that raises now yields error_class="crashed"
and logs at WARN naming the control. A crashed handler and a genuine
ERROR verdict were previously indistinguishable in default logs.

Context auto-detect: _get_remote_url distinguishes two cases that were
both silent before. A non-zero exit is git's legitimate answer ("no
such remote") and stays quiet -- warning there would make every
single-remote repo noisy, which is how loud logging becomes ignored
logging. An exception (timeout / missing git / OSError) means we could
not consult git at all, and now warns with the matching error_class.

Also fixes a bug I introduced in feature 418 (PR #424): inserting
_atomic_write_text directly after MCP_DEFAULT_TIMEOUT_SECONDS orphaned
that constant's attribute docstring, leaving it dangling as a bare
string expression after an unrelated function. Docstring reattached.

18 new tests (7 MCP mappings parameterized, orchestrator crash path,
3 auto-detect cases, happy-path no-WARN guard, dependency assertions).
Full suite: 2855 passed, 13 skipped. Baseline byte-identical.

* feat: surface error_class in reports, attestations, and CLI (036 US3+US4)

Completes feature 036. Machine-readable surfaces plus a spec gap found
during the manual walkthrough.

Output surfaces:
- JSON: the full shape already passed error_class through (it
  serializes CheckResult verbatim); the summary shape stripped it.
  Extracted _compact_result to make FR-011 testable and to keep the
  field -- a summary that hides "we could not verify" would let a CI
  job report an unreachable network as a compliance failure.
- SARIF: properties["errorClass"], camelCase to match the sibling
  resolvingPassHandler / passHistory keys.
- Attestation predicate: additive next to authority, no schema version
  bump (feature 025 set that precedent). A signed attestation carrying
  a bare FAIL when the check never reached GitHub is exactly the
  misleading claim Principle II forbids.
- CLI text output: also annotated. The quickstart promised
  "FAIL [auth]" in terminal output, and only the MCP-side markdown
  formatter had been wired -- the doc was writing a check the code
  could not cash.

Spec gap found by the T032 walkthrough, worth calling out:

FR-009a says error_class comes from the RESOLVING pass. But when every
pass is inconclusive there IS no resolving pass, and that is the most
common shape for a degraded audit -- so a fully broken environment
reported a bare "manual verification required" with no hint the token
had expired. That defeats US1 entirely.

Added a scoped fallback: with no conclusion to supersede it, the most
recent environmental classification is the best available explanation.
This extends FR-009a's letter while serving its intent (a later
conclusion supersedes an earlier failure); it only fires where FR-009a
is silent.

Getting the fallback right took two attempts. "Last pass's value" read
plausibly but was wrong: nearly every baseline control ends with a
`manual` pass, an ask-a-human placeholder that always returns
INCONCLUSIVE and can never conclude anything. Treating that as "the
final attempt ran cleanly" wiped the real exec failure preceding it.
Corrected to last non-None, with a test for that exact shape.

Verified end to end against a live repo with an invalid GH_TOKEN:
26 results split 20 unannotated / 6 classified (1 auth, 4 network,
1 not_found). The auth case is correctly distinguished from the 404s,
and every genuine finding stays unannotated.

Also fixes a bug from feature 418 (PR #424): inserting
_atomic_write_text after MCP_DEFAULT_TIMEOUT_SECONDS orphaned that
constant's attribute docstring.

Full suite: 2870 passed, 13 skipped. Baseline byte-identical.
validate_sync passes. ruff clean.

* docs(036): document the all-inconclusive WARN fallback as FR-009b

The fallback was added during implementation (T032 manual walkthrough)
and until now existed only in code comments and the PR body. Folding it
into the spec so the behavior is a stated requirement rather than an
undocumented implementation detail.

FR-009a says error_class comes from the RESOLVING pass. When every pass
returns INCONCLUSIVE and the control terminates WARN, there is no
resolving pass, so FR-009a has nothing to select -- and the strict
reading leaves the single most common degraded-audit shape with no
explanation at all. An operator whose token expired sees a bare "Could
not automatically verify" and never learns why.

FR-009b covers that gap: with no conclusion to supersede it, the most
recent non-null error_class across the chain propagates. It applies only
where FR-009a is silent and preserves FR-009a's ordering intent, so the
two do not conflict.

Non-null rather than simply most-recent is the load-bearing detail.
Nearly every OpenSSF Baseline control ends with a `manual` pass, an
ask-a-human placeholder that always returns INCONCLUSIVE and can never
conclude anything. Reading it as "the final attempt ran cleanly" erases
the real exec failure preceding it -- exactly the case the requirement
exists to serve. Contract section 3.2 carries the truth table.

Also records in the spec-quality checklist that FR-009b was added
retroactively, so a later reader can see the requirement postdates the
clarify session rather than assuming it was designed up front.

No code change -- the implementation already matches, with coverage in
TestAllInconclusiveWarnFallback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Determinism Tier 1: normally deterministic operations must be deterministic across runs

1 participant