@aiwg/cli 2026.9.20 → 2026.9.24

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 (275) 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/cli.js +23 -18
  8. package/dist/src/artifacts/enrichment/cli.js +1 -1
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
  10. package/dist/src/artifacts/query-engine.js +14 -4
  11. package/dist/src/artifacts/views/cli.js +1 -1
  12. package/dist/src/audit/operator-decision.js +10 -0
  13. package/dist/src/auth/credential-store.js +127 -27
  14. package/dist/src/catalog/builtin-models.json +272 -40
  15. package/dist/src/catalog/cli.js +1 -1
  16. package/dist/src/catalog/cli.mjs +1 -1
  17. package/dist/src/cli/handlers/effect.js +872 -0
  18. package/dist/src/cli/handlers/help.js +2 -0
  19. package/dist/src/cli/handlers/index.js +4 -2
  20. package/dist/src/cli/handlers/models.js +41 -10
  21. package/dist/src/cli/handlers/ralph.js +1 -1
  22. package/dist/src/cli/handlers/sessions.js +113 -37
  23. package/dist/src/cli/handlers/subcommands.js +31 -0
  24. package/dist/src/cli/handlers/use.js +46 -9
  25. package/dist/src/cli/handlers/utilities.js +10 -0
  26. package/dist/src/cli/handlers/workspace.js +3 -1
  27. package/dist/src/cli/scope-resolver.js +112 -9
  28. package/dist/src/cli/services/deployment-verification.js +5 -0
  29. package/dist/src/config/aiwg-config.js +3 -0
  30. package/dist/src/decision/acceptance-rollout.js +93 -0
  31. package/dist/src/decision/acceptance.js +175 -0
  32. package/dist/src/decision/adapters/jev.js +234 -13
  33. package/dist/src/decision/adapters/llm-subagent.js +60 -12
  34. package/dist/src/decision/admission.js +550 -0
  35. package/dist/src/decision/batch-benchmark.js +47 -0
  36. package/dist/src/decision/batch-receipts/accounting.js +75 -0
  37. package/dist/src/decision/batch-receipts/export.js +40 -0
  38. package/dist/src/decision/batch-receipts/index.js +9 -0
  39. package/dist/src/decision/batch-receipts/protection.js +40 -0
  40. package/dist/src/decision/batch-receipts/receipt.js +44 -0
  41. package/dist/src/decision/batch-receipts/result-store.js +341 -0
  42. package/dist/src/decision/batch-receipts/store.js +362 -0
  43. package/dist/src/decision/batch-receipts/types.js +2 -0
  44. package/dist/src/decision/batch-receipts/validate.js +127 -0
  45. package/dist/src/decision/batch.js +68 -0
  46. package/dist/src/decision/calibration/index.js +4 -0
  47. package/dist/src/decision/calibration/receipts.js +130 -0
  48. package/dist/src/decision/calibration/registry.js +258 -0
  49. package/dist/src/decision/calibration/types.js +2 -0
  50. package/dist/src/decision/compile-cache/benchmark.js +62 -0
  51. package/dist/src/decision/compile-cache/compatibility.js +65 -0
  52. package/dist/src/decision/compile-cache/file-store.js +280 -0
  53. package/dist/src/decision/compile-cache/identity.js +26 -0
  54. package/dist/src/decision/compile-cache/index.js +9 -0
  55. package/dist/src/decision/compile-cache/prefix.js +21 -0
  56. package/dist/src/decision/compile-cache/records.js +86 -0
  57. package/dist/src/decision/compile-cache/runtime.js +68 -0
  58. package/dist/src/decision/compile-cache/store.js +108 -0
  59. package/dist/src/decision/compile-cache/types.js +2 -0
  60. package/dist/src/decision/context-plan.js +234 -0
  61. package/dist/src/decision/context-qualification.js +60 -0
  62. package/dist/src/decision/ensemble/aggregate.js +215 -0
  63. package/dist/src/decision/ensemble/contract.js +316 -0
  64. package/dist/src/decision/ensemble/drift.js +51 -0
  65. package/dist/src/decision/ensemble/index.js +6 -0
  66. package/dist/src/decision/ensemble/report.js +71 -0
  67. package/dist/src/decision/ensemble/types.js +2 -0
  68. package/dist/src/decision/evaluate.js +1024 -78
  69. package/dist/src/decision/file-lifecycle-store.js +168 -0
  70. package/dist/src/decision/graph-beam.js +40 -0
  71. package/dist/src/decision/graph-budget.js +110 -0
  72. package/dist/src/decision/graph-decision-bridge.js +93 -0
  73. package/dist/src/decision/graph-evidence.js +123 -0
  74. package/dist/src/decision/graph-flow-adapter.js +56 -0
  75. package/dist/src/decision/graph-flow.js +105 -0
  76. package/dist/src/decision/graph-parallel.js +79 -0
  77. package/dist/src/decision/graph-patterns.js +20 -0
  78. package/dist/src/decision/graph-run-store.js +66 -0
  79. package/dist/src/decision/graph-run.js +166 -0
  80. package/dist/src/decision/graph-skill-bridge.js +91 -0
  81. package/dist/src/decision/graph-templates.js +56 -0
  82. package/dist/src/decision/graph.js +115 -0
  83. package/dist/src/decision/index.js +35 -0
  84. package/dist/src/decision/job-accounting.js +93 -0
  85. package/dist/src/decision/job-contract.js +186 -0
  86. package/dist/src/decision/job-effects.js +95 -0
  87. package/dist/src/decision/job-evaluate.js +80 -0
  88. package/dist/src/decision/job-gateway.js +121 -0
  89. package/dist/src/decision/job-http.js +194 -0
  90. package/dist/src/decision/job-payload-store.js +258 -0
  91. package/dist/src/decision/job-poll-limiter.js +92 -0
  92. package/dist/src/decision/job-quota.js +158 -0
  93. package/dist/src/decision/job-runtime.js +240 -0
  94. package/dist/src/decision/job-scheduler.js +26 -0
  95. package/dist/src/decision/job-service.js +117 -0
  96. package/dist/src/decision/job-store.js +264 -0
  97. package/dist/src/decision/job-worker.js +141 -0
  98. package/dist/src/decision/lifecycle.js +148 -0
  99. package/dist/src/decision/patterns/artifacts.js +173 -0
  100. package/dist/src/decision/patterns/catalog.js +108 -0
  101. package/dist/src/decision/patterns/durable-review.js +237 -0
  102. package/dist/src/decision/patterns/index.js +8 -0
  103. package/dist/src/decision/patterns/playground.js +401 -0
  104. package/dist/src/decision/patterns/recorded.js +43 -0
  105. package/dist/src/decision/patterns/specs.js +174 -0
  106. package/dist/src/decision/patterns/types.js +2 -0
  107. package/dist/src/decision/portable-secrets.js +53 -0
  108. package/dist/src/decision/projection.js +210 -0
  109. package/dist/src/decision/qualification/capture.js +67 -0
  110. package/dist/src/decision/qualification/drift.js +68 -0
  111. package/dist/src/decision/qualification/gate-evidence.js +172 -0
  112. package/dist/src/decision/qualification/gates.js +83 -0
  113. package/dist/src/decision/qualification/index.js +12 -0
  114. package/dist/src/decision/qualification/load.js +282 -0
  115. package/dist/src/decision/qualification/manifest.js +37 -0
  116. package/dist/src/decision/qualification/privacy.js +41 -0
  117. package/dist/src/decision/qualification/quality.js +213 -0
  118. package/dist/src/decision/qualification/release.js +116 -0
  119. package/dist/src/decision/qualification/runner.js +408 -0
  120. package/dist/src/decision/qualification/types.js +2 -0
  121. package/dist/src/decision/receipts.js +30 -7
  122. package/dist/src/decision/result-cache/index.js +6 -0
  123. package/dist/src/decision/result-cache/integrity.js +28 -0
  124. package/dist/src/decision/result-cache/key.js +28 -0
  125. package/dist/src/decision/result-cache/service.js +92 -0
  126. package/dist/src/decision/result-cache/store.js +327 -0
  127. package/dist/src/decision/result-cache/types.js +3 -0
  128. package/dist/src/decision/review/authorization.js +110 -0
  129. package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
  130. package/dist/src/decision/review/index.js +12 -0
  131. package/dist/src/decision/review/migrate.js +34 -0
  132. package/dist/src/decision/review/operator-audit.js +59 -0
  133. package/dist/src/decision/review/recovery-adapters.js +252 -0
  134. package/dist/src/decision/review/recovery.js +33 -0
  135. package/dist/src/decision/review/service.js +498 -0
  136. package/dist/src/decision/review/store.js +219 -0
  137. package/dist/src/decision/review/types.js +2 -0
  138. package/dist/src/decision/review/validate.js +205 -0
  139. package/dist/src/decision/scheduler.js +115 -0
  140. package/dist/src/decision/telemetry/context.js +42 -0
  141. package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
  142. package/dist/src/decision/telemetry/exporter.js +77 -0
  143. package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
  144. package/dist/src/decision/telemetry/index.js +13 -0
  145. package/dist/src/decision/telemetry/mapping.js +146 -0
  146. package/dist/src/decision/telemetry/metrics.js +164 -0
  147. package/dist/src/decision/telemetry/otlp-http.js +183 -0
  148. package/dist/src/decision/telemetry/redaction.js +93 -0
  149. package/dist/src/decision/telemetry/retention.js +109 -0
  150. package/dist/src/decision/telemetry/runtime.js +310 -0
  151. package/dist/src/decision/telemetry/trace.js +158 -0
  152. package/dist/src/decision/telemetry/types.js +3 -0
  153. package/dist/src/decision/validate.js +135 -5
  154. package/dist/src/effects/checkpoint-sinks.js +94 -0
  155. package/dist/src/effects/checkpoint.js +76 -0
  156. package/dist/src/effects/decision-links.js +54 -0
  157. package/dist/src/effects/errors.js +48 -0
  158. package/dist/src/effects/identity.js +178 -0
  159. package/dist/src/effects/index.js +25 -0
  160. package/dist/src/effects/keyring.js +160 -0
  161. package/dist/src/effects/keys.js +130 -0
  162. package/dist/src/effects/ledger.js +435 -0
  163. package/dist/src/effects/lifecycle.js +110 -0
  164. package/dist/src/effects/lock-recovery.js +269 -0
  165. package/dist/src/effects/reader.js +103 -0
  166. package/dist/src/effects/records.js +114 -0
  167. package/dist/src/effects/redaction.js +51 -0
  168. package/dist/src/effects/schema.js +73 -0
  169. package/dist/src/effects/store.js +239 -0
  170. package/dist/src/effects/types.js +49 -0
  171. package/dist/src/effects/verifiers/decision.js +168 -0
  172. package/dist/src/effects/verifiers/file.js +106 -0
  173. package/dist/src/effects/verifiers/git.js +206 -0
  174. package/dist/src/effects/verifiers/index.js +90 -0
  175. package/dist/src/effects/verifiers/registry.js +148 -0
  176. package/dist/src/effects/verifiers/review.js +123 -0
  177. package/dist/src/effects/verifiers/tracker.js +299 -0
  178. package/dist/src/effects/verifiers/types.js +21 -0
  179. package/dist/src/effects/verify.js +295 -0
  180. package/dist/src/evidence/bundle.js +2 -0
  181. package/dist/src/extensions/commands/definitions.js +28 -0
  182. package/dist/src/extensions/manifest.js +1 -0
  183. package/dist/src/marketplace/provenance.js +25 -27
  184. package/dist/src/mcp/cli.mjs +11 -1
  185. package/dist/src/mcp/grok-build-config.mjs +189 -0
  186. package/dist/src/mcp/registry.js +21 -1
  187. package/dist/src/mcp/registry.mjs +20 -1
  188. package/dist/src/models/grok-build-models.js +356 -0
  189. package/dist/src/models/index.js +1 -0
  190. package/dist/src/models/model-capabilities.v1.json +34 -0
  191. package/dist/src/models/model-catalog.v1.json +55 -13
  192. package/dist/src/models/model-discovery.js +41 -5
  193. package/dist/src/models/provider-policy.js +3 -2
  194. package/dist/src/plugin/skill-command-translator.js +1 -0
  195. package/dist/src/providers/capability-matrix.yaml +58 -6
  196. package/dist/src/providers/grok-build-acp.js +192 -0
  197. package/dist/src/providers/grok-build-dispatch.js +251 -0
  198. package/dist/src/providers/grok-build-headless.js +170 -0
  199. package/dist/src/providers/muse-paths.js +105 -0
  200. package/dist/src/providers/provider-definitions.js +93 -7
  201. package/dist/src/providers/provider-definitions.mjs +21 -1
  202. package/dist/src/providers/provider-inventory.js +3 -0
  203. package/dist/src/security/artifact-attestation.js +4 -8
  204. package/dist/src/security/signing.js +119 -0
  205. package/dist/src/sessions/adapters/grok-build.js +187 -0
  206. package/dist/src/sessions/adapters/muse.js +412 -0
  207. package/dist/src/sessions/contracts.js +1 -1
  208. package/dist/src/sessions/grok-build-cli.js +196 -0
  209. package/dist/src/sessions/index.js +3 -0
  210. package/dist/src/sessions/workspace-discovery.js +9 -5
  211. package/dist/src/skills/deployer.js +33 -5
  212. package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
  213. package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
  214. package/dist/src/smiths/context-pipeline/finalization.js +1 -32
  215. package/dist/src/smiths/context-pipeline/generator.js +12 -0
  216. package/dist/src/smiths/context-pipeline/index.js +1 -1
  217. package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
  218. package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
  219. package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
  220. package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
  221. package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
  222. package/dist/src/smiths/hook-bridge/index.js +3 -0
  223. package/dist/src/smiths/hook-bridge/shim.js +2 -0
  224. package/dist/src/smiths/hook-bridge/types.js +5 -0
  225. package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
  226. package/dist/src/storage/protected-files.js +102 -0
  227. package/dist/src/tracker/forge-http.js +371 -0
  228. package/dist/src/tracker/project-inputs.js +41 -0
  229. package/package.json +2 -1
  230. package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
  231. package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
  232. package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
  233. package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
  234. package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
  235. package/schemas/decision/DecisionBinding.schema.json +309 -0
  236. package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
  237. package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
  238. package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
  239. package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
  240. package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
  241. package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
  242. package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
  243. package/schemas/decision/DecisionDefinition.schema.json +159 -0
  244. package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
  245. package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
  246. package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
  247. package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
  248. package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
  249. package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
  250. package/schemas/decision/DecisionJob.v1.schema.json +71 -0
  251. package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
  252. package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
  253. package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
  254. package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
  255. package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
  256. package/schemas/decision/DecisionResult.schema.json +524 -0
  257. package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
  258. package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
  259. package/schemas/decision/DecisionReview.schema.json +29 -0
  260. package/schemas/decision/DecisionRuleset.schema.json +313 -0
  261. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
  262. package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
  263. package/schemas/decision/RulesetResult.schema.json +721 -0
  264. package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
  265. package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
  266. package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
  267. package/tools/agents/deploy-agents.mjs +21 -6
  268. package/tools/agents/providers/base.mjs +7 -4
  269. package/tools/agents/providers/codex.mjs +1 -1
  270. package/tools/agents/providers/grok-build.d.mts +7 -0
  271. package/tools/agents/providers/grok-build.mjs +249 -9
  272. package/tools/agents/providers/model-role.mjs +6 -3
  273. package/tools/agents/providers/muse-hooks.mjs +785 -0
  274. package/tools/agents/providers/muse-paths.mjs +112 -0
  275. package/tools/agents/providers/muse.mjs +336 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Tracker verifiers (#2719): `tracker.comment`, `tracker.issue.closed` and
3
+ * `tracker.pr.merged` for Gitea and GitHub.
4
+ *
5
+ * - **Authority.** The tracker comes from `resolveTrackerAuthority` over the
6
+ * project config and git remote URLs. A target whose forge is neither the
7
+ * internal nor the customer tracker, or whose repository differs from that
8
+ * tracker remote, is `unknown` / `tracker-blocked`. The API base is derived
9
+ * from the selected remote URL; no host is assumed.
10
+ * - **Access order.** `chooseTrackerAccess` over the probes a CLI process can
11
+ * offer: the tracker HTTP API (with credentials from the existing tracker
12
+ * token path, `resolveToken`), then the forge CLI (`gh`, GitHub only). MCP
13
+ * is never offered. A blocker is `unknown` / `tracker-blocked`.
14
+ * - **Matching.** A comment matches exactly when it carries the
15
+ * `<!-- aiwg-effect: <id> -->` marker and was written by the pinned tracker
16
+ * actor; a merged PR matches its merge commit's `Effect-Id:` trailer.
17
+ * Author and time-window matches give `heuristic-match` and never `absent`.
18
+ * - **Absent.** Only after an authenticated, complete query (every comment
19
+ * page read, or an authoritative issue/PR state read) and once the intent is
20
+ * older than `minAbsentAgeMs`; a younger intent is `unknown` /
21
+ * `consistency-lag`.
22
+ *
23
+ * Access material stays inside the transport: evidence carries IDs, SHAs and
24
+ * digests only, and errors carry fixed messages.
25
+ *
26
+ * @see docs/contracts/effect-ledger.v1.md "Tracker verifiers"
27
+ */
28
+ import { accessSync, constants as fsConstants } from 'node:fs';
29
+ import { delimiter, join } from 'node:path';
30
+ import { resolveToken } from '../../issues/live.js';
31
+ import { chooseTrackerAccess, resolveTrackerAuthority, } from '../../tracker/capability-protocol.js';
32
+ import { ForgeReadError, cliForgeTransport, createForgeReader, forgeEndpointFromRemote, httpForgeTransport, } from '../../tracker/forge-http.js';
33
+ import { sha256Digest } from '../identity.js';
34
+ import { EffectVerifierError } from './types.js';
35
+ export const TRACKER_VERIFIER_VERSION = '1.0.0';
36
+ /** Default minimum intent age before a tracker verifier may answer `absent`. */
37
+ export const DEFAULT_MIN_ABSENT_AGE_MS = 60_000;
38
+ /** Default page bound for a comment scan; more pages is `unknown` / `paging-incomplete`. */
39
+ export const DEFAULT_MAX_COMMENT_PAGES = 100;
40
+ const TARGET = /^(gitea|github):([\w.-]+\/[\w.-]+)#([1-9][0-9]{0,9})$/;
41
+ const OBJECT_ID = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
42
+ const DIGEST = /^sha256:[a-f0-9]{64}$/;
43
+ const MARKER = /<!--\s*aiwg-effect:\s*([^\s<>]+)\s*-->/g;
44
+ const HEURISTIC_SKEW_MS = 60_000;
45
+ /** The hidden marker line a writer embeds in a tracker comment so the verifier can match it exactly. */
46
+ export function renderEffectMarker(effectId) {
47
+ if (typeof effectId !== 'string' || !/^[A-Za-z0-9_:-]{1,128}$/.test(effectId))
48
+ throw new TypeError('Effect ID is required');
49
+ return `<!-- aiwg-effect: ${effectId} -->`;
50
+ }
51
+ /** Effect IDs named by `aiwg-effect` markers in a comment body. */
52
+ export function parseEffectMarkers(body) {
53
+ return [...String(body ?? '').matchAll(MARKER)].map(match => match[1]);
54
+ }
55
+ /** Values of `Effect-Id:` trailer lines in a commit message. */
56
+ export function parseEffectIdTrailers(message) {
57
+ return String(message ?? '').split(/\r?\n/).map(line => /^Effect-Id:\s*(\S+)\s*$/i.exec(line)?.[1]).filter((id) => Boolean(id));
58
+ }
59
+ const FAILURE_REASON = {
60
+ 'auth-denied': 'auth-denied',
61
+ 'not-found': 'container-unreadable',
62
+ 'rate-limited': 'rate-limited',
63
+ 'server-error': 'server-error',
64
+ 'network-error': 'network-error',
65
+ timeout: 'timeout',
66
+ oversized: 'malformed-response',
67
+ malformed: 'malformed-response',
68
+ 'unexpected-status': 'server-error',
69
+ };
70
+ async function read(operation) {
71
+ try {
72
+ return await operation();
73
+ }
74
+ catch (error) {
75
+ if (error instanceof ForgeReadError)
76
+ throw new EffectVerifierError(FAILURE_REASON[error.code]);
77
+ if (error instanceof EffectVerifierError)
78
+ throw error;
79
+ throw new EffectVerifierError('network-error');
80
+ }
81
+ }
82
+ function onPath(binary) {
83
+ const dirs = (process.env.PATH ?? '').split(delimiter).filter(Boolean);
84
+ const names = process.platform === 'win32' ? [`${binary}.exe`, `${binary}.cmd`, binary] : [binary];
85
+ return dirs.some(dir => names.some(name => {
86
+ try {
87
+ accessSync(join(dir, name), fsConstants.X_OK);
88
+ return true;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }));
94
+ }
95
+ /** Resolve the tracker lane for a target: authority, remote, access path and reader. Throws `tracker-blocked`. */
96
+ function resolveLane(options, target) {
97
+ const parsed = TARGET.exec(target);
98
+ if (!parsed)
99
+ throw new EffectVerifierError('malformed-response', 'Tracker target must be gitea:owner/repo#N or github:owner/repo#N');
100
+ const provider = parsed[1];
101
+ const number = Number(parsed[3]);
102
+ const authority = resolveTrackerAuthority(options.config, options.remoteUrls);
103
+ const remotes = options.config?.remotes;
104
+ let lane = null;
105
+ if (authority.provider === provider) {
106
+ lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor };
107
+ }
108
+ else if (authority.customerIssueTrackerRemote && authority.customerProvider === provider) {
109
+ lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor };
110
+ }
111
+ if (!lane)
112
+ throw new EffectVerifierError('tracker-blocked');
113
+ const endpoint = forgeEndpointFromRemote(provider, lane.url);
114
+ if (!endpoint || endpoint.repository.toLowerCase() !== parsed[2].toLowerCase())
115
+ throw new EffectVerifierError('tracker-blocked');
116
+ const laneAuthority = { ...authority, provider, issueTrackerRemote: lane.remote, issueTrackerUrl: lane.url };
117
+ const token = resolveToken(provider, options.env ?? process.env);
118
+ const cli = options.cli === false ? null : options.cli ?? {};
119
+ const cliAvailable = provider === 'github' && cli !== null && (cli.available ?? onPath(cli.binary ?? 'gh'));
120
+ const probes = [
121
+ { kind: 'http-api', provider, remoteName: lane.remote, available: Boolean(token), authenticated: Boolean(token) },
122
+ { kind: 'cli', provider, remoteName: lane.remote, cli: cli?.binary ?? 'gh', available: cliAvailable, ...(cli?.authenticated !== undefined ? { authenticated: cli.authenticated } : {}) },
123
+ ];
124
+ const decision = chooseTrackerAccess(laneAuthority, probes);
125
+ const readerOptions = options.pageSize ? { pageSize: options.pageSize } : {};
126
+ if (decision.kind === 'http-api' && token) {
127
+ const transport = httpForgeTransport({ provider, apiBaseUrl: endpoint.apiBaseUrl, token, fetchImpl: options.fetchImpl, maxResponseBytes: options.maxResponseBytes });
128
+ return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
129
+ }
130
+ if (decision.kind === 'cli' && cli) {
131
+ const transport = cliForgeTransport({ provider, hostname: endpoint.host, runner: cli.runner, binary: cli.binary, maxResponseBytes: options.maxResponseBytes });
132
+ return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
133
+ }
134
+ throw new EffectVerifierError('tracker-blocked');
135
+ }
136
+ const unknown = (reason, evidence) => ({ result: 'unknown', reason, complete: false, ...(evidence ? { evidence } : {}) });
137
+ /** `absent` once the intent is old enough, else `unknown` / `consistency-lag`. */
138
+ function absentOrLag(options, request, evidence) {
139
+ const recorded = Date.parse(request.intentRecordedAt);
140
+ if (!Number.isFinite(recorded))
141
+ return unknown('malformed-response');
142
+ const now = (options.now ?? Date.now)();
143
+ if (now - recorded < (options.minAbsentAgeMs ?? DEFAULT_MIN_ABSENT_AGE_MS))
144
+ return unknown('consistency-lag', evidence);
145
+ return { result: 'absent', reason: 'complete-query-no-match', complete: true, evidence };
146
+ }
147
+ const sameLogin = (a, b) => Boolean(a && b && a.toLowerCase() === b.toLowerCase());
148
+ function expectedString(request, name) {
149
+ const value = request.expected[name] ?? request.context[name];
150
+ return typeof value === 'string' ? value : undefined;
151
+ }
152
+ function laneEvidence(lane) {
153
+ return { tracker: lane.provider, repository: lane.repository, number: lane.number, access: lane.access };
154
+ }
155
+ /** `tracker.comment`: target `gitea:owner/repo#N` or `github:owner/repo#N` (an issue or PR). */
156
+ export function trackerCommentVerifier(options) {
157
+ return {
158
+ kind: 'tracker.comment',
159
+ version: TRACKER_VERIFIER_VERSION,
160
+ canReportAbsent: true,
161
+ async verify(request) {
162
+ const lane = resolveLane(options, request.target);
163
+ const expectedDigest = expectedString(request, 'digest');
164
+ if (expectedDigest !== undefined && !DIGEST.test(expectedDigest))
165
+ return unknown('malformed-response');
166
+ const pinned = lane.actor?.login;
167
+ const forbidden = lane.actor?.forbid_actors ?? [];
168
+ const recorded = Date.parse(request.intentRecordedAt);
169
+ const windowMs = options.heuristicWindowMs ?? 0;
170
+ const maxPages = options.maxPages ?? DEFAULT_MAX_COMMENT_PAGES;
171
+ const base = laneEvidence(lane);
172
+ let foreignMarker = false;
173
+ let digestMatch = null;
174
+ let heuristic = null;
175
+ let scanned = 0;
176
+ let complete = false;
177
+ let pages = 0;
178
+ for (let page = 1; page !== null;) {
179
+ if (pages >= maxPages)
180
+ break;
181
+ const current = page;
182
+ const result = await read(() => lane.reader.listIssueComments(lane.repository, lane.number, current, request.signal));
183
+ pages += 1;
184
+ for (const comment of result.items) {
185
+ scanned += 1;
186
+ const authorIsPinned = Boolean(pinned) && sameLogin(comment.author, pinned) && !forbidden.some(name => sameLogin(name, comment.author));
187
+ const bodyDigest = sha256Digest(comment.body);
188
+ if (parseEffectMarkers(comment.body).includes(request.effectId)) {
189
+ if (!authorIsPinned) {
190
+ foreignMarker = true;
191
+ continue;
192
+ }
193
+ const evidence = { ...base, commentId: comment.id, bodyDigest, pages, scanned, confidence: 'exact' };
194
+ if (expectedDigest !== undefined) {
195
+ return bodyDigest === expectedDigest
196
+ ? { result: 'present', reason: 'digest-match', complete: true, evidence }
197
+ : unknown('evidence-conflict', evidence);
198
+ }
199
+ return { result: 'present', reason: bodyDigest === request.payloadDigest ? 'digest-match' : 'marker-match', complete: true, evidence };
200
+ }
201
+ if (!authorIsPinned)
202
+ continue;
203
+ if (!digestMatch && bodyDigest === (expectedDigest ?? request.payloadDigest))
204
+ digestMatch = { id: comment.id, bodyDigest };
205
+ const created = comment.createdAt ? Date.parse(comment.createdAt) : Number.NaN;
206
+ if (!heuristic && windowMs > 0 && Number.isFinite(recorded) && Number.isFinite(created)
207
+ && created >= recorded - HEURISTIC_SKEW_MS && created <= recorded + windowMs)
208
+ heuristic = { id: comment.id, bodyDigest };
209
+ }
210
+ if (result.nextPage === null) {
211
+ complete = result.complete;
212
+ page = null;
213
+ }
214
+ else if (result.nextPage <= current) {
215
+ page = null;
216
+ }
217
+ else
218
+ page = result.nextPage;
219
+ }
220
+ const summary = { ...base, pages, scanned };
221
+ // A marker for this effect under another author is never ours; escalate instead of replaying.
222
+ if (foreignMarker)
223
+ return unknown('evidence-conflict', { ...summary, foreignMarker: true });
224
+ if (digestMatch) {
225
+ return { result: 'present', reason: 'digest-match', complete, evidence: { ...summary, commentId: digestMatch.id, bodyDigest: digestMatch.bodyDigest, confidence: 'exact' } };
226
+ }
227
+ if (heuristic) {
228
+ return { result: 'present', reason: 'heuristic-match', complete: false, evidence: { ...summary, commentId: heuristic.id, bodyDigest: heuristic.bodyDigest, confidence: 'heuristic' } };
229
+ }
230
+ if (!complete)
231
+ return unknown('paging-incomplete', summary);
232
+ return absentOrLag(options, request, { ...summary, found: false });
233
+ },
234
+ };
235
+ }
236
+ /** `tracker.issue.closed`: target `gitea:owner/repo#N` or `github:owner/repo#N`. */
237
+ export function trackerIssueClosedVerifier(options) {
238
+ return {
239
+ kind: 'tracker.issue.closed',
240
+ version: TRACKER_VERIFIER_VERSION,
241
+ canReportAbsent: true,
242
+ async verify(request) {
243
+ const lane = resolveLane(options, request.target);
244
+ const issue = await read(() => lane.reader.getIssue(lane.repository, lane.number, request.signal));
245
+ const evidence = { ...laneEvidence(lane), state: issue.state, closedAt: issue.closedAt };
246
+ if (issue.state === 'closed')
247
+ return { result: 'present', reason: 'state-match', complete: true, evidence };
248
+ return absentOrLag(options, request, evidence);
249
+ },
250
+ };
251
+ }
252
+ /**
253
+ * `tracker.pr.merged`: target `gitea:owner/repo#N` or `github:owner/repo#N`.
254
+ * The expected merge commit comes from `expected.object` or `context.object`.
255
+ */
256
+ export function trackerPrMergedVerifier(options) {
257
+ return {
258
+ kind: 'tracker.pr.merged',
259
+ version: TRACKER_VERIFIER_VERSION,
260
+ canReportAbsent: true,
261
+ async verify(request) {
262
+ const expectedSha = expectedString(request, 'object');
263
+ if (expectedSha !== undefined && !OBJECT_ID.test(expectedSha))
264
+ return unknown('malformed-response');
265
+ const lane = resolveLane(options, request.target);
266
+ const pr = await read(() => lane.reader.getPullState(lane.repository, lane.number, request.signal));
267
+ const evidence = {
268
+ ...laneEvidence(lane), state: pr.state, merged: pr.merged, mergedAt: pr.mergedAt,
269
+ mergeCommit: pr.mergeCommitSha, expectedMergeCommit: expectedSha ?? null, baseSha: pr.baseSha,
270
+ };
271
+ if (!pr.merged)
272
+ return absentOrLag(options, request, evidence);
273
+ if (!pr.mergeCommitSha)
274
+ return unknown(expectedSha ? 'malformed-response' : 'consistency-lag', evidence);
275
+ if (expectedSha !== undefined && pr.mergeCommitSha !== expectedSha) {
276
+ // Merged, but not as this effect expected: the expected merge did not happen.
277
+ return absentOrLag(options, request, { ...evidence, mergeCommitMatch: false });
278
+ }
279
+ // The merge commit trailer is a second, exact check; the PR state stays authoritative without it.
280
+ let marker = null;
281
+ try {
282
+ const message = await lane.reader.getCommitMessage(lane.repository, pr.mergeCommitSha, request.signal);
283
+ marker = message === null ? null : parseEffectIdTrailers(message).includes(request.effectId);
284
+ }
285
+ catch {
286
+ marker = null;
287
+ }
288
+ return {
289
+ result: 'present', reason: marker ? 'marker-match' : 'state-match', complete: true,
290
+ evidence: { ...evidence, mergeCommitMatch: expectedSha !== undefined ? true : null, marker },
291
+ };
292
+ },
293
+ };
294
+ }
295
+ /** The three tracker verifiers, for `createBuiltinVerifierRegistry(options, createTrackerVerifiers(...))`. */
296
+ export function createTrackerVerifiers(options) {
297
+ return [trackerCommentVerifier(options), trackerIssueClosedVerifier(options), trackerPrMergedVerifier(options)];
298
+ }
299
+ //# sourceMappingURL=tracker.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The effect verifier interface (#2718). This is the stable plugin contract
3
+ * that tracker verifiers (#2719), the `aiwg effect` CLI (#2720) and D13
4
+ * adoption (#2721) build on. Keep it minimal: additions must be optional.
5
+ *
6
+ * @see docs/contracts/effect-ledger.v1.md "Verifier interface"
7
+ */
8
+ /**
9
+ * Throw from `verify` to report a classified `unknown`. Any other throw maps to
10
+ * `unknown` / `server-error`; an abort after the timeout maps to `timeout`.
11
+ * The message is never recorded.
12
+ */
13
+ export class EffectVerifierError extends Error {
14
+ reason;
15
+ constructor(reason, message = 'Effect verifier could not settle the target') {
16
+ super(message);
17
+ this.reason = reason;
18
+ this.name = 'EffectVerifierError';
19
+ }
20
+ }
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Whole-ledger verification: keyring, every segment line (signature, key
3
+ * window, writer and seq binding, chain, record hash, scope, identity), the
4
+ * index/segment agreement, the local checkpoint chain, and the latest
5
+ * checkpoint from the independent sink (truncation and deletion).
6
+ *
7
+ * @see docs/contracts/effect-ledger.v1.md
8
+ */
9
+ import { readdir, readFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { canonicalJson } from '../security/artifact-trust.js';
12
+ import { exists } from '../storage/protected-files.js';
13
+ import { EFFECT_EXIT_CODES } from './errors.js';
14
+ import { sha256Digest } from './identity.js';
15
+ import { checkSignature, keyringFailure } from './keyring.js';
16
+ import { lineFailures, readAllSegments, readKeyring } from './reader.js';
17
+ import { isEffectSchemaValid } from './schema.js';
18
+ import { indexName, listIndexEntries, loadIndexKey, readPending } from './store.js';
19
+ import { CHECKPOINT_PAYLOAD_TYPE, } from './types.js';
20
+ /** `"sha256:"` plus hex SHA-256 of `canonicalJson(checkpoint)`, as used by `previousCheckpoint`. */
21
+ export function checkpointDigest(checkpoint) {
22
+ return sha256Digest(canonicalJson(checkpoint));
23
+ }
24
+ export function checkpointBody(checkpoint) {
25
+ const { signatures: _signatures, ...body } = checkpoint;
26
+ return body;
27
+ }
28
+ /**
29
+ * Digests of the keyring as it stood after each rotation (0..n). A checkpoint's
30
+ * `keyringDigest` must equal one of them, so a later keyring can only extend
31
+ * the one the checkpoint anchored, never replace or re-window it.
32
+ */
33
+ export function keyringPrefixDigests(keyring) {
34
+ const chain = [keyring.keys.find(key => !keyring.rotations.some(rotation => rotation.to === key.keyid)),
35
+ ...keyring.rotations.map(rotation => keyring.keys.find(key => key.keyid === rotation.to))];
36
+ return chain.map((_key, count) => {
37
+ const keys = chain.slice(0, count + 1).map(key => structuredClone(key));
38
+ const last = keys[count];
39
+ if (count < keyring.rotations.length && last.status !== 'revoked') {
40
+ delete last.validUntil;
41
+ last.status = 'active';
42
+ }
43
+ return sha256Digest(canonicalJson({ ...keyring, keys, rotations: keyring.rotations.slice(0, count) }));
44
+ });
45
+ }
46
+ /** Schema, scope, root, keyring lineage and signature checks for one checkpoint. Returns a failure reason or null. */
47
+ export function checkpointFailure(checkpoint, keyring, scope) {
48
+ if (!isEffectSchemaValid('checkpoint', checkpoint))
49
+ return 'checkpoint-schema-invalid';
50
+ const value = checkpoint;
51
+ if (canonicalJson(value.scope) !== canonicalJson(scope))
52
+ return 'checkpoint-scope-mismatch';
53
+ const writers = value.writers.map(entry => entry.writer);
54
+ if (writers.some((writer, index) => index > 0 && writers[index - 1] >= writer))
55
+ return 'checkpoint-writers-unsorted';
56
+ if (value.writers.some(entry => entry.segment !== `segments/${entry.writer}.jsonl`))
57
+ return 'checkpoint-segment-path';
58
+ if (value.root !== sha256Digest(canonicalJson(value.writers)))
59
+ return 'checkpoint-root-mismatch';
60
+ if (!keyringPrefixDigests(keyring).includes(value.keyringDigest))
61
+ return 'checkpoint-keyring-mismatch';
62
+ const payload = Buffer.from(canonicalJson(checkpointBody(value)), 'utf8');
63
+ for (const signature of value.signatures) {
64
+ const check = checkSignature(keyring, signature.keyid, signature.sig, CHECKPOINT_PAYLOAD_TYPE, payload, value.createdAt);
65
+ if (check !== 'ok')
66
+ return `checkpoint-${check}`;
67
+ }
68
+ return null;
69
+ }
70
+ export async function readLocalCheckpoints(paths) {
71
+ let names;
72
+ try {
73
+ names = await readdir(paths.checkpoints);
74
+ }
75
+ catch (error) {
76
+ if (error.code === 'ENOENT')
77
+ return [];
78
+ throw error;
79
+ }
80
+ const out = [];
81
+ for (const name of names) {
82
+ const match = /^(0|[1-9][0-9]*)\.json$/.exec(name);
83
+ if (!match)
84
+ continue;
85
+ let value;
86
+ try {
87
+ value = JSON.parse(await readFile(join(paths.checkpoints, name), 'utf8'));
88
+ }
89
+ catch {
90
+ value = null;
91
+ }
92
+ out.push({ sequence: Number(match[1]), value });
93
+ }
94
+ return out.sort((a, b) => a.sequence - b.sequence);
95
+ }
96
+ function verifySegments(segments, keyring, scope, failures) {
97
+ let records = 0;
98
+ for (const segment of segments) {
99
+ if (segment.torn)
100
+ failures.push({ reason: 'segment-torn', writer: segment.writer, seq: segment.lines.length - 1 });
101
+ let previous = null;
102
+ for (const read of segment.lines) {
103
+ records += 1;
104
+ if (!read.decoded) {
105
+ failures.push({ reason: read.failure, writer: segment.writer, seq: read.index });
106
+ previous = null;
107
+ continue;
108
+ }
109
+ for (const reason of lineFailures(keyring, scope, segment.writer, read.index, read.decoded, previous)) {
110
+ failures.push({ reason, writer: segment.writer, seq: read.index });
111
+ }
112
+ previous = read.decoded.line.recordHash;
113
+ }
114
+ }
115
+ return records;
116
+ }
117
+ async function verifyIndex(paths, segments, scope, failures, warnings) {
118
+ const hasRecords = segments.some(segment => segment.lines.length > 0);
119
+ if (!(await exists(paths.indexKey))) {
120
+ if (hasRecords)
121
+ failures.push({ reason: 'index-key-missing' });
122
+ return;
123
+ }
124
+ let key;
125
+ try {
126
+ key = await loadIndexKey(paths);
127
+ }
128
+ catch {
129
+ failures.push({ reason: 'index-key-malformed' });
130
+ return;
131
+ }
132
+ let entries;
133
+ try {
134
+ entries = await listIndexEntries(paths);
135
+ }
136
+ catch {
137
+ failures.push({ reason: 'index-entry-malformed' });
138
+ return;
139
+ }
140
+ const byWriter = new Map(segments.map(segment => [segment.writer, segment]));
141
+ const claimed = new Set();
142
+ for (const { name, entry } of entries) {
143
+ if (name !== indexName(key, scope, entry.effectId, entry.role)) {
144
+ failures.push({ reason: 'index-name-mismatch', writer: entry.writer, seq: entry.seq });
145
+ continue;
146
+ }
147
+ const read = byWriter.get(entry.writer)?.lines[entry.seq];
148
+ if (!read) {
149
+ const pending = await readPending(paths, entry.writer);
150
+ if (pending?.indexName === name)
151
+ warnings.push(`pending-append:${entry.writer}:${entry.seq}`);
152
+ else
153
+ failures.push({ reason: 'index-dangling', writer: entry.writer, seq: entry.seq });
154
+ continue;
155
+ }
156
+ const predicate = read.decoded?.statement.predicate;
157
+ const phase = predicate?.phase === 'tombstone' ? predicate.tombstone.originalPhase : predicate?.phase;
158
+ if (!predicate || predicate.effectId !== entry.effectId || read.decoded.line.recordHash !== entry.recordHash
159
+ || predicate.payloadDigest !== entry.payloadDigest || phase !== entry.phase
160
+ || (entry.role === 'intent') !== (phase === 'intent')) {
161
+ failures.push({ reason: 'index-segment-mismatch', writer: entry.writer, seq: entry.seq });
162
+ continue;
163
+ }
164
+ claimed.add(`${entry.writer}\0${entry.seq}`);
165
+ }
166
+ for (const segment of segments) {
167
+ for (const read of segment.lines) {
168
+ const predicate = read.decoded?.statement.predicate;
169
+ if (!predicate)
170
+ continue;
171
+ const phase = predicate.phase === 'tombstone' ? predicate.tombstone.originalPhase : predicate.phase;
172
+ if ((phase === 'intent' || phase === 'completed' || phase === 'failed') && !claimed.has(`${segment.writer}\0${read.index}`)) {
173
+ failures.push({ reason: 'index-missing', writer: segment.writer, seq: read.index });
174
+ }
175
+ }
176
+ }
177
+ }
178
+ /**
179
+ * Verify the whole ledger for this scope. Integrity failures are reported, not
180
+ * thrown (exit 6); an unavailable artifact root still throws (exit 7).
181
+ */
182
+ export async function verifyLedger(ledger, options = {}) {
183
+ const paths = ledger.paths();
184
+ const scope = ledger.scope;
185
+ const failures = [];
186
+ const warnings = [];
187
+ const segments = await readAllSegments(paths);
188
+ const writers = segments.map(segment => ({
189
+ writer: segment.writer, count: segment.lines.length, headHash: segment.lines.at(-1)?.decoded?.line.recordHash ?? null,
190
+ }));
191
+ const result = (records, checkpoint) => ({
192
+ ok: failures.length === 0,
193
+ exitCode: failures.length === 0 ? EFFECT_EXIT_CODES.ok : EFFECT_EXIT_CODES.integrity,
194
+ scope, records, writers, checkpoint, failures, warnings,
195
+ });
196
+ const stored = await readKeyring(paths).catch(() => 'malformed');
197
+ const localCheckpoints = await readLocalCheckpoints(paths);
198
+ if (stored === null) {
199
+ if (segments.some(segment => segment.lines.length) || localCheckpoints.length)
200
+ failures.push({ reason: 'keyring-missing' });
201
+ else
202
+ warnings.push('ledger-empty');
203
+ return result(0, null);
204
+ }
205
+ const ringFailure = stored === 'malformed' ? 'keyring-malformed' : keyringFailure(stored, scope);
206
+ if (ringFailure) {
207
+ failures.push({ reason: ringFailure });
208
+ return result(0, null);
209
+ }
210
+ const keyring = stored;
211
+ if (options.trustedKeyids && !options.trustedKeyids.includes(keyring.keys.find(key => !keyring.rotations.some(rotation => rotation.to === key.keyid)).keyid)) {
212
+ failures.push({ reason: 'keyring-untrusted' });
213
+ }
214
+ const records = verifySegments(segments, keyring, scope, failures);
215
+ if (options.checkIndex !== false)
216
+ await verifyIndex(paths, segments, scope, failures, warnings);
217
+ // Local checkpoint chain.
218
+ let local = null;
219
+ for (const [position, entry] of localCheckpoints.entries()) {
220
+ const failure = checkpointFailure(entry.value, keyring, scope);
221
+ if (failure) {
222
+ failures.push({ reason: failure, checkpoint: entry.sequence });
223
+ local = null;
224
+ continue;
225
+ }
226
+ const value = entry.value;
227
+ if (value.sequence !== entry.sequence || entry.sequence !== position)
228
+ failures.push({ reason: 'checkpoint-sequence-gap', checkpoint: entry.sequence });
229
+ const expectedPrevious = position === 0 ? null : localCheckpoints[position - 1].value;
230
+ if ((expectedPrevious ? checkpointDigest(expectedPrevious) : null) !== value.previousCheckpoint)
231
+ failures.push({ reason: 'checkpoint-chain-broken', checkpoint: entry.sequence });
232
+ local = value;
233
+ }
234
+ // Independent sink.
235
+ const sink = options.sink === undefined ? ledger.sink : options.sink;
236
+ let anchor = local ? { value: local, source: 'local' } : null;
237
+ if (sink) {
238
+ let remote = null;
239
+ let readable = true;
240
+ try {
241
+ remote = await sink.latest(scope);
242
+ }
243
+ catch {
244
+ readable = false;
245
+ warnings.push('checkpoint-sink-unreadable');
246
+ }
247
+ if (readable) {
248
+ if (remote) {
249
+ const failure = checkpointFailure(remote, keyring, scope);
250
+ if (failure)
251
+ failures.push({ reason: `sink-${failure}`, checkpoint: remote.sequence });
252
+ else {
253
+ const sameLocal = localCheckpoints.find(entry => entry.sequence === remote.sequence)?.value;
254
+ if (sameLocal && checkpointDigest(sameLocal) !== checkpointDigest(remote))
255
+ failures.push({ reason: 'checkpoint-divergence', checkpoint: remote.sequence });
256
+ if (local && remote.sequence < local.sequence)
257
+ failures.push({ reason: 'checkpoint-sink-behind', checkpoint: remote.sequence });
258
+ if (!local || remote.sequence > local.sequence) {
259
+ if (!sameLocal)
260
+ warnings.push('local-checkpoint-behind');
261
+ anchor = { value: remote, source: 'sink' };
262
+ }
263
+ else if (remote.sequence === local.sequence) {
264
+ anchor = { value: remote, source: 'sink' };
265
+ }
266
+ }
267
+ }
268
+ else if (localCheckpoints.length) {
269
+ failures.push({ reason: 'checkpoint-sink-missing' });
270
+ }
271
+ }
272
+ }
273
+ if (!anchor)
274
+ warnings.push('no-checkpoint');
275
+ else {
276
+ const bySegment = new Map(segments.map(segment => [segment.writer, segment]));
277
+ for (const entry of anchor.value.writers) {
278
+ const segment = bySegment.get(entry.writer);
279
+ if (!segment) {
280
+ failures.push({ reason: 'segment-missing', writer: entry.writer, checkpoint: anchor.value.sequence });
281
+ continue;
282
+ }
283
+ const complete = segment.torn ? segment.lines.length - 1 : segment.lines.length;
284
+ if (complete < entry.count) {
285
+ failures.push({ reason: 'segment-truncated', writer: entry.writer, checkpoint: anchor.value.sequence });
286
+ continue;
287
+ }
288
+ if (segment.lines[entry.count - 1].decoded?.line.recordHash !== entry.headHash) {
289
+ failures.push({ reason: 'checkpoint-head-mismatch', writer: entry.writer, checkpoint: anchor.value.sequence });
290
+ }
291
+ }
292
+ }
293
+ return result(records, anchor ? { sequence: anchor.value.sequence, source: anchor.source, digest: checkpointDigest(anchor.value) } : null);
294
+ }
295
+ //# sourceMappingURL=verify.js.map
@@ -4,6 +4,8 @@ import { promises as fs } from 'node:fs';
4
4
  import path from 'node:path';
5
5
  const SCHEMA_VERSION = 'aiwg.evidence.bundle/v1';
6
6
  const RESTRICTED_KEY = /(?:^|_)(?:content|terminal|prompt|environment|env|credential|secret|password|authorization|bearer|token|private_key|certificate|restricted_(?:url|uri|link))(?:$|_)/i;
7
+ /** Restricted evidence member names; the effect ledger digest-only guard applies the same pattern. */
8
+ export const EVIDENCE_RESTRICTED_KEY = RESTRICTED_KEY;
7
9
  const ALLOWED_POLICY_KEYS = new Set(['restricted_content_grants']);
8
10
  const EVIDENCE_ROLES = new Set(['activity-export', 'report', 'source', 'eval-config', 'provenance']);
9
11
  function sha256(value) {
@@ -1468,6 +1468,33 @@ export const artifactsCommand = {
1468
1468
  },
1469
1469
  },
1470
1470
  };
1471
+ export const effectCommand = {
1472
+ id: 'effect',
1473
+ type: 'skill',
1474
+ name: 'Effect Ledger',
1475
+ description: 'Record, look up, reconcile and verify side effects in the signed effect ledger',
1476
+ version: '1.0.0',
1477
+ capabilities: ['cli', 'effects', 'idempotency', 'verification', 'signing', 'provenance'],
1478
+ keywords: ['effect', 'aiwg effect', 'effect ledger', 'record an effect', 'sign an effect', 'reconcile effect', 'did this PR merge', 'idempotent side effect', 'effect id', 'stale ledger lock'],
1479
+ category: 'utility',
1480
+ platforms: {
1481
+ claude: 'full',
1482
+ generic: 'full',
1483
+ },
1484
+ deployment: {
1485
+ pathTemplate: '.{platform}/commands/{id}.md',
1486
+ core: true,
1487
+ },
1488
+ metadata: {
1489
+ type: 'skill',
1490
+ triggerPhrases: ['record an effect', 'sign an effect', 'reconcile effect', 'did this PR merge', 'effect ledger', 'idempotent side effect', 'look up an effect', 'verify the effect ledger'],
1491
+ commandHint: {
1492
+ template: 'utility',
1493
+ argumentHint: 'id|intent|record|lookup|reconcile|verify|checkpoint|kinds|keys|recover-lock [options]',
1494
+ allowedTools: ['Read', 'Bash'],
1495
+ },
1496
+ },
1497
+ };
1471
1498
  // Research-corpus tools — radar/freshness subsystem (#1498)
1472
1499
  export const corpusCommand = {
1473
1500
  id: 'corpus',
@@ -3789,6 +3816,7 @@ export const commandDefinitions = [
3789
3816
  // Index + Discovery
3790
3817
  indexCommand,
3791
3818
  artifactsCommand,
3819
+ effectCommand,
3792
3820
  corpusCommand,
3793
3821
  researchQueryCommand,
3794
3822
  discoverCommand,