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.
- package/README.md +5 -2
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +2 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/operate.d.ts.map +1 -1
- package/dist/cli/commands/operate.js +185 -6
- package/dist/cli/commands/operate.js.map +1 -1
- package/dist/cli/commands/runtime.d.ts.map +1 -1
- package/dist/cli/commands/runtime.js +22 -4
- package/dist/cli/commands/runtime.js.map +1 -1
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +18 -0
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/services/claude-plugin-service.d.ts +47 -0
- package/dist/services/claude-plugin-service.d.ts.map +1 -0
- package/dist/services/claude-plugin-service.js +259 -0
- package/dist/services/claude-plugin-service.js.map +1 -0
- package/dist/services/operate/advisors.d.ts +46 -1
- package/dist/services/operate/advisors.d.ts.map +1 -1
- package/dist/services/operate/advisors.js +124 -13
- package/dist/services/operate/advisors.js.map +1 -1
- package/dist/services/operate/artifacts.d.ts +27 -1
- package/dist/services/operate/artifacts.d.ts.map +1 -1
- package/dist/services/operate/artifacts.js +103 -1
- package/dist/services/operate/artifacts.js.map +1 -1
- package/dist/services/operate/cadence.d.ts +50 -0
- package/dist/services/operate/cadence.d.ts.map +1 -0
- package/dist/services/operate/cadence.js +56 -0
- package/dist/services/operate/cadence.js.map +1 -0
- package/dist/services/operate/citation-resolution.d.ts +101 -0
- package/dist/services/operate/citation-resolution.d.ts.map +1 -0
- package/dist/services/operate/citation-resolution.js +308 -0
- package/dist/services/operate/citation-resolution.js.map +1 -0
- package/dist/services/operate/config.d.ts +67 -1
- package/dist/services/operate/config.d.ts.map +1 -1
- package/dist/services/operate/config.js +158 -9
- package/dist/services/operate/config.js.map +1 -1
- package/dist/services/operate/decision-brief.d.ts +148 -0
- package/dist/services/operate/decision-brief.d.ts.map +1 -0
- package/dist/services/operate/decision-brief.js +194 -0
- package/dist/services/operate/decision-brief.js.map +1 -0
- package/dist/services/operate/doctor.d.ts.map +1 -1
- package/dist/services/operate/doctor.js +151 -3
- package/dist/services/operate/doctor.js.map +1 -1
- package/dist/services/operate/engine.d.ts +66 -2
- package/dist/services/operate/engine.d.ts.map +1 -1
- package/dist/services/operate/engine.js +219 -7
- package/dist/services/operate/engine.js.map +1 -1
- package/dist/services/operate/event-store.d.ts +35 -1
- package/dist/services/operate/event-store.d.ts.map +1 -1
- package/dist/services/operate/event-store.js +142 -29
- package/dist/services/operate/event-store.js.map +1 -1
- package/dist/services/operate/evidence-cache.d.ts +25 -1
- package/dist/services/operate/evidence-cache.d.ts.map +1 -1
- package/dist/services/operate/evidence-cache.js +62 -1
- package/dist/services/operate/evidence-cache.js.map +1 -1
- package/dist/services/operate/evidence.d.ts +20 -1
- package/dist/services/operate/evidence.d.ts.map +1 -1
- package/dist/services/operate/evidence.js +119 -0
- package/dist/services/operate/evidence.js.map +1 -1
- package/dist/services/operate/index.d.ts.map +1 -1
- package/dist/services/operate/index.js +72 -5
- package/dist/services/operate/index.js.map +1 -1
- package/dist/services/operate/interaction/action-service.d.ts +17 -0
- package/dist/services/operate/interaction/action-service.d.ts.map +1 -1
- package/dist/services/operate/interaction/action-service.js +19 -0
- package/dist/services/operate/interaction/action-service.js.map +1 -1
- package/dist/services/operate/journal.d.ts.map +1 -1
- package/dist/services/operate/journal.js +11 -5
- package/dist/services/operate/journal.js.map +1 -1
- package/dist/services/operate/maintenance.d.ts.map +1 -1
- package/dist/services/operate/maintenance.js +9 -2
- package/dist/services/operate/maintenance.js.map +1 -1
- package/dist/services/operate/migration.d.ts +68 -0
- package/dist/services/operate/migration.d.ts.map +1 -0
- package/dist/services/operate/migration.js +235 -0
- package/dist/services/operate/migration.js.map +1 -0
- package/dist/services/operate/mission-dispatch.d.ts +193 -0
- package/dist/services/operate/mission-dispatch.d.ts.map +1 -0
- package/dist/services/operate/mission-dispatch.js +444 -0
- package/dist/services/operate/mission-dispatch.js.map +1 -0
- package/dist/services/operate/projection-persistence.d.ts +6 -0
- package/dist/services/operate/projection-persistence.d.ts.map +1 -1
- package/dist/services/operate/projection-persistence.js +87 -11
- package/dist/services/operate/projection-persistence.js.map +1 -1
- package/dist/services/operate/projection.d.ts +26 -1
- package/dist/services/operate/projection.d.ts.map +1 -1
- package/dist/services/operate/projection.js +62 -0
- package/dist/services/operate/projection.js.map +1 -1
- package/dist/services/operate/protocol.d.ts +46 -2
- package/dist/services/operate/protocol.d.ts.map +1 -1
- package/dist/services/operate/protocol.js +57 -5
- package/dist/services/operate/protocol.js.map +1 -1
- package/dist/services/operate/read-only-providers.d.ts +37 -0
- package/dist/services/operate/read-only-providers.d.ts.map +1 -1
- package/dist/services/operate/read-only-providers.js +130 -0
- package/dist/services/operate/read-only-providers.js.map +1 -1
- package/dist/services/operate/reports.d.ts +13 -0
- package/dist/services/operate/reports.d.ts.map +1 -1
- package/dist/services/operate/reports.js +217 -1
- package/dist/services/operate/reports.js.map +1 -1
- package/dist/services/operate/routes.d.ts.map +1 -1
- package/dist/services/operate/routes.js +79 -5
- package/dist/services/operate/routes.js.map +1 -1
- package/dist/services/operate/types.d.ts +120 -4
- package/dist/services/operate/types.d.ts.map +1 -1
- package/dist/services/operate/types.js +13 -0
- package/dist/services/operate/types.js.map +1 -1
- package/dist/services/operate/workspace.d.ts +7 -0
- package/dist/services/operate/workspace.d.ts.map +1 -1
- package/dist/services/operate/workspace.js +14 -5
- package/dist/services/operate/workspace.js.map +1 -1
- package/dist/services/runtime-manager-service.d.ts +15 -0
- package/dist/services/runtime-manager-service.d.ts.map +1 -1
- package/dist/services/runtime-manager-service.js +151 -36
- package/dist/services/runtime-manager-service.js.map +1 -1
- 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":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type JournalWrite } from './journal.js';
|
|
2
|
-
import
|
|
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":"AAOA,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,cAAc,CAAC;
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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:
|
|
344
|
-
content:
|
|
484
|
+
relativePath: OPERATE_STATE_RECORDS,
|
|
485
|
+
content: `${operatingRecordsLogLine(record)}\n`,
|
|
345
486
|
operation: 'create',
|
|
346
487
|
},
|
|
347
488
|
{
|
|
348
|
-
relativePath:
|
|
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:
|
|
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')
|