@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 +11 -7
- package/README.md +35 -5
- package/package.json +1 -1
- package/sbom.json +16 -6
- package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +12 -3
- package/templates/.claude/skills/feature-adr/modules/07-code.md +34 -0
- package/templates/.claude/skills/feature-adr/modules/08-qe.md +29 -3
- package/templates/.claude/skills/feature-adr/scripts/check-code-targets.mjs +28 -0
- package/templates/.claude/skills/feature-adr/scripts/check-review-convergence.mjs +167 -11
package/.dz-manifest.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
392
|
+
"sha256": "d5c03890b2b3a34277c73a14d3d7c97741dec1d6c382e87ee4cc6a5a52d938d7"
|
|
393
393
|
},
|
|
394
394
|
{
|
|
395
395
|
"path": "templates/.claude/skills/feature-adr/modules/08-qe.md",
|
|
396
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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
|
-
|
|
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`
|
|
88
|
-
>
|
|
89
|
-
>
|
|
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
|
-
-
|
|
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.
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
|
102
|
-
|
|
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
|
|
95
|
-
|
|
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 !==
|
|
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
|
|
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 {
|