@pareto4/pp-core 1.13.0 → 1.18.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/.claude-plugin/plugin.json +3 -3
- package/README.md +31 -12
- package/agents/pp-code-fixer.md +0 -11
- package/agents/pp-code-reviewer.md +1 -77
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +12 -29
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +34 -20
- package/agents/pp-vault-reviewer.md +100 -23
- package/agents/pp-verifier.md +1 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -5
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +10 -18
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +201 -229
- package/pp-core/bin/lib/capability-state.cjs +93 -1
- package/pp-core/bin/lib/capability-writer.cjs +3 -3
- package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +151 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +11 -12
- package/pp-core/bin/lib/git-provider.cjs +9 -10
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +620 -738
- package/pp-core/bin/lib/init-interactive.cjs +62 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +4 -2
- package/pp-core/bin/lib/loop-resolver.cjs +165 -129
- package/pp-core/bin/lib/org-profile.cjs +402 -161
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
- package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
- package/pp-core/bin/lib/vault-index.cjs +656 -0
- package/pp-core/bin/lib/vault-seal.cjs +1851 -0
- package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
- package/pp-core/bin/lib/vault-standards.cjs +1504 -0
- package/pp-core/bin/lib/vault-trace.cjs +155 -2778
- package/pp-core/bin/lib/vault-update.cjs +10 -59
- package/pp-core/bin/lib/vault.cjs +2570 -950
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +157 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-standards.md +311 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +56 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -57
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
- package/pp-core/workflows/execute-phase.md +152 -128
- package/pp-core/workflows/execute-plan.md +54 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +161 -23
- package/pp-core/workflows/vault-write-back.md +73 -110
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +120 -13
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +3 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +19 -17
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/bin/lib/practices-review-check.cjs +0 -938
- package/pp-core/bin/lib/vault-audit.cjs +0 -2070
- package/pp-core/bin/lib/vault-practices.cjs +0 -1990
- package/pp-core/bin/lib/vault-review.cjs +0 -2359
- package/pp-core/bin/lib/vault-write-back.cjs +0 -877
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/references/planner-vault-practices.md +0 -112
- package/pp-core/templates/practices/README.md +0 -251
- package/pp-core/templates/practices/error-handling.md +0 -18
- package/pp-core/templates/practices/minimal-comments.md +0 -14
- package/pp-core/templates/practices/naming-convention.md +0 -18
- package/pp-core/templates/practices/practice-template.md +0 -23
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
|
@@ -1,25 +1,126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vault fetch + summary — the bespoke step dispatch, served at `plan:pre` and `execute:pre`
|
|
2
2
|
|
|
3
|
-
Capability-driven dispatch for an active step hook where `kind == "step"` and `capId == "vault"
|
|
3
|
+
Capability-driven dispatch for an active vault step hook where `kind == "step"` and `capId == "vault"`. The `vault` capability declares that step at **two** loop points — `plan:pre` and `execute:pre` — and this one file serves both, Read from ten call sites — nine hook-mode callers at those two points and one direct-mode caller with no loop point (see the Direct mode paragraph below); the caller table below names them. Runs `vault fetch`, which now writes `.planning/vault/SUMMARY.md` itself (STRICT-05). What that buys differs by point and both reasons are real: at `plan:pre` the summary exists on the researcher and planner read paths before the researcher `Agent()` spawn; at `execute:pre` the fetch is attempted *before* the gate decides, so a fresh clone or a CI runner with a reachable vault fetches and passes rather than being blocked for a cache it never had a chance to populate (GATE-03). The generic step contract dispatches only `ref.skill`/`ref.agent` hooks — not `ref.command` — so this bespoke block owns the `vault fetch` command run. The registry evaluates `when: vault.enabled`; an inert capability yields no hook.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Callers — bind `VAULT_FETCH_HOOKS_JSON` (hook mode) or set `VAULT_FETCH_DIRECT` (direct mode) before Reading this file
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`VAULT_FETCH_HOOKS_JSON` is a **parameter**, in exactly the sense `check-query-gate.md`'s
|
|
8
|
+
`GATE_HOOKS_JSON` is one: this file never renders hooks itself, and the caller sets it to the
|
|
9
|
+
`activeHooks` payload the caller already rendered for **its own** loop point. This file therefore
|
|
10
|
+
names no caller's payload variable anywhere, deliberately — it is Read from ten places, nine at
|
|
11
|
+
two loop points and one with no loop point at all, so any one variable name is dead at the other
|
|
12
|
+
nine, and the guard's next instruction is *skip*. A step file that hard-names one caller's variable
|
|
13
|
+
does not merely mis-document the other callers; it silently turns their fetch off
|
|
14
|
+
(02-VERIFICATION, G-2).
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
| Caller | Loop point | Binding |
|
|
17
|
+
|--------|------------|---------|
|
|
18
|
+
| `pp-core/workflows/plan-phase.md`, `### plan:pre vault fetch + summary` | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← the `plan:pre` payload that section already has in scope |
|
|
19
|
+
| `pp-core/workflows/autonomous.md`, §3a.5-i, the `ref.command` + `capId == "vault"` branch | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← the `plan:pre` payload §3a.5 rendered |
|
|
20
|
+
| `pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md`, step 1 "Fetch first" | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the `execute:pre` payload that dispatcher already reads its `activeHooks` from |
|
|
21
|
+
| `pp-core/workflows/quick.md`, Step 2.4 (16-11) | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← quick's `plan:pre` payload, filtered in the fence to the `vault` capability, after its own admission fence |
|
|
22
|
+
| `pp-core/workflows/debug.md`, § 0b Vault admission and load gate (16-14) | `plan:pre` | `VAULT_FETCH_HOOKS_JSON` ← debug's `plan:pre` payload, filtered in the fence to the `vault` capability, after its own admission fence |
|
|
23
|
+
| `pp-core/workflows/code-review-fix.md`, `vault_admission_gate` (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` capability, after its own admission fence |
|
|
24
|
+
| `pp-core/workflows/validate-phase.md`, § 0.5 (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` capability, after its own admission fence |
|
|
25
|
+
| `pp-core/workflows/add-tests.md`, `vault_admission_gate` (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` capability, after its own admission fence |
|
|
26
|
+
| `pp-core/workflows/audit-fix.md`, `vault_admission_gate` (16-15) | `execute:pre` | `VAULT_FETCH_HOOKS_JSON` ← the door's `execute:pre` payload, filtered in the fence to the `vault` capability, after its own admission fence |
|
|
27
|
+
| `pp-core/workflows/discovery-sprint/vault-context.md`, § 2 "Fetch" (Read from Flow 2 Step 1 and prefill Step 0 of the discovery sprint) | none — direct mode (D-11-09) | `VAULT_FETCH_DIRECT=true`; the payload parameter is left empty; the two activation guards run first, in order |
|
|
28
|
+
|
|
29
|
+
A **next caller** (the eleventh) inherits one obligation and one only: bind this parameter from the payload it
|
|
30
|
+
rendered for its own point, before the Read — or, when it has no loop point, run the two activation
|
|
31
|
+
guards and set `VAULT_FETCH_DIRECT` (Direct mode below). Nothing in this file needs to change to
|
|
32
|
+
accommodate it — that is the whole reason both parameters exist instead of a sentence naming a
|
|
33
|
+
caller's variable.
|
|
34
|
+
|
|
35
|
+
The step's static `onError: skip` remains the **registry-level dispatch policy** and is unchanged. Layered on top of it is a **runtime** decision this block makes for itself: when `vault.required` is true, a failed fetch or an unwritten summary hard-stops with `PARETO_GATE_BLOCKED:vault.fetch` and a non-zero exit (STRICT-03, GATE-09). A static `onError` cannot depend on a config value, which is why the arm lives here rather than in the capability declaration. With `vault.required` false — the default — every path below behaves exactly as it did before: a warning, and the run continues.
|
|
36
|
+
|
|
37
|
+
**Admission first (D-28, D-05).** This check runs before the hook-presence check below because a loop-point host renders no vault hook at all on a config the gate cannot read, so without it a direct or unconditional caller would skip the fetch silently. It is inert without `.platform-bindings.json` (a probe that cannot answer, such as on a malformed bindings file, defers to the verb, which checks bindings first itself); `on`, `off` and `inert` continue to the hook check exactly as before, and anything else (`config-unreadable`, or no answer) halts naming the file and the repair (research C-4).
|
|
10
38
|
|
|
11
39
|
```bash
|
|
12
40
|
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
|
|
42
|
+
VAULT_ADMIT=inert
|
|
43
|
+
if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
|
|
44
|
+
case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Read the active vault step hook from `VAULT_FETCH_HOOKS_JSON` (in-context) where `kind == "step"` and `capId == "vault"`. A caller binding this payload to `$X` means the JSON on the `X=` line the caller's render fence PRINTED (D-99): a payload a fence only assigned is invisible in the next call.
|
|
48
|
+
|
|
49
|
+
**If no active vault step hook exists** and `VAULT_FETCH_DIRECT` is not the literal `true`: skip (non-Pareto project, `vault.enabled` false, or vault bindings absent).
|
|
50
|
+
|
|
51
|
+
**Direct mode — `VAULT_FETCH_DIRECT` (D-11-09):** consulted **only** when `VAULT_FETCH_HOOKS_JSON` is empty, mirroring the `GATE_DIRECT_QUERY` parameter `check-query-gate.md` already ships for build entry points that declare no loop-host frontmatter. A caller with no loop point cannot render a payload at all: the `vault` capability declares this step at exactly `plan:pre` and `execute:pre`, and the twelve canonical loop points are frozen by the loop-host contract, so there is no third point such a caller could render and no payload for it to bind. Setting this parameter to the literal `true` makes the block below run exactly as it does for an active hook — the same `vault fetch`, the same `.planning/vault/SUMMARY.md` write, the same three arms and the same required-miss halt token and non-zero exit — so a direct caller inherits the first of the two `plan:pre` halt surfaces from its owner rather than authoring a fourth copy of it (D-11-11). A direct caller MUST run the two activation guards itself first, in this order: `pp_run query platform-bindings --pick present` (when not `true`, do not Read this file at all — non-Pareto project, fully inert), then `pp_run vault admission` (16-04, D-28: on `on`, Read this file; on `off` or `inert`, do not Read it; on anything else, `config-unreadable` or no answer, print `pp_run vault admission --explain` and halt with `PARETO_GATE_BLOCKED:vault.admission` and a non-zero exit, because a config the gate cannot read is not a vault that is off). A direct dispatch has not been through the registry's `when` evaluation and therefore carries none of its inertness, which is the same obligation `check-query-gate.md` § Direct-query mode places on a direct caller of the gate. Hook-mode callers are unaffected: with a non-empty payload this parameter is never read, so all hook-mode callers behave exactly as they did before it existed.
|
|
52
|
+
|
|
53
|
+
The block below also records the verdict before and after the fetch and says so when the fetch changed it (D-08-08): the plan:pre / execute:pre ordering is fetch-then-gate (`plan-phase.md` §5.1 then §5.6; `execute-pre-dispatch.md` steps 1-2), so a by-hand `check vault.status` taken earlier can legitimately disagree with the gate — the disagreement is temporal, and this line is what makes it explicable (D-08-05). Both snapshots are pure reads (D-08-04): they report the manifest, they never fetch, and they add nothing at all on a run that does not reach the block — an inactive hook with no direct-mode caller — because they live inside the run condition above and nowhere else.
|
|
54
|
+
|
|
55
|
+
**If an active vault step hook exists** (recognizable by `capId == "vault"` + `ref.command == "vault fetch"`) — or `VAULT_FETCH_DIRECT` is `true` with an empty payload:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# vault.required — read ONCE, with an explicit default (STRICT-03). The key is
|
|
59
|
+
# declared in `capabilities/vault/capability.json`, so this read returns the
|
|
60
|
+
# project's configured value; with it set true the hard-stop arm below is live.
|
|
61
|
+
VAULT_REQUIRED=$(pp_run query config-get vault.required --raw --default false 2>/dev/null || echo false)
|
|
62
|
+
|
|
63
|
+
# vault fetch: populates .planning/vault/ cache + manifest AND writes
|
|
64
|
+
# .planning/vault/SUMMARY.md itself. The separate `vault summary` subcommand
|
|
65
|
+
# call that used to live here is deliberately GONE (STRICT-05): the fetch and the
|
|
66
|
+
# summary were paired only by this markdown, so a direct CLI fetch, an --auto run
|
|
67
|
+
# that never dispatched this step, or an agent skipping the second instruction
|
|
68
|
+
# each produced a populated cache with no SUMMARY.md. The pairing is now code.
|
|
69
|
+
# D-08-08: a pure read of the manifest as it stands BEFORE this fetch rewrites
|
|
70
|
+
# it. This is the reading an operator's own by-hand `check vault.status` would
|
|
71
|
+
# have produced a moment ago, captured here because nothing downstream can
|
|
72
|
+
# reconstruct it once the fetch has landed.
|
|
73
|
+
VAULT_PRE_VERDICT=$(pp_run check vault.status --raw 2>/dev/null || echo '{}')
|
|
74
|
+
VAULT_FETCH_JSON=$(pp_run vault fetch --raw 2>/dev/null || echo '{}')
|
|
75
|
+
VAULT_SOURCE=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).source||'');}catch{process.stdout.write('');}")
|
|
76
|
+
VAULT_SUMMARY_WRITTEN=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).summaryWritten===true));}catch{process.stdout.write('false');}")
|
|
77
|
+
VAULT_SUMMARY_ERROR=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).summaryError||''));}catch{process.stdout.write('');}")
|
|
78
|
+
VAULT_FETCH_REASON=$(printf '%s' "$VAULT_FETCH_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const p=JSON.parse(d);process.stdout.write(String(p.fallbackReason||p.warning||''));}catch{process.stdout.write('');}")
|
|
79
|
+
|
|
80
|
+
# D-08-08: the same pure read AFTER the fetch, and a comparison of the five
|
|
81
|
+
# fields that decide the gate. Values are envelope fields this file's siblings
|
|
82
|
+
# already print; `reason` is a frozen code. Pipes and newlines are stripped and
|
|
83
|
+
# each summary is cut at 300 characters, because the two summaries are carried
|
|
84
|
+
# through a single pipe-delimited string that `cut` then splits.
|
|
85
|
+
VAULT_POST_VERDICT=$(pp_run check vault.status --raw 2>/dev/null || echo '{}')
|
|
86
|
+
VAULT_VERDICT_DIFF=$(PRE="$VAULT_PRE_VERDICT" POST="$VAULT_POST_VERDICT" node -e "const F=['tier','ref','stale','block','reason'];const g=(s)=>{let p={};try{p=JSON.parse(s)||{};}catch{p={};}return F.map((k)=>k+'='+String(p[k]===undefined||p[k]===null?'':p[k])).join(' ').replace(/[\r\n|]/g,'').slice(0,300);};const a=g(process.env.PRE||'');const b=g(process.env.POST||'');process.stdout.write(a===b?'same||':'changed|'+a+'|'+b);")
|
|
87
|
+
VAULT_VERDICT_CHANGED=$(printf '%s' "$VAULT_VERDICT_DIFF" | cut -d'|' -f1)
|
|
88
|
+
VAULT_VERDICT_BEFORE=$(printf '%s' "$VAULT_VERDICT_DIFF" | cut -d'|' -f2)
|
|
89
|
+
VAULT_VERDICT_AFTER=$(printf '%s' "$VAULT_VERDICT_DIFF" | cut -d'|' -f3)
|
|
90
|
+
if [ "$VAULT_VERDICT_CHANGED" = "changed" ]; then
|
|
91
|
+
echo "ℹ vault fetch changed the gate verdict: before — ${VAULT_VERDICT_BEFORE}; after — ${VAULT_VERDICT_AFTER}. A check vault.status read taken before this fetch described the earlier state (D-08-08)."
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Read the OUTCOME, never the exit code: `pp-tools vault ...` exits 0 on every
|
|
95
|
+
# path by design (D-VH-7 — the command reports, the workflow halts), so the old
|
|
96
|
+
# `if ! pp_run vault fetch` guard could not take its failure branch.
|
|
97
|
+
VAULT_FETCH_FAILED=false
|
|
98
|
+
if [ -z "$VAULT_SOURCE" ] || [ "$VAULT_SOURCE" = "miss" ]; then VAULT_FETCH_FAILED=true; fi
|
|
99
|
+
VAULT_LOAD_OK=true
|
|
100
|
+
if [ "$VAULT_FETCH_FAILED" = "true" ] || [ "$VAULT_SUMMARY_WRITTEN" != "true" ]; then VAULT_LOAD_OK=false; fi
|
|
101
|
+
|
|
102
|
+
if [ "$VAULT_REQUIRED" = "true" ] && [ "$VAULT_LOAD_OK" = "false" ]; then
|
|
103
|
+
# Arm 3 — vault.required is on: a miss, a failed fetch and an unwritable
|
|
104
|
+
# summary all hard-stop (D-VH-4). Print the failure verbatim, then the named
|
|
105
|
+
# halt token and a non-zero exit — the same two halves the extracted enforcer
|
|
106
|
+
# uses, so an agent stops and an unattended runner sees the exit code.
|
|
107
|
+
echo "✗ vault could not be loaded and vault.required is true (STRICT-03)" >&2
|
|
108
|
+
if [ -n "$VAULT_FETCH_REASON" ]; then echo " fetch: $VAULT_FETCH_REASON" >&2; fi
|
|
109
|
+
if [ -n "$VAULT_SUMMARY_ERROR" ]; then echo " summary: $VAULT_SUMMARY_ERROR" >&2; fi
|
|
110
|
+
echo "PARETO_GATE_BLOCKED:vault.fetch"
|
|
111
|
+
exit 1
|
|
112
|
+
elif [ "$VAULT_FETCH_FAILED" = "true" ]; then
|
|
113
|
+
# Arm 1 — fetch failed, vault.required off. Same effect as before this plan.
|
|
15
114
|
echo "⚠ vault fetch failed — continuing without vault context (onError:skip)" >&2
|
|
115
|
+
if [ -n "$VAULT_FETCH_REASON" ]; then echo " reason: $VAULT_FETCH_REASON" >&2; fi
|
|
116
|
+
elif [ "$VAULT_SUMMARY_WRITTEN" != "true" ]; then
|
|
117
|
+
# Arm 2 — fetch succeeded, the summary did not reach disk. STRICT-05: this is
|
|
118
|
+
# the branch the confirmation line used to fall through to unconditionally,
|
|
119
|
+
# pointing agents at a SUMMARY.md that was never written.
|
|
120
|
+
echo "⚠ vault summary not written — .planning/vault/SUMMARY.md is absent (onError:skip)" >&2
|
|
121
|
+
if [ -n "$VAULT_SUMMARY_ERROR" ]; then echo " reason: $VAULT_SUMMARY_ERROR" >&2; fi
|
|
16
122
|
else
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
if ! pp_run vault summary 2>&1; then
|
|
20
|
-
echo "⚠ vault summary failed — SUMMARY.md may be absent (onError:skip)" >&2
|
|
21
|
-
else
|
|
22
|
-
echo "✓ vault fetch + summary complete: .planning/vault/SUMMARY.md"
|
|
23
|
-
fi
|
|
123
|
+
# The claim is now reachable ONLY when summaryWritten is true.
|
|
124
|
+
echo "✓ vault fetch + summary complete: .planning/vault/SUMMARY.md"
|
|
24
125
|
fi
|
|
25
126
|
```
|
|
@@ -1,28 +1,227 @@
|
|
|
1
|
-
# Vault load gate (plan:pre + execute:pre)
|
|
1
|
+
# Vault load gate — state rendering (plan:pre + execute:pre)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Renders the vault load state for the operator before planning or build. The host sets
|
|
4
|
+
`VAULT_GATE_POINT` to `plan` (for plan:pre) or `execute` (for execute:pre) before
|
|
5
|
+
executing this step — **one file serves both call sites** (D-05). The §8 message table
|
|
6
|
+
(design §8) column is selected by this parameter.
|
|
7
|
+
|
|
8
|
+
**This file decides nothing.** The halt belongs to
|
|
9
|
+
`pp-core/workflows/execute-phase/steps/check-query-gate.md`, the single generic `check.query`
|
|
10
|
+
enforcer, which both call sites run *before* reading this file (GATE-02, D-VH-7). There is exactly
|
|
11
|
+
one halt implementation in the repo and this is not it. Deciding and rendering are separated on
|
|
12
|
+
purpose: if you are here because a gate did not stop a build, the enforcer is where to look. Do not
|
|
13
|
+
add a halt to this file.
|
|
14
|
+
|
|
15
|
+
Gated entirely on `.platform-bindings.json`: when no bindings file is present, this gate
|
|
16
|
+
is a **no-op** — fully inert for non-Pareto projects. The verdict is read from
|
|
17
|
+
`pp_run check vault.status --raw` — a pure manifest read that never fetches, never errors,
|
|
18
|
+
and degrades to `{"present":false}` on any problem.
|
|
7
19
|
|
|
8
20
|
```bash
|
|
9
21
|
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
VAULT_GATE_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**If `VAULT_GATE_PRESENT` != `true`:** skip entirely (non-Pareto project — fully inert). A probe
|
|
26
|
+
that cannot answer (a malformed bindings file exits 1) reads as present, as `hasPlatformBindings`
|
|
27
|
+
does (16-04 D-47 b), so the card renders the verdict instead of vanishing.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
VAULT_VERDICT=$(pp_run check vault.status --raw 2>/dev/null || echo '{"present":false}')
|
|
31
|
+
VAULT_STATUS=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).status||'miss');}catch{process.stdout.write('miss');}")
|
|
32
|
+
VAULT_TIER=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).tier||'');}catch{process.stdout.write('');}")
|
|
33
|
+
VAULT_FALLBACK_REASON=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).fallbackReason||'');}catch{process.stdout.write('');}")
|
|
34
|
+
VAULT_REPO=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const p=JSON.parse(d);process.stdout.write(p.repo||p.source||'');}catch{process.stdout.write('');}")
|
|
35
|
+
VAULT_REF=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).ref||'');}catch{process.stdout.write('');}")
|
|
36
|
+
VAULT_SHA=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const s=JSON.parse(d).resolvedSha||'';process.stdout.write(s.slice(0,8));}catch{process.stdout.write('');}")
|
|
37
|
+
VAULT_FILE_COUNT=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(JSON.parse(d).fileCount??0));}catch{process.stdout.write('0');}")
|
|
38
|
+
VAULT_STALE=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(String(!!JSON.parse(d).stale));}catch{process.stdout.write('false');}")
|
|
39
|
+
VAULT_FETCHED_AT=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{process.stdout.write(JSON.parse(d).fetchedAt||'');}catch{process.stdout.write('');}")
|
|
40
|
+
# 14-07 (D-21/D-25): the engine's operator message, relayed verbatim by the degraded row below — one text, one owner.
|
|
41
|
+
VAULT_MESSAGE=$(printf '%s' "$VAULT_VERDICT" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const m=JSON.parse(d).message;process.stdout.write(typeof m==='string'?m:'');}catch{process.stdout.write('');}")
|
|
42
|
+
# D-08-27: the CONFIGURED client coordinates, read straight from
|
|
43
|
+
# `.platform-bindings.json`. This is a PURE BINDINGS READ: the verdict envelope is
|
|
44
|
+
# deliberately not consulted for it, because with `vault.required` off the envelope
|
|
45
|
+
# carries no drift field by design (STRICT-01), and D-08-29 rules the drift fact is
|
|
46
|
+
# rendered as prose in this file rather than added as a new envelope key.
|
|
47
|
+
VAULT_BINDINGS_JSON=$(pp_run query platform-bindings --raw 2>/dev/null || echo '{}')
|
|
48
|
+
VAULT_CONFIGURED_REPO_SET=$(printf '%s' "$VAULT_BINDINGS_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const c=JSON.parse(d).client;process.stdout.write(c&&typeof c.vaultRepo==='string'&&c.vaultRepo.trim()?'true':'false');}catch{process.stdout.write('false');}")
|
|
49
|
+
# Mirrors `resolveClientCoordinates` (src/vault-coordinates.cts): `client.vaultRef`
|
|
50
|
+
# trimmed, or `main` when a client repo is configured with no ref, or empty when no
|
|
51
|
+
# client repo is configured at all — so the ref rendered below is the same one the
|
|
52
|
+
# verdict compares against. T-08-39: the value is project-authored, but it is
|
|
53
|
+
# interpolated into an agent's context, so bound it — charset `[A-Za-z0-9._/-]`,
|
|
54
|
+
# cut at 100 characters.
|
|
55
|
+
VAULT_CONFIGURED_REF=$(printf '%s' "$VAULT_BINDINGS_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const c=JSON.parse(d).client;const set=!!(c&&typeof c.vaultRepo==='string'&&c.vaultRepo.trim());const r=set?((typeof c.vaultRef==='string'?c.vaultRef.trim():'')||'main'):'';process.stdout.write(r.replace(/[^A-Za-z0-9._\/-]/g,'').slice(0,100));}catch{process.stdout.write('');}")
|
|
56
|
+
VAULT_CONFIGURED_REF_RAW=$(printf '%s' "$VAULT_BINDINGS_JSON" | node -e "const d=require('fs').readFileSync(0,'utf-8');try{const c=JSON.parse(d).client;const set=!!(c&&typeof c.vaultRepo==='string'&&c.vaultRepo.trim());const r=set?((typeof c.vaultRef==='string'?c.vaultRef.trim():'')||'main'):'';process.stdout.write(r);}catch{process.stdout.write('');}")
|
|
57
|
+
VAULT_CONFIGURED_REF_TEXT="${VAULT_CONFIGURED_REF:-(none configured)}"
|
|
58
|
+
# D-08-24 / D-08-29: ref drift — a client-tier cache whose ref is not the ref the
|
|
59
|
+
# bindings configure. With `VAULT_REQUIRED` on the enforcer already stopped the run
|
|
60
|
+
# for this state (`client_vault_ref_drifted_from_configured`, plan 08-01), so the
|
|
61
|
+
# warning this flag selects is reached only with the policy off — the schema default.
|
|
62
|
+
# D-08-39: these lines are a COPY of `resolveRefDrift`'s predicate (src/vault.cts) —
|
|
63
|
+
# the one consumer that cannot call the owner — and are PINNED against it by the
|
|
64
|
+
# agreement test in tests/vault-status-check.test.cjs, which extracts them BY PREFIX
|
|
65
|
+
# and runs them in bash and zsh. Compare the RAW ref: the charset bound above is a
|
|
66
|
+
# RENDERING bound, and applying it to a comparison input claimed drift on `v1.2.1+b`,
|
|
67
|
+
# a legal git ref, because the `+` was stripped before the test.
|
|
68
|
+
VAULT_REF_DRIFT=false
|
|
69
|
+
if [ "$VAULT_TIER" = "client" ] && [ "$VAULT_CONFIGURED_REPO_SET" = "true" ] && [ -n "$VAULT_REF" ] && [ "$VAULT_CONFIGURED_REF_RAW" != "$VAULT_REF" ]; then VAULT_REF_DRIFT=true; fi
|
|
70
|
+
# Compute human-readable cache age when stale
|
|
71
|
+
VAULT_AGE=""
|
|
72
|
+
if [ "$VAULT_STALE" = "true" ] && [ -n "$VAULT_FETCHED_AT" ]; then
|
|
73
|
+
VAULT_AGE=$(VAULT_FETCHED_AT="$VAULT_FETCHED_AT" node -e "const ms=Date.now()-new Date(process.env.VAULT_FETCHED_AT).getTime();const d=Math.floor(ms/86400000);const h=Math.floor((ms%86400000)/3600000);process.stdout.write(d>0?d+'d':h+'h');")
|
|
74
|
+
fi
|
|
75
|
+
# STRICT-03: the enforcement policy. Read once, here, through the same resolver
|
|
76
|
+
# every other read path uses. With it off this file behaves exactly as it always has.
|
|
77
|
+
VAULT_REQUIRED=$(pp_run query config-get vault.required --raw --default false 2>/dev/null || echo false)
|
|
78
|
+
# Detect --auto / --chain in host $ARGUMENTS. SURFACING ONLY — see below.
|
|
79
|
+
AUTO_MODE=false
|
|
80
|
+
if echo "${ARGUMENTS:-}" | grep -qE -- '--auto|--chain'; then AUTO_MODE=true; fi
|
|
16
81
|
```
|
|
17
82
|
|
|
18
|
-
|
|
19
|
-
|
|
83
|
+
Select message based on `VAULT_STATUS`, `VAULT_TIER`, and `VAULT_GATE_POINT`, then handle confirmation.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## What this file does under each policy setting (STRICT-03)
|
|
88
|
+
|
|
89
|
+
- **`VAULT_REQUIRED` is `false` or absent (the default).** Everything below behaves exactly as it
|
|
90
|
+
always has: surface the state message and require an explicit `confirmed`. The rendered text is
|
|
91
|
+
byte-identical to the pre-policy build for the same verdict, pinned by committed goldens in
|
|
92
|
+
`tests/vault-read-path.test.cjs`.
|
|
93
|
+
|
|
94
|
+
- **`VAULT_REQUIRED` is `true` and the verdict blocks.** You will not reach this file — the enforcer
|
|
95
|
+
has already stopped the run — unless an accepted attributed override suppressed the block; then
|
|
96
|
+
`status` may be `degraded` and the degraded row below applies (D-25). **There is deliberately
|
|
97
|
+
nothing to do here for those states.** Do not
|
|
98
|
+
add a halt, a warning or a prompt for them out of a sense of incompleteness; a second
|
|
99
|
+
implementation is exactly how two halts drift apart.
|
|
100
|
+
|
|
101
|
+
- **`VAULT_REQUIRED` is `true` and the verdict passes.** Render the success line for the point and
|
|
102
|
+
**skip the confirmation prompt**. A gate already enforced deterministically does not also need a
|
|
103
|
+
human to agree with it, and asking would train operators to press through gates — the habit this
|
|
104
|
+
phase exists to break.
|
|
105
|
+
|
|
106
|
+
Invocation mode never changes any of the above. `--auto` / `--chain` selects how the state is
|
|
107
|
+
surfaced, never whether the run proceeds. Mode-dependent gate behaviour was the precise shape of the
|
|
108
|
+
hole this phase closes, so it is stated here rather than left to be re-derived.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## §8 Message table — plan:pre column (`VAULT_GATE_POINT` == `plan`)
|
|
113
|
+
|
|
114
|
+
**`fetched` + `tier: client`:**
|
|
115
|
+
|
|
116
|
+
> Client vault loaded from `` `${VAULT_REPO}@${VAULT_REF}` `` (`` `${VAULT_FILE_COUNT}` `` files, sha `` `${VAULT_SHA}` ``). Planning will apply client standards.
|
|
117
|
+
|
|
118
|
+
When `VAULT_REQUIRED` is `true`, skip the confirmation.
|
|
119
|
+
Otherwise surface this message and require an explicit `confirmed` acknowledgment
|
|
120
|
+
(`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE).
|
|
121
|
+
|
|
122
|
+
**`fetched` + `tier: fallback` (intentionally more prominent — design §8):**
|
|
123
|
+
|
|
124
|
+
> ℹ Loaded a **fallback-tier** vault (tier `` `${VAULT_TIER}` ``), read from `` `${VAULT_REPO}@${VAULT_REF}` `` (`` `${VAULT_FILE_COUNT}` `` files) — *${VAULT_FALLBACK_REASON}*. The configured client ref `` `${VAULT_CONFIGURED_REF_TEXT}` `` did not load. Planning will apply this fallback vault's defaults, not the client's current standards. Proceed?
|
|
125
|
+
|
|
126
|
+
Surface this message and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). The fallback confirmation is deliberately more prominent than the client tier: proceeding on a fallback vault's defaults instead of the client's own standards is a state the user should consciously acknowledge. With `VAULT_REQUIRED` on, this state has already been stopped by the enforcer and is unreachable here. (D-08-27: the line names the tier that loaded and the ref that failed; it no longer calls the loaded vault "shared" or presents the client's own repo as evidence that a shared vault loaded. On this fork's ladder the fallback source can be the client repo itself at another ref, which is exactly how the retired wording read as a client-vault success.)
|
|
127
|
+
|
|
128
|
+
**`cache` (all tiers failed, using cached copy):**
|
|
129
|
+
|
|
130
|
+
> ⚠ Could not reach client or shared vault — using cached copy from `` `${VAULT_FETCHED_AT}` `` (tier `` `${VAULT_TIER}` ``, may be stale). Proceed?
|
|
131
|
+
|
|
132
|
+
Surface this warning and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). With `VAULT_REQUIRED` on, a stale cache has already been stopped by the enforcer; a TTL-fresh cache passes and is rendered without a prompt.
|
|
133
|
+
|
|
134
|
+
**`miss` (no vault reachable, no cached copy):**
|
|
135
|
+
|
|
136
|
+
> ⚠ No vault reachable (client or shared) and no cached copy. Planning will proceed WITHOUT vault context.
|
|
137
|
+
|
|
138
|
+
Surface this warning and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). With `VAULT_REQUIRED` on, this state has already been stopped by the enforcer and is unreachable here.
|
|
139
|
+
|
|
140
|
+
**`degraded`** (the vault loaded but its `INDEX.md` is missing, unparseable, empty or names no file in the fetched tree, or the engine could not check it at all (`vault_index_unchecked`: the cached manifest records no file list, or the check faulted — this says nothing about `INDEX.md` itself); `check vault.status` sets `status: degraded` and a `reason` of the form `vault_index_*`):
|
|
141
|
+
|
|
142
|
+
Print `${VAULT_MESSAGE}` verbatim — the engine built it; do not restate or paraphrase it here. With `VAULT_REQUIRED` false it is the degraded-index warning, naming `INDEX.md`, what was measured and the repair: surface it and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). With `VAULT_REQUIRED` true this row is reachable only after an accepted attributed override (`PP_VAULT_OVERRIDE_REASON`) suppressed the block at the enforcer (D-25), and the message is the override notice naming the suppressed `vault_index_*` reason (or the block text naming `INDEX.md`, if the override reached only the enforcer's own call): print it and the envelope's `override` record when present, and continue without a prompt — the override is the acknowledgment and is already recorded in the envelope the enforcer acted on. Planning proceeds without a usable index; the `## INDEX` section of `.planning/vault/SUMMARY.md` names the degradation.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## §8 Message table — execute:pre column (`VAULT_GATE_POINT` == `execute`)
|
|
147
|
+
|
|
148
|
+
Dispatched from `pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md` after the enforcer.
|
|
149
|
+
Until this phase, nothing dispatched this column at all — it was written and never read.
|
|
150
|
+
|
|
151
|
+
**`fetched` + `tier: client`:**
|
|
152
|
+
|
|
153
|
+
> Client vault present from `` `${VAULT_REPO}@${VAULT_REF}` `` (`` `${VAULT_FILE_COUNT}` `` files, sha `` `${VAULT_SHA}` ``). Build will apply client patterns.
|
|
154
|
+
|
|
155
|
+
When `VAULT_REQUIRED` is `true`, skip the confirmation.
|
|
156
|
+
Otherwise surface this message and require an explicit `confirmed` acknowledgment
|
|
157
|
+
(`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE).
|
|
158
|
+
|
|
159
|
+
**`fetched` + `tier: fallback` (intentionally more prominent — design §8):**
|
|
160
|
+
|
|
161
|
+
> ℹ Using a **fallback-tier** vault (tier `` `${VAULT_TIER}` ``), read from `` `${VAULT_REPO}@${VAULT_REF}` `` (`` `${VAULT_FILE_COUNT}` `` files, sha `` `${VAULT_SHA}` ``) — *${VAULT_FALLBACK_REASON}*. The configured client ref `` `${VAULT_CONFIGURED_REF_TEXT}` `` did not load. Build applies this fallback vault's defaults, not the client's current patterns. Proceed?
|
|
162
|
+
|
|
163
|
+
Surface this message and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). The fallback confirmation is deliberately more prominent than the client tier. With `VAULT_REQUIRED` on, this state has already been stopped by the enforcer and is unreachable here. (D-08-27: the line names the tier that loaded and the ref that failed; it no longer calls the loaded vault "shared" or presents the client's own repo as evidence that a shared vault loaded. On this fork's ladder the fallback source can be the client repo itself at another ref, which is exactly how the retired wording read as a client-vault success.)
|
|
164
|
+
|
|
165
|
+
**`cache` (all tiers failed, using cached copy):**
|
|
166
|
+
|
|
167
|
+
> ⚠ Could not reach client or shared vault — using cached copy from `` `${VAULT_FETCHED_AT}` `` (tier `` `${VAULT_TIER}` ``, `` `${VAULT_FILE_COUNT}` `` files, sha `` `${VAULT_SHA}` ``, may be stale). Proceed?
|
|
168
|
+
|
|
169
|
+
Surface this warning and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). With `VAULT_REQUIRED` on, a stale cache has already been stopped by the enforcer; a TTL-fresh cache passes and is rendered without a prompt.
|
|
170
|
+
|
|
171
|
+
**`miss` (no vault reachable, no cached copy):**
|
|
172
|
+
|
|
173
|
+
> ⚠ No vault reachable (client or shared) and no cached copy. Build will proceed WITHOUT vault context.
|
|
174
|
+
|
|
175
|
+
Surface this warning and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). With `VAULT_REQUIRED` on, this state has already been stopped by the enforcer and is unreachable here.
|
|
176
|
+
|
|
177
|
+
**`degraded`** (the vault loaded but its `INDEX.md` is missing, unparseable, empty or names no file in the fetched tree, or the engine could not check it at all (`vault_index_unchecked`: the cached manifest records no file list, or the check faulted — this says nothing about `INDEX.md` itself); `check vault.status` sets `status: degraded` and a `reason` of the form `vault_index_*`):
|
|
178
|
+
|
|
179
|
+
Print `${VAULT_MESSAGE}` verbatim — the engine built it; do not restate or paraphrase it here. With `VAULT_REQUIRED` false it is the degraded-index warning, naming `INDEX.md`, what was measured and the repair: surface it and require an explicit `confirmed` acknowledgment (`AskUserQuestion`, or a typed `confirmed` in TEXT_MODE). With `VAULT_REQUIRED` true this row is reachable only after an accepted attributed override (`PP_VAULT_OVERRIDE_REASON`) suppressed the block at the enforcer (D-25), and the message is the override notice naming the suppressed `vault_index_*` reason (or the block text naming `INDEX.md`, if the override reached only the enforcer's own call): print it and the envelope's `override` record when present, and continue without a prompt — the override is the acknowledgment and is already recorded in the envelope the enforcer acted on. Build proceeds without a usable index; the `## INDEX` section of `.planning/vault/SUMMARY.md` names the degradation.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Ref drift (policy off) — applies to the client-tier arm of both columns
|
|
184
|
+
|
|
185
|
+
When `VAULT_REF_DRIFT` is `true`, append the following paragraph to the selected client-tier
|
|
186
|
+
message before surfacing it (D-08-24, rendered here per D-08-29 so the JSON envelope stays
|
|
187
|
+
byte-identical with the policy off):
|
|
188
|
+
|
|
189
|
+
> ⚠ Ref drift: this cache was fetched from `` `${VAULT_REF}` `` but `.platform-bindings.json` configures `` `client.vaultRef: ${VAULT_CONFIGURED_REF}` `` — what loaded is not the configured ref, so the standards applied may not be the client's current ones. With `vault.required` off this is a warning; run `pp-tools vault fetch` to load the configured ref, then re-run `pp-tools check vault.status`.
|
|
190
|
+
|
|
191
|
+
Reachability: with `VAULT_REQUIRED` on, drift has already stopped the run at the enforcer
|
|
192
|
+
(`client_vault_ref_drifted_from_configured`, plan 08-01), so this paragraph is reached **only**
|
|
193
|
+
with the policy off — which is the schema default, and therefore the state most operators are in.
|
|
194
|
+
The facts it names come from `VAULT_CONFIGURED_REF` (a `.platform-bindings.json` read) and
|
|
195
|
+
`VAULT_REF` (an existing envelope field); no new envelope key is consulted, and none is added.
|
|
196
|
+
Appending this paragraph is the only change the client-tier arms take — their success wording is
|
|
197
|
+
unchanged, because plan 08-02's router success line is the gate SURFACE for a clean pass (D-08-20)
|
|
198
|
+
and a second rendering of it here would be a copy that can drift.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Stale suffix (applies to all statuses)
|
|
203
|
+
|
|
204
|
+
When `VAULT_STALE` is `true`, append ` (cache is \`${VAULT_AGE}\` old)` to the selected message before surfacing it to the user.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Surfacing mode (`--auto` / `--chain`)
|
|
209
|
+
|
|
210
|
+
When `AUTO_MODE` is `true`: **do not display an interactive prompt.** Print the selected message
|
|
211
|
+
(including the stale suffix when applicable) as a log line, so a non-interactive pipeline run leaves
|
|
212
|
+
an auditable record of the load state that was in effect.
|
|
213
|
+
|
|
214
|
+
This is a **presentation choice only**. It does not decide anything: whether the run proceeds was
|
|
215
|
+
settled by the enforcer before this file was read, identically in interactive and unattended runs.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Confirmation (interactive mode)
|
|
220
|
+
|
|
221
|
+
When `AUTO_MODE` is `false` and `VAULT_REQUIRED` is `false`:
|
|
222
|
+
|
|
223
|
+
Use `AskUserQuestion` to surface the selected message and await `confirmed`. In TEXT_MODE (`--text` in `$ARGUMENTS`), replace `AskUserQuestion` with a plain-text prompt and accept a typed `confirmed`. With the policy off the gate is advisory: proceeding after `confirmed` (or with a timeout / empty input) is allowed, exactly as before.
|
|
20
224
|
|
|
21
|
-
|
|
225
|
+
When `VAULT_REQUIRED` is `true`, do not prompt at all — see the policy section above.
|
|
22
226
|
|
|
23
|
-
|
|
24
|
-
|---|---|
|
|
25
|
-
| `none` | The vault is disabled — continue silently. |
|
|
26
|
-
| `proceed` | The client vault is loaded — show the card and continue. |
|
|
27
|
-
| `confirm` | Degraded and `vault.required` is false. Interactive: ask for an explicit `confirmed` (`AskUserQuestion`; in TEXT_MODE — `--text` in `$ARGUMENTS` — a plain-text prompt accepting a typed `confirmed`), then continue. With `--auto` / `--chain` in `$ARGUMENTS`: log the card and continue without prompting. |
|
|
28
|
-
| `stop` | The fence already exited non-zero: `vault.required` is true and the client vault is not loaded. **STOP** in every mode (including `--auto` / `--chain`); the operator fixes what the card names and re-runs. |
|
|
227
|
+
`TEXT_MODE` follows the same rule as the host workflow (`--text` in `$ARGUMENTS`): replace `AskUserQuestion` with a plain-text numbered prompt.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# `verify:post` capability gate dispatch — enforce, do not decorate (TGATE-02, D-05)
|
|
2
|
+
|
|
3
|
+
Extracted from `execute-phase.md` so the host loop body keeps shrinking, per ADR-857 Phase 6.
|
|
4
|
+
`steps/` files are outside `tests/workflow-size-baseline.json` by design; the host is not, and the
|
|
5
|
+
phase-6 ratchet on the host is a ratchet-DOWN that is never re-baselined. This is the second
|
|
6
|
+
application of that pattern in this workflow — `steps/execute-pre-dispatch.md` is the first — and it
|
|
7
|
+
is the mechanism rather than a workaround: every inline candidate for this body measured over the
|
|
8
|
+
host's remaining headroom, while the labelled Read the host gained costs under a hundred bytes.
|
|
9
|
+
|
|
10
|
+
This step is dispatched from two callers: `execute-phase.md`'s end-of-phase verification step, and
|
|
11
|
+
`verify-work.md`'s issues-are-zero branch. Each Reads it immediately after it has
|
|
12
|
+
`VERIFY_POST_HOOKS_JSON` in scope. It exists because **rendering is not enforcing**: every
|
|
13
|
+
`verify:post` call site in this repository resolved STEP-kind hooks only, so a gate declared at this
|
|
14
|
+
point would have rendered and been silently ignored — the decorative-dispatch defect already found
|
|
15
|
+
once at the execute-side point. This file is what makes the declaration enforce.
|
|
16
|
+
|
|
17
|
+
Read the `activeHooks` array from the printed `VERIFY_POST_HOOKS_JSON=` line, in-context (do NOT pipe it through a
|
|
18
|
+
shell parser).
|
|
19
|
+
|
|
20
|
+
**Admission first (D-28, D-05).** Both callers Read this file unconditionally right after rendering
|
|
21
|
+
`verify:post`, so this is where the strict point refuses a config the gate could not read. It
|
|
22
|
+
precedes the inert branch below because a broken config renders no gate and would take that branch.
|
|
23
|
+
Without `.platform-bindings.json` it is inert and prints nothing.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
27
|
+
VAULT_ADMIT_PRESENT=$(pp_run query platform-bindings --pick present 2>/dev/null || echo true)
|
|
28
|
+
VAULT_ADMIT=inert
|
|
29
|
+
if [ "$VAULT_ADMIT_PRESENT" = true ]; then VAULT_ADMIT=$(pp_run vault admission 2>/dev/null || echo unrecognised); fi
|
|
30
|
+
case "$VAULT_ADMIT" in on) echo "VAULT_ADMIT=on" ;; off|inert) : ;; *) pp_run vault admission --explain; echo "PARETO_GATE_BLOCKED:vault.admission"; exit 1 ;; esac
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**If `activeHooks` is empty or absent, or contains no entry where `kind == "gate"` and a
|
|
34
|
+
`check.query` is present:** skip the rest of this file silently. Print nothing. This branch comes
|
|
35
|
+
first deliberately — it is what keeps a project with no active `verify:post` capability
|
|
36
|
+
structurally inert, and a gate paragraph reached before it would fire on a project that never
|
|
37
|
+
enabled the capability at all.
|
|
38
|
+
|
|
39
|
+
The predicate tests BOTH conditions, which is the predicate `ship.md`'s generic loop tests. Until
|
|
40
|
+
04-12 it tested only the first, and that looser form was **harmless rather than correct**: an entry
|
|
41
|
+
carrying no `check.query` would have reached the enforcer, whose own loop filters it out — so the
|
|
42
|
+
right behaviour came from another file's guard rather than from this one's, and no predicate gate
|
|
43
|
+
is declared at this point today that would have exposed the difference. A guard that is correct by
|
|
44
|
+
another file's behaviour is one edit away from being wrong.
|
|
45
|
+
|
|
46
|
+
**Otherwise, dispatch the gate.** Set:
|
|
47
|
+
|
|
48
|
+
- `GATE_POINT=verify:post` — the loop point label, message text only.
|
|
49
|
+
- `GATE_HOOKS_JSON=$VERIFY_POST_HOOKS_JSON` — the payload the host already rendered above; this
|
|
50
|
+
file never renders hooks itself.
|
|
51
|
+
- `GATE_POSITIONAL_ARG="${PHASE_DIR}"` — the phase directory, which the host already has in scope
|
|
52
|
+
on the line after the render.
|
|
53
|
+
- `GATE_UNPAIRED=strict` — **but only on a FULL-PHASE run: when the host's `WAVE_FILTER` is unset
|
|
54
|
+
AND this invocation carried no `--gaps-only`.** On a full-phase run this is one of the two points
|
|
55
|
+
at which every plan must already have an executed counterpart, so a
|
|
56
|
+
plan artifact with no summary counterpart is a real defect here rather than the normal
|
|
57
|
+
mid-lifecycle state it is at `plan:post` and `execute:wave:post`. When **either** of those
|
|
58
|
+
partial-execution modes is active, do not set `GATE_UNPAIRED` at all.
|
|
59
|
+
|
|
60
|
+
Four facts a future editor needs before touching this condition (04-12, CR-04; 04-18, WR-01).
|
|
61
|
+
The condition is on **partial execution**, not on one flag — read all four before adding a fifth
|
|
62
|
+
mode, because two of these were written when the condition named `WAVE_FILTER` alone and the
|
|
63
|
+
omission of the other mode shipped a halt onto a supported command. **One:** a
|
|
64
|
+
`--wave N` run legitimately leaves every later-wave plan holding a PLAN.md and no SUMMARY.md —
|
|
65
|
+
that is what the mode is *for*, and the host sets `WAVE_FILTER` from `--wave N` during argument
|
|
66
|
+
parsing, filters the plan list on it, and runs a wave-safety check on it, all before this point.
|
|
67
|
+
**Two:** this step runs **before** the host's `handle_partial_wave_execution` step, which is the
|
|
68
|
+
step that exists precisely to handle an incomplete phase — so an unconditional strict verdict
|
|
69
|
+
here is a false halt on a supported execution mode reached *ahead of* the code that would have
|
|
70
|
+
explained it, and the gate is declared `blocking: true` / `onError: halt`, so the user cannot
|
|
71
|
+
advance to the next wave at all. **Three:** the `ship:pre` caller is deliberately **not**
|
|
72
|
+
conditioned the same way and must not be "harmonised" with this one. Its preflight requires
|
|
73
|
+
`verification.status == passed` before its gate loop is reachable, so a ship run is by
|
|
74
|
+
construction a full-phase run: it has no partial mode and therefore no false halt to fix. The
|
|
75
|
+
asymmetry between the two strict points is the whole finding.
|
|
76
|
+
|
|
77
|
+
**Four:** `--gaps-only` is a partial-execution mode in exactly the sense `--wave N` is, and the
|
|
78
|
+
original condition did not cover it — the prose here even asserted `--wave N` was "the third
|
|
79
|
+
mid-lifecycle case", which made a complete-sounding enumeration out of an incomplete one. The
|
|
80
|
+
host filters the plan list on `--gaps-only` on the same line it filters on `WAVE_FILTER`, so a
|
|
81
|
+
gaps-only run legitimately leaves every **non**-gap plan holding a PLAN.md and no SUMMARY.md.
|
|
82
|
+
With the condition keyed to `WAVE_FILTER` alone, such a run reached this gate with `WAVE_FILTER`
|
|
83
|
+
unset, set `GATE_UNPAIRED=strict`, and drew `coverage_pair_missing` / `block: true` on those
|
|
84
|
+
untouched plans — a hard halt, on a `blocking: true` / `onError: halt` hook, that the user cannot
|
|
85
|
+
advance past. And `handle_partial_wave_execution` does not rescue it twice over: it guards on
|
|
86
|
+
`WAVE_FILTER` only, and it runs *after* this point regardless. Worst of all, the host documents
|
|
87
|
+
`/pp:execute-phase {X} --gaps-only` (with the workstream flag) as the gap-closure cycle's **own**
|
|
88
|
+
command — so the halt landed on the documented remedy for a failed verification, i.e. on the next
|
|
89
|
+
pass of the very loop this gate serves. The workstream placeholder is deliberately NOT written out
|
|
90
|
+
here: this file emits no command, and the token carries a substitution obligation that only a file
|
|
91
|
+
which does emit one should take on.
|
|
92
|
+
|
|
93
|
+
**Why this is keyed to the flag and not to a plan count.** `--wave N` is bound to a named host
|
|
94
|
+
variable; `--gaps-only` is not — the host's argument parsing says it "keeps its current meaning"
|
|
95
|
+
and every later reference is to the flag by name. So the flag is the only form available at this
|
|
96
|
+
point, and using it matches how the host itself tests this mode. Keying instead on "no incomplete
|
|
97
|
+
plans remain" would be a better invariant and would cover the *next* mode without an edit, but
|
|
98
|
+
that count does not exist yet here: the post-execution plan index is computed inside
|
|
99
|
+
`handle_partial_wave_execution`, one step **later**. Moving the count earlier is a host edit, and
|
|
100
|
+
the host is under a ratchet-down byte budget this file exists to protect. If a future change puts
|
|
101
|
+
an incomplete-plan count in scope at this point, prefer it to both flags and delete this fact.
|
|
102
|
+
|
|
103
|
+
The tolerant path is reached by leaving `GATE_UNPAIRED` **unset**, never by setting it to the
|
|
104
|
+
literal `tolerant`. The enforcer emits this parameter as a two-expansion flag pair specifically so
|
|
105
|
+
an unset variable contributes no word at all and the command line stays byte-identical to the one
|
|
106
|
+
that shipped before the parameter existed. An editor who writes the tolerant literal instead gets
|
|
107
|
+
a command line that works for a different reason — its equivalence to the unset case is the
|
|
108
|
+
router's tolerance of an explicit value, not the enforcer's contract — and the next parameter
|
|
109
|
+
added to that line inherits the confusion.
|
|
110
|
+
|
|
111
|
+
Set **no** `GATE_WS`: a phase **directory** already carries the workstream partition — the
|
|
112
|
+
enforcer's own parameter table says so — so the flag is neither needed nor set. Set **no**
|
|
113
|
+
`GATE_STAGE`: this point validates BOTH stages, exactly as the `execute:post` caller deliberately
|
|
114
|
+
omits it, and that omission is the documented invariant rather than an oversight to tidy up later.
|
|
115
|
+
|
|
116
|
+
**A caller exposing neither partial-execution mode is a full-phase run by construction, and therefore
|
|
117
|
+
strict.** The condition above is written for the host, which binds `WAVE_FILTER` during argument
|
|
118
|
+
parsing and parses `--gaps-only`. `/pp-verify-work` binds neither, so both of its tests are vacuously
|
|
119
|
+
satisfied and the strict value applies on every one of its runs. That is the right verdict rather
|
|
120
|
+
than an accident of the wording: a UAT pass is a whole-phase judgement, and it is the point at which
|
|
121
|
+
that workflow marks the phase complete in ROADMAP.md and STATE.md, so a plan artifact with no
|
|
122
|
+
executed counterpart is exactly the defect the strict value exists to catch — not the normal
|
|
123
|
+
mid-lifecycle state it is at `plan:post` and `execute:wave:post`. The obligation that places on the
|
|
124
|
+
next caller: if one ever grows a partial-execution mode, it owes this condition the same two tests
|
|
125
|
+
the host already answers, written here rather than assumed at the call site.
|
|
126
|
+
|
|
127
|
+
Then Read and follow `pp-core/workflows/execute-phase/steps/check-query-gate.md` — the single
|
|
128
|
+
generic `check.query` gate enforcer (GATE-02, D-VH-7). **It owns the halt decision**, the two-step
|
|
129
|
+
contract and the `message` passthrough. Do not restate its loop here, and do not interpret its
|
|
130
|
+
verdict here: a third enforcement loop is precisely what that enforcer was created to prevent, and
|
|
131
|
+
two were already collapsed into it.
|
|
132
|
+
|
|
133
|
+
**After the enforcer returns, certify the phase's end anchors — on a full-phase run only.** The
|
|
134
|
+
enforcer halts on a blocking verdict, so this paragraph is reached only when it let the run go on;
|
|
135
|
+
nothing here assumes the phase is clean from that. On a full-phase run (the same two tests that set
|
|
136
|
+
`GATE_UNPAIRED=strict` above: `WAVE_FILTER` unset and no `--gaps-only`), run
|
|
137
|
+
`pp_run vault anchor certify "${PHASE_DIR}" --raw`, print its one-line result, and continue
|
|
138
|
+
regardless. On a `--wave N` or `--gaps-only` run, skip it: the verb's own strict check would block on
|
|
139
|
+
the plans not yet run, so it could only refuse `not-clean`. The verb re-runs
|
|
140
|
+
`check vault.trace "${PHASE_DIR}" --unpaired strict` itself and records each SUMMARY's end anchor in
|
|
141
|
+
the directory's `.vault-certificates.json` only when that check answers clean with no override, so
|
|
142
|
+
this step can never certify what the gate did not pass (D-107, D-108). A skipped or failed step
|
|
143
|
+
certifies nothing, which only widens the next phase's SEAL-01 reach and never hides a commit (D-110).
|
|
144
|
+
The verb prints nothing unless `vault admission` answers `on` (no `.platform-bindings.json`, or the
|
|
145
|
+
vault off), so print nothing yourself then either. Every result (a certificate, `kept`, or a
|
|
146
|
+
refusal such as `not-clean` or `overridden`) is printed, never retried, and never halts. No bash fence: this file carries one
|
|
147
|
+
launcher preamble, in the admission fence, and this step needs no second one.
|