diff --git a/.claude/skills/sdp-agent-surface/SKILL.md b/.agents/skills/sdp-agent-surface/SKILL.md similarity index 68% rename from .claude/skills/sdp-agent-surface/SKILL.md rename to .agents/skills/sdp-agent-surface/SKILL.md index 3b6ffe9..83f665c 100644 --- a/.claude/skills/sdp-agent-surface/SKILL.md +++ b/.agents/skills/sdp-agent-surface/SKILL.md @@ -15,18 +15,38 @@ 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 -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 +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 ``` -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. +`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 its repository script, which supplies +the exact three fixture exclusions: + +```sh +pnpm --silent sdp:q 'return g.specs().length' +pnpm --silent sdp:q 'return g.specContext("spec:consumers.reader")' --json +``` -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 +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 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 +`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, 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. @@ -74,6 +94,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/.agents/skills/sdp-authoring/SKILL.md b/.agents/skills/sdp-authoring/SKILL.md new file mode 100644 index 0000000..57adcc1 --- /dev/null +++ b/.agents/skills/sdp-authoring/SKILL.md @@ -0,0 +1,114 @@ +--- +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 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: + +```sh +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: + +```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 +``` + +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 + +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`. + 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. +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`. + +### 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. +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. 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. + +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 +`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/.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/.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/.gitignore b/.gitignore index 3fb93b2..865bed7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,12 @@ # User's scratchpad .tmp-scratch/ +# User-local editor state +.vscode/ + +# macOS Finder metadata +.DS_Store + # Tooling outputs node_modules/ dist/ @@ -15,4 +21,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 bb62d71..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 — 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 -> **`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,46 +60,56 @@ 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 | -| `.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 | +| `.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 | -| `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 -> 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 @@ -113,11 +129,19 @@ 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 +- **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 @@ -137,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 dc3fbeb..e5bd40c 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`) @@ -188,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 f70d097..68159ed 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,62 @@ 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. + +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` / `pnpm sdp:q` forms do not rewrite an npm-installed dependency tree. + +## Installed CLI (adopter repositories) + +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 +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. + +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/check-self-hosting-gates.mjs b/check-self-hosting-gates.mjs index 3bfe963..e8c3910 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 = /^(?\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, + `plan ${String(currentPlan?.number)} is ${currentStatus}`, + "the handbook status disagrees with the current primary plan", + ); +} + +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"); -// 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)`); +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], @@ -250,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`); + } } // --------------------------------------------------------------------------- @@ -358,6 +404,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/check-temporal.mjs b/check-temporal.mjs index 8d27a49..d551cdb 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -1,5 +1,7 @@ import { spawnSync } from "node:child_process"; -import { readFileSync } from "node:fs"; +import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs"; +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 @@ -33,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", }, ); @@ -45,7 +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 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 @@ -57,10 +65,38 @@ let selfAllowanceUsed = false; const violations = []; for (const path of paths) { + const absolute = join(repositoryRoot, path); let source; try { - source = readFileSync(path, "utf8"); + if (lstatSync(absolute).isSymbolicLink()) { + const target = realpathSync(absolute); + 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(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.split(sep).join("/"))) { + continue; + } + } + + source = readFileSync(absolute, "utf8"); } catch (error) { fail(`could not read ${path}`, error instanceof Error ? error.message : String(error)); } 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", ], }, diff --git a/docs/agent-surface/recipes.md b/docs/agent-surface/recipes.md index 48d9e0a..15cd5ee 100644 --- a/docs/agent-surface/recipes.md +++ b/docs/agent-surface/recipes.md @@ -1,20 +1,38 @@ # 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. Elsewhere, `--root PATH` picks the extraction root (default: the working -directory) and `--exclude` is repeatable for root-relative path prefixes. +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 +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 +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: @@ -53,13 +71,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) { @@ -73,11 +96,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 @@ -339,3 +372,104 @@ 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, +}; +``` + +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. + +## 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/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/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 2cf3728..e24dbf1 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -31,6 +31,8 @@ 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`) | +| 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/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", }, }, { 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 9145d65..29d5ae9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,11 @@ }, "type": "module", "files": [ - "dist" + "dist/**", + "docs/agent-surface/recipes.md", + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/SKILL.md" ], "bin": { "sdp": "./dist/cli/sdp.js" @@ -38,6 +42,8 @@ "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 .", "format:check": "prettier --check .", @@ -45,9 +51,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", @@ -58,7 +65,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/plans/23-outward-turn-origin-adoption.md b/plans/23-outward-turn-origin-adoption.md deleted file mode 100644 index 5da9557..0000000 --- a/plans/23-outward-turn-origin-adoption.md +++ /dev/null @@ -1,263 +0,0 @@ -# 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. -> -> **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. - -## (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. - -## (c) Charter — what phase 6 is and is not - -**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. - -**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. - -## (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. - -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 -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) - -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; 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. - -## (f) 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). - -## (g) 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) - -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. - -## (i) 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 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 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. -- **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 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. 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..e304acc --- /dev/null +++ b/plans/24-inward-turn-self-hosting-practice.md @@ -0,0 +1,249 @@ +# Plan 24 — The inward turn: self-hosting as the standing practice + +> **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 +> 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. + +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 +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 + +- 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. + +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 +a target. + +| Spec | Candidate seam | Disposition and current evidence | +|---|---|---| +| `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` | **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 + +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` | 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 | +| `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 + +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 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. + +## 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 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). +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 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. + +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/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/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/preflight.mjs b/preflight.mjs index d9b1b8e..8fd3d56 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); @@ -115,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 }); } } 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/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/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..d3ad580 --- /dev/null +++ b/specs/consumers/authoring-on-ramp.sdp.md @@ -0,0 +1,20 @@ +--- +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: 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. +- 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/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 new file mode 100644 index 0000000..fd44e07 --- /dev/null +++ b/specs/consumers/impact-graph.sdp.md @@ -0,0 +1,21 @@ +--- +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: 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/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/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/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/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/anchors.sdp.md b/specs/model/anchors.sdp.md index 5cef727..571d9f8 100644 --- a/specs/model/anchors.sdp.md +++ b/specs/model/anchors.sdp.md @@ -17,6 +17,8 @@ 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. - **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/specs/model/core-model.sdp.md b/specs/model/core-model.sdp.md index 88e17b5..2a73d6c 100644 --- a/specs/model/core-model.sdp.md +++ b/specs/model/core-model.sdp.md @@ -16,6 +16,8 @@ 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. +- **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/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/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/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/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 da833bb..de2779b 100644 --- a/specs/self-hosting.pack.sdp.ts +++ b/specs/self-hosting.pack.sdp.ts @@ -11,10 +11,13 @@ 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"), + ref("spec:extraction.build-pipeline.same-invocation"), ref("spec:extraction.excludes"), ref("spec:extraction.claim-taxonomy"), ref("spec:extraction.regenerability"), @@ -26,16 +29,25 @@ 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.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"), ref("spec:consumers.projections-model"), + ref("spec:consumers.impact-graph"), ref("spec:consumers.agent-surface"), ref("spec:consumers.design-review"), 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"), 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 +132,8 @@ 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"), + 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.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 new file mode 100644 index 0000000..ddfc3d7 --- /dev/null +++ b/specs/validation/oracle-target-eligibility.sdp.md @@ -0,0 +1,27 @@ +--- +id: spec:validation.oracle-target-eligibility +kind: rule +altitude: story +readiness: ready +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. + +## Rule +- 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. + +## 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 oracle linkage reports {findingCount:number} findings and resolving presence {oraclePresent:boolean} +``` 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/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/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/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..90a88e1 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(); @@ -599,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, @@ -608,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]); } } @@ -623,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), }), @@ -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/bootstrap.test.ts b/test/bootstrap.test.ts index 6624476..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"); @@ -67,10 +69,19 @@ 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["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 && 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", ); + + 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/check-self-hosting-gates.test.ts b/test/check-self-hosting-gates.test.ts new file mode 100644 index 0000000..1efbe2b --- /dev/null +++ b/test/check-self-hosting-gates.test.ts @@ -0,0 +1,121 @@ +import { spawnSync } from "node:child_process"; +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"; + +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 = /^(?\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", + "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("/"), + currentPlanPath, +] 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 ${String(currentPlan.number)} is ${currentStatus}`, + `plan ${String(currentPlan.number - 1)} is DRAFTED`, + ), + ); + + 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/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/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/test/package-smoke.test.ts b/test/package-smoke.test.ts index c412115..6a9e5c6 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,27 @@ 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 = [ + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/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 +236,36 @@ void [${expectedRootExports.join(", ")}]; ], consumer, ); + const installedAgentSkill = await readFile( + join( + consumer, + "node_modules", + "@libar-dev", + "software-delivery-protocol", + ".agents/skills/sdp-agent-surface/SKILL.md", + ), + "utf8", + ); + const installedAuthoringSkill = await readFile( + join( + consumer, + "node_modules", + "@libar-dev", + "software-delivery-protocol", + ".agents/skills/sdp-authoring/SKILL.md", + ), + "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(), @@ -224,6 +275,9 @@ 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"); + expect(installedSessionsSkill).toContain("name: sdp-sessions"); } finally { await rm(packageRoot, { force: true, recursive: true }); } 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/recipes.test.ts b/test/recipes.test.ts index f750a2f..1bde4cf 100644 --- a/test/recipes.test.ts +++ b/test/recipes.test.ts @@ -188,36 +188,46 @@ 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, + ".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; + }; + 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"); - // 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 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, - otherQuoting: [], + selfHosting: true, }); + expect({ + source, + otherQuoting: lines.filter((line) => line.includes(' q "') && line.includes("sdp")), + }).toEqual({ source, otherQuoting: [] }); - for (const line of commandLines) { - 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, + }); + 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); } } }); @@ -246,23 +256,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 () => { @@ -415,4 +453,52 @@ 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); + // 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) => { + const verifiers = reader.specContext(spec.id)?.verifiers ?? []; + + return verifiers.some((binding) => binding.via === "example" || binding.enabled); + }) + .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-consumers-oracle.test.ts b/test/self-hosting-consumers-oracle.test.ts new file mode 100644 index 0000000..75c0a01 --- /dev/null +++ b/test/self-hosting-consumers-oracle.test.ts @@ -0,0 +1,68 @@ +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"; + +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, + }; +} + +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 every generated reader witness the outcome its contract's Then step records", () => { + expect(readerSpace.examples.map(({ spec }) => spec).sort()).toEqual( + Object.keys(contractsBySpec).sort(), + ); + + // 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 new file mode 100644 index 0000000..670a80d --- /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", + }; + 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-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..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: 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: 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: 35, ready: 80 }); + ).toEqual({ defined: 37, idea: 4, ready: 87, scoped: 1 }); }); it("derives the Pack membership edges from the manifest, in manifest order", () => { @@ -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 9c1cac7..b3a5143 100644 --- a/test/self-hosting-oracle/anchors.ts +++ b/test/self-hosting-oracle/anchors.ts @@ -43,6 +43,76 @@ 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.authoring-on-ramp", + nodeType: "CodeNode", + label: "asserts realization of the shipped graph-first authoring skill document", + type: "satisfies", + target: "spec:consumers.authoring-on-ramp", + file: "test/skills.test.ts", + 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", + label: "asserts realization of the shipped authoring recipe catalog", + 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.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", + 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", @@ -863,4 +933,254 @@ 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", + }, + { + 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/carrier.ts b/test/self-hosting-oracle/carrier.ts index 8526bdc..fbdfb66 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", @@ -151,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 d997463..e86c104 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", @@ -788,4 +788,131 @@ 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.", + "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.", + "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.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", + 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.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.", + "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.", + ], + }, + }, + deliveryFacts: [], + }, + { + 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/decisions.ts b/test/self-hosting-oracle/decisions.ts index a3a54d2..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.", ], @@ -590,4 +592,63 @@ 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: [], + }, + { + 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 626b875..aabee42 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"], @@ -265,13 +267,19 @@ 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.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"], ["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 +312,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"], @@ -443,4 +452,34 @@ 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"], + [ + "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/extraction.ts b/test/self-hosting-oracle/extraction.ts index fbaa287..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", @@ -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/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 98600ef..9059a5d 100644 --- a/test/self-hosting-oracle/model.ts +++ b/test/self-hosting-oracle/model.ts @@ -43,9 +43,13 @@ 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.", + "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.", }, @@ -80,11 +84,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", @@ -106,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.", }, }, @@ -264,6 +303,10 @@ 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.", + "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 3f3be49..8ecad48 100644 --- a/test/self-hosting-oracle/pack-members.ts +++ b/test/self-hosting-oracle/pack-members.ts @@ -8,10 +8,13 @@ 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", + "spec:extraction.build-pipeline.same-invocation", "spec:extraction.excludes", "spec:extraction.claim-taxonomy", "spec:extraction.regenerability", @@ -23,16 +26,25 @@ export const expectedPackMembers = [ "spec:validation.referential-integrity", "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", "spec:consumers.projections-model", + "spec:consumers.impact-graph", "spec:consumers.agent-surface", "spec:consumers.design-review", "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", "spec:model.core-model", + "spec:model.enrichment-lifecycle", "spec:model.spec-sections", "spec:model.relations", "spec:model.stable-ids", @@ -117,4 +129,6 @@ export const expectedPackMembers = [ "spec:decisions.agent-surface-scripts-graph", "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..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.", @@ -147,7 +148,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.kind-evidence.constraints-alone", @@ -404,7 +405,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.referential-integrity.dangling-target", @@ -499,7 +500,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.claim-separation.collapsed-edge-claim", @@ -597,7 +598,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.verification-linkage.unbound-example", @@ -660,6 +661,100 @@ export const validationSpecs = [ }, deliveryFacts: ["has-verifier"], }, + { + id: "spec:validation.oracle-target-eligibility", + specKind: "rule", + altitude: "story", + readiness: "ready", + 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.", + }, + 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: [ + 'the oracle targets a {targetKind:"behavior"|"rule"} spec', + "the target owns an example space: {ownsExampleSpace:boolean}", + ], + when: ["oracle linkage is resolved"], + then: [ + "oracle linkage reports {findingCount:number} findings and resolving presence {oraclePresent:boolean}", + ], + }, + }, + }, + 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", specKind: "rule", @@ -677,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: { @@ -692,7 +788,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.pack-coherence.incoherent-aggregate", @@ -755,7 +851,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.authored-honesty.section-authored-fact", @@ -852,7 +948,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.warn-level-signals.orphan-signal", @@ -952,7 +1048,7 @@ export const validationSpecs = [ }, }, }, - deliveryFacts: ["has-verifier"], + deliveryFacts: ["implemented", "has-verifier"], }, { id: "spec:validation.diagnostic-rendering.composed-location", diff --git a/test/self-hosting-validators-oracle.test.ts b/test/self-hosting-validators-oracle.test.ts new file mode 100644 index 0000000..b86d1e2 --- /dev/null +++ b/test/self-hosting-validators-oracle.test.ts @@ -0,0 +1,65 @@ +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"; + +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, + }; +} + +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 both generated points the outcome their contract's Then step records", () => { + expect(oracleTargetEligibilitySpace.examples.map(({ spec }) => spec).sort()).toEqual( + Object.keys(contractsBySpec).sort(), + ); + + // 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); + }); +}); 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/skills.test.ts b/test/skills.test.ts new file mode 100644 index 0000000..c08d272 --- /dev/null +++ b/test/skills.test.ts @@ -0,0 +1,288 @@ +import { lstatSync, readFileSync, readlinkSync } 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 = [ + ".agents/skills/sdp-agent-surface/SKILL.md", + ".agents/skills/sdp-authoring/SKILL.md", + ".agents/skills/sdp-sessions/SKILL.md", +] as const; + +const authoringOnRampImplementationAnchor = codeAnchor({ + id: codeAnchorId("impl:protocol.authoring-on-ramp"), + label: "asserts realization of the shipped graph-first authoring skill document", + satisfies: ref("spec:consumers.authoring-on-ramp"), +}); +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); + + 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, + }; +} + +function shellFenceLines(body: string): readonly string[] { + const lines: string[] = []; + let inShellFence = false; + + for (const line of body.split("\n")) { + if (line.startsWith("```") && line !== "```") { + inShellFence = line === "```sh"; + continue; + } + if (line === "```") { + inShellFence = false; + continue; + } + if (inShellFence) { + lines.push(line); + } + } + + 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({ + 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 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", + verifies: ref("spec:consumers.agent-surface.authoring-recipes"), +}); +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); + 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 every skill 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( + "node_modules/@libar-dev/software-delivery-protocol/docs/agent-surface/recipes.md", + ); + expect(source).toContain("spec:"); + } + + const authoring = readSkill(".agents/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", + "contract-dependent-suites.mjs", + "mutation", + "cannot detect", + ]) { + expect(authoring).toContain(required); + } + }); + + 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 { + 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 = command.startsWith("pnpm --silent sdp:q ") + ? "q" + : /exec sdp (?[\w-]+)/u.exec(command)?.groups?.verb; + expect(verb === undefined ? false : knownVerbs.has(verb)).toBe(true); + } + } + }); + + it("uses the local runtime or package runner instead of a colliding global binary", () => { + // 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", () => { + 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); + } + } + }); + + 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(".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`); + }); +}); 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", () => { 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"]), );