openplanr 1.14.4 → 1.15.1
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/dist/cli/commands/operate.d.ts.map +1 -1
- package/dist/cli/commands/operate.js +55 -87
- package/dist/cli/commands/operate.js.map +1 -1
- package/dist/services/operate/config.d.ts +4 -1
- package/dist/services/operate/config.d.ts.map +1 -1
- package/dist/services/operate/config.js +56 -2
- package/dist/services/operate/config.js.map +1 -1
- package/dist/services/operate/doctor.d.ts.map +1 -1
- package/dist/services/operate/doctor.js +15 -0
- package/dist/services/operate/doctor.js.map +1 -1
- package/dist/services/operate/evidence-classifications.d.ts +59 -0
- package/dist/services/operate/evidence-classifications.d.ts.map +1 -0
- package/dist/services/operate/evidence-classifications.js +132 -0
- package/dist/services/operate/evidence-classifications.js.map +1 -0
- package/dist/services/operate/evidence-diagnostics.d.ts +23 -0
- package/dist/services/operate/evidence-diagnostics.d.ts.map +1 -0
- package/dist/services/operate/evidence-diagnostics.js +152 -0
- package/dist/services/operate/evidence-diagnostics.js.map +1 -0
- package/dist/services/operate/evidence.d.ts.map +1 -1
- package/dist/services/operate/evidence.js +54 -7
- 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 +465 -62
- 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 -0
- package/dist/services/operate/interaction/action-service.js +84 -0
- package/dist/services/operate/interaction/action-service.js.map +1 -0
- package/dist/services/operate/interaction/answer-service.d.ts +33 -0
- package/dist/services/operate/interaction/answer-service.d.ts.map +1 -0
- package/dist/services/operate/interaction/answer-service.js +239 -0
- package/dist/services/operate/interaction/answer-service.js.map +1 -0
- package/dist/services/operate/interaction/charter-suggestions.d.ts +29 -0
- package/dist/services/operate/interaction/charter-suggestions.d.ts.map +1 -0
- package/dist/services/operate/interaction/charter-suggestions.js +104 -0
- package/dist/services/operate/interaction/charter-suggestions.js.map +1 -0
- package/dist/services/operate/interaction/confirmation-service.d.ts +30 -0
- package/dist/services/operate/interaction/confirmation-service.d.ts.map +1 -0
- package/dist/services/operate/interaction/confirmation-service.js +78 -0
- package/dist/services/operate/interaction/confirmation-service.js.map +1 -0
- package/dist/services/operate/interaction/initialization-replay.d.ts +9 -0
- package/dist/services/operate/interaction/initialization-replay.d.ts.map +1 -0
- package/dist/services/operate/interaction/initialization-replay.js +64 -0
- package/dist/services/operate/interaction/initialization-replay.js.map +1 -0
- package/dist/services/operate/interaction/question-engine.d.ts +39 -0
- package/dist/services/operate/interaction/question-engine.d.ts.map +1 -0
- package/dist/services/operate/interaction/question-engine.js +277 -0
- package/dist/services/operate/interaction/question-engine.js.map +1 -0
- package/dist/services/operate/interaction/question-registry.d.ts +16 -0
- package/dist/services/operate/interaction/question-registry.d.ts.map +1 -0
- package/dist/services/operate/interaction/question-registry.js +190 -0
- package/dist/services/operate/interaction/question-registry.js.map +1 -0
- package/dist/services/operate/interaction/session-service.d.ts +55 -0
- package/dist/services/operate/interaction/session-service.d.ts.map +1 -0
- package/dist/services/operate/interaction/session-service.js +272 -0
- package/dist/services/operate/interaction/session-service.js.map +1 -0
- package/dist/services/operate/interaction/terminal-renderer.d.ts +8 -0
- package/dist/services/operate/interaction/terminal-renderer.d.ts.map +1 -0
- package/dist/services/operate/interaction/terminal-renderer.js +103 -0
- package/dist/services/operate/interaction/terminal-renderer.js.map +1 -0
- package/dist/services/operate/maintenance.d.ts.map +1 -1
- package/dist/services/operate/maintenance.js +40 -3
- package/dist/services/operate/maintenance.js.map +1 -1
- package/dist/services/operate/types.d.ts +194 -1
- package/dist/services/operate/types.d.ts.map +1 -1
- package/dist/services/operate/types.js.map +1 -1
- package/dist/services/operate/workspace.d.ts +1 -0
- package/dist/services/operate/workspace.d.ts.map +1 -1
- package/dist/services/operate/workspace.js +2 -0
- package/dist/services/operate/workspace.js.map +1 -1
- package/dist/services/pipeline-package-service.d.ts +14 -0
- package/dist/services/pipeline-package-service.d.ts.map +1 -1
- package/dist/services/pipeline-package-service.js +45 -1
- package/dist/services/pipeline-package-service.js.map +1 -1
- package/dist/services/runtime-manager-service.d.ts +3 -0
- package/dist/services/runtime-manager-service.d.ts.map +1 -1
- package/dist/services/runtime-manager-service.js +26 -2
- package/dist/services/runtime-manager-service.js.map +1 -1
- package/package.json +4 -3
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { canonicalDigest, sha256Digest } from './canonical.js';
|
|
4
|
+
import { applyOperatingInitialization, getOperatingProfile, listOperatingProfiles, normalizeCustomOperatingProfile, normalizeOperatingInitializationAnswers, prepareOperatingInitialization, validateOperatingConfiguration, } from './config.js';
|
|
4
5
|
import { runOperatingCycle } from './engine.js';
|
|
5
6
|
import { OperatingEventStore } from './event-store.js';
|
|
7
|
+
import { classifyEvidenceDiagnostic } from './evidence-classifications.js';
|
|
8
|
+
import { listEvidenceDiagnostics, readEvidenceDiagnostic } from './evidence-diagnostics.js';
|
|
6
9
|
import { parseStrictJson, readImportedEvidenceFile } from './evidence-import.js';
|
|
10
|
+
import { createOperatingAction } from './interaction/action-service.js';
|
|
11
|
+
import { persistableOperatingInitAnswers, resumeGuidedSession, submitGuidedAnswers, } from './interaction/answer-service.js';
|
|
12
|
+
import { assertOperatingConfirmation } from './interaction/confirmation-service.js';
|
|
13
|
+
import { decodeOperatingInitializationReplay, encodeOperatingInitializationReplay, } from './interaction/initialization-replay.js';
|
|
14
|
+
import { createOperatingInitQuestionnaire, evaluateOperatingInitQuestions, operatingInitAnswersFromOptions, } from './interaction/question-engine.js';
|
|
15
|
+
import { cancelGuidedSession, createGuidedSession, createGuidedSessionId, currentGuidedSessionBindings, updateGuidedSession, } from './interaction/session-service.js';
|
|
7
16
|
import { assertCommittedOperatingView } from './journal.js';
|
|
8
17
|
import { applyOperatingMigration, inspectOperatingMigration, rollbackOperatingMigration, } from './legacy-import-service.js';
|
|
9
18
|
import { answerOperatingGap, applyOrRollbackRoute, decideOperatingDecision, governOperatingFinding, readOperatingCollection, readOperatingReview, transitionOperatingCycle, verifyOperatingGap, } from './lifecycle.js';
|
|
@@ -122,11 +131,16 @@ const OPERATE_EXIT_CODES = {
|
|
|
122
131
|
E_OPERATE_CHARTER_INCOMPLETE: 2,
|
|
123
132
|
E_OPERATE_CONFIG_INVALID: 2,
|
|
124
133
|
E_OPERATE_INPUT_TOO_LARGE: 2,
|
|
134
|
+
E_OPERATE_QUESTIONNAIRE_INVALID: 2,
|
|
125
135
|
E_OPERATE_PATH_ESCAPE: 2,
|
|
126
136
|
E_OPERATE_NOT_INITIALIZED: 3,
|
|
127
137
|
E_OPERATE_PROJECT_REQUIRED: 3,
|
|
128
138
|
E_PIPELINE_NOT_INSTALLED: 3,
|
|
139
|
+
E_PIPELINE_VERSION_INCOMPATIBLE: 3,
|
|
129
140
|
E_OPERATE_AUTHORITY_REQUIRED: 4,
|
|
141
|
+
E_OPERATE_INPUT_REQUIRED: 4,
|
|
142
|
+
E_OPERATE_SESSION_CANCELLED: 4,
|
|
143
|
+
E_OPERATE_SESSION_EXPIRED: 4,
|
|
130
144
|
E_OPERATE_ROUTE_CONFIRMATION_REQUIRED: 4,
|
|
131
145
|
E_OPERATE_CHECKPOINT_INVALID: 5,
|
|
132
146
|
E_OPERATE_CYCLE_ACTIVE: 5,
|
|
@@ -142,6 +156,8 @@ const OPERATE_EXIT_CODES = {
|
|
|
142
156
|
E_OPERATE_STALE_LOCK_UNSAFE: 5,
|
|
143
157
|
E_OPERATE_STATE_INVALID: 5,
|
|
144
158
|
E_OPERATE_TRANSACTION_INVALID: 5,
|
|
159
|
+
E_OPERATE_SESSION_REPLAY_CONFLICT: 5,
|
|
160
|
+
E_OPERATE_SESSION_STALE: 5,
|
|
145
161
|
E_OPERATE_ADVISOR_FAILED: 6,
|
|
146
162
|
E_OPERATE_ADVISOR_ISOLATION: 6,
|
|
147
163
|
E_OPERATE_CAP_EXCEEDED: 6,
|
|
@@ -151,41 +167,46 @@ const OPERATE_EXIT_CODES = {
|
|
|
151
167
|
E_OPERATE_EVIDENCE_REJECTED: 6,
|
|
152
168
|
E_OPERATE_PROVIDER_READ_ONLY: 6,
|
|
153
169
|
E_OPERATE_SECRET_DETECTED: 6,
|
|
170
|
+
E_OPERATE_SESSION_INVALID: 2,
|
|
154
171
|
};
|
|
155
172
|
function operateExitCode(code) {
|
|
156
173
|
return OPERATE_EXIT_CODES[code];
|
|
157
174
|
}
|
|
158
175
|
function failure(action, error) {
|
|
159
176
|
const code = error instanceof OperateError ? error.code : 'E_OPERATE_INTERNAL';
|
|
160
|
-
const
|
|
161
|
-
? [
|
|
162
|
-
:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
? ['planr operate
|
|
176
|
-
: code === '
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
:
|
|
177
|
+
const explicitRecovery = error instanceof OperateError && typeof error.details?.recoveryCommand === 'string'
|
|
178
|
+
? [error.details.recoveryCommand]
|
|
179
|
+
: null;
|
|
180
|
+
const nextActions = explicitRecovery ??
|
|
181
|
+
(code === 'E_PIPELINE_NOT_INSTALLED'
|
|
182
|
+
? ['npm install -g openplanr@latest', 'planr setup --scope user', 'planr operate inspect']
|
|
183
|
+
: code === 'E_OPERATE_NOT_INITIALIZED'
|
|
184
|
+
? ['planr operate init']
|
|
185
|
+
: code === 'E_OPERATE_PROJECT_REQUIRED'
|
|
186
|
+
? ['cd /path/to/your/project', 'planr init', 'planr operate inspect']
|
|
187
|
+
: code === 'E_OPERATE_CONFIG_INVALID' || code === 'E_OPERATE_CHARTER_INCOMPLETE'
|
|
188
|
+
? ['planr operate config validate']
|
|
189
|
+
: code === 'E_OPERATE_ROUTE_CONFIRMATION_REQUIRED'
|
|
190
|
+
? ['planr operate routes apply <route-id> --preview --json']
|
|
191
|
+
: code === 'E_OPERATE_MIGRATION_CONFLICT'
|
|
192
|
+
? ['planr operate migrate inspect', 'planr operate migrations list']
|
|
193
|
+
: code === 'E_OPERATE_HEAD_DIVERGED' ||
|
|
194
|
+
code === 'E_OPERATE_CHECKPOINT_INVALID' ||
|
|
195
|
+
code === 'E_OPERATE_PROJECTION_DRIFT'
|
|
196
|
+
? ['planr operate integrity status', 'planr operate cycles recover <cycle-id>']
|
|
197
|
+
: code === 'E_OPERATE_CYCLE_ACTIVE' || code === 'E_OPERATE_CYCLE_INPUT_CONFLICT'
|
|
198
|
+
? ['planr operate status', 'planr operate cycles resume <cycle-id>']
|
|
199
|
+
: code === 'E_OPERATE_CYCLE_NOT_DISPOSED'
|
|
200
|
+
? [
|
|
201
|
+
'planr operate findings list',
|
|
202
|
+
'planr operate decisions list',
|
|
203
|
+
'planr operate cycles close <cycle-id>',
|
|
204
|
+
]
|
|
205
|
+
: code.startsWith('E_OPERATE_EVIDENCE') ||
|
|
206
|
+
code.startsWith('E_OPERATE_PROVIDER') ||
|
|
207
|
+
code.startsWith('E_OPERATE_ADVISOR')
|
|
208
|
+
? ['planr operate sources list', 'planr operate run --preview --json']
|
|
209
|
+
: ['planr operate diagnostics export']);
|
|
189
210
|
return {
|
|
190
211
|
schemaVersion: '1.0.0',
|
|
191
212
|
protocolVersion: '1.2.0',
|
|
@@ -205,6 +226,100 @@ function failure(action, error) {
|
|
|
205
226
|
exitCode: operateExitCode(code),
|
|
206
227
|
};
|
|
207
228
|
}
|
|
229
|
+
function publicActionCommands(nextActions) {
|
|
230
|
+
return [
|
|
231
|
+
...new Set(nextActions
|
|
232
|
+
.flatMap((value) => value.split(/\s+&&\s+/))
|
|
233
|
+
.map((value) => value
|
|
234
|
+
.trim()
|
|
235
|
+
.replace(/\s+--yes(?=\s|$)/g, '')
|
|
236
|
+
.replace(/\s+--confirm\s+\S+/g, '')
|
|
237
|
+
.replace(/\s+--preview-digest\s+\S+/g, ''))
|
|
238
|
+
.filter((value) => /^planr\s+/.test(value))),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
function actionEffect(command) {
|
|
242
|
+
if (/\b(?:inspect|status|brief|review|list|show)\b/.test(command) ||
|
|
243
|
+
/\bconfig validate\b/.test(command) ||
|
|
244
|
+
/\bprofiles validate\b/.test(command) ||
|
|
245
|
+
/\bsources test\b/.test(command) ||
|
|
246
|
+
/\brun\b.*\b--preview\b/.test(command) ||
|
|
247
|
+
/\bmigrate inspect\b/.test(command) ||
|
|
248
|
+
/\bcache status\b/.test(command) ||
|
|
249
|
+
/\bintegrity status\b/.test(command)) {
|
|
250
|
+
return 'read-only';
|
|
251
|
+
}
|
|
252
|
+
if (/\boperate run\b/.test(command) && !/\b--offline\b/.test(command)) {
|
|
253
|
+
return 'provider-call';
|
|
254
|
+
}
|
|
255
|
+
if (/\b(?:adapter (?:prepare|record|resume|finalize|cancel)|cache purge|diagnostics export)\b/.test(command)) {
|
|
256
|
+
return 'machine-local-write';
|
|
257
|
+
}
|
|
258
|
+
if (/\bpipeline (?:plan|ship)\b/.test(command))
|
|
259
|
+
return 'external-effect';
|
|
260
|
+
return 'project-write';
|
|
261
|
+
}
|
|
262
|
+
async function attachStructuredActions(request, result) {
|
|
263
|
+
if (result.actions?.length || result.action === 'input_required')
|
|
264
|
+
return result;
|
|
265
|
+
const commands = publicActionCommands(result.nextActions);
|
|
266
|
+
if (commands.length === 0)
|
|
267
|
+
return result;
|
|
268
|
+
// Planning-only installations intentionally omit the portable pipeline and
|
|
269
|
+
// therefore its Protocol v1.2 action validators. Provider-free inspection
|
|
270
|
+
// and demonstration must still work; their legacy nextActions remain the
|
|
271
|
+
// compatible fallback until the full package is installed.
|
|
272
|
+
if (!resolveOperatingPipelineRoot())
|
|
273
|
+
return result;
|
|
274
|
+
const bindings = await currentGuidedSessionBindings(request.projectRoot);
|
|
275
|
+
const eventHead = await new OperatingEventStore(request.projectRoot)
|
|
276
|
+
.replay()
|
|
277
|
+
.then((value) => (value.eventHead.hash ? value.eventHead : null))
|
|
278
|
+
.catch(() => null);
|
|
279
|
+
const requestDigest = canonicalDigest({
|
|
280
|
+
action: request.action,
|
|
281
|
+
arguments: request.arguments ?? {},
|
|
282
|
+
options: Object.fromEntries(Object.entries(request.options)
|
|
283
|
+
.filter(([key]) => !['yes', 'confirm', 'stdin'].includes(key))
|
|
284
|
+
.map(([key, value]) => [key, canonicalDigest({ value })])),
|
|
285
|
+
});
|
|
286
|
+
const sessionId = `GIS-action-${requestDigest.slice('sha256:'.length, 'sha256:'.length + 24)}`;
|
|
287
|
+
const actions = [];
|
|
288
|
+
for (const [index, command] of commands.entries()) {
|
|
289
|
+
const effect = actionEffect(command);
|
|
290
|
+
const id = `operate.next.${canonicalDigest({ command }).slice('sha256:'.length, 'sha256:'.length + 20)}`.toLowerCase();
|
|
291
|
+
const created = await createOperatingAction({
|
|
292
|
+
id,
|
|
293
|
+
label: (() => {
|
|
294
|
+
const publicLabel = command.replace(/^planr\s+/, '');
|
|
295
|
+
return publicLabel.length <= 160
|
|
296
|
+
? publicLabel
|
|
297
|
+
: `${publicLabel.slice(0, 157).trimEnd()}...`;
|
|
298
|
+
})(),
|
|
299
|
+
description: `Run the named ${effect} action returned by ${request.action}.`,
|
|
300
|
+
command,
|
|
301
|
+
effect,
|
|
302
|
+
recommended: index === 0,
|
|
303
|
+
...(effect === 'read-only'
|
|
304
|
+
? {}
|
|
305
|
+
: {
|
|
306
|
+
confirmation: {
|
|
307
|
+
sessionId,
|
|
308
|
+
confirmationScope: `${request.action}:${index + 1}`,
|
|
309
|
+
projectIdentity: bindings.projectIdentity,
|
|
310
|
+
projectHead: bindings.projectHead,
|
|
311
|
+
configHead: bindings.configHead,
|
|
312
|
+
eventHead,
|
|
313
|
+
arguments: [requestDigest],
|
|
314
|
+
destinations: [],
|
|
315
|
+
writes: [],
|
|
316
|
+
},
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
319
|
+
actions.push(created.action);
|
|
320
|
+
}
|
|
321
|
+
return { ...result, actions };
|
|
322
|
+
}
|
|
208
323
|
async function inspect(request) {
|
|
209
324
|
const pipelineRoot = resolveOperatingPipelineRoot();
|
|
210
325
|
const paths = resolveOperatingPaths(request.projectRoot);
|
|
@@ -324,75 +439,289 @@ function demo(request) {
|
|
|
324
439
|
next: ['planr operate init'],
|
|
325
440
|
});
|
|
326
441
|
}
|
|
442
|
+
async function initializationApplyAction(input) {
|
|
443
|
+
const sessionId = input.sessionId ??
|
|
444
|
+
`GIS-init-${input.preview.previewDigest.slice('sha256:'.length, 'sha256:'.length + 24)}`;
|
|
445
|
+
const command = input.sessionId
|
|
446
|
+
? `planr operate init --resume ${input.sessionId}`
|
|
447
|
+
: [
|
|
448
|
+
'planr operate init',
|
|
449
|
+
`--answers-token ${encodeOperatingInitializationReplay(input.answers)}`,
|
|
450
|
+
`--preview-created-at ${input.preview.workspace.capturedAt}`,
|
|
451
|
+
].join(' ');
|
|
452
|
+
return createOperatingAction({
|
|
453
|
+
id: 'operate.init.apply',
|
|
454
|
+
label: 'Apply Operating Board configuration',
|
|
455
|
+
description: 'Write only the reviewed charter, workspace, configuration, and machine-local preferences.',
|
|
456
|
+
command,
|
|
457
|
+
effect: 'project-write',
|
|
458
|
+
recommended: true,
|
|
459
|
+
confirmation: {
|
|
460
|
+
sessionId,
|
|
461
|
+
confirmationScope: 'operate.init.apply',
|
|
462
|
+
projectIdentity: input.bindings.projectIdentity,
|
|
463
|
+
projectHead: input.bindings.projectHead,
|
|
464
|
+
configHead: input.bindings.configHead,
|
|
465
|
+
eventHead: input.preview.expectedEventHead.hash ? input.preview.expectedEventHead : null,
|
|
466
|
+
arguments: [
|
|
467
|
+
`answers=${canonicalDigest(input.answers)}`,
|
|
468
|
+
`preview=${input.preview.previewDigest}`,
|
|
469
|
+
],
|
|
470
|
+
destinations: [
|
|
471
|
+
...input.preview.changedPaths,
|
|
472
|
+
...(input.preview.preferencesChanged ? ['~/.planr/operate/preferences.json'] : []),
|
|
473
|
+
],
|
|
474
|
+
writes: input.preview.writes.map((write) => `${write.relativePath}:${write.operation ?? 'replace'}:${sha256Digest(write.content)}`),
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
}
|
|
327
478
|
async function initialize(request) {
|
|
328
479
|
await loadOperatingProtocol();
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
480
|
+
const replayToken = option(request, 'answersToken', undefined);
|
|
481
|
+
let supplied = replayToken
|
|
482
|
+
? decodeOperatingInitializationReplay(replayToken)
|
|
483
|
+
: normalizeOperatingInitializationAnswers(operatingInitAnswersFromOptions(request.options));
|
|
484
|
+
const resumeId = option(request, 'resume', undefined);
|
|
485
|
+
const localRoot = option(request, 'localRoot', undefined);
|
|
486
|
+
if (option(request, 'cancelSession', false)) {
|
|
487
|
+
if (!resumeId) {
|
|
488
|
+
throw new OperateError('E_OPERATE_SESSION_INVALID', '--cancel-session requires --resume <session-id>.');
|
|
489
|
+
}
|
|
490
|
+
return success(request.action, {
|
|
491
|
+
message: 'Guided initialization session cancelled.',
|
|
492
|
+
state: 'cancelled',
|
|
493
|
+
data: await cancelGuidedSession({
|
|
494
|
+
projectRoot: request.projectRoot,
|
|
495
|
+
sessionId: resumeId,
|
|
496
|
+
localRoot,
|
|
497
|
+
}),
|
|
498
|
+
next: ['planr operate init --json'],
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
if (request.stdin !== undefined && !resumeId) {
|
|
502
|
+
throw new OperateError('E_OPERATE_SESSION_INVALID', '--stdin requires --resume <session-id>.');
|
|
503
|
+
}
|
|
504
|
+
const bindings = await currentGuidedSessionBindings(request.projectRoot);
|
|
505
|
+
let resumedSession = null;
|
|
506
|
+
if (resumeId) {
|
|
507
|
+
resumedSession =
|
|
508
|
+
request.stdin === undefined
|
|
509
|
+
? await resumeGuidedSession({
|
|
510
|
+
projectRoot: request.projectRoot,
|
|
511
|
+
sessionId: resumeId,
|
|
512
|
+
bindings,
|
|
513
|
+
localRoot,
|
|
514
|
+
})
|
|
515
|
+
: await submitGuidedAnswers({
|
|
516
|
+
projectRoot: request.projectRoot,
|
|
517
|
+
sessionId: resumeId,
|
|
518
|
+
raw: request.stdin,
|
|
519
|
+
bindings,
|
|
520
|
+
localRoot,
|
|
521
|
+
});
|
|
522
|
+
supplied = normalizeOperatingInitializationAnswers(resumedSession.answers);
|
|
523
|
+
if (resumedSession.status === 'input-required') {
|
|
524
|
+
return {
|
|
525
|
+
schemaVersion: '1.0.0',
|
|
526
|
+
protocolVersion: '1.2.0',
|
|
527
|
+
ok: false,
|
|
528
|
+
action: 'input_required',
|
|
529
|
+
code: 'E_OPERATE_INPUT_REQUIRED',
|
|
530
|
+
message: 'Operating Board initialization needs explicit human input.',
|
|
531
|
+
state: resumedSession.session.state,
|
|
532
|
+
paths: {},
|
|
533
|
+
counts: {},
|
|
534
|
+
warnings: [],
|
|
535
|
+
nextActions: [],
|
|
536
|
+
next: [],
|
|
537
|
+
questionnaire: resumedSession.questionnaire,
|
|
538
|
+
exitCode: operateExitCode('E_OPERATE_INPUT_REQUIRED'),
|
|
539
|
+
};
|
|
336
540
|
}
|
|
337
541
|
}
|
|
338
|
-
const profile = option(request, 'profile', 'saas');
|
|
339
542
|
let customProfile;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (!profileFile) {
|
|
343
|
-
throw new OperateError('E_OPERATE_CONFIG_INVALID', 'The custom profile requires --profile-file inside the project.');
|
|
344
|
-
}
|
|
345
|
-
customProfile = await readCustomOperatingProfile(request.projectRoot, profileFile);
|
|
543
|
+
if (supplied.profile === 'custom' && supplied.profileFile) {
|
|
544
|
+
customProfile = await readCustomOperatingProfile(request.projectRoot, supplied.profileFile);
|
|
346
545
|
}
|
|
347
|
-
else if (
|
|
546
|
+
else if (supplied.profile !== undefined &&
|
|
547
|
+
supplied.profile !== 'custom' &&
|
|
548
|
+
supplied.profileFile) {
|
|
348
549
|
throw new OperateError('E_OPERATE_CONFIG_INVALID', '--profile-file is valid only with --profile custom.');
|
|
349
550
|
}
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
551
|
+
const context = {
|
|
552
|
+
projectRoot: request.projectRoot,
|
|
553
|
+
...bindings,
|
|
554
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
555
|
+
availableSources: ['repository', 'planr', 'git', 'file-import'],
|
|
556
|
+
runtime: option(request, 'runtime', 'unknown'),
|
|
557
|
+
interaction: request.interactive ? 'terminal' : 'none',
|
|
558
|
+
};
|
|
559
|
+
const questionState = await evaluateOperatingInitQuestions({
|
|
560
|
+
answers: supplied,
|
|
561
|
+
context,
|
|
562
|
+
requireCharter: true,
|
|
563
|
+
});
|
|
564
|
+
if (questionState.status === 'input-required') {
|
|
565
|
+
const sessionId = createGuidedSessionId();
|
|
566
|
+
const questionnaire = await createOperatingInitQuestionnaire({
|
|
567
|
+
context,
|
|
568
|
+
questions: questionState.questions,
|
|
569
|
+
stage: questionState.stage,
|
|
570
|
+
sessionId,
|
|
571
|
+
});
|
|
572
|
+
await createGuidedSession({
|
|
573
|
+
projectRoot: request.projectRoot,
|
|
574
|
+
questionnaire,
|
|
575
|
+
persistedAnswers: persistableOperatingInitAnswers(questionState.answers, context),
|
|
576
|
+
localRoot,
|
|
577
|
+
});
|
|
578
|
+
return {
|
|
579
|
+
schemaVersion: '1.0.0',
|
|
580
|
+
protocolVersion: '1.2.0',
|
|
581
|
+
ok: false,
|
|
582
|
+
action: 'input_required',
|
|
583
|
+
code: 'E_OPERATE_INPUT_REQUIRED',
|
|
584
|
+
message: 'Operating Board initialization needs explicit human input.',
|
|
585
|
+
state: null,
|
|
586
|
+
paths: {},
|
|
587
|
+
counts: {},
|
|
588
|
+
warnings: [],
|
|
589
|
+
nextActions: [],
|
|
590
|
+
next: [],
|
|
591
|
+
questionnaire,
|
|
592
|
+
exitCode: operateExitCode('E_OPERATE_INPUT_REQUIRED'),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const profile = supplied.profile;
|
|
596
|
+
const decisionOwner = supplied.decisionOwner ?? option(request, 'decisionOwner', '').trim();
|
|
597
|
+
const planningEngine = supplied.planningEngine ??
|
|
598
|
+
option(request, 'planningEngine', 'openplanr');
|
|
599
|
+
const rawCharter = supplied.charter ?? option(request, 'charter', {});
|
|
353
600
|
const preview = await prepareOperatingInitialization({
|
|
354
601
|
projectRoot: request.projectRoot,
|
|
355
602
|
profile,
|
|
356
603
|
decisionOwner,
|
|
357
604
|
planningEngine,
|
|
358
|
-
runtime: option(request, 'runtime', 'auto'),
|
|
359
|
-
cadence: option(request, 'cadence', 'manual'),
|
|
360
|
-
timezone:
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
605
|
+
runtime: supplied.runtime ?? option(request, 'runtime', 'auto'),
|
|
606
|
+
cadence: supplied.cadence ?? option(request, 'cadence', 'manual'),
|
|
607
|
+
timezone: supplied.timezone ??
|
|
608
|
+
option(request, 'timezone', Intl.DateTimeFormat().resolvedOptions().timeZone),
|
|
609
|
+
sensitivityCeiling: supplied.sensitivityCeiling ?? option(request, 'sensitivityCeiling', 'internal'),
|
|
610
|
+
enabledProviders: (supplied.sources ?? stringList(request.options.sources)).length
|
|
611
|
+
? (supplied.sources ?? stringList(request.options.sources))
|
|
364
612
|
: undefined,
|
|
365
|
-
evidenceFiles: stringList(request.options.evidenceFile),
|
|
613
|
+
evidenceFiles: supplied.evidenceFiles ?? stringList(request.options.evidenceFile),
|
|
366
614
|
charter: rawCharter,
|
|
367
615
|
customProfile,
|
|
368
|
-
componentRoots: stringList(request.options.components),
|
|
616
|
+
componentRoots: supplied.componentRoots ?? stringList(request.options.components),
|
|
617
|
+
localRoot,
|
|
618
|
+
now: resumedSession?.session.createdAt ??
|
|
619
|
+
option(request, 'previewCreatedAt', undefined),
|
|
369
620
|
});
|
|
370
621
|
const legacyMigration = await inspectOperatingMigration({
|
|
371
622
|
projectRoot: request.projectRoot,
|
|
372
623
|
});
|
|
373
624
|
const previewData = {
|
|
374
625
|
previewDigest: preview.previewDigest,
|
|
626
|
+
expectedEventHead: preview.expectedEventHead,
|
|
627
|
+
resultingEventHead: preview.resultingEventHead,
|
|
375
628
|
changedPaths: preview.changedPaths,
|
|
376
629
|
localPreferencesChanged: preview.preferencesChanged,
|
|
630
|
+
previewCreatedAt: preview.workspace.capturedAt,
|
|
377
631
|
workspaceDigest: preview.workspace.workspaceDigest,
|
|
378
632
|
config: preview.config,
|
|
379
633
|
legacyMigration,
|
|
634
|
+
...(resumedSession ? { sessionId: resumedSession.session.sessionId } : {}),
|
|
380
635
|
};
|
|
636
|
+
const { action: applyAction, confirmation } = await initializationApplyAction({
|
|
637
|
+
request,
|
|
638
|
+
preview,
|
|
639
|
+
bindings,
|
|
640
|
+
sessionId: resumedSession?.session.sessionId,
|
|
641
|
+
answers: questionState.answers,
|
|
642
|
+
});
|
|
643
|
+
const requestedConfirmation = option(request, 'confirm', undefined);
|
|
644
|
+
const confirmed = option(request, 'yes', false);
|
|
645
|
+
if (resumedSession) {
|
|
646
|
+
await updateGuidedSession({
|
|
647
|
+
projectRoot: request.projectRoot,
|
|
648
|
+
localRoot,
|
|
649
|
+
session: {
|
|
650
|
+
...resumedSession.session,
|
|
651
|
+
state: 'preview-ready',
|
|
652
|
+
previewDigest: preview.previewDigest,
|
|
653
|
+
confirmationDigest: confirmation?.confirmationDigest,
|
|
654
|
+
updatedAt: new Date().toISOString(),
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
if (!confirmed || !requestedConfirmation) {
|
|
658
|
+
return success(request.action, {
|
|
659
|
+
message: 'Operating Board initialization preview is ready; no state was written.',
|
|
660
|
+
state: 'preview-ready',
|
|
661
|
+
preview: { ...previewData, confirmation },
|
|
662
|
+
actions: [applyAction],
|
|
663
|
+
next: [
|
|
664
|
+
`planr operate init --resume ${resumedSession.session.sessionId} --confirm ${confirmation?.confirmationDigest} --yes --json`,
|
|
665
|
+
],
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}
|
|
381
669
|
if (option(request, 'preview', false) || option(request, 'dryRun', false)) {
|
|
382
670
|
return success(request.action, {
|
|
383
671
|
message: 'Operating Board initialization preview is ready; no state was written.',
|
|
384
|
-
|
|
385
|
-
|
|
672
|
+
state: 'preview-ready',
|
|
673
|
+
preview: { ...previewData, confirmation },
|
|
674
|
+
actions: [applyAction],
|
|
675
|
+
next: [`${applyAction.command} --confirm ${confirmation?.confirmationDigest} --yes`],
|
|
386
676
|
});
|
|
387
677
|
}
|
|
388
|
-
if (!
|
|
389
|
-
throw new OperateError('E_OPERATE_ROUTE_CONFIRMATION_REQUIRED', '
|
|
678
|
+
if (!confirmed || !requestedConfirmation || !confirmation) {
|
|
679
|
+
throw new OperateError('E_OPERATE_ROUTE_CONFIRMATION_REQUIRED', 'Initialization requires the exact named confirmation returned by its preview.', {
|
|
680
|
+
preview: { ...previewData, confirmation },
|
|
681
|
+
action: applyAction,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
const accepted = assertOperatingConfirmation({
|
|
685
|
+
expected: confirmation,
|
|
686
|
+
actionId: applyAction.id,
|
|
687
|
+
confirmationDigest: requestedConfirmation,
|
|
688
|
+
confirmed,
|
|
689
|
+
});
|
|
690
|
+
if (resumedSession) {
|
|
691
|
+
await updateGuidedSession({
|
|
692
|
+
projectRoot: request.projectRoot,
|
|
693
|
+
localRoot,
|
|
694
|
+
session: {
|
|
695
|
+
...resumedSession.session,
|
|
696
|
+
state: 'confirmed',
|
|
697
|
+
previewDigest: preview.previewDigest,
|
|
698
|
+
confirmationDigest: accepted.confirmationDigest,
|
|
699
|
+
confirmedAt: accepted.confirmedAt,
|
|
700
|
+
updatedAt: accepted.confirmedAt ?? new Date().toISOString(),
|
|
701
|
+
},
|
|
702
|
+
});
|
|
390
703
|
}
|
|
391
704
|
const applied = await applyOperatingInitialization({
|
|
392
705
|
projectRoot: request.projectRoot,
|
|
393
706
|
preview,
|
|
394
707
|
confirmationDigest: preview.previewDigest,
|
|
395
708
|
});
|
|
709
|
+
if (resumedSession) {
|
|
710
|
+
const appliedAt = new Date().toISOString();
|
|
711
|
+
await updateGuidedSession({
|
|
712
|
+
projectRoot: request.projectRoot,
|
|
713
|
+
localRoot,
|
|
714
|
+
session: {
|
|
715
|
+
...resumedSession.session,
|
|
716
|
+
state: 'applied',
|
|
717
|
+
previewDigest: preview.previewDigest,
|
|
718
|
+
confirmationDigest: accepted.confirmationDigest,
|
|
719
|
+
confirmedAt: accepted.confirmedAt,
|
|
720
|
+
appliedAt,
|
|
721
|
+
updatedAt: appliedAt,
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
}
|
|
396
725
|
return success(request.action, {
|
|
397
726
|
message: applied.initialized
|
|
398
727
|
? 'Operating Board initialized.'
|
|
@@ -539,6 +868,7 @@ async function status(request) {
|
|
|
539
868
|
});
|
|
540
869
|
}
|
|
541
870
|
async function run(request) {
|
|
871
|
+
await validateOperatingConfiguration(request.projectRoot);
|
|
542
872
|
const requestedRuntime = option(request, 'runtime', 'auto');
|
|
543
873
|
const deferAdvisors = !option(request, 'offline', false) &&
|
|
544
874
|
!option(request, 'dryRun', false) &&
|
|
@@ -635,6 +965,71 @@ async function collections(request) {
|
|
|
635
965
|
}
|
|
636
966
|
return success(request.action, { data });
|
|
637
967
|
}
|
|
968
|
+
async function evidenceRecovery(request) {
|
|
969
|
+
const candidateId = argument(request, 'candidateId');
|
|
970
|
+
if (request.action === 'evidence.diagnose') {
|
|
971
|
+
const data = candidateId
|
|
972
|
+
? await readEvidenceDiagnostic({
|
|
973
|
+
projectRoot: request.projectRoot,
|
|
974
|
+
candidateId,
|
|
975
|
+
localRoot: option(request, 'localRoot', undefined),
|
|
976
|
+
})
|
|
977
|
+
: await listEvidenceDiagnostics({
|
|
978
|
+
projectRoot: request.projectRoot,
|
|
979
|
+
localRoot: option(request, 'localRoot', undefined),
|
|
980
|
+
});
|
|
981
|
+
const diagnostic = Array.isArray(data) ? null : data;
|
|
982
|
+
return success(request.action, {
|
|
983
|
+
data: {
|
|
984
|
+
diagnostic: data,
|
|
985
|
+
valueDisclosed: false,
|
|
986
|
+
recovery: diagnostic
|
|
987
|
+
? {
|
|
988
|
+
repairOrRemove: diagnostic.location
|
|
989
|
+
? `Repair or remove the candidate at ${diagnostic.location}${diagnostic.line ? `:${diagnostic.line}` : ''}, then rerun.`
|
|
990
|
+
: 'Repair or remove the candidate in the identified component, then rerun.',
|
|
991
|
+
rotateCredential: 'If this is a real credential, rotate it before removing it from source history.',
|
|
992
|
+
eligibleExclusion: 'Exclude only the exact eligible source path through operating source policy; broad scanner bypasses are not supported.',
|
|
993
|
+
falsePositive: `planr operate evidence classify ${diagnostic.candidateId} --status false-positive --reason "<reason>" --json`,
|
|
994
|
+
rerun: 'planr operate run --offline',
|
|
995
|
+
}
|
|
996
|
+
: null,
|
|
997
|
+
},
|
|
998
|
+
actions: diagnostic?.actions,
|
|
999
|
+
next: diagnostic
|
|
1000
|
+
? [
|
|
1001
|
+
`planr operate evidence classify ${diagnostic.candidateId} --status false-positive --reason "<reason>" --json`,
|
|
1002
|
+
'planr operate run --offline',
|
|
1003
|
+
]
|
|
1004
|
+
: [],
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
if (!candidateId) {
|
|
1008
|
+
throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Evidence candidate ID is required.');
|
|
1009
|
+
}
|
|
1010
|
+
const status = option(request, 'status', '');
|
|
1011
|
+
if (status !== 'false-positive' && status !== 'confirmed-secret') {
|
|
1012
|
+
throw new OperateError('E_OPERATE_CONFIG_INVALID', 'Evidence classification status must be false-positive or confirmed-secret.');
|
|
1013
|
+
}
|
|
1014
|
+
const config = await validateOperatingConfiguration(request.projectRoot);
|
|
1015
|
+
const data = await classifyEvidenceDiagnostic({
|
|
1016
|
+
projectRoot: request.projectRoot,
|
|
1017
|
+
candidateId,
|
|
1018
|
+
status,
|
|
1019
|
+
reason: option(request, 'reason', ''),
|
|
1020
|
+
classifiedBy: config.decisionOwner,
|
|
1021
|
+
confirmationDigest: option(request, 'confirm', undefined),
|
|
1022
|
+
confirmed: option(request, 'yes', false),
|
|
1023
|
+
localRoot: option(request, 'localRoot', undefined),
|
|
1024
|
+
});
|
|
1025
|
+
return success(request.action, {
|
|
1026
|
+
data,
|
|
1027
|
+
actions: data.state === 'preview' && data.action ? [data.action] : undefined,
|
|
1028
|
+
message: data.state === 'classified'
|
|
1029
|
+
? `Evidence candidate ${candidateId} was classified without storing or exposing its value.`
|
|
1030
|
+
: 'Review and confirm the exact evidence classification action.',
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
638
1033
|
async function cycleMutation(request) {
|
|
639
1034
|
const cycleId = argument(request, 'cycleId');
|
|
640
1035
|
if (!cycleId)
|
|
@@ -736,6 +1131,7 @@ async function maintenance(request) {
|
|
|
736
1131
|
projectRoot: request.projectRoot,
|
|
737
1132
|
action: request.action.endsWith('purge') ? 'purge' : 'status',
|
|
738
1133
|
confirmed: option(request, 'yes', false),
|
|
1134
|
+
localRoot: option(request, 'localRoot', undefined),
|
|
739
1135
|
}),
|
|
740
1136
|
});
|
|
741
1137
|
}
|
|
@@ -844,6 +1240,8 @@ const HANDLERS = {
|
|
|
844
1240
|
'gaps.verify': answerMutation,
|
|
845
1241
|
'evidence.list': collections,
|
|
846
1242
|
'evidence.show': collections,
|
|
1243
|
+
'evidence.diagnose': evidenceRecovery,
|
|
1244
|
+
'evidence.classify': evidenceRecovery,
|
|
847
1245
|
'migrate.inspect': maintenance,
|
|
848
1246
|
'migrate.apply': maintenance,
|
|
849
1247
|
'migrations.list': collections,
|
|
@@ -872,10 +1270,15 @@ export async function executeOperateAction(request) {
|
|
|
872
1270
|
if (!handler) {
|
|
873
1271
|
throw new OperateError('E_OPERATE_ACTION_UNKNOWN', `Unknown Operating Board action: ${request.action}.`);
|
|
874
1272
|
}
|
|
875
|
-
return await handler(request);
|
|
1273
|
+
return await attachStructuredActions(request, await handler(request));
|
|
876
1274
|
}
|
|
877
1275
|
catch (error) {
|
|
878
|
-
|
|
1276
|
+
try {
|
|
1277
|
+
return await attachStructuredActions(request, failure(request.action, error));
|
|
1278
|
+
}
|
|
1279
|
+
catch {
|
|
1280
|
+
return failure(request.action, error);
|
|
1281
|
+
}
|
|
879
1282
|
}
|
|
880
1283
|
}
|
|
881
1284
|
//# sourceMappingURL=index.js.map
|