@pareto4/pp-core 1.19.0 → 1.21.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/agents/pp-nyquist-auditor.md +19 -14
- package/agents/pp-phase-researcher.md +10 -0
- package/agents/pp-plan-checker.md +59 -36
- package/agents/pp-planner.md +49 -53
- package/agents/pp-vault-reviewer.md +2 -0
- package/agents/pp-verifier.md +15 -60
- package/commands/pp-autonomous.md +2 -1
- package/commands/pp-discovery.md +3 -2
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-plan-review-convergence.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +6 -3
- package/pareto-skills/_shared/pp-claude-md-protocol.md +1 -1
- package/pareto-skills/pp-project-setup/SKILL.md +18 -343
- package/pp-core/bin/lib/capability-activation.cjs +77 -0
- package/pp-core/bin/lib/capability-registry.cjs +15 -15
- package/pp-core/bin/lib/capability-state.cjs +8 -18
- package/pp-core/bin/lib/ceremony.cjs +1342 -0
- package/pp-core/bin/lib/check-command-router.cjs +62 -1
- package/pp-core/bin/lib/commands.cjs +29 -3
- package/pp-core/bin/lib/config.cjs +5 -0
- package/pp-core/bin/lib/discovery-access.cjs +276 -0
- package/pp-core/bin/lib/discovery-charter.cjs +664 -0
- package/pp-core/bin/lib/discovery-deviations.cjs +264 -0
- package/pp-core/bin/lib/discovery-entities.cjs +323 -0
- package/pp-core/bin/lib/discovery-for-phase.cjs +628 -0
- package/pp-core/bin/lib/discovery-integrations.cjs +286 -0
- package/pp-core/bin/lib/discovery-migration.cjs +246 -0
- package/pp-core/bin/lib/discovery-phases.cjs +233 -0
- package/pp-core/bin/lib/discovery-processes.cjs +355 -0
- package/pp-core/bin/lib/discovery-research-track.cjs +5 -4
- package/pp-core/bin/lib/discovery-rollup.cjs +8 -7
- package/pp-core/bin/lib/discovery-rules.cjs +216 -0
- package/pp-core/bin/lib/discovery-screens.cjs +1 -1
- package/pp-core/bin/lib/discovery-session-check.cjs +264 -0
- package/pp-core/bin/lib/discovery-stories.cjs +273 -0
- package/pp-core/bin/lib/discovery-sweep.cjs +1 -1
- package/pp-core/bin/lib/discovery-table.cjs +55 -0
- package/pp-core/bin/lib/init.cjs +50 -1
- package/pp-core/bin/lib/io.cjs +24 -0
- package/pp-core/bin/lib/loop-resolver.cjs +26 -12
- package/pp-core/bin/lib/phase.cjs +39 -3
- package/pp-core/bin/lib/plan-sizing.cjs +346 -0
- package/pp-core/bin/lib/test-substance.cjs +3190 -0
- package/pp-core/bin/lib/timeline.cjs +605 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +50 -2
- package/pp-core/bin/pp-tools.cjs +214 -5
- package/pp-core/bin/shared/ceremony-gates.json +17 -0
- package/pp-core/bin/shared/config-schema.manifest.json +1 -0
- package/pp-core/references/ceremony.md +81 -0
- package/pp-core/references/discovery-access.md +112 -0
- package/pp-core/references/discovery-build-consumption.md +256 -0
- package/pp-core/references/discovery-charter.md +127 -0
- package/pp-core/references/discovery-data-model.md +145 -0
- package/pp-core/references/discovery-id-conventions.md +50 -14
- package/pp-core/references/discovery-integrations.md +151 -0
- package/pp-core/references/discovery-processes.md +190 -0
- package/pp-core/references/discovery-rebaseline.md +2 -2
- package/pp-core/references/discovery-research-track.md +5 -6
- package/pp-core/references/discovery-session-output.md +70 -0
- package/pp-core/references/discovery-standards.md +107 -0
- package/pp-core/references/discovery-traceability.md +2 -2
- package/pp-core/references/few-shot-examples/plan-checker.md +8 -8
- package/pp-core/references/phase-timeline.md +107 -0
- package/pp-core/references/planner-antipatterns.md +32 -0
- package/pp-core/references/planner-discovery-extract.md +35 -0
- package/pp-core/references/planner-guidance.md +17 -14
- package/pp-core/references/planner-revision.md +1 -1
- package/pp-core/references/planning-config.md +3 -2
- package/pp-core/references/test-substance.md +324 -0
- package/pp-core/references/verifier-discovery-conformance.md +36 -0
- package/pp-core/references/verifier-stub-patterns.md +51 -0
- package/pp-core/templates/VALIDATION.md +1 -0
- package/pp-core/templates/phase-prompt.md +19 -7
- package/pp-core/templates/project-charter.md +161 -100
- package/pp-core/templates/project.md +3 -3
- package/pp-core/templates/requirements.md +18 -0
- package/pp-core/templates/roadmap.md +1 -1
- package/pp-core/templates/verification-report.md +17 -1
- package/pp-core/workflows/audit-milestone.md +4 -1
- package/pp-core/workflows/autonomous.md +11 -8
- package/pp-core/workflows/code-review.md +15 -6
- package/pp-core/workflows/discovery-sprint/charter.md +114 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +1 -1
- package/pp-core/workflows/discovery-sprint/prototype.md +4 -4
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +10 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +5 -2
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +27 -5
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +56 -31
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +1 -1
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +42 -19
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +27 -12
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +41 -23
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +50 -23
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +26 -11
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +28 -9
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +21 -0
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +24 -9
- package/pp-core/workflows/discovery-sprint.md +12 -15
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +80 -29
- package/pp-core/workflows/discovery-to-build/steps/{screen-inventory-gate.md → handoff-gates.md} +111 -3
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +12 -9
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +27 -26
- package/pp-core/workflows/discovery-to-build.md +70 -34
- package/pp-core/workflows/discuss-phase/discovery.md +83 -0
- package/pp-core/workflows/discuss-phase/templates/context.md +4 -0
- package/pp-core/workflows/discuss-phase.md +10 -7
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -7
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -2
- package/pp-core/workflows/execute-phase.md +14 -3
- package/pp-core/workflows/new-project.md +3 -3
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
- package/pp-core/workflows/plan-phase/steps/discovery-extract.md +41 -0
- package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
- package/pp-core/workflows/plan-phase.md +62 -124
- package/pp-core/workflows/plan-review-convergence.md +6 -3
- package/pp-core/workflows/ship.md +2 -2
- package/pp-core/workflows/stats.md +19 -4
- package/pp-core/workflows/validate-phase.md +19 -4
- package/pp-core/workflows/vault-review.md +5 -0
- package/pp-core/workflows/verify-phase.md +1 -1
- package/pp-core/workflows/verify-work.md +1 -1
- package/scripts/gen-capability-registry.cjs +43 -45
- package/scripts/run-tests.cjs +54 -30
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +0 -272
package/pp-core/bin/pp-tools.cjs
CHANGED
|
@@ -64,6 +64,19 @@
|
|
|
64
64
|
* Returns JSON { valid, errors[], slots: {role,capability,outcome} | null }
|
|
65
65
|
* --pick valid Emit bare boolean (for workflow boolean checks)
|
|
66
66
|
*
|
|
67
|
+
* Timeline (Phase 35):
|
|
68
|
+
* timeline start <role> <phase> Append a run record before an Agent() spawn; prints the run id
|
|
69
|
+
* [--plan P] [--agent A] [--model M] [--source S]
|
|
70
|
+
* timeline end <role> <phase> <outcome> Close the most recent open run of that role (and plan)
|
|
71
|
+
* [--plan P] [--id ID]
|
|
72
|
+
* timeline summary <phase> [--banner] Fold a phase's runs: wall-clock, agent time, longest
|
|
73
|
+
*
|
|
74
|
+
* Ceremony (Phase 37):
|
|
75
|
+
* ceremony classify <phase> [--full] [--research] [--skip-research] [--skip-verify] [--write]
|
|
76
|
+
* Classify a phase (trust boundary, codebase to match, single-concern), print the
|
|
77
|
+
* verdict table; --write records it as {padded}-CEREMONY.md
|
|
78
|
+
* ceremony get <phase> Read the recorded verdict; full when none is recorded
|
|
79
|
+
*
|
|
67
80
|
* Drift Guard (ADR-22):
|
|
68
81
|
* drift-guard authority Resolve effective source-grounding authority
|
|
69
82
|
* (reads plan_review.source_grounding_authority + intel.enabled from config)
|
|
@@ -177,7 +190,7 @@
|
|
|
177
190
|
* learnings delete <id> Delete a learning by ID
|
|
178
191
|
*
|
|
179
192
|
* Loop Extension Point Queries (ADR-857 phase 3c):
|
|
180
|
-
* loop render-hooks <point>
|
|
193
|
+
* loop render-hooks <point> [--phase <N>] Resolve + render active Capability hooks at a loop point
|
|
181
194
|
* Returns JSON envelope { point, activeHooks, rendered }
|
|
182
195
|
* Valid points: discuss:pre/post, plan:pre/post,
|
|
183
196
|
* execute:pre/wave:pre/wave:post/post, verify:pre/post, ship:pre/post
|
|
@@ -212,6 +225,7 @@ const roadmap = require('./lib/roadmap.cjs');
|
|
|
212
225
|
const verify = require('./lib/verify.cjs');
|
|
213
226
|
const config = require('./lib/config.cjs');
|
|
214
227
|
const platformBindings = require('./lib/platform-bindings.cjs');
|
|
228
|
+
const timeline = require('./lib/timeline.cjs');
|
|
215
229
|
const composeBindings = require('./lib/compose-bindings.cjs');
|
|
216
230
|
const initContract = require('./lib/init-contract.cjs');
|
|
217
231
|
const initInteractive = require('./lib/init-interactive.cjs');
|
|
@@ -529,9 +543,9 @@ async function main() {
|
|
|
529
543
|
'current-timestamp, detect-custom-files, discovery, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
530
544
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
531
545
|
'generate-client-context, generate-dev-preferences, generate-slug, graphify, history-digest, init, init-contract, intel, ' +
|
|
532
|
-
'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, platform-bindings, platform-latest-tag, profile-questionnaire, ' +
|
|
546
|
+
'capability, ceremony, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, platform-bindings, platform-latest-tag, profile-questionnaire, ' +
|
|
533
547
|
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
534
|
-
'task, template, user-story, validate, validate-bindings, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
548
|
+
'task, template, timeline, user-story, validate, validate-bindings, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
535
549
|
'Global flags:\n' +
|
|
536
550
|
' --raw Emit raw output without post-processing\n' +
|
|
537
551
|
' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
|
|
@@ -788,6 +802,32 @@ function makePlatformBindingsPresentResolver(cwd) {
|
|
|
788
802
|
};
|
|
789
803
|
}
|
|
790
804
|
|
|
805
|
+
/**
|
|
806
|
+
* Phase 37 D-14: the ceremony verdict `loop render-hooks --phase <N>` injects,
|
|
807
|
+
* built here at the CLI entry so the pure resolver imports no ceremony code.
|
|
808
|
+
* Returns `(phase) => gateFn`; gateFn reads the phase's recorded verdict once
|
|
809
|
+
* (any throw is a null verdict) and answers `ceremonyGateOpen(verdict, gate)`.
|
|
810
|
+
* Every failure answers true (T-37-13): a missing, unreadable or unknown-phase
|
|
811
|
+
* verdict leaves the hook rendered, so a doubt never lowers ceremony.
|
|
812
|
+
*/
|
|
813
|
+
function makeCeremonyGateResolver(cwd) {
|
|
814
|
+
return (phase) => {
|
|
815
|
+
let verdict = null;
|
|
816
|
+
try {
|
|
817
|
+
verdict = require('./lib/ceremony.cjs').readRecordedVerdict(cwd, phase);
|
|
818
|
+
} catch {
|
|
819
|
+
verdict = null;
|
|
820
|
+
}
|
|
821
|
+
return (gate) => {
|
|
822
|
+
try {
|
|
823
|
+
return require('./lib/ceremony.cjs').ceremonyGateOpen(verdict, gate) !== false;
|
|
824
|
+
} catch {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
791
831
|
async function runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext = null) {
|
|
792
832
|
switch (command) {
|
|
793
833
|
case 'agent': {
|
|
@@ -821,6 +861,18 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
821
861
|
break;
|
|
822
862
|
}
|
|
823
863
|
|
|
864
|
+
case 'timeline': {
|
|
865
|
+
timeline.cmdTimeline(cwd, args.slice(1), raw);
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
case 'ceremony': {
|
|
870
|
+
// WR-04: loaded here, not at the top, so a missing ceremony-gates.json fails
|
|
871
|
+
// only the ceremony command, never every pp-tools command.
|
|
872
|
+
require('./lib/ceremony.cjs').cmdCeremony(cwd, args.slice(1), raw);
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
|
|
824
876
|
case 'platform-latest-tag': {
|
|
825
877
|
// quick 261003-my0: the init.md / new-project.md preflights read
|
|
826
878
|
// `--pick latest` from here instead of composing the platform URL in
|
|
@@ -1655,7 +1707,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1655
1707
|
}
|
|
1656
1708
|
|
|
1657
1709
|
case 'loop': {
|
|
1658
|
-
// loop render-hooks <point> [--config-dir <dir>] [--active-cap <capId>] [--into <role>]
|
|
1710
|
+
// loop render-hooks <point> [--config-dir <dir>] [--active-cap <capId>] [--into <role>] [--phase <N>]
|
|
1659
1711
|
const loopSubcommand = args[1];
|
|
1660
1712
|
if (loopSubcommand === 'render-hooks') {
|
|
1661
1713
|
let loopConfigDir = null;
|
|
@@ -1703,10 +1755,25 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1703
1755
|
}
|
|
1704
1756
|
loopInto = value;
|
|
1705
1757
|
}
|
|
1758
|
+
// --phase <N>: read the phase's recorded ceremony verdict (Phase 37 D-14).
|
|
1759
|
+
// Unlike the flags above, a missing, empty or `--`-prefixed value is NOT an
|
|
1760
|
+
// error: a workflow whose phase variable is unset must keep today's hooks (T-37-13).
|
|
1761
|
+
let loopPhase = undefined;
|
|
1762
|
+
const phaseEqArg = args.find(arg => arg.startsWith('--phase='));
|
|
1763
|
+
const phaseIdx = args.indexOf('--phase');
|
|
1764
|
+
if (phaseEqArg) {
|
|
1765
|
+
const value = phaseEqArg.slice('--phase='.length).trim();
|
|
1766
|
+
if (value) loopPhase = value;
|
|
1767
|
+
} else if (phaseIdx !== -1) {
|
|
1768
|
+
const value = (args[phaseIdx + 1] || '').trim();
|
|
1769
|
+
if (value && !value.startsWith('--')) loopPhase = value;
|
|
1770
|
+
}
|
|
1706
1771
|
loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {
|
|
1707
1772
|
configDir: loopConfigDir ? path.resolve(loopConfigDir) : undefined,
|
|
1708
1773
|
activeCap: loopActiveCap,
|
|
1709
1774
|
into: loopInto,
|
|
1775
|
+
phase: loopPhase,
|
|
1776
|
+
resolveCeremonyGate: makeCeremonyGateResolver(cwd),
|
|
1710
1777
|
resolveRecordedArtifacts: makeRecordedArtifactsResolver(cwd),
|
|
1711
1778
|
resolvePlatformBindingsPresent: makePlatformBindingsPresentResolver(cwd),
|
|
1712
1779
|
});
|
|
@@ -2564,13 +2631,155 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
2564
2631
|
} catch (vaultCheckErr) {
|
|
2565
2632
|
error(`discovery vault-check: ${vaultCheckErr && vaultCheckErr.message ? vaultCheckErr.message : String(vaultCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2566
2633
|
}
|
|
2634
|
+
} else if (subcommand === 'charter-check') {
|
|
2635
|
+
// discovery charter-check [--require-signed]
|
|
2636
|
+
//
|
|
2637
|
+
// Structural check of docs/discovery/PROJECT-CHARTER.md: frontmatter
|
|
2638
|
+
// enums, the twenty `<!-- charter:<key> -->` sections in order, every
|
|
2639
|
+
// cited ID resolving to a session table or _requirements.md row, the
|
|
2640
|
+
// Go / No-Go table equal to Session 13's, must-resolve open items
|
|
2641
|
+
// closed or listed as conditions, sign-off rows agreeing with status.
|
|
2642
|
+
// --require-signed also fails (discovery_charter_unsigned) unless the
|
|
2643
|
+
// status is Signed or Signed with conditions — the handoff gate's arm.
|
|
2644
|
+
const charter = require('./lib/discovery-charter.cjs');
|
|
2645
|
+
try {
|
|
2646
|
+
charter.cmdDiscoveryCharterCheck(cwd, args, raw);
|
|
2647
|
+
} catch (charterCheckErr) {
|
|
2648
|
+
error(`discovery charter-check: ${charterCheckErr && charterCheckErr.message ? charterCheckErr.message : String(charterCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2649
|
+
}
|
|
2650
|
+
} else if (subcommand === 'entity-check') {
|
|
2651
|
+
// discovery entity-check [--require-complete] — _entities.md structure
|
|
2652
|
+
// (improvement plan, Batch 2): named entities, closed lifecycle and
|
|
2653
|
+
// classification cells, attributes per entity, Mermaid cardinalities,
|
|
2654
|
+
// step-matrix data objects resolving. --require-complete also fails on
|
|
2655
|
+
// pending `—` cells (discovery_entity_check_incomplete).
|
|
2656
|
+
const entities = require('./lib/discovery-entities.cjs');
|
|
2657
|
+
try {
|
|
2658
|
+
entities.cmdDiscoveryEntityCheck(cwd, args, raw);
|
|
2659
|
+
} catch (entityCheckErr) {
|
|
2660
|
+
error(`discovery entity-check: ${entityCheckErr && entityCheckErr.message ? entityCheckErr.message : String(entityCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2661
|
+
}
|
|
2662
|
+
} else if (subcommand === 'process-check') {
|
|
2663
|
+
// discovery process-check [--require-cited] — _processes.md structure:
|
|
2664
|
+
// PRC-/MP- sequences, Replaces resolving, one swimlane and one step
|
|
2665
|
+
// matrix per main process, actors, data objects, requirement IDs.
|
|
2666
|
+
// --require-cited also fails on a step citing no requirement.
|
|
2667
|
+
const processes = require('./lib/discovery-processes.cjs');
|
|
2668
|
+
try {
|
|
2669
|
+
processes.cmdDiscoveryProcessCheck(cwd, args, raw);
|
|
2670
|
+
} catch (processCheckErr) {
|
|
2671
|
+
error(`discovery process-check: ${processCheckErr && processCheckErr.message ? processCheckErr.message : String(processCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2672
|
+
}
|
|
2673
|
+
} else if (subcommand === 'rules-check') {
|
|
2674
|
+
// discovery rules-check — _rules.md structure: BR- sequence, layer
|
|
2675
|
+
// vocabulary, every rule citing an existing requirement and an existing
|
|
2676
|
+
// entity or process.
|
|
2677
|
+
const rules = require('./lib/discovery-rules.cjs');
|
|
2678
|
+
try {
|
|
2679
|
+
rules.cmdDiscoveryRulesCheck(cwd, args, raw);
|
|
2680
|
+
} catch (rulesCheckErr) {
|
|
2681
|
+
error(`discovery rules-check: ${rulesCheckErr && rulesCheckErr.message ? rulesCheckErr.message : String(rulesCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2682
|
+
}
|
|
2683
|
+
} else if (subcommand === 'stories-check') {
|
|
2684
|
+
// discovery stories-check — _stories.md structure (improvement plan,
|
|
2685
|
+
// Batch 4): US- sequence, EPIC shape, every story citing an existing
|
|
2686
|
+
// requirement, source step resolving, every MVP domain requirement
|
|
2687
|
+
// carrying at least one story.
|
|
2688
|
+
const stories = require('./lib/discovery-stories.cjs');
|
|
2689
|
+
try {
|
|
2690
|
+
stories.cmdDiscoveryStoriesCheck(cwd, args, raw);
|
|
2691
|
+
} catch (storiesCheckErr) {
|
|
2692
|
+
error(`discovery stories-check: ${storiesCheckErr && storiesCheckErr.message ? storiesCheckErr.message : String(storiesCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2693
|
+
}
|
|
2694
|
+
} else if (subcommand === 'phases-check') {
|
|
2695
|
+
// discovery phases-check — _phases.md structure (improvement plan,
|
|
2696
|
+
// Batch 4): contiguous phases, requirement IDs resolving and not
|
|
2697
|
+
// dropped, dependencies on earlier phases, every MVP requirement in
|
|
2698
|
+
// exactly one phase.
|
|
2699
|
+
const phasesCheck = require('./lib/discovery-phases.cjs');
|
|
2700
|
+
try {
|
|
2701
|
+
phasesCheck.cmdDiscoveryPhasesCheck(cwd, args, raw);
|
|
2702
|
+
} catch (phasesCheckErr) {
|
|
2703
|
+
error(`discovery phases-check: ${phasesCheckErr && phasesCheckErr.message ? phasesCheckErr.message : String(phasesCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2704
|
+
}
|
|
2705
|
+
} else if (subcommand === 'for-phase') {
|
|
2706
|
+
// discovery for-phase <N> [--markdown | --conformance-template |
|
|
2707
|
+
// --verify <file>] — the rows of every canonical artifact that cite
|
|
2708
|
+
// the phase's requirement IDs as one extract between
|
|
2709
|
+
// <!-- discovery:auto --> markers (improvement plan, Batch 4, R1);
|
|
2710
|
+
// the verifier's conformance block and its check (R3). Fails loud on
|
|
2711
|
+
// artifact structure only; no discovery package → exit 0, empty.
|
|
2712
|
+
const forPhase = require('./lib/discovery-for-phase.cjs');
|
|
2713
|
+
try {
|
|
2714
|
+
forPhase.cmdDiscoveryForPhase(cwd, args, raw);
|
|
2715
|
+
} catch (forPhaseErr) {
|
|
2716
|
+
error(`discovery for-phase: ${forPhaseErr && forPhaseErr.message ? forPhaseErr.message : String(forPhaseErr)}`, ERROR_REASON.UNKNOWN);
|
|
2717
|
+
}
|
|
2718
|
+
} else if (subcommand === 'roles-check') {
|
|
2719
|
+
// discovery roles-check — _roles.md structure (improvement plan,
|
|
2720
|
+
// Batch 3): every role a persona, a step-matrix actor or System;
|
|
2721
|
+
// permissions naming a known role, an existing entity, lowercase
|
|
2722
|
+
// operations and at least one requirement; screen rows resolving.
|
|
2723
|
+
const roles = require('./lib/discovery-access.cjs');
|
|
2724
|
+
try {
|
|
2725
|
+
roles.cmdDiscoveryRolesCheck(cwd, args, raw);
|
|
2726
|
+
} catch (rolesCheckErr) {
|
|
2727
|
+
error(`discovery roles-check: ${rolesCheckErr && rolesCheckErr.message ? rolesCheckErr.message : String(rolesCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2728
|
+
}
|
|
2729
|
+
} else if (subcommand === 'integrations-check') {
|
|
2730
|
+
// discovery integrations-check [--require-cited] — _integrations.md
|
|
2731
|
+
// structure: IF- sequence, direction vocabulary, contract and error
|
|
2732
|
+
// rows naming a known interface, step-matrix IF- tokens resolving.
|
|
2733
|
+
// --require-cited also fails on an uncited interface and on a step
|
|
2734
|
+
// naming a system without an IF- (discovery_integrations_uncited).
|
|
2735
|
+
const integrations = require('./lib/discovery-integrations.cjs');
|
|
2736
|
+
try {
|
|
2737
|
+
integrations.cmdDiscoveryIntegrationsCheck(cwd, args, raw);
|
|
2738
|
+
} catch (integrationsCheckErr) {
|
|
2739
|
+
error(`discovery integrations-check: ${integrationsCheckErr && integrationsCheckErr.message ? integrationsCheckErr.message : String(integrationsCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2740
|
+
}
|
|
2741
|
+
} else if (subcommand === 'migration-check') {
|
|
2742
|
+
// discovery migration-check [--require-cited] — _migration.md
|
|
2743
|
+
// structure: MG- sequence, source system in Session 2's landscape,
|
|
2744
|
+
// entities resolving, requirement IDs resolving. --require-cited
|
|
2745
|
+
// also fails on an uncited row (discovery_migration_uncited).
|
|
2746
|
+
const migration = require('./lib/discovery-migration.cjs');
|
|
2747
|
+
try {
|
|
2748
|
+
migration.cmdDiscoveryMigrationCheck(cwd, args, raw);
|
|
2749
|
+
} catch (migrationCheckErr) {
|
|
2750
|
+
error(`discovery migration-check: ${migrationCheckErr && migrationCheckErr.message ? migrationCheckErr.message : String(migrationCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2751
|
+
}
|
|
2752
|
+
} else if (subcommand === 'session-check') {
|
|
2753
|
+
// discovery session-check <N> [<N>…] | --all — every Notes Template
|
|
2754
|
+
// section of the session content file present exactly once in
|
|
2755
|
+
// session-NN.md, each table with a data row or the literal
|
|
2756
|
+
// `None identified in Discovery.`; --all covers the Completed sessions.
|
|
2757
|
+
const sessionCheck = require('./lib/discovery-session-check.cjs');
|
|
2758
|
+
try {
|
|
2759
|
+
sessionCheck.cmdDiscoverySessionCheck(cwd, args, raw);
|
|
2760
|
+
} catch (sessionCheckErr) {
|
|
2761
|
+
error(`discovery session-check: ${sessionCheckErr && sessionCheckErr.message ? sessionCheckErr.message : String(sessionCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2762
|
+
}
|
|
2763
|
+
} else if (subcommand === 'deviations-check') {
|
|
2764
|
+
// discovery deviations-check [--require-approved] — _deviations.md
|
|
2765
|
+
// structure: DEV- sequence, a [vault: path] standard present in the
|
|
2766
|
+
// cache, reversible and status vocabularies, approver in Session 1's
|
|
2767
|
+
// Approval Matrix, decision ID shape. --require-approved also fails
|
|
2768
|
+
// on a hard-to-reverse deviation still Proposed
|
|
2769
|
+
// (discovery_deviations_unapproved).
|
|
2770
|
+
const deviations = require('./lib/discovery-deviations.cjs');
|
|
2771
|
+
try {
|
|
2772
|
+
deviations.cmdDiscoveryDeviationsCheck(cwd, args, raw);
|
|
2773
|
+
} catch (deviationsCheckErr) {
|
|
2774
|
+
error(`discovery deviations-check: ${deviationsCheckErr && deviationsCheckErr.message ? deviationsCheckErr.message : String(deviationsCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2775
|
+
}
|
|
2567
2776
|
} else {
|
|
2568
2777
|
// WIRING, NOT HELP TEXT: tests/discovery-artifacts.test.cjs derives each
|
|
2569
2778
|
// subcommand spelling by regexing `<namespace>_<subcommand>_failed` out of
|
|
2570
2779
|
// the engine's exported exit identity, splits this string on commas and
|
|
2571
2780
|
// asserts membership. A spelling missing here is a command the engine
|
|
2572
2781
|
// claims to be and the router does not dispatch.
|
|
2573
|
-
error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check, vault-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2782
|
+
error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check, vault-check, charter-check, entity-check, process-check, rules-check, stories-check, phases-check, for-phase, roles-check, integrations-check, migration-check, deviations-check, session-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2574
2783
|
}
|
|
2575
2784
|
break;
|
|
2576
2785
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "The single gate vocabulary for per-phase ceremony (Phase 37). Read by src/ceremony.cts (table order), src/capability-activation.cts and scripts/gen-capability-registry.cjs (validation of `ceremony.<gate>` conjuncts). Keys are in verdict-table order; add a gate here and nowhere else.",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"gates": [
|
|
5
|
+
{ "key": "research", "label": "Researcher" },
|
|
6
|
+
{ "key": "pattern_mapper", "label": "Pattern mapper" },
|
|
7
|
+
{ "key": "threat_model", "label": "Threat model" },
|
|
8
|
+
{ "key": "plan_check", "label": "Plan checker" },
|
|
9
|
+
{ "key": "code_review", "label": "Code review" },
|
|
10
|
+
{ "key": "verifier", "label": "Verifier" },
|
|
11
|
+
{ "key": "security_review", "label": "Security review" },
|
|
12
|
+
{ "key": "nyquist", "label": "Nyquist audit" },
|
|
13
|
+
{ "key": "ui_review", "label": "UI review" },
|
|
14
|
+
{ "key": "ui", "label": "UI contract" },
|
|
15
|
+
{ "key": "vault", "label": "Vault" }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Phase Ceremony
|
|
2
|
+
|
|
3
|
+
Before any agent runs, plan-phase classifies the phase from what it touches (`/pp-plan-phase` writes the verdict in the classify step, ahead of research). Only `pp_run ceremony classify` computes a verdict; every other workflow, hook and agent reads the recorded one. The verdict is a pure function of the phase's ROADMAP section, its mapped REQUIREMENTS lines, its CONTEXT.md, the git file inventory, the completion state of other phases and the gate keys in `.planning/config.json`. No model judgment and no client identity is involved. It is recorded as `{padded}-CEREMONY.md`, and the same table is printed by the CLI.
|
|
4
|
+
|
|
5
|
+
The verdict only ever lowers ceremony relative to config. A gate that config turns off stays `off (config)`. The full gate stack is one flag (`--full`), one config key (`workflow.ceremony: full`) or one ROADMAP line (`**Ceremony:** full`, or `**Ceremony**: full`, anywhere in the phase section) away, and full mode runs every gate exactly as config says today. A `**Ceremony**` line with any other value is ignored with a warning line.
|
|
6
|
+
|
|
7
|
+
## The three questions
|
|
8
|
+
|
|
9
|
+
1. **Trust boundary crossed?** The ROADMAP section (up to its Plans list), the mapped REQUIREMENTS lines and CONTEXT.md (without its `<deferred>` block) are matched, case-insensitively on word boundaries, against four conservative term sets: authentication and authorization, secrets and crypto, personal and payment data, untrusted input. The security verbs also match their inflections and negations (encrypted, unencrypted, authenticated, unauthorized, uploaded, deserialization). "Log in" and "sign in" match only in their authentication sense (users log in, can sign in, allow log in, log in with, a logged-in or logged in user, not signed in, the login page, a sign in page, the log in screen), so logging prose such as "write the logs in JSON" or "structured logging" does not. Any match is "crossed", and the matched terms with their source are the evidence. A bare "token" does not match, because pp-core's own phases talk about context tokens. Over-match is accepted where it only adds ceremony: "logged in" or "signed in" followed by anything but a place or format word (the, a, its, CI, JSON, …), such as "decisions logged in STATE.md", reads as authentication. Detection only pushes a phase up. The residual risk is a phase worded without any trigger term, which reads as no trust boundary; `--full`, `workflow.ceremony: full` or a `**Ceremony:** full` ROADMAP line restore the full stack.
|
|
10
|
+
2. **Codebase to match?** Yes when the repository tracks at least 20 source files outside `.planning/` (vendored and generated directories excluded), or another roadmap phase or an archived milestone is complete. No git means yes (fails toward ceremony). A freshly generated scaffold stays below the threshold. The counts are the evidence.
|
|
11
|
+
3. **Serial single-concern?** Yes when the phase maps at most 3 requirement IDs sharing one category prefix, lists at most 4 success criteria, and does not span a UI surface plus a non-UI surface. `**UI:** none` counts as no UI. Requirement IDs and success criteria are read from the same line or from a numbered or bulleted list under the header. Only ID-shaped tokens (`SLUG-01`) count; other words on the line are dropped, and a range (`DATA-01 – DATA-03`, `to`, `through`) expands into every ID in it, up to 50.
|
|
12
|
+
|
|
13
|
+
Input the classifier cannot read fails toward ceremony and is named in a warning line. A missing ROADMAP section, no parseable requirement IDs (including `TBD` and `None`), a requirement ID with no REQUIREMENTS.md line, no parseable success criteria or a failed frontend check make the phase not single-concern, so it plans at least standard. An explicit `TBD`/`None`/`N/A` claims nothing; a ROADMAP section that is missing or unreadable, or a mapped requirement ID with no REQUIREMENTS.md line (REQUIREMENTS.md missing included), cannot rule out a trust boundary, so the answer is yes (`phase section unreadable — failing toward ceremony`, `requirement text unread for <IDs> — failing toward ceremony`) and the phase plans heavy. So a phase created by `/pp-phase add` or `insert` (`**Requirements**: TBD`) plans standard until its requirements are mapped, and heavy only when its wording names a trust term.
|
|
14
|
+
|
|
15
|
+
## Tiers
|
|
16
|
+
|
|
17
|
+
| Tier | When | Effect |
|
|
18
|
+
|------|------|--------|
|
|
19
|
+
| light | serial single-concern and no trust boundary | one blockers-only checker round, light verifier, quick code review, Nyquist audit and UI review skipped |
|
|
20
|
+
| heavy | not single-concern and a trust boundary crossed | up to 3 checker rounds, full verifier, configured review depth |
|
|
21
|
+
| standard | everything else | same as heavy |
|
|
22
|
+
|
|
23
|
+
"Codebase to match" never changes the tier; it gates the researcher and the pattern mapper directly. The tier names are the routing tiers' light, standard and heavy vocabulary, and the verdict has no effect on model routing.
|
|
24
|
+
|
|
25
|
+
## Gate rules
|
|
26
|
+
|
|
27
|
+
Every gate appears in the verdict with a decision (`run`, `skip` or `off`) and a reason. The hook conjunct is the `ceremony.<gate>` term a capability `when` may carry; it is false only for a `skip` in proportional mode.
|
|
28
|
+
|
|
29
|
+
| Gate key | Proportional rule | Full mode | Hook conjunct |
|
|
30
|
+
|----------|-------------------|-----------|---------------|
|
|
31
|
+
| `research` | runs only with a codebase to match; `--research` forces, `--skip-research` skips | runs if config enables it | `research` hook |
|
|
32
|
+
| `pattern_mapper` | runs only with a codebase to match | runs if config enables it | `pattern-mapper` hook |
|
|
33
|
+
| `threat_model` | runs only when a trust boundary is crossed, at any tier | runs if config enables it | `security` hook (threat-model contribution) |
|
|
34
|
+
| `plan_check` | light: one blockers-only round; standard and heavy: up to 3 rounds; `--skip-verify` skips | up to 3 rounds | none (read as `plan_check_rounds`, `plan_check_mode`) |
|
|
35
|
+
| `code_review` | light: `quick` depth; otherwise the configured depth, read live (a recorded depth never lowers review below current config) | configured depth | none (read as `code_review_depth`) |
|
|
36
|
+
| `verifier` | light and no trust boundary: `light` depth; otherwise `full` | `full` | none (read as `verifier_depth`) |
|
|
37
|
+
| `security_review` | runs only when a trust boundary is crossed | runs if config enables it | `security` hook (`secure-phase`, ship gate) |
|
|
38
|
+
| `nyquist` | skipped at light; runs at standard and heavy | runs if config enables it | `nyquist` hook |
|
|
39
|
+
| `ui_review` | skipped at light; runs otherwise | runs if config enables it | `ui` hook (`ui-review` step only) |
|
|
40
|
+
| `ui` | follows frontend detection exactly as today | same | none |
|
|
41
|
+
| `vault` | always runs when the vault is enabled | same | none |
|
|
42
|
+
|
|
43
|
+
A gate that config turns off is listed `off (config)` in every mode. If `workflow.security_enforcement` is off on a trust-boundary phase, the verdict adds a warning line. Deterministic checks (requirements coverage, decision coverage, plan-sizing, test substance, schema and codebase drift) always run. The vault gates and the UI design-contract gates (`ui-phase`, the UI plan gate and the UI safety gate) never depend on the tier. Plan-time VALIDATION.md is built from RESEARCH.md's Validation Architecture section, so when the verdict skips research (no codebase to match) it skips with it, and the research row's reason says so. The checker's Nyquist dimension (Dimension 8) runs with or without RESEARCH.md at every tier, light included, through `check test-substance`; only its VALIDATION.md check (8e) needs the Validation Architecture section. The post-execution Nyquist audit follows its own `nyquist` row.
|
|
44
|
+
|
|
45
|
+
## Verifier depth
|
|
46
|
+
|
|
47
|
+
`full` is today's verifier. `light` skips Step 4b (the data-flow trace, Level 4) and the Step 7b behavioural spot-check sweep. Must-haves, artifacts at levels 1-3, key links, requirements coverage, the Step 7 anti-pattern scan and the Step 7c probes always run. A behaviour-dependent truth still needs Step 3's single named passing test or routes to human verification: light depth never turns presence into VERIFIED. Light depth is reachable only at light tier, which has no trust boundary, so a trust-boundary phase is always verified at full depth.
|
|
48
|
+
|
|
49
|
+
## Blockers-only checker mode
|
|
50
|
+
|
|
51
|
+
`plan_check_mode: blockers-only` is used for the single light-tier round. The checker runs every dimension but reports only BLOCKER issues, never warnings or info, and returns `## VERIFICATION PASSED` when there is no blocker. If it returns blockers, the orchestrator runs one targeted revision for those blockers and does not re-check. Requirements coverage, decision coverage, plan-sizing and test substance still run deterministically in the planner's own validation. Every `check test-substance` finding is a BLOCKER, so it survives the single blockers-only round. `plan_check_mode: standard` is today's loop of up to 3 iterations.
|
|
52
|
+
|
|
53
|
+
## Lifecycle
|
|
54
|
+
|
|
55
|
+
The verdict is rewritten on every plan-phase run until the phase has its first SUMMARY.md, then reused: `--gaps` replans and execute-phase read the recorded verdict, so the phase's own code never flips "codebase to match" between planning and gap closure. `--full` always rewrites the verdict as full. `--skip-research` and `--skip-verify` decide only the run that passes them: the printed table shows them, the recorded verdict never does, and a recorded verdict that carries either reason does not parse. A phase with no CEREMONY.md, or one that does not parse, is treated as full, which is today's behaviour; this covers phases planned before this release and phases that skipped plan-phase.
|
|
56
|
+
|
|
57
|
+
`/pp-execute-phase` has no `--full`: re-plan with it. A `--full` verdict lasts only until the next plan-phase run without `--full` before the first SUMMARY. That run, a `--reviews` replan included, re-classifies the phase proportionally. Pass `--full` on every re-plan that should stay full; `/pp-autonomous --full` and `/pp-plan-review-convergence --full` forward it to every plan-phase call they make.
|
|
58
|
+
|
|
59
|
+
## Reading the verdict
|
|
60
|
+
|
|
61
|
+
No workflow parses CEREMONY.md or evaluates the verdict itself.
|
|
62
|
+
|
|
63
|
+
- **Init keys.** `pp_run query init.plan-phase` and `pp_run query init.execute-phase` return `ceremony_tier`, `ceremony_mode`, `plan_check_rounds` (1 or 3), `plan_check_mode` (`blockers-only` or `standard`), `verifier_depth` (`light` or `full`) and `code_review_depth`; `init.plan-phase` also returns `research_enabled` already folded through the verdict. `code-review` resolves its depth in the order `--depth` flag, then the verdict, then config. In proportional mode a gate the verdict skips is skipped without prompting; plan-phase's interactive research question is not asked. `--research` and `--full` are the overrides.
|
|
64
|
+
- **Hooks.** `pp_run loop render-hooks <point> --phase <N>` supplies the phase to the `when` evaluator. An empty or unknown phase, or no recorded file, leaves every `ceremony.*` conjunct true, so callers without a phase (`/pp-quick`, `/pp-debug`) are unchanged.
|
|
65
|
+
- **Direct read.** `pp_run ceremony get <N> [--pick <field>]` returns the recorded verdict, else the full default. Each gate carries a `report` string: `run`, `off (config)` or `skipped (ceremony: <reason>)`.
|
|
66
|
+
- **Ship and milestone audit** report a skipped gate as `skipped (ceremony: <reason>)`, never as missing and never as a ship block. Hand-invoked `/pp-secure-phase` and `/pp-validate-phase` always run, since that is an explicit request.
|
|
67
|
+
- A failed read falls back to full, never to less ceremony.
|
|
68
|
+
|
|
69
|
+
## CEREMONY.md
|
|
70
|
+
|
|
71
|
+
`{phase_dir}/{padded}-CEREMONY.md` is committed with the phase's planning docs. The YAML frontmatter is the machine contract; the body is the table the CLI printed. A frontmatter whose rows do not follow from its own answers, tier and mode (a hand edit or a half-resolved merge that skips a gate the rules would run) does not parse, so it reads as full. `ceremony get` and a reused `classify` print the table rendered from the frontmatter, not the stored body. There is no timestamp, so identical inputs give a byte-identical file.
|
|
72
|
+
|
|
73
|
+
| Field | Meaning |
|
|
74
|
+
|-------|---------|
|
|
75
|
+
| `schema_version` | `1` |
|
|
76
|
+
| `phase` | the padded phase token |
|
|
77
|
+
| `mode` | `proportional` or `full` |
|
|
78
|
+
| `forced_by` | `flag`, `config` or `roadmap` when full was forced; `none` otherwise |
|
|
79
|
+
| `tier` | `light`, `standard` or `heavy` |
|
|
80
|
+
| `answers` | `trust_boundary`, `codebase_to_match`, `serial_single_concern`, each `{ value, evidence }` |
|
|
81
|
+
| `gates` | one key per gate in the Gate rules table, each `{ decision: run\|skip\|off, depth?, rounds?, mode?, reason }` |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Discovery Access Model — `_roles.md` Template & Parser Contract
|
|
2
|
+
|
|
3
|
+
Single home for the roles artifact the build reads instead of Session 7's notes (Discovery Sprint
|
|
4
|
+
improvement plan, Batch 3): `_roles.md` holds the roles, what each may do to which entity, and which
|
|
5
|
+
screens each may open. Its gate is `pp_run query discovery.roles-check`. This file is Read lazily; it
|
|
6
|
+
is never `@`-imported.
|
|
7
|
+
|
|
8
|
+
## When the file appears
|
|
9
|
+
|
|
10
|
+
| Session | `_roles.md` |
|
|
11
|
+
|---|---|
|
|
12
|
+
| 7 | created from the template; topic 1 writes `## Roles` and `## Permissions`, topic 4 writes `## Screens`; `roles-check` in the session |
|
|
13
|
+
| 8 | `roles-check` in the Closing Step (reporting) |
|
|
14
|
+
| 13 | in the Step 0 gates (reporting) |
|
|
15
|
+
| handoff | `roles-check` hard-blocks |
|
|
16
|
+
|
|
17
|
+
Session 7's **Roles** section keeps a reading summary and points here: the canonical rows live in
|
|
18
|
+
the artifact, exactly as the requirements table lives in `_requirements.md`. The old preset matrix
|
|
19
|
+
(Admin / Manager / User / Viewer with one CRUD column per entity) is gone: a role is whatever the
|
|
20
|
+
client's process needs, and a permission is one row per role and entity with the operations as a
|
|
21
|
+
list, so a build phase can read exactly the rows that cite its requirements.
|
|
22
|
+
|
|
23
|
+
## Grammar
|
|
24
|
+
|
|
25
|
+
- **A role is an actor the process already has.** Every `## Roles` row names a Session 2 persona
|
|
26
|
+
(spelled as the User Personas table spells it), an actor a step matrix of `_processes.md` names,
|
|
27
|
+
or `System`. A role nobody plays in any step is a role the build cannot test; name the persona or
|
|
28
|
+
add the step. Unresolved while neither `session-02.md` nor `_processes.md` is on file.
|
|
29
|
+
- **Recovery path** is how the role is recovered when its last holder leaves or is locked out — the
|
|
30
|
+
vault's rule that no role may become unreachable (`[vault: <path>]` when the vault states it).
|
|
31
|
+
- **An operation is a lowercase verb** (`create`, `read`, `update`, `delete`, `assign`, `export`,
|
|
32
|
+
`approve`), comma-separated; the vocabulary is the product's, the shape is fixed.
|
|
33
|
+
- **An entity is a row of `_entities.md`**, one PascalCase token (`references/discovery-data-model.md`).
|
|
34
|
+
- **Condition** narrows a permission (`own team only`, `assignee or lead`); `—` is unconditional.
|
|
35
|
+
- **Requirements** names the `_requirements.md` IDs the permission realises — at least one; a
|
|
36
|
+
permission nothing requires is a question for the user, not a row.
|
|
37
|
+
- **A screen row is the authorization join**: the role may open the screen. `_screens.md` keeps its
|
|
38
|
+
own `Role` column as a reading aid (D-50); this table is the one the forbidden-path tests read.
|
|
39
|
+
- **Nothing is deleted.** A retired role keeps its row; a change after sign-off edits the row in
|
|
40
|
+
place and appends `→ amended by D-<ss>-<n>` (`references/discovery-id-conventions.md` § Precedence).
|
|
41
|
+
|
|
42
|
+
## `_roles.md`
|
|
43
|
+
|
|
44
|
+
Illustration only — not part of the template, names invented:
|
|
45
|
+
|
|
46
|
+
| Role | Description | Scope | Recovery path | Source |
|
|
47
|
+
|---|---|---|---|---|
|
|
48
|
+
| Team lead | Runs a team's work and membership | Teams they lead | An operator with `Teams.Recover` names a member lead | S7 |
|
|
49
|
+
|
|
50
|
+
| Role | Entity | Operation | Condition | Requirements |
|
|
51
|
+
|---|---|---|---|---|
|
|
52
|
+
| Team lead | Membership | create, delete, update | own team only | K-13, K-14 |
|
|
53
|
+
|
|
54
|
+
| Role | Screen |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Team lead | SCR-05 |
|
|
57
|
+
|
|
58
|
+
Bootstrap template — Session 7 copies it verbatim to `.planning/discovery/_roles.md`, replacing
|
|
59
|
+
`[PROJECT_NAME]`:
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# Roles — [PROJECT_NAME]
|
|
63
|
+
|
|
64
|
+
**Conventions:** pp-core reference `references/discovery-access.md`. A role is a Session 2 persona, a step-matrix actor or `System`; one permission row per role and entity, operations as a comma list; a screen row says the role may open the screen. Rows are never deleted; a change after sign-off edits the row in place and appends `→ amended by D-<ss>-<n>`.
|
|
65
|
+
|
|
66
|
+
## Roles
|
|
67
|
+
|
|
68
|
+
| Role | Description | Scope | Recovery path | Source |
|
|
69
|
+
|------|-------------|-------|---------------|--------|
|
|
70
|
+
|
|
71
|
+
## Permissions
|
|
72
|
+
|
|
73
|
+
| Role | Entity | Operation | Condition | Requirements |
|
|
74
|
+
|------|--------|-----------|-----------|--------------|
|
|
75
|
+
|
|
76
|
+
## Screens
|
|
77
|
+
|
|
78
|
+
| Role | Screen |
|
|
79
|
+
|------|--------|
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Parser contract — `pp_run query discovery.roles-check`
|
|
83
|
+
|
|
84
|
+
No positional path, no flags. Faults name the artifact, the 1-based line, a validated ID or name, a
|
|
85
|
+
section or column name and the reason code — never cell prose. Absent `_roles.md` → exit 0,
|
|
86
|
+
`present: false`.
|
|
87
|
+
|
|
88
|
+
| Cell / field | Grammar | Fault when violated |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `## Roles`, `## Permissions`, `## Screens` | each once as a `##` heading holding one table with the template's columns | `missing_section`, `bad_table_shape` naming the section |
|
|
91
|
+
| Roles `Role` | non-empty, listed once; a Session 2 persona, a step-matrix `Actor` token or `System` | `empty_cell (Role)`, `duplicate_role`, `unknown_role` naming the role. **Not resolved** while neither `session-02.md` nor `_processes.md` is on file |
|
|
92
|
+
| Roles `Description`, `Scope`, `Recovery path` | non-empty (`—` is a pending cell the facilitator owns) | `empty_cell` naming the column |
|
|
93
|
+
| Roles `Source` | free text | **not mechanically checked** |
|
|
94
|
+
| Permissions / Screens `Role` | a `## Roles` row | `unknown_role` |
|
|
95
|
+
| Permissions `Entity` | one PascalCase token present in `_entities.md` § Entities when on file | `bad_entity`; `unknown_entity` naming the entity |
|
|
96
|
+
| Permissions `Operation` | a comma list of `^[a-z][a-z-]*$` tokens | `bad_operation` |
|
|
97
|
+
| Permissions `Condition` | free text (`—` allowed) | **not mechanically checked** |
|
|
98
|
+
| Permissions `Requirements` | at least one requirement ID, each present in `_requirements.md` when on file and parseable | `no_requirement`, `unknown_requirement_id` |
|
|
99
|
+
| Screens `Screen` | `SCR-\d{2}`, present in `_screens.md` § Screens when on file | `bad_screen`; `unknown_screen` naming the screen |
|
|
100
|
+
| `_requirements.md` present but unparseable | the rollup grammar is owned by `discovery rollup` | `requirements_unparseable`, once; IDs are then shape-checked only |
|
|
101
|
+
| a source present but unreadable | present-and-readable | `artifact_unreadable` naming the path |
|
|
102
|
+
|
|
103
|
+
**Exit identity.** `discovery_roles_check_failed`. Payload: `ok`, `present`, `file`, `roles`,
|
|
104
|
+
`permissions`, `screens`.
|
|
105
|
+
|
|
106
|
+
## How the build reads it
|
|
107
|
+
|
|
108
|
+
`pp_run query discovery.for-phase <N>` selects the § Permissions rows citing the phase's requirement
|
|
109
|
+
IDs, then the § Roles row of every role those permissions name and the § Screens rows of those roles
|
|
110
|
+
and of the phase's screens (`references/discovery-build-consumption.md`). The conformance key is the
|
|
111
|
+
role name. The planner's auth phase reads the extract; the forbidden-path tests are written from the
|
|
112
|
+
screen rows and the permission conditions.
|