openplanr 1.19.0 → 1.20.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/dist/cli/commands/backlog.d.ts.map +1 -1
- package/dist/cli/commands/backlog.js +3 -0
- package/dist/cli/commands/backlog.js.map +1 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +3 -0
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/epic.d.ts.map +1 -1
- package/dist/cli/commands/epic.js +2 -0
- package/dist/cli/commands/epic.js.map +1 -1
- package/dist/cli/commands/feature.d.ts.map +1 -1
- package/dist/cli/commands/feature.js +2 -0
- package/dist/cli/commands/feature.js.map +1 -1
- package/dist/cli/commands/operate.d.ts +5 -2
- package/dist/cli/commands/operate.d.ts.map +1 -1
- package/dist/cli/commands/operate.js +10 -101
- package/dist/cli/commands/operate.js.map +1 -1
- package/dist/cli/commands/plan.d.ts.map +1 -1
- package/dist/cli/commands/plan.js +2 -0
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/quick.d.ts.map +1 -1
- package/dist/cli/commands/quick.js +2 -0
- package/dist/cli/commands/quick.js.map +1 -1
- package/dist/cli/commands/refine.d.ts.map +1 -1
- package/dist/cli/commands/refine.js +2 -0
- package/dist/cli/commands/refine.js.map +1 -1
- package/dist/cli/commands/revise.d.ts.map +1 -1
- package/dist/cli/commands/revise.js +2 -0
- package/dist/cli/commands/revise.js.map +1 -1
- package/dist/cli/commands/spec.d.ts.map +1 -1
- package/dist/cli/commands/spec.js +2 -0
- package/dist/cli/commands/spec.js.map +1 -1
- package/dist/cli/commands/sprint.d.ts.map +1 -1
- package/dist/cli/commands/sprint.js +2 -0
- package/dist/cli/commands/sprint.js.map +1 -1
- package/dist/cli/commands/story.d.ts.map +1 -1
- package/dist/cli/commands/story.js +3 -0
- package/dist/cli/commands/story.js.map +1 -1
- package/dist/cli/commands/task.d.ts.map +1 -1
- package/dist/cli/commands/task.js +3 -0
- package/dist/cli/commands/task.js.map +1 -1
- package/dist/services/ai-service.d.ts +3 -5
- package/dist/services/ai-service.d.ts.map +1 -1
- package/dist/services/ai-service.js +7 -1
- package/dist/services/ai-service.js.map +1 -1
- package/dist/services/credentials-service.d.ts.map +1 -1
- package/dist/services/credentials-service.js +2 -0
- package/dist/services/credentials-service.js.map +1 -1
- package/dist/services/deprecation-notices.d.ts +5 -0
- package/dist/services/deprecation-notices.d.ts.map +1 -0
- package/dist/services/deprecation-notices.js +6 -0
- package/dist/services/deprecation-notices.js.map +1 -0
- package/dist/services/operate/advisors.d.ts +144 -166
- package/dist/services/operate/advisors.d.ts.map +1 -1
- package/dist/services/operate/advisors.js +178 -543
- package/dist/services/operate/advisors.js.map +1 -1
- package/dist/services/operate/citation-resolution.d.ts.map +1 -1
- package/dist/services/operate/citation-resolution.js +50 -1
- package/dist/services/operate/citation-resolution.js.map +1 -1
- package/dist/services/operate/config.d.ts +18 -52
- package/dist/services/operate/config.d.ts.map +1 -1
- package/dist/services/operate/config.js +24 -206
- package/dist/services/operate/config.js.map +1 -1
- package/dist/services/operate/doctor.d.ts +15 -0
- package/dist/services/operate/doctor.d.ts.map +1 -1
- package/dist/services/operate/doctor.js +142 -32
- package/dist/services/operate/doctor.js.map +1 -1
- package/dist/services/operate/engine.d.ts +22 -78
- package/dist/services/operate/engine.d.ts.map +1 -1
- package/dist/services/operate/engine.js +269 -202
- package/dist/services/operate/engine.js.map +1 -1
- package/dist/services/operate/evidence.d.ts +9 -107
- package/dist/services/operate/evidence.d.ts.map +1 -1
- package/dist/services/operate/evidence.js +0 -1403
- 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 +10 -231
- package/dist/services/operate/index.js.map +1 -1
- package/dist/services/operate/integrity.d.ts +81 -0
- package/dist/services/operate/integrity.d.ts.map +1 -0
- package/dist/services/operate/integrity.js +152 -0
- package/dist/services/operate/integrity.js.map +1 -0
- package/dist/services/operate/interaction/answer-service.d.ts +0 -10
- package/dist/services/operate/interaction/answer-service.d.ts.map +1 -1
- package/dist/services/operate/interaction/answer-service.js +63 -43
- package/dist/services/operate/interaction/answer-service.js.map +1 -1
- package/dist/services/operate/interaction/question-engine.d.ts.map +1 -1
- package/dist/services/operate/interaction/question-engine.js +105 -21
- package/dist/services/operate/interaction/question-engine.js.map +1 -1
- package/dist/services/operate/interaction/question-registry.d.ts +16 -1
- package/dist/services/operate/interaction/question-registry.d.ts.map +1 -1
- package/dist/services/operate/interaction/question-registry.js +45 -39
- package/dist/services/operate/interaction/question-registry.js.map +1 -1
- package/dist/services/operate/interaction/session-service.d.ts.map +1 -1
- package/dist/services/operate/interaction/session-service.js +16 -11
- package/dist/services/operate/interaction/session-service.js.map +1 -1
- package/dist/services/operate/interaction/terminal-renderer.d.ts.map +1 -1
- package/dist/services/operate/interaction/terminal-renderer.js +16 -6
- package/dist/services/operate/interaction/terminal-renderer.js.map +1 -1
- package/dist/services/operate/maintenance.d.ts +1 -11
- package/dist/services/operate/maintenance.d.ts.map +1 -1
- package/dist/services/operate/maintenance.js +94 -342
- package/dist/services/operate/maintenance.js.map +1 -1
- package/dist/services/operate/mission-dispatch.d.ts +73 -55
- package/dist/services/operate/mission-dispatch.d.ts.map +1 -1
- package/dist/services/operate/mission-dispatch.js +91 -67
- package/dist/services/operate/mission-dispatch.js.map +1 -1
- package/dist/services/operate/projection-persistence.d.ts.map +1 -1
- package/dist/services/operate/projection-persistence.js +22 -2
- package/dist/services/operate/projection-persistence.js.map +1 -1
- package/dist/services/operate/projection.d.ts.map +1 -1
- package/dist/services/operate/projection.js +10 -3
- package/dist/services/operate/projection.js.map +1 -1
- package/dist/services/operate/protocol.d.ts +1 -41
- package/dist/services/operate/protocol.d.ts.map +1 -1
- package/dist/services/operate/protocol.js +7 -45
- package/dist/services/operate/protocol.js.map +1 -1
- package/dist/services/operate/redaction.d.ts +3 -3
- package/dist/services/operate/redaction.js +4 -4
- package/dist/services/operate/redaction.js.map +1 -1
- package/dist/services/operate/reports.d.ts +11 -1
- package/dist/services/operate/reports.d.ts.map +1 -1
- package/dist/services/operate/reports.js +88 -3
- package/dist/services/operate/reports.js.map +1 -1
- package/dist/services/operate/types.d.ts +42 -128
- 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 +0 -1
- package/dist/services/operate/workspace.d.ts.map +1 -1
- package/dist/services/operate/workspace.js +0 -2
- package/dist/services/operate/workspace.js.map +1 -1
- package/dist/services/runtime-manager-service.js +2 -2
- package/dist/services/runtime-manager-service.js.map +1 -1
- package/package.json +3 -3
- package/dist/services/operate/evidence-classifications.d.ts +0 -59
- package/dist/services/operate/evidence-classifications.d.ts.map +0 -1
- package/dist/services/operate/evidence-classifications.js +0 -132
- package/dist/services/operate/evidence-classifications.js.map +0 -1
- package/dist/services/operate/evidence-diagnostics.d.ts +0 -23
- package/dist/services/operate/evidence-diagnostics.d.ts.map +0 -1
- package/dist/services/operate/evidence-diagnostics.js +0 -152
- package/dist/services/operate/evidence-diagnostics.js.map +0 -1
- package/dist/services/operate/evidence-readiness.d.ts +0 -18
- package/dist/services/operate/evidence-readiness.d.ts.map +0 -1
- package/dist/services/operate/evidence-readiness.js +0 -94
- package/dist/services/operate/evidence-readiness.js.map +0 -1
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { mkdir, readdir, readFile, rename, rm, unlink, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { advisorResponseContractDetails, assertAdvisorOutputMatchesBrief,
|
|
4
|
+
import { advisorResponseContractDetails, assertAdvisorOutputMatchesBrief, buildOperatingMandate, createNativeMissionOperatingRoleResult, } from './advisors.js';
|
|
5
5
|
import { canonicalDigest, canonicalize, sha256Digest } from './canonical.js';
|
|
6
|
-
import { operatingProjectKey, readOperatingAdapterLeaseDurationMs
|
|
7
|
-
import {
|
|
6
|
+
import { operatingProjectKey, readOperatingAdapterLeaseDurationMs } from './config.js';
|
|
7
|
+
import { gateRecordedProposalCitations } from './engine.js';
|
|
8
8
|
import { OperatingEventStore } from './event-store.js';
|
|
9
|
-
import { buildOperatingEvidenceIndex, starvedRoleEvidenceGaps } from './evidence.js';
|
|
10
9
|
import { OperatingEvidenceCache } from './evidence-cache.js';
|
|
11
|
-
import { purgeStaleEvidenceClassifications } from './evidence-classifications.js';
|
|
12
|
-
import { listEvidenceDiagnostics } from './evidence-diagnostics.js';
|
|
13
|
-
import { evaluateEvidenceReadiness } from './evidence-readiness.js';
|
|
14
10
|
import { guidedSessionStatus, purgeGuidedSessions } from './interaction/session-service.js';
|
|
15
11
|
import { assertCommittedOperatingView, recoverOperatingTransactions } from './journal.js';
|
|
16
12
|
import { withOperatingLock } from './lock-service.js';
|
|
17
|
-
import { narrowMissionRootsToCeiling, operatingRegistryDispatchMode, operatingRuntimeEnforcesBoundedReadOnly, resolveOperatingDispatchMode, } from './mission-dispatch.js';
|
|
18
13
|
import { assertOperatingArtifact, loadOperatingProtocol } from './protocol.js';
|
|
19
|
-
import {
|
|
14
|
+
import { containsSecret, redactSensitiveText } from './redaction.js';
|
|
20
15
|
import { OPERATE_PROTOCOL_VERSION, OPERATE_SCHEMA_VERSION, OperateError, } from './types.js';
|
|
21
16
|
import { readOperatingConfig, refreshOperatingWorkspaceManifest, resolveContainedPath, resolveOperatingPaths, } from './workspace.js';
|
|
22
|
-
/**
|
|
23
|
-
* Whether any bound role of this session resolved to native mission dispatch, so
|
|
24
|
-
* both adapter-handoff call sites can pass `protocolVersion: '1.3.0'` and the
|
|
25
|
-
* pipeline emits the v1.3 mission record action (`dispatch.agent` +
|
|
26
|
-
* `dispatch.missionPacketPointer`) instead of the v1.2 `rolePackPointer`.
|
|
27
|
-
*/
|
|
28
|
-
function sessionHasMissionRole(session) {
|
|
29
|
-
return Boolean(session.roleMissionPackets && Object.keys(session.roleMissionPackets).length > 0);
|
|
30
|
-
}
|
|
31
17
|
function adapterSessionSummary(session) {
|
|
32
|
-
const { roleBriefs: _roleBriefs,
|
|
18
|
+
const { roleBriefs: _roleBriefs, roleMandates: _roleMandates, ...summary } = session;
|
|
33
19
|
return summary;
|
|
34
20
|
}
|
|
35
21
|
async function adapterHandoff(session) {
|
|
@@ -43,10 +29,7 @@ async function adapterHandoff(session) {
|
|
|
43
29
|
: 'finalize-required';
|
|
44
30
|
const protocol = await loadOperatingProtocol();
|
|
45
31
|
const handoff = protocol.createOperatingAdapterHandoff({
|
|
46
|
-
|
|
47
|
-
// generated lens agent and points at its mission packet; v1.2 (pack) default
|
|
48
|
-
// otherwise, byte-compatible with the pack-mode handoff.
|
|
49
|
-
...(sessionHasMissionRole(session) ? { protocolVersion: '1.3.0' } : {}),
|
|
32
|
+
protocolVersion: '1.3.0',
|
|
50
33
|
phase: session.phase,
|
|
51
34
|
state,
|
|
52
35
|
cycleId: session.cycleId,
|
|
@@ -187,25 +170,12 @@ export async function operatingCacheAction(input) {
|
|
|
187
170
|
.catch(() => ({ sensitivityCeiling: 'internal' }));
|
|
188
171
|
const cache = new OperatingEvidenceCache(paths.evidence, preferences.sensitivityCeiling ?? 'internal');
|
|
189
172
|
if (input.action === 'status') {
|
|
190
|
-
const diagnostics = await listEvidenceDiagnostics({
|
|
191
|
-
projectRoot: input.projectRoot,
|
|
192
|
-
localRoot: input.localRoot,
|
|
193
|
-
});
|
|
194
|
-
const classifications = await purgeStaleEvidenceClassifications({
|
|
195
|
-
projectRoot: input.projectRoot,
|
|
196
|
-
localRoot: input.localRoot,
|
|
197
|
-
});
|
|
198
173
|
return {
|
|
199
174
|
evidence: await cache.status(),
|
|
200
175
|
sessions: await guidedSessionStatus({
|
|
201
176
|
projectRoot: input.projectRoot,
|
|
202
177
|
localRoot: input.localRoot,
|
|
203
178
|
}),
|
|
204
|
-
diagnostics: {
|
|
205
|
-
candidates: diagnostics.length,
|
|
206
|
-
classified: diagnostics.filter((entry) => entry.classification).length,
|
|
207
|
-
staleClassifications: classifications.stale,
|
|
208
|
-
},
|
|
209
179
|
};
|
|
210
180
|
}
|
|
211
181
|
if (!input.confirmed) {
|
|
@@ -216,11 +186,6 @@ export async function operatingCacheAction(input) {
|
|
|
216
186
|
projectRoot: input.projectRoot,
|
|
217
187
|
localRoot: input.localRoot,
|
|
218
188
|
});
|
|
219
|
-
const classifications = await purgeStaleEvidenceClassifications({
|
|
220
|
-
projectRoot: input.projectRoot,
|
|
221
|
-
localRoot: input.localRoot,
|
|
222
|
-
purge: true,
|
|
223
|
-
});
|
|
224
189
|
// FR4/FR11: clearing machine-local caches also drops board-bound adapter
|
|
225
190
|
// sessions and incremental evidence baselines, so this is the scoped fix the
|
|
226
191
|
// doctor names for stale adapter sessions and stale incremental baselines.
|
|
@@ -231,12 +196,10 @@ export async function operatingCacheAction(input) {
|
|
|
231
196
|
return {
|
|
232
197
|
removed: removed.length +
|
|
233
198
|
sessions.removed +
|
|
234
|
-
classifications.purged +
|
|
235
199
|
board.removedAdvisorSessions +
|
|
236
200
|
board.removedIncrementalBaselines,
|
|
237
201
|
evidence: { removed: removed.length, entries: removed },
|
|
238
202
|
sessions,
|
|
239
|
-
classifications,
|
|
240
203
|
adapterSessions: { removed: board.removedAdvisorSessions },
|
|
241
204
|
incrementalBaselines: { removed: board.removedIncrementalBaselines },
|
|
242
205
|
};
|
|
@@ -701,76 +664,31 @@ async function readAdapterSensitivityCeiling(projectRoot, localRoot) {
|
|
|
701
664
|
'internal')
|
|
702
665
|
.catch(() => 'internal');
|
|
703
666
|
}
|
|
667
|
+
// The mandate `boundaries.roots` items must satisfy this pattern (leading dot
|
|
668
|
+
// permitted, no `..` traversal), so `.planr` is a valid declared root.
|
|
669
|
+
const MANDATE_ROOT_PATTERN = /^(?!.*\.\.)[A-Za-z0-9._][A-Za-z0-9._/-]*$/;
|
|
704
670
|
/**
|
|
705
|
-
* FR2:
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
671
|
+
* FR1/FR2: derive a mandate's declared read roots directly from the project
|
|
672
|
+
* working tree — every top-level directory the agent may traverse — rather than
|
|
673
|
+
* from a collected evidence index. `.planr/` is ALWAYS declared, whether or not
|
|
674
|
+
* it exists yet and REGARDLESS of git tracking, because the mission tool surface
|
|
675
|
+
* (`mission-dispatch.ts`) walks the filesystem directly rather than `git
|
|
676
|
+
* ls-files`, so a gitignored `.planr/` tree is fully readable (finding 2's
|
|
677
|
+
* tracked-file gap cannot reproduce here). `.git`/`node_modules` are never
|
|
678
|
+
* declared, and every root is filtered to the mandate's schema pattern.
|
|
712
679
|
*/
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const [top] = location.split('/');
|
|
724
|
-
return top && top.length > 0 ? top : null;
|
|
725
|
-
};
|
|
726
|
-
const declaredRoots = [
|
|
727
|
-
...new Set(compliantItems
|
|
728
|
-
.map((item) => topSegment(item.location))
|
|
729
|
-
.filter((segment) => Boolean(segment))),
|
|
730
|
-
];
|
|
731
|
-
const evidenceIndex = compliantItems.map((item) => ({
|
|
732
|
-
path: item.location,
|
|
733
|
-
sensitivity: item.sensitivity,
|
|
734
|
-
}));
|
|
735
|
-
const allowedRoots = new Set(narrowMissionRootsToCeiling({ declaredRoots, evidenceIndex, ceiling }));
|
|
736
|
-
return {
|
|
737
|
-
...evidence,
|
|
738
|
-
items: compliantItems.filter((item) => {
|
|
739
|
-
const top = topSegment(item.location);
|
|
740
|
-
return top === null || allowedRoots.has(top);
|
|
741
|
-
}),
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Resolve every requested role's effective dispatch mode for THIS runtime (FR1):
|
|
746
|
-
* the v1.3 registry default, overridden by the machine-local
|
|
747
|
-
* `dispatchModeOverrides`, reconciled against whether the runtime natively
|
|
748
|
-
* enforces the bounded read-only boundary. The adapter lifecycle IS the native
|
|
749
|
-
* runtime path, so the hosting adapter is native-capable by construction; the
|
|
750
|
-
* runtime's own enforceability then decides whether a role receives a native
|
|
751
|
-
* bounded mission lens (`resolution.native`) or falls closed to the v1.2 pack.
|
|
752
|
-
*/
|
|
753
|
-
async function resolveBoundRoleDispatchModes(input) {
|
|
754
|
-
const protocol = await loadOperatingProtocol();
|
|
755
|
-
const registryDefaults = new Map(protocol.listOperatingRoles().map((role) => [
|
|
756
|
-
role.id,
|
|
757
|
-
operatingRegistryDispatchMode(role),
|
|
758
|
-
]));
|
|
759
|
-
const overrides = await readOperatingDispatchModeOverrides(input.projectRoot, {
|
|
760
|
-
localRoot: input.localRoot,
|
|
761
|
-
});
|
|
762
|
-
const runtimeEnforcesBoundedReadOnly = await operatingRuntimeEnforcesBoundedReadOnly(input.runtime);
|
|
763
|
-
const resolved = new Map();
|
|
764
|
-
for (const roleId of input.roles) {
|
|
765
|
-
resolved.set(roleId, resolveOperatingDispatchMode({
|
|
766
|
-
roleId: roleId,
|
|
767
|
-
registryDefault: registryDefaults.get(roleId) ?? 'mission',
|
|
768
|
-
override: overrides[roleId],
|
|
769
|
-
runtimeEnforcesBoundedReadOnly,
|
|
770
|
-
adapterNativeCapable: true,
|
|
771
|
-
}));
|
|
680
|
+
async function deriveOperatingMandateRoots(projectRoot) {
|
|
681
|
+
const entries = await readdir(projectRoot, { withFileTypes: true }).catch(() => []);
|
|
682
|
+
const roots = new Set(['.planr']);
|
|
683
|
+
for (const entry of entries) {
|
|
684
|
+
if (!entry.isDirectory())
|
|
685
|
+
continue;
|
|
686
|
+
if (entry.name === '.git' || entry.name === 'node_modules')
|
|
687
|
+
continue;
|
|
688
|
+
if (MANDATE_ROOT_PATTERN.test(entry.name))
|
|
689
|
+
roots.add(entry.name);
|
|
772
690
|
}
|
|
773
|
-
return
|
|
691
|
+
return [...roots].sort();
|
|
774
692
|
}
|
|
775
693
|
export async function createOperatingAdapterStartHandoff(input) {
|
|
776
694
|
const target = adapterSessionPath(input.projectRoot, input.cycleId, input.localRoot);
|
|
@@ -797,19 +715,8 @@ export async function createOperatingAdapterStartHandoff(input) {
|
|
|
797
715
|
}
|
|
798
716
|
}
|
|
799
717
|
const protocol = await loadOperatingProtocol();
|
|
800
|
-
// FR1 call site 2 (the start / prepare-required handoff): stamp v1.3 whenever
|
|
801
|
-
// any bound role would resolve to native mission dispatch on this runtime, so
|
|
802
|
-
// the whole lifecycle — including the record actions built after prepare —
|
|
803
|
-
// carries the mission protocol version rather than defaulting to pack.
|
|
804
|
-
const dispatchModes = await resolveBoundRoleDispatchModes({
|
|
805
|
-
projectRoot: input.projectRoot,
|
|
806
|
-
localRoot: input.localRoot,
|
|
807
|
-
runtime: input.runtime,
|
|
808
|
-
roles: input.roles,
|
|
809
|
-
});
|
|
810
|
-
const anyMission = [...dispatchModes.values()].some((resolution) => resolution.native);
|
|
811
718
|
const handoff = protocol.createOperatingAdapterHandoff({
|
|
812
|
-
|
|
719
|
+
protocolVersion: '1.3.0',
|
|
813
720
|
phase: input.phase,
|
|
814
721
|
state: 'prepare-required',
|
|
815
722
|
cycleId: input.cycleId,
|
|
@@ -938,171 +845,38 @@ export async function operateAdapterLifecycle(input) {
|
|
|
938
845
|
// FR4: a finalized/expired/cancelled same-board session no longer forces
|
|
939
846
|
// a whole-cycle-cancelling error on a new compatible prepare. The
|
|
940
847
|
// advisors→chair continuation and a same-phase re-prepare both fall
|
|
941
|
-
// through and supersede the dead session; an exact dead session
|
|
942
|
-
//
|
|
943
|
-
// rather than recomputed.
|
|
848
|
+
// through and supersede the dead session; an exact dead session reuses
|
|
849
|
+
// its already-built mandates so recorded advisory work is not recomputed.
|
|
944
850
|
if (exact && (sessionExpired(normalized) || normalized.state === 'cancelled')) {
|
|
945
851
|
recoverableSession = normalized;
|
|
946
852
|
}
|
|
947
853
|
}
|
|
948
854
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
//
|
|
973
|
-
|
|
974
|
-
// body-free v1.3 mission packet; every other role keeps the v1.2 role pack.
|
|
975
|
-
// A recovered dead-but-exact session reuses its stored packs/packets verbatim.
|
|
976
|
-
const dispatchModes = await resolveBoundRoleDispatchModes({
|
|
977
|
-
projectRoot: input.projectRoot,
|
|
978
|
-
localRoot: input.localRoot,
|
|
979
|
-
runtime,
|
|
980
|
-
roles,
|
|
981
|
-
});
|
|
982
|
-
const missionRoleIds = readiness.roles
|
|
983
|
-
.map((role) => role.roleId)
|
|
984
|
-
.filter((roleId) => dispatchModes.get(roleId)?.native);
|
|
985
|
-
const packReadinessRoles = readiness.roles.filter((role) => !dispatchModes.get(role.roleId)?.native);
|
|
986
|
-
const rolePacks = recoverableSession?.rolePacks ??
|
|
987
|
-
Object.fromEntries(await Promise.all(packReadinessRoles.map(async (role) => [
|
|
988
|
-
role.roleId,
|
|
989
|
-
await createOperatingAdvisorPack({
|
|
990
|
-
cycleId: input.cycleId,
|
|
991
|
-
role,
|
|
992
|
-
evidence: roleEvidence,
|
|
993
|
-
context,
|
|
994
|
-
}),
|
|
995
|
-
])));
|
|
996
|
-
// FR1: build the natively-dispatched roles' mission packets from live
|
|
997
|
-
// cycle/workspace state. Only reached when a role actually resolved mission,
|
|
998
|
-
// so a pure pack-mode prepare never loads config/workspace and stays
|
|
999
|
-
// byte-compatible with the v1.2 path. The evidence is ceiling-narrowed by
|
|
1000
|
-
// `narrowMissionRootsToCeiling` first, so a declared read root can never reach
|
|
1001
|
-
// an above-ceiling file even before the bounded reader's read-time check.
|
|
1002
|
-
// FR2: after narrowing, the same POST-index gate the readiness evaluator now
|
|
1003
|
-
// understands runs here — a mission role whose repository items were all
|
|
1004
|
-
// dropped by the dot-prefixed/pattern filter retains zero repository index
|
|
1005
|
-
// items, is gated not-ready with a governed gap, and is dropped from
|
|
1006
|
-
// `missionRoleIds` instead of dispatching on an empty evidence base. The
|
|
1007
|
-
// other ready roles still dispatch.
|
|
1008
|
-
let starvedMissionRoleIds = [];
|
|
1009
|
-
let evidenceGaps = [];
|
|
1010
|
-
// FR4: index truncations the published pipeline performed for over-cap roles,
|
|
1011
|
-
// surfaced onto the prepare result so the drop reaches the cycle as a warning.
|
|
1012
|
-
let missionEvidenceTruncations = [];
|
|
1013
|
-
const roleMissionPackets = recoverableSession?.roleMissionPackets ??
|
|
1014
|
-
(missionRoleIds.length > 0
|
|
1015
|
-
? await (async () => {
|
|
1016
|
-
const [config, workspace, sensitivityCeiling] = await Promise.all([
|
|
1017
|
-
readOperatingConfig(input.projectRoot, { localRoot: input.localRoot }),
|
|
1018
|
-
refreshOperatingWorkspaceManifest(input.projectRoot, { localRoot: input.localRoot }),
|
|
1019
|
-
readAdapterSensitivityCeiling(input.projectRoot, input.localRoot),
|
|
1020
|
-
]);
|
|
1021
|
-
const narrowedEvidence = narrowEvidenceToMissionCeiling(roleEvidence, sensitivityCeiling);
|
|
1022
|
-
const missionEvidenceIndex = buildOperatingEvidenceIndex(narrowedEvidence, {
|
|
1023
|
-
sensitivityCeiling,
|
|
1024
|
-
});
|
|
1025
|
-
const postIndexReadiness = await evaluateEvidenceReadiness({
|
|
1026
|
-
cycleId: input.cycleId,
|
|
1027
|
-
evidence: narrowedEvidence,
|
|
1028
|
-
enabledRoles: missionRoleIds,
|
|
1029
|
-
now: new Date(),
|
|
1030
|
-
missionEvidenceIndex,
|
|
1031
|
-
});
|
|
1032
|
-
starvedMissionRoleIds = postIndexReadiness.roles
|
|
1033
|
-
.filter((role) => !role.modelCallAllowed)
|
|
1034
|
-
.map((role) => role.roleId);
|
|
1035
|
-
const dispatchableMissionRoleIds = missionRoleIds.filter((roleId) => !starvedMissionRoleIds.includes(roleId));
|
|
1036
|
-
if (starvedMissionRoleIds.length > 0) {
|
|
1037
|
-
evidenceGaps = await starvedRoleEvidenceGaps({
|
|
1038
|
-
cycleId: input.cycleId,
|
|
1039
|
-
roleIds: starvedMissionRoleIds,
|
|
1040
|
-
owner: config.decisionOwner,
|
|
1041
|
-
now: new Date().toISOString(),
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
if (dispatchableMissionRoleIds.length === 0)
|
|
1045
|
-
return {};
|
|
1046
|
-
// FR4: pass the configured item budget as the upper bound; each role's
|
|
1047
|
-
// effective cap is the smaller of it and the role's registry-sized
|
|
1048
|
-
// default. A monorepo-scale index is truncated (highest-priority items
|
|
1049
|
-
// kept) so its packet fits the derived budget instead of failing
|
|
1050
|
-
// closed; the truncation record is captured for the cycle warnings.
|
|
1051
|
-
const { packets, truncations } = await buildOperatingMissionPackets({
|
|
1052
|
-
cycleId: input.cycleId,
|
|
1053
|
-
config,
|
|
1054
|
-
workspace,
|
|
1055
|
-
context,
|
|
1056
|
-
evidence: narrowedEvidence,
|
|
1057
|
-
roleIds: dispatchableMissionRoleIds,
|
|
1058
|
-
sensitivityCeiling,
|
|
1059
|
-
maxEvidenceItems: config.budgets.maxItems,
|
|
1060
|
-
});
|
|
1061
|
-
missionEvidenceTruncations = truncations;
|
|
1062
|
-
return Object.fromEntries(packets.map((packet) => [packet.roleId, packet]));
|
|
1063
|
-
})()
|
|
1064
|
-
: {});
|
|
1065
|
-
// FR2: the session's bound role set drops any starved mission role, so its
|
|
1066
|
-
// handoff, briefs, and input digests never reference a role that will not
|
|
1067
|
-
// dispatch. When nothing is starved this is exactly `roles`, keeping the
|
|
1068
|
-
// pack-mode / healthy-mission prepare byte-identical.
|
|
1069
|
-
const dispatchedRoles = starvedMissionRoleIds.length > 0
|
|
1070
|
-
? roles.filter((role) => !starvedMissionRoleIds.includes(role))
|
|
1071
|
-
: roles;
|
|
1072
|
-
// FR2: fail closed before a native adapter session is assembled. A freshly
|
|
1073
|
-
// built pack is already measured inside `createOperatingAdvisorPack`, but the
|
|
1074
|
-
// session may instead be RECOVERED from disk (`recoverableSession`) — possibly
|
|
1075
|
-
// written before pack-budget enforcement existed. Re-measure every pack that
|
|
1076
|
-
// will back this session against its role's published v1.2 `maxInputBytes`, so
|
|
1077
|
-
// an oversized pack can never reach the adapter from either source instead of
|
|
1078
|
-
// silently constructing an oversized session. (Mission packets are measured
|
|
1079
|
-
// against their derived mission budget inside `buildOperatingMissionPackets`.)
|
|
1080
|
-
const packInputBudgets = new Map(protocol.listOperatingRoles().map((role) => {
|
|
1081
|
-
const maxInputBytes = role.budgets
|
|
1082
|
-
?.maxInputBytes;
|
|
1083
|
-
return [role.id, typeof maxInputBytes === 'number' ? maxInputBytes : 262_144];
|
|
1084
|
-
}));
|
|
1085
|
-
for (const [roleId, pack] of Object.entries(rolePacks)) {
|
|
1086
|
-
assertOperatingAdvisorPackWithinBudget(pack, packInputBudgets.get(roleId) ?? 262_144);
|
|
1087
|
-
}
|
|
1088
|
-
// A mission role's brief facet is the same registry-derived brief pack mode
|
|
1089
|
-
// uses, so the record path's brief-bound machinery (output limits, contract
|
|
1090
|
-
// details, output-vs-brief checks) applies uniformly across both modes.
|
|
1091
|
-
const roleBriefs = {
|
|
1092
|
-
...Object.fromEntries(Object.entries(rolePacks).map(([role, pack]) => [role, pack.roleBrief])),
|
|
1093
|
-
...Object.fromEntries(Object.keys(roleMissionPackets).map((role) => [
|
|
1094
|
-
role,
|
|
1095
|
-
protocol.createOperatingAdvisorBrief(role),
|
|
1096
|
-
])),
|
|
1097
|
-
};
|
|
1098
|
-
// A role's committed input digest is its pack's `inputDigest` (pack mode) or
|
|
1099
|
-
// its mission packet's `packetDigest` (mission mode). The record path binds a
|
|
1100
|
-
// recorded result to exactly this value, and a recovered machine-local result
|
|
1101
|
-
// is re-adopted only when it still matches it.
|
|
1102
|
-
const roleInputDigests = Object.fromEntries(dispatchedRoles.map((role) => {
|
|
1103
|
-
const digest = roleMissionPackets[role]?.packetDigest ?? rolePacks[role]?.inputDigest;
|
|
1104
|
-
return [role, digest];
|
|
1105
|
-
}));
|
|
855
|
+
// FR1/FR2: every requested role — the Chair included — dispatches an operating
|
|
856
|
+
// MANDATE. The role-dependent pack/mission split is retired, so there is no
|
|
857
|
+
// pre-dispatch readiness gate against collected evidence and no v1.2 role pack
|
|
858
|
+
// is built. A mandate carries the lens question, the investigation mandate, the
|
|
859
|
+
// declared read boundaries (the granted workspace roots — including a
|
|
860
|
+
// gitignored `.planr/` tree — the registry sensitivity ceiling, and any
|
|
861
|
+
// forbidden paths), the response schema, and the citation requirement. It
|
|
862
|
+
// carries NO evidence body and NO evidence index: nothing is collected,
|
|
863
|
+
// budgeted, packed, or pre-filtered. A response that grounds nothing is
|
|
864
|
+
// post-gated at record time by the universal citation gate
|
|
865
|
+
// (`gateRecordedProposalCitations`). A recovered dead-but-exact session reuses
|
|
866
|
+
// its stored mandates verbatim.
|
|
867
|
+
const roleMandates = recoverableSession?.roleMandates && Object.keys(recoverableSession.roleMandates).length > 0
|
|
868
|
+
? recoverableSession.roleMandates
|
|
869
|
+
: await (async () => {
|
|
870
|
+
const roots = await deriveOperatingMandateRoots(input.projectRoot);
|
|
871
|
+
const built = await Promise.all(roles.map(async (roleId) => [roleId, await buildOperatingMandate({ roleId, roots })]));
|
|
872
|
+
return Object.fromEntries(built);
|
|
873
|
+
})();
|
|
874
|
+
// Every requested role dispatches; the bound role set is exactly `roles`.
|
|
875
|
+
const dispatchedRoles = roles;
|
|
876
|
+
const roleBriefs = Object.fromEntries(Object.keys(roleMandates).map((role) => [role, protocol.createOperatingAdvisorBrief(role)]));
|
|
877
|
+
// A role's committed input digest is its mandate digest. The record path
|
|
878
|
+
// binds a recorded result to exactly this value.
|
|
879
|
+
const roleInputDigests = Object.fromEntries(dispatchedRoles.map((role) => [role, roleMandates[role].mandateDigest]));
|
|
1106
880
|
const validRecordedRoles = [];
|
|
1107
881
|
for (const role of dispatchedRoles) {
|
|
1108
882
|
const prior = await readFile(path.join(path.dirname(target), `${input.cycleId}.${role}.json`), 'utf8')
|
|
@@ -1137,27 +911,13 @@ export async function operateAdapterLifecycle(input) {
|
|
|
1137
911
|
roles: dispatchedRoles,
|
|
1138
912
|
recordedRoles: validRecordedRoles.sort(),
|
|
1139
913
|
roleBriefs,
|
|
1140
|
-
|
|
1141
|
-
// Omitted entirely for a pure pack-mode session so its on-disk shape stays
|
|
1142
|
-
// byte-compatible with the v1.2 path.
|
|
1143
|
-
...(Object.keys(roleMissionPackets).length > 0 ? { roleMissionPackets } : {}),
|
|
914
|
+
roleMandates,
|
|
1144
915
|
roleInputDigests,
|
|
1145
916
|
};
|
|
1146
917
|
await atomicPrivateWrite(target, session);
|
|
1147
918
|
return {
|
|
1148
919
|
...session,
|
|
1149
|
-
|
|
1150
|
-
// record action's `dispatch.missionPacketPointer` resolves against the
|
|
1151
|
-
// prepare result; pack roles keep resolving at `/data/rolePacks/<role>`.
|
|
1152
|
-
...(Object.keys(roleMissionPackets).length > 0 ? { missionPackets: roleMissionPackets } : {}),
|
|
1153
|
-
// FR2: surface the governed gaps for any starved mission role that was
|
|
1154
|
-
// dropped from dispatch. Omitted when nothing was starved so a healthy
|
|
1155
|
-
// prepare result stays byte-identical.
|
|
1156
|
-
...(evidenceGaps.length > 0 ? { evidenceGaps } : {}),
|
|
1157
|
-
// FR4: surface the evidence-index truncation warnings for any over-cap
|
|
1158
|
-
// mission role so the drop reaches the cycle, never a silent drop. Omitted
|
|
1159
|
-
// when nothing was truncated so a healthy prepare result stays byte-identical.
|
|
1160
|
-
...(missionEvidenceTruncations.length > 0 ? { missionEvidenceTruncations } : {}),
|
|
920
|
+
mandates: roleMandates,
|
|
1161
921
|
leaseStatus: adapterLeaseStatus(session, nowMs),
|
|
1162
922
|
handoff: await adapterHandoff(session),
|
|
1163
923
|
};
|
|
@@ -1240,9 +1000,12 @@ export async function operateAdapterLifecycle(input) {
|
|
|
1240
1000
|
}
|
|
1241
1001
|
const submittedRecord = submitted && typeof submitted === 'object' ? submitted : {};
|
|
1242
1002
|
let result;
|
|
1243
|
-
// FR1: unresolvable-citation gaps opened while
|
|
1244
|
-
// citations, surfaced in the record response
|
|
1003
|
+
// FR1/FR2: unresolvable-citation and empty-grounding gaps opened while
|
|
1004
|
+
// resolving a mandate response's citations, surfaced in the record response
|
|
1005
|
+
// for observability. `recordNotEvaluated` marks a role whose citations
|
|
1006
|
+
// grounded zero evidence (committed as a quiet result plus the governed gap).
|
|
1245
1007
|
let recordGaps = [];
|
|
1008
|
+
let recordNotEvaluated = false;
|
|
1246
1009
|
if (submittedRecord.kind === 'operating-role-result') {
|
|
1247
1010
|
throw new OperateError('E_OPERATE_ADVISOR_FAILED', 'Native advisors must submit only the compact response contract; OpenPlanr owns canonical result metadata.', {
|
|
1248
1011
|
...advisorResponseContractDetails(session.roleBriefs[input.role]),
|
|
@@ -1271,50 +1034,38 @@ export async function operateAdapterLifecycle(input) {
|
|
|
1271
1034
|
throw new OperateError('E_OPERATE_SECRET_DETECTED', `Native advisor response contains a secret at ${field.location}; nothing was persisted.`, { roleId: input.role, field: field.location });
|
|
1272
1035
|
}
|
|
1273
1036
|
}
|
|
1274
|
-
const
|
|
1037
|
+
const mandate = session.roleMandates[input.role];
|
|
1275
1038
|
try {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1039
|
+
const gated = await createNativeMissionOperatingRoleResult({
|
|
1040
|
+
mandate,
|
|
1041
|
+
cycleId: input.cycleId,
|
|
1042
|
+
response: submitted,
|
|
1043
|
+
runtime: session.runtime,
|
|
1044
|
+
resolveCitations: async (roleResults) => {
|
|
1045
|
+
const [workspace, sensitivityCeiling, config] = await Promise.all([
|
|
1046
|
+
refreshOperatingWorkspaceManifest(input.projectRoot, {
|
|
1047
|
+
localRoot: input.localRoot,
|
|
1048
|
+
}),
|
|
1049
|
+
readAdapterSensitivityCeiling(input.projectRoot, input.localRoot),
|
|
1050
|
+
readOperatingConfig(input.projectRoot, { localRoot: input.localRoot }).catch(() => null),
|
|
1051
|
+
]);
|
|
1052
|
+
return gateRecordedProposalCitations({
|
|
1053
|
+
roleResults,
|
|
1054
|
+
context: {
|
|
1055
|
+
projectRoot: input.projectRoot,
|
|
1056
|
+
cycleId: input.cycleId,
|
|
1057
|
+
descriptor: workspace.controlRepository,
|
|
1058
|
+
cache: new OperatingEvidenceCache(resolveOperatingPaths(input.projectRoot, {
|
|
1289
1059
|
localRoot: input.localRoot,
|
|
1290
|
-
}),
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
descriptor: workspace.controlRepository,
|
|
1300
|
-
cache: new OperatingEvidenceCache(resolveOperatingPaths(input.projectRoot, {
|
|
1301
|
-
localRoot: input.localRoot,
|
|
1302
|
-
}).evidence, sensitivityCeiling),
|
|
1303
|
-
owner: config?.decisionOwner,
|
|
1304
|
-
},
|
|
1305
|
-
});
|
|
1306
|
-
},
|
|
1307
|
-
});
|
|
1308
|
-
result = gated.result;
|
|
1309
|
-
recordGaps = gated.gaps;
|
|
1310
|
-
}
|
|
1311
|
-
else {
|
|
1312
|
-
result = await createNativeOperatingRoleResult({
|
|
1313
|
-
pack: session.rolePacks[input.role],
|
|
1314
|
-
response: submitted,
|
|
1315
|
-
runtime: session.runtime,
|
|
1316
|
-
});
|
|
1317
|
-
}
|
|
1060
|
+
}).evidence, sensitivityCeiling),
|
|
1061
|
+
owner: config?.decisionOwner,
|
|
1062
|
+
},
|
|
1063
|
+
});
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
result = gated.result;
|
|
1067
|
+
recordGaps = gated.gaps;
|
|
1068
|
+
recordNotEvaluated = gated.notEvaluated;
|
|
1318
1069
|
}
|
|
1319
1070
|
catch (error) {
|
|
1320
1071
|
if (error instanceof OperateError && error.code === 'E_OPERATE_ADVISOR_FAILED') {
|
|
@@ -1391,6 +1142,7 @@ export async function operateAdapterLifecycle(input) {
|
|
|
1391
1142
|
recorded: input.role,
|
|
1392
1143
|
result,
|
|
1393
1144
|
...(recordGaps.length > 0 ? { citationGaps: recordGaps } : {}),
|
|
1145
|
+
...(recordNotEvaluated ? { notEvaluated: true } : {}),
|
|
1394
1146
|
session: adapterSessionSummary(updated),
|
|
1395
1147
|
leaseStatus: adapterLeaseStatus(updated, nowMs),
|
|
1396
1148
|
handoff: await adapterHandoff(updated),
|