@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/agents/pp-code-fixer.md +9 -16
- package/agents/pp-code-reviewer.md +2 -78
- package/agents/pp-codebase-mapper.md +3 -0
- package/agents/pp-debugger.md +3 -2
- package/agents/pp-executor.md +25 -30
- package/agents/pp-integration-checker.md +1 -0
- package/agents/pp-phase-researcher.md +3 -1
- package/agents/pp-plan-checker.md +3 -0
- package/agents/pp-planner.md +16 -12
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-auditor.md +1 -0
- package/agents/pp-ui-researcher.md +3 -1
- package/agents/pp-vault-reviewer.md +43 -189
- package/agents/pp-verifier.md +10 -8
- package/commands/pp-add-tests.md +1 -1
- package/commands/pp-debug.md +1 -0
- package/commands/pp-init.md +4 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-quick.md +3 -0
- package/commands/pp-review-backlog.md +2 -1
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -7
- package/gemini-extension.json +1 -1
- package/package.json +3 -6
- package/pp-core/bin/lib/capability-registry.cjs +15 -357
- package/pp-core/bin/lib/capability-state.cjs +15 -79
- package/pp-core/bin/lib/capability-writer.cjs +2 -16
- package/pp-core/bin/lib/check-command-router.cjs +342 -2298
- package/pp-core/bin/lib/clusters.cjs +2 -2
- package/pp-core/bin/lib/commands.cjs +79 -4
- package/pp-core/bin/lib/compose-bindings.cjs +99 -16
- package/pp-core/bin/lib/config.cjs +3 -6
- package/pp-core/bin/lib/discovery-rollup.cjs +9 -6
- package/pp-core/bin/lib/discovery-screens.cjs +20 -2
- package/pp-core/bin/lib/discovery-sweep.cjs +12 -3
- package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
- package/pp-core/bin/lib/git-base-branch.cjs +10 -13
- package/pp-core/bin/lib/git-provider.cjs +5 -6
- package/pp-core/bin/lib/init-contract.cjs +55 -42
- package/pp-core/bin/lib/init-interactive.cjs +6 -20
- package/pp-core/bin/lib/io.cjs +2 -8
- package/pp-core/bin/lib/loop-resolver.cjs +8 -32
- package/pp-core/bin/lib/org-profile.cjs +16 -92
- package/pp-core/bin/lib/vault-command-router.cjs +174 -1206
- package/pp-core/bin/lib/vault-coordinates.cjs +4 -4
- package/pp-core/bin/lib/vault-index.cjs +44 -74
- package/pp-core/bin/lib/vault-seal.cjs +37 -122
- 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 +15 -16
- package/pp-core/bin/lib/vault-trace.cjs +40 -3016
- package/pp-core/bin/lib/vault-update.cjs +5 -26
- package/pp-core/bin/lib/vault.cjs +122 -316
- package/pp-core/bin/pp-tools.cjs +15 -36
- package/pp-core/org-profile.json +1 -1
- package/pp-core/references/autonomous-best-effort.md +4 -3
- package/pp-core/references/autonomous-lifecycle.md +1 -0
- package/pp-core/references/autonomous-smart-discuss.md +5 -1
- package/pp-core/references/discovery-id-conventions.md +35 -2
- package/pp-core/references/execute-mvp-tdd.md +3 -3
- package/pp-core/references/git-integration.md +57 -33
- package/pp-core/references/git-planning-commit.md +31 -9
- package/pp-core/references/phase-argument-parsing.md +1 -0
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-revision.md +3 -1
- package/pp-core/references/planner-tdd.md +4 -4
- package/pp-core/references/planner-vault-standards.md +16 -25
- package/pp-core/references/planning-config.md +3 -3
- package/pp-core/references/shell-portability.md +28 -0
- package/pp-core/references/tdd.md +20 -16
- package/pp-core/references/worktree-path-safety.md +1 -0
- package/pp-core/templates/README.md +1 -1
- package/pp-core/templates/VAULT-REVIEW.md +24 -170
- package/pp-core/templates/milestone.md +5 -5
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-tests.md +8 -5
- package/pp-core/workflows/add-todo.md +3 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -1
- package/pp-core/workflows/audit-fix.md +7 -6
- package/pp-core/workflows/audit-milestone.md +1 -0
- package/pp-core/workflows/autonomous.md +6 -0
- package/pp-core/workflows/check-todos.md +2 -1
- package/pp-core/workflows/cleanup.md +3 -1
- package/pp-core/workflows/code-review-fix.md +15 -8
- package/pp-core/workflows/code-review.md +16 -58
- package/pp-core/workflows/complete-milestone.md +13 -3
- package/pp-core/workflows/debug.md +4 -2
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/prototype.md +2 -2
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +1 -1
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +51 -7
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +5 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +10 -8
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +11 -2
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +5 -5
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +3 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +14 -2
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +10 -10
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +11 -8
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +5 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +9 -3
- package/pp-core/workflows/discovery-sprint/vault-context.md +17 -12
- package/pp-core/workflows/discovery-sprint.md +11 -8
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +1 -0
- package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
- package/pp-core/workflows/discuss-phase.md +8 -3
- package/pp-core/workflows/docs-update.md +1 -0
- package/pp-core/workflows/eval-review.md +2 -1
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +13 -14
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +5 -25
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +18 -6
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +6 -109
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +1 -17
- package/pp-core/workflows/execute-phase.md +21 -12
- package/pp-core/workflows/execute-plan.md +24 -15
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/forensics.md +4 -2
- package/pp-core/workflows/health.md +1 -0
- package/pp-core/workflows/help/modes/full.md +1 -1
- package/pp-core/workflows/import.md +2 -1
- package/pp-core/workflows/ingest-docs.md +1 -0
- package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
- package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
- package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
- package/pp-core/workflows/init.md +56 -66
- package/pp-core/workflows/map-codebase.md +5 -1
- package/pp-core/workflows/milestone-summary.md +2 -1
- package/pp-core/workflows/mvp-phase.md +1 -0
- package/pp-core/workflows/new-milestone.md +9 -5
- package/pp-core/workflows/new-project.md +9 -8
- package/pp-core/workflows/new-workspace.md +1 -0
- package/pp-core/workflows/next.md +2 -1
- package/pp-core/workflows/pause-work.md +6 -4
- package/pp-core/workflows/plan-milestone-gaps.md +3 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
- package/pp-core/workflows/plan-phase.md +21 -6
- package/pp-core/workflows/plan-review-convergence.md +2 -0
- package/pp-core/workflows/plant-seed.md +4 -2
- package/pp-core/workflows/pr-branch.md +2 -0
- package/pp-core/workflows/progress.md +5 -0
- package/pp-core/workflows/quick.md +19 -12
- package/pp-core/workflows/reapply-patches.md +3 -0
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/resume-project.md +3 -0
- package/pp-core/workflows/review.md +7 -1
- package/pp-core/workflows/secure-phase.md +2 -1
- package/pp-core/workflows/session-report.md +1 -0
- package/pp-core/workflows/ship.md +8 -1
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +3 -2
- package/pp-core/workflows/spec-phase.md +2 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +4 -3
- package/pp-core/workflows/sync-skills.md +5 -2
- package/pp-core/workflows/thread.md +3 -2
- package/pp-core/workflows/transition.md +6 -0
- package/pp-core/workflows/ui-phase.md +3 -1
- package/pp-core/workflows/ui-review.md +2 -1
- package/pp-core/workflows/ultraplan-phase.md +1 -0
- package/pp-core/workflows/undo.md +14 -10
- package/pp-core/workflows/update.md +5 -0
- package/pp-core/workflows/validate-phase.md +9 -6
- package/pp-core/workflows/vault-review.md +65 -186
- package/pp-core/workflows/vault-write-back.md +78 -110
- package/pp-core/workflows/verify-phase.md +5 -0
- package/pp-core/workflows/verify-work.md +9 -2
- package/scripts/gen-capability-registry.cjs +12 -93
- package/scripts/lint-test-file-count.allowlist.json +1 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +1 -4
- package/scripts/sync-runtime-launcher.cjs +115 -5
- package/pp-core/bin/lib/practices-review-check.cjs +0 -944
- package/pp-core/bin/lib/vault-audit.cjs +0 -2594
- package/pp-core/bin/lib/vault-mode.cjs +0 -235
- package/pp-core/bin/lib/vault-practices.cjs +0 -2057
- package/pp-core/bin/lib/vault-review.cjs +0 -2102
- package/pp-core/bin/lib/vault-write-back.cjs +0 -905
- package/pp-core/references/planner-vault-practices.md +0 -95
- package/pp-core/references/practice-arithmetic-contract.md +0 -121
- package/pp-core/references/verifier-practice-verdicts.md +0 -263
- package/pp-core/templates/practices/README.md +0 -248
- 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/execute-phase/steps/verifier-practice-payload.md +0 -165
- package/scripts/gen-practice-arithmetic-contract.cjs +0 -412
- package/scripts/practice-arithmetic-contract.allow.json +0 -35
- package/scripts/record-vault-mode-golden.cjs +0 -89
- package/scripts/vault-calibrate.cjs +0 -996
|
@@ -39,14 +39,14 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
40
|
const childProcess = require("node:child_process");
|
|
41
41
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
42
|
-
// CR-04 (03-11):
|
|
43
|
-
// way `cmdVaultTrace` in src/check-command-router.cts does. project-root.cjs is a
|
|
42
|
+
// CR-04 (03-11): this router's path arms resolve their containment boundary the
|
|
43
|
+
// same way `cmdVaultTrace` in src/check-command-router.cts does. project-root.cjs is a
|
|
44
44
|
// leaf (node builtins only), so this adds no cycle.
|
|
45
45
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
46
46
|
const projectRootMod = require("./project-root.cjs");
|
|
47
47
|
const { findProjectRoot } = projectRootMod;
|
|
48
48
|
// 15-05: the plan-identifier form moved into the vault-standards leaf (one
|
|
49
|
-
// definition shared by the
|
|
49
|
+
// definition shared by the standards verbs and the gate).
|
|
50
50
|
// That leaf requires only vault-index, which imports nothing, so no cycle.
|
|
51
51
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
52
|
const vaultStandardsLeaf = require("./vault-standards.cjs");
|
|
@@ -68,18 +68,7 @@ const VAULT_SUBCOMMANDS = [
|
|
|
68
68
|
'status',
|
|
69
69
|
'probe',
|
|
70
70
|
'summary',
|
|
71
|
-
'practices',
|
|
72
71
|
'org-profile',
|
|
73
|
-
'trace',
|
|
74
|
-
// 05-06 (CHECK-03): the mechanical practice audit. Added HERE and nowhere
|
|
75
|
-
// else — the header comment above, the dispatch chain and the
|
|
76
|
-
// unknown-subcommand message all derive from this list, which is exactly the
|
|
77
|
-
// three-place drift 02-02 removed.
|
|
78
|
-
'audit',
|
|
79
|
-
// 260915-ik2 (UAT-6-F1): the phase-manifest writer the execute-phase step
|
|
80
|
-
// files call — `start` at execute:pre, `record` at verify:post. Added HERE
|
|
81
|
-
// and nowhere else, for the same three-place reason as `audit` above.
|
|
82
|
-
'manifest',
|
|
83
72
|
// PBI 74316 (fork-only): move the project to another client vault version.
|
|
84
73
|
'update',
|
|
85
74
|
// 15-07 (D-06, D-13): the standards record verbs, `block` and `consulted`.
|
|
@@ -93,28 +82,11 @@ const VAULT_SUBCOMMANDS = [
|
|
|
93
82
|
'door-record',
|
|
94
83
|
];
|
|
95
84
|
/**
|
|
96
|
-
* The
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* disagree. The values themselves are the frozen contract's
|
|
100
|
-
* (`docs/vault-trace-format.md` § The freeze contract) — this list restates the
|
|
101
|
-
* two stage NAMES the CLI accepts, and nothing else about the schema.
|
|
85
|
+
* The standards verbs (15-07), same discipline: the verb check and the message
|
|
86
|
+
* naming the permitted verbs both derive from this list. `promote` writes to
|
|
87
|
+
* the client vault rather than a record, and answers its own refusals.
|
|
102
88
|
*/
|
|
103
|
-
const
|
|
104
|
-
/**
|
|
105
|
-
* The trace verbs, same discipline (Phase 03-05).
|
|
106
|
-
*/
|
|
107
|
-
const TRACE_VERBS = ['scaffold', 'render'];
|
|
108
|
-
/**
|
|
109
|
-
* The manifest verbs, same discipline (260915-ik2): the verb check and the
|
|
110
|
-
* operator-facing message that names the permitted verbs both derive from it.
|
|
111
|
-
*/
|
|
112
|
-
const MANIFEST_VERBS = ['start', 'record'];
|
|
113
|
-
/**
|
|
114
|
-
* The standards record verbs (15-07), same discipline: the verb check and the
|
|
115
|
-
* message naming the permitted verbs both derive from this list.
|
|
116
|
-
*/
|
|
117
|
-
const STANDARDS_VERBS = ['block', 'consulted'];
|
|
89
|
+
const STANDARDS_VERBS = ['block', 'consulted', 'promote'];
|
|
118
90
|
/**
|
|
119
91
|
* The anchor verbs (16-06), same discipline: the verb check and the message
|
|
120
92
|
* naming the permitted verbs both derive from this list.
|
|
@@ -133,10 +105,10 @@ const DOOR_RECORD_FLAGS = ['--door', '--artifact', '--phase'];
|
|
|
133
105
|
*/
|
|
134
106
|
const STANDARDS_PLAN_READ_BYTES = 1024 * 1024;
|
|
135
107
|
/**
|
|
136
|
-
* The plan-identifier shape `vault
|
|
108
|
+
* The plan-identifier shape `vault standards block --plan` accepts (04-14,
|
|
137
109
|
* TGATE-01, CR-03). Defined in `src/vault-standards.cts` since 15-05 and
|
|
138
110
|
* re-exported from here under the same name; the rationale stays here, beside
|
|
139
|
-
* the verb that
|
|
111
|
+
* the verb that uses it.
|
|
140
112
|
*
|
|
141
113
|
* ONE FORM, READ AND WRITTEN. `filenamePlanId` in `src/check-command-router.cts`
|
|
142
114
|
* extracts `<this form>-PLAN.md` / `-SUMMARY.md` from an artifact's basename,
|
|
@@ -166,19 +138,12 @@ const STANDARDS_PLAN_READ_BYTES = 1024 * 1024;
|
|
|
166
138
|
*/
|
|
167
139
|
const SCAFFOLD_PLAN_ID_RE = vaultStandardsLeaf.SCAFFOLD_PLAN_ID_RE;
|
|
168
140
|
/**
|
|
169
|
-
* The required form, in ONE wording
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* `capabilities/vault-practices/capability.json` (04-14 T2).
|
|
173
|
-
*
|
|
174
|
-
* It is a constant rather than three sentences because a form stated three ways
|
|
175
|
-
* is three forms (threat T-4-55). The test asserts the sentence extracted
|
|
176
|
-
* independently from each document is EQUAL to the other's and to this value,
|
|
177
|
-
* rather than asserting each document merely mentions a form.
|
|
141
|
+
* The required form, in ONE wording: the `vault standards block` rejection
|
|
142
|
+
* message (04-14 T2). A constant so the refusal and the test that pins it read
|
|
143
|
+
* one sentence (threat T-4-55).
|
|
178
144
|
*
|
|
179
145
|
* ASCII only, and no backticks: the same bytes have to survive a JSON string, a
|
|
180
|
-
* markdown paragraph and a terminal
|
|
181
|
-
* copy differ from the other two.
|
|
146
|
+
* markdown paragraph and a terminal.
|
|
182
147
|
*/
|
|
183
148
|
const SCAFFOLD_PLAN_ID_FORM = "The plan identifier must be the plan's phase-and-number identifier, exactly the " +
|
|
184
149
|
'prefix of its artifact filename (for example 04-02), not a bare plan number, a task ' +
|
|
@@ -187,23 +152,12 @@ const SCAFFOLD_PLAN_ID_FORM = "The plan identifier must be the plan's phase-and-
|
|
|
187
152
|
* The quick-task half of the form (16-05, D-29), appended to both rejection
|
|
188
153
|
* messages after `SCAFFOLD_PLAN_ID_FORM`.
|
|
189
154
|
*
|
|
190
|
-
* A
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* (`agents/pp-executor.md`). Rewording it would re-record golden rows Phase 16
|
|
195
|
-
* may not move. Its sentence is also already true of a quick task — "exactly
|
|
196
|
-
* the prefix of its artifact filename" — so only the refusal, the one surface
|
|
197
|
-
* a caller reads after being told no, needs to name the quick form.
|
|
155
|
+
* A second constant rather than an edit to the first: the first sentence is
|
|
156
|
+
* already true of a quick task — "exactly the prefix of its artifact filename"
|
|
157
|
+
* — so only the refusal, the one surface a caller reads after being told no,
|
|
158
|
+
* needs to name the quick form.
|
|
198
159
|
*/
|
|
199
160
|
const SCAFFOLD_QUICK_ID_FORM = 'A quick task uses its quick-task id instead, the same filename prefix (for example 261011-8yt).';
|
|
200
|
-
/**
|
|
201
|
-
* Bound on the bounded prefix read the `trace render` arm performs. The trace
|
|
202
|
-
* lives in the artifact's FRONTMATTER, so a head read is not a compromise — it
|
|
203
|
-
* is the exact region the parser needs, and a SUMMARY.md body of any size can
|
|
204
|
-
* never make this arm read more.
|
|
205
|
-
*/
|
|
206
|
-
const TRACE_ARTIFACT_READ_BYTES = 262_144;
|
|
207
161
|
/** Repo-relative artifact paths, long enough for a workstream phase tree. */
|
|
208
162
|
const TRACE_REFUSAL_PATH_CAP = 200;
|
|
209
163
|
/**
|
|
@@ -227,103 +181,20 @@ function boundedPath(value) {
|
|
|
227
181
|
: collapsed;
|
|
228
182
|
}
|
|
229
183
|
/**
|
|
230
|
-
* The gate an override recorded by
|
|
184
|
+
* The gate an override recorded by `vault standards block` suppresses (04-06, SC4).
|
|
231
185
|
*
|
|
232
186
|
* `vault.status` is not a name invented here: it is the `check.query` the
|
|
233
187
|
* blocking vault gate is declared under at both write points
|
|
234
|
-
* (`capabilities/vault/capability.json`), and it is the
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
188
|
+
* (`capabilities/vault/capability.json`), and it is the gate the override
|
|
189
|
+
* line names (`docs/vault-standards-mode.md` § Gates, "The override").
|
|
190
|
+
* Declared once so the emitted record and the documented contract cannot
|
|
191
|
+
* drift apart. Its one owner is the standards leaf
|
|
238
192
|
* (15 D-35), because the standards judgement accepts an override line only
|
|
239
193
|
* under this gate name, and a second literal could drift from the first.
|
|
240
194
|
*/
|
|
241
195
|
const TRACE_OVERRIDE_GATE = vaultStandardsLeaf.STANDARDS_OVERRIDE_GATE;
|
|
242
|
-
/**
|
|
243
|
-
* A `gitCommit` digest: lowercase hex at sha1 or sha256 length. 16-06 (D-35)
|
|
244
|
-
* re-binds it to the seal leaf's `ANCHOR_SHA_RE`: a per-file digest and a
|
|
245
|
-
* record's start/end anchors are the same shape, so they have one owner. The
|
|
246
|
-
* local name stays, as an alias, so this file's readers keep their wording.
|
|
247
|
-
*/
|
|
248
|
-
const GIT_COMMIT_DIGEST_RE = vaultSeal.ANCHOR_SHA_RE;
|
|
249
196
|
// Default CoreModule implementation. _core seam overrides this for test injection.
|
|
250
197
|
const _defaultCore = { output: io.output };
|
|
251
|
-
function tracePracticesActive(cwd) {
|
|
252
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
253
|
-
const { isPracticesGateActive } = require('./vault.cjs');
|
|
254
|
-
return isPracticesGateActive(cwd);
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* 16-09 (SEAL-02, D-39) — the `vault audit` verb's answer when the practices
|
|
258
|
-
* switch is off, or `null` for "print nothing" (the pre-16-09 behaviour).
|
|
259
|
-
*
|
|
260
|
-
* It asks what `check vault.audit` asks, in the gate's order (D-25), through
|
|
261
|
-
* the same readers: bindings first (no `.platform-bindings.json` never holds
|
|
262
|
-
* records, D-37/D-42); standards mode active answers nothing (the C-1 cell);
|
|
263
|
-
* the phase argument must resolve (the same `resolveTracePhaseDir`, so an
|
|
264
|
-
* argument error still prints nothing, T-03-43); then the leaf's
|
|
265
|
-
* `collectRecordedArtifacts` and `disabledWithArtifactsVerdict`, whose message
|
|
266
|
-
* is the gate's message byte for byte. Which switch is off is decided by the
|
|
267
|
-
* gate readers, never a raw config read. It blocks nothing itself: an operator
|
|
268
|
-
* surface, exit 0. There is no override.
|
|
269
|
-
*
|
|
270
|
-
* From the REAL leaf, not the `_vault` seam: a governance decision must not be
|
|
271
|
-
* switchable by a test mock (the `tracePracticesActive` rule above).
|
|
272
|
-
*/
|
|
273
|
-
function disabledWithArtifactsRefusal(cwd, args) {
|
|
274
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
275
|
-
const v = require('./vault.cjs');
|
|
276
|
-
let root;
|
|
277
|
-
try {
|
|
278
|
-
root = findProjectRoot(cwd) || cwd;
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
root = cwd;
|
|
282
|
-
}
|
|
283
|
-
if (!v.hasPlatformBindings(root))
|
|
284
|
-
return null;
|
|
285
|
-
if (v.isStandardsModeActive(cwd))
|
|
286
|
-
return null;
|
|
287
|
-
// The audit arm's own positional rule (below): the first token that is not a
|
|
288
|
-
// flag and not the `--range` value.
|
|
289
|
-
const auditArgs = args.slice(2);
|
|
290
|
-
const rangeAt = auditArgs.indexOf('--range');
|
|
291
|
-
const phaseArg = auditArgs.find((a, i) => !a.startsWith('--') && !(rangeAt !== -1 && i === rangeAt + 1));
|
|
292
|
-
if (phaseArg === undefined)
|
|
293
|
-
return null;
|
|
294
|
-
const phase = v.resolveTracePhaseDir(root, cwd, phaseArg);
|
|
295
|
-
if (!phase)
|
|
296
|
-
return null;
|
|
297
|
-
const whichSwitch = v.isVaultGateActive(root) ? 'vault.practices_enabled' : 'vault.enabled';
|
|
298
|
-
let mode;
|
|
299
|
-
if (whichSwitch === 'vault.practices_enabled') {
|
|
300
|
-
const m = v.vaultModeVerdict(cwd);
|
|
301
|
-
if (m.active && m.ok === true && m.mode === 'practices')
|
|
302
|
-
mode = 'practices';
|
|
303
|
-
}
|
|
304
|
-
const verdict = vaultSeal.disabledWithArtifactsVerdict(vaultSeal.collectRecordedArtifacts(phase.abs, { root }), {
|
|
305
|
-
switch: whichSwitch,
|
|
306
|
-
...(mode ? { mode } : {}),
|
|
307
|
-
phaseRel: phase.rel,
|
|
308
|
-
});
|
|
309
|
-
if (!verdict)
|
|
310
|
-
return null;
|
|
311
|
-
return { status: 'disabled_with_artifacts', block: true, present: true, ...verdict };
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* The value token immediately following each of `flags`, by index, so a
|
|
315
|
-
* flag's value is never mistaken for a positional argument. Mirrors the
|
|
316
|
-
* `--expect` handling in the `practices promote` arm.
|
|
317
|
-
*/
|
|
318
|
-
function flagValueIndices(argv, flags) {
|
|
319
|
-
const indices = new Set();
|
|
320
|
-
for (const flag of flags) {
|
|
321
|
-
const i = argv.indexOf(flag);
|
|
322
|
-
if (i !== -1)
|
|
323
|
-
indices.add(i + 1);
|
|
324
|
-
}
|
|
325
|
-
return indices;
|
|
326
|
-
}
|
|
327
198
|
/**
|
|
328
199
|
* Render a short plain-text `vault org-profile` report from its structured
|
|
329
200
|
* shape — one line naming the pointer, one naming the resolution, a
|
|
@@ -387,73 +258,37 @@ function renderOrgProfileReport(report) {
|
|
|
387
258
|
return lines.join('\n');
|
|
388
259
|
}
|
|
389
260
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
390
|
-
function routeVaultCommand({ args, cwd, raw, error, _vault, _core,
|
|
261
|
+
function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _exec, _orgProfile, _coordinates, _provider, _update, }) {
|
|
391
262
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
392
263
|
const vault = _vault ?? require('./vault.cjs');
|
|
393
264
|
const c = _core ?? _defaultCore;
|
|
394
265
|
const subcommand = args[1];
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
// for ten arms rather than ten copies that a new arm could forget; the test
|
|
398
|
-
// `D-15 sweep` in tests/vault-mode.test.cjs fails if the chain below and
|
|
399
|
-
// VAULT_SUBCOMMANDS ever disagree, so no arm can dodge this guard.
|
|
400
|
-
// - Keyed on VAULT_SUBCOMMANDS: an unknown subcommand never reads the mode
|
|
401
|
-
// and keeps its own message below.
|
|
402
|
-
// - The read is a config read; its one possible spawn is loadConfig's memoised, fixed-argument
|
|
403
|
-
// git check-ignore, so T-24-08b's typo never reaches the `--changed` git call. On an invalid-mode
|
|
404
|
-
// project this refusal now precedes an arm's unknown-flag error.
|
|
405
|
-
// - `vaultModeVerdict` answers `{ active: false }` without reading the mode
|
|
406
|
-
// when the gate is off, so a vault-off project is byte-identical (golden).
|
|
407
|
-
// - From the REAL leaf, not the `_vault` seam, like `isInsideDir` and
|
|
408
|
-
// `resolveTracePhaseDir` below: a governance decision must not be
|
|
409
|
-
// switchable by a test mock.
|
|
410
|
-
// - The message is the leaf's fixed text: it names both allowed values and
|
|
411
|
-
// never echoes the configured one (T-13-03).
|
|
412
|
-
// - Quick 261015-ect (QD-11): only where `.platform-bindings.json` exists. A
|
|
413
|
-
// project without it never reads the mode (T-16-13): admission's own step
|
|
414
|
-
// 1, read the same way, and the order every `check` vault handler keeps
|
|
415
|
-
// (bindings-inert first, D-22). The 1.16.0 release note stated the old
|
|
416
|
-
// order as measured; this reverses it, and closes `/pp-fast`'s backstop,
|
|
417
|
-
// which asks `vault admission` with no bindings probe of its own.
|
|
266
|
+
// From the REAL leaf, not the `_vault` seam: a governance answer must not be
|
|
267
|
+
// switchable by a test mock.
|
|
418
268
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
419
|
-
const
|
|
420
|
-
if (VAULT_SUBCOMMANDS.includes(subcommand) && modeSeam.hasPlatformBindings(cwd)) {
|
|
421
|
-
const modeVerdict = modeSeam.vaultModeVerdict(cwd);
|
|
422
|
-
if (modeVerdict.active && modeVerdict.ok === false) {
|
|
423
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
424
|
-
const leaf = require('./vault-mode.cjs');
|
|
425
|
-
// 13-REVIEW WR-01: a resolver failure is refused like an invalid value,
|
|
426
|
-
// under its own typed reason.
|
|
427
|
-
const reason = modeVerdict.reason ?? '';
|
|
428
|
-
error(leaf.vaultModeRefusalMessage(reason), reason === leaf.VAULT_MODE_REASON.UNRESOLVED ? ERROR_REASON.VAULT_MODE_UNRESOLVED : ERROR_REASON.VAULT_MODE_INVALID);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
269
|
+
const indexSeam = require('./vault.cjs');
|
|
432
270
|
if (subcommand === 'fetch') {
|
|
433
271
|
c.output(vault.vaultFetch(cwd), raw);
|
|
434
272
|
}
|
|
435
273
|
else if (subcommand === 'status') {
|
|
436
|
-
// 13-06 (D-10): `mode: standards` LAST, in standards mode only; the same
|
|
437
|
-
// object otherwise, so a practices-mode envelope gains no key (golden).
|
|
438
274
|
// 14-05 (D-11/D-14): the same five flat fields `check vault.status` carries,
|
|
439
275
|
// from the one state function, so the report and the gate cannot tell
|
|
440
|
-
// different stories (UAT F6). From the REAL module (`
|
|
276
|
+
// different stories (UAT F6). From the REAL module (`indexSeam`), not the
|
|
441
277
|
// injectable `vault` seam: a governance answer must not be switchable by a
|
|
442
278
|
// test mock (13-06 precedent). No `block` and no `reason`: this is a report.
|
|
443
|
-
// The state is null
|
|
279
|
+
// The state is null with the vault off, so the envelope gains no key.
|
|
444
280
|
const base = vault.vaultStatus(cwd);
|
|
445
|
-
const indexState =
|
|
281
|
+
const indexState = indexSeam.computeVaultIndexState(cwd);
|
|
446
282
|
const withIndex = indexState !== null && base['present'] === true
|
|
447
|
-
? { ...base, ...
|
|
283
|
+
? { ...base, ...indexSeam.vaultIndexEnvelopeFields(indexState) }
|
|
448
284
|
: base;
|
|
449
|
-
c.output(
|
|
285
|
+
c.output(withIndex, raw);
|
|
450
286
|
}
|
|
451
287
|
else if (subcommand === 'probe') {
|
|
452
288
|
// STRICT-02: pure reachability for the client vault, the shared fallback and
|
|
453
289
|
// the platform repo — no clone, no cache mutation.
|
|
454
290
|
//
|
|
455
|
-
// T-02-07: reject any unrecognized `--` token BEFORE anything spawns
|
|
456
|
-
// the `practices match` posture below verbatim. A typo must never reach a
|
|
291
|
+
// T-02-07: reject any unrecognized `--` token BEFORE anything spawns. A typo must never reach a
|
|
457
292
|
// subprocess. The host (pp-core/bin/pp-tools.cjs) already splices out its own
|
|
458
293
|
// global flags before dispatch, which is why the recognized set is one wide.
|
|
459
294
|
const RECOGNIZED_PROBE_FLAGS = new Set(['--raw']);
|
|
@@ -487,150 +322,10 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
487
322
|
c.output(summary.index, false);
|
|
488
323
|
}
|
|
489
324
|
}
|
|
490
|
-
else if (subcommand === 'practices') {
|
|
491
|
-
const verb = args[2];
|
|
492
|
-
if (verb === 'match') {
|
|
493
|
-
// Quick 261015-at3 (QD-02, todo practices-record-verbs-print-without-
|
|
494
|
-
// bindings): unless `vault admission` answers `on` this arm prints
|
|
495
|
-
// nothing, FIRST — before the flag guard and before the mode read. It was
|
|
496
|
-
// gated by no switch at all (measured 261014-na1: it printed a block on
|
|
497
|
-
// `vault.enabled: false`), so a record no gate reads could be produced on
|
|
498
|
-
// a project whose `check ship.practices` answers inert.
|
|
499
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
500
|
-
const owner = require('./vault.cjs');
|
|
501
|
-
if (owner.vaultProducerAdmissionVerdict(cwd).state !== 'on')
|
|
502
|
-
return;
|
|
503
|
-
// Quick 261015-ect (QD-14, at3 verifier W-1): in practices mode, silent
|
|
504
|
-
// where `check ship.practices` answers inert — the practices switch off —
|
|
505
|
-
// as the trace, audit and manifest arms already are: a block no gate
|
|
506
|
-
// reads is not produced. Standards mode keeps D-22's answer below on
|
|
507
|
-
// either switch, because the load-gate card relays that line as its
|
|
508
|
-
// standards-first clause (15-11) and it records no practice.
|
|
509
|
-
if (!modeSeam.isStandardsModeActive(cwd) && !tracePracticesActive(cwd))
|
|
510
|
-
return;
|
|
511
|
-
// T-24-08b: reject any unrecognized `--`-prefixed argument BEFORE the
|
|
512
|
-
// `--changed` git call (so a typo never spawns the diff) instead
|
|
513
|
-
// of silently filtering it out of the positional file list. Matched on
|
|
514
|
-
// the WHOLE token so `--files=…` is caught by the same check as
|
|
515
|
-
// `--files value`. DECISION (recorded, not deferred): `--files=` is
|
|
516
|
-
// NOT implemented as a comma-splitting alias here, unlike
|
|
517
|
-
// `/pp-code-review`'s `--files=a,b` (pp-core/workflows/code-review.md).
|
|
518
|
-
// The auditor marked the alias "optional and secondary"; the required
|
|
519
|
-
// behavior is loud failure, and the error message below names the
|
|
520
|
-
// supported forms instead. The host (pp-core/bin/pp-tools.cjs) already
|
|
521
|
-
// splices out its own global flags (--json-errors/--cwd/--ws/--raw/
|
|
522
|
-
// --pick/--default) before dispatch, which is why the recognized set
|
|
523
|
-
// here is only two flags wide.
|
|
524
|
-
const RECOGNIZED_MATCH_FLAGS = new Set(['--changed', '--raw', '--block']);
|
|
525
|
-
const unrecognizedFlag = args.slice(3).find((a) => a.startsWith('--') && !RECOGNIZED_MATCH_FLAGS.has(a));
|
|
526
|
-
if (unrecognizedFlag !== undefined) {
|
|
527
|
-
error(`Unrecognized flag "${unrecognizedFlag}" for vault practices match. Supported: positional file paths, --changed, --raw, or --block.`, ERROR_REASON.SDK_FAIL_FAST);
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
// 13-07 (D-12, PARK-01): in standards mode the matcher has no practice
|
|
531
|
-
// layer to read, so this arm answers not-applicable — one line naming the
|
|
532
|
-
// mode, or `--raw`'s envelope with NO match-set key, exit 0. AFTER the
|
|
533
|
-
// flag guard (a typo still fails first, T-24-08b) and BEFORE the
|
|
534
|
-
// `--changed` git call (a parked path spawns no diff, T-13-12). `--block`
|
|
535
|
-
// is non-raw, so it prints the same line and no `<vault_practices>` block.
|
|
536
|
-
// There is deliberately NO activation guard on this branch: the arm
|
|
537
|
-
// answers not-applicable whenever the vault gate is active, even with
|
|
538
|
-
// `vault.practices_enabled` false (D-22; QD-14 above gates practices mode
|
|
539
|
-
// only). The text is the leaf's.
|
|
540
|
-
if (modeSeam.isStandardsModeActive(cwd)) {
|
|
541
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
542
|
-
const { notApplicableMatchEnvelope } = require('./vault-mode.cjs');
|
|
543
|
-
const naMatch = notApplicableMatchEnvelope();
|
|
544
|
-
c.output(naMatch, true, raw ? JSON.stringify(naMatch) : naMatch.message);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
548
|
-
const practices = _practices ?? require('./vault-practices.cjs');
|
|
549
|
-
const runGit = _exec ?? shell_command_projection_cjs_1.execGitLocal;
|
|
550
|
-
let files;
|
|
551
|
-
if (args.includes('--changed')) {
|
|
552
|
-
const gitResult = runGit(['diff', '--name-only', 'HEAD'], { cwd });
|
|
553
|
-
if (gitResult.exitCode !== 0) {
|
|
554
|
-
error('Could not determine changed files: git diff failed (WR-02)', ERROR_REASON.SDK_FAIL_FAST);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
files = gitResult.stdout.split('\n').map((f) => f.trim()).filter((f) => f.length > 0);
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
files = args.slice(3).filter((a) => !a.startsWith('--'));
|
|
561
|
-
}
|
|
562
|
-
const result = practices.matchPractices({ cwd, files });
|
|
563
|
-
if (args.includes('--block')) {
|
|
564
|
-
// T-25-08: emit the finished <vault_practices> block so the planner
|
|
565
|
-
// embeds it VERBATIM. The planner never renames a field or applies a
|
|
566
|
-
// fence itself — that transformation used to be prose-only, which is
|
|
567
|
-
// what left the phase's prompt-injection mitigation unenforced.
|
|
568
|
-
c.output(result, true, practices.renderPracticesBlock(result));
|
|
569
|
-
}
|
|
570
|
-
else if (raw) {
|
|
571
|
-
c.output(result, true, JSON.stringify(result));
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
c.output(result, true, practices.renderPracticesReport(result));
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
else if (verb === 'promote') {
|
|
578
|
-
// T-29-16: reject any unrecognized `--`-prefixed argument BEFORE the
|
|
579
|
-
// lazy require of the write-back module and before any subprocess can
|
|
580
|
-
// spawn — mirrors the `match` branch's T-24-08b guard exactly. Matched
|
|
581
|
-
// on the WHOLE token so `--name=value` is caught by the same check as
|
|
582
|
-
// `--name value`. The recognized set holds exactly three flags — no
|
|
583
|
-
// flag that disables, bypasses, or relaxes any guard exists.
|
|
584
|
-
const RECOGNIZED_PROMOTE_FLAGS = new Set(['--confirmed', '--expect', '--raw']);
|
|
585
|
-
const promoteArgs = args.slice(3);
|
|
586
|
-
const unrecognizedPromoteFlag = promoteArgs.find((a) => a.startsWith('--') && !RECOGNIZED_PROMOTE_FLAGS.has(a));
|
|
587
|
-
if (unrecognizedPromoteFlag !== undefined) {
|
|
588
|
-
error(`Unrecognized flag "${unrecognizedPromoteFlag}" for vault practices promote. Supported: --confirmed, --expect <token>, --raw.`, ERROR_REASON.SDK_FAIL_FAST);
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
// Positional file path: the first token that is not `--`-prefixed and
|
|
592
|
-
// is not the value immediately following `--expect`.
|
|
593
|
-
const expectFlagIdx = promoteArgs.indexOf('--expect');
|
|
594
|
-
const expectValueIdx = expectFlagIdx !== -1 ? expectFlagIdx + 1 : -1;
|
|
595
|
-
const filePath = promoteArgs.find((a, i) => !a.startsWith('--') && i !== expectValueIdx);
|
|
596
|
-
if (filePath === undefined) {
|
|
597
|
-
error('No practice file was given. Usage: vault practices promote <file> [--confirmed --expect <token>] [--raw].', ERROR_REASON.SDK_MISSING_ARG);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
const confirmed = promoteArgs.includes('--confirmed');
|
|
601
|
-
const expect = expectValueIdx !== -1 ? promoteArgs[expectValueIdx] : undefined;
|
|
602
|
-
if (confirmed && (expect === undefined || expect.startsWith('--'))) {
|
|
603
|
-
error('This practice was not pushed: the confirmation is missing the preview token it needs. ' +
|
|
604
|
-
'Run the promotion without confirming first, then pass the token it prints.', ERROR_REASON.VAULT_PROMOTE_CONCURRENT_EDIT);
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
608
|
-
const writeBack = _writeBack ?? require('./vault-write-back.cjs');
|
|
609
|
-
const verdict = writeBack.promotePractice({ cwd, filePath, confirmed, expect });
|
|
610
|
-
// Emit the structured verdict BEFORE any error call — a refusal still
|
|
611
|
-
// carries its verdict to stdout. This ordering is the whole point of
|
|
612
|
-
// the uniform D-16 refusal contract and is what lets a wrapper
|
|
613
|
-
// workflow render results without scraping prose.
|
|
614
|
-
if (raw) {
|
|
615
|
-
c.output(verdict, true, JSON.stringify(verdict));
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
c.output(verdict, true, writeBack.renderPromoteReport(verdict));
|
|
619
|
-
}
|
|
620
|
-
if (verdict.status === 'refused') {
|
|
621
|
-
// An action command's refusal must be observable in the exit code —
|
|
622
|
-
// do not adopt the ship gate's always-exit-zero convention here.
|
|
623
|
-
error(verdict.message ?? 'This practice was not promoted.', verdict.reason ?? ERROR_REASON.UNKNOWN);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
else {
|
|
627
|
-
error('Unknown practices subcommand. Available: match, promote', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
325
|
else if (subcommand === 'org-profile') {
|
|
631
326
|
// T-33-13: reject any unrecognized `--`-prefixed argument BEFORE any
|
|
632
|
-
// lazy require or resolution work
|
|
633
|
-
//
|
|
327
|
+
// lazy require or resolution work (T-24-08b), so a typo can never trigger
|
|
328
|
+
// filesystem work.
|
|
634
329
|
const RECOGNIZED_ORG_PROFILE_FLAGS = new Set(['--raw']);
|
|
635
330
|
const unrecognizedOrgProfileFlag = args
|
|
636
331
|
.slice(2)
|
|
@@ -645,8 +340,6 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
645
340
|
const coordinates = _coordinates ?? require('./vault-coordinates.cjs');
|
|
646
341
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
647
342
|
const providerModule = _provider ?? require('./git-provider.cjs');
|
|
648
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
649
|
-
const { VAULT_MODE_CONFIG_KEY: vaultModeKey } = require('./vault-mode.cjs');
|
|
650
343
|
const resolution = orgProfile.resolveOrgProfile(cwd);
|
|
651
344
|
// Resolve the EFFECTIVE values by calling the real resolvers, so the
|
|
652
345
|
// report shows what actually won rather than what the profile offered.
|
|
@@ -677,14 +370,6 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
677
370
|
// report cannot say the profile lost while the gate enforces it. It is
|
|
678
371
|
// the policy only; with `vault.enabled` off no gate exists to apply it.
|
|
679
372
|
[orgProfile.VAULT_REQUIRED_CONFIG_KEY]: String(orgProfile.resolveVaultRequired(cwd).required),
|
|
680
|
-
// An invalid PROJECT mode reads as the typed reason
|
|
681
|
-
// (`vault_mode_invalid`), never the value: the project's invalid value
|
|
682
|
-
// did win over the profile, and the report says so without repeating
|
|
683
|
-
// operator text (T-13-03).
|
|
684
|
-
[vaultModeKey]: (() => {
|
|
685
|
-
const r = orgProfile.resolveVaultMode(cwd);
|
|
686
|
-
return r.ok ? r.mode : r.reason;
|
|
687
|
-
})(),
|
|
688
373
|
};
|
|
689
374
|
// Build `effective`'s key list from ORG_PROFILE_ALLOWLIST rather than a
|
|
690
375
|
// fresh literal array, so a future allowlist change surfaces here
|
|
@@ -741,615 +426,6 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
741
426
|
c.output(report, true, renderOrgProfileReport(report));
|
|
742
427
|
}
|
|
743
428
|
}
|
|
744
|
-
else if (subcommand === 'trace') {
|
|
745
|
-
// ── The practice-trace producer (Phase 03-05, TRACE-01/02/03) ──
|
|
746
|
-
//
|
|
747
|
-
// D-01's split, mechanised: the CLI owns everything that must be PROVABLE
|
|
748
|
-
// — the pinned `ref`, the `resolvedSha` and the considered practice-ID set
|
|
749
|
-
// — and the agent writes only the judgement half (`status` + `reason`).
|
|
750
|
-
// "Provable" cannot rest on an agent transcribing a forty-character SHA
|
|
751
|
-
// correctly, and a well-formedness gate by definition cannot catch a
|
|
752
|
-
// wrong-but-well-formed SHA.
|
|
753
|
-
//
|
|
754
|
-
// BOTH arms below are pass-throughs to `./vault-trace.cjs`. They collect
|
|
755
|
-
// arguments, call through, and emit. They hold NO envelope logic, NO glob
|
|
756
|
-
// pass and NO second read of the manifest: every provable field is the
|
|
757
|
-
// leaf module's business, and duplicating any of it here would create a
|
|
758
|
-
// second place where the trace can disagree with the gate that validates
|
|
759
|
-
// it.
|
|
760
|
-
//
|
|
761
|
-
// NEITHER ARM WRITES A FILE, and that is a requirement rather than a
|
|
762
|
-
// convenience not yet added. The producer emits text for an agent to embed
|
|
763
|
-
// in the single write it is already performing. A CLI that patched a
|
|
764
|
-
// plan's frontmatter would have to round-trip it through the shared
|
|
765
|
-
// serializer, which this repository documents as "a deliberately lossy
|
|
766
|
-
// serializer — it cannot faithfully re-emit nested object-list items" —
|
|
767
|
-
// so the same write that added the trace would destroy the plan's own
|
|
768
|
-
// must-haves block (T-03-23). If a direct write is ever wanted it must be a
|
|
769
|
-
// raw text splice on the delimited region, and that is not this phase's
|
|
770
|
-
// business. Do not add the convenience.
|
|
771
|
-
//
|
|
772
|
-
// ORDERING, and why it is this way round: the sub-verb check comes first,
|
|
773
|
-
// so a mistyped sub-verb is refused with the usage error, bindings or not,
|
|
774
|
-
// and produces no record (quick 261015-ect, QD-12 — measured and pinned).
|
|
775
|
-
// Then the admission verdict and the capability gate (quick 261015-at3 put
|
|
776
|
-
// the admission line ahead of the gate), ahead of the flag allowlist. The
|
|
777
|
-
// two orders both satisfy T-03-25 (nothing is required and nothing spawns
|
|
778
|
-
// before an unknown flag is rejected), because the gate is a bounded config
|
|
779
|
-
// read, not a subprocess. Only this order also satisfies D-04's
|
|
780
|
-
// non-negotiable clause — for every VALID sub-verb an inert project's
|
|
781
|
-
// output is byte-identical, on every flag path including a mistyped one
|
|
782
|
-
// (T-03-26).
|
|
783
|
-
const verb = args[2];
|
|
784
|
-
const traceArgs = args.slice(3);
|
|
785
|
-
if (verb !== 'scaffold' && verb !== 'render') {
|
|
786
|
-
error(`Unknown trace subcommand. Available: ${TRACE_VERBS.join(', ')}`, ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
// Quick 261015-at3: the admission verdict precedes the capability gate, as
|
|
790
|
-
// in `routeVaultAnchor` — no `.platform-bindings.json` means `check
|
|
791
|
-
// vault.trace` answers inert, so no trace may be produced either.
|
|
792
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
793
|
-
const owner = require('./vault.cjs');
|
|
794
|
-
if (owner.vaultProducerAdmissionVerdict(cwd).state !== 'on')
|
|
795
|
-
return;
|
|
796
|
-
if (!tracePracticesActive(cwd))
|
|
797
|
-
return;
|
|
798
|
-
if (verb === 'scaffold') {
|
|
799
|
-
const RECOGNIZED_SCAFFOLD_FLAGS = new Set(['--stage', '--plan', '--anchors-dir', '--raw']);
|
|
800
|
-
const unrecognizedScaffoldFlag = traceArgs.find((a) => a.startsWith('--') && !RECOGNIZED_SCAFFOLD_FLAGS.has(a));
|
|
801
|
-
if (unrecognizedScaffoldFlag !== undefined) {
|
|
802
|
-
error(`Unrecognized flag "${unrecognizedScaffoldFlag}" for vault trace scaffold. ` +
|
|
803
|
-
`Supported: positional file paths, --stage <${TRACE_STAGES.join('|')}>, --plan <id>, ` +
|
|
804
|
-
'--anchors-dir <dir> (execute stage), --raw.', ERROR_REASON.SDK_FAIL_FAST);
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
// Quick 261015-ect (QD-15): parked in standards mode, beside `practices
|
|
808
|
-
// match` (PARK-01) and `vault audit` (PARK-02). A standards-mode plan
|
|
809
|
-
// carries no practices ledger: `check ship.practices`, `check
|
|
810
|
-
// vault.audit` and `check vault.review` answer not_applicable here, and
|
|
811
|
-
// the standards `check vault.trace` reads `<vault_standards>` blocks, not
|
|
812
|
-
// this ledger. After admission, the practices gate and this verb's flag
|
|
813
|
-
// allowlist (a typo still fails first, as in the match and audit arms),
|
|
814
|
-
// before any read; one fixed line from the leaf, exit 0, nothing written.
|
|
815
|
-
if (modeSeam.isStandardsModeActive(cwd)) {
|
|
816
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
817
|
-
const { notApplicableTraceVerbEnvelope } = require('./vault-mode.cjs');
|
|
818
|
-
const naTrace = notApplicableTraceVerbEnvelope();
|
|
819
|
-
c.output(naTrace, true, raw ? JSON.stringify(naTrace) : naTrace.message);
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
const valueIndices = flagValueIndices(traceArgs, ['--stage', '--plan', '--anchors-dir']);
|
|
823
|
-
const stageIdx = traceArgs.indexOf('--stage');
|
|
824
|
-
const stage = stageIdx !== -1 ? traceArgs[stageIdx + 1] : undefined;
|
|
825
|
-
// A missing stage and an invalid one share a reason deliberately: both
|
|
826
|
-
// are "this invocation cannot be interpreted", and splitting them would
|
|
827
|
-
// make the operator read two messages to learn the same two words.
|
|
828
|
-
if (stage === undefined || !TRACE_STAGES.includes(stage)) {
|
|
829
|
-
error(`Missing or unrecognized --stage value for vault trace scaffold. ` +
|
|
830
|
-
`Supported: ${TRACE_STAGES.join(', ')}.`, ERROR_REASON.SDK_FAIL_FAST);
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
const planIdx = traceArgs.indexOf('--plan');
|
|
834
|
-
const planId = planIdx !== -1 ? traceArgs[planIdx + 1] : undefined;
|
|
835
|
-
if (planId === undefined || planId.startsWith('--')) {
|
|
836
|
-
error('No plan identifier was given. Usage: vault trace scaffold ' +
|
|
837
|
-
`--stage <${TRACE_STAGES.join('|')}> --plan <id> [files...] [--raw].`, ERROR_REASON.SDK_MISSING_ARG);
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
// 04-14 (TGATE-01, CR-03): the value is PRESENT but is not a plan
|
|
841
|
-
// identifier. Its own rejection, below both guards and beside the other
|
|
842
|
-
// two argument rejections, so a vault-disabled project still reaches
|
|
843
|
-
// none of them.
|
|
844
|
-
//
|
|
845
|
-
// THE REASON IS THE ESTABLISHED ONE, not a third vocabulary entry. It is
|
|
846
|
-
// `sdk_fail_fast`, the same reason the stage validation above uses,
|
|
847
|
-
// because this is the same class of defect: a value was supplied and
|
|
848
|
-
// cannot be interpreted. `sdk_missing_arg` belongs to the branch directly
|
|
849
|
-
// above, where the value was consumed by a following flag and no
|
|
850
|
-
// identifier reached the arm at all.
|
|
851
|
-
//
|
|
852
|
-
// IT TEACHES RATHER THAN REFUSING. The message names the value that was
|
|
853
|
-
// given, the required form and one conforming example, and then says what
|
|
854
|
-
// the gate will do with the value — because a caller who is told only
|
|
855
|
-
// that a value is invalid has to guess, and the guess CR-03 documents is
|
|
856
|
-
// the one this rejection exists to stop.
|
|
857
|
-
//
|
|
858
|
-
// FENCE 3 is untouched: this arm still writes no file, and a rejection
|
|
859
|
-
// returns before the leaf is required, so nothing is produced and nothing
|
|
860
|
-
// is partially produced.
|
|
861
|
-
if (!SCAFFOLD_PLAN_ID_RE.test(planId)) {
|
|
862
|
-
error(`Invalid --plan value "${planId}" for vault trace scaffold. ` +
|
|
863
|
-
`${SCAFFOLD_PLAN_ID_FORM} ${SCAFFOLD_QUICK_ID_FORM} The trace gate compares the identifier a plan artifact ` +
|
|
864
|
-
'records against the one its own filename states, and blocks on a disagreement.', ERROR_REASON.SDK_FAIL_FAST);
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
const files = traceArgs.filter((a, i) => !a.startsWith('--') && !valueIndices.has(i));
|
|
868
|
-
// The execute stage resolves a `gitCommit` digest per governed file, so
|
|
869
|
-
// the statement asserts something falsifiable: these practices governed
|
|
870
|
-
// THESE artifacts at this vault SHA (D-08). The digest is the commit
|
|
871
|
-
// that last produced the artifact, not merely HEAD, so a subject entry
|
|
872
|
-
// points at the work it actually describes. A file with no commit yields
|
|
873
|
-
// no digest — deliberately, because the leaf module records that gap as
|
|
874
|
-
// a LOUD note inside the statement, and a HEAD fallback would paper over
|
|
875
|
-
// a real coverage hole with a plausible-looking value.
|
|
876
|
-
const runGit = _exec ?? shell_command_projection_cjs_1.execGitLocal;
|
|
877
|
-
// 16-06 (D-14/D-31/D-35): the execute-stage record's anchors, read by the
|
|
878
|
-
// CLI and never typed. The END anchor is HEAD now; the START anchor is
|
|
879
|
-
// the one `vault anchor start` recorded in the plan's directory at
|
|
880
|
-
// execute:pre (phase) or where quick captured its base. `--anchors-dir`
|
|
881
|
-
// names that directory explicitly; otherwise it is derived from `--plan`
|
|
882
|
-
// (`resolveScaffoldAnchorsDir`). The plan stage reads neither and its
|
|
883
|
-
// output is byte-identical (golden-pinned).
|
|
884
|
-
const anchorsDirIdx = traceArgs.indexOf('--anchors-dir');
|
|
885
|
-
let anchors = null;
|
|
886
|
-
if (anchorsDirIdx !== -1) {
|
|
887
|
-
const anchorsDirArg = traceArgs[anchorsDirIdx + 1];
|
|
888
|
-
if (stage !== 'execute') {
|
|
889
|
-
error('--anchors-dir applies to --stage execute only; a plan-stage ledger records no anchors.', ERROR_REASON.SDK_FAIL_FAST);
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
if (anchorsDirArg === undefined || anchorsDirArg.startsWith('--')) {
|
|
893
|
-
error('--anchors-dir needs a directory under .planning/. Usage: vault trace scaffold --stage execute --plan <id> --anchors-dir <dir> [files...].', ERROR_REASON.SDK_MISSING_ARG);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
if (stage === 'execute') {
|
|
898
|
-
const head = runGit(['rev-parse', 'HEAD'], { cwd });
|
|
899
|
-
const end = head.exitCode === 0 ? head.stdout.trim() : '';
|
|
900
|
-
anchors = {};
|
|
901
|
-
if (vaultSeal.ANCHOR_SHA_RE.test(end))
|
|
902
|
-
anchors.endCommit = end;
|
|
903
|
-
const located = anchorsDirIdx !== -1
|
|
904
|
-
? resolvePlanningPath(cwd, traceArgs[anchorsDirIdx + 1], 'dir')
|
|
905
|
-
: resolveScaffoldAnchorsDir(cwd, planId);
|
|
906
|
-
if (anchorsDirIdx !== -1 && !located.ok) {
|
|
907
|
-
error(`--anchors-dir ${located.message}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
const start = located.ok ? vaultSeal.readDirAnchors(located.abs)?.start : undefined;
|
|
911
|
-
if (start !== undefined) {
|
|
912
|
-
anchors.startCommit = start;
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
// A note, not an error: the record is still written, with its END
|
|
916
|
-
// anchor, and the strict points type the missing start (16-07). One
|
|
917
|
-
// line on stderr, so the embedded block on stdout is unchanged.
|
|
918
|
-
process.stderr.write(`vault trace scaffold: no start anchor recorded for plan ${boundedPath(planId)} ` +
|
|
919
|
-
`(${located.ok ? `${located.rel} holds no ${vaultSeal.VAULT_ANCHORS_FILE}` : located.message}); ` +
|
|
920
|
-
'the ledger carries endCommit only. `pp_run vault anchor start <dir>` records it at execute:pre.\n');
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
const fileInputs = files.map((name) => {
|
|
924
|
-
if (stage !== 'execute')
|
|
925
|
-
return name;
|
|
926
|
-
const res = runGit(['log', '-1', '--format=%H', '--', name], { cwd });
|
|
927
|
-
const digest = res.exitCode === 0 ? res.stdout.trim() : '';
|
|
928
|
-
return GIT_COMMIT_DIGEST_RE.test(digest) ? { name, gitCommit: digest } : name;
|
|
929
|
-
});
|
|
930
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
931
|
-
const traceMod = _trace ?? require('./vault-trace.cjs');
|
|
932
|
-
// No files is a REAL path, not an error: a documentation-only plan is
|
|
933
|
-
// still governed by the global practices, and emitting nothing there
|
|
934
|
-
// would make the ruled-out set unauditable for exactly the plans where a
|
|
935
|
-
// reviewer most wants to know what was considered.
|
|
936
|
-
//
|
|
937
|
-
// THE OVERRIDE CONTEXT (04-06, SC4 / D-13 / D-14 / D-15).
|
|
938
|
-
//
|
|
939
|
-
// The verdict is obtained HERE rather than inside the leaf, and the
|
|
940
|
-
// reason is the same one that put the digest mapping above in the arm:
|
|
941
|
-
// this is the only place that knows a trace is being written NOW, and an
|
|
942
|
-
// override record is a statement about now. `buildTraceStatement`
|
|
943
|
-
// reaching for a live verdict from inside itself would make a pure
|
|
944
|
-
// producer's output depend on the machine's vault state through a path
|
|
945
|
-
// its signature does not declare — the producer/judge split D-01 drew,
|
|
946
|
-
// undone.
|
|
947
|
-
//
|
|
948
|
-
// The AGENT is never the courier for these three fields. Were the gate,
|
|
949
|
-
// the block and the suppressed reason passed as flags, a bypass could be
|
|
950
|
-
// concealed by simply not passing one — and the record that a bypass
|
|
951
|
-
// happened is the single field a bypass most wants hidden. The CLI
|
|
952
|
-
// computes all three, so there is nothing for a caller to omit.
|
|
953
|
-
//
|
|
954
|
-
// D-13: the arm runs once per stage per plan, and each invocation
|
|
955
|
-
// re-reads the environment and re-computes the verdict at its own
|
|
956
|
-
// timestamp. A bypass taken at execution but not at planning is
|
|
957
|
-
// therefore a distinct visible event, not an inference.
|
|
958
|
-
//
|
|
959
|
-
// The common path is unchanged. `buildOverrideRecord` returns null unless
|
|
960
|
-
// the verdict GENUINELY blocked *and* an accepted override reason is
|
|
961
|
-
// present in the environment; a non-blocking verdict with the reason set
|
|
962
|
-
// still records nothing, because recording one would manufacture evidence
|
|
963
|
-
// of a bypass that never happened (D-15).
|
|
964
|
-
//
|
|
965
|
-
// Obtaining a verdict is a READ. `computeVaultVerdict` never fetches or
|
|
966
|
-
// throws (its one possible spawn is a read-only git check-ignore), and this arm still writes no file and
|
|
967
|
-
// splices no frontmatter (FENCE 3).
|
|
968
|
-
const verdict = vault.computeVaultVerdict(cwd);
|
|
969
|
-
const statement = traceMod.buildTraceStatement({
|
|
970
|
-
cwd,
|
|
971
|
-
stage,
|
|
972
|
-
plan: planId,
|
|
973
|
-
files: fileInputs,
|
|
974
|
-
override: {
|
|
975
|
-
gate: TRACE_OVERRIDE_GATE,
|
|
976
|
-
block: verdict.block,
|
|
977
|
-
suppressedReason: verdict.reason,
|
|
978
|
-
},
|
|
979
|
-
// Spread, never `anchors: null` on the plan stage: a seam that records
|
|
980
|
-
// its arguments (the router tests' trace mock) then sees the call it
|
|
981
|
-
// saw before 16-06.
|
|
982
|
-
...(anchors !== null ? { anchors } : {}),
|
|
983
|
-
});
|
|
984
|
-
// The three-way emit the `practices match --block` arm established: the
|
|
985
|
-
// structured object is ALWAYS the output value, and the text a consumer
|
|
986
|
-
// embeds is the third argument. The planner embeds the block VERBATIM —
|
|
987
|
-
// it never renames a field and never re-derives a value, which is the
|
|
988
|
-
// whole point of D-01's split.
|
|
989
|
-
if (raw) {
|
|
990
|
-
c.output(statement, true, JSON.stringify(statement));
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
c.output(statement, true, traceMod.serializeTraceBlock(statement));
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
else {
|
|
997
|
-
const RECOGNIZED_RENDER_FLAGS = new Set(['--raw']);
|
|
998
|
-
const unrecognizedRenderFlag = traceArgs.find((a) => a.startsWith('--') && !RECOGNIZED_RENDER_FLAGS.has(a));
|
|
999
|
-
if (unrecognizedRenderFlag !== undefined) {
|
|
1000
|
-
error(`Unrecognized flag "${unrecognizedRenderFlag}" for vault trace render. ` +
|
|
1001
|
-
'Supported: a positional artifact path, --raw.', ERROR_REASON.SDK_FAIL_FAST);
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
// Quick 261015-ect (QD-15): parked in standards mode, as the scaffold
|
|
1005
|
-
// branch above is and for the same reason (no practices ledger is read).
|
|
1006
|
-
if (modeSeam.isStandardsModeActive(cwd)) {
|
|
1007
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1008
|
-
const { notApplicableTraceVerbEnvelope } = require('./vault-mode.cjs');
|
|
1009
|
-
const naRender = notApplicableTraceVerbEnvelope();
|
|
1010
|
-
c.output(naRender, true, raw ? JSON.stringify(naRender) : naRender.message);
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
const artifactPath = traceArgs.find((a) => !a.startsWith('--'));
|
|
1014
|
-
if (artifactPath === undefined) {
|
|
1015
|
-
error('No artifact path was given. Usage: vault trace render <artifact> [--raw].', ERROR_REASON.SDK_MISSING_ARG);
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
// CONTAINMENT (CR-04, T-03-50/51/52). `artifactPath` is an unvalidated
|
|
1019
|
-
// positional and the read below is unconditional, so before this branch
|
|
1020
|
-
// existed `vault trace render ../outside/secret.md` emitted an out-of-tree
|
|
1021
|
-
// file's content as the `## Practices Applied` section that
|
|
1022
|
-
// `agents/pp-executor.md` instructs the executor to include VERBATIM in
|
|
1023
|
-
// SUMMARY.md — an arbitrary file's bytes becoming governance-artifact
|
|
1024
|
-
// content a later reviewer agent reads.
|
|
1025
|
-
//
|
|
1026
|
-
// It sits HERE, inside the render arm, BELOW the `tracePracticesActive`
|
|
1027
|
-
// early return above — and must stay there. That return is the single
|
|
1028
|
-
// guard making the whole trace verb inert on a capability-off project, so
|
|
1029
|
-
// no containment decision, no project-root resolution and no read may be
|
|
1030
|
-
// reachable above it or an inert project stops being byte-identically
|
|
1031
|
-
// silent (D-04, T-03-26). Do not hoist any of this for a cheaper argument
|
|
1032
|
-
// check, and do not share a resolved root with the scaffold arm.
|
|
1033
|
-
//
|
|
1034
|
-
// It also sits BEFORE the read rather than being inferred from it:
|
|
1035
|
-
// `readHeadBounded` returns the empty string on any problem and never
|
|
1036
|
-
// throws, so a decision taken on its RESULT would be no decision at all.
|
|
1037
|
-
//
|
|
1038
|
-
// The predicate comes from the REAL leaf, not the `_vault` seam: a
|
|
1039
|
-
// security decision must not be switchable by a test mock. Same
|
|
1040
|
-
// `require('./vault.cjs')` the seam itself defaults to — one module, not
|
|
1041
|
-
// a second import path.
|
|
1042
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1043
|
-
const { isInsideDir } = require('./vault.cjs');
|
|
1044
|
-
if (artifactPath.indexOf('\u0000') !== -1) {
|
|
1045
|
-
error(`The artifact path is not a usable path: ${boundedPath(artifactPath)}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
let projectRoot;
|
|
1049
|
-
try {
|
|
1050
|
-
projectRoot = findProjectRoot(cwd) || cwd;
|
|
1051
|
-
}
|
|
1052
|
-
catch {
|
|
1053
|
-
projectRoot = cwd;
|
|
1054
|
-
}
|
|
1055
|
-
let artifactAbs;
|
|
1056
|
-
try {
|
|
1057
|
-
artifactAbs = node_path_1.default.resolve(cwd, artifactPath);
|
|
1058
|
-
}
|
|
1059
|
-
catch {
|
|
1060
|
-
error(`The artifact path could not be resolved: ${boundedPath(artifactPath)}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
if (!isInsideDir(projectRoot, artifactAbs)) {
|
|
1064
|
-
error(`The artifact path resolves outside the project and was not read: ${boundedPath(artifactPath)}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
1068
|
-
const traceMod = _trace ?? require('./vault-trace.cjs');
|
|
1069
|
-
const content = vault.readHeadBounded(artifactAbs, TRACE_ARTIFACT_READ_BYTES);
|
|
1070
|
-
const parsed = traceMod.parseVaultTraceBlock(content, artifactPath);
|
|
1071
|
-
// THE LEDGER, AND ONLY THE LEDGER. This arm must never call the matcher.
|
|
1072
|
-
// Re-deriving the practice set at render time would produce a section
|
|
1073
|
-
// that disagrees with the frontmatter it is supposedly generated from,
|
|
1074
|
-
// which is the precise failure D-03 exists to prevent: the whole point of
|
|
1075
|
-
// GENERATING the surface is that the two can never disagree (T-03-24).
|
|
1076
|
-
// Adding a matcher call here looks like a robustness improvement and is
|
|
1077
|
-
// the exact opposite — it reintroduces the drift the generation removed.
|
|
1078
|
-
//
|
|
1079
|
-
// An absent ledger yields the parser's documented absent result and NO
|
|
1080
|
-
// section — not an error, and not an empty heading. An empty ledger is a
|
|
1081
|
-
// different thing entirely and the leaf module's renderer handles it: it
|
|
1082
|
-
// emits the heading plus one explanatory line, because a degraded run's
|
|
1083
|
-
// record must stay visible and a reader who finds no section at all
|
|
1084
|
-
// cannot tell a degraded run from a plan that was never vault-enabled.
|
|
1085
|
-
const rendered = parsed.present ? traceMod.renderPracticesApplied(parsed.statement) : '';
|
|
1086
|
-
if (raw) {
|
|
1087
|
-
c.output(parsed, true, JSON.stringify(parsed));
|
|
1088
|
-
}
|
|
1089
|
-
else {
|
|
1090
|
-
c.output(parsed, true, rendered);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
else if (subcommand === 'audit') {
|
|
1095
|
-
// ── The mechanical practice audit (Phase 05-06, CHECK-03/SC1) ──
|
|
1096
|
-
//
|
|
1097
|
-
// A PASS-THROUGH, on exactly the rule the two trace arms above state: it
|
|
1098
|
-
// collects arguments, calls through and emits. It holds NO verdict, NO
|
|
1099
|
-
// message, NO range and NO cap logic. `check vault.audit` serializes the
|
|
1100
|
-
// SAME envelope from the SAME engine, and duplicating any of it here would
|
|
1101
|
-
// create a second place where the operator's report can disagree with the
|
|
1102
|
-
// gate that blocks the build on it (D-05-02). Even the human text is the
|
|
1103
|
-
// leaf's — `renderAuditReport` is called over the object the leaf returned,
|
|
1104
|
-
// never over a copy this arm rebuilt.
|
|
1105
|
-
//
|
|
1106
|
-
// ORDERING, copied from the trace arm above because the reason carries over
|
|
1107
|
-
// verbatim: the capability gate is the FIRST branch, ahead of the flag
|
|
1108
|
-
// allowlist (the admission verdict follows it here, per QD-03 below). Both orders satisfy T-02-07/T-03-25 — nothing is required and
|
|
1109
|
-
// nothing spawns before an unknown flag is rejected, because the gate is a
|
|
1110
|
-
// bounded config read and not a subprocess. Only THIS order also satisfies
|
|
1111
|
-
// D-04's non-negotiable clause: an inert project's output is byte-identical
|
|
1112
|
-
// on every path INCLUDING a mistyped one (T-03-26).
|
|
1113
|
-
//
|
|
1114
|
-
// 16-09 (SEAL-02, D-39) — ONE exception, and it is the gate's own: a
|
|
1115
|
-
// switch that is off over a phase holding engine-written vault records.
|
|
1116
|
-
// `check vault.audit` answers that `disabled_with_artifacts`, blocking, and
|
|
1117
|
-
// this verb prints the SAME line from the SAME leaf (an agreeing pair). A
|
|
1118
|
-
// project that never recorded anything still prints nothing.
|
|
1119
|
-
if (!tracePracticesActive(cwd)) {
|
|
1120
|
-
const refusal = disabledWithArtifactsRefusal(cwd, args);
|
|
1121
|
-
if (refusal)
|
|
1122
|
-
c.output(refusal, true, raw ? JSON.stringify(refusal) : refusal.message);
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
// Quick 261015-at3 (QD-03, D-39): the admission verdict comes AFTER the
|
|
1126
|
-
// switch block above, not before it. That block's refusal already asks
|
|
1127
|
-
// `hasPlatformBindings` first and is the verb's half of an agreeing pair with
|
|
1128
|
-
// `check vault.audit`; an admission-first line would silence the
|
|
1129
|
-
// `vault.enabled: false`-over-records refusal the gate still answers. This
|
|
1130
|
-
// line closes the remaining shape — both switches on, no bindings — where
|
|
1131
|
-
// `check vault.audit` answers `inert`.
|
|
1132
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1133
|
-
const owner = require('./vault.cjs');
|
|
1134
|
-
if (owner.vaultProducerAdmissionVerdict(cwd).state !== 'on')
|
|
1135
|
-
return;
|
|
1136
|
-
// T-5-19 / T-02-07: whole-token rejection, so `--raw=json` is caught by the
|
|
1137
|
-
// same check as a bare unknown flag rather than slipping past a prefix test
|
|
1138
|
-
// and reaching a subprocess. The host (pp-core/bin/pp-tools.cjs) splices out
|
|
1139
|
-
// its own global flags before dispatch, which is why the recognized set is
|
|
1140
|
-
// one wide.
|
|
1141
|
-
// The flag's NAME is spelled here and in `check-command-router.cts`'s
|
|
1142
|
-
// `AUDIT_RANGE_FLAG`. What must not be duplicated is the POLICY — what
|
|
1143
|
-
// counts as a well-formed range — and it is not: both surfaces call the
|
|
1144
|
-
// leaf's `isValidOperatorRange`. A shared constant for the token itself
|
|
1145
|
-
// would mean importing the audit leaf at this file's top level, which the
|
|
1146
|
-
// lazy-require convention here exists to avoid, to buy agreement on seven
|
|
1147
|
-
// characters that a test on each surface already pins.
|
|
1148
|
-
const AUDIT_RANGE_FLAG = '--range';
|
|
1149
|
-
const RECOGNIZED_AUDIT_FLAGS = new Set(['--raw', AUDIT_RANGE_FLAG]);
|
|
1150
|
-
const unrecognizedAuditFlag = args.slice(2).find((a) => a.startsWith('--') && !RECOGNIZED_AUDIT_FLAGS.has(a));
|
|
1151
|
-
if (unrecognizedAuditFlag !== undefined) {
|
|
1152
|
-
error(`Unrecognized flag "${unrecognizedAuditFlag}" for vault audit. ` +
|
|
1153
|
-
`Supported: a positional phase directory, --raw, ${AUDIT_RANGE_FLAG} <from>..<to>.`, ERROR_REASON.SDK_FAIL_FAST);
|
|
1154
|
-
return;
|
|
1155
|
-
}
|
|
1156
|
-
// 13-07 (D-13, PARK-02): standards mode has no practice layer to audit.
|
|
1157
|
-
// AFTER the inert gate above (D-22) and the flag guard, BEFORE the range,
|
|
1158
|
-
// the positional and the leaf — the same place `check vault.audit` answers
|
|
1159
|
-
// it, so the two surfaces agree on every argument shape. Still a
|
|
1160
|
-
// pass-through: it emits the leaf's builder output, never its own wording
|
|
1161
|
-
// (D-05-02).
|
|
1162
|
-
if (modeSeam.isStandardsModeActive(cwd)) {
|
|
1163
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1164
|
-
const { notApplicableGateEnvelope } = require('./vault-mode.cjs');
|
|
1165
|
-
const naAudit = notApplicableGateEnvelope('vault.audit');
|
|
1166
|
-
c.output(naAudit, true, raw ? JSON.stringify(naAudit) : naAudit.message);
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
// 08-06 (D-08-19): the operator's range, and the ONE token in this arm that
|
|
1170
|
-
// is a flag's VALUE rather than a positional. It is located before the
|
|
1171
|
-
// positional is picked because the pick below is "the first token that does
|
|
1172
|
-
// not start with `--`" — which, for `vault audit --range a..b <dir>`, is
|
|
1173
|
-
// `a..b`. That would have scoped the audit to a phase directory nobody
|
|
1174
|
-
// named, silently and with a plausible-looking envelope: the exact class of
|
|
1175
|
-
// the WR-08 defect the gate surface's positional scanner was rewritten to
|
|
1176
|
-
// remove. Here the fix is local, because this arm has one value-bearing
|
|
1177
|
-
// flag and no enforcer appending others.
|
|
1178
|
-
const auditArgs = args.slice(2);
|
|
1179
|
-
const rangeAt = auditArgs.indexOf(AUDIT_RANGE_FLAG);
|
|
1180
|
-
const auditRangeValue = rangeAt === -1 ? undefined : auditArgs[rangeAt + 1];
|
|
1181
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
1182
|
-
const auditMod = _audit ?? require('./vault-audit.cjs');
|
|
1183
|
-
if (rangeAt !== -1) {
|
|
1184
|
-
// HARVESTED, NEVER RESTATED: the predicate is the leaf's own, so this
|
|
1185
|
-
// operator surface and the gate surface cannot disagree about what a
|
|
1186
|
-
// well-formed range is. A seam that does not provide it (the test mock)
|
|
1187
|
-
// falls back to the real module rather than to "anything goes" — a
|
|
1188
|
-
// missing predicate must never mean a passing one.
|
|
1189
|
-
//
|
|
1190
|
-
// Each candidate is CALLED ON ITS OWN OBJECT rather than detached into a
|
|
1191
|
-
// variable: an unbound method is a `this` hazard the linter is right
|
|
1192
|
-
// about, and nothing here needs the function as a value.
|
|
1193
|
-
const rangeIsWellFormed = (v) => {
|
|
1194
|
-
if (typeof auditMod.isValidOperatorRange === 'function') {
|
|
1195
|
-
return auditMod.isValidOperatorRange(v);
|
|
1196
|
-
}
|
|
1197
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1198
|
-
const leaf = require('./vault-audit.cjs');
|
|
1199
|
-
return typeof leaf.isValidOperatorRange === 'function' ? leaf.isValidOperatorRange(v) : false;
|
|
1200
|
-
};
|
|
1201
|
-
if (typeof auditRangeValue !== 'string' || !rangeIsWellFormed(auditRangeValue)) {
|
|
1202
|
-
// The refused value is NOT echoed: it failed validation, so it is not
|
|
1203
|
-
// trusted text, and this arm's message is read by a person whose next
|
|
1204
|
-
// action is to retype it (T-08-32).
|
|
1205
|
-
error(`${AUDIT_RANGE_FLAG} needs a well-formed <from>..<to> git range (letters, digits, `
|
|
1206
|
-
+ '. _ / ^ ~ -, two or three dots, at most 200 characters). The supplied value is not '
|
|
1207
|
-
+ 'reproduced here because it failed validation. Re-run with a well-formed range, or '
|
|
1208
|
-
+ 'drop the flag to let the phase-start ladder resolve it.', ERROR_REASON.SDK_FAIL_FAST);
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
// The phase argument. A DIRECTORY path carries the workstream partition,
|
|
1213
|
-
// which is why neither dispatch point has to pass `--ws`
|
|
1214
|
-
// (check-query-gate.md:27, and the four-times-shipped omitted-`--ws`
|
|
1215
|
-
// lesson behind it). The range's VALUE is excluded by index, so the flag
|
|
1216
|
-
// may precede the positional.
|
|
1217
|
-
const auditPhaseArg = auditArgs.find((a, i) => !a.startsWith('--') && !(rangeAt !== -1 && i === rangeAt + 1));
|
|
1218
|
-
if (auditPhaseArg === undefined) {
|
|
1219
|
-
error(`No phase directory was given. Usage: vault audit <phase-dir> [${AUDIT_RANGE_FLAG} <from>..<to>] [--raw].`, ERROR_REASON.SDK_MISSING_ARG);
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
let auditRoot;
|
|
1223
|
-
try {
|
|
1224
|
-
auditRoot = findProjectRoot(cwd) || cwd;
|
|
1225
|
-
}
|
|
1226
|
-
catch {
|
|
1227
|
-
auditRoot = cwd;
|
|
1228
|
-
}
|
|
1229
|
-
// 08-10 (F4). THE ARGUMENT IS PART OF THE PASS-THROUGH. This arm read the
|
|
1230
|
-
// positional as a raw path while `check vault.audit` resolved it through
|
|
1231
|
-
// `resolveTracePhaseDir` — either an existing directory OR a phase token the
|
|
1232
|
-
// locator resolves. Measured on the live fixture, `01` therefore produced
|
|
1233
|
-
// `{"status":"ok","block":false,"rangeSource":"phase-start-commit"}` from the
|
|
1234
|
-
// gate and `{"status":"warn","block":true,"rangeSource":"base-branch",
|
|
1235
|
-
// "...":"manifest-not-found"}` from the operator's own command: a blocking,
|
|
1236
|
-
// meaningless verdict where the gate saw a clean phase-scoped pass. That is
|
|
1237
|
-
// exactly the disagreement this arm's own doc comment above forbids
|
|
1238
|
-
// (D-05-02), so the RESOLVER is harvested — never restated — from the leaf
|
|
1239
|
-
// both routers already import.
|
|
1240
|
-
//
|
|
1241
|
-
// From the REAL leaf, not the `_vault` seam — same rule and same reason as
|
|
1242
|
-
// the `isInsideDir` harvest in the render arm above: a containment decision
|
|
1243
|
-
// must not be switchable by a test mock.
|
|
1244
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1245
|
-
const { resolveTracePhaseDir } = require('./vault.cjs');
|
|
1246
|
-
const resolvedAuditPhase = resolveTracePhaseDir(auditRoot, cwd, auditPhaseArg);
|
|
1247
|
-
if (!resolvedAuditPhase) {
|
|
1248
|
-
// The SAME `unresolved` shape the gate emits (check-command-router.cts),
|
|
1249
|
-
// not a base-branch fallback that reads as a real audit of nothing. Stated
|
|
1250
|
-
// only from what was measured, and naming a re-run rather than a deletion
|
|
1251
|
-
// (T-4-75).
|
|
1252
|
-
const unresolved = {
|
|
1253
|
-
status: 'unresolved',
|
|
1254
|
-
block: true,
|
|
1255
|
-
message: `Vault audit: "${boundedPath(auditPhaseArg)}" is neither an existing `
|
|
1256
|
-
+ 'directory inside the project nor a phase the locator resolves, so no practice was '
|
|
1257
|
-
+ 'compared against anything. Repair: re-run this command with the phase directory.',
|
|
1258
|
-
};
|
|
1259
|
-
c.output(unresolved, true, raw ? JSON.stringify(unresolved) : unresolved.message);
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
const auditResult = auditMod.runVaultAudit({
|
|
1263
|
-
root: auditRoot,
|
|
1264
|
-
phaseDir: resolvedAuditPhase.abs,
|
|
1265
|
-
exec: _exec ?? shell_command_projection_cjs_1.execGitLocal,
|
|
1266
|
-
// Spread, never `range: undefined` — the leaf keys on the key's TYPE to
|
|
1267
|
-
// decide whether its ladder runs.
|
|
1268
|
-
...(typeof auditRangeValue === 'string' ? { range: auditRangeValue } : {}),
|
|
1269
|
-
});
|
|
1270
|
-
if (raw) {
|
|
1271
|
-
c.output(auditResult, true, JSON.stringify(auditResult));
|
|
1272
|
-
}
|
|
1273
|
-
else {
|
|
1274
|
-
c.output(auditResult, true, auditMod.renderAuditReport(auditResult));
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
else if (subcommand === 'manifest') {
|
|
1278
|
-
// ── The phase-manifest writer (260915-ik2, UAT-6-F1) ──
|
|
1279
|
-
//
|
|
1280
|
-
// `start` anchors `phases[NN].startCommit = HEAD` (write-once); `record`
|
|
1281
|
-
// writes `phases[NN].commits` from that anchor. Both are the leaf's —
|
|
1282
|
-
// this arm collects arguments, calls through and emits, exactly as the
|
|
1283
|
-
// `audit` arm above does, and in the same ORDER for the same reason: the
|
|
1284
|
-
// capability gate is FIRST and silent, so an inert project's output is
|
|
1285
|
-
// byte-identical on every path including a mistyped one (T-03-26,
|
|
1286
|
-
// T-ik2-08). It exits 0 on every path: the step files print the result and
|
|
1287
|
-
// continue, and a refusal travels as `ok: false` with a reason.
|
|
1288
|
-
//
|
|
1289
|
-
// Quick 261015-at3: the admission verdict precedes the capability gate — a
|
|
1290
|
-
// project with no `.platform-bindings.json` gets no manifest file written.
|
|
1291
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1292
|
-
const owner = require('./vault.cjs');
|
|
1293
|
-
if (owner.vaultProducerAdmissionVerdict(cwd).state !== 'on')
|
|
1294
|
-
return;
|
|
1295
|
-
if (!tracePracticesActive(cwd))
|
|
1296
|
-
return;
|
|
1297
|
-
const RECOGNIZED_MANIFEST_FLAGS = new Set(['--raw']);
|
|
1298
|
-
const unrecognizedManifestFlag = args.slice(2).find((a) => a.startsWith('--') && !RECOGNIZED_MANIFEST_FLAGS.has(a));
|
|
1299
|
-
if (unrecognizedManifestFlag !== undefined) {
|
|
1300
|
-
error(`Unrecognized flag "${unrecognizedManifestFlag}" for vault manifest. ` +
|
|
1301
|
-
`Supported: a verb (${MANIFEST_VERBS.join(' | ')}), a positional phase directory, --raw.`, ERROR_REASON.SDK_FAIL_FAST);
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
const manifestPositionals = args.slice(2).filter((a) => !a.startsWith('--'));
|
|
1305
|
-
const manifestVerb = manifestPositionals[0];
|
|
1306
|
-
if (manifestVerb !== 'start' && manifestVerb !== 'record') {
|
|
1307
|
-
error(`Unknown vault manifest verb. Available: ${MANIFEST_VERBS.join(', ')}`, ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
|
-
// Quick 261015-ect (QD-15): parked in standards mode. The range this verb
|
|
1311
|
-
// anchors serves two gates, `vault audit` and `check vault.review`, and
|
|
1312
|
-
// both answer not_applicable in this mode (PARK-02, PARK-04). Its third
|
|
1313
|
-
// reader, `/pp-undo --phase` (pp-core/workflows/undo.md), falls back to its
|
|
1314
|
-
// git-log search when the file or the phase entry is missing — the path a
|
|
1315
|
-
// project with no bindings or with the practices switch off already takes.
|
|
1316
|
-
// After the verb check and before any positional or phase read; the
|
|
1317
|
-
// envelope carries `ok: false` and a typed reason, which is how the step
|
|
1318
|
-
// files read a manifest refusal (print the line and continue).
|
|
1319
|
-
if (modeSeam.isStandardsModeActive(cwd)) {
|
|
1320
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1321
|
-
const { notApplicableManifestVerbEnvelope } = require('./vault-mode.cjs');
|
|
1322
|
-
const naManifest = notApplicableManifestVerbEnvelope();
|
|
1323
|
-
c.output(naManifest, true, raw ? JSON.stringify(naManifest) : naManifest.message);
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
// The phase DIRECTORY, as for `audit`: the path carries the workstream
|
|
1327
|
-
// partition, and the manifest is written two levels above it.
|
|
1328
|
-
const manifestPhaseDir = manifestPositionals[1];
|
|
1329
|
-
if (manifestPhaseDir === undefined) {
|
|
1330
|
-
error('No phase directory was given. Usage: vault manifest <start|record> <phase-dir> [--raw].', ERROR_REASON.SDK_MISSING_ARG);
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
let manifestRoot;
|
|
1334
|
-
try {
|
|
1335
|
-
manifestRoot = findProjectRoot(cwd) || cwd;
|
|
1336
|
-
}
|
|
1337
|
-
catch {
|
|
1338
|
-
manifestRoot = cwd;
|
|
1339
|
-
}
|
|
1340
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
1341
|
-
const manifestMod = _audit ?? require('./vault-audit.cjs');
|
|
1342
|
-
const writeOpts = { root: manifestRoot, phaseDir: manifestPhaseDir, cwd, exec: _exec ?? shell_command_projection_cjs_1.execGitLocal };
|
|
1343
|
-
const manifestResult = manifestVerb === 'start'
|
|
1344
|
-
? manifestMod.recordPhaseStart(writeOpts)
|
|
1345
|
-
: manifestMod.recordPhaseCommits(writeOpts);
|
|
1346
|
-
if (raw) {
|
|
1347
|
-
c.output(manifestResult, true, JSON.stringify(manifestResult));
|
|
1348
|
-
}
|
|
1349
|
-
else {
|
|
1350
|
-
c.output(manifestResult, true, manifestMod.renderManifestResult(manifestResult));
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
429
|
else if (subcommand === 'update') {
|
|
1354
430
|
// PBI 74316: `vault update [<tag>] [--yes]`. Without --yes it reports the
|
|
1355
431
|
// plan and writes nothing; see src/vault-update.cts for the contract.
|
|
@@ -1383,8 +459,7 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
1383
459
|
}
|
|
1384
460
|
else if (subcommand === 'standards') {
|
|
1385
461
|
// 15-07 (D-06, D-08, D-13): the standards record verbs. The arm body lives
|
|
1386
|
-
// in `routeVaultStandards` below
|
|
1387
|
-
// ran, because 'standards' is a member of VAULT_SUBCOMMANDS.
|
|
462
|
+
// in `routeVaultStandards` below.
|
|
1388
463
|
routeVaultStandards({ cwd, args, raw, error, vault, c, exec: _exec ?? shell_command_projection_cjs_1.execGitLocal });
|
|
1389
464
|
}
|
|
1390
465
|
else if (subcommand === 'admission') {
|
|
@@ -1411,7 +486,7 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
1411
486
|
*
|
|
1412
487
|
* The answer comes from `vaultAdmissionVerdict` in the REAL src/vault.cts, never
|
|
1413
488
|
* from the injectable `_vault` seam: a governance decision must not be
|
|
1414
|
-
* switchable by a test mock
|
|
489
|
+
* switchable by a test mock.
|
|
1415
490
|
* It stays on the cwd's own verdict, not the producer verdict the seven record
|
|
1416
491
|
* producers ask (quick 261015-ect, QD-10): this verb is a REPORT of the cwd,
|
|
1417
492
|
* which is what every fence asks, so a bindings-less linked worktree answers
|
|
@@ -1420,9 +495,7 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _
|
|
|
1420
495
|
* Exit 0 on every answer, because a door fence reads the TOKEN: any stdout other
|
|
1421
496
|
* than `on`, `off` or `inert` halts it, and its `|| echo unrecognised` fallback
|
|
1422
497
|
* turns a non-zero exit into a halt as well. The only non-zero paths are an
|
|
1423
|
-
* argument error here
|
|
1424
|
-
* vault subcommand where `.platform-bindings.json` exists (QD-11); both
|
|
1425
|
-
* therefore halt the fence, never skip it.
|
|
498
|
+
* argument error here, which therefore halts the fence, never skips it.
|
|
1426
499
|
*/
|
|
1427
500
|
function admissionVerb({ cwd, rest, raw, error, c }) {
|
|
1428
501
|
let explain = false;
|
|
@@ -1462,7 +535,7 @@ function admissionVerb({ cwd, rest, raw, error, c }) {
|
|
|
1462
535
|
*
|
|
1463
536
|
* ORDER: unless `vault admission` answers `on` (bindings present, every config
|
|
1464
537
|
* readable, the vault switch on) both verbs print nothing and write nothing,
|
|
1465
|
-
* before any argument
|
|
538
|
+
* before any argument is read — so a project without the vault is
|
|
1466
539
|
* byte-identical on every path including a mistyped one, and a standards record
|
|
1467
540
|
* cannot be produced on a project whose trace gate is inert (17-REVIEW CR-01,
|
|
1468
541
|
* D-13; the same gate `routeVaultAnchor` and `doorRecordVerb` ask). The verdict
|
|
@@ -1470,26 +543,23 @@ function admissionVerb({ cwd, rest, raw, error, c }) {
|
|
|
1470
543
|
* QD-09): in a linked worktree reading its owning checkout's cache — where an
|
|
1471
544
|
* executor runs `consulted` and has no untracked bindings of its own — that is
|
|
1472
545
|
* the owning checkout, which is also where the gate reading the record runs. Then
|
|
1473
|
-
*
|
|
1474
|
-
* flags are checked. An invalid mode never reaches here: the pre-dispatch
|
|
1475
|
-
* guard in `routeVaultCommand` refused it.
|
|
546
|
+
* the verb and the flags are checked.
|
|
1476
547
|
*
|
|
1477
|
-
* Every governance read is from the REAL module, never the `_vault` seam
|
|
1478
|
-
* mode-guard precedent); only the bounded PLAN read goes through the seam, so
|
|
548
|
+
* Every governance read is from the REAL module, never the `_vault` seam; only the bounded PLAN read goes through the seam, so
|
|
1479
549
|
* a test can count it. Neither verb writes a file.
|
|
1480
550
|
*/
|
|
1481
551
|
function routeVaultStandards({ cwd, args, raw, error, vault, c, exec }) {
|
|
552
|
+
// `promote` is an operator action, not a record producer: it is dispatched
|
|
553
|
+
// BEFORE the producer admission below, so a vault that is off answers with
|
|
554
|
+
// the promote module's own activation refusal instead of printing nothing.
|
|
555
|
+
if (args[2] === 'promote') {
|
|
556
|
+
standardsPromoteVerb({ cwd, rest: args.slice(3), raw, error, c, exec });
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
1482
559
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1483
560
|
const owner = require('./vault.cjs');
|
|
1484
561
|
if (owner.vaultProducerAdmissionVerdict(cwd).state !== 'on')
|
|
1485
562
|
return;
|
|
1486
|
-
if (!owner.isStandardsModeActive(cwd)) {
|
|
1487
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1488
|
-
const { notApplicableStandardsVerbEnvelope } = require('./vault-mode.cjs');
|
|
1489
|
-
const na = notApplicableStandardsVerbEnvelope();
|
|
1490
|
-
c.output(na, true, raw ? JSON.stringify(na) : na.message);
|
|
1491
|
-
return;
|
|
1492
|
-
}
|
|
1493
563
|
const verb = args[2];
|
|
1494
564
|
const rest = args.slice(3);
|
|
1495
565
|
if (verb === 'block') {
|
|
@@ -1502,6 +572,79 @@ function routeVaultStandards({ cwd, args, raw, error, vault, c, exec }) {
|
|
|
1502
572
|
error(`Unknown standards subcommand. Available: ${STANDARDS_VERBS.join(', ')}`, ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1503
573
|
}
|
|
1504
574
|
}
|
|
575
|
+
/** The flags `vault standards promote` takes that carry a value. */
|
|
576
|
+
const PROMOTE_VALUE_FLAGS = ['--path', '--entry', '--section', '--expect'];
|
|
577
|
+
/**
|
|
578
|
+
* `vault standards promote <file> [--path <vault-path>] [--entry "<what it is>;
|
|
579
|
+
* used when <…>"] [--section <INDEX section>] [--confirmed --expect <token>]
|
|
580
|
+
* [--raw]` (FORK-ONLY): add a standards document from this project to the
|
|
581
|
+
* client vault with its INDEX.md entry. Without `--confirmed` it only
|
|
582
|
+
* previews; see src/vault-standards-promote.cts for the guards and the two
|
|
583
|
+
* runs. An unrecognised flag is refused BEFORE the module loads or anything
|
|
584
|
+
* spawns (T-29-16); no flag disables or relaxes a guard. The verdict is
|
|
585
|
+
* written to stdout first, then a refusal also exits non-zero: an action
|
|
586
|
+
* command's refusal must be observable in the exit code.
|
|
587
|
+
*/
|
|
588
|
+
function standardsPromoteVerb({ cwd, rest, raw, error, c, exec }) {
|
|
589
|
+
const usage = 'Usage: vault standards promote <file> [--path <vault-path>] [--entry "<what it is>; used when <...>"] '
|
|
590
|
+
+ '[--section <INDEX section>] [--confirmed --expect <token>] [--raw].';
|
|
591
|
+
const values = {};
|
|
592
|
+
let confirmed = false;
|
|
593
|
+
let filePath;
|
|
594
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
595
|
+
const a = rest[i];
|
|
596
|
+
if (a === '--raw')
|
|
597
|
+
continue;
|
|
598
|
+
if (a === '--confirmed') {
|
|
599
|
+
confirmed = true;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (PROMOTE_VALUE_FLAGS.includes(a)) {
|
|
603
|
+
const v = rest[i + 1];
|
|
604
|
+
if (v === undefined || v.startsWith('--')) {
|
|
605
|
+
error(`${a} needs a value. ${usage}`, ERROR_REASON.SDK_MISSING_ARG);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
values[a] = v;
|
|
609
|
+
i += 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (a.startsWith('--')) {
|
|
613
|
+
error(`Unrecognized flag "${boundedPath(a)}" for vault standards promote. ${usage}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (filePath !== undefined) {
|
|
617
|
+
error(`vault standards promote takes exactly one document. ${usage}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
filePath = a;
|
|
621
|
+
}
|
|
622
|
+
if (filePath === undefined) {
|
|
623
|
+
error(`No standards document was given. ${usage}`, ERROR_REASON.SDK_MISSING_ARG);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (confirmed && values['--expect'] === undefined) {
|
|
627
|
+
error('This standards document was not pushed: the confirmation is missing the preview token. '
|
|
628
|
+
+ 'Run the promotion without --confirmed first, then pass the token it prints.', ERROR_REASON.VAULT_PROMOTE_CONCURRENT_EDIT);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
632
|
+
const promote = require('./vault-standards-promote.cjs');
|
|
633
|
+
const verdict = promote.promoteStandard({
|
|
634
|
+
cwd,
|
|
635
|
+
filePath,
|
|
636
|
+
vaultPath: values['--path'] ?? null,
|
|
637
|
+
entry: values['--entry'] ?? null,
|
|
638
|
+
section: values['--section'] ?? null,
|
|
639
|
+
confirmed,
|
|
640
|
+
expect: values['--expect'] ?? null,
|
|
641
|
+
_exec: exec,
|
|
642
|
+
});
|
|
643
|
+
c.output(verdict, true, raw ? JSON.stringify(verdict) : promote.renderPromoteReport(verdict));
|
|
644
|
+
if (verdict.status === 'refused') {
|
|
645
|
+
error(String(verdict.message), String(verdict.reason));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
1505
648
|
function standardsBlockVerb({ cwd, rest, raw, error, c, owner }) {
|
|
1506
649
|
// ── arguments ──
|
|
1507
650
|
let planId;
|
|
@@ -1602,7 +745,7 @@ function standardsBlockVerb({ cwd, rest, raw, error, c, owner }) {
|
|
|
1602
745
|
// trace leaf accepts the operator's override reason from this process's
|
|
1603
746
|
// environment; a non-blocking verdict records nothing (D-15 of Phase 4). The
|
|
1604
747
|
// router names no part of the override stack (the leaf owns it all). The
|
|
1605
|
-
// gate name is
|
|
748
|
+
// gate name is `TRACE_OVERRIDE_GATE`. The verdict is computed
|
|
1606
749
|
// against the SAME cache the stamp above measured (D-36, 15-REVIEW iter-2
|
|
1607
750
|
// WR-02), exactly as the trace gate recomputes it, so the suppressed reason
|
|
1608
751
|
// recorded here is the one the gate will compare it with, in any checkout.
|
|
@@ -1627,7 +770,7 @@ function standardsBlockVerb({ cwd, rest, raw, error, c, owner }) {
|
|
|
1627
770
|
error(`Vault standards block could not write the record: ${boundedPath(e instanceof Error ? e.message : String(e))}`, ERROR_REASON.UNKNOWN);
|
|
1628
771
|
return;
|
|
1629
772
|
}
|
|
1630
|
-
const envelope =
|
|
773
|
+
const envelope = {
|
|
1631
774
|
status: 'ok',
|
|
1632
775
|
planId,
|
|
1633
776
|
stamp: { repo: record.repo, ref: record.ref, sha: record.sha, indexStatus },
|
|
@@ -1635,7 +778,7 @@ function standardsBlockVerb({ cwd, rest, raw, error, c, owner }) {
|
|
|
1635
778
|
degraded: record.degraded,
|
|
1636
779
|
override: record.override,
|
|
1637
780
|
text,
|
|
1638
|
-
}
|
|
781
|
+
};
|
|
1639
782
|
c.output(envelope, true, raw ? JSON.stringify(envelope) : text);
|
|
1640
783
|
}
|
|
1641
784
|
function standardsConsultedVerb({ cwd, rest, raw, error, c, owner, vault, exec, }) {
|
|
@@ -1652,9 +795,9 @@ function standardsConsultedVerb({ cwd, rest, raw, error, c, owner, vault, exec,
|
|
|
1652
795
|
}
|
|
1653
796
|
const artifactPath = positionals[0];
|
|
1654
797
|
const shown = boundedPath(artifactPath);
|
|
1655
|
-
// CONTAINMENT FIRST, then the size, then the read (the
|
|
1656
|
-
//
|
|
1657
|
-
//
|
|
798
|
+
// CONTAINMENT FIRST, then the size, then the read (T-03-50): the plan path
|
|
799
|
+
// is an unvalidated positional, and a decision taken on the bounded read's
|
|
800
|
+
// RESULT would be no decision.
|
|
1658
801
|
if (artifactPath.indexOf('\u0000') !== -1) {
|
|
1659
802
|
error(`The plan path is not a usable path: ${shown}`, ERROR_REASON.SDK_FAIL_FAST);
|
|
1660
803
|
return;
|
|
@@ -1758,7 +901,7 @@ function standardsConsultedVerb({ cwd, rest, raw, error, c, owner, vault, exec,
|
|
|
1758
901
|
const start = vaultSeal.readDirAnchors(node_path_1.default.dirname(artifactAbs))?.start ?? null;
|
|
1759
902
|
const anchors = vaultSeal.ANCHOR_SHA_RE.test(end) ? { start, end } : null;
|
|
1760
903
|
const section = vaultStandardsLeaf.serializeConsultedSection(record, { anchors });
|
|
1761
|
-
const envelope =
|
|
904
|
+
const envelope = {
|
|
1762
905
|
status: 'ok',
|
|
1763
906
|
planId: record.planId,
|
|
1764
907
|
stamp: { repo: record.repo, ref: record.ref, sha: record.sha },
|
|
@@ -1767,13 +910,13 @@ function standardsConsultedVerb({ cwd, rest, raw, error, c, owner, vault, exec,
|
|
|
1767
910
|
degraded: record.degraded === null ? null : vaultStandardsLeaf.standardsEchoDegraded(record.degraded),
|
|
1768
911
|
cacheSource: cache.source,
|
|
1769
912
|
section,
|
|
1770
|
-
}
|
|
913
|
+
};
|
|
1771
914
|
c.output(envelope, true, raw ? JSON.stringify(envelope) : section);
|
|
1772
915
|
}
|
|
1773
916
|
/**
|
|
1774
917
|
* The real vault module, for the governance reads of the 16-06 verbs: the
|
|
1775
918
|
* admission verdict, containment and phase resolution must not be switchable
|
|
1776
|
-
* by a test mock
|
|
919
|
+
* by a test mock.
|
|
1777
920
|
*/
|
|
1778
921
|
function anchorOwner() {
|
|
1779
922
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -1856,52 +999,6 @@ function isDanglingLink(abs) {
|
|
|
1856
999
|
return false;
|
|
1857
1000
|
}
|
|
1858
1001
|
}
|
|
1859
|
-
/**
|
|
1860
|
-
* Where the execute-stage scaffold reads a plan's START anchor from, derived
|
|
1861
|
-
* from `--plan` (16-06, D-31). Measured order:
|
|
1862
|
-
*
|
|
1863
|
-
* 1. the QUICK form first: the one directory `<root>/.planning/quick/<id>-*`
|
|
1864
|
-
* (quick task directories always live under the ROOT planning tree,
|
|
1865
|
-
* `src/init.cts`). Exactly one match resolves; two or more is ambiguous
|
|
1866
|
-
* and resolves nothing. Tried for every id, so an all-digit quick id
|
|
1867
|
-
* (`261011-123`, which the phase alternative of `SCAFFOLD_PLAN_ID_RE`
|
|
1868
|
-
* also accepts) still finds its quick directory, and no second copy of
|
|
1869
|
-
* the quick-id form is needed here;
|
|
1870
|
-
* 2. otherwise the PHASE form: the segment before the first `-` (`02.1` of
|
|
1871
|
-
* `02.1-03`) resolved by `resolveTracePhaseDir`, the resolver the trace
|
|
1872
|
-
* gate uses for a phase-number positional, which honours the active
|
|
1873
|
-
* workstream (`--ws`, `GSD_WORKSTREAM`, the session pointer) through the
|
|
1874
|
-
* phase locator.
|
|
1875
|
-
*
|
|
1876
|
-
* Unresolvable → `ok: false`, and the scaffold writes `endCommit` only.
|
|
1877
|
-
*/
|
|
1878
|
-
function resolveScaffoldAnchorsDir(cwd, planId) {
|
|
1879
|
-
const root = projectRootOf(cwd);
|
|
1880
|
-
const quickRoot = node_path_1.default.join(root, '.planning', 'quick');
|
|
1881
|
-
let quick = [];
|
|
1882
|
-
try {
|
|
1883
|
-
quick = node_fs_1.default.readdirSync(quickRoot, { withFileTypes: true })
|
|
1884
|
-
.filter((d) => d.isDirectory() && d.name.startsWith(`${planId}-`))
|
|
1885
|
-
.map((d) => d.name);
|
|
1886
|
-
}
|
|
1887
|
-
catch {
|
|
1888
|
-
quick = [];
|
|
1889
|
-
}
|
|
1890
|
-
if (quick.length === 1) {
|
|
1891
|
-
const abs = node_path_1.default.join(quickRoot, quick[0]);
|
|
1892
|
-
return { ok: true, root, abs, rel: node_path_1.default.relative(root, abs).split(node_path_1.default.sep).join('/') };
|
|
1893
|
-
}
|
|
1894
|
-
if (quick.length > 1) {
|
|
1895
|
-
return { ok: false, reason: 'anchors-dir-ambiguous', message: `${quick.length} quick directories match .planning/quick/${boundedPath(planId)}-*` };
|
|
1896
|
-
}
|
|
1897
|
-
const dash = planId.indexOf('-');
|
|
1898
|
-
if (dash > 0) {
|
|
1899
|
-
const resolved = anchorOwner().resolveTracePhaseDir(root, cwd, planId.slice(0, dash));
|
|
1900
|
-
if (resolved && resolved.onlyFile === undefined)
|
|
1901
|
-
return { ok: true, root, abs: resolved.abs, rel: resolved.rel };
|
|
1902
|
-
}
|
|
1903
|
-
return { ok: false, reason: 'anchors-dir-unresolved', message: `no phase or quick directory resolves for plan ${boundedPath(planId)}` };
|
|
1904
|
-
}
|
|
1905
1002
|
/**
|
|
1906
1003
|
* `vault anchor <verb>` (16-06). INERT FIRST (D-13): unless the producer
|
|
1907
1004
|
* admission verdict is `on` (bindings present, every config readable, the
|
|
@@ -1938,8 +1035,6 @@ function routeVaultAnchor({ cwd, args, raw, error, c, exec }) {
|
|
|
1938
1035
|
* `vault anchor start <dir>` (16-06, D-31): record HEAD as the START anchor of
|
|
1939
1036
|
* a phase or quick directory, in `<dir>/.vault-anchors.json`
|
|
1940
1037
|
* (`{ start, recordedAt }`, written by the seal leaf's `writeDirStartAnchor`).
|
|
1941
|
-
* Mode-neutral: it is the start the practices-only `vault manifest start`
|
|
1942
|
-
* cannot give a standards-mode phase.
|
|
1943
1038
|
*
|
|
1944
1039
|
* Re-running at the SAME HEAD keeps the recorded start (`kept: true`, nothing
|
|
1945
1040
|
* written). At any other HEAD it records HEAD as the new start and says which
|
|
@@ -1957,8 +1052,8 @@ function routeVaultAnchor({ cwd, args, raw, error, c, exec }) {
|
|
|
1957
1052
|
* widening.
|
|
1958
1053
|
*
|
|
1959
1054
|
* Exit 0 on every refusal, which travels as `{ ok: false, reason, message }`
|
|
1960
|
-
* (the
|
|
1961
|
-
*
|
|
1055
|
+
* (the step prints it and continues; the strict points type a missing
|
|
1056
|
+
* start). An argument error is the only exit 1.
|
|
1962
1057
|
*/
|
|
1963
1058
|
function anchorStartVerb({ cwd, rest, raw, error, c, exec }) {
|
|
1964
1059
|
const positionals = [];
|
|
@@ -2247,75 +1342,6 @@ function insertLinesAfter(text, spans, after, lines) {
|
|
|
2247
1342
|
const at = spans[after].end;
|
|
2248
1343
|
return `${text.slice(0, at)}${lines.map((l) => `${eol}${l}`).join('')}${text.slice(at)}`;
|
|
2249
1344
|
}
|
|
2250
|
-
/**
|
|
2251
|
-
* The index of the practices ledger's `generatedAt` line, the line the
|
|
2252
|
-
* serializer writes the anchors after, located the way the parser locates the
|
|
2253
|
-
* block: the frontmatter from line 0 to the first later line opening `---`, the
|
|
2254
|
-
* top-level `vault:` key, its `predicate:` child, and that child's own
|
|
2255
|
-
* `generatedAt:` key. Also returns the indent of the predicate's keys and one
|
|
2256
|
-
* indentation step, so the spliced lines sit at the ledger's own indentation.
|
|
2257
|
-
* `null` when any of these is missing.
|
|
2258
|
-
*/
|
|
2259
|
-
function ledgerGeneratedAtLine(text, spans) {
|
|
2260
|
-
const line = (i) => text.slice(spans[i].start, spans[i].end);
|
|
2261
|
-
const indentOf = (l) => l.length - l.trimStart().length;
|
|
2262
|
-
if (spans.length < 3 || line(0) !== '---')
|
|
2263
|
-
return null;
|
|
2264
|
-
let close = -1;
|
|
2265
|
-
for (let i = 1; i < spans.length; i += 1) {
|
|
2266
|
-
if (line(i).startsWith('---')) {
|
|
2267
|
-
close = i;
|
|
2268
|
-
break;
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
if (close === -1)
|
|
2272
|
-
return null;
|
|
2273
|
-
const vaultAt = Array.from({ length: close - 1 }, (_, k) => k + 1).find((i) => /^vault:\s*$/.test(line(i)));
|
|
2274
|
-
if (vaultAt === undefined)
|
|
2275
|
-
return null;
|
|
2276
|
-
let predicateAt = -1;
|
|
2277
|
-
let childIndent = -1;
|
|
2278
|
-
for (let i = vaultAt + 1; i < close; i += 1) {
|
|
2279
|
-
const l = line(i);
|
|
2280
|
-
if (l.trim() === '')
|
|
2281
|
-
continue;
|
|
2282
|
-
const ind = indentOf(l);
|
|
2283
|
-
if (ind === 0)
|
|
2284
|
-
break;
|
|
2285
|
-
if (childIndent === -1)
|
|
2286
|
-
childIndent = ind;
|
|
2287
|
-
if (ind === childIndent && l.trim() === 'predicate:') {
|
|
2288
|
-
predicateAt = i;
|
|
2289
|
-
break;
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
if (predicateAt === -1)
|
|
2293
|
-
return null;
|
|
2294
|
-
let keyIndent = -1;
|
|
2295
|
-
for (let i = predicateAt + 1; i < close; i += 1) {
|
|
2296
|
-
const l = line(i);
|
|
2297
|
-
if (l.trim() === '')
|
|
2298
|
-
continue;
|
|
2299
|
-
const ind = indentOf(l);
|
|
2300
|
-
if (ind <= childIndent)
|
|
2301
|
-
break;
|
|
2302
|
-
if (keyIndent === -1)
|
|
2303
|
-
keyIndent = ind;
|
|
2304
|
-
if (ind === keyIndent && l.trimStart().startsWith('generatedAt:')) {
|
|
2305
|
-
return { index: i, keyIndent, step: keyIndent - childIndent };
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
return null;
|
|
2309
|
-
}
|
|
2310
|
-
/** The parsed statement minus its anchor keys, as text, for the before/after equality the back-fill asserts. */
|
|
2311
|
-
function statementWithoutAnchors(statement) {
|
|
2312
|
-
const st = JSON.parse(JSON.stringify(statement ?? null));
|
|
2313
|
-
if (st && st.predicate) {
|
|
2314
|
-
for (const k of ['startCommit', 'endCommit', 'anchorBackfill', 'anchorsDropped'])
|
|
2315
|
-
delete st.predicate[k];
|
|
2316
|
-
}
|
|
2317
|
-
return JSON.stringify(st);
|
|
2318
|
-
}
|
|
2319
1345
|
/**
|
|
2320
1346
|
* `vault anchor backfill <summary> --at <sha>` (16-06, D-32):
|
|
2321
1347
|
* give a record written before anchors existed its anchors, ATTRIBUTED. There
|
|
@@ -2325,13 +1351,9 @@ function statementWithoutAnchors(statement) {
|
|
|
2325
1351
|
* user.name`, bounded) and when. 16-07 reports a back-filled anchor as a note
|
|
2326
1352
|
* on every later check. `--at HEAD` is allowed, and is still attributed.
|
|
2327
1353
|
*
|
|
2328
|
-
*
|
|
2329
|
-
*
|
|
2330
|
-
*
|
|
2331
|
-
* ledger's `generatedAt` line with the serializer's own lines
|
|
2332
|
-
* (`traceAnchorLines`), so a later `trace render` round trip agrees;
|
|
2333
|
-
* - standards (a `## Standards Consulted` section): the anchor line with its
|
|
2334
|
-
* `backfilled by <who> at <iso>` tail, inserted as the section's last line.
|
|
1354
|
+
* The record is a SUMMARY's `## Standards Consulted` section: the anchor line
|
|
1355
|
+
* with its `backfilled by <who> at <iso>` tail is inserted as the section's
|
|
1356
|
+
* last line (no start: the record takes its directory's, D-59).
|
|
2335
1357
|
*
|
|
2336
1358
|
* Every other byte of the file is kept: the new text is the old one with lines
|
|
2337
1359
|
* inserted, and it is parsed back before it is written — the anchors must read
|
|
@@ -2340,8 +1362,8 @@ function statementWithoutAnchors(statement) {
|
|
|
2340
1362
|
* renamed into place.
|
|
2341
1363
|
*
|
|
2342
1364
|
* Refused, writing nothing: a record that already carries an anchor (a valid,
|
|
2343
|
-
* a dropped or a malformed one: `already-anchored`), a file with no
|
|
2344
|
-
*
|
|
1365
|
+
* a dropped or a malformed one: `already-anchored`), a file with no
|
|
1366
|
+
* Standards Consulted section (`no-record`), an `--at` that is not an ancestor of HEAD. A
|
|
2345
1367
|
* path outside `.planning/`, a malformed sha and `--from`/`--attest` beside
|
|
2346
1368
|
* `--at` are argument errors (exit 1), refused before any read or git call;
|
|
2347
1369
|
* the state refusals are `ok: false` envelopes (exit 0).
|
|
@@ -2429,20 +1451,13 @@ function anchorBackfillVerb({ cwd, rest, raw, error, c, exec }) {
|
|
|
2429
1451
|
const text = node_fs_1.default.readFileSync(located.abs, 'utf8');
|
|
2430
1452
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
2431
1453
|
const traceLeaf = require('./vault-trace.cjs');
|
|
2432
|
-
const
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
refuse('no-record', `${located.rel} carries no execute-stage vault: ledger and no ${vaultStandardsLeaf.STANDARDS_CONSULTED_HEADING} section to back-fill`);
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
const anchored = isLedger
|
|
2443
|
-
? ['startCommit', 'endCommit', 'anchorBackfill', 'anchorsDropped'].some((k) => k in st.predicate)
|
|
2444
|
-
: section.anchors !== undefined
|
|
2445
|
-
|| section.problems.some((p) => p.kind === vaultStandardsLeaf.STANDARDS_PROBLEM_KIND.ANCHOR_MALFORMED);
|
|
1454
|
+
const section = vaultStandardsLeaf.parseConsultedSection(text);
|
|
1455
|
+
if (!section.present || section.planId === null) {
|
|
1456
|
+
refuse('no-record', `${located.rel} carries no ${vaultStandardsLeaf.STANDARDS_CONSULTED_HEADING} section to back-fill`);
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
const anchored = section.anchors !== undefined
|
|
1460
|
+
|| section.problems.some((p) => p.kind === vaultStandardsLeaf.STANDARDS_PROBLEM_KIND.ANCHOR_MALFORMED);
|
|
2446
1461
|
if (at === undefined) {
|
|
2447
1462
|
// `--from` + `--attest`: the attestation (16 review iteration 3 CR-01, D-102).
|
|
2448
1463
|
if (!anchored) {
|
|
@@ -2450,7 +1465,7 @@ function anchorBackfillVerb({ cwd, rest, raw, error, c, exec }) {
|
|
|
2450
1465
|
return;
|
|
2451
1466
|
}
|
|
2452
1467
|
attestVerb({
|
|
2453
|
-
cwd, exec, text, located, from, attest,
|
|
1468
|
+
cwd, exec, text, located, from, attest, refuse, emit, traceLeaf,
|
|
2454
1469
|
});
|
|
2455
1470
|
return;
|
|
2456
1471
|
}
|
|
@@ -2465,49 +1480,20 @@ function anchorBackfillVerb({ cwd, rest, raw, error, c, exec }) {
|
|
|
2465
1480
|
}
|
|
2466
1481
|
const backfill = { by: traceLeaf.resolveBackfillIdentity(cwd), at: new Date().toISOString() };
|
|
2467
1482
|
const spans = lineSpans(text);
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
});
|
|
2481
|
-
|
|
2482
|
-
const back = traceLeaf.parseVaultTraceBlock(next, located.rel);
|
|
2483
|
-
const bp = back.present ? back.statement.predicate : {};
|
|
2484
|
-
const agrees = back.present
|
|
2485
|
-
&& bp.endCommit === at
|
|
2486
|
-
&& bp.startCommit === from
|
|
2487
|
-
&& JSON.stringify(bp.anchorBackfill) === JSON.stringify(backfill)
|
|
2488
|
-
&& !('anchorsDropped' in bp)
|
|
2489
|
-
&& statementWithoutAnchors(back.statement) === statementWithoutAnchors(st);
|
|
2490
|
-
if (!agrees) {
|
|
2491
|
-
refuse('rewrite-unverifiable', `the back-filled ledger in ${located.rel} would not read back as written, so nothing was written`);
|
|
2492
|
-
return;
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
else {
|
|
2496
|
-
const live = vaultStandardsLeaf.findLiveSection(text, vaultStandardsLeaf.STANDARDS_CONSULTED_HEADING);
|
|
2497
|
-
const lastBody = live.body[live.body.length - 1];
|
|
2498
|
-
const line = vaultStandardsLeaf.formatStandardsAnchorLine({ start: from ?? null, end: at, backfill });
|
|
2499
|
-
next = insertLinesAfter(text, spans, lastBody, [line]);
|
|
2500
|
-
const back = vaultStandardsLeaf.parseConsultedSection(next);
|
|
2501
|
-
const before = section;
|
|
2502
|
-
const expected = { ...(from !== undefined ? { start: from } : {}), end: at, backfill };
|
|
2503
|
-
const strip = (p) => JSON.stringify({ ...p, anchors: undefined, headingLine: undefined });
|
|
2504
|
-
const agrees = back.present
|
|
2505
|
-
&& JSON.stringify(back.anchors) === JSON.stringify({ end: expected.end, ...(from !== undefined ? { start: from } : {}), backfill })
|
|
2506
|
-
&& strip(back) === strip(before);
|
|
2507
|
-
if (!agrees) {
|
|
2508
|
-
refuse('rewrite-unverifiable', `the back-filled section in ${located.rel} would not read back as written, so nothing was written`);
|
|
2509
|
-
return;
|
|
2510
|
-
}
|
|
1483
|
+
const live = vaultStandardsLeaf.findLiveSection(text, vaultStandardsLeaf.STANDARDS_CONSULTED_HEADING);
|
|
1484
|
+
const lastBody = live.body[live.body.length - 1];
|
|
1485
|
+
const line = vaultStandardsLeaf.formatStandardsAnchorLine({ start: from ?? null, end: at, backfill });
|
|
1486
|
+
const next = insertLinesAfter(text, spans, lastBody, [line]);
|
|
1487
|
+
const back = vaultStandardsLeaf.parseConsultedSection(next);
|
|
1488
|
+
const before = section;
|
|
1489
|
+
const expected = { ...(from !== undefined ? { start: from } : {}), end: at, backfill };
|
|
1490
|
+
const strip = (p) => JSON.stringify({ ...p, anchors: undefined, headingLine: undefined });
|
|
1491
|
+
const agrees = back.present
|
|
1492
|
+
&& JSON.stringify(back.anchors) === JSON.stringify({ end: expected.end, ...(from !== undefined ? { start: from } : {}), backfill })
|
|
1493
|
+
&& strip(back) === strip(before);
|
|
1494
|
+
if (!agrees) {
|
|
1495
|
+
refuse('rewrite-unverifiable', `the back-filled section in ${located.rel} would not read back as written, so nothing was written`);
|
|
1496
|
+
return;
|
|
2511
1497
|
}
|
|
2512
1498
|
const tmp = `${located.abs}.${process.pid}.tmp`;
|
|
2513
1499
|
try {
|
|
@@ -2526,7 +1512,7 @@ function anchorBackfillVerb({ cwd, rest, raw, error, c, exec }) {
|
|
|
2526
1512
|
ok: true,
|
|
2527
1513
|
action: 'backfill',
|
|
2528
1514
|
file: located.rel,
|
|
2529
|
-
family:
|
|
1515
|
+
family: 'standards',
|
|
2530
1516
|
endCommit: at,
|
|
2531
1517
|
...(from !== undefined ? { startCommit: from } : {}),
|
|
2532
1518
|
backfilledBy: backfill.by,
|
|
@@ -2566,10 +1552,10 @@ function anchorBackfillVerb({ cwd, rest, raw, error, c, exec }) {
|
|
|
2566
1552
|
* whoever ran the command — what the CLI can read, not a verified identity —
|
|
2567
1553
|
* and the envelope says so (`byVerified: false`).
|
|
2568
1554
|
*/
|
|
2569
|
-
function attestVerb({ cwd, exec, text, located, from, attest,
|
|
1555
|
+
function attestVerb({ cwd, exec, text, located, from, attest, refuse, emit, traceLeaf }) {
|
|
2570
1556
|
const dirAbs = node_path_1.default.dirname(located.abs);
|
|
2571
1557
|
const recordName = node_path_1.default.basename(located.abs);
|
|
2572
|
-
const kind =
|
|
1558
|
+
const kind = vaultSeal.RECORDED_ARTIFACT_KIND.STANDARDS_CONSULTED;
|
|
2573
1559
|
const isAncestor = (a, d) => vaultSeal.isAnchorSha(a) && vaultSeal.isAnchorSha(d)
|
|
2574
1560
|
&& exec(['merge-base', '--is-ancestor', a, d], { cwd }).exitCode === 0;
|
|
2575
1561
|
const anchors = vaultSeal.resolveRecordAnchors(text, kind, dirAbs, { isAncestor });
|
|
@@ -2676,7 +1662,7 @@ function attestVerb({ cwd, exec, text, located, from, attest, isLedger, refuse,
|
|
|
2676
1662
|
ok: true,
|
|
2677
1663
|
action: 'attest',
|
|
2678
1664
|
file: located.rel,
|
|
2679
|
-
family:
|
|
1665
|
+
family: 'standards',
|
|
2680
1666
|
from,
|
|
2681
1667
|
endCommit: end,
|
|
2682
1668
|
attested: listed,
|
|
@@ -2690,7 +1676,7 @@ function attestVerb({ cwd, exec, text, located, from, attest, isLedger, refuse,
|
|
|
2690
1676
|
const DOOR_TEXT_MAX = 190;
|
|
2691
1677
|
/**
|
|
2692
1678
|
* A free-text value made safe for a door record line: the vault leaf's display
|
|
2693
|
-
* sanitiser (controls, bidi, zero-width
|
|
1679
|
+
* sanitiser (controls, bidi, zero-width), backticks turned into
|
|
2694
1680
|
* apostrophes, bounded. Empty or the literal `none` becomes `unknown`, because
|
|
2695
1681
|
* `none` is how a door record spells an absent value.
|
|
2696
1682
|
*/
|
|
@@ -2704,31 +1690,15 @@ function doorText(value, opts = {}) {
|
|
|
2704
1690
|
}
|
|
2705
1691
|
/**
|
|
2706
1692
|
* The vault coordinates a door worked under, or the degraded one-liner saying
|
|
2707
|
-
* why there are none (D-12 "or") — read by the
|
|
2708
|
-
*
|
|
1693
|
+
* why there are none (D-12 "or") — read by the standards block verb's own pin
|
|
1694
|
+
* reader, `standardsLoadedPin` over the manifest of the one resolved cache; no
|
|
1695
|
+
* loaded pin records the degraded line `vault standards block` would write.
|
|
2709
1696
|
*
|
|
2710
|
-
*
|
|
2711
|
-
*
|
|
2712
|
-
* fallback tier, ref drift, miss, no cache) records its reason instead,
|
|
2713
|
-
* the same distinction the practices ledger draws;
|
|
2714
|
-
* - standards: `standardsLoadedPin` over the manifest of the one resolved
|
|
2715
|
-
* cache, the block verb's reader; no loaded pin records the degraded line
|
|
2716
|
-
* `vault standards block` would write for it.
|
|
2717
|
-
*
|
|
2718
|
-
* The repo has already been through `redactCloneTarget` (both readers go
|
|
2719
|
-
* through `readManifest`), so no credential reaches the record.
|
|
1697
|
+
* The repo has already been through `redactCloneTarget` (`readManifest`), so
|
|
1698
|
+
* no credential reaches the record.
|
|
2720
1699
|
*/
|
|
2721
|
-
function doorVaultPin(cwd,
|
|
1700
|
+
function doorVaultPin(cwd, owner) {
|
|
2722
1701
|
const cacheDir = owner.resolveVaultCacheDir(cwd).dir;
|
|
2723
|
-
if (mode === 'practices') {
|
|
2724
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
2725
|
-
const traceLeaf = require('./vault-trace.cjs');
|
|
2726
|
-
const pin = traceLeaf.resolvePin(cwd, cacheDir);
|
|
2727
|
-
if (pin.state === 'loaded' && typeof pin.ref === 'string' && vaultSeal.ANCHOR_SHA_RE.test(pin.resolvedSha ?? '')) {
|
|
2728
|
-
return { vault: { repo: doorText(pin.repo, { token: true }), ref: doorText(pin.ref, { token: true }), sha: pin.resolvedSha }, degraded: null };
|
|
2729
|
-
}
|
|
2730
|
-
return { vault: null, degraded: doorText(pin.reason ?? 'the vault was not loaded') };
|
|
2731
|
-
}
|
|
2732
1702
|
const pin = vaultStandardsLeaf.standardsLoadedPin(owner.readManifest(cacheDir));
|
|
2733
1703
|
if (pin !== null && vaultSeal.ANCHOR_SHA_RE.test(pin.sha)) {
|
|
2734
1704
|
return { vault: { repo: doorText(pin.repo, { token: true }), ref: doorText(pin.ref, { token: true }), sha: pin.sha }, degraded: null };
|
|
@@ -2937,15 +1907,13 @@ function doorRecordVerb({ cwd, rest, raw, error, c, exec }) {
|
|
|
2937
1907
|
}
|
|
2938
1908
|
phase = doorText(resolved.rel, { token: true });
|
|
2939
1909
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
? modeVerdict.mode
|
|
2943
|
-
: null;
|
|
2944
|
-
if (mode === null) {
|
|
2945
|
-
error('The vault mode could not be resolved, so the record has no mode and was not written.', ERROR_REASON.SDK_FAIL_FAST);
|
|
1910
|
+
if (!owner.isVaultGateActive(cwd)) {
|
|
1911
|
+
error('The vault is off for this project, so there is no vault record to write.', ERROR_REASON.SDK_FAIL_FAST);
|
|
2946
1912
|
return;
|
|
2947
1913
|
}
|
|
2948
|
-
|
|
1914
|
+
// The record grammar keeps its `mode` field; standards is the only mode.
|
|
1915
|
+
const mode = 'standards';
|
|
1916
|
+
const pin = doorVaultPin(cwd, owner);
|
|
2949
1917
|
let existing = '';
|
|
2950
1918
|
if (node_fs_1.default.existsSync(located.abs)) {
|
|
2951
1919
|
const size = node_fs_1.default.statSync(located.abs).size;
|