@aiwg/cli 2026.10.5 → 2026.10.7

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 (263) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +7 -3
  6. package/dist/src/a2a/agent-card.js +11 -0
  7. package/dist/src/api/index.d.ts +1 -0
  8. package/dist/src/api/index.js +1 -0
  9. package/dist/src/artifacts/browser-export.js +1 -0
  10. package/dist/src/artifacts/citation-parser.js +8 -1
  11. package/dist/src/artifacts/cli.js +57 -6
  12. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  13. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  14. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
  15. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
  16. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
  17. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
  18. package/dist/src/artifacts/fortemi-shard-export.js +284 -0
  19. package/dist/src/artifacts/graph-query.js +18 -8
  20. package/dist/src/artifacts/index-adapters.js +339 -0
  21. package/dist/src/artifacts/index-builder.js +143 -14
  22. package/dist/src/artifacts/index-files.js +15 -4
  23. package/dist/src/artifacts/index-preparation.js +2 -0
  24. package/dist/src/artifacts/index-reader.js +44 -0
  25. package/dist/src/artifacts/index-refresh.js +114 -55
  26. package/dist/src/artifacts/index-status.js +20 -5
  27. package/dist/src/artifacts/memory-maintenance.js +503 -0
  28. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  29. package/dist/src/artifacts/query-engine.js +263 -108
  30. package/dist/src/artifacts/relations-builder.js +2 -2
  31. package/dist/src/artifacts/source-graph.js +2 -2
  32. package/dist/src/artifacts/types.js +9 -1
  33. package/dist/src/auth/client.js +5 -1
  34. package/dist/src/auth/credential-store.js +5 -1
  35. package/dist/src/auth/sensitive-input.js +151 -0
  36. package/dist/src/channel/manager.mjs +3 -0
  37. package/dist/src/cli/handlers/agentcard.js +47 -25
  38. package/dist/src/cli/handlers/auth.js +22 -2
  39. package/dist/src/cli/handlers/decision.js +22 -3
  40. package/dist/src/cli/handlers/effect.js +4 -84
  41. package/dist/src/cli/handlers/guided-setup.js +470 -0
  42. package/dist/src/cli/handlers/help.js +2 -2
  43. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  44. package/dist/src/cli/handlers/index.js +4 -2
  45. package/dist/src/cli/handlers/init.js +41 -6
  46. package/dist/src/cli/handlers/install.js +6 -0
  47. package/dist/src/cli/handlers/local-executor.js +5 -3
  48. package/dist/src/cli/handlers/marketplace.js +15 -4
  49. package/dist/src/cli/handlers/mc.js +509 -53
  50. package/dist/src/cli/handlers/models.js +32 -2
  51. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  52. package/dist/src/cli/handlers/rules.js +1 -1
  53. package/dist/src/cli/handlers/script-runner.js +73 -2
  54. package/dist/src/cli/handlers/serve.js +5 -3
  55. package/dist/src/cli/handlers/sessions.js +1 -0
  56. package/dist/src/cli/handlers/setup.js +432 -1
  57. package/dist/src/cli/handlers/skill-lint.js +1 -1
  58. package/dist/src/cli/handlers/subcommands.js +118 -8
  59. package/dist/src/cli/handlers/use.js +335 -107
  60. package/dist/src/cli/handlers/workspace.js +3 -1
  61. package/dist/src/cli/prompt-utils.js +81 -0
  62. package/dist/src/cli/router.js +16 -1
  63. package/dist/src/config/aiwg-config.js +42 -0
  64. package/dist/src/config/cli.js +7 -3
  65. package/dist/src/config/decision-config.js +14 -1
  66. package/dist/src/config/gitignore.js +4 -0
  67. package/dist/src/config/native-sandbox-warnings.js +25 -0
  68. package/dist/src/config/workspace.js +2 -1
  69. package/dist/src/contributors/validation.js +6 -5
  70. package/dist/src/decision/adapters/jev.js +20 -31
  71. package/dist/src/decision/admission.js +7 -3
  72. package/dist/src/decision/ask.js +6 -1
  73. package/dist/src/decision/benchmark.js +482 -0
  74. package/dist/src/decision/ledger.js +22 -3
  75. package/dist/src/decision/pricing.js +147 -0
  76. package/dist/src/decision/qualification/gate-evidence.js +23 -18
  77. package/dist/src/decision/qualification/load-staged.js +576 -0
  78. package/dist/src/decision/qualification/load.js +109 -33
  79. package/dist/src/decision/qualification/runner.js +12 -5
  80. package/dist/src/decision/review/service.js +6 -4
  81. package/dist/src/decision/review/validate.js +23 -2
  82. package/dist/src/decision/savings.js +8 -3
  83. package/dist/src/discovery/index.js +492 -0
  84. package/dist/src/discovery/interview.js +238 -0
  85. package/dist/src/discovery/plan-apply.js +567 -0
  86. package/dist/src/discovery/plan-store.js +171 -0
  87. package/dist/src/discovery/plan-types.js +2 -0
  88. package/dist/src/discovery/plan-validation.js +139 -0
  89. package/dist/src/discovery/types.js +2 -0
  90. package/dist/src/effects/host.js +91 -0
  91. package/dist/src/extensions/commands/definitions.js +49 -9
  92. package/dist/src/extensions/manifest.js +71 -44
  93. package/dist/src/extensions/project-local-discovery.js +1 -1
  94. package/dist/src/extensions/project-local-gitignore.js +2 -0
  95. package/dist/src/extensions/project-quickref.js +18 -17
  96. package/dist/src/extensions/validation.js +32 -29
  97. package/dist/src/housekeeper/daemon-config.js +148 -0
  98. package/dist/src/housekeeper/ipc.js +84 -0
  99. package/dist/src/housekeeper/lifecycle.js +148 -0
  100. package/dist/src/housekeeper/platform.js +35 -0
  101. package/dist/src/housekeeper/status.js +70 -0
  102. package/dist/src/housekeeper/units.js +114 -0
  103. package/dist/src/issues/cli.js +16 -5
  104. package/dist/src/issues/live.js +44 -12
  105. package/dist/src/jobs/flow.js +22 -21
  106. package/dist/src/lint/rule-activation.js +20 -5
  107. package/dist/src/lint/runner.js +1 -1
  108. package/dist/src/marketplace/exchange.js +18 -6
  109. package/dist/src/marketplace/provenance.js +3 -2
  110. package/dist/src/mcp/adapters/codex-runtime.js +46 -76
  111. package/dist/src/mcp/cli.mjs +22 -96
  112. package/dist/src/mcp/config-file.mjs +6 -2
  113. package/dist/src/mcp/config-layers.mjs +23 -7
  114. package/dist/src/mcp/grok-build-config.mjs +2 -1
  115. package/dist/src/mcp/muse-native-config.mjs +2 -1
  116. package/dist/src/mcp/profiles.js +18 -31
  117. package/dist/src/mcp/profiles.mjs +18 -32
  118. package/dist/src/mcp/registry.js +7 -27
  119. package/dist/src/mcp/registry.mjs +7 -28
  120. package/dist/src/mcp/server.mjs +34 -1
  121. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  122. package/dist/src/mcp/tools/command-run.mjs +3 -2
  123. package/dist/src/mcp/tools/decision.mjs +49 -48
  124. package/dist/src/mcp/tools/discovery.mjs +46 -21
  125. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  126. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  127. package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
  128. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  129. package/dist/src/memory/audit.js +719 -0
  130. package/dist/src/memory/canonical-context.js +392 -13
  131. package/dist/src/memory/canonical-maintenance.js +95 -0
  132. package/dist/src/memory/cli-result.js +22 -0
  133. package/dist/src/memory/cli.js +176 -0
  134. package/dist/src/memory/context-pack.js +57 -6
  135. package/dist/src/memory/cost.js +512 -0
  136. package/dist/src/memory/firewall-classification.d.mts +7 -0
  137. package/dist/src/memory/firewall-classification.mjs +32 -0
  138. package/dist/src/memory/index.js +8 -0
  139. package/dist/src/memory/line-maintenance.js +68 -0
  140. package/dist/src/memory/maintenance-cli.js +286 -0
  141. package/dist/src/memory/maintenance-contract.js +136 -0
  142. package/dist/src/memory/maintenance-index.js +18 -0
  143. package/dist/src/memory/maintenance-owner.js +2 -0
  144. package/dist/src/memory/maintenance-record.js +138 -0
  145. package/dist/src/memory/maintenance.js +254 -0
  146. package/dist/src/memory/project-registry.js +2 -1
  147. package/dist/src/memory/provider-memory-adapter.js +154 -0
  148. package/dist/src/memory/recommend-cli.js +83 -0
  149. package/dist/src/memory/recommend.js +269 -0
  150. package/dist/src/memory/scheduled.js +81 -0
  151. package/dist/src/memory/taxonomy.js +44 -0
  152. package/dist/src/mission-protocol/codecs.js +19 -9
  153. package/dist/src/mission-protocol/worker-status.js +91 -1
  154. package/dist/src/models/config-loader.js +11 -1
  155. package/dist/src/models/model-capabilities.v1.json +74 -50
  156. package/dist/src/models/provider-policy.js +125 -62
  157. package/dist/src/models/resolver.js +45 -8
  158. package/dist/src/models/worker-installation.d.mts +8 -1
  159. package/dist/src/models/worker-installation.mjs +231 -38
  160. package/dist/src/models/wrapper-route.js +1 -1
  161. package/dist/src/output-modes/registry.js +8 -7
  162. package/dist/src/packages/adapters/git.js +99 -23
  163. package/dist/src/packages/adapters/gitea.js +8 -27
  164. package/dist/src/packages/adapters/github.js +7 -25
  165. package/dist/src/packages/registry.js +4 -4
  166. package/dist/src/plugin/metadata-validator.js +1 -1
  167. package/dist/src/process/cli-command.d.mts +5 -0
  168. package/dist/src/process/cli-command.mjs +42 -0
  169. package/dist/src/providers/deploy-version.d.mts +1 -0
  170. package/dist/src/providers/deploy-version.mjs +17 -0
  171. package/dist/src/providers/provider-definitions.js +19 -16
  172. package/dist/src/providers/skill-ownership.d.mts +11 -2
  173. package/dist/src/providers/skill-ownership.mjs +35 -17
  174. package/dist/src/rules/activation.d.mts +11 -2
  175. package/dist/src/rules/activation.mjs +82 -6
  176. package/dist/src/rules/inventory.js +1 -1
  177. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  178. package/dist/src/schema/zod-validation.d.mts +6 -0
  179. package/dist/src/schema/zod-validation.mjs +50 -0
  180. package/dist/src/serve/executor-registry.js +297 -20
  181. package/dist/src/serve/executor-token.d.mts +1 -0
  182. package/dist/src/serve/executor-token.mjs +8 -0
  183. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  184. package/dist/src/serve/worker-invocation.js +124 -15
  185. package/dist/src/sessions/adapters/claude.js +19 -18
  186. package/dist/src/sessions/adapters/codex.js +10 -9
  187. package/dist/src/sessions/adapters/copilot.js +14 -13
  188. package/dist/src/sessions/adapters/cursor.js +19 -18
  189. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  190. package/dist/src/sessions/adapters/factory.js +11 -10
  191. package/dist/src/sessions/adapters/generic.js +32 -23
  192. package/dist/src/sessions/adapters/grokbot.js +35 -35
  193. package/dist/src/sessions/adapters/hermes.js +20 -19
  194. package/dist/src/sessions/adapters/muse.js +50 -49
  195. package/dist/src/sessions/adapters/omp.js +5 -4
  196. package/dist/src/sessions/adapters/openclaw.js +30 -29
  197. package/dist/src/sessions/adapters/opencode.js +26 -26
  198. package/dist/src/sessions/adapters/openhuman.js +24 -23
  199. package/dist/src/sessions/adapters/pi.js +5 -4
  200. package/dist/src/sessions/adapters/windsurf.js +8 -8
  201. package/dist/src/sessions/analytics.js +10 -9
  202. package/dist/src/sessions/candidates.js +7 -6
  203. package/dist/src/sessions/contracts.js +63 -53
  204. package/dist/src/sessions/output-registration.js +11 -10
  205. package/dist/src/sessions/workspace-discovery.js +3 -4
  206. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  207. package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
  208. package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
  209. package/dist/src/smiths/context-pipeline/finalization.js +3 -2
  210. package/dist/src/smiths/context-pipeline/generator.js +7 -19
  211. package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
  212. package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
  213. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  214. package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
  215. package/dist/src/tracker/project-inputs.js +2 -0
  216. package/dist/src/update/checker.d.mts +4 -0
  217. package/dist/src/update/checker.mjs +14 -0
  218. package/dist/src/update/notifier.mjs +2 -4
  219. package/dist/src/writing/exemplar-selection.js +6 -6
  220. package/dist/src/writing/voice-evaluation.js +24 -23
  221. package/dist/src/writing/writer-migration.js +14 -13
  222. package/dist/src/writing/writer-profile.js +19 -18
  223. package/dist/src/writing/writing-brief.js +18 -17
  224. package/dist/src/writing/writing-receipt.js +47 -46
  225. package/package.json +5 -2
  226. package/schemas/decision/DecisionReview.schema.json +78 -1
  227. package/schemas/mission-v1.schema.json +3 -0
  228. package/tools/agents/deploy-agents.mjs +67 -39
  229. package/tools/agents/providers/antigravity.mjs +6 -1
  230. package/tools/agents/providers/base.mjs +24 -14
  231. package/tools/agents/providers/claude.mjs +6 -3
  232. package/tools/agents/providers/codex.mjs +54 -57
  233. package/tools/agents/providers/context-write.mjs +28 -0
  234. package/tools/agents/providers/copilot.mjs +13 -4
  235. package/tools/agents/providers/cursor.mjs +1 -1
  236. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  237. package/tools/agents/providers/factory.mjs +7 -1
  238. package/tools/agents/providers/grok-build.mjs +18 -2
  239. package/tools/agents/providers/grokbot.mjs +1 -1
  240. package/tools/agents/providers/hermes.mjs +12 -15
  241. package/tools/agents/providers/muse-hooks.mjs +21 -14
  242. package/tools/agents/providers/muse.mjs +7 -2
  243. package/tools/agents/providers/omp.mjs +31 -5
  244. package/tools/agents/providers/opencode.mjs +14 -5
  245. package/tools/agents/providers/openhuman.mjs +1 -1
  246. package/tools/agents/providers/pi.mjs +1 -1
  247. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  248. package/tools/agents/providers/rule-delivery.mjs +289 -80
  249. package/tools/agents/providers/warp.mjs +4 -1
  250. package/tools/agents/providers/windsurf.mjs +13 -3
  251. package/tools/cli/workspace-status.mjs +5 -0
  252. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  253. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  254. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
  255. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
  256. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  257. package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
  258. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  259. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  260. package/tools/ralph-external/process-monitor.mjs +49 -8
  261. package/tools/ralph-external/session-launcher.mjs +405 -35
  262. package/tools/skills/deploy-skills-codex.mjs +11 -7
  263. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -1,14 +1,20 @@
1
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
2
- import { createHash } from 'node:crypto';
1
+ import { existsSync, mkdirSync, linkSync, statSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
3
  import { dirname, relative, resolve, sep } from 'node:path';
4
+ import { hostname } from 'node:os';
5
+ import { isDeepStrictEqual } from 'node:util';
4
6
  import { z } from 'zod';
7
+ import { dateTime } from '../schema/zod-validation.mjs';
8
+ import { validateMaintenancePlan } from './maintenance-contract.js';
9
+ import { loadMemoryRecord, memoryRecordId } from './maintenance-record.js';
10
+ import { projectAiwgPath, resolveProjectAiwgDir, resolveProjectAiwgDirForWrite, isProjectArtifactRootExternal } from '../config/project-artifacts.js';
5
11
  const DigestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/);
6
12
  const LocatorSchema = z.string().min(1).max(512).refine(value => !/[\r\n\0]/.test(value));
7
13
  export const CanonicalContextTargetSchema = z.enum([
8
14
  'project-fact', 'preference', 'goal', 'project', 'person', 'decision', 'operating-rule',
9
15
  ]);
10
16
  export const CanonicalContextClassificationSchema = z.enum(['public', 'internal']);
11
- export const CanonicalContextProposalSchema = z.object({
17
+ export const CanonicalContextProposalSchema = z.strictObject({
12
18
  target: CanonicalContextTargetSchema,
13
19
  key: z.string().min(1).max(128).regex(/^[a-z0-9][a-z0-9._-]*$/),
14
20
  value: z.string().min(1).max(8192),
@@ -18,9 +24,9 @@ export const CanonicalContextProposalSchema = z.object({
18
24
  reason: z.string().min(1).max(1024),
19
25
  scope: z.string().min(1).max(128),
20
26
  classification: CanonicalContextClassificationSchema,
21
- reviewAt: z.string().datetime().nullable().default(null),
22
- expiresAt: z.string().datetime().nullable().default(null),
23
- }).strict();
27
+ reviewAt: dateTime().nullable().default(null),
28
+ expiresAt: dateTime().nullable().default(null),
29
+ });
24
30
  function sha256(value) {
25
31
  return `sha256:${createHash('sha256').update(value).digest('hex')}`;
26
32
  }
@@ -54,17 +60,20 @@ function assertSafeProposal(proposal) {
54
60
  }
55
61
  }
56
62
  function ensureStorageInsideProject(projectRoot, target) {
57
- const root = realpathSync(projectRoot);
63
+ let rootAncestor = resolve(projectRoot);
64
+ while (!existsSync(rootAncestor))
65
+ rootAncestor = dirname(rootAncestor);
66
+ const root = resolve(realpathSync(rootAncestor), relative(rootAncestor, resolve(projectRoot)));
58
67
  const lexicalRoot = resolve(projectRoot);
59
68
  const candidate = resolve(target);
60
69
  if (candidate !== lexicalRoot && !candidate.startsWith(`${lexicalRoot}${sep}`)) {
61
- throw new Error('canonical context storage must remain inside the project');
70
+ throw new Error('canonical context storage must remain inside the resolved root');
62
71
  }
63
72
  let ancestor = candidate;
64
73
  while (!existsSync(ancestor))
65
74
  ancestor = dirname(ancestor);
66
75
  const actual = realpathSync(ancestor);
67
- if (actual !== root && !actual.startsWith(`${root}${sep}`)) {
76
+ if (existsSync(projectRoot) && actual !== root && !actual.startsWith(`${root}${sep}`)) {
68
77
  throw new Error('canonical context storage cannot traverse a link outside the project');
69
78
  }
70
79
  }
@@ -107,18 +116,73 @@ function proposalFromEntry(entry) {
107
116
  function storeDigest(store) {
108
117
  return sha256(JSON.stringify(store));
109
118
  }
119
+ /** Project the canonical owner data without creating a second record store. */
120
+ export function canonicalMemoryRecord(entry) {
121
+ const sourcePath = `.aiwg/context/compound-memory/context.json#${entry.entryId}`;
122
+ const digest = sha256(entry.value);
123
+ const id = memoryRecordId(sourcePath, digest);
124
+ const lifecycle = entry.maintenance?.lifecycle
125
+ ?? (entry.status === 'revoked' ? 'retired' : entry.status);
126
+ const tombstone = lifecycle === 'active' ? undefined : entry.maintenance?.tombstone ?? {
127
+ id, digest, disposition: lifecycle,
128
+ reason: entry.disposition?.reason ?? entry.reason,
129
+ reviewer: entry.disposition?.reviewer ?? entry.reviewer,
130
+ at: entry.disposition?.recordedAt ?? entry.updatedAt,
131
+ };
132
+ return loadMemoryRecord({
133
+ schemaVersion: 'aiwg.memory-record.v1', id, sourcePath, digest,
134
+ version: entry.maintenance?.version ?? 1,
135
+ aliases: [...new Set([entry.entryId, ...(entry.maintenance?.aliases ?? [])])],
136
+ supersedes: entry.maintenance?.supersedes ?? [],
137
+ mergedFrom: entry.maintenance?.mergedFrom ?? [],
138
+ splitFrom: entry.maintenance?.splitFrom ?? [],
139
+ scope: entry.maintenance?.scope ?? 'project',
140
+ placement: entry.maintenance?.placement ?? 'resident', lifecycle,
141
+ authority: entry.maintenance?.authority ?? {
142
+ owner: 'compound-memory', source: 'reviewer', protection: 'unprotected',
143
+ },
144
+ observation: {
145
+ status: 'on-disk', evidence: {
146
+ source: sourcePath, at: entry.updatedAt, session: null, provider: null, version: null,
147
+ },
148
+ },
149
+ ...(entry.importedFromWorkspace ? { sourceWorkspaceId: entry.importedFromWorkspace } : {}),
150
+ ...(tombstone ? { tombstone } : {}),
151
+ });
152
+ }
153
+ function maintenanceMetadata(record, category, prior, recordedAt) {
154
+ const { version, scope, placement, lifecycle, authority, aliases, supersedes, mergedFrom, splitFrom, tombstone } = record;
155
+ return { version, scope, placement, lifecycle, authority, aliases, supersedes, mergedFrom, splitFrom,
156
+ ...(tombstone ? { tombstone } : {}), ...(category ? { category } : {}),
157
+ ...(prior ? { history: [...(prior.maintenance?.history ?? []), {
158
+ record: canonicalMemoryRecord(prior), disposition: prior.disposition,
159
+ recordedAt: recordedAt ?? prior.updatedAt,
160
+ ...(prior.maintenance?.category ? { category: prior.maintenance.category } : {}),
161
+ }] } : {}),
162
+ };
163
+ }
110
164
  export class CanonicalContextRepository {
165
+ projectRoot;
111
166
  root;
112
167
  statePath;
113
168
  receiptRoot;
114
169
  lockPath;
115
170
  workspaceId;
116
171
  constructor(projectRoot) {
117
- this.root = resolve(projectRoot, '.aiwg/context/compound-memory');
172
+ this.projectRoot = projectRoot;
173
+ this.root = projectAiwgPath(projectRoot, 'context/compound-memory');
118
174
  this.statePath = resolve(this.root, 'context.json');
119
175
  this.receiptRoot = resolve(this.root, 'receipts');
120
176
  this.lockPath = resolve(this.root, '.lock');
121
- ensureStorageInsideProject(projectRoot, this.root);
177
+ if (isProjectArtifactRootExternal(projectRoot)) {
178
+ // Before artifact routing the store was always repository-local. Never
179
+ // silently hide that history behind an empty external store.
180
+ const legacy = resolve(projectRoot, '.aiwg/context/compound-memory');
181
+ if (existsSync(resolve(legacy, 'context.json')) && !existsSync(this.statePath)) {
182
+ throw new Error(`Canonical context exists at the repository-local path ${legacy}, but the configured artifact root is external. Move that directory to ${this.root} before using canonical context.`);
183
+ }
184
+ }
185
+ ensureStorageInsideProject(isProjectArtifactRootExternal(projectRoot) ? resolveProjectAiwgDir(projectRoot) : projectRoot, this.root);
122
186
  this.workspaceId = workspaceIdentity(projectRoot);
123
187
  }
124
188
  read() {
@@ -140,6 +204,292 @@ export class CanonicalContextRepository {
140
204
  }
141
205
  return store;
142
206
  }
207
+ snapshot() { return storeDigest(this.read()); }
208
+ /** Input to regenerated resident context. History remains available through export(). */
209
+ residentEntries() {
210
+ return Object.values(this.read().entries).filter(entry => {
211
+ const record = canonicalMemoryRecord(entry);
212
+ return record.lifecycle === 'active' && record.placement === 'resident';
213
+ });
214
+ }
215
+ maintenanceReceipt(key, operation) {
216
+ const receipt = this.read().maintenanceReceipts?.[key];
217
+ if (receipt && operation && receipt.operationId !== sha256(JSON.stringify({ operation, snapshot: operation.owner?.snapshot }))) {
218
+ throw new Error('Maintenance receipt key conflicts with another operation');
219
+ }
220
+ return receipt;
221
+ }
222
+ previewMaintenance(operation) {
223
+ const store = this.read();
224
+ if (!operation.owner || operation.owner.name !== 'compound-memory' || !operation.review) {
225
+ throw new Error('Canonical maintenance requires an owner binding and explicit review');
226
+ }
227
+ if (operation.owner.snapshot !== storeDigest(store))
228
+ throw new Error('Stale canonical owner snapshot');
229
+ const entry = store.entries[operation.owner.handle];
230
+ if (!entry)
231
+ throw new Error('Canonical maintenance entry was not found');
232
+ const record = canonicalMemoryRecord(entry);
233
+ if (record.authority.owner !== 'compound-memory')
234
+ throw new Error('Canonical maintenance cannot mutate another authority owner');
235
+ if (operation.target.id !== record.id && !record.aliases.includes(operation.target.id)) {
236
+ throw new Error('Canonical maintenance target does not match its owner handle');
237
+ }
238
+ validateMaintenancePlan({ schemaVersion: 'aiwg.maintenance-plan.v1', operations: [operation] }, Object.values(store.entries).map(canonicalMemoryRecord), { retirementBasis: operation.review.retirementBasis });
239
+ if (operation.sessionEffect !== 'next-session') {
240
+ throw new Error('Canonical maintenance can only promise next-session effect');
241
+ }
242
+ if (operation.op !== 'merge' && operation.related?.length)
243
+ throw new Error('Only merge may bind additional sources');
244
+ if (operation.op === 'restore' && (record.lifecycle === 'active'
245
+ || Object.values(store.entries).some(candidate => candidate.entryId !== entry.entryId
246
+ && candidate.target === entry.target && candidate.key === entry.key && candidate.status === 'active'))) {
247
+ throw new Error('Restore requires inactive history without an active conflicting entry');
248
+ }
249
+ if (operation.op === 'recategorize' && !operation.category)
250
+ throw new Error('Recategorize requires category');
251
+ if (operation.op === 'promote' || operation.op === 'demote') {
252
+ const ranks = { archived: 0, indexed: 1, 'task-context': 2, resident: 3 };
253
+ const direction = ranks[operation.to.placement] - ranks[operation.from.placement];
254
+ if ((operation.op === 'promote' && direction <= 0) || (operation.op === 'demote' && direction >= 0)
255
+ || operation.to.lifecycle !== operation.from.lifecycle) {
256
+ throw new Error('Placement maintenance requires the requested promotion or demotion');
257
+ }
258
+ }
259
+ else if (operation.to.placement !== operation.from.placement) {
260
+ throw new Error('Lifecycle and category maintenance cannot implicitly change placement');
261
+ }
262
+ if (operation.op === 'recategorize' && operation.to.lifecycle !== record.lifecycle) {
263
+ throw new Error('Recategorize cannot implicitly change lifecycle');
264
+ }
265
+ const replacements = operation.replacements ?? [];
266
+ if (['supersede', 'merge', 'split'].includes(operation.op)) {
267
+ if (operation.to.lifecycle !== 'superseded')
268
+ throw new Error('Replacement requires superseded lifecycle');
269
+ if ((operation.op === 'split' && replacements.length < 2)
270
+ || (operation.op !== 'split' && replacements.length !== 1)) {
271
+ throw new Error('Replacement operation requires the exact replacement count');
272
+ }
273
+ const keys = new Set();
274
+ for (const replacement of replacements) {
275
+ const proposal = CanonicalContextProposalSchema.parse({
276
+ ...proposalFromEntry(entry), key: replacement.key, value: replacement.value,
277
+ sourceRef: canonicalLocator(replacement.sourcePath ?? entry.sourceRef),
278
+ sourceDigest: replacement.sourcePath ? null : entry.sourceDigest,
279
+ reviewer: operation.review.reviewer, reason: operation.rationale,
280
+ });
281
+ assertSafeProposal(proposal);
282
+ const replacementId = stableEntryId(proposal);
283
+ if (keys.has(replacement.key) || store.entries[replacementId]) {
284
+ throw new Error('Replacement must have a unique new identity');
285
+ }
286
+ keys.add(replacement.key);
287
+ if (Object.values(store.entries).some(candidate => candidate.status === 'active'
288
+ && candidate.entryId !== entry.entryId && candidate.key === replacement.key
289
+ && candidate.target === entry.target && !(operation.related ?? []).some(binding => binding.id === candidate.entryId
290
+ || binding.id === canonicalMemoryRecord(candidate).id))) {
291
+ throw new Error('Replacement conflicts with an unreviewed active canonical entry');
292
+ }
293
+ }
294
+ }
295
+ else if (replacements.length)
296
+ throw new Error('This operation cannot replace canonical values');
297
+ if (operation.op === 'merge' && this.mergeEntries(store, operation).length < 2) {
298
+ throw new Error('Merge requires additional reviewed canonical sources');
299
+ }
300
+ return {
301
+ schemaVersion: 'aiwg.canonical-context-preview.v1', operation: 'maintenance',
302
+ operationId: sha256(JSON.stringify({ operation, snapshot: storeDigest(store) })),
303
+ workspaceId: this.workspaceId, storeDigest: storeDigest(store), duplicate: false,
304
+ confirmationRequired: true, conflicts: [], proposedEntryId: entry.entryId,
305
+ importCount: 0, diff: [{ field: entry.entryId, before: JSON.stringify(operation.from), after: JSON.stringify(operation.to) }],
306
+ };
307
+ }
308
+ mergeEntries(store, operation) {
309
+ const target = store.entries[operation.owner.handle];
310
+ const related = operation.related ?? [];
311
+ const sources = [target];
312
+ for (const binding of related) {
313
+ const source = Object.values(store.entries).find(candidate => {
314
+ const record = canonicalMemoryRecord(candidate);
315
+ return record.id === binding.id || record.aliases.includes(binding.id);
316
+ });
317
+ if (!source || sources.includes(source))
318
+ throw new Error('Merge source is missing or duplicated');
319
+ const record = canonicalMemoryRecord(source);
320
+ if (record.digest !== binding.digest || record.version !== binding.version)
321
+ throw new Error('Stale merge source digest or version');
322
+ sources.push(source);
323
+ }
324
+ for (const entry of sources) {
325
+ const record = canonicalMemoryRecord(entry);
326
+ const base = canonicalMemoryRecord(target);
327
+ if (record.lifecycle !== 'active' || record.scope !== base.scope
328
+ || !isDeepStrictEqual(record.authority, base.authority)
329
+ || record.placement !== base.placement || entry.scope !== target.scope || entry.classification !== target.classification) {
330
+ throw new Error('Merge sources must preserve scope, authority, placement and active lifecycle');
331
+ }
332
+ }
333
+ return sources;
334
+ }
335
+ publishOwnerLock(lockPath) {
336
+ // Publish complete owner identity with one link syscall. A crash before this
337
+ // point leaves only an unrelated temporary file, never an empty owner lock.
338
+ const temporary = `${lockPath}.${process.pid}.${randomUUID()}.tmp`;
339
+ try {
340
+ writeFileSync(temporary, JSON.stringify({ pid: process.pid, host: hostname() }), { flag: 'wx', mode: 0o600 });
341
+ linkSync(temporary, lockPath);
342
+ }
343
+ finally {
344
+ if (existsSync(temporary))
345
+ unlinkSync(temporary);
346
+ }
347
+ }
348
+ openOwnerLock(lockPath = this.lockPath, depth = 0) {
349
+ try {
350
+ return this.publishOwnerLock(lockPath);
351
+ }
352
+ catch (error) {
353
+ if (error.code !== 'EEXIST' || depth >= 8)
354
+ throw error;
355
+ // The same atomic lease protocol protects recovery for sync legacy and
356
+ // async maintenance callers, including recovery after a dead reclaimer.
357
+ const recoveryPath = `${lockPath}.recovery`;
358
+ this.openOwnerLock(recoveryPath, depth + 1);
359
+ try {
360
+ let owner;
361
+ try {
362
+ owner = JSON.parse(readFileSync(lockPath, 'utf8'));
363
+ }
364
+ catch (readError) {
365
+ if (readError.code === 'ENOENT')
366
+ return this.publishOwnerLock(lockPath);
367
+ }
368
+ if (!owner || typeof owner !== 'object' || Array.isArray(owner)
369
+ || typeof owner.host !== 'string' || !owner.host
370
+ || !Number.isSafeInteger(owner.pid) || owner.pid < 1) {
371
+ // A legacy writer could crash after exclusive create and before writing.
372
+ // Give fresh/partially written locks a bounded grace period.
373
+ if (Date.now() - statSync(lockPath).mtimeMs < 60_000)
374
+ throw error;
375
+ }
376
+ else {
377
+ if (owner.host !== hostname())
378
+ throw error;
379
+ try {
380
+ process.kill(owner.pid, 0);
381
+ throw error;
382
+ }
383
+ catch (probe) {
384
+ if (probe.code !== 'ESRCH')
385
+ throw probe;
386
+ }
387
+ }
388
+ unlinkSync(lockPath);
389
+ return this.publishOwnerLock(lockPath);
390
+ }
391
+ finally {
392
+ unlinkSync(recoveryPath);
393
+ }
394
+ }
395
+ }
396
+ async confirmMaintenance(input) {
397
+ resolveProjectAiwgDirForWrite(this.projectRoot);
398
+ mkdirSync(this.root, { recursive: true });
399
+ this.openOwnerLock();
400
+ try {
401
+ const store = this.read();
402
+ const prior = store.maintenanceReceipts?.[input.key];
403
+ if (prior) {
404
+ if (prior.operationId !== input.preview.operationId)
405
+ throw new Error('Maintenance receipt key conflicts with another operation');
406
+ return prior;
407
+ }
408
+ const preview = this.previewMaintenance(input.operation);
409
+ if (JSON.stringify(preview) !== JSON.stringify(input.preview)) {
410
+ throw new Error('Confirmation requires the exact current canonical maintenance preview');
411
+ }
412
+ const removesResident = input.operation.from.placement === 'resident'
413
+ && (input.operation.to.placement !== 'resident' || input.operation.to.lifecycle !== 'active');
414
+ if (removesResident && !input.verifyRetrieval) {
415
+ throw new Error('compound-memory resident removal is unsupported: no owner-native indexed retrieval projection is available');
416
+ }
417
+ if (removesResident && !await input.verifyRetrieval(input.operation)) {
418
+ throw new Error('Indexed retrieval must be verified before resident removal');
419
+ }
420
+ if (this.snapshot() !== preview.storeDigest)
421
+ throw new Error('Stale canonical owner snapshot after retrieval verification');
422
+ const now = new Date().toISOString();
423
+ const entry = store.entries[input.operation.owner.handle];
424
+ const original = canonicalMemoryRecord(entry);
425
+ const sources = input.operation.op === 'merge' ? this.mergeEntries(store, input.operation) : [entry];
426
+ const newIds = [];
427
+ for (const replacement of input.operation.replacements ?? []) {
428
+ const proposal = CanonicalContextProposalSchema.parse({
429
+ ...proposalFromEntry(entry), key: replacement.key, value: replacement.value,
430
+ sourceRef: canonicalLocator(replacement.sourcePath ?? entry.sourceRef),
431
+ sourceDigest: replacement.sourcePath ? null : entry.sourceDigest,
432
+ reviewer: input.operation.review.reviewer, reason: input.operation.rationale,
433
+ });
434
+ const entryId = stableEntryId(proposal);
435
+ const next = {
436
+ ...proposal, entryId, status: 'active', createdAt: now, updatedAt: now,
437
+ supersedes: entry.entryId, disposition: null, importedFromWorkspace: entry.importedFromWorkspace,
438
+ };
439
+ const nextRecord = canonicalMemoryRecord(next);
440
+ nextRecord.scope = original.scope;
441
+ nextRecord.authority = { ...original.authority };
442
+ nextRecord.placement = original.placement;
443
+ nextRecord.aliases = [...new Set([...nextRecord.aliases, ...sources.map(source => canonicalMemoryRecord(source).id)])];
444
+ const lineage = sources.map(source => canonicalMemoryRecord(source).id);
445
+ if (input.operation.op === 'merge')
446
+ nextRecord.mergedFrom = lineage;
447
+ else if (input.operation.op === 'split')
448
+ nextRecord.splitFrom = lineage;
449
+ else
450
+ nextRecord.supersedes = lineage;
451
+ next.maintenance = maintenanceMetadata(nextRecord, replacement.category ?? entry.maintenance?.category);
452
+ store.entries[entryId] = next;
453
+ newIds.push(entryId);
454
+ }
455
+ for (const source of sources) {
456
+ const record = canonicalMemoryRecord(source);
457
+ record.version += 1;
458
+ record.placement = input.operation.to.placement;
459
+ record.lifecycle = input.operation.to.lifecycle;
460
+ if (record.lifecycle !== 'active') {
461
+ record.tombstone = {
462
+ id: record.id, digest: record.digest, disposition: record.lifecycle,
463
+ reviewer: input.operation.review.reviewer, reason: input.operation.rationale, at: now,
464
+ ...(newIds[0] ? { successor: canonicalMemoryRecord(store.entries[newIds[0]]).id } : {}),
465
+ };
466
+ }
467
+ else
468
+ delete record.tombstone;
469
+ source.maintenance = maintenanceMetadata(record, input.operation.category ?? source.maintenance?.category, source, now);
470
+ source.status = record.lifecycle === 'active' ? 'active' : record.lifecycle === 'superseded' ? 'superseded' : 'revoked';
471
+ source.updatedAt = now;
472
+ source.disposition = record.lifecycle === 'active' ? null : {
473
+ reviewer: input.operation.review.reviewer, reason: input.operation.rationale, recordedAt: now,
474
+ };
475
+ }
476
+ store.revision += 1;
477
+ const receipt = {
478
+ schemaVersion: 'aiwg.canonical-context-receipt.v1',
479
+ receiptId: sha256(`${preview.operationId}\0${store.revision}`), operationId: preview.operationId,
480
+ operation: 'maintenance', workspaceId: this.workspaceId,
481
+ entryIds: [...sources.map(source => source.entryId), ...newIds],
482
+ revision: store.revision, duplicate: false, completedAt: now,
483
+ };
484
+ store.maintenanceReceipts = { ...store.maintenanceReceipts, [input.key]: receipt };
485
+ // Mutation and recovery receipt are a single atomic owner commit.
486
+ writeJsonAtomic(this.statePath, store);
487
+ return receipt;
488
+ }
489
+ finally {
490
+ unlinkSync(this.lockPath);
491
+ }
492
+ }
143
493
  previewUpsert(raw) {
144
494
  const proposal = CanonicalContextProposalSchema.parse({
145
495
  ...raw,
@@ -149,6 +499,11 @@ export class CanonicalContextRepository {
149
499
  const store = this.read();
150
500
  const entryId = stableEntryId(proposal);
151
501
  const active = Object.values(store.entries).find(entry => (entry.status === 'active' && entry.target === proposal.target && entry.key === proposal.key));
502
+ const prior = Object.values(store.entries).filter(entry => entry.target === proposal.target && entry.key === proposal.key);
503
+ if ((!active && prior.some(entry => canonicalMemoryRecord(entry).lifecycle === 'retired'))
504
+ || (active && canonicalMemoryRecord(active).placement !== 'resident' && active.entryId !== entryId)) {
505
+ throw new Error('Canonical retired or demoted content requires reviewed maintenance or restore');
506
+ }
152
507
  const duplicate = active?.entryId === entryId;
153
508
  const conflicts = active && !duplicate
154
509
  ? [{ entryId: active.entryId, valueDigest: sha256(active.value) }]
@@ -241,8 +596,9 @@ export class CanonicalContextRepository {
241
596
  };
242
597
  }
243
598
  confirm(input) {
599
+ resolveProjectAiwgDirForWrite(this.projectRoot);
244
600
  mkdirSync(this.root, { recursive: true });
245
- const lock = openSync(this.lockPath, 'wx', 0o600);
601
+ this.openOwnerLock();
246
602
  try {
247
603
  const requestedReceiptPath = resolve(this.receiptRoot, `${input.preview.operationId.replace(':', '_')}.json`);
248
604
  if (existsSync(requestedReceiptPath)) {
@@ -273,6 +629,12 @@ export class CanonicalContextRepository {
273
629
  if (!current.duplicate) {
274
630
  const active = Object.values(store.entries).find(entry => (entry.status === 'active' && entry.target === proposal.target && entry.key === proposal.key));
275
631
  if (active) {
632
+ const record = canonicalMemoryRecord(active);
633
+ record.version += 1;
634
+ record.lifecycle = 'superseded';
635
+ record.tombstone = { id: record.id, digest: record.digest, disposition: 'superseded',
636
+ reviewer: proposal.reviewer, reason: proposal.reason, at: now };
637
+ active.maintenance = maintenanceMetadata(record, active.maintenance?.category, active, now);
276
638
  active.status = 'superseded';
277
639
  active.updatedAt = now;
278
640
  active.disposition = { reviewer: proposal.reviewer, reason: proposal.reason, recordedAt: now };
@@ -288,12 +650,30 @@ export class CanonicalContextRepository {
288
650
  disposition: null,
289
651
  importedFromWorkspace: null,
290
652
  };
653
+ if (active?.maintenance) {
654
+ const successor = canonicalMemoryRecord(store.entries[current.proposedEntryId]);
655
+ const predecessor = canonicalMemoryRecord(active);
656
+ successor.scope = predecessor.scope;
657
+ successor.authority = { ...predecessor.authority };
658
+ successor.placement = predecessor.placement;
659
+ successor.supersedes = [predecessor.id];
660
+ successor.aliases.push(predecessor.id);
661
+ store.entries[current.proposedEntryId].maintenance = maintenanceMetadata(successor, active.maintenance.category);
662
+ if (active.maintenance.tombstone)
663
+ active.maintenance.tombstone.successor = successor.id;
664
+ }
291
665
  changed.push(current.proposedEntryId);
292
666
  }
293
667
  }
294
668
  else if (current.operation === 'revoke' && input.revoke) {
295
669
  const entry = store.entries[input.revoke.entryId];
296
670
  if (!current.duplicate) {
671
+ const record = canonicalMemoryRecord(entry);
672
+ record.version += 1;
673
+ record.lifecycle = 'retired';
674
+ record.tombstone = { id: record.id, digest: record.digest, disposition: 'retired',
675
+ reviewer: input.revoke.reviewer, reason: input.revoke.reason, at: now };
676
+ entry.maintenance = maintenanceMetadata(record, entry.maintenance?.category, entry, now);
297
677
  entry.status = 'revoked';
298
678
  entry.updatedAt = now;
299
679
  entry.disposition = {
@@ -334,7 +714,6 @@ export class CanonicalContextRepository {
334
714
  return receipt;
335
715
  }
336
716
  finally {
337
- closeSync(lock);
338
717
  unlinkSync(this.lockPath);
339
718
  }
340
719
  }
@@ -0,0 +1,95 @@
1
+ import { refreshMaintenanceIndexes } from './maintenance-index.js';
2
+ import { CanonicalContextRepository, canonicalMemoryRecord } from './canonical-context.js';
3
+ import { sha256 } from './context-pack.js';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ /** Routes reviewed placement and lifecycle changes through the canonical owner. */
6
+ export class CanonicalMaintenanceOwner {
7
+ name = 'compound-memory';
8
+ repository;
9
+ projectRoot;
10
+ options;
11
+ constructor(projectRoot, options = {}) {
12
+ this.options = options;
13
+ this.projectRoot = projectRoot;
14
+ this.repository = new CanonicalContextRepository(projectRoot);
15
+ }
16
+ async records() {
17
+ return Object.values(this.repository.read().entries).map(canonicalMemoryRecord);
18
+ }
19
+ async snapshot() { return this.repository.snapshot(); }
20
+ async validate(operation) {
21
+ this.repository.previewMaintenance(operation);
22
+ }
23
+ async apply(operation, key) {
24
+ const prior = this.repository.maintenanceReceipt(key, operation);
25
+ if (prior) {
26
+ await this.refreshIndex();
27
+ return prior;
28
+ }
29
+ const preview = this.repository.previewMaintenance(operation);
30
+ const receipt = await this.repository.confirmMaintenance({
31
+ preview, operation, key, verifyRetrieval: operation => this.verifyRetrieval(operation),
32
+ });
33
+ await this.refreshIndex();
34
+ return receipt;
35
+ }
36
+ async receipt(key, options = {}) {
37
+ const receipt = this.repository.maintenanceReceipt(key);
38
+ if (receipt !== undefined && !options.readOnly)
39
+ await this.refreshIndex();
40
+ return receipt;
41
+ }
42
+ async verifyEndState(operation, key) {
43
+ const state = this.repository.read();
44
+ const receipt = this.repository.maintenanceReceipt(key);
45
+ const findings = [];
46
+ if (receipt && (receipt.operation !== 'maintenance'
47
+ || receipt.operationId !== sha256(JSON.stringify({ operation, snapshot: operation.owner?.snapshot })))) {
48
+ findings.push('Owner receipt does not bind the exact reviewed operation');
49
+ }
50
+ for (const target of [operation.target, ...(operation.related ?? [])]) {
51
+ const entry = Object.values(state.entries).find(item => canonicalMemoryRecord(item).id === target.id);
52
+ if (!entry)
53
+ continue;
54
+ const history = entry.maintenance?.history;
55
+ if (history !== undefined && !Array.isArray(history))
56
+ throw new Error('Invalid canonical maintenance history');
57
+ const prior = history?.find(item => item.record.id === target.id && item.record.version === target.version
58
+ && item.record.digest === target.digest && item.record.placement === operation.from.placement
59
+ && item.record.lifecycle === operation.from.lifecycle);
60
+ if (!prior)
61
+ findings.push(`Reviewed source history is missing: ${target.id}`);
62
+ if (operation.category && entry.maintenance?.category !== operation.category)
63
+ findings.push(`Reviewed category differs: ${target.id}`);
64
+ if (prior && !isDeepStrictEqual(prior.record.authority, canonicalMemoryRecord(entry).authority)) {
65
+ findings.push(`Source authority changed: ${target.id}`);
66
+ }
67
+ if (operation.op === 'restore' && !prior?.record.tombstone)
68
+ findings.push(`Recovery tombstone history is missing: ${target.id}`);
69
+ if (canonicalMemoryRecord(entry).lifecycle !== 'active' && !entry.maintenance?.tombstone)
70
+ findings.push(`Retained owner tombstone is missing: ${target.id}`);
71
+ if (receipt && !receipt.entryIds.includes(entry.entryId))
72
+ findings.push(`Receipt omits retained source: ${target.id}`);
73
+ }
74
+ for (const replacement of operation.replacements ?? []) {
75
+ const entry = Object.values(state.entries).find(item => item.key === replacement.key
76
+ && receipt?.entryIds.includes(item.entryId));
77
+ if (!entry || sha256(entry.value) !== sha256(replacement.value))
78
+ findings.push(`Receipt replacement differs: ${replacement.key}`);
79
+ if (replacement.category && entry?.maintenance?.category !== replacement.category)
80
+ findings.push(`Replacement category differs: ${replacement.key}`);
81
+ }
82
+ return findings;
83
+ }
84
+ async refreshIndex() {
85
+ await refreshMaintenanceIndexes(this.projectRoot);
86
+ }
87
+ async verifyRetrieval(operation) {
88
+ if (operation.owner?.name !== this.name)
89
+ return false;
90
+ if (this.options.verifyRetrieval)
91
+ return this.options.verifyRetrieval(operation);
92
+ throw new Error('compound-memory resident removal is unsupported: no owner-native indexed retrieval projection is available');
93
+ }
94
+ }
95
+ //# sourceMappingURL=canonical-maintenance.js.map
@@ -0,0 +1,22 @@
1
+ import { AiwgError } from '../cli/errors.js';
2
+ /** Public maintenance command statuses; legacy storage commands retain their contract. */
3
+ export const MEMORY_EXIT_CODES = { success: 0, findings: 1, usage: 2, failed: 3 };
4
+ export const MEMORY_EXIT_USAGE = 'Exit codes: 0 success/no findings/help; 1 findings; 2 usage or malformed input; 3 stale or failed operation.';
5
+ export function memoryUsage(read) {
6
+ try {
7
+ return read();
8
+ }
9
+ catch (cause) {
10
+ throw new AiwgError({ code: 'ERR_USAGE_MEMORY', exitCode: MEMORY_EXIT_CODES.usage,
11
+ message: cause instanceof Error ? cause.message : String(cause), cause });
12
+ }
13
+ }
14
+ export async function memoryInput(read) {
15
+ try {
16
+ return await read();
17
+ }
18
+ catch (cause) {
19
+ return memoryUsage(() => { throw cause; });
20
+ }
21
+ }
22
+ //# sourceMappingURL=cli-result.js.map