openplanr 1.18.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.
Files changed (153) hide show
  1. package/dist/cli/commands/backlog.d.ts.map +1 -1
  2. package/dist/cli/commands/backlog.js +3 -0
  3. package/dist/cli/commands/backlog.js.map +1 -1
  4. package/dist/cli/commands/config.d.ts.map +1 -1
  5. package/dist/cli/commands/config.js +3 -0
  6. package/dist/cli/commands/config.js.map +1 -1
  7. package/dist/cli/commands/epic.d.ts.map +1 -1
  8. package/dist/cli/commands/epic.js +2 -0
  9. package/dist/cli/commands/epic.js.map +1 -1
  10. package/dist/cli/commands/feature.d.ts.map +1 -1
  11. package/dist/cli/commands/feature.js +2 -0
  12. package/dist/cli/commands/feature.js.map +1 -1
  13. package/dist/cli/commands/operate.d.ts +5 -2
  14. package/dist/cli/commands/operate.d.ts.map +1 -1
  15. package/dist/cli/commands/operate.js +10 -101
  16. package/dist/cli/commands/operate.js.map +1 -1
  17. package/dist/cli/commands/plan.d.ts.map +1 -1
  18. package/dist/cli/commands/plan.js +2 -0
  19. package/dist/cli/commands/plan.js.map +1 -1
  20. package/dist/cli/commands/quick.d.ts.map +1 -1
  21. package/dist/cli/commands/quick.js +2 -0
  22. package/dist/cli/commands/quick.js.map +1 -1
  23. package/dist/cli/commands/refine.d.ts.map +1 -1
  24. package/dist/cli/commands/refine.js +2 -0
  25. package/dist/cli/commands/refine.js.map +1 -1
  26. package/dist/cli/commands/revise.d.ts.map +1 -1
  27. package/dist/cli/commands/revise.js +2 -0
  28. package/dist/cli/commands/revise.js.map +1 -1
  29. package/dist/cli/commands/spec.d.ts.map +1 -1
  30. package/dist/cli/commands/spec.js +2 -0
  31. package/dist/cli/commands/spec.js.map +1 -1
  32. package/dist/cli/commands/sprint.d.ts.map +1 -1
  33. package/dist/cli/commands/sprint.js +2 -0
  34. package/dist/cli/commands/sprint.js.map +1 -1
  35. package/dist/cli/commands/story.d.ts.map +1 -1
  36. package/dist/cli/commands/story.js +3 -0
  37. package/dist/cli/commands/story.js.map +1 -1
  38. package/dist/cli/commands/task.d.ts.map +1 -1
  39. package/dist/cli/commands/task.js +3 -0
  40. package/dist/cli/commands/task.js.map +1 -1
  41. package/dist/services/ai-service.d.ts +3 -5
  42. package/dist/services/ai-service.d.ts.map +1 -1
  43. package/dist/services/ai-service.js +7 -1
  44. package/dist/services/ai-service.js.map +1 -1
  45. package/dist/services/credentials-service.d.ts.map +1 -1
  46. package/dist/services/credentials-service.js +2 -0
  47. package/dist/services/credentials-service.js.map +1 -1
  48. package/dist/services/deprecation-notices.d.ts +5 -0
  49. package/dist/services/deprecation-notices.d.ts.map +1 -0
  50. package/dist/services/deprecation-notices.js +6 -0
  51. package/dist/services/deprecation-notices.js.map +1 -0
  52. package/dist/services/operate/advisors.d.ts +146 -108
  53. package/dist/services/operate/advisors.d.ts.map +1 -1
  54. package/dist/services/operate/advisors.js +178 -448
  55. package/dist/services/operate/advisors.js.map +1 -1
  56. package/dist/services/operate/citation-resolution.d.ts.map +1 -1
  57. package/dist/services/operate/citation-resolution.js +50 -1
  58. package/dist/services/operate/citation-resolution.js.map +1 -1
  59. package/dist/services/operate/config.d.ts +28 -52
  60. package/dist/services/operate/config.d.ts.map +1 -1
  61. package/dist/services/operate/config.js +68 -203
  62. package/dist/services/operate/config.js.map +1 -1
  63. package/dist/services/operate/doctor.d.ts +15 -0
  64. package/dist/services/operate/doctor.d.ts.map +1 -1
  65. package/dist/services/operate/doctor.js +142 -32
  66. package/dist/services/operate/doctor.js.map +1 -1
  67. package/dist/services/operate/engine.d.ts +22 -62
  68. package/dist/services/operate/engine.d.ts.map +1 -1
  69. package/dist/services/operate/engine.js +269 -191
  70. package/dist/services/operate/engine.js.map +1 -1
  71. package/dist/services/operate/evidence.d.ts +9 -78
  72. package/dist/services/operate/evidence.d.ts.map +1 -1
  73. package/dist/services/operate/evidence.js +0 -1194
  74. package/dist/services/operate/evidence.js.map +1 -1
  75. package/dist/services/operate/index.d.ts.map +1 -1
  76. package/dist/services/operate/index.js +15 -231
  77. package/dist/services/operate/index.js.map +1 -1
  78. package/dist/services/operate/integrity.d.ts +81 -0
  79. package/dist/services/operate/integrity.d.ts.map +1 -0
  80. package/dist/services/operate/integrity.js +152 -0
  81. package/dist/services/operate/integrity.js.map +1 -0
  82. package/dist/services/operate/interaction/answer-service.d.ts +0 -10
  83. package/dist/services/operate/interaction/answer-service.d.ts.map +1 -1
  84. package/dist/services/operate/interaction/answer-service.js +63 -43
  85. package/dist/services/operate/interaction/answer-service.js.map +1 -1
  86. package/dist/services/operate/interaction/question-engine.d.ts.map +1 -1
  87. package/dist/services/operate/interaction/question-engine.js +105 -21
  88. package/dist/services/operate/interaction/question-engine.js.map +1 -1
  89. package/dist/services/operate/interaction/question-registry.d.ts +16 -1
  90. package/dist/services/operate/interaction/question-registry.d.ts.map +1 -1
  91. package/dist/services/operate/interaction/question-registry.js +45 -39
  92. package/dist/services/operate/interaction/question-registry.js.map +1 -1
  93. package/dist/services/operate/interaction/session-service.d.ts.map +1 -1
  94. package/dist/services/operate/interaction/session-service.js +16 -11
  95. package/dist/services/operate/interaction/session-service.js.map +1 -1
  96. package/dist/services/operate/interaction/terminal-renderer.d.ts.map +1 -1
  97. package/dist/services/operate/interaction/terminal-renderer.js +16 -6
  98. package/dist/services/operate/interaction/terminal-renderer.js.map +1 -1
  99. package/dist/services/operate/lifecycle.d.ts.map +1 -1
  100. package/dist/services/operate/lifecycle.js +17 -1
  101. package/dist/services/operate/lifecycle.js.map +1 -1
  102. package/dist/services/operate/maintenance.d.ts.map +1 -1
  103. package/dist/services/operate/maintenance.js +95 -276
  104. package/dist/services/operate/maintenance.js.map +1 -1
  105. package/dist/services/operate/mission-dispatch.d.ts +73 -55
  106. package/dist/services/operate/mission-dispatch.d.ts.map +1 -1
  107. package/dist/services/operate/mission-dispatch.js +91 -67
  108. package/dist/services/operate/mission-dispatch.js.map +1 -1
  109. package/dist/services/operate/projection-persistence.d.ts +35 -7
  110. package/dist/services/operate/projection-persistence.d.ts.map +1 -1
  111. package/dist/services/operate/projection-persistence.js +142 -53
  112. package/dist/services/operate/projection-persistence.js.map +1 -1
  113. package/dist/services/operate/projection.d.ts +9 -1
  114. package/dist/services/operate/projection.d.ts.map +1 -1
  115. package/dist/services/operate/projection.js +22 -12
  116. package/dist/services/operate/projection.js.map +1 -1
  117. package/dist/services/operate/protocol.d.ts +1 -41
  118. package/dist/services/operate/protocol.d.ts.map +1 -1
  119. package/dist/services/operate/protocol.js +7 -45
  120. package/dist/services/operate/protocol.js.map +1 -1
  121. package/dist/services/operate/redaction.d.ts +3 -3
  122. package/dist/services/operate/redaction.js +4 -4
  123. package/dist/services/operate/redaction.js.map +1 -1
  124. package/dist/services/operate/reports.d.ts +20 -1
  125. package/dist/services/operate/reports.d.ts.map +1 -1
  126. package/dist/services/operate/reports.js +108 -4
  127. package/dist/services/operate/reports.js.map +1 -1
  128. package/dist/services/operate/routes.d.ts +84 -0
  129. package/dist/services/operate/routes.d.ts.map +1 -1
  130. package/dist/services/operate/routes.js +519 -10
  131. package/dist/services/operate/routes.js.map +1 -1
  132. package/dist/services/operate/types.d.ts +48 -133
  133. package/dist/services/operate/types.d.ts.map +1 -1
  134. package/dist/services/operate/types.js.map +1 -1
  135. package/dist/services/operate/workspace.d.ts +0 -1
  136. package/dist/services/operate/workspace.d.ts.map +1 -1
  137. package/dist/services/operate/workspace.js +0 -2
  138. package/dist/services/operate/workspace.js.map +1 -1
  139. package/dist/services/runtime-manager-service.js +2 -2
  140. package/dist/services/runtime-manager-service.js.map +1 -1
  141. package/package.json +3 -3
  142. package/dist/services/operate/evidence-classifications.d.ts +0 -59
  143. package/dist/services/operate/evidence-classifications.d.ts.map +0 -1
  144. package/dist/services/operate/evidence-classifications.js +0 -132
  145. package/dist/services/operate/evidence-classifications.js.map +0 -1
  146. package/dist/services/operate/evidence-diagnostics.d.ts +0 -23
  147. package/dist/services/operate/evidence-diagnostics.d.ts.map +0 -1
  148. package/dist/services/operate/evidence-diagnostics.js +0 -152
  149. package/dist/services/operate/evidence-diagnostics.js.map +0 -1
  150. package/dist/services/operate/evidence-readiness.d.ts +0 -8
  151. package/dist/services/operate/evidence-readiness.d.ts.map +0 -1
  152. package/dist/services/operate/evidence-readiness.js +0 -84
  153. package/dist/services/operate/evidence-readiness.js.map +0 -1
@@ -1,34 +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, assertOperatingAdvisorPackWithinBudget, buildAdvisorOperatingContext, createNativeMissionOperatingRoleResult, createNativeOperatingRoleResult, createOperatingAdvisorPack, } from './advisors.js';
4
+ import { advisorResponseContractDetails, assertAdvisorOutputMatchesBrief, buildOperatingMandate, createNativeMissionOperatingRoleResult, } from './advisors.js';
5
5
  import { canonicalDigest, canonicalize, sha256Digest } from './canonical.js';
6
- import { operatingProjectKey, readOperatingAdapterLeaseDurationMs, readOperatingDispatchModeOverrides, } from './config.js';
7
- import { buildChairEvidence, buildOperatingMissionPackets, gateRecordedProposalCitations, } from './engine.js';
6
+ import { operatingProjectKey, readOperatingAdapterLeaseDurationMs } from './config.js';
7
+ import { gateRecordedProposalCitations } from './engine.js';
8
8
  import { OperatingEventStore } from './event-store.js';
9
9
  import { OperatingEvidenceCache } from './evidence-cache.js';
10
- import { purgeStaleEvidenceClassifications } from './evidence-classifications.js';
11
- import { listEvidenceDiagnostics } from './evidence-diagnostics.js';
12
- import { evaluateEvidenceReadiness } from './evidence-readiness.js';
13
10
  import { guidedSessionStatus, purgeGuidedSessions } from './interaction/session-service.js';
14
11
  import { assertCommittedOperatingView, recoverOperatingTransactions } from './journal.js';
15
12
  import { withOperatingLock } from './lock-service.js';
16
- import { narrowMissionRootsToCeiling, operatingRegistryDispatchMode, operatingRuntimeEnforcesBoundedReadOnly, resolveOperatingDispatchMode, } from './mission-dispatch.js';
17
13
  import { assertOperatingArtifact, loadOperatingProtocol } from './protocol.js';
18
14
  import { containsSecret, redactSensitiveText } from './redaction.js';
19
15
  import { OPERATE_PROTOCOL_VERSION, OPERATE_SCHEMA_VERSION, OperateError, } from './types.js';
20
16
  import { readOperatingConfig, refreshOperatingWorkspaceManifest, resolveContainedPath, resolveOperatingPaths, } from './workspace.js';
21
- /**
22
- * Whether any bound role of this session resolved to native mission dispatch, so
23
- * both adapter-handoff call sites can pass `protocolVersion: '1.3.0'` and the
24
- * pipeline emits the v1.3 mission record action (`dispatch.agent` +
25
- * `dispatch.missionPacketPointer`) instead of the v1.2 `rolePackPointer`.
26
- */
27
- function sessionHasMissionRole(session) {
28
- return Boolean(session.roleMissionPackets && Object.keys(session.roleMissionPackets).length > 0);
29
- }
30
17
  function adapterSessionSummary(session) {
31
- const { roleBriefs: _roleBriefs, rolePacks: _rolePacks, roleMissionPackets: _roleMissionPackets, ...summary } = session;
18
+ const { roleBriefs: _roleBriefs, roleMandates: _roleMandates, ...summary } = session;
32
19
  return summary;
33
20
  }
34
21
  async function adapterHandoff(session) {
@@ -42,10 +29,7 @@ async function adapterHandoff(session) {
42
29
  : 'finalize-required';
43
30
  const protocol = await loadOperatingProtocol();
44
31
  const handoff = protocol.createOperatingAdapterHandoff({
45
- // v1.3 when any bound role resolved mission, so the record action names the
46
- // generated lens agent and points at its mission packet; v1.2 (pack) default
47
- // otherwise, byte-compatible with the pack-mode handoff.
48
- ...(sessionHasMissionRole(session) ? { protocolVersion: '1.3.0' } : {}),
32
+ protocolVersion: '1.3.0',
49
33
  phase: session.phase,
50
34
  state,
51
35
  cycleId: session.cycleId,
@@ -186,25 +170,12 @@ export async function operatingCacheAction(input) {
186
170
  .catch(() => ({ sensitivityCeiling: 'internal' }));
187
171
  const cache = new OperatingEvidenceCache(paths.evidence, preferences.sensitivityCeiling ?? 'internal');
188
172
  if (input.action === 'status') {
189
- const diagnostics = await listEvidenceDiagnostics({
190
- projectRoot: input.projectRoot,
191
- localRoot: input.localRoot,
192
- });
193
- const classifications = await purgeStaleEvidenceClassifications({
194
- projectRoot: input.projectRoot,
195
- localRoot: input.localRoot,
196
- });
197
173
  return {
198
174
  evidence: await cache.status(),
199
175
  sessions: await guidedSessionStatus({
200
176
  projectRoot: input.projectRoot,
201
177
  localRoot: input.localRoot,
202
178
  }),
203
- diagnostics: {
204
- candidates: diagnostics.length,
205
- classified: diagnostics.filter((entry) => entry.classification).length,
206
- staleClassifications: classifications.stale,
207
- },
208
179
  };
209
180
  }
210
181
  if (!input.confirmed) {
@@ -215,11 +186,6 @@ export async function operatingCacheAction(input) {
215
186
  projectRoot: input.projectRoot,
216
187
  localRoot: input.localRoot,
217
188
  });
218
- const classifications = await purgeStaleEvidenceClassifications({
219
- projectRoot: input.projectRoot,
220
- localRoot: input.localRoot,
221
- purge: true,
222
- });
223
189
  // FR4/FR11: clearing machine-local caches also drops board-bound adapter
224
190
  // sessions and incremental evidence baselines, so this is the scoped fix the
225
191
  // doctor names for stale adapter sessions and stale incremental baselines.
@@ -230,12 +196,10 @@ export async function operatingCacheAction(input) {
230
196
  return {
231
197
  removed: removed.length +
232
198
  sessions.removed +
233
- classifications.purged +
234
199
  board.removedAdvisorSessions +
235
200
  board.removedIncrementalBaselines,
236
201
  evidence: { removed: removed.length, entries: removed },
237
202
  sessions,
238
- classifications,
239
203
  adapterSessions: { removed: board.removedAdvisorSessions },
240
204
  incrementalBaselines: { removed: board.removedIncrementalBaselines },
241
205
  };
@@ -700,68 +664,31 @@ async function readAdapterSensitivityCeiling(projectRoot, localRoot) {
700
664
  'internal')
701
665
  .catch(() => 'internal');
702
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._/-]*$/;
703
670
  /**
704
- * FR2: deny-list every declared read root that contains an above-ceiling evidence
705
- * item BEFORE the mission packet is built, so no above-ceiling file is reachable
706
- * even inside a granted root. Uses `mission-dispatch.ts`'s
707
- * `narrowMissionRootsToCeiling` over the FULL (pre-ceiling-filter) evidence, then
708
- * drops the items in the denied roots. `buildOperatingMissionPackets` then derives
709
- * the packet's `declaredRoots` (and thus its tool grant) from only the narrowed
710
- * roots. This is the belt to the bounded reader's read-time-ceiling suspenders.
711
- */
712
- function narrowEvidenceToMissionCeiling(evidence, ceiling) {
713
- const topSegment = (location) => {
714
- const [top] = location.split('/');
715
- return top && top.length > 0 ? top : null;
716
- };
717
- const declaredRoots = [
718
- ...new Set(evidence.items
719
- .map((item) => topSegment(item.location))
720
- .filter((segment) => Boolean(segment))),
721
- ];
722
- const evidenceIndex = evidence.items.map((item) => ({
723
- path: item.location,
724
- sensitivity: item.sensitivity,
725
- }));
726
- const allowedRoots = new Set(narrowMissionRootsToCeiling({ declaredRoots, evidenceIndex, ceiling }));
727
- return {
728
- ...evidence,
729
- items: evidence.items.filter((item) => {
730
- const top = topSegment(item.location);
731
- return top === null || allowedRoots.has(top);
732
- }),
733
- };
734
- }
735
- /**
736
- * Resolve every requested role's effective dispatch mode for THIS runtime (FR1):
737
- * the v1.3 registry default, overridden by the machine-local
738
- * `dispatchModeOverrides`, reconciled against whether the runtime natively
739
- * enforces the bounded read-only boundary. The adapter lifecycle IS the native
740
- * runtime path, so the hosting adapter is native-capable by construction; the
741
- * runtime's own enforceability then decides whether a role receives a native
742
- * bounded mission lens (`resolution.native`) or falls closed to the v1.2 pack.
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.
743
679
  */
744
- async function resolveBoundRoleDispatchModes(input) {
745
- const protocol = await loadOperatingProtocol();
746
- const registryDefaults = new Map(protocol.listOperatingRoles().map((role) => [
747
- role.id,
748
- operatingRegistryDispatchMode(role),
749
- ]));
750
- const overrides = await readOperatingDispatchModeOverrides(input.projectRoot, {
751
- localRoot: input.localRoot,
752
- });
753
- const runtimeEnforcesBoundedReadOnly = await operatingRuntimeEnforcesBoundedReadOnly(input.runtime);
754
- const resolved = new Map();
755
- for (const roleId of input.roles) {
756
- resolved.set(roleId, resolveOperatingDispatchMode({
757
- roleId: roleId,
758
- registryDefault: registryDefaults.get(roleId) ?? 'mission',
759
- override: overrides[roleId],
760
- runtimeEnforcesBoundedReadOnly,
761
- adapterNativeCapable: true,
762
- }));
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);
763
690
  }
764
- return resolved;
691
+ return [...roots].sort();
765
692
  }
766
693
  export async function createOperatingAdapterStartHandoff(input) {
767
694
  const target = adapterSessionPath(input.projectRoot, input.cycleId, input.localRoot);
@@ -788,19 +715,8 @@ export async function createOperatingAdapterStartHandoff(input) {
788
715
  }
789
716
  }
790
717
  const protocol = await loadOperatingProtocol();
791
- // FR1 call site 2 (the start / prepare-required handoff): stamp v1.3 whenever
792
- // any bound role would resolve to native mission dispatch on this runtime, so
793
- // the whole lifecycle — including the record actions built after prepare —
794
- // carries the mission protocol version rather than defaulting to pack.
795
- const dispatchModes = await resolveBoundRoleDispatchModes({
796
- projectRoot: input.projectRoot,
797
- localRoot: input.localRoot,
798
- runtime: input.runtime,
799
- roles: input.roles,
800
- });
801
- const anyMission = [...dispatchModes.values()].some((resolution) => resolution.native);
802
718
  const handoff = protocol.createOperatingAdapterHandoff({
803
- ...(anyMission ? { protocolVersion: '1.3.0' } : {}),
719
+ protocolVersion: '1.3.0',
804
720
  phase: input.phase,
805
721
  state: 'prepare-required',
806
722
  cycleId: input.cycleId,
@@ -929,123 +845,40 @@ export async function operateAdapterLifecycle(input) {
929
845
  // FR4: a finalized/expired/cancelled same-board session no longer forces
930
846
  // a whole-cycle-cancelling error on a new compatible prepare. The
931
847
  // advisors→chair continuation and a same-phase re-prepare both fall
932
- // through and supersede the dead session; an exact dead session still
933
- // recovers its already-built packs so recorded advisory work is reused
934
- // 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.
935
850
  if (exact && (sessionExpired(normalized) || normalized.state === 'cancelled')) {
936
851
  recoverableSession = normalized;
937
852
  }
938
853
  }
939
854
  }
940
- const roleEvidence = roles[0] === 'chair'
941
- ? buildChairEvidence(baseEvidence, await readPersistedOperatingRoleResults(store, input.cycleId), new Date().toISOString())
942
- : baseEvidence;
943
- const readiness = await evaluateEvidenceReadiness({
944
- cycleId: input.cycleId,
945
- evidence: roleEvidence,
946
- enabledRoles: roles,
947
- now: new Date(),
948
- });
949
- const unready = readiness.roles.filter((role) => !role.modelCallAllowed);
950
- if (unready.length > 0) {
951
- throw new OperateError('E_OPERATE_EVIDENCE_NOT_READY', `Native advisor preparation is blocked for: ${unready
952
- .map((role) => role.roleId)
953
- .sort()
954
- .join(', ')}.`);
955
- }
956
- const context = await buildAdvisorOperatingContext({
957
- charterPath: resolveOperatingPaths(input.projectRoot, {
958
- localRoot: input.localRoot,
959
- }).charter,
960
- state,
961
- cycleId: input.cycleId,
962
- });
963
- // FR1: resolve each requested role's dispatch mode for THIS runtime. A role
964
- // that resolves to a native bounded lens (`resolution.native`) gets a
965
- // body-free v1.3 mission packet; every other role keeps the v1.2 role pack.
966
- // A recovered dead-but-exact session reuses its stored packs/packets verbatim.
967
- const dispatchModes = await resolveBoundRoleDispatchModes({
968
- projectRoot: input.projectRoot,
969
- localRoot: input.localRoot,
970
- runtime,
971
- roles,
972
- });
973
- const missionRoleIds = readiness.roles
974
- .map((role) => role.roleId)
975
- .filter((roleId) => dispatchModes.get(roleId)?.native);
976
- const packReadinessRoles = readiness.roles.filter((role) => !dispatchModes.get(role.roleId)?.native);
977
- const rolePacks = recoverableSession?.rolePacks ??
978
- Object.fromEntries(await Promise.all(packReadinessRoles.map(async (role) => [
979
- role.roleId,
980
- await createOperatingAdvisorPack({
981
- cycleId: input.cycleId,
982
- role,
983
- evidence: roleEvidence,
984
- context,
985
- }),
986
- ])));
987
- // FR1: build the natively-dispatched roles' mission packets from live
988
- // cycle/workspace state. Only reached when a role actually resolved mission,
989
- // so a pure pack-mode prepare never loads config/workspace and stays
990
- // byte-compatible with the v1.2 path. The evidence is ceiling-narrowed by
991
- // `narrowMissionRootsToCeiling` first, so a declared read root can never reach
992
- // an above-ceiling file even before the bounded reader's read-time check.
993
- const roleMissionPackets = recoverableSession?.roleMissionPackets ??
994
- (missionRoleIds.length > 0
995
- ? await (async () => {
996
- const [config, workspace, sensitivityCeiling] = await Promise.all([
997
- readOperatingConfig(input.projectRoot, { localRoot: input.localRoot }),
998
- refreshOperatingWorkspaceManifest(input.projectRoot, { localRoot: input.localRoot }),
999
- readAdapterSensitivityCeiling(input.projectRoot, input.localRoot),
1000
- ]);
1001
- const packets = await buildOperatingMissionPackets({
1002
- cycleId: input.cycleId,
1003
- config,
1004
- workspace,
1005
- context,
1006
- evidence: narrowEvidenceToMissionCeiling(roleEvidence, sensitivityCeiling),
1007
- roleIds: missionRoleIds,
1008
- sensitivityCeiling,
1009
- });
1010
- return Object.fromEntries(packets.map((packet) => [packet.roleId, packet]));
1011
- })()
1012
- : {});
1013
- // FR2: fail closed before a native adapter session is assembled. A freshly
1014
- // built pack is already measured inside `createOperatingAdvisorPack`, but the
1015
- // session may instead be RECOVERED from disk (`recoverableSession`) — possibly
1016
- // written before pack-budget enforcement existed. Re-measure every pack that
1017
- // will back this session against its role's published v1.2 `maxInputBytes`, so
1018
- // an oversized pack can never reach the adapter from either source instead of
1019
- // silently constructing an oversized session. (Mission packets are measured
1020
- // against their derived mission budget inside `buildOperatingMissionPackets`.)
1021
- const packInputBudgets = new Map(protocol.listOperatingRoles().map((role) => {
1022
- const maxInputBytes = role.budgets
1023
- ?.maxInputBytes;
1024
- return [role.id, typeof maxInputBytes === 'number' ? maxInputBytes : 262_144];
1025
- }));
1026
- for (const [roleId, pack] of Object.entries(rolePacks)) {
1027
- assertOperatingAdvisorPackWithinBudget(pack, packInputBudgets.get(roleId) ?? 262_144);
1028
- }
1029
- // A mission role's brief facet is the same registry-derived brief pack mode
1030
- // uses, so the record path's brief-bound machinery (output limits, contract
1031
- // details, output-vs-brief checks) applies uniformly across both modes.
1032
- const roleBriefs = {
1033
- ...Object.fromEntries(Object.entries(rolePacks).map(([role, pack]) => [role, pack.roleBrief])),
1034
- ...Object.fromEntries(Object.keys(roleMissionPackets).map((role) => [
1035
- role,
1036
- protocol.createOperatingAdvisorBrief(role),
1037
- ])),
1038
- };
1039
- // A role's committed input digest is its pack's `inputDigest` (pack mode) or
1040
- // its mission packet's `packetDigest` (mission mode). The record path binds a
1041
- // recorded result to exactly this value, and a recovered machine-local result
1042
- // is re-adopted only when it still matches it.
1043
- const roleInputDigests = Object.fromEntries(roles.map((role) => {
1044
- const digest = roleMissionPackets[role]?.packetDigest ?? rolePacks[role]?.inputDigest;
1045
- return [role, digest];
1046
- }));
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]));
1047
880
  const validRecordedRoles = [];
1048
- for (const role of roles) {
881
+ for (const role of dispatchedRoles) {
1049
882
  const prior = await readFile(path.join(path.dirname(target), `${input.cycleId}.${role}.json`), 'utf8')
1050
883
  .then((raw) => JSON.parse(raw))
1051
884
  .catch(() => null);
@@ -1075,22 +908,16 @@ export async function operateAdapterLifecycle(input) {
1075
908
  idempotencyKey: input.idempotencyKey,
1076
909
  state: validRecordedRoles.length > 0 ? 'recording' : 'prepared',
1077
910
  expiresAt: new Date(nowMs + leaseDurationMs).toISOString(),
1078
- roles,
911
+ roles: dispatchedRoles,
1079
912
  recordedRoles: validRecordedRoles.sort(),
1080
913
  roleBriefs,
1081
- rolePacks,
1082
- // Omitted entirely for a pure pack-mode session so its on-disk shape stays
1083
- // byte-compatible with the v1.2 path.
1084
- ...(Object.keys(roleMissionPackets).length > 0 ? { roleMissionPackets } : {}),
914
+ roleMandates,
1085
915
  roleInputDigests,
1086
916
  };
1087
917
  await atomicPrivateWrite(target, session);
1088
918
  return {
1089
919
  ...session,
1090
- // Expose the mission packets at `/data/missionPackets/<role>` so the v1.3
1091
- // record action's `dispatch.missionPacketPointer` resolves against the
1092
- // prepare result; pack roles keep resolving at `/data/rolePacks/<role>`.
1093
- ...(Object.keys(roleMissionPackets).length > 0 ? { missionPackets: roleMissionPackets } : {}),
920
+ mandates: roleMandates,
1094
921
  leaseStatus: adapterLeaseStatus(session, nowMs),
1095
922
  handoff: await adapterHandoff(session),
1096
923
  };
@@ -1173,9 +1000,12 @@ export async function operateAdapterLifecycle(input) {
1173
1000
  }
1174
1001
  const submittedRecord = submitted && typeof submitted === 'object' ? submitted : {};
1175
1002
  let result;
1176
- // FR1: unresolvable-citation gaps opened while resolving a mission response's
1177
- // citations, surfaced in the record response for observability.
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).
1178
1007
  let recordGaps = [];
1008
+ let recordNotEvaluated = false;
1179
1009
  if (submittedRecord.kind === 'operating-role-result') {
1180
1010
  throw new OperateError('E_OPERATE_ADVISOR_FAILED', 'Native advisors must submit only the compact response contract; OpenPlanr owns canonical result metadata.', {
1181
1011
  ...advisorResponseContractDetails(session.roleBriefs[input.role]),
@@ -1204,50 +1034,38 @@ export async function operateAdapterLifecycle(input) {
1204
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 });
1205
1035
  }
1206
1036
  }
1207
- const missionPacket = session.roleMissionPackets?.[input.role];
1037
+ const mandate = session.roleMandates[input.role];
1208
1038
  try {
1209
- if (missionPacket) {
1210
- // FR1: a mission-mode role submits the v1.3 citation-bearing response.
1211
- // Its citations flow into the engine's already-live citation gate,
1212
- // which resolves them against the cycle's pinned revision and mints the
1213
- // evidenceRefs that make the committed result v1.2-valid — the pack
1214
- // path is untouched.
1215
- const gated = await createNativeMissionOperatingRoleResult({
1216
- packet: missionPacket,
1217
- response: submitted,
1218
- runtime: session.runtime,
1219
- resolveCitations: async (roleResults) => {
1220
- const [workspace, sensitivityCeiling, config] = await Promise.all([
1221
- refreshOperatingWorkspaceManifest(input.projectRoot, {
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, {
1222
1059
  localRoot: input.localRoot,
1223
- }),
1224
- readAdapterSensitivityCeiling(input.projectRoot, input.localRoot),
1225
- readOperatingConfig(input.projectRoot, { localRoot: input.localRoot }).catch(() => null),
1226
- ]);
1227
- return gateRecordedProposalCitations({
1228
- roleResults,
1229
- context: {
1230
- projectRoot: input.projectRoot,
1231
- cycleId: input.cycleId,
1232
- descriptor: workspace.controlRepository,
1233
- cache: new OperatingEvidenceCache(resolveOperatingPaths(input.projectRoot, {
1234
- localRoot: input.localRoot,
1235
- }).evidence, sensitivityCeiling),
1236
- owner: config?.decisionOwner,
1237
- },
1238
- });
1239
- },
1240
- });
1241
- result = gated.result;
1242
- recordGaps = gated.gaps;
1243
- }
1244
- else {
1245
- result = await createNativeOperatingRoleResult({
1246
- pack: session.rolePacks[input.role],
1247
- response: submitted,
1248
- runtime: session.runtime,
1249
- });
1250
- }
1060
+ }).evidence, sensitivityCeiling),
1061
+ owner: config?.decisionOwner,
1062
+ },
1063
+ });
1064
+ },
1065
+ });
1066
+ result = gated.result;
1067
+ recordGaps = gated.gaps;
1068
+ recordNotEvaluated = gated.notEvaluated;
1251
1069
  }
1252
1070
  catch (error) {
1253
1071
  if (error instanceof OperateError && error.code === 'E_OPERATE_ADVISOR_FAILED') {
@@ -1324,6 +1142,7 @@ export async function operateAdapterLifecycle(input) {
1324
1142
  recorded: input.role,
1325
1143
  result,
1326
1144
  ...(recordGaps.length > 0 ? { citationGaps: recordGaps } : {}),
1145
+ ...(recordNotEvaluated ? { notEvaluated: true } : {}),
1327
1146
  session: adapterSessionSummary(updated),
1328
1147
  leaseStatus: adapterLeaseStatus(updated, nowMs),
1329
1148
  handoff: await adapterHandoff(updated),