A record from the Factory repository, docs/repository-seam-receipt.json, shown as committed except that its local paths appear as placeholders in angle brackets, such as <local evidence archive>, standing for files that are not published; a placeholder that stands in for a key keeps the file's name, as in <local evidence file: report.md>. Every hash, date and count is as recorded. The build review says what the Factory's receipts support.
{
"recordedAt": "2026-09-29T11:12:40+00:00",
"base": "120b767",
"branch": "factory/delivery",
"disposition": "accepted-local",
"scope": "Step 1, increment 3b: an optional, additive pre-move guard on Repository.invokeOnce and the withheld invocation value; no identity, protocol, stored-record, Git-surface or unguarded-behaviour change",
"describes": "src/repository.ts from this change on; repository-nlink-receipt.json keeps its own hashes, which are now of superseded versions of src/repository.ts and test/repository-integration.test.ts",
"specification": "<local evidence archive> section 5 I8-I9 and build plan 3b",
"change": "invokeOnce(prepared, guard?) keeps every existing step. Only the call that holds the dispatch claim awaits guard({intent, dispatch}) once, after the claim and every other asynchronous step and immediately before moveRef, whose ownership check still runs before Git is spawned. Only exactly {proceed: true} lets the update start. A refusal {proceed: false, reason} (1-256 printable ASCII), a throw or rejection, any other answer, or no answer within gitTimeoutMs (an allowance also counts as late when the monotonic clock, read after the answer is validated, shows gitTimeoutMs or more since the guard was called, because synchronous caller code cannot be interrupted and holds back the timer) starts no update and returns {invocation: \"withheld\", dispatched: false, dispatch: <claim D>, receipt: null, target: <read before the claim>, problem: \"withheld before submission: <reason>\"} and publishes integration-withheld. The claim is kept, so the occurrence is consumed: inspection, replays, other handles and processes report it unknown and start nothing. A guard that is not a function is invalid-input before anything. Without a guard the behaviour is unchanged.",
"interface": {
"invokeOnce": "invokeOnce(prepared: PreparedIntegration, guard?: IntegrationGuard): Promise<IntegrationObservation>",
"IntegrationClaim": "{intent: payload digest, dispatch: claim D}, frozen",
"IntegrationGuardAnswer": "{proceed: true} | {proceed: false, reason: string}",
"IntegrationGuard": "(claim: IntegrationClaim) => IntegrationGuardAnswer | PromiseLike<IntegrationGuardAnswer>",
"IntegrationObservation.invocation": "\"not-dispatched\" | \"confirmed\" | \"unknown\" | \"withheld\"",
"diagnosticEvent": "integration-withheld"
},
"tests": {
"file": "test/repository-integration.test.ts",
"existingTestsChanged": "none; the import line gained three type imports and six tests were appended",
"newRows": [
"guard allows: consulted once by the claim holder after the claim (claim ref names D, target still B, no receipt, inspection unknown, only integration-claimed published) and immediately before the update; confirmed with the same records and events as unguarded; a replay consults no guard",
"guard refuses: withheld with its claim D (parent I), target B, no receipt, never submitted; inspection, guarded and unguarded replays, a fresh handle and fresh inspect/invoke processes report unknown and write nothing; a later occurrence integrates and the withheld one stays unknown",
"guard throws, rejects, throws a non-error or an undescribable value, a thenable whose then throws, 17 malformed answers (including accessor, class instance, proxy, extra fields, reason bounds and characters), the exact 256-character boundary, and no answer within gitTimeoutMs (a late allowance ignored): withheld, nothing started",
"a guard that holds the thread past the time bound and then allows (synchronous, async, thenable, and an answer whose reading blocks) is withheld with the late reason; no move, no receipt, and a replay reads unknown without consulting the guard (added for Astra round 1)",
"consulted only by the claim holder: never for a non-function guard, a tampered or unretained intent, a stale base or a replay; once among four concurrent calls, one of which dispatches",
"after an allowing guard a root replaced during the guard is refused unavailable before Git starts; neither root moved and no receipt"
],
"focusedRepositoryTests": {
"tests": 56,
"pass": 56,
"fail": 0,
"cancelled": 0,
"skipped": 0,
"todo": 0
}
},
"mutations": "Eight source mutations, each failing the guard rows, the source restored byte for byte after each. Before round 1 (mutations/summary.txt): guard ignored (5 rows fail), guard before the claim (5), a throw escaping (1), a truthy allowance (1), no time bound (1, times out), withheld reported as not-dispatched (2), guard consulted on replay (3). After round 1 (r1-fix/): the new row fails on the unfixed source (test-first.log) and on a clock read before answer validation (M8-clock-before-validation.log).",
"review": {
"model": "gpt-6-astra",
"effort": "high",
"sandbox": "read-only; it cannot create temporary directories, so it checked the archived host test logs against the file hashes they record",
"rounds": [
{
"report": "<local evidence archive>",
"verdict": "FAIL",
"findings": "a guard that held the thread past the deadline and then allowed was accepted, because the timer could not fire first; fixed test-first with a monotonic clock check after answer validation and a new test row"
},
{
"report": "<local evidence archive>",
"verdict": "PASS",
"findings": "none"
}
]
},
"verification": {
"strictTypecheck": "passed",
"focusedRepositoryTests": 56,
"newTests": 6,
"fullTests": 887,
"failures": 0,
"cancelled": 0,
"skipped": 0,
"checkExit": 0,
"checkLog": "<local evidence archive>"
},
"limits": [
"The guard narrows but does not close the window: a change of authority after it answers and before Git renames the ref is not seen; the residual race spans the update-ref spawn",
"The guard is trusted caller code and is not authenticated; one that never answers is abandoned after gitTimeoutMs but keeps running",
"Synchronous caller code cannot be interrupted: a guard that holds the thread delays the call beyond gitTimeoutMs; its late allowance is refused by the clock check, but the delay itself is not bounded",
"withheld is known only to the call that returned it; the retained claim without a receipt reads as unknown everywhere else, and a crash before the caller records the withheld result leaves unknown",
"Nothing in src/ passes a guard yet; delivery composition (increments 4-5) is not built",
"Trusted local repository storage only, as in the earlier repository receipts"
],
"evidenceSha256": {
"src/repository.ts": "d00063bdf4faa28d6453f91ca87efb4c4a758d404657138028f91c65be2d881d",
"test/repository-integration.test.ts": "88749d7c9ea684ef9972b0128b176496ba653bf3f0605f348b1947468180f341",
"docs/agents/repository.md": "68cd085315447306d439bd5e1f5401034cf2278382289b6acc404a1e79995804",
"docs/guide/repository.md": "02cacb527482ea06a6fa49e20d820a59694fb2cf9060108a78bdf1d40db162c4",
"docs/local-development.md": "ae5138916dd0f650273e244f6ccb730fdd4eb9fa7032367b4af106bebdd3cd1d",
"docs/build-review.md": "021e2e2e2e19501ed858a7024646d4e7d6381ee3dcf67b8b3ffff90a8e1ceb88",
"docs/agents/factory-model.md": "32b11619c8f910ff46f797540e540d2c3da4666369a4b8d1865c56e9869c7679",
"docs/agents/product-gate.md": "c15193c0b3fda217232e19ade70ff52ae238a389a73e5882ca98f85fdaca6e13",
"docs/guide/index.md": "86dbd638089b3ee49c78c0056321a89203f2c42b6316663a9a59e9ae831ff588",
"handover.md": "76f7d04c920a0c3ab9ffb83026840ffb5aaaf379f432064501153e89e57bd85a",
"<local evidence archive: step1-core-spec.md>": "2134eb0ef0d3bfa9f0f5b7a026f6a24f2448bc3684a47bbb1a947048aa26cf0b",
"<local evidence archive: tsc-final.log>": "615ef81717fb84b2ce2f41550f52e4c1afb8c2659845fe9451220b5cf1773504",
"<local evidence archive: focused-final.log>": "d5f950a86b696a9c5a6c53202a6ec17ca99d0eae4ca4952ed16ac67bdf72a2cb",
"<local evidence archive: check-final.log>": "255649d725b0dd1665c53f54d97722d672597ffc3de303a549b1fc40644eb7e8",
"<local evidence archive: run.py>": "abb972e8a9f38a9ec77b99a656dd446f89797a5cff920b42e173e6ced8775cb4",
"<local evidence archive: summary.txt>": "c44b082e96cfa159a32b08dca3171fec25d581dee7ab37a2b7eecfa6fcf54a5e",
"<local evidence archive: M1-guard-ignored.log>": "6652207773550ffdc702a0aa95d414cf0fd10e91b7db7c507f1d99627f976a2d",
"<local evidence archive: M2-guard-before-claim.log>": "f477a336b66cff579165fa8c393da33392569eac7ef655a0f35800780e811fe1",
"<local evidence archive: M3-throw-escapes.log>": "589a22f19e79d9d864df831b1bffc2c5859415f46452e05d22cf0f5d7fb726b9",
"<local evidence archive: M4-truthy-allowance.log>": "da63fcb29eb2f8030e372a8fa618829dbd7671aba5c26646ed6567cdc1a3d374",
"<local evidence archive: M5-no-time-bound.log>": "155e5c676a0091d2211bf6de797958498a6aff0d9d8e01543ba351cb905cb10f",
"<local evidence archive: M6-withheld-as-not-dispatched.log>": "f1fdbb7677d2c895e0e10484166a43af74fd925063c1d3b4a60ae2d794e8ad69",
"<local evidence archive: M7-guard-on-replay.log>": "8a8881997ede3b19ceaa0715d24f77f5a0ad887954c2a8d83620c74eb7b6e2fd",
"<local evidence archive: test-first.log>": "d7786871834e600fdfd870aed42c399f7ae68b7afcc6f67a7bd05da2c3593f3d",
"<local evidence archive: M8-clock-before-validation.log>": "9159f3f17a1d07bc0ccf59b86cc3b8fe0d404326a1ceabb77656bf5402eb2eec",
"<local evidence archive: astra-inc3b-tests-r1.log>": "059bf59a6b6b7ae27aebf53bece426547fd191bc708b6d3171f4a24b8899b1d5",
"<local evidence archive: astra-inc3b-r1.md>": "ada9a28b5b1188b78830a61ac5ec0490c01e396899c14576a7d8620bf025d1fb",
"<local evidence archive: astra-inc3b-tests.log>": "f70260c46599677ce031ce205f04e1e9863fa493be449d91b40c5371a2fe7631",
"<local evidence archive: astra-inc3b-r2.md>": "6585980733f296566eca1913e975e42abb1f596c600bd184a976b5a4b2aa8b16"
}
}
