Repository navigation
fix(runtime): stop host artifact gates rejecting valid campaign output - #1176
Merged
Merged
Conversation
…authority
The host re-check of aggregate-test-files built its source bundles from
its own walk of every planned transitive ancestor, keyed by planned node
ID, with its own marker, manifest and prerequisite-chain authentication.
That walk disagreed with the in-workflow verifier in two ways:
- It read every generated-tests producer, including a failure_policy
continue strategy or goal that failed before its verifier wrote a
marker. The verifier had admitted the aggregation without that
optional producer, but the host threw "artifact verification marker
does not exist", failing aggregate-test-files and, through it, the
final report.
- It looked a materialized dynamic producer up under its node ID
("dynamic:threat:<id>") instead of its storage attempt ID, and threw
"contains unsafe segment".
Take the producers from finalizedDeclaredContractProducers instead: it
walks the attempt's sealed ancestor closure, skips optional producers
the verifier-persisted admission omitted, maps each attempt to its
planned node, and reads each producer through the canonical
loadFinalizedNodeOutputSnapshot authority. The module keeps only the
bundle projection, attributed to the producer's loop attempt index as
the verifier does. The bespoke authentication chain and its tests go
away; the canonical reader has its own coverage.
The artifact-gates fixture sealer also stops giving deferred dynamic
templates workflow bindings, which real graphs never carry and the task
manifest validator rejects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… descendants assertExactSealedAttemptAuthority requires a consumer's sealed dependencyArtifactDirs to equal the attempts of every ancestor in the current planned graph. After a dynamic group expands, the lowered graph reaches the generated attempts from every descendant of the group's direct dependents, but lowerTaskDynamicDependencies adds those attempts only to the direct dependents' sealed tasks. Every deeper descendant therefore failed each contextual host gate that consults the sealed authority, even though the in-workflow verifier had admitted it through the same sealed closure: sealed Smithers attempt "<node>" artifact ancestor closure does not match the exact planned set; missing: ".../artifacts/dynamic-..." In the default topology dedupe-findings is the direct dependent of the goal groups, so triage, severity-classification, aggregate-test-files and final-report are such descendants. I reproduced the closure shape with startRun plus materializeDynamicRuntime on a planner -> dynamic fan-out -> join -> triage-shaped grandchild topology: the grandchild's sealed closure is [planner, join], the lowered graph's is [planner, join, dynamic:threat:<id>], and verifyRequiredArtifactsForAttempt failed with the message above. Expect a generated ancestor's attempts only when the sealed closure names them. Static ancestors must still all be present and nothing outside the planned closure is accepted, so the existing fan-in omitted-ancestor and unrelated-ancestor checks keep failing as before. Changing the lowering itself would rewrite in-flight runs' sealed task plans and widen what descendants must admit, so it is left alone here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
severity-matrix.ts ran only in the host re-check, keyed on a node whose logical ID is severity-classification or that declares report@3, and required machine-readable severity, impact and likelihood on every record. severity-classified-findings.schema.json requires those fields only for true positives, and the severity prompt keeps false-positive and non-production records in the file while forbidding a guess at their impact or likelihood. A compliant file with one false-positive record therefore passed JSON Schema and every registry gate inside the attempt, then failed host finalization with three SEVERITY_MATRIX_FIELD_MISSING errors, which blocks aggregate-test-files and final-report behind it. Everything else the module checked is already enforced where the verifier runs it too: both schemas are additionalProperties: false with High/Medium/Low enums (legacy aliases, lowercase levels, wrappers and non-object records), the severity-classification-matrix registry gate checks the matrix whenever all three fields are present, and in strict severity-handoff mode report rows must copy those fields exactly from the matrix-checked classified findings. One check has no replacement: report rows authored in bounded classification mode (topologies without a severity-classification node, such as smoke) are no longer matrix-checked anywhere. That mismatch now lets a report through instead of costing the run its final report; if it should be enforced, it belongs in the bounded report gate where the verifier can retry it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host re-check scans report.md, report.json strings and the coverage producer's Markdown for natural-language coverage scores and rejected any percentage or fraction that does not name an exact declaration-completeness scope on the same rendered line. Ordinary campaign prose matches it: "Recon reached 85% line coverage on Vault.sol." in a report.json note, or "Handlers reachable: 7/9" and "Retry 1/2 reproduced the same revert." in coverage-report.md. The in-workflow verifier has no equivalent check and agents cannot self-check it, so a compliant final report or coverage node passed its attempt and then failed host finalization. As a stop-gap, emit UNSCOPED_COVERAGE_SCORE, _PERCENTAGE and _FRACTION as warnings. Whether to delete the scanner is a separate decision. The typed coverage_evidence deep-equality check, the canonical scoped section projection (including a duplicated or contradicting scoped score anywhere in the document), the stylesheet and select-control checks and the 2,048-candidate scan limit keep their error severity. Tests that asserted these prose findings were fatal now assert that the scanner still reports them, as warnings, and that the node passes when nothing else is wrong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ucers The sealed-authority aggregation context attributes each source bundle to its producer's planned loop attempt index, which is what the in-workflow verifier uses (dependencyTask.metadata.loop.attemptIndex) and what the task manifest validator binds to the planned node. The previous host walk used the model-fanout attempt index instead, so for a two-profile producer the host expected attempt_index 1 for the second model while the verifier required 0, and any manifest one gate accepted the other rejected. On origin/main this test fails with "Aggregation source bundle attribution does not match authority" for strategy-a__model_1__attempt_1; it passes with the sealed-authority context. Destination paths in the fixture manifest now include the attempt ID so two attempts of one logical node do not collide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @@ -422,7 +421,6 @@ export function verifyRequiredArtifactsForAttempt( | |||
| diagnostics.push(diagnosticFromError(error, "artifact-gates", "REQUIRED_ARTIFACT_INVALID")); | |||
There was a problem hiding this comment.
Bounded report severities go unchecked
In a bounded topology such as smoke, the final report assigns each promoted finding’s severity, impact, and likelihood without an upstream severity-classification artifact. Removing this host check leaves no matrix check for those rows. A report can therefore publish High severity with High impact and Low likelihood, even though the matrix yields Medium. Keep the check in the verifier-accessible bounded-report path so contradictory severity cannot be published.
Knowledge Base Used: Artifact management
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/artifact-gates.ts
Line: 421
Comment:
**Bounded report severities go unchecked**
In a bounded topology such as `smoke`, the final report assigns each promoted finding’s severity, impact, and likelihood without an upstream severity-classification artifact. Removing this host check leaves no matrix check for those rows. A report can therefore publish `High` severity with `High` impact and `Low` likelihood, even though the matrix yields `Medium`. Keep the check in the verifier-accessible bounded-report path so contradictory severity cannot be published.
**Knowledge Base Used:** [Artifact management](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/artifact-management.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…oducer was admitted The previous commit made UNSCOPED_COVERAGE_* warnings, but verifyFinalReportCoverageEvidence still failed the final report when no coverage-evidence producer was planned or admitted. It treated any coverage-context score in report.md as invented coverage evidence and emitted REPORT_COVERAGE_EVIDENCE_UNPLANNED as an error. That path covers every smoke run, which plans no coverage producer, and default runs whose continue-policy stateful-invariant-coverage failed, so a sentence such as "Recon reached 85% line coverage on Vault.sol." still failed final-report there. Only a score bound to an exact declaration-completeness scope now counts as a Markdown coverage claim. Without an admitted producer, a typed report.json.coverage_evidence or a scoped report.md score still fails, so the existing invented-coverage case keeps failing. The producer-free and planned-but-unadmitted final-report tests now also publish unscoped coverage sentences. Both fail with REPORT_COVERAGE_EVIDENCE_UNPLANNED without this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eference docs 84b4a79 reworded the bare-score sentence only in docs/reference/artifacts-reports.md. The prompts semantic-anchors test requires that page to contain the coverage-evidence-markdown.mdx partial verbatim, so it failed. The partial also still told agents that runtime publication rejects bare coverage scores, which stopped being true when those became warnings. The partial now says publication rejects missing, duplicated, reordered, or contradicting scoped scores and warns about scores that name no exact scope. The docs page carries it verbatim again, followed by a docs-only paragraph on the scan limit and on final reports without an admitted coverage producer. The partial is rendered into the stateful-invariant coverage and final-report prompts; it is not a validator-build input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate results The deleted aggregation-semantic-context tests covered an admitted producer without controller finalization. The failed-optional aggregation test now also removes the admitted producer's verification marker and expects REQUIRED_ARTIFACT_INVALID, which pins that the canonical finalized reader still guards aggregation intake. The old authentication chain also rejected a producer without a marker (that is how the failed optional producer fails on origin/main), so this is a regression guard rather than a behavioural change. The implicit-visibility coverage loop lost its result assertion when the prose findings became warnings. It now asserts that only the two stylesheet cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rage prose sentence The entry said final-report rows authored in bounded classification mode are no longer matrix-checked. They are: the in-workflow verifier runs projectCanonicalFinalReport on every task that declares a report@3 output, and assertCanonicalIssuePresentation rejects an issue whose severity differs from the impact x likelihood result. The entry now says so, states that unscoped coverage prose is a warning even without an admitted coverage producer, and is shorter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every pull request in this batch inserts its entry at the same place in CHANGELOG.md, so each merge would conflict with the next. The entries are collected into one changelog update instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 29, 2026
aviggiano
added a commit
that referenced
this pull request
Sep 29, 2026
…whose dynamic groups expanded
Every render and every lifecycle admission re-derives the published
dynamic runtime controls from their sealed base with the current code,
including re-rendering each runtime-rendered prompt, and refused a
published prompt.rendered.md whose bytes differed from the fresh render
("runtime rendered prompt changed for <attempt>"). So any renderer or
projection change between the build that published a prompt and the
reading build stranded the run: why, stats, resume, replay, fork and
verified reads failed with WORKFLOW_CONTROL_EVIDENCE_INVALID, status and
inspect stopped synchronizing it, and a resumed controller threw on its
first render. #1176 changed the coverage projection that the stock final
report embeds, and the final report renders as soon as the goal groups
expand, so current main can neither synchronize nor resume a
packaged-topology run that v0.1.1 took past goal-plan. #1195's selector
order does the same for custom prompts with multi-path selectors.
A published prompt is what its task was, or will be, handed, so it is
now kept as published whatever this build renders: renderReadyRuntimePrompts
returns the drifted attempt IDs instead of throwing. The controller
adopts them silently. verifyWorkflowControlSnapshot carries them on the
verified snapshot (and only reuses a remembered snapshot whose drift list
matches), and synchronization adds one WORKFLOW_PUBLISHED_PROMPT_DRIFT
warning per pass naming the count and up to three attempts, so status,
inspect, why and stats return it. Every other re-derivation check is
unchanged: a missing prompt, a changed group template or expansion
manifest, and a task plan or graph that no longer re-derives still fail
closed. A prompt not yet published is rendered and published exactly as
before.
The cost: a prompt.rendered.md that a same-UID process writes or rewrites
for a later task, even before the controller first renders it, is now
adopted and reported as drift rather than refused.
Refs #921, #1188, #1195, #1176
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 29, 2026
aviggiano
added a commit
that referenced
this pull request
Sep 30, 2026
…ng change New entries now go straight into CHANGELOG.md under Unreleased, so the entry this branch had moved to consolidated release notes comes back. It sits under Breaking changes rather than Other changes: a run whose report producer succeeded under an earlier release's workflow and that is resumed with --refresh-controller before its verifier runs now fails that verifier until `resume --refresh-controller --retry-failed` reruns the producer, and the reference docs retire #585's claim that the run filesystem cannot be used to forge the producer. Main already files changes without a `!` whose only break is to older runs there (#1176, #1193). The entry also says that plain `resume` keeps the workflow persisted at launch, and that the recovered producer's failed_attempts omits the attempts from before the refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 30, 2026
Merged
aviggiano
added a commit
that referenced
this pull request
Sep 30, 2026
…ng change New entries now go straight into CHANGELOG.md under Unreleased, so the entry this branch had moved to consolidated release notes comes back. It sits under Breaking changes rather than Other changes: a run whose report producer succeeded under an earlier release's workflow and that is resumed with --refresh-controller before its verifier runs now fails that verifier until `resume --refresh-controller --retry-failed` reruns the producer, and the reference docs retire #585's claim that the run filesystem cannot be used to forge the producer. Main already files changes without a `!` whose only break is to older runs there (#1176, #1193). The entry also says that plain `resume` keeps the workflow persisted at launch, and that the recovered producer's failed_attempts omits the attempts from before the refresh. It describes what the replaced query cost (a runner subprocess with a 64 MiB read under a 180 s budget) rather than a missing `smithers` on PATH, since the run's `trusted-bin/smithers` shim now gives the controller PATH a runner on every host, and it says every producer attempt writes the record, since per-node cloud workers are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano
added a commit
that referenced
this pull request
Sep 30, 2026
An attempt's artifacts/<attempt>/prompt.rendered.md is now the prompt it receives on every verb, and nothing hashes, compares or seals it after launch. Prompt tamper detection cost more than it bought: an operator's edit of a task that had not run, or an upgrade that renders templates differently (#1176, #1195), stranded the run, and a static prompt had three different sources depending on the verb. Deleted: - the runtime byte comparison of published prompts, and the verify-mode "missing" throw (renderReadyRuntimePrompts renders only a missing file and otherwise adopts it; admission renders nothing); - the group-template digest gate (DYNAMIC_TEMPLATE_CHANGED) and its now-unused templatePath input; the manifest compatibility rows, which compare launch values with launch values, stay; - the prompt entries of the control seal and execution snapshot (controls/rendered-prompts/, controls/prompt-snapshots/), the workflow's sealed-copy read, and the --refresh-controller retained binding with its digest checks; - the digest gate on restoring a missing static prompt; - the dead dynamicRuntimePromptDigest export. Added: - a prompt problem fails only its task: a runtime prompt that cannot be rendered is returned as promptRenderFailures instead of thrown, a missing prompt file reads as empty in the render, and assert-task-inputs fails that task with the renderer's message or a "is missing; ultrafuzz resume restores ..." message. Every other task, status and sync keep working; - a missing static prompt is restored from prompt-snapshots/ before every engine start (resume, replay and fork), never over an existing file; - the #1220 retry archive moves the withdrawn generation's attempt state before it renames the manifests, so an interrupted archive leaves the published generation in place instead of a withdrawn item's prompt beside a new manifest. The docs say what is no longer checked and how to change a prompt of a running campaign; the CHANGELOG entry replaces the #1176/#1195 upgrade note, whose failure this removes. Runs launched before this change keep their launch engine's behaviour until they are resumed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano
added a commit
that referenced
this pull request
Sep 30, 2026
…ng change New entries now go straight into CHANGELOG.md under Unreleased, so the entry this branch had moved to consolidated release notes comes back. It sits under Breaking changes rather than Other changes: a run whose report producer succeeded under an earlier release's workflow and that is resumed with --refresh-controller before its verifier runs now fails that verifier until `resume --refresh-controller --retry-failed` reruns the producer, and the reference docs retire #585's claim that the run filesystem cannot be used to forge the producer. Main already files changes without a `!` whose only break is to older runs there (#1176, #1193). The entry also says that plain `resume` keeps the workflow persisted at launch, and that the recovered producer's failed_attempts omits the attempts from before the refresh. It describes what the replaced query cost (a runner subprocess with a 64 MiB read under a 180 s budget) rather than a missing `smithers` on PATH, since the run's `trusted-bin/smithers` shim now gives the controller PATH a runner on every host, and it says every producer attempt writes the record, since per-node cloud workers are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano
added a commit
that referenced
this pull request
Sep 30, 2026
An attempt's artifacts/<attempt>/prompt.rendered.md is now the prompt it receives on every verb, and nothing hashes, compares or seals it after launch. Prompt tamper detection cost more than it bought: an operator's edit of a task that had not run, or an upgrade that renders templates differently (#1176, #1195), stranded the run, and a static prompt had three different sources depending on the verb. Deleted: - the runtime byte comparison of published prompts, and the verify-mode "missing" throw (renderReadyRuntimePrompts renders only a missing file and otherwise adopts it; admission renders nothing); - the group-template digest gate (DYNAMIC_TEMPLATE_CHANGED) and its now-unused templatePath input; the manifest compatibility rows, which compare launch values with launch values, stay; - the prompt entries of the control seal and execution snapshot (controls/rendered-prompts/, controls/prompt-snapshots/), the workflow's sealed-copy read, and the --refresh-controller retained binding with its digest checks; - the digest gate on restoring a missing static prompt; - the dead dynamicRuntimePromptDigest export. Added: - a prompt problem fails only its task: a runtime prompt that cannot be rendered is returned as promptRenderFailures instead of thrown, a missing prompt file reads as empty in the render, and assert-task-inputs fails that task with the renderer's message or a "is missing; ultrafuzz resume restores ..." message. Every other task, status and sync keep working; - a missing static prompt is restored from prompt-snapshots/ before every engine start (resume, replay and fork), never over an existing file; - the #1220 retry archive moves the withdrawn generation's attempt state before it renames the manifests, so an interrupted archive leaves the published generation in place instead of a withdrawn item's prompt beside a new manifest. The docs say what is no longer checked and how to change a prompt of a running campaign; the CHANGELOG entry replaces the #1176/#1195 upgrade note, whose failure this removes. Runs launched before this change keep their launch engine's behaviour until they are resumed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aviggiano
added a commit
that referenced
this pull request
Sep 30, 2026
The owner's direction: > tamper detection is a feature we overly invested in and caused lots of problems, such as: inability to restart a run with a minor prompt change, which is NOT something we desire, so we should re-think this feature overall, maybe just remove tamper/sealing of prompts etc (stability is more important than 100% reproductibility) On the base, prompt sealing is what stops runs: - Every workflow render, and every lifecycle admission (`status`, sync, `why`, `stats`, verified reads, `replay`, `fork`), re-renders each published runtime prompt and byte-compares it with the file. An operator's edit, or an upgrade that renders a template differently (#1176, #1195), fails with `runtime rendered prompt changed for …`. The engine then dispatches nothing, and `status` stops synchronizing. - A template copy under `dynamic-prompt-templates/` is re-hashed on every render (`DYNAMIC_TEMPLATE_CHANGED`), with the same effect. - A static prompt has three sources, depending on the verb: - launch, `replay` and `fork` read the sealed copy in the execution snapshot; - native `resume` reads the run's file; Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 2, 2026
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.
Problem
The controller re-runs the artifact gates on the host after the in-workflow verifier has already accepted an attempt. Whatever the host rejects at that point cannot be retried inside the attempt. The node is recorded as
task-output-validation-failure, and its dependents then fail finalization because their prerequisite never got a controller manifest. This PR fixes five cases where valid campaign output was rejected that way:aggregate-test-filesthrewartifact verification marker does not exist: .../strategy-b.jsonwhen anyfailure_policy: continuestrategy or goal had failed. The verifier had admitted the aggregation without that producer.contains unsafe segment "dynamic:threat:<id>".artifact ancestor closure does not match the exact planned set; missing: ".../artifacts/dynamic-...". In the default topologydedupe-findingsis the only direct dependent of the goal groups, so by code reading this hits triage, severity-classification, aggregate-test-files and final-report whenever the goal plan expands to at least one goal. I found this while fixing case 2: once aggregation reads through the sealed authority, it hits this check too.severity-classificationfailed with 3×SEVERITY_MATRIX_FIELD_MISSINGfor a schema-valid false-positive record. The schema requires severity, impact and likelihood only for true positives.Recon reached 85% line coverage on Vault.sol.(in areport.jsonnote) orHandlers reachable: 7/9(incoverage-report.md), which the scanner reported asUNSCOPED_COVERAGE_PERCENTAGE/FRACTION/SCORE. When no coverage producer was planned or admitted, the same sentence inreport.mdalso failed asREPORT_COVERAGE_EVIDENCE_UNPLANNED. That covers everysmokerun, which plans no coverage producer, and default runs whosefailure_policy: continuestateful-invariant-coveragefailed.Root cause
Each case is host-side logic that disagrees with the verifier's authority or with the schema:
aggregation-semantic-context.tswalked every planned transitive ancestor, keyed by planned node ID, with its own marker, manifest and prerequisite-chain authentication. The verifier instead admits producers through the attempt's sealeddependencyArtifactDirsand its persisted optional-dependency admission, keyed by attempt ID. The same walk also attributed bundles from multi-model fan-out producers to the model attempt index. The verifier uses the loop attempt index (workflow.tsxdependencyTask.metadata.loop.attemptIndex), so for model indexes above 0 any manifest one gate accepted the other rejected.lowerTaskDynamicDependencies(dynamic-runtime.ts) adds a group's generated attempts only to its direct dependents' sealed tasks. The lowered planned graph, however, reaches those attempts from every deeper descendant.assertExactSealedAttemptAuthorityrequired the two closures to be equal.severity-matrix.tsran only on the host and required all three fields on every record.verifyFinalReportCoverageEvidencealso counted any coverage-context score inreport.mdas invented coverage evidence.Change
fix(runtime): build host aggregation sources from the sealed attempt authorityfinalizedDeclaredContractProducers(layout, "ultrafuzz/generated-tests@3", node, attemptAuthority). That walks the sealed closure, skips optional producers that the persisted admission omitted, maps each attempt to its planned node, and reads each producer throughloadFinalizedNodeOutputSnapshot.aggregation-semantic-context.tsshrinks from 483 to 92 lines. It now only projects bundles, attributed tonode.loop.attempt_index, which the task-manifest validator binds to the verifier's value.verified-output.test.ts.fix(runtime): accept the sealed closure dynamic lowering gives deeper descendantsfix(runtime): delete the host-only severity matrix gateseverity-matrix.ts, its call site, its export and its tests.additionalProperties: falsewith High/Medium/Low enums.severity-classification-matrixregistry gate checks the matrix on every classified record that carries all three fields.smoke's bounded mode included.projectCanonicalFinalReport→assertCanonicalIssuePresentation(final-report-markdown.ts) requires severity, impact and likelihood on each issue and throws when severity differs from the impact × likelihood result. The verifier runs that projection on every task that declares areport@3output (verifyFinalReportCanonicalProjectioninworkflow.tsx). The testcanonical final-report validation rejects presentation drift instead of repairing itpins the rejection.fix(runtime): report unscoped coverage prose scores as warningsUNSCOPED_COVERAGE_SCORE,_PERCENTAGEand_FRACTIONare now warnings. This is the stop-gap from the spec.coverage_evidencedeep-equality check, the canonical scoped-section projection (including a duplicated or contradicting scoped score anywhere in the document), the stylesheet and select-control checks, and the 2,048-candidate scan limit.fix(runtime): keep unscoped report prose advisory when no coverage producer was admitted(review round)REPORT_COVERAGE_EVIDENCE_UNPLANNEDnow counts onlyreport.mdscores bound to an exact declaration-completeness scope. A typedreport.json.coverage_evidencestill fails, and so does a scoped score such asrecon-selected-declaration-completeness: `1/1`. The final-report prompt already tells agents to omit the section when no producer is selected.fix(prompts): keep the coverage projection partial identical to its reference docs(review round)docs/reference/artifacts-reports.md, andpackages/prompts/test/semantic-anchors.test.tsfails when that page does not containcoverage-evidence-markdown.mdxverbatim. It failed on the previous head (expect(docs).toContain(partial)).test(runtime): pin host aggregation attribution for model-fanout producersadds the discriminating test for the attempt-index change.test(runtime): pin aggregation finalization and implicit-visibility gate results(review round)REQUIRED_ARTIFACT_INVALID). Before, the deleted test file covered this.Net:
packages/runtime/src+90/−649, tests +601/−888.Deliberately not built (and why)
verifyDynamicRuntimeMaterializationre-derives and fingerprints. It would also widen what descendants must admit and change their prompt authority.aggregate-test-filesdoes not aggregate the dynamic goal hunters' generated tests. Neither the verifier nor the agent's prompt sees them either.packages/modal/src/node-provider.ts~2776-2807 still has it; I did not touch the modal package. Code reading suggests a cloud-executed deeper descendant fails there the same way; I did not run it.report.mdfails asREPORT_COVERAGE_EVIDENCE_UNPLANNED.assertGeneratedTestManifestSemantics(packages/artifacts/src/generated-tests.ts). Its only caller outside the artifacts package was the deleted aggregation code. That file belongs to the artifacts dead-code item, so I left it there.Verification
Discriminating evidence. For each test I ran the test file against the
origin/mainruntime sources (git checkout origin/main -- packages/runtime/src, recompile, run, restore). Every test below fails there with the production message and passes on this branch:artifact-gates.test.ts)origin/mainartifact verification marker does not exist: .../strategy-b.jsongenerated-test aggregation source artifact directory contains unsafe segment "dynamic:threat:t1"artifact ancestor closure does not match the exact planned set; missing: ".../dynamic-goal-template-…"Aggregation source bundle attribution does not match authority ["strategy-a__model_1__attempt_1",…]SEVERITY_MATRIX_FIELD_MISSINGHandlers reachable: 7/9,Target: 90% of Recon-selected declarations.andAfter iteration 2 we covered 41 of 57 functions.)UNSCOPED_COVERAGE_*with severityerrorreport.jsonnotesRecon reached 85% line coverage on Vault.sol.andCoverage of withdraw() was 3/4 branches in the replay.)UNSCOPED_COVERAGE_*with severityerrorreport.mdsentences)UNSCOPED_COVERAGE_*errors plusREPORT_COVERAGE_EVIDENCE_UNPLANNEDreport.mdsentence, coverage producer planned but not admitted)UNSCOPED_COVERAGE_*errors plusREPORT_COVERAGE_EVIDENCE_UNPLANNEDd0091aba, where the only error left isREPORT_COVERAGE_EVIDENCE_UNPLANNED. They pass with99c2f7ca. The existing scopedrecon-selected-declaration-completeness: `1/1`case in the producer-free test still fails withREPORT_COVERAGE_EVIDENCE_UNPLANNED.origin/mainsources, a probe with the same fixture minus the failed producer rejects an admitted producer without a marker withartifact verification marker does not exist: .../strategy-a.json. On this branch it fails withREQUIRED_ARTIFACT_INVALID.startRunand thenmaterializeDynamicRuntimeon a planner → dynamic fan-out → join → triage-shaped grandchild topology:[planner, strict-join]; the lowered graph's was[dynamic:threat:…, planner, strict-join].assertSmithersTaskManifestMatchesPlannedGraphaccepted the materialized state.verifyRequiredArtifactsForAttemptfailed on the grandchild with the closure mismatch.Commands run on the final head (
266dafa0, onb6dd1da9):pnpm -w build.node --test dist-test/test/artifact-gates.test.js dist-test/test/verified-output.test.js dist-test/test/terminal-report-completion.test.js dist-test/test/final-report-markdown.test.js: 275/275.packages/cli:--test-name-pattern '^agent-owned bytes stay identical across validation, sync, aggregation'. This runs sync, the host aggregation gate and the verified reads end to end; it passes.packages/prompts:npx vitest run: 104/104, includingsemantic-anchors.test.ts, which fails ond0091aba.CI=1 ESLINT_PLUGIN_DIFF_COMMIT=origin/main pnpm -w lint:strict:ci: clean.npx eslintandnpx prettier --checkon the changed files, andgit diff --check: clean.pnpm --filter @ultrafuzz/runtime typecheckandpnpm --filter @ultrafuzz/prompts typecheck: clean.pnpm -w knip: clean.node scripts/docs-check.mjs,node scripts/prompt-catalog-docs.mjs --checkandnode scripts/audit-profile-docs.mjs --check: clean.Not run: the
runtime.test.tsshards, the full CLI suite,generated-workflow-verifier.test.ts, the Bun adapter contracts, the modal package, and any end-to-end campaign.Risk / compatibility
validator_builddoes not rotate and in-flight runs keep validating.coverage-evidence-markdown.mdxis rendered into the stateful-invariant coverage and final-report prompts, so runs planned after this change see the new sentence. It is not a validator-build input.@ultrafuzz/runtimeno longer exportsexpectedSeverityFromMatrix,validateSeverityMatrixArtifact,SeverityLevelorSeverityArtifactKind.authenticatedAggregationSemanticContextnow takes{ layout, attemptId, producers }and a newAggregationSourceProducertype is exported. Nothing in the repo besidesartifact-gates.tsused them, and the generatedworkflow.tsximports none of them.assertExactSealedAttemptAuthorityand requires admitted producers to be controller-finalized. Other contextual gates already required both, and host finalization already requires admitted direct prerequisites to be finalized before the gate runs, so I expect no new rejection in real runs. That expectation comes from reading the code, not from running a campaign.artifact-gates.tssit near w05b's areas but do not overlap its listed ranges.artifacts-reports.mdhunk stays within the scoped-coverage section, which w11 also edits elsewhere.coverage-evidence-markdown.mdxis not in w12's prompt list.🤖 Generated with Claude Code
The PR appears safe to merge, though its release-note entry should be restored.
Fix with agent prompt
Summary
The PR aligns host artifact gates with sealed attempt admission, relaxes host-only checks that rejected valid severity and coverage output, and updates coverage guidance.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Sealed attempt closure] --> B[Admitted producer attempts] B --> C[Finalized output snapshots] C --> D[Aggregation semantic context] D --> E[Host artifact gate]Reviews (3) · Last reviewed commit: "chore: move the changelog entry to the c..."