Factory docs, home
Page navigation

Pure judgement of repository-scope Evidence for one sealed Candidate against a Slice's frozen acceptance, recipe and environment. It is a sibling of the fixture judge (scope local-fixture), never a relabelling of it.

Status: Accepted as step 1, increment 1, of the step-1 core (handover), reviewed together with increment 2 (Product gate and delivery records). Independent implementation review by Astra (gpt-6-astra, high effort): rounds 1–5 FAIL (6, 4, 3, 2 and 1 findings), round 6 PASS on 29 September 2026 (<local evidence archive>). At that review the increment tests passed 73/73 and the full npm run check 428/428, none skipped, on the pre-merge base f5a8991. No receipt exists yet, and this reference has no separate reference-review report. Its src/ consumers are the Product gate, the delivery records and the Repository collector (step 1, increment 3), which writes its records; the collector is accepted too, and nothing composes any of them. Local development only: no release or customer claim, and all 27 epics remain open. Human page: guide.

  • Source: src/repository-assurance.ts
  • Tests: test/repository-assurance.test.ts (16); its import boundary is also asserted in test/delivery-boundaries.test.ts, and rejudgeRepository is exercised through deriveAssessment in test/delivery-adversarial.test.ts.
  • Helper: test/helpers/repository-proof.ts (genuine store-written records for a synthetic Candidate, with scope and edits options).

Intelligence: optional — repository-assurance/review via frontier/medium; repository-assurance/fresh-scenario via frontier/high

What it hides

  • Candidate-as-program checks. Each process check runs one stage's tree (the base B or the Candidate M) as the executor program over a host-held fixture input. It passes only on the pinned exit code and stdout whose SHA-256 is the pinned stdoutDigest. Expected stdout exists only as that digest.
  • Authentication before Evidence. judgeRepository first requires the manifest to hash to pins.candidateDigest and to name the pinned store, Product, base, commit, Slice and the three frozen-input digests. Its files must give pins.candidateSnapshot, and the base listing (same commit, same tree) pins.baseSnapshot, by listingDigest (the executor's snapshot formula, computed from file identities). A mismatch is a TypeError, not a rejection.
  • Record qualification. A gathered record counts only if all of these hold: it is associated with a process check; it came from EvidenceStore.get in this process (isStoreRecord); its bytes and metadata still re-encode to its id (so bytes or metadata replaced in memory fail); it carries this Attempt's eight pins; it decodes as an sf-repository-observation/1; and its facts agree with the check, the Candidate, the metadata observation, the recipe's time bound and the pinned program, input, runtime, profile and bounds.
  • Completeness. Every reference the expectation associates with a check must be supplied. A missing one is rejected as "the associated reference was not supplied", so leaving out a failing guardrail's record can never give Verified.

Public interface

Runtime imports: node:buffer, node:crypto, canonicalJson from ./canonical-json.ts, SNAPSHOT_PROTOCOL from ./confined-executor.ts, PIN_FIELDS and isStoreRecord from ./evidence.ts, parseRepositoryIdentity from ./repository.ts; types from ./assurance.ts, ./confined-executor.ts, ./evidence.ts and ./repository.ts. No node:fs, node:child_process or node:http (asserted by test).

  • Constants: REPOSITORY_VERDICT_SCOPE = "local-repository", ACCEPTANCE_PROTOCOL = "sf-repository-acceptance/1", RECIPE_PROTOCOL = "sf-repository-verify/1", ENVIRONMENT_PROTOCOL = "sf-confined-environment/1", REPOSITORY_COLLECTOR = "sf-repository-scenario-collector/1", OBSERVATION_FORMAT = "sf-repository-observation/1", REPOSITORY_ASSURANCE_LIMITS = {maxGuardrails: 120, maxReviews: 6, maxArgs: 32} (frozen).
  • Frozen inputs: parseAcceptance (before is stage base, after is stage candidate with a different expected exit code, guardrails are stage candidate, check names are unique; an extra member, or a review among the process checks, is a TypeError), parseRecipe, parseEnvironment, and acceptanceDigest, recipeDigest, environmentDigest ("sha256:" of the canonical record).
  • listingDigest(files): equals the executor's snapshotDigest (200 random listings in test) and refuses, with TypeError, what the executor refuses (at most 1024 files, 16 MiB, 256-character paths of at most 16 segments, case-folded collisions, a path that is both file and directory).
  • parseRepositoryPins, evidencePinsOf(pins): scenarioRevision = "sf-repository-scenario/1/" + sha256(canonical {productId, store, base, commit, slice, baseSnapshot, candidateSnapshot, acceptance}), where store is the hex SHA-256 of the canonical store identity and acceptance its digest. The recipe, environment and collector revisions are <protocol>/<hex>, the collector's hex being the recipe's collector.closure. Every pin is at most 99 characters at the longest legal ids. parseStoreIdentity wraps parseRepositoryIdentity in a TypeError.
  • Observations: encodeObservation(facts, stdout, stderr) and decodeObservation(bytes) (exact inverse, else undefined; canonical facts at most 320 KiB), observationOf(facts) (the ProcessObservation the facts imply), classifyObservation(observation, expectedExitCode) (identical to the fixture classify, pinned by an exhaustive test).
  • Judgement:
    • judgeRepository(expectation, manifest, base, gathered): RepositoryVerdict, pure.
    • rejudgeRepository(verdict, gathered): {verdict, facts}: the judgement the Verdict's own expectation gives these records now, without authenticating a manifest and base (the caller holds the Verdict's pins to its frozen records), plus the decoded facts of every qualifying record by reference. The Verdict is reproduced only when the result equals it.
    • parseRepositoryVerdict (a fixture Verdict, or any other scope, is a TypeError) and verdictDigest ("sha256:" of the canonical Verdict: the integration proof).
  • Types: ScenarioCheck, ReviewCheck (reserved), AcceptanceRecord, ExecutorBounds, RecipeRecord, EnvironmentRecord, ListingFile, RepositoryPins, BaseListing, RepositoryExpectation {pins, evidence: {ref, check}[]}, RepositoryVerdict {result, scope, expectation, checks, rejected (each with its check), evidence, reason}, ObservedProcess, ObservationFacts.

Invariants and guarantees

  1. Pure. No filesystem, process, network, journal or clock. The same inputs give a deep-equal Verdict with a stable digest.
  2. Result rules. Per check: no qualifying record gives inconclusive; among several, the first failed decides, else the first inconclusive, else the first passed. Any failed check gives Failed (including before exiting with the after code). Otherwise any inconclusive check or any rejected entry (a repeated reference, an unassociated one, or an associated one not supplied) gives Inconclusive. Otherwise Verified. Review checks are always inconclusive.
  3. Stdout is decisive. The pinned exit code with other stdout is failed (test "stdout digests are decisive…").
  4. Disqualification (test rows): no association, a fault entry, a hand-built or copied record, the wrong id, bytes or metadata swapped in memory, another check, another collector, environment, Product, Attempt or fixture pin, undecodable bytes, facts that disagree with the check, stage, Candidate or metadata, a run that was not attributable, had problems, was stopped or killed, left pipes open or has no exit, timeoutMs 0 or above the recipe bound, endedAt < startedAt or past deadline, and any identity other than the pinned program, entry, args, input, runtime, profile or bounds.
  5. A stored Verdict is internally consistent. parseRepositoryVerdict accepts only what the judge could have returned: checks in acceptance order, each reference judged at most once and only under its associated check, every associated reference either judged or rejected, rejected entries tagged with their associated check, review checks reserved, and a result that follows from the checks and rejections.
  6. No relabelling. Fixture Verdicts do not parse here; fixture records never qualify here, and repository records never qualify in judge (test "relabel guards…"). judge, Verdict.scope, EvidencePins, PIN_FIELDS and the fixture sources are unchanged.
  7. Envelope identity recomputed here equals evidence.ts's record id (test "the envelope digest…").

Failure semantics

TypeError for malformed pins, expectations, frozen inputs, listings, facts or Verdicts, for more than 1024 gathered entries, and for a manifest or base that is not the pinned one. Every other problem is a rejection, with its reason and associated check, or a check outcome. decodeObservation returns undefined rather than throwing.

Trust scope

  • Established locally: the rules above, over genuine records written to a real Evidence store and deliberately forged or mutated ones; macOS arm64, Node 26.8.1.
  • Not established: a composed collection or a real Slice's judgement (the collector's tests judge real sandboxed runs of test Candidates here), or attribution of hostile Candidate code beyond what the collector records. Authentication is re-derivation over trusted local storage: a writer with store access can forge. Review checks are not implemented.

Composition

  • Depends on: Command journal's canonical JSON, Confined executor's SNAPSHOT_PROTOCOL, Evidence / Assurance's PIN_FIELDS, isStoreRecord and types, and Repository's parseRepositoryIdentity and manifest types, all unchanged.
  • Used by: src/product-gate.ts (re-derivation at integration admission, frozen-input digests, Verdict parsing and digests), src/delivery-records.ts (record parsers, listingDigest, rejudgeRepository in deriveAssessment) and src/repository-collector.ts (parseRepositoryPins, evidencePinsOf, encodeObservation, observationOf, listingDigest and the protocol constants, to write the records). Nothing else in src/ or apps/ imports it.
  • Caller contract: the Repository collector (step 1, increment 3: accepted) runs each check through the Confined executor and stores one sf-repository-observation/1 record per run under evidencePinsOf(pins); its tests judge those records here. The delivery composition (increment 4, not built) is to name each reference's check in the expectation, read the records back with EvidenceStore.get and pass them here.

Changing it safely

  • Run node --test test/repository-assurance.test.ts, then the gate and records tests (they re-derive Verdicts), then npm run check.
  • Any change to a digest formula, pin formula, observation format or result rule invalidates every recorded repository Verdict and integration proof: bump the protocol constant.
  • Keep classifyObservation identical to classify, and keep the executor limits this module repeats in step with confined-executor.ts (both pinned by tests).

Source: docs/agents/repository-assurance.md