From 7a146dc11fc2fb5451d20a1f34d97f70e69988b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 17:10:05 +0200 Subject: [PATCH 01/24] docs(plans): checkpoint phase-6 adoption refinement --- plans/23-outward-turn-origin-adoption.md | 320 ++++++++++++++++++----- 1 file changed, 249 insertions(+), 71 deletions(-) diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index 5da9557..a21399d 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -1,15 +1,23 @@ -# Plan 23 — The outward turn: origin adoption and the second-caller evidence - -> **Status:** DRAFTED — PLAN-ONLY. No target file is touched under this plan until an execution -> session is designated. The latest ✅ EXECUTED ground is **plan 22** (self-hosting phase 5: the -> `05` dissolution and the agent front door; corpus at 115 Specs · 1 Pack · 86 anchors → -> 202 nodes · 397 edges · `ready: 80 / defined: 35`, 0 errors / 0 warnings). This plan proposes -> **phase 6 as the outward turn**: adopting the Protocol on the origin project -> (`new-convex-es` / `libar-platform`) — the stalled production codebase whose delivery process -> gave rise to the prior art in the first place — and producing the second-caller evidence the -> corpus cannot produce about itself. Stages S1–S4 execute in the origin repo with -> `@libar-dev/software-delivery-protocol` as a dependency; only S0 (rulings) and the two carried -> §5c items touch this repo. +# Plan 23 — The outward turn, refined: origin adoption, the usage layer, and the working ladder + +> **Status:** DRAFTED — PLAN-ONLY, **revision 2** (2026-07-28 refinement session). No target file is +> touched under this plan until an execution session is designated. The latest ✅ EXECUTED ground is +> **plan 22** (self-hosting phase 5: the `05` dissolution and the agent front door; corpus at +> 115 Specs · 1 Pack · 86 anchors → 202 nodes · 397 edges · `ready: 80 / defined: 35`, +> 0 errors / 0 warnings). This plan proposes **phase 6 as the usage turn**: the outward turn onto +> the origin project (`new-convex-es` / `libar-platform`) — unchanged from revision 1 — **plus the +> usage layer this repo owes any adopter**, promoted into scope by the refinement session. Four +> usage goals govern the phase (§a); a new workstream **W** (this repo) carries the authoring +> on-ramp, the authoring recipes, and the first honest use of the ladder's lower rungs; stages +> S1–S4 execute in the origin repo with `@libar-dev/software-delivery-protocol` as a dependency. +> +> **What revision 2 changed:** (1) the charter gains the four usage goals and the workstream W; +> (2) the authoring-workflow on-ramp moves from "named successor artifact" (§j of revision 1) to +> an in-scope deliverable; (3) S0 gains two named engine questions (the inert `verification.mode`, +> the anchored-verifier-without-binding seam); (4) S1/S3 gain explicit usage acceptance criteria +> (all four rungs exercised honestly; ≥1 end-to-end executable transition on foreign soil); +> (5) §b gains the inward evidence subsection (the 2026-07-28 engine survey). The origin evidence +> base, the S0 rulings, and the deferral list otherwise stand as drafted. > > **Spec anchors:** [the agent-surface ruling](../specs/decisions/agent-surface-scripts-graph.sdp.md) > (`spec:decisions.agent-surface-scripts-graph`) · [the agent front door](../specs/decisions/agent-front-door.sdp.md) @@ -36,6 +44,37 @@ a working delivery-process answer. That converts "SDP needs a second project" fr risk into a scheduled obligation, and it is why the outward turn outranks further inward readiness work. +**The refinement (revision 2).** The same-day engine survey (§b, inward evidence) showed that +what the origin will actually meet on arrival is not an engine gap but a **usage gap**, and +the phase is rechartered around four usage goals: + +1. **Anchors used effectively.** The binding layer works (86 anchors, three builders, the + enabled-verifier join), but coverage is asymmetric — `has-verifier: 80` against + `implemented: 25` — and nothing teaches when and where to bind. The origin's ~43k-LOC + platform is the first corpus where anchor placement is a real editorial decision. +2. **The whole readiness ladder used honestly.** The corpus holds **zero** Specs at `idea` or + `scoped` — the lower half of the ladder has never been used in anger, because the Protocol's + own forward-looking work lives in plan prose and docket rows instead of Specs. "Managing + requirements of all maturity levels" is the product's headline claim; today only the top two + rungs have ever carried weight. +3. **The executable transition as a taught workflow.** The machinery is landed and self-hosted + (50 bound points, 26 space contracts, the runner, the vitest adapter). What does not exist + is the *workflow*: the sanctioned path from a prose example to an enabled verifier + (example space → bound point → generated contract → `bindExample` + `specTest` → + mutation-probed red) is recorded in plans and exercised by hand, never taught. Two inert + seams compound it (§d, questions 5–6). +4. **The agent CLI as the working surface.** `sdp q` + the eight recipes + the reading skill + shipped at phase 5; the missing half is *authoring-side* usage — recipes that answer "why is + this Spec capped below `defined`," "which examples were refused contracts and by which + finding," "what would `ready` require here" — and a skill that teaches sessions to start + from the build-backlog recipe instead of from plan prose. + +The refined thesis: **phase 6 adopts the Protocol on the origin project, and uses the adoption +to force the usage layer into existence — with this repo eating the same food first.** The +workstream W applies the Protocol to phase 6's own deliverables (Specs entering at +`idea`/`scoped`, sessions driven off `ready ∧ ¬implemented`), which is simultaneously the first +honest exercise of the lower ladder and the dress rehearsal for S3's restart backlog. + ## (b) The evidence base (recorded here because the sources live outside this repo) Three surveys, one session, numbers re-checkable at the named paths. @@ -105,24 +144,53 @@ demand for generated views beyond the Design Review — and that demand goes thr standing bar (a recipe freezes into a projection when a second machine consumer needs it), not around it. -## (c) Charter — what phase 6 is and is not +**The inward evidence — the usage gaps (engine survey, 2026-07-28; added at revision 2).** +Same-day survey of `src/` (12.6k LOC) and the derived graph, re-checkable with `sdp q` and the +named files: + +- **The lower ladder is unexercised.** 115 Primitives: `ready: 80 · defined: 35 · scoped: 0 · + idea: 0`. Every forward-looking obligation (the write surface's design session, the + inline-carrier competition, the gate-leg change, the on-ramp itself) lives in plan prose, + docket rows, or deferral clauses — none is a Spec, so none appears in the build backlog. +- **Binding asymmetry.** `has-verifier: 80` vs `implemented: 25`; 30 `CodeNode`s against 56 + test/oracle anchors. Verification self-hosting ran far ahead of implementation self-hosting. +- **`verification.mode` is inert.** Authored, parsed (the `Verification — {mode}` heading), + rendered in the Design Review — and read by no floor clause, validator, or fact. + `mode: executable` currently *states* nothing checkable. +- **The anchor/binding seam is unobserved.** A `specTest` anchor (what makes an example an + *enabled* verifier) and a `bindExample` call (what makes it actually run) sit side by side + by convention only — nothing notices an anchored example whose contract no suite binds. +- **Oracles are example-corpus-only.** Zero `models` edges in the self-hosting graph; the + `specOracle` machinery is exercised only in `examples/checkout-v1`. +- **The on-ramp gap is real and one-sided.** `.claude/skills/sdp-agent-surface/SKILL.md` + teaches *reading* the graph (and does it well — the anti-anecdote rule, the eight recipes); + no surface teaches *authoring against it*. Gen 1's session-typed skills + (plan / design / implement / review) are evidence this layer mattered in production — + evidence, never template. -**Is:** the outward turn. Ruling the three questions the origin poses (§d), proving the -carrier and the verifier linkage on foreign content (S1), adopting the authored corpus (S2), -modeling the origin's restart plan as the first real external backlog (S3), and producing the -measured evidence phase 5 refused to manufacture (S4). Authoring-friction observations from -S1–S2 are captured as the evidence base the edit-model has been waiting for. - -**Is not:** another inward self-hosting phase. No readiness-sweep ceremony beyond what S0's -own edits force; the two-review cadence of phase 5 is not the standing default — one pre-close -review for the S0 rulings session, sized to its diff. The `06`/`07` standing gaps (five and -three rows) remain out of scope exactly as plan 22 §5c graded them. +## (c) Charter — what phase 6 is and is not -**Honesty guardrails, restated for foreign soil:** no origin Spec is promoted without a -resolving verifier; refusals are named with reasons; the S4 measurement is either instrumented -end-to-end or refused with its numbers recorded, exactly as the phase-5 precedent; and no -origin behavior is silently promoted into intent — where the origin's authored specs and its -code disagree, that is drift to record, not to resolve unilaterally. +**Is:** the usage turn. Ruling the questions the origin poses (§d), building the usage layer +and exercising the lower ladder on this repo's own phase-6 requirements (workstream W, §e), +proving the carrier and the verifier linkage on foreign content (S1), adopting the authored +corpus (S2), modeling the origin's restart plan as the first real external backlog (S3), and +producing the measured evidence phase 5 refused to manufacture (S4). Authoring-friction +observations from W and S1–S2 are captured as the evidence base the edit-model has been +waiting for. + +**Is not:** another inward self-hosting phase in the phase-1-to-5 sense. No readiness-sweep +ceremony beyond what S0's and W's own edits force; the two-review cadence of phase 5 is not +the standing default — one pre-close review for the S0 rulings session, sized to its diff, and +one for the W close, sized the same way. The `06`/`07` standing gaps (five and three rows) +remain out of scope exactly as plan 22 §5c graded them. + +**Honesty guardrails, restated for both soils:** no Spec — origin or inward — is promoted +without a resolving verifier; refusals are named with reasons; a W Spec is authored only for +genuine intended system truth, never as ladder ceremony (an `idea` Spec manufactured to +decorate the backlog is the padding the floor exists to refuse); the S4 measurement is either +instrumented end-to-end or refused with its numbers recorded, exactly as the phase-5 +precedent; and no origin behavior is silently promoted into intent — where the origin's +authored specs and its code disagree, that is drift to record, not to resolve unilaterally. ## (d) S0 — the rulings (this repo; PLAN → decision Specs where the bar is passed) @@ -166,15 +234,105 @@ Four questions the origin forces, none answerable by silence: real. Content-quality judgment stays out of validators either way — checks police conformance and honesty, never distillation. +Two engine questions added at revision 2, forced by the executable-transition goal (§a-3) — +each is *named* at S0 and ruled only if drafting shows the base forces an answer; neither +authorizes speculative building: + +5. **What may `verification.mode: executable` honestly state?** Today the field states + nothing checkable. Candidate: a **warn-level signal** — a Spec stating + `mode: executable` whose examples carry no enabled verifier is *stating a verification + posture it has not realized*, the same honesty family as `honesty/gaps` (informative, + never a gate). Counter-position: the field is descriptive authoring intent and policing it + drifts toward content-quality. S0 decides which reading the base forces, or records the + question on the carrying validation Spec. + +6. **The anchored-but-unbound seam.** An example can be an enabled verifier (resolvable + `specTest` anchor) while no suite binds its generated contract — the verifier *exists* and + is structurally honest, but the step-contract drift protection silently does not apply. + Candidate: a warn-level signal or a recipe (W3 ships the recipe regardless; the signal is + ruled only if the recipe proves the join is one agents hand-roll wrong — the standing + freeze bar, applied to a validator). + Also carried in S0, from plan 22 §5c: **the rule-6 bound point** on `spec:extraction.build-pipeline` (its world now exists — `sdp q` goes through the one extraction-and-validation seam; one point converts the strongest candidate the Spec has ever -had, and S0 is the natural session because it is this repo's only execution window in the +had, and S0 is the natural session because it is this repo's first execution window in the phase), and **the gate-leg question** for `check-self-hosting-gates.mjs` (a process change no standing ruling authorizes; put to the owner at the S0 close as a yes/no, and wired into `npm run check` only on a yes). -## (e) S1 — the tracer bullet on foreign soil (origin repo) +## (e) W — the usage layer (this repo; execution sessions after S0) + +The workstream that makes the four usage goals deliverables instead of hopes. Everything in W +is graph-read guidance and authored corpus — no new engine surface, no write path, no gate. + +**W1 — spec-first self-application (the working ladder).** Phase 6's own intended +capabilities are authored as Specs *before* they are built, entering at the rung their +structure earns — expected: `idea` or `scoped` — and enriched in place as W proceeds: + +- the authoring on-ramp (candidate `spec:consumers.authoring-on-ramp`, `behavior` at + `feature`); +- the authoring-recipe additions (a clause or child on `spec:consumers.agent-surface`'s + recipe family, not a new namespace); +- the two candidate signals from §d-5/6, *if and only if* S0 rules them in (a ruled-out + signal gets no Spec — refusal, not decoration). + +Sessions in W and after it open with the build-backlog and drift-alarm recipes — the plan +file stays the session record, but **what** is owed lives in the graph. Acceptance: the +corpus contains its first honestly-stated `idea`/`scoped` Specs; each one appears in (or +honestly outside) the build backlog for a stated reason; not one is promoted past its +structure. The guard from §(c) applies with teeth: if a candidate has no genuine intended +truth, it is not authored — three real Specs beat eight ceremonial ones. + +**W2 — the authoring on-ramp skill.** A second Protocol-shipped skill beside the reading +skill — working name `.claude/skills/sdp-authoring/` — teaching the full lifecycle without +gating any of it: + +- **create**: the envelope, the ID grammar, the carrier (`.sdp.md` by default, MD-18), where + a new Spec enters (`idea`: id · title · kind · altitude · an outcome or a parent relation); +- **enrich**: what each rung's floor actually reads (the kind-evidence table), `scoped` → + `defined` as evidence-present → evidence-complete, the concreteness law for examples, the + open-questions home (MD-9), promotion vs inline (MD-10); +- **the executable transition**: example space on the parent (`gwt-vocabulary` fence) → + bound point on the child (`gwt` fence) → `sdp build` emits the step/space contracts → + `bindExample` + `specTest` side by side in the suite → **mutation-probe red before + promotion** (the process discipline, stated as discipline, never as a check); +- **anchor**: when a `codeAnchor` is owed (the code that realizes a Spec's truth), the + binding-only law (id · one target · label — anything else refuses), one binding per anchor; +- **state `ready`**: the floor cleared *plus* a human's declared statement, in Design Review + context; `ready` is never conferred by tooling; +- **query throughout**: every step above names the recipe that answers its question. + +The skill obeys the anti-anecdote rule (the derived graph outranks the skill) and draws on +gen 1's session-typed skills as evidence of shape, never as template. It is drafted in W, +then **revised from S1's friction log** — the origin tracer is its first foreign user. + +**W3 — the authoring recipes.** Appended to the one catalog (`docs/agent-surface/recipes.md`), +each body executed verbatim by the recipe check, all read-only over `g` / `graph` / `report`: + +1. **The promotion preflight** — for a Spec id: stated rung, derived rung, and the first + unmet clause per rung above it ("what would `ready` require here"), from + `specContext(id).floorFailures` and the floor table's clause ids. +2. **The contract ledger** — which `example` Specs were refused step contracts and by which + `contracts/*` finding; which parents own spaces with zero bindable children. +3. **The verifier-binding audit** — enabled vs declared verifiers per Spec, and the + anchored-but-unbound set (§d-6's join, shipped as a recipe first). +4. **The lower-ladder view** — the corpus below `defined`, grouped by family, with each + Spec's distance-to-next-rung (first unmet clause) — the working surface for W1 and S3. + +The freeze rule stands: these are recipes; a join moves into the `reader` only at the +second-caller bar with evidence agents hand-roll it wrong. + +**W4 — the friction instrument, widened.** Revision 1's inline-carrier tally (§i) stands and +widens to four columns, kept per converted or authored Spec across W, S1, and S2: **anchor +friction** (binding placement, the builder forms, the import-binding boundary) · **ladder +friction** (floor clauses fought, rungs mis-entered) · **executable-transition friction** +(vocabulary resolution, contract refusals, the two-file seam) · **CLI friction** (exclusion +ergonomics, body ergonomics, missing joins). One log, four tallies — feeding respectively the +inline-carrier gate (§i), the on-ramp revision, any §d-5/6 ruling, and the edit-model +evidence base. + +## (f) S1 — the tracer bullet on foreign soil (origin repo) Hand-convert one coherent cluster — proposed: `production-hardening` (the largest authored spec, 26.8 KB, `roadmap`, with its 7-file executable directory of planning stubs) plus 2–4 of @@ -182,15 +340,20 @@ its neighbors — into `.sdp.md` under an `sdp/`-rooted corpus in the origin rep to the existing code and the existing `.feature` verifiers, and run the full recipe catalog over the derived graph. **Tracer-bullet discipline applies unchanged:** if the origin content stops extracting or validating, fix the carrier or extractor here — never fork the grammar in -the consumer. Deliverables: the converted cluster extracting at 0 errors; the eight recipes -returning honest answers about it (the 17 planning stubs must surface as -declared-not-enabled, not as coverage); a friction log — every place the grammar, the -envelope, or the binding model fought the content — as the S2 playbook input, the edit-model -evidence base, and the inline-carrier instrument (§(i)): each converted Spec is tallied as -leaf-altitude/single-module/under-30-lines or not, so the carrier-competition gate reads a -measured population, not an impression. +the consumer. + +Deliverables: the converted cluster extracting at 0 errors; the recipes (the eight standing +plus W3's four) returning honest answers about it (the 17 planning stubs must surface as +declared-not-enabled, not as coverage); the friction log per W4. **Usage acceptance, added at +revision 2:** the converted cluster spans **all four rungs** honestly (the origin's four-tier +ladder guarantees candidates for each — no rung is manufactured, and none is skipped by +convenience-rounding everything to `defined`); **at least one end-to-end executable +transition** lands on foreign soil — one example carried from prose to an enabled, +contract-bound, mutation-probed verifier through the W2 workflow, with the origin's +vitest-cucumber suite untouched beside it; and the W2 skill is exercised as written, with +every divergence logged as a skill bug or a friction entry, never silently absorbed. -## (f) S2 — the fleet backfill (origin repo) +## (g) S2 — the fleet backfill (origin repo) Convert the remaining authored surface (~17 specs, 23 PDRs, 4 release features → Pack/plan dispositions per S0) using the gen-1 annotation-fleet playbook (parallel agents, small @@ -201,16 +364,21 @@ against resolving verifiers before any `ready` statement. Expected outcome: an o of roughly 150–300 nodes — the first corpus at 2–3× self-hosting scale, which is also the first real test of `sdp q`'s derive-per-invocation latency (S4 measures it). -## (g) S3 — the restart plan as the first external backlog (origin repo) +## (h) S3 — the restart plan as the first external backlog (origin repo) Model the origin's own restart sequencing (its J5 strawman: green rails → phase-debt disposition → Agent-BC vertical → tranche-0 hardening → vision work) as Specs with honest -readiness. The deliverable is the origin's build backlog and drift alarm answering from the -derived graph — the exact questions its re-entry journey documents were hand-written to -approximate, now live instead of rotting. This stage is where the stalled project's "waiting -for a new solution" ends operationally: the restart plan is *in* the Protocol. - -## (h) S4 — the measurement, done honestly this time (origin repo) +readiness — **expected to enter overwhelmingly at `idea` and `scoped`**, which is the point: +this is the lower ladder doing the job it was designed for, at production scale, one +workstream after W1 rehearsed it inward. The deliverable is the origin's build backlog and +drift alarm answering from the derived graph — the exact questions its re-entry journey +documents were hand-written to approximate, now live instead of rotting. New work authored +during the restart follows the W2 workflow end-to-end (spec-first, enrich, transition, +anchor), which makes S3 the standing test of whether the on-ramp teaches or merely describes. +This stage is where the stalled project's "waiting for a new solution" ends operationally: +the restart plan is *in* the Protocol. + +## (i) S4 — the measurement, done honestly this time (origin repo) The phase-5 refusal named its own missing instrument: token accounting over two real agent sessions. The origin provides the honest setting — a year-paused repo with a hand-authored @@ -221,43 +389,53 @@ from the harness. Also measured: extraction latency at S2 scale. Both numbers pu their design stated, or the attempt is refused with its numbers recorded — the phase-5 precedent is the standard. -## (i) What this plan does not do +## (j) What this plan does not do -- **No write surface.** The edit-model's fourth rule still refuses; S1/S2's friction log is - the evidence its design has been waiting for, and the design session is the successor's. +- **No write surface.** The edit-model's fourth rule still refuses; W's and S1/S2's friction + log is the evidence its design has been waiting for, and the design session is the + successor's. - **No Gherkin importer, no `bySymbol`, no impact graph** — each named-and-deferred at the second-caller bar, with S2/S4 expected to produce the demand signal that either converts or retires them. +- **No new validators without an S0 ruling.** §d-5/6 name two candidate warn-level signals; + neither is built unless S0 finds the base forces it — W3's recipes carry the joins either + way, and a recipe that proves sufficient *retires* its signal candidate. - **No inline carrier — yet, and named rather than silent.** An in-code (JSDoc/comment-block) Spec carrier is a **recognized candidate**, deferred to a carrier-competition round gated on S2's friction evidence. The demand hypothesis: leaf-altitude, single-module Specs (a rule of one decider, an NFR of one store) fight the separate-file carrier hardest, and the origin's - fleet results (92%, 21/21) were achieved *because* its carrier was inline. The instrument: - S1/S2's friction log tallies which converted Specs are leaf-altitude, single-module, and - under ~30 lines. If that population is large, the response is a competition round in the - MD-18 style — same envelope statically extractable, loud refusals (no bare-marker invisible - nodes), parity fixtures against the Markdown form, duplicate-id policing the two-carriers- - one-ID case, and **no delivery vocabulary inline** (the reserved-property refusal applies - unchanged). Judged on exhibits, not on gen-1 nostalgia and not on this plan's prediction. + fleet results (92%, 21/21) were achieved *because* its carrier was inline. The instrument is + W4's first tally: each converted Spec logged as leaf-altitude/single-module/under-30-lines + or not, so the carrier-competition gate reads a measured population, not an impression. If + that population is large, the response is a competition round in the MD-18 style — same + envelope statically extractable, loud refusals (no bare-marker invisible nodes), parity + fixtures against the Markdown form, duplicate-id policing the two-carriers-one-ID case, and + **no delivery vocabulary inline** (the reserved-property refusal applies unchanged). Judged + on exhibits, not on gen-1 nostalgia and not on this plan's prediction. - **No new projections authored on speculation.** The owner's testimony (§(b)) predicts the restart will demand generated views beyond the Design Review (a roadmap/current-work surface, most likely); when a second machine consumer materializes, the standing recipe-to-projection bar converts it — never a pre-emptive generator suite, and never a committed artifact answering in the graph's name. -- **No workflow layer — but the on-ramp gap is named.** Gen 1's session-typed skills - (plan / design / implement / review-spec / review-implementation / handoff) were mature - *working guidance* the Protocol has no equivalent of — the shipped skill teaches reading - the graph, not authoring against it. The origin adoption will need an authoring-workflow - on-ramp (a skill, possibly Protocol-shipped) that teaches sessions without gating them — - guidance is lawful where gates are not. Scoped as a named successor artifact, informed by - S1/S2; not built here. +- **No session-typed workflow layer beyond the on-ramp.** Revision 1 deferred the whole + authoring-workflow question; revision 2 pulls in exactly one artifact — the W2 skill, + guidance that teaches without gating. Gen 1's full session taxonomy + (plan / design / implement / review-spec / review-implementation / handoff) stays evidence; + whether more skills are owed is a question W4's log answers for a successor, not this plan. - **No inward readiness sweep.** The 35 standing refusals stand; nothing here promotes them. - -## (j) Sequencing and session shape - -S0 is one session in this repo (rulings + rule-6 point + the gate-leg question), with one -review sized to its diff. S1–S4 execute in the origin repo as its own plan family (the origin -carries its own plans; this repo's record tracks only what changes here). S1 gates S2; S2 -gates S3 and S4's latency arm; S4's token arm can run any time after S1. The phase closes when -S0 has landed here and S1's tracer extracts green in the origin — the rest is the origin's -delivery, run under the Protocol it just adopted. + (W1 *adds* low-rung Specs; it promotes nothing existing.) + +## (k) Sequencing and session shape + +**S0** is one session in this repo (rulings §d-1..6 + the rule-6 point + the gate-leg +question), with one pre-close review sized to its diff. **W** follows in this repo — W1+W3 +land together (the backlog Specs and the recipes that read them), W2 lands against them, W4 +opens as a running log; one review at the W close, sized to its diff. **S1–S4** execute in the +origin repo as its own plan family (the origin carries its own plans; this repo's record +tracks only what changes here) — S1 gates S2; S2 gates S3 and S4's latency arm; S4's token arm +can run any time after S1. W2's skill is revised from S1's friction log as a small follow-up +session here. + +The phase closes when S0 and W have landed here, S1's tracer extracts green in the origin +with its usage acceptance met, and the friction log exists with all four tallies live — the +rest is the origin's delivery, run under the Protocol it just adopted. From e2feeaf6a4c6db034df79ca3904691135e13f4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 17:19:15 +0200 Subject: [PATCH 02/24] feat(protocol): establish phase-6 foundations and records gate --- AGENTS.md | 4 +- check-self-hosting-gates.mjs | 112 +++++++++++++----- docs/concept/DECISIONS.md | 1 + package.json | 3 +- plans/23-outward-turn-origin-adoption.md | 34 +++++- ...erification-posture-not-realization.sdp.md | 19 +++ .../build-pipeline.same-invocation.sdp.md | 21 ++++ specs/extraction/build-pipeline.sdp.md | 9 ++ specs/model/enrichment-lifecycle.sdp.md | 19 +++ specs/model/spec-sections.sdp.md | 2 + specs/self-hosting.pack.sdp.ts | 3 + test/bootstrap.test.ts | 5 +- test/check-self-hosting-gates.test.ts | 82 +++++++++++++ test/self-hosting-extraction.test.ts | 102 ++++++++++++++++ test/self-hosting-graph.test.ts | 14 +-- test/self-hosting-oracle/anchors.ts | 10 ++ test/self-hosting-oracle/decisions.ts | 29 +++++ .../self-hosting-oracle/declared-relations.ts | 5 + test/self-hosting-oracle/extraction.ts | 42 ++++++- test/self-hosting-oracle/model.ts | 33 ++++++ test/self-hosting-oracle/pack-members.ts | 3 + 21 files changed, 505 insertions(+), 47 deletions(-) create mode 100644 specs/decisions/verification-posture-not-realization.sdp.md create mode 100644 specs/extraction/build-pipeline.same-invocation.sdp.md create mode 100644 specs/model/enrichment-lifecycle.sdp.md create mode 100644 test/check-self-hosting-gates.test.ts diff --git a/AGENTS.md b/AGENTS.md index bb62d71..0826166 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ claims; **`src/` and tests** are authoritative evidence of current realization. > machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; > plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier > rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** ✅ EXECUTED — phase-5 implementation complete; the pre-close adversarial review is archived with every finding dispositioned. The prior `EXECUTED — phase-1 implementation complete; final audit passed` and the phase-2 to phase-4 statuses remain historic context. Two things landed. First, the validation-and-honesty concept document is **dissolved**: its last two uncarried deferrals — a per-team severity override and a team-overridable floor configuration — are carried as clauses on `spec:validation.warn-level-signals` and `spec:validation.readiness-floor`, and the document is deleted with every inbound reference re-pointed at its carrying Spec, leaving the surviving concept docs **`00` · `01` · `04` · `06` · `07`**. Second, the **agent front door** is ruled and shipped: `sdp q` (the agent front door, MD-22) is one evaluation sink over the very reader the package exports — two entrances, one seam, no query vocabulary minted — deriving the graph in process on every invocation and writing nothing, with the runnable **recipe catalog** at `docs/agent-surface/recipes.md` and the **skill on-ramp** at `.claude/skills/sdp-agent-surface/SKILL.md` beside it. The consumer family earned its promotions on **6 new whole-pipeline bound points**, each mutation-probed red: `spec:consumers.agent-surface` · `spec:consumers.reader` · `spec:consumers.design-review` are now `ready`. Corpus at **`ready: 80 / defined: 35`** over 115 Specs · 1 Pack · 86 anchors → 202 nodes · 397 edges, 0 errors / 0 warnings. The readiness sweep promoted nothing and named all 35 refusals honestly, and the optional measured-context refresh was **refused with its numbers recorded** rather than manufactured. **Next**, per plan 22 §5c: `spec:extraction.build-pipeline`'s rule 6 is the strongest bound-point candidate it has ever had (`sdp q` gives it a real world), the remaining `06` (five rows) and `07` (three rows) gaps stand out of scope, and the edit-model tail waits on a write surface that does not exist. Build state lives in +> as import source and a lawful per-ID option. · **what now:** plan 23 is EXECUTING — phase 6 is implementing the usage layer and the origin-project adoption from the reviewed plan. The latest settled ground remains the phase-5 close: the validation-and-honesty concept document dissolved, the agent front door and eight executable recipes shipped, and the corpus stood at `ready: 80 / defined: 35` over 115 Specs with zero findings. Phase 6 first generalizes the records audit, teaches authoring, and exercises the lower readiness rungs; it then installs the exact package snapshot in the origin, migrates the authored corpus, and cuts over atomically from Architect. Build state lives in > **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when @@ -62,7 +62,7 @@ Progressive disclosure — start at the top, follow the pointers down. | `examples/checkout-v1` | **the worked MVP example** (TS DSL tracer bullet) — specs, anchors, untracked `generated/` (regenerated in-pipeline); walkthrough in its README | when proving the loop end-to-end | | `explorations/` | **evidence only** (carrier exhibits, executable-example findings) — mapping evidence for design; **never promote spike code into product** | when judging design evidence; not a source tree to ship | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code — highest primary-numbered plan's status header, plus active subplans it designates; if DRAFTED, also the latest ✅ EXECUTED/RUN plan | -| `npm run check` | **the green gate** — `check:temporal` → lint → format:check → build → `generate:self-hosting` → `generate:example` → typecheck → `typecheck:examples` → test → `check:self-hosting` → `check:example` → `preflight` | before claiming green / after engine edits | +| `npm run check` | **the green gate** — `check:temporal` → `lint` → `format:check` → `build` → `generate:self-hosting` → `generate:example` → `typecheck` → `typecheck:examples` → `test` → `check:self-hosting-gates` → `check:self-hosting` → `check:example` → `preflight` | before claiming green / after engine edits | | `reviews/` | **archived session reviews** (implementation, founding-ideation, adversarial + prompts) — durable findings already folded into plans/DECISIONS; read for provenance | rarely | > Concept docs still carry implementation detail (TS shapes, DSL, graph JSON) for **unsettled and diff --git a/check-self-hosting-gates.mjs b/check-self-hosting-gates.mjs index 3bfe963..e6962cd 100644 --- a/check-self-hosting-gates.mjs +++ b/check-self-hosting-gates.mjs @@ -1,11 +1,11 @@ import { spawnSync } from "node:child_process"; -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, readFileSync, readdirSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -// check-self-hosting-gates — the structured consistency gate for the self-hosting phase's -// accepted docket close and its four-gate review ledger (git process evidence, never a -// graph fact). +// check-self-hosting-gates — frozen historical-record assertions plus universal current-record +// consistency. Historical process evidence stays pinned here, but later phases do not inherit +// plan-17/18's docket, owner-packet, or gate-ledger shapes. // // Asserts, and NAMES each disagreeing surface on failure: // 1. DOCKET — all obligations are non-pending (done/deferred/dropped with rationale), including @@ -17,10 +17,9 @@ import { fileURLToPath } from "node:url"; // phase-2 disposition. // 3. PACKET AGREEMENT — the ledger's fields agree with the owner-packet // dispositions, embedded here as constants read from those packets. -// 4. STATUS SURFACES — progress lives in the plan and the agent handbook only: the handbook -// stamps owner acceptance and final-audit pending, and its green-gate row names the current root+checkout -// chain; the handbook, the diary, and the glossary carry no plan-completion or -// plan-completion wording. +// 4. CURRENT RECORD — the highest primary-numbered plan has a readable status header, the +// handbook names that plan and status, semantic surfaces carry no plan status, and the +// documented green-gate legs agree with package.json. // 5. ADR DISPOSITIONS — both flagged §3 rulings carry explicit three-part-test outcomes: // lean diary entries (the strict consumer-exclusion contract (MD-20); the // envelope-grammar ownership posture (MD-21)) with registry rows, and the plan records @@ -45,6 +44,7 @@ const plan16Path = ["plans", "16-carrier-ruling.md"].join("/"); const agentsPath = "AGENTS.md"; const decisionsPath = "docs/concept/DECISIONS.md"; const glossaryPath = "CONTEXT.md"; +const packagePath = "package.json"; // Owner-packet dispositions, read from the three packets and embedded as constants: the // ledger must agree with them field for field. The shared gate date is assembled in parts. @@ -83,9 +83,28 @@ const plan = read(planPath); const agents = read(agentsPath); const decisions = read(decisionsPath); const glossary = read(glossaryPath); +const packageJson = JSON.parse(read(packagePath)); const plan16 = read(plan16Path); const plan18 = existsSync(join(rootDir, plan18Path)) ? read(plan18Path) : null; +const primaryPlans = readdirSync(join(rootDir, "plans"), { withFileTypes: true }) + .filter((entry) => entry.isFile()) + .map((entry) => { + const match = /^(?[1-9]\d*)-[a-z0-9][a-z0-9-]*\.md$/u.exec(entry.name); + const number = match?.groups?.number; + return number === undefined ? null : { name: entry.name, number: Number(number) }; + }) + .filter((entry) => entry !== null) + .sort((left, right) => right.number - left.number); +const currentPlan = primaryPlans[0]; + +if (currentPlan === undefined) { + failures.push("plans/ — no primary-numbered plan exists"); +} + +const currentPlanPath = currentPlan === undefined ? null : ["plans", currentPlan.name].join("/"); +const currentPlanSource = currentPlanPath === null ? "" : read(currentPlanPath); + // --------------------------------------------------------------------------- // 1. The docket: all 25 obligations are dispositioned. // --------------------------------------------------------------------------- @@ -204,40 +223,63 @@ if (ledgerStart === -1) { } // --------------------------------------------------------------------------- -// 4. Status surfaces: the handbook stamps the executed phase; semantics stay elsewhere. +// 4. Current record: select the current plan by convention and keep status/gate surfaces aligned. // --------------------------------------------------------------------------- -expectContains( - agentsPath, - agents, - "EXECUTED — phase-1 implementation complete; final audit passed", - "the handbook must stamp the executed phase status", -); +const currentStatusHeader = + currentPlanSource.split("\n").find((line) => line.startsWith("> **Status:**")) ?? ""; +const currentStatus = /\b(DRAFTED|EXECUTING|RUN|EXECUTED)\b/u.exec(currentStatusHeader)?.[1]; -const gateRowLine = agents.split("\n").find((line) => line.includes("the green gate")) ?? ""; -for (const needle of [ - "generate:self-hosting", - "generate:example", - "check:self-hosting", - "check:example", - "preflight", -]) { +if (currentStatusHeader === "") { + failures.push(`${currentPlanPath ?? "plans/"} — missing a readable blockquoted status header`); +} +if (currentStatus === undefined) { + failures.push( + `${currentPlanPath ?? "plans/"} — status header has no DRAFTED/EXECUTING/RUN/EXECUTED state`, + ); +} +if (currentPlan !== undefined) { expectContains( agentsPath, - gateRowLine, - needle, - `the green-gate row does not name the current root+checkout chain (${needle})`, + agents, + `plan ${String(currentPlan.number)}`, + "the handbook does not name the current primary plan", + ); +} +if (currentStatus !== undefined) { + expectContains( + agentsPath, + agents, + currentStatus, + "the handbook status disagrees with the current primary plan", ); } -// Numbered-plan wording: the handbook must not record the plan's execution state. -const numberedWording = /plan 17[^\n]{0,80}(landed|executed|accepted|completed|owner-accepted)/iu; -if (numberedWording.test(agents)) { - failures.push(`${agentsPath} — carries numbered wording (a plan-completion claim)`); +const checkScript = packageJson?.scripts?.check; +if (typeof checkScript !== "string") { + failures.push(`${packagePath} — scripts.check is missing`); +} +const actualGateLegs = + typeof checkScript !== "string" + ? [] + : [...checkScript.matchAll(/npm run ([\w:-]+)|npm test/gu)].map((match) => match[1] ?? "test"); +const gateRowLine = agents.split("\n").find((line) => line.includes("the green gate")) ?? ""; +const documentedGateLegs = [...gateRowLine.matchAll(/`([^`]+)`/gu)] + .map((match) => match[1]) + .filter((entry) => entry !== "npm run check"); + +if (gateRowLine === "") { + failures.push(`${agentsPath} — missing the documented green-gate row`); +} else if (JSON.stringify(documentedGateLegs) !== JSON.stringify(actualGateLegs)) { + failures.push( + `${agentsPath} ↔ ${packagePath} — documented green-gate legs disagree: documented ${documentedGateLegs.join(" → ")}; actual ${actualGateLegs.join(" → ")}`, + ); +} +if (!actualGateLegs.includes("check:self-hosting-gates")) { + failures.push(`${packagePath} — scripts.check does not require check:self-hosting-gates`); } -// The diary and the glossary are inspected for semantics only: no status may be written into -// them. (The diary's dated entries legitimately name the plan; completion claims are banned.) +// The diary and glossary are semantic surfaces: current and historical plan status stays out. for (const [surface, text] of [ [decisionsPath, decisions], [glossaryPath, glossary], @@ -358,6 +400,12 @@ const report = { decisions: decisionsPath, glossary: glossaryPath, phase2Plan: plan18 === null ? null : plan18Path, + currentPlan: currentPlanPath, + package: packagePath, + }, + currentRecord: { + status: currentStatus ?? null, + gateLegs: actualGateLegs, }, temporal, docket: { diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 2cf3728..7087976 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -31,6 +31,7 @@ positions and are never reused. | MD-20 | the strict consumer-exclusion contract | durable | Consumer exclusions are explicit root-relative paths. | [Spec](../../specs/decisions/exclusion-contract.sdp.md) (`spec:decisions.exclusion-contract`) | | MD-21 | the envelope-grammar ownership posture | durable | The Protocol owns the envelope contract, not the YAML library. | [Spec](../../specs/decisions/envelope-grammar-posture.sdp.md) (`spec:decisions.envelope-grammar-posture`) | | MD-22 | the agent front door | durable | The CLI's one evaluation sink and the exported reader are two entrances over one seam, deriving the graph in process on every invocation. | [Spec](../../specs/decisions/agent-front-door.sdp.md) (`spec:decisions.agent-front-door`) | +| MD-23 | verification posture, not realization | durable | `verification.mode` states intended posture; enabled-verifier realization remains derived. | [Spec](../../specs/decisions/verification-posture-not-realization.sdp.md) (`spec:decisions.verification-posture-not-realization`) | ### Current executable decision-spec pointers diff --git a/package.json b/package.json index 9145d65..3a4491b 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,10 @@ "generate:example": "node ./dist/cli/sdp.js build examples/checkout-v1", "generate:self-hosting": "node ./dist/cli/sdp.js view . --exclude explorations --exclude examples --exclude test/fixtures/import/parity", "check:self-hosting": "node ./dist/cli/sdp.js view . --exclude explorations --exclude examples --exclude test/fixtures/import/parity --check-clean", + "check:self-hosting-gates": "node ./check-self-hosting-gates.mjs", "check:example": "node ./dist/cli/sdp.js view examples/checkout-v1 --check-clean", "preflight": "node ./preflight.mjs", - "check": "npm run check:temporal && npm run lint && npm run format:check && npm run build && npm run generate:self-hosting && npm run generate:example && npm run typecheck && npm run typecheck:examples && npm test && npm run check:self-hosting && npm run check:example && npm run preflight" + "check": "npm run check:temporal && npm run lint && npm run format:check && npm run build && npm run generate:self-hosting && npm run generate:example && npm run typecheck && npm run typecheck:examples && npm test && npm run check:self-hosting-gates && npm run check:self-hosting && npm run check:example && npm run preflight" }, "devDependencies": { "@eslint/js": "^9.0.0", diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index a21399d..cde856f 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -1,10 +1,10 @@ # Plan 23 — The outward turn, refined: origin adoption, the usage layer, and the working ladder -> **Status:** DRAFTED — PLAN-ONLY, **revision 2** (2026-07-28 refinement session). No target file is -> touched under this plan until an execution session is designated. The latest ✅ EXECUTED ground is +> **Status:** EXECUTING — **revision 3** (2026-07-28 reviewed execution). Full S0–S4 execution, +> atomic cutover after parity, and local checkpoint/stage commits are authorized. The latest ✅ EXECUTED ground is > **plan 22** (self-hosting phase 5: the `05` dissolution and the agent front door; corpus at > 115 Specs · 1 Pack · 86 anchors → 202 nodes · 397 edges · `ready: 80 / defined: 35`, -> 0 errors / 0 warnings). This plan proposes **phase 6 as the usage turn**: the outward turn onto +> 0 errors / 0 warnings). This plan executes **phase 6 as the usage turn**: the outward turn onto > the origin project (`new-convex-es` / `libar-platform`) — unchanged from revision 1 — **plus the > usage layer this repo owes any adopter**, promoted into scope by the refinement session. Four > usage goals govern the phase (§a); a new workstream **W** (this repo) carries the authoring @@ -19,6 +19,12 @@ > (5) §b gains the inward evidence subsection (the 2026-07-28 engine survey). The origin evidence > base, the S0 rulings, and the deferral list otherwise stand as drafted. > +> **What revision 3 changed:** the execution review generalized the records gate before wiring it, +> authorized recoverable stage commits, returned both candidate rulings to the three-part test, +> restored the inline-carrier population instrument, matched the repository's single-file skill +> convention, fixed the package contract, and made stage gates explicit. The origin control-arm +> checkpoint is `4189d9cca`; the Protocol refinement checkpoint is `7a146dc`. +> > **Spec anchors:** [the agent-surface ruling](../specs/decisions/agent-surface-scripts-graph.sdp.md) > (`spec:decisions.agent-surface-scripts-graph`) · [the agent front door](../specs/decisions/agent-front-door.sdp.md) > (`spec:decisions.agent-front-door`, MD-22) · [the carrier ruling](../specs/decisions/markdown-carrier.sdp.md) @@ -261,6 +267,28 @@ phase), and **the gate-leg question** for `check-self-hosting-gates.mjs` (a proc standing ruling authorizes; put to the owner at the S0 close as a yes/no, and wired into `npm run check` only on a yes). +### S0 execution disposition + +The three-part test was written before the carrying Specs changed: + +| Candidate | Hard to reverse | Surprising without context | Real trade-off | Disposition | +|---|---|---|---|---| +| Atemporal planning | no new choice remains after the ratified descriptors, git-event-log law, and release-as-tag projection | no — the base already says schedule is not a Spec coordinate | no remaining representation trade-off | **fails admission** — no decision Spec. Packs may group wave-shaped review sets but never order them; the build backlog expresses eligibility, not a roadmap | +| Verification posture | yes — authored meaning becomes durable across carriers and consumers | yes — `mode: executable` can otherwise be mistaken for a realized verifier | yes — intended posture versus realization-derived honesty signal | **passes** — add the verification-posture-not-realization decision and no validator | +| Records-gate wiring | yes for repository process, not product semantics | yes — the historical audit looked like a perpetual phase template | yes — gate coverage versus mandatory legacy ceremony | **internal process ruling** — freeze plan-17/18 checks, require only universal current-record invariants, and wire the generalized audit | + +Anchored-but-unbound detection is explicitly deferred: `bindExample` call sites are not graph data, +so neither a signal nor a recipe can compute the claim honestly. The rule-6 bound point is added, +but `spec:extraction.build-pipeline` remains `defined` because one point does not cover its ordered +workflow. The enrichment-lifecycle question enters at `scoped` and remains blocking. + +**S0 gate evidence.** The generalized records audit accepts the current plan without imposing the +historical ledger shape. A mutated plan-17 owner-packet SHA fails with the historical surface +named; a handbook changed from `plan 23 is EXECUTING` to the stale plan-22 status fails both current +status invariants. The query-seam point passes through the real CLI/extractor, and replacing its +validation subjects with an empty result reddens exactly that point. The complete thirteen-leg +Protocol gate passes with `spec:extraction.build-pipeline` still stated `defined`. + ## (e) W — the usage layer (this repo; execution sessions after S0) The workstream that makes the four usage goals deliverables instead of hopes. Everything in W diff --git a/specs/decisions/verification-posture-not-realization.sdp.md b/specs/decisions/verification-posture-not-realization.sdp.md new file mode 100644 index 0000000..f3d5f0f --- /dev/null +++ b/specs/decisions/verification-posture-not-realization.sdp.md @@ -0,0 +1,19 @@ +--- +id: spec:decisions.verification-posture-not-realization +kind: decision +altitude: feature +readiness: defined +relations: + refines: spec:model.spec-sections +--- +# Verification mode states posture, not realization + +## Intent +- outcome: Keep authored verification intent distinct from derived evidence that a verifier exists. + +## Decision +- context: A Spec may declare `verification.mode: executable` before any resolving test anchor exists, while the graph already derives enabled-verifier realization from bindings. +- decision: `verification.mode` states the intended verification posture; enabled-verifier realization remains a derived fact and the two are never collapsed. +- rationale: Treating the authored mode as realization would duplicate and weaken the binding-derived fact, while warning on an unrealized posture would turn an intended direction into workflow or content-quality policing. +- consequence: No validator warns merely because `mode: executable` has no enabled verifier. +- consequence: Consumers report the authored mode and derived verifier bindings separately. diff --git a/specs/extraction/build-pipeline.same-invocation.sdp.md b/specs/extraction/build-pipeline.same-invocation.sdp.md new file mode 100644 index 0000000..3087c71 --- /dev/null +++ b/specs/extraction/build-pipeline.same-invocation.sdp.md @@ -0,0 +1,21 @@ +--- +id: spec:extraction.build-pipeline.same-invocation +kind: example +altitude: story +readiness: ready +relations: + refines: spec:extraction.build-pipeline + verifies: spec:extraction.build-pipeline +--- +# One query invocation shares its extracted graph and validation result + +## Intent +- outcome: Prove the query body receives a reader, raw graph, and validation report produced from the same invocation's extracted graph. + +```gwt +Given an extraction root containing the isolated spec {specId: "spec:probe.same-invocation"} +When one query invocation reads the reader, raw graph, and validation report +Then the query exits {exitCode: 0} +Then both graph entrances return the spec {returnedSpecId: "spec:probe.same-invocation"} +Then the validation report names the same subject {findingSubjectId: "spec:probe.same-invocation"} +``` diff --git a/specs/extraction/build-pipeline.sdp.md b/specs/extraction/build-pipeline.sdp.md index 4f23989..a05f1a5 100644 --- a/specs/extraction/build-pipeline.sdp.md +++ b/specs/extraction/build-pipeline.sdp.md @@ -19,3 +19,12 @@ relations: - Validate the graph. - Emit derived artifacts. - rule: Every command uses the same extracted graph and validation seam. + +## Example space +```gwt-vocabulary +Given an extraction root containing the isolated spec {specId:string} +When one query invocation reads the reader, raw graph, and validation report +Then the query exits {exitCode:number} +Then both graph entrances return the spec {returnedSpecId:string} +Then the validation report names the same subject {findingSubjectId:string} +``` diff --git a/specs/model/enrichment-lifecycle.sdp.md b/specs/model/enrichment-lifecycle.sdp.md new file mode 100644 index 0000000..a94717e --- /dev/null +++ b/specs/model/enrichment-lifecycle.sdp.md @@ -0,0 +1,19 @@ +--- +id: spec:model.enrichment-lifecycle +kind: model +altitude: feature +readiness: scoped +relations: + refines: spec:model.core-model +--- +# Enrichment keeps one Spec while its detail changes + +## Intent +- outcome: Keep a Spec useful after implementation without recreating value-transfer duplication. + +### Open questions +- [blocking] After implementation, which design-time detail stays in the Spec and which detail may be removed while preserving one durable home for each explanation? + +## Model +- **enrichment lifecycle** — The same Spec gains and may later slim typed detail without changing identity or moving truth into another artifact type. +- **distillation boundary** — Implemented code does not automatically justify either retaining or deleting design-time detail; the unresolved policy must preserve one home per explanation. diff --git a/specs/model/spec-sections.sdp.md b/specs/model/spec-sections.sdp.md index ae6b055..ac5c73f 100644 --- a/specs/model/spec-sections.sdp.md +++ b/specs/model/spec-sections.sdp.md @@ -9,6 +9,7 @@ relations: - spec:decisions.point-per-example - spec:decisions.content-only-sections - spec:decisions.typing-law + - spec:decisions.verification-posture-not-realization --- # Spec sections carry typed detail and direct verifier semantics @@ -22,3 +23,4 @@ relations: - **promotion** — Moving shared or independently reviewed content into a standalone Spec of the matching kind, exclusively rather than alongside inline content. - **verifies** — A direct verifier-to-target relation whose enabled test binding can derive has-verifier only for that stated target. - **enabled verifier** — An example or direct test with a linked, resolvable test anchor; runner execution and pass state remain outside the graph. +- **verification mode** — Authored intended posture such as executable; it never stands in for the derived enabled-verifier realization. diff --git a/specs/self-hosting.pack.sdp.ts b/specs/self-hosting.pack.sdp.ts index da833bb..e47554d 100644 --- a/specs/self-hosting.pack.sdp.ts +++ b/specs/self-hosting.pack.sdp.ts @@ -15,6 +15,7 @@ export const selfHostingV1Pack = pack({ ref("spec:extraction.derive-graph"), ref("spec:extraction.determinism"), ref("spec:extraction.build-pipeline"), + ref("spec:extraction.build-pipeline.same-invocation"), ref("spec:extraction.excludes"), ref("spec:extraction.claim-taxonomy"), ref("spec:extraction.regenerability"), @@ -36,6 +37,7 @@ export const selfHostingV1Pack = pack({ ref("spec:consumers.edit-model"), ref("spec:model.protocol-domain"), ref("spec:model.core-model"), + ref("spec:model.enrichment-lifecycle"), ref("spec:model.spec-sections"), ref("spec:model.relations"), ref("spec:model.stable-ids"), @@ -120,6 +122,7 @@ export const selfHostingV1Pack = pack({ ref("spec:decisions.agent-surface-scripts-graph"), ref("spec:decisions.mcp-deferred"), ref("spec:decisions.agent-front-door"), + ref("spec:decisions.verification-posture-not-realization"), ], modelRefs: [ref("spec:model.protocol-domain"), ref("spec:model.core-model")], }); diff --git a/test/bootstrap.test.ts b/test/bootstrap.test.ts index 6624476..6c78da8 100644 --- a/test/bootstrap.test.ts +++ b/test/bootstrap.test.ts @@ -67,9 +67,12 @@ describe("bootstrap package surface", () => { expect(packageJson.scripts["check:self-hosting"]).toBe( "node ./dist/cli/sdp.js view . --exclude explorations --exclude examples --exclude test/fixtures/import/parity --check-clean", ); + expect(packageJson.scripts["check:self-hosting-gates"]).toBe( + "node ./check-self-hosting-gates.mjs", + ); expect(packageJson.scripts.preflight).toBe("node ./preflight.mjs"); expect(packageJson.scripts.check).toBe( - "npm run check:temporal && npm run lint && npm run format:check && npm run build && npm run generate:self-hosting && npm run generate:example && npm run typecheck && npm run typecheck:examples && npm test && npm run check:self-hosting && npm run check:example && npm run preflight", + "npm run check:temporal && npm run lint && npm run format:check && npm run build && npm run generate:self-hosting && npm run generate:example && npm run typecheck && npm run typecheck:examples && npm test && npm run check:self-hosting-gates && npm run check:self-hosting && npm run check:example && npm run preflight", ); }); diff --git a/test/check-self-hosting-gates.test.ts b/test/check-self-hosting-gates.test.ts new file mode 100644 index 0000000..5ced9a0 --- /dev/null +++ b/test/check-self-hosting-gates.test.ts @@ -0,0 +1,82 @@ +import { spawnSync } from "node:child_process"; +import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { afterEach, describe, expect, it } from "vitest"; + +const repoRoot = fileURLToPath(new URL("..", import.meta.url)); +const guardPath = join(repoRoot, "check-self-hosting-gates.mjs"); +const roots: string[] = []; +const copiedPaths = [ + "AGENTS.md", + "CONTEXT.md", + "package.json", + "docs/concept/DECISIONS.md", + ["plans", "16-carrier-ruling.md"].join("/"), + ["plans", "17-self-hosting-v1.md"].join("/"), + ["plans", "18-self-hosting-phase-2.md"].join("/"), + ["plans", "23-outward-turn-origin-adoption.md"].join("/"), +] as const; + +function copyRecordTree(): string { + const root = mkdtempSync(join(tmpdir(), "sdp-self-hosting-gates-")); + roots.push(root); + + for (const path of copiedPaths) { + mkdirSync(dirname(join(root, path)), { recursive: true }); + copyFileSync(join(repoRoot, path), join(root, path)); + } + + return root; +} + +function runGuard(root: string) { + return spawnSync(process.execPath, [guardPath, root], { encoding: "utf8" }); +} + +afterEach(() => { + for (const root of roots.splice(0)) { + rmSync(root, { recursive: true, force: true }); + } +}); + +describe("the self-hosting records gate", () => { + it("accepts the current record without requiring the historical ledger shape", () => { + const result = runGuard(copyRecordTree()); + + expect({ status: result.status, stderr: result.stderr }).toEqual({ status: 0, stderr: "" }); + }); + + it("fails when a frozen historical owner-packet pin is broken", () => { + const root = copyRecordTree(); + const path = join(root, "plans", "17-self-hosting-v1.md"); + const source = readFileSync(path, "utf8"); + writeFileSync( + path, + source.replace( + "aca79090529c2f6625ceafc78f33e16da81bfcb1", + "0000000000000000000000000000000000000000", + ), + ); + + const result = runGuard(root); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("Gate 1 ledger SHA disagrees with the owner packet"); + }); + + it("fails when the handbook carries a stale current-plan status", () => { + const root = copyRecordTree(); + const path = join(root, "AGENTS.md"); + const source = readFileSync(path, "utf8"); + writeFileSync(path, source.replace("plan 23 is EXECUTING", "plan 22 is EXECUTED")); + + const result = runGuard(root); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("the handbook does not name the current primary plan"); + expect(result.stderr).toContain("the handbook status disagrees with the current primary plan"); + }); +}); diff --git a/test/self-hosting-extraction.test.ts b/test/self-hosting-extraction.test.ts index 728613b..c993237 100644 --- a/test/self-hosting-extraction.test.ts +++ b/test/self-hosting-extraction.test.ts @@ -7,6 +7,7 @@ import { afterEach, expect } from "vitest"; import { ref, specTest, testAnchorId } from "@libar-dev/software-delivery-protocol"; import { bindExample } from "@libar-dev/software-delivery-protocol/vitest"; +import { sameInvocationContract } from "../generated/contracts/extraction.build-pipeline.same-invocation.contract.js"; import { refusedPathContract } from "../generated/contracts/extraction.excludes.refused-path.contract.js"; import { segmentBoundaryContract } from "../generated/contracts/extraction.excludes.segment-boundary.contract.js"; import { caseCollidingPathContract } from "../generated/contracts/extraction.executable-contracts.case-colliding-path.contract.js"; @@ -22,6 +23,8 @@ import { deriveGraph, generateContracts, refines, spec, specId } from "../src/in import type { GeneratedContracts, GraphSchema, Spec } from "../src/index.js"; import { planExample, runExamplePlan } from "../src/runner/index.js"; import type { ExampleContract, StepKind } from "../src/runner/index.js"; +import { runSdpCli } from "../src/cli/sdp.js"; +import { createCaptureOutput } from "./helpers/cli-capture.js"; import { deriveFixtureGraph } from "./helpers/fixture-graph.js"; /** @@ -46,6 +49,105 @@ afterEach(() => { temporaryRoots.clear(); }); +/* ----- spec:extraction.build-pipeline ----- */ + +interface SameInvocationWorld { + readonly root: string; + exitCode: number | undefined; + answer: + | { + readonly reader: readonly string[]; + readonly graph: readonly string[]; + readonly findingSubjects: readonly string[]; + } + | undefined; +} + +function sameInvocationWorld(): SameInvocationWorld { + const root = mkdtempSync(join(tmpdir(), "sdp-same-invocation-")); + temporaryRoots.add(root); + + return { root, exitCode: undefined, answer: undefined }; +} + +const sameInvocationBindings = { + "an extraction root containing the isolated spec {specId}": ( + world: SameInvocationWorld, + params: { readonly specId: string }, + ) => { + writeFileSync( + join(world.root, "probe.sdp.md"), + `--- +id: ${params.specId} +kind: behavior +altitude: story +readiness: idea +relations: {} +--- +# Same-invocation probe + +## Intent +- outcome: Exist only for the query seam point. +`, + "utf8", + ); + }, + "one query invocation reads the reader, raw graph, and validation report": async ( + world: SameInvocationWorld, + ) => { + const capture = createCaptureOutput(); + const body = ` + return { + reader: g.specs().map((spec) => spec.id), + graph: graph.nodes.filter((node) => node.nodeType === "Primitive").map((node) => node.id), + findingSubjects: report.findings.map((finding) => finding.subjectId), + }; + `; + + world.exitCode = await runSdpCli(["q", body, "--root", world.root, "--json"], capture.output, { + query: { + isStdinTty: () => true, + readStdin: () => { + throw new Error("the point supplies its body on argv"); + }, + }, + }); + const stderr = capture.readStderr(); + const stdout = capture.readStdout(); + + expect({ exitCode: world.exitCode, stderr }).toEqual({ exitCode: 0, stderr: "" }); + world.answer = JSON.parse(stdout) as SameInvocationWorld["answer"]; + }, + "the query exits {exitCode}": ( + world: SameInvocationWorld, + params: { readonly exitCode: number }, + ) => { + expect(world.exitCode).toBe(params.exitCode); + }, + "both graph entrances return the spec {returnedSpecId}": ( + world: SameInvocationWorld, + params: { readonly returnedSpecId: string }, + ) => { + expect(world.answer?.reader).toEqual([params.returnedSpecId]); + expect(world.answer?.graph).toEqual([params.returnedSpecId]); + }, + "the validation report names the same subject {findingSubjectId}": ( + world: SameInvocationWorld, + params: { readonly findingSubjectId: string }, + ) => { + expect(world.answer?.findingSubjects).toContain(params.findingSubjectId); + }, +}; + +const buildPipelineSameInvocationTestAnchor = specTest({ + id: testAnchorId("test:protocol.build-pipeline.same-invocation"), + label: "the same-invocation point verifies the query extraction and validation seam", + verifies: ref("spec:extraction.build-pipeline.same-invocation"), +}); +void buildPipelineSameInvocationTestAnchor; + +bindExample(sameInvocationContract, sameInvocationWorld, sameInvocationBindings); + /* ----- spec:extraction.excludes ----- */ interface ExcludeWorld { diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index 6239e62..19df85a 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -90,12 +90,12 @@ describe("the self-hosting corpus", () => { // The literals are the corpus checkpoint. The authored arrays are measured against the same // literals rather than standing in for them, so a transcription slip in an oracle module // cannot certify itself by moving both sides of a comparison at once. - expect(result.counts).toEqual({ specs: 115, packs: 1, anchors: 86 }); - expect(expectedSpecs).toHaveLength(115); - expect(expectedPackMembers).toHaveLength(115); - expect(expectedAnchors).toHaveLength(86); - expect(result.graph.nodes).toHaveLength(202); - expect(result.graph.edges).toHaveLength(397); + expect(result.counts).toEqual({ specs: 118, packs: 1, anchors: 87 }); + expect(expectedSpecs).toHaveLength(118); + expect(expectedPackMembers).toHaveLength(118); + expect(expectedAnchors).toHaveLength(87); + expect(result.graph.nodes).toHaveLength(206); + expect(result.graph.edges).toHaveLength(406); }); it("rosters exactly the authored Spec, Pack, and anchor node ids", () => { @@ -145,7 +145,7 @@ describe("the self-hosting corpus", () => { }), {}, ), - ).toEqual({ defined: 35, ready: 80 }); + ).toEqual({ defined: 36, ready: 81, scoped: 1 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { diff --git a/test/self-hosting-oracle/anchors.ts b/test/self-hosting-oracle/anchors.ts index 9c1cac7..db2d280 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -43,6 +43,16 @@ export const expectedAnchors = [ constant: "cleanRepoDeterminismTestAnchor", site: 'it("clean-repo determinism: the full pipeline at a different absolute path is byte-identical"', }, + { + id: "test:protocol.build-pipeline.same-invocation", + nodeType: "Anchor", + label: "the same-invocation point verifies the query extraction and validation seam", + type: "verifies", + target: "spec:extraction.build-pipeline.same-invocation", + file: "test/self-hosting-extraction.test.ts", + constant: "buildPipelineSameInvocationTestAnchor", + site: "bindExample(sameInvocationContract", + }, { id: "impl:protocol.readiness-floor", nodeType: "CodeNode", diff --git a/test/self-hosting-oracle/decisions.ts b/test/self-hosting-oracle/decisions.ts index a3a54d2..37e7bfc 100644 --- a/test/self-hosting-oracle/decisions.ts +++ b/test/self-hosting-oracle/decisions.ts @@ -590,4 +590,33 @@ export const decisionsSpecs = [ }, deliveryFacts: [], }, + { + id: "spec:decisions.verification-posture-not-realization", + specKind: "decision", + altitude: "feature", + readiness: "defined", + file: "specs/decisions/verification-posture-not-realization.sdp.md", + title: "Verification mode states posture, not realization", + narrative: null, + sections: { + intent: { + outcome: + "Keep authored verification intent distinct from derived evidence that a verifier exists.", + }, + decision: { + context: + "A Spec may declare `verification.mode: executable` before any resolving test anchor exists, while the graph already derives enabled-verifier realization from bindings.", + decision: + "`verification.mode` states the intended verification posture; enabled-verifier realization remains a derived fact and the two are never collapsed.", + rationale: [ + "Treating the authored mode as realization would duplicate and weaken the binding-derived fact, while warning on an unrealized posture would turn an intended direction into workflow or content-quality policing.", + ], + consequences: [ + "No validator warns merely because `mode: executable` has no enabled verifier.", + "Consumers report the authored mode and derived verifier bindings separately.", + ], + }, + }, + deliveryFacts: [], + }, ] as const; diff --git a/test/self-hosting-oracle/declared-relations.ts b/test/self-hosting-oracle/declared-relations.ts index 626b875..ac7693b 100644 --- a/test/self-hosting-oracle/declared-relations.ts +++ b/test/self-hosting-oracle/declared-relations.ts @@ -28,6 +28,8 @@ export const expectedDeclaredRelations = [ ["spec:extraction.determinism", "refines", "spec:protocol.self-hosting"], ["spec:extraction.build-pipeline", "refines", "spec:protocol.self-hosting"], ["spec:extraction.build-pipeline", "dependsOn", "spec:extraction.derive-graph"], + ["spec:extraction.build-pipeline.same-invocation", "refines", "spec:extraction.build-pipeline"], + ["spec:extraction.build-pipeline.same-invocation", "verifies", "spec:extraction.build-pipeline"], ["spec:extraction.excludes", "refines", "spec:extraction.derive-graph"], ["spec:extraction.excludes", "decidedBy", "spec:decisions.exclusion-contract"], ["spec:extraction.excludes.segment-boundary", "refines", "spec:extraction.excludes"], @@ -267,11 +269,13 @@ export const expectedDeclaredRelations = [ ["spec:consumers.edit-model", "refines", "spec:consumers.projections-model"], ["spec:model.protocol-domain", "refines", "spec:protocol.self-hosting"], ["spec:model.core-model", "refines", "spec:protocol.self-hosting"], + ["spec:model.enrichment-lifecycle", "refines", "spec:model.core-model"], ["spec:model.core-model", "decidedBy", "spec:decisions.one-primitive"], ["spec:model.spec-sections", "refines", "spec:model.core-model"], ["spec:model.spec-sections", "decidedBy", "spec:decisions.point-per-example"], ["spec:model.spec-sections", "decidedBy", "spec:decisions.content-only-sections"], ["spec:model.spec-sections", "decidedBy", "spec:decisions.typing-law"], + ["spec:model.spec-sections", "decidedBy", "spec:decisions.verification-posture-not-realization"], ["spec:model.relations", "refines", "spec:model.core-model"], ["spec:model.stable-ids", "refines", "spec:model.core-model"], ["spec:model.stable-ids.namespaced-round-trip", "refines", "spec:model.stable-ids"], @@ -304,6 +308,7 @@ export const expectedDeclaredRelations = [ ["spec:decisions.agent-surface-scripts-graph", "refines", "spec:consumers.agent-surface"], ["spec:decisions.mcp-deferred", "refines", "spec:consumers.projections-model"], ["spec:decisions.agent-front-door", "refines", "spec:consumers.agent-surface"], + ["spec:decisions.verification-posture-not-realization", "refines", "spec:model.spec-sections"], ["spec:model.anchors.lookalike-refusal", "refines", "spec:model.anchors"], ["spec:model.anchors.lookalike-refusal", "verifies", "spec:model.anchors"], ["spec:model.anchors.physical-identity", "refines", "spec:model.anchors"], diff --git a/test/self-hosting-oracle/extraction.ts b/test/self-hosting-oracle/extraction.ts index fbaa287..91d56b6 100644 --- a/test/self-hosting-oracle/extraction.ts +++ b/test/self-hosting-oracle/extraction.ts @@ -524,8 +524,48 @@ export const extractionSpecs = [ "Validate the graph.", "Emit derived artifacts.", ], + exampleSpace: { + given: ["an extraction root containing the isolated spec {specId:string}"], + when: ["one query invocation reads the reader, raw graph, and validation report"], + then: [ + "the query exits {exitCode:number}", + "both graph entrances return the spec {returnedSpecId:string}", + "the validation report names the same subject {findingSubjectId:string}", + ], + }, }, }, - deliveryFacts: [], + deliveryFacts: ["has-verifier"], + }, + { + id: "spec:extraction.build-pipeline.same-invocation", + specKind: "example", + altitude: "story", + readiness: "ready", + file: "specs/extraction/build-pipeline.same-invocation.sdp.md", + title: "One query invocation shares its extracted graph and validation result", + narrative: null, + sections: { + intent: { + outcome: + "Prove the query body receives a reader, raw graph, and validation report produced from the same invocation's extracted graph.", + }, + behavior: { + examples: [ + { + given: [ + 'an extraction root containing the isolated spec {specId: "spec:probe.same-invocation"}', + ], + when: ["one query invocation reads the reader, raw graph, and validation report"], + then: [ + "the query exits {exitCode: 0}", + 'both graph entrances return the spec {returnedSpecId: "spec:probe.same-invocation"}', + 'the validation report names the same subject {findingSubjectId: "spec:probe.same-invocation"}', + ], + }, + ], + }, + }, + deliveryFacts: ["has-verifier"], }, ] as const; diff --git a/test/self-hosting-oracle/model.ts b/test/self-hosting-oracle/model.ts index 98600ef..998c66f 100644 --- a/test/self-hosting-oracle/model.ts +++ b/test/self-hosting-oracle/model.ts @@ -80,11 +80,44 @@ export const modelSpecs = [ "Every section read by a readiness-floor clause has a closed typed shape; unsettled design and ui surfaces remain open bags.", verifies: "A direct verifier-to-target relation whose enabled test binding can derive has-verifier only for that stated target.", + "verification mode": + "Authored intended posture such as executable; it never stands in for the derived enabled-verifier realization.", }, }, }, deliveryFacts: ["implemented"], }, + { + id: "spec:model.enrichment-lifecycle", + specKind: "model", + altitude: "feature", + readiness: "scoped", + file: "specs/model/enrichment-lifecycle.sdp.md", + title: "Enrichment keeps one Spec while its detail changes", + narrative: null, + sections: { + intent: { + outcome: + "Keep a Spec useful after implementation without recreating value-transfer duplication.", + openQuestions: [ + { + question: + "After implementation, which design-time detail stays in the Spec and which detail may be removed while preserving one durable home for each explanation?", + blocking: true, + }, + ], + }, + model: { + terms: { + "enrichment lifecycle": + "The same Spec gains and may later slim typed detail without changing identity or moving truth into another artifact type.", + "distillation boundary": + "Implemented code does not automatically justify either retaining or deleting design-time detail; the unresolved policy must preserve one home per explanation.", + }, + }, + }, + deliveryFacts: [], + }, { id: "spec:model.relations", specKind: "model", diff --git a/test/self-hosting-oracle/pack-members.ts b/test/self-hosting-oracle/pack-members.ts index 3f3be49..144de8a 100644 --- a/test/self-hosting-oracle/pack-members.ts +++ b/test/self-hosting-oracle/pack-members.ts @@ -12,6 +12,7 @@ export const expectedPackMembers = [ "spec:extraction.derive-graph", "spec:extraction.determinism", "spec:extraction.build-pipeline", + "spec:extraction.build-pipeline.same-invocation", "spec:extraction.excludes", "spec:extraction.claim-taxonomy", "spec:extraction.regenerability", @@ -33,6 +34,7 @@ export const expectedPackMembers = [ "spec:consumers.edit-model", "spec:model.protocol-domain", "spec:model.core-model", + "spec:model.enrichment-lifecycle", "spec:model.spec-sections", "spec:model.relations", "spec:model.stable-ids", @@ -117,4 +119,5 @@ export const expectedPackMembers = [ "spec:decisions.agent-surface-scripts-graph", "spec:decisions.mcp-deferred", "spec:decisions.agent-front-door", + "spec:decisions.verification-posture-not-realization", ] as const; From a16e5ce3be6904af82fb1490bdd906eafb25819d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 17:31:20 +0200 Subject: [PATCH 03/24] feat(protocol): ship graph-first authoring usage layer --- .claude/skills/sdp-agent-surface/SKILL.md | 6 +- .claude/skills/sdp-authoring/SKILL.md | 75 ++++++++ docs/agent-surface/recipes.md | 113 ++++++++++- package.json | 5 +- plans/23-outward-turn-origin-adoption.md | 57 +++--- .../agent-surface.authoring-recipes.sdp.md | 17 ++ specs/consumers/authoring-on-ramp.sdp.md | 19 ++ specs/consumers/intent-composition.sdp.md | 16 ++ specs/self-hosting.pack.sdp.ts | 3 + test/package-smoke.test.ts | 44 ++++- test/recipes.test.ts | 91 +++++++-- test/self-hosting-graph.test.ts | 14 +- test/self-hosting-oracle/anchors.ts | 40 ++++ test/self-hosting-oracle/consumers.ts | 70 +++++++ .../self-hosting-oracle/declared-relations.ts | 3 + test/self-hosting-oracle/pack-members.ts | 3 + test/skills.test.ts | 179 ++++++++++++++++++ 17 files changed, 705 insertions(+), 50 deletions(-) create mode 100644 .claude/skills/sdp-authoring/SKILL.md create mode 100644 specs/consumers/agent-surface.authoring-recipes.sdp.md create mode 100644 specs/consumers/authoring-on-ramp.sdp.md create mode 100644 specs/consumers/intent-composition.sdp.md create mode 100644 test/skills.test.ts diff --git a/.claude/skills/sdp-agent-surface/SKILL.md b/.claude/skills/sdp-agent-surface/SKILL.md index 3b6ffe9..5c50fd8 100644 --- a/.claude/skills/sdp-agent-surface/SKILL.md +++ b/.claude/skills/sdp-agent-surface/SKILL.md @@ -16,13 +16,15 @@ evaluation sink. There is no verb wall — you script the graph. For any corpus question, **query the graph before reading spec files**. ```sh -sdp q 'return g.specs().length' --exclude explorations --exclude examples --exclude test/fixtures/import/parity -sdp q 'return g.specContext("spec:consumers.reader")' --exclude explorations --exclude examples --exclude test/fixtures/import/parity --json +node ./dist/cli/sdp.js q 'return g.specs().length' --exclude explorations --exclude examples --exclude test/fixtures/import/parity +node ./dist/cli/sdp.js q 'return g.specContext("spec:consumers.reader")' --exclude explorations --exclude examples --exclude test/fixtures/import/parity --json ``` Those three exclusions are this repository's own and they are **required here**: the corpus carries deliberate duplicate-id and carrier-parity fixtures, so without them the graph does not derive and the sink refuses to run the body. They are the same list `npm run generate:self-hosting` passes. +Run `npm run build` first if `dist/` is absent. Do not invoke a global `sdp`: macOS also ships an +unrelated binary with that name. In an adopter, use its package runner, such as `pnpm exec sdp`. The catalog of ready-made bodies is `docs/agent-surface/recipes.md` — build backlog, drift alarm, per-Spec guarantees and verifiers, blast radius, Pack review backbone, concept search, readiness diff --git a/.claude/skills/sdp-authoring/SKILL.md b/.claude/skills/sdp-authoring/SKILL.md new file mode 100644 index 0000000..0f7984d --- /dev/null +++ b/.claude/skills/sdp-authoring/SKILL.md @@ -0,0 +1,75 @@ +--- +name: sdp-authoring +description: Author and mature Protocol Specs through the graph-first workflow. Use when creating or editing `.sdp.md` carriers, deciding the honest readiness rung, promoting inline content, generating executable contracts, binding examples or implementation anchors, mutation-probing evidence, or preparing a Spec for human review and a `ready` statement. +--- + +# Author Specs through the graph + +Treat the canonical carrier as the write surface and the derived graph as the read model. Start every +session with the build-backlog and drift-alarm recipes in `docs/agent-surface/recipes.md`. + +At this repository root, use the exact self-hosting exclusions: + +```sh +node ./dist/cli/sdp.js q 'return g.specs().filter((spec) => spec.statedReadiness === "ready" && !spec.deliveryFacts.includes("implemented")).map((spec) => spec.id)' --exclude explorations --exclude examples --exclude test/fixtures/import/parity +node ./dist/cli/sdp.js q 'return g.specs().filter((spec) => spec.deliveryFacts.includes("implemented") && spec.statedReadiness !== "ready").map((spec) => spec.id)' --exclude explorations --exclude examples --exclude test/fixtures/import/parity +``` + +For an adopter, select its root and exclusions explicitly: + +```sh +pnpm exec sdp q 'return g.specs().map((spec) => spec.id)' --root PATH +pnpm exec sdp q 'return g.specs().map((spec) => spec.id)' --root PATH --exclude PATH --exclude PATH +``` + +Build this repository before using its local CLI. Never rely on a global `sdp`: macOS also ships an +unrelated binary with that name. Adopters should use their chosen package runner. + +## Create and enrich + +1. Read `CONTEXT.md`, then query nearby Specs with recipe 3 or 6. Do not parse the corpus by hand. +2. Create the Markdown carrier with one stable `spec:` id, title, kind, altitude, readiness, and + relations. The carrier law is `spec:decisions.carrier-ruling`; the envelope and section law is + `spec:model.spec-sections`. +3. State only the rung the structure clears. Use recipe 9 for the current floor, recipe 11 for the + lower ladder, and read `spec:validation.readiness-floor` plus + `spec:validation.kind-evidence` for the clauses. +4. Keep local detail inline. Promote it only when it needs shared identity, binding, or independent + review; follow `spec:decisions.content-only-sections`. +5. Put unresolved durable questions under Intent. A blocking question honestly keeps the Spec + below `defined`. + +## Make an example executable + +1. Put the typed `gwt-vocabulary` example space on the parent. +2. Put one concrete `gwt` bound point on each example child, following + `spec:decisions.point-per-example`. +3. Generate contracts from the adopter root: + + ```sh + pnpm exec sdp build . + ``` + + Diagnose contract refusals from `sdp build`; `sdp q` receives graph-validation findings, not + codegen findings. + +4. In the verifier suite, colocate `bindExample(generatedContract, world, bindings)` with a + `specTest` anchor targeting that example. +5. Mutate one expected result and prove the new point goes red, then restore it. Keep runner + execution and pass state outside the graph. + +The graph can report a resolving `specTest` binding. It cannot detect a generated contract that no +suite binds because `bindExample` call sites are not extracted graph data. + +## Bind implementation and review + +Add a `codeAnchor` beside the code that realizes the Spec, following +`spec:decisions.binding-not-liveness` and `spec:model.anchors`. An anchor states identity and one +target only; it never carries intent, readiness, or runtime truth. + +Regenerate the Design Review, inspect the Spec in context, and run recipes 7–11. Tooling never +confers `ready`: after the floor clears and the evidence is reviewed, a human may state it by +editing the canonical carrier. + +The graph outranks this skill. If a recipe or instruction disagrees with current graph data or a +carrying Spec, report the skill as drift and follow the graph and Spec. diff --git a/docs/agent-surface/recipes.md b/docs/agent-surface/recipes.md index 48d9e0a..bb269af 100644 --- a/docs/agent-surface/recipes.md +++ b/docs/agent-surface/recipes.md @@ -13,8 +13,17 @@ duplicate-id and carrier-parity fixtures under `examples/`, `explorations/`, and `test/fixtures/import/parity/`; without those three exclusions the extractor reports errors, the graph does not derive, and the sink refuses to run the body at all. The three above are exactly the project's own — the same list `npm run generate:self-hosting` passes and the same list the recipe -check derives with. Elsewhere, `--root PATH` picks the extraction root (default: the working -directory) and `--exclude` is repeatable for root-relative path prefixes. +check derives with. + +For adopters, the portable form keeps root and exclusions project-selected: + +```sh +sdp q '' --root PATH +sdp q '' --root PATH --exclude PATH --exclude PATH +``` + +`--root PATH` picks the extraction root (default: the working directory) and `--exclude` is +repeatable for root-relative path prefixes. `PATH` is a placeholder, not a literal directory. **The contract, in one place.** The front door derives the graph in process and evaluates the body you supply; `return` is the output contract. Three bindings are injected: @@ -339,3 +348,103 @@ return { one. Gaps and orphans are warn-level by design: a `ready` Spec with no verifier and a Spec nothing points at are both worth surfacing and neither is a failure. Errors are the conformance and honesty refusals; on a green corpus both counts read zero. + +## 9. Promotion preflight + +*When you need this: you are considering a readiness edit and want the current graph-visible floor +evidence before touching the carrier.* + +```js +const id = "spec:model.enrichment-lifecycle"; +const context = g.specContext(id); + +if (context === undefined) { + return { id, found: false }; +} + +const rungs = ["idea", "scoped", "defined", "ready"]; +const reached = context.derivedReadiness ?? "none"; +const reachedIndex = reached === "none" ? -1 : rungs.indexOf(reached); + +return { + id, + found: true, + statedReadiness: context.statedReadiness, + floorReached: reached, + nextRung: rungs[reachedIndex + 1] ?? null, + currentFloorFailures: context.floorFailures.map((failure) => ({ + clauseId: failure.clauseId, + description: failure.description, + })), + firstUnmetClause: context.floorFailures[0]?.clauseId ?? null, + promotionRequiresHumanStatement: true, +}; +``` + +An empty `currentFloorFailures` list says the stated rung is honest. It does not confer the next +rung, and `floorReached` above the stated rung is information rather than an automatic edit. + +## 10. Declared versus enabled verifiers + +*When you need this: you want example intent and graph-visible verifier realization kept distinct.* + +```js +const rows = []; + +for (const spec of g.specs()) { + const context = g.specContext(spec.id); + if (context === undefined) continue; + + const declared = context.verifiers + .filter((binding) => binding.via === "example") + .map((binding) => binding.verifierId); + const enabled = context.verifiers + .filter((binding) => binding.enabled) + .map((binding) => binding.verifierId); + + if (declared.length > 0 || enabled.length > 0) { + rows.push({ id: spec.id, declared, enabled }); + } +} + +return { + total: rows.length, + withDeclaredOnly: rows.filter((row) => + row.declared.some((id) => !row.enabled.includes(id)), + ).length, + rows, +}; +``` + +Enabled means a resolving graph-visible test binding exists. This recipe cannot detect a generated +contract no suite binds, and it never reports runner pass or fail. + +## 11. The lower ladder + +*When you need this: you want every non-ready Spec grouped by family, with current floor evidence +visible instead of hidden in plan prose.* + +```js +const lower = g.specs().filter((spec) => spec.statedReadiness !== "ready"); +const byFamily = {}; + +for (const spec of lower) { + const family = spec.id.slice("spec:".length).split(".")[0]; + const context = g.specContext(spec.id); + const failures = context?.floorFailures ?? []; + + byFamily[family] = byFamily[family] ?? []; + byFamily[family].push({ + id: spec.id, + statedReadiness: spec.statedReadiness, + floorReached: spec.derivedReadiness ?? "none", + nextUnmetClause: failures[0]?.clauseId ?? null, + }); +} + +return { total: lower.length, byFamily }; +``` + +`nextUnmetClause: null` means the current stated floor has no failure. It is not permission to +promote: the next rung may require evidence the current-floor evaluator was not asked to police, +and `ready` always remains a human statement. diff --git a/package.json b/package.json index 3a4491b..47c68b6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ }, "type": "module", "files": [ - "dist" + "dist/**", + "docs/agent-surface/recipes.md", + ".claude/skills/sdp-agent-surface/SKILL.md", + ".claude/skills/sdp-authoring/SKILL.md" ], "bin": { "sdp": "./dist/cli/sdp.js" diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index cde856f..e76a620 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -335,30 +335,43 @@ The skill obeys the anti-anecdote rule (the derived graph outranks the skill) an gen 1's session-typed skills as evidence of shape, never as template. It is drafted in W, then **revised from S1's friction log** — the origin tracer is its first foreign user. -**W3 — the authoring recipes.** Appended to the one catalog (`docs/agent-surface/recipes.md`), -each body executed verbatim by the recipe check, all read-only over `g` / `graph` / `report`: - -1. **The promotion preflight** — for a Spec id: stated rung, derived rung, and the first - unmet clause per rung above it ("what would `ready` require here"), from - `specContext(id).floorFailures` and the floor table's clause ids. -2. **The contract ledger** — which `example` Specs were refused step contracts and by which - `contracts/*` finding; which parents own spaces with zero bindable children. -3. **The verifier-binding audit** — enabled vs declared verifiers per Spec, and the - anchored-but-unbound set (§d-6's join, shipped as a recipe first). -4. **The lower-ladder view** — the corpus below `defined`, grouped by family, with each - Spec's distance-to-next-rung (first unmet clause) — the working surface for W1 and S3. +**W3 — the authoring recipes.** Three bodies extend the catalog from eight to eleven, each +executed verbatim by the recipe check and read-only over `g` / `graph` / `report`: + +1. **The promotion preflight** — for a Spec id: stated rung, floor reached, current floor + failures, and an explicit human-statement reminder. +2. **The declared-versus-enabled verifier audit** — keeps authored example relations distinct + from resolving graph-visible verifier bindings. +3. **The lower-ladder view** — every non-ready Spec grouped by family with its current first + graph-visible unmet clause. + +There is no query-time contract ledger: `contracts/*` findings are emitted only by `sdp build`. +There is no anchored-but-unbound recipe: `bindExample` call sites are not extracted graph data. +The authoring skill teaches both limits instead of shipping a body that cannot compute its claim. The freeze rule stands: these are recipes; a join moves into the `reader` only at the second-caller bar with evidence agents hand-roll it wrong. -**W4 — the friction instrument, widened.** Revision 1's inline-carrier tally (§i) stands and -widens to four columns, kept per converted or authored Spec across W, S1, and S2: **anchor -friction** (binding placement, the builder forms, the import-binding boundary) · **ladder -friction** (floor clauses fought, rungs mis-entered) · **executable-transition friction** -(vocabulary resolution, contract refusals, the two-file seam) · **CLI friction** (exclusion -ergonomics, body ergonomics, missing joins). One log, four tallies — feeding respectively the -inline-carrier gate (§i), the on-ramp revision, any §d-5/6 ruling, and the edit-model -evidence base. +**W4 — the friction instrument, widened.** One row per converted or authored Spec records source +and target ids/files; anchor, ladder, executable-transition, and CLI friction; and three measured +booleans — `leafAltitude`, `singleModule`, `under30Lines` — plus their combined inline-candidate +result and disposition. W rows begin below; S1 imports them verbatim into the origin ledger with +`sourceRepo`, and that file becomes canonical. S2 reports numerator, denominator, and percentage +without turning the result into a carrier ruling. + +| sourceRepo | source id/file | target id/file | anchor friction | ladder friction | executable-transition friction | CLI friction | leafAltitude | singleModule | under30Lines | inline candidate | disposition | +|---|---|---|---|---|---|---|---:|---:|---:|---:|---| +| Protocol | `specs/consumers/authoring-on-ramp.sdp.md` | `spec:consumers.authoring-on-ramp` / same file | a Markdown skill cannot carry an extracted anchor, so validating code owns the binding | entered low and matured only after the shipped asset and verifier existed | taught, not itself a bound example | the first fresh-context run invoked macOS `/usr/bin/sdp`; the skill now requires the repository-local CLI or an adopter package runner, and root-specific versus portable invocations have separate checks | false | true | true | false | keep standalone: feature-sized workflow | +| Protocol | `specs/consumers/agent-surface.authoring-recipes.sdp.md` | `spec:consumers.agent-surface.authoring-recipes` / same file | in-tree recipe/skill checks own both bindings | matured after all three bodies returned complete graph-derived sets | not applicable | portable wording initially competed with mandatory exclusions; parameterized tests resolved it | true | false | true | false | keep standalone: multiple recipe and test modules | +| Protocol | `specs/consumers/intent-composition.sdp.md` | `spec:consumers.intent-composition` / same file | none while the realizing surface is absent | deliberately states `idea` even though its minimal structure clears higher floors | deferred with the absent surface | none | true | true | true | true | measure only; no carrier ruling follows | + +**W close evidence (2026-07-28).** A fresh, ephemeral, read-only `codex exec` context followed +the corrected authoring skill and reported `spec:model.enrichment-lifecycle` as stated +`scoped`, floor `scoped`, blocked by its authored post-implementation slimming question, with +`spec:model.enrichment-lifecycle` as the carrier. The first attempt had resolved bare `sdp` to +macOS `/usr/bin/sdp`; that observed friction produced the repository-local/package-runner rule +now checked for both shipped skills. The package smoke test additionally installs the packed +snapshot and reads both exact skill paths from the installed package. ## (f) S1 — the tracer bullet on foreign soil (origin repo) @@ -370,8 +383,8 @@ over the derived graph. **Tracer-bullet discipline applies unchanged:** if the o stops extracting or validating, fix the carrier or extractor here — never fork the grammar in the consumer. -Deliverables: the converted cluster extracting at 0 errors; the recipes (the eight standing -plus W3's four) returning honest answers about it (the 17 planning stubs must surface as +Deliverables: the converted cluster extracting at 0 errors; all eleven recipes returning honest +answers about it (the 17 planning stubs must surface as declared-not-enabled, not as coverage); the friction log per W4. **Usage acceptance, added at revision 2:** the converted cluster spans **all four rungs** honestly (the origin's four-tier ladder guarantees candidates for each — no rung is manufactured, and none is skipped by diff --git a/specs/consumers/agent-surface.authoring-recipes.sdp.md b/specs/consumers/agent-surface.authoring-recipes.sdp.md new file mode 100644 index 0000000..8672423 --- /dev/null +++ b/specs/consumers/agent-surface.authoring-recipes.sdp.md @@ -0,0 +1,17 @@ +--- +id: spec:consumers.agent-surface.authoring-recipes +kind: behavior +altitude: story +readiness: ready +relations: + refines: spec:consumers.agent-surface +--- +# Authoring questions stay executable graph recipes + +## Intent +- outcome: Answer recurring maturity and verifier questions by scripting the graph rather than adding query verbs. + +## Behavior +- rule: Promotion preflight reports the Spec's stated rung, floor reached, and any current unmet floor clause. +- rule: The verifier audit keeps declared example relations distinct from enabled verifier bindings. +- rule: The lower-ladder view groups non-ready Specs by family and reports their next graph-visible unmet clause without treating an empty failure list as automatic promotion. diff --git a/specs/consumers/authoring-on-ramp.sdp.md b/specs/consumers/authoring-on-ramp.sdp.md new file mode 100644 index 0000000..1b20add --- /dev/null +++ b/specs/consumers/authoring-on-ramp.sdp.md @@ -0,0 +1,19 @@ +--- +id: spec:consumers.authoring-on-ramp +kind: behavior +altitude: feature +readiness: ready +relations: + refines: spec:consumers.edit-model +--- +# Authors move one Spec from intent to reviewed evidence + +## Intent +- outcome: Give an agent or human one graph-first path for creating, enriching, binding, and reviewing a Spec without inventing a parallel workflow. + +## Behavior +- rule: An author starts from the build-backlog and drift-alarm recipes, reads carrying Specs for law, and edits the canonical carrier. +- rule: The executable transition is taught as parent example space, child bound point, generated contracts, colocated `bindExample` and `specTest`, and a mutation-probed red result before the human states `ready`. +- rule: Contract-generation refusals are diagnosed through `sdp build`; query-time validation does not claim to report codegen findings. +- rule: Verifier-binding queries report graph-visible anchors and cannot detect a suite whose generated contract is never bound. +- rule: Implementation anchors state identity-only bindings, and Design Review supplies context for the human readiness statement without becoming a workflow gate. diff --git a/specs/consumers/intent-composition.sdp.md b/specs/consumers/intent-composition.sdp.md new file mode 100644 index 0000000..19a2351 --- /dev/null +++ b/specs/consumers/intent-composition.sdp.md @@ -0,0 +1,16 @@ +--- +id: spec:consumers.intent-composition +kind: behavior +altitude: story +readiness: idea +relations: + refines: spec:consumers.edit-model +--- +# Intent composition needs a realizing surface + +## Intent +- outcome: Realize the absent user and agent surface that composes scoped intent before ordinary source edits. + +## Behavior +- rule: `spec:consumers.edit-model` owns the settled intent → agent → git law; this child owns only the future composing interaction that does not yet exist. +- rule: No entrypoint, persistence path, or structured patch contract is implied at the idea rung. diff --git a/specs/self-hosting.pack.sdp.ts b/specs/self-hosting.pack.sdp.ts index e47554d..d4c3a03 100644 --- a/specs/self-hosting.pack.sdp.ts +++ b/specs/self-hosting.pack.sdp.ts @@ -35,6 +35,9 @@ export const selfHostingV1Pack = pack({ ref("spec:consumers.design-review"), ref("spec:consumers.reader"), ref("spec:consumers.edit-model"), + ref("spec:consumers.authoring-on-ramp"), + ref("spec:consumers.agent-surface.authoring-recipes"), + ref("spec:consumers.intent-composition"), ref("spec:model.protocol-domain"), ref("spec:model.core-model"), ref("spec:model.enrichment-lifecycle"), diff --git a/test/package-smoke.test.ts b/test/package-smoke.test.ts index c412115..0ef3cf6 100644 --- a/test/package-smoke.test.ts +++ b/test/package-smoke.test.ts @@ -1,5 +1,5 @@ import { execFileSync } from "node:child_process"; -import { copyFile, mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises"; +import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -146,6 +146,26 @@ describe("published package surface", () => { try { run("npm", ["run", "build"], repositoryRoot); + const dryRun = JSON.parse( + run("npm", ["pack", "--dry-run", "--json"], repositoryRoot), + ) as readonly [{ readonly files: readonly { readonly path: string }[] }]; + const dryRunPaths = dryRun[0].files.map((file) => file.path); + const adopterAssets = [ + ".claude/skills/sdp-agent-surface/SKILL.md", + ".claude/skills/sdp-authoring/SKILL.md", + "docs/agent-surface/recipes.md", + ]; + + expect(dryRunPaths).toEqual(expect.arrayContaining(adopterAssets)); + expect( + dryRunPaths.filter( + (path) => + !["LICENSE", "README.md", "package.json", ...adopterAssets].includes(path) && + !path.startsWith("dist/"), + ), + ).toEqual([]); + expect(dryRunPaths.some((path) => path.endsWith(".sdp.md"))).toBe(false); + run("npm", ["pack", "--pack-destination", packageRoot], repositoryRoot); const tarballs = await readdir(packageRoot); @@ -215,6 +235,26 @@ void [${expectedRootExports.join(", ")}]; ], consumer, ); + const installedAgentSkill = await readFile( + join( + consumer, + "node_modules", + "@libar-dev", + "software-delivery-protocol", + ".claude/skills/sdp-agent-surface/SKILL.md", + ), + "utf8", + ); + const installedAuthoringSkill = await readFile( + join( + consumer, + "node_modules", + "@libar-dev", + "software-delivery-protocol", + ".claude/skills/sdp-authoring/SKILL.md", + ), + "utf8", + ); expect(JSON.parse(driver)).toEqual({ exports: [...expectedRootExports].sort(), @@ -224,6 +264,8 @@ void [${expectedRootExports.join(", ")}]; expect(sdpImportDryRun).toContain("id: spec:round-trip.behavior"); expect(await readdir(consumer)).not.toContain("behavior.sdp.md"); expect(barrelCheck).toBe("barrel imports available\n"); + expect(installedAgentSkill).toContain("name: sdp-agent-surface"); + expect(installedAuthoringSkill).toContain("name: sdp-authoring"); } finally { await rm(packageRoot, { force: true, recursive: true }); } diff --git a/test/recipes.test.ts b/test/recipes.test.ts index f750a2f..27bc425 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -188,27 +188,32 @@ describe("the agent-surface recipe corpus", () => { ); }); - // Given: the on-ramp surfaces that document how to invoke the sink at this root. When: their - // `sdp q` command lines are read. Then: each names the project's whole exclusion set, because a - // partial set does not derive here and the documented command would fail as written. - it("documents the invocation with the same exclusions the check derives with", () => { - const onRampSources = [recipesPath, ".claude/skills/sdp-agent-surface/SKILL.md"]; + // The self-hosting form pins this root's mandatory exclusions, while the adopter form keeps + // root and repeatable exclusions project-selected. The two contracts are checked separately so + // portability cannot weaken the root's real invocation. + it("keeps self-hosting and adopter invocation forms distinct", () => { + const onRampSources = [ + recipesPath, + ".claude/skills/sdp-agent-surface/SKILL.md", + ".claude/skills/sdp-authoring/SKILL.md", + ]; for (const source of onRampSources) { const lines = readFileSync(join(repoRoot, source), "utf8").split("\n"); - // A concrete invocation is one that carries a quoted body; the bare `sdp q [...]` usage - // grammar states the option shapes rather than a command to run, so it is not one. - const commandLines = lines.filter((line) => line.includes("sdp q '")); - - expect({ source, documented: commandLines.length > 0 }).toEqual({ source, documented: true }); - // The guard parses the single-quoted spelling only, so any other quoting would carry a - // documented invocation it cannot see — no other quoting may exist in the on-ramp files. - expect({ source, otherQuoting: lines.filter((line) => line.includes('sdp q "')) }).toEqual({ + const commandLines = lines.filter((line) => line.includes(" q '") && line.includes("sdp")); + const selfHostingLines = commandLines.filter((line) => !line.includes("--root PATH")); + const adopterLines = commandLines.filter((line) => line.includes("--root PATH")); + + expect({ source, selfHosting: selfHostingLines.length > 0 }).toEqual({ source, - otherQuoting: [], + selfHosting: true, }); + expect({ + source, + otherQuoting: lines.filter((line) => line.includes(' q "') && line.includes("sdp")), + }).toEqual({ source, otherQuoting: [] }); - for (const line of commandLines) { + for (const line of selfHostingLines) { expect({ source, line, @@ -219,6 +224,20 @@ describe("the agent-surface recipe corpus", () => { ).length, }).toEqual({ source, line, names: standardExcludes.length }); } + + if (source === ".claude/skills/sdp-agent-surface/SKILL.md") { + expect(adopterLines).toEqual([]); + continue; + } + + expect({ source, adopterForms: adopterLines.length }).toEqual({ + source, + adopterForms: 2, + }); + for (const line of adopterLines) { + expect(standardExcludes.some((path) => line.includes(`--exclude ${path}`))).toBe(false); + expect(line.match(/--exclude PATH/gu)?.length ?? 0).toBeLessThanOrEqual(2); + } } }); @@ -415,4 +434,46 @@ describe("the agent-surface recipe corpus", () => { expect(stringAt(row, "message").length).toBeGreaterThan(0); } }); + + it("returns promotion preflight without conferring a readiness edit", async () => { + const result = asRecord(await runRecipe(recipeByOrdinal(9))); + + expect(result.found).toBe(true); + expect(primitivesById.has(stringAt(result, "id"))).toBe(true); + expect(rungs).toContain(stringAt(result, "statedReadiness")); + expect([...rungs, "none"]).toContain(stringAt(result, "floorReached")); + expect(result.promotionRequiresHumanStatement).toBe(true); + expect(Array.isArray(result.currentFloorFailures)).toBe(true); + }); + + it("keeps declared examples distinct from enabled verifier bindings", async () => { + const result = asRecord(await runRecipe(recipeByOrdinal(10))); + const rows = asArray(result.rows); + const expected = reader + .specs() + .filter((spec) => (reader.specContext(spec.id)?.verifiers.length ?? 0) > 0) + .map((spec) => spec.id) + .sort(); + + expect(numberAt(result, "total")).toBe(rows.length); + expect(rows.map((row) => stringAt(asRecord(row), "id")).sort()).toEqual(expected); + + for (const row of rows) { + expect(Array.isArray(asRecord(row).declared)).toBe(true); + expect(Array.isArray(asRecord(row).enabled)).toBe(true); + } + }); + + it("returns the complete non-ready ladder grouped by family", async () => { + const result = asRecord(await runRecipe(recipeByOrdinal(11))); + const rows = Object.values(asRecord(result.byFamily)).flatMap((family) => asArray(family)); + const expected = reader + .specs() + .filter((spec) => spec.statedReadiness !== "ready") + .map((spec) => spec.id) + .sort(); + + expect(numberAt(result, "total")).toBe(rows.length); + expect(rows.map((row) => stringAt(asRecord(row), "id")).sort()).toEqual(expected); + }); }); diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index 19df85a..3eb8254 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -90,12 +90,12 @@ describe("the self-hosting corpus", () => { // The literals are the corpus checkpoint. The authored arrays are measured against the same // literals rather than standing in for them, so a transcription slip in an oracle module // cannot certify itself by moving both sides of a comparison at once. - expect(result.counts).toEqual({ specs: 118, packs: 1, anchors: 87 }); - expect(expectedSpecs).toHaveLength(118); - expect(expectedPackMembers).toHaveLength(118); - expect(expectedAnchors).toHaveLength(87); - expect(result.graph.nodes).toHaveLength(206); - expect(result.graph.edges).toHaveLength(406); + expect(result.counts).toEqual({ specs: 121, packs: 1, anchors: 91 }); + expect(expectedSpecs).toHaveLength(121); + expect(expectedPackMembers).toHaveLength(121); + expect(expectedAnchors).toHaveLength(91); + expect(result.graph.nodes).toHaveLength(213); + expect(result.graph.edges).toHaveLength(416); }); it("rosters exactly the authored Spec, Pack, and anchor node ids", () => { @@ -145,7 +145,7 @@ describe("the self-hosting corpus", () => { }), {}, ), - ).toEqual({ defined: 36, ready: 81, scoped: 1 }); + ).toEqual({ defined: 36, idea: 1, ready: 83, scoped: 1 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { diff --git a/test/self-hosting-oracle/anchors.ts b/test/self-hosting-oracle/anchors.ts index db2d280..22b38d7 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -53,6 +53,46 @@ export const expectedAnchors = [ constant: "buildPipelineSameInvocationTestAnchor", site: "bindExample(sameInvocationContract", }, + { + id: "impl:protocol.authoring-on-ramp", + nodeType: "CodeNode", + label: "validates the shipped graph-first authoring on-ramp", + type: "satisfies", + target: "spec:consumers.authoring-on-ramp", + file: "test/skills.test.ts", + constant: "authoringOnRampImplementationAnchor", + site: "function readSkill", + }, + { + id: "impl:protocol.authoring-recipes", + nodeType: "CodeNode", + label: "validates the shipped authoring recipe surface", + type: "satisfies", + target: "spec:consumers.agent-surface.authoring-recipes", + file: "test/skills.test.ts", + constant: "authoringRecipesImplementationAnchor", + site: "function documentedCommands", + }, + { + id: "test:protocol.authoring-on-ramp", + nodeType: "Anchor", + label: "skill-asset checks verify the authoring on-ramp", + type: "verifies", + target: "spec:consumers.authoring-on-ramp", + file: "test/skills.test.ts", + constant: "authoringOnRampTestAnchor", + site: 'describe("Protocol skill assets"', + }, + { + id: "test:protocol.authoring-recipes", + nodeType: "Anchor", + label: "skill-asset checks verify the authoring recipes", + type: "verifies", + target: "spec:consumers.agent-surface.authoring-recipes", + file: "test/skills.test.ts", + constant: "authoringRecipesTestAnchor", + site: 'describe("Protocol skill assets"', + }, { id: "impl:protocol.readiness-floor", nodeType: "CodeNode", diff --git a/test/self-hosting-oracle/consumers.ts b/test/self-hosting-oracle/consumers.ts index d997463..df8a653 100644 --- a/test/self-hosting-oracle/consumers.ts +++ b/test/self-hosting-oracle/consumers.ts @@ -788,4 +788,74 @@ export const consumersSpecs = [ }, deliveryFacts: ["has-verifier"], }, + { + id: "spec:consumers.authoring-on-ramp", + specKind: "behavior", + altitude: "feature", + readiness: "ready", + file: "specs/consumers/authoring-on-ramp.sdp.md", + title: "Authors move one Spec from intent to reviewed evidence", + narrative: null, + sections: { + intent: { + outcome: + "Give an agent or human one graph-first path for creating, enriching, binding, and reviewing a Spec without inventing a parallel workflow.", + }, + behavior: { + rules: [ + "An author starts from the build-backlog and drift-alarm recipes, reads carrying Specs for law, and edits the canonical carrier.", + "The executable transition is taught as parent example space, child bound point, generated contracts, colocated `bindExample` and `specTest`, and a mutation-probed red result before the human states `ready`.", + "Contract-generation refusals are diagnosed through `sdp build`; query-time validation does not claim to report codegen findings.", + "Verifier-binding queries report graph-visible anchors and cannot detect a suite whose generated contract is never bound.", + "Implementation anchors state identity-only bindings, and Design Review supplies context for the human readiness statement without becoming a workflow gate.", + ], + }, + }, + deliveryFacts: ["implemented", "has-verifier"], + }, + { + id: "spec:consumers.agent-surface.authoring-recipes", + specKind: "behavior", + altitude: "story", + readiness: "ready", + file: "specs/consumers/agent-surface.authoring-recipes.sdp.md", + title: "Authoring questions stay executable graph recipes", + narrative: null, + sections: { + intent: { + outcome: + "Answer recurring maturity and verifier questions by scripting the graph rather than adding query verbs.", + }, + behavior: { + rules: [ + "Promotion preflight reports the Spec's stated rung, floor reached, and any current unmet floor clause.", + "The verifier audit keeps declared example relations distinct from enabled verifier bindings.", + "The lower-ladder view groups non-ready Specs by family and reports their next graph-visible unmet clause without treating an empty failure list as automatic promotion.", + ], + }, + }, + deliveryFacts: ["implemented", "has-verifier"], + }, + { + id: "spec:consumers.intent-composition", + specKind: "behavior", + altitude: "story", + readiness: "idea", + file: "specs/consumers/intent-composition.sdp.md", + title: "Intent composition needs a realizing surface", + narrative: null, + sections: { + intent: { + outcome: + "Realize the absent user and agent surface that composes scoped intent before ordinary source edits.", + }, + behavior: { + rules: [ + "`spec:consumers.edit-model` owns the settled intent → agent → git law; this child owns only the future composing interaction that does not yet exist.", + "No entrypoint, persistence path, or structured patch contract is implied at the idea rung.", + ], + }, + }, + deliveryFacts: [], + }, ] as const; diff --git a/test/self-hosting-oracle/declared-relations.ts b/test/self-hosting-oracle/declared-relations.ts index ac7693b..c738e8d 100644 --- a/test/self-hosting-oracle/declared-relations.ts +++ b/test/self-hosting-oracle/declared-relations.ts @@ -267,6 +267,9 @@ export const expectedDeclaredRelations = [ ["spec:consumers.design-review", "refines", "spec:consumers.projections-model"], ["spec:consumers.reader", "refines", "spec:consumers.agent-surface"], ["spec:consumers.edit-model", "refines", "spec:consumers.projections-model"], + ["spec:consumers.authoring-on-ramp", "refines", "spec:consumers.edit-model"], + ["spec:consumers.agent-surface.authoring-recipes", "refines", "spec:consumers.agent-surface"], + ["spec:consumers.intent-composition", "refines", "spec:consumers.edit-model"], ["spec:model.protocol-domain", "refines", "spec:protocol.self-hosting"], ["spec:model.core-model", "refines", "spec:protocol.self-hosting"], ["spec:model.enrichment-lifecycle", "refines", "spec:model.core-model"], diff --git a/test/self-hosting-oracle/pack-members.ts b/test/self-hosting-oracle/pack-members.ts index 144de8a..cfe4372 100644 --- a/test/self-hosting-oracle/pack-members.ts +++ b/test/self-hosting-oracle/pack-members.ts @@ -32,6 +32,9 @@ export const expectedPackMembers = [ "spec:consumers.design-review", "spec:consumers.reader", "spec:consumers.edit-model", + "spec:consumers.authoring-on-ramp", + "spec:consumers.agent-surface.authoring-recipes", + "spec:consumers.intent-composition", "spec:model.protocol-domain", "spec:model.core-model", "spec:model.enrichment-lifecycle", diff --git a/test/skills.test.ts b/test/skills.test.ts new file mode 100644 index 0000000..652a25e --- /dev/null +++ b/test/skills.test.ts @@ -0,0 +1,179 @@ +import { readFileSync } from "node:fs"; +import { basename, dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { parse } from "yaml"; +import { describe, expect, it } from "vitest"; + +import { + codeAnchor, + codeAnchorId, + ref, + specTest, + testAnchorId, +} from "@libar-dev/software-delivery-protocol"; + +const repoRoot = fileURLToPath(new URL("..", import.meta.url)); +const skillPaths = [ + ".claude/skills/sdp-agent-surface/SKILL.md", + ".claude/skills/sdp-authoring/SKILL.md", +] as const; + +const authoringOnRampImplementationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.authoring-on-ramp"), + label: "validates the shipped graph-first authoring on-ramp", + satisfies: ref("spec:consumers.authoring-on-ramp"), +}); +void authoringOnRampImplementationAnchor; + +function readSkill(path: string) { + const source = readFileSync(join(repoRoot, path), "utf8"); + const match = /^---\n(?[\s\S]*?)\n---\n(?[\s\S]+)$/u.exec(source); + + if (match?.groups?.frontmatter === undefined || match.groups.body === undefined) { + throw new Error(`${path} does not carry one YAML frontmatter block`); + } + + return { + source, + body: match.groups.body, + frontmatter: parse(match.groups.frontmatter) as Record, + }; +} + +const authoringRecipesImplementationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.authoring-recipes"), + label: "validates the shipped authoring recipe surface", + satisfies: ref("spec:consumers.agent-surface.authoring-recipes"), +}); +void authoringRecipesImplementationAnchor; + +function documentedCommands(body: string): readonly string[] { + const commands: string[] = []; + let inShellFence = false; + + for (const line of body.split("\n")) { + if (line === "```sh") { + inShellFence = true; + continue; + } + if (line === "```") { + inShellFence = false; + continue; + } + if ( + inShellFence && + (line.startsWith("node ./dist/cli/sdp.js ") || line.startsWith("pnpm exec sdp ")) + ) { + commands.push(line); + } + } + + return commands; +} + +const authoringOnRampTestAnchor = specTest({ + id: testAnchorId("test:protocol.authoring-on-ramp"), + label: "skill-asset checks verify the authoring on-ramp", + verifies: ref("spec:consumers.authoring-on-ramp"), +}); +void authoringOnRampTestAnchor; + +const authoringRecipesTestAnchor = specTest({ + id: testAnchorId("test:protocol.authoring-recipes"), + label: "skill-asset checks verify the authoring recipes", + verifies: ref("spec:consumers.agent-surface.authoring-recipes"), +}); +void authoringRecipesTestAnchor; + +describe("Protocol skill assets", () => { + it("uses the repository's two-field single-file convention", () => { + for (const path of skillPaths) { + const skill = readSkill(path); + const folder = basename(dirname(path)); + + expect(Object.keys(skill.frontmatter).sort()).toEqual(["description", "name"]); + expect(skill.frontmatter.name).toBe(folder); + expect(typeof skill.frontmatter.description).toBe("string"); + expect(String(skill.frontmatter.description).length).toBeGreaterThan(40); + } + }); + + it("keeps both skills graph-first and the authoring law linked to carrying Specs", () => { + for (const path of skillPaths) { + const { source } = readSkill(path); + + expect(source).toContain("sdp q"); + expect(source).toContain("docs/agent-surface/recipes.md"); + expect(source).toContain("spec:"); + } + + const authoring = readSkill(".claude/skills/sdp-authoring/SKILL.md").source; + for (const required of [ + "spec:validation.readiness-floor", + "spec:validation.kind-evidence", + "spec:decisions.content-only-sections", + "spec:decisions.point-per-example", + "spec:decisions.binding-not-liveness", + "sdp build", + "bindExample", + "specTest", + "mutation", + "cannot detect", + ]) { + expect(authoring).toContain(required); + } + }); + + it("documents only valid CLI verbs and keeps root-specific exclusions intact", () => { + const knownVerbs = new Set(["build", "q"]); + + for (const path of skillPaths) { + const commands = documentedCommands(readSkill(path).body); + expect(commands.length).toBeGreaterThan(0); + + for (const command of commands) { + const verb = /(?:sdp\.js|exec sdp) (?[\w-]+)/u.exec(command)?.groups?.verb; + expect(verb === undefined ? false : knownVerbs.has(verb)).toBe(true); + + if (verb === "q" && !command.includes("--root PATH")) { + for (const exclusion of ["explorations", "examples", "test/fixtures/import/parity"]) { + expect(command).toContain(`--exclude ${exclusion}`); + } + } + } + } + }); + + it("uses the local runtime or package runner instead of a colliding global binary", () => { + const agentSurface = readSkill(".claude/skills/sdp-agent-surface/SKILL.md"); + const authoring = readSkill(".claude/skills/sdp-authoring/SKILL.md"); + + expect(documentedCommands(agentSurface.body).every((line) => line.startsWith("node "))).toBe( + true, + ); + expect( + documentedCommands(authoring.body).every( + (line) => line.startsWith("node ") || line.startsWith("pnpm exec "), + ), + ).toBe(true); + }); + + it("contains no contradictory shortcuts for readiness or verifier realization", () => { + const forbidden = [ + "has-verifier means tests pass", + "ready is conferred by tooling", + "ready is derived from the floor", + "bindExample call sites are extracted", + "verification mode proves a verifier exists", + ]; + + for (const path of skillPaths) { + const source = readSkill(path).source.toLowerCase(); + + for (const claim of forbidden) { + expect(source).not.toContain(claim); + } + } + }); +}); From d445e7b9019bd681ff09304e0d8f0d74f53e6eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 17:40:48 +0200 Subject: [PATCH 04/24] fix(skills): resolve adopter recipe catalog path --- .claude/skills/sdp-agent-surface/SKILL.md | 9 +++++---- .claude/skills/sdp-authoring/SKILL.md | 4 +++- plans/23-outward-turn-origin-adoption.md | 5 ++++- test/skills.test.ts | 3 +++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.claude/skills/sdp-agent-surface/SKILL.md b/.claude/skills/sdp-agent-surface/SKILL.md index 5c50fd8..4473f35 100644 --- a/.claude/skills/sdp-agent-surface/SKILL.md +++ b/.claude/skills/sdp-agent-surface/SKILL.md @@ -26,10 +26,11 @@ the sink refuses to run the body. They are the same list `npm run generate:self- Run `npm run build` first if `dist/` is absent. Do not invoke a global `sdp`: macOS also ships an unrelated binary with that name. In an adopter, use its package runner, such as `pnpm exec sdp`. -The catalog of ready-made bodies is `docs/agent-surface/recipes.md` — build backlog, drift alarm, -per-Spec guarantees and verifiers, blast radius, Pack review backbone, concept search, readiness -divergence, warn-level signals. Every body there runs verbatim and a test proves it. Start from a -recipe; adapt it in place. +The catalog of ready-made bodies is `docs/agent-surface/recipes.md` in the Protocol repository and +`node_modules/@libar-dev/software-delivery-protocol/docs/agent-surface/recipes.md` in an adopter — +build backlog, drift alarm, per-Spec guarantees and verifiers, blast radius, Pack review backbone, +concept search, readiness divergence, and warn-level signals. Every body there runs verbatim and a +test proves it. Start from a recipe; adapt it in place. Reach for the files only when you need the authored prose itself — the exact words to edit. diff --git a/.claude/skills/sdp-authoring/SKILL.md b/.claude/skills/sdp-authoring/SKILL.md index 0f7984d..6ed683a 100644 --- a/.claude/skills/sdp-authoring/SKILL.md +++ b/.claude/skills/sdp-authoring/SKILL.md @@ -6,7 +6,9 @@ description: Author and mature Protocol Specs through the graph-first workflow. # Author Specs through the graph Treat the canonical carrier as the write surface and the derived graph as the read model. Start every -session with the build-backlog and drift-alarm recipes in `docs/agent-surface/recipes.md`. +session with the build-backlog and drift-alarm recipes. In the Protocol repository the catalog is +`docs/agent-surface/recipes.md`; in an adopter, read the same shipped catalog at +`node_modules/@libar-dev/software-delivery-protocol/docs/agent-surface/recipes.md`. At this repository root, use the exact self-hosting exclusions: diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index e76a620..bb73a87 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -371,7 +371,10 @@ the corrected authoring skill and reported `spec:model.enrichment-lifecycle` as `spec:model.enrichment-lifecycle` as the carrier. The first attempt had resolved bare `sdp` to macOS `/usr/bin/sdp`; that observed friction produced the repository-local/package-runner rule now checked for both shipped skills. The package smoke test additionally installs the packed -snapshot and reads both exact skill paths from the installed package. +snapshot and reads both exact skill paths from the installed package. The S1 fresh-context run +then found that a copied skill could not resolve the repository-root recipe path from an adopter; +both skills now name the Protocol-root and installed-package catalog paths explicitly, and the +in-tree check pins both forms. ## (f) S1 — the tracer bullet on foreign soil (origin repo) diff --git a/test/skills.test.ts b/test/skills.test.ts index 652a25e..3d8e08c 100644 --- a/test/skills.test.ts +++ b/test/skills.test.ts @@ -105,6 +105,9 @@ describe("Protocol skill assets", () => { expect(source).toContain("sdp q"); expect(source).toContain("docs/agent-surface/recipes.md"); + expect(source).toContain( + "node_modules/@libar-dev/software-delivery-protocol/docs/agent-surface/recipes.md", + ); expect(source).toContain("spec:"); } From ed0f5401341750c9f2acdd9d89f7819f392bf0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 18:35:09 +0200 Subject: [PATCH 05/24] docs(plans): close phase-6 origin adoption --- AGENTS.md | 2 +- check-self-hosting-gates.mjs | 2 +- plans/23-outward-turn-origin-adoption.md | 51 +++++++++++++++++------- test/check-self-hosting-gates.test.ts | 2 +- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0826166..6a3f24f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ claims; **`src/` and tests** are authoritative evidence of current realization. > machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; > plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier > rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** plan 23 is EXECUTING — phase 6 is implementing the usage layer and the origin-project adoption from the reviewed plan. The latest settled ground remains the phase-5 close: the validation-and-honesty concept document dissolved, the agent front door and eight executable recipes shipped, and the corpus stood at `ready: 80 / defined: 35` over 115 Specs with zero findings. Phase 6 first generalizes the records audit, teaches authoring, and exercises the lower readiness rungs; it then installs the exact package snapshot in the origin, migrates the authored corpus, and cuts over atomically from Architect. Build state lives in +> as import source and a lawful per-ID option. · **what now:** ✅ EXECUTED — plan 23 is EXECUTED; phase-6 implementation is complete and the final clean-copy audit passed. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package; the Protocol corpus stands at `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. The origin project adopted the exact package snapshot, migrated its authored corpus and surviving verifier bindings, retired Architect atomically behind a 1,319-entry disposition manifest, and now derives 57 Specs · 1 Pack · 11 anchors with all four readiness rungs and zero findings. Matched second-caller sessions measured 73.1% fewer treatment tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in > **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when diff --git a/check-self-hosting-gates.mjs b/check-self-hosting-gates.mjs index e6962cd..24c7d9a 100644 --- a/check-self-hosting-gates.mjs +++ b/check-self-hosting-gates.mjs @@ -250,7 +250,7 @@ if (currentStatus !== undefined) { expectContains( agentsPath, agents, - currentStatus, + `plan ${String(currentPlan?.number)} is ${currentStatus}`, "the handbook status disagrees with the current primary plan", ); } diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index bb73a87..d675b69 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -1,15 +1,13 @@ # Plan 23 — The outward turn, refined: origin adoption, the usage layer, and the working ladder -> **Status:** EXECUTING — **revision 3** (2026-07-28 reviewed execution). Full S0–S4 execution, -> atomic cutover after parity, and local checkpoint/stage commits are authorized. The latest ✅ EXECUTED ground is -> **plan 22** (self-hosting phase 5: the `05` dissolution and the agent front door; corpus at -> 115 Specs · 1 Pack · 86 anchors → 202 nodes · 397 edges · `ready: 80 / defined: 35`, -> 0 errors / 0 warnings). This plan executes **phase 6 as the usage turn**: the outward turn onto -> the origin project (`new-convex-es` / `libar-platform`) — unchanged from revision 1 — **plus the -> usage layer this repo owes any adopter**, promoted into scope by the refinement session. Four -> usage goals govern the phase (§a); a new workstream **W** (this repo) carries the authoring -> on-ramp, the authoring recipes, and the first honest use of the ladder's lower rungs; stages -> S1–S4 execute in the origin repo with `@libar-dev/software-delivery-protocol` as a dependency. +> **Status:** ✅ EXECUTED — **phase-6 implementation complete; final clean-copy audit passed** +> (2026-07-28). S0–W landed in this repo; S1–S4 landed in the origin project +> (`new-convex-es` / `libar-platform`). The Protocol corpus closes at 121 Specs · 1 Pack · +> 91 anchors → 213 nodes · 416 edges, with `ready: 83 / defined: 36 / scoped: 1 / idea: 1` +> and zero findings. The origin closes at 57 Specs · 1 Pack · 11 anchors → 69 nodes · +> 111 edges, with `ready: 2 / defined: 50 / scoped: 2 / idea: 3` and zero findings. Architect +> carriers, the vendored package, process guards, annotations, release machinery, planning +> plugin, CI legs, and 333 committed projections are retired behind the committed parity ledger. > > **What revision 2 changed:** (1) the charter gains the four usage goals and the workstream W; > (2) the authoring-workflow on-ramp moves from "named successor artifact" (§j of revision 1) to @@ -284,7 +282,7 @@ workflow. The enrichment-lifecycle question enters at `scoped` and remains block **S0 gate evidence.** The generalized records audit accepts the current plan without imposing the historical ledger shape. A mutated plan-17 owner-packet SHA fails with the historical surface -named; a handbook changed from `plan 23 is EXECUTING` to the stale plan-22 status fails both current +named; a handbook changed from `plan 23 is EXECUTED` to the stale plan-22 status fails both current status invariants. The query-seam point passes through the real CLI/extractor, and replacing its validation subjects with an empty result reddens exactly that point. The complete thirteen-leg Protocol gate passes with `spec:extraction.build-pipeline` still stated `defined`. @@ -480,6 +478,31 @@ tracks only what changes here) — S1 gates S2; S2 gates S3 and S4's latency arm can run any time after S1. W2's skill is revised from S1's friction log as a small follow-up session here. -The phase closes when S0 and W have landed here, S1's tracer extracts green in the origin -with its usage acceptance met, and the friction log exists with all four tallies live — the -rest is the origin's delivery, run under the Protocol it just adopted. +### Phase-6 close record + +- **S0:** the records audit is a required universal gate with frozen plan-17/18 history checks; + the verification-posture decision is ratified; the build-pipeline rule-6 point is bound and + mutation-probed while its parent remains `defined`; anchored-but-unbound detection stays + explicitly deferred. +- **W:** both single-file skills ship in the exact package contract; eleven recipes execute + verbatim; fresh-context forward tests corrected the macOS binary collision and adopter catalog + path; the package tarball contains no Protocol corpus. +- **S1:** origin commit `59827121d` installs the package snapshot and lands the green foreign + tracer. The reactive-projection point calls the production API through its generated contract, + reddens under mutation, and re-earns `ready` without changing vitest-cucumber. +- **S2:** origin commit `dad63f1f4` freezes 1,319 exact source dispositions before deletion; + `0c3d79d0d` performs the atomic cutover. The measured inline-carrier population is + **1 / 57 (1.75%)**, recorded without a carrier ruling. +- **S3:** origin commit `ad02746a2` adds `pack:restart-horizon`, scoped test-reporting intent, + and idea-level dynamic-projection and generative-UI intent without encoding schedule. +- **S4:** origin commit `e698e0a2a` retains matched raw logs, structured outputs, hashes, rubric, + and latency. Treatment scores 12/12 versus control 11/12 and uses 987,559 versus 3,671,422 + completion-event tokens (**73.1% fewer**) without worse quality. Ten fresh derivations measure + **432.27 ms median / 444.19 ms p95** after one warm-up. +- **Close audit:** both repositories pass their complete gates from detached clean worktrees; + the origin frozen installs reproduce from both workspace views, all existing suites pass, the + graph has zero findings, and active-surface sweeps find no predecessor dependency, hook, CI leg, + carrier, annotation, or committed generated projection. + +Phase debt disposition and execution order remain in this record, not in ceremonial Specs. The +origin now runs under the Protocol it adopted. diff --git a/test/check-self-hosting-gates.test.ts b/test/check-self-hosting-gates.test.ts index 5ced9a0..c65ef44 100644 --- a/test/check-self-hosting-gates.test.ts +++ b/test/check-self-hosting-gates.test.ts @@ -71,7 +71,7 @@ describe("the self-hosting records gate", () => { const root = copyRecordTree(); const path = join(root, "AGENTS.md"); const source = readFileSync(path, "utf8"); - writeFileSync(path, source.replace("plan 23 is EXECUTING", "plan 22 is EXECUTED")); + writeFileSync(path, source.replace("plan 23 is EXECUTED", "plan 22 is DRAFTED")); const result = runGuard(root); From b6f123cdec06fe5c875838c352431f2a4d01e45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 20:05:34 +0200 Subject: [PATCH 06/24] =?UTF-8?q?fix(docs,tests,specs):=20land=20the=20pha?= =?UTF-8?q?se-6=20post-close=20review=20=E2=80=94=20prose=20sync,=20genera?= =?UTF-8?q?lized=20gates=20probe,=20portable=20skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close the implementation-review findings on the phase-6 usage layer. The AGENTS.md repo map and the reading skill now name all eleven recipes and the sdp-authoring skill; the gates mutation test discovers the highest primary-numbered plan with the same convention as the guard instead of pinning plan 23; the reading skill separates the Protocol-checkout invocation (three mandatory exclusions) from the adopter form and is held to the recipes check's two-form contract; and the test-file impl: anchors for the two document-realized consumer Specs are relabeled under a recorded document-realization binding clause on spec:model.anchors — vocabulary only, no node, edge, or readiness change (121 specs, 213 nodes, 416 edges, zero findings). Plan 23 gains the post-close corrections section and the origin measurement addendum record (73.1% gross / 38.0% non-cached). Claude-Session: https://claude.ai/code/session_0158eQyLnKGzNZTPYcTehw5M --- .claude/skills/sdp-agent-surface/SKILL.md | 31 ++++++++++---- AGENTS.md | 4 +- plans/23-outward-turn-origin-adoption.md | 51 +++++++++++++++++++++-- specs/model/anchors.sdp.md | 1 + test/check-self-hosting-gates.test.ts | 45 ++++++++++++++++++-- test/recipes.test.ts | 5 --- test/self-hosting-oracle/anchors.ts | 4 +- test/self-hosting-oracle/model.ts | 2 + test/skills.test.ts | 51 ++++++++++++++++++++--- 9 files changed, 165 insertions(+), 29 deletions(-) diff --git a/.claude/skills/sdp-agent-surface/SKILL.md b/.claude/skills/sdp-agent-surface/SKILL.md index 4473f35..74a3cc8 100644 --- a/.claude/skills/sdp-agent-surface/SKILL.md +++ b/.claude/skills/sdp-agent-surface/SKILL.md @@ -15,22 +15,37 @@ evaluation sink. There is no verb wall — you script the graph. For any corpus question, **query the graph before reading spec files**. +In an adopter, use the repository's package runner or its documented wrapper script. Select that +repository's root and repeat only the exclusions its corpus needs: + +```sh +pnpm exec sdp q 'return g.specs().length' --root PATH --exclude PATH +pnpm exec sdp q 'return g.specContext("spec:example.id")' --root PATH --exclude PATH --json +``` + +`PATH` is a placeholder, not a universal exclusion. For example, the origin adopter uses its +`pnpm sdp:q` wrapper and excludes only `deps-packages`. + +When working in the **Protocol source checkout itself**, use the built local CLI and its exact +three fixture exclusions: + ```sh node ./dist/cli/sdp.js q 'return g.specs().length' --exclude explorations --exclude examples --exclude test/fixtures/import/parity node ./dist/cli/sdp.js q 'return g.specContext("spec:consumers.reader")' --exclude explorations --exclude examples --exclude test/fixtures/import/parity --json ``` -Those three exclusions are this repository's own and they are **required here**: the corpus carries -deliberate duplicate-id and carrier-parity fixtures, so without them the graph does not derive and -the sink refuses to run the body. They are the same list `npm run generate:self-hosting` passes. -Run `npm run build` first if `dist/` is absent. Do not invoke a global `sdp`: macOS also ships an -unrelated binary with that name. In an adopter, use its package runner, such as `pnpm exec sdp`. +Those exclusions are required only for the Protocol source tree: it carries deliberate +duplicate-id and carrier-parity fixtures. They are the same list `npm run generate:self-hosting` +passes. Run `npm run build` first if `dist/` is absent. Do not invoke a global `sdp`: macOS also +ships an unrelated binary with that name. -The catalog of ready-made bodies is `docs/agent-surface/recipes.md` in the Protocol repository and +The catalog contains eleven ready-made bodies in `docs/agent-surface/recipes.md` in the Protocol +repository and `node_modules/@libar-dev/software-delivery-protocol/docs/agent-surface/recipes.md` in an adopter — build backlog, drift alarm, per-Spec guarantees and verifiers, blast radius, Pack review backbone, -concept search, readiness divergence, and warn-level signals. Every body there runs verbatim and a -test proves it. Start from a recipe; adapt it in place. +concept search, readiness divergence, warn-level signals, promotion preflight, declared-versus-enabled +verifiers, and the lower ladder. Every body there runs verbatim and a test proves it. Start from a +recipe; adapt it in place. Reach for the files only when you need the authored prose itself — the exact words to edit. diff --git a/AGENTS.md b/AGENTS.md index 6a3f24f..1ebc36b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ claims; **`src/` and tests** are authoritative evidence of current realization. > machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; > plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier > rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** ✅ EXECUTED — plan 23 is EXECUTED; phase-6 implementation is complete and the final clean-copy audit passed. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package; the Protocol corpus stands at `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. The origin project adopted the exact package snapshot, migrated its authored corpus and surviving verifier bindings, retired Architect atomically behind a 1,319-entry disposition manifest, and now derives 57 Specs · 1 Pack · 11 anchors with all four readiness rungs and zero findings. Matched second-caller sessions measured 73.1% fewer treatment tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in +> as import source and a lawful per-ID option. · **what now:** ✅ EXECUTED — plan 23 is EXECUTED; phase-6 implementation is complete and the final clean-copy audit passed. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package; the Protocol corpus stands at `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. The origin project adopted the exact package snapshot, migrated its authored corpus and surviving verifier bindings, and retired Architect behind a 1,627-entry disposition manifest plus a widened recurrence guard; its atemporal graph derives 57 Specs · 1 Pack · 11 anchors → 69 nodes · 105 edges with all four readiness rungs and zero findings. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in > **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when @@ -58,7 +58,7 @@ Progressive disclosure — start at the top, follow the pointers down. | `docs/concept/` (+ README) | **the technical design** — the surviving principle-led docs: vision & MVP boundary, founding principles (P1–P10), authoring & binding, consumers, roadmap; the core model dissolved into the model Specs (`spec:model.core-model`, `spec:model.spec-sections`, `spec:model.relations`, `spec:model.stable-ids`, `spec:model.pack-aggregate`), the one graph into the extraction Specs (`spec:extraction.derive-graph`, `spec:extraction.determinism`, `spec:extraction.claim-taxonomy`, `spec:extraction.regenerability`, `spec:extraction.schema-versioning`), and validation & honesty into the validation Specs (`spec:validation.two-check-families`, `spec:validation.readiness-floor`, `spec:validation.kind-evidence`, `spec:validation.warn-level-signals` and their siblings) | to know *how* it is designed | | `docs/concept/DECISIONS.md` | **the lean decision registry** — ratified names, one-line glosses, carrying Specs, and the D1–D6 lookup; historical rationale lives in git, plans, and the Specs themselves | when resolving a decision name or following its canonical pointer | | `src/` | **the engine** — `model` (Spec/descriptors/pack/anchors) · `extract` · `graph` · `validate` · `reader` (agent surface) · `projections` (Design Review) · `cli` (`sdp build` · `validate` · `view` · `import` · `q`) · `runner` / `codegen` / `notation` / `adapters` | when implementing or verifying **current engine** behavior | -| `.claude/skills/sdp-agent-surface/` + `docs/agent-surface/recipes.md` | **the agent on-ramp** — the skill that teaches the surface, and the catalog of runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals); every body is executed as written by `test/recipes.test.ts` | before answering any corpus question — query the graph, don't read spec files | +| `.claude/skills/sdp-agent-surface/` + `.claude/skills/sdp-authoring/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — the reading and authoring skills, plus eleven runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals · promotion preflight · declared/enabled verifier audit · lower ladder); every body is executed as written by `test/recipes.test.ts` | before answering a corpus question or authoring intent — query the graph, then follow the carrying Specs | | `examples/checkout-v1` | **the worked MVP example** (TS DSL tracer bullet) — specs, anchors, untracked `generated/` (regenerated in-pipeline); walkthrough in its README | when proving the loop end-to-end | | `explorations/` | **evidence only** (carrier exhibits, executable-example findings) — mapping evidence for design; **never promote spike code into product** | when judging design evidence; not a source tree to ship | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code — highest primary-numbered plan's status header, plus active subplans it designates; if DRAFTED, also the latest ✅ EXECUTED/RUN plan | diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index d675b69..6d9ff70 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -5,7 +5,7 @@ > (`new-convex-es` / `libar-platform`). The Protocol corpus closes at 121 Specs · 1 Pack · > 91 anchors → 213 nodes · 416 edges, with `ready: 83 / defined: 36 / scoped: 1 / idea: 1` > and zero findings. The origin closes at 57 Specs · 1 Pack · 11 anchors → 69 nodes · -> 111 edges, with `ready: 2 / defined: 50 / scoped: 2 / idea: 3` and zero findings. Architect +> 105 edges, with `ready: 2 / defined: 50 / scoped: 2 / idea: 3` and zero findings. Architect > carriers, the vendored package, process guards, annotations, release machinery, planning > plugin, CI legs, and 333 committed projections are retired behind the committed parity ledger. > @@ -490,14 +490,18 @@ session here. - **S1:** origin commit `59827121d` installs the package snapshot and lands the green foreign tracer. The reactive-projection point calls the production API through its generated contract, reddens under mutation, and re-earns `ready` without changing vitest-cucumber. -- **S2:** origin commit `dad63f1f4` freezes 1,319 exact source dispositions before deletion; - `0c3d79d0d` performs the atomic cutover. The measured inline-carrier population is +- **S2:** origin commit `dad63f1f4` freezes the historical 1,319-row parity population before + deletion; the post-close adversarial sweep expands the integrity index to 1,627 rows. + `0c3d79d0d` performs the selected atomic deletion pass: “atomic” names the committed + parity-checkpoint → deletion boundary, not a claim that later review could discover no missed + reference. The measured inline-carrier population is **1 / 57 (1.75%)**, recorded without a carrier ruling. - **S3:** origin commit `ad02746a2` adds `pack:restart-horizon`, scoped test-reporting intent, and idea-level dynamic-projection and generative-UI intent without encoding schedule. - **S4:** origin commit `e698e0a2a` retains matched raw logs, structured outputs, hashes, rubric, and latency. Treatment scores 12/12 versus control 11/12 and uses 987,559 versus 3,671,422 - completion-event tokens (**73.1% fewer**) without worse quality. Ten fresh derivations measure + gross completion-event tokens (**73.1% fewer**) and 113,575 versus 183,166 non-cached + input-plus-output tokens (**38.0% fewer**) without worse quality. Ten fresh derivations measure **432.27 ms median / 444.19 ms p95** after one warm-up. - **Close audit:** both repositories pass their complete gates from detached clean worktrees; the origin frozen installs reproduce from both workspace views, all existing suites pass, the @@ -506,3 +510,42 @@ session here. Phase debt disposition and execution order remain in this record, not in ceremonial Specs. The origin now runs under the Protocol it adopted. + +### Post-close implementation-review corrections + +The grounded review found four correctness/honesty defects and four Protocol hygiene defects. The +fix session re-measured and dispositioned all of them: + +- Origin test entry points now generate Protocol contracts before any unit/watch/coverage suite + that imports the bound contract; the PR template names the complete `pnpm sdp:check` gate. +- A second SHA-pinned cutover population, expanded by the final-pass audit, adds 308 exact rows for + root support, MCP, review, agent/command/plan, docs, inbox evidence, and the retired handbook + package. The manifest now has **1,627** rows; competing project-management and re-entry status + systems are deleted, retained technical docs are explicitly classified, and a mutation-tested + guard prevents active predecessor paths or instructions from returning. +- Four tranche-shaped IDs are renamed to capability names. Six schedule-shaped `dependsOn` edges + and ordering clauses are removed; the only surviving dependency in the repaired cluster is the + real security-to-authentication dependency. No Spec is promoted. +- The measurement publishes an addendum: 73.1% is gross and includes cached input; non-cached + input plus output falls 38.0%. Raw logs prove usage/cache splits but do not independently carry + the model string or control SHA. The reviewed phase-close count is 11,936 Vitest assertions plus + 8 script assertions, **11,944 total**. The first corrective gate adds two retired-surface + assertions and reaches 11,946; final-pass removal of the dead 385-assertion handbook package + leaves the active gate at **11,561 total**. +- The shipped reading skill separates adopter invocation from the Protocol source checkout, + names all eleven recipes, and the repository map includes the authoring skill. +- The records-gate mutation test discovers the current primary plan with the same convention as + the guard rather than pinning plan 23. +- `spec:model.anchors` now records the narrow document-realization convention used by the two + test-file implementation anchors, whose labels explicitly name the asserted document artifacts. +- The origin close record states the verifier batch denominator (nine of 127 executable step files + plus the bound tracer), the stricter local `ready` discipline, and the manifest/ledger evidence + boundary. Ignored cutover residue moved from `/tmp` to a durable recovery directory. +- The corrected 16-file package snapshot is pinned at SHA-256 + `7c5a176f658b9d477a324ac0017fd16e66eb04617db675e01cc1c9482512eb76`; the original S1 snapshot + remains immutable historical evidence. + +The Protocol audit still reports eight development-dependency advisories (3 moderate, 4 high, +1 critical). The actionable aggregate fix is a Vitest 2→4 major migration; it is recorded rather +than silently folded into this review repair because it changes the test runner and does not affect +the shipped runtime surface. diff --git a/specs/model/anchors.sdp.md b/specs/model/anchors.sdp.md index 5cef727..6dc5469 100644 --- a/specs/model/anchors.sdp.md +++ b/specs/model/anchors.sdp.md @@ -17,6 +17,7 @@ relations: - **code anchor** — An implementation-flavored binding that derives an anchored satisfies edge. - **test anchor** — A binding that derives an anchored verifies edge from a test to its target Spec. - **oracle anchor** — A binding that records an oracle's models target without deriving a delivery fact. +- **document-realization binding** — When the realizing artifact is authored Markdown that cannot carry an extracted in-code anchor, the executable suite that asserts the shipped document may carry its code anchor. Its label must name the document realization rather than imply the test body is the product, and file-level blast radius remains coverage-unknown for the Markdown artifact. - **anchor-constant form** — The top-level const builder call that the MVP extractor reifies; decorator and JSDoc forms remain unextracted representations. - **Protocol builder binding** — A builder import from the public Protocol package, or a relative import whose importer-relative resolution — including the TypeScript `.js`-to-`.ts` convention — canonicalizes to this package's `ids` or `model/code-anchor` module; consumer-local lookalike modules confer no binding authority. On the CommonJS package surface the trusted relative-module set is empty (`import.meta.url` is rewritten away), so relative bindings mint no anchors there while package imports stay trusted. - **untrusted builder** — A builder call whose import is no Protocol builder binding: it mints nothing and reports nothing, because a source file that never bound to the Protocol is not authoring drift to report. The realizing entrypoints are `protocolBindingScopeFor` and `collectProtocolBindings` in `src/extract/protocol-bindings.ts`. diff --git a/test/check-self-hosting-gates.test.ts b/test/check-self-hosting-gates.test.ts index c65ef44..0ef8d12 100644 --- a/test/check-self-hosting-gates.test.ts +++ b/test/check-self-hosting-gates.test.ts @@ -1,5 +1,13 @@ import { spawnSync } from "node:child_process"; -import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { + copyFileSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -9,6 +17,31 @@ import { afterEach, describe, expect, it } from "vitest"; const repoRoot = fileURLToPath(new URL("..", import.meta.url)); const guardPath = join(repoRoot, "check-self-hosting-gates.mjs"); const roots: string[] = []; +const primaryPlans = readdirSync(join(repoRoot, "plans"), { withFileTypes: true }) + .filter((entry) => entry.isFile()) + .map((entry) => { + const match = /^(?[1-9]\d*)-[a-z0-9][a-z0-9-]*\.md$/u.exec(entry.name); + const number = match?.groups?.number; + return number === undefined ? null : { name: entry.name, number: Number(number) }; + }) + .filter((entry) => entry !== null) + .sort((left, right) => right.number - left.number); +const currentPlan = primaryPlans[0]; + +if (currentPlan === undefined) { + throw new Error("test fixture requires a current primary-numbered plan"); +} + +const currentPlanPath = ["plans", currentPlan.name].join("/"); +const currentPlanSource = readFileSync(join(repoRoot, currentPlanPath), "utf8"); +const currentStatus = /\b(DRAFTED|EXECUTING|RUN|EXECUTED)\b/u.exec( + currentPlanSource.split("\n").find((line) => line.startsWith("> **Status:**")) ?? "", +)?.[1]; + +if (currentStatus === undefined) { + throw new Error(`${currentPlanPath} has no readable status`); +} + const copiedPaths = [ "AGENTS.md", "CONTEXT.md", @@ -17,7 +50,7 @@ const copiedPaths = [ ["plans", "16-carrier-ruling.md"].join("/"), ["plans", "17-self-hosting-v1.md"].join("/"), ["plans", "18-self-hosting-phase-2.md"].join("/"), - ["plans", "23-outward-turn-origin-adoption.md"].join("/"), + currentPlanPath, ] as const; function copyRecordTree(): string { @@ -71,7 +104,13 @@ describe("the self-hosting records gate", () => { const root = copyRecordTree(); const path = join(root, "AGENTS.md"); const source = readFileSync(path, "utf8"); - writeFileSync(path, source.replace("plan 23 is EXECUTED", "plan 22 is DRAFTED")); + writeFileSync( + path, + source.replace( + `plan ${String(currentPlan.number)} is ${currentStatus}`, + `plan ${String(currentPlan.number - 1)} is DRAFTED`, + ), + ); const result = runGuard(root); diff --git a/test/recipes.test.ts b/test/recipes.test.ts index 27bc425..96a0087 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -225,11 +225,6 @@ describe("the agent-surface recipe corpus", () => { }).toEqual({ source, line, names: standardExcludes.length }); } - if (source === ".claude/skills/sdp-agent-surface/SKILL.md") { - expect(adopterLines).toEqual([]); - continue; - } - expect({ source, adopterForms: adopterLines.length }).toEqual({ source, adopterForms: 2, diff --git a/test/self-hosting-oracle/anchors.ts b/test/self-hosting-oracle/anchors.ts index 22b38d7..67810be 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -56,7 +56,7 @@ export const expectedAnchors = [ { id: "impl:protocol.authoring-on-ramp", nodeType: "CodeNode", - label: "validates the shipped graph-first authoring on-ramp", + label: "asserts realization of the shipped graph-first authoring skill document", type: "satisfies", target: "spec:consumers.authoring-on-ramp", file: "test/skills.test.ts", @@ -66,7 +66,7 @@ export const expectedAnchors = [ { id: "impl:protocol.authoring-recipes", nodeType: "CodeNode", - label: "validates the shipped authoring recipe surface", + label: "asserts realization of the shipped authoring recipe catalog", type: "satisfies", target: "spec:consumers.agent-surface.authoring-recipes", file: "test/skills.test.ts", diff --git a/test/self-hosting-oracle/model.ts b/test/self-hosting-oracle/model.ts index 998c66f..36a395c 100644 --- a/test/self-hosting-oracle/model.ts +++ b/test/self-hosting-oracle/model.ts @@ -297,6 +297,8 @@ export const modelSpecs = [ "The top-level const builder call that the MVP extractor reifies; decorator and JSDoc forms remain unextracted representations.", "code anchor": "An implementation-flavored binding that derives an anchored satisfies edge.", + "document-realization binding": + "When the realizing artifact is authored Markdown that cannot carry an extracted in-code anchor, the executable suite that asserts the shipped document may carry its code anchor. Its label must name the document realization rather than imply the test body is the product, and file-level blast radius remains coverage-unknown for the Markdown artifact.", "oracle anchor": "A binding that records an oracle's models target without deriving a delivery fact.", "test anchor": diff --git a/test/skills.test.ts b/test/skills.test.ts index 3d8e08c..c78026c 100644 --- a/test/skills.test.ts +++ b/test/skills.test.ts @@ -21,7 +21,7 @@ const skillPaths = [ const authoringOnRampImplementationAnchor = codeAnchor({ id: codeAnchorId("impl:protocol.authoring-on-ramp"), - label: "validates the shipped graph-first authoring on-ramp", + label: "asserts realization of the shipped graph-first authoring skill document", satisfies: ref("spec:consumers.authoring-on-ramp"), }); void authoringOnRampImplementationAnchor; @@ -43,7 +43,7 @@ function readSkill(path: string) { const authoringRecipesImplementationAnchor = codeAnchor({ id: codeAnchorId("impl:protocol.authoring-recipes"), - label: "validates the shipped authoring recipe surface", + label: "asserts realization of the shipped authoring recipe catalog", satisfies: ref("spec:consumers.agent-surface.authoring-recipes"), }); void authoringRecipesImplementationAnchor; @@ -152,9 +152,11 @@ describe("Protocol skill assets", () => { const agentSurface = readSkill(".claude/skills/sdp-agent-surface/SKILL.md"); const authoring = readSkill(".claude/skills/sdp-authoring/SKILL.md"); - expect(documentedCommands(agentSurface.body).every((line) => line.startsWith("node "))).toBe( - true, - ); + expect( + documentedCommands(agentSurface.body).every( + (line) => line.startsWith("node ") || line.startsWith("pnpm exec "), + ), + ).toBe(true); expect( documentedCommands(authoring.body).every( (line) => line.startsWith("node ") || line.startsWith("pnpm exec "), @@ -179,4 +181,43 @@ describe("Protocol skill assets", () => { } } }); + + it("keeps recipe-count prose synchronized with the executable catalog", () => { + const catalog = readFileSync(join(repoRoot, "docs/agent-surface/recipes.md"), "utf8"); + const headings = [...catalog.matchAll(/^## \d+\. /gmu)]; + const countWords = [ + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "ten", + "eleven", + "twelve", + "thirteen", + "fourteen", + "fifteen", + "sixteen", + "seventeen", + "eighteen", + "nineteen", + "twenty", + ] as const; + const countWord = countWords[headings.length]; + + expect(countWord).toBeDefined(); + if (countWord === undefined) { + throw new Error(`recipe count ${String(headings.length)} is outside the checked prose range`); + } + + const agents = readFileSync(join(repoRoot, "AGENTS.md"), "utf8"); + const skill = readSkill(".claude/skills/sdp-agent-surface/SKILL.md").source; + expect(agents).toContain(`${countWord} runnable \`sdp q\` bodies`); + expect(skill).toContain(`catalog contains ${countWord} ready-made bodies`); + }); }); From 6c210ec770222578b490f46f643fcfda48664005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 20:53:05 +0200 Subject: [PATCH 07/24] =?UTF-8?q?docs(plans):=20draft=20plan=2024=20rev=20?= =?UTF-8?q?2=20=E2=80=94=20the=20inward=20turn;=20supersede=20plan=2023=20?= =?UTF-8?q?as=20working=20guidance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan 24 revision 2 re-derives every ground-truth number by graph query and restructures the phase around what the re-measurement found: the 64-row backlog is 51 example spaces + 13 laws (all verifier-bound), so S1 becomes one kind-level ruling on example implemented-semantics plus thirteen law dispositions; the 8 drift rows all derive ready, so their disposition is refusal re-argument, never enrichment. Names PR #15 as the delivery vehicle, states the self-hosted-setup milestone once, and carries the Vitest 2->4 debt as a named non-Spec chore. Plan 23 gains a supersession note: EXECUTED history only; origin-repo S1-S4 changes were reverted locally and survive solely in libar-ai/convex-event-sourcing#181. AGENTS.md status line moves with plan 24 (records gate green). Claude-Session: https://claude.ai/code/session_0158eQyLnKGzNZTPYcTehw5M --- AGENTS.md | 2 +- plans/23-outward-turn-origin-adoption.md | 11 + plans/24-inward-turn-self-hosting-practice.md | 339 ++++++++++++++++++ 3 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 plans/24-inward-turn-self-hosting-practice.md diff --git a/AGENTS.md b/AGENTS.md index 1ebc36b..43fd811 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ claims; **`src/` and tests** are authoritative evidence of current realization. > machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; > plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier > rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** ✅ EXECUTED — plan 23 is EXECUTED; phase-6 implementation is complete and the final clean-copy audit passed. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package; the Protocol corpus stands at `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. The origin project adopted the exact package snapshot, migrated its authored corpus and surviving verifier bindings, and retired Architect behind a 1,627-entry disposition manifest plus a widened recurrence guard; its atemporal graph derives 57 Specs · 1 Pack · 11 anchors → 69 nodes · 105 edges with all four readiness rungs and zero findings. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in +> as import source and a lawful per-ID option. · **what now:** DRAFTED — plan 24 is DRAFTED (the inward turn: self-hosting as the standing practice — the backlog becomes Specs, the binding layer completes honestly, the first inward oracle binds, and one engine slice runs the whole spec-first loop including post-implementation slimming). The prior status — plan 23 is EXECUTED; phase-6 implementation complete, final clean-copy audit passed — remains historic context. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package; the Protocol corpus stands at `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. The origin project adopted the exact package snapshot, migrated its authored corpus and surviving verifier bindings, and retired Architect behind a 1,627-entry disposition manifest plus a widened recurrence guard; its atemporal graph derives 57 Specs · 1 Pack · 11 anchors → 69 nodes · 105 edges with all four readiness rungs and zero findings. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in > **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md index 6d9ff70..c7043f0 100644 --- a/plans/23-outward-turn-origin-adoption.md +++ b/plans/23-outward-turn-origin-adoption.md @@ -1,5 +1,16 @@ # Plan 23 — The outward turn, refined: origin adoption, the usage layer, and the working ladder +> **⚠️ SUPERSEDED as working guidance (2026-07-28).** This plan is ✅ EXECUTED and stands only +> as the historical record of phase 6; the **standing plan is +> [plan 24](24-inward-turn-self-hosting-practice.md)** — read that, not this file, for what to +> do next. **Origin-repo caution:** the S1–S4 workstreams below executed in the origin project +> (`new-convex-es` / `libar-platform`), but those local changes were **reverted in the origin +> working copy** and survive only as PR libar-ai/convex-event-sourcing#181 (unmerged, awaiting +> the owner's decision). No session may treat the origin working tree as carrying this plan's +> S1–S4 results, and none re-applies them — plan 24's charter forbids origin-repo work +> entirely. The owner intends to delete this file once plan 24 is implemented; its evidence +> (commit SHAs, measurements, dispositions) remains recoverable from git history and PR #181. + > **Status:** ✅ EXECUTED — **phase-6 implementation complete; final clean-copy audit passed** > (2026-07-28). S0–W landed in this repo; S1–S4 landed in the origin project > (`new-convex-es` / `libar-platform`). The Protocol corpus closes at 121 Specs · 1 Pack · diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md new file mode 100644 index 0000000..a808c17 --- /dev/null +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -0,0 +1,339 @@ +# Plan 24 — The inward turn: self-hosting as the standing practice + +> **Status:** DRAFTED — PLAN-ONLY. No target file is touched under this plan until an execution +> session is designated. The latest ✅ EXECUTED ground is **plan 23** (phase 6: the usage turn — +> the S0 rulings, the records gate, the usage layer W, and the origin adoption; Protocol corpus +> at 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges, `ready: 83 / defined: 36 / +> scoped: 1 / idea: 1`, 0 findings). The origin adoption lives on its own PR +> (libar-ai/convex-event-sourcing#181) awaiting the owner's review; **nothing in this plan +> depends on that decision**, and no session under this plan touches the origin repo. This plan +> proposes **phase 7 as the inward turn**: self-hosting as the *operating mode* — the Protocol's +> own forward work managed as Specs through its own ladder, every engine change delivered +> spec-first with executable verification, and the graph (not plan prose) carrying what is owed. +> This is the original intention of the self-hosting program resumed: **validation and iteration +> on the Protocol, by the Protocol.** +> +> **What revision 2 changed** (2026-07-28, same-day graph re-measurement — every number below +> was re-derived by query, none inherited): (1) §b gains the composition facts that reshape S1 — +> the 64-row backlog is **51 example spaces + 13 laws**, every row already verifier-bound; the +> 8 drift rows are **one cluster** (the dissolved concept-doc Specs) whose floors already derive +> `ready`; all 37 stated/derived divergences are under-claims, zero over-claims. (2) S1 is +> restructured around **one kind-level ruling** — what `implemented` honestly means for an +> example-kind Spec — applied once, instead of 64 per-row decisions; the ceremonial third path +> (51 hand anchors) is named and forbidden. (3) The drift disposition is corrected: the floor is +> already met on all 8 rows, so the work is refusal re-argument, never enrichment. (4) S2 gains +> the example-`implemented` derivation as a candidate slice, contingent on S1's ruling. (5) The +> phase milestone is stated once in §a: the proper self-hosted setup — annotations honest, the +> agent CLI the standing working surface, Specs carried through to executable Specs — enabling +> iteration on the Protocol's design and implementation by the Protocol. (6) The delivery +> vehicle is named (§i): execution expands PR #15. (7) The Vitest 2→4 migration debt recorded +> at the plan-23 close is carried as a named non-Spec chore (§g). The charter, the honesty +> guardrails, and S0 stand as drafted. +> +> **Spec anchors:** [the agent front door](../specs/decisions/agent-front-door.sdp.md) +> (`spec:decisions.agent-front-door`, MD-22) · [the verification-posture ruling](../specs/decisions/verification-posture-not-realization.sdp.md) +> (`spec:decisions.verification-posture-not-realization`, MD-23) · +> [the intent-composition seed](../specs/consumers/intent-composition.sdp.md) +> (`spec:consumers.intent-composition`) · [the enrichment lifecycle](../specs/model/enrichment-lifecycle.sdp.md) +> (`spec:model.enrichment-lifecycle`) · plan 23 §(e) W (the usage layer this plan now *uses*). + +## (a) Why this plan + +Phase 6 built the usage layer and bought the first second-caller evidence, but by charter it was +not an inward phase: the Protocol repo received the tools of self-hosting practice without the +practice itself. The gap, re-measured at revision 2: + +1. **The graph knows what is owed, but nothing works off it.** The build backlog + (`ready ∧ ¬implemented`) stands at **64** Specs and the drift alarm at **8** — both now + queryable by recipe — while the Protocol's actual forward work (the write surface, the + observed rung, the impact graph, the Pack syntax question) still lives in plan prose, + deferral clauses, and `06`/`07` gap rows. The product's headline claim is that the graph is + the working surface; its own repo still works from documents. +2. **The lower ladder holds two tokens, not a practice.** `idea: 1 / scoped: 1` — the first + honest low-rung Specs exist (phase 6, W1), but no session has yet *managed* work through + them: entered at `idea`, enriched on evidence, promoted on the floor, implemented off the + backlog. +3. **The binding layer is one-sided — and the asymmetry has a shape.** `has-verifier` reaches + **84** of 121 Specs; `implemented` reaches **27**. Re-measurement locates the gap precisely: + **all 64 backlog rows already carry verifiers**, and **51 of the 64 are example-kind + Specs** — condition grids whose suites are bound and green while the fact that would call + them "built" is absent. Several belong to parents that *are* implemented + (`spec:consumers.reader` is implemented; its three example spaces sit on the backlog as + "unimplemented" — a claim no honest reading supports). The asymmetry is concentrated in one + kind, which makes most of the audit a single semantics ruling, not 64 binding decisions. The + defect is in the derived facts' *input*, not in the derivation. +4. **The oracle machinery has never run on this corpus.** Zero `models` edges in the + self-hosting graph; `specOracle` is exercised only in `examples/checkout-v1`. The + executable half's most distinctive piece has no second caller — and self-hosting *is* the + cheapest second caller available. +5. **Standing deferrals now have their evidence and no disposition.** The inline-carrier + instrument came back **1/57 (1.75%)** — a population that argues for *retiring* the + deferral, not pursuing it. The edit-model has its friction evidence and its `idea`-rung + seed. Neither has been acted on. + +The thesis: **phase 7 makes the Protocol its own first-class user.** Sessions open from the +graph, forward work enters as Specs at honest rungs, engine changes go spec-first through the +executable loop, and the phase is judged by whether the loop *iterated* — authored → enriched → +implemented → bound → verified → slimmed — on real engine work, not by corpus growth. + +**The milestone, stated once.** What the phase must leave standing is the proper self-hosted +setup: **annotations** honest and complete (every backlog and drift row deliberate — bound, +refused, or known-real), the **agent CLI** as the standing working surface (every session opens +from recipes, never from prose), and **Specs carried through to executable Specs** (the oracle +machinery bound inward; at least one engine change delivered spec-first with executable +verification end to end). That setup — not any corpus number — is what enables iteration on the +Protocol's design and implementation *by the Protocol*, and it is the standard §i's close +criteria encode. + +## (b) Ground truth at revision 2 (re-derived by query, not inherited) + +- Corpus: 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges · 0 errors / 0 warnings; + `ready: 83 / defined: 36 / scoped: 1 / idea: 1`. Delivery facts: `implemented` 27 · + `has-verifier` 84 · `models` edges 0. +- **Backlog 64, with composition:** 51 `example` + 11 `rule` + 1 `behavior` + (`spec:carrier.sdp-import`) + 1 `constraint` (`spec:extraction.determinism`). All 64 carry + `has-verifier`. The eleven rules: `spec:consumers.binding-language-views` · + `spec:consumers.derived-readiness-banner` · `spec:consumers.wholesale-view-rewrite` · + `spec:validation.authored-honesty` · `spec:validation.claim-separation` · + `spec:validation.diagnostic-rendering` · `spec:validation.kind-evidence` · + `spec:validation.pack-coherence` · `spec:validation.referential-integrity` · + `spec:validation.verification-linkage` · `spec:validation.warn-level-signals`. Note the + coupling: most of these laws themselves own example spaces on the backlog, so S1's kind + ruling and the 13 law bindings interact (§e). +- **Drift 8, one cluster:** `spec:model.core-model` · `spec:model.spec-sections` · + `spec:model.relations` · `spec:model.pack-aggregate` · `spec:extraction.claim-taxonomy` · + `spec:extraction.regenerability` · `spec:consumers.projections-model` · + `spec:carrier.markdown-authoring` — all dissolved-concept-doc Specs held at `defined`, and + **all eight derive `ready`**: the floor already passes; only the standing refusals hold the + rung. +- **Readiness divergence: 37 Specs, all under-claims** (stated below derived), zero + over-claims — the corpus's conservatism is uniform in the honest direction. +- Assets shipped by phase 6 and now to be *used*: the `sdp-authoring` skill (the full + create → enrich → transition → anchor → state-`ready` workflow), eleven recipes (including + promotion preflight, verifier audit, lower-ladder view), the records gate as a required + check leg, the adopter package contract, MD-23. +- Standing refusals: the 35 `defined` Specs refuse for named reasons (22 decisions · + 7 vocabulary · 3 whole-pipeline · 3 singletons); **they stand** — this plan promotes nothing + by sweep. +- Standing deferrals with state: inline carrier (instrument answered: 1/57 — retirement + candidate); edit-model / write surface (friction evidence + `spec:consumers.intent-composition` + at `idea`); anchored-but-unbound (deferred — `bindExample` is not graph data); `bySymbol` / + impact graph (aspirational, `06` gap row 8a); `observed` rung (aspirational, no producer); + Pack Markdown syntax (unruled — the one carrier question MD-18 left open); `06` five gap + rows · `07` three gap rows (graded out of scope at plan 22 §5c, unchanged since). +- **Carried debt (non-Spec):** the plan-23 close records 8 development-dependency advisories + (3 moderate · 4 high · 1 critical) whose actionable aggregate fix is a **Vitest 2→4 major + migration** — deliberately recorded, not silently folded in. It changes the test runner, not + the shipped runtime surface. Disposition in §g. +- Review soft spots carried from the phase-6 close, for the hygiene pass (§g): the + recipe-count prose in `AGENTS.md`/the reading skill is unguarded (staleness recurs at recipe + twelve); the records-gate discovery logic is duplicated between script and test (accepted, + watch for drift); the document-realization anchor convention is authored discipline with no + validator (by design — record only). + +## (c) Charter — what phase 7 is and is not + +**Is:** the inward practice. Converting the genuine forward intent into low-rung Specs and +managing it there (S0); completing the implementation-binding layer honestly — one kind ruling +plus thirteen law dispositions, not a binding sweep (S1); running the oracle machinery on this +corpus (S1); delivering at least one real engine improvement through the full spec-first +executable loop, selected *from the backlog by recipe at the session start* (S2); +dispositioning the deferrals whose evidence has arrived (S0); and exercising the enrichment +lifecycle's slimming half on at least one implemented Spec so +`spec:model.enrichment-lifecycle`'s question gains its first observed case (S2). + +**Is not:** an origin phase — PR #181 is the owner's decision and no session here touches that +repo (if the owner later wants the adoption resumed, revised, or abandoned, that is its own +plan). Not a readiness sweep — the 35 refusals stand unless new verifiers *naturally* convert +one, in which case the promotion is argued individually against the floor. Not an engine +expansion — no write surface build, no `bySymbol`, no `observed` producer, no MCP; those enter +as `idea`/`scoped` Specs (S0) and *wait on the backlog like everything else*. Not a workflow +layer — the one skill suffices until the inward friction log says otherwise. + +**Honesty guardrails:** a Spec is authored only for genuine intended system truth — three real +low-rung Specs beat eight ceremonial ones, and a refusal to author is recorded with its reason; +an anchor is added only where code genuinely realizes the Spec's truth (a law realized by many +files diffusely gets a named refusal, never a token anchor — and **51 hand anchors on example +spaces would be the same sin at scale**, forbidden by name in §e); no promotion without the +floor and a fresh human statement; every session opens with the build-backlog and drift-alarm +recipes and closes with the gate green. + +## (d) S0 — the backlog becomes Specs; the evidenced deferrals get dispositions + +One session. Two halves. + +**The conversion half.** Survey the standing forward intent (the deferral clauses, the `06`/`07` +gap rows, the phase-6 successor notes) and author the genuine capabilities as Specs entering at +the rung their structure earns — expected `idea`, some `scoped`: + +- **the runtime-observation overlay** (the `observed` delivery fact's producer — designed-in + since `00`; the ladder's third rung has intent and no Spec); +- **the impact graph** (`bySymbol`, the `inferred`-claim producer, the two assist roles of + `06` §2 — dissolving gap row 8a into the Spec if the audit shows the row fully carried); +- **the Pack Markdown carrier** *as intent* (the authoring side of the Pack-syntax question — + the Spec records what a Markdown Pack surface must honor; the *ruling* itself is S2's + candidate slice, and the Spec waits on it); +- **enrich `spec:consumers.intent-composition`** `idea → scoped` only if the friction evidence + genuinely forces an outcome statement — otherwise it stands at `idea` and the write-surface + design session stays a named successor. + +Each candidate passes the guard or is refused by name. The Vitest migration is **not** a Spec +candidate — it is maintenance, not system truth; it stays in §g. The lower-ladder recipe is the +working surface; the session's done-record lists every authored Spec beside every refusal. + +**The disposition half.** Two deferrals have their evidence: + +- **The inline carrier: retire on the measured population.** 1/57 leaf-altitude single-module + candidates is the instrument's answer — the separate-file carrier is not the friction point. + Record the retirement (plan record + the deferral clause re-pointed; a DECISIONS entry only + if drafting shows the three-part test genuinely passes — likely this is evidence-closure, not + a new decision). +- **Anchored-but-unbound: keep deferred, restate the boundary.** `bindExample` sites remain + non-graph data; extracting them is engine surface no current caller demands. The deferral + rides `spec:model.anchors`' clause as-is. + +## (e) S1 — the binding layer completed honestly; the first inward oracle + +One to two sessions, driven entirely by recipes. + +**First, the kind ruling.** 51 of the 64 backlog rows are example-kind Specs, every one +verifier-bound, several under parents already `implemented`. Before any binding, S1 answers +one question: **what does `implemented` honestly mean for an example-kind Spec?** Three +candidate outcomes, exactly one of which is ruled (three-part test applied — this looks like a +genuine decision: hard to reverse, surprising without the re-measurement, a real trade-off): + +1. **refuse by kind** — an example space's realization *is* its parent's realization plus its + bound suite; `implemented` does not apply below the parent. Recorded as a convention on the + kind-evidence law (or `spec:model.anchors`), and the backlog honestly shrinks to the 13 + laws; +2. **derive through the parent** — an example space is `implemented` when its `refines` parent + is; an extraction/validation change, which therefore enters S2 as a spec-first candidate + slice and *waits its turn* — S1 records the ruling, S2 builds it; +3. **bind individually** — rejected on its face as the ceremonial path (§c), admissible only + for a specific example space that genuinely owns a realization site distinct from its + parent's. + +Whichever way it lands, the ruling is made once and applied 51 times; the deliverable is the +*ruling with its reasoning*, not 51 edits. + +**Then, the thirteen laws.** Walk the non-example rows with the verifier-audit and +spec-guarantees recipes. Each gets exactly one of three dispositions, recorded: + +1. **bind** — engine code genuinely realizes it at a nameable location (the eleven validation + and consumer rules are expected mostly here: each names a concrete validator or view + behavior; `spec:extraction.determinism` names the determinism check; + `spec:carrier.sdp-import` names the import command): add the `codeAnchor` (binding-only law + unchanged), converting the row into honest `implemented`; +2. **refuse** — realized diffusely (a law the whole pipeline honors) or by a document: name + the refusal; the document-realization convention (phase 6) applies only where an asserting + suite genuinely exists; +3. **backlog** — genuinely unbuilt: the row stands, now known-real. + +Note the coupling: most of the eleven rules own example spaces of their own, so under outcome +(2) of the kind ruling, binding the laws collapses most of the 64 honestly; under outcome (1) +the backlog shrinks to 13 regardless. Either way the deliverable is not a number target — it is +a backlog whose every row is *deliberate*. + +**The drift rows, corrected framing.** All 8 drift rows (`implemented ∧ ¬ready`) are the +dissolved-concept cluster, and **their floors already derive `ready`** — there is nothing to +enrich. The disposition is a refusal re-argument, per row: either the standing refusal (a +vocabulary/singleton/decision reason) is re-stated against today's corpus and the rung stays +honestly `defined`, or the refusal no longer holds and the Spec is promoted *individually* on +the floor plus a fresh human statement. No batch promotion; "the floor passes" alone never +suffices — that is exactly the content-quality line checks refuse to police, so a human owns +it. + +**The first inward oracle.** Pick one self-hosting parent that owns an example space — the +standing candidates are confirmed live: `spec:validation.duplicate-ids` (one space) and +`spec:extraction.build-pipeline` (one space) — and author its oracle: the `specOracle` anchor +plus the typed `expected(conditions)` function beside the bound suite, mutation-probed (a wrong +expected outcome must redden). This is the oracle machinery's second caller, on the corpus that +can least fake it. If the space's Outcome union makes an oracle vacuous (one outcome class), +record that refusal and pick the other candidate — a vacuous oracle authored for coverage would +be exactly the padding the floor forbids. + +## (f) S2 — the iteration loop, proven on real engine work + +The phase's point. At the session open, run the build-backlog recipe and **select the top +genuine engine slice from the graph** — the plan deliberately does not hardcode the choice, +because working off the graph *is the deliverable*. Standing candidates the backlog is expected +to surface, with their gates: + +- **The example-`implemented` derivation** — *if and only if* S1's kind ruling landed on + outcome (2): the `refines`-parent derivation enters as a Spec (likely on + `spec:extraction.derive-graph`'s family or the kind-evidence law), is enriched to `ready`, + and lands spec-first with the backlog's honest collapse as its observable effect. This is + the natural first candidate because S1 will have already argued its design. +- **The Pack syntax ruling + the Markdown Pack carrier** — the one carrier question MD-18 left + open, now with a second corpus (the origin Pack) as evidence. Requires its ruling first + (MD-candidate, three-part test); then the carrier lands spec-first: the S0-authored intent + Spec enriched to `ready`, parity fixtures against the TS Pack form, duplicate-ID policing, + the one-canonical-surface law extended. +- **The write-surface design session** — PLAN-only by nature: the friction evidence read, the + scoped-intent contract drafted, `spec:consumers.intent-composition` enriched to what the + evidence supports. Build stays behind the second-machine-writer bar exactly as ruled. +- **A validator or extraction improvement the S1 audit surfaces** — the audit is expected to + find at least one place where the engine's behavior and a `ready` Spec's statement disagree; + that drift is repaired spec-first (fix the stale side deliberately, bound point red before + the fix, green after). + +Whatever is selected: the slice runs the full loop — enrich to `ready` on the floor · implement +· `codeAnchor` · executable verification bound and mutation-probed · gate green — and then the +**slimming half**: after the code ships, the Spec's design-time detail is distilled or removed +in place (git holding history), giving `spec:model.enrichment-lifecycle` its first observed +case and its open question a real datum. One slice done whole beats three started. + +## (g) The hygiene pass (small, riding any session's close) + +- Pin the recipe enumeration: one assertion tying the recipe *names* in `AGENTS.md` and the + reading skill to `recipes.md`'s headings, so recipe twelve cannot re-stale the prose. +- Keep the inward friction log: the four-column instrument (anchor / ladder / executable / CLI) + continues in this plan's record for every Spec authored or bound here — the on-ramp skill is + revised from it if any entry recurs twice. +- The records-gate duplication (script vs test discovery logic) stands as accepted; if either + side changes, the other moves in the same commit. +- **The Vitest 2→4 migration** stands recorded as a non-Spec chore: it is eligible as its own + mechanical session (runner migration, gate re-proven green, no engine or corpus change mixed + in), does not gate the phase close, and is never folded silently into another session's + diff. If it runs, its commit says exactly what it is. + +## (h) What this plan does not do + +- **No origin-repo work.** PR #181 is under the owner's review; its fate is a separate decision + and, if resumed, a separate plan. +- **No readiness sweep.** The 35 refusals stand; individual promotions must be argued against + the floor with new evidence, never batched. +- **No speculative engine surface.** The write surface, `bySymbol`, the `observed` producer, + and any new projection wait on their bars; S0 gives them Specs so the *waiting itself* is + graph-visible, which is the whole correction this plan makes. +- **No new query vocabulary, no reader freezes** without the second-caller-plus-hand-rolled-wrong + evidence, unchanged. +- **No inline carrier** — retired at S0 on its measured evidence, unless the session's drafting + overturns the 1/57 reading, in which case the competition round remains the ruled path. +- **No anchor sweep** — the example-kind question is answered by one ruling (§e), never by 51 + per-row anchors. + +## (i) Sequencing, delivery vehicle, and close + +S0 → S1 → S2 in order (S1's audit needs S0's honest backlog; S2's selection needs S1's +deliberate backlog and may be gated on S1's kind ruling); the hygiene pass rides any close. +Each session: one pre-close review sized to its diff; the gate green at every close; +`AGENTS.md`'s status line moves with this plan's status (the records gate enforces the +agreement). + +**Delivery vehicle.** Execution under this plan lands on the branch behind **PR #15** +(`feature/protocol-self-application-phase-6`), expanding that PR from the phase-6 record into +the self-hosted-setup milestone, per the owner's direction. At the phase close the PR title and +body are rewritten to state what it now carries — the phase-6 usage layer *plus* the phase-7 +practice — so the PR's claim and its diff agree. If the owner instead merges #15 first, the +plan continues unchanged on a successor branch; nothing here depends on the choice. + +The phase closes when the milestone of §a stands: the forward intent that was in prose is in +the graph at honest rungs (with refusals named); every backlog and drift row is deliberate — +the example-kind ruling made, the thirteen laws dispositioned, the eight refusals re-argued or +individually overturned; one inward oracle is bound and mutation-probed; **one engine slice has +run the whole loop including the post-implementation slimming**; the friction log is live; and +the corpus stands at 0 findings with nothing promoted that the floor and a fresh human +statement did not earn. The successor question phase 7 hands forward is the one it is designed +to answer with evidence: *does the loop hold as the standing way this repo works — and what +does it demand next?* From da470e3b56c6c482ac4b0a676436aefa20c3fd19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 21:33:44 +0200 Subject: [PATCH 08/24] feat(self-hosting): establish inward practice --- .claude/skills/sdp-agent-surface/SKILL.md | 3 + .claude/skills/sdp-authoring/SKILL.md | 6 +- .gitignore | 5 +- AGENTS.md | 5 +- CONTEXT.md | 8 +- docs/agent-surface/recipes.md | 29 +- docs/concept/DECISIONS.md | 1 + plans/24-inward-turn-self-hosting-practice.md | 494 ++++++------------ specs/carrier/markdown-pack-authoring.sdp.md | 21 + specs/consumers/impact-graph.sdp.md | 20 + .../example-realization-posture.sdp.md | 20 + specs/model/anchors.sdp.md | 1 + specs/model/core-model.sdp.md | 1 + specs/observation/runtime-overlay.sdp.md | 16 + specs/self-hosting.pack.sdp.ts | 5 + .../oracle-target-eligibility.sdp.md | 28 + test/recipes.test.ts | 36 +- test/self-hosting-graph.test.ts | 12 +- test/self-hosting-oracle/carrier.ts | 28 + test/self-hosting-oracle/consumers.ts | 29 + test/self-hosting-oracle/decisions.ts | 30 ++ .../self-hosting-oracle/declared-relations.ts | 10 + test/self-hosting-oracle/index.ts | 2 + test/self-hosting-oracle/model.ts | 4 + test/self-hosting-oracle/observation.ts | 28 + test/self-hosting-oracle/pack-members.ts | 5 + test/self-hosting-oracle/validation.ts | 39 ++ 27 files changed, 525 insertions(+), 361 deletions(-) create mode 100644 specs/carrier/markdown-pack-authoring.sdp.md create mode 100644 specs/consumers/impact-graph.sdp.md create mode 100644 specs/decisions/example-realization-posture.sdp.md create mode 100644 specs/observation/runtime-overlay.sdp.md create mode 100644 specs/validation/oracle-target-eligibility.sdp.md create mode 100644 test/self-hosting-oracle/observation.ts diff --git a/.claude/skills/sdp-agent-surface/SKILL.md b/.claude/skills/sdp-agent-surface/SKILL.md index 74a3cc8..cb363db 100644 --- a/.claude/skills/sdp-agent-surface/SKILL.md +++ b/.claude/skills/sdp-agent-surface/SKILL.md @@ -92,6 +92,9 @@ read the carrying Spec. Pass, fail, skip, and quarantine are CI's. - **Do not read `implemented` as "it is live."** It says a code anchor binds to the Spec. Runtime evidence would be `observed`, which is not tracked. +- **Do not use raw `ready ∧ ¬implemented` as the operational backlog.** Under the example realization + posture it also includes ready example evidence; recipe 1 excludes examples and reports their + verifier health without inventing inherited implementation. - **Do not collapse the claim taxonomy.** `declared` is authored intent, `anchored` is a human binding from source, `inferred` is machine-derived structure. Carry the claim into your answer. - **Do not treat stated readiness as derived readiness.** `statedReadiness` is the author's diff --git a/.claude/skills/sdp-authoring/SKILL.md b/.claude/skills/sdp-authoring/SKILL.md index 6ed683a..e8ef3c0 100644 --- a/.claude/skills/sdp-authoring/SKILL.md +++ b/.claude/skills/sdp-authoring/SKILL.md @@ -13,7 +13,7 @@ session with the build-backlog and drift-alarm recipes. In the Protocol reposito At this repository root, use the exact self-hosting exclusions: ```sh -node ./dist/cli/sdp.js q 'return g.specs().filter((spec) => spec.statedReadiness === "ready" && !spec.deliveryFacts.includes("implemented")).map((spec) => spec.id)' --exclude explorations --exclude examples --exclude test/fixtures/import/parity +node ./dist/cli/sdp.js q 'const ready = g.specs().filter((spec) => spec.statedReadiness === "ready"); const backlog = ready.filter((spec) => spec.specKind !== "example" && !spec.deliveryFacts.includes("implemented")); const excludedExamples = ready.filter((spec) => spec.specKind === "example" && !spec.deliveryFacts.includes("implemented")); return {backlog: backlog.map((spec) => spec.id), excludedReadyExamples: excludedExamples.length, excludedWithoutVerifier: excludedExamples.filter((spec) => !spec.deliveryFacts.includes("has-verifier")).map((spec) => spec.id)}' --exclude explorations --exclude examples --exclude test/fixtures/import/parity node ./dist/cli/sdp.js q 'return g.specs().filter((spec) => spec.deliveryFacts.includes("implemented") && spec.statedReadiness !== "ready").map((spec) => spec.id)' --exclude explorations --exclude examples --exclude test/fixtures/import/parity ``` @@ -63,6 +63,10 @@ unrelated binary with that name. Adopters should use their chosen package runner The graph can report a resolving `specTest` binding. It cannot detect a generated contract that no suite binds because `bindExample` call sites are not extracted graph data. +Ready examples normally carry verification evidence rather than build-backlog work. The canonical +backlog recipe excludes them while reporting their count and any missing verifier binding; it does +not infer `implemented` through their parent. + ## Bind implementation and review Add a `codeAnchor` beside the code that realizes the Spec, following diff --git a/.gitignore b/.gitignore index 3fb93b2..eb0c29e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # User's scratchpad .tmp-scratch/ +# User-local editor state +.vscode/ + # Tooling outputs node_modules/ dist/ @@ -15,4 +18,4 @@ generated/ .omo/ # Pi state (used by taskplane) -.pi/ \ No newline at end of file +.pi/ diff --git a/AGENTS.md b/AGENTS.md index 43fd811..710327b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ claims; **`src/` and tests** are authoritative evidence of current realization. > machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; > plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier > rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** DRAFTED — plan 24 is DRAFTED (the inward turn: self-hosting as the standing practice — the backlog becomes Specs, the binding layer completes honestly, the first inward oracle binds, and one engine slice runs the whole spec-first loop including post-implementation slimming). The prior status — plan 23 is EXECUTED; phase-6 implementation complete, final clean-copy audit passed — remains historic context. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package; the Protocol corpus stands at `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. The origin project adopted the exact package snapshot, migrated its authored corpus and surviving verifier bindings, and retired Architect behind a 1,627-entry disposition manifest plus a widened recurrence guard; its atemporal graph derives 57 Specs · 1 Pack · 11 anchors → 69 nodes · 105 edges with all four readiness rungs and zero findings. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in +> as import source and a lawful per-ID option. · **what now:** EXECUTING — plan 24 is EXECUTING revision 3, the inward turn: self-hosting as standing practice, with the example realization posture ratified, forward intent entering the graph, the binding layer audited honestly, two inward oracles, and one kind-neutral engine slice running the full spec-first loop through slimming. Settled phase-6 ground is recoverable at commit `b6f123c` and PR #15; the origin working-copy changes remain reverted and survive only on PR libar-ai/convex-event-sourcing#181. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package. The execution baseline was `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in > **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when @@ -113,7 +113,8 @@ Every doc honours both — never mistake one half for the other: typed `Spec`* until it is implementable. Implementation becomes near-autopilot ("implement `spec:…`"); the real, iterative work is maturing and **reviewing** specs — alone and in related sets (the `Pack` / **Design Review**). - **Maturity gates implementation; the graph is AI-native.** Don't ship code before a spec is `ready` — - `implemented ∧ ¬ready` is the **drift alarm**, `ready ∧ ¬implemented` the build backlog. A typed graph of related + `implemented ∧ ¬ready` is the **drift alarm**; `ready ∧ kind≠example ∧ ¬implemented` is the operational build + backlog under the example realization posture (MD-24). A typed graph of related specs is the shape an LLM already reasons in: feed the agent the graph, don't narrate at it. ## Working discipline diff --git a/CONTEXT.md b/CONTEXT.md index dc3fbeb..f95359f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -86,13 +86,15 @@ claim category** — it **inherits** its source's `claim` (so `belongsTo` carrie | Term | Definition | Aliases to avoid | |---|---|---| | **delivery fact** | a derived truth about a `Spec`'s *realization*, computed from edges, shown as a badge — **never authored** (authoring one is an honesty violation) | a readiness rung | -| **`implemented`** | ≥1 `satisfies` edge resolves to the Spec — code **claims** to realise it, *not* that it works or is live | — | +| **`implemented`** | ≥1 `satisfies` edge resolves directly to the Spec — code **claims** to realise it, *not* that it works or is live; the fact never propagates through refinement | — | | **`has-verifier`** | ≥1 `verifies` edge from an **enabled verifier** resolves to the Spec — a verifier *exists*, *not* that it passed | — | | **`observed`** *(aspirational)* | runtime evidence links to the Spec's target — the liveness rung | — | | **enabled verifier** | a verifying `example`/scenario backed by a **linked, resolvable test anchor** — *structurally bound*, not runner-executed (skip/quarantine is CI's, exactly as pass/fail is) | — | -The payoff queries: `ready ∧ ¬implemented` = the **build backlog**; `implemented ∧ ¬ready` = the **drift -alarm**. +The payoff queries: `ready ∧ kind≠example ∧ ¬implemented` = the operational **build backlog**; +`implemented ∧ ¬ready` = the **drift alarm**. The raw `ready ∧ ¬implemented` expression also names +ready example evidence; the example realization posture (MD-24) deliberately keeps that literal +fact while the canonical recipe excludes examples and audits their verifier bindings. ## The graph & extraction (→ `spec:extraction.derive-graph`) diff --git a/docs/agent-surface/recipes.md b/docs/agent-surface/recipes.md index bb269af..915bae2 100644 --- a/docs/agent-surface/recipes.md +++ b/docs/agent-surface/recipes.md @@ -62,13 +62,18 @@ for a new query verb. A join freezes into the reader only when a second machine ## 1. The build backlog -*When you need this: you are picking up work and want the Specs whose design is finished and whose -code is not — `ready ∧ ¬implemented`.* +*When you need this: you are picking up work and want the non-example Specs whose design is +finished and whose code is not — `ready ∧ kind≠example ∧ ¬implemented` — while keeping ready +example evidence visible as an audited exclusion.* ```js -const backlog = g - .specs() - .filter((spec) => spec.statedReadiness === "ready" && !spec.deliveryFacts.includes("implemented")); +const ready = g.specs().filter((spec) => spec.statedReadiness === "ready"); +const backlog = ready.filter( + (spec) => spec.specKind !== "example" && !spec.deliveryFacts.includes("implemented"), +); +const excludedExamples = ready.filter( + (spec) => spec.specKind === "example" && !spec.deliveryFacts.includes("implemented"), +); const byFamily = {}; for (const spec of backlog) { @@ -82,11 +87,21 @@ for (const spec of backlog) { }); } -return { total: backlog.length, byFamily }; +return { + total: backlog.length, + byFamily, + excludedReadyExamples: excludedExamples.length, + excludedWithoutVerifier: excludedExamples + .filter((spec) => !spec.deliveryFacts.includes("has-verifier")) + .map((spec) => spec.id), +}; ``` `implemented` is a delivery fact: it says a code anchor *binds* to the Spec, never that the code -works or is live. A `ready` Spec missing it is the backlog; the reverse pairing is recipe 2. +works or is live. It never propagates through refinement. Ready examples normally carry +verification evidence rather than implementation work, so the example realization posture +(MD-24) keeps the raw `ready ∧ ¬implemented` expression literally true while this operational +recipe excludes examples and audits their verifier bindings. The reverse pairing is recipe 2. ## 2. The drift alarm diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 7087976..e24dbf1 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -32,6 +32,7 @@ positions and are never reused. | MD-21 | the envelope-grammar ownership posture | durable | The Protocol owns the envelope contract, not the YAML library. | [Spec](../../specs/decisions/envelope-grammar-posture.sdp.md) (`spec:decisions.envelope-grammar-posture`) | | MD-22 | the agent front door | durable | The CLI's one evaluation sink and the exported reader are two entrances over one seam, deriving the graph in process on every invocation. | [Spec](../../specs/decisions/agent-front-door.sdp.md) (`spec:decisions.agent-front-door`) | | MD-23 | verification posture, not realization | durable | `verification.mode` states intended posture; enabled-verifier realization remains derived. | [Spec](../../specs/decisions/verification-posture-not-realization.sdp.md) (`spec:decisions.verification-posture-not-realization`) | +| MD-24 | the example realization posture | durable | Examples normally carry verification evidence rather than build-backlog work; implementation remains a direct binding-derived fact. | [Spec](../../specs/decisions/example-realization-posture.sdp.md) (`spec:decisions.example-realization-posture`) | ### Current executable decision-spec pointers diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md index a808c17..596f10c 100644 --- a/plans/24-inward-turn-self-hosting-practice.md +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -1,339 +1,159 @@ # Plan 24 — The inward turn: self-hosting as the standing practice -> **Status:** DRAFTED — PLAN-ONLY. No target file is touched under this plan until an execution -> session is designated. The latest ✅ EXECUTED ground is **plan 23** (phase 6: the usage turn — -> the S0 rulings, the records gate, the usage layer W, and the origin adoption; Protocol corpus -> at 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges, `ready: 83 / defined: 36 / -> scoped: 1 / idea: 1`, 0 findings). The origin adoption lives on its own PR -> (libar-ai/convex-event-sourcing#181) awaiting the owner's review; **nothing in this plan -> depends on that decision**, and no session under this plan touches the origin repo. This plan -> proposes **phase 7 as the inward turn**: self-hosting as the *operating mode* — the Protocol's -> own forward work managed as Specs through its own ladder, every engine change delivered -> spec-first with executable verification, and the graph (not plan prose) carrying what is owed. -> This is the original intention of the self-hosting program resumed: **validation and iteration -> on the Protocol, by the Protocol.** -> -> **What revision 2 changed** (2026-07-28, same-day graph re-measurement — every number below -> was re-derived by query, none inherited): (1) §b gains the composition facts that reshape S1 — -> the 64-row backlog is **51 example spaces + 13 laws**, every row already verifier-bound; the -> 8 drift rows are **one cluster** (the dissolved concept-doc Specs) whose floors already derive -> `ready`; all 37 stated/derived divergences are under-claims, zero over-claims. (2) S1 is -> restructured around **one kind-level ruling** — what `implemented` honestly means for an -> example-kind Spec — applied once, instead of 64 per-row decisions; the ceremonial third path -> (51 hand anchors) is named and forbidden. (3) The drift disposition is corrected: the floor is -> already met on all 8 rows, so the work is refusal re-argument, never enrichment. (4) S2 gains -> the example-`implemented` derivation as a candidate slice, contingent on S1's ruling. (5) The -> phase milestone is stated once in §a: the proper self-hosted setup — annotations honest, the -> agent CLI the standing working surface, Specs carried through to executable Specs — enabling -> iteration on the Protocol's design and implementation by the Protocol. (6) The delivery -> vehicle is named (§i): execution expands PR #15. (7) The Vitest 2→4 migration debt recorded -> at the plan-23 close is carried as a named non-Spec chore (§g). The charter, the honesty -> guardrails, and S0 stand as drafted. -> -> **Spec anchors:** [the agent front door](../specs/decisions/agent-front-door.sdp.md) -> (`spec:decisions.agent-front-door`, MD-22) · [the verification-posture ruling](../specs/decisions/verification-posture-not-realization.sdp.md) -> (`spec:decisions.verification-posture-not-realization`, MD-23) · -> [the intent-composition seed](../specs/consumers/intent-composition.sdp.md) -> (`spec:consumers.intent-composition`) · [the enrichment lifecycle](../specs/model/enrichment-lifecycle.sdp.md) -> (`spec:model.enrichment-lifecycle`) · plan 23 §(e) W (the usage layer this plan now *uses*). - -## (a) Why this plan - -Phase 6 built the usage layer and bought the first second-caller evidence, but by charter it was -not an inward phase: the Protocol repo received the tools of self-hosting practice without the -practice itself. The gap, re-measured at revision 2: - -1. **The graph knows what is owed, but nothing works off it.** The build backlog - (`ready ∧ ¬implemented`) stands at **64** Specs and the drift alarm at **8** — both now - queryable by recipe — while the Protocol's actual forward work (the write surface, the - observed rung, the impact graph, the Pack syntax question) still lives in plan prose, - deferral clauses, and `06`/`07` gap rows. The product's headline claim is that the graph is - the working surface; its own repo still works from documents. -2. **The lower ladder holds two tokens, not a practice.** `idea: 1 / scoped: 1` — the first - honest low-rung Specs exist (phase 6, W1), but no session has yet *managed* work through - them: entered at `idea`, enriched on evidence, promoted on the floor, implemented off the - backlog. -3. **The binding layer is one-sided — and the asymmetry has a shape.** `has-verifier` reaches - **84** of 121 Specs; `implemented` reaches **27**. Re-measurement locates the gap precisely: - **all 64 backlog rows already carry verifiers**, and **51 of the 64 are example-kind - Specs** — condition grids whose suites are bound and green while the fact that would call - them "built" is absent. Several belong to parents that *are* implemented - (`spec:consumers.reader` is implemented; its three example spaces sit on the backlog as - "unimplemented" — a claim no honest reading supports). The asymmetry is concentrated in one - kind, which makes most of the audit a single semantics ruling, not 64 binding decisions. The - defect is in the derived facts' *input*, not in the derivation. -4. **The oracle machinery has never run on this corpus.** Zero `models` edges in the - self-hosting graph; `specOracle` is exercised only in `examples/checkout-v1`. The - executable half's most distinctive piece has no second caller — and self-hosting *is* the - cheapest second caller available. -5. **Standing deferrals now have their evidence and no disposition.** The inline-carrier - instrument came back **1/57 (1.75%)** — a population that argues for *retiring* the - deferral, not pursuing it. The edit-model has its friction evidence and its `idea`-rung - seed. Neither has been acted on. - -The thesis: **phase 7 makes the Protocol its own first-class user.** Sessions open from the -graph, forward work enters as Specs at honest rungs, engine changes go spec-first through the -executable loop, and the phase is judged by whether the loop *iterated* — authored → enriched → -implemented → bound → verified → slimmed — on real engine work, not by corpus growth. - -**The milestone, stated once.** What the phase must leave standing is the proper self-hosted -setup: **annotations** honest and complete (every backlog and drift row deliberate — bound, -refused, or known-real), the **agent CLI** as the standing working surface (every session opens -from recipes, never from prose), and **Specs carried through to executable Specs** (the oracle -machinery bound inward; at least one engine change delivered spec-first with executable -verification end to end). That setup — not any corpus number — is what enables iteration on the -Protocol's design and implementation *by the Protocol*, and it is the standard §i's close -criteria encode. - -## (b) Ground truth at revision 2 (re-derived by query, not inherited) - -- Corpus: 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges · 0 errors / 0 warnings; - `ready: 83 / defined: 36 / scoped: 1 / idea: 1`. Delivery facts: `implemented` 27 · - `has-verifier` 84 · `models` edges 0. -- **Backlog 64, with composition:** 51 `example` + 11 `rule` + 1 `behavior` - (`spec:carrier.sdp-import`) + 1 `constraint` (`spec:extraction.determinism`). All 64 carry - `has-verifier`. The eleven rules: `spec:consumers.binding-language-views` · - `spec:consumers.derived-readiness-banner` · `spec:consumers.wholesale-view-rewrite` · - `spec:validation.authored-honesty` · `spec:validation.claim-separation` · - `spec:validation.diagnostic-rendering` · `spec:validation.kind-evidence` · - `spec:validation.pack-coherence` · `spec:validation.referential-integrity` · - `spec:validation.verification-linkage` · `spec:validation.warn-level-signals`. Note the - coupling: most of these laws themselves own example spaces on the backlog, so S1's kind - ruling and the 13 law bindings interact (§e). -- **Drift 8, one cluster:** `spec:model.core-model` · `spec:model.spec-sections` · - `spec:model.relations` · `spec:model.pack-aggregate` · `spec:extraction.claim-taxonomy` · - `spec:extraction.regenerability` · `spec:consumers.projections-model` · - `spec:carrier.markdown-authoring` — all dissolved-concept-doc Specs held at `defined`, and - **all eight derive `ready`**: the floor already passes; only the standing refusals hold the - rung. -- **Readiness divergence: 37 Specs, all under-claims** (stated below derived), zero - over-claims — the corpus's conservatism is uniform in the honest direction. -- Assets shipped by phase 6 and now to be *used*: the `sdp-authoring` skill (the full - create → enrich → transition → anchor → state-`ready` workflow), eleven recipes (including - promotion preflight, verifier audit, lower-ladder view), the records gate as a required - check leg, the adopter package contract, MD-23. -- Standing refusals: the 35 `defined` Specs refuse for named reasons (22 decisions · - 7 vocabulary · 3 whole-pipeline · 3 singletons); **they stand** — this plan promotes nothing - by sweep. -- Standing deferrals with state: inline carrier (instrument answered: 1/57 — retirement - candidate); edit-model / write surface (friction evidence + `spec:consumers.intent-composition` - at `idea`); anchored-but-unbound (deferred — `bindExample` is not graph data); `bySymbol` / - impact graph (aspirational, `06` gap row 8a); `observed` rung (aspirational, no producer); - Pack Markdown syntax (unruled — the one carrier question MD-18 left open); `06` five gap - rows · `07` three gap rows (graded out of scope at plan 22 §5c, unchanged since). -- **Carried debt (non-Spec):** the plan-23 close records 8 development-dependency advisories - (3 moderate · 4 high · 1 critical) whose actionable aggregate fix is a **Vitest 2→4 major - migration** — deliberately recorded, not silently folded in. It changes the test runner, not - the shipped runtime surface. Disposition in §g. -- Review soft spots carried from the phase-6 close, for the hygiene pass (§g): the - recipe-count prose in `AGENTS.md`/the reading skill is unguarded (staleness recurs at recipe - twelve); the records-gate discovery logic is duplicated between script and test (accepted, - watch for drift); the document-realization anchor convention is authored discipline with no - validator (by design — record only). - -## (c) Charter — what phase 7 is and is not - -**Is:** the inward practice. Converting the genuine forward intent into low-rung Specs and -managing it there (S0); completing the implementation-binding layer honestly — one kind ruling -plus thirteen law dispositions, not a binding sweep (S1); running the oracle machinery on this -corpus (S1); delivering at least one real engine improvement through the full spec-first -executable loop, selected *from the backlog by recipe at the session start* (S2); -dispositioning the deferrals whose evidence has arrived (S0); and exercising the enrichment -lifecycle's slimming half on at least one implemented Spec so -`spec:model.enrichment-lifecycle`'s question gains its first observed case (S2). - -**Is not:** an origin phase — PR #181 is the owner's decision and no session here touches that -repo (if the owner later wants the adoption resumed, revised, or abandoned, that is its own -plan). Not a readiness sweep — the 35 refusals stand unless new verifiers *naturally* convert -one, in which case the promotion is argued individually against the floor. Not an engine -expansion — no write surface build, no `bySymbol`, no `observed` producer, no MCP; those enter -as `idea`/`scoped` Specs (S0) and *wait on the backlog like everything else*. Not a workflow -layer — the one skill suffices until the inward friction log says otherwise. - -**Honesty guardrails:** a Spec is authored only for genuine intended system truth — three real -low-rung Specs beat eight ceremonial ones, and a refusal to author is recorded with its reason; -an anchor is added only where code genuinely realizes the Spec's truth (a law realized by many -files diffusely gets a named refusal, never a token anchor — and **51 hand anchors on example -spaces would be the same sin at scale**, forbidden by name in §e); no promotion without the -floor and a fresh human statement; every session opens with the build-backlog and drift-alarm -recipes and closes with the gate green. - -## (d) S0 — the backlog becomes Specs; the evidenced deferrals get dispositions - -One session. Two halves. - -**The conversion half.** Survey the standing forward intent (the deferral clauses, the `06`/`07` -gap rows, the phase-6 successor notes) and author the genuine capabilities as Specs entering at -the rung their structure earns — expected `idea`, some `scoped`: - -- **the runtime-observation overlay** (the `observed` delivery fact's producer — designed-in - since `00`; the ladder's third rung has intent and no Spec); -- **the impact graph** (`bySymbol`, the `inferred`-claim producer, the two assist roles of - `06` §2 — dissolving gap row 8a into the Spec if the audit shows the row fully carried); -- **the Pack Markdown carrier** *as intent* (the authoring side of the Pack-syntax question — - the Spec records what a Markdown Pack surface must honor; the *ruling* itself is S2's - candidate slice, and the Spec waits on it); -- **enrich `spec:consumers.intent-composition`** `idea → scoped` only if the friction evidence - genuinely forces an outcome statement — otherwise it stands at `idea` and the write-surface - design session stays a named successor. - -Each candidate passes the guard or is refused by name. The Vitest migration is **not** a Spec -candidate — it is maintenance, not system truth; it stays in §g. The lower-ladder recipe is the -working surface; the session's done-record lists every authored Spec beside every refusal. - -**The disposition half.** Two deferrals have their evidence: - -- **The inline carrier: retire on the measured population.** 1/57 leaf-altitude single-module - candidates is the instrument's answer — the separate-file carrier is not the friction point. - Record the retirement (plan record + the deferral clause re-pointed; a DECISIONS entry only - if drafting shows the three-part test genuinely passes — likely this is evidence-closure, not - a new decision). -- **Anchored-but-unbound: keep deferred, restate the boundary.** `bindExample` sites remain - non-graph data; extracting them is engine surface no current caller demands. The deferral - rides `spec:model.anchors`' clause as-is. - -## (e) S1 — the binding layer completed honestly; the first inward oracle - -One to two sessions, driven entirely by recipes. - -**First, the kind ruling.** 51 of the 64 backlog rows are example-kind Specs, every one -verifier-bound, several under parents already `implemented`. Before any binding, S1 answers -one question: **what does `implemented` honestly mean for an example-kind Spec?** Three -candidate outcomes, exactly one of which is ruled (three-part test applied — this looks like a -genuine decision: hard to reverse, surprising without the re-measurement, a real trade-off): - -1. **refuse by kind** — an example space's realization *is* its parent's realization plus its - bound suite; `implemented` does not apply below the parent. Recorded as a convention on the - kind-evidence law (or `spec:model.anchors`), and the backlog honestly shrinks to the 13 - laws; -2. **derive through the parent** — an example space is `implemented` when its `refines` parent - is; an extraction/validation change, which therefore enters S2 as a spec-first candidate - slice and *waits its turn* — S1 records the ruling, S2 builds it; -3. **bind individually** — rejected on its face as the ceremonial path (§c), admissible only - for a specific example space that genuinely owns a realization site distinct from its - parent's. - -Whichever way it lands, the ruling is made once and applied 51 times; the deliverable is the -*ruling with its reasoning*, not 51 edits. - -**Then, the thirteen laws.** Walk the non-example rows with the verifier-audit and -spec-guarantees recipes. Each gets exactly one of three dispositions, recorded: - -1. **bind** — engine code genuinely realizes it at a nameable location (the eleven validation - and consumer rules are expected mostly here: each names a concrete validator or view - behavior; `spec:extraction.determinism` names the determinism check; - `spec:carrier.sdp-import` names the import command): add the `codeAnchor` (binding-only law - unchanged), converting the row into honest `implemented`; -2. **refuse** — realized diffusely (a law the whole pipeline honors) or by a document: name - the refusal; the document-realization convention (phase 6) applies only where an asserting - suite genuinely exists; -3. **backlog** — genuinely unbuilt: the row stands, now known-real. - -Note the coupling: most of the eleven rules own example spaces of their own, so under outcome -(2) of the kind ruling, binding the laws collapses most of the 64 honestly; under outcome (1) -the backlog shrinks to 13 regardless. Either way the deliverable is not a number target — it is -a backlog whose every row is *deliberate*. - -**The drift rows, corrected framing.** All 8 drift rows (`implemented ∧ ¬ready`) are the -dissolved-concept cluster, and **their floors already derive `ready`** — there is nothing to -enrich. The disposition is a refusal re-argument, per row: either the standing refusal (a -vocabulary/singleton/decision reason) is re-stated against today's corpus and the rung stays -honestly `defined`, or the refusal no longer holds and the Spec is promoted *individually* on -the floor plus a fresh human statement. No batch promotion; "the floor passes" alone never -suffices — that is exactly the content-quality line checks refuse to police, so a human owns -it. - -**The first inward oracle.** Pick one self-hosting parent that owns an example space — the -standing candidates are confirmed live: `spec:validation.duplicate-ids` (one space) and -`spec:extraction.build-pipeline` (one space) — and author its oracle: the `specOracle` anchor -plus the typed `expected(conditions)` function beside the bound suite, mutation-probed (a wrong -expected outcome must redden). This is the oracle machinery's second caller, on the corpus that -can least fake it. If the space's Outcome union makes an oracle vacuous (one outcome class), -record that refusal and pick the other candidate — a vacuous oracle authored for coverage would -be exactly the padding the floor forbids. - -## (f) S2 — the iteration loop, proven on real engine work - -The phase's point. At the session open, run the build-backlog recipe and **select the top -genuine engine slice from the graph** — the plan deliberately does not hardcode the choice, -because working off the graph *is the deliverable*. Standing candidates the backlog is expected -to surface, with their gates: - -- **The example-`implemented` derivation** — *if and only if* S1's kind ruling landed on - outcome (2): the `refines`-parent derivation enters as a Spec (likely on - `spec:extraction.derive-graph`'s family or the kind-evidence law), is enriched to `ready`, - and lands spec-first with the backlog's honest collapse as its observable effect. This is - the natural first candidate because S1 will have already argued its design. -- **The Pack syntax ruling + the Markdown Pack carrier** — the one carrier question MD-18 left - open, now with a second corpus (the origin Pack) as evidence. Requires its ruling first - (MD-candidate, three-part test); then the carrier lands spec-first: the S0-authored intent - Spec enriched to `ready`, parity fixtures against the TS Pack form, duplicate-ID policing, - the one-canonical-surface law extended. -- **The write-surface design session** — PLAN-only by nature: the friction evidence read, the - scoped-intent contract drafted, `spec:consumers.intent-composition` enriched to what the - evidence supports. Build stays behind the second-machine-writer bar exactly as ruled. -- **A validator or extraction improvement the S1 audit surfaces** — the audit is expected to - find at least one place where the engine's behavior and a `ready` Spec's statement disagree; - that drift is repaired spec-first (fix the stale side deliberately, bound point red before - the fix, green after). - -Whatever is selected: the slice runs the full loop — enrich to `ready` on the floor · implement -· `codeAnchor` · executable verification bound and mutation-probed · gate green — and then the -**slimming half**: after the code ships, the Spec's design-time detail is distilled or removed -in place (git holding history), giving `spec:model.enrichment-lifecycle` its first observed -case and its open question a real datum. One slice done whole beats three started. - -## (g) The hygiene pass (small, riding any session's close) - -- Pin the recipe enumeration: one assertion tying the recipe *names* in `AGENTS.md` and the - reading skill to `recipes.md`'s headings, so recipe twelve cannot re-stale the prose. -- Keep the inward friction log: the four-column instrument (anchor / ladder / executable / CLI) - continues in this plan's record for every Spec authored or bound here — the on-ramp skill is - revised from it if any entry recurs twice. -- The records-gate duplication (script vs test discovery logic) stands as accepted; if either - side changes, the other moves in the same commit. -- **The Vitest 2→4 migration** stands recorded as a non-Spec chore: it is eligible as its own - mechanical session (runner migration, gate re-proven green, no engine or corpus change mixed - in), does not gate the phase close, and is never folded silently into another session's - diff. If it runs, its commit says exactly what it is. - -## (h) What this plan does not do - -- **No origin-repo work.** PR #181 is under the owner's review; its fate is a separate decision - and, if resumed, a separate plan. -- **No readiness sweep.** The 35 refusals stand; individual promotions must be argued against - the floor with new evidence, never batched. -- **No speculative engine surface.** The write surface, `bySymbol`, the `observed` producer, - and any new projection wait on their bars; S0 gives them Specs so the *waiting itself* is - graph-visible, which is the whole correction this plan makes. -- **No new query vocabulary, no reader freezes** without the second-caller-plus-hand-rolled-wrong - evidence, unchanged. -- **No inline carrier** — retired at S0 on its measured evidence, unless the session's drafting - overturns the 1/57 reading, in which case the competition round remains the ruled path. -- **No anchor sweep** — the example-kind question is answered by one ruling (§e), never by 51 - per-row anchors. - -## (i) Sequencing, delivery vehicle, and close - -S0 → S1 → S2 in order (S1's audit needs S0's honest backlog; S2's selection needs S1's -deliberate backlog and may be gated on S1's kind ruling); the hygiene pass rides any close. -Each session: one pre-close review sized to its diff; the gate green at every close; -`AGENTS.md`'s status line moves with this plan's status (the records gate enforces the -agreement). - -**Delivery vehicle.** Execution under this plan lands on the branch behind **PR #15** -(`feature/protocol-self-application-phase-6`), expanding that PR from the phase-6 record into -the self-hosted-setup milestone, per the owner's direction. At the phase close the PR title and -body are rewritten to state what it now carries — the phase-6 usage layer *plus* the phase-7 -practice — so the PR's claim and its diff agree. If the owner instead merges #15 first, the -plan continues unchanged on a successor branch; nothing here depends on the choice. - -The phase closes when the milestone of §a stands: the forward intent that was in prose is in -the graph at honest rungs (with refusals named); every backlog and drift row is deliberate — -the example-kind ruling made, the thirteen laws dispositioned, the eight refusals re-argued or -individually overturned; one inward oracle is bound and mutation-probed; **one engine slice has -run the whole loop including the post-implementation slimming**; the friction log is live; and -the corpus stands at 0 findings with nothing promoted that the floor and a fresh human -statement did not earn. The successor question phase 7 hands forward is the one it is designed -to answer with evidence: *does the loop hold as the standing way this repo works — and what -does it demand next?* +> **Status:** EXECUTING — revision 3. Phase 7 is the inward turn: the Protocol's own forward +> work is carried as Specs, sessions open from graph recipes, implementation bindings are +> audited rather than assumed, expected-outcome oracles bind inward, and one real engine slice +> runs the complete spec-first loop through post-implementation slimming. Settled phase-6 +> ground is recoverable at commit `b6f123c` and on PR #15; the reverted origin working-copy +> changes survive only on `libar-ai/convex-event-sourcing#181`. This plan never touches that +> origin checkout. + +## Revision-3 execution ruling + +The owner explicitly requested execution of revision 3 on 2026-07-28. Before changing the +backlog recipe, the session rebuilt the CLI and re-ran the canonical graph recipes: + +- raw backlog: 64 = 51 examples + 13 non-example laws; all 64 have verifier evidence; +- drift alarm: 8; every row states `defined`, reaches the `ready` floor, and has a direct + implementation binding; +- corpus before edits: 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges, zero findings. + +### The example realization posture (MD-24) + +The proposal passes the three-part test on the fresh measurement and owner statement: + +1. **Hard to reverse:** deriving implementation through refinement would change the meaning and + claim of a delivery fact across every extractor, consumer, and adopter query. +2. **Surprising without context:** the literal `ready ∧ ¬implemented` set contains 51 executable + example points even though their suites are bound and their parents own the implementation. +3. **Real trade-off:** direct anchor-derived facts preserve the epistemic boundary but make the + unqualified raw query operationally misleading; parent propagation would make the convenient + query smaller by asserting inferred realization no source bound. + +**Ruling:** ready examples normally carry verification evidence rather than backlog work. +`implemented` remains direct and anchor-derived, never inherited through refinement. Recipe 1 is +the canonical operational backlog: `ready ∧ kind≠example ∧ ¬implemented`; it also reports the +excluded ready-example count and any excluded example lacking verifier evidence. A rare example +that genuinely owns a distinct realization may still carry its own direct code anchor. + +### Oracle choices + +`spec:validation.duplicate-ids` is eligible under the current behavior-only oracle law, but its +one bound point fixes one literal finding and one literal absence. An oracle there would be a +constant repetition of the bound test, so revision 3 records that vacuity refusal. +`spec:consumers.reader` instead has three existing witness points with three distinct result +variants; it is the non-vacuous first inward oracle. + +The phase's engine slice was selected during revision-3 planning, not at execution theater: +`spec:validation.oracle-target-eligibility`. Eleven of the thirteen non-example backlog laws are +rule Specs, most owning example spaces, while the shared fail-closed predicate admits behavior +targets only. S2 reruns the lower-ladder and backlog recipes to confirm that no newer prerequisite +outranks the slice; it does not pretend to choose an already planted winner. + +## Charter and sequence + +### S0 — standing intent and working-surface semantics + +- Ratify MD-24 and update recipe 1, the glossary payoff query, skills, tests, and adopter-facing + guidance without changing the graph schema or reader API. +- Author the runtime-observation overlay under the deliberate `observation` domain, the impact + graph, Markdown Pack authoring, and oracle-target eligibility at the rungs their structures + earn. Pack syntax remains unruled and unimplemented. +- Keep `spec:consumers.intent-composition` at `idea` and + `spec:model.enrichment-lifecycle` at `scoped`. +- Retire the inline-carrier deferral on its measured 1/57 population as evidence closure, not an + ADR. Restate the `bindExample`/`specTest` graph boundary on `spec:model.anchors`. +- Ignore `.vscode/` as user-local state. Preserve and run the existing recipe-name enumeration + pin rather than duplicating it. + +### S1 — deliberate bindings and first inward oracle + +- Audit the 13 non-example backlog laws. Each receives exactly one recorded disposition: + `bind`, `refuse`, or `known-real backlog`. Existing function names are candidates, never + permission for a ceremonial anchor. In particular, bind determinism only if a concrete seam + owns the guarantee; otherwise refuse it as whole-pipeline truth. +- Re-measure and re-argue every one of the eight drift refusals against the current graph and + verifier evidence. No refusal is inherited and no promotion is batched. +- Bind `oracle:protocol.reader-entry-map` to all three generated reader points, assert three + distinct expected variants, and mutation-probe its expected function. + +### S2 — kind-neutral oracle eligibility + +- Mature `spec:validation.oracle-target-eligibility` with an implementing Design section and two + example children: rule-space accepted and missing-space refused. +- Change the shared resolution predicate from behavior-kind eligibility to Spec-plus-example-space + eligibility; preserve every other fail-closed refusal and update diagnostics and reader coverage. +- Bind a self-referential rule-kind oracle over the two points and mutation-probe both predicate + and expected-outcome seams. +- After green implementation, remove only Design detail whose implementation-time purpose is over. + Keep durable behavior and trade-offs, and record the before/after commits as one datum without + promoting the enrichment-lifecycle Spec. + +## Disposition ledgers + +The backlog ledger is filled from current evidence during S1. Zero remaining rows is likely, not +a target. + +| Spec | Candidate seam | Disposition and current evidence | +|---|---|---| +| `spec:carrier.sdp-import` | `runImport` | pending | +| `spec:consumers.binding-language-views` | binding renderers | pending | +| `spec:consumers.derived-readiness-banner` | readiness renderer | pending | +| `spec:consumers.wholesale-view-rewrite` | view/build rewrite boundary | pending | +| `spec:extraction.determinism` | concrete clean-state seam, if any | pending | +| `spec:validation.authored-honesty` | authoring-shape and delivery-fact checks | pending | +| `spec:validation.claim-separation` | claim-separation check | pending | +| `spec:validation.diagnostic-rendering` | CLI and projection renderers | pending | +| `spec:validation.kind-evidence` | kind-evidence table | pending | +| `spec:validation.pack-coherence` | Pack-coherence check | pending | +| `spec:validation.referential-integrity` | referential-integrity check | pending | +| `spec:validation.verification-linkage` | verifier and oracle linkage checks | pending | +| `spec:validation.warn-level-signals` | orphan and gap checks | pending | + +| Drift Spec | Fresh disposition | +|---|---| +| `spec:carrier.markdown-authoring` | pending re-argument | +| `spec:consumers.projections-model` | pending re-argument | +| `spec:extraction.claim-taxonomy` | pending re-argument | +| `spec:extraction.regenerability` | pending re-argument | +| `spec:model.core-model` | pending re-argument | +| `spec:model.pack-aggregate` | pending re-argument | +| `spec:model.relations` | pending re-argument | +| `spec:model.spec-sections` | pending re-argument | + +## Inward friction ledger + +Every affected Spec records all four surfaces; “none observed” is an explicit result. A skill +changes only when the same friction recurs twice. + +| Spec | Anchor / binding | Ladder / readiness | Executable contract | CLI / recipe | +|---|---|---|---|---| +| `spec:decisions.example-realization-posture` | none observed | raw-query trade-off required explicit ruling | none observed | recipe 1 needed example accounting | +| `spec:observation.runtime-overlay` | none observed | blocking producer boundary keeps `idea` honest | none observed | none observed | +| `spec:consumers.impact-graph` | none observed | language-neutral identity question keeps `idea` honest | none observed | none observed | +| `spec:carrier.markdown-pack-authoring` | none observed | unresolved syntax keeps `idea` honest | none observed | none observed | +| `spec:validation.oracle-target-eligibility` | pending S2 | blocking implementation seam supports `scoped` | pending S2 | graph exposed rule-kind demand directly | + +## Verification and close + +Every checkpoint re-derives corpus and delivery-fact counts and runs relevant targeted suites. +Every committed phase boundary receives the complete thirteen-leg `npm run check`, independently +confirmed from a clean worktree. Mutation probes must redden for the named law and be restored +before a tracked gate. + +Close requires: every backlog and drift row deliberate; two inward `models` edges; one genuine +spec-first engine slice slimmed after implementation; the friction ledger complete; zero findings; +no readiness sweep; and plan/AGENTS status synchronized. + +The owner authorized deletion of the superseded plan-23 file after plan 24 lands. Before deletion, +replace this plan's and AGENTS' live plan-23 pointers with carrying Specs, commit evidence, and PR +references; verify no live link depends on the file. Historical evidence remains in git and origin +PR #181. + +Execution expands PR #15. At close its title becomes +`feat: make self-hosting the standing delivery practice (phases 6–7)` and its body names both the +phase-6 usage layer and phase-7 inward evidence. No origin-repo work occurs. + +After the phase closes, Vitest 2→4 runs as a separate mechanical commit. The session inspects the +actual v4 configuration and help before replacing the removed single-fork option, changes no +Specs or graph semantics, re-measures advisories, and stops if derived graph output moves. diff --git a/specs/carrier/markdown-pack-authoring.sdp.md b/specs/carrier/markdown-pack-authoring.sdp.md new file mode 100644 index 0000000..efad202 --- /dev/null +++ b/specs/carrier/markdown-pack-authoring.sdp.md @@ -0,0 +1,21 @@ +--- +id: spec:carrier.markdown-pack-authoring +kind: behavior +altitude: feature +readiness: idea +relations: + refines: spec:model.pack-aggregate + dependsOn: + - spec:carrier.markdown-parser + - spec:decisions.carrier-ruling +--- +# Packs may gain a Markdown authoring carrier + +## Intent +- outcome: Let a Markdown Pack manifest carry the same grouping identity, framing, membership, and model references as the TypeScript form. + +### Open questions +- [blocking] What Pack-specific Markdown syntax preserves one canonical surface per Pack without pretending the Spec carrier ruling already chose it? + +## Behavior +- rule: A future Markdown Pack carrier must preserve parity with the Pack aggregate and duplicate-ID laws before it can replace the TypeScript default. diff --git a/specs/consumers/impact-graph.sdp.md b/specs/consumers/impact-graph.sdp.md new file mode 100644 index 0000000..f38f221 --- /dev/null +++ b/specs/consumers/impact-graph.sdp.md @@ -0,0 +1,20 @@ +--- +id: spec:consumers.impact-graph +kind: behavior +altitude: feature +readiness: idea +relations: + refines: spec:consumers.projections-model + dependsOn: spec:extraction.derive-graph +--- +# An impact graph can answer exhaustive code-structure questions + +## Intent +- outcome: Give symbol and import impact questions an exhaustive derived substrate without promoting mechanical structure into curated intent. + +### Open questions +- [blocking] Which language-neutral identity and extraction boundary can support exhaustive symbol reach without freezing a single compiler's representation into the Protocol? + +## Behavior +- rule: Mechanical import and symbol structure is inferred and remains distinct from the sparse curated graph. +- rule: Candidate relationship suggestions and unambiguous-drift flags are assistive outputs; neither authors intent or silently rewrites the curated graph. diff --git a/specs/decisions/example-realization-posture.sdp.md b/specs/decisions/example-realization-posture.sdp.md new file mode 100644 index 0000000..2bac8b1 --- /dev/null +++ b/specs/decisions/example-realization-posture.sdp.md @@ -0,0 +1,20 @@ +--- +id: spec:decisions.example-realization-posture +kind: decision +altitude: feature +readiness: defined +relations: + refines: spec:model.core-model +--- +# Example realization stays evidence, not backlog work + +## Intent +- outcome: Keep implementation bindings direct while making the operational build backlog name work that can own a realization. + +## Decision +- context: The raw `ready ∧ ¬implemented` query includes every ready example whose bound suite verifies its parent even though the example usually owns no implementation site distinct from that parent. +- decision: Ready example Specs are verification evidence and are excluded from the canonical build-backlog recipe; `implemented` remains a direct, anchor-derived delivery fact with no propagation through refinement. +- rationale: Deriving an example's implementation through its parent would introduce an inferred realization claim that no source binding asserted, while adding one anchor per example would turn evidence points into ceremonial implementation sites. Keeping the fact direct preserves the claim boundary and leaves a rare example that genuinely owns a distinct realization free to carry its own code anchor. +- consequence: The unqualified raw `ready ∧ ¬implemented` expression remains literally true but is not the operational backlog definition because it includes example evidence. +- consequence: The canonical backlog recipe and adopter guidance filter out examples and report both the excluded count and any excluded ready example missing verifier evidence. +- consequence: Consumers that hand-roll the raw expression must opt into the example posture explicitly rather than assuming refinement confers implementation. diff --git a/specs/model/anchors.sdp.md b/specs/model/anchors.sdp.md index 6dc5469..571d9f8 100644 --- a/specs/model/anchors.sdp.md +++ b/specs/model/anchors.sdp.md @@ -17,6 +17,7 @@ relations: - **code anchor** — An implementation-flavored binding that derives an anchored satisfies edge. - **test anchor** — A binding that derives an anchored verifies edge from a test to its target Spec. - **oracle anchor** — A binding that records an oracle's models target without deriving a delivery fact. +- **executable binding boundary** — A resolving `specTest` anchor can establish verifier realization; a `bindExample` call executes a generated contract but is not extracted graph data, so the graph cannot claim from that call alone that the contract is bound. - **document-realization binding** — When the realizing artifact is authored Markdown that cannot carry an extracted in-code anchor, the executable suite that asserts the shipped document may carry its code anchor. Its label must name the document realization rather than imply the test body is the product, and file-level blast radius remains coverage-unknown for the Markdown artifact. - **anchor-constant form** — The top-level const builder call that the MVP extractor reifies; decorator and JSDoc forms remain unextracted representations. - **Protocol builder binding** — A builder import from the public Protocol package, or a relative import whose importer-relative resolution — including the TypeScript `.js`-to-`.ts` convention — canonicalizes to this package's `ids` or `model/code-anchor` module; consumer-local lookalike modules confer no binding authority. On the CommonJS package surface the trusted relative-module set is empty (`import.meta.url` is rewritten away), so relative bindings mint no anchors there while package imports stay trusted. diff --git a/specs/model/core-model.sdp.md b/specs/model/core-model.sdp.md index 88e17b5..8377822 100644 --- a/specs/model/core-model.sdp.md +++ b/specs/model/core-model.sdp.md @@ -19,3 +19,4 @@ relations: - **altitude** — The scope position `epic`, `feature`, or `story`. - **readiness** — The author-stated design-maturity position `idea`, `scoped`, `defined`, or `ready`, checked against a structural floor. - **delivery fact** — A derived realization signal such as implemented or has-verifier; it is never authored readiness. +- **direct realization** — `implemented` follows a resolving implementation binding and never propagates through refinement; examples normally provide verification evidence rather than implementation work. diff --git a/specs/observation/runtime-overlay.sdp.md b/specs/observation/runtime-overlay.sdp.md new file mode 100644 index 0000000..4da3446 --- /dev/null +++ b/specs/observation/runtime-overlay.sdp.md @@ -0,0 +1,16 @@ +--- +id: spec:observation.runtime-overlay +kind: behavior +altitude: feature +readiness: idea +relations: + refines: spec:protocol.self-hosting + dependsOn: spec:model.core-model +--- +# Runtime observations can close the liveness loop + +## Intent +- outcome: Associate runtime evidence with measurable Spec targets without turning operational payloads into authored model truth. + +### Open questions +- [blocking] Which external observation identity and freshness boundary is small enough for the graph while still supporting an honest `observed` delivery fact? diff --git a/specs/self-hosting.pack.sdp.ts b/specs/self-hosting.pack.sdp.ts index d4c3a03..22759e5 100644 --- a/specs/self-hosting.pack.sdp.ts +++ b/specs/self-hosting.pack.sdp.ts @@ -11,7 +11,9 @@ export const selfHostingV1Pack = pack({ ref("spec:carrier.sdp-import"), ref("spec:carrier.sdp-import.round-trip"), ref("spec:carrier.prose-ownership-rule"), + ref("spec:carrier.markdown-pack-authoring"), ref("spec:protocol.self-hosting"), + ref("spec:observation.runtime-overlay"), ref("spec:extraction.derive-graph"), ref("spec:extraction.determinism"), ref("spec:extraction.build-pipeline"), @@ -27,10 +29,12 @@ export const selfHostingV1Pack = pack({ ref("spec:validation.referential-integrity"), ref("spec:validation.claim-separation"), ref("spec:validation.verification-linkage"), + ref("spec:validation.oracle-target-eligibility"), ref("spec:validation.pack-coherence"), ref("spec:validation.authored-honesty"), ref("spec:validation.warn-level-signals"), ref("spec:consumers.projections-model"), + ref("spec:consumers.impact-graph"), ref("spec:consumers.agent-surface"), ref("spec:consumers.design-review"), ref("spec:consumers.reader"), @@ -126,6 +130,7 @@ export const selfHostingV1Pack = pack({ ref("spec:decisions.mcp-deferred"), ref("spec:decisions.agent-front-door"), ref("spec:decisions.verification-posture-not-realization"), + ref("spec:decisions.example-realization-posture"), ], modelRefs: [ref("spec:model.protocol-domain"), ref("spec:model.core-model")], }); diff --git a/specs/validation/oracle-target-eligibility.sdp.md b/specs/validation/oracle-target-eligibility.sdp.md new file mode 100644 index 0000000..faa5c17 --- /dev/null +++ b/specs/validation/oracle-target-eligibility.sdp.md @@ -0,0 +1,28 @@ +--- +id: spec:validation.oracle-target-eligibility +kind: rule +altitude: story +readiness: scoped +relations: + refines: spec:validation.verification-linkage + dependsOn: spec:model.anchors +--- +# Oracle eligibility follows example-space ownership + +## Intent +- outcome: Let an expected-outcome oracle model any Spec whose own law defines an example space, regardless of Spec kind. + +### Open questions +- [blocking] Which shared resolution predicate and diagnostic seam must change so validator and reader consumers stay fail-closed together? + +## Rule +- Oracle target eligibility follows ownership of an example space, not a behavior-kind check. + +## Example space +```gwt-vocabulary +Given the oracle targets a {targetKind:"behavior"|"rule"} spec +Given the target owns an example space: {ownsExampleSpace:boolean} +When oracle linkage is resolved +Then the report contains {findingCount:number} oracle-linkage findings +Then a resolving oracle is present: {oraclePresent:boolean} +``` diff --git a/test/recipes.test.ts b/test/recipes.test.ts index 96a0087..4823470 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -260,23 +260,51 @@ describe("the agent-surface recipe corpus", () => { } }); - it("returns a build backlog of stated-ready specs no code binds", async () => { + it("returns the non-example build backlog and audits excluded example evidence", async () => { const result = asRecord(await runRecipe(recipeByOrdinal(1))); const byFamily = asRecord(result.byFamily); const entries = Object.values(byFamily).flatMap((family) => asArray(family)); expect(numberAt(result, "total")).toBe(entries.length); - // Completeness, not just soundness: the rows must be exactly the graph's `ready ∧ ¬implemented` - // set, so a body that under-reports — an empty backlog over a corpus that has one — reddens. + // Completeness, not just soundness: the rows must be exactly the operational + // `ready ∧ kind≠example ∧ ¬implemented` set. The excluded example set is checked separately, + // so filtering it from backlog work cannot hide missing verification evidence. const expected = [...primitivesById.values()] .filter( - (node) => node.readiness === "ready" && !(node.deliveryFacts ?? []).includes("implemented"), + (node) => + node.readiness === "ready" && + node.specKind !== "example" && + !(node.deliveryFacts ?? []).includes("implemented"), ) .map((node) => node.id) .sort(); expect(entries.map((entry) => stringAt(asRecord(entry), "id")).sort()).toEqual(expected); + + const excluded = [...primitivesById.values()].filter( + (node) => + node.readiness === "ready" && + node.specKind === "example" && + !(node.deliveryFacts ?? []).includes("implemented"), + ); + const excludedWithoutVerifier = excluded + .filter((node) => !(node.deliveryFacts ?? []).includes("has-verifier")) + .map((node) => node.id) + .sort(); + + expect(numberAt(result, "excludedReadyExamples")).toBe(excluded.length); + expect( + asArray(result.excludedWithoutVerifier) + .map((id) => { + if (typeof id !== "string") { + throw new Error(`expected an excluded example id, got ${JSON.stringify(id)}`); + } + + return id; + }) + .sort(), + ).toEqual(excludedWithoutVerifier); }); it("returns a drift alarm of code-bound specs below ready, with the floor named", async () => { diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index 3eb8254..00218ad 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -90,12 +90,12 @@ describe("the self-hosting corpus", () => { // The literals are the corpus checkpoint. The authored arrays are measured against the same // literals rather than standing in for them, so a transcription slip in an oracle module // cannot certify itself by moving both sides of a comparison at once. - expect(result.counts).toEqual({ specs: 121, packs: 1, anchors: 91 }); - expect(expectedSpecs).toHaveLength(121); - expect(expectedPackMembers).toHaveLength(121); + expect(result.counts).toEqual({ specs: 126, packs: 1, anchors: 91 }); + expect(expectedSpecs).toHaveLength(126); + expect(expectedPackMembers).toHaveLength(126); expect(expectedAnchors).toHaveLength(91); - expect(result.graph.nodes).toHaveLength(213); - expect(result.graph.edges).toHaveLength(416); + expect(result.graph.nodes).toHaveLength(218); + expect(result.graph.edges).toHaveLength(431); }); it("rosters exactly the authored Spec, Pack, and anchor node ids", () => { @@ -145,7 +145,7 @@ describe("the self-hosting corpus", () => { }), {}, ), - ).toEqual({ defined: 36, idea: 1, ready: 83, scoped: 1 }); + ).toEqual({ defined: 37, idea: 4, ready: 83, scoped: 2 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { diff --git a/test/self-hosting-oracle/carrier.ts b/test/self-hosting-oracle/carrier.ts index 8526bdc..331b312 100644 --- a/test/self-hosting-oracle/carrier.ts +++ b/test/self-hosting-oracle/carrier.ts @@ -48,6 +48,34 @@ export const carrierSpecs = [ }, deliveryFacts: ["implemented", "has-verifier"], }, + { + id: "spec:carrier.markdown-pack-authoring", + specKind: "behavior", + altitude: "feature", + readiness: "idea", + file: "specs/carrier/markdown-pack-authoring.sdp.md", + title: "Packs may gain a Markdown authoring carrier", + narrative: null, + sections: { + intent: { + outcome: + "Let a Markdown Pack manifest carry the same grouping identity, framing, membership, and model references as the TypeScript form.", + openQuestions: [ + { + question: + "What Pack-specific Markdown syntax preserves one canonical surface per Pack without pretending the Spec carrier ruling already chose it?", + blocking: true, + }, + ], + }, + behavior: { + rules: [ + "A future Markdown Pack carrier must preserve parity with the Pack aggregate and duplicate-ID laws before it can replace the TypeScript default.", + ], + }, + }, + deliveryFacts: [], + }, { id: "spec:carrier.markdown-parser", specKind: "behavior", diff --git a/test/self-hosting-oracle/consumers.ts b/test/self-hosting-oracle/consumers.ts index df8a653..ec5e81b 100644 --- a/test/self-hosting-oracle/consumers.ts +++ b/test/self-hosting-oracle/consumers.ts @@ -836,6 +836,35 @@ export const consumersSpecs = [ }, deliveryFacts: ["implemented", "has-verifier"], }, + { + id: "spec:consumers.impact-graph", + specKind: "behavior", + altitude: "feature", + readiness: "idea", + file: "specs/consumers/impact-graph.sdp.md", + title: "An impact graph can answer exhaustive code-structure questions", + narrative: null, + sections: { + intent: { + outcome: + "Give symbol and import impact questions an exhaustive derived substrate without promoting mechanical structure into curated intent.", + openQuestions: [ + { + question: + "Which language-neutral identity and extraction boundary can support exhaustive symbol reach without freezing a single compiler's representation into the Protocol?", + blocking: true, + }, + ], + }, + behavior: { + rules: [ + "Mechanical import and symbol structure is inferred and remains distinct from the sparse curated graph.", + "Candidate relationship suggestions and unambiguous-drift flags are assistive outputs; neither authors intent or silently rewrites the curated graph.", + ], + }, + }, + deliveryFacts: [], + }, { id: "spec:consumers.intent-composition", specKind: "behavior", diff --git a/test/self-hosting-oracle/decisions.ts b/test/self-hosting-oracle/decisions.ts index 37e7bfc..94f8521 100644 --- a/test/self-hosting-oracle/decisions.ts +++ b/test/self-hosting-oracle/decisions.ts @@ -619,4 +619,34 @@ export const decisionsSpecs = [ }, deliveryFacts: [], }, + { + id: "spec:decisions.example-realization-posture", + specKind: "decision", + altitude: "feature", + readiness: "defined", + file: "specs/decisions/example-realization-posture.sdp.md", + title: "Example realization stays evidence, not backlog work", + narrative: null, + sections: { + intent: { + outcome: + "Keep implementation bindings direct while making the operational build backlog name work that can own a realization.", + }, + decision: { + context: + "The raw `ready ∧ ¬implemented` query includes every ready example whose bound suite verifies its parent even though the example usually owns no implementation site distinct from that parent.", + decision: + "Ready example Specs are verification evidence and are excluded from the canonical build-backlog recipe; `implemented` remains a direct, anchor-derived delivery fact with no propagation through refinement.", + rationale: [ + "Deriving an example's implementation through its parent would introduce an inferred realization claim that no source binding asserted, while adding one anchor per example would turn evidence points into ceremonial implementation sites. Keeping the fact direct preserves the claim boundary and leaves a rare example that genuinely owns a distinct realization free to carry its own code anchor.", + ], + consequences: [ + "The unqualified raw `ready ∧ ¬implemented` expression remains literally true but is not the operational backlog definition because it includes example evidence.", + "The canonical backlog recipe and adopter guidance filter out examples and report both the excluded count and any excluded ready example missing verifier evidence.", + "Consumers that hand-roll the raw expression must opt into the example posture explicitly rather than assuming refinement confers implementation.", + ], + }, + }, + deliveryFacts: [], + }, ] as const; diff --git a/test/self-hosting-oracle/declared-relations.ts b/test/self-hosting-oracle/declared-relations.ts index c738e8d..c1b75b8 100644 --- a/test/self-hosting-oracle/declared-relations.ts +++ b/test/self-hosting-oracle/declared-relations.ts @@ -451,4 +451,14 @@ export const expectedDeclaredRelations = [ "spec:validation.diagnostic-rendering", ], ["spec:validation.validator-self-testing", "refines", "spec:validation.two-check-families"], + ["spec:decisions.example-realization-posture", "refines", "spec:model.core-model"], + ["spec:observation.runtime-overlay", "refines", "spec:protocol.self-hosting"], + ["spec:observation.runtime-overlay", "dependsOn", "spec:model.core-model"], + ["spec:consumers.impact-graph", "refines", "spec:consumers.projections-model"], + ["spec:consumers.impact-graph", "dependsOn", "spec:extraction.derive-graph"], + ["spec:carrier.markdown-pack-authoring", "refines", "spec:model.pack-aggregate"], + ["spec:carrier.markdown-pack-authoring", "dependsOn", "spec:carrier.markdown-parser"], + ["spec:carrier.markdown-pack-authoring", "dependsOn", "spec:decisions.carrier-ruling"], + ["spec:validation.oracle-target-eligibility", "refines", "spec:validation.verification-linkage"], + ["spec:validation.oracle-target-eligibility", "dependsOn", "spec:model.anchors"], ] as const; diff --git a/test/self-hosting-oracle/index.ts b/test/self-hosting-oracle/index.ts index 4cb7aa5..04ce314 100644 --- a/test/self-hosting-oracle/index.ts +++ b/test/self-hosting-oracle/index.ts @@ -9,6 +9,7 @@ import { consumersSpecs } from "./consumers.js"; import { decisionsSpecs } from "./decisions.js"; import { extractionSpecs } from "./extraction.js"; import { modelSpecs } from "./model.js"; +import { observationSpecs } from "./observation.js"; import { protocolSpecs } from "./protocol.js"; import { validationSpecs } from "./validation.js"; @@ -39,6 +40,7 @@ export const specFamilies: readonly SpecFamily[] = [ { name: "extraction", prefix: "spec:extraction.", specs: extractionSpecs }, { name: "validation", prefix: "spec:validation.", specs: validationSpecs }, { name: "model", prefix: "spec:model.", specs: modelSpecs }, + { name: "observation", prefix: "spec:observation.", specs: observationSpecs }, { name: "consumers", prefix: "spec:consumers.", specs: consumersSpecs }, { name: "decisions", prefix: "spec:decisions.", specs: decisionsSpecs }, ]; diff --git a/test/self-hosting-oracle/model.ts b/test/self-hosting-oracle/model.ts index 36a395c..9a82bf9 100644 --- a/test/self-hosting-oracle/model.ts +++ b/test/self-hosting-oracle/model.ts @@ -43,6 +43,8 @@ export const modelSpecs = [ altitude: "The scope position `epic`, `feature`, or `story`.", "delivery fact": "A derived realization signal such as implemented or has-verifier; it is never authored readiness.", + "direct realization": + "`implemented` follows a resolving implementation binding and never propagates through refinement; examples normally provide verification evidence rather than implementation work.", envelope: "The stable outer shape of id, title, kind, altitude, readiness, and relations; sections carry extension detail.", kind: "The true subtype that categorizes a Spec's truth and changes its required detail and validation.", @@ -299,6 +301,8 @@ export const modelSpecs = [ "An implementation-flavored binding that derives an anchored satisfies edge.", "document-realization binding": "When the realizing artifact is authored Markdown that cannot carry an extracted in-code anchor, the executable suite that asserts the shipped document may carry its code anchor. Its label must name the document realization rather than imply the test body is the product, and file-level blast radius remains coverage-unknown for the Markdown artifact.", + "executable binding boundary": + "A resolving `specTest` anchor can establish verifier realization; a `bindExample` call executes a generated contract but is not extracted graph data, so the graph cannot claim from that call alone that the contract is bound.", "oracle anchor": "A binding that records an oracle's models target without deriving a delivery fact.", "test anchor": diff --git a/test/self-hosting-oracle/observation.ts b/test/self-hosting-oracle/observation.ts new file mode 100644 index 0000000..1b90445 --- /dev/null +++ b/test/self-hosting-oracle/observation.ts @@ -0,0 +1,28 @@ +// The authored descriptors of the `observation` family of the self-hosting corpus — +// human transcription of intended truth, never computed from the derived graph. + +export const observationSpecs = [ + { + id: "spec:observation.runtime-overlay", + specKind: "behavior", + altitude: "feature", + readiness: "idea", + file: "specs/observation/runtime-overlay.sdp.md", + title: "Runtime observations can close the liveness loop", + narrative: null, + sections: { + intent: { + outcome: + "Associate runtime evidence with measurable Spec targets without turning operational payloads into authored model truth.", + openQuestions: [ + { + question: + "Which external observation identity and freshness boundary is small enough for the graph while still supporting an honest `observed` delivery fact?", + blocking: true, + }, + ], + }, + }, + deliveryFacts: [], + }, +] as const; diff --git a/test/self-hosting-oracle/pack-members.ts b/test/self-hosting-oracle/pack-members.ts index cfe4372..627fed6 100644 --- a/test/self-hosting-oracle/pack-members.ts +++ b/test/self-hosting-oracle/pack-members.ts @@ -8,7 +8,9 @@ export const expectedPackMembers = [ "spec:carrier.sdp-import", "spec:carrier.sdp-import.round-trip", "spec:carrier.prose-ownership-rule", + "spec:carrier.markdown-pack-authoring", "spec:protocol.self-hosting", + "spec:observation.runtime-overlay", "spec:extraction.derive-graph", "spec:extraction.determinism", "spec:extraction.build-pipeline", @@ -24,10 +26,12 @@ export const expectedPackMembers = [ "spec:validation.referential-integrity", "spec:validation.claim-separation", "spec:validation.verification-linkage", + "spec:validation.oracle-target-eligibility", "spec:validation.pack-coherence", "spec:validation.authored-honesty", "spec:validation.warn-level-signals", "spec:consumers.projections-model", + "spec:consumers.impact-graph", "spec:consumers.agent-surface", "spec:consumers.design-review", "spec:consumers.reader", @@ -123,4 +127,5 @@ export const expectedPackMembers = [ "spec:decisions.mcp-deferred", "spec:decisions.agent-front-door", "spec:decisions.verification-posture-not-realization", + "spec:decisions.example-realization-posture", ] as const; diff --git a/test/self-hosting-oracle/validation.ts b/test/self-hosting-oracle/validation.ts index dff58e1..a4d82c3 100644 --- a/test/self-hosting-oracle/validation.ts +++ b/test/self-hosting-oracle/validation.ts @@ -660,6 +660,45 @@ export const validationSpecs = [ }, deliveryFacts: ["has-verifier"], }, + { + id: "spec:validation.oracle-target-eligibility", + specKind: "rule", + altitude: "story", + readiness: "scoped", + file: "specs/validation/oracle-target-eligibility.sdp.md", + title: "Oracle eligibility follows example-space ownership", + narrative: null, + sections: { + intent: { + outcome: + "Let an expected-outcome oracle model any Spec whose own law defines an example space, regardless of Spec kind.", + openQuestions: [ + { + question: + "Which shared resolution predicate and diagnostic seam must change so validator and reader consumers stay fail-closed together?", + blocking: true, + }, + ], + }, + behavior: { + rules: [ + "Oracle target eligibility follows ownership of an example space, not a behavior-kind check.", + ], + exampleSpace: { + given: [ + 'the oracle targets a {targetKind:"behavior"|"rule"} spec', + "the target owns an example space: {ownsExampleSpace:boolean}", + ], + when: ["oracle linkage is resolved"], + then: [ + "the report contains {findingCount:number} oracle-linkage findings", + "a resolving oracle is present: {oraclePresent:boolean}", + ], + }, + }, + }, + deliveryFacts: [], + }, { id: "spec:validation.pack-coherence", specKind: "rule", From b94d1f0de6fb19938dde204c000b7eb169d4b368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 21:39:58 +0200 Subject: [PATCH 09/24] feat(self-hosting): bind protocol backlog and reader oracle --- plans/24-inward-turn-self-hosting-practice.md | 60 +++-- src/cli/build-command.ts | 14 ++ src/cli/import-command.ts | 9 + src/cli/output.ts | 9 + src/cli/validate-view-command.ts | 9 + src/projections/design-review-context.ts | 23 ++ src/projections/design-review-pages.ts | 16 ++ src/validate/readiness-floor.ts | 7 + src/validate/validators.ts | 63 ++++++ test/self-hosting-consumers-oracle.test.ts | 51 +++++ test/self-hosting-consumers.oracle.ts | 33 +++ test/self-hosting-graph.test.ts | 12 +- test/self-hosting-oracle/anchors.ts | 210 ++++++++++++++++++ test/self-hosting-oracle/carrier.ts | 2 +- test/self-hosting-oracle/consumers.ts | 6 +- test/self-hosting-oracle/extraction.ts | 2 +- test/self-hosting-oracle/validation.ts | 16 +- 17 files changed, 502 insertions(+), 40 deletions(-) create mode 100644 test/self-hosting-consumers-oracle.test.ts create mode 100644 test/self-hosting-consumers.oracle.ts diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md index 596f10c..193b8d6 100644 --- a/plans/24-inward-turn-self-hosting-practice.md +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -44,6 +44,10 @@ constant repetition of the bound test, so revision 3 records that vacuity refusa `spec:consumers.reader` instead has three existing witness points with three distinct result variants; it is the non-vacuous first inward oracle. +S1 bound `oracle:protocol.reader-entry-map` over those three points. Mutating the concept branch +from `sections.behavior.rules` to `sections.intent.outcome` reddened the exact oracle assertion; +restoration returned the focused suite green. + The phase's engine slice was selected during revision-3 planning, not at execution theater: `spec:validation.oracle-target-eligibility`. Eleven of the thirteen non-example backlog laws are rule Specs, most owning example spaces, while the shared fail-closed predicate admits behavior @@ -96,30 +100,30 @@ a target. | Spec | Candidate seam | Disposition and current evidence | |---|---|---| -| `spec:carrier.sdp-import` | `runImport` | pending | -| `spec:consumers.binding-language-views` | binding renderers | pending | -| `spec:consumers.derived-readiness-banner` | readiness renderer | pending | -| `spec:consumers.wholesale-view-rewrite` | view/build rewrite boundary | pending | -| `spec:extraction.determinism` | concrete clean-state seam, if any | pending | -| `spec:validation.authored-honesty` | authoring-shape and delivery-fact checks | pending | -| `spec:validation.claim-separation` | claim-separation check | pending | -| `spec:validation.diagnostic-rendering` | CLI and projection renderers | pending | -| `spec:validation.kind-evidence` | kind-evidence table | pending | -| `spec:validation.pack-coherence` | Pack-coherence check | pending | -| `spec:validation.referential-integrity` | referential-integrity check | pending | -| `spec:validation.verification-linkage` | verifier and oracle linkage checks | pending | -| `spec:validation.warn-level-signals` | orphan and gap checks | pending | +| `spec:carrier.sdp-import` | `runImport` | **bind** — the entrypoint owns scan/refusal/planning/publication orchestration | +| `spec:consumers.binding-language-views` | binding renderers | **bind ×3** — the Spec explicitly names the Spec page, Pack table, and index table | +| `spec:consumers.derived-readiness-banner` | readiness renderer | **bind** — `renderReadiness` owns the paired rungs and one-direction banner | +| `spec:consumers.wholesale-view-rewrite` | view/build rewrite boundary | **bind ×2** — `runBuild` owns up-front invalidation and `runView` owns atomic replacement | +| `spec:extraction.determinism` | concrete clean-state seam | **bind** — `runBuild --check-clean` independently repeats and byte-compares graph/contracts; not a whole-pipeline token | +| `spec:validation.authored-honesty` | authoring-shape and delivery-fact checks | **bind ×2** — each named check owns one half of the authored/derived refusal | +| `spec:validation.claim-separation` | claim-separation check | **bind** — one check owns the typed claim/descriptor/edge contract | +| `spec:validation.diagnostic-rendering` | CLI and projection renderers | **bind ×2** — the two named renderers own the command and table forms | +| `spec:validation.kind-evidence` | kind-evidence table | **bind** — the exported table is the ruled code-level row set | +| `spec:validation.pack-coherence` | Pack-coherence check | **bind** — one check owns membership and modelRef coherence | +| `spec:validation.referential-integrity` | referential-integrity check | **bind** — one check owns edge endpoints, modelRefs, and nearest-ID suggestions | +| `spec:validation.verification-linkage` | verifier and oracle linkage checks | **bind ×2** — the Spec explicitly names both resolution checks | +| `spec:validation.warn-level-signals` | orphan and gap checks | **bind ×2** — each named check owns one informative signal | | Drift Spec | Fresh disposition | |---|---| -| `spec:carrier.markdown-authoring` | pending re-argument | -| `spec:consumers.projections-model` | pending re-argument | -| `spec:extraction.claim-taxonomy` | pending re-argument | -| `spec:extraction.regenerability` | pending re-argument | -| `spec:model.core-model` | pending re-argument | -| `spec:model.pack-aggregate` | pending re-argument | -| `spec:model.relations` | pending re-argument | -| `spec:model.spec-sections` | pending re-argument | +| `spec:carrier.markdown-authoring` | **stay `defined`** — fresh graph: seven refining carrier laws, one implementation binding, zero resolving verifiers; the umbrella promise has no direct executable witness | +| `spec:consumers.projections-model` | **stay `defined`** — fresh graph: five refining consumer Specs, one projection binding, zero resolving verifiers; vocabulary umbrella rather than one executable guarantee | +| `spec:extraction.claim-taxonomy` | **stay `defined`** — fresh graph: model vocabulary, one enum binding, zero resolving verifiers; the floor passes but no direct witness justifies the human `ready` statement | +| `spec:extraction.regenerability` | **stay `defined`** — fresh graph: one `runBuild` binding, zero resolving verifiers; the determinism test targets its constraint parent, not this rule | +| `spec:model.core-model` | **stay `defined`** — fresh graph: eight refining Specs, two primitive/descriptor bindings, zero resolving verifiers; root vocabulary remains deliberately conservative | +| `spec:model.pack-aggregate` | **stay `defined`** — fresh graph: two refining Specs, one Pack binding, zero resolving verifiers; no direct aggregate witness targets the model Spec | +| `spec:model.relations` | **stay `defined`** — fresh graph: relation vocabulary, one builder binding, zero resolving verifiers; structural completeness alone does not supply the human statement | +| `spec:model.spec-sections` | **stay `defined`** — fresh graph: four refining Specs, two section/verifier bindings, zero resolving verifiers; the umbrella has no direct executable witness | ## Inward friction ledger @@ -133,6 +137,20 @@ changes only when the same friction recurs twice. | `spec:consumers.impact-graph` | none observed | language-neutral identity question keeps `idea` honest | none observed | none observed | | `spec:carrier.markdown-pack-authoring` | none observed | unresolved syntax keeps `idea` honest | none observed | none observed | | `spec:validation.oracle-target-eligibility` | pending S2 | blocking implementation seam supports `scoped` | pending S2 | graph exposed rule-kind demand directly | +| `spec:carrier.sdp-import` | concrete entrypoint; no friction | already ready | existing point; no friction | backlog recipe led directly to the missing binding | +| `spec:consumers.binding-language-views` | three honest sites required | already ready | existing points; no friction | guarantee recipe named all renderers | +| `spec:consumers.derived-readiness-banner` | one honest site | already ready | existing points; no friction | none observed | +| `spec:consumers.wholesale-view-rewrite` | split build/view ownership | already ready | existing points; no friction | none observed | +| `spec:extraction.determinism` | audit distinguished a real check-clean seam from diffuse pipeline truth | already ready | direct test anchor already resolves | none observed | +| `spec:validation.authored-honesty` | two named check sites | already ready | existing points; no friction | none observed | +| `spec:validation.claim-separation` | one named check site | already ready | existing points; no friction | none observed | +| `spec:validation.diagnostic-rendering` | two renderer sites | already ready | existing points; no friction | none observed | +| `spec:validation.kind-evidence` | one table site | already ready | existing points; no friction | none observed | +| `spec:validation.pack-coherence` | one check site | already ready | existing point; no friction | none observed | +| `spec:validation.referential-integrity` | one check site | already ready | existing points; no friction | none observed | +| `spec:validation.verification-linkage` | two named resolution sites | already ready | existing points; no friction | none observed | +| `spec:validation.warn-level-signals` | two informative-check sites | already ready | existing points; no friction | none observed | +| `spec:consumers.reader` | oracle binding resolved directly | already ready | three partial points required explicit defaults | graph context supplied all three witness IDs | ## Verification and close diff --git a/src/cli/build-command.ts b/src/cli/build-command.ts index 182fe56..f15de90 100644 --- a/src/cli/build-command.ts +++ b/src/cli/build-command.ts @@ -40,6 +40,13 @@ function contractFilesEqual( return true; } +const determinismAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.extraction-determinism"), + label: "repeats and byte-compares graph and contract generation under --check-clean", + satisfies: ref("spec:extraction.determinism"), +}); +void determinismAnchor; + const regenerabilityAnchor = codeAnchor({ id: codeAnchorId("impl:protocol.regenerability"), label: "repeats graph and contract producers for deterministic regeneration", @@ -47,6 +54,13 @@ const regenerabilityAnchor = codeAnchor({ }); void regenerabilityAnchor; +const wholesaleViewBuildInvalidationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.wholesale-view-build-invalidation"), + label: "invalidates the prior Design Review before every build attempt", + satisfies: ref("spec:consumers.wholesale-view-rewrite"), +}); +void wholesaleViewBuildInvalidationAnchor; + export function runBuild( parsed: BuildArgs, output: CliOutput, diff --git a/src/cli/import-command.ts b/src/cli/import-command.ts index 5b9d520..eeb6942 100644 --- a/src/cli/import-command.ts +++ b/src/cli/import-command.ts @@ -2,6 +2,8 @@ import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; import { importFindingIds, importTypeScriptSpec } from "../index.js"; +import { codeAnchorId, ref } from "../ids.js"; +import { codeAnchor } from "../model/code-anchor.js"; import type { Finding } from "../validate/contracts.js"; import { publishImports } from "./import-publish.js"; import type { ImportPublicationHooks, PlannedImport } from "./import-publish.js"; @@ -66,6 +68,13 @@ function targetExistsFinding(targetPath: string): Finding { }; } +const sdpImportAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.sdp-import"), + label: "plans, refuses, and publishes TypeScript-to-Markdown Spec imports", + satisfies: ref("spec:carrier.sdp-import"), +}); +void sdpImportAnchor; + export function runImport(parsed: ImportArgs, output: CliOutput, hooks: ImportHooks = {}): number { const read = hooks.readFileSync ?? readFileSync; const targetExists = hooks.existsSync ?? existsSync; diff --git a/src/cli/output.ts b/src/cli/output.ts index 9a146a4..fb6622f 100644 --- a/src/cli/output.ts +++ b/src/cli/output.ts @@ -1,4 +1,6 @@ import type { Finding } from "../validate/contracts.js"; +import { codeAnchorId, ref } from "../ids.js"; +import { codeAnchor } from "../model/code-anchor.js"; export interface CliOutput { readonly stdout?: { readonly write: (chunk: string) => void }; @@ -18,6 +20,13 @@ export function writeStderr(output: CliOutput, text: string): void { output.stderr?.write(text); } +const diagnosticCliAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.diagnostic-rendering-cli"), + label: "composes structured finding fields into the one command-line diagnostic form", + satisfies: ref("spec:validation.diagnostic-rendering"), +}); +void diagnosticCliAnchor; + export function formatFinding(finding: Finding): string { const location = finding.file === undefined diff --git a/src/cli/validate-view-command.ts b/src/cli/validate-view-command.ts index 026ac92..c0d61bc 100644 --- a/src/cli/validate-view-command.ts +++ b/src/cli/validate-view-command.ts @@ -3,6 +3,8 @@ import { dirname, join } from "node:path"; import { renderDesignReview } from "../projections/design-review.js"; import { createReader } from "../reader/reader.js"; +import { codeAnchorId, ref } from "../ids.js"; +import { codeAnchor } from "../model/code-anchor.js"; import { validateGraph } from "../validate/validators.js"; import { removeArtifacts } from "./artifacts.js"; import type { BuildArgs } from "./build-args.js"; @@ -52,6 +54,13 @@ export function runValidate( } } +const wholesaleViewRewriteAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.wholesale-view-rewrite"), + label: "publishes each Design Review as one wholesale temporary-directory replacement", + satisfies: ref("spec:consumers.wholesale-view-rewrite"), +}); +void wholesaleViewRewriteAnchor; + export function runView(parsed: BuildArgs, output: CliOutput, hooks: ValidationViewHooks): number { const render = hooks.renderDesignReview ?? renderDesignReview; const recoveryRm = hooks.rmSync ?? rmSync; diff --git a/src/projections/design-review-context.ts b/src/projections/design-review-context.ts index fec6484..5368df2 100644 --- a/src/projections/design-review-context.ts +++ b/src/projections/design-review-context.ts @@ -1,4 +1,6 @@ import { SPEC_READINESS } from "../model/descriptors.js"; +import { codeAnchorId, ref } from "../ids.js"; +import { codeAnchor } from "../model/code-anchor.js"; import type { RelationEnd, SpecContext, VerifierBinding } from "../reader/reader.js"; import type { Finding } from "../validate/contracts.js"; import { escapeRenderedField } from "./owned-prose.js"; @@ -10,6 +12,13 @@ import { tableCell, } from "./design-review-markdown.js"; +const derivedReadinessBannerAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.derived-readiness-banner"), + label: "renders stated readiness beside the structural floor and dishonest divergence", + satisfies: ref("spec:consumers.derived-readiness-banner"), +}); +void derivedReadinessBannerAnchor; + export function renderReadiness(context: SpecContext): readonly string[] { const derived = context.derivedReadiness; const lines = [ @@ -54,6 +63,13 @@ function describeVerifier(verifier: VerifierBinding): string { : "**not enabled** (an off-contract `verifies` edge — it confers no verifier binding)"; } +const bindingLanguageSpecPageAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.binding-language-spec-page"), + label: "renders implementation, verifier, oracle, and observation binding language", + satisfies: ref("spec:consumers.binding-language-views"), +}); +void bindingLanguageSpecPageAnchor; + export function renderBindings(context: SpecContext, page: string): readonly string[] { const present = (fact: "implemented" | "has-verifier"): string => context.deliveryFacts.includes(fact) ? "present" : "none"; @@ -151,6 +167,13 @@ export function renderRelationsAndImpact(context: SpecContext, page: string): re return lines.length === 4 ? [] : lines; } +const diagnosticDesignReviewAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.diagnostic-rendering-design-review"), + label: "renders structured finding locations in the Design Review table", + satisfies: ref("spec:validation.diagnostic-rendering"), +}); +void diagnosticDesignReviewAnchor; + export function renderFindings(findings: readonly Finding[]): readonly string[] { if (findings.length === 0) { return ["## Findings", "", "None — conformance + honesty clean for this page's subject."]; diff --git a/src/projections/design-review-pages.ts b/src/projections/design-review-pages.ts index ecc4871..632f623 100644 --- a/src/projections/design-review-pages.ts +++ b/src/projections/design-review-pages.ts @@ -1,4 +1,6 @@ import type { PackContext, Reader, SpecContext, SpecSummary } from "../reader/reader.js"; +import { codeAnchorId, ref } from "../ids.js"; +import { codeAnchor } from "../model/code-anchor.js"; import { escapeRenderedField, renderNarrative } from "./owned-prose.js"; import type { DesignReviewPage } from "./design-review.js"; import { @@ -46,6 +48,13 @@ export function renderSpecPage(context: SpecContext): DesignReviewPage { return { path: page, content: `${lines.join("\n")}\n` }; } +const bindingLanguagePackTableAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.binding-language-pack-table"), + label: "renders Pack member implementation and verifier bindings as present or none", + satisfies: ref("spec:consumers.binding-language-views"), +}); +void bindingLanguagePackTableAnchor; + export function renderPackPage( context: PackContext, specLabel: (id: string) => string, @@ -111,6 +120,13 @@ export function renderPackPage( return { path: page, content: `${lines.join("\n")}\n` }; } +const bindingLanguageIndexTableAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.binding-language-index-table"), + label: "renders index implementation and verifier bindings as present or none", + satisfies: ref("spec:consumers.binding-language-views"), +}); +void bindingLanguageIndexTableAnchor; + export function renderIndexPage(reader: Reader, specs: readonly SpecSummary[]): DesignReviewPage { const page = "index.md"; const packs = reader.packs(); diff --git a/src/validate/readiness-floor.ts b/src/validate/readiness-floor.ts index 1892b1d..ec4195e 100644 --- a/src/validate/readiness-floor.ts +++ b/src/validate/readiness-floor.ts @@ -326,6 +326,13 @@ const behaviorFamilyEvidence: KindEvidenceRow = { }, }; +const kindEvidenceAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.kind-evidence"), + label: "defines the code-level per-kind evidence rows for readiness floors", + satisfies: ref("spec:validation.kind-evidence"), +}); +void kindEvidenceAnchor; + export const kindEvidence = { behavior: behaviorFamilyEvidence, workflow: behaviorFamilyEvidence, diff --git a/src/validate/validators.ts b/src/validate/validators.ts index cdfb27b..35d34ed 100644 --- a/src/validate/validators.ts +++ b/src/validate/validators.ts @@ -147,6 +147,13 @@ function describeMissingTarget(missingId: string, index: GraphIndex): string { /* ----- conformance/referential-integrity (`spec:validation.referential-integrity`) ----- */ +const referentialIntegrityAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.referential-integrity"), + label: "checks every graph edge endpoint and Pack model reference resolves", + satisfies: ref("spec:validation.referential-integrity"), +}); +void referentialIntegrityAnchor; + function checkReferentialIntegrity(graph: GraphSchema, index: GraphIndex): readonly Finding[] { const findings: Finding[] = []; @@ -456,6 +463,13 @@ function checkEdgeContractRow(edge: GraphEdge, index: GraphIndex, findings: Find } } +const claimSeparationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.claim-separation"), + label: "checks graph claims, descriptors, node shapes, and relation endpoint contracts", + satisfies: ref("spec:validation.claim-separation"), +}); +void claimSeparationAnchor; + function checkClaimSeparation(graph: GraphSchema, index: GraphIndex): readonly Finding[] { const findings: Finding[] = []; @@ -515,6 +529,13 @@ function checkClaimSeparation(graph: GraphSchema, index: GraphIndex): readonly F * spec a resolving test anchor binds), so an unenabled or wrong-kind verifier is named loudly * instead of silently conferring nothing. */ +const verifiesLinkageAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.verifies-linkage"), + label: "resolves declared example verification through enabled test bindings", + satisfies: ref("spec:validation.verification-linkage"), +}); +void verifiesLinkageAnchor; + function checkVerifiesLinkage(graph: GraphSchema, index: GraphIndex): readonly Finding[] { const anchorVerified = new Set(); @@ -572,6 +593,13 @@ function checkVerifiesLinkage(graph: GraphSchema, index: GraphIndex): readonly F return findings; } +const oracleLinkageAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.oracle-linkage"), + label: "resolves and de-duplicates expected-outcome oracle bindings", + satisfies: ref("spec:validation.verification-linkage"), +}); +void oracleLinkageAnchor; + function checkOracleLinkage(graph: GraphSchema, index: GraphIndex): readonly Finding[] { const findings: Finding[] = []; const resolvingByTarget = new Map(); @@ -696,6 +724,13 @@ function checkPackModelRefs(pack: PackNode, index: GraphIndex, findings: Finding } } +const packCoherenceAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.pack-coherence"), + label: "checks Pack membership uniqueness and model-reference kinds", + satisfies: ref("spec:validation.pack-coherence"), +}); +void packCoherenceAnchor; + function checkPackCoherence(graph: GraphSchema, index: GraphIndex): readonly Finding[] { const findings: Finding[] = []; @@ -713,6 +748,13 @@ function checkPackCoherence(graph: GraphSchema, index: GraphIndex): readonly Fin /* ----- conformance/orphans (`spec:validation.warn-level-signals` — informative) ----- */ +const orphanSignalAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.orphan-signal"), + label: "surfaces relationless Specs as informative orphan warnings", + satisfies: ref("spec:validation.warn-level-signals"), +}); +void orphanSignalAnchor; + function checkOrphans(graph: GraphSchema, index: GraphIndex): readonly Finding[] { const findings: Finding[] = []; @@ -757,6 +799,13 @@ function isRecord(value: unknown): value is Record { * entries): nested content stays content — a vocabulary may legitimately name a term * "implemented". */ +const authoringShapeAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.authored-honesty-shape"), + label: "refuses delivery facts smuggled through authored section carriers", + satisfies: ref("spec:validation.authored-honesty"), +}); +void authoringShapeAnchor; + function checkAuthoringShape(node: PrimitiveNode, findings: Finding[]): void { const appendSmuggledFact = (factName: string, path: string): void => { findings.push( @@ -816,6 +865,13 @@ function checkAuthoringShape(node: PrimitiveNode, findings: Finding[]): void { * omitted fact corrupts the backlog/drift queries, and `observed` has no producer yet * (aspirational, the liveness rung). */ +const deliveryFactsHonestyAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.authored-honesty-delivery-facts"), + label: "checks stated delivery facts equal the one recomputed derivation", + satisfies: ref("spec:validation.authored-honesty"), +}); +void deliveryFactsHonestyAnchor; + function checkDeliveryFacts( graph: GraphSchema, derivedFacts: ReadonlyMap, @@ -916,6 +972,13 @@ function checkReadinessFloors(graph: GraphSchema, index: GraphIndex): readonly F * binding earns must not silence the gap (that disagreement is the delivery-facts check's error; * this check stays truthful either way). */ +const gapSignalAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.verifier-gap-signal"), + label: "surfaces ready Specs without recomputed verifier bindings as informative gaps", + satisfies: ref("spec:validation.warn-level-signals"), +}); +void gapSignalAnchor; + function checkGaps( graph: GraphSchema, derivedFacts: ReadonlyMap, diff --git a/test/self-hosting-consumers-oracle.test.ts b/test/self-hosting-consumers-oracle.test.ts new file mode 100644 index 0000000..437a889 --- /dev/null +++ b/test/self-hosting-consumers-oracle.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; + +import type { ReaderConditions } from "../generated/contracts/consumers.reader.space.js"; +import { readerSpace } from "../generated/contracts/consumers.reader.space.js"; +import { expectedReaderOutcome } from "./self-hosting-consumers.oracle.js"; + +function completeConditions(point: Partial): ReaderConditions { + if (point.entry === undefined) { + throw new Error("every reader witness must bind its entry mode"); + } + + return { + concept: point.concept ?? "", + conceptSpecId: point.conceptSpecId ?? "", + boundFile: point.boundFile ?? "", + bindingId: point.bindingId ?? "", + unrecordedFile: point.unrecordedFile ?? "", + entry: point.entry, + }; +} + +describe("the reader entry-map oracle", () => { + it("assigns a distinct expected outcome to every generated reader witness", () => { + const outcomes = readerSpace.examples.map(({ point }) => + expectedReaderOutcome(completeConditions(point)), + ); + + expect(readerSpace.examples.map(({ spec }) => spec).sort()).toEqual([ + "spec:consumers.reader.changeset-entry", + "spec:consumers.reader.concept-entry", + "spec:consumers.reader.file-entry", + ]); + expect(outcomes).toEqual([ + { + kind: "the coverage-unknown files name {coverageUnknownFile}", + coverageUnknownFile: "src/price-book.ts", + }, + { + kind: "the reader names {matchedId} as a match on the field {matchedField}", + matchedId: "spec:orders.order-management", + matchedField: "sections.behavior.rules", + }, + { + kind: "the file entry names the node {nodeId} the graph records at that path", + nodeId: "impl:orders.create-order", + }, + ]); + expect(new Set(outcomes.map(({ kind }) => kind)).size).toBe(3); + expect(outcomes.every(({ kind }) => kind !== "unspecified")).toBe(true); + }); +}); diff --git a/test/self-hosting-consumers.oracle.ts b/test/self-hosting-consumers.oracle.ts new file mode 100644 index 0000000..4bc27fe --- /dev/null +++ b/test/self-hosting-consumers.oracle.ts @@ -0,0 +1,33 @@ +import { oracleAnchorId, ref, specOracle } from "@libar-dev/software-delivery-protocol"; + +import type { + ReaderConditions, + ReaderOutcome, +} from "../generated/contracts/consumers.reader.space.js"; + +export const readerEntryMapOracle = specOracle({ + id: oracleAnchorId("oracle:protocol.reader-entry-map"), + label: "expected reader outcome for each graph entry mode", + models: ref("spec:consumers.reader"), +}); + +export function expectedReaderOutcome(conditions: ReaderConditions): ReaderOutcome { + switch (conditions.entry) { + case "concept": + return { + kind: "the reader names {matchedId} as a match on the field {matchedField}", + matchedId: conditions.conceptSpecId, + matchedField: "sections.behavior.rules", + }; + case "file": + return { + kind: "the file entry names the node {nodeId} the graph records at that path", + nodeId: conditions.bindingId, + }; + case "changeset": + return { + kind: "the coverage-unknown files name {coverageUnknownFile}", + coverageUnknownFile: conditions.unrecordedFile, + }; + } +} diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index 00218ad..c946148 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -90,12 +90,12 @@ describe("the self-hosting corpus", () => { // The literals are the corpus checkpoint. The authored arrays are measured against the same // literals rather than standing in for them, so a transcription slip in an oracle module // cannot certify itself by moving both sides of a comparison at once. - expect(result.counts).toEqual({ specs: 126, packs: 1, anchors: 91 }); + expect(result.counts).toEqual({ specs: 126, packs: 1, anchors: 112 }); expect(expectedSpecs).toHaveLength(126); expect(expectedPackMembers).toHaveLength(126); - expect(expectedAnchors).toHaveLength(91); - expect(result.graph.nodes).toHaveLength(218); - expect(result.graph.edges).toHaveLength(431); + expect(expectedAnchors).toHaveLength(112); + expect(result.graph.nodes).toHaveLength(239); + expect(result.graph.edges).toHaveLength(452); }); it("rosters exactly the authored Spec, Pack, and anchor node ids", () => { @@ -274,14 +274,14 @@ describe("the self-hosting corpus", () => { ]); }); - it("derives the sdp-import round-trip delivery facts from the child's verifier", () => { + it("derives sdp-import realization from its direct code and child verifier bindings", () => { const importChildId = "spec:carrier.sdp-import.round-trip"; const importParentId = "spec:carrier.sdp-import"; const importChild = primitiveNodes.find((node) => node.id === importChildId); const importParent = primitiveNodes.find((node) => node.id === importParentId); expect(importChild?.deliveryFacts).toEqual(["has-verifier"]); - expect(importParent?.deliveryFacts).toEqual(["has-verifier"]); + expect(importParent?.deliveryFacts).toEqual(["implemented", "has-verifier"]); expect( result.graph.edges.filter( (edge) => diff --git a/test/self-hosting-oracle/anchors.ts b/test/self-hosting-oracle/anchors.ts index 67810be..fc3f24e 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -913,4 +913,214 @@ export const expectedAnchors = [ constant: "changesetEntryTestAnchor", site: "bindExample(changesetEntryContract", }, + { + id: "impl:protocol.sdp-import", + nodeType: "CodeNode", + label: "plans, refuses, and publishes TypeScript-to-Markdown Spec imports", + type: "satisfies", + target: "spec:carrier.sdp-import", + file: "src/cli/import-command.ts", + constant: "sdpImportAnchor", + site: "export function runImport", + }, + { + id: "impl:protocol.extraction-determinism", + nodeType: "CodeNode", + label: "repeats and byte-compares graph and contract generation under --check-clean", + type: "satisfies", + target: "spec:extraction.determinism", + file: "src/cli/build-command.ts", + constant: "determinismAnchor", + site: "function contractFilesEqual", + }, + { + id: "impl:protocol.wholesale-view-build-invalidation", + nodeType: "CodeNode", + label: "invalidates the prior Design Review before every build attempt", + type: "satisfies", + target: "spec:consumers.wholesale-view-rewrite", + file: "src/cli/build-command.ts", + constant: "wholesaleViewBuildInvalidationAnchor", + site: 'const viewPath = join(resolvedRoot, "generated", "design-review")', + }, + { + id: "impl:protocol.wholesale-view-rewrite", + nodeType: "CodeNode", + label: "publishes each Design Review as one wholesale temporary-directory replacement", + type: "satisfies", + target: "spec:consumers.wholesale-view-rewrite", + file: "src/cli/validate-view-command.ts", + constant: "wholesaleViewRewriteAnchor", + site: "export function runView", + }, + { + id: "impl:protocol.derived-readiness-banner", + nodeType: "CodeNode", + label: "renders stated readiness beside the structural floor and dishonest divergence", + type: "satisfies", + target: "spec:consumers.derived-readiness-banner", + file: "src/projections/design-review-context.ts", + constant: "derivedReadinessBannerAnchor", + site: "export function renderReadiness", + }, + { + id: "impl:protocol.binding-language-spec-page", + nodeType: "CodeNode", + label: "renders implementation, verifier, oracle, and observation binding language", + type: "satisfies", + target: "spec:consumers.binding-language-views", + file: "src/projections/design-review-context.ts", + constant: "bindingLanguageSpecPageAnchor", + site: "export function renderBindings", + }, + { + id: "impl:protocol.diagnostic-rendering-design-review", + nodeType: "CodeNode", + label: "renders structured finding locations in the Design Review table", + type: "satisfies", + target: "spec:validation.diagnostic-rendering", + file: "src/projections/design-review-context.ts", + constant: "diagnosticDesignReviewAnchor", + site: "export function renderFindings", + }, + { + id: "impl:protocol.binding-language-pack-table", + nodeType: "CodeNode", + label: "renders Pack member implementation and verifier bindings as present or none", + type: "satisfies", + target: "spec:consumers.binding-language-views", + file: "src/projections/design-review-pages.ts", + constant: "bindingLanguagePackTableAnchor", + site: "export function renderPackPage", + }, + { + id: "impl:protocol.binding-language-index-table", + nodeType: "CodeNode", + label: "renders index implementation and verifier bindings as present or none", + type: "satisfies", + target: "spec:consumers.binding-language-views", + file: "src/projections/design-review-pages.ts", + constant: "bindingLanguageIndexTableAnchor", + site: "export function renderIndexPage", + }, + { + id: "impl:protocol.diagnostic-rendering-cli", + nodeType: "CodeNode", + label: "composes structured finding fields into the one command-line diagnostic form", + type: "satisfies", + target: "spec:validation.diagnostic-rendering", + file: "src/cli/output.ts", + constant: "diagnosticCliAnchor", + site: "export function formatFinding", + }, + { + id: "impl:protocol.kind-evidence", + nodeType: "CodeNode", + label: "defines the code-level per-kind evidence rows for readiness floors", + type: "satisfies", + target: "spec:validation.kind-evidence", + file: "src/validate/readiness-floor.ts", + constant: "kindEvidenceAnchor", + site: "export const kindEvidence", + }, + { + id: "impl:protocol.referential-integrity", + nodeType: "CodeNode", + label: "checks every graph edge endpoint and Pack model reference resolves", + type: "satisfies", + target: "spec:validation.referential-integrity", + file: "src/validate/validators.ts", + constant: "referentialIntegrityAnchor", + site: "function checkReferentialIntegrity", + }, + { + id: "impl:protocol.claim-separation", + nodeType: "CodeNode", + label: "checks graph claims, descriptors, node shapes, and relation endpoint contracts", + type: "satisfies", + target: "spec:validation.claim-separation", + file: "src/validate/validators.ts", + constant: "claimSeparationAnchor", + site: "function checkClaimSeparation", + }, + { + id: "impl:protocol.verifies-linkage", + nodeType: "CodeNode", + label: "resolves declared example verification through enabled test bindings", + type: "satisfies", + target: "spec:validation.verification-linkage", + file: "src/validate/validators.ts", + constant: "verifiesLinkageAnchor", + site: "function checkVerifiesLinkage", + }, + { + id: "impl:protocol.oracle-linkage", + nodeType: "CodeNode", + label: "resolves and de-duplicates expected-outcome oracle bindings", + type: "satisfies", + target: "spec:validation.verification-linkage", + file: "src/validate/validators.ts", + constant: "oracleLinkageAnchor", + site: "function checkOracleLinkage", + }, + { + id: "impl:protocol.pack-coherence", + nodeType: "CodeNode", + label: "checks Pack membership uniqueness and model-reference kinds", + type: "satisfies", + target: "spec:validation.pack-coherence", + file: "src/validate/validators.ts", + constant: "packCoherenceAnchor", + site: "function checkPackCoherence", + }, + { + id: "impl:protocol.orphan-signal", + nodeType: "CodeNode", + label: "surfaces relationless Specs as informative orphan warnings", + type: "satisfies", + target: "spec:validation.warn-level-signals", + file: "src/validate/validators.ts", + constant: "orphanSignalAnchor", + site: "function checkOrphans", + }, + { + id: "impl:protocol.authored-honesty-shape", + nodeType: "CodeNode", + label: "refuses delivery facts smuggled through authored section carriers", + type: "satisfies", + target: "spec:validation.authored-honesty", + file: "src/validate/validators.ts", + constant: "authoringShapeAnchor", + site: "function checkAuthoringShape", + }, + { + id: "impl:protocol.authored-honesty-delivery-facts", + nodeType: "CodeNode", + label: "checks stated delivery facts equal the one recomputed derivation", + type: "satisfies", + target: "spec:validation.authored-honesty", + file: "src/validate/validators.ts", + constant: "deliveryFactsHonestyAnchor", + site: "function checkDeliveryFacts", + }, + { + id: "impl:protocol.verifier-gap-signal", + nodeType: "CodeNode", + label: "surfaces ready Specs without recomputed verifier bindings as informative gaps", + type: "satisfies", + target: "spec:validation.warn-level-signals", + file: "src/validate/validators.ts", + constant: "gapSignalAnchor", + site: "function checkGaps", + }, + { + id: "oracle:protocol.reader-entry-map", + nodeType: "Anchor", + label: "expected reader outcome for each graph entry mode", + type: "models", + target: "spec:consumers.reader", + file: "test/self-hosting-consumers.oracle.ts", + constant: "readerEntryMapOracle", + site: "export function expectedReaderOutcome", + }, ] as const; diff --git a/test/self-hosting-oracle/carrier.ts b/test/self-hosting-oracle/carrier.ts index 331b312..fbdfb66 100644 --- a/test/self-hosting-oracle/carrier.ts +++ b/test/self-hosting-oracle/carrier.ts @@ -179,7 +179,7 @@ export const carrierSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:carrier.sdp-import.round-trip", diff --git a/test/self-hosting-oracle/consumers.ts b/test/self-hosting-oracle/consumers.ts index ec5e81b..3a1d23a 100644 --- a/test/self-hosting-oracle/consumers.ts +++ b/test/self-hosting-oracle/consumers.ts @@ -240,7 +240,7 @@ export const consumersSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:consumers.derived-readiness-banner.dishonest-divergence", @@ -345,7 +345,7 @@ export const consumersSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:consumers.binding-language-views.bound-spec-page", @@ -450,7 +450,7 @@ export const consumersSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:consumers.wholesale-view-rewrite.stale-page-removed", diff --git a/test/self-hosting-oracle/extraction.ts b/test/self-hosting-oracle/extraction.ts index 91d56b6..d8fbed3 100644 --- a/test/self-hosting-oracle/extraction.ts +++ b/test/self-hosting-oracle/extraction.ts @@ -53,7 +53,7 @@ export const extractionSpecs = [ ], }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:extraction.excludes", diff --git a/test/self-hosting-oracle/validation.ts b/test/self-hosting-oracle/validation.ts index a4d82c3..52206b1 100644 --- a/test/self-hosting-oracle/validation.ts +++ b/test/self-hosting-oracle/validation.ts @@ -147,7 +147,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.kind-evidence.constraints-alone", @@ -404,7 +404,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.referential-integrity.dangling-target", @@ -499,7 +499,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.claim-separation.collapsed-edge-claim", @@ -597,7 +597,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.verification-linkage.unbound-example", @@ -731,7 +731,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.pack-coherence.incoherent-aggregate", @@ -794,7 +794,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.authored-honesty.section-authored-fact", @@ -891,7 +891,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.warn-level-signals.orphan-signal", @@ -991,7 +991,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.diagnostic-rendering.composed-location", From 019d0639819abe4a2312c3622372bbfffe318045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 21:44:17 +0200 Subject: [PATCH 10/24] docs(self-hosting): design oracle eligibility slice --- .../oracle-target-eligibility.sdp.md | 15 +++++++----- test/self-hosting-graph.test.ts | 2 +- test/self-hosting-oracle/validation.ts | 24 +++++++++++-------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/specs/validation/oracle-target-eligibility.sdp.md b/specs/validation/oracle-target-eligibility.sdp.md index faa5c17..0e72b96 100644 --- a/specs/validation/oracle-target-eligibility.sdp.md +++ b/specs/validation/oracle-target-eligibility.sdp.md @@ -2,7 +2,7 @@ id: spec:validation.oracle-target-eligibility kind: rule altitude: story -readiness: scoped +readiness: defined relations: refines: spec:validation.verification-linkage dependsOn: spec:model.anchors @@ -12,17 +12,20 @@ relations: ## Intent - outcome: Let an expected-outcome oracle model any Spec whose own law defines an example space, regardless of Spec kind. -### Open questions -- [blocking] Which shared resolution predicate and diagnostic seam must change so validator and reader consumers stay fail-closed together? - ## Rule - Oracle target eligibility follows ownership of an example space, not a behavior-kind check. +## Design +The validator and reader must continue to share one fail-closed definition of a resolving oracle. +- eligibilityPredicate: `isResolvingOracleModel` accepts an anchored `models` edge from an `oracle:` Anchor to any Primitive that owns `behavior.exampleSpace`; Spec kind does not participate. +- resolutionFlow: `checkOracleLinkage`, delivery-fact derivation, `specContext`, `byFile`, and blast-radius traversal consume the shared predicate rather than restating eligibility. +- failureDiagnostic: A non-resolving candidate says that the target must be a Spec owning an example space; competing-oracle diagnostics name the target as a Spec. +- executableSeams: Validator regressions cover rule-with-space acceptance and missing-space refusal; reader regressions prove the same rule-kind binding resolves through the shared predicate. + ## Example space ```gwt-vocabulary Given the oracle targets a {targetKind:"behavior"|"rule"} spec Given the target owns an example space: {ownsExampleSpace:boolean} When oracle linkage is resolved -Then the report contains {findingCount:number} oracle-linkage findings -Then a resolving oracle is present: {oraclePresent:boolean} +Then oracle linkage reports {findingCount:number} findings and resolving presence {oraclePresent:boolean} ``` diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index c946148..52f8a88 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -145,7 +145,7 @@ describe("the self-hosting corpus", () => { }), {}, ), - ).toEqual({ defined: 37, idea: 4, ready: 83, scoped: 2 }); + ).toEqual({ defined: 38, idea: 4, ready: 83, scoped: 1 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { diff --git a/test/self-hosting-oracle/validation.ts b/test/self-hosting-oracle/validation.ts index 52206b1..6a9e8ee 100644 --- a/test/self-hosting-oracle/validation.ts +++ b/test/self-hosting-oracle/validation.ts @@ -664,7 +664,7 @@ export const validationSpecs = [ id: "spec:validation.oracle-target-eligibility", specKind: "rule", altitude: "story", - readiness: "scoped", + readiness: "defined", file: "specs/validation/oracle-target-eligibility.sdp.md", title: "Oracle eligibility follows example-space ownership", narrative: null, @@ -672,13 +672,6 @@ export const validationSpecs = [ intent: { outcome: "Let an expected-outcome oracle model any Spec whose own law defines an example space, regardless of Spec kind.", - openQuestions: [ - { - question: - "Which shared resolution predicate and diagnostic seam must change so validator and reader consumers stay fail-closed together?", - blocking: true, - }, - ], }, behavior: { rules: [ @@ -691,11 +684,22 @@ export const validationSpecs = [ ], when: ["oracle linkage is resolved"], then: [ - "the report contains {findingCount:number} oracle-linkage findings", - "a resolving oracle is present: {oraclePresent:boolean}", + "oracle linkage reports {findingCount:number} findings and resolving presence {oraclePresent:boolean}", ], }, }, + design: { + description: + "The validator and reader must continue to share one fail-closed definition of a resolving oracle.", + eligibilityPredicate: + "`isResolvingOracleModel` accepts an anchored `models` edge from an `oracle:` Anchor to any Primitive that owns `behavior.exampleSpace`; Spec kind does not participate.", + resolutionFlow: + "`checkOracleLinkage`, delivery-fact derivation, `specContext`, `byFile`, and blast-radius traversal consume the shared predicate rather than restating eligibility.", + failureDiagnostic: + "A non-resolving candidate says that the target must be a Spec owning an example space; competing-oracle diagnostics name the target as a Spec.", + executableSeams: + "Validator regressions cover rule-with-space acceptance and missing-space refusal; reader regressions prove the same rule-kind binding resolves through the shared predicate.", + }, }, deliveryFacts: [], }, From 341ead656f9c71cf5d4ac8d3c6bb6a91fbe2586a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 21:50:42 +0200 Subject: [PATCH 11/24] feat(validation): make oracle eligibility kind-neutral --- plans/24-inward-turn-self-hosting-practice.md | 22 ++++- specs/self-hosting.pack.sdp.ts | 2 + ...t-eligibility.missing-space-refused.sdp.md | 20 +++++ ...get-eligibility.rule-space-accepted.sdp.md | 20 +++++ .../oracle-target-eligibility.sdp.md | 2 +- src/graph/oracle-bindings.ts | 11 ++- src/validate/validators.ts | 6 +- test/reader.test.ts | 34 ++++++++ test/self-hosting-graph.test.ts | 14 ++-- test/self-hosting-oracle/anchors.ts | 40 +++++++++ .../self-hosting-oracle/declared-relations.ts | 20 +++++ test/self-hosting-oracle/pack-members.ts | 2 + test/self-hosting-oracle/validation.ts | 64 ++++++++++++++- test/self-hosting-validators-oracle.test.ts | 44 ++++++++++ test/self-hosting-validators.oracle.ts | 22 +++++ test/self-hosting-validators.test.ts | 82 ++++++++++++++++++- test/validators.test.ts | 22 ++--- 17 files changed, 399 insertions(+), 28 deletions(-) create mode 100644 specs/validation/oracle-target-eligibility.missing-space-refused.sdp.md create mode 100644 specs/validation/oracle-target-eligibility.rule-space-accepted.sdp.md create mode 100644 test/self-hosting-validators-oracle.test.ts create mode 100644 test/self-hosting-validators.oracle.ts diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md index 193b8d6..157ee73 100644 --- a/plans/24-inward-turn-self-hosting-practice.md +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -54,6 +54,12 @@ rule Specs, most owning example spaces, while the shared fail-closed predicate a targets only. S2 reruns the lower-ladder and backlog recipes to confirm that no newer prerequisite outranks the slice; it does not pretend to choose an already planted winner. +S2 opened from the regenerated graph at 126 Specs · 1 Pack · 112 anchors → 239 nodes · 452 +edges, zero findings. The lower ladder held 43 entries; the named slice stated `scoped`, reached +that floor with no failures, and had `defined` as its next rung. Recipe 1 returned no operational +backlog, while the drift recipe returned the same eight freshly argued rows. No new prerequisite +or higher-severity evidence displaced the planning-time selection. + ## Charter and sequence ### S0 — standing intent and working-surface semantics @@ -93,6 +99,20 @@ outranks the slice; it does not pretend to choose an already planted winner. Keep durable behavior and trade-offs, and record the before/after commits as one datum without promoting the enrichment-lifecycle Spec. +The implementing Design was committed independently at `019d063`: it named the shared predicate, +resolution flow, diagnostic language, and validator/reader seams before any engine code changed. +The owner's explicit implementation request is the fresh human readiness statement; after the +blocking question was answered by that Design, the rule's complete evidence and resolving +relations passed the floor and Design Review before promotion. The two generated child contracts +bind the rule-with-space acceptance and missing-space refusal points directly. + +Both mutation probes reddened as intended before restoration: + +- restoring `target.specKind === "behavior"` in `isResolvingOracleModel` failed the focused + validator regression, reader regression, and bound rule-space example; +- reversing the expected `findingCount` branch failed the self-referential oracle assertion across + both generated points. + ## Disposition ledgers The backlog ledger is filled from current evidence during S1. Zero remaining rows is likely, not @@ -136,7 +156,7 @@ changes only when the same friction recurs twice. | `spec:observation.runtime-overlay` | none observed | blocking producer boundary keeps `idea` honest | none observed | none observed | | `spec:consumers.impact-graph` | none observed | language-neutral identity question keeps `idea` honest | none observed | none observed | | `spec:carrier.markdown-pack-authoring` | none observed | unresolved syntax keeps `idea` honest | none observed | none observed | -| `spec:validation.oracle-target-eligibility` | pending S2 | blocking implementation seam supports `scoped` | pending S2 | graph exposed rule-kind demand directly | +| `spec:validation.oracle-target-eligibility` | one shared-predicate anchor; none observed | Design resolved the blocker; fresh statement plus floor/Review earned `ready` | two generated points bound without friction | lower ladder confirmed the preselected slice; no competing evidence | | `spec:carrier.sdp-import` | concrete entrypoint; no friction | already ready | existing point; no friction | backlog recipe led directly to the missing binding | | `spec:consumers.binding-language-views` | three honest sites required | already ready | existing points; no friction | guarantee recipe named all renderers | | `spec:consumers.derived-readiness-banner` | one honest site | already ready | existing points; no friction | none observed | diff --git a/specs/self-hosting.pack.sdp.ts b/specs/self-hosting.pack.sdp.ts index 22759e5..9dadf16 100644 --- a/specs/self-hosting.pack.sdp.ts +++ b/specs/self-hosting.pack.sdp.ts @@ -30,6 +30,8 @@ export const selfHostingV1Pack = pack({ ref("spec:validation.claim-separation"), ref("spec:validation.verification-linkage"), ref("spec:validation.oracle-target-eligibility"), + ref("spec:validation.oracle-target-eligibility.rule-space-accepted"), + ref("spec:validation.oracle-target-eligibility.missing-space-refused"), ref("spec:validation.pack-coherence"), ref("spec:validation.authored-honesty"), ref("spec:validation.warn-level-signals"), diff --git a/specs/validation/oracle-target-eligibility.missing-space-refused.sdp.md b/specs/validation/oracle-target-eligibility.missing-space-refused.sdp.md new file mode 100644 index 0000000..7d12909 --- /dev/null +++ b/specs/validation/oracle-target-eligibility.missing-space-refused.sdp.md @@ -0,0 +1,20 @@ +--- +id: spec:validation.oracle-target-eligibility.missing-space-refused +kind: example +altitude: story +readiness: ready +relations: + refines: spec:validation.oracle-target-eligibility + verifies: spec:validation.oracle-target-eligibility +--- +# A target without an example space refuses an oracle + +## Intent +- outcome: Execute the fail-closed refusal when an otherwise valid Spec target owns no example space. + +```gwt +Given the oracle targets a {targetKind: "behavior"} spec +Given the target owns an example space: {ownsExampleSpace: false} +When oracle linkage is resolved +Then oracle linkage reports {findingCount: 1} findings and resolving presence {oraclePresent: false} +``` diff --git a/specs/validation/oracle-target-eligibility.rule-space-accepted.sdp.md b/specs/validation/oracle-target-eligibility.rule-space-accepted.sdp.md new file mode 100644 index 0000000..030900f --- /dev/null +++ b/specs/validation/oracle-target-eligibility.rule-space-accepted.sdp.md @@ -0,0 +1,20 @@ +--- +id: spec:validation.oracle-target-eligibility.rule-space-accepted +kind: example +altitude: story +readiness: ready +relations: + refines: spec:validation.oracle-target-eligibility + verifies: spec:validation.oracle-target-eligibility +--- +# A rule owning an example space accepts an oracle + +## Intent +- outcome: Execute kind-neutral oracle resolution for a rule that owns the vocabulary its oracle models. + +```gwt +Given the oracle targets a {targetKind: "rule"} spec +Given the target owns an example space: {ownsExampleSpace: true} +When oracle linkage is resolved +Then oracle linkage reports {findingCount: 0} findings and resolving presence {oraclePresent: true} +``` diff --git a/specs/validation/oracle-target-eligibility.sdp.md b/specs/validation/oracle-target-eligibility.sdp.md index 0e72b96..75b9df9 100644 --- a/specs/validation/oracle-target-eligibility.sdp.md +++ b/specs/validation/oracle-target-eligibility.sdp.md @@ -2,7 +2,7 @@ id: spec:validation.oracle-target-eligibility kind: rule altitude: story -readiness: defined +readiness: ready relations: refines: spec:validation.verification-linkage dependsOn: spec:model.anchors diff --git a/src/graph/oracle-bindings.ts b/src/graph/oracle-bindings.ts index 358cbc8..d766202 100644 --- a/src/graph/oracle-bindings.ts +++ b/src/graph/oracle-bindings.ts @@ -1,4 +1,5 @@ -import { parseId } from "../ids.js"; +import { codeAnchorId, parseId, ref } from "../ids.js"; +import { codeAnchor } from "../model/code-anchor.js"; import type { GraphEdge, GraphNode, PrimitiveNode } from "./schema.js"; function asRecord(value: unknown): Record | undefined { @@ -21,6 +22,13 @@ function hasOracleNamespace(id: string): boolean { } /** The complete, fail-closed oracle binding contract shared by validators and consumers. */ +const oracleTargetEligibilityAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.oracle-target-eligibility"), + label: "resolves oracle targets by example-space ownership across Spec kinds", + satisfies: ref("spec:validation.oracle-target-eligibility"), +}); +void oracleTargetEligibilityAnchor; + export function isResolvingOracleModel( edge: GraphEdge, nodesById: ReadonlyMap, @@ -34,7 +42,6 @@ export function isResolvingOracleModel( source?.nodeType === "Anchor" && hasOracleNamespace(source.id) && target?.nodeType === "Primitive" && - target.specKind === "behavior" && ownsExampleSpace(target) ); } diff --git a/src/validate/validators.ts b/src/validate/validators.ts index 35d34ed..90a88e1 100644 --- a/src/validate/validators.ts +++ b/src/validate/validators.ts @@ -627,7 +627,7 @@ function checkOracleLinkage(graph: GraphSchema, index: GraphIndex): readonly Fin validatorId: graphValidatorIds.oracleLinkage, family: "conformance", severity: "error", - message: `Oracle binding "${edge.from}" → "${edge.to}" must use an oracle: anchor and target a behavior spec with an example space.`, + message: `Oracle binding "${edge.from}" → "${edge.to}" must use an oracle: anchor and target a Spec that owns an example space.`, subjectId: edge.from, relatedId: edge.to, file: source.file, @@ -636,7 +636,7 @@ function checkOracleLinkage(graph: GraphSchema, index: GraphIndex): readonly Fin continue; } - if (target.specKind === "behavior" && ownsExampleSpace(target)) { + if (ownsExampleSpace(target)) { resolvingByTarget.set(edge.to, [...(resolvingByTarget.get(edge.to) ?? []), edge]); } } @@ -651,7 +651,7 @@ function checkOracleLinkage(graph: GraphSchema, index: GraphIndex): readonly Fin validatorId: graphValidatorIds.oracleLinkage, family: "conformance", severity: "error", - message: `Behavior spec "${targetId}" has ${String(edges.length)} resolving oracle bindings (${edges.map((edge) => `"${edge.from}"`).join(" · ")}) — at most one expected-outcome authority may model an example space.`, + message: `Spec "${targetId}" has ${String(edges.length)} resolving oracle bindings (${edges.map((edge) => `"${edge.from}"`).join(" · ")}) — at most one expected-outcome authority may model an example space.`, subjectId: targetId, file: fileOf(index, targetId), }), diff --git a/test/reader.test.ts b/test/reader.test.ts index 875995c..b5e00c2 100644 --- a/test/reader.test.ts +++ b/test/reader.test.ts @@ -9,11 +9,13 @@ import { createReader, extract, graphValidatorIds, + oracleAnchorId, pack, packId, refines, spec, specId, + specOracle, specTest, testAnchorId, verifies, @@ -448,6 +450,38 @@ describe("the reader — the thin typed loader behind the agent surface", () => ).toBeUndefined(); }); + it("decodes a rule-kind oracle through the shared example-space predicate", () => { + const rule = spec({ + id: specId("spec:orders.order-routing"), + title: "Order routing rule", + kind: "rule", + altitude: "story", + readiness: "idea", + intent: { outcome: "Rule the routing policy." }, + behavior: { + rules: ["A route is selected."], + exampleSpace: { then: ["the route is selected"] }, + }, + }); + const reader = createReader( + deriveFixtureGraph({ + specs: [rule], + anchors: [ + specOracle({ + id: oracleAnchorId("oracle:orders.order-routing"), + label: "expected routing outcome", + models: rule.id, + }), + ], + }), + ); + + expect(reader.specContext(rule.id)?.oracle).toMatchObject({ + anchorId: "oracle:orders.order-routing", + claim: "anchored", + }); + }); + it("fails closed on off-contract or competing oracle edges", () => { const offClaim: GraphSchema = { ...exampleGraph, diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index 52f8a88..f5a63c4 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -90,12 +90,12 @@ describe("the self-hosting corpus", () => { // The literals are the corpus checkpoint. The authored arrays are measured against the same // literals rather than standing in for them, so a transcription slip in an oracle module // cannot certify itself by moving both sides of a comparison at once. - expect(result.counts).toEqual({ specs: 126, packs: 1, anchors: 112 }); - expect(expectedSpecs).toHaveLength(126); - expect(expectedPackMembers).toHaveLength(126); - expect(expectedAnchors).toHaveLength(112); - expect(result.graph.nodes).toHaveLength(239); - expect(result.graph.edges).toHaveLength(452); + expect(result.counts).toEqual({ specs: 128, packs: 1, anchors: 116 }); + expect(expectedSpecs).toHaveLength(128); + expect(expectedPackMembers).toHaveLength(128); + expect(expectedAnchors).toHaveLength(116); + expect(result.graph.nodes).toHaveLength(245); + expect(result.graph.edges).toHaveLength(462); }); it("rosters exactly the authored Spec, Pack, and anchor node ids", () => { @@ -145,7 +145,7 @@ describe("the self-hosting corpus", () => { }), {}, ), - ).toEqual({ defined: 38, idea: 4, ready: 83, scoped: 1 }); + ).toEqual({ defined: 37, idea: 4, ready: 86, scoped: 1 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { diff --git a/test/self-hosting-oracle/anchors.ts b/test/self-hosting-oracle/anchors.ts index fc3f24e..5588d64 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -1123,4 +1123,44 @@ export const expectedAnchors = [ constant: "readerEntryMapOracle", site: "export function expectedReaderOutcome", }, + { + id: "impl:protocol.oracle-target-eligibility", + nodeType: "CodeNode", + label: "resolves oracle targets by example-space ownership across Spec kinds", + type: "satisfies", + target: "spec:validation.oracle-target-eligibility", + file: "src/graph/oracle-bindings.ts", + constant: "oracleTargetEligibilityAnchor", + site: "export function isResolvingOracleModel", + }, + { + id: "test:protocol.oracle-target-eligibility.rule-space-accepted", + nodeType: "Anchor", + label: "the rule-space point verifies kind-neutral oracle resolution", + type: "verifies", + target: "spec:validation.oracle-target-eligibility.rule-space-accepted", + file: "test/self-hosting-validators.test.ts", + constant: "ruleSpaceAcceptedTestAnchor", + site: "bindExample(ruleSpaceAcceptedContract", + }, + { + id: "test:protocol.oracle-target-eligibility.missing-space-refused", + nodeType: "Anchor", + label: "the missing-space point verifies fail-closed oracle resolution", + type: "verifies", + target: "spec:validation.oracle-target-eligibility.missing-space-refused", + file: "test/self-hosting-validators.test.ts", + constant: "missingSpaceRefusedTestAnchor", + site: "bindExample(missingSpaceRefusedContract", + }, + { + id: "oracle:protocol.oracle-target-eligibility", + nodeType: "Anchor", + label: "expected oracle-linkage result by example-space ownership", + type: "models", + target: "spec:validation.oracle-target-eligibility", + file: "test/self-hosting-validators.oracle.ts", + constant: "oracleTargetEligibilityOracle", + site: "export function expectedOracleTargetEligibilityOutcome", + }, ] as const; diff --git a/test/self-hosting-oracle/declared-relations.ts b/test/self-hosting-oracle/declared-relations.ts index c1b75b8..d6ac633 100644 --- a/test/self-hosting-oracle/declared-relations.ts +++ b/test/self-hosting-oracle/declared-relations.ts @@ -461,4 +461,24 @@ export const expectedDeclaredRelations = [ ["spec:carrier.markdown-pack-authoring", "dependsOn", "spec:decisions.carrier-ruling"], ["spec:validation.oracle-target-eligibility", "refines", "spec:validation.verification-linkage"], ["spec:validation.oracle-target-eligibility", "dependsOn", "spec:model.anchors"], + [ + "spec:validation.oracle-target-eligibility.rule-space-accepted", + "refines", + "spec:validation.oracle-target-eligibility", + ], + [ + "spec:validation.oracle-target-eligibility.rule-space-accepted", + "verifies", + "spec:validation.oracle-target-eligibility", + ], + [ + "spec:validation.oracle-target-eligibility.missing-space-refused", + "refines", + "spec:validation.oracle-target-eligibility", + ], + [ + "spec:validation.oracle-target-eligibility.missing-space-refused", + "verifies", + "spec:validation.oracle-target-eligibility", + ], ] as const; diff --git a/test/self-hosting-oracle/pack-members.ts b/test/self-hosting-oracle/pack-members.ts index 627fed6..5ca0e5e 100644 --- a/test/self-hosting-oracle/pack-members.ts +++ b/test/self-hosting-oracle/pack-members.ts @@ -27,6 +27,8 @@ export const expectedPackMembers = [ "spec:validation.claim-separation", "spec:validation.verification-linkage", "spec:validation.oracle-target-eligibility", + "spec:validation.oracle-target-eligibility.rule-space-accepted", + "spec:validation.oracle-target-eligibility.missing-space-refused", "spec:validation.pack-coherence", "spec:validation.authored-honesty", "spec:validation.warn-level-signals", diff --git a/test/self-hosting-oracle/validation.ts b/test/self-hosting-oracle/validation.ts index 6a9e8ee..fa5f0df 100644 --- a/test/self-hosting-oracle/validation.ts +++ b/test/self-hosting-oracle/validation.ts @@ -664,7 +664,7 @@ export const validationSpecs = [ id: "spec:validation.oracle-target-eligibility", specKind: "rule", altitude: "story", - readiness: "defined", + readiness: "ready", file: "specs/validation/oracle-target-eligibility.sdp.md", title: "Oracle eligibility follows example-space ownership", narrative: null, @@ -701,7 +701,67 @@ export const validationSpecs = [ "Validator regressions cover rule-with-space acceptance and missing-space refusal; reader regressions prove the same rule-kind binding resolves through the shared predicate.", }, }, - deliveryFacts: [], + deliveryFacts: ["implemented", "has-verifier"], + }, + { + id: "spec:validation.oracle-target-eligibility.rule-space-accepted", + specKind: "example", + altitude: "story", + readiness: "ready", + file: "specs/validation/oracle-target-eligibility.rule-space-accepted.sdp.md", + title: "A rule owning an example space accepts an oracle", + narrative: null, + sections: { + intent: { + outcome: + "Execute kind-neutral oracle resolution for a rule that owns the vocabulary its oracle models.", + }, + behavior: { + examples: [ + { + given: [ + 'the oracle targets a {targetKind: "rule"} spec', + "the target owns an example space: {ownsExampleSpace: true}", + ], + when: ["oracle linkage is resolved"], + then: [ + "oracle linkage reports {findingCount: 0} findings and resolving presence {oraclePresent: true}", + ], + }, + ], + }, + }, + deliveryFacts: ["has-verifier"], + }, + { + id: "spec:validation.oracle-target-eligibility.missing-space-refused", + specKind: "example", + altitude: "story", + readiness: "ready", + file: "specs/validation/oracle-target-eligibility.missing-space-refused.sdp.md", + title: "A target without an example space refuses an oracle", + narrative: null, + sections: { + intent: { + outcome: + "Execute the fail-closed refusal when an otherwise valid Spec target owns no example space.", + }, + behavior: { + examples: [ + { + given: [ + 'the oracle targets a {targetKind: "behavior"} spec', + "the target owns an example space: {ownsExampleSpace: false}", + ], + when: ["oracle linkage is resolved"], + then: [ + "oracle linkage reports {findingCount: 1} findings and resolving presence {oraclePresent: false}", + ], + }, + ], + }, + }, + deliveryFacts: ["has-verifier"], }, { id: "spec:validation.pack-coherence", diff --git a/test/self-hosting-validators-oracle.test.ts b/test/self-hosting-validators-oracle.test.ts new file mode 100644 index 0000000..c133b9f --- /dev/null +++ b/test/self-hosting-validators-oracle.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; + +import type { OracleTargetEligibilityConditions } from "../generated/contracts/validation.oracle-target-eligibility.space.js"; +import { oracleTargetEligibilitySpace } from "../generated/contracts/validation.oracle-target-eligibility.space.js"; +import { expectedOracleTargetEligibilityOutcome } from "./self-hosting-validators.oracle.js"; + +function completeConditions( + point: Partial, +): OracleTargetEligibilityConditions { + if (point.targetKind === undefined || point.ownsExampleSpace === undefined) { + throw new Error("every oracle-eligibility witness must bind kind and example-space ownership"); + } + + return { + targetKind: point.targetKind, + ownsExampleSpace: point.ownsExampleSpace, + }; +} + +describe("the oracle-target-eligibility oracle", () => { + it("assigns finding count and resolving presence to both generated points", () => { + const outcomes = oracleTargetEligibilitySpace.examples.map(({ point }) => + expectedOracleTargetEligibilityOutcome(completeConditions(point)), + ); + + expect(oracleTargetEligibilitySpace.examples.map(({ spec }) => spec).sort()).toEqual([ + "spec:validation.oracle-target-eligibility.missing-space-refused", + "spec:validation.oracle-target-eligibility.rule-space-accepted", + ]); + expect(outcomes).toEqual([ + { + kind: "oracle linkage reports {findingCount} findings and resolving presence {oraclePresent}", + findingCount: 1, + oraclePresent: false, + }, + { + kind: "oracle linkage reports {findingCount} findings and resolving presence {oraclePresent}", + findingCount: 0, + oraclePresent: true, + }, + ]); + expect(outcomes.every(({ kind }) => kind !== "unspecified")).toBe(true); + }); +}); diff --git a/test/self-hosting-validators.oracle.ts b/test/self-hosting-validators.oracle.ts new file mode 100644 index 0000000..6cb4a04 --- /dev/null +++ b/test/self-hosting-validators.oracle.ts @@ -0,0 +1,22 @@ +import { oracleAnchorId, ref, specOracle } from "@libar-dev/software-delivery-protocol"; + +import type { + OracleTargetEligibilityConditions, + OracleTargetEligibilityOutcome, +} from "../generated/contracts/validation.oracle-target-eligibility.space.js"; + +export const oracleTargetEligibilityOracle = specOracle({ + id: oracleAnchorId("oracle:protocol.oracle-target-eligibility"), + label: "expected oracle-linkage result by example-space ownership", + models: ref("spec:validation.oracle-target-eligibility"), +}); + +export function expectedOracleTargetEligibilityOutcome( + conditions: OracleTargetEligibilityConditions, +): OracleTargetEligibilityOutcome { + return { + kind: "oracle linkage reports {findingCount} findings and resolving presence {oraclePresent}", + findingCount: conditions.ownsExampleSpace ? 0 : 1, + oraclePresent: conditions.ownsExampleSpace, + }; +} diff --git a/test/self-hosting-validators.test.ts b/test/self-hosting-validators.test.ts index 8dccdb2..6d8964d 100644 --- a/test/self-hosting-validators.test.ts +++ b/test/self-hosting-validators.test.ts @@ -20,7 +20,9 @@ import { incoherentAggregateContract } from "../generated/contracts/validation.p import { splitReportContract } from "../generated/contracts/validation.two-check-families.split-report.contract.js"; import { unboundExampleContract } from "../generated/contracts/validation.verification-linkage.unbound-example.contract.js"; import { unresolvedOracleContract } from "../generated/contracts/validation.verification-linkage.unresolved-oracle.contract.js"; -import { computeDeliveryFacts, schemaVersion, validateGraph } from "../src/index.js"; +import { missingSpaceRefusedContract } from "../generated/contracts/validation.oracle-target-eligibility.missing-space-refused.contract.js"; +import { ruleSpaceAcceptedContract } from "../generated/contracts/validation.oracle-target-eligibility.rule-space-accepted.contract.js"; +import { computeDeliveryFacts, createReader, schemaVersion, validateGraph } from "../src/index.js"; import type { Finding, GraphClaim, @@ -684,6 +686,84 @@ void unresolvedOracleTestAnchor; bindExample(unresolvedOracleContract, validatorWorld, verificationLinkageBindings); +/* ----- spec:validation.oracle-target-eligibility ----- */ + +const oracleTargetEligibilityBindings = { + "the oracle targets a {targetKind} spec": ( + world: ValidatorWorld, + params: { readonly targetKind: "behavior" | "rule" }, + ) => { + world.subjectId = "spec:probe.oracle-target"; + world.nodes.push(probeSpec(world.subjectId, { kind: params.targetKind })); + world.nodes.push({ + id: "oracle:probe.oracle-target", + nodeType: "Anchor", + claim: "anchored", + label: "probe expected outcome", + file: "test/probe-oracle.test.ts", + line: 5, + }); + world.edges.push({ + from: "oracle:probe.oracle-target", + type: "models", + to: world.subjectId, + claim: "anchored", + }); + }, + "the target owns an example space: {ownsExampleSpace}": ( + world: ValidatorWorld, + params: { readonly ownsExampleSpace: boolean }, + ) => { + if (!params.ownsExampleSpace) { + return; + } + + reviseSubject( + world, + (node) => ({ + ...node, + sections: { + ...node.sections, + behavior: { + ...node.sections?.behavior, + exampleSpace: { then: ["the probe resolves"] }, + }, + }, + }), + "The target-kind step must run before example-space ownership is recorded.", + ); + }, + "oracle linkage is resolved": validate, + "oracle linkage reports {findingCount} findings and resolving presence {oraclePresent}": ( + world: ValidatorWorld, + params: { readonly findingCount: number; readonly oraclePresent: boolean }, + ) => { + expect(findingsOf(world, "conformance/oracle-linkage")).toHaveLength(params.findingCount); + const graph = { schemaVersion, nodes: world.nodes, edges: world.edges }; + expect(createReader(graph).specContext(world.subjectId)?.oracle !== undefined).toBe( + params.oraclePresent, + ); + }, +}; + +const ruleSpaceAcceptedTestAnchor = specTest({ + id: testAnchorId("test:protocol.oracle-target-eligibility.rule-space-accepted"), + label: "the rule-space point verifies kind-neutral oracle resolution", + verifies: ref("spec:validation.oracle-target-eligibility.rule-space-accepted"), +}); +void ruleSpaceAcceptedTestAnchor; + +bindExample(ruleSpaceAcceptedContract, validatorWorld, oracleTargetEligibilityBindings); + +const missingSpaceRefusedTestAnchor = specTest({ + id: testAnchorId("test:protocol.oracle-target-eligibility.missing-space-refused"), + label: "the missing-space point verifies fail-closed oracle resolution", + verifies: ref("spec:validation.oracle-target-eligibility.missing-space-refused"), +}); +void missingSpaceRefusedTestAnchor; + +bindExample(missingSpaceRefusedContract, validatorWorld, oracleTargetEligibilityBindings); + /* ----- spec:validation.pack-coherence ----- */ const packCoherenceBindings = { diff --git a/test/validators.test.ts b/test/validators.test.ts index 1e7a07c..67449bb 100644 --- a/test/validators.test.ts +++ b/test/validators.test.ts @@ -820,7 +820,7 @@ describe("the models edge — the oracle anchor's contract row", () => { expect(findings).toHaveLength(1); expect(findings[0]?.severity).toBe("error"); - expect(findings[0]?.message).toContain("behavior spec with an example space"); + expect(findings[0]?.message).toContain("Spec that owns an example space"); }); it("rejects competing oracle anchors for one parent example space", () => { @@ -869,22 +869,22 @@ describe("the models edge — the oracle anchor's contract row", () => { expect(findings[0]?.message).toContain("oracle: anchor"); }); - it("rejects a non-behavior oracle target even when it carries an example-space-shaped section", () => { - const decision = spec({ - id: specId("spec:decisions.order-routing"), - title: "Order routing decision", - kind: "decision", + it("accepts a rule-kind oracle target when the rule owns an example space", () => { + const rule = spec({ + id: specId("spec:orders.order-routing"), + title: "Order routing rule", + kind: "rule", altitude: "feature", readiness: "idea", - intent: { outcome: "Choose the routing policy." }, + intent: { outcome: "Rule the routing policy." }, behavior: { exampleSpace: { then: ["the routing policy is chosen"] } }, }); const graph = deriveFixtureGraph({ - specs: [decision], + specs: [rule], anchors: [ specOracle({ - id: oracleAnchorId("oracle:decisions.order-routing"), - models: decision.id, + id: oracleAnchorId("oracle:orders.order-routing"), + models: rule.id, }), ], }); @@ -893,7 +893,7 @@ describe("the models edge — the oracle anchor's contract row", () => { validateGraph(graph).findings.some( (finding) => finding.validatorId === graphValidatorIds.oracleLinkage, ), - ).toBe(true); + ).toBe(false); }); it("rejects a models edge from a non-Anchor source — the endpoints are typed", () => { From 7ae8087ad0dc9fc6384f24ebc5a3bfd241a09541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 21:51:53 +0200 Subject: [PATCH 12/24] docs(self-hosting): slim oracle eligibility spec --- specs/validation/oracle-target-eligibility.sdp.md | 10 +++------- test/self-hosting-oracle/validation.ts | 15 +++------------ 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/specs/validation/oracle-target-eligibility.sdp.md b/specs/validation/oracle-target-eligibility.sdp.md index 75b9df9..ddfc3d7 100644 --- a/specs/validation/oracle-target-eligibility.sdp.md +++ b/specs/validation/oracle-target-eligibility.sdp.md @@ -14,13 +14,9 @@ relations: ## Rule - Oracle target eligibility follows ownership of an example space, not a behavior-kind check. - -## Design -The validator and reader must continue to share one fail-closed definition of a resolving oracle. -- eligibilityPredicate: `isResolvingOracleModel` accepts an anchored `models` edge from an `oracle:` Anchor to any Primitive that owns `behavior.exampleSpace`; Spec kind does not participate. -- resolutionFlow: `checkOracleLinkage`, delivery-fact derivation, `specContext`, `byFile`, and blast-radius traversal consume the shared predicate rather than restating eligibility. -- failureDiagnostic: A non-resolving candidate says that the target must be a Spec owning an example space; competing-oracle diagnostics name the target as a Spec. -- executableSeams: Validator regressions cover rule-with-space acceptance and missing-space refusal; reader regressions prove the same rule-kind binding resolves through the shared predicate. +- A resolving binding is an anchored `models` edge from an `oracle:` Anchor to a Spec that owns an example space. +- Missing targets, wrong namespaces, absent example spaces, and competing oracles remain fail-closed refusals. +- Validators and graph readers consume the same eligibility result. ## Example space ```gwt-vocabulary diff --git a/test/self-hosting-oracle/validation.ts b/test/self-hosting-oracle/validation.ts index fa5f0df..5164332 100644 --- a/test/self-hosting-oracle/validation.ts +++ b/test/self-hosting-oracle/validation.ts @@ -676,6 +676,9 @@ export const validationSpecs = [ behavior: { rules: [ "Oracle target eligibility follows ownership of an example space, not a behavior-kind check.", + "A resolving binding is an anchored `models` edge from an `oracle:` Anchor to a Spec that owns an example space.", + "Missing targets, wrong namespaces, absent example spaces, and competing oracles remain fail-closed refusals.", + "Validators and graph readers consume the same eligibility result.", ], exampleSpace: { given: [ @@ -688,18 +691,6 @@ export const validationSpecs = [ ], }, }, - design: { - description: - "The validator and reader must continue to share one fail-closed definition of a resolving oracle.", - eligibilityPredicate: - "`isResolvingOracleModel` accepts an anchored `models` edge from an `oracle:` Anchor to any Primitive that owns `behavior.exampleSpace`; Spec kind does not participate.", - resolutionFlow: - "`checkOracleLinkage`, delivery-fact derivation, `specContext`, `byFile`, and blast-radius traversal consume the shared predicate rather than restating eligibility.", - failureDiagnostic: - "A non-resolving candidate says that the target must be a Spec owning an example space; competing-oracle diagnostics name the target as a Spec.", - executableSeams: - "Validator regressions cover rule-with-space acceptance and missing-space refusal; reader regressions prove the same rule-kind binding resolves through the shared predicate.", - }, }, deliveryFacts: ["implemented", "has-verifier"], }, From 4e2f5a1b5d3c92eee90eaf2666129b6109dc1982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 21:54:36 +0200 Subject: [PATCH 13/24] docs(plans): close inward self-hosting phase --- AGENTS.md | 2 +- plans/23-outward-turn-origin-adoption.md | 562 ------------------ plans/24-inward-turn-self-hosting-practice.md | 43 +- 3 files changed, 36 insertions(+), 571 deletions(-) delete mode 100644 plans/23-outward-turn-origin-adoption.md diff --git a/AGENTS.md b/AGENTS.md index 710327b..bb8337f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ claims; **`src/` and tests** are authoritative evidence of current realization. > machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; > plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier > rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** EXECUTING — plan 24 is EXECUTING revision 3, the inward turn: self-hosting as standing practice, with the example realization posture ratified, forward intent entering the graph, the binding layer audited honestly, two inward oracles, and one kind-neutral engine slice running the full spec-first loop through slimming. Settled phase-6 ground is recoverable at commit `b6f123c` and PR #15; the origin working-copy changes remain reverted and survive only on PR libar-ai/convex-event-sourcing#181. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package. The execution baseline was `ready: 83 / defined: 36 / scoped: 1 / idea: 1` over 121 Specs · 1 Pack · 91 anchors → 213 nodes · 416 edges with zero findings. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in +> as import source and a lawful per-ID option. · **what now:** EXECUTED — plan 24 is EXECUTED and landed the inward turn: self-hosting is the standing practice, the example realization posture is ratified, forward intent lives in the graph, the binding layer is deliberately dispositioned, two inward oracles resolve, and the kind-neutral oracle slice completed the full spec-first loop through slimming. The corpus stands at `ready: 86 / defined: 37 / scoped: 1 / idea: 4` over 128 Specs · 1 Pack · 116 anchors → 245 nodes · 462 edges with zero findings. Phase-6 and phase-7 evidence is recoverable from commits `b6f123c` through `7ae8087` and PR #15; the origin working-copy changes remain reverted and survive only on PR libar-ai/convex-event-sourcing#181. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in > **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when diff --git a/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md deleted file mode 100644 index c7043f0..0000000 --- a/plans/23-outward-turn-origin-adoption.md +++ /dev/null @@ -1,562 +0,0 @@ -# Plan 23 — The outward turn, refined: origin adoption, the usage layer, and the working ladder - -> **⚠️ SUPERSEDED as working guidance (2026-07-28).** This plan is ✅ EXECUTED and stands only -> as the historical record of phase 6; the **standing plan is -> [plan 24](24-inward-turn-self-hosting-practice.md)** — read that, not this file, for what to -> do next. **Origin-repo caution:** the S1–S4 workstreams below executed in the origin project -> (`new-convex-es` / `libar-platform`), but those local changes were **reverted in the origin -> working copy** and survive only as PR libar-ai/convex-event-sourcing#181 (unmerged, awaiting -> the owner's decision). No session may treat the origin working tree as carrying this plan's -> S1–S4 results, and none re-applies them — plan 24's charter forbids origin-repo work -> entirely. The owner intends to delete this file once plan 24 is implemented; its evidence -> (commit SHAs, measurements, dispositions) remains recoverable from git history and PR #181. - -> **Status:** ✅ EXECUTED — **phase-6 implementation complete; final clean-copy audit passed** -> (2026-07-28). S0–W landed in this repo; S1–S4 landed in the origin project -> (`new-convex-es` / `libar-platform`). The Protocol corpus closes at 121 Specs · 1 Pack · -> 91 anchors → 213 nodes · 416 edges, with `ready: 83 / defined: 36 / scoped: 1 / idea: 1` -> and zero findings. The origin closes at 57 Specs · 1 Pack · 11 anchors → 69 nodes · -> 105 edges, with `ready: 2 / defined: 50 / scoped: 2 / idea: 3` and zero findings. Architect -> carriers, the vendored package, process guards, annotations, release machinery, planning -> plugin, CI legs, and 333 committed projections are retired behind the committed parity ledger. -> -> **What revision 2 changed:** (1) the charter gains the four usage goals and the workstream W; -> (2) the authoring-workflow on-ramp moves from "named successor artifact" (§j of revision 1) to -> an in-scope deliverable; (3) S0 gains two named engine questions (the inert `verification.mode`, -> the anchored-verifier-without-binding seam); (4) S1/S3 gain explicit usage acceptance criteria -> (all four rungs exercised honestly; ≥1 end-to-end executable transition on foreign soil); -> (5) §b gains the inward evidence subsection (the 2026-07-28 engine survey). The origin evidence -> base, the S0 rulings, and the deferral list otherwise stand as drafted. -> -> **What revision 3 changed:** the execution review generalized the records gate before wiring it, -> authorized recoverable stage commits, returned both candidate rulings to the three-part test, -> restored the inline-carrier population instrument, matched the repository's single-file skill -> convention, fixed the package contract, and made stage gates explicit. The origin control-arm -> checkpoint is `4189d9cca`; the Protocol refinement checkpoint is `7a146dc`. -> -> **Spec anchors:** [the agent-surface ruling](../specs/decisions/agent-surface-scripts-graph.sdp.md) -> (`spec:decisions.agent-surface-scripts-graph`) · [the agent front door](../specs/decisions/agent-front-door.sdp.md) -> (`spec:decisions.agent-front-door`, MD-22) · [the carrier ruling](../specs/decisions/markdown-carrier.sdp.md) -> (`spec:decisions.markdown-carrier`, MD-18) · plan 22 §5c (the standing gap ledger and the -> successor items named there). - -## (a) Why this plan - -Plan 22 §5c named three successor items: the rule-6 bound-point candidate on -`spec:extraction.build-pipeline`, the `check-self-hosting-gates.mjs` gate-leg process change, -and the edit-model tail waiting on a write surface. This plan carries all three — and -subordinates them to a larger judgment the phase-5 close itself forced: **every piece of -evidence the corpus holds is first-caller evidence.** The second-caller bar governs every -freeze decision inside the product, but the product has no second caller. The measured-context -refusal (plan 22 §7) is the honest statement of the gap: no number exists showing the Protocol -helps a project that is not itself, and no number can be produced here. - -The 2026-07-28 investigation session (gen-1 survey, playground survey, origin survey — evidence -summarized in §(b), primary sources in the two repos) established that the origin project is -not merely a candidate second caller. It is the **intended consumer, with a deadline**: its -own re-entry corpus (drafted 2026-07-14) plans a restart "in ~2 months" whose precondition is -a working delivery-process answer. That converts "SDP needs a second project" from an abstract -risk into a scheduled obligation, and it is why the outward turn outranks further inward -readiness work. - -**The refinement (revision 2).** The same-day engine survey (§b, inward evidence) showed that -what the origin will actually meet on arrival is not an engine gap but a **usage gap**, and -the phase is rechartered around four usage goals: - -1. **Anchors used effectively.** The binding layer works (86 anchors, three builders, the - enabled-verifier join), but coverage is asymmetric — `has-verifier: 80` against - `implemented: 25` — and nothing teaches when and where to bind. The origin's ~43k-LOC - platform is the first corpus where anchor placement is a real editorial decision. -2. **The whole readiness ladder used honestly.** The corpus holds **zero** Specs at `idea` or - `scoped` — the lower half of the ladder has never been used in anger, because the Protocol's - own forward-looking work lives in plan prose and docket rows instead of Specs. "Managing - requirements of all maturity levels" is the product's headline claim; today only the top two - rungs have ever carried weight. -3. **The executable transition as a taught workflow.** The machinery is landed and self-hosted - (50 bound points, 26 space contracts, the runner, the vitest adapter). What does not exist - is the *workflow*: the sanctioned path from a prose example to an enabled verifier - (example space → bound point → generated contract → `bindExample` + `specTest` → - mutation-probed red) is recorded in plans and exercised by hand, never taught. Two inert - seams compound it (§d, questions 5–6). -4. **The agent CLI as the working surface.** `sdp q` + the eight recipes + the reading skill - shipped at phase 5; the missing half is *authoring-side* usage — recipes that answer "why is - this Spec capped below `defined`," "which examples were refused contracts and by which - finding," "what would `ready` require here" — and a skill that teaches sessions to start - from the build-backlog recipe instead of from plan prose. - -The refined thesis: **phase 6 adopts the Protocol on the origin project, and uses the adoption -to force the usage layer into existence — with this repo eating the same food first.** The -workstream W applies the Protocol to phase 6's own deliverables (Specs entering at -`idea`/`scoped`, sessions driven off `ready ∧ ¬implemented`), which is simultaneously the first -honest exercise of the lower ladder and the dress rehearsal for S3's restart backlog. - -## (b) The evidence base (recorded here because the sources live outside this repo) - -Three surveys, one session, numbers re-checkable at the named paths. - -**Gen 1 (`~/dev-projects/architect`, `@libar-dev/architect` 2.0.0-pre.1).** The verb wall was -real: 24 top-level verbs expanding to 74 distinct invocations (11 `arch` + 28 `query` -passthroughs + 14 `documentation` types), 23 MCP tools, 6 bins, ~78k LOC, ~1,800 lines of -mandatory skill preamble, and a 66 KB `FEEDBACK.md` of agent friction. Its own terminal -experiment (the playground) measured ~89% of a PatternGraph snapshot to be precomputed views, -most with exactly one consumer — failing gen 1's own second-caller bar — and measured scripting -over loaded shapes at ~⅕ the context of the grep/verb path. The repo died mid-pivot after 211 -commits in ~4 weeks; its `graph-handle` skill is the direct ancestor of `sdp q`. - -**Gen 0/0.5 (the origin: `~/dev-projects/new-convex-es`, running vendored -`@libar-dev/architect` 1.0.0-pre.3).** A real production platform — event sourcing on Convex; -~43k LOC platform-core plus a 61k LOC example app — whose delivery process grew in-repo -(`libar-platform/delivery-process`, Jan 2026), was extracted, and became the prior art. -Product velocity: 122 commits (Dec) · **615** (Jan) · 217 (Feb) · 21 (Mar) · 18 (Apr) · -34 (May) · **zero after 2026-05-09**. The terminal commits are all process work -(value-transfer doctrine PDR-022, its bulk-rollback PDR-023, deleting 21 design specs, CI -ordering for guard and docs jobs). The re-entry corpus -(`docs/sessions/reentry-journeys/`, drafted 2026-07-14) states the diagnosis in the repo's own -words: the repo is "unusually hostile to cold-start agents" partly because *"Process is part -of the product. Architect FSM, dual-source annotations, and living docs mean 'just code' -sessions fail process guardrails."* - -**What the origin corpus is, concretely.** 22 authored Gherkin specs (5,675 lines) + 23 PDR -decision features + 26 design-stub TS files (6,574 LOC) + 4 release features on the authored -side; on the executable side, **124 `.feature` files / 2,434 scenarios / 830 Rule blocks** -under `packages/platform-core/tests/features/behavior/` that are the platform's *primary test -suite* (123/124 bound to vitest-cucumber step files; 17 deliberately non-executable planning -stubs). Plus `docs-living/`: 333 committed generated files (3.5 MB) frozen at the last commit -date, with internally contradictory progress counters (90/152 in `PATTERNS.md` vs 76/116 in -`REMAINING-WORK.md`) — the committed-projection anti-pattern as a museum exhibit. - -**Why the mapping is favorable.** Authored spec → `behavior`-kind Spec at feature altitude; -`Rule:` blocks → `rule` children; scenarios → `example` children; PDRs → `decision` Specs; -`@architect-executable-specs` paths → verifier bindings; the 17 planning-stub features → -declared-but-not-enabled verifiers (the enabled/declared distinction models this exactly); -`@architect-status:completed` → the derived `implemented` fact it always should have been. -The four-tier ladder maps onto the readiness ladder: idea → `idea`, candidate (its -`**Open Questions:**` block is the discriminator) → `scoped`, plan → `defined`, design → -`ready` — each converted Spec entering at the rung its structure earns under the floor, never -at the tier label's word. Two authored surfaces deliberately do **not** migrate: deliverables -tables' per-row `Status` columns are authored delivery claims (the reserved-property refusal -polices the equivalent today — completion derives from anchors, never from a table cell), and -the temporal tags (`@architect-phase` / `-quarter` / `-completed:` / `-release`) drop -entirely — git is the event log. - -**The owner's testimony, on the record (2026-07-28).** The specs — both planned and -implemented/executable — *"truly served as replacement for PRDs, and value transfer + docs -generation proved invaluable."* This corrects two earlier framings and this plan carries the -corrected versions. First, the deletion **mechanics** of value transfer dissolve under the -Protocol (gen 0/1 had two artifacts — a planning spec and an executable spec — so -implementation left a redundant copy to distill and delete; the Protocol has one primitive -enriched in place, so the duplicate never exists), but the **outcomes** the doctrine bought -must be preserved on their own terms: the distillation discipline ("distill, don't -transcribe"; one home per explanation), and a corpus that stays lean and live because specs -are *consumed*, not accumulated. What happens to a Spec's design-time detail after its code -ships has no ruling here — that is S0's fourth question, not a solved problem. Second, the -gen-1 playground's projection critique (most projections one-consumer waste) was measured from -the *agent-sink* perspective; the owner's testimony is the *product-management* perspective, -where the generated pattern registry, roadmap, and per-pattern pages were the working PRD -surface. Both are true: the origin's failure was staleness-by-committed-artifact, never -generation itself. The restart should therefore be expected to produce honest second-caller -demand for generated views beyond the Design Review — and that demand goes through the -standing bar (a recipe freezes into a projection when a second machine consumer needs it), -not around it. - -**The inward evidence — the usage gaps (engine survey, 2026-07-28; added at revision 2).** -Same-day survey of `src/` (12.6k LOC) and the derived graph, re-checkable with `sdp q` and the -named files: - -- **The lower ladder is unexercised.** 115 Primitives: `ready: 80 · defined: 35 · scoped: 0 · - idea: 0`. Every forward-looking obligation (the write surface's design session, the - inline-carrier competition, the gate-leg change, the on-ramp itself) lives in plan prose, - docket rows, or deferral clauses — none is a Spec, so none appears in the build backlog. -- **Binding asymmetry.** `has-verifier: 80` vs `implemented: 25`; 30 `CodeNode`s against 56 - test/oracle anchors. Verification self-hosting ran far ahead of implementation self-hosting. -- **`verification.mode` is inert.** Authored, parsed (the `Verification — {mode}` heading), - rendered in the Design Review — and read by no floor clause, validator, or fact. - `mode: executable` currently *states* nothing checkable. -- **The anchor/binding seam is unobserved.** A `specTest` anchor (what makes an example an - *enabled* verifier) and a `bindExample` call (what makes it actually run) sit side by side - by convention only — nothing notices an anchored example whose contract no suite binds. -- **Oracles are example-corpus-only.** Zero `models` edges in the self-hosting graph; the - `specOracle` machinery is exercised only in `examples/checkout-v1`. -- **The on-ramp gap is real and one-sided.** `.claude/skills/sdp-agent-surface/SKILL.md` - teaches *reading* the graph (and does it well — the anti-anecdote rule, the eight recipes); - no surface teaches *authoring against it*. Gen 1's session-typed skills - (plan / design / implement / review) are evidence this layer mattered in production — - evidence, never template. - -## (c) Charter — what phase 6 is and is not - -**Is:** the usage turn. Ruling the questions the origin poses (§d), building the usage layer -and exercising the lower ladder on this repo's own phase-6 requirements (workstream W, §e), -proving the carrier and the verifier linkage on foreign content (S1), adopting the authored -corpus (S2), modeling the origin's restart plan as the first real external backlog (S3), and -producing the measured evidence phase 5 refused to manufacture (S4). Authoring-friction -observations from W and S1–S2 are captured as the evidence base the edit-model has been -waiting for. - -**Is not:** another inward self-hosting phase in the phase-1-to-5 sense. No readiness-sweep -ceremony beyond what S0's and W's own edits force; the two-review cadence of phase 5 is not -the standing default — one pre-close review for the S0 rulings session, sized to its diff, and -one for the W close, sized the same way. The `06`/`07` standing gaps (five and three rows) -remain out of scope exactly as plan 22 §5c graded them. - -**Honesty guardrails, restated for both soils:** no Spec — origin or inward — is promoted -without a resolving verifier; refusals are named with reasons; a W Spec is authored only for -genuine intended system truth, never as ladder ceremony (an `idea` Spec manufactured to -decorate the backlog is the padding the floor exists to refuse); the S4 measurement is either -instrumented end-to-end or refused with its numbers recorded, exactly as the phase-5 -precedent; and no origin behavior is silently promoted into intent — where the origin's -authored specs and its code disagree, that is drift to record, not to resolve unilaterally. - -## (d) S0 — the rulings (this repo; PLAN → decision Specs where the bar is passed) - -Four questions the origin forces, none answerable by silence: - -1. **The temporal/planning axis.** The origin's phases 13–100, quarters, releases, efforts, - and tranches were load-bearing for planning a large platform; gen 1 retired them and the - Protocol inherited the retirement (de-temporalization, plan 05). Proposed ruling: the model - stays atemporal — **Packs are the lawful home for wave/tranche-shaped aggregates**, the - build backlog (`ready ∧ ¬implemented`) replaces the roadmap document, and dates live in git - and plans, never in descriptors. This passes the ADR three-part test (hard to reverse once - a corpus is authored against it; surprising — a delivery protocol with no schedule axis; a - real trade-off against restart-planning ergonomics) → candidate decision Spec - `spec:decisions.atemporal-planning` unless drafting shows the base already forced it, in - which case it is drift repair and rides the plan record only. - -2. **The Gherkin stance.** Proposed ruling: **the executable Gherkin corpus is not migrated.** - The 124 features stay exactly where they are as the verifier layer — anchors bind them, - `verifies` edges carry the linkage — and only the ~45 authored specs/PDRs are re-authored - as `.sdp.md`. No `.feature → .sdp.md` importer is built at this bar (one caller, and the - fleet path in S2 is cheaper than a parser). Named-and-deferred, not stubbed, exactly as - `bySymbol` is. - -3. **Design stubs.** The origin's 26 DS-stub files (compiling TS targeted at future paths via - `@architect-target`) have no Protocol home. Proposed disposition: their *content* value - lands in the carrying Spec's `design` section during S2 where still live, and the rest is - evidence for the edit-model design — not a new artifact type. Any stronger mechanism waits - for the write surface. - -4. **The enrichment lifecycle** (raised by the owner's value-transfer testimony, §(b)). The - Protocol rules what a Spec is *before* implementation (the floor) and what may never be - authored (delivery vocabulary), but is silent on what happens to a Spec's design-time - detail *after* its code ships — the content gen 1 classified as scaffold and deleted, and - whose deletion discipline kept that corpus lean and consumed rather than accumulated. The - Protocol's one-primitive answer ("enriched in place, never migrated") implies the honest - equivalent is **slimming in place**: post-implementation, design-time detail either - distills into the durable sections or is removed, with git holding the history. S0 does - not have to rule the full policy — the origin corpus is too young in Protocol terms — but - it must at least *scope* the question on the record (a named open question on the carrying - model Spec is the minimum), so the corpus-growth curve has an owner before S2 makes it - real. Content-quality judgment stays out of validators either way — checks police - conformance and honesty, never distillation. - -Two engine questions added at revision 2, forced by the executable-transition goal (§a-3) — -each is *named* at S0 and ruled only if drafting shows the base forces an answer; neither -authorizes speculative building: - -5. **What may `verification.mode: executable` honestly state?** Today the field states - nothing checkable. Candidate: a **warn-level signal** — a Spec stating - `mode: executable` whose examples carry no enabled verifier is *stating a verification - posture it has not realized*, the same honesty family as `honesty/gaps` (informative, - never a gate). Counter-position: the field is descriptive authoring intent and policing it - drifts toward content-quality. S0 decides which reading the base forces, or records the - question on the carrying validation Spec. - -6. **The anchored-but-unbound seam.** An example can be an enabled verifier (resolvable - `specTest` anchor) while no suite binds its generated contract — the verifier *exists* and - is structurally honest, but the step-contract drift protection silently does not apply. - Candidate: a warn-level signal or a recipe (W3 ships the recipe regardless; the signal is - ruled only if the recipe proves the join is one agents hand-roll wrong — the standing - freeze bar, applied to a validator). - -Also carried in S0, from plan 22 §5c: **the rule-6 bound point** on -`spec:extraction.build-pipeline` (its world now exists — `sdp q` goes through the one -extraction-and-validation seam; one point converts the strongest candidate the Spec has ever -had, and S0 is the natural session because it is this repo's first execution window in the -phase), and **the gate-leg question** for `check-self-hosting-gates.mjs` (a process change no -standing ruling authorizes; put to the owner at the S0 close as a yes/no, and wired into -`npm run check` only on a yes). - -### S0 execution disposition - -The three-part test was written before the carrying Specs changed: - -| Candidate | Hard to reverse | Surprising without context | Real trade-off | Disposition | -|---|---|---|---|---| -| Atemporal planning | no new choice remains after the ratified descriptors, git-event-log law, and release-as-tag projection | no — the base already says schedule is not a Spec coordinate | no remaining representation trade-off | **fails admission** — no decision Spec. Packs may group wave-shaped review sets but never order them; the build backlog expresses eligibility, not a roadmap | -| Verification posture | yes — authored meaning becomes durable across carriers and consumers | yes — `mode: executable` can otherwise be mistaken for a realized verifier | yes — intended posture versus realization-derived honesty signal | **passes** — add the verification-posture-not-realization decision and no validator | -| Records-gate wiring | yes for repository process, not product semantics | yes — the historical audit looked like a perpetual phase template | yes — gate coverage versus mandatory legacy ceremony | **internal process ruling** — freeze plan-17/18 checks, require only universal current-record invariants, and wire the generalized audit | - -Anchored-but-unbound detection is explicitly deferred: `bindExample` call sites are not graph data, -so neither a signal nor a recipe can compute the claim honestly. The rule-6 bound point is added, -but `spec:extraction.build-pipeline` remains `defined` because one point does not cover its ordered -workflow. The enrichment-lifecycle question enters at `scoped` and remains blocking. - -**S0 gate evidence.** The generalized records audit accepts the current plan without imposing the -historical ledger shape. A mutated plan-17 owner-packet SHA fails with the historical surface -named; a handbook changed from `plan 23 is EXECUTED` to the stale plan-22 status fails both current -status invariants. The query-seam point passes through the real CLI/extractor, and replacing its -validation subjects with an empty result reddens exactly that point. The complete thirteen-leg -Protocol gate passes with `spec:extraction.build-pipeline` still stated `defined`. - -## (e) W — the usage layer (this repo; execution sessions after S0) - -The workstream that makes the four usage goals deliverables instead of hopes. Everything in W -is graph-read guidance and authored corpus — no new engine surface, no write path, no gate. - -**W1 — spec-first self-application (the working ladder).** Phase 6's own intended -capabilities are authored as Specs *before* they are built, entering at the rung their -structure earns — expected: `idea` or `scoped` — and enriched in place as W proceeds: - -- the authoring on-ramp (candidate `spec:consumers.authoring-on-ramp`, `behavior` at - `feature`); -- the authoring-recipe additions (a clause or child on `spec:consumers.agent-surface`'s - recipe family, not a new namespace); -- the two candidate signals from §d-5/6, *if and only if* S0 rules them in (a ruled-out - signal gets no Spec — refusal, not decoration). - -Sessions in W and after it open with the build-backlog and drift-alarm recipes — the plan -file stays the session record, but **what** is owed lives in the graph. Acceptance: the -corpus contains its first honestly-stated `idea`/`scoped` Specs; each one appears in (or -honestly outside) the build backlog for a stated reason; not one is promoted past its -structure. The guard from §(c) applies with teeth: if a candidate has no genuine intended -truth, it is not authored — three real Specs beat eight ceremonial ones. - -**W2 — the authoring on-ramp skill.** A second Protocol-shipped skill beside the reading -skill — working name `.claude/skills/sdp-authoring/` — teaching the full lifecycle without -gating any of it: - -- **create**: the envelope, the ID grammar, the carrier (`.sdp.md` by default, MD-18), where - a new Spec enters (`idea`: id · title · kind · altitude · an outcome or a parent relation); -- **enrich**: what each rung's floor actually reads (the kind-evidence table), `scoped` → - `defined` as evidence-present → evidence-complete, the concreteness law for examples, the - open-questions home (MD-9), promotion vs inline (MD-10); -- **the executable transition**: example space on the parent (`gwt-vocabulary` fence) → - bound point on the child (`gwt` fence) → `sdp build` emits the step/space contracts → - `bindExample` + `specTest` side by side in the suite → **mutation-probe red before - promotion** (the process discipline, stated as discipline, never as a check); -- **anchor**: when a `codeAnchor` is owed (the code that realizes a Spec's truth), the - binding-only law (id · one target · label — anything else refuses), one binding per anchor; -- **state `ready`**: the floor cleared *plus* a human's declared statement, in Design Review - context; `ready` is never conferred by tooling; -- **query throughout**: every step above names the recipe that answers its question. - -The skill obeys the anti-anecdote rule (the derived graph outranks the skill) and draws on -gen 1's session-typed skills as evidence of shape, never as template. It is drafted in W, -then **revised from S1's friction log** — the origin tracer is its first foreign user. - -**W3 — the authoring recipes.** Three bodies extend the catalog from eight to eleven, each -executed verbatim by the recipe check and read-only over `g` / `graph` / `report`: - -1. **The promotion preflight** — for a Spec id: stated rung, floor reached, current floor - failures, and an explicit human-statement reminder. -2. **The declared-versus-enabled verifier audit** — keeps authored example relations distinct - from resolving graph-visible verifier bindings. -3. **The lower-ladder view** — every non-ready Spec grouped by family with its current first - graph-visible unmet clause. - -There is no query-time contract ledger: `contracts/*` findings are emitted only by `sdp build`. -There is no anchored-but-unbound recipe: `bindExample` call sites are not extracted graph data. -The authoring skill teaches both limits instead of shipping a body that cannot compute its claim. - -The freeze rule stands: these are recipes; a join moves into the `reader` only at the -second-caller bar with evidence agents hand-roll it wrong. - -**W4 — the friction instrument, widened.** One row per converted or authored Spec records source -and target ids/files; anchor, ladder, executable-transition, and CLI friction; and three measured -booleans — `leafAltitude`, `singleModule`, `under30Lines` — plus their combined inline-candidate -result and disposition. W rows begin below; S1 imports them verbatim into the origin ledger with -`sourceRepo`, and that file becomes canonical. S2 reports numerator, denominator, and percentage -without turning the result into a carrier ruling. - -| sourceRepo | source id/file | target id/file | anchor friction | ladder friction | executable-transition friction | CLI friction | leafAltitude | singleModule | under30Lines | inline candidate | disposition | -|---|---|---|---|---|---|---|---:|---:|---:|---:|---| -| Protocol | `specs/consumers/authoring-on-ramp.sdp.md` | `spec:consumers.authoring-on-ramp` / same file | a Markdown skill cannot carry an extracted anchor, so validating code owns the binding | entered low and matured only after the shipped asset and verifier existed | taught, not itself a bound example | the first fresh-context run invoked macOS `/usr/bin/sdp`; the skill now requires the repository-local CLI or an adopter package runner, and root-specific versus portable invocations have separate checks | false | true | true | false | keep standalone: feature-sized workflow | -| Protocol | `specs/consumers/agent-surface.authoring-recipes.sdp.md` | `spec:consumers.agent-surface.authoring-recipes` / same file | in-tree recipe/skill checks own both bindings | matured after all three bodies returned complete graph-derived sets | not applicable | portable wording initially competed with mandatory exclusions; parameterized tests resolved it | true | false | true | false | keep standalone: multiple recipe and test modules | -| Protocol | `specs/consumers/intent-composition.sdp.md` | `spec:consumers.intent-composition` / same file | none while the realizing surface is absent | deliberately states `idea` even though its minimal structure clears higher floors | deferred with the absent surface | none | true | true | true | true | measure only; no carrier ruling follows | - -**W close evidence (2026-07-28).** A fresh, ephemeral, read-only `codex exec` context followed -the corrected authoring skill and reported `spec:model.enrichment-lifecycle` as stated -`scoped`, floor `scoped`, blocked by its authored post-implementation slimming question, with -`spec:model.enrichment-lifecycle` as the carrier. The first attempt had resolved bare `sdp` to -macOS `/usr/bin/sdp`; that observed friction produced the repository-local/package-runner rule -now checked for both shipped skills. The package smoke test additionally installs the packed -snapshot and reads both exact skill paths from the installed package. The S1 fresh-context run -then found that a copied skill could not resolve the repository-root recipe path from an adopter; -both skills now name the Protocol-root and installed-package catalog paths explicitly, and the -in-tree check pins both forms. - -## (f) S1 — the tracer bullet on foreign soil (origin repo) - -Hand-convert one coherent cluster — proposed: `production-hardening` (the largest authored -spec, 26.8 KB, `roadmap`, with its 7-file executable directory of planning stubs) plus 2–4 of -its neighbors — into `.sdp.md` under an `sdp/`-rooted corpus in the origin repo, bind anchors -to the existing code and the existing `.feature` verifiers, and run the full recipe catalog -over the derived graph. **Tracer-bullet discipline applies unchanged:** if the origin content -stops extracting or validating, fix the carrier or extractor here — never fork the grammar in -the consumer. - -Deliverables: the converted cluster extracting at 0 errors; all eleven recipes returning honest -answers about it (the 17 planning stubs must surface as -declared-not-enabled, not as coverage); the friction log per W4. **Usage acceptance, added at -revision 2:** the converted cluster spans **all four rungs** honestly (the origin's four-tier -ladder guarantees candidates for each — no rung is manufactured, and none is skipped by -convenience-rounding everything to `defined`); **at least one end-to-end executable -transition** lands on foreign soil — one example carried from prose to an enabled, -contract-bound, mutation-probed verifier through the W2 workflow, with the origin's -vitest-cucumber suite untouched beside it; and the W2 skill is exercised as written, with -every divergence logged as a skill bug or a friction entry, never silently absorbed. - -## (g) S2 — the fleet backfill (origin repo) - -Convert the remaining authored surface (~17 specs, 23 PDRs, 4 release features → Pack/plan -dispositions per S0) using the gen-1 annotation-fleet playbook (parallel agents, small -batches, per-batch verification against the derived graph — the origin's own fleet measured -92% and 21/21 pass rates on comparable work). Every converted Spec enters at the readiness -its structure earns and not one rung higher; `@architect-status:completed` claims are checked -against resolving verifiers before any `ready` statement. Expected outcome: an origin graph -of roughly 150–300 nodes — the first corpus at 2–3× self-hosting scale, which is also the -first real test of `sdp q`'s derive-per-invocation latency (S4 measures it). - -## (h) S3 — the restart plan as the first external backlog (origin repo) - -Model the origin's own restart sequencing (its J5 strawman: green rails → phase-debt -disposition → Agent-BC vertical → tranche-0 hardening → vision work) as Specs with honest -readiness — **expected to enter overwhelmingly at `idea` and `scoped`**, which is the point: -this is the lower ladder doing the job it was designed for, at production scale, one -workstream after W1 rehearsed it inward. The deliverable is the origin's build backlog and -drift alarm answering from the derived graph — the exact questions its re-entry journey -documents were hand-written to approximate, now live instead of rotting. New work authored -during the restart follows the W2 workflow end-to-end (spec-first, enrich, transition, -anchor), which makes S3 the standing test of whether the on-ramp teaches or merely describes. -This stage is where the stalled project's "waiting for a new solution" ends operationally: -the restart plan is *in* the Protocol. - -## (i) S4 — the measurement, done honestly this time (origin repo) - -The phase-5 refusal named its own missing instrument: token accounting over two real agent -sessions. The origin provides the honest setting — a year-paused repo with a hand-authored -re-entry corpus as the control arm. Design: matched cold-start sessions on the same re-entry -questions (J1/J3-shaped: "what is real vs aspirational," "what breaks if I change X"), one arm -on journeys + grep, one arm on the skill + `sdp q`, with per-session token totals captured -from the harness. Also measured: extraction latency at S2 scale. Both numbers publish with -their design stated, or the attempt is refused with its numbers recorded — the phase-5 -precedent is the standard. - -## (j) What this plan does not do - -- **No write surface.** The edit-model's fourth rule still refuses; W's and S1/S2's friction - log is the evidence its design has been waiting for, and the design session is the - successor's. -- **No Gherkin importer, no `bySymbol`, no impact graph** — each named-and-deferred at the - second-caller bar, with S2/S4 expected to produce the demand signal that either converts or - retires them. -- **No new validators without an S0 ruling.** §d-5/6 name two candidate warn-level signals; - neither is built unless S0 finds the base forces it — W3's recipes carry the joins either - way, and a recipe that proves sufficient *retires* its signal candidate. -- **No inline carrier — yet, and named rather than silent.** An in-code (JSDoc/comment-block) - Spec carrier is a **recognized candidate**, deferred to a carrier-competition round gated on - S2's friction evidence. The demand hypothesis: leaf-altitude, single-module Specs (a rule of - one decider, an NFR of one store) fight the separate-file carrier hardest, and the origin's - fleet results (92%, 21/21) were achieved *because* its carrier was inline. The instrument is - W4's first tally: each converted Spec logged as leaf-altitude/single-module/under-30-lines - or not, so the carrier-competition gate reads a measured population, not an impression. If - that population is large, the response is a competition round in the MD-18 style — same - envelope statically extractable, loud refusals (no bare-marker invisible nodes), parity - fixtures against the Markdown form, duplicate-id policing the two-carriers-one-ID case, and - **no delivery vocabulary inline** (the reserved-property refusal applies unchanged). Judged - on exhibits, not on gen-1 nostalgia and not on this plan's prediction. -- **No new projections authored on speculation.** The owner's testimony (§(b)) predicts the - restart will demand generated views beyond the Design Review (a roadmap/current-work - surface, most likely); when a second machine consumer materializes, the standing - recipe-to-projection bar converts it — never a pre-emptive generator suite, and never a - committed artifact answering in the graph's name. -- **No session-typed workflow layer beyond the on-ramp.** Revision 1 deferred the whole - authoring-workflow question; revision 2 pulls in exactly one artifact — the W2 skill, - guidance that teaches without gating. Gen 1's full session taxonomy - (plan / design / implement / review-spec / review-implementation / handoff) stays evidence; - whether more skills are owed is a question W4's log answers for a successor, not this plan. -- **No inward readiness sweep.** The 35 standing refusals stand; nothing here promotes them. - (W1 *adds* low-rung Specs; it promotes nothing existing.) - -## (k) Sequencing and session shape - -**S0** is one session in this repo (rulings §d-1..6 + the rule-6 point + the gate-leg -question), with one pre-close review sized to its diff. **W** follows in this repo — W1+W3 -land together (the backlog Specs and the recipes that read them), W2 lands against them, W4 -opens as a running log; one review at the W close, sized to its diff. **S1–S4** execute in the -origin repo as its own plan family (the origin carries its own plans; this repo's record -tracks only what changes here) — S1 gates S2; S2 gates S3 and S4's latency arm; S4's token arm -can run any time after S1. W2's skill is revised from S1's friction log as a small follow-up -session here. - -### Phase-6 close record - -- **S0:** the records audit is a required universal gate with frozen plan-17/18 history checks; - the verification-posture decision is ratified; the build-pipeline rule-6 point is bound and - mutation-probed while its parent remains `defined`; anchored-but-unbound detection stays - explicitly deferred. -- **W:** both single-file skills ship in the exact package contract; eleven recipes execute - verbatim; fresh-context forward tests corrected the macOS binary collision and adopter catalog - path; the package tarball contains no Protocol corpus. -- **S1:** origin commit `59827121d` installs the package snapshot and lands the green foreign - tracer. The reactive-projection point calls the production API through its generated contract, - reddens under mutation, and re-earns `ready` without changing vitest-cucumber. -- **S2:** origin commit `dad63f1f4` freezes the historical 1,319-row parity population before - deletion; the post-close adversarial sweep expands the integrity index to 1,627 rows. - `0c3d79d0d` performs the selected atomic deletion pass: “atomic” names the committed - parity-checkpoint → deletion boundary, not a claim that later review could discover no missed - reference. The measured inline-carrier population is - **1 / 57 (1.75%)**, recorded without a carrier ruling. -- **S3:** origin commit `ad02746a2` adds `pack:restart-horizon`, scoped test-reporting intent, - and idea-level dynamic-projection and generative-UI intent without encoding schedule. -- **S4:** origin commit `e698e0a2a` retains matched raw logs, structured outputs, hashes, rubric, - and latency. Treatment scores 12/12 versus control 11/12 and uses 987,559 versus 3,671,422 - gross completion-event tokens (**73.1% fewer**) and 113,575 versus 183,166 non-cached - input-plus-output tokens (**38.0% fewer**) without worse quality. Ten fresh derivations measure - **432.27 ms median / 444.19 ms p95** after one warm-up. -- **Close audit:** both repositories pass their complete gates from detached clean worktrees; - the origin frozen installs reproduce from both workspace views, all existing suites pass, the - graph has zero findings, and active-surface sweeps find no predecessor dependency, hook, CI leg, - carrier, annotation, or committed generated projection. - -Phase debt disposition and execution order remain in this record, not in ceremonial Specs. The -origin now runs under the Protocol it adopted. - -### Post-close implementation-review corrections - -The grounded review found four correctness/honesty defects and four Protocol hygiene defects. The -fix session re-measured and dispositioned all of them: - -- Origin test entry points now generate Protocol contracts before any unit/watch/coverage suite - that imports the bound contract; the PR template names the complete `pnpm sdp:check` gate. -- A second SHA-pinned cutover population, expanded by the final-pass audit, adds 308 exact rows for - root support, MCP, review, agent/command/plan, docs, inbox evidence, and the retired handbook - package. The manifest now has **1,627** rows; competing project-management and re-entry status - systems are deleted, retained technical docs are explicitly classified, and a mutation-tested - guard prevents active predecessor paths or instructions from returning. -- Four tranche-shaped IDs are renamed to capability names. Six schedule-shaped `dependsOn` edges - and ordering clauses are removed; the only surviving dependency in the repaired cluster is the - real security-to-authentication dependency. No Spec is promoted. -- The measurement publishes an addendum: 73.1% is gross and includes cached input; non-cached - input plus output falls 38.0%. Raw logs prove usage/cache splits but do not independently carry - the model string or control SHA. The reviewed phase-close count is 11,936 Vitest assertions plus - 8 script assertions, **11,944 total**. The first corrective gate adds two retired-surface - assertions and reaches 11,946; final-pass removal of the dead 385-assertion handbook package - leaves the active gate at **11,561 total**. -- The shipped reading skill separates adopter invocation from the Protocol source checkout, - names all eleven recipes, and the repository map includes the authoring skill. -- The records-gate mutation test discovers the current primary plan with the same convention as - the guard rather than pinning plan 23. -- `spec:model.anchors` now records the narrow document-realization convention used by the two - test-file implementation anchors, whose labels explicitly name the asserted document artifacts. -- The origin close record states the verifier batch denominator (nine of 127 executable step files - plus the bound tracer), the stricter local `ready` discipline, and the manifest/ledger evidence - boundary. Ignored cutover residue moved from `/tmp` to a durable recovery directory. -- The corrected 16-file package snapshot is pinned at SHA-256 - `7c5a176f658b9d477a324ac0017fd16e66eb04617db675e01cc1c9482512eb76`; the original S1 snapshot - remains immutable historical evidence. - -The Protocol audit still reports eight development-dependency advisories (3 moderate, 4 high, -1 critical). The actionable aggregate fix is a Vitest 2→4 major migration; it is recorded rather -than silently folded into this review repair because it changes the test runner and does not affect -the shipped runtime surface. diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md index 157ee73..825bf21 100644 --- a/plans/24-inward-turn-self-hosting-practice.md +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -1,6 +1,6 @@ # Plan 24 — The inward turn: self-hosting as the standing practice -> **Status:** EXECUTING — revision 3. Phase 7 is the inward turn: the Protocol's own forward +> **Status:** EXECUTED — revision 3. Phase 7 is the inward turn: the Protocol's own forward > work is carried as Specs, sessions open from graph recipes, implementation bindings are > audited rather than assumed, expected-outcome oracles bind inward, and one real engine slice > runs the complete spec-first loop through post-implementation slimming. Settled phase-6 @@ -183,15 +183,42 @@ Close requires: every backlog and drift row deliberate; two inward `models` edge spec-first engine slice slimmed after implementation; the friction ledger complete; zero findings; no readiness sweep; and plan/AGENTS status synchronized. -The owner authorized deletion of the superseded plan-23 file after plan 24 lands. Before deletion, -replace this plan's and AGENTS' live plan-23 pointers with carrying Specs, commit evidence, and PR -references; verify no live link depends on the file. Historical evidence remains in git and origin -PR #181. +The owner authorized deletion of the superseded phase-6 execution plan after this plan landed. +Before deletion, replace live pointers with carrying Specs, commit evidence, and PR references; +verify no live link depends on the file. Historical evidence remains in git and origin PR #181. Execution expands PR #15. At close its title becomes `feat: make self-hosting the standing delivery practice (phases 6–7)` and its body names both the phase-6 usage layer and phase-7 inward evidence. No origin-repo work occurs. -After the phase closes, Vitest 2→4 runs as a separate mechanical commit. The session inspects the -actual v4 configuration and help before replacing the removed single-fork option, changes no -Specs or graph semantics, re-measures advisories, and stops if derived graph output moves. +## Close record + +The inward phase closed on 2026-07-28 with: + +- 128 Specs · 1 Pack · 116 anchors → 245 nodes · 462 edges, zero findings; +- stated readiness `ready: 86 / defined: 37 / scoped: 1 / idea: 4`; +- delivery facts `implemented: 41 / has-verifier: 87 / observed: 0`; +- recipe 1 returning zero operational backlog rows, with 53 excluded ready examples and none + lacking a verifier; +- the same eight freshly argued drift rows, each at `defined` with floor `ready`; +- exactly two inward `models` edges: + `oracle:protocol.reader-entry-map → spec:consumers.reader` and + `oracle:protocol.oracle-target-eligibility → spec:validation.oracle-target-eligibility`; +- no repeated friction category, so neither agent skill needed a speculative revision. + +The genuine enrichment datum is the sequence `019d063` (implementing Design), +`341ead6` (implementation and executable seams), and `7ae8087` (post-green slimming). +The final Spec retains the observable eligibility, refusal, uniqueness, and shared-consumer laws; +only symbol placement, implementation sequencing, and test-placement scaffolding left. One datum +does not establish a general enrichment lifecycle, so `spec:model.enrichment-lifecycle` remains +`scoped`. + +The owner explicitly authorized deletion of the superseded phase-6 plan after this plan landed. +Before deletion, live references were replaced by carrying Specs, commits `b6f123c` through +`7ae8087`, PR #15, and origin PR `libar-ai/convex-event-sourcing#181`; a repository-wide search +found no remaining live dependency on that file. Its evidence remains recoverable from git. + +The phase-close thirteen-leg gate passed over the committed checkpoints. A separate clean-worktree +run is recorded after the close commit. Vitest 2→4 follows as a separate mechanical commit: inspect +the installed v4 help and actual configuration, change no Specs or graph semantics, re-measure +advisories, and stop if derived graph output moves. From f8a50bb868ee157afeb538ba236b8457ff489d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 22:00:58 +0200 Subject: [PATCH 14/24] chore(test): migrate Vitest 2 to 4 --- package-lock.json | 1550 ++++++++++++++++++++++++++------------------- package.json | 2 +- test/ids.test.ts | 10 +- vitest-test.mjs | 4 +- 4 files changed, 905 insertions(+), 661 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3043a70..dbbed52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "tsup": "^8.0.0", "typescript": "^5.0.0", "typescript-eslint": "^8.0.0", - "vitest": "^2.0.0" + "vitest": "^4.1.10" }, "engines": { "node": ">=20" @@ -38,6 +38,43 @@ } } }, + "node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -742,6 +779,336 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.0.tgz", + "integrity": "sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^2.0.0-alpha.3", + "@emnapi/runtime": "^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", @@ -1092,6 +1459,13 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@ts-morph/common": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.29.0.tgz", @@ -1139,6 +1513,35 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1446,38 +1849,40 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.9", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -1489,84 +1894,68 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1707,18 +2096,11 @@ } }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -1740,16 +2122,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1826,6 +2198,13 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1859,16 +2238,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1876,10 +2245,20 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, @@ -2409,6 +2788,267 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -2462,13 +3102,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2525,9 +3158,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -2560,6 +3193,20 @@ "node": ">=0.10.0" } }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2656,16 +3303,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2674,9 +3311,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -2708,9 +3345,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.24.tgz", + "integrity": "sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==", "dev": true, "funding": [ { @@ -2728,7 +3365,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2839,6 +3476,40 @@ "node": ">=4" } }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, "node_modules/rollup": { "version": "4.61.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", @@ -2955,9 +3626,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, @@ -3063,30 +3734,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -3133,6 +3784,14 @@ "code-block-writer": "^13.0.3" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tsup": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", @@ -3272,21 +3931,23 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -3295,23 +3956,33 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -3328,522 +3999,89 @@ }, "terser": { "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-node/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, + "@opentelemetry/api": { + "optional": true + }, "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { "optional": true }, "@vitest/ui": { @@ -3854,15 +4092,21 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, - "node_modules/vitest/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "node_modules/vitest/node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/which": { "version": "2.0.2", diff --git a/package.json b/package.json index 47c68b6..53f9d65 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "tsup": "^8.0.0", "typescript": "^5.0.0", "typescript-eslint": "^8.0.0", - "vitest": "^2.0.0" + "vitest": "^4.1.10" }, "peerDependencies": { "vitest": ">=2" diff --git a/test/ids.test.ts b/test/ids.test.ts index da70c6b..5bfec6a 100644 --- a/test/ids.test.ts +++ b/test/ids.test.ts @@ -59,16 +59,16 @@ describe("ids", () => { }); it.each(invalidIds)("rejects malformed IDs: %s", (value) => { - expect(() => parseId(value)).toThrowError(value); + expect(() => parseId(value)).toThrow(value); }); it("rejects wrong namespaces in helper branding", () => { - expect(() => specId("pack:checkout-v1")).toThrowError('expected namespace "spec"'); - expect(() => packId("spec:orders.create-order")).toThrowError('expected namespace "pack"'); - expect(() => codeAnchorId("test:orders.create-order.valid-cart")).toThrowError( + expect(() => specId("pack:checkout-v1")).toThrow('expected namespace "spec"'); + expect(() => packId("spec:orders.create-order")).toThrow('expected namespace "pack"'); + expect(() => codeAnchorId("test:orders.create-order.valid-cart")).toThrow( 'expected one of the namespaces "impl" · "api" · "component"', ); - expect(() => testAnchorId("impl:orders.create-order-use-case")).toThrowError( + expect(() => testAnchorId("impl:orders.create-order-use-case")).toThrow( 'expected namespace "test"', ); }); diff --git a/vitest-test.mjs b/vitest-test.mjs index 35636ab..25206a4 100644 --- a/vitest-test.mjs +++ b/vitest-test.mjs @@ -139,7 +139,7 @@ if (hasPathFilter) { } process.exit( - runVitest(["--run", cliTestPath, "--pool", "forks", "--poolOptions.forks.singleFork"]), + runVitest(["--run", cliTestPath, "--pool", "forks", "--maxWorkers", "1", "--no-isolate"]), ); } @@ -152,5 +152,5 @@ if (parallelExitCode !== 0) { } process.exit( - runVitest(["--run", cliTestPath, "--pool", "forks", "--poolOptions.forks.singleFork"]), + runVitest(["--run", cliTestPath, "--pool", "forks", "--maxWorkers", "1", "--no-isolate"]), ); From 191cf49b29a950b5a413c69359a8c17cf6c51602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 22:05:10 +0200 Subject: [PATCH 15/24] fix(test): register inward oracle contract consumers --- contract-dependent-suites.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contract-dependent-suites.mjs b/contract-dependent-suites.mjs index c132b9c..74e3994 100644 --- a/contract-dependent-suites.mjs +++ b/contract-dependent-suites.mjs @@ -20,12 +20,16 @@ export const contractDependentSuites = [ generation: "npm run generate:self-hosting", testPaths: [ "test/self-hosting-carrier.test.ts", + "test/self-hosting-consumers-oracle.test.ts", + "test/self-hosting-consumers.oracle.ts", "test/self-hosting-consumers.test.ts", "test/self-hosting-duplicate-ids.test.ts", "test/self-hosting-extraction.test.ts", "test/self-hosting-model.test.ts", "test/self-hosting-projections.test.ts", "test/self-hosting-sdp-import.test.ts", + "test/self-hosting-validators-oracle.test.ts", + "test/self-hosting-validators.oracle.ts", "test/self-hosting-validators.test.ts", ], }, From fe42233ecefb6aa586a1ee4443dacd81b87b8073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 22:06:02 +0200 Subject: [PATCH 16/24] fix(lint): allow clean-room generated returns --- eslint.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.js b/eslint.config.js index 01a12da..e71c5f1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -65,6 +65,7 @@ export default tseslint.config( "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", }, }, { From c060f05d050df2115d5acb6ab0c3da2de72728a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 22:32:17 +0200 Subject: [PATCH 17/24] fix(skills): make .agents the canonical skill home --- .../skills/sdp-agent-surface/SKILL.md | 0 .../skills/sdp-authoring/SKILL.md | 3 +- .claude/skills | 1 + AGENTS.md | 2 +- check-temporal.mjs | 24 +++++++++++- package.json | 4 +- plans/24-inward-turn-self-hosting-practice.md | 25 +++++++++--- test/check-temporal.test.ts | 38 ++++++++++++++++++- test/package-smoke.test.ts | 8 ++-- test/recipes.test.ts | 4 +- test/skills.test.ts | 22 +++++++---- 11 files changed, 107 insertions(+), 24 deletions(-) rename {.claude => .agents}/skills/sdp-agent-surface/SKILL.md (100%) rename {.claude => .agents}/skills/sdp-authoring/SKILL.md (96%) create mode 120000 .claude/skills diff --git a/.claude/skills/sdp-agent-surface/SKILL.md b/.agents/skills/sdp-agent-surface/SKILL.md similarity index 100% rename from .claude/skills/sdp-agent-surface/SKILL.md rename to .agents/skills/sdp-agent-surface/SKILL.md diff --git a/.claude/skills/sdp-authoring/SKILL.md b/.agents/skills/sdp-authoring/SKILL.md similarity index 96% rename from .claude/skills/sdp-authoring/SKILL.md rename to .agents/skills/sdp-authoring/SKILL.md index e8ef3c0..2a511fd 100644 --- a/.claude/skills/sdp-authoring/SKILL.md +++ b/.agents/skills/sdp-authoring/SKILL.md @@ -56,7 +56,8 @@ unrelated binary with that name. Adopters should use their chosen package runner codegen findings. 4. In the verifier suite, colocate `bindExample(generatedContract, world, bindings)` with a - `specTest` anchor targeting that example. + `specTest` anchor targeting that example. In this repository, registering every suite that + imports a generated contract in `contract-dependent-suites.mjs` is part of binding. 5. Mutate one expected result and prove the new point goes red, then restore it. Keep runner execution and pass state outside the graph. diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000..2b7a412 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index bb8337f..d745e8e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,7 +58,7 @@ Progressive disclosure — start at the top, follow the pointers down. | `docs/concept/` (+ README) | **the technical design** — the surviving principle-led docs: vision & MVP boundary, founding principles (P1–P10), authoring & binding, consumers, roadmap; the core model dissolved into the model Specs (`spec:model.core-model`, `spec:model.spec-sections`, `spec:model.relations`, `spec:model.stable-ids`, `spec:model.pack-aggregate`), the one graph into the extraction Specs (`spec:extraction.derive-graph`, `spec:extraction.determinism`, `spec:extraction.claim-taxonomy`, `spec:extraction.regenerability`, `spec:extraction.schema-versioning`), and validation & honesty into the validation Specs (`spec:validation.two-check-families`, `spec:validation.readiness-floor`, `spec:validation.kind-evidence`, `spec:validation.warn-level-signals` and their siblings) | to know *how* it is designed | | `docs/concept/DECISIONS.md` | **the lean decision registry** — ratified names, one-line glosses, carrying Specs, and the D1–D6 lookup; historical rationale lives in git, plans, and the Specs themselves | when resolving a decision name or following its canonical pointer | | `src/` | **the engine** — `model` (Spec/descriptors/pack/anchors) · `extract` · `graph` · `validate` · `reader` (agent surface) · `projections` (Design Review) · `cli` (`sdp build` · `validate` · `view` · `import` · `q`) · `runner` / `codegen` / `notation` / `adapters` | when implementing or verifying **current engine** behavior | -| `.claude/skills/sdp-agent-surface/` + `.claude/skills/sdp-authoring/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — the reading and authoring skills, plus eleven runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals · promotion preflight · declared/enabled verifier audit · lower ladder); every body is executed as written by `test/recipes.test.ts` | before answering a corpus question or authoring intent — query the graph, then follow the carrying Specs | +| `.agents/skills/sdp-agent-surface/` + `.agents/skills/sdp-authoring/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — repository-owned reading and authoring skills (also exposed to Claude through the `.claude/skills` symlink), plus eleven runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals · promotion preflight · declared/enabled verifier audit · lower ladder); every body is executed as written by `test/recipes.test.ts` | before answering a corpus question or authoring intent — query the graph, then follow the carrying Specs | | `examples/checkout-v1` | **the worked MVP example** (TS DSL tracer bullet) — specs, anchors, untracked `generated/` (regenerated in-pipeline); walkthrough in its README | when proving the loop end-to-end | | `explorations/` | **evidence only** (carrier exhibits, executable-example findings) — mapping evidence for design; **never promote spike code into product** | when judging design evidence; not a source tree to ship | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code — highest primary-numbered plan's status header, plus active subplans it designates; if DRAFTED, also the latest ✅ EXECUTED/RUN plan | diff --git a/check-temporal.mjs b/check-temporal.mjs index 8d27a49..cf71129 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -1,5 +1,6 @@ import { spawnSync } from "node:child_process"; -import { readFileSync } from "node:fs"; +import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs"; +import { isAbsolute, relative, resolve, sep } from "node:path"; // The temporal-token guard: durable artifacts carry current truth, so calendar and session tokens // (session/wave/fold handles, ISO dates, numbered plan-file refs) are banned from every delivery @@ -46,6 +47,7 @@ if (enumerated.error !== undefined || enumerated.status !== 0) { } const paths = enumerated.stdout.split("\0").filter((path) => path !== "" && !isExcluded(path)); +const repositoryRoot = realpathSync(resolve(".")); // This file is swept like every other; its single allowance is line-level use–mention: the guard // must name the tokens it bans, so exactly one line — the pattern literal, alone on its line — is @@ -60,6 +62,26 @@ for (const path of paths) { let source; try { + if (lstatSync(path).isSymbolicLink()) { + const target = realpathSync(path); + const targetFromRoot = relative(repositoryRoot, target); + const escapesRepository = + isAbsolute(targetFromRoot) || + targetFromRoot === ".." || + targetFromRoot.startsWith(`..${sep}`); + + if (escapesRepository) { + fail(`symlink ${path} escapes the repository`, target); + } + + // Git enumerates a tracked directory symlink as one entry. Its canonical in-repository files + // are enumerated independently, so reading the directory as text would both duplicate the + // sweep and fail with EISDIR. + if (statSync(path).isDirectory()) { + continue; + } + } + source = readFileSync(path, "utf8"); } catch (error) { fail(`could not read ${path}`, error instanceof Error ? error.message : String(error)); diff --git a/package.json b/package.json index 53f9d65..1b07296 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "files": [ "dist/**", "docs/agent-surface/recipes.md", - ".claude/skills/sdp-agent-surface/SKILL.md", - ".claude/skills/sdp-authoring/SKILL.md" + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md" ], "bin": { "sdp": "./dist/cli/sdp.js" diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md index 825bf21..c734fff 100644 --- a/plans/24-inward-turn-self-hosting-practice.md +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -156,7 +156,7 @@ changes only when the same friction recurs twice. | `spec:observation.runtime-overlay` | none observed | blocking producer boundary keeps `idea` honest | none observed | none observed | | `spec:consumers.impact-graph` | none observed | language-neutral identity question keeps `idea` honest | none observed | none observed | | `spec:carrier.markdown-pack-authoring` | none observed | unresolved syntax keeps `idea` honest | none observed | none observed | -| `spec:validation.oracle-target-eligibility` | one shared-predicate anchor; none observed | Design resolved the blocker; fresh statement plus floor/Review earned `ready` | two generated points bound without friction | lower ladder confirmed the preselected slice; no competing evidence | +| `spec:validation.oracle-target-eligibility` | one shared-predicate anchor; none observed | Design resolved the blocker; fresh statement plus floor/Review earned `ready` | two generated points bound; post-close clean-room verification exposed omitted suite registration | lower ladder confirmed the preselected slice; no competing evidence | | `spec:carrier.sdp-import` | concrete entrypoint; no friction | already ready | existing point; no friction | backlog recipe led directly to the missing binding | | `spec:consumers.binding-language-views` | three honest sites required | already ready | existing points; no friction | guarantee recipe named all renderers | | `spec:consumers.derived-readiness-banner` | one honest site | already ready | existing points; no friction | none observed | @@ -204,7 +204,8 @@ The inward phase closed on 2026-07-28 with: - exactly two inward `models` edges: `oracle:protocol.reader-entry-map → spec:consumers.reader` and `oracle:protocol.oracle-target-eligibility → spec:validation.oracle-target-eligibility`; -- no repeated friction category, so neither agent skill needed a speculative revision. +- no friction category repeated within the inward phase at close, so neither agent skill received a + speculative revision before the clean-room follow-up described below. The genuine enrichment datum is the sequence `019d063` (implementing Design), `341ead6` (implementation and executable seams), and `7ae8087` (post-green slimming). @@ -219,6 +220,20 @@ Before deletion, live references were replaced by carrying Specs, commits `b6f12 found no remaining live dependency on that file. Its evidence remains recoverable from git. The phase-close thirteen-leg gate passed over the committed checkpoints. A separate clean-worktree -run is recorded after the close commit. Vitest 2→4 follows as a separate mechanical commit: inspect -the installed v4 help and actual configuration, change no Specs or graph semantics, re-measure -advisories, and stop if derived graph output moves. +run is recorded after the close commit. Vitest 2→4 followed as a separate mechanical commit: +`f8a50bb`; it changed no Specs or graph semantics and left derived graph output unchanged. + +Post-close clean-install CI then exposed that the four new oracle suites imported generated +contracts without appearing in the shared contract-dependent suite registry. Commit `191cf49` +registered those suites, and `fe42233` completed the matching clean-room lint allowance. This was +the second occurrence of that binding-friction class across the Protocol and origin adoption, so +the friction-ledger trigger earned a non-speculative revision to the authoring skill: registering a +generated-contract consumer is now an explicit part of binding. The exact `npm ci && npm run check` +path passed after both fixes. + +The review follow-up also made `.agents/skills/` the repository-owned and packaged canonical skill +location. Claude resolves the same files through the relative `.claude/skills → ../.agents/skills` +symlink, so supporting both agent conventions creates no second skill copy and depends on no +user-level installation. The temporal sweep accepts that tracked in-repository directory symlink, +continues to scan the canonical tracked files, and fails closed when a symlink escapes the +repository. diff --git a/test/check-temporal.test.ts b/test/check-temporal.test.ts index 784013b..4d08692 100644 --- a/test/check-temporal.test.ts +++ b/test/check-temporal.test.ts @@ -1,4 +1,12 @@ -import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { + copyFileSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -98,6 +106,34 @@ describe("check:temporal", () => { }); }); + it("accepts an in-repository directory symlink when its canonical files are tracked", () => { + withRoot((root) => { + mkdirSync(join(root, ".agents", "skills", "example"), { recursive: true }); + mkdirSync(join(root, ".claude"), { recursive: true }); + writeFileSync( + join(root, ".agents", "skills", "example", "SKILL.md"), + "repository-owned skill", + "utf8", + ); + symlinkSync("../.agents/skills", join(root, ".claude", "skills")); + runGit(root, ["add", ".agents/skills/example/SKILL.md", ".claude/skills"]); + + expect(runGuard(root).status).toBe(0); + }); + }); + + it("fails closed when a tracked symlink escapes the repository", () => { + withRoot((root) => { + symlinkSync(tmpdir(), join(root, "external")); + runGit(root, ["add", "external"]); + + const result = runGuard(root); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("symlink external escapes the repository"); + }); + }); + it("keeps each explicit temporal genre exclusion", () => { withRoot((root) => { writeExcludedFile(root, "docs/concept/DECISIONS.md"); diff --git a/test/package-smoke.test.ts b/test/package-smoke.test.ts index 0ef3cf6..f12ceae 100644 --- a/test/package-smoke.test.ts +++ b/test/package-smoke.test.ts @@ -151,8 +151,8 @@ describe("published package surface", () => { ) as readonly [{ readonly files: readonly { readonly path: string }[] }]; const dryRunPaths = dryRun[0].files.map((file) => file.path); const adopterAssets = [ - ".claude/skills/sdp-agent-surface/SKILL.md", - ".claude/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", "docs/agent-surface/recipes.md", ]; @@ -241,7 +241,7 @@ void [${expectedRootExports.join(", ")}]; "node_modules", "@libar-dev", "software-delivery-protocol", - ".claude/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-agent-surface/SKILL.md", ), "utf8", ); @@ -251,7 +251,7 @@ void [${expectedRootExports.join(", ")}]; "node_modules", "@libar-dev", "software-delivery-protocol", - ".claude/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", ), "utf8", ); diff --git a/test/recipes.test.ts b/test/recipes.test.ts index 4823470..c41c784 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -194,8 +194,8 @@ describe("the agent-surface recipe corpus", () => { it("keeps self-hosting and adopter invocation forms distinct", () => { const onRampSources = [ recipesPath, - ".claude/skills/sdp-agent-surface/SKILL.md", - ".claude/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", ]; for (const source of onRampSources) { diff --git a/test/skills.test.ts b/test/skills.test.ts index c78026c..0309f7a 100644 --- a/test/skills.test.ts +++ b/test/skills.test.ts @@ -1,4 +1,4 @@ -import { readFileSync } from "node:fs"; +import { lstatSync, readFileSync, readlinkSync } from "node:fs"; import { basename, dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -15,8 +15,8 @@ import { const repoRoot = fileURLToPath(new URL("..", import.meta.url)); const skillPaths = [ - ".claude/skills/sdp-agent-surface/SKILL.md", - ".claude/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", ] as const; const authoringOnRampImplementationAnchor = codeAnchor({ @@ -87,6 +87,13 @@ const authoringRecipesTestAnchor = specTest({ void authoringRecipesTestAnchor; describe("Protocol skill assets", () => { + it("owns skills under .agents and exposes them to Claude through one relative symlink", () => { + const claudeSkills = join(repoRoot, ".claude", "skills"); + + expect(lstatSync(claudeSkills).isSymbolicLink()).toBe(true); + expect(readlinkSync(claudeSkills)).toBe("../.agents/skills"); + }); + it("uses the repository's two-field single-file convention", () => { for (const path of skillPaths) { const skill = readSkill(path); @@ -111,7 +118,7 @@ describe("Protocol skill assets", () => { expect(source).toContain("spec:"); } - const authoring = readSkill(".claude/skills/sdp-authoring/SKILL.md").source; + const authoring = readSkill(".agents/skills/sdp-authoring/SKILL.md").source; for (const required of [ "spec:validation.readiness-floor", "spec:validation.kind-evidence", @@ -121,6 +128,7 @@ describe("Protocol skill assets", () => { "sdp build", "bindExample", "specTest", + "contract-dependent-suites.mjs", "mutation", "cannot detect", ]) { @@ -149,8 +157,8 @@ describe("Protocol skill assets", () => { }); it("uses the local runtime or package runner instead of a colliding global binary", () => { - const agentSurface = readSkill(".claude/skills/sdp-agent-surface/SKILL.md"); - const authoring = readSkill(".claude/skills/sdp-authoring/SKILL.md"); + const agentSurface = readSkill(".agents/skills/sdp-agent-surface/SKILL.md"); + const authoring = readSkill(".agents/skills/sdp-authoring/SKILL.md"); expect( documentedCommands(agentSurface.body).every( @@ -216,7 +224,7 @@ describe("Protocol skill assets", () => { } const agents = readFileSync(join(repoRoot, "AGENTS.md"), "utf8"); - const skill = readSkill(".claude/skills/sdp-agent-surface/SKILL.md").source; + const skill = readSkill(".agents/skills/sdp-agent-surface/SKILL.md").source; expect(agents).toContain(`${countWord} runnable \`sdp q\` bodies`); expect(skill).toContain(`catalog contains ${countWord} ready-made bodies`); }); From 6763594e8d3d48d7adccbc703c5b531b0ac53186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 22:55:27 +0200 Subject: [PATCH 18/24] fix: make source checkout pnpm queries safe --- .agents/skills/sdp-agent-surface/SKILL.md | 14 +++--- .agents/skills/sdp-authoring/SKILL.md | 11 +++-- AGENTS.md | 2 +- README.md | 44 +++++++++++++++++++ docs/agent-surface/recipes.md | 17 ++++--- package.json | 1 + plans/24-inward-turn-self-hosting-practice.md | 10 +++++ pnpm-workspace.yaml | 3 ++ test/bootstrap.test.ts | 8 ++++ test/recipes.test.ts | 25 +++++------ test/skills.test.ts | 24 +++++----- 11 files changed, 116 insertions(+), 43 deletions(-) create mode 100644 pnpm-workspace.yaml diff --git a/.agents/skills/sdp-agent-surface/SKILL.md b/.agents/skills/sdp-agent-surface/SKILL.md index cb363db..83f665c 100644 --- a/.agents/skills/sdp-agent-surface/SKILL.md +++ b/.agents/skills/sdp-agent-surface/SKILL.md @@ -26,18 +26,20 @@ pnpm exec sdp q 'return g.specContext("spec:example.id")' --root PATH --exclude `PATH` is a placeholder, not a universal exclusion. For example, the origin adopter uses its `pnpm sdp:q` wrapper and excludes only `deps-packages`. -When working in the **Protocol source checkout itself**, use the built local CLI and its exact -three fixture exclusions: +When working in the **Protocol source checkout itself**, use its repository script, which supplies +the exact three fixture exclusions: ```sh -node ./dist/cli/sdp.js q 'return g.specs().length' --exclude explorations --exclude examples --exclude test/fixtures/import/parity -node ./dist/cli/sdp.js q 'return g.specContext("spec:consumers.reader")' --exclude explorations --exclude examples --exclude test/fixtures/import/parity --json +pnpm --silent sdp:q 'return g.specs().length' +pnpm --silent sdp:q 'return g.specContext("spec:consumers.reader")' --json ``` Those exclusions are required only for the Protocol source tree: it carries deliberate duplicate-id and carrier-parity fixtures. They are the same list `npm run generate:self-hosting` -passes. Run `npm run build` first if `dist/` is absent. Do not invoke a global `sdp`: macOS also -ships an unrelated binary with that name. +passes. Run `npm run build` first if `dist/` is absent. Do not use `pnpm exec` in this source +checkout: `exec` resolves dependency binaries, while this package does not link itself into its +own `node_modules/.bin`; an unresolved `sdp` can select macOS's unrelated binary. Do not invoke a +global `sdp` either. The catalog contains eleven ready-made bodies in `docs/agent-surface/recipes.md` in the Protocol repository and diff --git a/.agents/skills/sdp-authoring/SKILL.md b/.agents/skills/sdp-authoring/SKILL.md index 2a511fd..8eef9f3 100644 --- a/.agents/skills/sdp-authoring/SKILL.md +++ b/.agents/skills/sdp-authoring/SKILL.md @@ -13,8 +13,8 @@ session with the build-backlog and drift-alarm recipes. In the Protocol reposito At this repository root, use the exact self-hosting exclusions: ```sh -node ./dist/cli/sdp.js q 'const ready = g.specs().filter((spec) => spec.statedReadiness === "ready"); const backlog = ready.filter((spec) => spec.specKind !== "example" && !spec.deliveryFacts.includes("implemented")); const excludedExamples = ready.filter((spec) => spec.specKind === "example" && !spec.deliveryFacts.includes("implemented")); return {backlog: backlog.map((spec) => spec.id), excludedReadyExamples: excludedExamples.length, excludedWithoutVerifier: excludedExamples.filter((spec) => !spec.deliveryFacts.includes("has-verifier")).map((spec) => spec.id)}' --exclude explorations --exclude examples --exclude test/fixtures/import/parity -node ./dist/cli/sdp.js q 'return g.specs().filter((spec) => spec.deliveryFacts.includes("implemented") && spec.statedReadiness !== "ready").map((spec) => spec.id)' --exclude explorations --exclude examples --exclude test/fixtures/import/parity +pnpm --silent sdp:q 'const ready = g.specs().filter((spec) => spec.statedReadiness === "ready"); const backlog = ready.filter((spec) => spec.specKind !== "example" && !spec.deliveryFacts.includes("implemented")); const excludedExamples = ready.filter((spec) => spec.specKind === "example" && !spec.deliveryFacts.includes("implemented")); return {backlog: backlog.map((spec) => spec.id), excludedReadyExamples: excludedExamples.length, excludedWithoutVerifier: excludedExamples.filter((spec) => !spec.deliveryFacts.includes("has-verifier")).map((spec) => spec.id)}' +pnpm --silent sdp:q 'return g.specs().filter((spec) => spec.deliveryFacts.includes("implemented") && spec.statedReadiness !== "ready").map((spec) => spec.id)' ``` For an adopter, select its root and exclusions explicitly: @@ -24,8 +24,11 @@ pnpm exec sdp q 'return g.specs().map((spec) => spec.id)' --root PATH pnpm exec sdp q 'return g.specs().map((spec) => spec.id)' --root PATH --exclude PATH --exclude PATH ``` -Build this repository before using its local CLI. Never rely on a global `sdp`: macOS also ships an -unrelated binary with that name. Adopters should use their chosen package runner. +The Protocol wrapper supplies the root's three exclusions; run `npm run build` first if `dist/` is +absent. Do not use `pnpm exec` in this source checkout: `exec` resolves dependency binaries, while +the package does not link itself into its own `node_modules/.bin`; an unresolved `sdp` can select +macOS's unrelated binary. Never rely on a global `sdp`. Adopters should use their chosen package +runner. ## Create and enrich diff --git a/AGENTS.md b/AGENTS.md index d745e8e..7a5f05a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,7 +58,7 @@ Progressive disclosure — start at the top, follow the pointers down. | `docs/concept/` (+ README) | **the technical design** — the surviving principle-led docs: vision & MVP boundary, founding principles (P1–P10), authoring & binding, consumers, roadmap; the core model dissolved into the model Specs (`spec:model.core-model`, `spec:model.spec-sections`, `spec:model.relations`, `spec:model.stable-ids`, `spec:model.pack-aggregate`), the one graph into the extraction Specs (`spec:extraction.derive-graph`, `spec:extraction.determinism`, `spec:extraction.claim-taxonomy`, `spec:extraction.regenerability`, `spec:extraction.schema-versioning`), and validation & honesty into the validation Specs (`spec:validation.two-check-families`, `spec:validation.readiness-floor`, `spec:validation.kind-evidence`, `spec:validation.warn-level-signals` and their siblings) | to know *how* it is designed | | `docs/concept/DECISIONS.md` | **the lean decision registry** — ratified names, one-line glosses, carrying Specs, and the D1–D6 lookup; historical rationale lives in git, plans, and the Specs themselves | when resolving a decision name or following its canonical pointer | | `src/` | **the engine** — `model` (Spec/descriptors/pack/anchors) · `extract` · `graph` · `validate` · `reader` (agent surface) · `projections` (Design Review) · `cli` (`sdp build` · `validate` · `view` · `import` · `q`) · `runner` / `codegen` / `notation` / `adapters` | when implementing or verifying **current engine** behavior | -| `.agents/skills/sdp-agent-surface/` + `.agents/skills/sdp-authoring/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — repository-owned reading and authoring skills (also exposed to Claude through the `.claude/skills` symlink), plus eleven runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals · promotion preflight · declared/enabled verifier audit · lower ladder); every body is executed as written by `test/recipes.test.ts` | before answering a corpus question or authoring intent — query the graph, then follow the carrying Specs | +| `.agents/skills/sdp-agent-surface/` + `.agents/skills/sdp-authoring/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — repository-owned reading and authoring skills (also exposed to Claude through the `.claude/skills` symlink), plus eleven runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals · promotion preflight · declared/enabled verifier audit · lower ladder); every body is executed as written by `test/recipes.test.ts`; this checkout exposes them through `pnpm --silent sdp:q ''` (not `pnpm exec`, which cannot resolve the package's own bin) | before answering a corpus question or authoring intent — query the graph, then follow the carrying Specs | | `examples/checkout-v1` | **the worked MVP example** (TS DSL tracer bullet) — specs, anchors, untracked `generated/` (regenerated in-pipeline); walkthrough in its README | when proving the loop end-to-end | | `explorations/` | **evidence only** (carrier exhibits, executable-example findings) — mapping evidence for design; **never promote spike code into product** | when judging design evidence; not a source tree to ship | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code — highest primary-numbered plan's status header, plus active subplans it designates; if DRAFTED, also the latest ✅ EXECUTED/RUN plan | diff --git a/README.md b/README.md index f70d097..54df258 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,47 @@ DSL survives as import source and a lawful per-ID option. Read the [concept](docs/concept/README.md), the [ubiquitous language](CONTEXT.md), and the [checkout-v1 walkthrough](examples/checkout-v1/README.md). + +## Source-checkout quick start + +Install from the committed lockfile and build the CLI: + +```sh +npm ci +npm run build +``` + +Then query this repository's self-hosting graph with either supported script runner: + +```sh +npm run --silent sdp:q -- 'return g.specs().length' +pnpm --silent sdp:q 'return g.specs().length' +``` + +The `sdp:q` script supplies this repository's required fixture exclusions. Use the +[eleven graph-first recipes](docs/agent-surface/recipes.md) for backlog, drift, verifier, impact, +Pack, readiness, and promotion queries. + +Do **not** use `pnpm exec sdp` in the Protocol's own checkout. `pnpm exec` resolves dependency +binaries, but a package does not install or link itself into its own `node_modules/.bin`; on macOS, +the unresolved name may select Apple's unrelated `/usr/bin/sdp`. The checked-in pnpm setting also +disables pnpm 11's dependency auto-reconciliation for repository scripts, so the supported +`pnpm sdp:q` form does not rewrite an npm-installed dependency tree. + +## Installed CLI + +In an adopter repository where the Protocol is installed as a dependency, invoke its binary with +that repository's package runner: + +```sh +pnpm exec sdp --help +pnpm exec sdp build . +pnpm exec sdp validate . +pnpm exec sdp view . +pnpm exec sdp q 'return g.specs().map((spec) => spec.id)' +``` + +`build` derives the graph and executable contracts, `validate` adds conformance and honesty +checks, `view` generates the Design Review, `import` converts TypeScript carriers to Markdown, and +`q` evaluates a local JavaScript query body against a freshly derived graph. Run `sdp --help` for +the complete option contract. Adopters own their root and exclusion policy. diff --git a/docs/agent-surface/recipes.md b/docs/agent-surface/recipes.md index 915bae2..fa0ebae 100644 --- a/docs/agent-surface/recipes.md +++ b/docs/agent-surface/recipes.md @@ -1,25 +1,28 @@ # Agent-surface recipes Runnable bodies for the agent front door. Each recipe below is a body you can pass verbatim to -`sdp q`, unchanged: +`sdp q`, unchanged. In the Protocol source checkout, pass one to the repository wrapper: ```sh -sdp q '' --exclude explorations --exclude examples --exclude test/fixtures/import/parity -sdp q '' --exclude explorations --exclude examples --exclude test/fixtures/import/parity --json +pnpm --silent sdp:q '' +pnpm --silent sdp:q '' --json ``` **At this repository's root the exclusions are not optional.** The corpus carries deliberate duplicate-id and carrier-parity fixtures under `examples/`, `explorations/`, and `test/fixtures/import/parity/`; without those three exclusions the extractor reports errors, the graph does not derive, and the sink refuses to run the body at all. The three above are exactly the -project's own — the same list `npm run generate:self-hosting` passes and the same list the recipe -check derives with. +project's own — the `sdp:q` wrapper owns the same list `npm run generate:self-hosting` passes and +the recipe check derives with. Run `npm run build` first if `dist/` is absent. Do not substitute +`pnpm exec` in this source checkout: `exec` resolves dependency binaries, while a package does not +link itself into its own `node_modules/.bin`; an unresolved `sdp` can select macOS's unrelated +binary. For adopters, the portable form keeps root and exclusions project-selected: ```sh -sdp q '' --root PATH -sdp q '' --root PATH --exclude PATH --exclude PATH +pnpm exec sdp q '' --root PATH +pnpm exec sdp q '' --root PATH --exclude PATH --exclude PATH ``` `--root PATH` picks the extraction root (default: the working directory) and `--exclude` is diff --git a/package.json b/package.json index 1b07296..2ecb072 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "typecheck:examples": "tsc --noEmit -p tsconfig.examples.json", "test": "node ./vitest-test.mjs", "test:watch": "vitest", + "sdp:q": "node ./dist/cli/sdp.js q --exclude explorations --exclude examples --exclude test/fixtures/import/parity", "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", diff --git a/plans/24-inward-turn-self-hosting-practice.md b/plans/24-inward-turn-self-hosting-practice.md index c734fff..e304acc 100644 --- a/plans/24-inward-turn-self-hosting-practice.md +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -237,3 +237,13 @@ symlink, so supporting both agent conventions creates no second skill copy and d user-level installation. The temporal sweep accepts that tracked in-repository directory symlink, continues to scan the canonical tracked files, and fails closed when a symlink escapes the repository. + +A later source-checkout probe showed that `pnpm exec sdp q` never reached the Protocol CLI. Pnpm 11 +first tried to reconcile the package-lock-based `node_modules`, generated pnpm state, and refused +an unreviewed `esbuild` install script; after that reconciliation was disabled, `exec` correctly +found no local dependency bin and fell through to macOS's unrelated `/usr/bin/sdp`. The repository +therefore disables pnpm's run-time dependency verification and exposes +`pnpm --silent sdp:q ''` as the non-mutating source-checkout wrapper over the built CLI and +its three required exclusions. `npm run --silent sdp:q -- ''` remains equivalent. Adopter +commands remain owned by each adopter's selected package runner, where `pnpm exec sdp` is valid +because the Protocol is an installed dependency. diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..b07e67c --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +# pnpm 11 otherwise tries to reconcile this package-lock-based checkout before `pnpm run` or +# `pnpm exec`. Running a repository script must not mutate the dependency tree. +verifyDepsBeforeRun: false diff --git a/test/bootstrap.test.ts b/test/bootstrap.test.ts index 6c78da8..68b15a5 100644 --- a/test/bootstrap.test.ts +++ b/test/bootstrap.test.ts @@ -47,6 +47,7 @@ describe("bootstrap package surface", () => { const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url)); const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as { type: string; + packageManager?: string; bin: { sdp: string }; exports: Record; scripts: Record; @@ -58,6 +59,7 @@ describe("bootstrap package surface", () => { } expect(packageJson.type).toBe("module"); + expect(packageJson.packageManager).toBeUndefined(); expect(packageJson.bin.sdp).toBe("./dist/cli/sdp.js"); expect(rootExport.types).toBe("./dist/index.d.ts"); expect(rootExport.import).toBe("./dist/index.js"); @@ -70,10 +72,16 @@ describe("bootstrap package surface", () => { expect(packageJson.scripts["check:self-hosting-gates"]).toBe( "node ./check-self-hosting-gates.mjs", ); + expect(packageJson.scripts["sdp:q"]).toBe( + "node ./dist/cli/sdp.js q --exclude explorations --exclude examples --exclude test/fixtures/import/parity", + ); expect(packageJson.scripts.preflight).toBe("node ./preflight.mjs"); expect(packageJson.scripts.check).toBe( "npm run check:temporal && npm run lint && npm run format:check && npm run build && npm run generate:self-hosting && npm run generate:example && npm run typecheck && npm run typecheck:examples && npm test && npm run check:self-hosting-gates && npm run check:self-hosting && npm run check:example && npm run preflight", ); + + const pnpmWorkspacePath = fileURLToPath(new URL("../pnpm-workspace.yaml", import.meta.url)); + expect(await readFile(pnpmWorkspacePath, "utf8")).toContain("verifyDepsBeforeRun: false"); }); it("reifies a TypeScript carrier and derives a graph through the public root", () => { diff --git a/test/recipes.test.ts b/test/recipes.test.ts index c41c784..989080a 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -197,12 +197,19 @@ describe("the agent-surface recipe corpus", () => { ".agents/skills/sdp-agent-surface/SKILL.md", ".agents/skills/sdp-authoring/SKILL.md", ]; + const packageJson = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as { + scripts: Record; + }; + const localQuery = packageJson.scripts["sdp:q"] ?? ""; + + for (const path of standardExcludes) { + expect(localQuery).toContain(`--exclude ${path}`); + } for (const source of onRampSources) { const lines = readFileSync(join(repoRoot, source), "utf8").split("\n"); - const commandLines = lines.filter((line) => line.includes(" q '") && line.includes("sdp")); - const selfHostingLines = commandLines.filter((line) => !line.includes("--root PATH")); - const adopterLines = commandLines.filter((line) => line.includes("--root PATH")); + const selfHostingLines = lines.filter((line) => line.startsWith("pnpm --silent sdp:q '")); + const adopterLines = lines.filter((line) => line.startsWith("pnpm exec sdp q '")); expect({ source, selfHosting: selfHostingLines.length > 0 }).toEqual({ source, @@ -213,18 +220,6 @@ describe("the agent-surface recipe corpus", () => { otherQuoting: lines.filter((line) => line.includes(' q "') && line.includes("sdp")), }).toEqual({ source, otherQuoting: [] }); - for (const line of selfHostingLines) { - expect({ - source, - line, - // A word boundary, not a substring: `--exclude examples/checkout` must not count as - // naming `examples`. - names: standardExcludes.filter((path) => - new RegExp(`--exclude ${path}(?=[\\s'"]|$)`, "u").test(line), - ).length, - }).toEqual({ source, line, names: standardExcludes.length }); - } - expect({ source, adopterForms: adopterLines.length }).toEqual({ source, adopterForms: 2, diff --git a/test/skills.test.ts b/test/skills.test.ts index 0309f7a..87a2a7a 100644 --- a/test/skills.test.ts +++ b/test/skills.test.ts @@ -63,7 +63,7 @@ function documentedCommands(body: string): readonly string[] { } if ( inShellFence && - (line.startsWith("node ./dist/cli/sdp.js ") || line.startsWith("pnpm exec sdp ")) + (line.startsWith("pnpm --silent sdp:q ") || line.startsWith("pnpm exec sdp ")) ) { commands.push(line); } @@ -138,20 +138,24 @@ describe("Protocol skill assets", () => { it("documents only valid CLI verbs and keeps root-specific exclusions intact", () => { const knownVerbs = new Set(["build", "q"]); + const packageJson = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as { + scripts: Record; + }; + const localQuery = packageJson.scripts["sdp:q"] ?? ""; + + for (const exclusion of ["explorations", "examples", "test/fixtures/import/parity"]) { + expect(localQuery).toContain(`--exclude ${exclusion}`); + } for (const path of skillPaths) { const commands = documentedCommands(readSkill(path).body); expect(commands.length).toBeGreaterThan(0); for (const command of commands) { - const verb = /(?:sdp\.js|exec sdp) (?[\w-]+)/u.exec(command)?.groups?.verb; + const verb = command.startsWith("pnpm --silent sdp:q ") + ? "q" + : /exec sdp (?[\w-]+)/u.exec(command)?.groups?.verb; expect(verb === undefined ? false : knownVerbs.has(verb)).toBe(true); - - if (verb === "q" && !command.includes("--root PATH")) { - for (const exclusion of ["explorations", "examples", "test/fixtures/import/parity"]) { - expect(command).toContain(`--exclude ${exclusion}`); - } - } } } }); @@ -162,12 +166,12 @@ describe("Protocol skill assets", () => { expect( documentedCommands(agentSurface.body).every( - (line) => line.startsWith("node ") || line.startsWith("pnpm exec "), + (line) => line.startsWith("pnpm --silent ") || line.startsWith("pnpm exec "), ), ).toBe(true); expect( documentedCommands(authoring.body).every( - (line) => line.startsWith("node ") || line.startsWith("pnpm exec "), + (line) => line.startsWith("pnpm --silent ") || line.startsWith("pnpm exec "), ), ).toBe(true); }); From 454e9e95d804190f0e37280af884b6861de65a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 23:11:07 +0200 Subject: [PATCH 19/24] fix(review): close the post-review findings on recipes, skills checks, and the plan selector - Document the recipe parameterization contract: recipes 3, 4, 6, and 9 open with a subject const naming this repository's corpus; the intro and recipe 9 now say so, closing the reported portability gap without breaking the run-verbatim recipe check. - Replace the tautological colliding-global-binary skill test: the collector only ever gathered pnpm-prefixed lines, so the assertion could not fail. The check now scans every shell-fenced line and reddens on any bare sdp or npx sdp invocation. - Mirror recipe 10's row predicate in its completeness expectation: a spec whose only binding is an off-contract, not-enabled verify edge lawfully produces no row, so the broader verifiers.length > 0 predicate was a latent false alarm. - Let zero-padded primary plans enter the gate's plan selector; letter-suffixed plans stay excluded because the hyphen must follow the digits directly. Full thirteen-leg npm run check green. Claude-Session: https://claude.ai/code/session_0158eQyLnKGzNZTPYcTehw5M --- check-self-hosting-gates.mjs | 2 +- docs/agent-surface/recipes.md | 7 ++++ test/recipes.test.ts | 8 ++++- test/skills.test.ts | 60 +++++++++++++++++------------------ 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/check-self-hosting-gates.mjs b/check-self-hosting-gates.mjs index 24c7d9a..10e4b1e 100644 --- a/check-self-hosting-gates.mjs +++ b/check-self-hosting-gates.mjs @@ -90,7 +90,7 @@ const plan18 = existsSync(join(rootDir, plan18Path)) ? read(plan18Path) : null; const primaryPlans = readdirSync(join(rootDir, "plans"), { withFileTypes: true }) .filter((entry) => entry.isFile()) .map((entry) => { - const match = /^(?[1-9]\d*)-[a-z0-9][a-z0-9-]*\.md$/u.exec(entry.name); + const match = /^(?\d+)-[a-z0-9][a-z0-9-]*\.md$/u.exec(entry.name); const number = match?.groups?.number; return number === undefined ? null : { name: entry.name, number: Number(number) }; }) diff --git a/docs/agent-surface/recipes.md b/docs/agent-surface/recipes.md index fa0ebae..15cd5ee 100644 --- a/docs/agent-surface/recipes.md +++ b/docs/agent-surface/recipes.md @@ -28,6 +28,12 @@ pnpm exec sdp q '' --root PATH --exclude PATH --exclude PATH `--root PATH` picks the extraction root (default: the working directory) and `--exclude` is repeatable for root-relative path prefixes. `PATH` is a placeholder, not a literal directory. +**Some recipes open with a parameter.** Recipes 3, 4, 6, and 9 take their subject on the opening +`const` line(s) — a Spec id, a changed-file list, a search term. Those lines name *this* +repository's corpus so every body runs as written here (the recipe check executes each one +verbatim); in your own corpus, substitute your subject on that line before running. A Spec id +absent from the graph returns `{ found: false }` rather than failing. + **The contract, in one place.** The front door derives the graph in process and evaluates the body you supply; `return` is the output contract. Three bindings are injected: @@ -399,6 +405,7 @@ return { }; ``` +The `id` line is the recipe's parameter — substitute the Spec whose promotion you are weighing. An empty `currentFloorFailures` list says the stated rung is honest. It does not confer the next rung, and `floorReached` above the stated rung is information rather than an automatic edit. diff --git a/test/recipes.test.ts b/test/recipes.test.ts index 989080a..77023d3 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -467,9 +467,15 @@ describe("the agent-surface recipe corpus", () => { it("keeps declared examples distinct from enabled verifier bindings", async () => { const result = asRecord(await runRecipe(recipeByOrdinal(10))); const rows = asArray(result.rows); + // The completeness predicate mirrors the recipe's row predicate exactly: a spec whose only + // binding is an off-contract (not-enabled, non-example) verify edge lawfully produces no row. const expected = reader .specs() - .filter((spec) => (reader.specContext(spec.id)?.verifiers.length ?? 0) > 0) + .filter((spec) => { + const verifiers = reader.specContext(spec.id)?.verifiers ?? []; + + return verifiers.some((binding) => binding.via === "example" || binding.enabled); + }) .map((spec) => spec.id) .sort(); diff --git a/test/skills.test.ts b/test/skills.test.ts index 87a2a7a..ae654a4 100644 --- a/test/skills.test.ts +++ b/test/skills.test.ts @@ -41,35 +41,38 @@ function readSkill(path: string) { }; } -const authoringRecipesImplementationAnchor = codeAnchor({ - id: codeAnchorId("impl:protocol.authoring-recipes"), - label: "asserts realization of the shipped authoring recipe catalog", - satisfies: ref("spec:consumers.agent-surface.authoring-recipes"), -}); -void authoringRecipesImplementationAnchor; - -function documentedCommands(body: string): readonly string[] { - const commands: string[] = []; +function shellFenceLines(body: string): readonly string[] { + const lines: string[] = []; let inShellFence = false; for (const line of body.split("\n")) { - if (line === "```sh") { - inShellFence = true; + if (line.startsWith("```") && line !== "```") { + inShellFence = line === "```sh"; continue; } if (line === "```") { inShellFence = false; continue; } - if ( - inShellFence && - (line.startsWith("pnpm --silent sdp:q ") || line.startsWith("pnpm exec sdp ")) - ) { - commands.push(line); + if (inShellFence) { + lines.push(line); } } - return commands; + return lines; +} + +const authoringRecipesImplementationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.authoring-recipes"), + label: "asserts realization of the shipped authoring recipe catalog", + satisfies: ref("spec:consumers.agent-surface.authoring-recipes"), +}); +void authoringRecipesImplementationAnchor; + +function documentedCommands(body: string): readonly string[] { + return shellFenceLines(body).filter( + (line) => line.startsWith("pnpm --silent sdp:q ") || line.startsWith("pnpm exec sdp "), + ); } const authoringOnRampTestAnchor = specTest({ @@ -161,19 +164,16 @@ describe("Protocol skill assets", () => { }); it("uses the local runtime or package runner instead of a colliding global binary", () => { - const agentSurface = readSkill(".agents/skills/sdp-agent-surface/SKILL.md"); - const authoring = readSkill(".agents/skills/sdp-authoring/SKILL.md"); - - expect( - documentedCommands(agentSurface.body).every( - (line) => line.startsWith("pnpm --silent ") || line.startsWith("pnpm exec "), - ), - ).toBe(true); - expect( - documentedCommands(authoring.body).every( - (line) => line.startsWith("pnpm --silent ") || line.startsWith("pnpm exec "), - ), - ).toBe(true); + // The hazard is a documented invocation the collector above would never collect: a bare + // `sdp` resolves to whatever binary shadows it on PATH (macOS ships an unrelated `sdp`), + // so the check scans every shell-fenced line rather than the pnpm-prefixed subset. + for (const path of skillPaths) { + const bareInvocations = shellFenceLines(readSkill(path).body).filter((line) => + /^\s*(?:sdp|npx +sdp|npm +exec +sdp)\b/u.test(line), + ); + + expect({ path, bareInvocations }).toEqual({ path, bareInvocations: [] }); + } }); it("contains no contradictory shortcuts for readiness or verifier realization", () => { From 4079825d146d94e32f46d47978eec44efeb02b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Tue, 28 Jul 2026 23:20:52 +0200 Subject: [PATCH 20/24] fix(cli,temporal): give the source checkout a real CLI entry and harden the temporal guard - Add the sdp package script so the full CLI resolves inside the source checkout (pnpm --silent sdp --help); document it in the README beside the exact failure signature pnpm exec sdp produces here (macOS resolves Apple's unrelated /usr/bin/sdp, which demands Xcode), and retitle the pnpm exec section as adopter-repository usage where the binary really is linked. - Anchor the temporal guard's repository root to the script's own directory instead of the caller's working directory, so the sweep reaches the same files from any cwd; enumeration and reads now go through that root. - Skip a tracked file symlink whose canonical file git enumerates on its own: CLAUDE.md -> AGENTS.md was being swept twice, and a link into an excluded genre would have been un-exempted by reading through the link. Full thirteen-leg npm run check green. Claude-Session: https://claude.ai/code/session_0158eQyLnKGzNZTPYcTehw5M --- README.md | 24 +++++++++++++++++------- check-temporal.mjs | 28 +++++++++++++++++++++------- package.json | 1 + 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 54df258..a050292 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,26 @@ The `sdp:q` script supplies this repository's required fixture exclusions. Use t [eleven graph-first recipes](docs/agent-surface/recipes.md) for backlog, drift, verifier, impact, Pack, readiness, and promotion queries. -Do **not** use `pnpm exec sdp` in the Protocol's own checkout. `pnpm exec` resolves dependency -binaries, but a package does not install or link itself into its own `node_modules/.bin`; on macOS, -the unresolved name may select Apple's unrelated `/usr/bin/sdp`. The checked-in pnpm setting also +The full CLI is available in the checkout through the `sdp` script; graph-deriving verbs at this +root need the same three fixture exclusions the `sdp:q` script supplies: + +```sh +pnpm --silent sdp --help +npm run --silent sdp -- --help +pnpm --silent sdp validate . --exclude explorations --exclude examples --exclude test/fixtures/import/parity +``` + +Do **not** use `pnpm exec sdp` (or `npx sdp`) in the Protocol's own checkout. `pnpm exec` resolves +dependency binaries, but a package does not install or link itself into its own +`node_modules/.bin`; on macOS the unresolved name selects Apple's unrelated `/usr/bin/sdp`, which +fails with `xcode-select: error: tool 'sdp' requires Xcode`. The checked-in pnpm setting also disables pnpm 11's dependency auto-reconciliation for repository scripts, so the supported -`pnpm sdp:q` form does not rewrite an npm-installed dependency tree. +`pnpm sdp` / `pnpm sdp:q` forms do not rewrite an npm-installed dependency tree. -## Installed CLI +## Installed CLI (adopter repositories) -In an adopter repository where the Protocol is installed as a dependency, invoke its binary with -that repository's package runner: +In an adopter repository where the Protocol is installed as a dependency, its binary **is** linked +into `node_modules/.bin`, so the package runner resolves it: ```sh pnpm exec sdp --help diff --git a/check-temporal.mjs b/check-temporal.mjs index cf71129..8b86938 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -1,6 +1,7 @@ import { spawnSync } from "node:child_process"; import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs"; -import { isAbsolute, relative, resolve, sep } from "node:path"; +import { dirname, isAbsolute, join, relative, sep } from "node:path"; +import { fileURLToPath } from "node:url"; // The temporal-token guard: durable artifacts carry current truth, so calendar and session tokens // (session/wave/fold handles, ISO dates, numbered plan-file refs) are banned from every delivery @@ -34,10 +35,15 @@ function fail(message, detail = "") { process.exit(1); } +// The root is the guard's own directory, never the caller's working directory: the sweep must +// reach the same files no matter where the invocation starts. +const repositoryRoot = realpathSync(dirname(fileURLToPath(import.meta.url))); + const enumerated = spawnSync( "git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { + cwd: repositoryRoot, encoding: "utf8", }, ); @@ -46,8 +52,8 @@ if (enumerated.error !== undefined || enumerated.status !== 0) { fail("file enumeration failed", enumerated.stderr); } -const paths = enumerated.stdout.split("\0").filter((path) => path !== "" && !isExcluded(path)); -const repositoryRoot = realpathSync(resolve(".")); +const allEnumerated = new Set(enumerated.stdout.split("\0").filter((path) => path !== "")); +const paths = [...allEnumerated].filter((path) => !isExcluded(path)); // This file is swept like every other; its single allowance is line-level use–mention: the guard // must name the tokens it bans, so exactly one line — the pattern literal, alone on its line — is @@ -59,11 +65,12 @@ let selfAllowanceUsed = false; const violations = []; for (const path of paths) { + const absolute = join(repositoryRoot, path); let source; try { - if (lstatSync(path).isSymbolicLink()) { - const target = realpathSync(path); + if (lstatSync(absolute).isSymbolicLink()) { + const target = realpathSync(absolute); const targetFromRoot = relative(repositoryRoot, target); const escapesRepository = isAbsolute(targetFromRoot) || @@ -77,12 +84,19 @@ for (const path of paths) { // Git enumerates a tracked directory symlink as one entry. Its canonical in-repository files // are enumerated independently, so reading the directory as text would both duplicate the // sweep and fail with EISDIR. - if (statSync(path).isDirectory()) { + if (statSync(absolute).isDirectory()) { + continue; + } + + // The same holds for a file symlink whose canonical file git enumerates on its own: that + // entry is swept — or genre-exempt — under its canonical path, and reading through the link + // would either duplicate the sweep or un-exempt an excluded genre. + if (allEnumerated.has(targetFromRoot)) { continue; } } - source = readFileSync(path, "utf8"); + source = readFileSync(absolute, "utf8"); } catch (error) { fail(`could not read ${path}`, error instanceof Error ? error.message : String(error)); } diff --git a/package.json b/package.json index 2ecb072..aeab542 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "typecheck:examples": "tsc --noEmit -p tsconfig.examples.json", "test": "node ./vitest-test.mjs", "test:watch": "vitest", + "sdp": "node ./dist/cli/sdp.js", "sdp:q": "node ./dist/cli/sdp.js q --exclude explorations --exclude examples --exclude test/fixtures/import/parity", "lint": "eslint .", "format": "prettier --write .", From 57368775bb2ded2e84a2c60d52dd379102ed4db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 29 Jul 2026 06:03:13 +0200 Subject: [PATCH 21/24] feat(process): recover dissolved guidance and ship the sdp-sessions on-ramp (plan 25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-home the seven PARTIAL items from the dissolution value-transfer audit (reviews/13) into their carrying Specs — the one-kind rule's split guidance, child-above-parent readiness independence, the typed-dependency rationale, the graph(A)/graph(B) diff framing, the no-duplicated-intent-on-Packs negative ruling, MD-1's recovered-surface gloss, and the softened curation claim — and rebuild the process layer as guidance without gates: a packaged sdp-sessions skill, owned by the new ready behavior spec:consumers.delivery-session-on-ramp, routes capture, design, implement, review, and close work shapes through the existing eleven graph recipes. sdp-authoring gains the cheap idea-capture template; CONTEXT.md declares the guidance-only labels; AGENTS.md turns graph-first with derived-never-quoted status; the README names the three shipped skills for adopters. Claude-Session: https://claude.ai/code/session_01TX4fxCUDfukBZo6LBNRKNw --- .agents/skills/sdp-authoring/SKILL.md | 29 +++ .agents/skills/sdp-sessions/SKILL.md | 79 ++++++++ AGENTS.md | 114 +++++++---- CONTEXT.md | 4 + README.md | 5 + docs/concept/06-consumers-and-projections.md | 2 +- package.json | 3 +- ...guidance-recovery-and-the-process-layer.md | 158 +++++++++++++++ .../13-dissolution-value-transfer-audit.md | 190 ++++++++++++++++++ specs/consumers/authoring-on-ramp.sdp.md | 1 + .../consumers/delivery-session-on-ramp.sdp.md | 20 ++ specs/consumers/impact-graph.sdp.md | 1 + specs/decisions/executable-meta-model.sdp.md | 2 +- specs/model/core-model.sdp.md | 1 + specs/model/relations.sdp.md | 1 + specs/self-hosting.pack.sdp.ts | 1 + specs/validation/pack-coherence.sdp.md | 1 + specs/validation/readiness-floor.sdp.md | 1 + test/package-smoke.test.ts | 12 ++ test/recipes.test.ts | 1 + test/self-hosting-graph.test.ts | 14 +- test/self-hosting-oracle/anchors.ts | 20 ++ test/self-hosting-oracle/consumers.ts | 28 +++ test/self-hosting-oracle/decisions.ts | 4 +- .../self-hosting-oracle/declared-relations.ts | 1 + test/self-hosting-oracle/model.ts | 4 + test/self-hosting-oracle/pack-members.ts | 1 + test/self-hosting-oracle/validation.ts | 2 + test/skills.test.ts | 55 ++++- 29 files changed, 699 insertions(+), 56 deletions(-) create mode 100644 .agents/skills/sdp-sessions/SKILL.md create mode 100644 plans/25-guidance-recovery-and-the-process-layer.md create mode 100644 reviews/13-dissolution-value-transfer-audit.md create mode 100644 specs/consumers/delivery-session-on-ramp.sdp.md diff --git a/.agents/skills/sdp-authoring/SKILL.md b/.agents/skills/sdp-authoring/SKILL.md index 8eef9f3..57adcc1 100644 --- a/.agents/skills/sdp-authoring/SKILL.md +++ b/.agents/skills/sdp-authoring/SKILL.md @@ -36,6 +36,8 @@ runner. 2. Create the Markdown carrier with one stable `spec:` id, title, kind, altitude, readiness, and relations. The carrier law is `spec:decisions.carrier-ruling`; the envelope and section law is `spec:model.spec-sections`. + A Spec carries one kind. If a fact straddles kinds, split it into two Specs and join them with + the relation that preserves their distinct intents, following `spec:model.core-model`. 3. State only the rung the structure clears. Use recipe 9 for the current floor, recipe 11 for the lower ladder, and read `spec:validation.readiness-floor` plus `spec:validation.kind-evidence` for the clauses. @@ -44,6 +46,33 @@ runner. 5. Put unresolved durable questions under Intent. A blocking question honestly keeps the Spec below `defined`. +### Capture a cheap idea + +Run concept search (recipe 6) first and place the carrier beside the family it finds. In the +Protocol repository that normally means `specs//`; an adopter follows its own canonical +carrier root rather than inventing a second one. This is the complete cheap-capture shape: + +```md +--- +id: spec:. +kind: +altitude: +readiness: idea +relations: {} +--- + +# + +## Intent + +- outcome: +``` + +The `idea` floor is the whole shape: stable envelope coordinates plus either an outcome or a +`refines` parent. The template states the outcome explicitly so the capture remains intelligible +without its parent. Before every later human readiness edit, run promotion preflight (recipe 9); +the reported floor never makes the edit on the author's behalf. + ## Make an example executable 1. Put the typed `gwt-vocabulary` example space on the parent. diff --git a/.agents/skills/sdp-sessions/SKILL.md b/.agents/skills/sdp-sessions/SKILL.md new file mode 100644 index 0000000..121a197 --- /dev/null +++ b/.agents/skills/sdp-sessions/SKILL.md @@ -0,0 +1,79 @@ +--- +name: sdp-sessions +description: Route delivery work through graph-first capture, design, implementation, review, and close shapes without workflow gates. +--- + +# Route delivery work from current graph state + +Use this skill when opening or handing off a delivery session. It realizes +`spec:consumers.delivery-session-on-ramp`: one current graph serves every work shape, and the shape +only selects the advisory preflight and the carrying reference to open. Shapes are not phases, +statuses, or a required sequence. + +## Bootstrap the graph + +The runnable bodies live in `docs/agent-surface/recipes.md` in the Protocol repository and in +`node_modules/@libar-dev/software-delivery-protocol/docs/agent-surface/recipes.md` in an adopter. +In this source checkout use the repository wrapper, which supplies the required exclusions: + +```sh +pnpm --silent sdp:q 'return g.specs().length' +``` + +If the active pnpm binary cannot run this package-lock-based checkout, the equivalent source +wrapper is `npm run --silent sdp:q -- ''`. Never fall through to a global `sdp`. + +An adopter selects its root and exclusions through its installed package runner: + +```sh +pnpm exec sdp q '' --root PATH +pnpm exec sdp q '' --root PATH --exclude PATH --exclude PATH +``` + +Open the catalog and substitute only the parameter line of a parameterized recipe. The recipe +result informs the session; it never grants permission or records a verdict. + +## Choose an advisory work shape + +### Capture / refine + +Use concept search (recipe 6) to find the existing family and avoid duplicate intent. Use the +lower ladder (recipe 11) to see current stated and derived readiness, then promotion preflight +(recipe 9) before a human changes a rung. Follow `sdp-authoring` for the minimal lawful `idea` +carrier and the one-kind rule. + +### Design + +Use promotion preflight (recipe 9) on the target and readiness divergence (recipe 7) across the +corpus. Resolve blocking open questions and review the carrying Specs. A clear floor is evidence, +not an automatic `ready` statement. + +### Implement + +Use the build backlog (recipe 1) to orient the available ready work and the target Spec context +(recipe 3) to read guarantees, relations, implementation bindings, and verifiers. Bind anchors +and executable examples through `sdp-authoring`; an `implemented` fact names a binding, not a +passing or live system. + +### Review + +For a Pack, use the Pack review backbone (recipe 5) and warn-level signals (recipe 8). Without a +Pack, use the target Spec context (recipe 3) with warn-level signals (recipe 8). Review findings +and gaps as data; the review never becomes a workflow gate. + +### Close / slim + +Use the drift alarm (recipe 2) and changed-file blast radius (recipe 4) over the session diff. +Slimming is optional judgment: remove obsolete implementation-sequencing scaffolding only when +durable law and one prose owner remain. `spec:model.enrichment-lifecycle` deliberately leaves the +universal distillation boundary unresolved. + +## Hand off for re-measurement + +Name the chosen shape, target Spec ids or Pack, changed files, stated and derived readiness, +findings or blocking open questions, and the exact commands, commits, or artifact locations from +which evidence can be re-derived. Never hand off a carried "verified" verdict: the next session +re-runs the named evidence. Git remains the event log; no session state enters the graph. + +All preflights are advisory. They never authorize, block, scope, unlock, or advance delivery work, +and they never create a process state machine. diff --git a/AGENTS.md b/AGENTS.md index 7a5f05a..f23ce76 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,24 +5,30 @@ the repo, derive **one graph** from it, and generate every other artifact off th take on `@libar-dev/architect` — a year of design thinking front-loaded, **zero back-compat, zero old sins carried over**. -The concept synthesis, the ratified ubiquitous language, and the JTBD stories together **are the -spec** — the product's own thesis (*the spec is the prompt is the design*) applied to itself: there -is no separate PRD. The engine is **implemented under `src/`** (MVP slices 0–5 landed on `main`); -concept docs explain design and still hold unsettled post-MVP detail. **Intended truth** -(authored specs, ratified decisions, concept material) is authoritative for what the Protocol -claims; **`src/` and tests** are authoritative evidence of current realization. A disagreement is -**drift to resolve**, never permission to silently promote code behavior into intent. - -> **Status:** concept ratified · MVP slices 0–5 landed on `main` (plan 10) · post-MVP executable -> machinery landed (plan 13) · authoring **carrier ruled** as `.sdp.md` (the carrier ruling, MD-18; -> plan 16) — product Markdown parser and self-hosting landed · **canonical-default carrier -> rule:** Specs default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives -> as import source and a lawful per-ID option. · **what now:** EXECUTED — plan 24 is EXECUTED and landed the inward turn: self-hosting is the standing practice, the example realization posture is ratified, forward intent lives in the graph, the binding layer is deliberately dispositioned, two inward oracles resolve, and the kind-neutral oracle slice completed the full spec-first loop through slimming. The corpus stands at `ready: 86 / defined: 37 / scoped: 1 / idea: 4` over 128 Specs · 1 Pack · 116 anchors → 245 nodes · 462 edges with zero findings. Phase-6 and phase-7 evidence is recoverable from commits `b6f123c` through `7ae8087` and PR #15; the origin working-copy changes remain reverted and survive only on PR libar-ai/convex-event-sourcing#181. The generalized records audit is a required gate; the authoring on-ramp and eleven graph-first recipes ship in the adopter package. Matched second-caller sessions measured 73.1% fewer gross completion-event tokens and 38.0% fewer non-cached input-plus-output tokens without worse rubric quality; ten fresh `sdp q` derivations measured 432.27 ms median / 444.19 ms p95. Build state lives in -> **`plans/`** — read the highest +There is no separate PRD — the product's own thesis (*the spec is the prompt is the design*) +applies to itself. This repo **self-hosts**: it authors its own Specs in its own carrier (the +`.sdp.md` corpus under `specs/`), derives its own graph, and validates itself in CI — the +Protocol's first production corpus is the Protocol. The ratified ubiquitous language +(`CONTEXT.md`) and the JTBD stories carry the vocabulary and the jobs; the engine lives under +`src/`; the surviving concept docs hold the principle-led design that has not yet dissolved into +carrying Specs. **Intended truth** (authored Specs, ratified decisions, concept material) is +authoritative for what the Protocol claims; **`src/` and tests** are authoritative evidence of +current realization. A disagreement is **drift to resolve**, never permission to silently promote +code behavior into intent. + +> **Status:** the authoring **carrier is ruled** as `.sdp.md` (the carrier ruling, MD-18): Specs +> default to Markdown; Packs remain TS until a Pack syntax ruling; the TS DSL survives as import +> source and a lawful per-ID option. **plan 25 is EXECUTED** — the recovered guidance now has +> typed owners, and the packaged `sdp-sessions` on-ramp routes advisory delivery work shapes +> through the existing graph recipes without gates. Plan 24's inward turn remains the standing +> practice: forward intent lives in the graph, so the live backlog is a graph query, not a +> document. Corpus counts, +> readiness, and findings are **derived, never quoted** — run `sdp validate` (or recipe 1) for +> the current numbers. Build state lives in **`plans/`** — read the highest > **primary-numbered** plan's status header, plus any **active subplans it (or its parent family) > explicitly designates as current**; ignore unnumbered files and letter-suffixed plans only when > no primary/active plan designates them. If that plan is DRAFTED, also read the latest ✅ -> EXECUTED/RUN plan for settled ground. The historical slice roadmap is **`docs/concept/07`**. +> EXECUTED/RUN plan for settled ground. ## The frame @@ -54,11 +60,12 @@ Progressive disclosure — start at the top, follow the pointers down. | Look here | What you get | Read | |---|---|---| | `CONTEXT.md` (repo root) | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology; the model exposition lives in the Specs under `specs/` and in the surviving concept docs | **first, always** | +| `specs/` | **the self-hosted corpus** — the Protocol's own Specs in its own carrier (families: `model` · `extraction` · `validation` · `carrier` · `consumers` · `protocol` · `observation` · `decisions`, plus the self-hosting Pack); the primary carrier of intended truth | when design truth is in question — but query it through `sdp q` first, then read the carrying Spec | | `jtbd-stories/` | **the jobs (functional spec)** — stable `When / I want / so I can` stories (themes A–H); no personas, because consumers are heterogeneous (humans, CI, CLIs, **AI agents**) | to know *what* we serve | -| `docs/concept/` (+ README) | **the technical design** — the surviving principle-led docs: vision & MVP boundary, founding principles (P1–P10), authoring & binding, consumers, roadmap; the core model dissolved into the model Specs (`spec:model.core-model`, `spec:model.spec-sections`, `spec:model.relations`, `spec:model.stable-ids`, `spec:model.pack-aggregate`), the one graph into the extraction Specs (`spec:extraction.derive-graph`, `spec:extraction.determinism`, `spec:extraction.claim-taxonomy`, `spec:extraction.regenerability`, `spec:extraction.schema-versioning`), and validation & honesty into the validation Specs (`spec:validation.two-check-families`, `spec:validation.readiness-floor`, `spec:validation.kind-evidence`, `spec:validation.warn-level-signals` and their siblings) | to know *how* it is designed | +| `docs/concept/` (+ README) | **the technical design** — the surviving principle-led docs: vision & MVP boundary, founding principles (P1–P10), authoring & binding, consumers, roadmap; the core model, the one graph, and validation & honesty dissolved into the `model.*`, `extraction.*`, and `validation.*` Spec families — locate any of them with concept search (recipe 6) | to know *how* it is designed | | `docs/concept/DECISIONS.md` | **the lean decision registry** — ratified names, one-line glosses, carrying Specs, and the D1–D6 lookup; historical rationale lives in git, plans, and the Specs themselves | when resolving a decision name or following its canonical pointer | | `src/` | **the engine** — `model` (Spec/descriptors/pack/anchors) · `extract` · `graph` · `validate` · `reader` (agent surface) · `projections` (Design Review) · `cli` (`sdp build` · `validate` · `view` · `import` · `q`) · `runner` / `codegen` / `notation` / `adapters` | when implementing or verifying **current engine** behavior | -| `.agents/skills/sdp-agent-surface/` + `.agents/skills/sdp-authoring/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — repository-owned reading and authoring skills (also exposed to Claude through the `.claude/skills` symlink), plus eleven runnable `sdp q` bodies (build backlog · drift alarm · guarantees & verifiers · blast radius · Pack backbone · concept search · readiness divergence · warn-level signals · promotion preflight · declared/enabled verifier audit · lower ladder); every body is executed as written by `test/recipes.test.ts`; this checkout exposes them through `pnpm --silent sdp:q ''` (not `pnpm exec`, which cannot resolve the package's own bin) | before answering a corpus question or authoring intent — query the graph, then follow the carrying Specs | +| `.agents/skills/sdp-agent-surface/` + `.agents/skills/sdp-authoring/` + `.agents/skills/sdp-sessions/` + `docs/agent-surface/recipes.md` | **the agent on-ramps** — repository-owned reading, authoring, and advisory delivery-session skills (also exposed to Claude through the `.claude/skills` symlink) plus the eleven runnable `sdp q` bodies; see "Query the graph first" below | before answering a corpus question, authoring intent, or routing delivery work — query the graph, then follow the carrying Specs | | `examples/checkout-v1` | **the worked MVP example** (TS DSL tracer bullet) — specs, anchors, untracked `generated/` (regenerated in-pipeline); walkthrough in its README | when proving the loop end-to-end | | `explorations/` | **evidence only** (carrier exhibits, executable-example findings) — mapping evidence for design; **never promote spike code into product** | when judging design evidence; not a source tree to ship | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code — highest primary-numbered plan's status header, plus active subplans it designates; if DRAFTED, also the latest ✅ EXECUTED/RUN plan | @@ -66,34 +73,43 @@ Progressive disclosure — start at the top, follow the pointers down. | `reviews/` | **archived session reviews** (implementation, founding-ideation, adversarial + prompts) — durable findings already folded into plans/DECISIONS; read for provenance | rarely | > Concept docs still carry implementation detail (TS shapes, DSL, graph JSON) for **unsettled and -> post-MVP** design. **Intended truth** (ratified decisions, concept material, and — once -> authored — specs) is authoritative for design claims; **`src/` and the test suite** are -> authoritative evidence of current realization. A disagreement is **drift to resolve** — fix the -> stale side deliberately; do not invent a third behavior, and do not silently promote code into -> intent. A clean concept/representation split remains a recorded future direction. +> post-MVP** design — on a disagreement with `src/`, the drift rule above applies: fix the stale +> side deliberately, never invent a third behavior. A clean concept/representation split remains +> a recorded future direction. + +## Query the graph first + +The graph is the sole read model, and `sdp q` is the agent front door (MD-22): it derives the +graph in process and evaluates a plain JavaScript async-function body you supply, with three +bindings injected — `g` (the reader), `graph` (the raw schema), `report` (the validation +report). `return` is the output contract; add `--json` for machine-readable output. For any +corpus question — what a Spec guarantees, what is ready but unimplemented, what a change +touches, where a concept lives — script the graph instead of reading `.sdp.md` files by hand: + +```bash +# The build backlog (recipe 1, condensed): ready non-example Specs with no implementation binding +pnpm --silent sdp:q 'return g.specs().filter((s) => s.statedReadiness === "ready" && s.specKind !== "example" && !s.deliveryFacts.includes("implemented")).map((s) => s.id)' + +# Concept search (recipe 6): where does a concept live? +pnpm --silent sdp:q 'return g.findByConcept("readiness floor").slice(0, 5).map((n) => n.id)' +``` + +The full CLI surface is `sdp build · validate · view · import · q`. The eleven runnable recipe +bodies live in `docs/agent-surface/recipes.md` (each executed as written by +`test/recipes.test.ts`), and the repository-owned skills (`sdp-agent-surface` for reading, +`sdp-authoring` for writing intent, `sdp-sessions` for advisory work-shape routing) are the +on-ramps. In this checkout, always go through +`pnpm --silent sdp:q ''` — `pnpm exec` cannot resolve the package's own bin. ## The build path -The MVP proves the founding principle on **one** bounded context — Order Management, `pack:checkout-v1`, ~8–12 -specs (`spec:orders.create-order` + a few child scenarios/rules + 1 NFR + the parent `spec:orders.order-management` -behavior + the pack); **not** the whole checkout flow. The worked example lives at `examples/checkout-v1` -(documented walkthrough in its README). It is built as thin **end-to-end slices on the Phase 0 -foundation**. `docs/concept/07` is the slice roadmap; **`plans/` holds the live, canonical per-session plan** — -read it before writing code. - -**MVP slices 0–5 are complete** (plan 10). The table below is provenance, not the live backlog — -live work is the highest primary-numbered plan under `plans/` (currently the self-hosting plan). - -| Slice | Delivers | -|---|---| -| **0** | **Phase 0 — the protocol as code**: the `Spec` primitive, its three descriptors, the relation set, and every validator, as typed code. The extractor, the graph schema, and every check presuppose it — the foundation, not a detour. | -| **1** | Spec **extraction** over the DSL → a basic graph (nodes + declared relations) → `graph.json`. | -| **2** | Generic anchors + implementation binding + spec↔test linkage → `verifies` edges (`anchored` claim). | -| **3** | Core conformance + honesty checks (referential integrity · duplicate IDs · honest readiness against the floor · orphans · `verifies` linkage · authoring-shape honesty) + the CI gate. | -| **4** | The agent surface (the `reader` — entry adapters + impact) + the Design Review / one generated read-only view — both fully derived. | -| **5** | Polish: the CLI surface resolved (`build` · `validate` · `view`; `explain`/`search` below the second-caller bar), one diagnostic rendering rule, the documented example walkthrough, the clean-repo determinism test. | - -> **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the DSL and extractor are +The MVP proved the founding principle on **one** bounded context — Order Management, +`pack:checkout-v1` — built as thin **end-to-end slices on the Phase 0 foundation**; the worked +example lives at `examples/checkout-v1` (documented walkthrough in its README). **Slices 0–5 are +complete** — the per-slice record is plan 10 and the roadmap `docs/concept/07`. Live work is the +highest primary-numbered plan under `plans/` — read it before writing code. + +> **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the carrier and extractor are > forced to be usable before they are finished. If the example stops extracting or validating, fix the carrier or extractor — not the example. ## Two reading conventions @@ -119,6 +135,13 @@ Every doc honours both — never mistake one half for the other: ## Working discipline +- **Query before you read.** A corpus question goes to the graph first (`pnpm --silent sdp:q` + with a recipe body), then to the carrying Spec it points at. Scanning `specs/` files to learn + state is a smell — the graph is derived from the same carrier and is always current. +- **Write lean, and write for outsiders.** Cut unnecessary verbosity and noise in every session + artifact — plans, records, summaries, spec prose. Use technical but plain language a wider + open-source audience can follow: the ratified terms are the shared vocabulary, not a license + for dense insider prose. - **Terminology is ratified, not provisional.** Use the exact terms in the language base; flag, don't silently invent, new ones. The docs speak the ratified language end-to-end — a residual pre-ratification term (`abstraction`, `provenance`, `marker`, `facet`, "two axes", the old readiness ladder) is a **bug to fix against @@ -138,8 +161,11 @@ Every doc honours both — never mistake one half for the other: - **Plan vs execution.** Distinguish **PLAN-ONLY** work (designing, deciding) from **execution** (editing code or docs). For a plan under `plans/`, don't touch its target files unless the session is execution. - **Lineage is evidence, not template.** Prior art **`@libar-dev/architect`** (local clone when - present, e.g. a sibling `architect` checkout) taught us the problem in production; treat its - *shape* as evidence about the problem, never as the answer. + present, e.g. a sibling `architect` checkout) taught us the problem in production — source-first + annotations, one graph as the read model, a scriptable graph handle (the direct ancestor of + `sdp q`). Its old sins — a sprawling authored tag registry, workflow gates on the lifecycle, + hand-authored delivery status — are what this design deliberately rejects. Treat its *shape* + as evidence about the problem, never as the answer. - **A "verified" row is re-measured, never inherited.** A docket or ledger row claiming *fixed* or *verified* is re-checked against the tree at the moment of verification, never trusted from the row that closed it — the phase-4 close caught a "verified — intact" row that had been false since the commit after the one it cited. diff --git a/CONTEXT.md b/CONTEXT.md index f95359f..e5bd40c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -190,6 +190,10 @@ milestone** (descriptive vocabulary, optional roadmap projections, never gates) a git-tag projection) · **baseline** (a named approved snapshot; the **signed git tag is the approval artifact** — approval remains outside the model, never an authored primitive). +**Guidance-only labels:** `delivery session` and `work shape` describe an agent interaction and +its advisory entry in `sdp-sessions`; neither names a Protocol primitive, descriptor, relation, +delivery fact, workflow state, or graph state. + ## A worked dialogue (the language in use) > **Engineer:** Is `spec:orders.create-order` ready to implement? diff --git a/README.md b/README.md index a050292..68159ed 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,8 @@ pnpm exec sdp q 'return g.specs().map((spec) => spec.id)' checks, `view` generates the Design Review, `import` converts TypeScript carriers to Markdown, and `q` evaluates a local JavaScript query body against a freshly derived graph. Run `sdp --help` for the complete option contract. Adopters own their root and exclusion policy. + +The package also ships the three agent on-ramps — `sdp-agent-surface` (reading the graph), +`sdp-authoring` (authoring intent), and `sdp-sessions` (advisory delivery-session routing) — as +`SKILL.md` files under `node_modules/@libar-dev/software-delivery-protocol/.agents/skills/`, +beside the eleven recipe bodies at `docs/agent-surface/recipes.md` in the same package. diff --git a/docs/concept/06-consumers-and-projections.md b/docs/concept/06-consumers-and-projections.md index b3a8ae4..9f0251d 100644 --- a/docs/concept/06-consumers-and-projections.md +++ b/docs/concept/06-consumers-and-projections.md @@ -58,7 +58,7 @@ This is the `claim` taxonomy (P9) elevated to two consumable surfaces: the curat **Do not derive the architecture from code.** Architectural significance is an editorial judgment no import graph can produce. So: -- The divergence between the curated graph and the impact graph is **curation, not drift.** A curated graph that is a deliberately small curated selection of the mechanical firehose is *correct by design*. +- The divergence between the curated graph and the impact graph is **curation, not drift.** A curated graph that selects architectural significance from the mechanical firehose is *correct by design*. - Never "densify" the curated graph by inferring edges from imports — that just rebuilds the language server and throws away the curation. - The impact graph has exactly one firehose job (impact / re-test scope) plus two *assist* roles that never overwrite the curated layer: **propose candidates** (e.g. high-fan-in modules with no node) and **flag unambiguous drift** (a `satisfies` target whose source file was deleted) — narrow, honest signals only. diff --git a/package.json b/package.json index aeab542..29d5ae9 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "dist/**", "docs/agent-surface/recipes.md", ".agents/skills/sdp-agent-surface/SKILL.md", - ".agents/skills/sdp-authoring/SKILL.md" + ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/SKILL.md" ], "bin": { "sdp": "./dist/cli/sdp.js" diff --git a/plans/25-guidance-recovery-and-the-process-layer.md b/plans/25-guidance-recovery-and-the-process-layer.md new file mode 100644 index 0000000..0d46847 --- /dev/null +++ b/plans/25-guidance-recovery-and-the-process-layer.md @@ -0,0 +1,158 @@ +# Plan 25 — Guidance recovery and the process layer + +> **Status:** ✅ EXECUTED 2026-07-29 — Workstreams A and B landed as one graph-first batch. The +> seven audit partials are re-homed or explicitly dispositioned; the packaged `sdp-sessions` +> on-ramp is owned by `spec:consumers.delivery-session-on-ramp`; the three-part test kept +> slimming as advisory practice rather than a Decision Spec; and the complete repository gate is +> the re-measurement surface named in the close record. **What this plan does:** closes the two guidance debts +> the 2026-07-29 audits surfaced — (A) re-homes the seven PARTIAL items from the dissolution +> value-transfer audit (`reviews/13`: zero lost laws, seven pieces of orphaned authoring guidance +> and rationale), and (B) rebuilds the **process layer** gen 1 had and this repo deliberately +> dissolved along with its enforcement machinery: a sessions on-ramp that routes delivery work +> shapes (capture · design · implement · review · close) through graph recipes — **guidance +> without gates**. Everything here is advisory by law: checks police conformance and honesty, +> never content-quality and never workflow (the two-check families; adopt the nouns, reject the +> gates, MD-2). No new validators, no new graph verbs, no FSM. + +## Why now — the evidence + +Two reviews in one session converged on the same shape of debt: + +1. **The dissolution audit** (`reviews/13`) confirmed the dissolution wave lost no laws — but + seven pieces of *authoring guidance and rationale* now live only in git history. Three of them + (P1, P2, P5) are rules an authoring agent would actually need mid-session, and their absence + is invisible until someone models a straddling fact, hesitates over a child's readiness, or + proposes the Pack check the design already rejected. +2. **The gen-1 comparison** (this session's analysis of `@libar-dev/architect`'s formal spec and + `architect-sessions` skill) found the pattern behind the remaining gap: gen 1 encoded its + delivery process in *enforcement* machinery (FSM, ProcessGuard, scope gates) because its + authored facts could lie. This design made facts underivable-from-lies, which dissolved the + enforcement layer — but the **guidance half** of that layer (work-shape routing, session + pre-flights, promotion habits, handoff discipline) dissolved with it and was never rebuilt. + Before this plan, the practice lived in plan 24's prose and eleven recipes an agent had to + already know to run; the reading and authoring skills carried no session routing. + +The corpus itself was healthy; recipe and validation queries, rather than quoted counts, remain +the source for its current state. The debt was entirely in the guidance layer above it. + +## Workstream A — re-home the dissolution partials + +Small, surgical, execution-ready. Each item lands in its carrying Spec's prose (narrative or +rationale — content the typing law leaves open), or in a skill body; none changes a validator, +an enum, or a floor clause. Authored through the graph per the standing practice. + +| Item | What lands | Where | +| --- | --- | --- | +| **A1** (P1) | The one-kind rule's second half: a fact that straddles kinds is modeled as **two Specs joined by a relation**, never one Spec with a blurred kind | `spec:model.core-model` narrative + a line in the `sdp-authoring` skill | +| **A2** (P2) | The affirmative permission: a child Spec may be **born at a higher readiness than its parent** (descriptor independence, P8); only the floor's target bound constrains it | `spec:validation.readiness-floor` narrative | +| **A3** (P5) | The named negative ruling: there is **no duplicated-intent check on Packs** — a Pack states no truth, so there is nothing to duplicate; semantic duplication is human/agent judgment. Keep the motivation: large coherent groups of low-detail Specs must not make the build demand implementation | `spec:validation.pack-coherence`; `spec:model.pack-aggregate` remains the truth-free premise | +| **A4** (P3 + P6) | One rationale line each: why `constrainedBy`/`decidedBy` stay distinct from generic `dependsOn` (separately-queryable intents a generic edge would flatten); and MD-1's gloss refinement (gen-1's disease was dual-source binding invisible to the type system — executability returns as a *recovered surface*) | `spec:model.relations` rationale; `spec:decisions.executable-meta-model` rationale | +| **A5** (P4 + P7) | P4's graph-diff-as-two-projections behavior is findable on `spec:consumers.impact-graph` without promoting that Spec past `idea`. P7 is resolved by softening the current concept claim so it asserts editorial selection without an unevidenced magnitude; the historical figure remains audit provenance. | the impact-graph Spec; `docs/concept/06` | + +Done-signal for A: each re-homed line is grep-findable in its carrier; `npm run check` stays +green; corpus stays at zero findings; `reviews/13` items marked dispositioned in this plan's +done-record. + +## Workstream B — the process layer (sessions on-ramp) + +The larger piece, and design-first: **B1 is a design session before it is an execution session.** + +- **B1 — the `sdp-sessions` skill.** A repository-owned skill (beside `sdp-agent-surface` and + `sdp-authoring`, canonical home `.agents/skills/`), owned by the ready story behavior + `spec:consumers.delivery-session-on-ramp`, that routes the delivery work shapes: + - **capture / refine** — draft an `idea` Spec cheaply; enrich toward `scoped`/`defined`. + Pre-flight: concept search (recipe 6) to find the family and avoid duplicate intent. + - **design** — mature toward `ready`; resolve blocking open questions. Pre-flight: promotion + preflight (recipe 9) + readiness divergence (recipe 7). + - **implement** — "implement `spec:…`": bind anchors, make examples executable via generated + contracts. Pre-flight: build backlog (recipe 1) + the Spec's own context (guarantees & + verifiers, recipe 3). + - **review** — a Pack / Design Review pass over a related set. Pre-flight: Pack backbone + (recipe 5) + warn-level signals (recipe 8), or Spec context (recipe 3) + warn-level signals + when no Pack exists. + - **close / slim** — post-implementation slimming and drift check. Pre-flight: drift alarm + (recipe 2) + blast radius (recipe 4) over the session's diff. + The gen-1 lesson to keep: **state-driven, not intent-driven** — the same graph reads serve + every shape; the shape only picks which reference to open and which advisory pre-flight to run. + The gen-1 lesson to reject: no verdicts that block, no session scoping enforcement, no + unlock-reason machinery — the recipes *inform*, the human decides (MD-2). + Handoffs carry targets and pointers to commands or evidence locations that the next session + re-runs; they never carry an inherited verified verdict. +- **B2 — the idea-capture flow.** Extend `sdp-authoring` with the cheap-capture path: the minimal + lawful `idea`-tier Spec (the floor's idea clauses are the whole shape), where it lives, and the + promotion habit (recipe 9 before each rung). Gen 1 needed a folder convention and a line + budget; here the kind-conditional floor already *is* the tier shape — the missing piece is only + the documented habit. +- **B3 — slimming is advisory practice, not a decision.** Plan 24's oracle slice ran the full + loop through post-implementation slimming once. The three-part test rejects a Decision Spec: + the guidance is reversible and follows prose ownership plus enrich-in-place, while the universal + distillation boundary remains explicitly unresolved on `spec:model.enrichment-lifecycle`. + Close/slim guidance therefore preserves durable law and one prose owner without claiming a + universal deletion rule. + +Done-signal for B: the skill exists and is exercised by `test/skills.test.ts`-style checks (as +the existing skills are); every named pre-flight is one of the eleven recipe bodies (no new +verbs — recipes are the growth valve); a fresh session can open any work shape from the skill +alone without reading a plan. + +## Non-goals + +- **No MCP surface** — D6 stands; `sdp q` through the shell is the agent front door (MD-22). +- **No new validators or floor clauses** — everything in this plan is prose, skill, or + disposition; the check families are untouched. +- **No gen-1-style gates** — no FSM, no scope enforcement, no blocking pre-flights. +- **No new reader verbs** — a join freezes into the reader only at the second machine consumer; + every pre-flight above is an existing recipe. + +## Sequencing + +The review of this plan was B1's design session and ratified the decisions below. Execution landed +A and B in one batch. The new behavior Spec was authored at `defined`; its complete rules, +resolving parent, packaged skill, and implementation/test anchors then cleared the `ready` floor; +only after that evidence review did the human readiness statement move to `ready` and the close +queries run. + +## Ratified decisions + +1. `spec:consumers.delivery-session-on-ramp` is a behavior-kind, story-altitude child of + `spec:consumers.authoring-on-ramp`, targeted at `ready`; per Sequencing it was authored at + `defined` and stated `ready` only after its evidence cleared the floor. +2. Slimming remains advisory skill guidance; no Decision Spec or registry entry is created. +3. P7 softens the live concept claim instead of restoring a historical ratio. +4. `pnpm-workspace.yaml` does not change. Homebrew pnpm 10.4.1 reproduced + `packages field missing or empty`, while NVM pnpm 11.12.0 ran the documented query and returned + the corpus count. That is a PATH/toolchain discrepancy; the supported npm wrapper is the + execution fallback, not a reason to change workspace resolution. + +## Provenance + +- `reviews/13` — the dissolution value-transfer audit (the P1–P7 items and their suggested homes). +- This session's gen-1 comparison: `architect-sessions` (work shapes, state-driven routing, + universal session rules), the architect formal spec (the enforcement machinery this design + deliberately does not rebuild), and the PR #15 implementation-state review (anchor layer, + executable machinery, query surface — all healthy; the debt is guidance, not machinery). + +## Close record + +| Audit item | Disposition | +| --- | --- | +| P1 | `spec:model.core-model` plus `sdp-authoring` carry the two-Spec rule. | +| P2 | `spec:validation.readiness-floor` states child/parent readiness independence and its target bound. | +| P3 | `spec:model.relations` carries why the typed dependency relations remain distinct. | +| P4 | `spec:consumers.impact-graph` carries `graph(A)` versus `graph(B)` without promotion. | +| P5 | `spec:validation.pack-coherence` names the rejected duplicated-intent check and its motivation. | +| P6 | `spec:decisions.executable-meta-model` carries the recovered-surface rationale. | +| P7 | `docs/concept/06` no longer implies a measured selectivity magnitude; the historical ratio remains in `reviews/13`. | + +Re-measure from the repository root rather than inheriting this record: + +- recipe 1 through `npm run --silent sdp:q -- ''` returns no operational backlog; +- recipe 2 returns the same eight deliberate drift IDs recorded by plan 24, with no new row; +- recipe 3 for `spec:consumers.delivery-session-on-ramp` returns stated and derived `ready`, direct + `impl:protocol.delivery-session-on-ramp`, and enabled + `test:protocol.delivery-session-on-ramp`; +- `npm test -- --run test/skills.test.ts test/recipes.test.ts test/self-hosting-graph.test.ts` + exercises the guidance, recipe routing, and authored graph oracle; +- `npm test -- --run test/package-smoke.test.ts` proves the installed tarball carries all three + skills (use a writable temporary npm cache when the user cache is not writable); +- `npm run check` is the complete repository gate and the only whole-tree green verdict. diff --git a/reviews/13-dissolution-value-transfer-audit.md b/reviews/13-dissolution-value-transfer-audit.md new file mode 100644 index 0000000..bafd75e --- /dev/null +++ b/reviews/13-dissolution-value-transfer-audit.md @@ -0,0 +1,190 @@ +# 13 - Dissolution value-transfer audit + +**Reviewed:** the three dissolved concept docs — `02 — Core Model` (deleted at `fb1a14a`), +`03 — The One Graph` (deleted at `d5af001`), `05 — Validation & Honesty` (deleted at `9f2b069`) — +and the folded `DECISIONS.md` diary bodies (removed across `3bcfa45`, `2e9d5a6`, `b471189`), +each mapped to its current carriers with a gap check, at the owner's request (2026-07-29, on +`feature/protocol-self-application-phase-6`). + +**Method:** four parallel agents, one per deleted artifact. Each recovered the deleted content +from git at the commit before its deletion, enumerated every content unit (sections, laws, tables, +shape sketches, clauses, rulings, open questions), then located and **read** each claimed carrier — +a matching heading was not accepted as a match; the substance was compared. Verdicts: +**CARRIED** (carrier named) · **PARTIAL** (core carried, named substance missing) · +**DROPPED-DELIBERATE** (temporal/summary/history content consistent with the lean-registry +doctrine: "historical rationale lives in git, plans, and the Specs themselves") · **GAP** +(substantive design content found nowhere current). + +| Deleted artifact | Recovered from | Units traced | GAP | PARTIAL | +| --- | --- | --- | --- | --- | +| `docs/concept/02-core-model.md` | `fb1a14a^` | 43 | 0 | 3 | +| `docs/concept/03-the-one-graph.md` | `d5af001^` | 26 | 0 | 1 | +| `docs/concept/05-validation-and-honesty.md` | `9f2b069^` | 25 | 0 | 1 | +| DECISIONS.md diary bodies (MD-1..MD-21 + aux blocks) | each commit's parent | 19 decisions + 5 blocks | 0 | 2 | + +**Overall verdict: zero GAP-class losses.** Every law, rule, table row, enum, clause, and ruling in +the deleted material has a live carrier — a Spec, a surviving concept doc, `CONTEXT.md`, or a named +`src/` file. Several units were *sharpened* after dissolution rather than merely copied (duplicate-ID +exclusion semantics, tie-silent "did you mean" suggestions, oracle linkage rules, the split-report +family rule, regenerability thresholds). What dropped to git is consistently evidence detail — +line counts, corpus figures, war stories, reviewer provenance — exactly what the lean-registry +doctrine assigns there. + +**Seven PARTIALs survive for review** (all prose guidance / rationale, no lost laws), listed at the +end with suggested re-homing targets. Their disposition is recorded in plan 25's close record +(`plans/25`); the quoted carrier text below is the audit-time snapshot, not the current tree. + +--- + +## 1. `02 — Core Model` → the `spec:model.*` family + +| # | Unit | Class | Carried by | Notes | +| --- | --- | --- | --- | --- | +| 1 | Spec definition; enrich-in-place; one truth-primitive | CARRIED | `CONTEXT.md`, `specs/model/core-model.sdp.md`, `docs/concept/01` (P4), `docs/concept/04` | | +| 2 | Spec TS envelope shape | CARRIED | `src/model/descriptors.ts`, `src/model/sections.ts` | Protocol-as-code is the canonical shape now | +| 3 | Envelope stability contract (L9) | CARRIED | `docs/concept/01` L9 | "MINOR change" semver framing dropped; substance intact | +| 4 | Carrier note — `relations: {}` physical vs logical | CARRIED | `specs/carrier/envelope-contract.sdp.md`, `src/import/emit-markdown.ts:213` | | +| 5 | No Requirement/ImplementedRequirement split; Pack + anchor non-truth | CARRIED | `CONTEXT.md`, `specs/model/protocol-domain.sdp.md` | | +| 6 | Three-descriptor table; independence (P8) | CARRIED | `CONTEXT.md`, `docs/concept/01` (incl. "linear pipeline cannot represent this") | | +| 7 | `SpecKind` 8 values + display labels | CARRIED | `src/model/descriptors.ts`, `CONTEXT.md` | | +| 8 | **One-kind rule** — "straddling fact = two specs + a relation" | **PARTIAL** | type system only | Prose authoring rule absent — item P1 below | +| 9 | Altitude ladder; above-epic deferred; Scenario ≠ altitude | CARRIED | `CONTEXT.md` | | +| 10 | Readiness rung meanings | CARRIED | `CONTEXT.md`, `specs/validation/readiness-floor.sdp.md` | Floor spec is clause-level, more precise than the doc | +| 11 | **Non-linear progression; child born above parent** | **PARTIAL** | floor bound carried | Affirmative permission absent — item P2 below | +| 12 | "Why the enums" — 4 rationale points | CARRIED | `CONTEXT.md` named-coordinates table + rejected-terms ledger | | +| 13 | Delivery-fact table; hand-authoring = honesty violation | CARRIED | `CONTEXT.md`, core-model spec, `specs/validation/authored-honesty.sdp.md` | | +| 14 | Build backlog / drift alarm queries | CARRIED | `CONTEXT.md` | Updated by MD-24 (`kind≠example`) — deliberate evolution | +| 15 | Floor checks structure-to-derive, never the fact | CARRIED | readiness-floor spec, `specs/decisions/verification-posture-not-realization.sdp.md` | | +| 16 | Liveness ladder; binding never liveness | CARRIED | `specs/decisions/binding-not-liveness.sdp.md`, `CONTEXT.md` | | +| 17 | Sections table (8 sections, field detail) | CARRIED | `src/model/sections.ts` (full typed shapes) | | +| 18 | narrative/description ownership; constraints exception | CARRIED | `specs/carrier/prose-ownership-rule.sdp.md`, MD-19 spec | | +| 19 | Decision section carries no `status` | CARRIED | `src/model/sections.ts:95`, `CONTEXT.md` rejected list | | +| 20 | Typing law (MD-11) | CARRIED | `specs/decisions/typing-law.sdp.md`, spec-sections spec | | +| 21 | Section⟷kind duality; inline vs promote | CARRIED | `CONTEXT.md`, spec-sections spec | | +| 22 | Content-only sections (MD-10); promoted children count as evidence | CARRIED | MD-10 spec, `specs/validation/kind-evidence.sdp.md` | | +| 23 | `modelRefs` → standalone model specs only | CARRIED | pack-aggregate + pack-coherence specs | | +| 24 | Worked CreateOrder example | CARRIED | `examples/checkout-v1/` | Live example replaces two-snapshot pedagogy; TS DSL form superseded by MD-18 | +| 25 | No code/test fields on Spec; anchor → `satisfies` → `implemented` | CARRIED | `CONTEXT.md`, `docs/concept/04` §2, `specs/model/anchors.sdp.md` | | +| 26 | `exemplifies` dropped; verification = mode+criteria | CARRIED | `CONTEXT.md`, sections.ts | | +| 27 | Verifier semantics — direct, per-spec, non-transitive | CARRIED | spec-sections spec, `specs/validation/verification-linkage.sdp.md` | | +| 28 | Pack = truth-free aggregate; never reconciled against members | CARRIED | `specs/model/pack-aggregate.sdp.md`, `CONTEXT.md` | | +| 29 | Many packs per spec; `belongsTo` declared, no 4th claim | CARRIED | pack-aggregate spec, `CONTEXT.md` | | +| 30 | Pack coherence check list | CARRIED | `specs/validation/pack-coherence.sdp.md` | | +| 31 | Refinement vs aggregate; Pack = Design Review unit | CARRIED | pack-aggregate spec, `docs/concept/06` | | +| 32 | ID grammar + `#sub`; MVP namespaces | CARRIED | `specs/model/stable-ids.sdp.md` (now more precise), `src/ids.ts` | | +| 33 | `spec:decisions.*` convention; `doc:` external-only | CARRIED | stable-ids spec | | +| 34 | String IDs never imports; `spec-ids` union deferred (L8) | CARRIED | stable-ids spec, referential-integrity spec, `docs/concept/01` | | +| 35 | IDs carry no history | CARRIED | stable-ids spec | | +| 36 | Relation table (6 authored relations) | CARRIED | `specs/model/relations.sdp.md`, `CONTEXT.md` | | +| 37 | `supersedes` decision-only, both extant | CARRIED | `src/validate/validators.ts:449-455`, `CONTEXT.md` | | +| 38 | UML alignment («refine»/«trace»/«verify») | CARRIED | `CONTEXT.md` relations table "Industry anchor" column | | +| 39 | **`constrainedBy`/`decidedBy` ≠ generic `dependsOn` — why** | **PARTIAL** | `CONTEXT.md` gloss only | Rationale absent — item P3 below | +| 40 | `doc:` relation targets = named deferral (MD-16) | CARRIED | stable-ids spec, MD-16 spec | | +| 41 | `satisfies` derived, anchored; old `satisfiedBy` inversion note | CARRIED / DROPPED | current semantics carried; migration history → git | | +| 42 | Claims never merged into declared relations | CARRIED | `specs/validation/claim-separation.sdp.md` | | +| 43 | Blocking `openQuestions` blocks `defined`+ (MD-9) | CARRIED | `src/model/sections.ts:28-31`, readiness-floor `defined` clause | | + +## 2. `03 — The One Graph` → the `spec:extraction.*` family + +| Unit | Class | Carried by | Notes | +| --- | --- | --- | --- | +| Graph = projection of repo at a commit, never a second source | CARRIED | `specs/extraction/derive-graph.sdp.md`, `docs/concept/01` | | +| Two-pure-steps pipeline (`graph = f(repo)`, `output = f(graph)`) | CARRIED | derive-graph + `specs/extraction/build-pipeline.sdp.md`, `docs/concept/06` §1 | | +| Discovery by suffix, exclusion rules | CARRIED | `src/extract/discover.ts`, `specs/extraction/excludes.sdp.md`, MD-15 spec | | +| Anchor = identity + label + one target, no intent | CARRIED | `specs/model/anchors.sdp.md` — substantially deepened | | +| Inferred layer designed-in, empty in MVP; first producer = impact graph | CARRIED | derive-graph rule 5, `specs/consumers/impact-graph.sdp.md` (idea) | | +| Flat graph: arrays only, hierarchy via edges | CARRIED | derive-graph rule 2 | Rationale prose dropped — cosmetic | +| JSON sketch, schema `0.4.0` shapes | CARRIED | `src/graph/schema.ts` (code is the carrier) | | +| `nodeType` vs `specKind` split | CARRIED | `CONTEXT.md:105`, schema.ts | | +| Prose fields on singular owners; fixed key order | CARRIED | `specs/carrier/prose-ownership-rule.sdp.md`, `src/extract/serialize.ts` | | +| Consolidated edge-contract table | DROPPED-DELIBERATE | rows dissolved across relations / derive-graph / referential-integrity / readiness-floor / verification-linkage / pack specs | Every row's law verified individually carried; only the single-table *presentation* is gone | +| `decidedBy` `doc:` target = named deferral | CARRIED | `specs/decisions/carried-evidence.sdp.md` | | +| Delivery facts computed from edges, never propagated up `refines` | CARRIED | derive-graph rules 3–4, claim-taxonomy spec | | +| Determinism: byte-identical, sort orders, no timestamps | CARRIED | `specs/extraction/determinism.sdp.md` (measurable, sha256) | | +| `--check-clean` = independent rebuild self-comparison | CARRIED | determinism rule 2, `docs/concept/07` | | +| Two-tier non-static handling; MD all-or-nothing asymmetry | CARRIED | determinism rule 3, `docs/concept/04` §1 | | +| Claim table + authority; inferred never authoritative | CARRIED | claim-taxonomy spec, `CONTEXT.md`, `docs/concept/01` P3 | | +| Claim inheritance, no 4th claim | CARRIED | claim-taxonomy spec ("derivation is a mechanism, not a fourth claim") | | +| Ambiguity is loud (L2) | CARRIED | `docs/concept/01`, duplicate-ids + claim-separation specs | | +| `observed` aspirational; run results not ingested | CARRIED | spec-sections spec, `specs/observation/` | | +| `generated/` disposable; delete-and-rebuild same bytes | CARRIED | `specs/extraction/regenerability.sdp.md` rules 1–2 | | +| No-second-store (R2) with source-location carve-out | CARRIED | regenerability rule 2 (exact carve-out preserved) | | +| Graph DB deferred until measured pain | CARRIED | regenerability rules 3–5 — strengthened with thresholds | | +| Git is event log (all four bullets) | CARRIED | `docs/concept/01` near-verbatim | | +| **Graph diff = two projections** (`graph(A)` vs `graph(B)`) | **PARTIAL** | parts implied by determinism + 01; MVP impact is file-level | Item P4 below | +| Schema versioning: self-described; SemVer deferred; additive growth | CARRIED | `specs/extraction/schema-versioning.sdp.md` (all four clauses) | | + +## 3. `05 — Validation & Honesty` → the `spec:validation.*` family + +| # | Unit | Class | Carried by | Notes | +| --- | --- | --- | --- | --- | +| 1 | Framing; "checked, never workflow-gated"; both honesty guardrails | CARRIED | `CONTEXT.md` (verbatim), MD-1 spec, two-check-families spec | | +| 2 | Two check families with example lists | CARRIED | `specs/validation/two-check-families.sdp.md` + `.split-report` child | Sharpened beyond the doc | +| 3 | Error-fails-build vs gap-informs; `validator`/`gap`/`orphan` nouns | CARRIED | two-check-families rule 2, warn-level-signals spec, `CONTEXT.md` | | +| 4 | Layered-by-mechanism table; P7 | CARRIED | two-check-families rule 3, `docs/concept/07` (aspirational layers), `01` P7 | | +| 5 | One validation path (MD-14); phantom evaluated-form argument | CARRIED | MD-14 spec (rationale survives explicitly) | | +| 6 | Referential integrity + "did you mean" | CARRIED | referential-integrity spec + children | Sharpened: suggestion silent on two-candidate tie | +| 7 | Duplicate IDs, never auto-merged | CARRIED | duplicate-ids spec + `.dual-carrier` | Redesigned stronger: extraction-time exclusion | +| 8 | Claim separation; kind-typed edge-endpoint contracts | CARRIED | claim-separation spec + relations spec + `src/validate/validators.ts` | | +| 9 | `verifies` linkage; wrong-kind verifier confers nothing | CARRIED | verification-linkage spec + children | Extended with oracle traces | +| 10 | Authoring-shape honesty | CARRIED | authored-honesty spec + `.section-authored-fact` | | +| 11 | Derived-facts honesty; faked fact never silences gap check | CARRIED | authored-honesty (+ `.unearned-stated-fact`), warn-level-signals rule 2 | | +| 12 | Honest readiness against floor | CARRIED | readiness-floor spec + children | | +| 13 | Orphans + ready-gap as warnings; severity override deferred | CARRIED | warn-level-signals spec + children (deferral restated) | | +| 14 | Cross-cutting L2 / L3 | CARRIED | `docs/concept/01` (L2, L3 survive) | L3 has no dedicated validation Spec; principle doc is the carrier | +| 15 | Kind-blind floor clause table; cumulative; floor-not-quota | CARRIED | readiness-floor spec — clause-for-clause match | | +| 16 | Per-kind evidence table (all 7 rows incl. contract interim) | CARRIED | kind-evidence spec — row-for-row match | | +| 17 | Three table laws (monotonic, promotion-neutral, honest convergence) | CARRIED | kind-evidence + MD-12 + MD-16 specs | | +| 18 | MD-13 floor-table-as-truth | CARRIED | readiness-floor spec + `src/validate/readiness-floor.ts` | | +| 19 | `ready` ≠ delivery fact; no approval fact; signed git tag | CARRIED | readiness-floor, `specs/consumers/design-review.sdp.md`, `CONTEXT.md` | | +| 20 | Stated vs derived readiness; one-directional divergence banner | CARRIED | readiness-floor + `specs/consumers/derived-readiness-banner.sdp.md` + children | | +| 21 | **Pack coherence — no duplicated-intent check (negative ruling)** | **PARTIAL** | pack-coherence + pack-aggregate specs carry the checks and posture | Named rejection absent — item P5 below | +| 22 | Validator self-testing fixtures | CARRIED | `specs/validation/validator-self-testing.sdp.md` — strengthened | | +| 23 | Aspirational tiers (`observed`, `--lenient`, caching, custom rules) | CARRIED | `docs/concept/07`, `specs/observation/runtime-overlay.sdp.md` | | +| 24 | "What CI guarantees at MVP" recap | DROPPED-DELIBERATE | — | Pure summary; every guarantee individually carried | +| 25 | Historical asides (pre-MD-12 failure; verb note) | DROPPED-DELIBERATE | git; "stated, never claimed" survives in `CONTEXT.md` | | + +## 4. DECISIONS.md diary bodies → `specs/decisions/*.sdp.md` + +Recovery: `3bcfa45^` (full 553-line diary), plus `2e9d5a6^` and `b471189^`. MD-22/23/24 and the +D3/D5/D6 / plain-language / concept-dissolve decisions were born as Specs — no diary ever existed. + +| MD-n | Name | Class | Carried by | +| --- | --- | --- | --- | +| MD-1 | executable meta-model | **PARTIAL** (minor) | MD-1 spec + guardrails verbatim in `AGENTS.md`/`CONTEXT.md`; the 2026-07-11 gloss refinement missing — item P6 below | +| MD-2 | adopt the nouns, reject the gates | CARRIED | `adopt-the-nouns.sdp.md` (both term tests compressed into consequence) | +| MD-4 | one primitive, named coordinates | CARRIED | `one-primitive.sdp.md` (combinatorial-explosion rationale carried) | +| MD-5 | protocol naming | CARRIED | `protocol-naming.sdp.md` (the "surgical split" survives) | +| MD-7 | binding, never liveness | CARRIED | `binding-not-liveness.sdp.md` + `src/model/anchors.ts` comments (all 3 points + both rejected remedies) | +| MD-8 | generic `codeAnchor` | CARRIED | `src/model/anchors.ts:6-14` doc-comment — the fold target the diary itself named | +| MD-9 | open-questions home | CARRIED | `src/model/sections.ts` + readiness-floor `defined` clause | +| MD-10 | content-only sections | CARRIED | `content-only-sections.sdp.md` (incl. double-linkage rationale) | +| MD-11 | typing law | CARRIED | `typing-law.sdp.md` + `CONTEXT.md:241` (rejected `decision.status` vocabulary in the ledger) | +| MD-12 | kind-conditional floor | CARRIED | MD-12 spec + kind-evidence spec (contract-row repoint trigger survives at line 23) | +| MD-13 | floor-table-as-truth | CARRIED | readiness-floor spec ("never a second floor") + `src/validate/readiness-floor.ts` header | +| MD-14 | one validation path | CARRIED | MD-14 spec — phantom-validation rationale survives explicitly | +| MD-15 | `.sdp.ts` extension | CARRIED | MD-15 spec (ruling + re-point) | +| MD-16 | carried evidence | CARRIED | MD-16 spec (all 3 points + rejected readiness-gate) | +| MD-17 | point-per-example | CARRIED | MD-17 spec (both rejected alternatives; gen-1 corpus figures → git) | +| MD-18 | carrier ruling | CARRIED | MD-18 spec (all three rejected paths in the rationale) | +| MD-19 | prose-ownership law | CARRIED | MD-19 spec (both rejected homes + refuse-loudly) | +| MD-20 | consumer-exclusion contract | CARRIED | MD-20 spec + `spec:extraction.excludes` | +| MD-21 | envelope-grammar posture | CARRIED | MD-21 spec + `package.json` (`"yaml": "2.9.0"` exact pin realized in code) | + +Auxiliary blocks: preamble decoder, R-series change-log, scope note — DROPPED-DELIBERATE (substance +verified live elsewhere). D1–D6 shorthand — CARRIED (lean table; D3/D5/D6 have their own Specs). +Measured-evidence table — PARTIAL (item P7 below). + +--- + +## The seven review items (all PARTIAL; no lost laws) + +| # | Missing substance | Was in | Suggested home | +| --- | --- | --- | --- | +| P1 | The one-kind authoring rule's second half: _"if a fact straddles kinds, model it as two specs with a relation between them"_ — enum enforces single-kind structurally, but the split guidance is nowhere (zero grep hits) | 02 §2 | `spec:model.core-model` narrative, or the `sdp-authoring` skill | +| P2 | The affirmative permission: _a child spec can be born at a higher readiness than its parent_ (e.g. a low-altitude example inside an already-`ready` feature) — the floor bound survives, the permission doesn't | 02 §2 | `spec:validation.readiness-floor` narrative or `CONTEXT.md` | +| P3 | Why `constrainedBy` / `decidedBy` stay distinct from generic `dependsOn`: _"high-value, separately-queryable intents a generic dependency edge would flatten"_ | 02 §6 | `spec:model.relations` rationale | +| P4 | The consequence statement: _comparing two commits is comparing two graphs — `graph(A)` vs `graph(B)` → added/removed/changed nodes and edges, change-impact without a second store_ — constituent parts survive, the framing doesn't | 03 §5 | `spec:consumers.impact-graph` (currently `idea`) when it matures | +| P5 | The named negative ruling: _there is **no** duplicated-intent check on Packs_ (a Pack states no truth, so there is nothing to duplicate; semantic duplication is human/agent judgment) + its motivation (large coherent groups of low-detail specs without the build demanding implementation) — now only inferable | 05 §4 | `spec:validation.pack-coherence` or `spec:model.pack-aggregate` | +| P6 | MD-1's 2026-07-11 gloss refinement: _gen-1's disease was dual-source binding invisible to the type system, not executable specs; executability returns as a recovered surface_ — the rationale that makes MD-18's DSL retirement and the oracle work intelligible; today only hinted by "dual-source truth path" in the carrier-ruling spec | DECISIONS diary | `spec:decisions.executable-meta-model` rationale (borderline DROPPED-DELIBERATE — full text lives in plan 12 §8) | +| P7 | The curated-graph selectivity measurement (single-digit-to-~25%) backing the still-asserted, now figure-free claim at `docs/concept/06:61` ("a deliberately small curated selection") | DECISIONS measured-evidence table | either restore the figure to `06` or soften the claim; the other dropped figure (~⅕ tokens) is superseded by the fresh 73.1%/38.0% measurements | diff --git a/specs/consumers/authoring-on-ramp.sdp.md b/specs/consumers/authoring-on-ramp.sdp.md index 1b20add..d3ad580 100644 --- a/specs/consumers/authoring-on-ramp.sdp.md +++ b/specs/consumers/authoring-on-ramp.sdp.md @@ -13,6 +13,7 @@ relations: ## Behavior - rule: An author starts from the build-backlog and drift-alarm recipes, reads carrying Specs for law, and edits the canonical carrier. +- rule: Cheap capture starts with the minimal lawful `idea` carrier in the family found through concept search, and every later readiness edit is preceded by the promotion-preflight recipe and remains a human statement. - rule: The executable transition is taught as parent example space, child bound point, generated contracts, colocated `bindExample` and `specTest`, and a mutation-probed red result before the human states `ready`. - rule: Contract-generation refusals are diagnosed through `sdp build`; query-time validation does not claim to report codegen findings. - rule: Verifier-binding queries report graph-visible anchors and cannot detect a suite whose generated contract is never bound. diff --git a/specs/consumers/delivery-session-on-ramp.sdp.md b/specs/consumers/delivery-session-on-ramp.sdp.md new file mode 100644 index 0000000..1be4264 --- /dev/null +++ b/specs/consumers/delivery-session-on-ramp.sdp.md @@ -0,0 +1,20 @@ +--- +id: spec:consumers.delivery-session-on-ramp +kind: behavior +altitude: story +readiness: ready +relations: + refines: spec:consumers.authoring-on-ramp +--- +# Delivery sessions route work from current graph state + +## Intent +- outcome: Let an agent enter capture, design, implementation, review, or close work from current graph evidence without inventing workflow state or gates. + +## Behavior +- rule: Work shapes are advisory entries over the same current graph; they are neither phases nor a required sequence, and a session may enter or revisit any shape. +- rule: Capture or refinement uses concept search, the lower ladder, and promotion preflight; design uses promotion preflight and readiness divergence. +- rule: Implementation uses the build backlog and the target Spec context; review uses the Pack backbone and warn-level signals, or the target Spec context and warn-level signals when no Pack exists. +- rule: Close uses the drift alarm and changed-file blast radius; optional slimming preserves durable law and one prose owner without claiming a universal distillation boundary. +- rule: A handoff names targets, changed files, current readiness, findings or open questions, and commands or evidence locations to re-run; it never carries an inherited verification verdict. +- rule: Every preflight informs human or agent judgment and never authorizes, blocks, scopes, or advances delivery work. diff --git a/specs/consumers/impact-graph.sdp.md b/specs/consumers/impact-graph.sdp.md index f38f221..fd44e07 100644 --- a/specs/consumers/impact-graph.sdp.md +++ b/specs/consumers/impact-graph.sdp.md @@ -17,4 +17,5 @@ relations: ## Behavior - rule: Mechanical import and symbol structure is inferred and remains distinct from the sparse curated graph. +- rule: Comparing commits derives `graph(A)` and `graph(B)` and reports added, removed, or changed nodes and edges without persisting either projection as a second store. - rule: Candidate relationship suggestions and unambiguous-drift flags are assistive outputs; neither authors intent or silently rewrites the curated graph. diff --git a/specs/decisions/executable-meta-model.sdp.md b/specs/decisions/executable-meta-model.sdp.md index 3568509..ec4e4e3 100644 --- a/specs/decisions/executable-meta-model.sdp.md +++ b/specs/decisions/executable-meta-model.sdp.md @@ -14,5 +14,5 @@ relations: ## Decision - context: Delivery tools can describe work without making their model executable. - decision: The Protocol models authored Specs, Packs, and anchors in typed code, derives one graph, and checks conformance and honesty. -- rationale: Executable specs alone and workflow tooling omit the meta-model contract. +- rationale: Gen 1's failure was dual-source binding hidden from the type system, not executability itself; the typed meta-model removes that hidden truth path while allowing executability to return as a recovered surface. - consequence: The Protocol is deterministically validated without judging content quality or enforcing workflow. diff --git a/specs/model/core-model.sdp.md b/specs/model/core-model.sdp.md index 8377822..2a73d6c 100644 --- a/specs/model/core-model.sdp.md +++ b/specs/model/core-model.sdp.md @@ -16,6 +16,7 @@ relations: - **Spec** — The one authored truth-primitive, enriched in place without changing artifact type. - **envelope** — The stable outer shape of id, title, kind, altitude, readiness, and relations; sections carry extension detail. - **kind** — The true subtype that categorizes a Spec's truth and changes its required detail and validation. +- **one-kind rule** — A Spec states one category of truth; when one fact straddles kinds, author two Specs and join them with the relation that preserves their distinct intents. - **altitude** — The scope position `epic`, `feature`, or `story`. - **readiness** — The author-stated design-maturity position `idea`, `scoped`, `defined`, or `ready`, checked against a structural floor. - **delivery fact** — A derived realization signal such as implemented or has-verifier; it is never authored readiness. diff --git a/specs/model/relations.sdp.md b/specs/model/relations.sdp.md index 34ba337..e8d9d52 100644 --- a/specs/model/relations.sdp.md +++ b/specs/model/relations.sdp.md @@ -17,5 +17,6 @@ relations: - **dependsOn** — A dependent Spec points to the Spec it needs. - **constrainedBy** — A bounded Spec points to its rule, constraint, or policy Spec. - **decidedBy** — A shaped Spec points to its Decision Record. +- **typed dependency distinction** — `constrainedBy` and `decidedBy` preserve separately queryable intents that a generic `dependsOn` edge would flatten. - **verifies** — A verifier points to the Spec it verifies. - **supersedes** — A current Decision Record points forward to the decision it replaces. diff --git a/specs/self-hosting.pack.sdp.ts b/specs/self-hosting.pack.sdp.ts index 9dadf16..de2779b 100644 --- a/specs/self-hosting.pack.sdp.ts +++ b/specs/self-hosting.pack.sdp.ts @@ -42,6 +42,7 @@ export const selfHostingV1Pack = pack({ ref("spec:consumers.reader"), ref("spec:consumers.edit-model"), ref("spec:consumers.authoring-on-ramp"), + ref("spec:consumers.delivery-session-on-ramp"), ref("spec:consumers.agent-surface.authoring-recipes"), ref("spec:consumers.intent-composition"), ref("spec:model.protocol-domain"), diff --git a/specs/validation/pack-coherence.sdp.md b/specs/validation/pack-coherence.sdp.md index 63ea845..a057b83 100644 --- a/specs/validation/pack-coherence.sdp.md +++ b/specs/validation/pack-coherence.sdp.md @@ -14,6 +14,7 @@ relations: ## Rule - Pack membership must not repeat a Spec, and every modelRef must resolve to a model-kind Spec. - Membership is counted on the derived belongsTo edges the manifest re-expresses, so a repeated manifest entry is named once per repeated member. +- There is no duplicated-intent check on a Pack: a Pack states no system truth to duplicate, and semantic overlap among its members remains human or agent review rather than validator judgment. This lets a coherent group contain low-detail Specs without turning grouping into implementation demand. - The realizing validator entrypoint is `checkPackCoherence` in `src/validate/validators.ts`. ## Example space diff --git a/specs/validation/readiness-floor.sdp.md b/specs/validation/readiness-floor.sdp.md index 06fea16..a075acb 100644 --- a/specs/validation/readiness-floor.sdp.md +++ b/specs/validation/readiness-floor.sdp.md @@ -22,6 +22,7 @@ relations: - The `scoped` floor adds three clauses: the intended outcome is stated, at least one authored relation is declared, and the kind's natural evidence is present. - The `defined` floor adds two clauses: the kind's natural evidence is complete, and no open question the Spec records is flagged as blocking. - The `ready` floor reads the Spec's own edges through three clauses: every authored relation resolves to a known target, every `refines` and `dependsOn` target itself stands at least `defined`, and every anchor bound to the Spec resolves. +- Readiness is independent across a refinement relation: a child may be authored at a higher readiness than its parent. Only the child's own cumulative floor applies, including the `ready` target bound above when the child states `ready`. - The anchor clause reads the bindings that are present, so a Spec carrying no anchor clears it — the floor never demands a binding an author has not made. - Only relations the Spec itself declares count toward the relation clauses; membership of a Pack is derived from the manifest and never stands in for an authored relation. - Every clause stated here is kind-blind. The two evidence clauses are the one kind-conditional place in the floor, and what counts as a kind's natural evidence is stated in full by the refining Spec that carries the per-kind evidence table. diff --git a/test/package-smoke.test.ts b/test/package-smoke.test.ts index f12ceae..6a9e5c6 100644 --- a/test/package-smoke.test.ts +++ b/test/package-smoke.test.ts @@ -153,6 +153,7 @@ describe("published package surface", () => { const adopterAssets = [ ".agents/skills/sdp-agent-surface/SKILL.md", ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/SKILL.md", "docs/agent-surface/recipes.md", ]; @@ -255,6 +256,16 @@ void [${expectedRootExports.join(", ")}]; ), "utf8", ); + const installedSessionsSkill = await readFile( + join( + consumer, + "node_modules", + "@libar-dev", + "software-delivery-protocol", + ".agents/skills/sdp-sessions/SKILL.md", + ), + "utf8", + ); expect(JSON.parse(driver)).toEqual({ exports: [...expectedRootExports].sort(), @@ -266,6 +277,7 @@ void [${expectedRootExports.join(", ")}]; expect(barrelCheck).toBe("barrel imports available\n"); expect(installedAgentSkill).toContain("name: sdp-agent-surface"); expect(installedAuthoringSkill).toContain("name: sdp-authoring"); + expect(installedSessionsSkill).toContain("name: sdp-sessions"); } finally { await rm(packageRoot, { force: true, recursive: true }); } diff --git a/test/recipes.test.ts b/test/recipes.test.ts index 77023d3..1bde4cf 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -196,6 +196,7 @@ describe("the agent-surface recipe corpus", () => { recipesPath, ".agents/skills/sdp-agent-surface/SKILL.md", ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/SKILL.md", ]; const packageJson = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as { scripts: Record; diff --git a/test/self-hosting-graph.test.ts b/test/self-hosting-graph.test.ts index f5a63c4..e576a52 100644 --- a/test/self-hosting-graph.test.ts +++ b/test/self-hosting-graph.test.ts @@ -90,12 +90,12 @@ describe("the self-hosting corpus", () => { // The literals are the corpus checkpoint. The authored arrays are measured against the same // literals rather than standing in for them, so a transcription slip in an oracle module // cannot certify itself by moving both sides of a comparison at once. - expect(result.counts).toEqual({ specs: 128, packs: 1, anchors: 116 }); - expect(expectedSpecs).toHaveLength(128); - expect(expectedPackMembers).toHaveLength(128); - expect(expectedAnchors).toHaveLength(116); - expect(result.graph.nodes).toHaveLength(245); - expect(result.graph.edges).toHaveLength(462); + expect(result.counts).toEqual({ specs: 129, packs: 1, anchors: 118 }); + expect(expectedSpecs).toHaveLength(129); + expect(expectedPackMembers).toHaveLength(129); + expect(expectedAnchors).toHaveLength(118); + expect(result.graph.nodes).toHaveLength(248); + expect(result.graph.edges).toHaveLength(466); }); it("rosters exactly the authored Spec, Pack, and anchor node ids", () => { @@ -145,7 +145,7 @@ describe("the self-hosting corpus", () => { }), {}, ), - ).toEqual({ defined: 37, idea: 4, ready: 86, scoped: 1 }); + ).toEqual({ defined: 37, idea: 4, ready: 87, scoped: 1 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { diff --git a/test/self-hosting-oracle/anchors.ts b/test/self-hosting-oracle/anchors.ts index 5588d64..b3a5143 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -63,6 +63,16 @@ export const expectedAnchors = [ constant: "authoringOnRampImplementationAnchor", site: "function readSkill", }, + { + id: "impl:protocol.delivery-session-on-ramp", + nodeType: "CodeNode", + label: "asserts realization of the shipped advisory delivery-session skill document", + type: "satisfies", + target: "spec:consumers.delivery-session-on-ramp", + file: "test/skills.test.ts", + constant: "deliverySessionOnRampImplementationAnchor", + site: "function readSkill", + }, { id: "impl:protocol.authoring-recipes", nodeType: "CodeNode", @@ -83,6 +93,16 @@ export const expectedAnchors = [ constant: "authoringOnRampTestAnchor", site: 'describe("Protocol skill assets"', }, + { + id: "test:protocol.delivery-session-on-ramp", + nodeType: "Anchor", + label: "skill-asset checks verify advisory delivery-session routing", + type: "verifies", + target: "spec:consumers.delivery-session-on-ramp", + file: "test/skills.test.ts", + constant: "deliverySessionOnRampTestAnchor", + site: 'describe("Protocol skill assets"', + }, { id: "test:protocol.authoring-recipes", nodeType: "Anchor", diff --git a/test/self-hosting-oracle/consumers.ts b/test/self-hosting-oracle/consumers.ts index 3a1d23a..e86c104 100644 --- a/test/self-hosting-oracle/consumers.ts +++ b/test/self-hosting-oracle/consumers.ts @@ -804,6 +804,7 @@ export const consumersSpecs = [ behavior: { rules: [ "An author starts from the build-backlog and drift-alarm recipes, reads carrying Specs for law, and edits the canonical carrier.", + "Cheap capture starts with the minimal lawful `idea` carrier in the family found through concept search, and every later readiness edit is preceded by the promotion-preflight recipe and remains a human statement.", "The executable transition is taught as parent example space, child bound point, generated contracts, colocated `bindExample` and `specTest`, and a mutation-probed red result before the human states `ready`.", "Contract-generation refusals are diagnosed through `sdp build`; query-time validation does not claim to report codegen findings.", "Verifier-binding queries report graph-visible anchors and cannot detect a suite whose generated contract is never bound.", @@ -813,6 +814,32 @@ export const consumersSpecs = [ }, deliveryFacts: ["implemented", "has-verifier"], }, + { + id: "spec:consumers.delivery-session-on-ramp", + specKind: "behavior", + altitude: "story", + readiness: "ready", + file: "specs/consumers/delivery-session-on-ramp.sdp.md", + title: "Delivery sessions route work from current graph state", + narrative: null, + sections: { + intent: { + outcome: + "Let an agent enter capture, design, implementation, review, or close work from current graph evidence without inventing workflow state or gates.", + }, + behavior: { + rules: [ + "Work shapes are advisory entries over the same current graph; they are neither phases nor a required sequence, and a session may enter or revisit any shape.", + "Capture or refinement uses concept search, the lower ladder, and promotion preflight; design uses promotion preflight and readiness divergence.", + "Implementation uses the build backlog and the target Spec context; review uses the Pack backbone and warn-level signals, or the target Spec context and warn-level signals when no Pack exists.", + "Close uses the drift alarm and changed-file blast radius; optional slimming preserves durable law and one prose owner without claiming a universal distillation boundary.", + "A handoff names targets, changed files, current readiness, findings or open questions, and commands or evidence locations to re-run; it never carries an inherited verification verdict.", + "Every preflight informs human or agent judgment and never authorizes, blocks, scopes, or advances delivery work.", + ], + }, + }, + deliveryFacts: ["implemented", "has-verifier"], + }, { id: "spec:consumers.agent-surface.authoring-recipes", specKind: "behavior", @@ -859,6 +886,7 @@ export const consumersSpecs = [ behavior: { rules: [ "Mechanical import and symbol structure is inferred and remains distinct from the sparse curated graph.", + "Comparing commits derives `graph(A)` and `graph(B)` and reports added, removed, or changed nodes and edges without persisting either projection as a second store.", "Candidate relationship suggestions and unambiguous-drift flags are assistive outputs; neither authors intent or silently rewrites the curated graph.", ], }, diff --git a/test/self-hosting-oracle/decisions.ts b/test/self-hosting-oracle/decisions.ts index 94f8521..302dfcc 100644 --- a/test/self-hosting-oracle/decisions.ts +++ b/test/self-hosting-oracle/decisions.ts @@ -253,7 +253,9 @@ export const decisionsSpecs = [ context: "Delivery tools can describe work without making their model executable.", decision: "The Protocol models authored Specs, Packs, and anchors in typed code, derives one graph, and checks conformance and honesty.", - rationale: ["Executable specs alone and workflow tooling omit the meta-model contract."], + rationale: [ + "Gen 1's failure was dual-source binding hidden from the type system, not executability itself; the typed meta-model removes that hidden truth path while allowing executability to return as a recovered surface.", + ], consequences: [ "The Protocol is deterministically validated without judging content quality or enforcing workflow.", ], diff --git a/test/self-hosting-oracle/declared-relations.ts b/test/self-hosting-oracle/declared-relations.ts index d6ac633..aabee42 100644 --- a/test/self-hosting-oracle/declared-relations.ts +++ b/test/self-hosting-oracle/declared-relations.ts @@ -268,6 +268,7 @@ export const expectedDeclaredRelations = [ ["spec:consumers.reader", "refines", "spec:consumers.agent-surface"], ["spec:consumers.edit-model", "refines", "spec:consumers.projections-model"], ["spec:consumers.authoring-on-ramp", "refines", "spec:consumers.edit-model"], + ["spec:consumers.delivery-session-on-ramp", "refines", "spec:consumers.authoring-on-ramp"], ["spec:consumers.agent-surface.authoring-recipes", "refines", "spec:consumers.agent-surface"], ["spec:consumers.intent-composition", "refines", "spec:consumers.edit-model"], ["spec:model.protocol-domain", "refines", "spec:protocol.self-hosting"], diff --git a/test/self-hosting-oracle/model.ts b/test/self-hosting-oracle/model.ts index 9a82bf9..9059a5d 100644 --- a/test/self-hosting-oracle/model.ts +++ b/test/self-hosting-oracle/model.ts @@ -48,6 +48,8 @@ export const modelSpecs = [ envelope: "The stable outer shape of id, title, kind, altitude, readiness, and relations; sections carry extension detail.", kind: "The true subtype that categorizes a Spec's truth and changes its required detail and validation.", + "one-kind rule": + "A Spec states one category of truth; when one fact straddles kinds, author two Specs and join them with the relation that preserves their distinct intents.", readiness: "The author-stated design-maturity position `idea`, `scoped`, `defined`, or `ready`, checked against a structural floor.", }, @@ -141,6 +143,8 @@ export const modelSpecs = [ dependsOn: "A dependent Spec points to the Spec it needs.", refines: "A child points to its more precise parent.", supersedes: "A current Decision Record points forward to the decision it replaces.", + "typed dependency distinction": + "`constrainedBy` and `decidedBy` preserve separately queryable intents that a generic `dependsOn` edge would flatten.", verifies: "A verifier points to the Spec it verifies.", }, }, diff --git a/test/self-hosting-oracle/pack-members.ts b/test/self-hosting-oracle/pack-members.ts index 5ca0e5e..8ecad48 100644 --- a/test/self-hosting-oracle/pack-members.ts +++ b/test/self-hosting-oracle/pack-members.ts @@ -39,6 +39,7 @@ export const expectedPackMembers = [ "spec:consumers.reader", "spec:consumers.edit-model", "spec:consumers.authoring-on-ramp", + "spec:consumers.delivery-session-on-ramp", "spec:consumers.agent-surface.authoring-recipes", "spec:consumers.intent-composition", "spec:model.protocol-domain", diff --git a/test/self-hosting-oracle/validation.ts b/test/self-hosting-oracle/validation.ts index 5164332..5f33301 100644 --- a/test/self-hosting-oracle/validation.ts +++ b/test/self-hosting-oracle/validation.ts @@ -21,6 +21,7 @@ export const validationSpecs = [ "The `scoped` floor adds three clauses: the intended outcome is stated, at least one authored relation is declared, and the kind's natural evidence is present.", "The `defined` floor adds two clauses: the kind's natural evidence is complete, and no open question the Spec records is flagged as blocking.", "The `ready` floor reads the Spec's own edges through three clauses: every authored relation resolves to a known target, every `refines` and `dependsOn` target itself stands at least `defined`, and every anchor bound to the Spec resolves.", + "Readiness is independent across a refinement relation: a child may be authored at a higher readiness than its parent. Only the child's own cumulative floor applies, including the `ready` target bound above when the child states `ready`.", "The anchor clause reads the bindings that are present, so a Spec carrying no anchor clears it — the floor never demands a binding an author has not made.", "Only relations the Spec itself declares count toward the relation clauses; membership of a Pack is derived from the manifest and never stands in for an authored relation.", "Every clause stated here is kind-blind. The two evidence clauses are the one kind-conditional place in the floor, and what counts as a kind's natural evidence is stated in full by the refining Spec that carries the per-kind evidence table.", @@ -771,6 +772,7 @@ export const validationSpecs = [ rules: [ "Pack membership must not repeat a Spec, and every modelRef must resolve to a model-kind Spec.", "Membership is counted on the derived belongsTo edges the manifest re-expresses, so a repeated manifest entry is named once per repeated member.", + "There is no duplicated-intent check on a Pack: a Pack states no system truth to duplicate, and semantic overlap among its members remains human or agent review rather than validator judgment. This lets a coherent group contain low-detail Specs without turning grouping into implementation demand.", "The realizing validator entrypoint is `checkPackCoherence` in `src/validate/validators.ts`.", ], exampleSpace: { diff --git a/test/skills.test.ts b/test/skills.test.ts index ae654a4..c08d272 100644 --- a/test/skills.test.ts +++ b/test/skills.test.ts @@ -17,6 +17,7 @@ const repoRoot = fileURLToPath(new URL("..", import.meta.url)); const skillPaths = [ ".agents/skills/sdp-agent-surface/SKILL.md", ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/SKILL.md", ] as const; const authoringOnRampImplementationAnchor = codeAnchor({ @@ -26,6 +27,13 @@ const authoringOnRampImplementationAnchor = codeAnchor({ }); void authoringOnRampImplementationAnchor; +const deliverySessionOnRampImplementationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.delivery-session-on-ramp"), + label: "asserts realization of the shipped advisory delivery-session skill document", + satisfies: ref("spec:consumers.delivery-session-on-ramp"), +}); +void deliverySessionOnRampImplementationAnchor; + function readSkill(path: string) { const source = readFileSync(join(repoRoot, path), "utf8"); const match = /^---\n(?[\s\S]*?)\n---\n(?[\s\S]+)$/u.exec(source); @@ -82,6 +90,12 @@ const authoringOnRampTestAnchor = specTest({ }); void authoringOnRampTestAnchor; +const deliverySessionOnRampTestAnchor = specTest({ + id: testAnchorId("test:protocol.delivery-session-on-ramp"), + label: "skill-asset checks verify advisory delivery-session routing", + verifies: ref("spec:consumers.delivery-session-on-ramp"), +}); +void deliverySessionOnRampTestAnchor; const authoringRecipesTestAnchor = specTest({ id: testAnchorId("test:protocol.authoring-recipes"), label: "skill-asset checks verify the authoring recipes", @@ -109,7 +123,7 @@ describe("Protocol skill assets", () => { } }); - it("keeps both skills graph-first and the authoring law linked to carrying Specs", () => { + it("keeps every skill graph-first and the authoring law linked to carrying Specs", () => { for (const path of skillPaths) { const { source } = readSkill(path); @@ -139,6 +153,45 @@ describe("Protocol skill assets", () => { } }); + it("routes delivery sessions through the five advisory graph shapes", () => { + const sessions = readSkill(".agents/skills/sdp-sessions/SKILL.md").source; + + for (const required of [ + "spec:consumers.delivery-session-on-ramp", + "Capture / refine", + "recipe 6", + "recipe 11", + "recipe 9", + "Design", + "recipe 7", + "Implement", + "recipe 1", + "recipe 3", + "Review", + "recipe 5", + "recipe 8", + "Close / slim", + "recipe 2", + "recipe 4", + "Never hand off a carried", + "re-runs the named evidence", + "never create a process state machine", + ]) { + expect(sessions).toContain(required); + } + + const authoring = readSkill(".agents/skills/sdp-authoring/SKILL.md").source; + for (const required of [ + "Capture a cheap idea", + "readiness: idea", + "relations: {}", + "promotion preflight (recipe 9)", + "If a fact straddles kinds", + ]) { + expect(authoring).toContain(required); + } + }); + it("documents only valid CLI verbs and keeps root-specific exclusions intact", () => { const knownVerbs = new Set(["build", "q"]); const packageJson = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as { From c945d8076b5630d162ac8d6cbff61c758a3736a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 29 Jul 2026 06:03:27 +0200 Subject: [PATCH 22/24] fix(review): tie the oracle suites to contract Then params and harden the guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reader entry-map oracle stated matchedField "sections.behavior.rules" — an outcome the reader never emits and the authored concept-entry example contradicts — and nothing failed, because both oracle suites compared the oracle to literals hand-copied from the oracle itself. The suites now assert each expected outcome against the contract-recorded Then parameters, so an oracle that drifts from authored intent goes red (both mutation-probed). Also: align the gate test's plan-selector regex with the guard's zero-padded form, ban the general plan-status wording shape on the semantic surfaces instead of three frozen strings, and normalize the temporal guard's symlink dedup to git's forward-slash paths so it holds off-darwin. Claude-Session: https://claude.ai/code/session_01TX4fxCUDfukBZo6LBNRKNw --- check-self-hosting-gates.mjs | 4 ++ check-temporal.mjs | 2 +- test/check-self-hosting-gates.test.ts | 2 +- test/self-hosting-consumers-oracle.test.ts | 63 +++++++++++++-------- test/self-hosting-consumers.oracle.ts | 2 +- test/self-hosting-validators-oracle.test.ts | 59 ++++++++++++------- 6 files changed, 87 insertions(+), 45 deletions(-) diff --git a/check-self-hosting-gates.mjs b/check-self-hosting-gates.mjs index 10e4b1e..e8c3910 100644 --- a/check-self-hosting-gates.mjs +++ b/check-self-hosting-gates.mjs @@ -292,6 +292,10 @@ for (const [surface, text] of [ "carries plan-status wording — semantics only, never status", ); } + // Beyond the frozen historical strings, ban the general shape of a plan-status claim. + if (/\bplan \d+ (?:is )?(?:executed|drafted|run|landed|complete|closed)\b/iu.test(norm(text))) { + failures.push(`${surface} — carries plan-status wording — semantics only, never status`); + } } // --------------------------------------------------------------------------- diff --git a/check-temporal.mjs b/check-temporal.mjs index 8b86938..d551cdb 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -91,7 +91,7 @@ for (const path of paths) { // The same holds for a file symlink whose canonical file git enumerates on its own: that // entry is swept — or genre-exempt — under its canonical path, and reading through the link // would either duplicate the sweep or un-exempt an excluded genre. - if (allEnumerated.has(targetFromRoot)) { + if (allEnumerated.has(targetFromRoot.split(sep).join("/"))) { continue; } } diff --git a/test/check-self-hosting-gates.test.ts b/test/check-self-hosting-gates.test.ts index 0ef8d12..1efbe2b 100644 --- a/test/check-self-hosting-gates.test.ts +++ b/test/check-self-hosting-gates.test.ts @@ -20,7 +20,7 @@ const roots: string[] = []; const primaryPlans = readdirSync(join(repoRoot, "plans"), { withFileTypes: true }) .filter((entry) => entry.isFile()) .map((entry) => { - const match = /^(?[1-9]\d*)-[a-z0-9][a-z0-9-]*\.md$/u.exec(entry.name); + const match = /^(?\d+)-[a-z0-9][a-z0-9-]*\.md$/u.exec(entry.name); const number = match?.groups?.number; return number === undefined ? null : { name: entry.name, number: Number(number) }; }) diff --git a/test/self-hosting-consumers-oracle.test.ts b/test/self-hosting-consumers-oracle.test.ts index 437a889..75c0a01 100644 --- a/test/self-hosting-consumers-oracle.test.ts +++ b/test/self-hosting-consumers-oracle.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; +import { changesetEntryContract } from "../generated/contracts/consumers.reader.changeset-entry.contract.js"; +import { conceptEntryContract } from "../generated/contracts/consumers.reader.concept-entry.contract.js"; +import { fileEntryContract } from "../generated/contracts/consumers.reader.file-entry.contract.js"; import type { ReaderConditions } from "../generated/contracts/consumers.reader.space.js"; import { readerSpace } from "../generated/contracts/consumers.reader.space.js"; import { expectedReaderOutcome } from "./self-hosting-consumers.oracle.js"; @@ -19,32 +22,46 @@ function completeConditions(point: Partial): ReaderConditions }; } +interface AnyContractStep { + readonly kind: "given" | "when" | "then"; + readonly text: string; + readonly params: Readonly>; +} + +interface AnyContract { + readonly spec: string; + readonly steps: readonly AnyContractStep[]; +} + +const contractsBySpec = { + "spec:consumers.reader.changeset-entry": changesetEntryContract, + "spec:consumers.reader.concept-entry": conceptEntryContract, + "spec:consumers.reader.file-entry": fileEntryContract, +} as const satisfies Record; + describe("the reader entry-map oracle", () => { - it("assigns a distinct expected outcome to every generated reader witness", () => { - const outcomes = readerSpace.examples.map(({ point }) => - expectedReaderOutcome(completeConditions(point)), + it("assigns every generated reader witness the outcome its contract's Then step records", () => { + expect(readerSpace.examples.map(({ spec }) => spec).sort()).toEqual( + Object.keys(contractsBySpec).sort(), ); - expect(readerSpace.examples.map(({ spec }) => spec).sort()).toEqual([ - "spec:consumers.reader.changeset-entry", - "spec:consumers.reader.concept-entry", - "spec:consumers.reader.file-entry", - ]); - expect(outcomes).toEqual([ - { - kind: "the coverage-unknown files name {coverageUnknownFile}", - coverageUnknownFile: "src/price-book.ts", - }, - { - kind: "the reader names {matchedId} as a match on the field {matchedField}", - matchedId: "spec:orders.order-management", - matchedField: "sections.behavior.rules", - }, - { - kind: "the file entry names the node {nodeId} the graph records at that path", - nodeId: "impl:orders.create-order", - }, - ]); + // The oracle is authoritative only if it restates the authored expectation, so each outcome + // is compared to the contract-recorded Then parameters — never to a hand-copied literal a + // transcription slip could move in step with the oracle. + const outcomes = readerSpace.examples.map(({ spec, point }) => { + const outcome = expectedReaderOutcome(completeConditions(point)); + const { kind, ...fields } = outcome; + const contract: AnyContract = contractsBySpec[spec]; + expect(contract.spec).toBe(spec); + const thenStep = contract.steps.find((step) => step.kind === "then" && step.text === kind); + expect( + thenStep, + `${spec} states no Then step matching the oracle outcome "${kind}"`, + ).toBeDefined(); + expect(thenStep?.params).toEqual(fields); + return outcome; + }); + expect(new Set(outcomes.map(({ kind }) => kind)).size).toBe(3); expect(outcomes.every(({ kind }) => kind !== "unspecified")).toBe(true); }); diff --git a/test/self-hosting-consumers.oracle.ts b/test/self-hosting-consumers.oracle.ts index 4bc27fe..670a80d 100644 --- a/test/self-hosting-consumers.oracle.ts +++ b/test/self-hosting-consumers.oracle.ts @@ -17,7 +17,7 @@ export function expectedReaderOutcome(conditions: ReaderConditions): ReaderOutco return { kind: "the reader names {matchedId} as a match on the field {matchedField}", matchedId: conditions.conceptSpecId, - matchedField: "sections.behavior.rules", + matchedField: "sections.behavior", }; case "file": return { diff --git a/test/self-hosting-validators-oracle.test.ts b/test/self-hosting-validators-oracle.test.ts index c133b9f..b86d1e2 100644 --- a/test/self-hosting-validators-oracle.test.ts +++ b/test/self-hosting-validators-oracle.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from "vitest"; +import { missingSpaceRefusedContract } from "../generated/contracts/validation.oracle-target-eligibility.missing-space-refused.contract.js"; +import { ruleSpaceAcceptedContract } from "../generated/contracts/validation.oracle-target-eligibility.rule-space-accepted.contract.js"; import type { OracleTargetEligibilityConditions } from "../generated/contracts/validation.oracle-target-eligibility.space.js"; import { oracleTargetEligibilitySpace } from "../generated/contracts/validation.oracle-target-eligibility.space.js"; import { expectedOracleTargetEligibilityOutcome } from "./self-hosting-validators.oracle.js"; @@ -17,28 +19,47 @@ function completeConditions( }; } +interface AnyContractStep { + readonly kind: "given" | "when" | "then"; + readonly text: string; + readonly params: Readonly>; +} + +interface AnyContract { + readonly spec: string; + readonly steps: readonly AnyContractStep[]; +} + +const contractsBySpec = { + "spec:validation.oracle-target-eligibility.missing-space-refused": missingSpaceRefusedContract, + "spec:validation.oracle-target-eligibility.rule-space-accepted": ruleSpaceAcceptedContract, +} as const satisfies Record; + describe("the oracle-target-eligibility oracle", () => { - it("assigns finding count and resolving presence to both generated points", () => { - const outcomes = oracleTargetEligibilitySpace.examples.map(({ point }) => - expectedOracleTargetEligibilityOutcome(completeConditions(point)), + it("assigns both generated points the outcome their contract's Then step records", () => { + expect(oracleTargetEligibilitySpace.examples.map(({ spec }) => spec).sort()).toEqual( + Object.keys(contractsBySpec).sort(), ); - expect(oracleTargetEligibilitySpace.examples.map(({ spec }) => spec).sort()).toEqual([ - "spec:validation.oracle-target-eligibility.missing-space-refused", - "spec:validation.oracle-target-eligibility.rule-space-accepted", - ]); - expect(outcomes).toEqual([ - { - kind: "oracle linkage reports {findingCount} findings and resolving presence {oraclePresent}", - findingCount: 1, - oraclePresent: false, - }, - { - kind: "oracle linkage reports {findingCount} findings and resolving presence {oraclePresent}", - findingCount: 0, - oraclePresent: true, - }, - ]); + // The oracle is authoritative only if it restates the authored expectation, so each outcome + // is compared to the contract-recorded Then parameters — never to a hand-copied literal a + // transcription slip could move in step with the oracle. + const outcomes = oracleTargetEligibilitySpace.examples.map(({ spec, point }) => { + const outcome = expectedOracleTargetEligibilityOutcome(completeConditions(point)); + const { kind, ...fields } = outcome; + const contract: AnyContract = contractsBySpec[spec]; + expect(contract.spec).toBe(spec); + const thenStep = contract.steps.find((step) => step.kind === "then" && step.text === kind); + expect( + thenStep, + `${spec} states no Then step matching the oracle outcome "${kind}"`, + ).toBeDefined(); + expect(thenStep?.params).toEqual(fields); + return outcome; + }); + + // The two witnesses share one Then vocabulary; their parameters must still diverge. + expect(new Set(outcomes.map((outcome) => JSON.stringify(outcome))).size).toBe(2); expect(outcomes.every(({ kind }) => kind !== "unspecified")).toBe(true); }); }); From 9922cd6ef41d40ab4b892239ad2cfa016803df35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 29 Jul 2026 06:09:12 +0200 Subject: [PATCH 23/24] fix(preflight): treat Finder .DS_Store as OS metadata, never generated drift Browsing generated/ in Finder drops .DS_Store files that the preflight compared as content against the clean regeneration, turning the gate red on any Mac. readTree now skips them and .gitignore excludes them repo-wide, so the drift and runtime-garbage checks keep policing real content only. Claude-Session: https://claude.ai/code/session_01TX4fxCUDfukBZo6LBNRKNw --- .gitignore | 3 +++ preflight.mjs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index eb0c29e..865bed7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ # User-local editor state .vscode/ +# macOS Finder metadata +.DS_Store + # Tooling outputs node_modules/ dist/ diff --git a/preflight.mjs b/preflight.mjs index d9b1b8e..8371de1 100644 --- a/preflight.mjs +++ b/preflight.mjs @@ -72,6 +72,11 @@ function readTree(root) { } for (const entry of readdirSync(root, { withFileTypes: true })) { + // Finder drops .DS_Store into any browsed directory; it is OS metadata, never generated content. + if (entry.name === ".DS_Store") { + continue; + } + const relativePath = entry.name; const absolutePath = join(root, relativePath); From 6e76af57f88d5417ec76cb1dd74b5eca80617fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 29 Jul 2026 06:11:24 +0200 Subject: [PATCH 24/24] fix(preflight): retry the scratch-tree removal against transient metadata races Finder and Spotlight can recreate metadata inside the preflight's temporary regeneration tree while rmSync is emptying it, crashing the gate with ENOTEMPTY. Retry the recursive removal instead of failing on the first race. Claude-Session: https://claude.ai/code/session_01TX4fxCUDfukBZo6LBNRKNw --- preflight.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/preflight.mjs b/preflight.mjs index 8371de1..8fd3d56 100644 --- a/preflight.mjs +++ b/preflight.mjs @@ -120,7 +120,8 @@ function regenerateExpectedTree(target) { readTree(join(temporaryRoot, target.generatedPath)), ); } finally { - rmSync(temporaryRoot, { recursive: true, force: true }); + // Finder/Spotlight can drop metadata into the tree mid-delete; retry the transient ENOTEMPTY. + rmSync(temporaryRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }