From b0c8cc70cde54cdd93fc4db64674ec9db37e1650 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 01:47:01 +0000 Subject: [PATCH 01/17] Split OutputContractRef: opaque identity stays; obligations become a sealed roster OutputContractRef held both plain names and content-serialised obligation payloads, so no consumer could tell which it held. Keep the opaque name, put DeclaredOutputObligations on WorkContract, and mint terminal-success rosters only through resolve_output_obligations (sole_constructor RED). Co-authored-by: Cursor --- dag/gunbc/fabric/fabric_witness_run.dag | 31 +++- dag/gunbc/product/fabric/work.dag | 87 ++++++++++ dag/gunbc/product/training/resolution.dag | 7 +- .../fabric_capacity_class_witness_test.dag | 2 + .../fabric_grant_issuance_witness_test.dag | 3 + ...utput_contract_resolution_witness_test.dag | 155 ++++++++++++++++++ .../fabric_terminal_contract_witness_test.dag | 5 +- 7 files changed, 282 insertions(+), 8 deletions(-) create mode 100644 dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag diff --git a/dag/gunbc/fabric/fabric_witness_run.dag b/dag/gunbc/fabric/fabric_witness_run.dag index 9dbeb81c91b..29d93064803 100644 --- a/dag/gunbc/fabric/fabric_witness_run.dag +++ b/dag/gunbc/fabric/fabric_witness_run.dag @@ -15,6 +15,7 @@ import product.fabric.work { ExecutionRequirements, Shape, HardRequirements, CapacityClass, ControlPlaneCapacity, CapabilityManifestRef, TrustDomainRef, ProgramRef, SourceManifestRef, ArtifactManifestRef, EffectContractRef, OutputContractRef, + declared_output_obligations, work_for, } import std.content_hash { ContentHash, content_hash_of_value, serialize_content_hash } @@ -168,9 +169,12 @@ fn required_build_contract_from_materials( environment: NonEmptyStr, cache: NonEmptyStr, credentials: NonEmptyStr, - outputs: NonEmptyStr, - terminal: NonEmptyStr, + output_manifest: List, + terminal_result_manifest: List, ) -> WorkContract { + // Opaque contract name. Obligations are the structured field below — they used + // to be framed into the OutputContractRef string (content-serialised meaning + // fork). Deleting that encoding is the cut; the roster no longer hides in a name. WorkContract { program: concat("gunbc.required-witnesses-build.program:", framed_string(value: command)), steps: [ @@ -182,7 +186,10 @@ fn required_build_contract_from_materials( inputs: concat("gunbc.required-witnesses-build.inputs:", concat(framed_string(value: environment), concat(framed_string(value: cache), framed_string(value: credentials)))), runtime_closure: concat("gunbc.required-witnesses-build.runtime:", concat(framed_string(value: toolchain), framed_string(value: executable))), effect_contract: "gunbc.required-witnesses-build.effects.checkout-read-cache-and-permitted-credentials-only", - output_contract: concat("gunbc.required-witnesses-build.outputs:", concat(framed_string(value: outputs), framed_string(value: terminal))), + output_contract: "gunbc.required-witnesses-build.outputs", + output_obligations: declared_output_obligations( + ids: output_manifest + terminal_result_manifest, + ), } } @@ -208,12 +215,23 @@ fn bind_required_witnesses_build_work( Absent => RequiredBuildWorkRefused { cause: RequiredBuildCoordinateMissing { coordinate: "permitted-credential-refs" } } Present { value: credentials } => match non_empty_strings_material(label: "outputs", values: spec.output_manifest) { Absent => RequiredBuildWorkRefused { cause: RequiredBuildCoordinateMissing { coordinate: "output-manifest" } } - Present { value: outputs } => match non_empty_strings_material(label: "terminal", values: spec.terminal_result_manifest) { + Present { value: _ } => match non_empty_strings_material(label: "terminal", values: spec.terminal_result_manifest) { Absent => RequiredBuildWorkRefused { cause: RequiredBuildCoordinateMissing { coordinate: "terminal-result-manifest" } } - Present { value: terminal } => { + Present { value: _ } => { let source = concat("git-source:", concat(framed_string(value: git_object_id_wire_hex(oid: subject.commit)), framed_string(value: git_object_id_wire_hex(oid: subject.tree)))) let command = command_material(command: spec.command) - let contract = required_build_contract_from_materials(source: source, checkout: checkout, toolchain: toolchain, executable: executable, command: command, environment: environment, cache: cache, credentials: credentials, outputs: outputs, terminal: terminal) + let contract = required_build_contract_from_materials( + source: source, + checkout: checkout, + toolchain: toolchain, + executable: executable, + command: command, + environment: environment, + cache: cache, + credentials: credentials, + output_manifest: spec.output_manifest, + terminal_result_manifest: spec.terminal_result_manifest, + ) let requirements = floor_execution_requirements() let work = work_for(principal: principal, contract: contract, requirements: requirements) RequiredBuildWorkBound { @@ -390,6 +408,7 @@ fn floor_work_contract(source: SourceManifestRef) -> WorkContract { runtime_closure: "gunbc.toolchain.rust-1.93.0+clippy+rustfmt", effect_contract: "gunbc.floor.effects.read-only-corpus", output_contract: "gunbc.floor.outputs.required-floor-summary", + output_obligations: declared_output_obligations(ids: ["required-floor-summary"]), } } diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index 517fd6303e3..68b51ee6ece 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -43,10 +43,61 @@ type ArtifactManifestRef = NonEmptyStr where brand("ArtifactManifestRef") type EffectContractRef = NonEmptyStr where brand("EffectContractRef") +// OPAQUE CONTRACT IDENTITY ONLY. A prior meaning fork let this brand carry either a +// plain name or a content-serialised obligation roster (framed outputs stuffed into +// the same NonEmptyStr). Both inhabited one type, so no consumer could tell which it +// held — DESIGN section 3. The opaque name survives; obligations live on +// DeclaredOutputObligations and reach a gate only through resolve_output_obligations. type OutputContractRef = NonEmptyStr where brand("OutputContractRef") type SourceManifestRef = NonEmptyStr where brand("SourceManifestRef") +// Stable subject identity for one owed output. Commit manifests and committed rows +// key by this id; inventing a fresh spelling at the gate is how one job discharged +// another's obligations. +type OutputObligationId = NonEmptyStr where brand("OutputObligationId") + +// Declared roster on the Work. Empty is a legitimate declaration (a job that owes +// nothing), never an unsupplied one — that distinction belongs to resolution below. +type DeclaredOutputObligations { + required: List +} + +fn output_obligation_id(raw: NonEmptyStr) -> OutputObligationId { + raw as OutputObligationId +} + +fn declared_output_obligations(ids: List) -> DeclaredOutputObligations { + DeclaredOutputObligations { + required: map(ids, id => output_obligation_id(raw: id)), + } +} + +fn empty_output_obligations() -> DeclaredOutputObligations { + DeclaredOutputObligations { required: [] } +} + +// Revision of a declared roster. Carried on the resolution result so two readers +// cannot silently disagree about whether they judged the start-of-job roster or a +// later one: the sealed result names which revision it holds. Policy of WHEN to +// resolve (job start vs gate time) is the caller's; the answer is in the result. +type OutputObligationsRevision = ContentHash + +// Sealed: the only mint is resolve_output_obligations in this module. A caller that +// needs a terminal-success-eligible roster must resolve; constructing the resolved +// arm by hand is unwritable (sole_constructor), which is the discriminating wall +// for the empty-roster-without-authority defect. +type ResolvedOutputObligations sole_constructor { + contract: OutputContractRef + revision: OutputObligationsRevision + work_key: WorkKey + required: List +} + +type OutputObligationsResolution + = OutputObligationsResolved { value: ResolvedOutputObligations } + | OutputObligationsUnreadable { contract: OutputContractRef, cause: NonEmptyStr } + // Resumability is a property of what a step DECLARES, never of the fabric. A step // whose effects are undeclared cannot be resumed by any mechanism, so the tier is // carried on the contract and the fabric refuses to claim checkpointing it cannot @@ -85,7 +136,12 @@ type WorkContract { inputs: ArtifactManifestRef runtime_closure: ArtifactManifestRef effect_contract: EffectContractRef + // Opaque identity of the output contract — never the obligation roster. output_contract: OutputContractRef + // Structured obligations the work owes. Identity material includes this field, + // so changing what is owed changes the work key without stuffing the roster into + // the contract name. + output_obligations: DeclaredOutputObligations } // Scheduling facts that do not change the promised answer live BESIDE the @@ -216,6 +272,36 @@ type Work

{ // discipline. Requirements are included because a Work that needs a different // architecture or trust domain does not promise the same answer. +fn output_obligations_material(declared: DeclaredOutputObligations) -> NonEmptyStr { + fold( + declared.required, + init: concat("output-obligations:", concat(to_string(declared.required.count()), ":")), + f: (acc, id) => concat(acc, "\u{1e}", id), + ) +} + +fn output_obligations_revision(declared: DeclaredOutputObligations) -> OutputObligationsRevision { + content_hash_of_value(value: output_obligations_material(declared: declared)) +} + +// The only route to a terminal-success-eligible roster. Empty required is a +// Resolved value produced here, never an arm a gate caller can invent without +// having resolved. work_key binds the roster to one work identity so a foreign +// job's commitments cannot discharge it. +fn resolve_output_obligations( + contract: WorkContract, + work_key: WorkKey, +) -> OutputObligationsResolution { + OutputObligationsResolved { + value: ResolvedOutputObligations { + contract: contract.output_contract, + revision: output_obligations_revision(declared: contract.output_obligations), + work_key: work_key, + required: contract.output_obligations.required, + } + } +} + fn work_identity_material(contract: WorkContract, requirements: ExecutionRequirements) -> NonEmptyStr { concat( contract.program, "\u{1f}", @@ -225,6 +311,7 @@ fn work_identity_material(contract: WorkContract, requirements: ExecutionRequire contract.runtime_closure, "\u{1f}", contract.effect_contract, "\u{1f}", contract.output_contract, "\u{1f}", + output_obligations_material(declared: contract.output_obligations), "\u{1f}", capacity_class_wire(c: requirements.capacity_class), "\u{1f}", requirements.capabilities, "\u{1f}", requirements.trust_domain, "\u{1f}", diff --git a/dag/gunbc/product/training/resolution.dag b/dag/gunbc/product/training/resolution.dag index 9d577052cf4..290c68d2548 100644 --- a/dag/gunbc/product/training/resolution.dag +++ b/dag/gunbc/product/training/resolution.dag @@ -2,6 +2,7 @@ module product.training.resolution import product.fabric.work { Work, WorkContract, ExecutionRequirements, WorkStep, ReceiptSatisfiable, work_for, + declared_output_obligations, } import product.fabric.demand { Demand, ObservationReceiptRef } import product.fabric.identity { @@ -471,6 +472,9 @@ fn training_manifest_reference(m: TrainingResolvedManifest) -> NonEmptyStr { // reads them for its own purposes and flattening them into the reference would hide them. fn training_work_contract(evidence: TrainingResolutionEvidence) -> WorkContract { let m = training_resolved_manifest(evidence: evidence) + // Content-hash wire as opaque OutputContractRef identity (not content-serialised + // obligations). The owed subject is that same identity as a single obligation id. + let contract_id = serialize_content_hash(hash: m.output_contract) WorkContract { program: "gunbc-training-run-low-rank-adaptation", steps: [WorkStep { step_id: "train", resumability: ReceiptSatisfiable }], @@ -478,7 +482,8 @@ fn training_work_contract(evidence: TrainingResolutionEvidence) -> WorkContract inputs: training_manifest_reference(m: m), runtime_closure: serialize_content_hash(hash: m.trainer_closure), effect_contract: "training-write-checkpoints-and-adapter", - output_contract: serialize_content_hash(hash: m.output_contract), + output_contract: contract_id, + output_obligations: declared_output_obligations(ids: [contract_id]), } } diff --git a/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag b/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag index 6965f848127..1cc8eae2e7f 100644 --- a/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag +++ b/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag @@ -8,6 +8,7 @@ import product.fabric.work { CapacityClass, ControlPlaneCapacity, CustomerExecutableCapacity, ExecutionRequirements, Shape, HardRequirements, capacity_class_eq, capacity_class_wire, work_identity_material, WorkContract, WorkStep, + declared_output_obligations, } import product.fabric.envelope { unstated_envelope } import product.fabric.isolation { IsolationProfile } @@ -46,6 +47,7 @@ fn cc_contract() -> WorkContract { runtime_closure: "gunbc.runtime.closure-1", effect_contract: "gunbc.effect.contract-1", output_contract: "gunbc.output.contract-1", + output_obligations: declared_output_obligations(ids: ["gunbc.output.contract-1"]), } } diff --git a/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag b/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag index f91d2f9ee8e..3802c110af8 100644 --- a/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag +++ b/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag @@ -13,6 +13,7 @@ import product.fabric.envelope { unstated_envelope } import product.fabric.work { Work, WorkContract, WorkStep, ReceiptSatisfiable, ExecutionRequirements, Shape, HardRequirements, CapacityClass, ControlPlaneCapacity, work_for, + declared_output_obligations, } import product.fabric.demand { ObservationReceiptRef } import product.fabric.supply { SupplierOffer, QuotedPerSecond, ObservedSupply } @@ -62,6 +63,7 @@ data fixture_contract: WorkContract = WorkContract { runtime_closure: "runtime:1", effect_contract: "effects:1", output_contract: "outputs:1", + output_obligations: declared_output_obligations(ids: ["outputs:1"]), } data owning_principal: NonEmptyStr = "gunbc" @@ -78,6 +80,7 @@ data other_contract: WorkContract = WorkContract { runtime_closure: "runtime:1", effect_contract: "effects:1", output_contract: "outputs:1", + output_obligations: declared_output_obligations(ids: ["outputs:1"]), } data other_work: Work = work_for(principal: owning_principal, contract: other_contract, requirements: fixture_requirements) diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag new file mode 100644 index 00000000000..456bb693c3a --- /dev/null +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -0,0 +1,155 @@ +module test.claim.fabric_output_contract_resolution_witness + +import std.types { NonEmptyStr, Bool, String, Int, List } +import std.measure { hardware_thread_count } +import std.content_hash { serialize_content_hash } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import gunbc.compile_diagnostic_census { + CompileDiagnosticCensus, CensusObserved, CensusNotRunnable, + census_rows_of_class, census_total_count, +} +import product.fabric.work { + WorkContract, WorkStep, ReceiptSatisfiable, ExecutionRequirements, + Shape, HardRequirements, ControlPlaneCapacity, derive_work_key, + declared_output_obligations, empty_output_obligations, + resolve_output_obligations, OutputObligationsResolved, OutputObligationsUnreadable, +} +import product.fabric.envelope { unstated_envelope } +import product.fabric.isolation { shared_kernel_sandbox_profile } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// OutputContractRef meaning-fork cut: opaque identity survives; obligations are a +// separate carrier; a terminal-success-eligible roster is sealed behind resolution. +// +// Producer census by shape (instrument: WorkContract constructions whose +// output_contract field inhabits OutputContractRef — verified nonempty for each +// class before classifying): +// content-serialised — required_build_contract_from_materials (DELETED by this cut) +// opaque name — floor_work_contract; fixture contracts in fabric_*_witness +// content-addressed opaque — training_work_contract (hash wire as name, not framed roster) +// Consumers: work_identity_material (opaque string bytes + obligations material); +// no consumer ever parsed the framed form — anemic modeling confirmed. + +fn fixture_requirements() -> ExecutionRequirements { + ExecutionRequirements { + capacity_class: ControlPlaneCapacity, + shape: Shape { + hard: HardRequirements { threads: hardware_thread_count(8) }, + envelope: unstated_envelope(), + }, + capabilities: "linux-arm64", + trust_domain: "fleet-owned", + isolation: shared_kernel_sandbox_profile(), + } +} + +fn contract_with(obligations_ids: List) -> WorkContract { + WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: declared_output_obligations(ids: obligations_ids), + } +} + +test fn empty_declared_roster_resolves_as_resolved_empty_not_unreadable() -> Bool { + let contract = WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: empty_output_obligations(), + } + let key = derive_work_key(contract: contract, requirements: fixture_requirements()) + match resolve_output_obligations(contract: contract, work_key: key) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: r } => + (r.required.count() == 0) + && (r.contract == contract.output_contract) + && (r.work_key == key) + } +} + +test fn resolved_roster_carries_declared_obligation_ids() -> Bool { + let contract = contract_with(obligations_ids: ["artifact-a", "terminal-b"]) + let key = derive_work_key(contract: contract, requirements: fixture_requirements()) + match resolve_output_obligations(contract: contract, work_key: key) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: r } => + (r.required.count() == 2) + && (r.required == declared_output_obligations(ids: ["artifact-a", "terminal-b"]).required) + } +} + +test fn changing_obligations_changes_work_key_without_renaming_the_contract() -> Bool { + let a = contract_with(obligations_ids: ["artifact-a"]) + let b = contract_with(obligations_ids: ["artifact-b"]) + (a.output_contract == b.output_contract) + && (derive_work_key(contract: a, requirements: fixture_requirements()) + != derive_work_key(contract: b, requirements: fixture_requirements())) +} + +test fn resolution_revision_tracks_the_declared_roster() -> Bool { + let a = contract_with(obligations_ids: ["artifact-a"]) + let b = contract_with(obligations_ids: ["artifact-b"]) + let ka = derive_work_key(contract: a, requirements: fixture_requirements()) + let kb = derive_work_key(contract: b, requirements: fixture_requirements()) + match resolve_output_obligations(contract: a, work_key: ka) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: ra } => match resolve_output_obligations(contract: b, work_key: kb) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: rb } => + serialize_content_hash(hash: ra.revision) != serialize_content_hash(hash: rb.revision) + } + } +} + +test fn resolution_binds_the_work_key_it_was_resolved_against() -> Bool { + let a = contract_with(obligations_ids: ["artifact-a"]) + let b = contract_with(obligations_ids: ["artifact-a", "extra"]) + let ka = derive_work_key(contract: a, requirements: fixture_requirements()) + let kb = derive_work_key(contract: b, requirements: fixture_requirements()) + match resolve_output_obligations(contract: a, work_key: ka) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: ra } => + (ra.work_key == ka) && (ka != kb) + } +} + +// Discriminating RED: a terminal-success-eligible roster is unwritable without +// resolve_output_obligations. Per DESIGN 4b the RED is authorable as a fixture +// handed to the compiler — SoleConstructorViolation on ResolvedOutputObligations. + +fn census_of(source: String) -> CompileDiagnosticCensus { + compile_dag_diagnostic_census(source) +} + +fn violation_count(source: String, wanted: String) -> Int { + match census_of(source: source) { + CensusObserved { rows: rows } => census_total_count(rows: census_rows_of_class(rows: rows, wanted: wanted)) + CensusNotRunnable { cause: _ } => 0 - 1 + } +} + +data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId }\nimport product.fabric.identity { WorkKey }\nimport std.content_hash { content_hash_of_value }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: content_hash_of_value(value: \"x\"),\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" + +test fn forged_resolved_roster_is_sole_constructor_refused() -> Bool { + violation_count(source: forged_resolved_roster_source, wanted: "SoleConstructorViolation") >= 1 +} + +test fn resolve_is_the_sanctioned_mint() -> Bool { + let contract = contract_with(obligations_ids: []) + let key = derive_work_key(contract: contract, requirements: fixture_requirements()) + match resolve_output_obligations(contract: contract, work_key: key) { + OutputObligationsResolved { value: _ } => true + OutputObligationsUnreadable { contract: _, cause: _ } => false + } +} diff --git a/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag b/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag index 107aa6d2315..d656a564549 100644 --- a/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag +++ b/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag @@ -5,7 +5,7 @@ import std.measure { money_amount_micro, money_amount_micro_count, hardware_thre import std.content_hash { content_hash_of_value } import extdeps.currency.currency { Usd } import product.fabric.identity { FabricIdentity, fabric_identity_key, DemandKey, WorkKey } -import product.fabric.work { CapacityClass, ControlPlaneCapacity, Shape, HardRequirements, Work, WorkContract, ExecutionRequirements, WorkStep, derive_work_key, work_for, ReceiptSatisfiable, IdempotentUnderKey, OpaqueEffects } +import product.fabric.work { CapacityClass, ControlPlaneCapacity, Shape, HardRequirements, Work, WorkContract, ExecutionRequirements, WorkStep, derive_work_key, work_for, ReceiptSatisfiable, IdempotentUnderKey, OpaqueEffects, declared_output_obligations } import product.fabric.demand { Demand, AdmissionTerms, BuyTerms, SatisfactionRequirement, demand_work, demand_reservation_price, demand_maximum_buy_order } import product.fabric.supply { SupplierOffer, QuotedPerSecond, offer_quote_amount, offer_quote_currency, ObservedSupply, QuotedSupply } import product.fabric.execution { Attempt, InitialAttempt, InfrastructureRetry, CapabilityEscalation, LeaseIdentity, Receipt, ExecutionReceipt, ExecutionGrant, FabricGrantObject, FabricGrantContext, FabricGrantEvidence, ReceiptAdmissible, ReceiptFromDifferentLease, ReceiptFromDifferentAttempt, ReceiptFingerprintIncomparable, ReceiptGenerationStale, admit_receipt_for_grant } @@ -45,6 +45,7 @@ data fixture_contract: WorkContract = WorkContract { runtime_closure: "toolchain-rust-1.89", effect_contract: "effects-read-only", output_contract: "outputs-verdict", + output_obligations: declared_output_obligations(ids: ["outputs-verdict"]), } // The key is DERIVED, never authored. An earlier version of this file declared @@ -73,6 +74,7 @@ data other_source_contract: WorkContract = WorkContract { runtime_closure: "toolchain-rust-1.89", effect_contract: "effects-read-only", output_contract: "outputs-verdict", + output_obligations: declared_output_obligations(ids: ["outputs-verdict"]), } // Same contract, same capabilities and trust domain, DIFFERENT shape. Before @@ -188,6 +190,7 @@ test fn identical_contracts_derive_one_key_regardless_of_who_asked() -> Bool { runtime_closure: "toolchain-rust-1.89", effect_contract: "effects-read-only", output_contract: "outputs-verdict", + output_obligations: declared_output_obligations(ids: ["outputs-verdict"]), }, requirements: fixture_requirements, ) From a8639088f46a013e9a500e6005a35527b549dd04 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 01:56:02 +0000 Subject: [PATCH 02/17] Stamp judgment policy on ResolvedOutputObligations; path-scope the sole_constructor rung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Warm-moth-142: revision identity alone does not answer start-vs-gate; the sealed result now carries OutputObligationsJudgment. Also correct the unwritable claim to path-scoped rung 4 on source→.dag with a named degradation when a Rust seed consumer appears. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 42 ++++++++++----- ...utput_contract_resolution_witness_test.dag | 52 +++++++++++++++---- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index 68b51ee6ece..e624ffedb6f 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -77,19 +77,34 @@ fn empty_output_obligations() -> DeclaredOutputObligations { DeclaredOutputObligations { required: [] } } -// Revision of a declared roster. Carried on the resolution result so two readers -// cannot silently disagree about whether they judged the start-of-job roster or a -// later one: the sealed result names which revision it holds. Policy of WHEN to -// resolve (job start vs gate time) is the caller's; the answer is in the result. +// Revision of a declared roster — identity of WHICH roster was resolved. type OutputObligationsRevision = ContentHash -// Sealed: the only mint is resolve_output_obligations in this module. A caller that -// needs a terminal-success-eligible roster must resolve; constructing the resolved -// arm by hand is unwritable (sole_constructor), which is the discriminating wall -// for the empty-roster-without-authority defect. +// WHICH policy a sealed result answers under. Warm-moth-142: revision identity +// alone does not say whether a job is judged against the roster it started on or +// the current one; that answer must be IN the value or two readers will differ +// and both look right. The caller names the policy at resolve time; the sealed +// result carries it. This module does not pick a global default. +type OutputObligationsJudgment + = ObligationsJudgedAtJobStart + | ObligationsJudgedAtAdmissionGate + +// Sealed on the source→.dag acceptance path: the only mint is +// resolve_output_obligations in this module. A caller that needs a +// terminal-success-eligible roster must resolve; constructing the resolved arm +// by hand is SoleConstructorViolation (fixture RED). +// +// RUNG HONESTY, PATH-SCOPED (DESIGN 4b(1); gunbc.model.population): structurally +// impossible on source→.dag. The emitted Rust mirror of a sole_constructor type is +// a public struct with public fields, so a consumer writing Rust against the seed +// is OUTSIDE this guarantee. Fabric-M0 has no such Rust consumer today, so rung 4 +// holds NOW with a named degradation condition: the first Rust consumer of the +// seed against this carrier. Claiming "unwritable" unqualified would be the +// inflation 4b(1) forbids. type ResolvedOutputObligations sole_constructor { contract: OutputContractRef revision: OutputObligationsRevision + judgment: OutputObligationsJudgment work_key: WorkKey required: List } @@ -284,18 +299,21 @@ fn output_obligations_revision(declared: DeclaredOutputObligations) -> OutputObl content_hash_of_value(value: output_obligations_material(declared: declared)) } -// The only route to a terminal-success-eligible roster. Empty required is a -// Resolved value produced here, never an arm a gate caller can invent without -// having resolved. work_key binds the roster to one work identity so a foreign -// job's commitments cannot discharge it. +// The only route to a terminal-success-eligible roster on the .dag path. Empty +// required is a Resolved value produced here, never an arm a gate caller can +// invent without having resolved. work_key binds the roster to one work identity +// so a foreign job's commitments cannot discharge it. judgment is required and +// stamped so start-vs-gate policy is readable from the value. fn resolve_output_obligations( contract: WorkContract, work_key: WorkKey, + judgment: OutputObligationsJudgment, ) -> OutputObligationsResolution { OutputObligationsResolved { value: ResolvedOutputObligations { contract: contract.output_contract, revision: output_obligations_revision(declared: contract.output_obligations), + judgment: judgment, work_key: work_key, required: contract.output_obligations.required, } diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 456bb693c3a..83c58dbeb1c 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -13,7 +13,10 @@ import product.fabric.work { Shape, HardRequirements, ControlPlaneCapacity, derive_work_key, declared_output_obligations, empty_output_obligations, resolve_output_obligations, OutputObligationsResolved, OutputObligationsUnreadable, + OutputObligationsResolution, ObligationsJudgedAtJobStart, ObligationsJudgedAtAdmissionGate, } +import product.fabric.identity { WorkKey } + import product.fabric.envelope { unstated_envelope } import product.fabric.isolation { shared_kernel_sandbox_profile } @@ -57,6 +60,14 @@ fn contract_with(obligations_ids: List) -> WorkContract { } } +fn resolve_at_start(contract: WorkContract, work_key: WorkKey) -> OutputObligationsResolution { + resolve_output_obligations( + contract: contract, + work_key: work_key, + judgment: ObligationsJudgedAtJobStart, + ) +} + test fn empty_declared_roster_resolves_as_resolved_empty_not_unreadable() -> Bool { let contract = WorkContract { program: "gunbc.probe.output-contract", @@ -69,19 +80,23 @@ test fn empty_declared_roster_resolves_as_resolved_empty_not_unreadable() -> Boo output_obligations: empty_output_obligations(), } let key = derive_work_key(contract: contract, requirements: fixture_requirements()) - match resolve_output_obligations(contract: contract, work_key: key) { + match resolve_at_start(contract: contract, work_key: key) { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: r } => (r.required.count() == 0) && (r.contract == contract.output_contract) && (r.work_key == key) + && match r.judgment { + ObligationsJudgedAtJobStart => true + ObligationsJudgedAtAdmissionGate => false + } } } test fn resolved_roster_carries_declared_obligation_ids() -> Bool { let contract = contract_with(obligations_ids: ["artifact-a", "terminal-b"]) let key = derive_work_key(contract: contract, requirements: fixture_requirements()) - match resolve_output_obligations(contract: contract, work_key: key) { + match resolve_at_start(contract: contract, work_key: key) { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: r } => (r.required.count() == 2) @@ -102,9 +117,9 @@ test fn resolution_revision_tracks_the_declared_roster() -> Bool { let b = contract_with(obligations_ids: ["artifact-b"]) let ka = derive_work_key(contract: a, requirements: fixture_requirements()) let kb = derive_work_key(contract: b, requirements: fixture_requirements()) - match resolve_output_obligations(contract: a, work_key: ka) { + match resolve_at_start(contract: a, work_key: ka) { OutputObligationsUnreadable { contract: _, cause: _ } => false - OutputObligationsResolved { value: ra } => match resolve_output_obligations(contract: b, work_key: kb) { + OutputObligationsResolved { value: ra } => match resolve_at_start(contract: b, work_key: kb) { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: rb } => serialize_content_hash(hash: ra.revision) != serialize_content_hash(hash: rb.revision) @@ -117,16 +132,33 @@ test fn resolution_binds_the_work_key_it_was_resolved_against() -> Bool { let b = contract_with(obligations_ids: ["artifact-a", "extra"]) let ka = derive_work_key(contract: a, requirements: fixture_requirements()) let kb = derive_work_key(contract: b, requirements: fixture_requirements()) - match resolve_output_obligations(contract: a, work_key: ka) { + match resolve_at_start(contract: a, work_key: ka) { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: ra } => (ra.work_key == ka) && (ka != kb) } } -// Discriminating RED: a terminal-success-eligible roster is unwritable without -// resolve_output_obligations. Per DESIGN 4b the RED is authorable as a fixture -// handed to the compiler — SoleConstructorViolation on ResolvedOutputObligations. +test fn judgment_policy_is_stamped_on_the_sealed_result() -> Bool { + let contract = contract_with(obligations_ids: ["artifact-a"]) + let key = derive_work_key(contract: contract, requirements: fixture_requirements()) + match resolve_output_obligations( + contract: contract, + work_key: key, + judgment: ObligationsJudgedAtAdmissionGate, + ) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: r } => match r.judgment { + ObligationsJudgedAtAdmissionGate => true + ObligationsJudgedAtJobStart => false + } + } +} + +// Discriminating RED on source→.dag: a terminal-success-eligible roster is +// SoleConstructorViolation without resolve_output_obligations. Path-scoped — +// see ResolvedOutputObligations annotation. Fixture form is enrollable; an +// in-corpus witness asserting the refusal would be permanently green. fn census_of(source: String) -> CompileDiagnosticCensus { compile_dag_diagnostic_census(source) @@ -139,7 +171,7 @@ fn violation_count(source: String, wanted: String) -> Int { } } -data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId }\nimport product.fabric.identity { WorkKey }\nimport std.content_hash { content_hash_of_value }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: content_hash_of_value(value: \"x\"),\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" +data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nimport std.content_hash { content_hash_of_value }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: content_hash_of_value(value: \"x\"),\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" test fn forged_resolved_roster_is_sole_constructor_refused() -> Bool { violation_count(source: forged_resolved_roster_source, wanted: "SoleConstructorViolation") >= 1 @@ -148,7 +180,7 @@ test fn forged_resolved_roster_is_sole_constructor_refused() -> Bool { test fn resolve_is_the_sanctioned_mint() -> Bool { let contract = contract_with(obligations_ids: []) let key = derive_work_key(contract: contract, requirements: fixture_requirements()) - match resolve_output_obligations(contract: contract, work_key: key) { + match resolve_at_start(contract: contract, work_key: key) { OutputObligationsResolved { value: _ } => true OutputObligationsUnreadable { contract: _, cause: _ } => false } From b47df9ae0fd98d690b5df4f6d2dea0e435550286 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 02:30:07 +0000 Subject: [PATCH 03/17] Move OutputContractRef annotations to module-item grain Body and field-inline // comments refuse under the annotation channel; CI floor failed on WorkContract and required_build_contract_from_materials. Co-authored-by: Cursor --- dag/gunbc/fabric/fabric_witness_run.dag | 7 ++++--- dag/gunbc/product/fabric/work.dag | 8 ++++---- dag/gunbc/product/training/resolution.dag | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dag/gunbc/fabric/fabric_witness_run.dag b/dag/gunbc/fabric/fabric_witness_run.dag index 29d93064803..e6dcea41005 100644 --- a/dag/gunbc/fabric/fabric_witness_run.dag +++ b/dag/gunbc/fabric/fabric_witness_run.dag @@ -160,6 +160,10 @@ fn action_ref_material(action: ActionRef) -> NonEmptyStr { concat("action:", concat(framed_string(value: action.owner), concat(framed_string(value: action.repo), framed_string(value: action.ref)))) } +// Opaque contract name. Obligations are the structured field — +// they used to be framed into the OutputContractRef string (content-serialised +// meaning fork). Deleting that encoding is the cut; the roster no longer hides +// in a name. fn required_build_contract_from_materials( source: NonEmptyStr, checkout: NonEmptyStr, @@ -172,9 +176,6 @@ fn required_build_contract_from_materials( output_manifest: List, terminal_result_manifest: List, ) -> WorkContract { - // Opaque contract name. Obligations are the structured field below — they used - // to be framed into the OutputContractRef string (content-serialised meaning - // fork). Deleting that encoding is the cut; the roster no longer hides in a name. WorkContract { program: concat("gunbc.required-witnesses-build.program:", framed_string(value: command)), steps: [ diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index e624ffedb6f..92962d9edb2 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -144,6 +144,10 @@ type WorkStep { resumability: ResumabilityTier } +// WorkContract.output_contract is opaque identity of the output contract — never +// the obligation roster. output_obligations is the structured roster the work +// owes; identity material includes it, so changing what is owed changes the +// work key without stuffing the roster into the contract name. type WorkContract { program: ProgramRef steps: List @@ -151,11 +155,7 @@ type WorkContract { inputs: ArtifactManifestRef runtime_closure: ArtifactManifestRef effect_contract: EffectContractRef - // Opaque identity of the output contract — never the obligation roster. output_contract: OutputContractRef - // Structured obligations the work owes. Identity material includes this field, - // so changing what is owed changes the work key without stuffing the roster into - // the contract name. output_obligations: DeclaredOutputObligations } diff --git a/dag/gunbc/product/training/resolution.dag b/dag/gunbc/product/training/resolution.dag index 290c68d2548..8134b01170e 100644 --- a/dag/gunbc/product/training/resolution.dag +++ b/dag/gunbc/product/training/resolution.dag @@ -470,10 +470,10 @@ fn training_manifest_reference(m: TrainingResolvedManifest) -> NonEmptyStr { // manifest reference, so a change to ANY coordinate changes the contract and therefore the key; // runtime_closure and output_contract additionally travel in their own fields because the fabric // reads them for its own purposes and flattening them into the reference would hide them. +// Content-hash wire as opaque OutputContractRef identity (not content-serialised +// obligations). The owed subject is that same identity as a single obligation id. fn training_work_contract(evidence: TrainingResolutionEvidence) -> WorkContract { let m = training_resolved_manifest(evidence: evidence) - // Content-hash wire as opaque OutputContractRef identity (not content-serialised - // obligations). The owed subject is that same identity as a single obligation id. let contract_id = serialize_content_hash(hash: m.output_contract) WorkContract { program: "gunbc-training-run-low-rank-adaptation", From d1ff4db78eb9190a83872d73b9b06e9d6a93b49b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 03:52:30 +0000 Subject: [PATCH 04/17] Make Unreadable reachable and declare the gate frontier resolve_output_obligations now takes requirements and refuses a foreign work_key (WorkKeyDoesNotMatchContract). Declared frontier: warm-moth-142 admit_job_publication (#10641). Reclassify content-addressed digests as opaque. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 55 +++++++++++++++---- ...utput_contract_resolution_witness_test.dag | 27 ++++++++- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index 92962d9edb2..acb788154f2 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -48,6 +48,10 @@ type EffectContractRef = NonEmptyStr where brand("EffectContractRef") // the same NonEmptyStr). Both inhabited one type, so no consumer could tell which it // held — DESIGN section 3. The opaque name survives; obligations live on // DeclaredOutputObligations and reach a gate only through resolve_output_obligations. +// +// A content-addressed digest (e.g. training_work_contract's serialize_content_hash +// wire) is THIS same opaque shape: derived from content, but opaque to every reader +// and carrying no decodable payload — not a third contract and not content-serialised. type OutputContractRef = NonEmptyStr where brand("OutputContractRef") type SourceManifestRef = NonEmptyStr where brand("SourceManifestRef") @@ -101,6 +105,14 @@ type OutputObligationsJudgment // holds NOW with a named degradation condition: the first Rust consumer of the // seed against this carrier. Claiming "unwritable" unqualified would be the // inflation 4b(1) forbids. +// +// CONSUMPTION FRONTIER (DESIGN §3c): this sealed family is not yet read by a +// production gate in this closure. Named consumer: gunbc durable-commit +// admit_job_publication (warm-moth-142 / gunbc#10641). Trigger: that gate takes +// OutputObligationsResolution (or ResolvedOutputObligations) instead of a +// caller-supplied coproduct. Until the trigger fires, DeclaredOutputObligations +// in work_identity_material is the live production consumer of the roster half; +// the seal exists so the gate cannot mint terminal-success without resolving. type ResolvedOutputObligations sole_constructor { contract: OutputContractRef revision: OutputObligationsRevision @@ -109,9 +121,19 @@ type ResolvedOutputObligations sole_constructor { required: List } +// Causes resolve_output_obligations can produce today. WorkKeyDoesNotMatchContract +// is the binding warm-moth asked for: a foreign work_key cannot seal this contract's +// roster. Extend the coproduct when a new readable refusal exists — never invent an +// Unreadable by hand outside this module's resolve. +type OutputObligationsUnreadableCause + = WorkKeyDoesNotMatchContract + type OutputObligationsResolution = OutputObligationsResolved { value: ResolvedOutputObligations } - | OutputObligationsUnreadable { contract: OutputContractRef, cause: NonEmptyStr } + | OutputObligationsUnreadable { + contract: OutputContractRef + cause: OutputObligationsUnreadableCause + } // Resumability is a property of what a step DECLARES, never of the fabric. A step // whose effects are undeclared cannot be resumed by any mechanism, so the tier is @@ -301,21 +323,34 @@ fn output_obligations_revision(declared: DeclaredOutputObligations) -> OutputObl // The only route to a terminal-success-eligible roster on the .dag path. Empty // required is a Resolved value produced here, never an arm a gate caller can -// invent without having resolved. work_key binds the roster to one work identity -// so a foreign job's commitments cannot discharge it. judgment is required and -// stamped so start-vs-gate policy is readable from the value. +// invent without having resolved. work_key must equal derive_work_key for this +// contract+requirements — a foreign key refuses as Unreadable so one job cannot +// seal another's roster. judgment is required and stamped so start-vs-gate +// policy is readable from the value. +// +// Production consumer: declared frontier on ResolvedOutputObligations (warm-moth-142 +// admit_job_publication / gunbc#10641). Witnesses below exercise both arms. fn resolve_output_obligations( contract: WorkContract, + requirements: ExecutionRequirements, work_key: WorkKey, judgment: OutputObligationsJudgment, ) -> OutputObligationsResolution { - OutputObligationsResolved { - value: ResolvedOutputObligations { + let expected = derive_work_key(contract: contract, requirements: requirements) + if work_key != expected { + OutputObligationsUnreadable { contract: contract.output_contract, - revision: output_obligations_revision(declared: contract.output_obligations), - judgment: judgment, - work_key: work_key, - required: contract.output_obligations.required, + cause: WorkKeyDoesNotMatchContract, + } + } else { + OutputObligationsResolved { + value: ResolvedOutputObligations { + contract: contract.output_contract, + revision: output_obligations_revision(declared: contract.output_obligations), + judgment: judgment, + work_key: work_key, + required: contract.output_obligations.required, + } } } } diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 83c58dbeb1c..37d657fc265 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -14,6 +14,7 @@ import product.fabric.work { declared_output_obligations, empty_output_obligations, resolve_output_obligations, OutputObligationsResolved, OutputObligationsUnreadable, OutputObligationsResolution, ObligationsJudgedAtJobStart, ObligationsJudgedAtAdmissionGate, + WorkKeyDoesNotMatchContract, } import product.fabric.identity { WorkKey } @@ -29,10 +30,11 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // output_contract field inhabits OutputContractRef — verified nonempty for each // class before classifying): // content-serialised — required_build_contract_from_materials (DELETED by this cut) -// opaque name — floor_work_contract; fixture contracts in fabric_*_witness -// content-addressed opaque — training_work_contract (hash wire as name, not framed roster) +// opaque name — floor_work_contract; fixture contracts in fabric_*_witness; +// training_work_contract (content-addressed digest is opaque: +// derived from content, no decodable payload — same surviving shape) // Consumers: work_identity_material (opaque string bytes + obligations material); -// no consumer ever parsed the framed form — anemic modeling confirmed. +// parse consumers of the framed form: 0 — anemic modeling confirmed (write-only fork). fn fixture_requirements() -> ExecutionRequirements { ExecutionRequirements { @@ -63,6 +65,7 @@ fn contract_with(obligations_ids: List) -> WorkContract { fn resolve_at_start(contract: WorkContract, work_key: WorkKey) -> OutputObligationsResolution { resolve_output_obligations( contract: contract, + requirements: fixture_requirements(), work_key: work_key, judgment: ObligationsJudgedAtJobStart, ) @@ -144,6 +147,7 @@ test fn judgment_policy_is_stamped_on_the_sealed_result() -> Bool { let key = derive_work_key(contract: contract, requirements: fixture_requirements()) match resolve_output_obligations( contract: contract, + requirements: fixture_requirements(), work_key: key, judgment: ObligationsJudgedAtAdmissionGate, ) { @@ -155,6 +159,23 @@ test fn judgment_policy_is_stamped_on_the_sealed_result() -> Bool { } } +// Discriminating producer for OutputObligationsUnreadable: a foreign work_key +// cannot seal this contract's roster. Without this path the Unreadable arm is +// permanently green by construction (DESIGN §4b). +test fn foreign_work_key_is_unreadable_not_resolved() -> Bool { + let a = contract_with(obligations_ids: ["artifact-a"]) + let b = contract_with(obligations_ids: ["artifact-b"]) + let foreign = derive_work_key(contract: b, requirements: fixture_requirements()) + match resolve_at_start(contract: a, work_key: foreign) { + OutputObligationsResolved { value: _ } => false + OutputObligationsUnreadable { contract: c, cause: cause } => + (c == a.output_contract) + && match cause { + WorkKeyDoesNotMatchContract => true + } + } +} + // Discriminating RED on source→.dag: a terminal-success-eligible roster is // SoleConstructorViolation without resolve_output_obligations. Path-scoped — // see ResolvedOutputObligations annotation. Fixture form is enrollable; an From 7c50c13205522730cd904a192be92d53c14b7e75 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 04:02:57 +0000 Subject: [PATCH 05/17] Unit-variant Unreadable cause needs {} construction form A bare `= WorkKeyDoesNotMatchContract` parsed as a type alias, so resolve and heal both refused unresolved type / undefined variable. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index acb788154f2..a3c2f2e28a2 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -126,7 +126,7 @@ type ResolvedOutputObligations sole_constructor { // roster. Extend the coproduct when a new readable refusal exists — never invent an // Unreadable by hand outside this module's resolve. type OutputObligationsUnreadableCause - = WorkKeyDoesNotMatchContract + = WorkKeyDoesNotMatchContract {} type OutputObligationsResolution = OutputObligationsResolved { value: ResolvedOutputObligations } @@ -340,7 +340,7 @@ fn resolve_output_obligations( if work_key != expected { OutputObligationsUnreadable { contract: contract.output_contract, - cause: WorkKeyDoesNotMatchContract, + cause: WorkKeyDoesNotMatchContract {}, } } else { OutputObligationsResolved { From 5d9a3f193fb9c7a1b4d7d66d618809f3e4c073de Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 04:03:17 +0000 Subject: [PATCH 06/17] Match WorkKeyDoesNotMatchContract with unit-variant {} form Co-authored-by: Cursor --- .../fabric/fabric_output_contract_resolution_witness_test.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 37d657fc265..b8905bfe5d7 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -171,7 +171,7 @@ test fn foreign_work_key_is_unreadable_not_resolved() -> Bool { OutputObligationsUnreadable { contract: c, cause: cause } => (c == a.output_contract) && match cause { - WorkKeyDoesNotMatchContract => true + WorkKeyDoesNotMatchContract {} => true } } } From fac180146e095d148c7ca8ebbc3a020176078826 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 04:20:15 +0000 Subject: [PATCH 07/17] Brand OutputObligationsRevision so foreign digests are ill-typed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bare ContentHash alias let any digest inhabit the sealed revision field; brand matches every other identity carrier in work.dag (§3 / §6). Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 6 ++++-- .../fabric_output_contract_resolution_witness_test.dag | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index a3c2f2e28a2..e0d5b4cebfe 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -82,7 +82,9 @@ fn empty_output_obligations() -> DeclaredOutputObligations { } // Revision of a declared roster — identity of WHICH roster was resolved. -type OutputObligationsRevision = ContentHash +// Branded so a foreign ContentHash (source manifest, trainer closure, …) is +// ill-typed as a revision rather than silently inhabiting the field (§3 / §6). +type OutputObligationsRevision = ContentHash where brand("OutputObligationsRevision") // WHICH policy a sealed result answers under. Warm-moth-142: revision identity // alone does not say whether a job is judged against the roster it started on or @@ -318,7 +320,7 @@ fn output_obligations_material(declared: DeclaredOutputObligations) -> NonEmptyS } fn output_obligations_revision(declared: DeclaredOutputObligations) -> OutputObligationsRevision { - content_hash_of_value(value: output_obligations_material(declared: declared)) + content_hash_of_value(value: output_obligations_material(declared: declared)) as OutputObligationsRevision } // The only route to a terminal-success-eligible roster on the .dag path. Empty diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index b8905bfe5d7..2f41753e452 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -192,7 +192,7 @@ fn violation_count(source: String, wanted: String) -> Int { } } -data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nimport std.content_hash { content_hash_of_value }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: content_hash_of_value(value: \"x\"),\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" +data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId, OutputObligationsRevision, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nimport std.content_hash { content_hash_of_value }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: content_hash_of_value(value: \"x\") as OutputObligationsRevision,\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" test fn forged_resolved_roster_is_sole_constructor_refused() -> Bool { violation_count(source: forged_resolved_roster_source, wanted: "SoleConstructorViolation") >= 1 From d56de531cdecd3332dad3982f0c8f382e98af5b8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 05:02:54 +0000 Subject: [PATCH 08/17] Mint OutputObligationsRevision as branded NonEmptyStr ContentHash is a sum; casting a variant into ContentHash-where-brand refused at runtime. Same identity shape as WorkKey: serialize then brand. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 11 +++++++---- ...fabric_output_contract_resolution_witness_test.dag | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index e0d5b4cebfe..bf0e98be574 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -82,9 +82,10 @@ fn empty_output_obligations() -> DeclaredOutputObligations { } // Revision of a declared roster — identity of WHICH roster was resolved. -// Branded so a foreign ContentHash (source manifest, trainer closure, …) is -// ill-typed as a revision rather than silently inhabiting the field (§3 / §6). -type OutputObligationsRevision = ContentHash where brand("OutputObligationsRevision") +// Branded NonEmptyStr (same shape as WorkKey): ContentHash is a sum, so +// `ContentHash where brand(...)` cannot be minted by casting a variant (§3 / §6 +// still hold — a foreign digest string is ill-typed as this brand). +type OutputObligationsRevision = NonEmptyStr where brand("OutputObligationsRevision") // WHICH policy a sealed result answers under. Warm-moth-142: revision identity // alone does not say whether a job is judged against the roster it started on or @@ -320,7 +321,9 @@ fn output_obligations_material(declared: DeclaredOutputObligations) -> NonEmptyS } fn output_obligations_revision(declared: DeclaredOutputObligations) -> OutputObligationsRevision { - content_hash_of_value(value: output_obligations_material(declared: declared)) as OutputObligationsRevision + serialize_content_hash( + hash: content_hash_of_value(value: output_obligations_material(declared: declared)) + ) as OutputObligationsRevision } // The only route to a terminal-success-eligible roster on the .dag path. Empty diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 2f41753e452..838329cc9f0 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -125,7 +125,7 @@ test fn resolution_revision_tracks_the_declared_roster() -> Bool { OutputObligationsResolved { value: ra } => match resolve_at_start(contract: b, work_key: kb) { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: rb } => - serialize_content_hash(hash: ra.revision) != serialize_content_hash(hash: rb.revision) + ra.revision != rb.revision } } } @@ -192,7 +192,7 @@ fn violation_count(source: String, wanted: String) -> Int { } } -data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId, OutputObligationsRevision, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nimport std.content_hash { content_hash_of_value }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: content_hash_of_value(value: \"x\") as OutputObligationsRevision,\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" +data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId, OutputObligationsRevision, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: \"x\" as OutputObligationsRevision,\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" test fn forged_resolved_roster_is_sole_constructor_refused() -> Bool { violation_count(source: forged_resolved_roster_source, wanted: "SoleConstructorViolation") >= 1 From e7a775c864cd83365639112619841c6d58bce1c4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 05:56:44 +0000 Subject: [PATCH 09/17] Drop unused serialize_content_hash import from resolution witness Co-authored-by: Cursor --- .../fabric/fabric_output_contract_resolution_witness_test.dag | 1 - 1 file changed, 1 deletion(-) diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 838329cc9f0..81d25393bf0 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -2,7 +2,6 @@ module test.claim.fabric_output_contract_resolution_witness import std.types { NonEmptyStr, Bool, String, Int, List } import std.measure { hardware_thread_count } -import std.content_hash { serialize_content_hash } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import gunbc.compile_diagnostic_census { CompileDiagnosticCensus, CensusObserved, CensusNotRunnable, From ca3032398beb7239257c764f7dbd5ae1cc5ec5f8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 06:02:37 +0000 Subject: [PATCH 10/17] Record that obligation revision mints only Fnv1a64 structural serialize_content_hash is family-asymmetric; this carrier never enters the other arms, so revision identity is not riding the incidental width split. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index bf0e98be574..d7eb8e70c74 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -320,6 +320,12 @@ fn output_obligations_material(declared: DeclaredOutputObligations) -> NonEmptyS ) } +// FAMILY-FIXED MINT. content_hash_of_value is Fnv1a64 structural only; +// serialize_content_hash of that arm is the bare 16-hex digest. Roster revision +// is computation identity, not byte integrity — Sha256Digest remains a different +// question (warm-moth-142 completion module). serialize_content_hash is +// family-asymmetric (two arms tagged, two bare); this mint never enters those +// other arms, so the incidental width discriminator is not load-bearing here. fn output_obligations_revision(declared: DeclaredOutputObligations) -> OutputObligationsRevision { serialize_content_hash( hash: content_hash_of_value(value: output_obligations_material(declared: declared)) From f75d3d04f8d3ced3f386b0a4071e19a1d1c3c6c6 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 06:28:23 +0000 Subject: [PATCH 11/17] Keep artifact vs terminal as kinds on one obligation roster Concatenating required-build manifests made split and collapsed id lists the same WorkKey. Kind is identity material; resolution still returns ids. Co-authored-by: Cursor --- dag/gunbc/fabric/fabric_witness_run.dag | 6 +- dag/gunbc/product/fabric/work.dag | 56 +++++++++++++++++-- ...utput_contract_resolution_witness_test.dag | 37 +++++++++++- 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/dag/gunbc/fabric/fabric_witness_run.dag b/dag/gunbc/fabric/fabric_witness_run.dag index e6dcea41005..93ec9859900 100644 --- a/dag/gunbc/fabric/fabric_witness_run.dag +++ b/dag/gunbc/fabric/fabric_witness_run.dag @@ -16,6 +16,7 @@ import product.fabric.work { CapabilityManifestRef, TrustDomainRef, ProgramRef, SourceManifestRef, ArtifactManifestRef, EffectContractRef, OutputContractRef, declared_output_obligations, + declared_output_obligations_from_manifests, work_for, } import std.content_hash { ContentHash, content_hash_of_value, serialize_content_hash } @@ -188,8 +189,9 @@ fn required_build_contract_from_materials( runtime_closure: concat("gunbc.required-witnesses-build.runtime:", concat(framed_string(value: toolchain), framed_string(value: executable))), effect_contract: "gunbc.required-witnesses-build.effects.checkout-read-cache-and-permitted-credentials-only", output_contract: "gunbc.required-witnesses-build.outputs", - output_obligations: declared_output_obligations( - ids: output_manifest + terminal_result_manifest, + output_obligations: declared_output_obligations_from_manifests( + artifacts: output_manifest, + terminals: terminal_result_manifest, ), } } diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index d7eb8e70c74..e77f398ce4a 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -63,17 +63,59 @@ type OutputObligationId = NonEmptyStr where brand("OutputObligationId") // Declared roster on the Work. Empty is a legitimate declaration (a job that owes // nothing), never an unsupplied one — that distinction belongs to resolution below. +// One roster of typed obligations — not two lists. Kind is part of identity +// material, so the same id as an output artifact versus a terminal result is a +// different owed subject (required-build used to frame those groups separately +// in OutputContractRef; concatenating ids dropped the boundary). +type OutputObligationKind + = OutputArtifactObligation {} + | TerminalResultObligation {} + +type OutputObligation { + id: OutputObligationId + kind: OutputObligationKind +} + type DeclaredOutputObligations { - required: List + required: List } fn output_obligation_id(raw: NonEmptyStr) -> OutputObligationId { raw as OutputObligationId } +fn output_obligation_kind_material(kind: OutputObligationKind) -> NonEmptyStr { + match kind { + OutputArtifactObligation {} => "artifact" + TerminalResultObligation {} => "terminal" + } +} + +fn output_obligation_ids(declared: DeclaredOutputObligations) -> List { + map(declared.required, ob => ob.id) +} + fn declared_output_obligations(ids: List) -> DeclaredOutputObligations { DeclaredOutputObligations { - required: map(ids, id => output_obligation_id(raw: id)), + required: map(ids, id => OutputObligation { + id: output_obligation_id(raw: id), + kind: OutputArtifactObligation {}, + }), + } +} + +fn declared_output_obligations_from_manifests( + artifacts: List, + terminals: List, +) -> DeclaredOutputObligations { + DeclaredOutputObligations { + required: map(artifacts, id => OutputObligation { + id: output_obligation_id(raw: id), + kind: OutputArtifactObligation {}, + }) + map(terminals, id => OutputObligation { + id: output_obligation_id(raw: id), + kind: TerminalResultObligation {}, + }), } } @@ -316,7 +358,13 @@ fn output_obligations_material(declared: DeclaredOutputObligations) -> NonEmptyS fold( declared.required, init: concat("output-obligations:", concat(to_string(declared.required.count()), ":")), - f: (acc, id) => concat(acc, "\u{1e}", id), + f: (acc, ob) => concat( + acc, + "\u{1e}", + ob.id, + "\u{1f}", + output_obligation_kind_material(kind: ob.kind), + ), ) } @@ -360,7 +408,7 @@ fn resolve_output_obligations( revision: output_obligations_revision(declared: contract.output_obligations), judgment: judgment, work_key: work_key, - required: contract.output_obligations.required, + required: output_obligation_ids(declared: contract.output_obligations), } } } diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 81d25393bf0..4cbef192047 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -11,6 +11,7 @@ import product.fabric.work { WorkContract, WorkStep, ReceiptSatisfiable, ExecutionRequirements, Shape, HardRequirements, ControlPlaneCapacity, derive_work_key, declared_output_obligations, empty_output_obligations, + declared_output_obligations_from_manifests, output_obligation_ids, resolve_output_obligations, OutputObligationsResolved, OutputObligationsUnreadable, OutputObligationsResolution, ObligationsJudgedAtJobStart, ObligationsJudgedAtAdmissionGate, WorkKeyDoesNotMatchContract, @@ -102,7 +103,7 @@ test fn resolved_roster_carries_declared_obligation_ids() -> Bool { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: r } => (r.required.count() == 2) - && (r.required == declared_output_obligations(ids: ["artifact-a", "terminal-b"]).required) + && (r.required == output_obligation_ids(declared: declared_output_obligations(ids: ["artifact-a", "terminal-b"]))) } } @@ -114,6 +115,40 @@ test fn changing_obligations_changes_work_key_without_renaming_the_contract() -> != derive_work_key(contract: b, requirements: fixture_requirements())) } +// Concatenating artifact and terminal ids (the first required-build encoding after +// the OutputContractRef cut) made ["a"]+["b"] and ["a","b"]+[] the same WorkKey. +test fn artifact_versus_terminal_kind_is_work_key_identity() -> Bool { + let shared = WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: declared_output_obligations_from_manifests( + artifacts: ["a"], + terminals: ["b"], + ), + } + let collapsed = WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: declared_output_obligations_from_manifests( + artifacts: ["a", "b"], + terminals: [], + ), + } + (shared.output_contract == collapsed.output_contract) + && (derive_work_key(contract: shared, requirements: fixture_requirements()) + != derive_work_key(contract: collapsed, requirements: fixture_requirements())) +} + test fn resolution_revision_tracks_the_declared_roster() -> Bool { let a = contract_with(obligations_ids: ["artifact-a"]) let b = contract_with(obligations_ids: ["artifact-b"]) From e2f21823fa8de0ffc06c8544a0f556a7ccdfa711 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 06:45:41 +0000 Subject: [PATCH 12/17] Name obligation kinds at construction instead of defaulting to artifact. An ids-only constructor silently stamped OutputArtifactObligation, so terminal subjects (floor summary, outputs-verdict) minted the wrong work key. Training now owes a stable adapter id rather than reusing the contract digest. Co-authored-by: Cursor --- dag/gunbc/fabric/fabric_witness_run.dag | 4 ++-- dag/gunbc/product/fabric/work.dag | 11 +++++++++- dag/gunbc/product/training/resolution.dag | 8 ++++--- .../fabric_capacity_class_witness_test.dag | 4 ++-- .../fabric_grant_issuance_witness_test.dag | 6 +++--- ...utput_contract_resolution_witness_test.dag | 21 +++++++++++++++---- .../fabric_terminal_contract_witness_test.dag | 8 +++---- 7 files changed, 43 insertions(+), 19 deletions(-) diff --git a/dag/gunbc/fabric/fabric_witness_run.dag b/dag/gunbc/fabric/fabric_witness_run.dag index 93ec9859900..7d75ec6e013 100644 --- a/dag/gunbc/fabric/fabric_witness_run.dag +++ b/dag/gunbc/fabric/fabric_witness_run.dag @@ -15,8 +15,8 @@ import product.fabric.work { ExecutionRequirements, Shape, HardRequirements, CapacityClass, ControlPlaneCapacity, CapabilityManifestRef, TrustDomainRef, ProgramRef, SourceManifestRef, ArtifactManifestRef, EffectContractRef, OutputContractRef, - declared_output_obligations, declared_output_obligations_from_manifests, + declared_terminal_obligations, work_for, } import std.content_hash { ContentHash, content_hash_of_value, serialize_content_hash } @@ -411,7 +411,7 @@ fn floor_work_contract(source: SourceManifestRef) -> WorkContract { runtime_closure: "gunbc.toolchain.rust-1.93.0+clippy+rustfmt", effect_contract: "gunbc.floor.effects.read-only-corpus", output_contract: "gunbc.floor.outputs.required-floor-summary", - output_obligations: declared_output_obligations(ids: ["required-floor-summary"]), + output_obligations: declared_terminal_obligations(ids: ["required-floor-summary"]), } } diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index e77f398ce4a..954814e3468 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -95,7 +95,7 @@ fn output_obligation_ids(declared: DeclaredOutputObligations) -> List ob.id) } -fn declared_output_obligations(ids: List) -> DeclaredOutputObligations { +fn declared_artifact_obligations(ids: List) -> DeclaredOutputObligations { DeclaredOutputObligations { required: map(ids, id => OutputObligation { id: output_obligation_id(raw: id), @@ -104,6 +104,15 @@ fn declared_output_obligations(ids: List) -> DeclaredOutputObligati } } +fn declared_terminal_obligations(ids: List) -> DeclaredOutputObligations { + DeclaredOutputObligations { + required: map(ids, id => OutputObligation { + id: output_obligation_id(raw: id), + kind: TerminalResultObligation {}, + }), + } +} + fn declared_output_obligations_from_manifests( artifacts: List, terminals: List, diff --git a/dag/gunbc/product/training/resolution.dag b/dag/gunbc/product/training/resolution.dag index 8134b01170e..063e6565208 100644 --- a/dag/gunbc/product/training/resolution.dag +++ b/dag/gunbc/product/training/resolution.dag @@ -2,7 +2,7 @@ module product.training.resolution import product.fabric.work { Work, WorkContract, ExecutionRequirements, WorkStep, ReceiptSatisfiable, work_for, - declared_output_obligations, + declared_artifact_obligations, } import product.fabric.demand { Demand, ObservationReceiptRef } import product.fabric.identity { @@ -471,7 +471,9 @@ fn training_manifest_reference(m: TrainingResolvedManifest) -> NonEmptyStr { // runtime_closure and output_contract additionally travel in their own fields because the fabric // reads them for its own purposes and flattening them into the reference would hide them. // Content-hash wire as opaque OutputContractRef identity (not content-serialised -// obligations). The owed subject is that same identity as a single obligation id. +// obligations). The owed artifact is a stable subject id, not the contract digest — +// commit manifests key by obligation id; a digest that changes with every +// coordinate would mint a new owed subject per resolution. fn training_work_contract(evidence: TrainingResolutionEvidence) -> WorkContract { let m = training_resolved_manifest(evidence: evidence) let contract_id = serialize_content_hash(hash: m.output_contract) @@ -483,7 +485,7 @@ fn training_work_contract(evidence: TrainingResolutionEvidence) -> WorkContract runtime_closure: serialize_content_hash(hash: m.trainer_closure), effect_contract: "training-write-checkpoints-and-adapter", output_contract: contract_id, - output_obligations: declared_output_obligations(ids: [contract_id]), + output_obligations: declared_artifact_obligations(ids: ["training-low-rank-adapter"]), } } diff --git a/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag b/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag index 1cc8eae2e7f..93edd1fca76 100644 --- a/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag +++ b/dag/test/claim/fabric/fabric_capacity_class_witness_test.dag @@ -8,7 +8,7 @@ import product.fabric.work { CapacityClass, ControlPlaneCapacity, CustomerExecutableCapacity, ExecutionRequirements, Shape, HardRequirements, capacity_class_eq, capacity_class_wire, work_identity_material, WorkContract, WorkStep, - declared_output_obligations, + declared_artifact_obligations, } import product.fabric.envelope { unstated_envelope } import product.fabric.isolation { IsolationProfile } @@ -47,7 +47,7 @@ fn cc_contract() -> WorkContract { runtime_closure: "gunbc.runtime.closure-1", effect_contract: "gunbc.effect.contract-1", output_contract: "gunbc.output.contract-1", - output_obligations: declared_output_obligations(ids: ["gunbc.output.contract-1"]), + output_obligations: declared_artifact_obligations(ids: ["gunbc.output.artifact-1"]), } } diff --git a/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag b/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag index 3802c110af8..adc96019ac0 100644 --- a/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag +++ b/dag/test/claim/fabric/fabric_grant_issuance_witness_test.dag @@ -13,7 +13,7 @@ import product.fabric.envelope { unstated_envelope } import product.fabric.work { Work, WorkContract, WorkStep, ReceiptSatisfiable, ExecutionRequirements, Shape, HardRequirements, CapacityClass, ControlPlaneCapacity, work_for, - declared_output_obligations, + declared_artifact_obligations, } import product.fabric.demand { ObservationReceiptRef } import product.fabric.supply { SupplierOffer, QuotedPerSecond, ObservedSupply } @@ -63,7 +63,7 @@ data fixture_contract: WorkContract = WorkContract { runtime_closure: "runtime:1", effect_contract: "effects:1", output_contract: "outputs:1", - output_obligations: declared_output_obligations(ids: ["outputs:1"]), + output_obligations: declared_artifact_obligations(ids: ["outputs:1"]), } data owning_principal: NonEmptyStr = "gunbc" @@ -80,7 +80,7 @@ data other_contract: WorkContract = WorkContract { runtime_closure: "runtime:1", effect_contract: "effects:1", output_contract: "outputs:1", - output_obligations: declared_output_obligations(ids: ["outputs:1"]), + output_obligations: declared_artifact_obligations(ids: ["outputs:1"]), } data other_work: Work = work_for(principal: owning_principal, contract: other_contract, requirements: fixture_requirements) diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index 4cbef192047..a258ea9fe04 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -10,7 +10,7 @@ import gunbc.compile_diagnostic_census { import product.fabric.work { WorkContract, WorkStep, ReceiptSatisfiable, ExecutionRequirements, Shape, HardRequirements, ControlPlaneCapacity, derive_work_key, - declared_output_obligations, empty_output_obligations, + declared_artifact_obligations, empty_output_obligations, declared_output_obligations_from_manifests, output_obligation_ids, resolve_output_obligations, OutputObligationsResolved, OutputObligationsUnreadable, OutputObligationsResolution, ObligationsJudgedAtJobStart, ObligationsJudgedAtAdmissionGate, @@ -58,7 +58,7 @@ fn contract_with(obligations_ids: List) -> WorkContract { runtime_closure: "runtime-1", effect_contract: "effects-1", output_contract: "gunbc.probe.outputs", - output_obligations: declared_output_obligations(ids: obligations_ids), + output_obligations: declared_artifact_obligations(ids: obligations_ids), } } @@ -97,13 +97,26 @@ test fn empty_declared_roster_resolves_as_resolved_empty_not_unreadable() -> Boo } test fn resolved_roster_carries_declared_obligation_ids() -> Bool { - let contract = contract_with(obligations_ids: ["artifact-a", "terminal-b"]) + let declared = declared_output_obligations_from_manifests( + artifacts: ["artifact-a"], + terminals: ["terminal-b"], + ) + let contract = WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: declared, + } let key = derive_work_key(contract: contract, requirements: fixture_requirements()) match resolve_at_start(contract: contract, work_key: key) { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: r } => (r.required.count() == 2) - && (r.required == output_obligation_ids(declared: declared_output_obligations(ids: ["artifact-a", "terminal-b"]))) + && (r.required == output_obligation_ids(declared: declared)) } } diff --git a/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag b/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag index d656a564549..0cedb682419 100644 --- a/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag +++ b/dag/test/claim/fabric/fabric_terminal_contract_witness_test.dag @@ -5,7 +5,7 @@ import std.measure { money_amount_micro, money_amount_micro_count, hardware_thre import std.content_hash { content_hash_of_value } import extdeps.currency.currency { Usd } import product.fabric.identity { FabricIdentity, fabric_identity_key, DemandKey, WorkKey } -import product.fabric.work { CapacityClass, ControlPlaneCapacity, Shape, HardRequirements, Work, WorkContract, ExecutionRequirements, WorkStep, derive_work_key, work_for, ReceiptSatisfiable, IdempotentUnderKey, OpaqueEffects, declared_output_obligations } +import product.fabric.work { CapacityClass, ControlPlaneCapacity, Shape, HardRequirements, Work, WorkContract, ExecutionRequirements, WorkStep, derive_work_key, work_for, ReceiptSatisfiable, IdempotentUnderKey, OpaqueEffects, declared_terminal_obligations } import product.fabric.demand { Demand, AdmissionTerms, BuyTerms, SatisfactionRequirement, demand_work, demand_reservation_price, demand_maximum_buy_order } import product.fabric.supply { SupplierOffer, QuotedPerSecond, offer_quote_amount, offer_quote_currency, ObservedSupply, QuotedSupply } import product.fabric.execution { Attempt, InitialAttempt, InfrastructureRetry, CapabilityEscalation, LeaseIdentity, Receipt, ExecutionReceipt, ExecutionGrant, FabricGrantObject, FabricGrantContext, FabricGrantEvidence, ReceiptAdmissible, ReceiptFromDifferentLease, ReceiptFromDifferentAttempt, ReceiptFingerprintIncomparable, ReceiptGenerationStale, admit_receipt_for_grant } @@ -45,7 +45,7 @@ data fixture_contract: WorkContract = WorkContract { runtime_closure: "toolchain-rust-1.89", effect_contract: "effects-read-only", output_contract: "outputs-verdict", - output_obligations: declared_output_obligations(ids: ["outputs-verdict"]), + output_obligations: declared_terminal_obligations(ids: ["outputs-verdict"]), } // The key is DERIVED, never authored. An earlier version of this file declared @@ -74,7 +74,7 @@ data other_source_contract: WorkContract = WorkContract { runtime_closure: "toolchain-rust-1.89", effect_contract: "effects-read-only", output_contract: "outputs-verdict", - output_obligations: declared_output_obligations(ids: ["outputs-verdict"]), + output_obligations: declared_terminal_obligations(ids: ["outputs-verdict"]), } // Same contract, same capabilities and trust domain, DIFFERENT shape. Before @@ -190,7 +190,7 @@ test fn identical_contracts_derive_one_key_regardless_of_who_asked() -> Bool { runtime_closure: "toolchain-rust-1.89", effect_contract: "effects-read-only", output_contract: "outputs-verdict", - output_obligations: declared_output_obligations(ids: ["outputs-verdict"]), + output_obligations: declared_terminal_obligations(ids: ["outputs-verdict"]), }, requirements: fixture_requirements, ) From 41a5dbf71ecce9bdedc4ccc81dc8ed62cb945872 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 08:02:02 +0000 Subject: [PATCH 13/17] Admit #10706 OutsideModeledGuarantee citations as next-rung triggers. Declarations was failing the floor on three stamp required_capability refs and one fixture absence. Those names must stay unresolved; PLANTED_CONTROL is the wrong roster because resolve there means a lost control. Same join as Co-authored-by: Cursor --- src/v1/stage0/src/declaration_index.rs | 202 +++++++++++++----- .../tests/declaration_index_integrity.rs | 72 ++++++- 2 files changed, 206 insertions(+), 68 deletions(-) diff --git a/src/v1/stage0/src/declaration_index.rs b/src/v1/stage0/src/declaration_index.rs index 82835261e49..ffecec9e4dc 100644 --- a/src/v1/stage0/src/declaration_index.rs +++ b/src/v1/stage0/src/declaration_index.rs @@ -269,6 +269,9 @@ pub enum DeclarationIntegrityKind { /// A `PLANTED_CONTROL_CITATIONS` row whose citation stopped refusing — the control is no /// longer discriminating. The inverse reading of the same trigger as the row above. PlantedControlNoLongerRefuses, + /// A `NEXT_RUNG_TRIGGER_CITATIONS` row whose citation now resolves — the named + /// `required_capability` was authored, which is the `OutsideModeledGuarantee` stamp firing. + NextRungTriggerCitationResolved, } pub fn integrity_kind_label(kind: &DeclarationIntegrityKind) -> &'static str { @@ -281,6 +284,7 @@ pub fn integrity_kind_label(kind: &DeclarationIntegrityKind) -> &'static str { DeclarationIntegrityKind::DuplicateModuleDeclaration => "DUPLICATE-MODULE", DeclarationIntegrityKind::CitationDebtRowStale => "CITATION-DEBT-ROW-STALE", DeclarationIntegrityKind::PlantedControlNoLongerRefuses => "PLANTED-CONTROL-RESOLVES", + DeclarationIntegrityKind::NextRungTriggerCitationResolved => "TRIGGER-CITATION-RESOLVES", } } @@ -1347,6 +1351,17 @@ pub fn import_member_findings(index: &DeclarationIndex) -> Vec Vec String, +) -> Vec { + let still_refusing = refusing_sites(index); + roster + .iter() + .filter(|row| !still_refusing.contains(&site_owned(row))) + .map( + |(citer, in_decl, module, decl, field)| DeclarationIntegrityFinding { + kind: kind.clone(), + rel_path: "src/v1/stage0/src/declaration_index.rs".to_string(), + offset: None, + message: message(citer, in_decl, module, decl, field), + }, + ) + .collect() +} + +fn citation_field_suffix(field: &str) -> String { + if field.is_empty() { + String::new() + } else { + format!(" field `{field}`") + } +} + +/// A control that has STOPPED refusing has lost its discriminating power, and that is a red in +/// its own right — the inverse of a spent debt row, and the reason these are a separate roster. +pub fn planted_control_findings(index: &DeclarationIndex) -> Vec { + planted_control_findings_against(index, PLANTED_CONTROL_CITATIONS) +} + +pub fn planted_control_findings_against( + index: &DeclarationIndex, + roster: &[(&str, &str, &str, &str, &str)], +) -> Vec { + resolved_roster_findings( + index, + roster, + DeclarationIntegrityKind::PlantedControlNoLongerRefuses, + |citer, in_decl, module, decl, field| { + format!( + "PLANTED_CONTROL_CITATIONS lists `{citer}` `{in_decl}` citing `{module}` `{decl}`{} as a \ + control that must NOT resolve, and it no longer refuses — the control has \ + lost its discriminating power and the mechanism it proves is now unevidenced", + citation_field_suffix(field) + ) + }, + ) +} + +/// PRODUCTION NEXT-RUNG TRIGGER CITATIONS, 2026-09-07. Three `OutsideModeledGuarantee` stamps +/// name a `required_capability` that must not exist yet; authoring it is the climb that spends +/// the stamp (`grounding_name_only_residual_boundary` says "then this stamp reds"). Each row +/// is one SITE; two `DeclarationRef`s inside one stamp that name the same absent symbol share +/// that identity. /// -/// Site grain: `(citing_module, in_declaration, cited_module, cited_decl, field)`. Two -/// DeclarationRef literals inside one stamp that name the same absent symbol share one row. -const PLANTED_CONTROL_CITATIONS: &[(&str, &str, &str, &str, &str)] = &[ +/// NOT `PLANTED_CONTROL_CITATIONS`. That roster's diagnostic says a resolving control has lost +/// its discriminating power. These rows resolve WHEN THE STAMP FIRES AS DESIGNED. Same +/// underlying trigger (citation used to refuse, now does not), materially different contract, +/// so a different name, kind, and message. A comment asserting direction coincidence is not +/// a second contract. +const NEXT_RUNG_TRIGGER_CITATIONS: &[(&str, &str, &str, &str, &str)] = &[ ( "v2.lens.enforcement.complexity_contract_subject", "complexity_optimality_boundary", @@ -2043,45 +2147,32 @@ const PLANTED_CONTROL_CITATIONS: &[(&str, &str, &str, &str, &str)] = &[ "unrestricted_cheaper_equivalent", "", ), - ( - "v2.test.claim.construction_justification.outside_modeled_guarantee_witness_test", - "nonexistent_capability_ref", - "v2.lens.cost", - "capability_absent_from_decl_facts", - "", - ), ]; -/// A control that has STOPPED refusing has lost its discriminating power, and that is a red in -/// its own right — the inverse of a spent debt row, and the reason these are a separate roster. -pub fn planted_control_findings(index: &DeclarationIndex) -> Vec { - planted_control_findings_against(index, PLANTED_CONTROL_CITATIONS) +pub fn next_rung_trigger_citation_findings( + index: &DeclarationIndex, +) -> Vec { + next_rung_trigger_citation_findings_against(index, NEXT_RUNG_TRIGGER_CITATIONS) } -pub fn planted_control_findings_against( +pub fn next_rung_trigger_citation_findings_against( index: &DeclarationIndex, roster: &[(&str, &str, &str, &str, &str)], ) -> Vec { - let still_refusing = refusing_sites(index); - roster - .iter() - .filter(|row| !still_refusing.contains(&site_owned(row))) - .map(|(citer, in_decl, module, decl, field)| DeclarationIntegrityFinding { - kind: DeclarationIntegrityKind::PlantedControlNoLongerRefuses, - rel_path: "src/v1/stage0/src/declaration_index.rs".to_string(), - offset: None, - message: format!( - "PLANTED_CONTROL_CITATIONS lists `{citer}` `{in_decl}` citing `{module}` `{decl}`{} as a \ - control that must NOT resolve, and it no longer refuses — the control has \ - lost its discriminating power and the mechanism it proves is now unevidenced", - if field.is_empty() { - String::new() - } else { - format!(" field `{field}`") - } - ), - }) - .collect() + resolved_roster_findings( + index, + roster, + DeclarationIntegrityKind::NextRungTriggerCitationResolved, + |citer, in_decl, module, decl, field| { + format!( + "NEXT_RUNG_TRIGGER_CITATIONS lists `{citer}` `{in_decl}` citing `{module}` `{decl}`{} as \ + an OutsideModeledGuarantee required_capability that must not exist yet, and it \ + now resolves — the stamp's climb has fired; delete this row and take the stamp \ + off OutsideModeledGuarantee", + citation_field_suffix(field) + ) + }, + ) } /// The debt join, over an EXPLICIT roster. @@ -2107,26 +2198,19 @@ pub fn citation_debt_findings_named( roster: &[(&str, &str, &str, &str, &str)], roster_name: &str, ) -> Vec { - let live = refusing_sites(index); - roster - .iter() - .filter(|row| !live.contains(&site_owned(row))) - .map(|(citer, in_decl, module, decl, field)| DeclarationIntegrityFinding { - kind: DeclarationIntegrityKind::CitationDebtRowStale, - rel_path: "src/v1/stage0/src/declaration_index.rs".to_string(), - offset: None, - message: format!( + resolved_roster_findings( + index, + roster, + DeclarationIntegrityKind::CitationDebtRowStale, + |citer, in_decl, module, decl, field| { + format!( "{roster_name} still lists `{citer}` `{in_decl}` citing `{module}` `{decl}`{} — that \ citation no longer refuses, so the row is spent and must be deleted; the \ roster only shrinks", - if field.is_empty() { - String::new() - } else { - format!(" field `{field}`") - } - ), - }) - .collect() + citation_field_suffix(field) + ) + }, + ) } /// (2) The cited-symbol wall — §3's cite-the-symbol rule, executing. @@ -2331,6 +2415,7 @@ pub fn corpus_findings(index: &DeclarationIndex) -> Vec Vec Date: Mon, 7 Sep 2026 08:17:44 +0000 Subject: [PATCH 14/17] Seal resolved obligations with kind, not ids alone. The work key already treated artifact vs terminal as distinct owed subjects; dropping kind at resolve made same-id mixed rosters collapse for the gate. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 8 +-- ...utput_contract_resolution_witness_test.dag | 53 +++++++++++++++++-- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index 954814e3468..1de78326d7c 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -91,10 +91,6 @@ fn output_obligation_kind_material(kind: OutputObligationKind) -> NonEmptyStr { } } -fn output_obligation_ids(declared: DeclaredOutputObligations) -> List { - map(declared.required, ob => ob.id) -} - fn declared_artifact_obligations(ids: List) -> DeclaredOutputObligations { DeclaredOutputObligations { required: map(ids, id => OutputObligation { @@ -172,7 +168,7 @@ type ResolvedOutputObligations sole_constructor { revision: OutputObligationsRevision judgment: OutputObligationsJudgment work_key: WorkKey - required: List + required: List } // Causes resolve_output_obligations can produce today. WorkKeyDoesNotMatchContract @@ -417,7 +413,7 @@ fn resolve_output_obligations( revision: output_obligations_revision(declared: contract.output_obligations), judgment: judgment, work_key: work_key, - required: output_obligation_ids(declared: contract.output_obligations), + required: contract.output_obligations.required, } } } diff --git a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag index a258ea9fe04..3bfc9d725e0 100644 --- a/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag +++ b/dag/test/claim/fabric/fabric_output_contract_resolution_witness_test.dag @@ -11,7 +11,7 @@ import product.fabric.work { WorkContract, WorkStep, ReceiptSatisfiable, ExecutionRequirements, Shape, HardRequirements, ControlPlaneCapacity, derive_work_key, declared_artifact_obligations, empty_output_obligations, - declared_output_obligations_from_manifests, output_obligation_ids, + declared_output_obligations_from_manifests, resolve_output_obligations, OutputObligationsResolved, OutputObligationsUnreadable, OutputObligationsResolution, ObligationsJudgedAtJobStart, ObligationsJudgedAtAdmissionGate, WorkKeyDoesNotMatchContract, @@ -96,7 +96,7 @@ test fn empty_declared_roster_resolves_as_resolved_empty_not_unreadable() -> Boo } } -test fn resolved_roster_carries_declared_obligation_ids() -> Bool { +test fn resolved_roster_carries_declared_obligations() -> Bool { let declared = declared_output_obligations_from_manifests( artifacts: ["artifact-a"], terminals: ["terminal-b"], @@ -116,7 +116,7 @@ test fn resolved_roster_carries_declared_obligation_ids() -> Bool { OutputObligationsUnreadable { contract: _, cause: _ } => false OutputObligationsResolved { value: r } => (r.required.count() == 2) - && (r.required == output_obligation_ids(declared: declared)) + && (r.required == declared.required) } } @@ -162,6 +162,51 @@ test fn artifact_versus_terminal_kind_is_work_key_identity() -> Bool { != derive_work_key(contract: collapsed, requirements: fixture_requirements())) } +// Same spelling as an artifact and as a terminal is two owed subjects. Sealing +// only ids would collapse them to ["a","a"] and the #10641 gate could not tell. +test fn same_id_artifact_and_terminal_remain_distinct_after_resolve() -> Bool { + let mixed = declared_output_obligations_from_manifests( + artifacts: ["a"], + terminals: ["a"], + ) + let doubled_artifact = declared_output_obligations_from_manifests( + artifacts: ["a", "a"], + terminals: [], + ) + let mixed_contract = WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: mixed, + } + let doubled_contract = WorkContract { + program: "gunbc.probe.output-contract", + steps: [WorkStep { step_id: "probe", resumability: ReceiptSatisfiable }], + source: "source-1", + inputs: "inputs-1", + runtime_closure: "runtime-1", + effect_contract: "effects-1", + output_contract: "gunbc.probe.outputs", + output_obligations: doubled_artifact, + } + let km = derive_work_key(contract: mixed_contract, requirements: fixture_requirements()) + let kd = derive_work_key(contract: doubled_contract, requirements: fixture_requirements()) + match resolve_at_start(contract: mixed_contract, work_key: km) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: rm } => match resolve_at_start(contract: doubled_contract, work_key: kd) { + OutputObligationsUnreadable { contract: _, cause: _ } => false + OutputObligationsResolved { value: rd } => + (rm.required == mixed.required) + && (rd.required == doubled_artifact.required) + && (rm.required != rd.required) + } + } +} + test fn resolution_revision_tracks_the_declared_roster() -> Bool { let a = contract_with(obligations_ids: ["artifact-a"]) let b = contract_with(obligations_ids: ["artifact-b"]) @@ -239,7 +284,7 @@ fn violation_count(source: String, wanted: String) -> Int { } } -data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationId, OutputObligationsRevision, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: \"x\" as OutputObligationsRevision,\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" +data forged_resolved_roster_source: String = "module probe_forged_resolved_output_obligations\nimport product.fabric.work { ResolvedOutputObligations, OutputContractRef, OutputObligationsRevision, ObligationsJudgedAtJobStart }\nimport product.fabric.identity { WorkKey }\nfn forged() -> ResolvedOutputObligations {\n ResolvedOutputObligations {\n contract: \"forged\" as OutputContractRef,\n revision: \"x\" as OutputObligationsRevision,\n judgment: ObligationsJudgedAtJobStart,\n work_key: \"k\" as WorkKey,\n required: [],\n }\n}\n" test fn forged_resolved_roster_is_sole_constructor_refused() -> Bool { violation_count(source: forged_resolved_roster_source, wanted: "SoleConstructorViolation") >= 1 From 4045a6dfeba46944d37ee5498959b50fd08a13dc Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 08:38:06 +0000 Subject: [PATCH 15/17] Enumerate the next-rung trigger helpers on the declaration-index seed-growth roster. The four production fns and the discriminating test landed in the same boundary the justification already lists at item grain; leaving them off the roster was a silent delta. Co-authored-by: Cursor --- dag/gunbc/declaration_index_seed_growth.dag | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/dag/gunbc/declaration_index_seed_growth.dag b/dag/gunbc/declaration_index_seed_growth.dag index 267f48b069c..82582d13438 100644 --- a/dag/gunbc/declaration_index_seed_growth.dag +++ b/dag/gunbc/declaration_index_seed_growth.dag @@ -201,6 +201,26 @@ data declaration_index_seed_growth_justification: SeedGrowthJustification = Seed decl_name: "citation_debt_findings", field: WholeDeclaration }, + DeclarationRef { + module_path: "v1_compiler.declaration_index", + decl_name: "resolved_roster_findings", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.declaration_index", + decl_name: "citation_field_suffix", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.declaration_index", + decl_name: "next_rung_trigger_citation_findings", + field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.declaration_index", + decl_name: "next_rung_trigger_citation_findings_against", + field: WholeDeclaration + }, DeclarationRef { module_path: "v1_compiler.declaration_index", decl_name: "cited_symbol_findings", @@ -325,11 +345,16 @@ data declaration_index_seed_growth_justification: SeedGrowthJustification = Seed module_path: "v1_compiler.tests.declaration_index_integrity", decl_name: "a_second_citation_of_an_enrolled_target_in_another_declaration_still_refuses", field: WholeDeclaration + }, + DeclarationRef { + module_path: "v1_compiler.tests.declaration_index_integrity", + decl_name: "a_next_rung_trigger_that_resolves_is_the_stamp_firing_not_a_lost_control", + field: WholeDeclaration } ], reason: "WHY RUST IS STILL NEEDED, and it is a REACHABILITY limit rather than a modeling gap: the subject is INGESTION -- the moment a .dag file is read off the filesystem and parsed -- and the only ingestion that executes today is host Rust. A .dag witness cannot reach it: run_required_floor's hermetic envelope refuses host effects during preparation, so a witness whose subject is a filesystem walk is counted executed while its assertion never runs. Asserting the index's behaviour from a mocked file tree would assert the mock, which is the specification-without-execution trap, so the construction and its discriminating evidence both have to live where the walk does.\n\nTHE CONSEQUENCE OF REFUSING IT, stated second because it is not the admitting argument: there is no second executing ingestion to re-home this to, so a refusal would leave both of DESIGN's next-rung triggers -- section 6's module-authorship trigger and section 3's cited-symbol restoration trigger -- undischargeable until v2 is the compiler.\n\nWHAT THE GROWTH BUYS, at identity grain rather than as a category: three walls on one construction, replacing one deleted corpus-walk census and two obligations that had no mechanism at all. It also NETS DOWN inside claim_executor, which loses the --required-cited-symbol mode and its three helper functions.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, - trigger: "Delete all 62 when INGESTION is itself modeled -- when the .dag source walk and the per-module record it derives are expressed as a .dag operation over a typed filesystem transport, the index becomes an ordinary substrate fold and these declarations move to the substrate. A PARTIAL migration IS admissible and is the expected shape: the moment the RECORD DERIVATION is modeled (record_from_module is a pure function of one parse tree and needs no host effect at all), the record builder and every finding function follow it, while the directory walk and the parse-clean plumbing stay behind for as long as the WALK is host-only; delete those on the second step. What does NOT dissolve them is the checks moving to another host entry point, and what does NOT dissolve them is a hermetic witness asserting a fabricated module tree -- that would retire the evidence while retiring nothing it guards.", + trigger: "Delete all 67 when INGESTION is itself modeled -- when the .dag source walk and the per-module record it derives are expressed as a .dag operation over a typed filesystem transport, the index becomes an ordinary substrate fold and these declarations move to the substrate. A PARTIAL migration IS admissible and is the expected shape: the moment the RECORD DERIVATION is modeled (record_from_module is a pure function of one parse tree and needs no host effect at all), the record builder and every finding function follow it, while the directory walk and the parse-clean plumbing stay behind for as long as the WALK is host-only; delete those on the second step. What does NOT dissolve them is the checks moving to another host entry point, and what does NOT dissolve them is a hermetic witness asserting a fabricated module tree -- that would retire the evidence while retiring nothing it guards.", current_boundary: "src/v1/stage0/src/declaration_index.rs; src/v1/stage0/src/cli_run.rs run_dag_parse_sweep; src/v1/stage0/tests/declaration_index_integrity.rs; src/v1/stage0/src/bin/claim_executor.rs; src/v1/stage0/src/bin/v1_src_dag_parse.rs; dag/gunbc/declaration_index_seed_growth.dag" } From 27b39d65aa4ed5ad733543ee75664439d9e78b38 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 09:02:10 +0000 Subject: [PATCH 16/17] Enroll NEXT_RUNG_TRIGGER_CITATIONS on the declaration-index seed-growth roster. The join helpers were listed; the production roster they close over was not. PRE_EXISTING_CITATION_DEBT is already a DeclarationRef on the same justification. Co-authored-by: Cursor --- dag/gunbc/declaration_index_seed_growth.dag | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dag/gunbc/declaration_index_seed_growth.dag b/dag/gunbc/declaration_index_seed_growth.dag index 82582d13438..7eddc4236fc 100644 --- a/dag/gunbc/declaration_index_seed_growth.dag +++ b/dag/gunbc/declaration_index_seed_growth.dag @@ -211,6 +211,11 @@ data declaration_index_seed_growth_justification: SeedGrowthJustification = Seed decl_name: "citation_field_suffix", field: WholeDeclaration }, + DeclarationRef { + module_path: "v1_compiler.declaration_index", + decl_name: "NEXT_RUNG_TRIGGER_CITATIONS", + field: WholeDeclaration + }, DeclarationRef { module_path: "v1_compiler.declaration_index", decl_name: "next_rung_trigger_citation_findings", @@ -354,7 +359,7 @@ data declaration_index_seed_growth_justification: SeedGrowthJustification = Seed ], reason: "WHY RUST IS STILL NEEDED, and it is a REACHABILITY limit rather than a modeling gap: the subject is INGESTION -- the moment a .dag file is read off the filesystem and parsed -- and the only ingestion that executes today is host Rust. A .dag witness cannot reach it: run_required_floor's hermetic envelope refuses host effects during preparation, so a witness whose subject is a filesystem walk is counted executed while its assertion never runs. Asserting the index's behaviour from a mocked file tree would assert the mock, which is the specification-without-execution trap, so the construction and its discriminating evidence both have to live where the walk does.\n\nTHE CONSEQUENCE OF REFUSING IT, stated second because it is not the admitting argument: there is no second executing ingestion to re-home this to, so a refusal would leave both of DESIGN's next-rung triggers -- section 6's module-authorship trigger and section 3's cited-symbol restoration trigger -- undischargeable until v2 is the compiler.\n\nWHAT THE GROWTH BUYS, at identity grain rather than as a category: three walls on one construction, replacing one deleted corpus-walk census and two obligations that had no mechanism at all. It also NETS DOWN inside claim_executor, which loses the --required-cited-symbol mode and its three helper functions.", owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, - trigger: "Delete all 67 when INGESTION is itself modeled -- when the .dag source walk and the per-module record it derives are expressed as a .dag operation over a typed filesystem transport, the index becomes an ordinary substrate fold and these declarations move to the substrate. A PARTIAL migration IS admissible and is the expected shape: the moment the RECORD DERIVATION is modeled (record_from_module is a pure function of one parse tree and needs no host effect at all), the record builder and every finding function follow it, while the directory walk and the parse-clean plumbing stay behind for as long as the WALK is host-only; delete those on the second step. What does NOT dissolve them is the checks moving to another host entry point, and what does NOT dissolve them is a hermetic witness asserting a fabricated module tree -- that would retire the evidence while retiring nothing it guards.", + trigger: "Delete all 68 when INGESTION is itself modeled -- when the .dag source walk and the per-module record it derives are expressed as a .dag operation over a typed filesystem transport, the index becomes an ordinary substrate fold and these declarations move to the substrate. A PARTIAL migration IS admissible and is the expected shape: the moment the RECORD DERIVATION is modeled (record_from_module is a pure function of one parse tree and needs no host effect at all), the record builder and every finding function follow it, while the directory walk and the parse-clean plumbing stay behind for as long as the WALK is host-only; delete those on the second step. What does NOT dissolve them is the checks moving to another host entry point, and what does NOT dissolve them is a hermetic witness asserting a fabricated module tree -- that would retire the evidence while retiring nothing it guards.", current_boundary: "src/v1/stage0/src/declaration_index.rs; src/v1/stage0/src/cli_run.rs run_dag_parse_sweep; src/v1/stage0/tests/declaration_index_integrity.rs; src/v1/stage0/src/bin/claim_executor.rs; src/v1/stage0/src/bin/v1_src_dag_parse.rs; dag/gunbc/declaration_index_seed_growth.dag" } From 19ab64b8104ac3e4e270d389e47bbe32904e5216 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 7 Sep 2026 09:30:52 +0000 Subject: [PATCH 17/17] Keep the sealed-roster kind note and drop the #10718 occupancy comment. PLANTED_CONTROL stays empty; #10706 stamp sites remain on NEXT_RUNG_TRIGGER_CITATIONS. Co-authored-by: Cursor --- dag/gunbc/product/fabric/work.dag | 2 ++ src/v1/stage0/src/declaration_index.rs | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/dag/gunbc/product/fabric/work.dag b/dag/gunbc/product/fabric/work.dag index 1de78326d7c..85a70c164ca 100644 --- a/dag/gunbc/product/fabric/work.dag +++ b/dag/gunbc/product/fabric/work.dag @@ -163,6 +163,8 @@ type OutputObligationsJudgment // caller-supplied coproduct. Until the trigger fires, DeclaredOutputObligations // in work_identity_material is the live production consumer of the roster half; // the seal exists so the gate cannot mint terminal-success without resolving. +// required is the typed roster (id AND kind). Projecting to ids at this seal +// would make artifact "a" and terminal "a" indistinguishable to #10641. type ResolvedOutputObligations sole_constructor { contract: OutputContractRef revision: OutputObligationsRevision diff --git a/src/v1/stage0/src/declaration_index.rs b/src/v1/stage0/src/declaration_index.rs index ffecec9e4dc..1b803525c41 100644 --- a/src/v1/stage0/src/declaration_index.rs +++ b/src/v1/stage0/src/declaration_index.rs @@ -2026,13 +2026,6 @@ pub fn citation_debt_findings(index: &DeclarationIndex) -> Vec