This gate enforces the completion close condition for the franken_node platform: the program is only considered complete when all three oracle dimensions are green. No partial success is accepted.
| Dimension | Owner Track | Description | Artifact |
|---|---|---|---|
| L1 Product Oracle | 10.2 | Spec-first compatibility oracle validates product-level semantics against Node/Bun behavior and requires proof-carrying host-effect evidence for first-tranche effects | artifacts/oracle/l1_product_verdict.json |
| L2 Engine-Boundary Oracle | 10.17 | Engine-boundary oracle validates that franken_engine integration points conform to spec | artifacts/oracle/l2_engine_verdict.json |
| Release Policy Linkage | 10.2 | Policy linkage validates that release gates consume both L1 and L2 verdicts and enforce pass-through | artifacts/oracle/release_policy_verdict.json |
PASS if and only if:
L1.verdict == "GREEN"
AND L2.verdict == "GREEN"
AND release_policy.verdict == "GREEN"
AND all three artifacts exist and are well-formed
AND the L1 compatibility result contains verified proof-carrying
EffectReceipt evidence for fs.read, fs.write, and http.request
FAIL if:
any dimension is missing, malformed, RED, or YELLOW
The L1 Product Oracle is defined by the acceptance invariant
(INV-PCG-ACCEPTANCE, bd-f5b04.2.4):
No canonical operation is "done" until it is both lockstep/parity-GREEN and proof-carrying (a verifiable
EffectReceiptfor the operation's L1 subject).
The invariant rules out the two failure shapes by construction:
| Shape | Example | Outcome |
|---|---|---|
| Parity-GREEN-but-unproven | Compatibility corpus passes, but proof_carrying_effects evidence is missing, partial, invalid, or chain-unverified |
L1 = RED (fail closed) |
| Proven-but-parity-RED | Effect receipts verify, but the corpus pass rate is below threshold or has errored cases | L1 = RED (fail closed) |
Both legs feed the same blocking_findings list in the L1 evaluator, so a
single failing condition on either leg makes the dimension RED and therefore
the composite verdict FAIL. There is no partial credit and no waiver.
The per-operation acceptance subjects are owned by
crates/franken-node/src/schema_versions.rs
(L1_PROOF_CARRYING_ACCEPTANCE_SUBJECTS): fs.read, fs.write,
http.request. The list is bound at three points so it cannot drift:
- Contract layer:
api::compat_gate::l1_proof_carrying_acceptance_subjects()derives the same list fromFIRST_TRANCHE_OPERATION_CONTRACTS(CompatOperationId::l1_proof_carrying_subject); conformance tests fail if the derivation and the canonical constant diverge. Operations without a first-tranche host effect (process.env,module.resolve) carry no subject and are accepted on parity alone. - Gate:
ops::close_conditionenforces the list fail-closed (evaluate_l1_product_oracle+validate_l1_proof_carrying_effects). - CI mirror:
scripts/check_oracle_close_condition.py(REQUIRED_L1_PROOF_SUBJECTS) applies the same list to the committed verdict artifacts in.github/workflows/execution-normalization-gate.yml.
franken-node doctor close-condition evaluates the invariant and emits the
stable FN-ACCEPT-* event stream under --structured-logs-jsonl:
FN-ACCEPT-001 (evaluated), then exactly one of FN-ACCEPT-002 (PASS) or
FN-ACCEPT-003 (FAIL-CLOSED), plus one FN-ACCEPT-004 line per blocking
finding. SIEM filters should pin on these codes, not message text.
Only the v2 proof_carrying_effects schema is accepted:
- v1 (
franken-node/l1-proof-carrying-effects/v1) — RETIRED (bd-qr5i2.4). The legacy declared summary carried no receipts the gate could re-derive, so its acceptance is withdrawn in both the Rust doctor gate and the Python CI gate; a v1 block now fails closed with an unsupported-schema finding. The schema id stays registered inschema_versions.rs(the registry is append-only) for historical artifacts. - v2 (
franken-node/l1-proof-carrying-effects/v2) — adds mandatoryreceipt_chain_entries(serializedEffectReceiptChainEntryarray). The gate re-derives the evidence natively: chain integrity (EffectReceiptChain::verify_entries_integrity), per-receipt validity, subjects (viaEffectKind::l1_acceptance_subject, counting onlyallowedreceipts), and counts. Any mismatch between the declared summary fields and the re-derived values is a blocking finding, and the acceptance requirements are evaluated over the derived values only. Denied receipts are legitimate chain content but never evidence an executed subject.
v2 evidence is produced from a real native-engine run by
franken-node ops proof-carrying-evidence
(ops::proof_carrying_evidence::produce_proof_carrying_effects_evidence,
bd-qr5i2.2). The producer executes one guest program covering every
acceptance subject (fs.write + fs.read against the run sandbox,
http.request against a loopback sink allowlisted through the standard
[security.network_policy] mechanism), harvests the signed
host_effect_ledger from the dispatch report, re-verifies it natively with
the same primitives the gate uses, and emits the v2 block whose declared
summary equals the derived values by construction. --merge-corpus artifacts/13/compatibility_corpus_results.json writes the block into the
artifact this gate reads (--out writes the block standalone). The producer
fails closed — dispatch failure, fallback runtime, missing ledger, chain or
receipt invalidity, a denied effect, a missing subject, or an egress that
never reached the loopback sink each abort production — and requires the
engine feature (no native run, no evidence).
The Python CI gate (scripts/check_oracle_close_condition.py) applies the
same v2 re-derivation independently (bd-qr5i2.3): it re-implements the
canonical receipt/chain hash preimages, re-derives chain integrity,
per-receipt validity, subjects, and counts from receipt_chain_entries, and
fail-closes on any declared↔derived mismatch — alongside the legacy v1
declared-summary path. A cross-language parity pin (the Rust
effect_receipt_hash_cross_language_parity_pin_bd_qr5i2_3 test and the
Python test_parity_pin_hashes test assert identical deterministic hash
constants) makes preimage drift between the two implementations break CI
immediately. Reference fixtures: tests/fixtures/oracle_gate/pass_v2/ and
tests/fixtures/oracle_gate/fail_v2_tampered/.
The committed artifacts are regenerated from real runs (bd-qr5i2.4), and the Rust and Python gate inputs are unified with the real lockstep-oracle verdict wired into the L1 leg (bd-ry7d1) — see the sections below.
The l1_product verdict artifact's evidence object must also carry a
lockstep_verdict block (franken-node/l1-lockstep-verdict/v1) produced by a
real dual-runtime lockstep-oracle run: bun as the independent reference
leg (subprocess) and the native in-process franken_engine as the franken leg,
both executing one deterministic guest program and compared through
runtime::nversion_oracle::RuntimeOracle. The block embeds the full
DivergenceReport; a declared "pass" is never trusted. Both gates
RE-DERIVE the verdict from the embedded report and fail closed unless:
- ≥ 2 registered runtimes with ≥ 2 distinct executor names (self-agreement is not a cross-check), at least one reference leg and one franken leg;
- ≥ 1 cross-runtime check, every check outcome
Agree; - zero divergences and a
Passverdict consistent with all of the above; - every declared summary field (
oracle_verdict,trace_id,runtimes,checks_total,divergence_count) equals its re-derived counterpart.
Both gate implementations consume ONE input set and bind it together:
- The Rust doctor gate (
ops::close_condition::evaluate_l1_product_oracle) readsartifacts/13/compatibility_corpus_results.json(parity pass rate + proof evidence) andartifacts/oracle/l1_product_verdict.json(declared verdict, lockstep verdict), and fails closed unless the verdict artifact'sproof_carrying_effectscopy is value-identical to the corpus-results copy. - The Python CI gate (
scripts/check_oracle_close_condition.py) reads the verdict triple, re-derives the proof chain and the lockstep verdict, and enforces the same corpus binding (on by default against the live repo;--corpus-results PATHfor custom layouts). - The section-10.N gate (
scripts/verify_section_10n.py, 10N-ORACLE) RUNS the Python gate — its committed output atartifacts/section/10.N/gate_verdict/bd-1neb_section_gate.jsonis what the Rust gate's release-policy leg consumes, closing the loop.
Regenerate BOTH L1 gate inputs from real runs with one command:
franken-node ops proof-carrying-evidence \
--merge-corpus artifacts/13/compatibility_corpus_results.json \
--merge-l1-verdict artifacts/oracle/l1_product_verdict.jsonThe producer fails closed (nothing is written) on any lockstep divergence, missing bun reference runtime, fallback-runtime engine run, or proof-evidence shortfall.
Each oracle dimension produces a verdict artifact:
{
"dimension": "l1_product | l2_engine_boundary | release_policy_linkage",
"verdict": "GREEN | YELLOW | RED",
"owner_track": "10.2 | 10.17",
"timestamp": "<ISO-8601 UTC>",
"evidence": {
"tests_passed": "<int>",
"tests_failed": "<int>",
"tests_skipped": "<int>",
"coverage_pct": "<float>",
"details_ref": "<path to detailed report>",
"proof_carrying_effects": {
"schema_version": "franken-node/l1-proof-carrying-effects/v2",
"required_subjects": ["fs.read", "fs.write", "http.request"],
"verified_subjects": ["fs.read", "fs.write", "http.request"],
"effect_receipts_verified": 3,
"invalid_receipts": 0,
"receipt_chain_verified": true,
"receipt_chain_entries": ["… serialized EffectReceiptChainEntry array — see producer output …"]
},
"lockstep_verdict": {
"schema_version": "franken-node/l1-lockstep-verdict/v1",
"trace_id": "l1-lockstep:<uuid>",
"produced_at": "<ISO-8601 UTC>",
"producer": "franken-node ops proof-carrying-evidence",
"guest_program_content_hash": "sha256:<CAS content hash>",
"runtimes": ["bun", "franken-engine-native"],
"oracle_verdict": "pass",
"checks_total": 1,
"divergence_count": 0,
"report": "… full serialized runtime::nversion_oracle::DivergenceReport …"
}
},
"blocking_findings": []
}The proof_carrying_effects and lockstep_verdict evidence objects are
mandatory for the l1_product verdict artifact and are ignored for non-L1
dimensions.
The Rust doctor close-condition L1 evaluator also consumes
artifacts/13/compatibility_corpus_results.json. That artifact must include a
proof_carrying_effects object with:
{
"schema_version": "franken-node/l1-proof-carrying-effects/v2",
"required_subjects": ["fs.read", "fs.write", "http.request"],
"verified_subjects": ["fs.read", "fs.write", "http.request"],
"effect_receipts_verified": 3,
"invalid_receipts": 0,
"receipt_chain_verified": true,
"receipt_chain_entries": ["… serialized EffectReceiptChainEntry array — see producer output …"]
}Generate/refresh this block from a real run with
franken-node ops proof-carrying-evidence --merge-corpus artifacts/13/compatibility_corpus_results.json; the declared summary
fields must equal the values re-derived from the embedded entries, which
the producer guarantees by construction.
Parity-only evidence is not enough. A GREEN compatibility pass rate with missing,
partial, invalid, or chain-unverified proof_carrying_effects evidence makes the
L1 dimension RED and therefore makes the composite close-condition RED.
The close-condition gate produces:
{
"gate": "dual_oracle_close_condition",
"verdict": "PASS | FAIL",
"timestamp": "<ISO-8601 UTC>",
"dimensions": {
"l1_product": { "present": true, "verdict": "GREEN" },
"l2_engine_boundary": { "present": true, "verdict": "GREEN" },
"release_policy_linkage": { "present": true, "verdict": "GREEN" }
},
"failing_dimensions": []
}No waivers are supported for the dual-oracle close condition. All three dimensions must be GREEN for the program to be considered complete.
The gate is invoked:
- Before any release candidate is promoted
- As part of the section-wide verification gate for 10.N
- During the final program completion check (PLAN 10.N → canonical graph)