@dzhechkov/keysarium 1.7.10 → 1.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dz-manifest.json CHANGED
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "10e8e4f470823f26d20a8cb4b5680dde97fd9654f9545b81de099405bd2dba32"
12
+ "sha256": "0da5eb80b9be5aaadbc333ac83ba3aa9bd466cf548e52ae377576e17315a391c"
13
13
  },
14
14
  {
15
15
  "path": "README.ru.md",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  {
23
23
  "path": "package.json",
24
- "sha256": "fec2ebcb8a572ee95f1ac5b41264933d853a01d96b85a826e208a7bf4b49da1b"
24
+ "sha256": "5e602e8a73b42ba0fc9b449958e25a4f6730b3ec03cb301de4797b394a0eb9ab"
25
25
  },
26
26
  {
27
27
  "path": "src/cli.js",
@@ -373,7 +373,7 @@
373
373
  },
374
374
  {
375
375
  "path": "templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md",
376
- "sha256": "b6e505083c1b8bc92243379b72ff4667b5b91ef65fd9c2ef7407129018b3e56b"
376
+ "sha256": "798c04595c5b0f8c5688334413c744b6d1dda6f9ad9d4fab263c96dcc2c4187e"
377
377
  },
378
378
  {
379
379
  "path": "templates/.claude/skills/feature-adr/modules/04-ddd.md",
@@ -389,11 +389,11 @@
389
389
  },
390
390
  {
391
391
  "path": "templates/.claude/skills/feature-adr/modules/07-code.md",
392
- "sha256": "db79f8d026cc47edd1e5d2f30d0e1455c55c570dd022d549d440985a3e5940a5"
392
+ "sha256": "d5c03890b2b3a34277c73a14d3d7c97741dec1d6c382e87ee4cc6a5a52d938d7"
393
393
  },
394
394
  {
395
395
  "path": "templates/.claude/skills/feature-adr/modules/08-qe.md",
396
- "sha256": "4aa7ef1dea08b8d1995d968a317da5d36a4859ec299637bdc60170cf577c87af"
396
+ "sha256": "759e67695b5761ff5a80e0aa89dccf18d38807063c7d911f3a80fe03bd56b82a"
397
397
  },
398
398
  {
399
399
  "path": "templates/.claude/skills/feature-adr/modules/09-fleet-qe.md",
@@ -487,13 +487,17 @@
487
487
  "path": "templates/.claude/skills/feature-adr/scripts/build-coder-context.mjs",
488
488
  "sha256": "19c3991bfb5e88205c9ff6d4d44037c136a190581bab199d3928196e4616fe15"
489
489
  },
490
+ {
491
+ "path": "templates/.claude/skills/feature-adr/scripts/check-code-targets.mjs",
492
+ "sha256": "bfbad12b54508d5d4c71de640c5e19b3d19317f660e2986d02338ef502f8007e"
493
+ },
490
494
  {
491
495
  "path": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
492
496
  "sha256": "8b93949ce4f671d932c5389050db3a3e2750efcbed69681d69f392ccf4d2a168"
493
497
  },
494
498
  {
495
499
  "path": "templates/.claude/skills/feature-adr/scripts/check-review-convergence.mjs",
496
- "sha256": "09c74f1441d9c42603fbb84402e6826cf96535d209be9802e0e4b2fea01dac7d"
500
+ "sha256": "e903d0eb52a126db98a70d3851d3ed630598d10e843164683d6e48b29c93ff64"
497
501
  },
498
502
  {
499
503
  "path": "templates/.claude/skills/feature-adr/scripts/markdown-masker.mjs",
@@ -1001,5 +1005,5 @@
1001
1005
  }
1002
1006
  ]
1003
1007
  },
1004
- "signature": "wfYnIS4MAw7T3m282RmpXCeIUVVAi+TDfNI4DmRaHJEy3h/gpJdtlv1TMTRz6dVfMA0fOcxZrJKWBXO+JFEcCg=="
1008
+ "signature": "e6DEbQ+EkBR8BvLrXC2dvo75iRBq/pS+F8XlSC6fmgKU3X7zs39Kdtr51+H8edGEeI/eAF6Um0MZvdTiotM9AQ=="
1005
1009
  }
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # @dzhechkov/keysarium
2
2
 
3
- Current package version: `1.7.10`. <!-- dz:version -->
3
+ The bundled Feature ADR projection supports installed-CLI native QE history opt-in with a fixed three-cycle ceiling and explicit repair. Its canonical helper/guidance remain synchronized; compatible core/CLI capabilities are required, and historical bridge behavior remains available.
4
+
5
+
6
+ Current package version: `1.7.12`. <!-- dz:version -->
4
7
 
5
8
  Site: https://aicoding.space · Source: https://github.com/djd1m/dz-harness/tree/main/packages/@dzhechkov/keysarium
6
9
 
@@ -34,6 +37,13 @@ Required missing or incomplete context refuses coding. This helper is separate f
34
37
  > whether a lesson describes a method or a person, and says so: that judgement is the agent's, per
35
38
  > the teach protocol. See `skills/goap-research-ed25519/SKILL.md`.
36
39
 
40
+ Feature-adr structural `implementationVerified:true` has phase-specific meaning: ideation verifies
41
+ corrected ADR, architecture and phase artifacts at the current revision/nonce, without claiming
42
+ production code or implementation tests exist; QE requires actual implementation and relevant tests.
43
+ False or missing structural verification still blocks. Receipt fields, boolean strength, originating
44
+ reviewer ownership, phase/nonce/manifest binding and legacy receipt compatibility remain unchanged.
45
+ The gate checks receipt consistency and freshness; independent reviewers judge the evidence.
46
+
37
47
  ## Growth engine (reverse-engineering-unicorn M5)
38
48
  ### What M5 produces, since 0.2.0 / 1.7.5 / 1.6.0
39
49
 
@@ -70,6 +80,11 @@ After installation, open Claude Code in your project directory and start using s
70
80
 
71
81
  ## What You Get
72
82
 
83
+ MEASURED package inventory from `npm pack --dry-run --json` in this package directory.
84
+ Skills are `templates/.claude/skills/*/SKILL.md`; commands are Markdown files in
85
+ `templates/.claude/commands/`. Rules, shards and agent templates are beneath `templates/.claude/`; the library is
86
+ `templates/lib/` and optional documentation is `templates/docs/`. Counts describe bundled files,
87
+ not execution results.
73
88
  | Component | Count | Description |
74
89
  |-----------|-------|-------------|
75
90
  | **Skills** | 12 | `explore`, `frontend-design`, `goap-research-ed25519`, `presentation-storyteller`, `problem-solver-enhanced`, `reverse-engineering-unicorn`, `knowledge-extractor`, `ai-factory-mapper`, `analyst-manual-full`, `edu-site-generator`, `transcript-site-generator`, `feature-adr` |
@@ -84,9 +99,10 @@ After installation, open Claude Code in your project directory and start using s
84
99
  Everything is installed into your project's `.claude/` directory and works natively with Claude Code.
85
100
 
86
101
  > Counts above are MEASURED from the shipped `templates/` tree — reproducer:
87
- > `npx @dzhechkov/keysarium init --dry-run` (the install plan prints the same numbers, computed at
88
- > runtime) or, on a real install, `npx @dzhechkov/keysarium list`. If a count here disagrees with the
89
- > CLI, trust the CLI — it counts files, this table is hand-written.
102
+ > `npx @dzhechkov/keysarium init --dry-run --with-docs` prints these component counts, including
103
+ > optional documentation. On an installed project, `npx @dzhechkov/keysarium list` reports recursive
104
+ > file totals; its skills row counts skill assets, not skill directories, so its units differ from
105
+ > this table. Compare matching units when checking an installed project.
90
106
 
91
107
  ---
92
108
 
@@ -245,7 +261,9 @@ The toolkit automatically detects the domain from the case description and appli
245
261
  - Visual palette: Blue / Navy / Silver
246
262
 
247
263
  ### Retail / E-commerce
248
- - Latency budget: < 200ms for real-time recommendations
264
+ - ESTIMATED example design target: response latency below 200 ms for real-time recommendations.
265
+ This is a planning constraint for a retail case, not measured Keysarium performance; validate it
266
+ against the proposed system and its workload.
249
267
  - A/B testing as primary validation method
250
268
  - Personalization vs. privacy balance (GDPR / FZ-152)
251
269
  - Seasonality and cold-start handling
@@ -530,3 +548,15 @@ Before fallback, host prepare retains independently written pending receipt find
530
548
  After a reviewed artifact is renamed or removed, convergence keeps its previously host-bound condition scope in the measured manifest; an absent old path has a null digest. Current discovered paths remain measured. Both prepare and evaluate validate the retained snapshot and reject unbound or unsafe scope paths. The original condition and originating reviewer verification remain required; a rename alone does not close a finding.
531
549
 
532
550
  QE also measures the exact `.dz/guard.json` policy configuration when it is in the current Git index or selected review base. This includes newly staged and intent-to-add files; base-tracked deletions retain a null digest. Both declared targets and discovered changes use this rule. Untracked files absent from that base do not qualify through older history. Other `.dz/` runtime files remain excluded, and existing path, symlink, freshness and reviewer-ownership checks still apply.
551
+
552
+ ### Pre-code target admission
553
+
554
+ Step 7 now runs the installed read-only `scripts/check-code-targets.mjs` before literal context,
555
+ code-cache lookup or any coder route, on every entry including auto/force resume. It reuses the
556
+ colocated review manifest authority: strict targets, circular exclusions, discovered source changes,
557
+ path/Git safety and the exact `.dz/guard.json` index-or-selected-base exception are unchanged.
558
+ Missing planned files remain admissible. Workflow requires admitted JSON plus the actual producer's
559
+ terminal `RC_EXIT:0` witness; failure refuses coding/cache access. Admission preserves review state
560
+ and existing `.fa-state/base-ref` (HEAD only when absent); actual coder initialization never resets
561
+ an existing base. Plain uses the same executable helper and mandatory instructions, without an
562
+ automatic dispatcher. Admission observes current bytes; final QE remains the freshness check.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/keysarium",
3
- "version": "1.7.10",
3
+ "version": "1.7.12",
4
4
  "scripts": {},
5
5
  "description": "AI Case Research Toolkit for Claude Code — full 7-phase pipeline with feature-adr, dream cycles, background workers, multi-platform support, reward learning, and Cloud.ru AI Factory mapping (--ai-factory flag)",
6
6
  "main": "src/cli.js",
package/sbom.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "hashes": [
26
26
  {
27
27
  "alg": "SHA-256",
28
- "content": "10e8e4f470823f26d20a8cb4b5680dde97fd9654f9545b81de099405bd2dba32"
28
+ "content": "0da5eb80b9be5aaadbc333ac83ba3aa9bd466cf548e52ae377576e17315a391c"
29
29
  }
30
30
  ]
31
31
  },
@@ -59,7 +59,7 @@
59
59
  },
60
60
  {
61
61
  "name": "dz:canonical-json-sha256-v2",
62
- "value": "fec2ebcb8a572ee95f1ac5b41264933d853a01d96b85a826e208a7bf4b49da1b"
62
+ "value": "5e602e8a73b42ba0fc9b449958e25a4f6730b3ec03cb301de4797b394a0eb9ab"
63
63
  }
64
64
  ]
65
65
  },
@@ -939,7 +939,7 @@
939
939
  "hashes": [
940
940
  {
941
941
  "alg": "SHA-256",
942
- "content": "b6e505083c1b8bc92243379b72ff4667b5b91ef65fd9c2ef7407129018b3e56b"
942
+ "content": "798c04595c5b0f8c5688334413c744b6d1dda6f9ad9d4fab263c96dcc2c4187e"
943
943
  }
944
944
  ]
945
945
  },
@@ -979,7 +979,7 @@
979
979
  "hashes": [
980
980
  {
981
981
  "alg": "SHA-256",
982
- "content": "db79f8d026cc47edd1e5d2f30d0e1455c55c570dd022d549d440985a3e5940a5"
982
+ "content": "d5c03890b2b3a34277c73a14d3d7c97741dec1d6c382e87ee4cc6a5a52d938d7"
983
983
  }
984
984
  ]
985
985
  },
@@ -989,7 +989,7 @@
989
989
  "hashes": [
990
990
  {
991
991
  "alg": "SHA-256",
992
- "content": "4aa7ef1dea08b8d1995d968a317da5d36a4859ec299637bdc60170cf577c87af"
992
+ "content": "759e67695b5761ff5a80e0aa89dccf18d38807063c7d911f3a80fe03bd56b82a"
993
993
  }
994
994
  ]
995
995
  },
@@ -1223,6 +1223,16 @@
1223
1223
  }
1224
1224
  ]
1225
1225
  },
1226
+ {
1227
+ "type": "file",
1228
+ "name": "templates/.claude/skills/feature-adr/scripts/check-code-targets.mjs",
1229
+ "hashes": [
1230
+ {
1231
+ "alg": "SHA-256",
1232
+ "content": "bfbad12b54508d5d4c71de640c5e19b3d19317f660e2986d02338ef502f8007e"
1233
+ }
1234
+ ]
1235
+ },
1226
1236
  {
1227
1237
  "type": "file",
1228
1238
  "name": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
@@ -1239,7 +1249,7 @@
1239
1249
  "hashes": [
1240
1250
  {
1241
1251
  "alg": "SHA-256",
1242
- "content": "09c74f1441d9c42603fbb84402e6826cf96535d209be9802e0e4b2fea01dac7d"
1252
+ "content": "e903d0eb52a126db98a70d3851d3ed630598d10e843164683d6e48b29c93ff64"
1243
1253
  }
1244
1254
  ]
1245
1255
  },
@@ -91,6 +91,15 @@ on every pass. Reviewer verifications have closed fields
91
91
  `{id, revision, classification, evidence, implementationVerified}`. Only the originating independent
92
92
  role can verify its own condition at the current revision; author/lead/scribe assertions cannot.
93
93
 
94
+ `implementationVerified` is phase-specific for both condition verifications and
95
+ `deltaVerification`. In phase `ideation`, structural `implementationVerified:true` means the
96
+ originating independent reviewer verified the corrected ADR, architecture and phase artifacts at
97
+ the current revision/nonce; it does not claim production code or implementation tests exist. In
98
+ phase `qe`, structural `implementationVerified:true` requires independent verification of the
99
+ actual implementation and relevant tests at the current revision/nonce. False or missing structural
100
+ verification remains blocking in both phases. Field names, boolean strength, originating ownership,
101
+ phase/nonce/manifest binding and legacy receipt compatibility are unchanged.
102
+
94
103
  Rework requires the actual author object
95
104
  `{addressed, changedScope, classification, delta, evidence, newRisks}`. Addressed IDs name prior
96
105
  conditions; changedScope includes ALL host-measured changes and affected dependencies. Delta/evidence
@@ -98,9 +107,9 @@ explain the actual fix. Both author and reviewer `newRisks` require explicit ass
98
107
  conditions array, including an explicit none-identified assessment when empty. The reviewer must set
99
108
  `authorAssessmentChecked:true` and supply independent
100
109
  `deltaVerification:{classification,evidence,implementationVerified}`. Structural changes affect
101
- behavior, contracts, safeguards, architecture or acceptance meaning and require affected implementation
102
- and test verification. Wording changes affect expression only; the independent reviewer checks that
103
- classification, so an author's wording label cannot weaken structural verification.
110
+ behavior, contracts, safeguards, architecture or acceptance meaning and require phase-specific
111
+ structural verification as defined above. Wording changes affect expression only; the independent
112
+ reviewer checks that classification, so an author's wording label cannot weaken structural verification.
104
113
 
105
114
  Focused scope is outstanding conditions plus actual changes and affected dependencies. New serious
106
115
  risks always expand that scope. Retain ALL conditions and serious primary/precision findings until
@@ -26,6 +26,40 @@ opus (complex code generation)
26
26
 
27
27
  ### 1. Pre-Implementation Checklist
28
28
 
29
+ ### Current target admission (before any coding)
30
+
31
+ After prerequisite gates complete, and on **every** Step-7 entry (including auto/force resume),
32
+ run the installed `scripts/check-code-targets.mjs` beside this module **before** context assembly,
33
+ code-cache lookup, delegation or in-session edits. Set `TARGET_HELPER` to that installation's
34
+ absolute helper path, `EXECUTION_PROJECT` to the absolute execution repository root, and
35
+ `FEATURE_DIR` to its absolute `features/<slug>` directory:
36
+
37
+ ```bash
38
+ node "$TARGET_HELPER" --repo "$EXECUTION_PROJECT" --feature "$FEATURE_DIR"
39
+ ```
40
+
41
+ Require the actual command exit 0 and exactly one `fa-code-targets-1` JSON document with
42
+ `status: "admitted"`, a SHA-256 `manifestDigest` and empty `reasons`. Any missing/broken helper,
43
+ command failure, refused/unavailable status or malformed/contradictory output stops coding and
44
+ cache access; repair the named cause first. Do not substitute a narrated success or saved admission.
45
+ For a host relay, the shell must capture the producer's exit immediately and append one terminal
46
+ `RC_EXIT:<integer>` line; Workflow requires both admitted JSON and observed `RC_EXIT:0` and refuses
47
+ missing/duplicate/conflicting witnesses. Keep stderr separate from the bounded relay.
48
+
49
+ The helper only calls the colocated review checker's `measureManifest(repo, feature, 'qe')`.
50
+ Its strict `- ` target grammar, circular-root exclusions, discovered changed/untracked paths,
51
+ filesystem/Git safety and exact `.dz/guard.json` index-or-selected-base authority remain unchanged.
52
+ K2 remains an additional legacy Markdown-friendly check; admission does not normalize its bullets
53
+ or backticks. Planned missing files/deletions can have null digests. The admission digest records
54
+ this observation, not QE approval or a replacement cache key. Admission never prepares/evaluates
55
+ review lineage or writes source, receipts or `.fa-state`.
56
+
57
+ The selected baseline is existing `.fa-state/base-ref`, otherwise HEAD. Before actual coding,
58
+ initialize HEAD only if that base file is absent; preserve an existing original selection through
59
+ retries/re-entry. Refuse a failed initialization before edits. Admission does not initialize it.
60
+ This is executable Plain guidance with mandatory orchestration, not an automatic Plain dispatcher.
61
+ An unrelated process can change source after admission; ordinary final QE still checks freshness.
62
+
29
63
  ### Current literal context (plain and delegated coding)
30
64
 
31
65
  Before coding or delegating, run the installed helper beside this module. Set `CONTEXT_HELPER` to
@@ -1,5 +1,22 @@
1
1
  # Step 8: Quality Engineering
2
2
 
3
+ ### Native history through the installed CLI
4
+
5
+ For an explicitly native review, use the actual installed producer before dispatching any formal QE role:
6
+
7
+ ```bash
8
+ dz qe-rounds --source native --action prepare --project "$REPO" --feature-dir "$FEATURE" --reviewers '[{"id":"qe-primary","family":"codex"}]' --json
9
+ ```
10
+
11
+ Bind the actual selected families and use the returned full roster, nonce and revision in the existing QE receipt at `feature/.fa-state/review-convergence-qe-review.json`. An owner-approved family exception must use `owner-exception` and preserve its explicit approval. Call `dz qe-rounds --source native --action evaluate --project "$REPO" --feature-dir "$FEATURE" --json` after actual independent review; missing or incompatible installed core/helper capabilities are NOT ESTABLISHED. No project-local module search, helper override, automatic installation or fake bridge evidence is allowed.
12
+
13
+ Native history fixes the ceiling at three witnessed cycles, including partial and unresolved originating reviews. Multiple roles, fallback, repeated prepare/evaluate and restart share one cycle. Prepare may refresh artifacts before a review originates. After witnessing, a measured code/design change requires explicit `--action begin-repair` with the actual roster before another review; unchanged third-cycle completion may finish, while a fourth opening is refused. The ceiling applies to formal feature review as well as focused roles. Do not evade it by changing family, host, source or nonce. No-verdict attempts are reported separately. Native `--ceiling` is refused.
14
+
15
+ Omit `--action` for read-only native reporting; omitted `--source` retains the historical bridge reader. Locally bound native/bridge collisions refuse either selection with both locations rather than unioning or resetting. A reviewed legacy QE host cannot retrospectively initialize a zero history. Initial admission allows only the documented surviving-evidence matrix; parseable ideation state alone is permitted.
16
+
17
+ The helper runs at most four Git calls before locking (2s and 4 MiB per call), then rechecks bounded authoritative bytes, index, directory candidates and known artifacts in one short directory-lock transaction. Limits are 256 artifact paths, 2 MiB per artifact, 16 MiB aggregate artifacts, 256 KiB host/receipt/head, 2 MiB checkpoints, a separate 4 MiB index metadata fence, 8 MiB journal, 1024 events and 256 directory candidates. The 2s monotonic budget is checked before authoritative writes; lock stale/timeout are explicitly 30s/5s. Returned late I/O refuses, but synchronous I/O cannot be interrupted. This fenced observation does not serialize arbitrary external editors or prove reviewer authenticity or deletion of all writable evidence. Commit failures preserve paths and report possible partial writes; never delete authority or reconstruct zero to resume.
18
+
19
+
3
20
  > Verify the implementation through testing, review, and validation.
4
21
 
5
22
  ## When
@@ -84,6 +101,15 @@ on every pass. Reviewer verifications have closed fields
84
101
  `{id, revision, classification, evidence, implementationVerified}`. Only the originating independent
85
102
  role can verify its own condition at the current revision; author/lead/scribe assertions cannot.
86
103
 
104
+ `implementationVerified` is phase-specific for both condition verifications and
105
+ `deltaVerification`. In phase `ideation`, structural `implementationVerified:true` means the
106
+ originating independent reviewer verified the corrected ADR, architecture and phase artifacts at
107
+ the current revision/nonce; it does not claim production code or implementation tests exist. In
108
+ phase `qe`, structural `implementationVerified:true` requires independent verification of the
109
+ actual implementation and relevant tests at the current revision/nonce. False or missing structural
110
+ verification remains blocking in both phases. Field names, boolean strength, originating ownership,
111
+ phase/nonce/manifest binding and legacy receipt compatibility are unchanged.
112
+
87
113
  Rework requires the actual author object
88
114
  `{addressed, changedScope, classification, delta, evidence, newRisks}`. Addressed IDs name prior
89
115
  conditions; changedScope includes ALL host-measured changes and affected dependencies. Delta/evidence
@@ -91,9 +117,9 @@ explain the actual fix. Both author and reviewer `newRisks` require explicit ass
91
117
  conditions array, including an explicit none-identified assessment when empty. The reviewer must set
92
118
  `authorAssessmentChecked:true` and supply independent
93
119
  `deltaVerification:{classification,evidence,implementationVerified}`. Structural changes affect
94
- behavior, contracts, safeguards, architecture or acceptance meaning and require affected implementation
95
- and test verification. Wording changes affect expression only; the independent reviewer checks that
96
- classification, so an author's wording label cannot weaken structural verification.
120
+ behavior, contracts, safeguards, architecture or acceptance meaning and require phase-specific
121
+ structural verification as defined above. Wording changes affect expression only; the independent
122
+ reviewer checks that classification, so an author's wording label cannot weaken structural verification.
97
123
 
98
124
  Focused scope is outstanding conditions plus actual changes and affected dependencies. New serious
99
125
  risks always expand that scope. Retain ALL conditions and serious primary/precision findings until
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // Read-only current source admission. This is neither a QE receipt nor a cache key.
3
+ import { createHash } from 'node:crypto'
4
+ import { realpathSync } from 'node:fs'
5
+ import { resolve } from 'node:path'
6
+ import { measureManifest } from './check-review-convergence.mjs'
7
+
8
+ const schema = 'fa-code-targets-1'
9
+ let result
10
+ try {
11
+ const args = process.argv.slice(2)
12
+ if (args.length !== 4 || args[0] !== '--repo' || !args[1] || args[2] !== '--feature' || !args[3]) throw Error('invalid-arguments: expected --repo <project> --feature <feature>')
13
+ // Match the checker: real execution root, feature resolved from that root, then
14
+ // its own inside-repo/path/Git checks. Do not reinterpret feature from cwd.
15
+ const repo = realpathSync(args[1])
16
+ const feature = resolve(repo, args[3])
17
+ const manifest = measureManifest(repo, feature, 'qe')
18
+ result = { schema, status: 'admitted', manifestDigest: createHash('sha256').update(JSON.stringify(manifest)).digest('hex'), reasons: [] }
19
+ } catch (error) {
20
+ // execFileSync errors include raw subprocess output in message. Keep that out
21
+ // of the wire; ordinary checker diagnostics are bounded and carry no contents.
22
+ const reason = error && (error.stdout !== undefined || error.stderr !== undefined)
23
+ ? 'git-evidence-unavailable: exit=' + String(error.status ?? error.code ?? 'unknown')
24
+ : String(error?.message || 'measurement-unavailable').slice(0, 4096)
25
+ result = { schema, status: 'refused', manifestDigest: null, reasons: [reason] }
26
+ }
27
+ process.stdout.write(JSON.stringify(result) + '\n')
28
+ process.exitCode = result.status === 'admitted' ? 0 : 1
@@ -1,8 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  // Host checkpoint lineage, not a reviewer-supplied prior array, owns historical conditions.
3
3
  // This checks consistency/freshness; it does not authenticate models or judge their evidence.
4
+ // For structural condition/delta verification, implementationVerified:true is phase-specific:
5
+ // ideation = independently verified corrected ADR, architecture and phase artifacts at the
6
+ // current revision/nonce, without claiming production code or implementation tests exist;
7
+ // qe = independently verified actual implementation and relevant tests at that revision/nonce.
8
+ // False/missing remains blocking in both phases; field names, boolean strength, origin ownership,
9
+ // phase/nonce/manifest binding and legacy receipt compatibility are unchanged.
4
10
  import { createHash, randomUUID } from 'node:crypto'
5
- import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from 'node:fs'
11
+ import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, opendirSync, readSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from 'node:fs'
6
12
  import { dirname, relative, resolve, sep } from 'node:path'
7
13
  import { execFileSync } from 'node:child_process'
8
14
  import { fileURLToPath } from 'node:url'
@@ -47,7 +53,7 @@ function hostProblem(host) {
47
53
  if (host.conditions.some(c => c.scope.some(path => !manifest.some(p => p.path === path)))) return 'host-condition-scope-unbound'
48
54
  return null
49
55
  }
50
- export function evaluateReviewConvergence(host, receipt, currentManifest) {
56
+ export function evaluateReviewConvergence(host, receipt, currentManifest, originOnly = false) {
51
57
  const phase = host?.phase
52
58
  const refuse = reason => result(phase || 'qe', 'not-established', host?.snapshot?.revision || null, [reason], (array(host?.conditions) ? host.conditions : []).map(c => c?.id))
53
59
  const problem = hostProblem(host)
@@ -55,7 +61,7 @@ export function evaluateReviewConvergence(host, receipt, currentManifest) {
55
61
  const owners = host.reviewers.map(r => r.id)
56
62
  if (!same(host.snapshot.manifest, currentManifest) || digest(JSON.stringify(currentManifest)) !== host.snapshot.revision) return refuse('artifact-manifest-changed')
57
63
  if (!dictionary(receipt, ['schema', 'phase', 'nonce', 'revision', 'reviews', 'author']) || receipt.schema !== SCHEMA || receipt.phase !== phase || receipt.nonce !== host.snapshot.nonce || receipt.revision !== host.snapshot.revision) return refuse('receipt-phase-or-revision-invalid')
58
- if (!array(receipt.reviews, 16) || receipt.reviews.length !== owners.length || new Set(receipt.reviews.map(r => r?.reviewer)).size !== owners.length) return refuse('reviewer-set-invalid')
64
+ if (!array(receipt.reviews, 16) || (originOnly ? !receipt.reviews.length || receipt.reviews.length > owners.length : receipt.reviews.length !== owners.length) || new Set(receipt.reviews.map(r => r?.reviewer)).size !== receipt.reviews.length) return refuse('reviewer-set-invalid')
59
65
  const all = new Map(host.conditions.map(c => [c.id, c]))
60
66
  const verified = new Set()
61
67
  const reasons = []
@@ -129,7 +135,7 @@ function writeJson(path, value) {
129
135
  writeFileSync(temporary, JSON.stringify(value, null, 2) + '\n', { flag: 'wx' })
130
136
  renameSync(temporary, path)
131
137
  }
132
- function pathsFor(repo, feature, phase) {
138
+ function pathsFor(repo, feature, phase, native = false) {
133
139
  const prefix = relative(repo, feature).split(sep).join('/')
134
140
  if (!prefix || prefix.startsWith('../')) throw Error('feature-must-be-inside-repo')
135
141
  safePath(repo, prefix, false)
@@ -137,12 +143,13 @@ function pathsFor(repo, feature, phase) {
137
143
  if (phase === 'qe') targets.push('06_implementation_plan.md', '03.5_ideation_report.md')
138
144
  const adr = safePath(repo, prefix + '/03_adr')
139
145
  if (existsSync(adr) && !lstatSync(adr).isDirectory()) throw Error('adr-not-directory')
140
- const decisions = existsSync(adr) ? readdirSync(adr).filter(p => /^[0-9]{3}-.+\.md$/.test(p)).sort() : []
146
+ const decisions = existsSync(adr) ? (native ? nativeNames(adr) : readdirSync(adr)).filter(p => /^[0-9]{3}-.+\.md$/.test(p)).sort() : []
141
147
  if (phase === 'ideation' && !decisions.length) throw Error('adr-missing')
142
148
  const paths = targets.filter(p => existsSync(resolve(feature, p))).concat(decisions.map(p => '03_adr/' + p)).map(p => prefix + '/' + p)
143
149
  for (const required of phase === 'ideation' ? ['01_requirements.md', '05_architecture.md'] : ['01_requirements.md', '06_implementation_plan.md']) if (!paths.includes(prefix + '/' + required)) throw Error('design-input-missing:' + required)
144
150
  if (phase === 'qe') {
145
- const plan = readFileSync(safePath(repo, prefix + '/06_implementation_plan.md', false), 'utf8')
151
+ const planFile = safePath(repo, prefix + '/06_implementation_plan.md', false)
152
+ const plan = native ? new TextDecoder('utf-8', { fatal: true }).decode(nativeBytes(planFile, 2 * 1024 * 1024)) : readFileSync(planFile, 'utf8')
146
153
  const block = plan.split(/^EXPECTED_CODE_TARGETS:\s*$/m)
147
154
  if (block.length !== 2) throw Error('target-block-missing-or-duplicate')
148
155
  const planned = block[1].split('\n').filter(line => line.trim()).map(line => {
@@ -152,19 +159,19 @@ function pathsFor(repo, feature, phase) {
152
159
  })
153
160
  if (!planned.length) throw Error('target-block-empty')
154
161
  const baseFile = resolve(feature, '.fa-state/base-ref')
155
- const base = existsSync(baseFile) ? readFileSync(baseFile, 'utf8').trim() : 'HEAD'
162
+ const base = existsSync(baseFile) ? (native ? nativeBytes(baseFile, 256 * 1024).toString('utf8') : readFileSync(baseFile, 'utf8')).trim() : 'HEAD'
156
163
  if (!/^[A-Za-z0-9_./:-]+$/.test(base) || base.startsWith('-')) throw Error('base-ref-invalid')
157
164
  const guard = '.dz/guard.json'
158
165
  // Exact index membership includes staged additions/intent-to-add. The selected
159
166
  // base proves deletions; descendants and unrelated history confer no authority.
160
167
  const proof = [['ls-files', '--cached', '-z', '--', guard], ['ls-tree', '-r', '--name-only', '-z', base, '--', guard]].map(command =>
161
- execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }).split('\0').includes(guard))
168
+ execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, ...(native ? { timeout: 2000, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } } : {}) }).split('\0').includes(guard))
162
169
  const allowedGuard = p => p === guard && proof.some(Boolean)
163
170
  if (planned.some(p => /^(features|\.dz|\.agentic-qe|roam)\//.test(p) && !allowedGuard(p))) throw Error('circular-review-target')
164
171
  paths.push(...planned)
165
172
  // Additions/deletions are discovered by the host, not the author delta request.
166
173
  for (const command of [['diff', '--no-renames', '--name-only', '-z', base, '--'], ['ls-files', '--others', '--exclude-standard', '-z']]) {
167
- const names = execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }).split('\0').filter(Boolean)
174
+ const names = execFileSync('git', command, { cwd: repo, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, ...(native ? { timeout: 2000, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } } : {}) }).split('\0').filter(Boolean)
168
175
  paths.push(...names.filter(p => allowedGuard(p) || !p.startsWith('features/') && !p.startsWith('.dz/') && !p.startsWith('.agentic-qe/') && !p.startsWith('roam/') && p !== 'architecture/map.json'))
169
176
  }
170
177
  }
@@ -201,7 +208,7 @@ function qeCheckpoint(repo, feature) {
201
208
  function seriousFindings(source) {
202
209
  return (array(source?.gaps) ? source.gaps : []).filter(gap => [gap.sev, gap.severity, gap.priority].some(v => /(?:^|[^A-Z0-9])(BLOCKER|CRITICAL|HIGH|P0|P1)(?:$|[^A-Z0-9])/.test(String(v || '').toUpperCase())))
203
210
  }
204
- function reconcileCheckpoint(host, receipt, measured, repo, feature) {
211
+ function reconcileCheckpoint(host, receipt, measured, repo, feature, knownReport) {
205
212
  if (!measured) return null
206
213
  const stage = measured.checkpoint
207
214
  for (const [base, source, family, missing] of [
@@ -222,7 +229,8 @@ function reconcileCheckpoint(host, receipt, measured, repo, feature) {
222
229
  }
223
230
  if (base === 'qe-precision' && missing) {
224
231
  const file = safePath(repo, relative(repo, feature).split(sep).join('/') + '/08_qe_report.md', false)
225
- const report = readFileSync(file)
232
+ const report = knownReport === undefined ? readFileSync(file) : knownReport
233
+ if (report === null) return null
226
234
  if (digest(report) !== proof.reportDigest || !/## Primary QE pass/.test(report.toString()) || !/## Precision QE pass/.test(report.toString()) || !/Combined Step-8 grade:/.test(report.toString())) return null
227
235
  }
228
236
  }
@@ -322,6 +330,154 @@ export function runGate({ action, repo, feature, phase, reviewers }) {
322
330
  return verdict
323
331
  }
324
332
 
333
+ // Only the CLI supplies this capability. The default standalone runGate remains synchronous.
334
+ export const NATIVE_REVIEW_API_VERSION = 1
335
+ function nativeNames(dir) {
336
+ if (!existsSync(dir)) return []
337
+ const st = lstatSync(dir)
338
+ if (!st.isDirectory() || st.isSymbolicLink()) throw Error('native-directory-unsafe:' + dir)
339
+ const names = [], fd = opendirSync(dir)
340
+ try { let entry; while ((entry = fd.readSync())) { names.push(entry.name); if (names.length > 256) throw Error('native-directory-candidate-limit:' + dir) } }
341
+ finally { fd.closeSync() }
342
+ return names.sort()
343
+ }
344
+ function nativeBytes(file, limit) {
345
+ let st
346
+ try { st = lstatSync(file) } catch (error) { if (error.code === 'ENOENT') return null; throw error }
347
+ if (!st.isFile() || st.isSymbolicLink()) throw Error('native-file-unsafe:' + file)
348
+ if (st.size > limit) throw Error('native-file-limit:' + file)
349
+ const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW)
350
+ try {
351
+ const opened = fstatSync(fd)
352
+ if (!opened.isFile() || opened.ino !== st.ino || opened.dev !== st.dev) throw Error('native-file-changed:' + file)
353
+ const bytes = Buffer.alloc(limit + 1); let n = 0
354
+ while (n <= limit) { const got = readSync(fd, bytes, n, bytes.length - n, null); if (!got) break; n += got }
355
+ if (n > limit) throw Error('native-file-limit:' + file)
356
+ return bytes.subarray(0, n)
357
+ } finally { closeSync(fd) }
358
+ }
359
+ function nativeManifest(repo, paths) {
360
+ if (new Set(paths).size > 256) throw Error('native-artifact-path-limit')
361
+ let total = 0
362
+ return [...new Set(paths)].sort().map(path => {
363
+ const bytes = nativeBytes(safePath(repo, path), 2 * 1024 * 1024)
364
+ total += bytes?.length || 0
365
+ if (total > 16 * 1024 * 1024) throw Error('native-artifact-aggregate-limit')
366
+ return { path, digest: bytes === null ? null : digest(bytes) }
367
+ })
368
+ }
369
+ function nativeReceipt(host, receipt, manifest) {
370
+ // Reuse the unchanged strong aggregate gate with just the originating subset roster.
371
+ // Missing reviewers can prevent closure without invalidating a structurally valid partial origin.
372
+ if (!host || host.phase !== 'qe' || !dictionary(receipt, ['schema', 'phase', 'nonce', 'revision', 'reviews', 'author']) || receipt.schema !== SCHEMA || receipt.phase !== 'qe' || receipt.nonce !== host.snapshot.nonce || receipt.revision !== host.snapshot.revision || !array(receipt.reviews, 16) || !receipt.reviews.length || new Set(receipt.reviews.map(r => r?.reviewer)).size !== receipt.reviews.length) return null
373
+ if (!receipt.reviews.every(r => reviewerEntry(r, host.reviewers))) return null
374
+ const checked = evaluateReviewConvergence(host, receipt, manifest, true)
375
+ if (checked.verdict === 'not-established') return null
376
+ return receipt.reviews.map(r => ({ reviewer: r.reviewer, family: r.family }))
377
+ }
378
+ export function runNativeReviewGate({ action, repo, feature, reviewers, api }) {
379
+ if (!api || api.version !== 1 || typeof api.transact !== 'function') throw Error('native-core-capability-not-established')
380
+ if (!['prepare', 'evaluate', 'begin-repair'].includes(action)) throw Error('native-action-invalid')
381
+ repo = realpathSync(repo); feature = realpathSync(resolve(repo, feature))
382
+ const prefix = relative(repo, feature).split(sep).join('/')
383
+ if (!prefix || prefix.startsWith('../') || api.projectRoot !== repo || api.featurePath !== feature) throw Error('native-feature-binding-invalid')
384
+ safePath(repo, prefix, false)
385
+ const stateDir = safePath(repo, prefix + '/.fa-state')
386
+ const hostFile = safePath(repo, prefix + '/.fa-state/review-convergence-qe-host.json')
387
+ const receiptFile = safePath(repo, prefix + '/.fa-state/review-convergence-qe-review.json')
388
+ const checkpointFile = safePath(repo, prefix + '/.fa-state/checkpoints.jsonl')
389
+ const baseFile = safePath(repo, prefix + '/.fa-state/base-ref')
390
+ // Index bytes fence staging changes; no Git is run by the locked callback.
391
+ let gitDir = resolve(repo, '.git')
392
+ const gitStat = lstatSync(gitDir)
393
+ if (gitStat.isFile()) {
394
+ const match = /^gitdir: ([^\r\n]+)\r?\n?$/.exec(nativeBytes(gitDir, 256 * 1024).toString('utf8'))
395
+ if (!match) throw Error('native-gitdir-invalid')
396
+ gitDir = realpathSync(resolve(repo, match[1]))
397
+ } else if (!gitStat.isDirectory() || gitStat.isSymbolicLink()) throw Error('native-gitdir-invalid')
398
+ const indexFile = resolve(gitDir, 'index')
399
+ const measuredFiles = [hostFile, receiptFile, checkpointFile, baseFile, indexFile,
400
+ safePath(repo, prefix + '/.fa-state/review-convergence-ideation-host.json'),
401
+ safePath(repo, prefix + '/.fa-state/review-convergence-ideation-review.json'),
402
+ safePath(repo, prefix + '/08_qe_report.md'),
403
+ ...(gitStat.isFile() ? [resolve(repo, '.git')] : []), resolve(gitDir, 'HEAD')]
404
+ const limits = measuredFiles.map(f => f === indexFile ? 4 * 1024 * 1024 : f === checkpointFile ? 2 * 1024 * 1024 : 256 * 1024)
405
+ const observed = measuredFiles.map((f, i) => nativeBytes(f, limits[i]))
406
+ const json = (index) => observed[index] === null ? null : JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(observed[index]))
407
+ const host = json(0)
408
+ let receipt = null, receiptProblem = null
409
+ try { receipt = json(1) } catch { receiptProblem = 'reviewer-receipt-malformed' }
410
+ if (observed[0] !== null && (hostProblem(host) || host.phase !== 'qe')) throw Error(hostProblem(host) || 'native-host-phase-invalid')
411
+ if (observed[3] !== null && !/^[A-Za-z0-9_./:-]+$/.test(observed[3].toString('utf8').trim())) throw Error('native-base-ref-invalid')
412
+ const ideationHost = json(5), ideationReceipt = json(6)
413
+ if (observed[5] !== null && (hostProblem(ideationHost) || ideationHost.phase !== 'ideation')) throw Error('native-ideation-state-invalid')
414
+ if (observed[6] !== null) {
415
+ if (!ideationHost || !dictionary(ideationReceipt, ['schema', 'phase', 'nonce', 'revision', 'reviews', 'author']) || ideationReceipt.schema !== SCHEMA || ideationReceipt.phase !== 'ideation' || !text(ideationReceipt.nonce) || !hash(ideationReceipt.revision) || !array(ideationReceipt.reviews, 16) || !ideationReceipt.reviews.length || new Set(ideationReceipt.reviews.map(r => r?.reviewer)).size !== ideationReceipt.reviews.length || !ideationReceipt.reviews.every(r => reviewerEntry(r, ideationHost.reviewers) && (!Object.hasOwn(r, 'checkpointVerification') || checkpointProof(r.checkpointVerification)))) throw Error('native-ideation-receipt-invalid')
416
+ const owners = ideationHost.reviewers.map(r => r.id), author = ideationReceipt.author
417
+ if (author !== null && (!dictionary(author, ['addressed', 'changedScope', 'classification', 'delta', 'evidence', 'newRisks']) || !strings(author.addressed) || !strings(author.changedScope, true) || !kinds.includes(author.classification) || !text(author.delta) || !text(author.evidence) || !risks(author.newRisks, owners))) throw Error('native-ideation-author-invalid')
418
+ for (const r of ideationReceipt.reviews) {
419
+ if (author ? !dictionary(r.deltaVerification, ['classification', 'evidence', 'implementationVerified']) || !kinds.includes(r.deltaVerification.classification) || !text(r.deltaVerification.evidence) || typeof r.deltaVerification.implementationVerified !== 'boolean' : r.deltaVerification !== null) throw Error('native-ideation-delta-invalid')
420
+ if (!r.verifications.every(v => dictionary(v, ['id', 'revision', 'classification', 'evidence', 'implementationVerified']) && text(v.id) && hash(v.revision) && kinds.includes(v.classification) && text(v.evidence) && typeof v.implementationVerified === 'boolean') || new Set(r.verifications.map(v => v.id)).size !== r.verifications.length) throw Error('native-ideation-verification-invalid')
421
+ }
422
+ }
423
+ let checkpoint = null
424
+ if (observed[2] !== null) {
425
+ if (observed[2].length && observed[2][observed[2].length - 1] !== 10) throw Error('native-checkpoint-candidate-torn')
426
+ for (const line of new TextDecoder('utf-8', { fatal: true }).decode(observed[2]).split('\n').filter(Boolean)) {
427
+ const entry = JSON.parse(line)
428
+ if (!entry || typeof entry !== 'object' || typeof entry.stage !== 'string' || !entry.stage || typeof entry.inputHash !== 'string' || !entry.inputHash || !Object.hasOwn(entry, 'result') || entry.result === null) throw Error('native-checkpoint-candidate-invalid')
429
+ if (entry.stage === 'qe') checkpoint = { checkpointDigest: digest(JSON.stringify(entry.result)), checkpoint: entry.result }
430
+ }
431
+ }
432
+ const directoryFence = () => ({ adr: nativeNames(safePath(repo, prefix + '/03_adr')), bridge: nativeNames(safePath(repo, prefix + '/.fa-state/qe-bridge')), fragments: nativeNames(stateDir).filter(n => /^native-qe-(history\.jsonl|head\.json)\..*\.tmp$/.test(n)) })
433
+ const membership = directoryFence()
434
+ const paths = pathsFor(repo, feature, 'qe', true).concat((host?.conditions || []).flatMap(c => c.scope))
435
+ const manifest = nativeManifest(repo, paths)
436
+ if (action !== 'evaluate' && (!array(reviewers, 16) || !reviewers.length || !reviewers.every(r => dictionary(r, ['id', 'family']) && text(r.id) && ['codex', 'claude', 'owner-exception'].includes(r.family)) || new Set(reviewers.map(r => r.id)).size !== reviewers.length)) throw Error('reviewers-invalid')
437
+ const observationDigest = digest(JSON.stringify({ files: observed.slice(1).map(b => b === null ? null : digest(b)), membership, manifest }))
438
+ const checkpointClosure = host && receipt && checkpoint ? reconcileCheckpoint(host, receipt, checkpoint, repo, feature, observed[7]) : null
439
+ return api.transact(action, active => {
440
+ if (realpathSync(repo) !== repo || realpathSync(feature) !== feature) throw Error('native-project-feature-observation-changed')
441
+ const currentGit = lstatSync(resolve(repo, '.git'))
442
+ if (currentGit.ino !== gitStat.ino || currentGit.dev !== gitStat.dev) throw Error('native-gitdir-observation-changed')
443
+ // Only descriptor/known-path work here; no discovery through runGate, Git, model or network.
444
+ measuredFiles.forEach((file, i) => { const bytes = nativeBytes(file, limits[i]); if ((bytes === null) !== (observed[i] === null) || bytes !== null && !bytes.equals(observed[i])) throw Error('native-observation-changed:' + file) })
445
+ const currentMembership = directoryFence()
446
+ if (currentMembership.bridge.some(n => /^(signoff-|failed-).*\.json$/.test(n))) throw Error('native-bridge-source-collision:' + stateDir + '/native-qe-history.jsonl:' + stateDir + '/qe-bridge')
447
+ if (!same(membership, currentMembership)) throw Error('native-candidate-membership-changed')
448
+ if (!same(manifest, nativeManifest(repo, paths))) throw Error('native-artifact-observation-changed')
449
+ if (membership.bridge.some(n => /^(signoff-|failed-).*\.json$/.test(n))) throw Error('native-bridge-source-collision:' + stateDir + '/native-qe-history.jsonl:' + stateDir + '/qe-bridge')
450
+ if (membership.fragments.length) throw Error('native-temporary-fragment')
451
+ if (!active && (observed[1] !== null || host && (host.reviewSeen || host.rework || host.conditions.length) || checkpoint)) throw Error('native-surviving-review-evidence-refuses-admission')
452
+ if (active && (!host || !same(host.snapshot, active.snapshot))) throw Error('native-host-admitted-snapshot-mismatch')
453
+ const revision = digest(JSON.stringify(manifest))
454
+ const priorOrigins = nativeReceipt(host, receipt, host?.snapshot.manifest)
455
+ if (action === 'prepare' && host && host.snapshot.revision !== revision && (active?.witnessed || priorOrigins)) throw Error('native-repair-required')
456
+ if (action === 'evaluate') {
457
+ if (!active || !host) throw Error('native-prepare-required')
458
+ if (!same(manifest, host.snapshot.manifest)) throw Error('native-repair-required')
459
+ const origins = nativeReceipt(host, receipt, manifest)
460
+ let verdict = receipt === null ? result('qe', 'not-established', revision, [receiptProblem || 'reviewer-receipt-missing']) : evaluateReviewConvergence(host, receipt, manifest)
461
+ // Closure is deliberately stronger than originating partial/unresolved evidence.
462
+ if (verdict.verdict === 'closed' && checkpoint && !checkpointClosure) verdict = result('qe', 'not-established', revision, ['historical-checkpoint-own-verification-missing'])
463
+ const preserved = new Map(host.conditions.map(c => [c.id, c]))
464
+ if (origins) {
465
+ for (const r of receipt.reviews) for (const c of r.conditions.concat(r.newRisks.conditions)) preserved.set(c.id, c)
466
+ if (receipt.author) for (const c of receipt.author.newRisks.conditions) if (c.scope.every(path => manifest.some(p => p.path === path))) preserved.set(c.id, c)
467
+ }
468
+ if (verdict.verdict === 'closed' && checkpointClosure) verdict = { ...verdict, checkpointClosure }
469
+ const next = { ...host, conditions: [...preserved.values()], reviewSeen: host.reviewSeen || Boolean(origins), changedPaths: verdict.verdict === 'closed' ? [] : host.changedPaths }
470
+ return { host: next, snapshot: host.snapshot, origins: origins || [], receiptDigest: observed[1] === null ? null : digest(observed[1]), reason: origins ? null : verdict.reasons[0] || 'native-no-valid-origin', observationDigest, result: verdict, admission: false }
471
+ }
472
+ const bound = bindReviewers(host, reviewers, checkpoint)
473
+ const snapshot = active?.snapshot.revision === revision ? active.snapshot : host?.snapshot.revision === revision ? host.snapshot : { nonce: randomUUID(), revision, manifest }
474
+ const old = new Map((host?.snapshot.manifest || []).map(p => [p.path, p.digest])), current = new Map(manifest.map(p => [p.path, p.digest]))
475
+ const changedPaths = host && host.snapshot.revision !== revision ? [...new Set([...old.keys(), ...current.keys()])].filter(p => old.get(p) !== current.get(p)).sort() : host?.changedPaths || []
476
+ const next = { schema: SCHEMA, phase: 'qe', reviewers: bound, snapshot, conditions: host?.conditions || [], reviewSeen: host?.reviewSeen || false, rework: Boolean(host && (host.rework || host.reviewSeen && host.snapshot.revision !== revision)), changedPaths }
477
+ return { host: next, snapshot, origins: [], receiptDigest: null, reason: null, observationDigest, result: { ...result('qe', 'prepared', revision, []), nonce: snapshot.nonce, manifest, reviewers: bound, conditions: next.conditions, checkpointDigest: checkpoint?.checkpointDigest || null }, admission: !active }
478
+ })
479
+ }
480
+
325
481
  if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
326
482
  let phase = 'qe'
327
483
  try {