@davesheffer/hunch 1.7.0 → 1.7.1

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.
Files changed (63) hide show
  1. package/README.md +214 -0
  2. package/bench/constitution-exp03-v1.json +70 -0
  3. package/dist/cli/index.js +1203 -24
  4. package/dist/constitution/adapters.js +487 -0
  5. package/dist/constitution/behaviorAttestationBinding.js +17 -0
  6. package/dist/constitution/behaviorEvaluator.js +220 -0
  7. package/dist/constitution/behaviorProof.js +205 -0
  8. package/dist/constitution/behaviorWorkspace.js +124 -0
  9. package/dist/constitution/bootstrap.js +133 -0
  10. package/dist/constitution/canonical.js +51 -0
  11. package/dist/constitution/card.js +133 -0
  12. package/dist/constitution/compiler.js +176 -0
  13. package/dist/constitution/composition.js +101 -0
  14. package/dist/constitution/corpus.js +58 -0
  15. package/dist/constitution/delta.js +154 -0
  16. package/dist/constitution/disposition.js +141 -0
  17. package/dist/constitution/evaluator.js +435 -0
  18. package/dist/constitution/experiment.js +948 -0
  19. package/dist/constitution/experimentRunner.js +344 -0
  20. package/dist/constitution/g2.js +291 -0
  21. package/dist/constitution/g2BehaviorAttestation.js +209 -0
  22. package/dist/constitution/g2BehaviorCandidates.js +703 -0
  23. package/dist/constitution/g2BehaviorDependencies.js +379 -0
  24. package/dist/constitution/g2BehaviorMaterialization.js +171 -0
  25. package/dist/constitution/g2BehaviorPolicyMaterializer.js +241 -0
  26. package/dist/constitution/g2CandidateAttestation.js +179 -0
  27. package/dist/constitution/g2Candidates.js +195 -0
  28. package/dist/constitution/g2Drills.js +122 -0
  29. package/dist/constitution/g3.js +511 -0
  30. package/dist/constitution/g3Conformance.js +115 -0
  31. package/dist/constitution/lifecycle.js +189 -0
  32. package/dist/constitution/mutation.js +262 -0
  33. package/dist/constitution/nodeTestEvidence.js +47 -0
  34. package/dist/constitution/plan.js +172 -0
  35. package/dist/constitution/policyRuntime.js +8 -0
  36. package/dist/constitution/proof.js +166 -0
  37. package/dist/constitution/replay.js +361 -0
  38. package/dist/constitution/replayCache.js +89 -0
  39. package/dist/constitution/replayWorker.js +34 -0
  40. package/dist/constitution/repository.js +533 -0
  41. package/dist/constitution/schema.js +545 -0
  42. package/dist/constitution/scorecard.js +106 -0
  43. package/dist/constitution/service.js +1149 -0
  44. package/dist/constitution/shadow.js +235 -0
  45. package/dist/constitution/sourceMutation.js +316 -0
  46. package/dist/constitution/structural.js +601 -0
  47. package/dist/core/autoreview.js +27 -3
  48. package/dist/core/dupdetect.js +10 -3
  49. package/dist/core/events.js +61 -0
  50. package/dist/core/externalImports.js +24 -0
  51. package/dist/core/hookpolicy.js +3 -0
  52. package/dist/core/relativeImports.js +33 -0
  53. package/dist/core/stats.js +115 -0
  54. package/dist/extractors/git.js +81 -0
  55. package/dist/extractors/indexer.js +39 -38
  56. package/dist/extractors/nativeTreeSitter.js +108 -0
  57. package/dist/extractors/parse.js +5 -15
  58. package/dist/integrations/claudemd.js +8 -1
  59. package/dist/integrations/gitignore.js +8 -0
  60. package/dist/integrations/providers.js +32 -10
  61. package/dist/integrations/sync.js +16 -1
  62. package/dist/mcp/server.js +284 -0
  63. package/package.json +5 -1
@@ -0,0 +1,1149 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import { headSha } from "../extractors/git.js";
3
+ import { canonicalHash, canonicalJson } from "./canonical.js";
4
+ import { shortHash } from "../core/ids.js";
5
+ import { compileDecisionPolicy } from "./compiler.js";
6
+ import { evaluatePolicy, policyBlocks, policyIsActive } from "./evaluator.js";
7
+ import { approvePolicy, blockingProofError, demotePolicy, linkPolicyException, proposeProvedPolicy } from "./lifecycle.js";
8
+ import { provePolicy } from "./proof.js";
9
+ import { PolicyRepository } from "./repository.js";
10
+ import { bootstrapPolicies } from "./bootstrap.js";
11
+ import { bootstrapStructuralPolicies, inspectStructuralDecision } from "./structural.js";
12
+ import { createProofPlan } from "./plan.js";
13
+ import { ingestLocalEvidence } from "./adapters.js";
14
+ import { buildProofCard } from "./card.js";
15
+ import { compileProofCorpus } from "./corpus.js";
16
+ import { compileHistoryDisposition, currentHistoryDispositions } from "./disposition.js";
17
+ import { compositionDescendants, policyProofHash } from "./composition.js";
18
+ import { compileShadowDisposition, compileShadowEvaluation, currentShadowDispositions, scoreShadowPrecision, } from "./shadow.js";
19
+ import { assessHistoryDispositions } from "./disposition.js";
20
+ import { evaluatorForPolicy, mutationEngineForPolicy } from "./policyRuntime.js";
21
+ import { G2_RUNBOOK_CATEGORIES, G2EvidenceRepository, compileG2Plan, compileRunbookRehearsal, currentRunbookRehearsals, runbookContentHash, scoreG2Readiness, } from "./g2.js";
22
+ import { buildG2CandidateReview } from "./g2Candidates.js";
23
+ import { G2CandidateAttestationRepository, compileG2CandidateAttestation, } from "./g2CandidateAttestation.js";
24
+ import { buildG2BehaviorCandidateReview, replayG2BehaviorCandidate, } from "./g2BehaviorCandidates.js";
25
+ import { provisionG2BehaviorDependencySnapshots, } from "./g2BehaviorDependencies.js";
26
+ import { G2BehaviorAttestationRepository, compileG2BehaviorAttestation, } from "./g2BehaviorAttestation.js";
27
+ import { assessG2BehaviorMaterialization, } from "./g2BehaviorMaterialization.js";
28
+ import { materializeSelectedG2BehaviorPolicies, } from "./g2BehaviorPolicyMaterializer.js";
29
+ import { executableBehaviorAttestationError } from "./behaviorAttestationBinding.js";
30
+ import { evaluateExecutableBehaviorPolicy } from "./behaviorEvaluator.js";
31
+ import { executeG2OperationalDrill } from "./g2Drills.js";
32
+ import { G3_REQUIRED_EXPERIMENTS, G3EvidenceRepository, compileExperimentPreregistration, compileG3Plan, compileProofReviewMeasurement, scoreG3Readiness, } from "./g3.js";
33
+ import { executeG3AdapterConformance, g3ConformanceSourceHash } from "./g3Conformance.js";
34
+ import { ExperimentRepository, assignmentTreatment, buildExperimentReport, compileExperimentCaseBank, compileExperimentFollowup, compileExperimentOutcome, compileExperimentReviewStart, compileExperimentRun, compileExperimentStop, currentExperimentOutcomes, normalizedEditDistance, } from "./experiment.js";
35
+ import { executeExp01Assignment } from "./experimentRunner.js";
36
+ function relationSummary(policy) {
37
+ return {
38
+ id: policy.id,
39
+ statement: policy.statement,
40
+ state: policy.state,
41
+ severity: policy.severity,
42
+ data_class: policy.data_class,
43
+ scope: policy.scope,
44
+ exception_of: policy.exception_of,
45
+ };
46
+ }
47
+ function sameStringSet(left, right) {
48
+ const leftSorted = [...left].sort();
49
+ const rightSorted = [...right].sort();
50
+ return leftSorted.length === rightSorted.length
51
+ && leftSorted.every((value, index) => value === rightSorted[index]);
52
+ }
53
+ function pathFallsWithin(path, broader) {
54
+ if (path === broader)
55
+ return true;
56
+ if (!broader.endsWith("/**"))
57
+ return false;
58
+ const prefix = broader.slice(0, -3);
59
+ return path.startsWith(`${prefix}/`);
60
+ }
61
+ /** Conservative, syntactic containment for advisory consolidation only. A
62
+ * suggestion that uses another glob shape stays non-reviewable rather than
63
+ * guessing glob semantics. */
64
+ function scopeFallsWithin(scope, suggestion) {
65
+ if (!sameStringSet(scope.repos, suggestion.repos))
66
+ return false;
67
+ if (!scope.components.every((component) => suggestion.components.includes(component)))
68
+ return false;
69
+ return scope.paths.length > 0
70
+ && suggestion.paths.length > 0
71
+ && scope.paths.every((path) => suggestion.paths.some((broader) => pathFallsWithin(path, broader)));
72
+ }
73
+ export function shadowCommitEligible(root, policy, commit) {
74
+ if (policy.assertion.kind !== "executable-behavior")
75
+ return true;
76
+ const sourceCommit = policy.assertion.test.source_commit;
77
+ const check = spawnSync("git", ["-C", root, "merge-base", "--is-ancestor", sourceCommit, commit], {
78
+ encoding: "utf8",
79
+ stdio: ["ignore", "ignore", "pipe"],
80
+ });
81
+ if (check.status === 0)
82
+ return true;
83
+ if (check.status === 1)
84
+ return false;
85
+ throw new Error(`cannot compare shadow commit ${commit} with policy introduction ${sourceCommit}: ${(check.stderr ?? "").trim() || `git exited ${check.status}`}`);
86
+ }
87
+ export class ConstitutionService {
88
+ store;
89
+ root;
90
+ repository;
91
+ g2Repository;
92
+ g3Repository;
93
+ experimentRepository;
94
+ g2CandidateRepository;
95
+ g2BehaviorAttestationRepository;
96
+ constructor(store, root) {
97
+ this.store = store;
98
+ this.root = root;
99
+ this.repository = new PolicyRepository(root, store);
100
+ this.g2Repository = new G2EvidenceRepository(store);
101
+ this.g3Repository = new G3EvidenceRepository(store);
102
+ this.experimentRepository = new ExperimentRepository(store);
103
+ this.g2CandidateRepository = new G2CandidateAttestationRepository(store);
104
+ this.g2BehaviorAttestationRepository = new G2BehaviorAttestationRepository(store);
105
+ }
106
+ createG2Plan(input, opts = {}) {
107
+ const plan = compileG2Plan(input, opts);
108
+ for (const policyId of plan.policy_ids) {
109
+ const policy = this.repository.getPolicy(policyId, { privateOnly: true });
110
+ const publicDuplicate = this.repository.getPolicy(policyId, { publicOnly: true });
111
+ if (!policy || publicDuplicate || this.repository.homeOfPolicy(policyId) !== "private" || policy.data_class === "public") {
112
+ throw new Error(`G2 selected policy ${policyId} must exist only in the configured private overlay`);
113
+ }
114
+ }
115
+ for (const [category, runbookId] of Object.entries(plan.runbooks)) {
116
+ const publicDuplicate = this.store.recsInHome("runbooks", "public").some((runbook) => runbook.id === runbookId);
117
+ if (!this.store.getPrivateRec("runbooks", runbookId) || publicDuplicate) {
118
+ throw new Error(`G2 ${category} runbook ${runbookId} must exist in the configured private overlay`);
119
+ }
120
+ }
121
+ return this.g2Repository.putPlan(plan);
122
+ }
123
+ recordRunbookRehearsal(runbookId, result, actor, evidenceHashes, notes, opts = {}) {
124
+ const runbook = this.store.getPrivateRec("runbooks", runbookId);
125
+ if (!runbook)
126
+ throw new Error(`private runbook ${runbookId} not found`);
127
+ const receipt = compileRunbookRehearsal({
128
+ runbook_id: runbookId,
129
+ runbook_hash: runbookContentHash(runbook),
130
+ result,
131
+ actor,
132
+ evidence_hashes: evidenceHashes,
133
+ notes,
134
+ supersedes: opts.supersedes,
135
+ }, { now: opts.now });
136
+ return this.g2Repository.putRehearsal(receipt);
137
+ }
138
+ g2Readiness() {
139
+ const manifest = this.g2Repository.currentPlan();
140
+ const privatePolicies = this.repository.listPolicies({ privateOnly: true });
141
+ const publicPolicies = this.repository.listPolicies({ publicOnly: true });
142
+ const privateRunbooks = this.store.recsInHome("runbooks", "private");
143
+ const publicRunbooks = this.store.recsInHome("runbooks", "public");
144
+ const rehearsals = this.g2Repository.listRehearsals();
145
+ const currentRehearsals = currentRunbookRehearsals(rehearsals);
146
+ const activeBlocking = this.repository.listPolicies().filter((policy) => policy.state === "active_blocking").map((policy) => policy.id);
147
+ const policyEvidence = [];
148
+ const runbookEvidence = [];
149
+ for (const policyId of manifest?.policy_ids ?? []) {
150
+ const reasons = [];
151
+ const policy = privatePolicies.find((candidate) => candidate.id === policyId);
152
+ const publicDuplicate = publicPolicies.some((candidate) => candidate.id === policyId);
153
+ let proofId = null;
154
+ let corpusId = null;
155
+ let shadowApplicable = 0;
156
+ let shadowViolations = 0;
157
+ let shadowUnclassified = 0;
158
+ let confirmedPrecision = null;
159
+ if (!policy || publicDuplicate || this.repository.homeOfPolicy(policyId) !== "private" || policy.data_class === "public") {
160
+ reasons.push("selected policy is missing from its exact private home");
161
+ }
162
+ else if (!policy.proof) {
163
+ reasons.push("selected policy has no current proof");
164
+ }
165
+ else {
166
+ const behaviorAttestationError = executableBehaviorAttestationError(policy, this.g2BehaviorAttestationRepository.current());
167
+ if (behaviorAttestationError)
168
+ reasons.push(behaviorAttestationError);
169
+ const composition = compositionDescendants(policy, privatePolicies);
170
+ const proof = this.repository.getProof(policy.proof, { privateOnly: true });
171
+ proofId = proof?.id ?? null;
172
+ if (!proof) {
173
+ reasons.push(`linked proof ${policy.proof} is missing from the private overlay`);
174
+ }
175
+ else {
176
+ if (proof.policy_hash !== policyProofHash(policy, composition))
177
+ reasons.push("proof does not bind current policy/composite semantics");
178
+ const expectedEvaluator = evaluatorForPolicy(policy);
179
+ const expectedMutationEngine = mutationEngineForPolicy(policy);
180
+ if (proof.evaluator.name !== expectedEvaluator.name || proof.evaluator.version !== expectedEvaluator.version)
181
+ reasons.push("proof evaluator version is stale");
182
+ if (proof.mutation_engine?.name !== expectedMutationEngine.name || proof.mutation_engine.version !== expectedMutationEngine.version)
183
+ reasons.push("proof mutation engine version is stale");
184
+ if (!["P3", "P4", "P5"].includes(proof.proof_class))
185
+ reasons.push(`proof class ${proof.proof_class} is below P3`);
186
+ if (proof.current.satisfied !== 1 || proof.current.total !== 1 || proof.current.violated || proof.current.unknown || proof.current.error)
187
+ reasons.push("proof has no exact clean satisfied baseline");
188
+ const plan = this.repository.listPlans({ privateOnly: true }).find((candidate) => candidate.content_hash === proof.plan_hash && candidate.policy_id === policy.id);
189
+ if (!plan)
190
+ reasons.push("proof has no exact bound private proof plan");
191
+ const corpus = this.repository.getCorpus(policy.id, { privateOnly: true });
192
+ corpusId = corpus?.id ?? null;
193
+ if (!corpus) {
194
+ reasons.push("policy has no imported private proof corpus");
195
+ }
196
+ else {
197
+ if (!plan?.corpus_manifest || plan.corpus_manifest.id !== corpus.id || plan.corpus_manifest.content_hash !== corpus.content_hash)
198
+ reasons.push("proof plan does not bind the exact imported corpus manifest");
199
+ if (corpus.known_bad.length < 1)
200
+ reasons.push("corpus has no declared known-bad fixture");
201
+ else if (proof.known_bad.total !== corpus.known_bad.length || proof.known_bad.violated !== proof.known_bad.total)
202
+ reasons.push("proof did not catch every imported known-bad fixture");
203
+ if (corpus.known_good.length < 1)
204
+ reasons.push("corpus has no declared known-good fixture");
205
+ else if (proof.known_good.total !== corpus.known_good.length || proof.known_good.satisfied !== proof.known_good.total)
206
+ reasons.push("proof did not satisfy every imported known-good fixture");
207
+ }
208
+ if (!plan?.mutations.length)
209
+ reasons.push("proof plan has no mutation corpus");
210
+ else {
211
+ const available = [...proof.mutation_receipts];
212
+ for (const mutation of plan.mutations.filter((candidate) => candidate.required)) {
213
+ const index = available.findIndex((receipt) => receipt.operator === mutation.operator && receipt.required);
214
+ const receipt = index >= 0 ? available.splice(index, 1)[0] : undefined;
215
+ if (!receipt)
216
+ reasons.push(`required mutation ${mutation.operator} has no exact receipt`);
217
+ else if (!receipt.passed)
218
+ reasons.push(`required mutation ${mutation.operator} failed`);
219
+ }
220
+ }
221
+ if (proof.mutation_controls.total < 1)
222
+ reasons.push("proof has no mutation controls");
223
+ if (proof.mutation_controls.failed > 0 || proof.mutation_controls.passed !== proof.mutation_controls.total)
224
+ reasons.push("proof mutation controls are incomplete or failed");
225
+ const dispositionAssessment = assessHistoryDispositions(proof, this.repository.listDispositions({ privateOnly: true }).filter((record) => record.policy_id === policy.id && record.proof_id === proof.id));
226
+ if (dispositionAssessment.blocking_error)
227
+ reasons.push(dispositionAssessment.blocking_error);
228
+ try {
229
+ const shadow = this.shadowReport(policy.id, { minApplicable: manifest?.min_shadow_applicable ?? 20 }, { privateOnly: true });
230
+ shadowApplicable = shadow.counts.applicable;
231
+ shadowViolations = shadow.counts.violated;
232
+ shadowUnclassified = shadow.dispositions.unclassified;
233
+ confirmedPrecision = shadow.precision.confirmed;
234
+ if (shadowApplicable < (manifest?.min_shadow_applicable ?? 20))
235
+ reasons.push(`shadow baseline has ${shadowApplicable}/${manifest?.min_shadow_applicable ?? 20} applicable observations`);
236
+ if (shadowUnclassified > 0)
237
+ reasons.push(`shadow baseline has ${shadowUnclassified} unclassified violation(s)`);
238
+ }
239
+ catch (error) {
240
+ reasons.push(`shadow precision baseline unavailable: ${error.message}`);
241
+ }
242
+ }
243
+ }
244
+ policyEvidence.push({
245
+ policy_id: policyId,
246
+ complete: reasons.length === 0,
247
+ proof_id: proofId,
248
+ corpus_id: corpusId,
249
+ shadow_applicable: shadowApplicable,
250
+ shadow_violations: shadowViolations,
251
+ shadow_unclassified: shadowUnclassified,
252
+ confirmed_precision: confirmedPrecision,
253
+ reasons,
254
+ });
255
+ }
256
+ for (const category of G2_RUNBOOK_CATEGORIES) {
257
+ const runbookId = manifest?.runbooks[category];
258
+ if (!runbookId)
259
+ continue;
260
+ const reasons = [];
261
+ const runbook = privateRunbooks.find((candidate) => candidate.id === runbookId);
262
+ const publicDuplicate = publicRunbooks.some((candidate) => candidate.id === runbookId);
263
+ const hash = runbook ? runbookContentHash(runbook) : null;
264
+ const rehearsal = hash ? currentRehearsals.find((candidate) => candidate.runbook_id === runbookId && candidate.runbook_hash === hash) : undefined;
265
+ if (!runbook || publicDuplicate)
266
+ reasons.push("selected runbook is missing from its exact private-only home");
267
+ else if (!rehearsal)
268
+ reasons.push("exact runbook content has no current rehearsal receipt");
269
+ else if (rehearsal.result !== "passed")
270
+ reasons.push(`current rehearsal result is ${rehearsal.result}`);
271
+ runbookEvidence.push({ category, runbook_id: runbookId, runbook_hash: hash, rehearsal_id: rehearsal?.id ?? null, passed: rehearsal?.result === "passed", reasons });
272
+ }
273
+ return scoreG2Readiness({
274
+ manifest,
275
+ policy_evidence: policyEvidence,
276
+ runbook_evidence: runbookEvidence,
277
+ active_blocking_policy_ids: activeBlocking,
278
+ inventory: {
279
+ private_policies: privatePolicies.length,
280
+ public_policies: publicPolicies.length,
281
+ private_proofs: this.repository.listProofs({ privateOnly: true }).length,
282
+ private_corpora: this.repository.listCorpora({ privateOnly: true }).length,
283
+ private_shadow_evaluations: this.repository.listShadowEvaluations({ privateOnly: true }).length,
284
+ private_shadow_dispositions: this.repository.listShadowDispositions({ privateOnly: true }).length,
285
+ private_runbooks: privateRunbooks.length,
286
+ public_runbooks: publicRunbooks.length,
287
+ private_plans: this.g2Repository.listPlans().length,
288
+ private_rehearsals: rehearsals.length,
289
+ },
290
+ });
291
+ }
292
+ registerG3Experiment(input, opts = {}) {
293
+ return this.g3Repository.putExperiment(compileExperimentPreregistration(input, opts));
294
+ }
295
+ createG3Plan(input, opts = {}) {
296
+ const g2 = this.g2Readiness();
297
+ if (g2.recommendation !== "eligible_for_human_g2_signoff" || !g2.manifest) {
298
+ throw new Error("G3 plan requires an exact currently eligible private G2 packet");
299
+ }
300
+ const plan = compileG3Plan(input, opts);
301
+ if (plan.g2_readiness_hash !== g2.content_hash)
302
+ throw new Error(`G3 plan must bind current G2 readiness hash ${g2.content_hash}`);
303
+ if (canonicalJson(plan.policy_ids) !== canonicalJson([...g2.manifest.policy_ids].sort())) {
304
+ throw new Error("G3 plan must carry the complete exact G2 policy set; cherry-picked subsets cannot clear the advisory gate");
305
+ }
306
+ const currentExperiments = this.g3Repository.currentExperiments();
307
+ const retrieval = currentExperiments.find((record) => record.experiment === "EXP-01");
308
+ const compiler = currentExperiments.find((record) => record.experiment === "EXP-03");
309
+ if (!retrieval || plan.experiments.retrieval !== retrieval.id)
310
+ throw new Error("G3 plan must bind the exact current EXP-01 retrieval preregistration");
311
+ if (!compiler || plan.experiments.compiler !== compiler.id)
312
+ throw new Error("G3 plan must bind the exact current EXP-03 compiler preregistration");
313
+ for (const policyId of plan.policy_ids) {
314
+ const policy = this.repository.getPolicy(policyId, { privateOnly: true });
315
+ const publicDuplicate = this.repository.getPolicy(policyId, { publicOnly: true });
316
+ if (!policy || publicDuplicate || this.repository.homeOfPolicy(policyId) !== "private" || policy.data_class !== "private") {
317
+ throw new Error(`G3 selected policy ${policyId} must exist only in the configured private overlay`);
318
+ }
319
+ }
320
+ return this.g3Repository.putPlan(plan);
321
+ }
322
+ recordG3ProofReview(input, opts = {}) {
323
+ const plan = this.g3Repository.currentPlan();
324
+ if (!plan)
325
+ throw new Error("No current private G3 plan; proof review cannot bind an exact policy packet");
326
+ if (!plan.policy_ids.includes(input.policy_id))
327
+ throw new Error(`policy ${input.policy_id} is not selected by current G3 plan ${plan.id}`);
328
+ const policy = this.get(input.policy_id, { privateOnly: true });
329
+ if (this.repository.getPolicy(input.policy_id, { publicOnly: true }) || this.repository.homeOfPolicy(input.policy_id) !== "private" || policy.data_class !== "private") {
330
+ throw new Error(`G3 proof review policy ${input.policy_id} must exist only in the configured private overlay`);
331
+ }
332
+ if (!policy.proof)
333
+ throw new Error(`policy ${input.policy_id} has no proof to review`);
334
+ const proof = this.proof(policy.proof, { privateOnly: true });
335
+ const card = this.card(policy.id, { privateOnly: true });
336
+ return this.g3Repository.putReview(compileProofReviewMeasurement({
337
+ plan_id: plan.id,
338
+ policy_id: policy.id,
339
+ policy_hash: policyProofHash(policy, this.composition(policy, { privateOnly: true })),
340
+ proof_id: proof.id,
341
+ proof_hash: canonicalHash(proof),
342
+ card_hash: card.card_hash,
343
+ reviewer: input.reviewer,
344
+ duration_ms: input.duration_ms,
345
+ comprehension: input.comprehension,
346
+ notes: input.notes,
347
+ supersedes: input.supersedes,
348
+ }, opts));
349
+ }
350
+ g3AdapterConformance(opts = {}) {
351
+ const plan = this.g3Repository.currentPlan();
352
+ if (!plan)
353
+ throw new Error("No current private G3 plan; adapter conformance cannot bind an exact client set");
354
+ const current = this.g3Repository.currentConformance().find((record) => record.plan_id === plan.id);
355
+ const receipt = executeG3AdapterConformance(this.root, plan, { ...opts, supersedes: current?.id ?? null });
356
+ return this.g3Repository.putConformance(receipt);
357
+ }
358
+ g3Readiness() {
359
+ const manifest = this.g3Repository.currentPlan();
360
+ const g2 = this.g2Readiness();
361
+ const g2Evidence = new Map(g2.policy_evidence.map((evidence) => [evidence.policy_id, evidence]));
362
+ const currentReviews = this.g3Repository.currentReviews();
363
+ const policyEvidence = [];
364
+ for (const policyId of manifest?.policy_ids ?? []) {
365
+ const reasons = [];
366
+ const policy = this.repository.getPolicy(policyId, { privateOnly: true });
367
+ const publicDuplicate = this.repository.getPolicy(policyId, { publicOnly: true });
368
+ const g2Policy = g2Evidence.get(policyId);
369
+ let cardHash = null;
370
+ let proofCurrent = false;
371
+ let review = null;
372
+ if (!policy || publicDuplicate || this.repository.homeOfPolicy(policyId) !== "private" || policy.data_class !== "private") {
373
+ reasons.push("selected policy is missing from its exact private-only home");
374
+ }
375
+ else {
376
+ if (policy.state !== "active_advisory")
377
+ reasons.push(`policy state is ${policy.state}, not active_advisory under explicit human authority`);
378
+ if (policy.authority?.kind !== "human")
379
+ reasons.push("policy has no explicit human advisory authority");
380
+ if (!g2Policy?.complete)
381
+ reasons.push("current G2 proof/corpus/shadow evidence is incomplete");
382
+ try {
383
+ const card = this.card(policyId, { privateOnly: true });
384
+ cardHash = card.card_hash;
385
+ proofCurrent = !!policy.proof && g2Policy?.proof_id === policy.proof;
386
+ const matching = currentReviews.filter((candidate) => candidate.plan_id === manifest.id && candidate.policy_id === policyId && candidate.card_hash === card.card_hash);
387
+ if (matching.length !== 1)
388
+ reasons.push(matching.length ? "multiple current proof reviews make the measurement target ambiguous" : "current proof card has no exact human review measurement");
389
+ else {
390
+ review = matching[0];
391
+ if (review.policy_hash !== policyProofHash(policy, this.composition(policy, { privateOnly: true })))
392
+ reasons.push("proof review does not bind current policy/composite semantics");
393
+ const proof = policy.proof ? this.proof(policy.proof, { privateOnly: true }) : null;
394
+ if (!proof || review.proof_id !== proof.id || review.proof_hash !== canonicalHash(proof))
395
+ reasons.push("proof review does not bind the exact current proof artifact");
396
+ if (Object.values(review.comprehension).some((answer) => answer !== "correct"))
397
+ reasons.push("proof-card comprehension measurement contains an incorrect answer");
398
+ }
399
+ }
400
+ catch (error) {
401
+ reasons.push(`proof card is unavailable: ${error.message}`);
402
+ }
403
+ }
404
+ policyEvidence.push({
405
+ policy_id: policyId,
406
+ state: policy?.state ?? null,
407
+ authority_kind: policy?.authority?.kind ?? null,
408
+ proof_current: proofCurrent,
409
+ card_hash: cardHash,
410
+ review_id: review?.id ?? null,
411
+ review_duration_ms: review?.duration_ms ?? null,
412
+ comprehension_correct: review ? Object.values(review.comprehension).every((answer) => answer === "correct") : null,
413
+ reasons,
414
+ });
415
+ }
416
+ const currentExperiments = this.g3Repository.currentExperiments();
417
+ const experiments = G3_REQUIRED_EXPERIMENTS.map((experiment) => {
418
+ const record = currentExperiments.find((candidate) => candidate.experiment === experiment);
419
+ const expectedId = experiment === "EXP-01" ? manifest?.experiments.retrieval : manifest?.experiments.compiler;
420
+ const reasons = [];
421
+ if (!record)
422
+ reasons.push("no current immutable preregistration exists");
423
+ else if (expectedId && record.id !== expectedId)
424
+ reasons.push(`current preregistration ${record.id} differs from plan-bound ${expectedId}`);
425
+ if (!manifest)
426
+ reasons.push("no G3 plan binds this preregistration");
427
+ return { experiment, id: record?.id ?? null, content_hash: record?.content_hash ?? null, current: !!record && !!manifest && record.id === expectedId, reasons };
428
+ });
429
+ const conformance = manifest
430
+ ? this.g3Repository.currentConformance().find((record) => record.plan_id === manifest.id
431
+ && record.test.source_hash === g3ConformanceSourceHash(this.root)
432
+ && record.test.file === "test/behavior-workspace.test.ts") ?? null
433
+ : null;
434
+ const activeBlocking = this.repository.listPolicies().filter((policy) => policy.state === "active_blocking").map((policy) => policy.id);
435
+ return scoreG3Readiness({ manifest, g2_readiness: g2, policy_evidence: policyEvidence, experiments, conformance, active_blocking_policy_ids: activeBlocking });
436
+ }
437
+ validateExperimentCaseBank(input, opts = {}) {
438
+ const preregistration = this.g3Repository.currentExperiments().find((item) => item.id === input.preregistration_id);
439
+ if (!preregistration)
440
+ throw new Error(`case bank must bind a current G3 preregistration: ${input.preregistration_id}`);
441
+ return compileExperimentCaseBank(input, preregistration, opts);
442
+ }
443
+ lockExperimentCaseBank(input, opts = {}) {
444
+ return this.experimentRepository.putCaseBank(this.validateExperimentCaseBank(input, opts));
445
+ }
446
+ createExperimentRun(caseBankId, input, opts = {}) {
447
+ const bank = this.experimentRepository.listCaseBanks().find((item) => item.id === caseBankId);
448
+ if (!bank)
449
+ throw new Error(`unknown private experiment case bank: ${caseBankId}`);
450
+ const preregistration = this.g3Repository.currentExperiments().find((item) => item.id === bank.preregistration_id);
451
+ if (!preregistration)
452
+ throw new Error(`case bank ${caseBankId} is not bound to a current preregistration`);
453
+ return this.experimentRepository.putRun(compileExperimentRun(input, preregistration, bank, opts));
454
+ }
455
+ experimentRun(runId) {
456
+ const run = this.experimentRepository.listRuns().find((item) => item.id === runId);
457
+ if (!run)
458
+ throw new Error(`unknown private experiment run: ${runId}`);
459
+ return run;
460
+ }
461
+ executeExperimentRun(runId, opts = {}) {
462
+ const run = this.experimentRun(runId);
463
+ if (run.experiment !== "EXP-01")
464
+ throw new Error("automated run execution is available only for EXP-01; use the timed review queue for EXP-03");
465
+ const bank = this.experimentRepository.listCaseBanks().find((item) => item.id === run.case_bank_id);
466
+ if (!bank)
467
+ throw new Error(`run ${run.id} is missing exact case bank ${run.case_bank_id}`);
468
+ const current = new Set(currentExperimentOutcomes(this.experimentRepository.listOutcomes()).filter((item) => item.run_id === run.id).map((item) => item.assignment_id));
469
+ if (this.experimentReport(run.id).status === "guardrail_stopped")
470
+ throw new Error("experiment is stopped by an independently recorded safety/privacy guardrail");
471
+ const limit = opts.limit ?? 1;
472
+ if (!Number.isInteger(limit) || limit < 1 || limit > run.assignments.length)
473
+ throw new Error("experiment execution limit must be a positive integer within the assignment count");
474
+ const outcomes = [];
475
+ for (const assignment of [...run.assignments].sort((a, b) => a.order - b.order)) {
476
+ if (outcomes.length >= limit)
477
+ break;
478
+ if (current.has(assignment.id))
479
+ continue;
480
+ outcomes.push(executeExp01Assignment(this.experimentRepository, run, bank, assignment, { timeoutMs: opts.timeoutMs, now: opts.now }));
481
+ if (this.experimentReport(run.id).status === "guardrail_stopped")
482
+ break;
483
+ }
484
+ return { outcomes, report: this.experimentReport(run.id) };
485
+ }
486
+ nextExperimentReview(runId, reviewer, opts = {}) {
487
+ const run = this.experimentRun(runId);
488
+ if (run.experiment !== "EXP-03")
489
+ throw new Error("timed review queue is available only for EXP-03");
490
+ if (this.experimentReport(run.id).status === "guardrail_stopped")
491
+ throw new Error("experiment is stopped by an independently recorded safety/privacy guardrail");
492
+ const bank = this.experimentRepository.listCaseBanks().find((item) => item.id === run.case_bank_id);
493
+ if (!bank)
494
+ throw new Error(`run ${run.id} is missing exact case bank ${run.case_bank_id}`);
495
+ const current = new Set(currentExperimentOutcomes(this.experimentRepository.listOutcomes()).filter((item) => item.run_id === run.id).map((item) => item.assignment_id));
496
+ const starts = this.experimentRepository.listReviewStarts().filter((item) => item.run_id === run.id);
497
+ const existing = starts.find((item) => item.reviewer === reviewer && !current.has(item.assignment_id));
498
+ const assignment = existing
499
+ ? run.assignments.find((item) => item.id === existing.assignment_id)
500
+ : [...run.assignments].sort((a, b) => a.order - b.order).find((item) => {
501
+ if (current.has(item.id) || starts.some((start) => start.assignment_id === item.id))
502
+ return false;
503
+ const c = bank.cases.find((candidate) => candidate.id === item.case_id);
504
+ const assignedReviewer = c?.strata.reviewer;
505
+ return !assignedReviewer || assignedReviewer === reviewer || assignedReviewer === reviewer.replace(/^human:/i, "");
506
+ });
507
+ if (!assignment)
508
+ throw new Error(`no unreviewed EXP-03 assignment is available for ${reviewer}`);
509
+ const start = existing ?? this.experimentRepository.putReviewStart(compileExperimentReviewStart(run, assignment, reviewer, opts));
510
+ return { start, assignment, treatment: assignmentTreatment(bank, run, assignment) };
511
+ }
512
+ submitExperimentReview(runId, assignmentId, input, opts = {}) {
513
+ const run = this.experimentRun(runId);
514
+ if (run.experiment !== "EXP-03")
515
+ throw new Error("human review submission is available only for EXP-03");
516
+ const start = this.experimentRepository.listReviewStarts().find((item) => item.run_id === run.id && item.assignment_id === assignmentId);
517
+ if (!start || start.reviewer !== input.reviewer)
518
+ throw new Error("review submission must bind the machine-recorded start and exact reviewer");
519
+ const recordedAt = opts.now ?? new Date().toISOString();
520
+ const duration = Date.parse(recordedAt) - Date.parse(start.started_at);
521
+ if (!Number.isFinite(duration) || duration < 1)
522
+ throw new Error("review completion must occur after the machine-recorded start");
523
+ const current = currentExperimentOutcomes(this.experimentRepository.listOutcomes()).find((item) => item.run_id === run.id && item.assignment_id === assignmentId);
524
+ if (current)
525
+ throw new Error(`assignment ${assignmentId} already has current outcome ${current.id}; use an explicit append-only correction workflow`);
526
+ const assignment = run.assignments.find((item) => item.id === assignmentId);
527
+ const bank = this.experimentRepository.listCaseBanks().find((item) => item.id === run.case_bank_id);
528
+ const item = bank?.cases.find((candidate) => candidate.id === assignment?.case_id);
529
+ if (!assignment || !bank || !item || !("compiler_candidate" in item))
530
+ throw new Error("review submission cannot resolve the exact assigned case and treatment");
531
+ const accepted = input.decision.startsWith("accepted");
532
+ const result = input.result?.trim() || null;
533
+ if (accepted !== (result !== null))
534
+ throw new Error("accepted reviews require a result; non-accepted reviews must not claim one");
535
+ const editDistance = accepted && assignment.arm !== "A" ? normalizedEditDistance(item.compiler_candidate, result) : null;
536
+ return this.experimentRepository.putOutcome(compileExperimentOutcome({
537
+ run_id: run.id,
538
+ assignment_id: assignmentId,
539
+ status: "completed",
540
+ invocation_started: true,
541
+ metrics: {
542
+ decision: input.decision,
543
+ precise: input.precise,
544
+ proof_inspected: input.proof_inspected,
545
+ result_hash: result ? canonicalHash(result) : null,
546
+ semantic_edit_distance: editDistance,
547
+ silent_semantic_substitution: input.silent_semantic_substitution,
548
+ rejection_reason: input.rejection_reason,
549
+ duration_ms: duration,
550
+ },
551
+ output_hash: canonicalHash({ decision: input.decision, precise: input.precise, rejection_reason: input.rejection_reason, result_hash: result ? canonicalHash(result) : null }),
552
+ diff_hash: null,
553
+ evaluator_hash: start.treatment_hash,
554
+ error_code: null,
555
+ incidents: {
556
+ confirmed_private_leak: input.confirmed_private_leak,
557
+ data_loss_or_corruption: input.data_loss_or_corruption,
558
+ unsafe_evaluator_behavior: input.unsafe_evaluator_behavior,
559
+ },
560
+ recorder: input.reviewer,
561
+ reason: input.reason,
562
+ supersedes: null,
563
+ }, run, { now: recordedAt }));
564
+ }
565
+ recordExperimentFollowup(runId, assignmentId, input, opts = {}) {
566
+ const run = this.experimentRun(runId);
567
+ const outcome = currentExperimentOutcomes(this.experimentRepository.listOutcomes()).find((item) => item.run_id === run.id && item.assignment_id === assignmentId);
568
+ if (!outcome)
569
+ throw new Error(`assignment ${assignmentId} has no current outcome to follow up`);
570
+ return this.experimentRepository.putFollowup(compileExperimentFollowup({ ...input, supersedes: input.supersedes ?? null }, run, outcome, opts));
571
+ }
572
+ stopExperiment(runId, input, opts = {}) {
573
+ const run = this.experimentRun(runId);
574
+ return this.experimentRepository.putStop(compileExperimentStop(input, run, opts));
575
+ }
576
+ experimentReport(runId) {
577
+ const run = this.experimentRun(runId);
578
+ const bank = this.experimentRepository.listCaseBanks().find((item) => item.id === run.case_bank_id);
579
+ if (!bank)
580
+ throw new Error(`run ${run.id} is missing exact case bank ${run.case_bank_id}`);
581
+ return buildExperimentReport(run, bank, this.experimentRepository.listOutcomes(), this.experimentRepository.listFollowups(), this.experimentRepository.listStops());
582
+ }
583
+ g2ShadowSweep(opts = {}) {
584
+ const manifest = this.g2Repository.currentPlan();
585
+ const recorded = [];
586
+ const existing = [];
587
+ const failures = [];
588
+ if (manifest) {
589
+ const before = new Set(this.repository.listShadowEvaluations({ privateOnly: true }).map((record) => record.id));
590
+ for (const policyId of manifest.policy_ids) {
591
+ try {
592
+ const policy = this.repository.getPolicy(policyId, { privateOnly: true });
593
+ const publicDuplicate = this.repository.getPolicy(policyId, { publicOnly: true });
594
+ if (!policy || publicDuplicate || this.repository.homeOfPolicy(policyId) !== "private" || policy.data_class === "public") {
595
+ throw new Error("selected policy is not in one exact private-only home");
596
+ }
597
+ const record = this.recordShadow(policyId, { now: opts.now });
598
+ if (before.has(record.id))
599
+ existing.push(record.id);
600
+ else {
601
+ recorded.push(record.id);
602
+ before.add(record.id);
603
+ }
604
+ }
605
+ catch (error) {
606
+ failures.push({ policy_id: policyId, error: error.message });
607
+ }
608
+ }
609
+ }
610
+ const body = {
611
+ plan_id: manifest?.id ?? null,
612
+ selected: manifest?.policy_ids.length ?? 0,
613
+ recorded: recorded.sort(),
614
+ existing: existing.sort(),
615
+ failures: failures.sort((left, right) => left.policy_id.localeCompare(right.policy_id)),
616
+ skipped_reason: manifest ? null : "No current private G2 plan; shadow sweep wrote nothing.",
617
+ authority: "none",
618
+ effects: "shadow_only",
619
+ };
620
+ const contentHash = canonicalHash(body);
621
+ return { id: `g2sweep_${shortHash(contentHash)}`, content_hash: contentHash, ...body };
622
+ }
623
+ g2ShadowBackfill(maxCommits = 20, opts = {}) {
624
+ if (!Number.isInteger(maxCommits) || maxCommits < 1 || maxCommits > 100) {
625
+ throw new Error("G2 shadow backfill maxCommits must be a positive integer no greater than 100");
626
+ }
627
+ const manifest = this.g2Repository.currentPlan();
628
+ const commits = manifest ? execFileSync("git", ["-C", this.root, "rev-list", "--first-parent", `--max-count=${maxCommits}`, "HEAD"], {
629
+ encoding: "utf8",
630
+ stdio: ["ignore", "pipe", "ignore"],
631
+ }).trim().split("\n").filter((commit) => /^[a-f0-9]{40}$/.test(commit)) : [];
632
+ const attempted = (manifest?.policy_ids.length ?? 0) * commits.length;
633
+ const preflightFailures = [];
634
+ const ineligible = [];
635
+ const candidates = [];
636
+ if (manifest) {
637
+ for (const commit of commits) {
638
+ for (const policyId of manifest.policy_ids) {
639
+ try {
640
+ const policy = this.repository.getPolicy(policyId, { privateOnly: true });
641
+ if (!policy)
642
+ throw new Error("selected policy is missing from the private overlay");
643
+ if (!shadowCommitEligible(this.root, policy, commit)) {
644
+ ineligible.push({ policy_id: policyId, commit, reason: "commit predates the executable policy fixing commit" });
645
+ continue;
646
+ }
647
+ const record = this.compileShadowRecord(policyId, { commit, now: opts.now });
648
+ if (record.evaluation.result === "unknown" || record.evaluation.result === "error") {
649
+ preflightFailures.push({ policy_id: policyId, commit, error: record.evaluation.explanation });
650
+ }
651
+ else {
652
+ candidates.push(record);
653
+ }
654
+ }
655
+ catch (error) {
656
+ preflightFailures.push({ policy_id: policyId, commit, error: error.message });
657
+ }
658
+ }
659
+ }
660
+ }
661
+ const recorded = [];
662
+ const existing = [];
663
+ if (manifest && !preflightFailures.length) {
664
+ const before = new Set(this.repository.listShadowEvaluations({ privateOnly: true }).map((record) => record.id));
665
+ for (const candidate of candidates) {
666
+ const stored = this.repository.putShadowEvaluation(candidate, candidate.policy_id);
667
+ if (before.has(stored.id))
668
+ existing.push(stored.id);
669
+ else {
670
+ recorded.push(stored.id);
671
+ before.add(stored.id);
672
+ }
673
+ }
674
+ }
675
+ const skippedReason = !manifest
676
+ ? "No current private G2 plan; historical shadow backfill wrote nothing."
677
+ : preflightFailures.length
678
+ ? "Historical shadow preflight produced unknown/error evidence; the batch wrote nothing."
679
+ : null;
680
+ const body = {
681
+ plan_id: manifest?.id ?? null,
682
+ max_commits: maxCommits,
683
+ commits,
684
+ selected: manifest?.policy_ids.length ?? 0,
685
+ attempted,
686
+ recorded: recorded.sort(),
687
+ existing: existing.sort(),
688
+ ineligible: ineligible.sort((left, right) => left.commit.localeCompare(right.commit) || left.policy_id.localeCompare(right.policy_id)),
689
+ preflight_failures: preflightFailures.sort((left, right) => left.commit.localeCompare(right.commit) || left.policy_id.localeCompare(right.policy_id)),
690
+ skipped_reason: skippedReason,
691
+ authority: "none",
692
+ effects: "shadow_only",
693
+ writes: manifest && !preflightFailures.length ? "atomic_after_preflight" : "none",
694
+ };
695
+ const contentHash = canonicalHash(body);
696
+ return { id: `g2backfill_${shortHash(contentHash)}`, content_hash: contentHash, ...body };
697
+ }
698
+ g2OperationalDrill(category) {
699
+ const manifest = this.g2Repository.currentPlan();
700
+ if (!manifest)
701
+ throw new Error("No current private G2 plan; operational drill cannot bind an exact runbook");
702
+ if (!G2_RUNBOOK_CATEGORIES.includes(category))
703
+ throw new Error(`unknown G2 operational drill category ${category}`);
704
+ const runbookId = manifest.runbooks[category];
705
+ const runbook = this.store.getPrivateRec("runbooks", runbookId);
706
+ const publicDuplicate = this.store.recsInHome("runbooks", "public").some((candidate) => candidate.id === runbookId);
707
+ if (!runbook || publicDuplicate)
708
+ throw new Error(`G2 ${category} runbook ${runbookId} is not in one exact private-only home`);
709
+ return executeG2OperationalDrill(this.root, manifest, runbook, category);
710
+ }
711
+ g2CandidateReview(opts = {}) {
712
+ return buildG2CandidateReview(this.store, this.root, opts, this.g2CandidateRepository.resolutions());
713
+ }
714
+ attestG2Candidate(candidateId, reviewHash, disposition, actor, reason, opts = {}) {
715
+ const existing = this.g2CandidateRepository.current().find((record) => (record.candidate_id === candidateId
716
+ && record.review_hash === reviewHash
717
+ && record.disposition === disposition
718
+ && record.actor === actor
719
+ && record.reason === reason.trim()
720
+ && record.supersedes === (opts.supersedes ?? null)));
721
+ if (existing)
722
+ return existing;
723
+ const report = this.g2CandidateReview(opts);
724
+ const attestation = compileG2CandidateAttestation(report, candidateId, reviewHash, disposition, actor, reason, opts);
725
+ return this.g2CandidateRepository.put(attestation);
726
+ }
727
+ g2BehaviorCandidateReview(opts = {}) {
728
+ return buildG2BehaviorCandidateReview(this.store, this.root, opts, this.g2CandidateRepository.resolutions(), this.g2BehaviorAttestationRepository.resolutions());
729
+ }
730
+ g2BehaviorCandidateReplay(candidateId, reviewHash, opts = {}) {
731
+ const report = this.g2BehaviorCandidateReview(opts);
732
+ return replayG2BehaviorCandidate(this.root, report, candidateId, reviewHash, { timeoutMs: opts.timeoutMs });
733
+ }
734
+ g2BehaviorMaterializationAssessment(opts = {}) {
735
+ const report = this.g2BehaviorCandidateReview(opts);
736
+ return assessG2BehaviorMaterialization(report, this.g2BehaviorAttestationRepository.current());
737
+ }
738
+ g2BehaviorPolicyMaterialize(opts = {}) {
739
+ const report = this.g2BehaviorCandidateReview(opts);
740
+ return materializeSelectedG2BehaviorPolicies(this.store, this.root, this.repository, report, this.g2BehaviorAttestationRepository.current(), opts);
741
+ }
742
+ g2BehaviorDependencySnapshots(candidateId, reviewHash, opts = {}) {
743
+ const report = this.g2BehaviorCandidateReview(opts);
744
+ if (reviewHash !== report.content_hash)
745
+ throw new Error("behavior candidate review hash does not match the exact current review packet");
746
+ const candidate = report.items.find((item) => item.id === candidateId);
747
+ if (!candidate)
748
+ throw new Error(`behavior candidate ${candidateId} is not present in review ${report.id}`);
749
+ return provisionG2BehaviorDependencySnapshots(this.root, report, candidate, opts.allowInstallScripts ?? [], opts.timeoutMs ?? 300_000);
750
+ }
751
+ attestG2BehaviorCandidate(candidateId, reviewHash, disposition, actor, reason, opts = {}) {
752
+ const existing = this.g2BehaviorAttestationRepository.current().find((record) => (record.candidate_id === candidateId
753
+ && record.review_hash === reviewHash
754
+ && record.disposition === disposition
755
+ && record.actor === actor
756
+ && record.reason === reason.trim()
757
+ && record.supersedes === (opts.supersedes ?? null)));
758
+ if (existing)
759
+ return existing;
760
+ const report = this.g2BehaviorCandidateReview(opts);
761
+ const replay = replayG2BehaviorCandidate(this.root, report, candidateId, reviewHash, { timeoutMs: opts.timeoutMs });
762
+ const attestation = compileG2BehaviorAttestation(report, candidateId, reviewHash, replay, disposition, actor, reason, opts);
763
+ return this.g2BehaviorAttestationRepository.put(attestation);
764
+ }
765
+ g2ShadowQueue(limit = 20) {
766
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
767
+ throw new Error("G2 shadow queue limit must be a positive integer no greater than 100");
768
+ const manifest = this.g2Repository.currentPlan();
769
+ const policies = this.repository.listPolicies({ privateOnly: true });
770
+ const proofs = this.repository.listProofs({ privateOnly: true });
771
+ const currentDispositions = currentShadowDispositions(this.repository.listShadowDispositions({ privateOnly: true }));
772
+ const exactRecords = this.repository.listShadowEvaluations({ privateOnly: true }).filter((record) => {
773
+ if (!manifest?.policy_ids.includes(record.policy_id))
774
+ return false;
775
+ const policy = policies.find((candidate) => candidate.id === record.policy_id);
776
+ const publicDuplicate = this.repository.getPolicy(record.policy_id, { publicOnly: true });
777
+ if (!policy || publicDuplicate || policy.proof !== record.proof_id)
778
+ return false;
779
+ const proof = proofs.find((candidate) => candidate.id === record.proof_id);
780
+ if (!proof || proof.policy_hash !== record.policy_hash)
781
+ return false;
782
+ if (!shadowCommitEligible(this.root, policy, record.evaluation.repository.head))
783
+ return false;
784
+ const composition = compositionDescendants(policy, policies);
785
+ return proof.policy_hash === policyProofHash(policy, composition);
786
+ });
787
+ const unclassified = exactRecords
788
+ .filter((record) => record.evaluation.result === "violated")
789
+ .filter((record) => !currentDispositions.some((disposition) => disposition.shadow_id === record.id
790
+ && disposition.policy_id === record.policy_id
791
+ && disposition.proof_id === record.proof_id
792
+ && disposition.policy_hash === record.policy_hash
793
+ && disposition.evaluation_hash === record.evaluation.deterministic_hash))
794
+ .sort((left, right) => left.observed_at.localeCompare(right.observed_at) || left.id.localeCompare(right.id));
795
+ const items = unclassified.slice(0, limit).map((record) => ({
796
+ policy_id: record.policy_id,
797
+ shadow_id: record.id,
798
+ proof_id: record.proof_id,
799
+ policy_hash: record.policy_hash,
800
+ evaluation_hash: record.evaluation.deterministic_hash,
801
+ observed_at: record.observed_at,
802
+ result: "violated",
803
+ explanation: record.evaluation.explanation,
804
+ matches: record.evaluation.matches,
805
+ }));
806
+ const body = {
807
+ plan_id: manifest?.id ?? null,
808
+ total_unclassified: unclassified.length,
809
+ unresolved_unknown_error: exactRecords.filter((record) => record.evaluation.result === "unknown" || record.evaluation.result === "error").length,
810
+ limit,
811
+ items,
812
+ has_more: unclassified.length > items.length,
813
+ authority: "none",
814
+ };
815
+ const contentHash = canonicalHash(body);
816
+ return { id: `g2queue_${shortHash(contentHash)}`, content_hash: contentHash, ...body };
817
+ }
818
+ list(opts = {}) {
819
+ const all = this.repository.listPolicies(opts);
820
+ return opts.state ? all.filter((p) => p.state === opts.state) : all;
821
+ }
822
+ get(id, opts = {}) {
823
+ const policy = this.repository.getPolicy(id, opts);
824
+ if (!policy)
825
+ throw new Error(`policy ${id} not found`);
826
+ return policy;
827
+ }
828
+ composition(policy, opts = {}) {
829
+ return compositionDescendants(policy, this.list(opts));
830
+ }
831
+ proof(id, opts = {}) {
832
+ const proof = this.repository.getProof(id, opts);
833
+ if (!proof)
834
+ throw new Error(`proof ${id} not found`);
835
+ return proof;
836
+ }
837
+ card(id, opts = {}) {
838
+ const policy = this.get(id, opts);
839
+ if (!policy.proof)
840
+ throw new Error(`policy ${id} has no proof`);
841
+ const proof = this.proof(policy.proof, opts);
842
+ const dispositions = this.repository.listDispositions(opts).filter((record) => record.policy_id === policy.id && record.proof_id === proof.id);
843
+ const shadow = this.shadowReport(id, {}, opts);
844
+ return buildProofCard(policy, proof, dispositions, this.composition(policy, opts), shadow.counts.total || shadow.counts.stale_excluded ? shadow : null);
845
+ }
846
+ corpus(id, opts = {}) {
847
+ this.get(id, opts);
848
+ const corpus = this.repository.getCorpus(id, opts);
849
+ if (!corpus)
850
+ throw new Error(`policy ${id} has no imported proof corpus`);
851
+ return corpus;
852
+ }
853
+ importCorpus(id, raw, opts = {}) {
854
+ const policy = this.get(id);
855
+ const compiled = compileProofCorpus(this.root, policy, raw, opts);
856
+ const home = this.repository.homeOfPolicy(policy.id);
857
+ const existing = this.repository.getCorpus(policy.id, home === "public" ? { publicOnly: true } : { privateOnly: true });
858
+ return existing?.id === compiled.id ? existing : this.repository.putCorpus(compiled, policy.id);
859
+ }
860
+ compile(decisionId, opts = {}) {
861
+ const compiled = compileDecisionPolicy(this.store, decisionId, opts);
862
+ const home = compiled.private || this.store.unified
863
+ ? { privateOnly: true }
864
+ : { publicOnly: true };
865
+ const existing = this.repository.getPolicy(compiled.policy.id, home);
866
+ if (existing)
867
+ return existing;
868
+ return this.repository.putPolicy(compiled.policy, { private: compiled.private });
869
+ }
870
+ bootstrap(opts = {}) {
871
+ return opts.history
872
+ ? bootstrapStructuralPolicies(this.store, this.root, this.repository, opts)
873
+ : bootstrapPolicies(this.store, this.root, this.repository, opts);
874
+ }
875
+ inspectStructural(decisionId, opts = {}) {
876
+ return inspectStructuralDecision(this.store, this.root, decisionId, opts);
877
+ }
878
+ ingest(opts = {}) {
879
+ return ingestLocalEvidence(this.store, this.root, this.repository, opts);
880
+ }
881
+ plan(id, opts = {}) {
882
+ const policy = this.get(id, opts);
883
+ const home = opts.publicOnly ? "public" : opts.privateOnly ? "private" : this.repository.homeOfPolicy(id);
884
+ const homeOpts = home === "public" ? { publicOnly: true } : { privateOnly: true };
885
+ const composition = this.composition(policy, homeOpts);
886
+ const generated = createProofPlan(this.store, this.root, this.repository, policy, { ...opts, ...homeOpts, composition });
887
+ return this.repository.getPlan(generated.id, homeOpts)
888
+ ?? this.repository.putPlan(generated, policy.id, { private: home === "private", public: home === "public" });
889
+ }
890
+ prove(id, opts = {}) {
891
+ const policy = this.get(id);
892
+ const behaviorBindingError = executableBehaviorAttestationError(policy, this.g2BehaviorAttestationRepository.current());
893
+ if (behaviorBindingError)
894
+ throw new Error(behaviorBindingError);
895
+ const publicOnly = policy.data_class === "public" && this.repository.homeOfPolicy(id) === "public";
896
+ const homeOpts = publicOnly ? { publicOnly: true } : { privateOnly: true };
897
+ const composition = this.composition(policy, homeOpts);
898
+ const plan = this.plan(id, { publicOnly, now: opts.now });
899
+ const proof = provePolicy(this.store, this.root, policy, { publicOnly, now: opts.now, plan, composition });
900
+ this.repository.putProof(proof, policy.id);
901
+ const proposed = proposeProvedPolicy(policy, proof, opts.now ?? proof.generated_at, composition, this.g2BehaviorAttestationRepository.current());
902
+ return { policy: this.repository.putPolicy(proposed), proof };
903
+ }
904
+ approve(id, mode, actor, opts = {}) {
905
+ const policy = this.get(id);
906
+ const behaviorBindingError = executableBehaviorAttestationError(policy, this.g2BehaviorAttestationRepository.current());
907
+ if (behaviorBindingError)
908
+ throw new Error(behaviorBindingError);
909
+ if (!policy.proof)
910
+ throw new Error(`policy ${id} has no proof`);
911
+ const home = this.repository.homeOfPolicy(id);
912
+ const homeOpts = home === "public" ? { publicOnly: true } : { privateOnly: true };
913
+ const proof = this.proof(policy.proof, homeOpts);
914
+ const dispositions = this.repository.listDispositions(homeOpts).filter((record) => record.policy_id === id && record.proof_id === proof.id);
915
+ const composition = this.composition(policy, homeOpts);
916
+ const approved = approvePolicy(policy, proof, mode, actor, opts.now ?? new Date().toISOString(), dispositions, composition, this.g2BehaviorAttestationRepository.current());
917
+ return this.repository.putPolicy(approved);
918
+ }
919
+ historyDispositions(id, opts = {}) {
920
+ const policy = this.get(id, opts);
921
+ if (!policy.proof)
922
+ throw new Error(`policy ${id} has no proof`);
923
+ const proof = this.proof(policy.proof, opts);
924
+ const audit = this.repository.listDispositions(opts).filter((record) => record.policy_id === id && record.proof_id === proof.id);
925
+ const current = currentHistoryDispositions(audit);
926
+ const violations = proof.replay_receipts
927
+ .filter((receipt) => receipt.leg === "accepted_history" && receipt.result === "violated")
928
+ .map((receipt) => ({
929
+ commit: receipt.commit,
930
+ receipt_hash: receipt.deterministic_hash,
931
+ disposition: current.find((record) => record.commit === receipt.commit && record.receipt_hash === receipt.deterministic_hash) ?? null,
932
+ }));
933
+ return { policy_id: id, proof_id: proof.id, violations, current, audit };
934
+ }
935
+ classifyHistory(id, commit, classification, actor, reason, opts = {}) {
936
+ if (!/^[a-f0-9]{40}$/.test(commit))
937
+ throw new Error("history disposition commit must be a full lowercase 40-character SHA");
938
+ const policy = this.get(id);
939
+ if (!policy.proof)
940
+ throw new Error(`policy ${id} has no proof`);
941
+ const home = this.repository.homeOfPolicy(id);
942
+ const homeOpts = home === "public" ? { publicOnly: true } : { privateOnly: true };
943
+ const proof = this.proof(policy.proof, homeOpts);
944
+ const receipt = proof.replay_receipts.find((candidate) => candidate.leg === "accepted_history" && candidate.result === "violated" && candidate.commit === commit);
945
+ if (!receipt)
946
+ throw new Error(`proof ${proof.id} has no violated accepted-history receipt for commit ${commit}`);
947
+ const disposition = compileHistoryDisposition(policy, proof, receipt, classification, actor, reason, {
948
+ ...opts,
949
+ composition: this.composition(policy, homeOpts),
950
+ });
951
+ return this.repository.putDisposition(disposition, policy.id);
952
+ }
953
+ compileShadowRecord(id, opts = {}) {
954
+ const policy = this.get(id);
955
+ if (!policy.proof)
956
+ throw new Error(`policy ${id} has no proof`);
957
+ const home = this.repository.homeOfPolicy(id);
958
+ const homeOpts = home === "public" ? { publicOnly: true } : { privateOnly: true };
959
+ const proof = this.proof(policy.proof, homeOpts);
960
+ const composition = this.composition(policy, homeOpts);
961
+ const expectedHash = policyProofHash(policy, composition);
962
+ if (proof.policy_hash !== expectedHash)
963
+ throw new Error(`proof ${proof.id} does not match current policy semantics`);
964
+ const started = performance.now();
965
+ const currentHead = headSha(this.root);
966
+ let evaluation;
967
+ if (opts.commit && policy.assertion.kind === "executable-behavior") {
968
+ evaluation = evaluateExecutableBehaviorPolicy(this.root, policy, { commit: opts.commit });
969
+ }
970
+ else if (opts.commit && opts.commit !== currentHead) {
971
+ throw new Error(`historical shadow backfill supports executable-behavior policies only; ${policy.id} is ${policy.assertion.kind}`);
972
+ }
973
+ else {
974
+ evaluation = evaluatePolicy(this.store, this.root, policy, { publicOnly: home === "public", composition });
975
+ }
976
+ const latencyMs = opts.latencyMs ?? performance.now() - started;
977
+ const sameMatches = canonicalJson(evaluation.matches);
978
+ const alsoDetectedBy = evaluation.result === "violated"
979
+ && (!opts.commit || opts.commit === currentHead)
980
+ ? this.evaluate({ activeOnly: true, publicOnly: home === "public" })
981
+ .filter((candidate) => candidate.policy.id !== id
982
+ && candidate.evaluation.result === "violated"
983
+ && canonicalJson(candidate.evaluation.matches) === sameMatches)
984
+ .map((candidate) => candidate.policy.id)
985
+ : [];
986
+ const record = compileShadowEvaluation(policy, proof, expectedHash, evaluation, alsoDetectedBy, latencyMs, opts.now);
987
+ return record;
988
+ }
989
+ recordShadow(id, opts = {}) {
990
+ const record = this.compileShadowRecord(id, opts);
991
+ return this.repository.putShadowEvaluation(record, record.policy_id);
992
+ }
993
+ classifyShadow(id, shadowId, classification, actor, reason, opts = {}) {
994
+ const policy = this.get(id);
995
+ const home = this.repository.homeOfPolicy(id);
996
+ const homeOpts = home === "public" ? { publicOnly: true } : { privateOnly: true };
997
+ const evaluation = this.repository.listShadowEvaluations(homeOpts).find((record) => record.id === shadowId && record.policy_id === id);
998
+ if (!evaluation)
999
+ throw new Error(`shadow evaluation ${shadowId} not found for policy ${id}`);
1000
+ return this.repository.putShadowDisposition(compileShadowDisposition(evaluation, classification, actor, reason, opts), policy.id);
1001
+ }
1002
+ shadowReport(id, thresholds = {}, opts = {}) {
1003
+ const policy = this.get(id, opts);
1004
+ if (!policy.proof)
1005
+ throw new Error(`policy ${id} has no proof`);
1006
+ const proof = this.proof(policy.proof, opts);
1007
+ const records = this.repository.listShadowEvaluations(opts).filter((record) => record.policy_id === id);
1008
+ const audit = this.repository.listShadowDispositions(opts).filter((record) => record.policy_id === id);
1009
+ const current = currentShadowDispositions(audit);
1010
+ const history = this.repository.listDispositions(opts).filter((record) => record.policy_id === id && record.proof_id === proof.id);
1011
+ const scoringRecords = records.filter((record) => shadowCommitEligible(this.root, policy, record.evaluation.repository.head));
1012
+ const report = scoreShadowPrecision(policy, proof, scoringRecords, audit, history, thresholds);
1013
+ return {
1014
+ ...report,
1015
+ evaluations: records.map((record) => ({
1016
+ record,
1017
+ disposition: current.find((candidate) => candidate.shadow_id === record.id) ?? null,
1018
+ })),
1019
+ disposition_audit: audit,
1020
+ };
1021
+ }
1022
+ demote(id, actor, reason, opts = {}) {
1023
+ const policy = this.get(id);
1024
+ const demoted = demotePolicy(policy, actor, reason, opts.now ?? new Date().toISOString());
1025
+ return this.repository.putPolicy(demoted);
1026
+ }
1027
+ linkException(id, parentId, actor, reason, opts = {}) {
1028
+ const child = this.get(id);
1029
+ const parent = this.get(parentId);
1030
+ const childHome = this.repository.homeOfPolicy(child.id);
1031
+ const parentHome = this.repository.homeOfPolicy(parent.id);
1032
+ if (childHome !== parentHome)
1033
+ throw new Error("exception and parent must live in the same public/private policy home");
1034
+ const linked = linkPolicyException(child, parent, actor, reason, opts.now ?? new Date().toISOString());
1035
+ return this.repository.putPolicy(linked, { private: childHome === "private" });
1036
+ }
1037
+ relations(id, opts = {}) {
1038
+ const policy = this.get(id, opts);
1039
+ const policies = this.list(opts);
1040
+ const parent = policy.exception_of
1041
+ ? policies.find((candidate) => candidate.id === policy.exception_of)
1042
+ : undefined;
1043
+ return {
1044
+ policy: relationSummary(policy),
1045
+ exception_parent: parent ? relationSummary(parent) : null,
1046
+ missing_exception_parent: policy.exception_of && !parent ? policy.exception_of : null,
1047
+ exceptions: policies
1048
+ .filter((candidate) => candidate.exception_of === policy.id)
1049
+ .map(relationSummary),
1050
+ };
1051
+ }
1052
+ consolidation(id, opts = {}) {
1053
+ const anchor = this.get(id, opts);
1054
+ const suggestion = anchor.candidate.scope_suggestion;
1055
+ if (!suggestion) {
1056
+ return {
1057
+ anchor: relationSummary(anchor),
1058
+ suggested_scope: null,
1059
+ status: "unavailable",
1060
+ members: [],
1061
+ independent_decisions: [],
1062
+ counterexamples: [],
1063
+ conflicts: [],
1064
+ exception_linked_members: [],
1065
+ reasons: ["Policy has no compiler-produced advisory scope suggestion; consolidation is unavailable."],
1066
+ };
1067
+ }
1068
+ if (anchor.exception_of) {
1069
+ return {
1070
+ anchor: relationSummary(anchor),
1071
+ suggested_scope: suggestion,
1072
+ status: "unavailable",
1073
+ members: [],
1074
+ independent_decisions: [],
1075
+ counterexamples: [],
1076
+ conflicts: [],
1077
+ exception_linked_members: [anchor.id],
1078
+ reasons: ["An explicit exception policy cannot anchor consolidation until combined exception semantics are implemented and proved."],
1079
+ };
1080
+ }
1081
+ const assertion = canonicalJson(anchor.assertion);
1082
+ const policies = this.list(opts);
1083
+ const members = policies
1084
+ .filter((candidate) => candidate.data_class === anchor.data_class)
1085
+ .filter((candidate) => canonicalJson(candidate.assertion) === assertion)
1086
+ .filter((candidate) => candidate.exception_of === null)
1087
+ .filter((candidate) => scopeFallsWithin(candidate.scope, suggestion));
1088
+ const independentDecisions = [...new Set(members.flatMap((candidate) => candidate.legacy_refs.filter((ref) => ref.startsWith("dec_"))))].sort();
1089
+ const counterexamples = [...new Set(members.flatMap((candidate) => candidate.candidate.counterexamples))].sort();
1090
+ const conflicts = [...new Set(members.flatMap((candidate) => candidate.candidate.conflicts))].sort();
1091
+ const exceptionLinkedMembers = members
1092
+ .filter((candidate) => policies.some((other) => other.exception_of === candidate.id))
1093
+ .map((candidate) => candidate.id);
1094
+ const active = members.filter((candidate) => candidate.state === "active_advisory" || candidate.state === "active_blocking").map((candidate) => candidate.id);
1095
+ const reasons = [
1096
+ "Candidate is an advisory review packet only; no policy is merged, widened, evaluated, activated, or enforced.",
1097
+ ...(members.length < 3 ? [`Only ${members.length} matching narrow policy record(s) fall within the suggested scope; at least three are required.`] : []),
1098
+ ...(independentDecisions.length < 3 ? [`Only ${independentDecisions.length} independent decision reference(s) support the group; at least three are required.`] : []),
1099
+ ...(counterexamples.length ? [`${counterexamples.length} counterexample signal(s) challenge broader consolidation.`] : []),
1100
+ ...(conflicts.length ? [`${conflicts.length} direct conflict signal(s) require human disposition first.`] : []),
1101
+ ...(exceptionLinkedMembers.length ? [`Exception-linked policy member(s) require combined exception semantics first: ${exceptionLinkedMembers.join(", ")}.`] : []),
1102
+ ...(active.length ? [`Active policy member(s) require explicit human migration rather than consolidation: ${active.join(", ")}.`] : []),
1103
+ ];
1104
+ const reviewable = members.length >= 3
1105
+ && independentDecisions.length >= 3
1106
+ && counterexamples.length === 0
1107
+ && conflicts.length === 0
1108
+ && exceptionLinkedMembers.length === 0
1109
+ && active.length === 0;
1110
+ return {
1111
+ anchor: relationSummary(anchor),
1112
+ suggested_scope: suggestion,
1113
+ status: reviewable ? "reviewable" : "challenged",
1114
+ members: members.map(relationSummary),
1115
+ independent_decisions: independentDecisions,
1116
+ counterexamples,
1117
+ conflicts,
1118
+ exception_linked_members: exceptionLinkedMembers,
1119
+ reasons,
1120
+ };
1121
+ }
1122
+ evaluate(opts = {}) {
1123
+ let policies = opts.id ? [this.get(opts.id, opts)] : this.list(opts);
1124
+ if (opts.activeOnly)
1125
+ policies = policies.filter(policyIsActive);
1126
+ return policies.map((policy) => {
1127
+ const composition = this.composition(policy, opts);
1128
+ const evaluation = evaluatePolicy(this.store, this.root, policy, { publicOnly: opts.publicOnly, composition, behavior: opts.behavior });
1129
+ let proof;
1130
+ let dispositions = [];
1131
+ if (policy.state === "active_blocking" && policy.proof) {
1132
+ const home = opts.publicOnly ? "public" : this.repository.homeOfPolicy(policy.id);
1133
+ const homeOpts = home === "public" ? { publicOnly: true } : { privateOnly: true };
1134
+ proof = this.repository.getProof(policy.proof, homeOpts);
1135
+ dispositions = this.repository.listDispositions(homeOpts).filter((record) => record.policy_id === policy.id && record.proof_id === policy.proof);
1136
+ }
1137
+ const behaviorBindingError = executableBehaviorAttestationError(policy, this.g2BehaviorAttestationRepository.current());
1138
+ const gateError = behaviorBindingError ?? blockingProofError(policy, proof, dispositions, composition, this.g2BehaviorAttestationRepository.current());
1139
+ return {
1140
+ policy,
1141
+ evaluation,
1142
+ blocks: !gateError && policyBlocks(policy, evaluation),
1143
+ strict_error: policy.state === "active_blocking" && (evaluation.result === "error" || !!gateError),
1144
+ ...(gateError ? { gate_error: gateError } : {}),
1145
+ };
1146
+ });
1147
+ }
1148
+ }
1149
+ //# sourceMappingURL=service.js.map