@dzhechkov/keysarium 1.7.11 → 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": "a81456ef7ae79788d7859aab9a2238c541bb61a41b9d289a27ae6992119538db"
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": "844c32f67fc8ed8099a388aa53f597a100371c6a693d4b99730a5808593a4843"
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",
@@ -393,7 +393,7 @@
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",
@@ -497,7 +497,7 @@
497
497
  },
498
498
  {
499
499
  "path": "templates/.claude/skills/feature-adr/scripts/check-review-convergence.mjs",
500
- "sha256": "09c74f1441d9c42603fbb84402e6826cf96535d209be9802e0e4b2fea01dac7d"
500
+ "sha256": "e903d0eb52a126db98a70d3851d3ed630598d10e843164683d6e48b29c93ff64"
501
501
  },
502
502
  {
503
503
  "path": "templates/.claude/skills/feature-adr/scripts/markdown-masker.mjs",
@@ -1005,5 +1005,5 @@
1005
1005
  }
1006
1006
  ]
1007
1007
  },
1008
- "signature": "p0J0fnCC54JAc14pg5UuIgUGtG8u7YLxPlBS6YEXl4qUf5yCqWGZ4g6Bs2Tz8XSfIg1uHkBkAAczkeGaKeBeBw=="
1008
+ "signature": "e6DEbQ+EkBR8BvLrXC2dvo75iRBq/pS+F8XlSC6fmgKU3X7zs39Kdtr51+H8edGEeI/eAF6Um0MZvdTiotM9AQ=="
1009
1009
  }
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # @dzhechkov/keysarium
2
2
 
3
- Current package version: `1.7.11`. <!-- 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/keysarium",
3
- "version": "1.7.11",
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": "a81456ef7ae79788d7859aab9a2238c541bb61a41b9d289a27ae6992119538db"
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": "844c32f67fc8ed8099a388aa53f597a100371c6a693d4b99730a5808593a4843"
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
  },
@@ -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
  },
@@ -1249,7 +1249,7 @@
1249
1249
  "hashes": [
1250
1250
  {
1251
1251
  "alg": "SHA-256",
1252
- "content": "09c74f1441d9c42603fbb84402e6826cf96535d209be9802e0e4b2fea01dac7d"
1252
+ "content": "e903d0eb52a126db98a70d3851d3ed630598d10e843164683d6e48b29c93ff64"
1253
1253
  }
1254
1254
  ]
1255
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
@@ -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
@@ -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 {