@aiwg/cli 2026.9.20 → 2026.9.21

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 (272) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +58 -6
  2. package/agentic/code/providers/model-capabilities.v1.json +34 -0
  3. package/agentic/code/providers/model-catalog.v1.json +55 -13
  4. package/bin/aiwg.mjs +2 -0
  5. package/dist/src/api/index.d.ts +1 -0
  6. package/dist/src/api/index.js +1 -0
  7. package/dist/src/artifacts/enrichment/cli.js +1 -1
  8. package/dist/src/artifacts/views/cli.js +1 -1
  9. package/dist/src/audit/operator-decision.js +10 -0
  10. package/dist/src/auth/credential-store.js +127 -27
  11. package/dist/src/catalog/builtin-models.json +272 -40
  12. package/dist/src/catalog/cli.js +1 -1
  13. package/dist/src/catalog/cli.mjs +1 -1
  14. package/dist/src/cli/handlers/effect.js +872 -0
  15. package/dist/src/cli/handlers/help.js +2 -0
  16. package/dist/src/cli/handlers/index.js +4 -2
  17. package/dist/src/cli/handlers/models.js +41 -10
  18. package/dist/src/cli/handlers/ralph.js +1 -1
  19. package/dist/src/cli/handlers/sessions.js +113 -37
  20. package/dist/src/cli/handlers/subcommands.js +31 -0
  21. package/dist/src/cli/handlers/use.js +46 -9
  22. package/dist/src/cli/handlers/utilities.js +10 -0
  23. package/dist/src/cli/handlers/workspace.js +3 -1
  24. package/dist/src/cli/scope-resolver.js +112 -9
  25. package/dist/src/cli/services/deployment-verification.js +5 -0
  26. package/dist/src/config/aiwg-config.js +3 -0
  27. package/dist/src/decision/acceptance-rollout.js +93 -0
  28. package/dist/src/decision/acceptance.js +175 -0
  29. package/dist/src/decision/adapters/jev.js +234 -13
  30. package/dist/src/decision/adapters/llm-subagent.js +60 -12
  31. package/dist/src/decision/admission.js +550 -0
  32. package/dist/src/decision/batch-benchmark.js +47 -0
  33. package/dist/src/decision/batch-receipts/accounting.js +75 -0
  34. package/dist/src/decision/batch-receipts/export.js +40 -0
  35. package/dist/src/decision/batch-receipts/index.js +9 -0
  36. package/dist/src/decision/batch-receipts/protection.js +40 -0
  37. package/dist/src/decision/batch-receipts/receipt.js +44 -0
  38. package/dist/src/decision/batch-receipts/result-store.js +341 -0
  39. package/dist/src/decision/batch-receipts/store.js +362 -0
  40. package/dist/src/decision/batch-receipts/types.js +2 -0
  41. package/dist/src/decision/batch-receipts/validate.js +127 -0
  42. package/dist/src/decision/batch.js +68 -0
  43. package/dist/src/decision/calibration/index.js +4 -0
  44. package/dist/src/decision/calibration/receipts.js +130 -0
  45. package/dist/src/decision/calibration/registry.js +258 -0
  46. package/dist/src/decision/calibration/types.js +2 -0
  47. package/dist/src/decision/compile-cache/benchmark.js +62 -0
  48. package/dist/src/decision/compile-cache/compatibility.js +65 -0
  49. package/dist/src/decision/compile-cache/file-store.js +280 -0
  50. package/dist/src/decision/compile-cache/identity.js +26 -0
  51. package/dist/src/decision/compile-cache/index.js +9 -0
  52. package/dist/src/decision/compile-cache/prefix.js +21 -0
  53. package/dist/src/decision/compile-cache/records.js +86 -0
  54. package/dist/src/decision/compile-cache/runtime.js +68 -0
  55. package/dist/src/decision/compile-cache/store.js +108 -0
  56. package/dist/src/decision/compile-cache/types.js +2 -0
  57. package/dist/src/decision/context-plan.js +234 -0
  58. package/dist/src/decision/context-qualification.js +60 -0
  59. package/dist/src/decision/ensemble/aggregate.js +215 -0
  60. package/dist/src/decision/ensemble/contract.js +316 -0
  61. package/dist/src/decision/ensemble/drift.js +51 -0
  62. package/dist/src/decision/ensemble/index.js +6 -0
  63. package/dist/src/decision/ensemble/report.js +71 -0
  64. package/dist/src/decision/ensemble/types.js +2 -0
  65. package/dist/src/decision/evaluate.js +1024 -78
  66. package/dist/src/decision/file-lifecycle-store.js +168 -0
  67. package/dist/src/decision/graph-beam.js +40 -0
  68. package/dist/src/decision/graph-budget.js +110 -0
  69. package/dist/src/decision/graph-decision-bridge.js +93 -0
  70. package/dist/src/decision/graph-evidence.js +123 -0
  71. package/dist/src/decision/graph-flow-adapter.js +56 -0
  72. package/dist/src/decision/graph-flow.js +105 -0
  73. package/dist/src/decision/graph-parallel.js +79 -0
  74. package/dist/src/decision/graph-patterns.js +20 -0
  75. package/dist/src/decision/graph-run-store.js +66 -0
  76. package/dist/src/decision/graph-run.js +166 -0
  77. package/dist/src/decision/graph-skill-bridge.js +91 -0
  78. package/dist/src/decision/graph-templates.js +56 -0
  79. package/dist/src/decision/graph.js +115 -0
  80. package/dist/src/decision/index.js +35 -0
  81. package/dist/src/decision/job-accounting.js +93 -0
  82. package/dist/src/decision/job-contract.js +186 -0
  83. package/dist/src/decision/job-effects.js +95 -0
  84. package/dist/src/decision/job-evaluate.js +80 -0
  85. package/dist/src/decision/job-gateway.js +121 -0
  86. package/dist/src/decision/job-http.js +194 -0
  87. package/dist/src/decision/job-payload-store.js +258 -0
  88. package/dist/src/decision/job-poll-limiter.js +92 -0
  89. package/dist/src/decision/job-quota.js +158 -0
  90. package/dist/src/decision/job-runtime.js +240 -0
  91. package/dist/src/decision/job-scheduler.js +26 -0
  92. package/dist/src/decision/job-service.js +117 -0
  93. package/dist/src/decision/job-store.js +264 -0
  94. package/dist/src/decision/job-worker.js +141 -0
  95. package/dist/src/decision/lifecycle.js +148 -0
  96. package/dist/src/decision/patterns/artifacts.js +173 -0
  97. package/dist/src/decision/patterns/catalog.js +108 -0
  98. package/dist/src/decision/patterns/durable-review.js +237 -0
  99. package/dist/src/decision/patterns/index.js +8 -0
  100. package/dist/src/decision/patterns/playground.js +401 -0
  101. package/dist/src/decision/patterns/recorded.js +43 -0
  102. package/dist/src/decision/patterns/specs.js +174 -0
  103. package/dist/src/decision/patterns/types.js +2 -0
  104. package/dist/src/decision/portable-secrets.js +53 -0
  105. package/dist/src/decision/projection.js +210 -0
  106. package/dist/src/decision/qualification/capture.js +67 -0
  107. package/dist/src/decision/qualification/drift.js +68 -0
  108. package/dist/src/decision/qualification/gate-evidence.js +172 -0
  109. package/dist/src/decision/qualification/gates.js +83 -0
  110. package/dist/src/decision/qualification/index.js +12 -0
  111. package/dist/src/decision/qualification/load.js +275 -0
  112. package/dist/src/decision/qualification/manifest.js +37 -0
  113. package/dist/src/decision/qualification/privacy.js +41 -0
  114. package/dist/src/decision/qualification/quality.js +213 -0
  115. package/dist/src/decision/qualification/release.js +116 -0
  116. package/dist/src/decision/qualification/runner.js +408 -0
  117. package/dist/src/decision/qualification/types.js +2 -0
  118. package/dist/src/decision/receipts.js +30 -7
  119. package/dist/src/decision/result-cache/index.js +6 -0
  120. package/dist/src/decision/result-cache/integrity.js +28 -0
  121. package/dist/src/decision/result-cache/key.js +28 -0
  122. package/dist/src/decision/result-cache/service.js +92 -0
  123. package/dist/src/decision/result-cache/store.js +327 -0
  124. package/dist/src/decision/result-cache/types.js +3 -0
  125. package/dist/src/decision/review/authorization.js +110 -0
  126. package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
  127. package/dist/src/decision/review/index.js +12 -0
  128. package/dist/src/decision/review/migrate.js +34 -0
  129. package/dist/src/decision/review/operator-audit.js +59 -0
  130. package/dist/src/decision/review/recovery-adapters.js +252 -0
  131. package/dist/src/decision/review/recovery.js +33 -0
  132. package/dist/src/decision/review/service.js +498 -0
  133. package/dist/src/decision/review/store.js +219 -0
  134. package/dist/src/decision/review/types.js +2 -0
  135. package/dist/src/decision/review/validate.js +205 -0
  136. package/dist/src/decision/scheduler.js +115 -0
  137. package/dist/src/decision/telemetry/context.js +42 -0
  138. package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
  139. package/dist/src/decision/telemetry/exporter.js +77 -0
  140. package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
  141. package/dist/src/decision/telemetry/index.js +13 -0
  142. package/dist/src/decision/telemetry/mapping.js +146 -0
  143. package/dist/src/decision/telemetry/metrics.js +164 -0
  144. package/dist/src/decision/telemetry/otlp-http.js +183 -0
  145. package/dist/src/decision/telemetry/redaction.js +93 -0
  146. package/dist/src/decision/telemetry/retention.js +109 -0
  147. package/dist/src/decision/telemetry/runtime.js +310 -0
  148. package/dist/src/decision/telemetry/trace.js +158 -0
  149. package/dist/src/decision/telemetry/types.js +3 -0
  150. package/dist/src/decision/validate.js +135 -5
  151. package/dist/src/effects/checkpoint-sinks.js +94 -0
  152. package/dist/src/effects/checkpoint.js +76 -0
  153. package/dist/src/effects/decision-links.js +54 -0
  154. package/dist/src/effects/errors.js +48 -0
  155. package/dist/src/effects/identity.js +178 -0
  156. package/dist/src/effects/index.js +25 -0
  157. package/dist/src/effects/keyring.js +160 -0
  158. package/dist/src/effects/keys.js +130 -0
  159. package/dist/src/effects/ledger.js +435 -0
  160. package/dist/src/effects/lifecycle.js +110 -0
  161. package/dist/src/effects/lock-recovery.js +269 -0
  162. package/dist/src/effects/reader.js +103 -0
  163. package/dist/src/effects/records.js +114 -0
  164. package/dist/src/effects/redaction.js +51 -0
  165. package/dist/src/effects/schema.js +73 -0
  166. package/dist/src/effects/store.js +239 -0
  167. package/dist/src/effects/types.js +49 -0
  168. package/dist/src/effects/verifiers/decision.js +168 -0
  169. package/dist/src/effects/verifiers/file.js +106 -0
  170. package/dist/src/effects/verifiers/git.js +206 -0
  171. package/dist/src/effects/verifiers/index.js +90 -0
  172. package/dist/src/effects/verifiers/registry.js +148 -0
  173. package/dist/src/effects/verifiers/review.js +123 -0
  174. package/dist/src/effects/verifiers/tracker.js +299 -0
  175. package/dist/src/effects/verifiers/types.js +21 -0
  176. package/dist/src/effects/verify.js +295 -0
  177. package/dist/src/evidence/bundle.js +2 -0
  178. package/dist/src/extensions/commands/definitions.js +28 -0
  179. package/dist/src/extensions/manifest.js +1 -0
  180. package/dist/src/marketplace/provenance.js +25 -27
  181. package/dist/src/mcp/cli.mjs +11 -1
  182. package/dist/src/mcp/grok-build-config.mjs +189 -0
  183. package/dist/src/mcp/registry.js +21 -1
  184. package/dist/src/mcp/registry.mjs +20 -1
  185. package/dist/src/models/grok-build-models.js +356 -0
  186. package/dist/src/models/index.js +1 -0
  187. package/dist/src/models/model-capabilities.v1.json +34 -0
  188. package/dist/src/models/model-catalog.v1.json +55 -13
  189. package/dist/src/models/model-discovery.js +41 -5
  190. package/dist/src/models/provider-policy.js +3 -2
  191. package/dist/src/plugin/skill-command-translator.js +1 -0
  192. package/dist/src/providers/capability-matrix.yaml +58 -6
  193. package/dist/src/providers/grok-build-acp.js +192 -0
  194. package/dist/src/providers/grok-build-dispatch.js +251 -0
  195. package/dist/src/providers/grok-build-headless.js +170 -0
  196. package/dist/src/providers/muse-paths.js +105 -0
  197. package/dist/src/providers/provider-definitions.js +93 -7
  198. package/dist/src/providers/provider-definitions.mjs +21 -1
  199. package/dist/src/providers/provider-inventory.js +3 -0
  200. package/dist/src/security/artifact-attestation.js +4 -8
  201. package/dist/src/security/signing.js +119 -0
  202. package/dist/src/sessions/adapters/grok-build.js +187 -0
  203. package/dist/src/sessions/adapters/muse.js +412 -0
  204. package/dist/src/sessions/contracts.js +1 -1
  205. package/dist/src/sessions/grok-build-cli.js +196 -0
  206. package/dist/src/sessions/index.js +3 -0
  207. package/dist/src/sessions/workspace-discovery.js +9 -5
  208. package/dist/src/skills/deployer.js +33 -5
  209. package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
  210. package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
  211. package/dist/src/smiths/context-pipeline/finalization.js +1 -32
  212. package/dist/src/smiths/context-pipeline/generator.js +12 -0
  213. package/dist/src/smiths/context-pipeline/index.js +1 -1
  214. package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
  215. package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
  216. package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
  217. package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
  218. package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
  219. package/dist/src/smiths/hook-bridge/index.js +3 -0
  220. package/dist/src/smiths/hook-bridge/shim.js +2 -0
  221. package/dist/src/smiths/hook-bridge/types.js +5 -0
  222. package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
  223. package/dist/src/storage/protected-files.js +102 -0
  224. package/dist/src/tracker/forge-http.js +371 -0
  225. package/dist/src/tracker/project-inputs.js +41 -0
  226. package/package.json +2 -1
  227. package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
  228. package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
  229. package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
  230. package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
  231. package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
  232. package/schemas/decision/DecisionBinding.schema.json +309 -0
  233. package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
  234. package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
  235. package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
  236. package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
  237. package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
  238. package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
  239. package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
  240. package/schemas/decision/DecisionDefinition.schema.json +159 -0
  241. package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
  242. package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
  243. package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
  244. package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
  245. package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
  246. package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
  247. package/schemas/decision/DecisionJob.v1.schema.json +71 -0
  248. package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
  249. package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
  250. package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
  251. package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
  252. package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
  253. package/schemas/decision/DecisionResult.schema.json +524 -0
  254. package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
  255. package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
  256. package/schemas/decision/DecisionReview.schema.json +29 -0
  257. package/schemas/decision/DecisionRuleset.schema.json +313 -0
  258. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
  259. package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
  260. package/schemas/decision/RulesetResult.schema.json +721 -0
  261. package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
  262. package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
  263. package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
  264. package/tools/agents/deploy-agents.mjs +21 -6
  265. package/tools/agents/providers/base.mjs +7 -4
  266. package/tools/agents/providers/codex.mjs +1 -1
  267. package/tools/agents/providers/grok-build.d.mts +7 -0
  268. package/tools/agents/providers/grok-build.mjs +249 -9
  269. package/tools/agents/providers/model-role.mjs +6 -3
  270. package/tools/agents/providers/muse-hooks.mjs +785 -0
  271. package/tools/agents/providers/muse-paths.mjs +112 -0
  272. package/tools/agents/providers/muse.mjs +336 -0
@@ -0,0 +1,219 @@
1
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
2
+ import { link, mkdir, open, readFile, readdir, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { canonicalJson } from '../../security/artifact-trust.js';
5
+ import { assertImmutable, ReviewIntegrityError, validateReview } from './validate.js';
6
+ /**
7
+ * Revisions live in one directory per (tenant, project) scope, named by a keyed
8
+ * digest, so reading or listing a scope never opens another scope's files.
9
+ */
10
+ export class FileDecisionReviewStore {
11
+ directory;
12
+ integrityKey;
13
+ options;
14
+ constructor(directory, integrityKey, options = {}) {
15
+ this.directory = directory;
16
+ this.integrityKey = integrityKey;
17
+ this.options = options;
18
+ if (integrityKey.length < 32)
19
+ throw new Error('Review integrity key must be at least 32 bytes');
20
+ }
21
+ prefix(id) { return createHash('sha256').update(id).digest('hex'); }
22
+ /** Keyed so a directory name cannot be confirmed from guessed tenant or project names. */
23
+ scopeDirectory(tenantId, projectId) {
24
+ return join(this.directory, createHmac('sha256', this.integrityKey)
25
+ .update(`decision-review-scope/v1\0${canonicalJson([tenantId, projectId])}`).digest('hex'));
26
+ }
27
+ path(scope, id, revision) { return join(scope, `${this.prefix(id)}.r${revision}.json`); }
28
+ markerPath(scope, id) { return join(scope, `${this.prefix(id)}.purged.json`); }
29
+ mac(value) { return createHmac('sha256', this.integrityKey).update(canonicalJson(value)).digest('hex'); }
30
+ async names(scope) {
31
+ try {
32
+ return (await readdir(scope)).sort();
33
+ }
34
+ catch (error) {
35
+ if (error.code === 'ENOENT')
36
+ return [];
37
+ throw error;
38
+ }
39
+ }
40
+ async marker(scope, idDigest) {
41
+ let raw;
42
+ try {
43
+ raw = await readFile(join(scope, `${idDigest}.purged.json`), 'utf8');
44
+ }
45
+ catch (error) {
46
+ if (error.code === 'ENOENT')
47
+ return null;
48
+ throw error;
49
+ }
50
+ try {
51
+ const envelope = JSON.parse(raw);
52
+ const expected = Buffer.from(this.mac(envelope.receipt), 'hex');
53
+ const actual = Buffer.from(envelope.mac ?? '', 'hex');
54
+ if (actual.length !== expected.length || !timingSafeEqual(actual, expected) ||
55
+ envelope.receipt.reviewIdDigest !== `sha256:${idDigest}` ||
56
+ !Number.isSafeInteger(envelope.receipt.lastRevision) || envelope.receipt.lastRevision < 1)
57
+ throw new Error();
58
+ return envelope.receipt;
59
+ }
60
+ catch {
61
+ throw new ReviewIntegrityError('Review purge marker integrity check failed');
62
+ }
63
+ }
64
+ async read(reviewId, tenantId, projectId) {
65
+ const scope = this.scopeDirectory(tenantId, projectId);
66
+ const review = await this.readChain(scope, this.prefix(reviewId), await this.names(scope));
67
+ if (review && review.reviewId !== reviewId)
68
+ throw new ReviewIntegrityError('Review revision identity mismatch');
69
+ return review && review.tenantId === tenantId && review.projectId === projectId ? review : null;
70
+ }
71
+ /** Verify one review's revision chain using names already listed from its scope directory. */
72
+ async readChain(scope, idDigest, names) {
73
+ if (await this.marker(scope, idDigest))
74
+ return null;
75
+ const prefix = `${idDigest}.r`;
76
+ const revisions = names.filter(name => name.startsWith(prefix) && name.endsWith('.json')).map(name => Number(name.slice(prefix.length, -5))).sort((a, b) => a - b);
77
+ if (!revisions.length)
78
+ return null;
79
+ let previous = null;
80
+ for (let i = 0; i < revisions.length; i += 1) {
81
+ if (revisions[i] !== i + 1)
82
+ throw new ReviewIntegrityError('Review revision gap');
83
+ const envelope = JSON.parse(await readFile(join(scope, `${prefix}${revisions[i]}.json`), 'utf8'));
84
+ const expected = Buffer.from(this.mac(envelope.review), 'hex');
85
+ const actual = Buffer.from(envelope.mac ?? '', 'hex');
86
+ if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
87
+ throw new ReviewIntegrityError('Review integrity check failed');
88
+ validateReview(envelope.review);
89
+ if (envelope.review.revision !== revisions[i] || this.prefix(envelope.review.reviewId) !== idDigest)
90
+ throw new ReviewIntegrityError('Review revision identity mismatch');
91
+ if (previous)
92
+ assertImmutable(previous, envelope.review);
93
+ previous = envelope.review;
94
+ }
95
+ return structuredClone(previous);
96
+ }
97
+ async create(review) {
98
+ validateReview(review);
99
+ if (await this.marker(this.scopeDirectory(review.tenantId, review.projectId), this.prefix(review.reviewId)))
100
+ return false;
101
+ return this.publish(review);
102
+ }
103
+ async list(tenantId, projectId) {
104
+ // Enumerate and open only this scope's directory, in sorted digest order.
105
+ const scope = this.scopeDirectory(tenantId, projectId);
106
+ const names = await this.names(scope);
107
+ const digests = [...new Set(names.flatMap(name => /^([0-9a-f]{64})\.r\d+\.json$/.exec(name)?.[1] ?? []))];
108
+ const reviews = [];
109
+ for (const digest of digests) {
110
+ const review = await this.readChain(scope, digest, names);
111
+ if (review && review.tenantId === tenantId && review.projectId === projectId)
112
+ reviews.push(review);
113
+ }
114
+ return reviews.sort((a, b) => a.reviewId.localeCompare(b.reviewId));
115
+ }
116
+ async compareAndSwap(reviewId, tenantId, projectId, expectedRevision, next) {
117
+ const current = await this.read(reviewId, tenantId, projectId);
118
+ if (!current)
119
+ throw new ReviewIntegrityError('Missing review');
120
+ if (current.revision !== expectedRevision)
121
+ return false;
122
+ assertImmutable(current, next);
123
+ return this.publish(next);
124
+ }
125
+ async purgeTombstoned(reviewId, tenantId, projectId) {
126
+ const scope = this.scopeDirectory(tenantId, projectId);
127
+ let receipt = await this.marker(scope, this.prefix(reviewId));
128
+ if (!receipt) {
129
+ const review = await this.read(reviewId, tenantId, projectId);
130
+ if (!review || review.status !== 'tombstoned' || review.lifecycle?.legalHold)
131
+ throw new ReviewIntegrityError('Review purge requires an unheld tombstone');
132
+ receipt = {
133
+ reviewIdDigest: `sha256:${this.prefix(reviewId)}`, tenantDigest: `sha256:${this.prefix(tenantId)}`,
134
+ projectDigest: `sha256:${this.prefix(projectId)}`, lastRevision: review.revision,
135
+ finalReviewMac: this.mac(review),
136
+ };
137
+ const destination = this.markerPath(scope, reviewId);
138
+ const temporary = `${destination}.${randomUUID()}.tmp`;
139
+ const file = await open(temporary, 'wx', 0o600);
140
+ try {
141
+ await file.writeFile(`${JSON.stringify({ receipt, mac: this.mac(receipt) })}\n`);
142
+ await file.sync();
143
+ }
144
+ finally {
145
+ await file.close();
146
+ }
147
+ try {
148
+ await link(temporary, destination);
149
+ const dir = await open(scope, 'r');
150
+ try {
151
+ await dir.sync();
152
+ }
153
+ finally {
154
+ await dir.close();
155
+ }
156
+ }
157
+ catch (error) {
158
+ if (error.code !== 'EEXIST')
159
+ throw error;
160
+ receipt = (await this.marker(scope, this.prefix(reviewId)));
161
+ }
162
+ finally {
163
+ await rm(temporary, { force: true });
164
+ }
165
+ }
166
+ if (receipt.tenantDigest !== `sha256:${this.prefix(tenantId)}` || receipt.projectDigest !== `sha256:${this.prefix(projectId)}`) {
167
+ throw new ReviewIntegrityError('Review purge scope mismatch');
168
+ }
169
+ this.options.fault?.('purge-marker-published');
170
+ const names = await this.names(scope);
171
+ for (const name of names.filter(item => new RegExp(`^${this.prefix(reviewId)}\\.r[0-9]+\\.json$`).test(item))) {
172
+ await rm(join(scope, name));
173
+ }
174
+ const dir = await open(scope, 'r');
175
+ try {
176
+ await dir.sync();
177
+ }
178
+ finally {
179
+ await dir.close();
180
+ }
181
+ return receipt;
182
+ }
183
+ async publish(review) {
184
+ const scope = this.scopeDirectory(review.tenantId, review.projectId);
185
+ await mkdir(scope, { recursive: true, mode: 0o700 });
186
+ const destination = this.path(scope, review.reviewId, review.revision);
187
+ const temporary = `${destination}.${randomUUID()}.tmp`;
188
+ const file = await open(temporary, 'wx', 0o600);
189
+ try {
190
+ await file.writeFile(`${JSON.stringify({ review, mac: this.mac(review) })}\n`);
191
+ await file.sync();
192
+ }
193
+ finally {
194
+ await file.close();
195
+ }
196
+ try {
197
+ this.options.fault?.('review-before-publication');
198
+ await link(temporary, destination);
199
+ const dir = await open(scope, 'r');
200
+ try {
201
+ await dir.sync();
202
+ }
203
+ finally {
204
+ await dir.close();
205
+ }
206
+ this.options.fault?.('review-after-publication');
207
+ return true;
208
+ }
209
+ catch (error) {
210
+ if (error.code === 'EEXIST')
211
+ return false;
212
+ throw error;
213
+ }
214
+ finally {
215
+ await rm(temporary, { force: true });
216
+ }
217
+ }
218
+ }
219
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,2 @@
1
+ export const DECISION_REVIEW_API_VERSION = 'decision.aiwg.io/v1alpha1';
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,205 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from '../../security/artifact-trust.js';
3
+ export class ReviewIntegrityError extends Error {
4
+ }
5
+ export class ReviewAccessError extends Error {
6
+ }
7
+ export class ReviewConflictError extends Error {
8
+ }
9
+ export function reviewDigest(value) {
10
+ return `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
11
+ }
12
+ export function reviewOperatorEventId(reviewId, sequence) {
13
+ return reviewDigest({ reviewId, sequence, schema: 'operator-decision.aiwg.io/v1' });
14
+ }
15
+ const digestPattern = /^sha256:[a-f0-9]{64}$/;
16
+ const restrictedKey = /(?:password|credential|api.?key|secret|private.?reasoning|provider.?body|raw.?state|vault.?locator)/i;
17
+ const restrictedValue = /(?:vault:\/\/|\b(?:sk-(?:test-)?[a-z0-9_-]{12,}|ghp_[a-z0-9]{12,})\b)/i;
18
+ /** Enforce reference-only default review payloads; do not echo rejected material. */
19
+ export function assertReviewProjection(value) {
20
+ if (typeof value === 'string') {
21
+ if (restrictedValue.test(value))
22
+ throw new ReviewIntegrityError('Restricted review payload');
23
+ }
24
+ else if (Array.isArray(value)) {
25
+ value.forEach(assertReviewProjection);
26
+ }
27
+ else if (value && typeof value === 'object') {
28
+ for (const [key, child] of Object.entries(value)) {
29
+ if (restrictedKey.test(key))
30
+ throw new ReviewIntegrityError('Restricted review payload');
31
+ assertReviewProjection(child);
32
+ }
33
+ }
34
+ }
35
+ const legalEvents = new Set(['created', 'claimed', 'approved', 'rejected', 'edited', 'expired', 'escalated', 'canceled', 'resumed', 'execution-completed', 'execution-failed', 'legal-hold-placed', 'legal-hold-released', 'tombstoned', 'authorization-denied', 'sensitive-view-accessed']);
36
+ export function currentProposal(review) {
37
+ const proposal = review.proposals.at(-1);
38
+ if (!proposal)
39
+ throw new ReviewIntegrityError('Review has no proposal');
40
+ return proposal;
41
+ }
42
+ export function validateReview(review) {
43
+ if (!review || review.apiVersion !== 'decision.aiwg.io/v1alpha1' || review.kind !== 'DecisionReview' || review.schema !== 'decision-review/v1'
44
+ || !Number.isSafeInteger(review.revision) || review.revision < 1 || !review.reviewId || !review.tenantId || !review.projectId
45
+ || !digestPattern.test(review.sourceReceipt.digest) || !digestPattern.test(review.continuation.tokenDigest)
46
+ || !Number.isSafeInteger(review.quorum) || review.quorum < 1 || review.quorum > 16
47
+ || !Number.isSafeInteger(review.createdAtEpochMs) || review.updatedAtEpochMs < review.createdAtEpochMs
48
+ || review.expiresAtEpochMs <= review.createdAtEpochMs ||
49
+ (review.retentionUntilEpochMs !== undefined && (!Number.isSafeInteger(review.retentionUntilEpochMs) || review.retentionUntilEpochMs < review.expiresAtEpochMs)) ||
50
+ !Array.isArray(review.proposals) || !review.proposals.length
51
+ || !Array.isArray(review.events) || !review.events.length)
52
+ throw new ReviewIntegrityError('Invalid review envelope');
53
+ if (review.lifecycle && (typeof review.lifecycle.legalHold !== 'boolean'
54
+ || (review.lifecycle.tombstonedAtEpochMs !== undefined && !Number.isSafeInteger(review.lifecycle.tombstonedAtEpochMs)))) {
55
+ throw new ReviewIntegrityError('Invalid review lifecycle');
56
+ }
57
+ assertReviewProjection(review.presentation);
58
+ assertReviewProjection(review.reviewId);
59
+ assertReviewProjection(review.requesterId);
60
+ assertReviewProjection(review.sourceReceipt.id);
61
+ assertReviewProjection(review.evidencePins);
62
+ assertReviewProjection(review.policyPins);
63
+ review.proposals.forEach((proposal, index) => {
64
+ assertReviewProjection(proposal.editor);
65
+ assertReviewProjection(proposal.action);
66
+ assertReviewProjection(proposal.rationale);
67
+ if (proposal.version !== index + 1 || proposal.actionDigest !== reviewDigest(proposal.action))
68
+ throw new ReviewIntegrityError('Invalid proposal lineage');
69
+ });
70
+ review.decisions.forEach(decision => { assertReviewProjection(decision.reviewer); assertReviewProjection(decision.rationale); });
71
+ review.events.forEach(event => { assertReviewProjection(event.actor); assertReviewProjection(event.rationale); });
72
+ if (review.effectReceipt)
73
+ assertReviewProjection(review.effectReceipt.result);
74
+ review.events.forEach((event, index) => {
75
+ if (event.sequence !== index + 1 || !legalEvents.has(event.type) || event.proposalVersion < 1 || event.proposalVersion > review.proposals.length ||
76
+ (event.operatorDecisionEventId && event.operatorDecisionEventId !== reviewOperatorEventId(review.reviewId, event.sequence))) {
77
+ throw new ReviewIntegrityError('Invalid event lineage');
78
+ }
79
+ if (index && event.atEpochMs < review.events[index - 1].atEpochMs)
80
+ throw new ReviewIntegrityError('Event time reordered');
81
+ });
82
+ if (review.revision !== review.events.length || review.events[0]?.type !== 'created' ||
83
+ review.events[0]?.proposalVersion !== 1 || review.events[0]?.atEpochMs !== review.createdAtEpochMs ||
84
+ review.events.at(-1)?.atEpochMs !== review.updatedAtEpochMs)
85
+ throw new ReviewIntegrityError('Review event envelope mismatch');
86
+ let state = 'pending';
87
+ let version = 1;
88
+ let legalHold = false;
89
+ let decisionIndex = 0;
90
+ for (const event of review.events.slice(1)) {
91
+ if (event.type === 'edited') {
92
+ if (!['pending', 'claimed'].includes(state) || event.proposalVersion !== ++version)
93
+ throw new ReviewIntegrityError('Illegal proposal edit');
94
+ state = 'pending';
95
+ }
96
+ else {
97
+ if (event.proposalVersion !== version)
98
+ throw new ReviewIntegrityError('Event refers to wrong proposal');
99
+ switch (event.type) {
100
+ case 'claimed':
101
+ if (!['pending', 'claimed'].includes(state))
102
+ throw new ReviewIntegrityError('Illegal claim');
103
+ state = 'claimed';
104
+ break;
105
+ case 'approved':
106
+ case 'rejected': {
107
+ if (!['pending', 'claimed'].includes(state))
108
+ throw new ReviewIntegrityError('Illegal decision');
109
+ const decision = review.decisions[decisionIndex++];
110
+ if (!decision || decision.proposalVersion !== version || decision.decision !== (event.type === 'approved' ? 'approve' : 'reject') ||
111
+ canonicalJson(decision.reviewer) !== canonicalJson(event.actor) || decision.rationale !== event.rationale || decision.atEpochMs !== event.atEpochMs) {
112
+ throw new ReviewIntegrityError('Decision event mismatch');
113
+ }
114
+ if (event.type === 'rejected')
115
+ state = 'rejected';
116
+ else
117
+ state = review.decisions.slice(0, decisionIndex).filter(item => item.proposalVersion === version && item.decision === 'approve').length >= review.quorum
118
+ ? 'approved' : 'claimed';
119
+ break;
120
+ }
121
+ case 'resumed':
122
+ if (state !== 'approved' && state !== 'resuming')
123
+ throw new ReviewIntegrityError('Action before approval');
124
+ if (event.data?.effectId !== reviewDigest({ reviewId: review.reviewId, continuationId: review.continuation.id, proposalVersion: version }))
125
+ throw new ReviewIntegrityError('Effect identity mismatch');
126
+ state = 'resuming';
127
+ break;
128
+ case 'execution-completed':
129
+ if (state !== 'resuming' || event.data?.effectId !== review.effectReceipt?.effectId)
130
+ throw new ReviewIntegrityError('Completion without effect receipt');
131
+ state = 'completed';
132
+ break;
133
+ case 'execution-failed':
134
+ if (state !== 'resuming')
135
+ throw new ReviewIntegrityError('Failure without dispatch');
136
+ state = 'execution-failed';
137
+ break;
138
+ case 'expired':
139
+ case 'escalated':
140
+ case 'canceled':
141
+ if (!['pending', 'claimed', 'approved', 'escalated'].includes(state))
142
+ throw new ReviewIntegrityError('Illegal terminal transition');
143
+ if (event.type === 'escalated' && !['pending', 'claimed'].includes(state))
144
+ throw new ReviewIntegrityError('Illegal escalation');
145
+ state = event.type;
146
+ break;
147
+ case 'tombstoned':
148
+ if (state === 'tombstoned' || legalHold)
149
+ throw new ReviewIntegrityError('Illegal tombstone');
150
+ state = 'tombstoned';
151
+ break;
152
+ case 'legal-hold-placed':
153
+ case 'legal-hold-released':
154
+ if (state === 'tombstoned' || legalHold === (event.type === 'legal-hold-placed'))
155
+ throw new ReviewIntegrityError('Illegal legal-hold change');
156
+ legalHold = event.type === 'legal-hold-placed';
157
+ break;
158
+ case 'authorization-denied':
159
+ if (state !== 'approved' && state !== 'resuming')
160
+ throw new ReviewIntegrityError('Illegal authorization denial');
161
+ break;
162
+ case 'sensitive-view-accessed':
163
+ // Audit-only event: it never changes review state and is illegal after tombstoning.
164
+ if (state === 'tombstoned')
165
+ throw new ReviewIntegrityError('Illegal sensitive view');
166
+ break;
167
+ default: throw new ReviewIntegrityError('Unexpected review event');
168
+ }
169
+ }
170
+ }
171
+ if (version !== review.proposals.length || decisionIndex !== review.decisions.length || state !== review.status ||
172
+ legalHold !== (review.lifecycle?.legalHold ?? false))
173
+ throw new ReviewIntegrityError('Review history does not match state');
174
+ if ((review.status === 'completed' && !review.effectReceipt) ||
175
+ (review.effectReceipt && (review.status !== 'completed' && review.status !== 'tombstoned' ||
176
+ review.effectReceipt.proposalVersion !== version || review.effectReceipt.continuationId !== review.continuation.id ||
177
+ review.effectReceipt.effectId !== reviewDigest({ reviewId: review.reviewId, continuationId: review.continuation.id, proposalVersion: version })))) {
178
+ throw new ReviewIntegrityError('Effect receipt does not match continuation');
179
+ }
180
+ }
181
+ export function assertImmutable(previous, next) {
182
+ const fixed = ['reviewId', 'tenantId', 'projectId', 'requesterId', 'sourceReceipt', 'evidencePins', 'policyPins',
183
+ 'reasonCodes', 'riskTier', 'presentation', 'createdAtEpochMs', 'expiresAtEpochMs', 'retentionUntilEpochMs', 'escalationAtEpochMs', 'quorum'];
184
+ for (const key of fixed)
185
+ if (canonicalJson(previous[key] ?? null) !== canonicalJson(next[key] ?? null))
186
+ throw new ReviewIntegrityError(`Immutable review field changed: ${key}`);
187
+ if (previous.continuation.id !== next.continuation.id ||
188
+ (next.events.at(-1)?.type === 'edited') !== (previous.continuation.tokenDigest !== next.continuation.tokenDigest)) {
189
+ throw new ReviewIntegrityError('Continuation token must rotate only on proposal edit');
190
+ }
191
+ if (previous.effectReceipt && canonicalJson(previous.effectReceipt) !== canonicalJson(next.effectReceipt ?? null)) {
192
+ throw new ReviewIntegrityError('Completed effect receipt changed');
193
+ }
194
+ if (canonicalJson(next.decisions.slice(0, previous.decisions.length)) !== canonicalJson(previous.decisions)) {
195
+ throw new ReviewIntegrityError('Review decision history changed');
196
+ }
197
+ if (next.revision !== previous.revision + 1 || next.events.length !== previous.events.length + 1
198
+ || canonicalJson(next.events.slice(0, -1)) !== canonicalJson(previous.events)
199
+ || next.proposals.length < previous.proposals.length
200
+ || canonicalJson(next.proposals.slice(0, previous.proposals.length)) !== canonicalJson(previous.proposals)) {
201
+ throw new ReviewIntegrityError('Review append-only lineage changed');
202
+ }
203
+ validateReview(next);
204
+ }
205
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,115 @@
1
+ export class SchedulerWaitError extends Error {
2
+ reason;
3
+ constructor(reason) {
4
+ super(`scheduler wait ${reason}`);
5
+ this.reason = reason;
6
+ }
7
+ }
8
+ /**
9
+ * Deterministic-output, round-robin scheduler. Work may complete in any order,
10
+ * but returned slots always match input order. A lane can consume at most one
11
+ * newly assigned permit per round, preventing a busy lane from draining the
12
+ * queue ahead of other eligible lanes.
13
+ */
14
+ export async function runBoundedFair(work, concurrency, worker, options = {}) {
15
+ if (!Number.isInteger(concurrency) || concurrency < 1)
16
+ throw new TypeError('scheduler concurrency must be a positive integer');
17
+ const now = options.now ?? Date.now;
18
+ const results = new Array(work.length);
19
+ const lanes = new Map();
20
+ const order = [];
21
+ work.forEach((item, index) => {
22
+ let queue = lanes.get(item.lane);
23
+ if (!queue) {
24
+ queue = [];
25
+ lanes.set(item.lane, queue);
26
+ order.push(item.lane);
27
+ }
28
+ queue.push(index);
29
+ });
30
+ let cursor = 0;
31
+ let active = 0;
32
+ let remaining = work.length;
33
+ const resuming = [];
34
+ if (!remaining)
35
+ return results;
36
+ return await new Promise((resolve, reject) => {
37
+ const take = () => {
38
+ if (!order.length)
39
+ return undefined;
40
+ for (let checked = 0; checked < order.length; checked += 1) {
41
+ const position = cursor % order.length;
42
+ cursor = (position + 1) % order.length;
43
+ const queue = lanes.get(order[position]);
44
+ const index = queue.shift();
45
+ if (index !== undefined)
46
+ return index;
47
+ }
48
+ return undefined;
49
+ };
50
+ const finishWaiting = () => {
51
+ const reason = options.signal?.aborted ? 'cancelled' : 'deadline-exceeded';
52
+ let index;
53
+ while ((index = take()) !== undefined) {
54
+ results[index] = new SchedulerWaitError(reason);
55
+ remaining -= 1;
56
+ }
57
+ };
58
+ const slot = {
59
+ suspend: async (during) => {
60
+ active -= 1;
61
+ pump();
62
+ try {
63
+ return await during();
64
+ }
65
+ finally {
66
+ // Suspended work re-enters before unstarted work so a backoff cannot be
67
+ // starved. After cancellation or deadline it resumes at once; the worker
68
+ // then observes the interruption and starts no new adapter call.
69
+ await new Promise(resume => {
70
+ if (active < concurrency || interrupted()) {
71
+ active += 1;
72
+ resume();
73
+ }
74
+ else
75
+ resuming.push(() => { active += 1; resume(); });
76
+ });
77
+ }
78
+ },
79
+ };
80
+ const interrupted = () => Boolean(options.signal?.aborted)
81
+ || (options.deadlineEpochMs !== undefined && now() >= options.deadlineEpochMs);
82
+ const pump = () => {
83
+ if (interrupted()) {
84
+ finishWaiting();
85
+ while (resuming.length)
86
+ resuming.shift()();
87
+ }
88
+ while (active < concurrency && resuming.length)
89
+ resuming.shift()();
90
+ while (active < concurrency) {
91
+ const index = take();
92
+ if (index === undefined)
93
+ break;
94
+ active += 1;
95
+ void worker(work[index].value, index, slot).then(value => { results[index] = value; }, error => {
96
+ if (error instanceof SchedulerWaitError)
97
+ results[index] = error;
98
+ else
99
+ reject(error);
100
+ }).finally(() => {
101
+ active -= 1;
102
+ remaining -= 1;
103
+ if (remaining === 0)
104
+ resolve(results);
105
+ else
106
+ pump();
107
+ });
108
+ }
109
+ if (remaining === 0)
110
+ resolve(results);
111
+ };
112
+ pump();
113
+ });
114
+ }
115
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1,42 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-(0[01])$/;
3
+ export const randomTelemetryIds = {
4
+ traceId: () => randomBytes(16).toString('hex'),
5
+ spanId: () => randomBytes(8).toString('hex'),
6
+ };
7
+ function validNonZero(value) {
8
+ return !/^0+$/.test(value);
9
+ }
10
+ export function createTelemetryContext(ids = randomTelemetryIds, parent) {
11
+ const traceId = parent?.traceId ?? ids.traceId();
12
+ const spanId = ids.spanId();
13
+ if (!/^[0-9a-f]{32}$/.test(traceId) || !validNonZero(traceId)
14
+ || !/^[0-9a-f]{16}$/.test(spanId) || !validNonZero(spanId)) {
15
+ throw new Error('Telemetry ID source returned an invalid W3C identifier');
16
+ }
17
+ return { traceId, spanId, traceFlags: parent?.traceFlags ?? '01', ...(parent?.traceState ? { traceState: parent.traceState } : {}) };
18
+ }
19
+ export function injectTraceContext(context) {
20
+ return {
21
+ traceparent: `00-${context.traceId}-${context.spanId}-${context.traceFlags}`,
22
+ ...(context.traceState ? { tracestate: context.traceState.slice(0, 512) } : {}),
23
+ };
24
+ }
25
+ /** Provider-bound propagation: `traceparent` only, never vendor `tracestate`. */
26
+ export function transportTraceContext(context) {
27
+ return { traceparent: injectTraceContext(context).traceparent };
28
+ }
29
+ export function isTraceparent(value) {
30
+ return typeof value === 'string' && extractTraceContext({ traceparent: value }) !== null && value === value.trim().toLowerCase();
31
+ }
32
+ export function extractTraceContext(headers) {
33
+ const match = TRACEPARENT.exec(headers.traceparent?.trim().toLowerCase() ?? '');
34
+ if (!match || !validNonZero(match[1]) || !validNonZero(match[2]))
35
+ return null;
36
+ const traceState = headers.tracestate?.trim();
37
+ return {
38
+ traceId: match[1], spanId: match[2], traceFlags: match[3],
39
+ ...(traceState && traceState.length <= 512 ? { traceState } : {}),
40
+ };
41
+ }
42
+ //# sourceMappingURL=context.js.map