@pareto4/pp-core 1.17.1 → 1.19.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 (199) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -3
  3. package/agents/pp-code-fixer.md +9 -16
  4. package/agents/pp-code-reviewer.md +2 -78
  5. package/agents/pp-codebase-mapper.md +3 -0
  6. package/agents/pp-debugger.md +3 -2
  7. package/agents/pp-executor.md +25 -30
  8. package/agents/pp-integration-checker.md +1 -0
  9. package/agents/pp-phase-researcher.md +3 -1
  10. package/agents/pp-plan-checker.md +3 -0
  11. package/agents/pp-planner.md +16 -12
  12. package/agents/pp-research-synthesizer.md +1 -1
  13. package/agents/pp-ui-auditor.md +1 -0
  14. package/agents/pp-ui-researcher.md +3 -1
  15. package/agents/pp-vault-reviewer.md +43 -189
  16. package/agents/pp-verifier.md +10 -8
  17. package/commands/pp-add-tests.md +1 -1
  18. package/commands/pp-debug.md +1 -0
  19. package/commands/pp-init.md +4 -3
  20. package/commands/pp-ns-manage.md +1 -1
  21. package/commands/pp-ns-review.md +1 -1
  22. package/commands/pp-quick.md +3 -0
  23. package/commands/pp-review-backlog.md +2 -1
  24. package/commands/pp-vault-promote.md +7 -8
  25. package/commands/pp-vault-review.md +6 -7
  26. package/gemini-extension.json +1 -1
  27. package/package.json +3 -6
  28. package/pp-core/bin/lib/capability-registry.cjs +15 -357
  29. package/pp-core/bin/lib/capability-state.cjs +15 -79
  30. package/pp-core/bin/lib/capability-writer.cjs +2 -16
  31. package/pp-core/bin/lib/check-command-router.cjs +342 -2298
  32. package/pp-core/bin/lib/clusters.cjs +2 -2
  33. package/pp-core/bin/lib/commands.cjs +79 -4
  34. package/pp-core/bin/lib/compose-bindings.cjs +99 -16
  35. package/pp-core/bin/lib/config.cjs +3 -6
  36. package/pp-core/bin/lib/discovery-rollup.cjs +9 -6
  37. package/pp-core/bin/lib/discovery-screens.cjs +20 -2
  38. package/pp-core/bin/lib/discovery-sweep.cjs +12 -3
  39. package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
  40. package/pp-core/bin/lib/git-base-branch.cjs +10 -13
  41. package/pp-core/bin/lib/git-provider.cjs +5 -6
  42. package/pp-core/bin/lib/init-contract.cjs +55 -42
  43. package/pp-core/bin/lib/init-interactive.cjs +6 -20
  44. package/pp-core/bin/lib/io.cjs +2 -8
  45. package/pp-core/bin/lib/loop-resolver.cjs +8 -32
  46. package/pp-core/bin/lib/org-profile.cjs +16 -92
  47. package/pp-core/bin/lib/vault-command-router.cjs +174 -1206
  48. package/pp-core/bin/lib/vault-coordinates.cjs +4 -4
  49. package/pp-core/bin/lib/vault-index.cjs +44 -74
  50. package/pp-core/bin/lib/vault-seal.cjs +37 -122
  51. package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
  52. package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
  53. package/pp-core/bin/lib/vault-standards.cjs +15 -16
  54. package/pp-core/bin/lib/vault-trace.cjs +40 -3016
  55. package/pp-core/bin/lib/vault-update.cjs +5 -26
  56. package/pp-core/bin/lib/vault.cjs +122 -316
  57. package/pp-core/bin/pp-tools.cjs +15 -36
  58. package/pp-core/org-profile.json +1 -1
  59. package/pp-core/references/autonomous-best-effort.md +4 -3
  60. package/pp-core/references/autonomous-lifecycle.md +1 -0
  61. package/pp-core/references/autonomous-smart-discuss.md +5 -1
  62. package/pp-core/references/discovery-id-conventions.md +35 -2
  63. package/pp-core/references/execute-mvp-tdd.md +3 -3
  64. package/pp-core/references/git-integration.md +57 -33
  65. package/pp-core/references/git-planning-commit.md +31 -9
  66. package/pp-core/references/phase-argument-parsing.md +1 -0
  67. package/pp-core/references/planner-gap-closure.md +1 -0
  68. package/pp-core/references/planner-revision.md +3 -1
  69. package/pp-core/references/planner-tdd.md +4 -4
  70. package/pp-core/references/planner-vault-standards.md +16 -25
  71. package/pp-core/references/planning-config.md +3 -3
  72. package/pp-core/references/shell-portability.md +28 -0
  73. package/pp-core/references/tdd.md +20 -16
  74. package/pp-core/references/worktree-path-safety.md +1 -0
  75. package/pp-core/templates/README.md +1 -1
  76. package/pp-core/templates/VAULT-REVIEW.md +24 -170
  77. package/pp-core/templates/milestone.md +5 -5
  78. package/pp-core/workflows/add-backlog.md +1 -1
  79. package/pp-core/workflows/add-tests.md +8 -5
  80. package/pp-core/workflows/add-todo.md +3 -1
  81. package/pp-core/workflows/ai-integration-phase.md +2 -1
  82. package/pp-core/workflows/audit-fix.md +7 -6
  83. package/pp-core/workflows/audit-milestone.md +1 -0
  84. package/pp-core/workflows/autonomous.md +6 -0
  85. package/pp-core/workflows/check-todos.md +2 -1
  86. package/pp-core/workflows/cleanup.md +3 -1
  87. package/pp-core/workflows/code-review-fix.md +15 -8
  88. package/pp-core/workflows/code-review.md +16 -58
  89. package/pp-core/workflows/complete-milestone.md +13 -3
  90. package/pp-core/workflows/debug.md +4 -2
  91. package/pp-core/workflows/diagnose-issues.md +1 -1
  92. package/pp-core/workflows/discovery-sprint/prototype.md +2 -2
  93. package/pp-core/workflows/discovery-sprint/session-13-gates.md +1 -1
  94. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +51 -7
  95. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +5 -3
  96. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +10 -8
  97. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +11 -2
  98. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +5 -5
  99. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +3 -3
  100. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +2 -2
  101. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +14 -2
  102. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +2 -2
  103. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +10 -10
  104. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +11 -8
  105. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
  106. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +5 -3
  107. package/pp-core/workflows/discovery-sprint/templates/context-template.md +9 -3
  108. package/pp-core/workflows/discovery-sprint/vault-context.md +17 -12
  109. package/pp-core/workflows/discovery-sprint.md +11 -8
  110. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +1 -0
  111. package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
  112. package/pp-core/workflows/discuss-phase.md +8 -3
  113. package/pp-core/workflows/docs-update.md +1 -0
  114. package/pp-core/workflows/eval-review.md +2 -1
  115. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +13 -14
  116. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
  117. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +5 -25
  118. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
  119. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +18 -6
  120. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +6 -109
  121. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +1 -17
  122. package/pp-core/workflows/execute-phase.md +21 -12
  123. package/pp-core/workflows/execute-plan.md +24 -15
  124. package/pp-core/workflows/explore.md +1 -1
  125. package/pp-core/workflows/forensics.md +4 -2
  126. package/pp-core/workflows/health.md +1 -0
  127. package/pp-core/workflows/help/modes/full.md +1 -1
  128. package/pp-core/workflows/import.md +2 -1
  129. package/pp-core/workflows/ingest-docs.md +1 -0
  130. package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
  131. package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
  132. package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
  133. package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
  134. package/pp-core/workflows/init.md +56 -66
  135. package/pp-core/workflows/map-codebase.md +5 -1
  136. package/pp-core/workflows/milestone-summary.md +2 -1
  137. package/pp-core/workflows/mvp-phase.md +1 -0
  138. package/pp-core/workflows/new-milestone.md +9 -5
  139. package/pp-core/workflows/new-project.md +9 -8
  140. package/pp-core/workflows/new-workspace.md +1 -0
  141. package/pp-core/workflows/next.md +2 -1
  142. package/pp-core/workflows/pause-work.md +6 -4
  143. package/pp-core/workflows/plan-milestone-gaps.md +3 -1
  144. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
  145. package/pp-core/workflows/plan-phase.md +21 -6
  146. package/pp-core/workflows/plan-review-convergence.md +2 -0
  147. package/pp-core/workflows/plant-seed.md +4 -2
  148. package/pp-core/workflows/pr-branch.md +2 -0
  149. package/pp-core/workflows/progress.md +5 -0
  150. package/pp-core/workflows/quick.md +19 -12
  151. package/pp-core/workflows/reapply-patches.md +3 -0
  152. package/pp-core/workflows/remove-phase.md +1 -1
  153. package/pp-core/workflows/resume-project.md +3 -0
  154. package/pp-core/workflows/review.md +7 -1
  155. package/pp-core/workflows/secure-phase.md +2 -1
  156. package/pp-core/workflows/session-report.md +1 -0
  157. package/pp-core/workflows/ship.md +8 -1
  158. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  159. package/pp-core/workflows/sketch.md +3 -2
  160. package/pp-core/workflows/spec-phase.md +2 -1
  161. package/pp-core/workflows/spike-wrap-up.md +1 -1
  162. package/pp-core/workflows/spike.md +4 -3
  163. package/pp-core/workflows/sync-skills.md +5 -2
  164. package/pp-core/workflows/thread.md +3 -2
  165. package/pp-core/workflows/transition.md +6 -0
  166. package/pp-core/workflows/ui-phase.md +3 -1
  167. package/pp-core/workflows/ui-review.md +2 -1
  168. package/pp-core/workflows/ultraplan-phase.md +1 -0
  169. package/pp-core/workflows/undo.md +14 -10
  170. package/pp-core/workflows/update.md +5 -0
  171. package/pp-core/workflows/validate-phase.md +9 -6
  172. package/pp-core/workflows/vault-review.md +65 -186
  173. package/pp-core/workflows/vault-write-back.md +78 -110
  174. package/pp-core/workflows/verify-phase.md +5 -0
  175. package/pp-core/workflows/verify-work.md +9 -2
  176. package/scripts/gen-capability-registry.cjs +12 -93
  177. package/scripts/lint-test-file-count.allowlist.json +1 -19
  178. package/scripts/lint-test-file-count.cjs +5 -6
  179. package/scripts/prompt-injection-scan.sh +1 -4
  180. package/scripts/sync-runtime-launcher.cjs +115 -5
  181. package/pp-core/bin/lib/practices-review-check.cjs +0 -944
  182. package/pp-core/bin/lib/vault-audit.cjs +0 -2594
  183. package/pp-core/bin/lib/vault-mode.cjs +0 -235
  184. package/pp-core/bin/lib/vault-practices.cjs +0 -2057
  185. package/pp-core/bin/lib/vault-review.cjs +0 -2102
  186. package/pp-core/bin/lib/vault-write-back.cjs +0 -905
  187. package/pp-core/references/planner-vault-practices.md +0 -95
  188. package/pp-core/references/practice-arithmetic-contract.md +0 -121
  189. package/pp-core/references/verifier-practice-verdicts.md +0 -263
  190. package/pp-core/templates/practices/README.md +0 -248
  191. package/pp-core/templates/practices/error-handling.md +0 -18
  192. package/pp-core/templates/practices/minimal-comments.md +0 -14
  193. package/pp-core/templates/practices/naming-convention.md +0 -18
  194. package/pp-core/templates/practices/practice-template.md +0 -23
  195. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +0 -165
  196. package/scripts/gen-practice-arithmetic-contract.cjs +0 -412
  197. package/scripts/practice-arithmetic-contract.allow.json +0 -35
  198. package/scripts/record-vault-mode-golden.cjs +0 -89
  199. package/scripts/vault-calibrate.cjs +0 -996
@@ -7,11 +7,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.INIT_SESSION_WRITE_FAILED = exports.INIT_SESSION_MALFORMED = exports.INIT_CONTRACT_FLAGS = exports.INIT_PROBE_STATUS = exports.INIT_PROBE_NOTE_KEY = exports.INIT_PROBE_WHO_KEY = exports.INIT_PROBE_WHEN_KEY = exports.INIT_PROBE_STATUS_KEY = exports.INIT_ORG_PROFILE_DECLINED_KEY = exports.INIT_COMPOSE_DECLINED_KEY = void 0;
9
9
  exports.resolveVaultEnabledState = resolveVaultEnabledState;
10
- exports.resolveVaultPracticesEnabledState = resolveVaultPracticesEnabledState;
11
10
  exports.resolveVaultRequiredState = resolveVaultRequiredState;
12
11
  exports.readVaultEnabledState = readVaultEnabledState;
13
- exports.readVaultPracticesEnabledState = readVaultPracticesEnabledState;
14
- exports.readVaultStandardsModeActive = readVaultStandardsModeActive;
15
12
  exports.readVaultRequiredState = readVaultRequiredState;
16
13
  exports.buildInitQuestions = buildInitQuestions;
17
14
  exports.deriveSlug = deriveSlug;
@@ -116,18 +113,12 @@ function getDotted(obj, dottedPath) {
116
113
  }
117
114
  /** Arms the vault capability; written by the session machine, never by an org profile. */
118
115
  const VAULT_ENABLED_CONFIG_KEY = 'vault.enabled';
119
- /** Arms vault-practices; set beside `vault.enabled` under the same absent-only rule, inert without it. */
120
- const VAULT_PRACTICES_ENABLED_CONFIG_KEY = 'vault.practices_enabled';
121
116
  /** The single reading of `vault.enabled` in this module, in the form the gate honours
122
117
  * (`resolveGateHonouredKeyState`, Phase 15 D-32). `value` is the observed value, never a
123
118
  * coercion, so reports name what is really in the file. */
124
119
  function resolveVaultEnabledState(parsed) {
125
120
  return resolveGateHonouredKeyState(parsed, VAULT_ENABLED_CONFIG_KEY);
126
121
  }
127
- /** Four-state reading of `vault.practices_enabled`, like `resolveVaultEnabledState`. */
128
- function resolveVaultPracticesEnabledState(parsed) {
129
- return resolveGateHonouredKeyState(parsed, VAULT_PRACTICES_ENABLED_CONFIG_KEY);
130
- }
131
122
  /** Four-state reading of the project's OWN `vault.required` (not the profile-layered gate value),
132
123
  * so the arming guard respects an explicit per-project `false`. */
133
124
  function resolveVaultRequiredState(parsed) {
@@ -151,12 +142,11 @@ function resolveBooleanKeyState(parsed, key) {
151
142
  }
152
143
  /**
153
144
  * Four-state reading of a vault switch whose gates read ONLY the nested form (Phase 15
154
- * D-32): `vault.enabled` and `vault.practices_enabled`. `loadConfig` materialises their
155
- * nested defaults, so the gates' evaluator answers at its first level and never reaches a
145
+ * D-32): `vault.enabled`. `loadConfig` materialises its nested default, so the gates' evaluator answers at its first level and never reaches a
156
146
  * hand-written FLAT key (it warns that the key is ignored). Reading the flat form as
157
147
  * `armed` therefore told /pp-init that a switch was on while no gate rendered. The
158
148
  * absent-only arming then skipped it, and the done line promised governance nobody
159
- * enforced (todo init-practices-reader-reads-flat-form-as-armed).
149
+ * enforced.
160
150
  *
161
151
  * - nested present: its value decides, exactly as before;
162
152
  * - flat-only `true`: `absent`, because the gate sees nothing. The arming writes the
@@ -222,21 +212,6 @@ function readRootKeyState(cwd, resolve) {
222
212
  function readVaultEnabledState(cwd) {
223
213
  return agreeWithGate(readRootKeyState(cwd, resolveVaultEnabledState), gateReadsOn((v) => v.isVaultGateActive(cwd)));
224
214
  }
225
- /** `vault.practices_enabled` for the project at `cwd`: `armed` exactly when
226
- * `isPracticesGateActive`, the one practices-switch reader (15-02), is ON. Never throws. */
227
- function readVaultPracticesEnabledState(cwd) {
228
- return agreeWithGate(readRootKeyState(cwd, resolveVaultPracticesEnabledState), gateReadsOn((v) => v.isPracticesGateActive(cwd)));
229
- }
230
- /**
231
- * Whether the project at `cwd` is in standards mode, asked of the one mode owner
232
- * (`isStandardsModeActive`, which honours the org-profile ladder). The /pp-init done line
233
- * uses it so that, in standards mode, it names the standards record the `vault.trace`
234
- * gate checks instead of practice recording, which standards mode does not do
235
- * (15-REVIEW WR-09). Any throw reads as not standards. Never throws.
236
- */
237
- function readVaultStandardsModeActive(cwd) {
238
- return gateReadsOn((v) => v.isStandardsModeActive(cwd));
239
- }
240
215
  /** `vault.required` for the project at `cwd`: `armed` exactly when the load gate's own
241
216
  * answer (`resolveVaultRequiredFlag`) is true. Never throws. */
242
217
  function readVaultRequiredState(cwd) {
@@ -250,7 +225,7 @@ function isVaultEnabledInConfig(parsed) {
250
225
  // ─── buildInitQuestions ────────────────────────────────────────────────────
251
226
  /** The canonical /pp-init question set, as data; the session may answer some itself (see "Automatic answers").
252
227
  * No org-profile question in this fork (PBI 74316); provider options come from `PROVIDERS`, preselected via `resolveProvider`. */
253
- function buildInitQuestions(cwd) {
228
+ function buildInitQuestions(cwd, opts = {}) {
254
229
  let preselected = git_provider_cjs_1.DEFAULT_PROVIDER;
255
230
  try {
256
231
  preselected = (0, git_provider_cjs_1.resolveProvider)(cwd).provider;
@@ -272,6 +247,7 @@ function buildInitQuestions(cwd) {
272
247
  prompt: 'Client slug (kebab-case, e.g. "acme") — type "internal" if none',
273
248
  optionalAnswer: false,
274
249
  },
250
+ ...clientVaultQuestion(cwd, opts),
275
251
  { id: 'description', prompt: 'One-line description (what this project does)', optionalAnswer: false },
276
252
  {
277
253
  id: 'provider',
@@ -284,6 +260,23 @@ function buildInitQuestions(cwd) {
284
260
  { id: 'visibility', prompt: 'Visibility (private / public / internal)', optionalAnswer: false },
285
261
  ];
286
262
  }
263
+ /** The client-vault question, asked while no vault is bound and a binding can be composed (`clientVaultChoice`).
264
+ * Blank or "none" keeps the org profile's seeded vault, if any; an answer replaces it in the binding.
265
+ * An internal project is answered "none" automatically (see `autoAnswerFor`). */
266
+ function clientVaultQuestion(cwd, opts) {
267
+ const choice = (0, compose_bindings_cjs_1.clientVaultChoice)(cwd, opts);
268
+ if (!choice.offered)
269
+ return [];
270
+ return [
271
+ {
272
+ id: 'client_vault',
273
+ prompt: choice.defaultRepo === null
274
+ ? 'Client vault repo ("owner/name" or a full clone URL) — type "none" to bind no client vault'
275
+ : `Client vault repo ("owner/name" or a full clone URL) — type "none" to use the default vault (${choice.defaultRepo})`,
276
+ optionalAnswer: true,
277
+ },
278
+ ];
279
+ }
287
280
  // ─── Automatic answers (PBI 74299): an org profile init section fixes answers and can derive slugs ───
288
281
  /** The visibility answers the question accepts. */
289
282
  const VISIBILITY_OPTIONS = Object.freeze(['private', 'public', 'internal']);
@@ -345,6 +338,9 @@ function autoAnswerFor(id, answers, init) {
345
338
  const value = init.fixed[id];
346
339
  return value === undefined ? null : { value, source: 'org-profile' };
347
340
  }
341
+ if (id === 'client_vault' && answers.client_slug === 'internal') {
342
+ return { value: 'none', source: 'derived' };
343
+ }
348
344
  if (init.deriveSlugs && (id === 'project_slug' || id === 'client_slug')) {
349
345
  const name = answers[id === 'project_slug' ? 'project_name' : 'client_name'];
350
346
  const slug = typeof name === 'string' ? deriveSlug(name) : null;
@@ -353,10 +349,10 @@ function autoAnswerFor(id, answers, init) {
353
349
  return null;
354
350
  }
355
351
  /** The session's automatic answers, in question order. */
356
- function listAutoAnswers(cwd, session) {
352
+ function listAutoAnswers(cwd, session, opts) {
357
353
  const sources = session.autoAnswers ?? {};
358
354
  const out = [];
359
- for (const q of buildInitQuestions(cwd)) {
355
+ for (const q of buildInitQuestions(cwd, { _globalRoot: opts._globalRoot })) {
360
356
  const source = sources[q.id];
361
357
  const value = session.answers[q.id];
362
358
  if (source !== undefined && typeof value === 'string')
@@ -662,27 +658,24 @@ function writeIdentityAndProvider(cwd, answers) {
662
658
  writeConfigMerge(cwd, patch);
663
659
  }
664
660
  /** The ONE owner of the arm-where-absent rule (quick 261003-kmd): arms `vault.enabled`,
665
- * `vault.practices_enabled` and `vault.required` only where ABSENT, from one read in one write.
661
+ * and `vault.required` only where ABSENT, from one read in one write.
666
662
  *
667
- * A successful composition means a client vault exists, so the three keys are armed (OD-1,
663
+ * A successful composition means a client vault exists, so the two keys are armed (OD-1,
668
664
  * 260915-lmc, 260916-s5n). An explicit `false` or a malformed value is kept — that is the
669
665
  * operator's decision, never overwritten (07-09 WR-07). The write happens only when there is
670
666
  * something to arm: the nested- and flat-form byte-identity pins depend on a no-op writing
671
667
  * nothing. Callers: the session's `'unchanged'` decision (how existing projects get armed by a
672
668
  * plain re-run), the approved `compose_confirm`, and `armVaultFromProfile` (`/pp-new-project`).
673
- * It exists because the two session copies had already drifted once — the practices key was
674
- * added to both by hand in 260915-lmc — and a third route would have been a third copy.
669
+ * It exists because the two session copies had already drifted once, and a third route would
670
+ * have been a third copy.
675
671
  *
676
- * @returns the armed key names, in the fixed order enabled, practices_enabled, required. */
672
+ * @returns the armed key names, in the fixed order enabled, required. */
677
673
  function armVaultKeysWhereAbsent(cwd) {
678
674
  const { parsed } = readConfigFile(cwd);
679
675
  const vaultPatch = {};
680
676
  if (resolveVaultEnabledState(parsed).state === 'absent') {
681
677
  vaultPatch.enabled = true;
682
678
  }
683
- if (resolveVaultPracticesEnabledState(parsed).state === 'absent') {
684
- vaultPatch.practices_enabled = true;
685
- }
686
679
  if (resolveVaultRequiredState(parsed).state === 'absent') {
687
680
  vaultPatch.required = true;
688
681
  }
@@ -786,10 +779,15 @@ function normalizeYesNo(value) {
786
779
  }
787
780
  /** Decide the current step, performing its idempotent side effects once: automatic answers, the identity write,
788
781
  * the compose decision and `doneAt`. Recomputes the question set on every call. */
782
+ /** The client vault the operator named, or `undefined` to keep the seeded one. */
783
+ function answeredClientVault(session) {
784
+ const checked = (0, compose_bindings_cjs_1.validateClientVaultAnswer)(session.answers.client_vault);
785
+ return checked.ok && checked.value !== null ? checked.value : undefined;
786
+ }
789
787
  function computeCurrentStep(cwd, session, opts) {
790
788
  // Any failure degrades to a `write_failed` step instead of throwing.
791
789
  try {
792
- const questions = buildInitQuestions(cwd);
790
+ const questions = buildInitQuestions(cwd, { _globalRoot: opts._globalRoot });
793
791
  // PBI 74299: read once per call; only consulted for an unanswered question.
794
792
  let profileInit = null;
795
793
  for (const q of questions) {
@@ -812,7 +810,11 @@ function computeCurrentStep(cwd, session, opts) {
812
810
  persistSession(cwd, session);
813
811
  }
814
812
  if (session.composeDecision === null) {
815
- const dryRun = (0, compose_bindings_cjs_1.composeBindings)(cwd, { offline: opts.offline === true, _globalRoot: opts._globalRoot });
813
+ const dryRun = (0, compose_bindings_cjs_1.composeBindings)(cwd, {
814
+ offline: opts.offline === true,
815
+ _globalRoot: opts._globalRoot,
816
+ clientVaultRepo: answeredClientVault(session),
817
+ });
816
818
  // Offer compose_confirm only when approving would change something; 'unchanged' and 'skipped' are distinct end states.
817
819
  if (dryRun.composable === true && dryRun.action !== 'unchanged') {
818
820
  // VAULT-EGRESS-1: `info` is display-only (the CLI and the TTY renderer print it); approval
@@ -917,7 +919,7 @@ function initSessionNext(cwd, opts = {}) {
917
919
  }
918
920
  const session = loaded.session;
919
921
  const state = currentStepToState(computeCurrentStep(cwd, session, opts));
920
- const autoAnswered = listAutoAnswers(cwd, session);
922
+ const autoAnswered = listAutoAnswers(cwd, session, opts);
921
923
  return autoAnswered.length > 0 ? { ...state, autoAnswered } : state;
922
924
  }
923
925
  /** Record one answer for the current step only; out-of-order, blank required, unparseable or post-done answers are rejected. */
@@ -957,6 +959,12 @@ function initSessionAnswer(cwd, stepId, value, opts = {}) {
957
959
  return { ok: false, reason: 'answer_required', message: `Question "${stepId}" requires a non-blank answer.` };
958
960
  }
959
961
  const answerValue = typeof value === 'string' ? value : '';
962
+ if (stepId === 'client_vault') {
963
+ const checked = (0, compose_bindings_cjs_1.validateClientVaultAnswer)(answerValue);
964
+ if (!checked.ok) {
965
+ return { ok: false, reason: 'invalid_answer', message: `Question "client_vault": ${checked.reason}` };
966
+ }
967
+ }
960
968
  session.answers[stepId] = answerValue;
961
969
  persistSession(cwd, session);
962
970
  return { ok: true };
@@ -979,7 +987,12 @@ function initSessionAnswer(cwd, stepId, value, opts = {}) {
979
987
  }
980
988
  if (normalized === true) {
981
989
  // Check the write result before recording "approved": the file can change between the dry run and this write.
982
- const writeResult = (0, compose_bindings_cjs_1.composeBindings)(cwd, { write: true, offline: opts.offline === true, _globalRoot: opts._globalRoot });
990
+ const writeResult = (0, compose_bindings_cjs_1.composeBindings)(cwd, {
991
+ write: true,
992
+ offline: opts.offline === true,
993
+ _globalRoot: opts._globalRoot,
994
+ clientVaultRepo: answeredClientVault(session),
995
+ });
983
996
  if (writeResult.composable !== true || writeResult.wrote !== true) {
984
997
  // Leave the decision unset so the next call re-evaluates and can re-offer compose_confirm.
985
998
  return {
@@ -161,36 +161,22 @@ async function runInitInteractive(cwd, prompter, opts = {}) {
161
161
  'INIT_VAULT_ENABLED_MALFORMED; store the boolean with pp-tools config-set vault.enabled true.');
162
162
  }
163
163
  else if (vaultState === 'armed') {
164
- // Quick 260915-lmc: vault.practices_enabled is armed beside it under
165
- // the same absent-only rule, so the line names it only when the
166
- // READING says true — an explicit false is the project's choice and
167
- // is not contradicted here.
168
- const practicesArmed = (0, init_contract_cjs_1.readVaultPracticesEnabledState)(cwd).state === 'armed';
169
164
  // Quick 260916-s5n: vault.required is named only when the READING
170
165
  // says armed, by the same rule — an explicit false is the project's
171
166
  // choice and the last line printed must not contradict it.
172
167
  const requiredArmed = (0, init_contract_cjs_1.readVaultRequiredState)(cwd).state === 'armed';
173
- // Built as a head plus two OPTIONAL clauses carrying no terminator
174
- // of their own, with exactly one full stop appended at the end. The
175
- // stop used to live inside the practices branch, which made every
176
- // further clause a sentence continuing past its own period.
168
+ // Built as a head plus clauses carrying no terminator of their own,
169
+ // with exactly one full stop appended at the end.
177
170
  const head = 'Done — .platform-bindings.json was composed and written; vault.enabled is now true ' +
178
171
  '(the vault capability is armed — /pp-init Step 2.6 probes reachability next)';
179
- // 15-REVIEW WR-09: in standards mode no phase records practices (the
180
- // practices planner fragment and trace gates are filtered out), so the
181
- // clause names what IS recorded and checked there, whatever the
182
- // practices switch reads.
183
- const practicesClause = (0, init_contract_cjs_1.readVaultStandardsModeActive)(cwd)
184
- ? '; vault.mode is standards, so each plan records which client standards documents governed it ' +
185
- '(<vault_standards>), checked by the vault.trace gate'
186
- : practicesArmed
187
- ? '; vault.practices_enabled is true, so each phase records which client vault practices it applied'
188
- : '';
172
+ // 15-REVIEW WR-09: the clause names what is recorded and checked.
173
+ const standardsClause = '; each plan records which client standards documents governed it ' +
174
+ '(<vault_standards>), checked by the vault.trace gate';
189
175
  const requiredClause = requiredArmed
190
176
  ? '; vault.required is true, so a client vault miss, a failed fetch or an unwritable vault summary ' +
191
177
  'stops the build at plan:pre and execute:pre'
192
178
  : '';
193
- prompter.print(head + practicesClause + requiredClause + '.');
179
+ prompter.print(head + standardsClause + requiredClause + '.');
194
180
  }
195
181
  else {
196
182
  // 'absent' — unreachable after an approved compose (the arming
@@ -180,19 +180,13 @@ const ERROR_REASON = Object.freeze({
180
180
  PLATFORM_BINDINGS_PARSE_FAILED: 'platform_bindings_parse_failed',
181
181
  // init (Phase 34-09 gap closure: `pp-tools init` interactive command)
182
182
  INIT_NON_INTERACTIVE: 'init_non_interactive',
183
- // vault write-back promote arm (Phase 29 WRB-01/03)
183
+ // vault promote (a standards document into the client vault)
184
184
  VAULT_PROMOTE_DISABLED: 'vault_promote_disabled',
185
- VAULT_PROMOTE_INVALID_PRACTICE: 'vault_promote_invalid_practice',
185
+ VAULT_PROMOTE_INVALID_DOCUMENT: 'vault_promote_invalid_document',
186
186
  VAULT_PROMOTE_SECRET_DETECTED: 'vault_promote_secret_detected',
187
187
  VAULT_PROMOTE_TARGET_REFUSED: 'vault_promote_target_refused',
188
188
  VAULT_PROMOTE_CONCURRENT_EDIT: 'vault_promote_concurrent_edit',
189
189
  VAULT_PROMOTE_TRANSPORT_FAILED: 'vault_promote_transport_failed',
190
- // vault.mode (Vault Assurance 13-03, D-08/D-14): an unrecognised mode value,
191
- // and the standards-mode promote refusal
192
- VAULT_MODE_INVALID: 'vault_mode_invalid',
193
- // 13-REVIEW WR-01: the mode resolver failed; refused like an invalid value
194
- VAULT_MODE_UNRESOLVED: 'vault_mode_unresolved',
195
- VAULT_PROMOTE_NOT_APPLICABLE: 'vault_promote_not_applicable',
196
190
  // discovery rollup (discovery-v2 Phase 1: D-07/D-08 gate)
197
191
  DISCOVERY_ROLLUP_PARSE_FAILED: 'discovery_rollup_parse_failed',
198
192
  DISCOVERY_ROLLUP_MISMATCH: 'discovery_rollup_mismatch',
@@ -13,7 +13,7 @@
13
13
  * Command surface: pp-tools loop render-hooks <point>
14
14
  *
15
15
  * Exports (three things):
16
- * resolveLoopHooks({ point, registry, config, vaultMode?, recordedArtifacts? }) → { point, activeHooks }
16
+ * resolveLoopHooks({ point, registry, config, recordedArtifacts? }) → { point, activeHooks }
17
17
  * renderLoopHooks(resolved) → markdown string
18
18
  * cmdLoopRenderHooks(cwd, point, raw, options) — I/O entry point
19
19
  *
@@ -36,7 +36,7 @@ const configLoaderModule = require("./config-loader.cjs");
36
36
  const { loadConfig } = configLoaderModule;
37
37
  // eslint-disable-next-line @typescript-eslint/no-require-imports
38
38
  const capabilityStateModule = require("./capability-state.cjs");
39
- const { resolveCapabilityRuntimeState, vaultModeFilterVerdict, resolveInjectedVaultMode, SEAL_FIELD, sealRenderVerdict, resolveInjectedRecordedArtifacts, resolveInjectedPlatformBindingsPresent, } = capabilityStateModule;
39
+ const { resolveCapabilityRuntimeState, SEAL_FIELD, sealRenderVerdict, resolveInjectedRecordedArtifacts, resolveInjectedPlatformBindingsPresent, } = capabilityStateModule;
40
40
  // ─── Capability-activation engine (single owner for config-key precedence) ────
41
41
  // eslint-disable-next-line @typescript-eslint/no-require-imports
42
42
  const capabilityActivationModule = require("./capability-activation.cjs");
@@ -103,7 +103,7 @@ function _getCanonicalPoints(_registry) {
103
103
  * resolved against `config`; active iff truthy. Inactive hooks are filtered out.
104
104
  */
105
105
  function resolveLoopHooks(input) {
106
- const { point, registry, config, cwd, capabilityStatesById, vaultMode, recordedArtifacts, platformBindingsPresent, } = input;
106
+ const { point, registry, config, cwd, capabilityStatesById, recordedArtifacts, platformBindingsPresent, } = input;
107
107
  // Validate point
108
108
  const canonicalPoints = _getCanonicalPoints(registry);
109
109
  if (!canonicalPoints.includes(point)) {
@@ -122,12 +122,6 @@ function resolveLoopHooks(input) {
122
122
  }
123
123
  const entryMap = entry;
124
124
  const activeHooks = [];
125
- // Phase 15 D-02/D-28: the per-hook `vaultMode` filter, one rule owned by
126
- // capability-state (`vaultModeFilterVerdict`), evaluated against the injected
127
- // mode VALUE. The field is read here and never copied into an ActiveHook.
128
- function modeOk(hook, kind) {
129
- return vaultModeFilterVerdict(hook['vaultMode'], kind, vaultMode);
130
- }
131
125
  // Helper: check activation using single-key precedence resolver (FIX 1 + FIX 3)
132
126
  function isActive(hook) {
133
127
  const when = hook['when'];
@@ -260,8 +254,6 @@ function resolveLoopHooks(input) {
260
254
  continue;
261
255
  if (droppedForBindings(capId))
262
256
  continue;
263
- if (!modeOk(hook, 'step'))
264
- continue;
265
257
  if (!isActive(hook))
266
258
  continue;
267
259
  const ref = (typeof hook['ref'] === 'object' && hook['ref'] !== null)
@@ -298,8 +290,6 @@ function resolveLoopHooks(input) {
298
290
  continue;
299
291
  if (droppedForBindings(capId))
300
292
  continue;
301
- if (!modeOk(hook, 'contribution'))
302
- continue;
303
293
  if (!isActive(hook))
304
294
  continue;
305
295
  const into = typeof hook['into'] === 'string' ? hook['into'] : undefined;
@@ -338,12 +328,6 @@ function resolveLoopHooks(input) {
338
328
  // vault gate already answers inert there, so nothing is left ungated.
339
329
  if (droppedForBindings(capId))
340
330
  continue;
341
- // Phase 16 D-19 / research C-1: the mode filter runs FIRST, so a
342
- // mode-filtered seal gate never consults the recorded-artifacts predicate
343
- // (a default standards project pays no planning-tree read for a seal it
344
- // cannot render). `modeOk` is pure, so the order changes no other result.
345
- if (!modeOk(hook, 'gate'))
346
- continue;
347
331
  const verdict = sealRenderVerdict(hook[SEAL_FIELD], gateStateOf(capId), recordedArtifactsOnce);
348
332
  if (verdict === 'drop')
349
333
  continue;
@@ -496,9 +480,7 @@ function renderLoopHooks(resolved) {
496
480
  * 0) when no contribution matches. No JSON envelope; `--raw` is ignored. This
497
481
  * is the text execute-phase interpolates verbatim into a spawn prompt (15-09).
498
482
  * `--active-cap` takes precedence: given both, `--into` is ignored.
499
- *
500
- * options.resolveVaultMode: an injected `() => mode | null` (pp-tools builds it
501
- * on vaultModeVerdict). This module never imports vault code (D-02).
483
+
502
484
  *
503
485
  * options.resolveRecordedArtifacts: an injected `() => boolean` (pp-tools builds
504
486
  * it on the seal leaf's hasRecordedVaultArtifacts); only literal `true` lets a
@@ -552,11 +534,8 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
552
534
  for (const cap of state.capabilities || []) {
553
535
  capabilityStatesById.set(cap.id, cap);
554
536
  }
555
- // Phase 15 D-02: the vault mode arrives through an injected resolver (pp-tools
556
- // passes one built on vaultModeVerdict). A missing resolver, a throw or a
557
- // non-mode answer is `null`, the fail-closed undefined mode (D-28).
558
- const vaultMode = resolveInjectedVaultMode(options['resolveVaultMode']);
559
- // Phase 16 D-19: the recorded-artifacts predicate arrives the same way (pp-tools
537
+ // Phase 16 D-19: the recorded-artifacts predicate arrives through an injected
538
+ // resolver (pp-tools
560
539
  // passes one built on the seal leaf's hasRecordedVaultArtifacts). The resolver
561
540
  // memoises it and asks it only for an enabled, config-inactive seal gate.
562
541
  const resolveRecordedArtifacts = options['resolveRecordedArtifacts'];
@@ -570,7 +549,7 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
570
549
  // --active-cap reads the UNFILTERED resolution (16-16 D-81, see above):
571
550
  // without the bindings thunk, so it keeps answering the config switch.
572
551
  resolved = resolveLoopHooks({
573
- point, registry, config, cwd, capabilityStatesById, vaultMode, recordedArtifacts,
552
+ point, registry, config, cwd, capabilityStatesById, recordedArtifacts,
574
553
  ...(activeCapId === undefined ? { platformBindingsPresent } : {}),
575
554
  });
576
555
  }
@@ -583,7 +562,7 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
583
562
  if (activeCapId !== undefined) {
584
563
  // Phase 16 D-27 (research C-3): a hook rendering is no longer enough — the
585
564
  // capability must itself be active. A seal gate renders for a config-
586
- // inactive vault-practices, and the doors read `--active-cap` as their
565
+ // inactive vault, and the doors read `--active-cap` as their
587
566
  // switch, so a seal must never make an inactive capability answer `true`.
588
567
  // Equivalent to the old answer on every input without a seal: a normal
589
568
  // hook renders only for an active capability.
@@ -634,9 +613,6 @@ module.exports = {
634
613
  // Re-exported for identity parity guard (FIX 2: resolveConfigValues in this module
635
614
  // calls resolveConfigKey; exporting it here makes the single-owner contract testable).
636
615
  resolveConfigKey,
637
- // Re-exported for the same identity pin: the mode-filter rule has one owner,
638
- // capability-state.cjs (Phase 15 D-02/D-28).
639
- vaultModeFilterVerdict,
640
616
  // Re-exported for the same identity pin: the seal rule has one owner,
641
617
  // capability-state.cjs (Phase 16 D-19).
642
618
  sealRenderVerdict,
@@ -18,7 +18,7 @@
18
18
  * explicit opt-out.
19
19
  *
20
20
  * It imports ONLY node builtins (`node:fs`, `node:path`) at the top level —
21
- * no `./vault.cjs`, `./vault-coordinates.cjs`, `./vault-write-back.cjs`,
21
+ * no `./vault.cjs`, `./vault-coordinates.cjs`,
22
22
  * `./io.cjs`, `./shell-command-projection.cjs`. This is load-bearing: in
23
23
  * 33-03 BOTH `src/git-provider.cts` and `src/vault-coordinates.cts` import
24
24
  * THIS module at their own top level. If this module imported either of
@@ -33,7 +33,7 @@
33
33
  * inside `profileValueDisposition`, executed only when a profile value is
34
34
  * actually being validated, never at module load. This is the same
35
35
  * lazy-require seam `src/vault-command-router.cts` already uses for
36
- * `./vault.cjs` and `./vault-write-back.cjs`, and it is safe here precisely
36
+ * `./vault.cjs`, and it is safe here precisely
37
37
  * because `git-provider.cjs` has no top-level dependency on this module —
38
38
  * by the time any value is being validated, that module is either already
39
39
  * in the require cache or loads cleanly on its own. D-10 requires reusing
@@ -41,13 +41,6 @@
41
41
  * than writing a second regex; the lazy seam is how a zero-import leaf can
42
42
  * still do that.
43
43
  *
44
- * The second exception (Vault Assurance 13-03, D-23) is a top-level import
45
- * of `./vault-mode.cjs`, the mode vocabulary leaf. That module imports
46
- * nothing at all, so it can never require this module back and cannot close
47
- * a cycle; it is imported so `resolveVaultMode` and the `vault.mode`
48
- * disposition quote the key and the two literals from their one owner rather
49
- * than spelling them here.
50
- *
51
44
  * This file's own prose and code deliberately never name a concrete org
52
45
  * profile package or any client — the pointer is described only as "the
53
46
  * npm package name an operator configures". `tests/org-profile.test.cjs`
@@ -68,12 +61,8 @@ exports.resolveOrgProfile = resolveOrgProfile;
68
61
  exports.readProfileValue = readProfileValue;
69
62
  exports.booleanPolicyValue = booleanPolicyValue;
70
63
  exports.resolveVaultRequired = resolveVaultRequired;
71
- exports.resolveVaultMode = resolveVaultMode;
72
64
  const node_fs_1 = __importDefault(require("node:fs"));
73
65
  const node_path_1 = __importDefault(require("node:path"));
74
- // eslint-disable-next-line @typescript-eslint/no-require-imports
75
- const vaultMode = require("./vault-mode.cjs");
76
- const { VAULT_MODE_CONFIG_KEY, DEFAULT_VAULT_MODE, VAULT_MODE_REASON, isVaultMode } = vaultMode;
77
66
  // ─── Constants ───────────────────────────────────────────────────────────────
78
67
  /** The dotted `.planning/config.json` key naming the profile package. Deliberately
79
68
  * top-level, NOT nested under `vault.`, because the profile also supplies
@@ -98,7 +87,7 @@ exports.ORG_PROFILE_NONE = 'none';
98
87
  * separate package. It carries coordinates only — never a vault ref: the
99
88
  * vault version a project builds on is committed per project in its
100
89
  * `.platform-bindings.json` (`client.vaultRef`), so no pp-core release can
101
- * move it. Never offered upstream — see docs/PORT-BACK-DELTA.md.
90
+ * move it.
102
91
  */
103
92
  exports.EMBEDDED_ORG_PROFILE_NAME = 'embedded';
104
93
  /**
@@ -159,10 +148,10 @@ function readEmbeddedProfileVersion(dataPath) {
159
148
  * guard nor `resolveVaultRequired` hand-restates the string. */
160
149
  exports.VAULT_REQUIRED_CONFIG_KEY = 'vault.required';
161
150
  /**
162
- * D-06's fixed allowlist: a profile may contribute exactly these seven dotted
163
- * config keys (five coordinates and two policy keys; the seventh, vault.mode,
164
- * is documented at its entry). Entry order is not contractual; membership is. Anything else
165
- * present in a profile document is dropped with a bounded warning.
151
+ * D-06's fixed allowlist: a profile may contribute exactly these six dotted
152
+ * config keys (five coordinates and one policy key). Entry order is not
153
+ * contractual; membership is. Anything else present in a profile document is
154
+ * dropped with a bounded warning, except the retired keys below.
166
155
  *
167
156
  * FIVE COORDINATES AND ONE POLICY (02-04, D-VH-4 / STRICT-01). The first five
168
157
  * members answer *where to fetch from* — a provider, three shared-vault
@@ -197,15 +186,14 @@ exports.ORG_PROFILE_ALLOWLIST = Object.freeze([
197
186
  'vault.fallback_host',
198
187
  'platform.host',
199
188
  'vault.required',
200
- // Vault Assurance 13-03 (D-06): the second POLICY key, after vault.required
201
- // (02-04). Unlike vault.required, a profile value here can make a gate LESS
202
- // strict — `standards` turns the blocking vault.audit gate into
203
- // not_applicable — so the widening is accepted and bounded in 13-CONTEXT
204
- // D-24 (threat T-13-01): project config outranks the profile, only the two
205
- // literals pass the disposition below, and the embedded fork profile does
206
- // not seed it. vault.enabled and init.* stay absent from this list.
207
- VAULT_MODE_CONFIG_KEY,
208
189
  ]);
190
+ /**
191
+ * Keys a profile written for an older pp-core may still carry, which this
192
+ * engine no longer reads (FORK-ONLY: the vault once had a second, practices
193
+ * mode). They are dropped without a warning: a profile that still says
194
+ * `vault.mode` needs no attention, because the standards vault is the only vault.
195
+ */
196
+ const RETIRED_PROFILE_KEYS = new Set(['vault.mode', 'vault.practices_enabled']);
209
197
  /**
210
198
  * The single top-level key naming the Phase 34 init-only seed section.
211
199
  *
@@ -656,15 +644,6 @@ function profileValueDisposition(key, value) {
656
644
  }
657
645
  return { ok: false, reason: 'value must be the boolean true or false' };
658
646
  }
659
- // 13-03 (D-06, D-09): the mode accepts only its two literals, trimmed.
660
- // Anything else is dropped through the existing warning / rejected[]
661
- // path, and the mode then resolves to the default. The reason names the
662
- // allowed values and never echoes the rejected one.
663
- if (key === VAULT_MODE_CONFIG_KEY) {
664
- if (isVaultMode(value))
665
- return { ok: true, value: value.trim() };
666
- return { ok: false, reason: 'value must be practices or standards' };
667
- }
668
647
  if (typeof value !== 'string') {
669
648
  return { ok: false, reason: 'value must be a string' };
670
649
  }
@@ -950,6 +929,8 @@ function resolveOrgProfile(cwd, opts) {
950
929
  if (leaf.key === exports.ORG_PROFILE_INIT_SECTION || leaf.key.startsWith(`${exports.ORG_PROFILE_INIT_SECTION}.`)) {
951
930
  continue;
952
931
  }
932
+ if (RETIRED_PROFILE_KEYS.has(leaf.key))
933
+ continue;
953
934
  if (!exports.ORG_PROFILE_ALLOWLIST.includes(leaf.key)) {
954
935
  ignored.push(leaf.key);
955
936
  if (ignoredWarningCount < PROFILE_IGNORED_WARNING_CAP) {
@@ -1128,60 +1109,3 @@ function resolveVaultRequired(cwd, opts) {
1128
1109
  // Level 3 — the registered default from capabilities/vault/capability.json.
1129
1110
  return withIgnored({ required: false, source: 'default', warnings: profile.warnings });
1130
1111
  }
1131
- /**
1132
- * Resolve `vault.mode` for `cwd`: project config, then the org profile, then
1133
- * the registered default `practices` — the same three levels as
1134
- * `resolveVaultRequired` above.
1135
- *
1136
- * Like `resolveVaultRequired` (the one ladder both the load gate and the
1137
- * report ask, since 13-REVIEW CR-02), this resolver is the ONE reader of the
1138
- * mode for every mode-aware surface: the `vault
1139
- * org-profile` report (13-05), the gate, refusals and status field (13-06)
1140
- * and the parked practices surfaces (13-07). No surface re-reads the key
1141
- * from config itself; the negative-grep anti-drift test in
1142
- * `tests/vault-mode.test.cjs` is what keeps a second reader from appearing.
1143
- *
1144
- * Level 1 reads RAW config (D-21): `resolveConfigKey` with `config: {}` and
1145
- * `registry: {}` reads the workstream config.json and then the root
1146
- * config.json, each in either dotted form (nested first), with NO schema
1147
- * default and no federated fallback. The merged config from
1148
- * `config-loader.cts` is deliberately not used:
1149
- * `federated-config`'s `_typeMatches` substitutes the schema default for an
1150
- * out-of-enum value, and its warning is suppressed at load
1151
- * (`config-loader.cts`), so `"whatever"` would silently read as `practices`.
1152
- * A project value that is present but not one of the two literals returns
1153
- * `ok: false` with `vault_mode_invalid` and NEVER falls back (D-08): an
1154
- * unknown mode must be refused, not treated as practices.
1155
- *
1156
- * Level 2 is `readProfileValue`; `profileValueDisposition` has already
1157
- * narrowed the profile value to one of the two literals or dropped it with a
1158
- * warning (D-09), so an invalid profile value lands at level 3.
1159
- *
1160
- * KNOWN GAP (D-25), recorded rather than hidden: `config-get` reads only the
1161
- * planning dir's config.json, while level 1 here reads the workstream config
1162
- * and then the root config. In flat mode the two agree (pinned by the parity
1163
- * test in `tests/vault-mode.test.cjs`); on a partitioned project a root-only
1164
- * `vault.mode` can read differently. Filed as a todo; the same gap pre-exists
1165
- * for `vault.required`.
1166
- *
1167
- * Never throws: both seams already degrade to a miss on any fault.
1168
- */
1169
- function resolveVaultMode(cwd, opts) {
1170
- // Level 1 — project config, raw (ws then root, either dotted form).
1171
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1172
- const activation = require('./capability-activation.cjs');
1173
- const fromConfig = activation.resolveConfigKey(VAULT_MODE_CONFIG_KEY, { config: {}, cwd, registry: {} });
1174
- if (fromConfig.found) {
1175
- if (isVaultMode(fromConfig.value)) {
1176
- return { ok: true, mode: fromConfig.value.trim(), source: 'config', warnings: [] };
1177
- }
1178
- return { ok: false, source: 'config', reason: VAULT_MODE_REASON.INVALID, warnings: [] };
1179
- }
1180
- // Level 2 — the org profile, already narrowed by profileValueDisposition.
1181
- const profile = readProfileValue(cwd, VAULT_MODE_CONFIG_KEY, opts);
1182
- if (isVaultMode(profile.value)) {
1183
- return { ok: true, mode: profile.value.trim(), source: 'profile', warnings: profile.warnings };
1184
- }
1185
- // Level 3 — the registered default from capabilities/vault/capability.json.
1186
- return { ok: true, mode: DEFAULT_VAULT_MODE, source: 'default', warnings: profile.warnings };
1187
- }