openplanr 1.16.1 → 1.17.0

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 (117) hide show
  1. package/README.md +5 -2
  2. package/dist/cli/commands/doctor.d.ts.map +1 -1
  3. package/dist/cli/commands/doctor.js +2 -0
  4. package/dist/cli/commands/doctor.js.map +1 -1
  5. package/dist/cli/commands/operate.d.ts.map +1 -1
  6. package/dist/cli/commands/operate.js +185 -6
  7. package/dist/cli/commands/operate.js.map +1 -1
  8. package/dist/cli/commands/runtime.d.ts.map +1 -1
  9. package/dist/cli/commands/runtime.js +22 -4
  10. package/dist/cli/commands/runtime.js.map +1 -1
  11. package/dist/cli/commands/setup.d.ts.map +1 -1
  12. package/dist/cli/commands/setup.js +18 -0
  13. package/dist/cli/commands/setup.js.map +1 -1
  14. package/dist/services/claude-plugin-service.d.ts +47 -0
  15. package/dist/services/claude-plugin-service.d.ts.map +1 -0
  16. package/dist/services/claude-plugin-service.js +259 -0
  17. package/dist/services/claude-plugin-service.js.map +1 -0
  18. package/dist/services/operate/advisors.d.ts +46 -1
  19. package/dist/services/operate/advisors.d.ts.map +1 -1
  20. package/dist/services/operate/advisors.js +124 -13
  21. package/dist/services/operate/advisors.js.map +1 -1
  22. package/dist/services/operate/artifacts.d.ts +27 -1
  23. package/dist/services/operate/artifacts.d.ts.map +1 -1
  24. package/dist/services/operate/artifacts.js +103 -1
  25. package/dist/services/operate/artifacts.js.map +1 -1
  26. package/dist/services/operate/cadence.d.ts +50 -0
  27. package/dist/services/operate/cadence.d.ts.map +1 -0
  28. package/dist/services/operate/cadence.js +56 -0
  29. package/dist/services/operate/cadence.js.map +1 -0
  30. package/dist/services/operate/citation-resolution.d.ts +101 -0
  31. package/dist/services/operate/citation-resolution.d.ts.map +1 -0
  32. package/dist/services/operate/citation-resolution.js +308 -0
  33. package/dist/services/operate/citation-resolution.js.map +1 -0
  34. package/dist/services/operate/config.d.ts +67 -1
  35. package/dist/services/operate/config.d.ts.map +1 -1
  36. package/dist/services/operate/config.js +158 -9
  37. package/dist/services/operate/config.js.map +1 -1
  38. package/dist/services/operate/decision-brief.d.ts +148 -0
  39. package/dist/services/operate/decision-brief.d.ts.map +1 -0
  40. package/dist/services/operate/decision-brief.js +194 -0
  41. package/dist/services/operate/decision-brief.js.map +1 -0
  42. package/dist/services/operate/doctor.d.ts.map +1 -1
  43. package/dist/services/operate/doctor.js +151 -3
  44. package/dist/services/operate/doctor.js.map +1 -1
  45. package/dist/services/operate/engine.d.ts +66 -2
  46. package/dist/services/operate/engine.d.ts.map +1 -1
  47. package/dist/services/operate/engine.js +219 -7
  48. package/dist/services/operate/engine.js.map +1 -1
  49. package/dist/services/operate/event-store.d.ts +35 -1
  50. package/dist/services/operate/event-store.d.ts.map +1 -1
  51. package/dist/services/operate/event-store.js +142 -29
  52. package/dist/services/operate/event-store.js.map +1 -1
  53. package/dist/services/operate/evidence-cache.d.ts +25 -1
  54. package/dist/services/operate/evidence-cache.d.ts.map +1 -1
  55. package/dist/services/operate/evidence-cache.js +62 -1
  56. package/dist/services/operate/evidence-cache.js.map +1 -1
  57. package/dist/services/operate/evidence.d.ts +20 -1
  58. package/dist/services/operate/evidence.d.ts.map +1 -1
  59. package/dist/services/operate/evidence.js +119 -0
  60. package/dist/services/operate/evidence.js.map +1 -1
  61. package/dist/services/operate/index.d.ts.map +1 -1
  62. package/dist/services/operate/index.js +72 -5
  63. package/dist/services/operate/index.js.map +1 -1
  64. package/dist/services/operate/interaction/action-service.d.ts +17 -0
  65. package/dist/services/operate/interaction/action-service.d.ts.map +1 -1
  66. package/dist/services/operate/interaction/action-service.js +19 -0
  67. package/dist/services/operate/interaction/action-service.js.map +1 -1
  68. package/dist/services/operate/journal.d.ts.map +1 -1
  69. package/dist/services/operate/journal.js +11 -5
  70. package/dist/services/operate/journal.js.map +1 -1
  71. package/dist/services/operate/maintenance.d.ts.map +1 -1
  72. package/dist/services/operate/maintenance.js +9 -2
  73. package/dist/services/operate/maintenance.js.map +1 -1
  74. package/dist/services/operate/migration.d.ts +68 -0
  75. package/dist/services/operate/migration.d.ts.map +1 -0
  76. package/dist/services/operate/migration.js +235 -0
  77. package/dist/services/operate/migration.js.map +1 -0
  78. package/dist/services/operate/mission-dispatch.d.ts +193 -0
  79. package/dist/services/operate/mission-dispatch.d.ts.map +1 -0
  80. package/dist/services/operate/mission-dispatch.js +444 -0
  81. package/dist/services/operate/mission-dispatch.js.map +1 -0
  82. package/dist/services/operate/projection-persistence.d.ts +6 -0
  83. package/dist/services/operate/projection-persistence.d.ts.map +1 -1
  84. package/dist/services/operate/projection-persistence.js +87 -11
  85. package/dist/services/operate/projection-persistence.js.map +1 -1
  86. package/dist/services/operate/projection.d.ts +26 -1
  87. package/dist/services/operate/projection.d.ts.map +1 -1
  88. package/dist/services/operate/projection.js +62 -0
  89. package/dist/services/operate/projection.js.map +1 -1
  90. package/dist/services/operate/protocol.d.ts +46 -2
  91. package/dist/services/operate/protocol.d.ts.map +1 -1
  92. package/dist/services/operate/protocol.js +57 -5
  93. package/dist/services/operate/protocol.js.map +1 -1
  94. package/dist/services/operate/read-only-providers.d.ts +37 -0
  95. package/dist/services/operate/read-only-providers.d.ts.map +1 -1
  96. package/dist/services/operate/read-only-providers.js +130 -0
  97. package/dist/services/operate/read-only-providers.js.map +1 -1
  98. package/dist/services/operate/reports.d.ts +13 -0
  99. package/dist/services/operate/reports.d.ts.map +1 -1
  100. package/dist/services/operate/reports.js +217 -1
  101. package/dist/services/operate/reports.js.map +1 -1
  102. package/dist/services/operate/routes.d.ts.map +1 -1
  103. package/dist/services/operate/routes.js +79 -5
  104. package/dist/services/operate/routes.js.map +1 -1
  105. package/dist/services/operate/types.d.ts +120 -4
  106. package/dist/services/operate/types.d.ts.map +1 -1
  107. package/dist/services/operate/types.js +13 -0
  108. package/dist/services/operate/types.js.map +1 -1
  109. package/dist/services/operate/workspace.d.ts +7 -0
  110. package/dist/services/operate/workspace.d.ts.map +1 -1
  111. package/dist/services/operate/workspace.js +14 -5
  112. package/dist/services/operate/workspace.js.map +1 -1
  113. package/dist/services/runtime-manager-service.d.ts +15 -0
  114. package/dist/services/runtime-manager-service.d.ts.map +1 -1
  115. package/dist/services/runtime-manager-service.js +151 -36
  116. package/dist/services/runtime-manager-service.js.map +1 -1
  117. package/package.json +2 -2
@@ -0,0 +1,308 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { resolvePipelinePackage } from '../pipeline-package-service.js';
5
+ import { resolvePlanrArtifactCitation } from './artifacts.js';
6
+ import { canonicalDigest } from './canonical.js';
7
+ import { assertOperatingArtifact } from './protocol.js';
8
+ import { gitRevisionResolves, readGitCommitSummary, readGitPathAtRevision, } from './read-only-providers.js';
9
+ import { redactSensitiveText } from './redaction.js';
10
+ import { OperateError, } from './types.js';
11
+ import { isPathInside } from './workspace.js';
12
+ /**
13
+ * FR3 (E-003) — resolve every citation an advisor returns against the cycle's
14
+ * pinned revision and snapshot the cited content into machine-local evidence.
15
+ *
16
+ * This module owns NO precedence and NO citation/gap shape. Every fact/reason
17
+ * mapping (path → line → revision → artifact) and the canonical resolution and
18
+ * unresolvable-citation gap records come from the installed pipeline's
19
+ * `lib/operate/citation.mjs` (`operating-citation@1.3.0`). This module supplies
20
+ * the facts — computed honestly against live git/`.planr/` state at the pinned
21
+ * revision — and, on a resolved outcome, snapshots the cited bytes through the
22
+ * standard redaction + secret-scan path before attaching the resulting evidence
23
+ * ID to the proposal. A citation with any unresolvable component NEVER produces
24
+ * an evidence ID: it fails closed into a gap, and its proposal never reaches
25
+ * consolidation.
26
+ *
27
+ * The one classification this module adds beyond the library — which by design
28
+ * has no repo access and cannot make it — is the DISTINCT dirty-working-tree
29
+ * rejection. The pin is frozen at cycle start; a citation into content that is
30
+ * present in the working tree but not committed at the pin would otherwise be
31
+ * indistinguishable from a fabricated path. Naming it distinctly keeps a
32
+ * legitimate finding that points at in-flight work from being silently
33
+ * discarded as a fabrication.
34
+ */
35
+ const DEFAULT_SNAPSHOT_TTL_MS = 24 * 60 * 60 * 1_000;
36
+ let cachedModule = null;
37
+ async function loadCitationModule() {
38
+ cachedModule ??= (async () => {
39
+ const pkg = resolvePipelinePackage(false);
40
+ if (!pkg) {
41
+ throw new OperateError('E_PIPELINE_NOT_INSTALLED', 'Citation resolution requires the pipeline package with Protocol v1.3 (operating-citation@1.3.0).');
42
+ }
43
+ const modulePath = path.join(pkg.root, 'lib', 'operate', 'citation.mjs');
44
+ const loaded = (await import(pathToFileURL(modulePath).href));
45
+ if (typeof loaded.resolveOperatingCitation !== 'function' ||
46
+ typeof loaded.validateOperatingCitation !== 'function' ||
47
+ typeof loaded.buildUnresolvableCitationGap !== 'function') {
48
+ throw new OperateError('E_PIPELINE_VERSION_INCOMPATIBLE', `Installed planr-pipeline ${pkg.version} does not export the Protocol v1.3 citation resolver.`);
49
+ }
50
+ return loaded;
51
+ })();
52
+ return cachedModule;
53
+ }
54
+ function citationKind(citation) {
55
+ if (typeof citation.repositoryPath === 'string')
56
+ return 'repo-path';
57
+ if (typeof citation.gitRevision === 'string')
58
+ return 'git-revision';
59
+ if (typeof citation.planrArtifactId === 'string')
60
+ return 'planr-artifact';
61
+ return null;
62
+ }
63
+ function stableCitationKey(citation) {
64
+ if (typeof citation.citationKey === 'string' && /^[A-Za-z0-9._-]+$/.test(citation.citationKey)) {
65
+ return citation.citationKey;
66
+ }
67
+ return `cite-${canonicalDigest(citation).slice('sha256:'.length, 34)}`;
68
+ }
69
+ function lineRangeWithin(range, lineCount) {
70
+ return (Number.isInteger(range.start) &&
71
+ Number.isInteger(range.end) &&
72
+ range.start >= 1 &&
73
+ range.end >= range.start &&
74
+ range.end <= lineCount);
75
+ }
76
+ async function workingTreeHasFile(projectRoot, relativePath) {
77
+ const root = path.resolve(projectRoot);
78
+ const target = path.resolve(root, relativePath);
79
+ if (!isPathInside(root, target))
80
+ return false;
81
+ try {
82
+ return (await stat(target)).isFile();
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ function dirtyWorkingTreeGap(citation, context, now) {
89
+ const range = citation.lineRange
90
+ ? ` lines ${citation.lineRange.start}-${citation.lineRange.end}`
91
+ : '';
92
+ const createdAt = now.toISOString();
93
+ const gap = {
94
+ kind: 'operating-data-gap',
95
+ schemaVersion: '1.0.0',
96
+ protocolVersion: '1.3.0',
97
+ id: `GAP-${canonicalDigest({ citation, reason: 'dirty-working-tree' }).slice('sha256:'.length)}`,
98
+ cycleId: context.cycleId,
99
+ category: 'unresolvable-citation',
100
+ question: `The repository path "${citation.repositoryPath}"${range} cites uncommitted working-tree ` +
101
+ `content that is not present at pinned revision ${citation.pinnedRevision}; commit the change ` +
102
+ 'or cite the pinned revision before this proposal can be accepted.',
103
+ reason: 'dirty-working-tree',
104
+ unblocks: [],
105
+ status: 'open',
106
+ owner: context.owner && context.owner.length > 0 ? context.owner : 'chair',
107
+ evidenceRefs: [],
108
+ createdAt,
109
+ updatedAt: createdAt,
110
+ };
111
+ return gap;
112
+ }
113
+ async function observeCitation(citation, context, sensitivity) {
114
+ const kind = citationKind(citation);
115
+ if (kind === 'repo-path' && citation.repositoryPath) {
116
+ const blob = await readGitPathAtRevision(context.projectRoot, citation.pinnedRevision, citation.repositoryPath);
117
+ const inWorkingTree = blob.exists
118
+ ? true
119
+ : await workingTreeHasFile(context.projectRoot, citation.repositoryPath);
120
+ return {
121
+ kind,
122
+ pathExistsAtRevision: blob.exists,
123
+ inWorkingTree,
124
+ content: blob.content,
125
+ lineCount: blob.lineCount,
126
+ location: citation.repositoryPath,
127
+ };
128
+ }
129
+ if (kind === 'git-revision' && citation.gitRevision) {
130
+ const resolves = await gitRevisionResolves(context.projectRoot, citation.gitRevision);
131
+ const summary = resolves
132
+ ? await readGitCommitSummary(context.projectRoot, citation.gitRevision)
133
+ : null;
134
+ return {
135
+ kind,
136
+ revisionResolves: resolves,
137
+ content: summary ?? `commit ${citation.gitRevision}`,
138
+ location: `git:${citation.gitRevision}`,
139
+ };
140
+ }
141
+ // planr-artifact
142
+ const resolution = await resolvePlanrArtifactCitation({
143
+ projectRoot: context.projectRoot,
144
+ pinnedRevision: citation.pinnedRevision,
145
+ artifactId: citation.planrArtifactId,
146
+ sensitivity,
147
+ });
148
+ return {
149
+ kind: 'planr-artifact',
150
+ artifactExists: resolution.artifactExists,
151
+ content: resolution.content,
152
+ location: resolution.location,
153
+ preRedacted: true,
154
+ };
155
+ }
156
+ function factsFor(citation, observation, cyclePinnedRevision, sensitivity) {
157
+ const facts = { sensitivity };
158
+ if (observation.kind === 'repo-path') {
159
+ facts.pathExistsAtRevision = observation.pathExistsAtRevision === true;
160
+ if (citation.lineRange) {
161
+ facts.lineRangeInBounds =
162
+ observation.pathExistsAtRevision === true &&
163
+ lineRangeWithin(citation.lineRange, observation.lineCount ?? 0);
164
+ }
165
+ facts.revisionIsCurrent = citation.pinnedRevision === cyclePinnedRevision;
166
+ }
167
+ else if (observation.kind === 'git-revision') {
168
+ facts.revisionIsCurrent =
169
+ citation.pinnedRevision === cyclePinnedRevision && observation.revisionResolves === true;
170
+ }
171
+ else {
172
+ facts.artifactExists = observation.artifactExists === true;
173
+ }
174
+ return facts;
175
+ }
176
+ /**
177
+ * Resolve one citation against the cycle's pinned revision. Never throws for a
178
+ * resolvable/unresolvable outcome: a resolved citation carries an `evidenceId`
179
+ * whose snapshot has been persisted to machine-local evidence; a rejected one
180
+ * carries a distinct `reason` and a single unresolvable-citation `gap`.
181
+ */
182
+ export async function resolveOperatingCitationAtPin(citation, context) {
183
+ const module = await loadCitationModule();
184
+ try {
185
+ module.validateOperatingCitation(citation);
186
+ }
187
+ catch (error) {
188
+ throw new OperateError('E_OPERATE_STATE_INVALID', `Citation is not a valid operating-citation@1.3.0 anchor: ${error instanceof Error ? error.message : String(error)}.`);
189
+ }
190
+ const now = context.now ?? new Date();
191
+ const citationKey = stableCitationKey(citation);
192
+ const keyed = { ...citation, citationKey };
193
+ const sensitivity = context.sensitivityFor?.(citation) ?? context.defaultSensitivity ?? 'internal';
194
+ const observation = await observeCitation(keyed, context, sensitivity);
195
+ // Distinct dirty-working-tree classification, ahead of the library's
196
+ // path-existence precedence: a cited path absent at the pin but present and
197
+ // uncommitted in a dirty working tree is named separately from a fabrication.
198
+ if (observation.kind === 'repo-path' &&
199
+ observation.pathExistsAtRevision === false &&
200
+ observation.inWorkingTree === true &&
201
+ context.descriptor.dirtyFingerprint !== null) {
202
+ const gap = await assertOperatingArtifact('operating-data-gap', dirtyWorkingTreeGap(keyed, context, now));
203
+ return {
204
+ citation: keyed,
205
+ citationKey,
206
+ outcome: 'rejected',
207
+ reason: 'dirty-working-tree',
208
+ gap,
209
+ sensitivity,
210
+ };
211
+ }
212
+ const facts = factsFor(keyed, observation, context.descriptor.pinnedRevision, sensitivity);
213
+ const resolution = module.resolveOperatingCitation(keyed, facts);
214
+ if (resolution.outcome === 'rejected') {
215
+ const gap = module.buildUnresolvableCitationGap(keyed, resolution, {
216
+ cycleId: context.cycleId,
217
+ createdAt: now.toISOString(),
218
+ owner: context.owner,
219
+ affectedRoles: context.affectedRoles,
220
+ });
221
+ return {
222
+ citation: keyed,
223
+ citationKey,
224
+ outcome: 'rejected',
225
+ reason: resolution.reason,
226
+ gap,
227
+ sensitivity,
228
+ };
229
+ }
230
+ // Resolved: snapshot the cited bytes through the standard redaction path and
231
+ // persist them as machine-local evidence under the resolver-minted id.
232
+ const rawContent = observation.content ?? '';
233
+ const content = observation.preRedacted ? rawContent : redactSensitiveText(rawContent).value;
234
+ await context.cache.putCitationSnapshot({
235
+ evidenceId: resolution.evidenceId,
236
+ citationKey,
237
+ snapshotDigest: resolution.snapshotDigest,
238
+ sourceLocation: observation.location ?? citationKey,
239
+ sensitivity,
240
+ content,
241
+ }, context.snapshotTtlMs ?? DEFAULT_SNAPSHOT_TTL_MS, now);
242
+ return {
243
+ citation: keyed,
244
+ citationKey,
245
+ outcome: 'resolved',
246
+ evidenceId: resolution.evidenceId,
247
+ snapshotDigest: resolution.snapshotDigest,
248
+ sensitivity,
249
+ };
250
+ }
251
+ /**
252
+ * Resolve and enforce every proposal's citations before consolidation. A
253
+ * proposal whose citations all resolve is accepted with its minted evidence IDs
254
+ * attached; a proposal with ANY unresolvable citation is dropped and a single
255
+ * unresolvable-citation gap is opened in its place. Nothing with an unresolved
256
+ * citation is ever returned in `accepted`, so it can never reach consolidation.
257
+ */
258
+ export async function enforceProposalCitations(proposals, context) {
259
+ const accepted = [];
260
+ const rejected = [];
261
+ const gapsById = new Map();
262
+ const evidenceIds = new Set();
263
+ const resolutions = [];
264
+ for (const proposal of proposals) {
265
+ const citations = Array.isArray(proposal.citations) ? proposal.citations : [];
266
+ const proposalResolutions = [];
267
+ for (const citation of citations) {
268
+ const resolved = await resolveOperatingCitationAtPin(citation, context);
269
+ proposalResolutions.push(resolved);
270
+ resolutions.push(resolved);
271
+ }
272
+ const rejectedCitations = proposalResolutions.filter((resolution) => resolution.outcome === 'rejected');
273
+ if (rejectedCitations.length > 0) {
274
+ // Exactly one gap per rejected proposal: the first rejected citation is the
275
+ // representative cause and owns the opened gap.
276
+ const primary = rejectedCitations[0];
277
+ if (primary.gap)
278
+ gapsById.set(primary.gap.id, primary.gap);
279
+ rejected.push({
280
+ proposalKey: proposal.proposalKey,
281
+ reason: primary.reason,
282
+ reasons: [
283
+ ...new Set(rejectedCitations
284
+ .map((resolution) => resolution.reason)
285
+ .filter((reason) => Boolean(reason))),
286
+ ].sort(),
287
+ gapId: primary.gap?.id ?? '',
288
+ });
289
+ continue;
290
+ }
291
+ const evidenceRefs = [
292
+ ...new Set(proposalResolutions
293
+ .map((resolution) => resolution.evidenceId)
294
+ .filter((id) => Boolean(id))),
295
+ ].sort();
296
+ for (const id of evidenceRefs)
297
+ evidenceIds.add(id);
298
+ accepted.push({ proposal, evidenceRefs });
299
+ }
300
+ return {
301
+ accepted,
302
+ rejected,
303
+ gaps: [...gapsById.values()].sort((left, right) => left.id.localeCompare(right.id)),
304
+ evidenceIds: [...evidenceIds].sort(),
305
+ resolutions,
306
+ };
307
+ }
308
+ //# sourceMappingURL=citation-resolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citation-resolution.js","sourceRoot":"","sources":["../../../src/services/operate/citation-resolution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EACL,YAAY,GAGb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;AA+ErD,IAAI,YAAY,GAA4C,IAAI,CAAC;AAEjE,KAAK,UAAU,kBAAkB;IAC/B,YAAY,KAAK,CAAC,KAAK,IAAI,EAAE;QAC3B,MAAM,GAAG,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,YAAY,CACpB,0BAA0B,EAC1B,kGAAkG,CACnG,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAC1B,aAAa,CAAC,UAAU,CAAC,CAAC,IAAI,CAC/B,CAAqC,CAAC;QACvC,IACE,OAAO,MAAM,CAAC,wBAAwB,KAAK,UAAU;YACrD,OAAO,MAAM,CAAC,yBAAyB,KAAK,UAAU;YACtD,OAAO,MAAM,CAAC,4BAA4B,KAAK,UAAU,EACzD,CAAC;YACD,MAAM,IAAI,YAAY,CACpB,iCAAiC,EACjC,4BAA4B,GAAG,CAAC,OAAO,uDAAuD,CAC/F,CAAC;QACJ,CAAC;QACD,OAAO,MAAiC,CAAC;IAC3C,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,YAAY,CAAC;AACtB,CAAC;AA8BD,SAAS,YAAY,CACnB,QAA2B;IAE3B,IAAI,OAAO,QAAQ,CAAC,cAAc,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC;IACpE,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,cAAc,CAAC;IACpE,IAAI,OAAO,QAAQ,CAAC,eAAe,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAC;IAC1E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CAAC,QAA2B;IACpD,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ,IAAI,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/F,OAAO,QAAQ,CAAC,WAAW,CAAC;IAC9B,CAAC;IACD,OAAO,QAAQ,eAAe,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,SAAS,eAAe,CAAC,KAAqC,EAAE,SAAiB;IAC/E,OAAO,CACL,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC;QAC7B,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;QAC3B,KAAK,CAAC,KAAK,IAAI,CAAC;QAChB,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,KAAK;QACxB,KAAK,CAAC,GAAG,IAAI,SAAS,CACvB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,WAAmB,EAAE,YAAoB;IACzE,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAChD,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAC1B,QAA2B,EAC3B,OAAkC,EAClC,GAAS;IAET,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS;QAC9B,CAAC,CAAC,UAAU,QAAQ,CAAC,SAAS,CAAC,KAAK,IAAI,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;QAChE,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,GAAG,GAAyB;QAChC,IAAI,EAAE,oBAAoB;QAC1B,aAAa,EAAE,OAAO;QACtB,eAAe,EAAE,OAAO;QACxB,EAAE,EAAE,OAAO,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE;QAChG,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ,EAAE,uBAAuB;QACjC,QAAQ,EACN,wBAAwB,QAAQ,CAAC,cAAc,IAAI,KAAK,kCAAkC;YAC1F,kDAAkD,QAAQ,CAAC,cAAc,sBAAsB;YAC/F,mEAAmE;QACrE,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,MAAM;QACd,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;QAC1E,YAAY,EAAE,EAAE;QAChB,SAAS;QACT,SAAS,EAAE,SAAS;KACrB,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC;AAeD,KAAK,UAAU,eAAe,CAC5B,QAA2B,EAC3B,OAAkC,EAClC,WAAiC;IAEjC,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,WAAW,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,qBAAqB,CACtC,OAAO,CAAC,WAAW,EACnB,QAAQ,CAAC,cAAc,EACvB,QAAQ,CAAC,cAAc,CACxB,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM;YAC/B,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,MAAM,kBAAkB,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC3E,OAAO;YACL,IAAI;YACJ,oBAAoB,EAAE,IAAI,CAAC,MAAM;YACjC,aAAa;YACb,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,QAAQ,CAAC,cAAc;SAClC,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,KAAK,cAAc,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,QAAQ;YACtB,CAAC,CAAC,MAAM,oBAAoB,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC;YACvE,CAAC,CAAC,IAAI,CAAC;QACT,OAAO;YACL,IAAI;YACJ,gBAAgB,EAAE,QAAQ;YAC1B,OAAO,EAAE,OAAO,IAAI,UAAU,QAAQ,CAAC,WAAW,EAAE;YACpD,QAAQ,EAAE,OAAO,QAAQ,CAAC,WAAW,EAAE;SACxC,CAAC;IACJ,CAAC;IACD,iBAAiB;IACjB,MAAM,UAAU,GAAG,MAAM,4BAA4B,CAAC;QACpD,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,cAAc,EAAE,QAAQ,CAAC,cAAc;QACvC,UAAU,EAAE,QAAQ,CAAC,eAAyB;QAC9C,WAAW;KACZ,CAAC,CAAC;IACH,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,WAAW,EAAE,IAAI;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CACf,QAA2B,EAC3B,WAAgC,EAChC,mBAA2B,EAC3B,WAAiC;IAEjC,MAAM,KAAK,GAAkB,EAAE,WAAW,EAAE,CAAC;IAC7C,IAAI,WAAW,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACrC,KAAK,CAAC,oBAAoB,GAAG,WAAW,CAAC,oBAAoB,KAAK,IAAI,CAAC;QACvE,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;YACvB,KAAK,CAAC,iBAAiB;gBACrB,WAAW,CAAC,oBAAoB,KAAK,IAAI;oBACzC,eAAe,CAAC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,CAAC,iBAAiB,GAAG,QAAQ,CAAC,cAAc,KAAK,mBAAmB,CAAC;IAC5E,CAAC;SAAM,IAAI,WAAW,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAC/C,KAAK,CAAC,iBAAiB;YACrB,QAAQ,CAAC,cAAc,KAAK,mBAAmB,IAAI,WAAW,CAAC,gBAAgB,KAAK,IAAI,CAAC;IAC7F,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,cAAc,GAAG,WAAW,CAAC,cAAc,KAAK,IAAI,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,QAA2B,EAC3B,OAAkC;IAElC,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CACpB,yBAAyB,EACzB,4DACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,KAAK,GAAsB,EAAE,GAAG,QAAQ,EAAE,WAAW,EAAE,CAAC;IAC9D,MAAM,WAAW,GACf,OAAO,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,kBAAkB,IAAI,UAAU,CAAC;IAEjF,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IAEvE,qEAAqE;IACrE,4EAA4E;IAC5E,8EAA8E;IAC9E,IACE,WAAW,CAAC,IAAI,KAAK,WAAW;QAChC,WAAW,CAAC,oBAAoB,KAAK,KAAK;QAC1C,WAAW,CAAC,aAAa,KAAK,IAAI;QAClC,OAAO,CAAC,UAAU,CAAC,gBAAgB,KAAK,IAAI,EAC5C,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,uBAAuB,CACvC,oBAAoB,EACpB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CACzC,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,WAAW;YACX,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,oBAAoB;YAC5B,GAAG;YACH,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC3F,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEjE,IAAI,UAAU,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,4BAA4B,CAAC,KAAK,EAAE,UAAU,EAAE;YACjE,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,SAAS,EAAE,GAAG,CAAC,WAAW,EAAE;YAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,aAAa,EAAE,OAAO,CAAC,aAAa;SACrC,CAAC,CAAC;QACH,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,WAAW;YACX,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,GAAG;YACH,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,uEAAuE;IACvE,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,IAAI,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC;IAC7F,MAAM,OAAO,CAAC,KAAK,CAAC,mBAAmB,CACrC;QACE,UAAU,EAAE,UAAU,CAAC,UAAoB;QAC3C,WAAW;QACX,cAAc,EAAE,UAAU,CAAC,cAAoC;QAC/D,cAAc,EAAE,WAAW,CAAC,QAAQ,IAAI,WAAW;QACnD,WAAW;QACX,OAAO;KACR,EACD,OAAO,CAAC,aAAa,IAAI,uBAAuB,EAChD,GAAG,CACJ,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,WAAW;QACX,OAAO,EAAE,UAAU;QACnB,UAAU,EAAE,UAAU,CAAC,UAAU;QACjC,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,WAAW;KACZ,CAAC;AACJ,CAAC;AA4BD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,SAAuB,EACvB,OAAkC;IAElC,MAAM,QAAQ,GAAmD,EAAE,CAAC;IACpE,MAAM,QAAQ,GAA+B,EAAE,CAAC;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgC,CAAC;IACzD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,MAAM,WAAW,GAAuB,EAAE,CAAC;IAE3C,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,MAAM,mBAAmB,GAAuB,EAAE,CAAC;QACnD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,MAAM,6BAA6B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACxE,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,MAAM,CAClD,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,KAAK,UAAU,CAClD,CAAC;QACF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,4EAA4E;YAC5E,gDAAgD;YAChD,MAAM,OAAO,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;YACrC,IAAI,OAAO,CAAC,GAAG;gBAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC;gBACZ,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,MAAM,EAAE,OAAO,CAAC,MAAiC;gBACjD,OAAO,EAAE;oBACP,GAAG,IAAI,GAAG,CACR,iBAAiB;yBACd,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;yBACtC,MAAM,CAAC,CAAC,MAAM,EAAqC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAC1E;iBACF,CAAC,IAAI,EAAE;gBACR,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE;aAC7B,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,YAAY,GAAG;YACnB,GAAG,IAAI,GAAG,CACR,mBAAmB;iBAChB,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;iBAC1C,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAC7C;SACF,CAAC,IAAI,EAAE,CAAC;QACT,KAAK,MAAM,EAAE,IAAI,YAAY;YAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO;QACL,QAAQ;QACR,QAAQ;QACR,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnF,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,EAAE;QACpC,WAAW;KACZ,CAAC;AACJ,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { type JournalWrite } from './journal.js';
2
- import { type OperatingCharter, type OperatingConfig, type OperatingEventHead, type OperatingInitAnswers, type OperatingLocalPreferences, type OperatingProfile, type OperatingWorkspaceManifest } from './types.js';
2
+ import type { OperatingDispatchMode } from './mission-dispatch.js';
3
+ import { type OperatingCharter, type OperatingConfig, type OperatingEventHead, type OperatingInitAnswers, type OperatingLocalPreferences, type OperatingProfile, type OperatingRoleId, type OperatingWorkspaceManifest } from './types.js';
3
4
  export declare const OPERATING_PROFILES: readonly OperatingProfile[];
4
5
  export declare function listOperatingProfiles(): OperatingProfile[];
5
6
  export declare function getOperatingProfile(id: OperatingProfile['id']): OperatingProfile;
@@ -9,6 +10,70 @@ export declare function getOperatingProfile(id: OperatingProfile['id']): Operati
9
10
  * (including accidental secrets) never reach command results.
10
11
  */
11
12
  export declare function normalizeCustomOperatingProfile(value: unknown): Partial<OperatingProfile>;
13
+ /**
14
+ * Per-project dispatch-mode overrides (FR4 / E-004). A map of role IDs to a
15
+ * `pack | mission` selection that overrides the derived v1.3 registry default.
16
+ *
17
+ * These live in the machine-local `preferences.json`, NOT the schema-frozen
18
+ * `operating-config` artifact: the v1.2 on-disk config surface is frozen
19
+ * (`additionalProperties: false`), so v1.3 dispatch policy is carried in the
20
+ * local operating preferences alongside `sensitivityCeiling` and `runtime`.
21
+ */
22
+ export type OperatingDispatchModeOverrides = Record<OperatingRoleId, OperatingDispatchMode>;
23
+ /**
24
+ * Strictly allowlist per-project dispatch-mode overrides before they are echoed,
25
+ * persisted, or consumed by dispatch — mirroring the `normalizeCustomOperatingProfile`
26
+ * pattern. Keys must be known registry role IDs; values must be exactly `pack`
27
+ * or `mission`. Unknown roles or values fail closed with `E_OPERATE_CONFIG_INVALID`.
28
+ */
29
+ export declare function normalizeOperatingDispatchModeOverrides(value: unknown): OperatingDispatchModeOverrides;
30
+ /**
31
+ * Parse repeatable `--dispatch-mode-override <roleId>=<pack|mission>` CLI tokens
32
+ * into a validated override map. Each token must be exactly `roleId=mode`.
33
+ */
34
+ export declare function parseOperatingDispatchModeOverrideFlags(flags?: readonly string[]): OperatingDispatchModeOverrides;
35
+ /** Read the validated per-project dispatch-mode overrides, or `{}` when none set. */
36
+ export declare function readOperatingDispatchModeOverrides(projectRoot: string, options?: {
37
+ localRoot?: string;
38
+ }): Promise<OperatingDispatchModeOverrides>;
39
+ /**
40
+ * Merge validated dispatch-mode overrides into the machine-local preferences and
41
+ * persist atomically (temp + rename, mode 0o600), matching how initialization
42
+ * writes preferences.json. An override set to the registry default can be
43
+ * cleared by omitting it. Requires the project to have been initialized.
44
+ */
45
+ export declare function applyOperatingDispatchModeOverrides(input: {
46
+ projectRoot: string;
47
+ localRoot?: string;
48
+ overrides: OperatingDispatchModeOverrides;
49
+ }): Promise<{
50
+ dispatchModeOverrides: OperatingDispatchModeOverrides;
51
+ changed: boolean;
52
+ }>;
53
+ /**
54
+ * Read the persisted per-project cadence `lastRunAt` marker (FR8 / E-008), or
55
+ * `null` when no cycle has completed yet. Machine-local, alongside
56
+ * `dispatchModeOverrides`.
57
+ */
58
+ export declare function readOperatingLastRunAt(projectRoot: string, options?: {
59
+ localRoot?: string;
60
+ }): Promise<string | null>;
61
+ /**
62
+ * Persist the per-project cadence `lastRunAt` marker into the machine-local
63
+ * preferences atomically (temp + rename, mode 0o600), matching how dispatch-mode
64
+ * overrides are written. FR8 / E-008: recorded whenever a cycle reaches
65
+ * reviewable/blocked/closed so `operate status` can surface the pipeline's
66
+ * `nextDueAt` under an injected clock. `lastRunAt` is the injected cycle instant,
67
+ * never a wall-clock read here. Requires an initialized project.
68
+ */
69
+ export declare function recordOperatingLastRunAt(input: {
70
+ projectRoot: string;
71
+ localRoot?: string;
72
+ lastRunAt: string;
73
+ }): Promise<{
74
+ lastRunAt: string;
75
+ changed: boolean;
76
+ }>;
12
77
  export declare function normalizeCharter(input?: Partial<OperatingCharter>): OperatingCharter;
13
78
  /** Normalize already-validated questionnaire or explicit CLI answers once. */
14
79
  export declare function normalizeOperatingInitializationAnswers(input: OperatingInitAnswers): OperatingInitAnswers;
@@ -41,6 +106,7 @@ export declare function prepareOperatingInitialization(input: {
41
106
  charter?: Partial<OperatingCharter>;
42
107
  customProfile?: Partial<OperatingProfile>;
43
108
  componentRoots?: string[];
109
+ dispatchModeOverrides?: OperatingDispatchModeOverrides;
44
110
  localRoot?: string;
45
111
  now?: string;
46
112
  }): Promise<OperatingInitializationPreview>;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/services/operate/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/services/operate/config.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { spliceManagedBlock } from '../../utils/splice-managed-block.js';
5
5
  import { canonicalDigest, canonicalize, sha256Digest } from './canonical.js';
6
- import { OperatingEventStore } from './event-store.js';
6
+ import { OperatingEventStore, operatingRecordsLogLine } from './event-store.js';
7
7
  import { resolveEvidenceImportPath } from './evidence-import.js';
8
8
  import { applyJournalTransaction, prepareJournalTransaction, rollbackJournalTransaction, } from './journal.js';
9
9
  import { withOperatingLock } from './lock-service.js';
@@ -179,6 +179,145 @@ export function normalizeCustomOperatingProfile(value) {
179
179
  }
180
180
  return normalized;
181
181
  }
182
+ const DISPATCH_MODES = new Set(['pack', 'mission']);
183
+ /**
184
+ * Strictly allowlist per-project dispatch-mode overrides before they are echoed,
185
+ * persisted, or consumed by dispatch — mirroring the `normalizeCustomOperatingProfile`
186
+ * pattern. Keys must be known registry role IDs; values must be exactly `pack`
187
+ * or `mission`. Unknown roles or values fail closed with `E_OPERATE_CONFIG_INVALID`.
188
+ */
189
+ export function normalizeOperatingDispatchModeOverrides(value) {
190
+ const record = plainRecord(value, 'dispatchModeOverrides');
191
+ const entries = Object.entries(record);
192
+ if (entries.length > ALL_ROLES.length) {
193
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'dispatchModeOverrides contains more entries than there are operating roles.');
194
+ }
195
+ const normalized = {};
196
+ for (const [roleId, mode] of entries) {
197
+ if (!ALL_ROLES.includes(roleId)) {
198
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', `dispatchModeOverrides references an unknown role: ${roleId}.`);
199
+ }
200
+ if (typeof mode !== 'string' || !DISPATCH_MODES.has(mode)) {
201
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', `dispatchModeOverrides value for ${roleId} must be "pack" or "mission".`);
202
+ }
203
+ normalized[roleId] = mode;
204
+ }
205
+ return normalized;
206
+ }
207
+ /**
208
+ * Parse repeatable `--dispatch-mode-override <roleId>=<pack|mission>` CLI tokens
209
+ * into a validated override map. Each token must be exactly `roleId=mode`.
210
+ */
211
+ export function parseOperatingDispatchModeOverrideFlags(flags = []) {
212
+ const raw = {};
213
+ for (const flag of flags) {
214
+ const separator = flag.indexOf('=');
215
+ if (separator <= 0) {
216
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', `--dispatch-mode-override must be "<roleId>=<pack|mission>", received "${flag}".`);
217
+ }
218
+ const roleId = flag.slice(0, separator).trim();
219
+ const mode = flag.slice(separator + 1).trim();
220
+ if (raw[roleId] && raw[roleId] !== mode) {
221
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', `--dispatch-mode-override sets conflicting modes for role ${roleId}.`);
222
+ }
223
+ raw[roleId] = mode;
224
+ }
225
+ return normalizeOperatingDispatchModeOverrides(raw);
226
+ }
227
+ async function readOperatingPreferencesRecord(projectRoot, options = {}) {
228
+ const paths = resolveOperatingPaths(projectRoot, { localRoot: options.localRoot });
229
+ const raw = await readFile(path.join(paths.localRoot, 'preferences.json'), 'utf8').catch(() => null);
230
+ if (raw === null)
231
+ return null;
232
+ try {
233
+ return JSON.parse(raw);
234
+ }
235
+ catch {
236
+ throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Operating preferences.json is not valid JSON.');
237
+ }
238
+ }
239
+ /** Read the validated per-project dispatch-mode overrides, or `{}` when none set. */
240
+ export async function readOperatingDispatchModeOverrides(projectRoot, options = {}) {
241
+ const preferences = await readOperatingPreferencesRecord(projectRoot, options);
242
+ if (!preferences?.dispatchModeOverrides)
243
+ return {};
244
+ return normalizeOperatingDispatchModeOverrides(preferences.dispatchModeOverrides);
245
+ }
246
+ /**
247
+ * Merge validated dispatch-mode overrides into the machine-local preferences and
248
+ * persist atomically (temp + rename, mode 0o600), matching how initialization
249
+ * writes preferences.json. An override set to the registry default can be
250
+ * cleared by omitting it. Requires the project to have been initialized.
251
+ */
252
+ export async function applyOperatingDispatchModeOverrides(input) {
253
+ const preferences = await readOperatingPreferencesRecord(input.projectRoot, {
254
+ localRoot: input.localRoot,
255
+ });
256
+ if (!preferences) {
257
+ throw new OperateError('E_OPERATE_NOT_INITIALIZED', 'Operating Board is not initialized in this project; run `planr operate init` first.');
258
+ }
259
+ const validated = normalizeOperatingDispatchModeOverrides(input.overrides);
260
+ const merged = {
261
+ ...(preferences.dispatchModeOverrides ?? {}),
262
+ ...validated,
263
+ };
264
+ const next = { ...preferences };
265
+ if (Object.keys(merged).length > 0)
266
+ next.dispatchModeOverrides = merged;
267
+ else
268
+ delete next.dispatchModeOverrides;
269
+ const paths = resolveOperatingPaths(input.projectRoot, { localRoot: input.localRoot });
270
+ const preferencePath = path.join(paths.localRoot, 'preferences.json');
271
+ const before = await readFile(preferencePath, 'utf8').catch(() => null);
272
+ const serialized = `${canonicalize(next)}\n`;
273
+ const changed = before !== serialized;
274
+ if (changed) {
275
+ await mkdir(paths.localRoot, { recursive: true, mode: 0o700 });
276
+ const temporary = `${preferencePath}.${process.pid}.${randomUUID()}.tmp`;
277
+ await writeFile(temporary, serialized, { mode: 0o600 });
278
+ await rename(temporary, preferencePath);
279
+ }
280
+ return { dispatchModeOverrides: merged, changed };
281
+ }
282
+ /**
283
+ * Read the persisted per-project cadence `lastRunAt` marker (FR8 / E-008), or
284
+ * `null` when no cycle has completed yet. Machine-local, alongside
285
+ * `dispatchModeOverrides`.
286
+ */
287
+ export async function readOperatingLastRunAt(projectRoot, options = {}) {
288
+ const preferences = await readOperatingPreferencesRecord(projectRoot, options);
289
+ const value = preferences?.lastRunAt;
290
+ return typeof value === 'string' && value.trim() ? value : null;
291
+ }
292
+ /**
293
+ * Persist the per-project cadence `lastRunAt` marker into the machine-local
294
+ * preferences atomically (temp + rename, mode 0o600), matching how dispatch-mode
295
+ * overrides are written. FR8 / E-008: recorded whenever a cycle reaches
296
+ * reviewable/blocked/closed so `operate status` can surface the pipeline's
297
+ * `nextDueAt` under an injected clock. `lastRunAt` is the injected cycle instant,
298
+ * never a wall-clock read here. Requires an initialized project.
299
+ */
300
+ export async function recordOperatingLastRunAt(input) {
301
+ const preferences = await readOperatingPreferencesRecord(input.projectRoot, {
302
+ localRoot: input.localRoot,
303
+ });
304
+ if (!preferences) {
305
+ throw new OperateError('E_OPERATE_NOT_INITIALIZED', 'Operating Board is not initialized in this project; run `planr operate init` first.');
306
+ }
307
+ const next = { ...preferences, lastRunAt: input.lastRunAt };
308
+ const paths = resolveOperatingPaths(input.projectRoot, { localRoot: input.localRoot });
309
+ const preferencePath = path.join(paths.localRoot, 'preferences.json');
310
+ const before = await readFile(preferencePath, 'utf8').catch(() => null);
311
+ const serialized = `${canonicalize(next)}\n`;
312
+ const changed = before !== serialized;
313
+ if (changed) {
314
+ await mkdir(paths.localRoot, { recursive: true, mode: 0o700 });
315
+ const temporary = `${preferencePath}.${process.pid}.${randomUUID()}.tmp`;
316
+ await writeFile(temporary, serialized, { mode: 0o600 });
317
+ await rename(temporary, preferencePath);
318
+ }
319
+ return { lastRunAt: input.lastRunAt, changed };
320
+ }
182
321
  export function normalizeCharter(input = {}) {
183
322
  const list = (values) => [
184
323
  ...new Set((values ?? []).map((value) => value.trim()).filter(Boolean)),
@@ -263,10 +402,12 @@ export function renderOperatingCharter(config, input = {}) {
263
402
  }
264
403
  const INITIALIZATION_RECOVERY_ID = 'RCV-operating-board-initialized';
265
404
  const INITIALIZATION_CORRELATION_ID = 'operate-initialization-v1';
266
- function operatingRecordPath(digest) {
267
- const hex = digest.slice('sha256:'.length);
268
- return `.planr/operate/records/sha256/${hex.slice(0, 2)}/${hex.slice(2)}.json`;
269
- }
405
+ // Protocol v1.3 (FR5/E-005) init layout: internals collapse under `.state/`
406
+ // (events.jsonl, records.jsonl, checkpoint.json). Fresh `operate init` writes
407
+ // this layout directly — there is no SPEC-002 tree to migrate for a new project.
408
+ const OPERATE_STATE_EVENTS = '.planr/operate/.state/events.jsonl';
409
+ const OPERATE_STATE_RECORDS = '.planr/operate/.state/records.jsonl';
410
+ const OPERATE_STATE_CHECKPOINT = '.planr/operate/.state/checkpoint.json';
270
411
  async function buildInitialOperatingState(input) {
271
412
  const protocol = await loadOperatingProtocol();
272
413
  const genesis = { sequence: 0, hash: null };
@@ -340,12 +481,12 @@ async function buildInitialOperatingState(input) {
340
481
  const projectionByPath = new Map(projection.files.map((file) => [file.relativePath, file]));
341
482
  const writes = [
342
483
  {
343
- relativePath: operatingRecordPath(record.digest),
344
- content: canonicalize(record),
484
+ relativePath: OPERATE_STATE_RECORDS,
485
+ content: `${operatingRecordsLogLine(record)}\n`,
345
486
  operation: 'create',
346
487
  },
347
488
  {
348
- relativePath: '.planr/operate/checkpoints/current.json',
489
+ relativePath: OPERATE_STATE_CHECKPOINT,
349
490
  content: `${canonicalize(checkpoint)}\n`,
350
491
  },
351
492
  ...renderOperatingProjectionFiles(state).map((file) => ({
@@ -355,7 +496,7 @@ async function buildInitialOperatingState(input) {
355
496
  // Keep the canonical event last: journal head revalidation remains at
356
497
  // genesis for every preceding write, then changes exactly once.
357
498
  {
358
- relativePath: '.planr/operate/events.jsonl',
499
+ relativePath: OPERATE_STATE_EVENTS,
359
500
  content: `${canonicalize(event)}\n`,
360
501
  operation: 'create',
361
502
  },
@@ -461,6 +602,13 @@ export async function prepareOperatingInitialization(input) {
461
602
  ]);
462
603
  resolvedImportPaths.push(resolved.absolutePath);
463
604
  }
605
+ // Dispatch-mode overrides are validated once here and carried in the
606
+ // machine-local preferences (the v1.2 config artifact surface is frozen).
607
+ // Omitted entirely when empty so a project with no overrides keeps a
608
+ // byte-identical preferences payload and preview digest.
609
+ const dispatchModeOverrides = input.dispatchModeOverrides
610
+ ? normalizeOperatingDispatchModeOverrides(input.dispatchModeOverrides)
611
+ : {};
464
612
  const preferences = {
465
613
  runtime: input.runtime ?? 'auto',
466
614
  timezone,
@@ -470,6 +618,7 @@ export async function prepareOperatingInitialization(input) {
470
618
  ...(resolvedImportPaths.length > 0
471
619
  ? { importPaths: [...new Set(resolvedImportPaths)].sort() }
472
620
  : {}),
621
+ ...(Object.keys(dispatchModeOverrides).length > 0 ? { dispatchModeOverrides } : {}),
473
622
  };
474
623
  const paths = resolveOperatingPaths(input.projectRoot, { localRoot: input.localRoot });
475
624
  const existingWorkspace = await readFile(paths.workspace, 'utf8')