From 38457a238dde7c3819afb01675d4d925c4e5a082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 09:09:16 +0200 Subject: [PATCH 01/29] Refresh AGENTS.md for the implementation phase: navigation map, status, build path --- AGENTS.md | 188 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 106 insertions(+), 82 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bd6685c..51585c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,93 +1,117 @@ -# Libar Software Delivery Protocol — concept repo +# Libar Software Delivery Protocol -Second-generation concept for a **programmatic, AI-native software-delivery system**: model the *whole -delivery lifecycle* as typed code in the repo, derive one graph from it, and generate every other artifact -off that graph. This repo holds the **concept synthesis**, its **ratified ubiquitous language**, and the -**JTBD stories** that together serve **as the spec** — there is no separate PRD. They are the direct input to -building the MVP. +A **programmatic, AI-native software-delivery system**: model the *whole delivery lifecycle* as typed code in +the repo, derive **one graph** from it, and generate every other artifact off that graph. A deliberate second +take on `@libar-dev/architect` — a year of design thinking front-loaded, **zero back-compat, zero old sins +carried over**. -## What's here +This repo is the **concept made right before the engine exists**. 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. -| Path | What it is | +> **Status:** concept ratified and locked; the repo is now in its **first implementation sessions**, starting with +> **Phase 0 — the protocol as typed code**. The live per-session build plan is in **`plans/`**; the slice roadmap is +> **`docs/concept/07`**. (`plans/` is the source of truth for "what now.") + +## The frame + +An **executable, self-validating meta-model of the software-delivery process**, on three clean levels: +**protocol** (the primitive, descriptors, relations, validators *as typed code* — **Phase 0**) → **authored model** +(a project's instances, which **conform** — checked, never workflow-gated) → **derived facts** (machine truth, +never authored). + +Two permanent honesty guardrails: checks police **conformance & honesty**, never content-quality and never +workflow; and we claim **"deterministically validated," never "provably correct."** + +## The model in one breath + +One authored **truth-primitive — `Spec`** — enriched in place (never migrated to a new artifact type), positioned +by **three descriptors**: `kind` (a *true subtype*; 8 values) × `altitude` (`epic → feature → story`) × `readiness` +(`idea → scoped → defined → ready`). Familiar delivery nouns (Use Case / NFR / Decision Record / Contract; Epic / +Feature / Story) are **named coordinates on the one primitive, not separate types**. Two more authored things state +**no system truth**: the **`Pack`** (the grouping / review aggregate) and the in-code **`anchor`** (a binding — +identity only, never intent). **Delivery facts** (`implemented` / `has-verifier` / `observed`) are **derived, never +authored**. An **`extractor`** derives **one regenerable graph** (the sole read model); CI runs **conformance + +honesty checks**; the **`claim`** taxonomy (`declared` / `anchored` / `inferred`) is never collapsed; **git is the +event log**. Coding agents **script the typed graph** (the **agent surface** — no verb wall); edits flow +**intent → agent → git → conformance checks** (no patch loop). + +## Where to look + +Progressive disclosure — start at the top, follow the pointers down. + +| Look here | What you get | Read | +|---|---|---| +| `docs/concept/ubiquitous-language.md` | **the vocabulary & the model** — the ratified base (§0–§8, all `[SETTLED]`); our bounded context and sole source of truth for terminology | **first, always** | +| `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/00`–`07` (+ README) | **the technical design** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model, the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | +| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (MD-1/2/4/5/7, the D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | +| `plans/` | **the build plan** — what each implementation session does, and why | before writing code | + +> The concept docs intentionally carry implementation detail (TS shapes, DSL, graph JSON): they double as the +> implementation spec until the code can hold it. A clean concept/representation split is a recorded future +> direction, not done yet. + +## 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. 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. + +| Slice | Delivers | |---|---| -| `docs/concept/ubiquitous-language.md` | **The ratified base** (§0–§8 + §4b, all `[SETTLED]`). **Sole source of truth for terminology and the model.** Read this first. | -| `docs/concept/DECISIONS.md` | The decision diary (why-log) for *building the Protocol itself* — MD-1/2/4/5 + scope note, plus the D1–D6 shorthand and measured-evidence tables. Recaptures rationale + open tensions; distinct from in-system `kind:"decision"` records. | -| `docs/concept/` (`00`–`07` + README) | 9 principle-led concept docs. The design: one `Spec` primitive, three descriptors, one derived graph, honest `claim`s, conformance + honesty checks, consumer surfaces. *(Now ratified-current — they speak the language base end-to-end, cleanup executed 2026-06-07.)* | -| `jtbd-stories/` | JTBD **job stories** (`When [situation], I want to [motivation], so I can [outcome]`, themes A–H) + README. Stable, solution-agnostic jobs — chosen over user stories because consumers are heterogeneous and evolving (humans, CI, CLIs, **AI agents**). Ratified-current. | - -> Note: `CLAUDE.md` is a symlink to `AGENTS.md` — edit `AGENTS.md`. The earlier input drafts (`GLOSSARY.md`, -> `UBIQUITOUS_LANGUAGE_{1,2}.md`) and the language-finalization brief have been **deleted** (consolidated into -> the base). - -## How to read the design - -Two conventions run through every doc — honour them: - -- **Principle vs Representation** — a *Principle* is a law the design stands or falls on; a *Representation* is - one chosen mechanism among several. Never mistake one for the other. -- **CORE / MVP vs ASPIRATIONAL** — in the first buildable slice, or designed-for-and-deferred (named so the - model never paints itself into a corner). - -**The frame:** Libar Software Delivery Protocol is an **executable, self-validating meta-model of the software-delivery process** — three -levels: the **protocol** (the primitive, descriptors, relations, validators *as typed code* — **Phase 0 -of the MVP**) → the **authored model** (a project's instances, which *conform*) → **derived facts** (machine -truth, never authored). Instances **conform** (checked, never gated). Two permanent honesty guardrails: checks -police **conformance & honesty**, never content-quality and never workflow; and we claim **"deterministically -validated," never "provably correct."** - -**The model in one breath:** one authored **truth-primitive — `Spec`** — enriched in place (never migrated to a -new artifact type), positioned by **three descriptors**: `kind` (a *true subtype*; 8 values) × `altitude` -(`epic → feature → story`) × `readiness` (`idea → scoped → defined → ready`). Familiar delivery nouns -(Use Case / NFR / Decision Record / Contract; Epic / Feature / Story) are **named coordinates on the one -primitive, not separate types**. Two more authored things state **no system truth**: the **`Pack`** (the -grouping / review aggregate) and the in-code **`anchor`** (a binding — identity only, never intent). **Delivery -facts** (`implemented` / `has-verifier` / `observed`) are **derived, never authored**. An `extractor` derives -**one regenerable graph** (the sole read model); CI runs **conformance + honesty checks**; the **`claim`** -taxonomy (`declared` / `anchored` / `inferred`) is never collapsed; git is the event log. Consumers: coding -agents **script a visible typed graph** (the **agent surface** — no verb wall); user-facing apps integrate -through the **MCP surface** (designed-in, deferred build); edits flow **intent → agent → git → conformance -checks** (no patch loop). - -> Concept docs intentionally carry some implementation detail (TS shapes, DSL, graph JSON). Known antipattern, -> deliberate: the docs double as the implementation spec until the code can hold it. Separating an -> implementation-agnostic concept layer from a representation layer is a recorded future direction, not done yet. - -## Why this shape pays off - -The bets the design rests on — and why the language/spec discipline is load-bearing, not ceremony: +| **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 `sdp` CLI (`build` · `validate` · maybe `explain`/`search`), error messages, the documented example, a clean-repo determinism test. | + +**Where we are now (`plans/`):** **Session 1** = Slice 0 + the repo/toolchain bootstrap, authoring the +`checkout-v1` example against the DSL **so it typechecks** — pure types, no I/O, deterministic by construction; +stops *before* the extractor. **Session 2** = Slice 1, the `ts-morph` extractor (deterministic rebuild P3 + +graceful partial extraction L3); session 1's example becomes its first real input. + +> **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the DSL and extractor are +> forced to be usable before they are finished. If the example doesn't typecheck, fix the DSL — not the example. + +## Two reading conventions + +Every doc honours both — never mistake one half for the other: + +- **Principle vs Representation** — a *Principle* is a law the design stands or falls on; a *Representation* is one + chosen mechanism among several. +- **CORE / MVP vs ASPIRATIONAL** — in the first buildable slice, or designed-for-and-deferred (named so the model + never paints itself into a corner). + +## The bets (why this shape) - **Shared language is a performance feature.** A ratified ubiquitous language measurably leans out reasoning and - lifts result quality — for solo and subagent work, not just human↔agent chat. Terminology rigor *is* throughput. -- **The spec is the prompt is the design.** One language spans intent → spec → design → code; you don't translate - prose into code, you *enrich the same typed `Spec`* until it is implementable. Implementation becomes the - near-autopilot session ("implement `spec:…`"); the real, iterative work is maturing and **reviewing** specs — - alone and in related sets (the `Pack` / **Design Review** projection). -- **Maturity gates implementation.** Don't ship code before a spec is `ready`; premature implementation forces - expensive refactoring. The `implemented ∧ ¬ready` **drift alarm** exists to catch exactly that (and - `ready ∧ ¬implemented` is the build backlog). -- **The graph is AI-native.** A typed graph of related specs is the shape an LLM already reasons in — - relationships and structure, not prose. Feed the agent the graph (the **agent surface**); don't narrate at it. - -## Lineage & stance - -Built on hard-won learnings from **`@libar-dev/architect`** (the `architect` CLI, Libar Studio desktop app, MCP, -projection pipeline) at `/Users/darkomijic/dev-projects/architect`. That codebase is a **source of learnings, -not a template**: this is a redesign from scratch — **0 old sins carried over**, no back-compat, every decision -and verb re-thought. When a prior implementation's *shape* is offered, treat it as evidence about the problem, -never as the answer. + lifts quality — solo and subagent work, not just human↔agent chat. Terminology rigor *is* throughput. +- **The spec is the prompt is the design.** One language spans intent → spec → design → code; you *enrich the same + 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 + specs is the shape an LLM already reasons in: feed the agent the graph, don't narrate at it. ## Working discipline -- **Terminology is ratified, not provisional.** Use the exact terms in `docs/concept/ubiquitous-language.md` - (and the rationale in `DECISIONS.md`). Flag, don't silently invent, any new term. The `00`–`07` docs and the - JTBD stories now speak the ratified language end-to-end (cleanup executed 2026-06-07); if you spot any residual - pre-ratification term (`abstraction`, `provenance`, `marker`, `facet`, "two axes", the old readiness ladder), - treat it as a bug to fix against the base, not as current. -- **Respect plan mode.** Distinguish **PLAN-ONLY** work (designing, deciding) from **execution** (editing docs or - code). For any plan under `plans/`, don't start editing its target files unless the session is execution. -- **Naming is resolved — use these names.** Product: **Libar Software Delivery Protocol** (short form "the - Protocol"); CLI **`sdp`**; npm **`@libar-dev/software-delivery-protocol`** (single package; subpackage - boundaries are a later, non-near-term concern); repo `libar-dev/software-delivery-protocol`. MD-5 is resolved - (the meta-model is "the protocol"; the modeled "software-delivery process" keeps "process"). +- **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 + the base**, not current usage. +- **Naming is resolved — use these names.** Product **Libar Software Delivery Protocol** (short form "the + Protocol"); CLI **`sdp`**; npm **`@libar-dev/software-delivery-protocol`** (single package); repo + `libar-dev/software-delivery-protocol`. Namespaces: `@libar-dev/` (OSS) vs `@libar-ai/` (commercial). +- **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` + (`/Users/darkomijic/dev-projects/architect`) taught us the problem in production; treat its *shape* as evidence + about the problem, never as the answer. - **Git hygiene** follows the global rules (no `git stash`; commit early on a WIP branch; commit/push only when asked). + +> `CLAUDE.md` is a symlink to `AGENTS.md` — **edit `AGENTS.md`.** From eb6bf2a8326923cd3ec54999e31860a9e348998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 13:13:25 +0200 Subject: [PATCH 02/29] =?UTF-8?q?Impement=20Session=201=20=E2=80=94=20Boot?= =?UTF-8?q?strap=20+=20Phase=200=20(the=20protocol=20as=20typed=20code)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 40 + .gitignore | 6 + .prettierignore | 6 + .prettierrc.json | 6 + .sisyphus/boulder.json | 93 + .../session-1-bootstrap-phase0/decisions.md | 16 + .../session-1-bootstrap-phase0/issues.md | 2 + .../session-1-bootstrap-phase0/learnings.md | 26 + .../session-1-bootstrap-phase0/problems.md | 0 eslint.config.js | 52 + examples/bootstrap-alias.ts | 3 + examples/checkout-v1/model.ts | 30 + examples/checkout-v1/specs/checkout.pack.ts | 19 + .../specs/decisions/order-lifecycle.spec.ts | 29 + .../orders/create-order-invalid-cart.spec.ts | 47 + .../orders/create-order-valid-cart.spec.ts | 48 + .../specs/orders/create-order.spec.ts | 33 + .../specs/orders/order-inventory-rule.spec.ts | 20 + .../orders/order-latency-constraint.spec.ts | 23 + .../specs/orders/order-management.spec.ts | 20 + .../specs/orders/order-model.spec.ts | 28 + .../specs/orders/order-total-rule.spec.ts | 20 + .../src/orders/create-order.use-case.ts | 54 + .../orders/create-order.valid-cart.test.ts | 7 + package-lock.json | 3845 +++++++++++++++++ package.json | 42 + src/cli/sdp.ts | 64 + src/graph/schema.ts | 71 + src/ids.ts | 141 + src/index.ts | 12 + src/model/anchors.ts | 27 + src/model/descriptors.ts | 33 + src/model/pack.ts | 17 + src/model/relations.ts | 53 + src/model/sections.ts | 34 + src/model/spec.ts | 23 + src/validate/authored-model.ts | 12 + src/validate/contracts.ts | 27 + src/validate/readiness-floor.ts | 165 + src/validate/validators.ts | 453 ++ test/bootstrap.test.ts | 32 + test/bootstrap.typecheck.ts | 1 + test/builders.test.ts | 157 + test/builders.typecheck.ts | 75 + test/checkout-v1.test.ts | 43 + test/cli.test.ts | 83 + test/descriptors.test.ts | 58 + test/descriptors.typecheck.ts | 21 + test/graph-schema.test.ts | 28 + test/graph-schema.typecheck.ts | 29 + test/ids.test.ts | 70 + test/ids.typecheck.ts | 22 + test/readiness.test.ts | 108 + test/readiness.typecheck.ts | 84 + test/validators.test.ts | 268 ++ tsconfig.examples.json | 11 + tsconfig.json | 19 + tsup.config.ts | 55 + vitest-test.mjs | 14 + vitest.config.ts | 17 + 60 files changed, 6842 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 .sisyphus/boulder.json create mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/decisions.md create mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/issues.md create mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/learnings.md create mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/problems.md create mode 100644 eslint.config.js create mode 100644 examples/bootstrap-alias.ts create mode 100644 examples/checkout-v1/model.ts create mode 100644 examples/checkout-v1/specs/checkout.pack.ts create mode 100644 examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts create mode 100644 examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts create mode 100644 examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts create mode 100644 examples/checkout-v1/specs/orders/create-order.spec.ts create mode 100644 examples/checkout-v1/specs/orders/order-inventory-rule.spec.ts create mode 100644 examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts create mode 100644 examples/checkout-v1/specs/orders/order-management.spec.ts create mode 100644 examples/checkout-v1/specs/orders/order-model.spec.ts create mode 100644 examples/checkout-v1/specs/orders/order-total-rule.spec.ts create mode 100644 examples/checkout-v1/src/orders/create-order.use-case.ts create mode 100644 examples/checkout-v1/test/orders/create-order.valid-cart.test.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/cli/sdp.ts create mode 100644 src/graph/schema.ts create mode 100644 src/ids.ts create mode 100644 src/index.ts create mode 100644 src/model/anchors.ts create mode 100644 src/model/descriptors.ts create mode 100644 src/model/pack.ts create mode 100644 src/model/relations.ts create mode 100644 src/model/sections.ts create mode 100644 src/model/spec.ts create mode 100644 src/validate/authored-model.ts create mode 100644 src/validate/contracts.ts create mode 100644 src/validate/readiness-floor.ts create mode 100644 src/validate/validators.ts create mode 100644 test/bootstrap.test.ts create mode 100644 test/bootstrap.typecheck.ts create mode 100644 test/builders.test.ts create mode 100644 test/builders.typecheck.ts create mode 100644 test/checkout-v1.test.ts create mode 100644 test/cli.test.ts create mode 100644 test/descriptors.test.ts create mode 100644 test/descriptors.typecheck.ts create mode 100644 test/graph-schema.test.ts create mode 100644 test/graph-schema.typecheck.ts create mode 100644 test/ids.test.ts create mode 100644 test/ids.typecheck.ts create mode 100644 test/readiness.test.ts create mode 100644 test/readiness.typecheck.ts create mode 100644 test/validators.test.ts create mode 100644 tsconfig.examples.json create mode 100644 tsconfig.json create mode 100644 tsup.config.ts create mode 100644 vitest-test.mjs create mode 100644 vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..54beca5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Typecheck + run: npm run typecheck + + - name: Typecheck examples + run: npm run typecheck:examples + + - name: Lint + run: npm run lint + + - name: Format check + run: npm run format:check + + - name: Test + run: npm test + + - name: Build + run: npm run build diff --git a/.gitignore b/.gitignore index 51b454f..2f15136 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ # Local planning artifacts — kept on disk for working notes, out of the published repo plans/ + +# Tooling outputs +node_modules/ +dist/ +generated/ +.sisyphus/evidence/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fcb175d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +AGENTS.md +CLAUDE.md +docs/** +jtbd-stories/** +plans/** +.sisyphus/** diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..bccee91 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100 +} diff --git a/.sisyphus/boulder.json b/.sisyphus/boulder.json new file mode 100644 index 0000000..8ba7e02 --- /dev/null +++ b/.sisyphus/boulder.json @@ -0,0 +1,93 @@ +{ + "active_plan": "/Users/darkomijic/dev-projects/libar-omni-concept/.sisyphus/plans/session-1-bootstrap-phase0.md", + "started_at": "2026-06-07T08:37:00.722Z", + "session_ids": [ + "ses_15ec5956ffferI4mLRGUxHmpHl", + "ses_15ec3ee46ffehXzpSwd3K8j4Zr", + "ses_15ec3ee1affevHWWGHKoDAVqu3", + "ses_15ec3edf3ffe1Ah0U1FSV1dLaF", + "ses_15ec3edb1ffe1af15nigJWwPBs" + ], + "plan_name": "session-1-bootstrap-phase0", + "session_origins": { + "ses_15ec5956ffferI4mLRGUxHmpHl": "direct", + "ses_15ec3ee46ffehXzpSwd3K8j4Zr": "appended", + "ses_15ec3ee1affevHWWGHKoDAVqu3": "appended", + "ses_15ec3edf3ffe1Ah0U1FSV1dLaF": "appended", + "ses_15ec3edb1ffe1af15nigJWwPBs": "appended" + }, + "task_sessions": { + "todo:1": { + "task_key": "todo:1", + "task_label": "1", + "task_title": "Bootstrap package, TypeScript toolchain, scripts, ignore rules, and CI", + "session_id": "ses_15ebd033effeo6aTUdkGjlW0L6", + "agent": "Sisyphus-Junior", + "category": "", + "updated_at": "2026-06-07T09:02:47.924Z" + }, + "todo:2": { + "task_key": "todo:2", + "task_label": "2", + "task_title": "Implement stable ID primitives and the initial public barrel", + "session_id": "ses_15eac1fbcffebFQ40GOZqcPrQB", + "agent": "Sisyphus-Junior", + "category": "", + "updated_at": "2026-06-07T09:18:38.067Z" + }, + "todo:3": { + "task_key": "todo:3", + "task_label": "3", + "task_title": "Implement descriptor literals, display labels, and optional section types", + "session_id": "ses_15e9ccdbbffeaw9jWvCtbxsuiC", + "agent": "Sisyphus-Junior", + "category": "", + "updated_at": "2026-06-07T09:28:04.998Z" + }, + "todo:4": { + "task_key": "todo:4", + "task_label": "4", + "task_title": "Implement Spec, Pack, relation, and anchor DSL builders", + "session_id": "ses_15e9531e3ffekXUe31TT6CW7Xn", + "agent": "Sisyphus-Junior", + "category": "", + "updated_at": "2026-06-07T09:43:03.044Z" + }, + "todo:6": { + "task_key": "todo:6", + "task_label": "6", + "task_title": "Define validation contracts, authored model seam, and readiness-floor data", + "session_id": "ses_15e876d0fffesZMAX5rB4WiqzO", + "agent": "Sisyphus-Junior", + "category": "", + "updated_at": "2026-06-07T09:54:18.121Z" + }, + "todo:7": { + "task_key": "todo:7", + "task_label": "7", + "task_title": "Implement tiny pure authored-layer validators with TDD", + "session_id": "ses_15e7c6fd0ffe9SvHbGztylfVNB", + "agent": "Sisyphus-Junior", + "category": "deep", + "updated_at": "2026-06-07T10:08:44.954Z" + }, + "todo:9": { + "task_key": "todo:9", + "task_label": "9", + "task_title": "Author valid `examples/checkout-v1` tracer bullet and integration gates", + "session_id": "ses_15e6f938affePvKDW62drjb224", + "agent": "Sisyphus-Junior", + "category": "deep", + "updated_at": "2026-06-07T10:29:02.184Z" + }, + "final-wave:f1": { + "task_key": "final-wave:f1", + "task_label": "F1", + "task_title": "Plan Compliance Audit — oracle", + "session_id": "ses_15e548982ffeAvvV8VYPLpxbtB", + "agent": "oracle", + "category": "", + "updated_at": "2026-06-07T10:44:49.621Z" + } + } +} \ No newline at end of file diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/decisions.md b/.sisyphus/notepads/session-1-bootstrap-phase0/decisions.md new file mode 100644 index 0000000..5dd2c49 --- /dev/null +++ b/.sisyphus/notepads/session-1-bootstrap-phase0/decisions.md @@ -0,0 +1,16 @@ +- Use a source-file shebang for the CLI entry instead of a tsup banner so the library build stays clean; if a banner is ever needed, split the CLI build from the library build. +- Resolve the package name to `src/index.ts` in Vitest via top-level `resolve.alias`; do not rely on `test.alias` for this path. +- Keep the source shebang in `src/cli/sdp.ts` and let tsup post-process the emitted CLI file back to one hashbang line after build, so the source stays executable-friendly without shipping a duplicated shebang. +- Let `examples/**` compile only through `tsconfig.examples.json`, with its own `paths` mapping and an explicit empty `exclude`, so the example can prove the public package alias without loosening the root program. +- Keep `src/index.ts` as a Phase 0 barrel only: export the ID parser/formatter helpers, `ref`, and the branded ID helpers from `src/ids.ts`—nothing model-specific yet. +- Use a tiny `vitest-test.mjs` wrapper so the package can accept `npm test -- --run ids` without double-injecting `--run` while preserving the plain `npm test` behavior. +- Keep the `#` suffix grammar narrow (`` only, no dotted subpaths) so the ID parser stays aligned with the concept docs and rejects malformed examples early. +- Model the descriptor/section layer as optional, shape-only TypeScript: literal arrays define the canonical values, and section containers stay fully optional so idea-stage fixtures typecheck without readiness-based promotion rules. +- Keep `SpecSections` as an optional interface of optional section payloads, with each payload using a permissive `Record` shape so the model stays shape-only and lint-clean. +- Extend `src/index.ts` into the Phase 0 builder barrel by exporting the new `spec`/`pack`/relation/anchor helpers alongside IDs and existing schema exports, so Task 4 adds the authored DSL surface without regressing already-present graph-schema typecheck consumers. +- Keep `anchorImplementation()` singular at the builder layer: the identity-only implementation anchor binds one target `SpecId` via `satisfies`, not an array, matching the Task 4/Task 9 contract and avoiding premature many-target semantics in Phase 0. +- Keep the graph schema as a pure, inert module at `src/graph/schema.ts`, and re-export it from the package barrel without adding extractor, writer, or reader behavior. +- Model the Task 6 validation seam as pure authored-layer contracts plus inert readiness-floor data: `AuthoredModel` is only in-memory collections, while graph-shaped `ready` checks stay represented as `deferredInSession1` clauses instead of executable enforcement. +- Keep the CLI stub isolated from the validator/runtime layer: manual `process.argv` parsing only, exact fixed help/error strings, and no filesystem or source-walking imports in `src/cli/sdp.ts`. + +- Keep validateAuthoredModel as a pre-graph aggregator over duplicate-id, dangling-reference, and readiness-floor findings, with individual finding families preserved even though the top-level report stays on the conformance surface for Session 1. diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/issues.md b/.sisyphus/notepads/session-1-bootstrap-phase0/issues.md new file mode 100644 index 0000000..c1b4220 --- /dev/null +++ b/.sisyphus/notepads/session-1-bootstrap-phase0/issues.md @@ -0,0 +1,2 @@ + +- `lsp_diagnostics` can lag behind file rewrites on `src/index.ts`/`test/ids.test.ts`, reporting stale pre-edit syntax until the workspace refreshes; CLI checks (`typecheck`, `test`, `lint`, `build`) were the reliable source of truth here. diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/learnings.md b/.sisyphus/notepads/session-1-bootstrap-phase0/learnings.md new file mode 100644 index 0000000..272ef50 --- /dev/null +++ b/.sisyphus/notepads/session-1-bootstrap-phase0/learnings.md @@ -0,0 +1,26 @@ +- tsup: prefer one ESM-only library build with named entries, `dts: true`, and a real `#!/usr/bin/env node` shebang in `src/cli.ts`; tsup docs say hashbang entries become executable. +- Vitest: package-name aliases belong at top-level `resolve.alias`, not under `test.alias`; `test.alias` is for mock/non-existent modules. +- typescript-eslint: current strict flat-config stack is `strictTypeChecked` + `stylisticTypeChecked` + `parserOptions.projectService: true`. +- package.json: use `type: "module"`, `exports` for the library entry, and `bin` for the CLI; keep `files: ["dist"]` so only build output ships. +- Typed linting with `projectService: true` needs the root TS config files (`tsup.config.ts`, `vitest.config.ts`) included in `tsconfig.json`, or ESLint will refuse to analyze them. +- Prettier `--check .` on this repo needs a `.prettierignore` for the docs/plans/notepad surface so bootstrap verification stays focused on the toolchain files. +- When tsup outputs both a source shebang and a banner shebang, the emitted CLI needs a tiny post-build normalization step to collapse it back to one executable hashbang. +- `tsconfig.examples.json` must explicitly reset `exclude` to `[]` after extending the root config, otherwise the inherited `examples` exclusion leaves the example project empty and TS18003 fires. +- A bootstrap Vitest file can meaningfully prove the package surface by checking both `import "@libar-dev/software-delivery-protocol"` and the package.json `bin`/`exports` shape. +- `npm test -- --run ids` needs a small wrapper script when the package script already bakes in `--run`; otherwise Vitest sees `--run` twice and exits early. +- Named export tests for the public barrel are easiest against `../src/index.js` before the package is built; the alias-only bootstrap test can stay a namespace import. +- The stable-ID parser should accept any lowercase namespace shape and defer broader namespace policy; helper functions (`specId`, `packId`, `implAnchorId`, `testAnchorId`) enforce their own prefixes. +- The optional `#` suffix is a single segment, not another dotted path; treating it like the main path would accept malformed IDs such as `spec:orders.create-order#valid.cart`. +- Canonical descriptor literals should stay separate from display labels: `SpecKind` is the authored value set, while labels live in a dedicated lookup so `Scenario`/`NFR` remain display language only. +- For optional section bags, `Record` fits the open-ended payload while the exported container shape stays as an `interface`; that combination satisfies the lint rules without introducing index signatures. +- Bootstrap schema guards should import `../src/index.js` instead of the package name when they need new source exports; package-name imports can still resolve to stale dist declarations during `tsc --noEmit`. +- Readiness-floor coverage is easiest to keep stable when the floor data exposes clause IDs as inert metadata; tests can then assert the canonical rungs and Session 1 deferred graph-shaped `ready` clauses without coupling to validator logic that does not exist yet. +- The minimal `sdp` stub can stay fully import-free and deterministic: export a pure `runSdpCli(args)` helper, write directly to `process.stdout` / `process.stderr`, and gate auto-execution on the built `dist/cli/sdp.js` path so tests can import the module safely. + +- Validator TDD stayed stable when readiness-floor enforcement flattened the local idea→scoped→defined clauses for a stated rung while skipping and every ready clause; that keeps Session 1 checks authored-layer only without duplicating graph validation. + +- Validator TDD stayed stable when readiness-floor enforcement flattened the local idea-to-scoped-to-defined clauses for a stated rung while skipping the ready defined-floor placeholder and all Session 1 deferred ready clauses; that keeps the checks authored-layer only without duplicating graph validation. + +- When the authored example must keep concept-aligned `*.spec.ts` filenames under `examples/`, Vitest needs an explicit `test.include` such as `test/**/*.test.ts`; otherwise it will collect authored spec files as empty test suites and fail with `No test suite found`. +- On current TypeScript, `tsconfig.examples.json` needs `compilerOptions.ignoreDeprecations: "6.0"` alongside its package-alias `paths` mapping, or `tsc --noEmit -p tsconfig.examples.json` fails on the deprecated `baseUrl` setting before the example code is even checked. +- TypeScript 5.9 rejects `ignoreDeprecations: "6.0"` in `tsconfig.examples.json` with TS5103, so the examples config should stay compatible with the actually installed compiler version instead of pinning a forward-looking deprecation override. diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/problems.md b/.sisyphus/notepads/session-1-bootstrap-phase0/problems.md new file mode 100644 index 0000000..e69de29 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..1b7b327 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,52 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import { fileURLToPath } from "node:url"; + +const tsconfigRootDir = fileURLToPath(new URL(".", import.meta.url)); +const typedTsFiles = ["src/**/*.ts", "test/**/*.ts", "tsup.config.ts", "vitest.config.ts"]; +const exampleTsFiles = ["examples/**/*.ts"]; +const typeCheckedConfigs = [ + ...tseslint.configs.strictTypeChecked, + ...tseslint.configs.stylisticTypeChecked, +].map((config) => ({ + ...config, + files: typedTsFiles, +})); + +export default tseslint.config( + { + ignores: ["dist/**", "generated/**", ".sisyphus/evidence/**", "node_modules/**"], + }, + js.configs.recommended, + ...typeCheckedConfigs, + { + files: ["**/*.{js,mjs,cjs}"], + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: globals.node, + }, + }, + { + files: exampleTsFiles, + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 2022, + sourceType: "module", + globals: globals.node, + }, + }, + { + files: typedTsFiles, + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + parserOptions: { + projectService: true, + tsconfigRootDir, + }, + globals: globals.node, + }, + }, +); diff --git a/examples/bootstrap-alias.ts b/examples/bootstrap-alias.ts new file mode 100644 index 0000000..a948fcc --- /dev/null +++ b/examples/bootstrap-alias.ts @@ -0,0 +1,3 @@ +import * as protocol from "@libar-dev/software-delivery-protocol"; + +export const bootstrapAliasSurface = protocol; diff --git a/examples/checkout-v1/model.ts b/examples/checkout-v1/model.ts new file mode 100644 index 0000000..a5f666b --- /dev/null +++ b/examples/checkout-v1/model.ts @@ -0,0 +1,30 @@ +import type { AuthoredModel } from "@libar-dev/software-delivery-protocol"; + +import { checkoutV1Pack } from "./specs/checkout.pack.js"; +import { orderLifecycleDecisionSpec } from "./specs/decisions/order-lifecycle.spec.js"; +import { createOrderInvalidCartSpec } from "./specs/orders/create-order-invalid-cart.spec.js"; +import { createOrderValidCartSpec } from "./specs/orders/create-order-valid-cart.spec.js"; +import { createOrderSpec } from "./specs/orders/create-order.spec.js"; +import { orderInventoryRuleSpec } from "./specs/orders/order-inventory-rule.spec.js"; +import { orderLatencyConstraintSpec } from "./specs/orders/order-latency-constraint.spec.js"; +import { orderManagementSpec } from "./specs/orders/order-management.spec.js"; +import { orderModelSpec } from "./specs/orders/order-model.spec.js"; +import { orderTotalRuleSpec } from "./specs/orders/order-total-rule.spec.js"; +import { createOrderUseCaseAnchor } from "./src/orders/create-order.use-case.js"; +import { createOrderValidCartTest } from "./test/orders/create-order.valid-cart.test.js"; + +export const checkoutV1Model: AuthoredModel = { + specs: [ + orderManagementSpec, + createOrderSpec, + createOrderValidCartSpec, + createOrderInvalidCartSpec, + orderTotalRuleSpec, + orderInventoryRuleSpec, + orderLatencyConstraintSpec, + orderModelSpec, + orderLifecycleDecisionSpec, + ], + packs: [checkoutV1Pack], + anchors: [createOrderUseCaseAnchor, createOrderValidCartTest], +}; diff --git a/examples/checkout-v1/specs/checkout.pack.ts b/examples/checkout-v1/specs/checkout.pack.ts new file mode 100644 index 0000000..6d35ad9 --- /dev/null +++ b/examples/checkout-v1/specs/checkout.pack.ts @@ -0,0 +1,19 @@ +import { pack, packId, ref } from "@libar-dev/software-delivery-protocol"; + +export const checkoutV1Pack = pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + framing: "Let customers create orders from valid carts with honest authored traceability.", + specs: [ + ref("spec:orders.order-management"), + ref("spec:orders.create-order"), + ref("spec:orders.create-order.valid-cart"), + ref("spec:orders.create-order.invalid-cart"), + ref("spec:orders.order-total-rule"), + ref("spec:orders.order-inventory-rule"), + ref("spec:orders.order-latency-constraint"), + ref("spec:orders.order-model"), + ref("spec:decisions.order-lifecycle"), + ], + modelRefs: [ref("spec:orders.order-model")], +}); diff --git a/examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts b/examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts new file mode 100644 index 0000000..4775ef9 --- /dev/null +++ b/examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts @@ -0,0 +1,29 @@ +import { refines, spec, specId } from "@libar-dev/software-delivery-protocol"; + +export const orderLifecycleDecisionSpec = spec({ + id: specId("spec:decisions.order-lifecycle"), + title: "Order lifecycle keeps validation before creation", + kind: "decision", + altitude: "feature", + readiness: "defined", + intent: { + outcome: "Decide when checkout-v1 may create an order.", + value: "The authored example has one stable lifecycle rule for success and rejection paths.", + }, + behavior: { + rules: [ + "Order creation happens only after cart validation and inventory checks succeed.", + "Rejected carts do not create partial orders.", + ], + }, + decision: { + status: "accepted", + decision: + "Create orders only after cart validation confirms non-empty input and sufficient inventory.", + rationale: [ + "The valid-cart and invalid-cart examples need one consistent gate.", + "Rejecting before persistence keeps the tracer bullet small and internally consistent.", + ], + }, + relations: [refines(specId("spec:orders.create-order"))], +}); diff --git a/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts b/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts new file mode 100644 index 0000000..61e0b48 --- /dev/null +++ b/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts @@ -0,0 +1,47 @@ +import { refines, spec, specId, verifies } from "@libar-dev/software-delivery-protocol"; + +const thenKey = "then"; + +const createOrderInvalidCartBehavior = Object.fromEntries([ + ["examples", ["Out-of-stock or empty carts are rejected before an order is created."]], + [ + "given", + [ + "A cart is empty or contains at least one item without available inventory.", + "The cart is submitted for order creation.", + ], + ], + ["when", ["The create-order use case validates the cart."]], + [ + thenKey, + [ + "No order is created.", + "The caller receives a validation error explaining why the cart is invalid.", + ], + ], +]) as Record; + +export const createOrderInvalidCartSpec = spec({ + id: specId("spec:orders.create-order.invalid-cart"), + title: "Invalid cart is rejected", + kind: "example", + altitude: "story", + readiness: "defined", + intent: { + outcome: "Show that an invalid cart does not become an order.", + value: + "The authored example captures the rejection path without adding invalid fixtures to the model.", + }, + behavior: createOrderInvalidCartBehavior, + verification: { + mode: "executable", + criteria: [ + "The use case throws when inventory is missing.", + "The use case throws when the cart is empty.", + ], + }, + relations: [ + refines(specId("spec:orders.create-order")), + verifies(specId("spec:orders.create-order")), + ], +}); diff --git a/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts b/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts new file mode 100644 index 0000000..05af054 --- /dev/null +++ b/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts @@ -0,0 +1,48 @@ +import { refines, spec, specId, verifies } from "@libar-dev/software-delivery-protocol"; + +const thenKey = "then"; + +const createOrderValidCartBehavior = Object.fromEntries([ + ["examples", ["Valid cart becomes an order with the computed total."]], + [ + "given", + [ + "A customer has a cart with one or more line items.", + "Every cart item is in stock.", + "Each line item has a positive quantity and a unit price.", + ], + ], + ["when", ["The customer submits the cart for order creation."]], + [ + thenKey, + [ + "An order is created.", + "The order total equals the sum of quantity multiplied by unit price for each line item.", + "The order contains the original cart lines.", + ], + ], +]) as Record; + +export const createOrderValidCartSpec = spec({ + id: specId("spec:orders.create-order.valid-cart"), + title: "Valid cart creates an order", + kind: "example", + altitude: "story", + readiness: "defined", + intent: { + outcome: "Show that a valid cart can become an order.", + value: "The authored example demonstrates the happy path for create-order.", + }, + behavior: createOrderValidCartBehavior, + verification: { + mode: "executable", + criteria: [ + "The order result contains a stable id.", + "The returned total matches the cart math.", + ], + }, + relations: [ + refines(specId("spec:orders.create-order")), + verifies(specId("spec:orders.create-order")), + ], +}); diff --git a/examples/checkout-v1/specs/orders/create-order.spec.ts b/examples/checkout-v1/specs/orders/create-order.spec.ts new file mode 100644 index 0000000..9999fb4 --- /dev/null +++ b/examples/checkout-v1/specs/orders/create-order.spec.ts @@ -0,0 +1,33 @@ +import { + constrainedBy, + decidedBy, + ref, + refines, + spec, + specId, +} from "@libar-dev/software-delivery-protocol"; + +export const createOrderSpec = spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "defined", + intent: { + actor: "customer", + outcome: "Turn a valid cart into an order.", + value: "Customers can complete purchases without the example modeling the rest of checkout.", + }, + behavior: { + rules: [ref("spec:orders.order-total-rule"), ref("spec:orders.order-inventory-rule")], + examples: [ + ref("spec:orders.create-order.valid-cart"), + ref("spec:orders.create-order.invalid-cart"), + ], + }, + relations: [ + refines(specId("spec:orders.order-management")), + constrainedBy(specId("spec:orders.order-latency-constraint")), + decidedBy(specId("spec:decisions.order-lifecycle")), + ], +}); diff --git a/examples/checkout-v1/specs/orders/order-inventory-rule.spec.ts b/examples/checkout-v1/specs/orders/order-inventory-rule.spec.ts new file mode 100644 index 0000000..f510e59 --- /dev/null +++ b/examples/checkout-v1/specs/orders/order-inventory-rule.spec.ts @@ -0,0 +1,20 @@ +import { refines, spec, specId } from "@libar-dev/software-delivery-protocol"; + +export const orderInventoryRuleSpec = spec({ + id: specId("spec:orders.order-inventory-rule"), + title: "Order creation requires available inventory", + kind: "rule", + altitude: "story", + readiness: "defined", + intent: { + outcome: "Reject carts whose items are not fully available.", + value: "Order creation does not over-promise unavailable stock.", + }, + behavior: { + rules: [ + "Every cart line must have at least the requested quantity available.", + "Any unavailable line blocks order creation for the whole cart.", + ], + }, + relations: [refines(specId("spec:orders.create-order"))], +}); diff --git a/examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts b/examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts new file mode 100644 index 0000000..453f570 --- /dev/null +++ b/examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts @@ -0,0 +1,23 @@ +import { refines, spec, specId } from "@libar-dev/software-delivery-protocol"; + +export const orderLatencyConstraintSpec = spec({ + id: specId("spec:orders.order-latency-constraint"), + title: "Create-order latency stays within checkout budget", + kind: "constraint", + altitude: "story", + readiness: "defined", + intent: { + outcome: "Keep create-order fast enough for interactive checkout.", + value: "Customers are not left waiting after submitting a valid cart.", + }, + behavior: { + rules: ["The create-order path stays inside a machine-readable latency budget."], + }, + constraints: { + statement: "Create-order should respond within the checkout latency budget.", + target: "latency.p95.lt:250ms", + unit: "ms", + percentile: 95, + }, + relations: [refines(specId("spec:orders.create-order"))], +}); diff --git a/examples/checkout-v1/specs/orders/order-management.spec.ts b/examples/checkout-v1/specs/orders/order-management.spec.ts new file mode 100644 index 0000000..5ee8407 --- /dev/null +++ b/examples/checkout-v1/specs/orders/order-management.spec.ts @@ -0,0 +1,20 @@ +import { decidedBy, spec, specId } from "@libar-dev/software-delivery-protocol"; + +export const orderManagementSpec = spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "defined", + intent: { + outcome: "Coordinate the authored order-management slice for checkout v1.", + value: "The pack can express order creation behavior without modeling the full checkout flow.", + }, + behavior: { + rules: [ + "Order management keeps order creation, rules, constraints, and decisions traceable in one authored slice.", + "Every order-management child spec keeps its targets inside the checkout-v1 example set.", + ], + }, + relations: [decidedBy(specId("spec:decisions.order-lifecycle"))], +}); diff --git a/examples/checkout-v1/specs/orders/order-model.spec.ts b/examples/checkout-v1/specs/orders/order-model.spec.ts new file mode 100644 index 0000000..06413a5 --- /dev/null +++ b/examples/checkout-v1/specs/orders/order-model.spec.ts @@ -0,0 +1,28 @@ +import { refines, spec, specId } from "@libar-dev/software-delivery-protocol"; + +export const orderModelSpec = spec({ + id: specId("spec:orders.order-model"), + title: "Order-management domain vocabulary", + kind: "model", + altitude: "story", + readiness: "defined", + intent: { + outcome: "Define the core terms used by the checkout-v1 order-management slice.", + value: "Specs, code, and tests use the same vocabulary for carts, orders, and inventory.", + }, + behavior: { + rules: [ + "The authored slice uses shared term definitions instead of inlining conflicting vocabulary.", + ], + }, + model: { + terms: { + cart: "A customer-selected set of line items that has not yet become an order.", + cartLine: "A requested product, quantity, and unit price inside a cart.", + inventorySnapshot: "The available quantity for each product at validation time.", + order: "The persisted result of accepting a valid cart.", + orderTotal: "The sum of all accepted cart line subtotals.", + }, + }, + relations: [refines(specId("spec:orders.order-management"))], +}); diff --git a/examples/checkout-v1/specs/orders/order-total-rule.spec.ts b/examples/checkout-v1/specs/orders/order-total-rule.spec.ts new file mode 100644 index 0000000..44a8f61 --- /dev/null +++ b/examples/checkout-v1/specs/orders/order-total-rule.spec.ts @@ -0,0 +1,20 @@ +import { refines, spec, specId } from "@libar-dev/software-delivery-protocol"; + +export const orderTotalRuleSpec = spec({ + id: specId("spec:orders.order-total-rule"), + title: "Order total matches cart math", + kind: "rule", + altitude: "story", + readiness: "defined", + intent: { + outcome: "Keep the order total equal to the sum of cart line subtotals.", + value: "Customers and downstream systems see one deterministic order total.", + }, + behavior: { + rules: [ + "Each line subtotal is quantity multiplied by unit price.", + "The order total is the sum of all line subtotals.", + ], + }, + relations: [refines(specId("spec:orders.create-order"))], +}); diff --git a/examples/checkout-v1/src/orders/create-order.use-case.ts b/examples/checkout-v1/src/orders/create-order.use-case.ts new file mode 100644 index 0000000..6a1b1c5 --- /dev/null +++ b/examples/checkout-v1/src/orders/create-order.use-case.ts @@ -0,0 +1,54 @@ +import { anchorImplementation, implAnchorId, ref } from "@libar-dev/software-delivery-protocol"; + +export interface CartLine { + readonly productId: string; + readonly quantity: number; + readonly unitPrice: number; +} + +export interface CartInput { + readonly customerId: string; + readonly lines: readonly CartLine[]; +} + +export type InventorySnapshot = Readonly>; + +export interface CreatedOrder { + readonly orderId: string; + readonly customerId: string; + readonly total: number; + readonly lines: readonly CartLine[]; +} + +export const createOrderUseCaseAnchor = anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + label: "createOrderFromCart", + satisfies: ref("spec:orders.create-order"), +}); + +export function createOrderFromCart(cart: CartInput, inventory: InventorySnapshot): CreatedOrder { + if (cart.lines.length === 0) { + throw new Error("Cart must contain at least one line."); + } + + for (const line of cart.lines) { + const availableQuantity = inventory[line.productId] ?? 0; + + if (line.quantity <= 0) { + throw new Error(`Cart line ${line.productId} must request a positive quantity.`); + } + + if (availableQuantity < line.quantity) { + throw new Error(`Inventory is unavailable for product ${line.productId}.`); + } + } + + const total = cart.lines.reduce((sum, line) => sum + line.quantity * line.unitPrice, 0); + + return { + orderId: `order-${cart.customerId}`, + customerId: cart.customerId, + total, + lines: cart.lines, + }; +} diff --git a/examples/checkout-v1/test/orders/create-order.valid-cart.test.ts b/examples/checkout-v1/test/orders/create-order.valid-cart.test.ts new file mode 100644 index 0000000..d53ac37 --- /dev/null +++ b/examples/checkout-v1/test/orders/create-order.valid-cart.test.ts @@ -0,0 +1,7 @@ +import { ref, specTest, testAnchorId } from "@libar-dev/software-delivery-protocol"; + +export const createOrderValidCartTest = specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + label: "valid cart verifies the create-order happy path", + verifies: ref("spec:orders.create-order.valid-cart"), +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..66e7941 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3845 @@ +{ + "name": "@libar-dev/software-delivery-protocol", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@libar-dev/software-delivery-protocol", + "version": "0.0.0", + "bin": { + "sdp": "dist/cli/sdp.js" + }, + "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/node": "^20.0.0", + "eslint": "^9.0.0", + "globals": "^15.0.0", + "prettier": "^3.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.42.tgz", + "integrity": "sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "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==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "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" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "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", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "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", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "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", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "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" + } + }, + "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==", + "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", + "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", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.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", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2a75d0a --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "@libar-dev/software-delivery-protocol", + "version": "0.0.0", + "type": "module", + "files": [ + "dist" + ], + "bin": { + "sdp": "./dist/cli/sdp.js" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "engines": { + "node": ">=20" + }, + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit -p tsconfig.json", + "typecheck:examples": "tsc --noEmit -p tsconfig.examples.json", + "test": "node ./vitest-test.mjs", + "test:watch": "vitest", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "npm run typecheck && npm run typecheck:examples && npm run lint && npm run format:check && npm test && npm run build" + }, + "devDependencies": { + "@eslint/js": "^9.0.0", + "@types/node": "^20.0.0", + "eslint": "^9.0.0", + "globals": "^15.0.0", + "prettier": "^3.0.0", + "tsup": "^8.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.0.0", + "vitest": "^2.0.0" + } +} diff --git a/src/cli/sdp.ts b/src/cli/sdp.ts new file mode 100644 index 0000000..5d05eb2 --- /dev/null +++ b/src/cli/sdp.ts @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +export const SDP_HELP_TEXT = `sdp — Libar Software Delivery Protocol +Usage: + sdp --help + sdp build + sdp validate + +Commands: + build Not implemented yet (Slice 1: extractor) + validate Validation gate not wired yet (Slice 3: graph validator gate)`; + +interface CliOutput { + stdout?: { write: (chunk: string) => void }; + stderr?: { write: (chunk: string) => void }; +} + +const defaultCliOutput: CliOutput = { + stdout: process.stdout, + stderr: process.stderr, +}; + +function writeStdout(output: CliOutput, text: string): void { + if (output.stdout !== undefined) { + output.stdout.write(text); + } +} + +function writeStderr(output: CliOutput, text: string): void { + if (output.stderr !== undefined) { + output.stderr.write(text); + } +} + +export function runSdpCli(args: readonly string[], output: CliOutput = defaultCliOutput): number { + const [command] = args; + + if (command === undefined || command === "--help") { + writeStdout(output, `${SDP_HELP_TEXT}\n`); + return 0; + } + + if (command === "build") { + writeStderr(output, "sdp build is not implemented yet (Slice 1: extractor).\n"); + return 1; + } + + if (command === "validate") { + writeStderr(output, "sdp validate gate is not wired yet (Slice 3: graph validator gate).\n"); + return 1; + } + + writeStderr(output, `${SDP_HELP_TEXT}\n\nUnknown command: ${command}\n`); + return 1; +} + +const executedPath = process.argv[1]; + +if ( + executedPath !== undefined && + (executedPath.endsWith("/dist/cli/sdp.js") || executedPath.endsWith("\\dist\\cli\\sdp.js")) +) { + process.exitCode = runSdpCli(process.argv.slice(2)); +} diff --git a/src/graph/schema.ts b/src/graph/schema.ts new file mode 100644 index 0000000..4d09206 --- /dev/null +++ b/src/graph/schema.ts @@ -0,0 +1,71 @@ +import type { SpecAltitude, SpecKind, SpecReadiness } from "../model/descriptors.js"; + +export const schemaVersion = "0.1.0" as const; + +export const graphNodeTypes = ["Primitive", "Pack", "Anchor", "CodeNode"] as const; +export type GraphNodeType = (typeof graphNodeTypes)[number]; + +export const graphClaims = ["declared", "anchored", "inferred"] as const; +export type GraphClaim = (typeof graphClaims)[number]; + +export const deliveryFactNames = ["implemented", "has-verifier", "observed"] as const; +export type DeliveryFactName = (typeof deliveryFactNames)[number]; + +export const authoredEdgeTypes = [ + "refines", + "dependsOn", + "constrainedBy", + "decidedBy", + "verifies", + "supersedes", +] as const; +export type AuthoredEdgeType = (typeof authoredEdgeTypes)[number]; + +export const derivedEdgeTypes = ["belongsTo", "satisfies"] as const; +export type DerivedEdgeType = (typeof derivedEdgeTypes)[number]; + +export const graphEdgeTypes = [...authoredEdgeTypes, ...derivedEdgeTypes] as const; +export type GraphEdgeType = (typeof graphEdgeTypes)[number]; + +interface GraphNodeBase { + readonly id: string; + readonly nodeType: GraphNodeType; + readonly claim: GraphClaim; +} + +export interface PrimitiveNode extends GraphNodeBase { + readonly nodeType: "Primitive"; + readonly specKind: SpecKind; + readonly altitude: SpecAltitude; + readonly readiness: SpecReadiness; + readonly deliveryFacts?: readonly DeliveryFactName[]; +} + +export interface PackNode extends GraphNodeBase { + readonly nodeType: "Pack"; +} + +export interface AnchorNode extends GraphNodeBase { + readonly nodeType: "Anchor"; +} + +export interface CodeNode extends GraphNodeBase { + readonly nodeType: "CodeNode"; + readonly file: string; + readonly line?: number; +} + +export type GraphNode = PrimitiveNode | PackNode | AnchorNode | CodeNode; + +export interface GraphEdge { + readonly from: string; + readonly type: GraphEdgeType; + readonly to: string; + readonly claim: GraphClaim; +} + +export interface GraphSchema { + readonly schemaVersion: typeof schemaVersion; + readonly nodes: readonly GraphNode[]; + readonly edges: readonly GraphEdge[]; +} diff --git a/src/ids.ts b/src/ids.ts new file mode 100644 index 0000000..c3a3954 --- /dev/null +++ b/src/ids.ts @@ -0,0 +1,141 @@ +type Brand = string & { + readonly __brand: TBrand; +}; + +const LOWERCASE_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*$/u; +const PATH_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*$/u; + +export type SpecId = Brand<"SpecId">; +export type PackId = Brand<"PackId">; +export type AnchorId = Brand<"AnchorId">; +export type ImplAnchorId = AnchorId & Brand<"ImplAnchorId">; +export type TestAnchorId = AnchorId & Brand<"TestAnchorId">; + +export interface IdParts { + readonly namespace: string; + readonly path: string; + readonly subpath?: string; +} + +function failId(value: string, reason: string): never { + throw new Error(`Invalid ID "${value}": ${reason}`); +} + +function brandId(value: string): Brand { + return value as Brand; +} + +function validatePath(value: string, path: string): void { + const segments = path.split("."); + + if (segments.some((segment) => segment.length === 0)) { + failId(value, "empty path segment"); + } + + if (segments.some((segment) => !PATH_SEGMENT_PATTERN.test(segment))) { + failId(value, "invalid path segment"); + } +} + +function validateSubpath(value: string, subpath: string): void { + if (!PATH_SEGMENT_PATTERN.test(subpath)) { + failId(value, "malformed # suffix"); + } +} + +function validateParsedNamespace(value: string, namespace: string): void { + if (!LOWERCASE_NAMESPACE_PATTERN.test(namespace)) { + failId(value, "namespace must be lowercase"); + } +} + +function validateIdShape(value: string): IdParts { + if (value.length === 0) { + failId(value, "missing namespace"); + } + + if (/\s/u.test(value)) { + failId(value, "whitespace is not allowed"); + } + + const colonIndex = value.indexOf(":"); + + if (colonIndex <= 0) { + failId(value, "missing namespace"); + } + + const namespace = value.slice(0, colonIndex); + validateParsedNamespace(value, namespace); + + const body = value.slice(colonIndex + 1); + + if (body.length === 0) { + failId(value, "missing path"); + } + + const hashIndex = body.indexOf("#"); + const path = hashIndex === -1 ? body : body.slice(0, hashIndex); + const subpath = hashIndex === -1 ? undefined : body.slice(hashIndex + 1); + + if (path.length === 0) { + failId(value, "missing path"); + } + + validatePath(value, path); + + if (hashIndex !== -1) { + if (subpath === undefined || subpath.length === 0 || subpath.includes("#")) { + failId(value, "malformed # suffix"); + } + + validateSubpath(value, subpath); + } + + return subpath === undefined ? { namespace, path } : { namespace, path, subpath }; +} + +function requireNamespace( + value: string, + expectedNamespace: string, +): Brand { + const parsed = validateIdShape(value); + + if (parsed.namespace !== expectedNamespace) { + failId(value, `expected namespace "${expectedNamespace}"`); + } + + return brandId(value); +} + +export function parseId(value: string): IdParts { + return validateIdShape(value); +} + +export function formatId(parts: IdParts): string { + return parts.subpath === undefined + ? `${parts.namespace}:${parts.path}` + : `${parts.namespace}:${parts.path}#${parts.subpath}`; +} + +export function anchorId(value: string): AnchorId { + validateIdShape(value); + return brandId<"AnchorId">(value); +} + +export function specId(value: string): SpecId { + return requireNamespace<"SpecId">(value, "spec"); +} + +export function packId(value: string): PackId { + return requireNamespace<"PackId">(value, "pack"); +} + +export function implAnchorId(value: string): ImplAnchorId { + return requireNamespace<"ImplAnchorId">(value, "impl") as ImplAnchorId; +} + +export function testAnchorId(value: string): TestAnchorId { + return requireNamespace<"TestAnchorId">(value, "test") as TestAnchorId; +} + +export { specId as ref }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..cae0cd9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,12 @@ +export * from "./ids.js"; +export * from "./graph/schema.js"; +export * from "./model/anchors.js"; +export * from "./model/descriptors.js"; +export * from "./model/pack.js"; +export * from "./model/relations.js"; +export * from "./model/sections.js"; +export * from "./model/spec.js"; +export * from "./validate/authored-model.js"; +export * from "./validate/contracts.js"; +export * from "./validate/readiness-floor.js"; +export * from "./validate/validators.js"; diff --git a/src/model/anchors.ts b/src/model/anchors.ts new file mode 100644 index 0000000..63dd36e --- /dev/null +++ b/src/model/anchors.ts @@ -0,0 +1,27 @@ +import type { ImplAnchorId, SpecId, TestAnchorId } from "../ids.js"; + +export interface ImplementationAnchor { + readonly id: ImplAnchorId; + readonly label?: string; + readonly satisfies: SpecId; +} + +export interface SpecTestAnchor { + readonly id: TestAnchorId; + readonly label?: string; + readonly verifies: SpecId; +} + +export type Anchor = ImplementationAnchor | SpecTestAnchor; + +export function anchorImplementation(anchor: ImplementationAnchor): ImplementationAnchor { + return { + ...anchor, + }; +} + +export function specTest(anchor: SpecTestAnchor): SpecTestAnchor { + return { + ...anchor, + }; +} diff --git a/src/model/descriptors.ts b/src/model/descriptors.ts new file mode 100644 index 0000000..ac80537 --- /dev/null +++ b/src/model/descriptors.ts @@ -0,0 +1,33 @@ +export const SPEC_KINDS = [ + "behavior", + "workflow", + "example", + "rule", + "constraint", + "model", + "decision", + "contract", +] as const; + +export type SpecKind = (typeof SPEC_KINDS)[number]; + +export const SPEC_ALTITUDES = ["epic", "feature", "story"] as const; + +export type SpecAltitude = (typeof SPEC_ALTITUDES)[number]; + +export const SPEC_READINESS = ["idea", "scoped", "defined", "ready"] as const; + +export type SpecReadiness = (typeof SPEC_READINESS)[number]; + +export const SPEC_KIND_DISPLAY_LABELS = { + behavior: "Use Case / Behavior", + workflow: "Workflow", + example: "Example / Scenario", + rule: "Business Rule", + constraint: "Constraint (NFR)", + model: "Domain Model", + decision: "Decision Record", + contract: "Contract", +} as const satisfies Record; + +export type SpecKindDisplayLabel = (typeof SPEC_KIND_DISPLAY_LABELS)[SpecKind]; diff --git a/src/model/pack.ts b/src/model/pack.ts new file mode 100644 index 0000000..35a249d --- /dev/null +++ b/src/model/pack.ts @@ -0,0 +1,17 @@ +import type { PackId, SpecId } from "../ids.js"; + +export interface Pack { + readonly id: PackId; + readonly title: string; + readonly framing?: string; + readonly specs: readonly SpecId[]; + readonly modelRefs?: readonly SpecId[]; +} + +export function pack(definition: Pack): Pack { + return { + ...definition, + specs: [...definition.specs], + modelRefs: definition.modelRefs === undefined ? undefined : [...definition.modelRefs], + }; +} diff --git a/src/model/relations.ts b/src/model/relations.ts new file mode 100644 index 0000000..24414a4 --- /dev/null +++ b/src/model/relations.ts @@ -0,0 +1,53 @@ +import type { SpecId } from "../ids.js"; + +export const SPEC_RELATION_TYPES = [ + "refines", + "dependsOn", + "constrainedBy", + "decidedBy", + "verifies", + "supersedes", +] as const; + +export type SpecRelationType = (typeof SPEC_RELATION_TYPES)[number]; + +export interface SpecRelation { + readonly type: TType; + readonly target: SpecId; + readonly claim: "declared"; +} + +function declaredRelation( + type: TType, + target: SpecId, +): SpecRelation { + return { + type, + target, + claim: "declared", + }; +} + +export function refines(target: SpecId): SpecRelation<"refines"> { + return declaredRelation("refines", target); +} + +export function dependsOn(target: SpecId): SpecRelation<"dependsOn"> { + return declaredRelation("dependsOn", target); +} + +export function constrainedBy(target: SpecId): SpecRelation<"constrainedBy"> { + return declaredRelation("constrainedBy", target); +} + +export function decidedBy(target: SpecId): SpecRelation<"decidedBy"> { + return declaredRelation("decidedBy", target); +} + +export function verifies(target: SpecId): SpecRelation<"verifies"> { + return declaredRelation("verifies", target); +} + +export function supersedes(target: SpecId): SpecRelation<"supersedes"> { + return declaredRelation("supersedes", target); +} diff --git a/src/model/sections.ts b/src/model/sections.ts new file mode 100644 index 0000000..4d0de7c --- /dev/null +++ b/src/model/sections.ts @@ -0,0 +1,34 @@ +export const SPEC_SECTION_NAMES = [ + "intent", + "behavior", + "constraints", + "model", + "design", + "decision", + "verification", + "ui", +] as const; + +export type SpecSectionName = (typeof SPEC_SECTION_NAMES)[number]; + +export type SpecSectionContent = Record; + +export type IntentSection = SpecSectionContent; +export type BehaviorSection = SpecSectionContent; +export type ConstraintsSection = SpecSectionContent; +export type ModelSection = SpecSectionContent; +export type DesignSection = SpecSectionContent; +export type DecisionSection = SpecSectionContent; +export type VerificationSection = SpecSectionContent; +export type UiSection = SpecSectionContent; + +export interface SpecSections { + readonly intent?: IntentSection; + readonly behavior?: BehaviorSection; + readonly constraints?: ConstraintsSection; + readonly model?: ModelSection; + readonly design?: DesignSection; + readonly decision?: DecisionSection; + readonly verification?: VerificationSection; + readonly ui?: UiSection; +} diff --git a/src/model/spec.ts b/src/model/spec.ts new file mode 100644 index 0000000..a89da59 --- /dev/null +++ b/src/model/spec.ts @@ -0,0 +1,23 @@ +import type { SpecId } from "../ids.js"; +import type { SpecAltitude, SpecKind, SpecReadiness } from "./descriptors.js"; +import type { SpecRelation } from "./relations.js"; +import type { SpecSections } from "./sections.js"; + +export interface Spec extends SpecSections { + readonly id: SpecId; + readonly title: string; + readonly kind: SpecKind; + readonly altitude: SpecAltitude; + readonly readiness: SpecReadiness; + readonly relations?: readonly SpecRelation[]; +} + +export function spec(definition: Spec): Spec { + return { + ...definition, + relations: + definition.relations?.map((relation) => ({ + ...relation, + })) ?? undefined, + }; +} diff --git a/src/validate/authored-model.ts b/src/validate/authored-model.ts new file mode 100644 index 0000000..0866afe --- /dev/null +++ b/src/validate/authored-model.ts @@ -0,0 +1,12 @@ +import type { Anchor } from "../model/anchors.js"; +import type { Pack } from "../model/pack.js"; +import type { Spec } from "../model/spec.js"; + +/** + * Session 1 authored-layer DTO for pre-graph checks; not persisted, not a graph, and not the Slice 3 validation gate. + */ +export interface AuthoredModel { + readonly specs: readonly Spec[]; + readonly packs: readonly Pack[]; + readonly anchors: readonly Anchor[]; +} diff --git a/src/validate/contracts.ts b/src/validate/contracts.ts new file mode 100644 index 0000000..8f5e691 --- /dev/null +++ b/src/validate/contracts.ts @@ -0,0 +1,27 @@ +export const validatorFamilies = ["conformance", "honesty"] as const; +export type ValidatorFamily = (typeof validatorFamilies)[number]; + +export const validationSeverities = ["error", "warning"] as const; +export type Severity = (typeof validationSeverities)[number]; + +export interface Finding { + readonly validatorId: string; + readonly family: ValidatorFamily; + readonly severity: Severity; + readonly message: string; + readonly subjectId?: string; + readonly relatedId?: string; + readonly path?: string; +} + +export interface ValidationReport { + readonly validatorId: string; + readonly family: ValidatorFamily; + readonly findings: readonly Finding[]; +} + +export interface Validator { + readonly id: string; + readonly family: ValidatorFamily; + validate(model: TModel): ValidationReport; +} diff --git a/src/validate/readiness-floor.ts b/src/validate/readiness-floor.ts new file mode 100644 index 0000000..386401e --- /dev/null +++ b/src/validate/readiness-floor.ts @@ -0,0 +1,165 @@ +import type { SpecKind, SpecReadiness } from "../model/descriptors.js"; + +export interface ReadinessClause { + readonly id: string; + readonly description: string; + readonly authoredPaths?: readonly string[]; + readonly deferredInSession1?: true; +} + +export interface ReadinessFloor { + readonly readiness: SpecReadiness; + readonly clauses: readonly ReadinessClause[]; +} + +export type ReadinessOverlayKind = Extract; + +export interface ReadinessKindOverlay { + readonly kind: ReadinessOverlayKind; + readonly appliesAtOrAbove: Extract; + readonly clauses: readonly ReadinessClause[]; +} + +export const readinessFloors = { + idea: { + readiness: "idea", + clauses: [ + { + id: "id", + description: "Envelope includes a stable spec id.", + authoredPaths: ["id"], + }, + { + id: "title", + description: "Envelope includes a human-readable title.", + authoredPaths: ["title"], + }, + { + id: "kind", + description: "Envelope states the authored spec kind.", + authoredPaths: ["kind"], + }, + { + id: "altitude", + description: "Envelope states the authored altitude.", + authoredPaths: ["altitude"], + }, + { + id: "intent.outcome-or-parent-relation", + description: "Spec states intent.outcome or declares a parent relation via refines.", + authoredPaths: ["intent.outcome", "relations"], + }, + ], + }, + scoped: { + readiness: "scoped", + clauses: [ + { + id: "intent.outcome", + description: "Spec states the intended outcome.", + authoredPaths: ["intent.outcome"], + }, + { + id: "at-least-one-relation", + description: "Spec declares at least one authored relation.", + authoredPaths: ["relations"], + }, + { + id: "rules-examples-or-constraints", + description: + "Spec includes at least one of behavior.rules, behavior.examples, or constraints.", + authoredPaths: ["behavior.rules", "behavior.examples", "constraints"], + }, + ], + }, + defined: { + readiness: "defined", + clauses: [ + { + id: "rules-and-or-examples", + description: "Spec includes rules and/or examples.", + authoredPaths: ["behavior.rules", "behavior.examples"], + }, + { + id: "constraint-targets-are-machine-readable", + description: "Any authored constraint detail includes a machine-readable target.", + authoredPaths: ["constraints.*.target"], + }, + { + id: "no-blocking-open-questions", + description: "Spec has no blocking open questions.", + authoredPaths: ["design.openQuestions", "decision.openQuestions"], + }, + ], + }, + ready: { + readiness: "ready", + clauses: [ + { + id: "defined-floor", + description: "Spec satisfies the defined readiness floor.", + }, + { + id: "no-blocking-open-questions", + description: "Spec has no blocking open questions.", + authoredPaths: ["design.openQuestions", "decision.openQuestions"], + }, + { + id: "all-relations-resolve", + description: "All authored relations resolve to known targets.", + authoredPaths: ["relations"], + deferredInSession1: true, + }, + { + id: "depends-on-and-refines-targets-are-defined", + description: "Every dependsOn and refines target is at least defined.", + authoredPaths: ["relations"], + deferredInSession1: true, + }, + { + id: "anchors-resolve", + description: "Any authored anchors present resolve.", + authoredPaths: ["anchors"], + deferredInSession1: true, + }, + ], + }, +} as const satisfies Record; + +export const readinessKindOverlays = { + constraint: { + kind: "constraint", + appliesAtOrAbove: "defined", + clauses: [ + { + id: "constraint-machine-readable-target", + description: + "Constraint specs need a parseable, machine-readable target before defined and ready.", + authoredPaths: ["constraints.target"], + }, + ], + }, + example: { + kind: "example", + appliesAtOrAbove: "defined", + clauses: [ + { + id: "example-given-when-then", + description: + "Example specs need structured given, when, and then detail before defined and ready.", + authoredPaths: ["behavior.given", "behavior.when", "behavior.then"], + }, + ], + }, + model: { + kind: "model", + appliesAtOrAbove: "defined", + clauses: [ + { + id: "model-term-definitions", + description: "Model specs need term definitions before defined and ready.", + authoredPaths: ["model.terms"], + }, + ], + }, +} as const satisfies Record; diff --git a/src/validate/validators.ts b/src/validate/validators.ts new file mode 100644 index 0000000..e5c6690 --- /dev/null +++ b/src/validate/validators.ts @@ -0,0 +1,453 @@ +import type { AnchorId, PackId, SpecId } from "../ids.js"; +import type { Spec } from "../model/spec.js"; +import type { SpecReadiness } from "../model/descriptors.js"; +import type { AuthoredModel } from "./authored-model.js"; +import type { Finding, ValidationReport, ValidatorFamily } from "./contracts.js"; +import { readinessFloors, readinessKindOverlays } from "./readiness-floor.js"; + +const duplicateIdsValidatorId = "conformance/duplicate-ids"; +const danglingReferencesValidatorId = "conformance/dangling-references"; +const readinessFloorValidatorId = "honesty/readiness-floor"; +const authoredModelValidatorId = "conformance/authored-model"; + +const readinessOrder = [ + "idea", + "scoped", + "defined", + "ready", +] as const satisfies readonly SpecReadiness[]; + +type AuthoredId = SpecId | PackId | AnchorId; + +type ReadinessClauseId = + | "id" + | "title" + | "kind" + | "altitude" + | "intent.outcome-or-parent-relation" + | "intent.outcome" + | "at-least-one-relation" + | "rules-examples-or-constraints" + | "rules-and-or-examples" + | "constraint-targets-are-machine-readable" + | "no-blocking-open-questions" + | "constraint-machine-readable-target" + | "example-given-when-then" + | "model-term-definitions"; + +type OverlayKind = keyof typeof readinessKindOverlays; + +function createReport( + validatorId: string, + family: ValidatorFamily, + findings: readonly Finding[], +): ValidationReport { + return { + validatorId, + family, + findings, + }; +} + +function createFinding( + validatorId: string, + family: ValidatorFamily, + message: string, + subjectId?: string, + relatedId?: string, + path?: string, +): Finding { + return { + validatorId, + family, + severity: "error", + message, + subjectId, + relatedId, + path, + }; +} + +function collectAuthoredIds(model: AuthoredModel): readonly AuthoredId[] { + return [ + ...model.specs.map((entry) => entry.id), + ...model.packs.map((entry) => entry.id), + ...model.anchors.map((entry) => entry.id), + ]; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function hasNonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.trim().length > 0; +} + +function hasPresentValue(value: unknown): boolean { + if (value === undefined || value === null) { + return false; + } + + if (typeof value === "string") { + return value.trim().length > 0; + } + + if (Array.isArray(value)) { + return value.length > 0; + } + + if (isRecord(value)) { + return Object.keys(value).length > 0; + } + + return true; +} + +function hasIntentOutcome(spec: Spec): boolean { + return isRecord(spec.intent) && hasNonEmptyString(spec.intent.outcome); +} + +function hasParentRelation(spec: Spec): boolean { + return spec.relations?.some((relation) => relation.type === "refines") ?? false; +} + +function hasAtLeastOneRelation(spec: Spec): boolean { + return (spec.relations?.length ?? 0) > 0; +} + +function hasBehaviorRules(spec: Spec): boolean { + return isRecord(spec.behavior) && hasPresentValue(spec.behavior.rules); +} + +function hasBehaviorExamples(spec: Spec): boolean { + return isRecord(spec.behavior) && hasPresentValue(spec.behavior.examples); +} + +function hasBehaviorRulesOrExamples(spec: Spec): boolean { + return hasBehaviorRules(spec) || hasBehaviorExamples(spec); +} + +function hasRulesExamplesOrConstraints(spec: Spec): boolean { + return hasBehaviorRules(spec) || hasBehaviorExamples(spec) || hasPresentValue(spec.constraints); +} + +function hasConstraintTarget(value: unknown): boolean { + return isRecord(value) && hasPresentValue(value.target); +} + +function constraintsHaveMachineReadableTargets(spec: Spec): boolean { + if (spec.constraints === undefined) { + return true; + } + + const { constraints } = spec; + + if (Array.isArray(constraints)) { + return constraints.every((entry) => hasConstraintTarget(entry)); + } + + if (!isRecord(constraints)) { + return false; + } + + if (hasPresentValue(constraints.target)) { + return true; + } + + const entries = Object.values(constraints); + + if (entries.length === 0) { + return false; + } + + return entries.every((entry) => hasConstraintTarget(entry)); +} + +function isBlockingOpenQuestion(value: unknown): boolean { + if (value === undefined || value === null) { + return false; + } + + if (Array.isArray(value)) { + return value.some((entry) => isBlockingOpenQuestion(entry)); + } + + if (typeof value === "string") { + return value.trim().length > 0; + } + + if (typeof value === "boolean") { + return value; + } + + if (typeof value === "number") { + return true; + } + + if (!isRecord(value)) { + return true; + } + + if ("blocking" in value) { + return value.blocking === true; + } + + return Object.keys(value).length > 0; +} + +function hasNoBlockingOpenQuestions(spec: Spec): boolean { + const designOpenQuestions = isRecord(spec.design) ? spec.design.openQuestions : undefined; + const decisionOpenQuestions = isRecord(spec.decision) ? spec.decision.openQuestions : undefined; + + return ( + !isBlockingOpenQuestion(designOpenQuestions) && !isBlockingOpenQuestion(decisionOpenQuestions) + ); +} + +function hasStructuredExampleDetail(spec: Spec): boolean { + if (!isRecord(spec.behavior)) { + return false; + } + + return ( + hasPresentValue(spec.behavior.given) && + hasPresentValue(spec.behavior.when) && + hasPresentValue(spec.behavior.then) + ); +} + +function hasModelTermDefinitions(spec: Spec): boolean { + return isRecord(spec.model) && hasPresentValue(spec.model.terms); +} + +function evaluateClause(spec: Spec, clauseId: ReadinessClauseId): boolean { + switch (clauseId) { + case "id": + return hasNonEmptyString(spec.id); + case "title": + return hasNonEmptyString(spec.title); + case "kind": + return hasNonEmptyString(spec.kind); + case "altitude": + return hasNonEmptyString(spec.altitude); + case "intent.outcome-or-parent-relation": + return hasIntentOutcome(spec) || hasParentRelation(spec); + case "intent.outcome": + return hasIntentOutcome(spec); + case "at-least-one-relation": + return hasAtLeastOneRelation(spec); + case "rules-examples-or-constraints": + return hasRulesExamplesOrConstraints(spec); + case "rules-and-or-examples": + return hasBehaviorRulesOrExamples(spec); + case "constraint-targets-are-machine-readable": + return constraintsHaveMachineReadableTargets(spec); + case "no-blocking-open-questions": + return hasNoBlockingOpenQuestions(spec); + case "constraint-machine-readable-target": + return constraintsHaveMachineReadableTargets(spec); + case "example-given-when-then": + return hasStructuredExampleDetail(spec); + case "model-term-definitions": + return hasModelTermDefinitions(spec); + } +} + +function readinessIndex(readiness: SpecReadiness): number { + return readinessOrder.indexOf(readiness); +} + +function isOverlayKind(kind: Spec["kind"]): kind is OverlayKind { + return kind in readinessKindOverlays; +} + +function toSupportedReadinessClauseId(clauseId: string): ReadinessClauseId | undefined { + switch (clauseId) { + case "id": + case "title": + case "kind": + case "altitude": + case "intent.outcome-or-parent-relation": + case "intent.outcome": + case "at-least-one-relation": + case "rules-examples-or-constraints": + case "rules-and-or-examples": + case "constraint-targets-are-machine-readable": + case "no-blocking-open-questions": + case "constraint-machine-readable-target": + case "example-given-when-then": + case "model-term-definitions": + return clauseId; + default: + return undefined; + } +} + +function getRequiredReadinessClauseIds(spec: Spec): readonly ReadinessClauseId[] { + const clauseIds: ReadinessClauseId[] = []; + const seen = new Set(); + + for (let index = 0; index <= readinessIndex(spec.readiness); index += 1) { + const readiness = readinessOrder[index]; + + if (readiness === undefined) { + continue; + } + + for (const clause of readinessFloors[readiness].clauses) { + if ("deferredInSession1" in clause || clause.id === "defined-floor") { + continue; + } + + const supportedClauseId = toSupportedReadinessClauseId(clause.id); + + if (supportedClauseId === undefined) { + continue; + } + + if (seen.has(supportedClauseId)) { + continue; + } + + seen.add(supportedClauseId); + clauseIds.push(supportedClauseId); + } + } + + const overlay = isOverlayKind(spec.kind) ? readinessKindOverlays[spec.kind] : undefined; + + if ( + overlay !== undefined && + readinessIndex(spec.readiness) >= readinessIndex(overlay.appliesAtOrAbove) + ) { + for (const clause of overlay.clauses) { + const supportedClauseId = toSupportedReadinessClauseId(clause.id); + + if (supportedClauseId === undefined || seen.has(supportedClauseId)) { + continue; + } + + seen.add(supportedClauseId); + clauseIds.push(supportedClauseId); + } + } + + return clauseIds; +} + +export function validateDuplicateIds(model: AuthoredModel): ValidationReport { + const seen = new Set(); + const emitted = new Set(); + const findings: Finding[] = []; + + for (const id of collectAuthoredIds(model)) { + if (!seen.has(id)) { + seen.add(id); + continue; + } + + if (emitted.has(id)) { + continue; + } + + emitted.add(id); + findings.push( + createFinding(duplicateIdsValidatorId, "conformance", `Duplicate authored id "${id}".`, id), + ); + } + + return createReport(duplicateIdsValidatorId, "conformance", findings); +} + +export function validateDanglingReferences(model: AuthoredModel): ValidationReport { + const knownSpecIds = new Set(model.specs.map((entry) => entry.id)); + const findings: Finding[] = []; + + const appendMissingReference = (subjectId: string, targetId: SpecId, path: string): void => { + if (knownSpecIds.has(targetId)) { + return; + } + + findings.push( + createFinding( + danglingReferencesValidatorId, + "conformance", + `Authored reference from "${subjectId}" points to missing target "${targetId}" at "${path}".`, + subjectId, + targetId, + path, + ), + ); + }; + + for (const authoredSpec of model.specs) { + for (const [index, relation] of (authoredSpec.relations ?? []).entries()) { + appendMissingReference( + authoredSpec.id, + relation.target, + `relations[${String(index)}].target`, + ); + } + } + + for (const authoredPack of model.packs) { + for (const [index, targetId] of authoredPack.specs.entries()) { + appendMissingReference(authoredPack.id, targetId, `specs[${String(index)}]`); + } + + for (const [index, targetId] of (authoredPack.modelRefs ?? []).entries()) { + appendMissingReference(authoredPack.id, targetId, `modelRefs[${String(index)}]`); + } + } + + for (const anchor of model.anchors) { + if ("satisfies" in anchor) { + appendMissingReference(anchor.id, anchor.satisfies, "satisfies"); + continue; + } + + appendMissingReference(anchor.id, anchor.verifies, "verifies"); + } + + return createReport(danglingReferencesValidatorId, "conformance", findings); +} + +export function validateReadinessFloors(model: AuthoredModel): ValidationReport { + const findings: Finding[] = []; + + for (const authoredSpec of model.specs) { + for (const clauseId of getRequiredReadinessClauseIds(authoredSpec)) { + if (evaluateClause(authoredSpec, clauseId)) { + continue; + } + + findings.push( + createFinding( + readinessFloorValidatorId, + "honesty", + `Spec "${authoredSpec.id}" states readiness "${authoredSpec.readiness}" but does not satisfy authored clause "${clauseId}".`, + authoredSpec.id, + clauseId, + "readiness", + ), + ); + } + } + + return createReport(readinessFloorValidatorId, "honesty", findings); +} + +/** + * Pre-graph authored-layer validation only. This composes the tiny Session 1 authored-model checks and is not the + * Slice 3 graph validator gate. + */ +export function validateAuthoredModel(model: AuthoredModel): ValidationReport { + const findings = [ + ...validateDuplicateIds(model).findings, + ...validateDanglingReferences(model).findings, + ...validateReadinessFloors(model).findings, + ]; + + return createReport(authoredModelValidatorId, "conformance", findings); +} diff --git a/test/bootstrap.test.ts b/test/bootstrap.test.ts new file mode 100644 index 0000000..40a7f79 --- /dev/null +++ b/test/bootstrap.test.ts @@ -0,0 +1,32 @@ +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +import { describe, expect, it } from "vitest"; + +import * as protocol from "@libar-dev/software-delivery-protocol"; + +describe("bootstrap package surface", () => { + it("resolves the package name through the Vitest alias", () => { + expect(protocol).toBeDefined(); + expect(typeof protocol).toBe("object"); + }); + + it("keeps the expected package.json bootstrap shape", async () => { + const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url)); + const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) as { + type: string; + bin: { sdp: string }; + exports: Record; + }; + const rootExport = packageJson.exports["."]; + + if (rootExport === undefined) { + throw new Error("Missing root export in package.json"); + } + + expect(packageJson.type).toBe("module"); + expect(packageJson.bin.sdp).toBe("./dist/cli/sdp.js"); + expect(rootExport.types).toBe("./dist/index.d.ts"); + expect(rootExport.import).toBe("./dist/index.js"); + }); +}); diff --git a/test/bootstrap.typecheck.ts b/test/bootstrap.typecheck.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/test/bootstrap.typecheck.ts @@ -0,0 +1 @@ +export {}; diff --git a/test/builders.test.ts b/test/builders.test.ts new file mode 100644 index 0000000..f427bd3 --- /dev/null +++ b/test/builders.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, it } from "vitest"; + +import { + anchorImplementation, + constrainedBy, + decidedBy, + dependsOn, + implAnchorId, + pack, + packId, + ref, + refines, + spec, + specId, + specTest, + supersedes, + testAnchorId, + verifies, +} from "../src/index.js"; + +describe("builders", () => { + it("builds plain serializable specs with declared relations", () => { + const parent = ref("spec:orders.order-management"); + const dependency = ref("spec:payments.authorize-payment"); + const constraint = ref("spec:checkout.performance-budget"); + const decision = ref("spec:decisions.order-lifecycle"); + const verificationTarget = ref("spec:orders.create-order"); + const supersededDecision = ref("spec:decisions.order-lifecycle-v0"); + + const createOrder = spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "defined", + intent: { + actor: "customer", + outcome: "turn a valid cart into an order", + value: "customers can complete purchases", + }, + relations: [ + refines(parent), + dependsOn(dependency), + constrainedBy(constraint), + decidedBy(decision), + verifies(verificationTarget), + supersedes(supersededDecision), + ], + }); + + expect(Object.getPrototypeOf(createOrder)).toBe(Object.prototype); + expect(createOrder.relations).toEqual([ + { type: "refines", target: parent, claim: "declared" }, + { type: "dependsOn", target: dependency, claim: "declared" }, + { type: "constrainedBy", target: constraint, claim: "declared" }, + { type: "decidedBy", target: decision, claim: "declared" }, + { type: "verifies", target: verificationTarget, claim: "declared" }, + { type: "supersedes", target: supersededDecision, claim: "declared" }, + ]); + expect(JSON.parse(JSON.stringify(createOrder))).toEqual({ + id: "spec:orders.create-order", + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "defined", + intent: { + actor: "customer", + outcome: "turn a valid cart into an order", + value: "customers can complete purchases", + }, + relations: [ + { type: "refines", target: "spec:orders.order-management", claim: "declared" }, + { + type: "dependsOn", + target: "spec:payments.authorize-payment", + claim: "declared", + }, + { + type: "constrainedBy", + target: "spec:checkout.performance-budget", + claim: "declared", + }, + { + type: "decidedBy", + target: "spec:decisions.order-lifecycle", + claim: "declared", + }, + { type: "verifies", target: "spec:orders.create-order", claim: "declared" }, + { + type: "supersedes", + target: "spec:decisions.order-lifecycle-v0", + claim: "declared", + }, + ], + }); + }); + + it("builds packs as plain manifests with no authored truth fields", () => { + const source = { + id: packId("pack:checkout-v1"), + title: "Checkout v1", + framing: "let customers complete purchases reliably", + modelRefs: [ref("spec:checkout.glossary")], + specs: [ref("spec:orders.create-order"), ref("spec:payments.authorize-payment")], + }; + + const checkout = pack(source); + + expect(Object.getPrototypeOf(checkout)).toBe(Object.prototype); + expect(checkout).toEqual(source); + expect(checkout.specs).not.toBe(source.specs); + expect(checkout.modelRefs).not.toBe(source.modelRefs); + expect(JSON.parse(JSON.stringify(checkout))).toEqual({ + id: "pack:checkout-v1", + title: "Checkout v1", + framing: "let customers complete purchases reliably", + modelRefs: ["spec:checkout.glossary"], + specs: ["spec:orders.create-order", "spec:payments.authorize-payment"], + }); + }); + + it("builds identity-only anchors as plain serializable objects", () => { + const implementationSource = { + id: implAnchorId("impl:orders.create-order-use-case"), + label: "CreateOrderUseCase", + satisfies: ref("spec:orders.create-order"), + }; + + const implementation = anchorImplementation(implementationSource); + const testBinding = specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + label: "valid cart creates order", + verifies: ref("spec:orders.create-order.valid-cart"), + }); + + expect(Object.getPrototypeOf(implementation)).toBe(Object.prototype); + expect(Object.getPrototypeOf(testBinding)).toBe(Object.prototype); + expect(implementation).toEqual(implementationSource); + expect(testBinding).toEqual({ + id: "test:orders.create-order.valid-cart", + label: "valid cart creates order", + verifies: "spec:orders.create-order.valid-cart", + }); + expect(JSON.parse(JSON.stringify({ implementation, testBinding }))).toEqual({ + implementation: { + id: "impl:orders.create-order-use-case", + label: "CreateOrderUseCase", + satisfies: "spec:orders.create-order", + }, + testBinding: { + id: "test:orders.create-order.valid-cart", + label: "valid cart creates order", + verifies: "spec:orders.create-order.valid-cart", + }, + }); + }); +}); diff --git a/test/builders.typecheck.ts b/test/builders.typecheck.ts new file mode 100644 index 0000000..894e75a --- /dev/null +++ b/test/builders.typecheck.ts @@ -0,0 +1,75 @@ +import { + anchorImplementation, + implAnchorId, + pack, + packId, + ref, + spec, + specId, + specTest, + testAnchorId, + verifies, +} from "../src/index.js"; + +const authoredSpec = spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "idea", +}); + +const authoredPack = pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [ref("spec:orders.create-order")], +}); + +const implementationAnchor = anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + label: "CreateOrderUseCase", + satisfies: ref("spec:orders.create-order"), +}); + +const testAnchor = specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + label: "valid cart creates order", + verifies: ref("spec:orders.create-order.valid-cart"), +}); + +void [authoredSpec, authoredPack, implementationAnchor, testAnchor]; + +anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + satisfies: ref("spec:orders.create-order"), + // @ts-expect-error anchors are identity-only and do not carry readiness + readiness: "ready", +}); + +anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + satisfies: ref("spec:orders.create-order"), + // @ts-expect-error anchors do not accept spec sections such as intent + intent: { outcome: "turn a valid cart into an order" }, +}); + +anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + satisfies: ref("spec:orders.create-order"), + // @ts-expect-error anchors do not author delivery facts + implemented: true, +}); + +specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + verifies: ref("spec:orders.create-order.valid-cart"), + // @ts-expect-error test anchors do not accept authored relations + relations: [verifies(specId("spec:orders.create-order"))], +}); + +specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + verifies: ref("spec:orders.create-order.valid-cart"), + // @ts-expect-error test anchors do not accept delivery fact badges + "has-verifier": true, +}); diff --git a/test/checkout-v1.test.ts b/test/checkout-v1.test.ts new file mode 100644 index 0000000..32104df --- /dev/null +++ b/test/checkout-v1.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; + +import { checkoutV1Model } from "../examples/checkout-v1/model.js"; +import { validateAuthoredModel } from "../src/index.js"; + +describe("checkout-v1 tracer bullet", () => { + it("assembles a valid authored model with zero error findings", () => { + expect(validateAuthoredModel(checkoutV1Model).findings).toEqual([]); + }); + + it("keeps every authored relation target inside the checkout-v1 example spec set", () => { + const exampleSpecIds = new Set(checkoutV1Model.specs.map((entry) => entry.id)); + + for (const authoredSpec of checkoutV1Model.specs) { + for (const relation of authoredSpec.relations ?? []) { + expect(exampleSpecIds.has(relation.target)).toBe(true); + } + } + + for (const authoredPack of checkoutV1Model.packs) { + for (const specTarget of authoredPack.specs) { + expect(exampleSpecIds.has(specTarget)).toBe(true); + } + + for (const modelRef of authoredPack.modelRefs ?? []) { + expect(exampleSpecIds.has(modelRef)).toBe(true); + } + } + + for (const anchor of checkoutV1Model.anchors) { + const target = "satisfies" in anchor ? anchor.satisfies : anchor.verifies; + expect(exampleSpecIds.has(target)).toBe(true); + } + }); + + it("includes the pack, implementation anchor, and spec-linked test anchor in the assembled model", () => { + expect(checkoutV1Model.packs.map((entry) => entry.id)).toEqual(["pack:checkout-v1"]); + expect(checkoutV1Model.anchors.map((entry) => entry.id)).toEqual([ + "impl:orders.create-order-use-case", + "test:orders.create-order.valid-cart", + ]); + }); +}); diff --git a/test/cli.test.ts b/test/cli.test.ts new file mode 100644 index 0000000..358311b --- /dev/null +++ b/test/cli.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; + +import { SDP_HELP_TEXT, runSdpCli } from "../src/cli/sdp.js"; + +function createCaptureOutput() { + const stdoutChunks: string[] = []; + const stderrChunks: string[] = []; + + return { + output: { + stdout: { + write(chunk: string) { + stdoutChunks.push(chunk); + }, + }, + stderr: { + write(chunk: string) { + stderrChunks.push(chunk); + }, + }, + }, + readStdout() { + return stdoutChunks.join(""); + }, + readStderr() { + return stderrChunks.join(""); + }, + }; +} + +describe("sdp cli", () => { + it("prints the exact help text for no args", () => { + const capture = createCaptureOutput(); + + const exitCode = runSdpCli([], capture.output); + + expect(exitCode).toBe(0); + expect(capture.readStdout()).toBe(`${SDP_HELP_TEXT}\n`); + expect(capture.readStderr()).toBe(""); + }); + + it("prints the exact help text for --help", () => { + const capture = createCaptureOutput(); + + const exitCode = runSdpCli(["--help"], capture.output); + + expect(exitCode).toBe(0); + expect(capture.readStdout()).toBe(`${SDP_HELP_TEXT}\n`); + expect(capture.readStderr()).toBe(""); + }); + + it("rejects build as not implemented", () => { + const capture = createCaptureOutput(); + + const exitCode = runSdpCli(["build"], capture.output); + + expect(exitCode).toBe(1); + expect(capture.readStdout()).toBe(""); + expect(capture.readStderr()).toBe("sdp build is not implemented yet (Slice 1: extractor).\n"); + }); + + it("rejects validate as not wired", () => { + const capture = createCaptureOutput(); + + const exitCode = runSdpCli(["validate"], capture.output); + + expect(exitCode).toBe(1); + expect(capture.readStdout()).toBe(""); + expect(capture.readStderr()).toBe( + "sdp validate gate is not wired yet (Slice 3: graph validator gate).\n", + ); + }); + + it("prints help plus an unknown-command error", () => { + const capture = createCaptureOutput(); + + const exitCode = runSdpCli(["bogus"], capture.output); + + expect(exitCode).toBe(1); + expect(capture.readStdout()).toBe(""); + expect(capture.readStderr()).toBe(`${SDP_HELP_TEXT}\n\nUnknown command: bogus\n`); + }); +}); diff --git a/test/descriptors.test.ts b/test/descriptors.test.ts new file mode 100644 index 0000000..911a77c --- /dev/null +++ b/test/descriptors.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; + +import { + SPEC_ALTITUDES, + SPEC_KIND_DISPLAY_LABELS, + SPEC_KINDS, + SPEC_READINESS, +} from "../src/model/descriptors.js"; +import { SPEC_SECTION_NAMES } from "../src/model/sections.js"; + +describe("descriptors", () => { + it("exports the canonical kind literals", () => { + expect(SPEC_KINDS).toEqual([ + "behavior", + "workflow", + "example", + "rule", + "constraint", + "model", + "decision", + "contract", + ]); + }); + + it("exports the canonical altitude literals", () => { + expect(SPEC_ALTITUDES).toEqual(["epic", "feature", "story"]); + }); + + it("exports the canonical readiness literals", () => { + expect(SPEC_READINESS).toEqual(["idea", "scoped", "defined", "ready"]); + }); + + it("exports the canonical kind display labels", () => { + expect(SPEC_KIND_DISPLAY_LABELS).toEqual({ + behavior: "Use Case / Behavior", + workflow: "Workflow", + example: "Example / Scenario", + rule: "Business Rule", + constraint: "Constraint (NFR)", + model: "Domain Model", + decision: "Decision Record", + contract: "Contract", + }); + }); + + it("exports the optional section names", () => { + expect(SPEC_SECTION_NAMES).toEqual([ + "intent", + "behavior", + "constraints", + "model", + "design", + "decision", + "verification", + "ui", + ]); + }); +}); diff --git a/test/descriptors.typecheck.ts b/test/descriptors.typecheck.ts new file mode 100644 index 0000000..64906c2 --- /dev/null +++ b/test/descriptors.typecheck.ts @@ -0,0 +1,21 @@ +import type { SpecAltitude, SpecKind, SpecReadiness } from "../src/model/descriptors.js"; +import type { SpecSections } from "../src/model/sections.js"; + +interface MinimalIdeaSpecFixture { + readonly id: string; + readonly title: string; + readonly kind: SpecKind; + readonly altitude: SpecAltitude; + readonly readiness: SpecReadiness; + readonly sections?: SpecSections; +} + +const minimalIdeaSpec = { + id: "spec:orders.create-order", + title: "Create order", + kind: "behavior", + altitude: "story", + readiness: "idea", +} satisfies MinimalIdeaSpecFixture; + +void minimalIdeaSpec; diff --git a/test/graph-schema.test.ts b/test/graph-schema.test.ts new file mode 100644 index 0000000..e5950a5 --- /dev/null +++ b/test/graph-schema.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "vitest"; + +import { + deliveryFactNames, + derivedEdgeTypes, + graphEdgeTypes, + graphNodeTypes, + schemaVersion, +} from "../src/index.js"; + +describe("graph schema", () => { + it("exports the inert graph schema contracts", () => { + expect(schemaVersion).toBe("0.1.0"); + expect(graphNodeTypes).toEqual(["Primitive", "Pack", "Anchor", "CodeNode"]); + expect(deliveryFactNames).toEqual(["implemented", "has-verifier", "observed"]); + expect(derivedEdgeTypes).toEqual(["belongsTo", "satisfies"]); + expect(graphEdgeTypes).toEqual([ + "refines", + "dependsOn", + "constrainedBy", + "decidedBy", + "verifies", + "supersedes", + "belongsTo", + "satisfies", + ]); + }); +}); diff --git a/test/graph-schema.typecheck.ts b/test/graph-schema.typecheck.ts new file mode 100644 index 0000000..7a2de61 --- /dev/null +++ b/test/graph-schema.typecheck.ts @@ -0,0 +1,29 @@ +import type { + DeliveryFactName, + GraphEdge, + GraphEdgeType, + GraphNode, + PrimitiveNode, +} from "../src/index.js"; + +const primitiveNode = { + id: "spec:orders.create-order", + nodeType: "Primitive", + claim: "declared", + specKind: "behavior", + altitude: "feature", + readiness: "ready", + deliveryFacts: ["implemented", "has-verifier"] as const satisfies readonly DeliveryFactName[], +} satisfies PrimitiveNode; + +const graphNode = primitiveNode satisfies GraphNode; + +const graphEdge = { + from: "impl:orders.create-order-use-case", + type: "satisfies" satisfies GraphEdgeType, + to: "spec:orders.create-order", + claim: "anchored", +} satisfies GraphEdge; + +void graphNode; +void graphEdge; diff --git a/test/ids.test.ts b/test/ids.test.ts new file mode 100644 index 0000000..ce260ed --- /dev/null +++ b/test/ids.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from "vitest"; + +import { + anchorId, + formatId, + implAnchorId, + packId, + parseId, + ref, + specId, + testAnchorId, +} from "../src/index.js"; + +const validIds = [ + "spec:orders.create-order", + "spec:orders.create-order#valid-cart", + "pack:checkout-v1", + "impl:orders.create-order-use-case", + "test:orders.create-order.valid-cart", +] as const; + +const invalidIds = [ + "orders.create-order", + "spec:orders..create-order", + "spec:orders.create order", + "Spec:orders.create-order", + "spec:orders.create-order#", + "spec:orders.create-order#valid.cart", +] as const; + +describe("ids", () => { + it.each(validIds)("round-trips %s", (value) => { + const parsed = parseId(value); + + expect(formatId(parsed)).toBe(value); + }); + + it("preserves the branded spec reference string", () => { + const id = "spec:orders.create-order.valid-cart"; + + expect(ref(id)).toBe(id); + expect(specId(id)).toBe(id); + }); + + it("brands the required helper namespaces", () => { + expect(packId("pack:checkout-v1")).toBe("pack:checkout-v1"); + expect(implAnchorId("impl:orders.create-order-use-case")).toBe( + "impl:orders.create-order-use-case", + ); + expect(testAnchorId("test:orders.create-order.valid-cart")).toBe( + "test:orders.create-order.valid-cart", + ); + expect(anchorId("api:orders.post")).toBe("api:orders.post"); + }); + + it.each(invalidIds)("rejects malformed IDs: %s", (value) => { + expect(() => parseId(value)).toThrowError(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(() => implAnchorId("test:orders.create-order.valid-cart")).toThrowError( + 'expected namespace "impl"', + ); + expect(() => testAnchorId("impl:orders.create-order-use-case")).toThrowError( + 'expected namespace "test"', + ); + }); +}); diff --git a/test/ids.typecheck.ts b/test/ids.typecheck.ts new file mode 100644 index 0000000..d310002 --- /dev/null +++ b/test/ids.typecheck.ts @@ -0,0 +1,22 @@ +import { + type AnchorId, + type ImplAnchorId, + type PackId, + type SpecId, + type TestAnchorId, + anchorId, + implAnchorId, + packId, + ref, + specId, + testAnchorId, +} from "../src/index.js"; + +const spec: SpecId = ref("spec:orders.create-order.valid-cart"); +const alsoSpec: SpecId = specId("spec:orders.create-order"); +const pack: PackId = packId("pack:checkout-v1"); +const impl: ImplAnchorId = implAnchorId("impl:orders.create-order-use-case"); +const test: TestAnchorId = testAnchorId("test:orders.create-order.valid-cart"); +const anchor: AnchorId = anchorId("api:orders.post"); + +void [spec, alsoSpec, pack, impl, test, anchor]; diff --git a/test/readiness.test.ts b/test/readiness.test.ts new file mode 100644 index 0000000..4f8c0de --- /dev/null +++ b/test/readiness.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it } from "vitest"; + +import { + readinessFloors, + readinessKindOverlays, + validationSeverities, + validatorFamilies, +} from "../src/index.js"; + +describe("readiness and validation contracts", () => { + it("exports the canonical validator families and severities", () => { + expect(validatorFamilies).toEqual(["conformance", "honesty"]); + expect(validationSeverities).toEqual(["error", "warning"]); + }); + + it("defines all canonical readiness floors as inert data", () => { + expect(Object.keys(readinessFloors)).toEqual(["idea", "scoped", "defined", "ready"]); + + expect(readinessFloors.idea.clauses.map((clause) => clause.id)).toEqual([ + "id", + "title", + "kind", + "altitude", + "intent.outcome-or-parent-relation", + ]); + + expect(readinessFloors.scoped.clauses.map((clause) => clause.id)).toEqual([ + "intent.outcome", + "at-least-one-relation", + "rules-examples-or-constraints", + ]); + + expect(readinessFloors.defined.clauses.map((clause) => clause.id)).toEqual([ + "rules-and-or-examples", + "constraint-targets-are-machine-readable", + "no-blocking-open-questions", + ]); + + expect(readinessFloors.ready.clauses.map((clause) => clause.id)).toEqual([ + "defined-floor", + "no-blocking-open-questions", + "all-relations-resolve", + "depends-on-and-refines-targets-are-defined", + "anchors-resolve", + ]); + }); + + it("marks graph-shaped ready clauses as deferred in Session 1", () => { + const deferredClauses = readinessFloors.ready.clauses + .filter( + ( + clause, + ): clause is Extract< + (typeof readinessFloors.ready.clauses)[number], + { deferredInSession1: true } + > => "deferredInSession1" in clause, + ) + .map((clause) => clause.id); + + expect(deferredClauses).toEqual([ + "all-relations-resolve", + "depends-on-and-refines-targets-are-defined", + "anchors-resolve", + ]); + }); + + it("defines kind-aware overlays for constraint, example, and model", () => { + expect(Object.keys(readinessKindOverlays)).toEqual(["constraint", "example", "model"]); + + expect(readinessKindOverlays.constraint).toEqual({ + kind: "constraint", + appliesAtOrAbove: "defined", + clauses: [ + { + id: "constraint-machine-readable-target", + description: + "Constraint specs need a parseable, machine-readable target before defined and ready.", + authoredPaths: ["constraints.target"], + }, + ], + }); + + expect(readinessKindOverlays.example).toEqual({ + kind: "example", + appliesAtOrAbove: "defined", + clauses: [ + { + id: "example-given-when-then", + description: + "Example specs need structured given, when, and then detail before defined and ready.", + authoredPaths: ["behavior.given", "behavior.when", "behavior.then"], + }, + ], + }); + + expect(readinessKindOverlays.model).toEqual({ + kind: "model", + appliesAtOrAbove: "defined", + clauses: [ + { + id: "model-term-definitions", + description: "Model specs need term definitions before defined and ready.", + authoredPaths: ["model.terms"], + }, + ], + }); + }); +}); diff --git a/test/readiness.typecheck.ts b/test/readiness.typecheck.ts new file mode 100644 index 0000000..f952803 --- /dev/null +++ b/test/readiness.typecheck.ts @@ -0,0 +1,84 @@ +import { + anchorImplementation, + authoredEdgeTypes, + implAnchorId, + pack, + packId, + ref, + spec, + specId, +} from "../src/index.js"; +import type { AuthoredModel, Finding, ValidationReport, Validator } from "../src/index.js"; + +const model = { + specs: [ + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + }), + ], + packs: [ + pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [ref("spec:orders.create-order")], + }), + ], + anchors: [ + anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + satisfies: ref("spec:orders.create-order"), + }), + ], +} satisfies AuthoredModel; + +const finding = { + validatorId: "honesty/readiness-floor", + family: "honesty", + severity: "error", + message: "readiness floor is not satisfied", + subjectId: model.specs[0]?.id, + relatedId: authoredEdgeTypes[0], + path: "readiness", +} satisfies Finding; + +const report = { + validatorId: "honesty/readiness-floor", + family: "honesty", + findings: [finding], +} satisfies ValidationReport; + +const validator: Validator = { + id: "honesty/readiness-floor", + family: "honesty", + validate(input) { + void input; + return report; + }, +}; + +void [model, finding, report, validator]; + +const invalidAuthoredModel: AuthoredModel = { + specs: [], + packs: [], + anchors: [], + // @ts-expect-error Session 1 authored models stay in-memory only. + sourceFiles: [], +}; + +void invalidAuthoredModel; + +const invalidFinding: Finding = { + validatorId: "honesty/readiness-floor", + family: "honesty", + severity: "error", + // @ts-expect-error findings need a stable message field. + detail: "missing stable message field", + message: "missing stable message field", +}; + +void invalidFinding; diff --git a/test/validators.test.ts b/test/validators.test.ts new file mode 100644 index 0000000..9d03ca5 --- /dev/null +++ b/test/validators.test.ts @@ -0,0 +1,268 @@ +import { describe, expect, it } from "vitest"; + +import { + anchorImplementation, + dependsOn, + implAnchorId, + pack, + packId, + ref, + spec, + specId, + specTest, + testAnchorId, + validateAuthoredModel, + validateDanglingReferences, + validateDuplicateIds, + validateReadinessFloors, +} from "../src/index.js"; + +import type { AuthoredModel, Spec } from "../src/index.js"; + +function createBehaviorSpec( + id: ReturnType, + readiness: Spec["readiness"], + overrides: Partial = {}, +): Spec { + return spec({ + id, + title: `Title for ${id}`, + kind: "behavior", + altitude: "feature", + readiness, + ...overrides, + }); +} + +describe("validators", () => { + it("reports duplicate authored ids with the exact duplicated id", () => { + const duplicateId = specId("spec:orders.create-order"); + const report = validateDuplicateIds({ + specs: [ + spec({ + id: duplicateId, + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + }), + spec({ + id: duplicateId, + title: "Create order duplicate", + kind: "behavior", + altitude: "feature", + readiness: "idea", + }), + ], + packs: [], + anchors: [], + }); + + expect(report.validatorId).toBe("conformance/duplicate-ids"); + expect(report.family).toBe("conformance"); + expect(report.findings).toHaveLength(1); + expect(report.findings[0]).toMatchObject({ + validatorId: "conformance/duplicate-ids", + family: "conformance", + severity: "error", + subjectId: duplicateId, + message: 'Duplicate authored id "spec:orders.create-order".', + }); + }); + + it("reports dangling authored references across relations, packs, and anchors", () => { + const existingSpecId = specId("spec:orders.order-management"); + const relationTarget = specId("spec:orders.missing-target"); + const packSpecTarget = specId("spec:orders.missing-pack-member"); + const packModelTarget = specId("spec:checkout.missing-glossary"); + const anchorTarget = specId("spec:orders.missing-anchor-target"); + const testTarget = specId("spec:orders.missing-test-target"); + + const report = validateDanglingReferences({ + specs: [ + createBehaviorSpec(specId("spec:orders.create-order"), "scoped", { + intent: { outcome: "turn a cart into an order" }, + behavior: { examples: ["valid cart"] }, + relations: [dependsOn(relationTarget)], + }), + createBehaviorSpec(existingSpecId, "idea", { + intent: { outcome: "define order management" }, + }), + ], + packs: [ + pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [ref(existingSpecId), packSpecTarget], + modelRefs: [packModelTarget], + }), + ], + anchors: [ + anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + satisfies: anchorTarget, + }), + specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + verifies: testTarget, + }), + ], + }); + + expect(report.validatorId).toBe("conformance/dangling-references"); + expect(report.family).toBe("conformance"); + expect(report.findings).toEqual([ + { + validatorId: "conformance/dangling-references", + family: "conformance", + severity: "error", + subjectId: "spec:orders.create-order", + relatedId: "spec:orders.missing-target", + path: "relations[0].target", + message: + 'Authored reference from "spec:orders.create-order" points to missing target "spec:orders.missing-target" at "relations[0].target".', + }, + { + validatorId: "conformance/dangling-references", + family: "conformance", + severity: "error", + subjectId: "pack:checkout-v1", + relatedId: "spec:orders.missing-pack-member", + path: "specs[1]", + message: + 'Authored reference from "pack:checkout-v1" points to missing target "spec:orders.missing-pack-member" at "specs[1]".', + }, + { + validatorId: "conformance/dangling-references", + family: "conformance", + severity: "error", + subjectId: "pack:checkout-v1", + relatedId: "spec:checkout.missing-glossary", + path: "modelRefs[0]", + message: + 'Authored reference from "pack:checkout-v1" points to missing target "spec:checkout.missing-glossary" at "modelRefs[0]".', + }, + { + validatorId: "conformance/dangling-references", + family: "conformance", + severity: "error", + subjectId: "impl:orders.create-order-use-case", + relatedId: "spec:orders.missing-anchor-target", + path: "satisfies", + message: + 'Authored reference from "impl:orders.create-order-use-case" points to missing target "spec:orders.missing-anchor-target" at "satisfies".', + }, + { + validatorId: "conformance/dangling-references", + family: "conformance", + severity: "error", + subjectId: "test:orders.create-order.valid-cart", + relatedId: "spec:orders.missing-test-target", + path: "verifies", + message: + 'Authored reference from "test:orders.create-order.valid-cart" points to missing target "spec:orders.missing-test-target" at "verifies".', + }, + ]); + }); + + it("reports a single-spec ready floor failure with the spec id and stated readiness", () => { + const report = validateReadinessFloors({ + specs: [ + createBehaviorSpec(specId("spec:orders.create-order"), "ready", { + intent: { outcome: "turn a valid cart into an order" }, + relations: [dependsOn(ref("spec:orders.order-management"))], + constraints: { + latencyBudget: { + target: "p95 < 200ms", + }, + }, + }), + ], + packs: [], + anchors: [], + }); + + expect(report.validatorId).toBe("honesty/readiness-floor"); + expect(report.family).toBe("honesty"); + expect(report.findings).toEqual([ + { + validatorId: "honesty/readiness-floor", + family: "honesty", + severity: "error", + subjectId: "spec:orders.create-order", + relatedId: "rules-and-or-examples", + path: "readiness", + message: + 'Spec "spec:orders.create-order" states readiness "ready" but does not satisfy authored clause "rules-and-or-examples".', + }, + ]); + }); + + it("ignores Session 1 deferred ready checks for target readiness and anchor resolution", () => { + const report = validateReadinessFloors({ + specs: [ + createBehaviorSpec(specId("spec:orders.create-order"), "ready", { + intent: { outcome: "turn a valid cart into an order" }, + behavior: { rules: ["persist the order"] }, + relations: [dependsOn(ref("spec:orders.undefined-dependency"))], + }), + ], + packs: [], + anchors: [ + anchorImplementation({ + id: implAnchorId("impl:orders.undefined-anchor"), + satisfies: ref("spec:orders.undefined-anchor-target"), + }), + ], + }); + + expect(report.findings).toEqual([]); + }); + + it("returns a valid empty authored-model report without throwing", () => { + const report = validateAuthoredModel({ + specs: [], + packs: [], + anchors: [], + }); + + expect(report.validatorId).toBe("conformance/authored-model"); + expect(report.family).toBe("conformance"); + expect(report.findings).toEqual([]); + }); + + it("composes the authored-layer validators for a valid non-empty model", () => { + const orderManagement = createBehaviorSpec(specId("spec:orders.order-management"), "idea", { + intent: { outcome: "define order management" }, + }); + const createOrder = createBehaviorSpec(specId("spec:orders.create-order"), "ready", { + intent: { outcome: "turn a valid cart into an order" }, + behavior: { rules: ["persist the order"] }, + relations: [dependsOn(orderManagement.id)], + }); + + const model: AuthoredModel = { + specs: [orderManagement, createOrder], + packs: [ + pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [orderManagement.id, createOrder.id], + modelRefs: [orderManagement.id], + }), + ], + anchors: [ + anchorImplementation({ + id: implAnchorId("impl:orders.create-order-use-case"), + satisfies: createOrder.id, + }), + specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + verifies: createOrder.id, + }), + ], + }; + + expect(validateAuthoredModel(model).findings).toEqual([]); + }); +}); diff --git a/tsconfig.examples.json b/tsconfig.examples.json new file mode 100644 index 0000000..52db1ff --- /dev/null +++ b/tsconfig.examples.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@libar-dev/software-delivery-protocol": ["src/index.ts"] + } + }, + "exclude": [], + "include": ["examples/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9ae8b1f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "strict": true, + "isolatedModules": true, + "noUncheckedIndexedAccess": true, + "verbatimModuleSyntax": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "types": ["node"], + "noEmit": true + }, + "include": ["src/**/*.ts", "test/**/*.ts", "tsup.config.ts", "vitest.config.ts"], + "exclude": ["dist", "generated", "examples"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..c50420a --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,55 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import type { Plugin } from "esbuild"; +import { defineConfig } from "tsup"; + +const cliShebangStripper = { + name: "strip-cli-shebang", + setup(build: Parameters[0]) { + build.onResolve({ filter: /[\\/]src[\\/]cli[\\/]sdp\.ts$/ }, (args) => ({ + path: args.path, + namespace: "cli-shebang", + })); + + build.onLoad({ filter: /.*/, namespace: "cli-shebang" }, async (args) => { + const contents = await readFile(args.path, "utf8"); + + return { + contents: contents.replace(/^#!.*(?:\r?\n)?/, ""), + loader: "ts", + }; + }); + }, +}; + +const cliOutputPath = fileURLToPath(new URL("./dist/cli/sdp.js", import.meta.url)); + +async function normalizeCliOutputShebang() { + const contents = await readFile(cliOutputPath, "utf8"); + const duplicateShebang = "#!/usr/bin/env node\n#!/usr/bin/env node\n"; + const normalized = contents.startsWith(duplicateShebang) + ? contents.slice("#!/usr/bin/env node\n".length) + : contents; + + if (normalized !== contents) { + await writeFile(cliOutputPath, normalized, "utf8"); + } +} + +export default defineConfig({ + entry: ["src/index.ts", "src/cli/sdp.ts"], + format: ["esm"], + dts: true, + platform: "node", + target: "es2022", + clean: true, + splitting: false, + outDir: "dist", + esbuildOptions(options) { + options.plugins = [...(options.plugins ?? []), cliShebangStripper]; + }, + banner: { + js: "#!/usr/bin/env node", + }, + onSuccess: normalizeCliOutputShebang, +}); diff --git a/vitest-test.mjs b/vitest-test.mjs new file mode 100644 index 0000000..f365830 --- /dev/null +++ b/vitest-test.mjs @@ -0,0 +1,14 @@ +import { spawnSync } from "node:child_process"; + +const argv = process.argv.slice(2); +const vitestArgs = argv.includes("--run") ? argv : ["--run", ...argv]; + +const result = spawnSync("vitest", vitestArgs, { + stdio: "inherit", +}); + +if (result.error !== undefined) { + throw result.error; +} + +process.exit(result.status ?? 1); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..80ca24e --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,17 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +const packageAliasTarget = fileURLToPath(new URL("./src/index.ts", import.meta.url)); + +export default defineConfig({ + resolve: { + alias: { + "@libar-dev/software-delivery-protocol": packageAliasTarget, + }, + }, + test: { + environment: "node", + globals: true, + include: ["test/**/*.test.ts"], + }, +}); From 6136fb4230d73495f1bc9cf2625f8cc9989d1180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 14:27:52 +0200 Subject: [PATCH 03/29] Produce initial version of the hardening plan after first implementation session --- .gitignore | 8 +- plans/01-session-1-bootstrap-phase0.md | 195 +++++++++++++++++++++ plans/02-phase0-hardening.md | 234 +++++++++++++++++++++++++ 3 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 plans/01-session-1-bootstrap-phase0.md create mode 100644 plans/02-phase0-hardening.md diff --git a/.gitignore b/.gitignore index 2f15136..0a80ffd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ # Local planning artifacts — kept on disk for working notes, out of the published repo -plans/ +# plans/ + +# User's scratchpad +.tmp-scratch/ # Tooling outputs node_modules/ dist/ generated/ .sisyphus/evidence/ + +# OmO plans and state +.sisyphus/ \ No newline at end of file diff --git a/plans/01-session-1-bootstrap-phase0.md b/plans/01-session-1-bootstrap-phase0.md new file mode 100644 index 0000000..7611e99 --- /dev/null +++ b/plans/01-session-1-bootstrap-phase0.md @@ -0,0 +1,195 @@ +# Session 1 — Bootstrap + Phase 0 (the protocol as typed code) + +> **Status: SCOPED · awaiting detailed implementation plan.** This document fixes the *scope and shape* of the +> very first setup/implementation session. A fully detailed implementation plan (file-by-file contents) will be +> authored next and reviewed against this scope before execution. +> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state at planning time:** docs-only; npm not yet +> initialized. + +This is the first *code* session of the MVP. It implements **Slice 0** of the roadmap (`docs/concept/07` §1) — +*Phase 0, the protocol as code* — plus the repo/toolchain bootstrap that must precede any code, and it authors the +running example against the new DSL as a usability tracer bullet. It deliberately **stops before the `ts-morph` +extractor** (Slice 1). + +--- + +## §0 — How this fits + +The roadmap (`07` §1) sequences the build as Slices 0→5. Slice 0 is explicitly *the foundation, not a detour*: the +extractor, the graph schema, and every validator presuppose the typed meta-model. So the first session is "bootstrap +the repo, then build Slice 0," with the running example written first per the roadmap's tip: + +> *"write the example specs and anchored code first — it forces the DSL and extractor to be usable before they are +> finished."* (`07` §1) + +There is a clean complexity seam between Slice 0 (pure types + thin builders + the read-model schema — no I/O, +deterministic by construction) and Slice 1 (the `ts-morph` extractor, where source-reading, determinism P3, and +graceful-partial-extraction L3 enter). Keeping the extractor out of session 1 is what makes session 1 bounded and +green-at-the-end. + +--- + +## §1 — Decisions locked + +**Scope (selected):** Bootstrap the package + build Phase 0 (the typed meta-model + DSL) + author the +`checkout-v1` example specs against it so they typecheck. No extractor, no validator firing logic this session. + +**Toolchain (selected):** **npm · ESM · TypeScript strict · Vitest · tsup · ESLint + Prettier · Node ≥ 20.** + +Two cross-cutting setup decisions made here (override in the detailed plan if preferred): + +1. **The example imports the package by name, via a `paths` alias.** The fixture must + `import { spec } from "@libar-dev/software-delivery-protocol"` — the *public* DX — not `../../src`. Pre-publish, + a `tsconfig.examples.json` maps the package name → `src/index.ts`. If this alias is wrong, the example silently + typechecks against internal paths and the tracer bullet is defeated; verifying the public barrel re-exports + everything the example needs is part of "done." +2. **The running example lives under `examples/checkout-v1/`, not `/specs/`.** The design's repo-shape (`04` §5) + puts `/specs` at a *consuming project's* root; the Protocol's own repo self-hosting is an explicit *later + milestone* (base §1). A clearly-labeled fixture avoids prematurely claiming self-hosting. + +--- + +## §2 — Session goal & "Done" gates + +**Goal:** a buildable `@libar-dev/software-delivery-protocol` package whose typed meta-model (the protocol *as +code*) is complete enough that the `checkout-v1` example authors against it and compiles. + +| Gate | Command | +|---|---| +| Package + DSL typecheck | `npm run typecheck` (src) | +| Example specs compile against the public barrel | `tsc --noEmit -p tsconfig.examples.json` | +| Unit tests pass | `npm test` (Vitest) | +| Build emits JS + `.d.ts` | `npm run build` (tsup) | +| Lint clean | `npm run lint` | +| CLI runs | `npx sdp --help` (build/validate print "not implemented — Slice 1/3") | + +--- + +## §3 — Part A: Repo & toolchain bootstrap + +| File | Purpose | +|---|---| +| `package.json` | name `@libar-dev/software-delivery-protocol`, `"type":"module"`, `exports` map, `bin: { sdp }`, scripts (`build`/`test`/`typecheck`/`lint`/`format`), `engines.node ">=20"` | +| `tsconfig.json` | strict, `moduleResolution: "bundler"`, target ES2022, `declaration`, `isolatedModules`, `noUncheckedIndexedAccess` | +| `tsconfig.examples.json` | extends base, includes `examples/**`, `paths` alias → `src/index.ts`, `noEmit` | +| `tsup.config.ts` | ESM build, `dts: true`, CLI shebang banner | +| `vitest.config.ts` | node env | +| `eslint.config.js` + Prettier | flat config | +| `.gitignore` | append `node_modules/`, `dist/`, `generated/` (L8 — generated is disposable) | +| `.github/workflows/ci.yml` | install → typecheck (both tsconfigs) → lint → test → build | + +--- + +## §4 — Part B: Phase 0 — the protocol as typed code + +``` +src/ + index.ts # public barrel (the agent/author-facing surface) + ids.ts # branded SpecId/PackId/AnchorId; grammar :#; parse/format; ref() + model/ + descriptors.ts # SpecKind(8) · SpecAltitude(epic→feature→story) · SpecReadiness(idea→scoped→defined→ready) + display labels + sections.ts # intent·behavior·constraints[]·model·design·decision·verification·ui (all optional — P7) + spec.ts # Spec envelope type + spec() builder + pack.ts # Pack type + pack() builder (framing = plain note, not truth — 02 §4) + relations.ts # refines·dependsOn·constrainedBy·decidedBy·verifies·supersedes builders (all claim:"declared") + anchors.ts # anchorImplementation()/specTest() — identity-only, intent-forbidden (P9/P10) + graph/ + schema.ts # read-model types: Node union (Primitive/Pack/Anchor/CodeNode…), Edge{from,type,to,claim}, deliveryFacts, schemaVersion "0.1.0" + validate/ + contracts.ts # Validator iface, ValidatorFamily(conformance|honesty), Severity, Finding, ValidationReport + readiness-floor.ts# the 05 §3 floor table as data (declared, NOT enforced yet) + cli/ + sdp.ts # bin entry; --help; build/validate => "not implemented (Slice 1/3)" +``` + +Each piece is type/contract-only and pure — no I/O — so it is deterministic by construction. The graph schema and +validator contracts ship now (everything downstream presupposes them) but stay inert until the extractor (Slice 1) +and validators (Slice 3) arrive. + +**Envelope discipline (L9):** the `Spec` envelope (`id · title · kind · altitude · readiness · relations`) is the +stability contract; all detail lives in optional `sections`. The session must not encode completeness in conditional +types — *types describe shape; validators decide completeness* (P7). + +--- + +## §5 — Part C: The running example (the tracer bullet) + +`pack:checkout-v1`, Order Management, ~8–12 specs (`07` §1): + +``` +examples/checkout-v1/ + specs/ checkout.pack.ts · orders/order-management.spec.ts · orders/create-order.spec.ts + · 2–3 scenarios · 1–2 rules · 1 NFR(constraint) · decisions/order-lifecycle.spec.ts (~8–12 specs) + src/ orders/create-order.use-case.ts # anchored: impl:orders.create-order-use-case + test/ orders/create-order.valid-cart.test.ts # specTest verifies spec:orders.create-order.valid-cart +``` + +These compile against the **public barrel**. If they do not typecheck, the DSL is not usable — we fix the DSL, not +the example. + +--- + +## §6 — Part D: Tests (Vitest) + +- `ids.test.ts` — parse/format round-trip; reject malformed IDs; `ref()` branding. +- `builders.test.ts` — `spec()`/`pack()`/relation builders produce the expected plain objects. +- Example typecheck wired as a CI step (the `tsconfig.examples.json` gate). + +--- + +## §7 — Deferred (NOT this session) + +`ts-morph` extractor + `graph.json` (Slice 1) · validator firing logic + CI graph-gate (Slice 3) · reader / agent +surface + view (Slice 4) · determinism `--check-clean` (needs the extractor) · validator self-test fixtures +(`05` §5, needs validators). + +--- + +## §8 — Suggested commits (small, on `feature/mvp-init`) + +1. `chore: bootstrap package + tsconfig + vitest/tsup/eslint/prettier + gitignore` +2. `chore: CI (typecheck, lint, test, build)` +3. `feat: stable IDs — branded types, grammar, parse, ref` +4. `feat: descriptors + sections types` +5. `feat: Spec/Pack/relations/anchors DSL builders` +6. `feat: graph read-model schema types` +7. `feat: validator + report contracts + readiness-floor table (data)` +8. `feat: sdp CLI stub` +9. `test: ids + builders` +10. `feat(examples): checkout-v1 running example + typecheck gate` + +--- + +## §9 — Risks & gotchas + +- **Self-referencing import alias** (§1.1) — the single most likely thing to get subtly wrong; verify the example + resolves the package *name*, not `src`. +- **Over-reach into Slice 1** — resist adding any source-reading / `ts-morph` here; the green seam is "types compile, + example compiles," nothing runtime-derived. +- **Encoding completeness in types** — keep all sections optional; readiness floors are *data* this session, enforced + later (P7, P8). + +--- + +## §10 — Traceability + +| Session element | Anchored in | +|---|---| +| Slice 0 = protocol as code | `07` §1; `00` §3; base §1 | +| `Spec` envelope + 3 descriptors + sections | `02` §1–§3; P4, P7, P8, L9 | +| Stable IDs grammar | `02` §5; P6 | +| Relations + claim:"declared" | `02` §6; `03` edge contract; P9 | +| Anchors identity-only | `04` §2; P9, P10 | +| Graph read-model schema types | `03` §1 | +| Validator/report contracts + two families | `05` §1–§2 | +| Readiness-floor table (data) | `05` §3 | +| Running example as tracer bullet | `07` §1 tip; `04` §5 | +| `generated/` disposable / gitignored | L8; `03` §4 | + +--- + +## §11 — Next session preview + +**Session 2 = Slice 1:** the `ts-morph` one-graph extractor reading `examples/checkout-v1/specs/**` + anchors, +emitting `graph.json` (nodes + declared relations), with the deterministic-rebuild discipline (P3) and graceful +partial extraction (L3). The example authored in session 1 becomes its first real input. diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md new file mode 100644 index 0000000..a900699 --- /dev/null +++ b/plans/02-phase0-hardening.md @@ -0,0 +1,234 @@ +# Phase 0 Hardening — Fidelity & Simplification Pass (post-Session-1 review) + +> **Status: DRAFT · iterating to implementation-ready.** This plan records the findings of the Session 1 +> implementation review and turns them into actionable work items. It is **not** locked: several items carry an +> **open decision** (marked ⊳) that we will resolve together before this is execution-ready. Sequencing here is a +> first cut, not a contract. +> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 committed (`eb6bf2a`); fully +> green (`npm run check`: typecheck ×2, lint, format, 43 tests, build). + +This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the extractor +can safely build on."* It is a **hardening pass on the already-implemented Phase 0**, not new scope. It deliberately +stays inside the Slice 0 boundary — **no extractor, no graph emission, no graph gate** — except where a fix must be +*designed now* so the extractor (Slice 1) doesn't inherit a landmine. + +--- + +## §0 — How this fits + +Session 1 implemented Slice 0 (the protocol as typed code) + bootstrap + the `checkout-v1` tracer bullet, and it is +green and largely faithful to the ratified base. The review found a through-line of issues that are **fidelity +corrections and net complexity reductions**, not polish — so they belong *before* the design ossifies under the +extractor, while it is still cheap to move. + +Two framing commitments carried from `AGENTS.md`: + +- **This is not over-perfection.** Every "now" item below is either (a) a divergence from the ratified base + (`docs/concept/ubiquitous-language.md`, `02`, `04`, `05`) or (b) a *removal* of complexity that masks the design. + General code-quality/type-coverage polish stays deferred per the working discipline ("let the design breathe"). +- **Tracer-bullet discipline.** The example must keep forcing the DSL to be usable — and, from Slice 1 on, must + survive *static* extraction. One finding (H1) is precisely where the current example would break that. + +--- + +## §1 — Baseline: what is correct and must not regress + +Recorded so the hardening pass is measured against it: + +- Trust-model boundaries intact: no extractor, no `graph.json`, no graph gate; delivery facts are **derived-only + types**; anchors are **identity-only** (`@ts-expect-error` fixtures prove they reject readiness/intent/facts). +- `graph/schema.ts` is faithful and inert: `nodeType` vs `specKind` split (`03`/`05`), `claim` on every node/edge, + delivery facts on `PrimitiveNode`, authored vs derived edge types separated. +- IDs (`02` §5): grammar + branding only, namespace policy correctly deferred. +- Relations (`02` §6): direction + `claim:"declared"` exactly per the table; verb-form names correct. +- Core `src/` is domain-neutral (zero order/checkout names). The example's two-edge verifier semantics (test + `verifies` the example; example `verifies` the parent) match `02` "Verifier semantics" precisely. +- `AuthoredModel` is honestly fenced (doc-comment + `deferredInSession1` clauses). + +**Regression gate for this pass:** `npm run check` stays green; all current invariants above still hold. + +--- + +## §2 — The root tension (drives H2–H5) + +`src/model/sections.ts:14-23` types every section as `Record`. The ratified design (**L9**, +`02` §3) intends **typed-but-optional** sections; **P7** ("types describe *shape*; validators decide +*completeness*") governs *which fields are required*, not *whether fields are typed at all*. Open bags collapse +"optional" into "unknown," with three concrete costs: + +1. **The tracer bullet proves less than it claims.** The example typechecks against `unknown`, so "it compiles" + says nothing about the DSL capturing section *shape* — the session's headline goal. +2. **The authoring-shape honesty check (`05` #5) is bypassable.** Verified: the envelope rejects a top-level + `implemented: true` (excess-property check), but `behavior: { "has-verifier": true }` **typechecks** — a + hand-authored delivery fact hides one level down, and no validator catches it in Session 1. +3. **Validators must string-probe at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a section + field name silently passes the floor. + +⊳ **Open decision D1 — how much to type now.** Recommendation: type the **five sections the floors and the +extractor depend on** — `intent`, `behavior`, `constraints`, `model`, `verification` — and leave `design` / +`decision` / `ui` as open bags so the unsettled surfaces keep breathing. This closes H2/H3/H4 and the honesty +bypass for object literals, without over-committing the volatile sections. (Alternatives: type all eight now; or +type none and add a runtime authoring-shape validator instead. To resolve.) + +--- + +## §3 — Work items + +Severity: 🔴 act before Slice 1 · 🟠 fidelity fix · 🟡 simplification · ⚪ hygiene. +Disposition: **NOW** (this pass) · **DEFER** (named, later). + +### 🔴 H1 — Make the example statically extractable (P5) · **NOW** + +- **Finding.** Both scenario specs build `behavior` via a runtime call: + `examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts:5` and + `…/create-order-invalid-cart.spec.ts:5` use `const thenKey = "then"` + + `Object.fromEntries([...]) as Record`. +- **Why it matters.** `04` §1 (the static-data constraint, P5): a spec file is "a JSON file that TypeScript happens + to validate"; the Slice 1 `ts-morph` extractor **reifies without executing**. `Object.fromEntries(...)` cannot be + statically evaluated. Per the two-tier rule, a non-static *section* is **silently dropped with a warning** (L3) — + so under Slice 1 these scenarios lose their `given/when/then`, and the `example` readiness floor (which requires + them at `defined`+) flips them from valid → honesty violation. The tracer bullet would mine the very extractor it + exists to de-risk. +- **Verified root cause.** The dynamic form buys nothing: a plain object literal with a `then` key **lints clean + (eslint exit 0)** and typechecks. The `thenKey`/`Object.fromEntries` indirection is gratuitous. +- **Change.** Replace both with plain static object literals (no `thenKey`, no `Object.fromEntries`). +- **Files.** the two scenario `*.spec.ts` only. +- **Verify.** `tsc --noEmit -p tsconfig.examples.json` green; `npm test` green; manual: no function calls remain in + `examples/**/specs/**`. +- **Forward hook (DEFER, but name now).** Slice 1's "done" must include *the example survives static extraction + byte-for-byte*; the `sdp/spec-static` lint rule (`04` §1) is the earlier backstop. + +### 🟠 H2 — Honesty check points at the wrong section (blocking open questions) · **NOW** + +- **Finding.** `src/validate/validators.ts:199-206` (`hasNoBlockingOpenQuestions`) and + `src/validate/readiness-floor.ts:91,105` read `spec.design.openQuestions` / `spec.decision.openQuestions`. The + canonical home is **`intent.openQuestions`** (`02` §3: "openQuestions may be flagged `blocking` to prevent stating + a readiness past `defined`"; the `04` worked example puts it under `intent`). `design`/`decision` do not + canonically carry open questions. +- **Impact.** A doc-following author who flags a `blocking` question in `intent.openQuestions` can still state + `defined`/`ready` and the honesty check **won't fire** — the marquee differentiator aimed at the wrong target. + (Note: the Session-1 pre-plan introduced this path; the implementation faithfully followed the plan. It is a + plan↔concept drift to correct at the source.) +- **Change.** Read open questions from `intent.openQuestions`; update the floor data `authoredPaths` to match. With + a typed `IntentSection` (D1) this predicate shrinks to a few lines. +- **Files.** `validators.ts`, `readiness-floor.ts`; tests in `test/readiness.test.ts` / `test/validators.test.ts`. +- **Verify.** New test: a spec with `intent.openQuestions:[{blocking:true}]` stating `defined` **fails**; with + `blocking:false` (or absent) **passes**. + +### 🟠 H3 — `constraints` must be an array, not a single object · **NOW** + +- **Finding.** `src/model/sections.ts:18` types `constraints?: ConstraintsSection` (one `Record`); `02` §1 + specifies `constraints?: ConstraintSection[]`. A spec can be bounded by several NFRs (perf + security + …); a + single object can't express that. `validators.ts:139-165` already defends against **both** array and record forms + — a tell that the shape was unsettled. +- **Change.** `constraints?: ConstraintSection[]` (typed per D1: `{ flavor?, statement, target?, measurableBy? }`, + all optional except where the floor requires). Update the example (`order-latency-constraint.spec.ts`) to author + an array. **Simplify** the validator: drop the dual array/record handling — one shape only. +- **Files.** `sections.ts`, `order-latency-constraint.spec.ts`, `validators.ts`. +- **Verify.** `constraint` floor test still passes; validator no longer branches on `Array.isArray`. + +### 🟠 H4 — Referential integrity is blind to section-embedded refs · **DECIDE (likely DEFER to Slice 1)** + +- **Finding.** `validateDanglingReferences` (`validators.ts:363-414`) checks relations, pack members, `modelRefs`, + anchors — but not refs inside sections. Yet `create-order.spec.ts:22-26` puts real refs there + (`behavior.rules:[ref(...)]`, `behavior.examples:[ref(...)]`). A typo there escapes the check. `05` §2.1 wants + *every* referenced ID to resolve. +- **Why deferred-ish.** Session 1's dangling-ref scope was explicitly relations/packs/anchors. The proper fix + belongs with the extractor, which must know *structurally* where refs can live — impossible while sections are + `unknown` (this is a direct consequence of §2). Typing `behavior` (D1) makes both the check and the extractor + tractable. +- ⊳ **Open decision D2 — modeling of `behavior.rules`.** The docs model `behavior.rules` as **prose strings** and + only `examples` as refs; the example currently puts refs in `rules` *and* promotes the same rules to standalone + `kind:"rule"` specs (`refines` create-order) — the linkage exists twice, once invisibly. Resolve the + inline-vs-promote duality (`02` §3 "Section ⟷ kind"): pick prose-in-`rules` + relations for the promoted specs, + or a typed ref list with referential-integrity coverage. To resolve before typing `behavior`. +- **Change (when scheduled).** Extend referential integrity to typed ref-bearing section fields; align the example + to the resolved D2. + +### 🟡 H5 — Trim the readiness-floor validator (complexity masking a simple design) · **NOW (small)** + +- **Finding.** `validators.ts` is 453 lines for three tiny checks. Duplicate-IDs and dangling-refs are clean (~50 + lines); the bulk is readiness-floor machinery with a **two-sources-of-truth** smell, verified: + - Clause ids are enumerated in **four** places — the `readinessFloors` data (`readiness-floor.ts`), the + `ReadinessClauseId` union (`validators.ts:22-36`), the `toSupportedReadinessClauseId` switch + (`validators.ts:265-285`), and the `evaluateClause` switch (`validators.ts:224-255`). Add a clause → edit 3–4 + spots; miss one and `toSupportedReadinessClauseId` **silently skips it**. + - `authoredPaths` in the floor data is **decorative** — grep confirms it is referenced only by its own + declaration and by tests, **never by the evaluator**, which hardcodes a predicate per clause id. The "which + field does this clause check" fact lives in two places, free to drift (already visible: + `constraints.*.target` in the `defined` floor vs `constraints.target` in the overlay). +- ⊳ **Open decision D3 — which direction to collapse.** Either (a) make the table the single source of truth — the + evaluator reads `authoredPaths` + a few generic predicates (cleanest once sections are typed, D1), or (b) shrink + the table and let the evaluator be the spec, dropping the redundant union/switches. Recommendation: (a), because + typed sections make path-driven predicates safe and it kills the 4× enumeration. To resolve. +- **Verify.** Same floor test outcomes; clause-id list defined once; no decorative metadata. + +### 🟡 H6 — Simplify the tsup build; stop leaking a shebang onto the library entry · **NOW** + +- **Finding.** `dist/index.js` starts with `#!/usr/bin/env node` because `tsup.config.ts` applies `banner.js` to + **all** entries — contradicting the implementer's own recorded decision (`.sisyphus/notepads/.../decisions.md:1`: + "use a source-file shebang … so the library build stays clean"). The config compensates with an esbuild plugin to + strip the CLI's source shebang **and** an `onSuccess` hook to collapse the resulting doubled CLI shebang — an + elaborate dance around a wrong default. +- **Change.** Remove the `banner`; keep the source shebang only on `src/cli/sdp.ts`; delete the strip-plugin and the + normalize hook (or split the CLI into its own minimal tsup build). Net: less config, correct output. +- **Files.** `tsup.config.ts` (+ confirm `src/cli/sdp.ts` keeps its source shebang). +- **Verify.** `dist/index.js` has **no** shebang; `dist/cli/sdp.js` first line is exactly `#!/usr/bin/env node`; + `node dist/cli/sdp.js --help` exits 0. + +### ⚪ H7 — Hygiene · **DECIDE (cheap)** + +- **`.tmp-scratch/` is neither gitignored nor prettier-ignored** — scratch files risk being committed, and + `npm run check` fails locally on any scratch file. Add `.tmp-scratch/` to `.gitignore` and `.prettierignore`. +- ⊳ **`.sisyphus/` is tracked** (only `.sisyphus/evidence/` is ignored) — boulder.json + notepads will enter + history. Decide: keep runner state in the repo, or gitignore the dir. (Open.) +- `vitest-test.mjs` wrapper exists only so `npm test -- --run x` doesn't double-`--run`. Optional: revert to + `"test": "vitest run"` unless the `-- --run` ergonomic is wanted. +- `package.json` lacks `description`/`license`/`repository` — fine until publish; note for the publish checklist. + +--- + +## §4 — Forward-looking decision (resolve before Slice 3, name now) + +⊳ **Open decision D4 — the `AuthoredModel` seam vs the one graph (P2).** The design says validators run over **the +one graph** ("no consumer maintains a parallel model", P2). When the extractor lands, either (a) these validators +migrate to consume `GraphSchema` and `AuthoredModel` retires, or (b) `AuthoredModel` stays as an explicit, +documented *pre-graph authoring lint*. Avoid two validation code paths drifting apart — H1 is already a preview of +authored-model-truth ≠ extracted-graph-truth. Pick a direction now; execute at Slice 1/3. + +--- + +## §5 — Explicitly out of scope (still deferred) + +Unchanged from the roadmap; this pass does **not** introduce any of them: the `ts-morph` extractor · `graph.json` / +graph emission · the graph-level validator gate · `--check-clean` · reader / views / Design Review · architecture +rules · custom team rules · `--lenient` · derived-readiness banner · runtime `observed` path · MCP surface · +self-hosting the Protocol's own repo. Full typing of `design`/`decision`/`ui` sections stays deferred per D1. + +--- + +## §6 — Sequencing (first cut — to iterate) + +A loose ordering, not a contract; we will refine once D1–D4 are settled. + +1. **Quick, decision-free wins:** H1 (static example), H6 (tsup), H7 (gitignore/prettier for `.tmp-scratch`). +2. **Resolve D1** (how much to type) → then H3 (`constraints` array) and the typed `intent`/`behavior` shapes. +3. **With typed sections in place:** H2 (open-questions home) and H5 (collapse the floor validator, D3). +4. **Resolve D2** → H4 (section-ref referential integrity), likely folded into the Slice 1 extractor work. +5. **Resolve D4** as a written direction before Slice 3; no code this pass. + +**Done gate for the pass:** `npm run check` green; §1 baseline invariants intact; the example contains only static +literals; the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for typed sections; the floor +clause-id list is defined exactly once; `dist/index.js` carries no shebang. + +--- + +## §7 — Open decisions to resolve (collected) + +| # | Decision | Recommendation | +|---|---|---| +| **D1** | How much to type sections now | Type `intent`, `behavior`, `constraints`, `model`, `verification`; leave `design`/`decision`/`ui` open | +| **D2** | `behavior.rules` — prose vs ref list; inline-vs-promote duality | Resolve `02` §3 "Section ⟷ kind" before typing `behavior` | +| **D3** | Collapse direction for the floor validator | Table-as-single-source-of-truth (path-driven predicates) once D1 lands | +| **D4** | `AuthoredModel` future vs the one graph (P2) | Decide migrate-to-`GraphSchema` vs documented pre-graph lint; execute Slice 1/3 | +| (H7) | `.sisyphus/` in history | Keep vs gitignore the runner state | From b93c12292568e7362017e97f601bb7b841c292e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 14:56:40 +0200 Subject: [PATCH 04/29] Refine the hardening plan with founding-ideation layer --- plans/02-phase0-hardening.md | 137 +++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 14 deletions(-) diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index a900699..1adaa52 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -6,6 +6,13 @@ > first cut, not a contract. > **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 committed (`eb6bf2a`); fully > green (`npm run check`: typecheck ×2, lint, format, 43 tests, build). +> +> **Three perspectives layered.** (1) a *fresh* implementation review of the committed code; (2) the *planning +> session* that formed the pre-plan context before the first line of code, framed by the full MVP; (3) the +> *founding-ideation* review (the ChatGPT web session that seeded the project), framed by the **whole MVP scope** +> more than Session 1's. This revision absorbs (3): it confirms the concept spine, hard-reinforces the anti-bloat +> thesis, supplies a should-fail/should-pass fixture set that exercises these findings, and seeds forward-looking +> acceptance criteria + two concept-wording tightenings. This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the extractor can safely build on."* It is a **hardening pass on the already-implemented Phase 0**, not new scope. It deliberately @@ -21,13 +28,19 @@ green and largely faithful to the ratified base. The review found a through-line corrections and net complexity reductions**, not polish — so they belong *before* the design ossifies under the extractor, while it is still cheap to move. -Two framing commitments carried from `AGENTS.md`: +Three framing commitments carried from `AGENTS.md` (and reinforced by the founding-ideation review): - **This is not over-perfection.** Every "now" item below is either (a) a divergence from the ratified base (`docs/concept/ubiquitous-language.md`, `02`, `04`, `05`) or (b) a *removal* of complexity that masks the design. General code-quality/type-coverage polish stays deferred per the working discipline ("let the design breathe"). - **Tracer-bullet discipline.** The example must keep forcing the DSL to be usable — and, from Slice 1 on, must survive *static* extraction. One finding (H1) is precisely where the current example would break that. +- **Phase 0 stays aggressively small.** The founding review's blunt warning: *"Do not build a beautiful protocol + framework in Phase 0. Build the smallest executable conformance contract that Slice 1 needs."* Phase 0 is exactly + the place scope quietly expands. This is the full-scope lens on H5 (the floor validator is already 453 lines for + three checks) — and the standing test against every "now" item: does it shrink the contract or grow it? + Corollary (`05` §3, P4): a readiness floor is **a floor to clear, never a quota to fill** — validators must make + *dishonesty* fail without training authors or agents to pad specs with low-signal content. --- @@ -176,16 +189,58 @@ Disposition: **NOW** (this pass) · **DEFER** (named, later). - **Verify.** `dist/index.js` has **no** shebang; `dist/cli/sdp.js` first line is exactly `#!/usr/bin/env node`; `node dist/cli/sdp.js --help` exits 0. -### ⚪ H7 — Hygiene · **DECIDE (cheap)** +### ⚪ H7 — Hygiene · **MOSTLY RESOLVED** -- **`.tmp-scratch/` is neither gitignored nor prettier-ignored** — scratch files risk being committed, and - `npm run check` fails locally on any scratch file. Add `.tmp-scratch/` to `.gitignore` and `.prettierignore`. -- ⊳ **`.sisyphus/` is tracked** (only `.sisyphus/evidence/` is ignored) — boulder.json + notepads will enter - history. Decide: keep runner state in the repo, or gitignore the dir. (Open.) +- **DONE (user):** `.tmp-scratch/` is now gitignored; `plans/` is un-ignored (tracked, the more logical setup now); + `.sisyphus/` is now fully gitignored going forward. +- **Still open — `.tmp-scratch/` is not prettier-ignored**, so `npm run check` (format:check) still trips on scratch + files locally. Add `.tmp-scratch/` to `.prettierignore`. (CI is unaffected — scratch is uncommitted.) +- **Note — already-committed `.sisyphus/` files** (`boulder.json` + notepads, from `eb6bf2a`) remain tracked despite + the new ignore; a `git rm --cached .sisyphus` would stop tracking them if that's wanted. (Open, low.) - `vitest-test.mjs` wrapper exists only so `npm test -- --run x` doesn't double-`--run`. Optional: revert to `"test": "vitest run"` unless the `-- --run` ergonomic is wanted. - `package.json` lacks `description`/`license`/`repository` — fine until publish; note for the publish checklist. +### 🟡 H8 — A should-fail / should-pass validator fixture suite (`05` §5) · **NOW** + +- **Finding.** The validators have good *exact-message* tests, but no systematic should-fail/should-pass fixture + set. `05` §5 ("Validator self-testing") asks each validator to ship fixtures so a regression that stops it firing + is itself caught. The founding review independently proposes nearly the same set as the Phase-0 "done" gate. +- **Why now.** It is cheap insurance, it is concept-sanctioned, and — critically — it is the lock that proves the + honesty fixes (§2 bypass, H2, H3) actually fire. Several fixtures map 1:1 onto findings, so authoring them *is* + the regression net for this whole pass. +- **Fixtures (active in Session-1 authored-layer scope):** + - `valid-minimal-idea-spec` → passes. + - `invalid-duplicate-id` → duplicate-ids error. + - `invalid-scoped-without-relation` → readiness-floor error. + - `invalid-defined-constraint-without-target` → readiness-floor error. + - `invalid-ready-with-blocking-question` → readiness-floor error **(locks H2 — must be `intent.openQuestions`)**. + - `invalid-hand-authored-delivery-fact-in-section` → **must fail once §2/D1 lands** (today it silently passes — + this is the bypass made executable). +- **Fixtures (deferred — they need the extractor/graph; name now, write at Slice 1+):** + `invalid-non-static-id` (P5 envelope tier) · `invalid-non-static-section` (P5 section tier, the H1 case) · + `invalid-hand-authored-satisfies-edge` · `invalid-ready-with-unresolved-dependency` · + `invalid-ready-with-target-below-defined`. +- **Files.** `test/fixtures/**` (invalid fixtures live in tests, never under `examples/checkout-v1`), wired into + `test/validators.test.ts`. +- **Verify.** Each active fixture asserts the expected validator id + finding; the deferred set exists as skipped/ + documented stubs so Slice 1 inherits the checklist. + +### 🟡 H9 — Lock the type-level honesty defenses with compile-time fixtures · **NOW (cheap)** + +- **Finding.** Two type defenses already work but are **untested**, so a future refactor could silently open them: + - The `Spec` envelope rejects a top-level hand-authored delivery fact (`spec({ …, implemented: true })` → + TS2353) — verified, untested. + - `Pack` rejects truth-bearing fields (`pack({ …, intent })` / `readiness` / `constraints` → TS2353) — verified, + untested. This is the founding review's #7 ("keep `Pack` truthless, but defend it with types"); the current + `Pack` shape already matches the suggested restrictive type exactly. +- **Change.** Add `@ts-expect-error` fixtures (alongside the existing anchor ones in `test/builders.typecheck.ts`) + for: `Spec` rejecting `implemented`/`has-verifier` at the envelope; `Pack` rejecting `intent`/`readiness`/ + `constraints`. Note explicitly that the **in-section** bypass (`behavior:{ "has-verifier": true }`) is *not* closed + by these — it is closed by typing sections (D1) — so the two items are tracked together. +- **Files.** `test/builders.typecheck.ts`. +- **Verify.** `npm run typecheck` consumes the new `@ts-expect-error`s. + --- ## §4 — Forward-looking decision (resolve before Slice 3, name now) @@ -198,6 +253,51 @@ authored-model-truth ≠ extracted-graph-truth. Pick a direction now; execute at --- +## §4b — Concept-base refinements (proposed; route to `docs/concept`, not code) + +The founding review surfaced two wording imprecisions in the ratified base. The **code is already correct** in both +cases; these are language tightenings to consider against the base (terminology is ratified, so we *flag* rather +than silently edit — `AGENTS.md` working discipline). No code change. + +- ⊳ **R1 — "anchor carries identity only" → "binding assertion only, never system-truth content."** An anchor emits + a `satisfies`/`verifies` edge, which is a *binding assertion*, not "identity" in the ordinary sense — so the + current phrasing (`ubiquitous-language.md` §2/§4) risks reading as self-contradictory next to the `anchored` + claim. Suggested: *"an anchor says 'this code location is the implementation/test binding for this Spec ID'; it + must never carry behavior, rationale, readiness, acceptance criteria, or delivery facts."* The code already + enforces exactly this (anchors hold only `id`/`label`/target; `@ts-expect-error` proves the rest is rejected). +- ⊳ **R2 — "no consumer reads source directly" → permit source *links*, forbid independent re-parsing.** The + principle (`03`/`05`/`06`) is right, but a Design Review linking to source locations *recorded in the graph* is + legitimate. Suggested: *"Consumers may link to source locations recorded in the graph; consumers must not + independently parse source to derive their own model."* Matters when the Slice 4 Design Review lands. + +## §4c — Forward-looking acceptance criteria, seeded by the full-MVP review (route to `07`) + +Not Phase-0 work; recorded here so the full-scope lens isn't lost, to fold into the roadmap (`docs/concept/07`) and +the relevant slice's "done." Each is honesty-posture-aligned. + +- **Golden-graph determinism fixture (P3) — at Slice 1.** Generalizes H1's forward hook: a tiny fixture repo with + `expected/graph.json`; every extractor change must rebuild **byte-identically** (`diff` clean after deleting + `generated/`). Forces the hard calls early — path normalization, line-number stability, sort order, schema + versioning, unknown-reference handling, claim inheritance, delivery-fact derivation. +- **`implemented` is a UI hazard — at Slice 4.** Keep the internal fact name `implemented` (it powers the + `implemented ∧ ¬ready` drift query), but render binding language in views: *"Implementation binding: present / + Verifier binding: present / Runtime observation: not tracked."* Don't let the view overpromise liveness. +- **Derived-readiness banner in the MVP view if cheap — at Slice 4.** *"Stated readiness: ready · Structural floor + reached: defined · Problem: blocking open question."* It teaches the core honesty concept (stated, then checked). + Cheaply enabled by H5: a floor evaluator that reports *which clause fails* is exactly the banner's substrate. +- **`coverage-unknown` as a first-class reader/impact output — Slice 4 acceptance.** File-level blast-radius must + report changed-but-unanchored files as `coverage-unknown`, never silently under-report (honest "impact is bounded" + vs false "impact is complete"). Make it acceptance, not a design note. +- **Authoring ergonomics is the biggest remaining risk — Slice 5 / ongoing.** Optimize the loop early + (`sdp new spec`, `sdp explain`, `sdp validate --watch`, great error messages, copy-pasteable examples, minimal + boilerplate). Threads back to the anti-padding corollary in §0: validators make dishonesty fail without rewarding + low-signal filler. +- **Adopt the founding review's MVP acceptance checklist** (spec extraction · anchor extraction · claim honesty · + readiness honesty · delivery facts · traceability · determinism · view) as the roadmap's acceptance suite, + mapped across Slices 1–5. + +--- + ## §5 — Explicitly out of scope (still deferred) Unchanged from the roadmap; this pass does **not** introduce any of them: the `ts-morph` extractor · `graph.json` / @@ -211,15 +311,21 @@ self-hosting the Protocol's own repo. Full typing of `design`/`decision`/`ui` se A loose ordering, not a contract; we will refine once D1–D4 are settled. -1. **Quick, decision-free wins:** H1 (static example), H6 (tsup), H7 (gitignore/prettier for `.tmp-scratch`). -2. **Resolve D1** (how much to type) → then H3 (`constraints` array) and the typed `intent`/`behavior` shapes. -3. **With typed sections in place:** H2 (open-questions home) and H5 (collapse the floor validator, D3). -4. **Resolve D2** → H4 (section-ref referential integrity), likely folded into the Slice 1 extractor work. -5. **Resolve D4** as a written direction before Slice 3; no code this pass. +1. **Quick, decision-free wins:** H1 (static example), H6 (tsup), H9 (compile-time honesty fixtures), the remaining + H7 bit (`.prettierignore` for `.tmp-scratch`). +2. **Regression net:** H8 — author the active should-fail/should-pass fixtures now (all except the in-section + delivery-fact one, which is gated on D1); stub the deferred extractor-era fixtures. +3. **Resolve D1** (how much to type) → then H3 (`constraints` array), the typed `intent`/`behavior` shapes, and the + H8 `invalid-hand-authored-delivery-fact-in-section` fixture flips to failing. +4. **With typed sections in place:** H2 (open-questions home) and H5 (collapse the floor validator, D3). +5. **Resolve D2** → H4 (section-ref referential integrity), likely folded into the Slice 1 extractor work. +6. **Resolve D4** as a written direction before Slice 3; **R1/R2** routed to `docs/concept` (no code). §4c items + routed to `07`. No code this pass for §4b/§4c. **Done gate for the pass:** `npm run check` green; §1 baseline invariants intact; the example contains only static -literals; the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for typed sections; the floor -clause-id list is defined exactly once; `dist/index.js` carries no shebang. +literals; the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for typed sections **and** locked by +the H8 fixture; the floor clause-id list is defined exactly once; `dist/index.js` carries no shebang; the H8 active +fixture suite passes; H9 `@ts-expect-error` honesty fixtures are in place. --- @@ -231,4 +337,7 @@ clause-id list is defined exactly once; `dist/index.js` carries no shebang. | **D2** | `behavior.rules` — prose vs ref list; inline-vs-promote duality | Resolve `02` §3 "Section ⟷ kind" before typing `behavior` | | **D3** | Collapse direction for the floor validator | Table-as-single-source-of-truth (path-driven predicates) once D1 lands | | **D4** | `AuthoredModel` future vs the one graph (P2) | Decide migrate-to-`GraphSchema` vs documented pre-graph lint; execute Slice 1/3 | -| (H7) | `.sisyphus/` in history | Keep vs gitignore the runner state | +| **R1** | Tighten "anchor = identity only" → "binding assertion only" (`§4b`) | Adopt against the ratified base; code already conforms | +| **R2** | Tighten "no consumer reads source" → links-ok, re-parse-no (`§4b`) | Adopt before Slice 4 Design Review | +| **D5** | Pull the derived-readiness banner into the MVP view? (`§4c`) | Yes if cheap; H5 already produces the substrate | +| (H7) | Untrack already-committed `.sisyphus/` files? | `git rm --cached` if runner state shouldn't stay in history | From f83e872c9645f336a9d629f8b5c262ef5e97c180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 15:17:09 +0200 Subject: [PATCH 05/29] Reinforce the hardening plan with additional founding-ideation layer view --- plans/02-phase0-hardening.md | 113 +++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index 1adaa52..ffc4b6b 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -7,12 +7,16 @@ > **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 committed (`eb6bf2a`); fully > green (`npm run check`: typecheck ×2, lint, format, 43 tests, build). > -> **Three perspectives layered.** (1) a *fresh* implementation review of the committed code; (2) the *planning +> **Layered review provenance.** (1) a *fresh* implementation review of the committed code; (2) the *planning > session* that formed the pre-plan context before the first line of code, framed by the full MVP; (3) the -> *founding-ideation* review (the ChatGPT web session that seeded the project), framed by the **whole MVP scope** -> more than Session 1's. This revision absorbs (3): it confirms the concept spine, hard-reinforces the anti-bloat -> thesis, supplies a should-fail/should-pass fixture set that exercises these findings, and seeds forward-looking -> acceptance criteria + two concept-wording tightenings. +> *founding-ideation* review (the ChatGPT web session that seeded the project), framed by the **whole MVP scope**; +> then (4) a *planning-session synthesis* that re-read the shipped code and sorted (3) into act / already-handled / +> extend buckets. This revision absorbs (3) **and** (4): it confirms the concept spine, hard-reinforces the +> anti-bloat thesis, supplies a should-fail/should-pass fixture set, seeds forward-looking acceptance criteria + two +> concept-wording tightenings, **elevates authoring ergonomics to the headline forward risk**, and adds one new +> tracer-bullet gap (H10). Layering is deliberate while the repo is mostly code-free: improvisation is fine until +> there is enough code + graph state for the Design Review to do this in-system (the same principle the product is +> built on). This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the extractor can safely build on."* It is a **hardening pass on the already-implemented Phase 0**, not new scope. It deliberately @@ -83,6 +87,15 @@ extractor depend on** — `intent`, `behavior`, `constraints`, `model`, `verific bypass for object literals, without over-committing the volatile sections. (Alternatives: type all eight now; or type none and add a runtime authoring-shape validator instead. To resolve.) +> **D1 is bigger than honesty/floors — it is the linchpin of the headline MVP risk (synthesis, bucket A/C).** The +> founding review's strongest contribution is that *authoring ergonomics, not graph theory, is the biggest risk*: +> if authoring feels heavy, authors (humans **and** agents) avoid the system or overfit specs to satisfy tooling. +> Untyped sections give **no autocomplete and no shape guardrails** — precisely the heavy-authoring loop. H1 is the +> first symptom already in the repo: an agent reached for `Object.fromEntries([...]) as Record` +> instead of a plain literal — an author fighting an untyped surface. So typing the five sections is not only the +> cheapest path to H2/H3/H4 and the honesty fix; it is the **single highest-value lever on adoption**. (This is why +> authoring ergonomics is elevated to a named Slice-2 concern in §4c.) + --- ## §3 — Work items @@ -241,6 +254,26 @@ Disposition: **NOW** (this pass) · **DEFER** (named, later). - **Files.** `test/builders.typecheck.ts`. - **Verify.** `npm run typecheck` consumes the new `@ts-expect-error`s. +### 🟠 H10 — The example under-proves the generic-anchor claim · **DECIDE (pairs with Slice 2 anchor extraction)** + +- **Finding (synthesis, bucket C — new; missed by all prior passes).** The whole point of *generic* anchors is that + they bind **any** code location, not just classes (`00`/`04`). But the shipped example has only `impl` + `test` + anchors (`anchorImplementation` is namespace-locked to `impl:`; `specTest` to `test:`), and the docs' own example + (`04` §5) includes a route anchor `api:orders.post`. So the tracer bullet never exercises the genericity claim — + the strongest proof would be binding a **non-class location** (a route/endpoint). +- **Two layers to the gap:** + - **Example layer:** add an `api`/route anchor to `examples/checkout-v1` so the generic-binding claim is actually + demonstrated. + - ⊳ **DSL-shape question (D6):** the satisfies-anchor builder is locked to `impl:`. Generic binding implies the + "code → spec" (`satisfies`) anchor should accept the implementation-flavored code namespaces (`impl` / `api` / + `component`), with `specTest` (`verifies`) staying the `test:` variant. Decide: generalize `anchorImplementation` + to a `codeAnchor` over those namespaces, or add sibling builders. The `api:` namespace already parses (`ids.ts`); + only a builder + branded id is missing. +- **Why deferred-ish.** It pairs naturally with Slice 2 (anchors + `satisfies`/`verifies` edges). Naming it now keeps + the tracer bullet honest about what it proves. +- **Verify (when scheduled).** Example contains ≥1 non-`impl` code anchor; it extracts to a `satisfies` edge with + `claim:"anchored"` like any other. + --- ## §4 — Forward-looking decision (resolve before Slice 3, name now) @@ -259,12 +292,14 @@ The founding review surfaced two wording imprecisions in the ratified base. The cases; these are language tightenings to consider against the base (terminology is ratified, so we *flag* rather than silently edit — `AGENTS.md` working discipline). No code change. -- ⊳ **R1 — "anchor carries identity only" → "binding assertion only, never system-truth content."** An anchor emits - a `satisfies`/`verifies` edge, which is a *binding assertion*, not "identity" in the ordinary sense — so the - current phrasing (`ubiquitous-language.md` §2/§4) risks reading as self-contradictory next to the `anchored` - claim. Suggested: *"an anchor says 'this code location is the implementation/test binding for this Spec ID'; it - must never carry behavior, rationale, readiness, acceptance criteria, or delivery facts."* The code already - enforces exactly this (anchors hold only `id`/`label`/target; `@ts-expect-error` proves the rest is rejected). +- ⊳ **R1 — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4).** The synthesis + pins this as a *mild internal inconsistency in the same doc*: `ubiquitous-language.md` §2 says anchors carry + "identity only," while §4's `claim` table calls `anchored` "a human binding" — and a binding (it emits a + `satisfies`/`verifies` edge) is more than identity. Suggested unified phrasing: *"an anchor says 'this code + location is the implementation/test binding for this Spec ID'; binding only, never system-truth content — never + behavior, rationale, readiness, acceptance criteria, or delivery facts."* Substance is already present and the + code already conforms (anchors hold only `id`/`label`/target; `@ts-expect-error` proves the rest is rejected) — + this is a wording harmonization, not a model change. - ⊳ **R2 — "no consumer reads source directly" → permit source *links*, forbid independent re-parsing.** The principle (`03`/`05`/`06`) is right, but a Design Review linking to source locations *recorded in the graph* is legitimate. Suggested: *"Consumers may link to source locations recorded in the graph; consumers must not @@ -273,28 +308,41 @@ than silently edit — `AGENTS.md` working discipline). No code change. ## §4c — Forward-looking acceptance criteria, seeded by the full-MVP review (route to `07`) Not Phase-0 work; recorded here so the full-scope lens isn't lost, to fold into the roadmap (`docs/concept/07`) and -the relevant slice's "done." Each is honesty-posture-aligned. - -- **Golden-graph determinism fixture (P3) — at Slice 1.** Generalizes H1's forward hook: a tiny fixture repo with - `expected/graph.json`; every extractor change must rebuild **byte-identically** (`diff` clean after deleting - `generated/`). Forces the hard calls early — path normalization, line-number stability, sort order, schema - versioning, unknown-reference handling, claim inheritance, delivery-fact derivation. -- **`implemented` is a UI hazard — at Slice 4.** Keep the internal fact name `implemented` (it powers the +the relevant slice's "done." Each is honesty-posture-aligned. Ordering reflects the synthesis's priority. + +- **① Authoring ergonomics — the headline risk; elevate to a named Slice-2 concern.** The synthesis calls this the + feedback's highest-value idea and notes there is *no authoring-ergonomics workstream anywhere in `00`–`07`* (the + MVP CLI is just `build`/`validate`). The first lever is **typed sections (D1)** for autocomplete + shape + guardrails; then great error messages and `sdp validate --watch`; later `sdp new spec` / `sdp explain`. Threads + back to the §0 anti-padding corollary: make dishonesty fail without rewarding low-signal filler. (Was framed as + Slice 5; the synthesis pulls the *typed-sections* half forward to Slice 2 because it is also D1.) +- **② Golden-graph fixture — at Slice 1; keep it distinct from `--check-clean`.** The synthesis corrects a + conflation of *determinism* and *correctness* — adopt **both**, labeled distinctly: + - **Determinism self-check** (`03` §2): rebuild twice, assert **byte-identical** — explicitly a self-comparison, + **never** a diff against a committed `generated/` artifact (`generated/` is gitignored, L8). + - **Correctness oracle:** a committed `fixtures/order-management/expected/graph.json` ("did the extractor produce + the *right* graph," not just the *same* graph) — legitimate because it lives in `fixtures/`, not `generated/`. + - Heed the sub-list: make paths **repo-relative / POSIX**, and decide consciously whether **line numbers** enter + the golden (deterministic, but they make the oracle brittle to unrelated edits). +- **③ Derived-readiness banner in the MVP view — at Slice 4, but blocked on H2.** *"Stated readiness: ready · + Structural floor reached: defined · Problem: blocking open question."* Teaches the core honesty concept (stated, + then checked), and is cheaply enabled by H5 (a floor evaluator that reports *which* clause fails is the banner's + substrate). **Do not pull it forward before H2** — today the floor reads open questions from the wrong section, so + the banner would *confidently display the wrong thing* (a UI proposal exposing the model bug). +- **④ `implemented` is a UI hazard — at Slice 4, view-label only.** Model semantics are already settled (§4b / + DECISIONS MD-7: binding/existence, never liveness). Keep the internal fact name `implemented` (it powers the `implemented ∧ ¬ready` drift query), but render binding language in views: *"Implementation binding: present / - Verifier binding: present / Runtime observation: not tracked."* Don't let the view overpromise liveness. -- **Derived-readiness banner in the MVP view if cheap — at Slice 4.** *"Stated readiness: ready · Structural floor - reached: defined · Problem: blocking open question."* It teaches the core honesty concept (stated, then checked). - Cheaply enabled by H5: a floor evaluator that reports *which clause fails* is exactly the banner's substrate. -- **`coverage-unknown` as a first-class reader/impact output — Slice 4 acceptance.** File-level blast-radius must - report changed-but-unanchored files as `coverage-unknown`, never silently under-report (honest "impact is bounded" - vs false "impact is complete"). Make it acceptance, not a design note. -- **Authoring ergonomics is the biggest remaining risk — Slice 5 / ongoing.** Optimize the loop early - (`sdp new spec`, `sdp explain`, `sdp validate --watch`, great error messages, copy-pasteable examples, minimal - boilerplate). Threads back to the anti-padding corollary in §0: validators make dishonesty fail without rewarding - low-signal filler. + Verifier binding: present / Runtime observation: not tracked."* +- **`coverage-unknown` — already a settled model commitment; make it Slice-4 acceptance.** Per DECISIONS MD-7 / + `07` §4 this is *decided*, not open: file-level blast-radius reports changed-but-unanchored files as + `coverage-unknown`, never silently under-reporting (honest "impact is bounded" vs false "impact is complete"). The + only add is promoting it from design note → explicit Slice-4 acceptance criterion. - **Adopt the founding review's MVP acceptance checklist** (spec extraction · anchor extraction · claim honesty · - readiness honesty · delivery facts · traceability · determinism · view) as the roadmap's acceptance suite, - mapped across Slices 1–5. + readiness honesty · delivery facts · traceability · determinism · view) as the roadmap's acceptance suite, mapped + across Slices 1–5 — with three synthesis sharpenings: (a) *"ready spec with blocking open questions fails"* is the + regression test to add **after** H2 (and is locked early by H8); (b) extend *"rejects non-static envelope fields"* + to *"the example fixture survives static extraction with **no dropped sections**"* (envelope is clean today; + sections aren't — H1); (c) the checklist's *"extracts one api anchor"* is the H10 gap. --- @@ -339,5 +387,6 @@ fixture suite passes; H9 `@ts-expect-error` honesty fixtures are in place. | **D4** | `AuthoredModel` future vs the one graph (P2) | Decide migrate-to-`GraphSchema` vs documented pre-graph lint; execute Slice 1/3 | | **R1** | Tighten "anchor = identity only" → "binding assertion only" (`§4b`) | Adopt against the ratified base; code already conforms | | **R2** | Tighten "no consumer reads source" → links-ok, re-parse-no (`§4b`) | Adopt before Slice 4 Design Review | -| **D5** | Pull the derived-readiness banner into the MVP view? (`§4c`) | Yes if cheap; H5 already produces the substrate | +| **D5** | Pull the derived-readiness banner into the MVP view? (`§4c`) | Yes if cheap — but **only after H2**; H5 already produces the substrate | +| **D6** | Generic-anchor DSL shape — `codeAnchor` over `impl`/`api`/`component` vs sibling builders (`H10`) | Generalize the `satisfies` anchor; pairs with Slice-2 anchor extraction | | (H7) | Untrack already-committed `.sisyphus/` files? | `git rm --cached` if runner state shouldn't stay in history | From 478cc00a3448ea3880f453ab8f0f661b7c122549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 22:48:08 +0200 Subject: [PATCH 06/29] =?UTF-8?q?Execute=20Phase=200=20hardening=20Wave=20?= =?UTF-8?q?A=20=E2=80=94=20decision-free=20fidelity=20+=20simplification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - H1: make the checkout-v1 scenario specs statically extractable (plain object literals; drop Object.fromEntries/thenKey/as-Record) so Slice 1's ts-morph extractor can reify them without executing. - H6: simplify tsup — drop the banner + strip-plugin + post-build normalise hook; rely on esbuild preserving the CLI entry's source shebang. dist/index.js is now shebang-free; dist/cli/sdp.js carries exactly one. - H7: prettier-ignore .tmp-scratch so format:check stops tripping on scratch. - H8: add a should-pass/should-fail validator fixture suite (4 active fixtures + 2 it.todo stubs gated on H2 and D1) as the regression net (05 §5). - H9: lock the Spec-envelope and Pack type-level honesty defenses with @ts-expect-error fixtures; record the excess-property/inline-literal caveat (F7). - npm run check green: typecheck x2, lint, format, 47 tests + 2 todo, build. --- .prettierignore | 1 + .../orders/create-order-invalid-cart.spec.ts | 34 ++--- .../orders/create-order-valid-cart.spec.ts | 38 ++--- test/builders.typecheck.ts | 52 +++++++ test/fixtures.test.ts | 33 +++++ test/fixtures/authored-model.fixtures.ts | 132 ++++++++++++++++++ tsup.config.ts | 46 +----- 7 files changed, 247 insertions(+), 89 deletions(-) create mode 100644 test/fixtures.test.ts create mode 100644 test/fixtures/authored-model.fixtures.ts diff --git a/.prettierignore b/.prettierignore index fcb175d..5cf7ec7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,4 @@ docs/** jtbd-stories/** plans/** .sisyphus/** +.tmp-scratch/** diff --git a/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts b/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts index 61e0b48..d995f59 100644 --- a/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts +++ b/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts @@ -1,26 +1,5 @@ import { refines, spec, specId, verifies } from "@libar-dev/software-delivery-protocol"; -const thenKey = "then"; - -const createOrderInvalidCartBehavior = Object.fromEntries([ - ["examples", ["Out-of-stock or empty carts are rejected before an order is created."]], - [ - "given", - [ - "A cart is empty or contains at least one item without available inventory.", - "The cart is submitted for order creation.", - ], - ], - ["when", ["The create-order use case validates the cart."]], - [ - thenKey, - [ - "No order is created.", - "The caller receives a validation error explaining why the cart is invalid.", - ], - ], -]) as Record; - export const createOrderInvalidCartSpec = spec({ id: specId("spec:orders.create-order.invalid-cart"), title: "Invalid cart is rejected", @@ -32,7 +11,18 @@ export const createOrderInvalidCartSpec = spec({ value: "The authored example captures the rejection path without adding invalid fixtures to the model.", }, - behavior: createOrderInvalidCartBehavior, + behavior: { + examples: ["Out-of-stock or empty carts are rejected before an order is created."], + given: [ + "A cart is empty or contains at least one item without available inventory.", + "The cart is submitted for order creation.", + ], + when: ["The create-order use case validates the cart."], + then: [ + "No order is created.", + "The caller receives a validation error explaining why the cart is invalid.", + ], + }, verification: { mode: "executable", criteria: [ diff --git a/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts b/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts index 05af054..75048ba 100644 --- a/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts +++ b/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts @@ -1,28 +1,5 @@ import { refines, spec, specId, verifies } from "@libar-dev/software-delivery-protocol"; -const thenKey = "then"; - -const createOrderValidCartBehavior = Object.fromEntries([ - ["examples", ["Valid cart becomes an order with the computed total."]], - [ - "given", - [ - "A customer has a cart with one or more line items.", - "Every cart item is in stock.", - "Each line item has a positive quantity and a unit price.", - ], - ], - ["when", ["The customer submits the cart for order creation."]], - [ - thenKey, - [ - "An order is created.", - "The order total equals the sum of quantity multiplied by unit price for each line item.", - "The order contains the original cart lines.", - ], - ], -]) as Record; - export const createOrderValidCartSpec = spec({ id: specId("spec:orders.create-order.valid-cart"), title: "Valid cart creates an order", @@ -33,7 +10,20 @@ export const createOrderValidCartSpec = spec({ outcome: "Show that a valid cart can become an order.", value: "The authored example demonstrates the happy path for create-order.", }, - behavior: createOrderValidCartBehavior, + behavior: { + examples: ["Valid cart becomes an order with the computed total."], + given: [ + "A customer has a cart with one or more line items.", + "Every cart item is in stock.", + "Each line item has a positive quantity and a unit price.", + ], + when: ["The customer submits the cart for order creation."], + then: [ + "An order is created.", + "The order total equals the sum of quantity multiplied by unit price for each line item.", + "The order contains the original cart lines.", + ], + }, verification: { mode: "executable", criteria: [ diff --git a/test/builders.typecheck.ts b/test/builders.typecheck.ts index 894e75a..eeb337a 100644 --- a/test/builders.typecheck.ts +++ b/test/builders.typecheck.ts @@ -73,3 +73,55 @@ specTest({ // @ts-expect-error test anchors do not accept delivery fact badges "has-verifier": true, }); + +// The Spec envelope rejects hand-authored delivery facts at the top level: delivery facts are +// derived-only (base §4b). Caveat: the guarantee is "rejected for inline object literals" — it relies +// on TypeScript's excess-property checking, which only fires on literals passed directly. Sound in +// practice because P5 mandates static inline literals, but it is not a structural guarantee. Note too: +// the in-section bypass (`behavior: { "has-verifier": true }`) is NOT closed by these and stays open +// until sections are typed (D1, plan 03 / Wave B). +spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + // @ts-expect-error specs never author the derived `implemented` delivery fact at the envelope + implemented: true, +}); + +spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + // @ts-expect-error specs never author the derived `has-verifier` delivery fact at the envelope + "has-verifier": true, +}); + +// The Pack stays truthless (base §2 boundary): it states no system truth, so it rejects +// truth-bearing fields — intent, readiness, and constraints all belong on a Spec, never a Pack. +pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [ref("spec:orders.create-order")], + // @ts-expect-error packs carry no intent section — intent is system truth, authored on a Spec + intent: { outcome: "turn a valid cart into an order" }, +}); + +pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [ref("spec:orders.create-order")], + // @ts-expect-error packs state no readiness — readiness is a Spec descriptor + readiness: "ready", +}); + +pack({ + id: packId("pack:checkout-v1"), + title: "Checkout v1", + specs: [ref("spec:orders.create-order")], + // @ts-expect-error packs carry no constraints section — constraints are authored on a Spec + constraints: [{ statement: "p95 < 200ms" }], +}); diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts new file mode 100644 index 0000000..159e1ec --- /dev/null +++ b/test/fixtures.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; + +import { validateAuthoredModel } from "../src/index.js"; +import { activeValidatorFixtures } from "./fixtures/authored-model.fixtures.js"; + +describe("authored-model validator fixtures (should-pass / should-fail regression net)", () => { + for (const fixture of activeValidatorFixtures) { + it(fixture.name, () => { + const { findings } = validateAuthoredModel(fixture.model); + const { expect: expected } = fixture; + + if (expected === "pass") { + expect(findings).toEqual([]); + return; + } + + const matching = findings.filter( + (finding) => + finding.validatorId === expected.validatorId && + (expected.relatedId === undefined || finding.relatedId === expected.relatedId), + ); + + expect(matching.length).toBeGreaterThan(0); + }); + } + + // Gated fixtures — the inherited checklist for Wave B (plan 03). They are deliberately not active + // yet because the behavior they assert does not exist in Session-1 code: + // - the open-questions home is still design/decision, not intent (H2); + // - a hand-authored delivery fact inside an untyped section still typechecks and is uncaught (D1). + it.todo("invalid-ready-with-blocking-question fails once H2 reads intent.openQuestions"); + it.todo("invalid-hand-authored-delivery-fact-in-section fails once sections are typed (D1)"); +}); diff --git a/test/fixtures/authored-model.fixtures.ts b/test/fixtures/authored-model.fixtures.ts new file mode 100644 index 0000000..2bd3482 --- /dev/null +++ b/test/fixtures/authored-model.fixtures.ts @@ -0,0 +1,132 @@ +import { refines, spec, specId } from "../../src/index.js"; +import type { AuthoredModel } from "../../src/index.js"; + +/** + * Systematic should-pass / should-fail fixtures for the Session-1 authored-layer validators + * (`05` §5 "Validator self-testing"). Each fixture pins a single validator outcome so a regression + * that silently stops a validator firing is itself caught. + * + * Scope: only the fixtures that exercise *current, decision-free* behavior live here as ACTIVE. + * Two further fixtures from the hardening plan are gated on later work and are tracked as `it.todo` + * stubs in `fixtures.test.ts`: + * - `invalid-ready-with-blocking-question` — gated on H2 (open-questions canonical home) + * - `invalid-hand-authored-delivery-fact-in-section` — gated on D1 (typed sections) + * Both flip to ACTIVE in Wave B (see `plans/03-decision-resolution-and-base-reconciliation.md`). + */ +export interface ValidatorFixture { + readonly name: string; + readonly model: AuthoredModel; + /** + * `"pass"` asserts `validateAuthoredModel` returns no findings. Otherwise the model must produce a + * finding from `validatorId`; when `relatedId` is given, at least one finding must also match it. + */ + readonly expect: "pass" | { readonly validatorId: string; readonly relatedId?: string }; +} + +const validMinimalIdeaSpec: ValidatorFixture = { + name: "valid-minimal-idea-spec", + model: { + specs: [ + spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "idea", + intent: { outcome: "Own the order lifecycle for checkout." }, + }), + ], + packs: [], + anchors: [], + }, + expect: "pass", +}; + +const invalidDuplicateId: ValidatorFixture = { + name: "invalid-duplicate-id", + model: { + specs: [ + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + intent: { outcome: "Turn a valid cart into an order." }, + }), + spec({ + id: specId("spec:orders.create-order"), + title: "Create order (duplicate)", + kind: "behavior", + altitude: "feature", + readiness: "idea", + intent: { outcome: "Accidental second definition of the same id." }, + }), + ], + packs: [], + anchors: [], + }, + expect: { validatorId: "conformance/duplicate-ids" }, +}; + +const invalidScopedWithoutRelation: ValidatorFixture = { + name: "invalid-scoped-without-relation", + model: { + specs: [ + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "scoped", + intent: { outcome: "Turn a valid cart into an order." }, + behavior: { examples: ["Valid cart becomes an order."] }, + // No relations: a scoped spec must declare at least one authored relation. + }), + ], + packs: [], + anchors: [], + }, + expect: { validatorId: "honesty/readiness-floor", relatedId: "at-least-one-relation" }, +}; + +const invalidDefinedConstraintWithoutTarget: ValidatorFixture = { + name: "invalid-defined-constraint-without-target", + model: { + specs: [ + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + intent: { outcome: "Turn a valid cart into an order." }, + }), + spec({ + id: specId("spec:orders.order-latency-constraint"), + title: "Create-order latency budget", + kind: "constraint", + altitude: "story", + readiness: "defined", + intent: { outcome: "Keep create-order fast enough for interactive checkout." }, + behavior: { rules: ["The create-order path stays inside a latency budget."] }, + // A defined constraint must carry a machine-readable target; this one omits it. + constraints: { statement: "Create-order should respond quickly." }, + relations: [refines(specId("spec:orders.create-order"))], + }), + ], + packs: [], + anchors: [], + }, + expect: { + validatorId: "honesty/readiness-floor", + relatedId: "constraint-machine-readable-target", + }, +}; + +export const activeValidatorFixtures: readonly ValidatorFixture[] = [ + validMinimalIdeaSpec, + invalidDuplicateId, + invalidScopedWithoutRelation, + invalidDefinedConstraintWithoutTarget, +]; diff --git a/tsup.config.ts b/tsup.config.ts index c50420a..4699e88 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,41 +1,8 @@ -import { readFile, writeFile } from "node:fs/promises"; -import { fileURLToPath } from "node:url"; -import type { Plugin } from "esbuild"; import { defineConfig } from "tsup"; -const cliShebangStripper = { - name: "strip-cli-shebang", - setup(build: Parameters[0]) { - build.onResolve({ filter: /[\\/]src[\\/]cli[\\/]sdp\.ts$/ }, (args) => ({ - path: args.path, - namespace: "cli-shebang", - })); - - build.onLoad({ filter: /.*/, namespace: "cli-shebang" }, async (args) => { - const contents = await readFile(args.path, "utf8"); - - return { - contents: contents.replace(/^#!.*(?:\r?\n)?/, ""), - loader: "ts", - }; - }); - }, -}; - -const cliOutputPath = fileURLToPath(new URL("./dist/cli/sdp.js", import.meta.url)); - -async function normalizeCliOutputShebang() { - const contents = await readFile(cliOutputPath, "utf8"); - const duplicateShebang = "#!/usr/bin/env node\n#!/usr/bin/env node\n"; - const normalized = contents.startsWith(duplicateShebang) - ? contents.slice("#!/usr/bin/env node\n".length) - : contents; - - if (normalized !== contents) { - await writeFile(cliOutputPath, normalized, "utf8"); - } -} - +// The CLI entry (`src/cli/sdp.ts`) carries its own source shebang; esbuild preserves a leading +// shebang on an entry point, so `dist/cli/sdp.js` stays executable while `dist/index.js` (the +// library entry, no shebang) stays clean. No banner / strip-plugin / post-build normalisation. export default defineConfig({ entry: ["src/index.ts", "src/cli/sdp.ts"], format: ["esm"], @@ -45,11 +12,4 @@ export default defineConfig({ clean: true, splitting: false, outDir: "dist", - esbuildOptions(options) { - options.plugins = [...(options.plugins ?? []), cliShebangStripper]; - }, - banner: { - js: "#!/usr/bin/env node", - }, - onSuccess: normalizeCliOutputShebang, }); From c8a7ce0d4fe5c501241b3698b41effdd84231672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Sun, 7 Jun 2026 22:48:23 +0200 Subject: [PATCH 07/29] Split the hardening plan into execution / decisions / concept; absorb adversarial review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-home the post-Session-1 review's three work-kinds so each gets the right rigor. - plans/02: refocus to EXECUTION-only code hardening. Wave A marked done with verification; Wave B (H2/H3/H4/H5/H10) kept in full but decision-gated on plan 03. Adds a carried review backlog (F2 ref naming, F3 aggregate family, F4 modelRefs kind). - plans/03 (new): the fresh-session grilling agenda — D1-D4, D6 in dependency order, the base-first method, what each unblocks. This is the 'item 1' landing zone. - Absorb the post-split adversarial review (archived in gitignored .tmp-scratch) into tracked docs so the signal survives: add D7 (kind-aware defined floor; the kind-blind floor forces the example to pad — F1), D8 (.spec.ts test-glob collision), widen D2 to cover behavior.examples (F5), and §3a 'resolvable now' (R1/R2/H2-direction/D6). - DECISIONS.md: record R1/R2/R3 as PROPOSED base refinements (code already conforms). - docs/concept/07: fold in forward-looking acceptance criteria mapped across Slices 1-5. --- .../07-mvp-roadmap-and-open-questions.md | 39 ++ docs/concept/DECISIONS.md | 24 + plans/02-phase0-hardening.md | 547 ++++++------------ ...sion-resolution-and-base-reconciliation.md | 220 +++++++ 4 files changed, 469 insertions(+), 361 deletions(-) create mode 100644 plans/03-decision-resolution-and-base-reconciliation.md diff --git a/docs/concept/07-mvp-roadmap-and-open-questions.md b/docs/concept/07-mvp-roadmap-and-open-questions.md index 684d912..e9331e3 100644 --- a/docs/concept/07-mvp-roadmap-and-open-questions.md +++ b/docs/concept/07-mvp-roadmap-and-open-questions.md @@ -85,3 +85,42 @@ After the MVP loop works, let observed pain — not this roadmap — order the n - "is this NFR actually met in prod?" → begin the runtime-observation overlay (the `observed` delivery fact). The point of the principle-led core is that each of these slots in cleanly, without refactoring the laws in `01`. + +--- + +## 6. Forward-looking acceptance criteria (seeded by the post-Session-1 full-MVP review) + +Recorded here so the full-scope lens isn't lost; each is honesty-posture-aligned and maps to a slice's "done." +These came out of the Phase-0 hardening review and were routed here (rather than into that code-only plan) so they +land in the roadmap at the right altitude. Ordering reflects the synthesis's priority. + +- **① Authoring ergonomics — the headline forward risk; a named Slice-2 concern.** There is *no + authoring-ergonomics workstream* anywhere in `00`–`07` today (the MVP CLI is just `build`/`validate`), yet if + authoring feels heavy, authors (human **and** agent) avoid the system or overfit specs to satisfy tooling. The + first lever is **typed sections** (autocomplete + shape guardrails); then great error messages and `sdp validate + --watch`; later `sdp new spec` / `sdp explain`. Threads back to the anti-padding rule: make *dishonesty* fail + without rewarding low-signal filler (a floor to clear, never a quota to fill). +- **② Golden-graph fixture — at Slice 1; keep it distinct from `--check-clean`.** Adopt **both**, labeled + distinctly: a **determinism self-check** (`03` §2 — rebuild twice, assert **byte-identical**; a self-comparison, + **never** a diff against a committed `generated/` artifact, which is gitignored, L8) **and** a **correctness + oracle** (a committed `fixtures/order-management/expected/graph.json` — "did the extractor produce the *right* + graph," legitimate because it lives in `fixtures/`, not `generated/`). Make paths **repo-relative / POSIX**, and + decide consciously whether **line numbers** enter the golden (deterministic, but brittle to unrelated edits). +- **③ Derived-readiness banner in the MVP view — at Slice 4, but blocked on the open-questions-home fix (H2).** + *"Stated readiness: ready · Structural floor reached: defined · Problem: blocking open question."* Teaches the + core honesty concept (stated, then checked); cheaply enabled by a floor evaluator that reports *which* clause + fails. **Do not pull it forward before H2** — today the floor reads open questions from the wrong section, so the + banner would confidently display the wrong thing. +- **④ `implemented` is a UI hazard — at Slice 4, view-label only.** Model semantics are settled (DECISIONS MD-7: + binding/existence, never liveness). Keep the internal fact name `implemented` (it powers the `implemented ∧ + ¬ready` drift query), but render binding language in views: *"Implementation binding: present / Verifier binding: + present / Runtime observation: not tracked."* +- **`coverage-unknown` — already a settled model commitment (MD-7 / §4 above); make it Slice-4 acceptance.** + File-level blast-radius reports changed-but-unanchored files as `coverage-unknown`, never silently + under-reporting. The only add is promoting it from design note → explicit Slice-4 acceptance criterion. +- **The MVP acceptance checklist, mapped across Slices 1–5:** spec extraction · anchor extraction · claim honesty · + readiness honesty · delivery facts · traceability · determinism · view — with three sharpenings: (a) *"ready spec + with blocking open questions fails"* is the regression test to add **after** H2 (locked early by an H8 fixture + stub); (b) extend *"rejects non-static envelope fields"* to *"the example fixture survives static extraction with + **no dropped sections**"* (envelope is clean; sections were the H1 risk, now fixed); (c) *"extracts one api + anchor"* is the H10 gap (Slice 2). diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 8fe4c9c..8b0783b 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -99,6 +99,30 @@ derived"), not reversals of direction. home for approval provenance is **signed git tags** (identity + approved-at, already in git) — not a new authored primitive. +### Proposed base refinements (R-series) [PROPOSED 2026-06-07 · awaiting grilling ratification] +**Context.** The post-Session-1 founding-ideation review surfaced two wording imprecisions in the ratified base. +The **code already conforms** in both cases; these are language tightenings, so per the working discipline +(terminology is ratified — *flag*, don't silently edit) they are recorded here as **PROPOSED** and routed to the +fresh decision-resolution session (`plans/03-decision-resolution-and-base-reconciliation.md` §4) to ratify and +apply against `docs/concept/ubiquitous-language.md`. They are **not** model changes. +- **R1 — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4).** §2 says anchors + carry "identity only," while §4's `claim` table calls `anchored` "a human binding" — and a binding (it emits a + `satisfies`/`verifies` edge) is more than bare identity. Proposed unified phrasing: *"an anchor says 'this code + location is the implementation/test binding for this Spec ID'; binding only, never system-truth content — never + behavior, rationale, readiness, acceptance criteria, or delivery facts."* The code already conforms (anchors hold + only `id`/`label`/target; `@ts-expect-error` proves the rest is rejected). +- **R2 — "no consumer reads source directly" → permit source *links*, forbid independent re-parsing.** The + principle (`03`/`05`/`06`) is right, but a Design Review linking to source locations *recorded in the graph* is + legitimate. Proposed: *"Consumers may link to source locations recorded in the graph; consumers must not + independently parse source to derive their own model."* Matters when the Slice-4 Design Review lands. +- **R3 — reconcile `04`'s `specTest` signature to binding-only.** `04` §2 documents `specTest(id, { verifies, + run })` with an executing `run` callback; the implementation (`src/model/anchors.ts`) is identity-only + (`{ id, label?, verifies }`, **no `run`**). The code is the *more* faithful one: a binding anchor carrying + `run` would couple the graph binding to execution, contradicting "the graph records that an enabled verifier + *exists*, never that it ran" (§4b / MD-7). So this is a **doc fidelity bug, not a code gap** — reconcile `04` + down to a binding-only signature. Same pattern as R1/R2 (the code already conforms). Surfaced by the + post-Session-1 adversarial review (F6). + ### Scope note — relationship to the prior plan & brief **Adopt-the-nouns reversal (kept for the record).** An earlier brief held "the Protocol is **not** a PM tool; do **not** adopt SAFe/PM ladder names." MD-2 **reverses** that: the commercial Studio means users must not relearn delivery diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index ffc4b6b..34afe30 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -1,392 +1,217 @@ -# Phase 0 Hardening — Fidelity & Simplification Pass (post-Session-1 review) - -> **Status: DRAFT · iterating to implementation-ready.** This plan records the findings of the Session 1 -> implementation review and turns them into actionable work items. It is **not** locked: several items carry an -> **open decision** (marked ⊳) that we will resolve together before this is execution-ready. Sequencing here is a -> first cut, not a contract. -> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 committed (`eb6bf2a`); fully -> green (`npm run check`: typecheck ×2, lint, format, 43 tests, build). -> -> **Layered review provenance.** (1) a *fresh* implementation review of the committed code; (2) the *planning -> session* that formed the pre-plan context before the first line of code, framed by the full MVP; (3) the -> *founding-ideation* review (the ChatGPT web session that seeded the project), framed by the **whole MVP scope**; -> then (4) a *planning-session synthesis* that re-read the shipped code and sorted (3) into act / already-handled / -> extend buckets. This revision absorbs (3) **and** (4): it confirms the concept spine, hard-reinforces the -> anti-bloat thesis, supplies a should-fail/should-pass fixture set, seeds forward-looking acceptance criteria + two -> concept-wording tightenings, **elevates authoring ergonomics to the headline forward risk**, and adds one new -> tracer-bullet gap (H10). Layering is deliberate while the repo is mostly code-free: improvisation is fine until -> there is enough code + graph state for the Design Review to do this in-system (the same principle the product is -> built on). - -This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the extractor -can safely build on."* It is a **hardening pass on the already-implemented Phase 0**, not new scope. It deliberately -stays inside the Slice 0 boundary — **no extractor, no graph emission, no graph gate** — except where a fix must be -*designed now* so the extractor (Slice 1) doesn't inherit a landmine. +# Phase 0 Hardening — Fidelity & Simplification Pass (post-Session-1) + +> **Status: EXECUTION · Wave A done; Wave B blocked on plan 03 decisions.** This is the **code +> hardening** half of the post-Session-1 review — fidelity corrections and net complexity reductions on +> the already-implemented Phase 0. The decisions these fixes' siblings depend on, the concept-base +> wording tightenings, and the roadmap acceptance criteria were **split out of this plan** into their +> proper homes (see "Where the rest went," below). +> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 (`eb6bf2a`) + Wave A +> green (`npm run check`: typecheck ×2, lint, format, **47 tests + 2 todo**, build). + +This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the +extractor can safely build on."* It stays inside the Slice 0 boundary — **no extractor, no graph +emission, no graph gate** — except where a fix must be *designed now* so the extractor (Slice 1) +doesn't inherit a landmine. + +**Where the rest went (the re-home).** This plan was originally one document fusing three kinds of work; +they now live where each gets the right rigor: +- **Open decisions (D1–D4, D6)** → `plans/03-decision-resolution-and-base-reconciliation.md` — the + fresh-session grilling agenda. Wave B below is **blocked on these**. +- **Concept-base wording (R1, R2)** → `docs/concept/DECISIONS.md` (recorded **PROPOSED**), to ratify in + the grilling session. +- **Forward-looking acceptance criteria** → `docs/concept/07` §6 (mapped across Slices 1–5). --- -## §0 — How this fits +## §0 — Framing commitments (carried from `AGENTS.md` + the founding-ideation review) -Session 1 implemented Slice 0 (the protocol as typed code) + bootstrap + the `checkout-v1` tracer bullet, and it is -green and largely faithful to the ratified base. The review found a through-line of issues that are **fidelity -corrections and net complexity reductions**, not polish — so they belong *before* the design ossifies under the -extractor, while it is still cheap to move. - -Three framing commitments carried from `AGENTS.md` (and reinforced by the founding-ideation review): - -- **This is not over-perfection.** Every "now" item below is either (a) a divergence from the ratified base - (`docs/concept/ubiquitous-language.md`, `02`, `04`, `05`) or (b) a *removal* of complexity that masks the design. - General code-quality/type-coverage polish stays deferred per the working discipline ("let the design breathe"). -- **Tracer-bullet discipline.** The example must keep forcing the DSL to be usable — and, from Slice 1 on, must - survive *static* extraction. One finding (H1) is precisely where the current example would break that. -- **Phase 0 stays aggressively small.** The founding review's blunt warning: *"Do not build a beautiful protocol - framework in Phase 0. Build the smallest executable conformance contract that Slice 1 needs."* Phase 0 is exactly - the place scope quietly expands. This is the full-scope lens on H5 (the floor validator is already 453 lines for - three checks) — and the standing test against every "now" item: does it shrink the contract or grow it? - Corollary (`05` §3, P4): a readiness floor is **a floor to clear, never a quota to fill** — validators must make - *dishonesty* fail without training authors or agents to pad specs with low-signal content. - ---- +- **This is not over-perfection.** Every item is either (a) a divergence from the ratified base + (`ubiquitous-language.md`, `02`, `04`, `05`) or (b) a *removal* of complexity that masks the design. + General code-quality/type-coverage polish stays deferred ("let the design breathe"). +- **Tracer-bullet discipline.** The example must keep forcing the DSL to be usable — and, from Slice 1 + on, must survive *static* extraction. H1 (done) was exactly where it would have broken that. +- **Phase 0 stays aggressively small.** *"Build the smallest executable conformance contract that Slice + 1 needs."* The standing test against every item: **does it shrink the contract or grow it?** A + readiness floor is **a floor to clear, never a quota to fill** (`05` §3, P4). ## §1 — Baseline: what is correct and must not regress -Recorded so the hardening pass is measured against it: - -- Trust-model boundaries intact: no extractor, no `graph.json`, no graph gate; delivery facts are **derived-only - types**; anchors are **identity-only** (`@ts-expect-error` fixtures prove they reject readiness/intent/facts). -- `graph/schema.ts` is faithful and inert: `nodeType` vs `specKind` split (`03`/`05`), `claim` on every node/edge, +- Trust-model boundaries intact: no extractor, no `graph.json`, no graph gate; delivery facts are + **derived-only types**; anchors are **identity-only** (`@ts-expect-error` fixtures prove they reject + readiness/intent/facts). +- `graph/schema.ts` faithful and inert: `nodeType` vs `specKind` split, `claim` on every node/edge, delivery facts on `PrimitiveNode`, authored vs derived edge types separated. - IDs (`02` §5): grammar + branding only, namespace policy correctly deferred. - Relations (`02` §6): direction + `claim:"declared"` exactly per the table; verb-form names correct. -- Core `src/` is domain-neutral (zero order/checkout names). The example's two-edge verifier semantics (test - `verifies` the example; example `verifies` the parent) match `02` "Verifier semantics" precisely. -- `AuthoredModel` is honestly fenced (doc-comment + `deferredInSession1` clauses). +- Core `src/` is domain-neutral (zero order/checkout names). The example's two-edge verifier semantics + match `02` "Verifier semantics" precisely. +- `AuthoredModel` honestly fenced (doc-comment + `deferredInSession1` clauses). -**Regression gate for this pass:** `npm run check` stays green; all current invariants above still hold. +**Regression gate:** `npm run check` stays green; all invariants above still hold. ---- +## §2 — The root tension (drives the Wave-B fidelity fixes) -## §2 — The root tension (drives H2–H5) - -`src/model/sections.ts:14-23` types every section as `Record`. The ratified design (**L9**, -`02` §3) intends **typed-but-optional** sections; **P7** ("types describe *shape*; validators decide -*completeness*") governs *which fields are required*, not *whether fields are typed at all*. Open bags collapse -"optional" into "unknown," with three concrete costs: - -1. **The tracer bullet proves less than it claims.** The example typechecks against `unknown`, so "it compiles" - says nothing about the DSL capturing section *shape* — the session's headline goal. -2. **The authoring-shape honesty check (`05` #5) is bypassable.** Verified: the envelope rejects a top-level - `implemented: true` (excess-property check), but `behavior: { "has-verifier": true }` **typechecks** — a - hand-authored delivery fact hides one level down, and no validator catches it in Session 1. -3. **Validators must string-probe at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a section - field name silently passes the floor. - -⊳ **Open decision D1 — how much to type now.** Recommendation: type the **five sections the floors and the -extractor depend on** — `intent`, `behavior`, `constraints`, `model`, `verification` — and leave `design` / -`decision` / `ui` as open bags so the unsettled surfaces keep breathing. This closes H2/H3/H4 and the honesty -bypass for object literals, without over-committing the volatile sections. (Alternatives: type all eight now; or -type none and add a runtime authoring-shape validator instead. To resolve.) - -> **D1 is bigger than honesty/floors — it is the linchpin of the headline MVP risk (synthesis, bucket A/C).** The -> founding review's strongest contribution is that *authoring ergonomics, not graph theory, is the biggest risk*: -> if authoring feels heavy, authors (humans **and** agents) avoid the system or overfit specs to satisfy tooling. -> Untyped sections give **no autocomplete and no shape guardrails** — precisely the heavy-authoring loop. H1 is the -> first symptom already in the repo: an agent reached for `Object.fromEntries([...]) as Record` -> instead of a plain literal — an author fighting an untyped surface. So typing the five sections is not only the -> cheapest path to H2/H3/H4 and the honesty fix; it is the **single highest-value lever on adoption**. (This is why -> authoring ergonomics is elevated to a named Slice-2 concern in §4c.) +`src/model/sections.ts` types every section as `Record`; the base (**L9**, `02` §3) +intends **typed-but-optional** sections. This is the linchpin — its resolution (**D1**) is owned by +**plan 03 §2–§3**, where the full framing and the authoring-ergonomics rationale live. Wave B cannot +execute its field-shape fixes until D1/D2 land there. --- -## §3 — Work items - -Severity: 🔴 act before Slice 1 · 🟠 fidelity fix · 🟡 simplification · ⚪ hygiene. -Disposition: **NOW** (this pass) · **DEFER** (named, later). - -### 🔴 H1 — Make the example statically extractable (P5) · **NOW** - -- **Finding.** Both scenario specs build `behavior` via a runtime call: - `examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts:5` and - `…/create-order-invalid-cart.spec.ts:5` use `const thenKey = "then"` + - `Object.fromEntries([...]) as Record`. -- **Why it matters.** `04` §1 (the static-data constraint, P5): a spec file is "a JSON file that TypeScript happens - to validate"; the Slice 1 `ts-morph` extractor **reifies without executing**. `Object.fromEntries(...)` cannot be - statically evaluated. Per the two-tier rule, a non-static *section* is **silently dropped with a warning** (L3) — - so under Slice 1 these scenarios lose their `given/when/then`, and the `example` readiness floor (which requires - them at `defined`+) flips them from valid → honesty violation. The tracer bullet would mine the very extractor it - exists to de-risk. -- **Verified root cause.** The dynamic form buys nothing: a plain object literal with a `then` key **lints clean - (eslint exit 0)** and typechecks. The `thenKey`/`Object.fromEntries` indirection is gratuitous. -- **Change.** Replace both with plain static object literals (no `thenKey`, no `Object.fromEntries`). -- **Files.** the two scenario `*.spec.ts` only. -- **Verify.** `tsc --noEmit -p tsconfig.examples.json` green; `npm test` green; manual: no function calls remain in - `examples/**/specs/**`. -- **Forward hook (DEFER, but name now).** Slice 1's "done" must include *the example survives static extraction - byte-for-byte*; the `sdp/spec-static` lint rule (`04` §1) is the earlier backstop. +## §3 — Wave A (decision-free) — ✅ DONE -### 🟠 H2 — Honesty check points at the wrong section (blocking open questions) · **NOW** - -- **Finding.** `src/validate/validators.ts:199-206` (`hasNoBlockingOpenQuestions`) and - `src/validate/readiness-floor.ts:91,105` read `spec.design.openQuestions` / `spec.decision.openQuestions`. The - canonical home is **`intent.openQuestions`** (`02` §3: "openQuestions may be flagged `blocking` to prevent stating - a readiness past `defined`"; the `04` worked example puts it under `intent`). `design`/`decision` do not - canonically carry open questions. -- **Impact.** A doc-following author who flags a `blocking` question in `intent.openQuestions` can still state - `defined`/`ready` and the honesty check **won't fire** — the marquee differentiator aimed at the wrong target. - (Note: the Session-1 pre-plan introduced this path; the implementation faithfully followed the plan. It is a - plan↔concept drift to correct at the source.) -- **Change.** Read open questions from `intent.openQuestions`; update the floor data `authoredPaths` to match. With - a typed `IntentSection` (D1) this predicate shrinks to a few lines. -- **Files.** `validators.ts`, `readiness-floor.ts`; tests in `test/readiness.test.ts` / `test/validators.test.ts`. -- **Verify.** New test: a spec with `intent.openQuestions:[{blocking:true}]` stating `defined` **fails**; with - `blocking:false` (or absent) **passes**. - -### 🟠 H3 — `constraints` must be an array, not a single object · **NOW** - -- **Finding.** `src/model/sections.ts:18` types `constraints?: ConstraintsSection` (one `Record`); `02` §1 - specifies `constraints?: ConstraintSection[]`. A spec can be bounded by several NFRs (perf + security + …); a - single object can't express that. `validators.ts:139-165` already defends against **both** array and record forms - — a tell that the shape was unsettled. -- **Change.** `constraints?: ConstraintSection[]` (typed per D1: `{ flavor?, statement, target?, measurableBy? }`, - all optional except where the floor requires). Update the example (`order-latency-constraint.spec.ts`) to author - an array. **Simplify** the validator: drop the dual array/record handling — one shape only. -- **Files.** `sections.ts`, `order-latency-constraint.spec.ts`, `validators.ts`. -- **Verify.** `constraint` floor test still passes; validator no longer branches on `Array.isArray`. +Quick, contract-shrinking, reversible wins. All landed and verified; `npm run check` green. + +### ✅ H1 — Make the example statically extractable (P5) +- **Was.** Both scenario specs built `behavior` via `Object.fromEntries([...]) as Record` (+ a `thenKey` indirection) — not statically evaluable, so the Slice-1 `ts-morph` extractor + (which reifies without executing, `04` §1) would silently drop the section and flip the `example` + floor from valid → honesty violation. +- **Done.** Replaced both with plain static object literals. +- **Verified.** `grep -rn "Object.fromEntries\|thenKey\|as Record" examples/` → none; example typecheck + + tests green. +- **Forward hook (DEFER, named).** Slice 1's "done" must include *the example survives static extraction + byte-for-byte*; the `sdp/spec-static` lint rule (`04` §1) is the earlier backstop. -### 🟠 H4 — Referential integrity is blind to section-embedded refs · **DECIDE (likely DEFER to Slice 1)** - -- **Finding.** `validateDanglingReferences` (`validators.ts:363-414`) checks relations, pack members, `modelRefs`, - anchors — but not refs inside sections. Yet `create-order.spec.ts:22-26` puts real refs there - (`behavior.rules:[ref(...)]`, `behavior.examples:[ref(...)]`). A typo there escapes the check. `05` §2.1 wants - *every* referenced ID to resolve. -- **Why deferred-ish.** Session 1's dangling-ref scope was explicitly relations/packs/anchors. The proper fix - belongs with the extractor, which must know *structurally* where refs can live — impossible while sections are - `unknown` (this is a direct consequence of §2). Typing `behavior` (D1) makes both the check and the extractor - tractable. -- ⊳ **Open decision D2 — modeling of `behavior.rules`.** The docs model `behavior.rules` as **prose strings** and - only `examples` as refs; the example currently puts refs in `rules` *and* promotes the same rules to standalone - `kind:"rule"` specs (`refines` create-order) — the linkage exists twice, once invisibly. Resolve the - inline-vs-promote duality (`02` §3 "Section ⟷ kind"): pick prose-in-`rules` + relations for the promoted specs, - or a typed ref list with referential-integrity coverage. To resolve before typing `behavior`. -- **Change (when scheduled).** Extend referential integrity to typed ref-bearing section fields; align the example - to the resolved D2. - -### 🟡 H5 — Trim the readiness-floor validator (complexity masking a simple design) · **NOW (small)** - -- **Finding.** `validators.ts` is 453 lines for three tiny checks. Duplicate-IDs and dangling-refs are clean (~50 - lines); the bulk is readiness-floor machinery with a **two-sources-of-truth** smell, verified: - - Clause ids are enumerated in **four** places — the `readinessFloors` data (`readiness-floor.ts`), the - `ReadinessClauseId` union (`validators.ts:22-36`), the `toSupportedReadinessClauseId` switch - (`validators.ts:265-285`), and the `evaluateClause` switch (`validators.ts:224-255`). Add a clause → edit 3–4 - spots; miss one and `toSupportedReadinessClauseId` **silently skips it**. - - `authoredPaths` in the floor data is **decorative** — grep confirms it is referenced only by its own - declaration and by tests, **never by the evaluator**, which hardcodes a predicate per clause id. The "which - field does this clause check" fact lives in two places, free to drift (already visible: - `constraints.*.target` in the `defined` floor vs `constraints.target` in the overlay). -- ⊳ **Open decision D3 — which direction to collapse.** Either (a) make the table the single source of truth — the - evaluator reads `authoredPaths` + a few generic predicates (cleanest once sections are typed, D1), or (b) shrink - the table and let the evaluator be the spec, dropping the redundant union/switches. Recommendation: (a), because - typed sections make path-driven predicates safe and it kills the 4× enumeration. To resolve. -- **Verify.** Same floor test outcomes; clause-id list defined once; no decorative metadata. - -### 🟡 H6 — Simplify the tsup build; stop leaking a shebang onto the library entry · **NOW** - -- **Finding.** `dist/index.js` starts with `#!/usr/bin/env node` because `tsup.config.ts` applies `banner.js` to - **all** entries — contradicting the implementer's own recorded decision (`.sisyphus/notepads/.../decisions.md:1`: - "use a source-file shebang … so the library build stays clean"). The config compensates with an esbuild plugin to - strip the CLI's source shebang **and** an `onSuccess` hook to collapse the resulting doubled CLI shebang — an - elaborate dance around a wrong default. -- **Change.** Remove the `banner`; keep the source shebang only on `src/cli/sdp.ts`; delete the strip-plugin and the - normalize hook (or split the CLI into its own minimal tsup build). Net: less config, correct output. -- **Files.** `tsup.config.ts` (+ confirm `src/cli/sdp.ts` keeps its source shebang). -- **Verify.** `dist/index.js` has **no** shebang; `dist/cli/sdp.js` first line is exactly `#!/usr/bin/env node`; - `node dist/cli/sdp.js --help` exits 0. - -### ⚪ H7 — Hygiene · **MOSTLY RESOLVED** - -- **DONE (user):** `.tmp-scratch/` is now gitignored; `plans/` is un-ignored (tracked, the more logical setup now); - `.sisyphus/` is now fully gitignored going forward. -- **Still open — `.tmp-scratch/` is not prettier-ignored**, so `npm run check` (format:check) still trips on scratch - files locally. Add `.tmp-scratch/` to `.prettierignore`. (CI is unaffected — scratch is uncommitted.) -- **Note — already-committed `.sisyphus/` files** (`boulder.json` + notepads, from `eb6bf2a`) remain tracked despite - the new ignore; a `git rm --cached .sisyphus` would stop tracking them if that's wanted. (Open, low.) -- `vitest-test.mjs` wrapper exists only so `npm test -- --run x` doesn't double-`--run`. Optional: revert to - `"test": "vitest run"` unless the `-- --run` ergonomic is wanted. -- `package.json` lacks `description`/`license`/`repository` — fine until publish; note for the publish checklist. - -### 🟡 H8 — A should-fail / should-pass validator fixture suite (`05` §5) · **NOW** - -- **Finding.** The validators have good *exact-message* tests, but no systematic should-fail/should-pass fixture - set. `05` §5 ("Validator self-testing") asks each validator to ship fixtures so a regression that stops it firing - is itself caught. The founding review independently proposes nearly the same set as the Phase-0 "done" gate. -- **Why now.** It is cheap insurance, it is concept-sanctioned, and — critically — it is the lock that proves the - honesty fixes (§2 bypass, H2, H3) actually fire. Several fixtures map 1:1 onto findings, so authoring them *is* - the regression net for this whole pass. -- **Fixtures (active in Session-1 authored-layer scope):** +### ✅ H6 — Simplify the tsup build; stop leaking a shebang onto the library entry +- **Was.** `dist/index.js` started with `#!/usr/bin/env node` because `banner.js` applied to **all** + entries, compensated by an esbuild strip-plugin + an `onSuccess` shebang-normalise hook. +- **Done.** Removed the banner, the strip-plugin, and the hook; rely on esbuild preserving the source + shebang on the CLI **entry** only. +- **Verified.** `dist/index.js` first line is `// src/ids.ts` (no shebang); `dist/cli/sdp.js` has + exactly one `#!/usr/bin/env node`; `node dist/cli/sdp.js --help` exits 0. + +### ✅ H7 — Hygiene (`.prettierignore`) +- **Done.** Added `.tmp-scratch/**` to `.prettierignore` so `format:check` no longer trips on scratch. +- **Still open (low, optional).** Already-committed `.sisyphus/` files (from `eb6bf2a`) remain tracked + despite the ignore; `git rm --cached .sisyphus` would untrack them if wanted. `package.json` lacks + `description`/`license`/`repository` — note for the publish checklist. The `vitest-test.mjs` wrapper + is optional ergonomics. + +### ✅ H8 (active) — A should-fail / should-pass validator fixture suite (`05` §5) +- **Done.** `test/fixtures/authored-model.fixtures.ts` + `test/fixtures.test.ts` with the **active** + (decision-free) fixtures, each pinning one validator outcome: - `valid-minimal-idea-spec` → passes. - - `invalid-duplicate-id` → duplicate-ids error. - - `invalid-scoped-without-relation` → readiness-floor error. - - `invalid-defined-constraint-without-target` → readiness-floor error. - - `invalid-ready-with-blocking-question` → readiness-floor error **(locks H2 — must be `intent.openQuestions`)**. - - `invalid-hand-authored-delivery-fact-in-section` → **must fail once §2/D1 lands** (today it silently passes — - this is the bypass made executable). -- **Fixtures (deferred — they need the extractor/graph; name now, write at Slice 1+):** - `invalid-non-static-id` (P5 envelope tier) · `invalid-non-static-section` (P5 section tier, the H1 case) · - `invalid-hand-authored-satisfies-edge` · `invalid-ready-with-unresolved-dependency` · - `invalid-ready-with-target-below-defined`. -- **Files.** `test/fixtures/**` (invalid fixtures live in tests, never under `examples/checkout-v1`), wired into - `test/validators.test.ts`. -- **Verify.** Each active fixture asserts the expected validator id + finding; the deferred set exists as skipped/ - documented stubs so Slice 1 inherits the checklist. - -### 🟡 H9 — Lock the type-level honesty defenses with compile-time fixtures · **NOW (cheap)** - -- **Finding.** Two type defenses already work but are **untested**, so a future refactor could silently open them: - - The `Spec` envelope rejects a top-level hand-authored delivery fact (`spec({ …, implemented: true })` → - TS2353) — verified, untested. - - `Pack` rejects truth-bearing fields (`pack({ …, intent })` / `readiness` / `constraints` → TS2353) — verified, - untested. This is the founding review's #7 ("keep `Pack` truthless, but defend it with types"); the current - `Pack` shape already matches the suggested restrictive type exactly. -- **Change.** Add `@ts-expect-error` fixtures (alongside the existing anchor ones in `test/builders.typecheck.ts`) - for: `Spec` rejecting `implemented`/`has-verifier` at the envelope; `Pack` rejecting `intent`/`readiness`/ - `constraints`. Note explicitly that the **in-section** bypass (`behavior:{ "has-verifier": true }`) is *not* closed - by these — it is closed by typing sections (D1) — so the two items are tracked together. -- **Files.** `test/builders.typecheck.ts`. -- **Verify.** `npm run typecheck` consumes the new `@ts-expect-error`s. - -### 🟠 H10 — The example under-proves the generic-anchor claim · **DECIDE (pairs with Slice 2 anchor extraction)** - -- **Finding (synthesis, bucket C — new; missed by all prior passes).** The whole point of *generic* anchors is that - they bind **any** code location, not just classes (`00`/`04`). But the shipped example has only `impl` + `test` - anchors (`anchorImplementation` is namespace-locked to `impl:`; `specTest` to `test:`), and the docs' own example - (`04` §5) includes a route anchor `api:orders.post`. So the tracer bullet never exercises the genericity claim — - the strongest proof would be binding a **non-class location** (a route/endpoint). -- **Two layers to the gap:** - - **Example layer:** add an `api`/route anchor to `examples/checkout-v1` so the generic-binding claim is actually - demonstrated. - - ⊳ **DSL-shape question (D6):** the satisfies-anchor builder is locked to `impl:`. Generic binding implies the - "code → spec" (`satisfies`) anchor should accept the implementation-flavored code namespaces (`impl` / `api` / - `component`), with `specTest` (`verifies`) staying the `test:` variant. Decide: generalize `anchorImplementation` - to a `codeAnchor` over those namespaces, or add sibling builders. The `api:` namespace already parses (`ids.ts`); - only a builder + branded id is missing. -- **Why deferred-ish.** It pairs naturally with Slice 2 (anchors + `satisfies`/`verifies` edges). Naming it now keeps - the tracer bullet honest about what it proves. -- **Verify (when scheduled).** Example contains ≥1 non-`impl` code anchor; it extracts to a `satisfies` edge with - `claim:"anchored"` like any other. + - `invalid-duplicate-id` → `conformance/duplicate-ids`. + - `invalid-scoped-without-relation` → `honesty/readiness-floor` (`at-least-one-relation`). + - `invalid-defined-constraint-without-target` → `honesty/readiness-floor` + (`constraint-machine-readable-target`). +- **Gated stubs (inherited checklist, `it.todo`):** `invalid-ready-with-blocking-question` (flips active + with **H2**) · `invalid-hand-authored-delivery-fact-in-section` (flips active with **D1**). The + extractor-era fixtures (`invalid-non-static-id`, `invalid-non-static-section`, + `invalid-hand-authored-satisfies-edge`, `invalid-ready-with-unresolved-dependency`, + `invalid-ready-with-target-below-defined`) remain named for Slice 1+. + +### ✅ H9 — Lock the type-level honesty defenses with compile-time fixtures +- **Done.** Added `@ts-expect-error` fixtures in `test/builders.typecheck.ts`: the `Spec` envelope + rejects a top-level `implemented` / `has-verifier`; the `Pack` rejects `intent` / `readiness` / + `constraints`. `npm run typecheck` consumes them (an unused directive would fail the build, so they + *prove* the defenses fire). +- **Note (tracked with D1).** The **in-section** bypass (`behavior: { "has-verifier": true }`) is *not* + closed by these — it is closed only by typing sections (D1, plan 03) and locked by the gated H8 + fixture. --- -## §4 — Forward-looking decision (resolve before Slice 3, name now) - -⊳ **Open decision D4 — the `AuthoredModel` seam vs the one graph (P2).** The design says validators run over **the -one graph** ("no consumer maintains a parallel model", P2). When the extractor lands, either (a) these validators -migrate to consume `GraphSchema` and `AuthoredModel` retires, or (b) `AuthoredModel` stays as an explicit, -documented *pre-graph authoring lint*. Avoid two validation code paths drifting apart — H1 is already a preview of -authored-model-truth ≠ extracted-graph-truth. Pick a direction now; execute at Slice 1/3. - ---- +## §4 — Wave B (blocked on plan 03 decisions) — fidelity fixes, DECISION-GATED + +Execution-ready specs, held until the named decision lands in +`plans/03-decision-resolution-and-base-reconciliation.md`. **Sequence after D1/D2/D3.** + +### 🟠 H2 — Honesty check points at the wrong section (open questions) · gated on **D1** (+ home decision) +- **Finding.** `validators.ts` (`hasNoBlockingOpenQuestions`) and `readiness-floor.ts` read + `spec.design.openQuestions` / `spec.decision.openQuestions`. The canonical home is + **`intent.openQuestions`** (`02` §3; the `04` worked example puts it under `intent`). +- **Impact.** A doc-following author who flags a `blocking` question in `intent.openQuestions` can still + state `defined`/`ready` and the honesty check **won't fire** — the marquee differentiator aimed at the + wrong target. (Plan↔concept drift introduced by the Session-1 pre-plan; the implementation faithfully + followed it.) +- **Change.** Read open questions from `intent.openQuestions`; update the floor data `authoredPaths`. A + typed `IntentSection` (D1) shrinks the predicate to a few lines. +- **Files.** `validators.ts`, `readiness-floor.ts`; `test/readiness.test.ts` / `test/validators.test.ts`. +- **Verify.** Flip the gated H8 fixture `invalid-ready-with-blocking-question` to active: a spec with + `intent.openQuestions:[{blocking:true}]` stating `defined` **fails**; with `blocking:false`/absent + **passes**. + +### 🟠 H3 — `constraints` must be an array, not a single object · gated on **D1** +- **Finding.** `sections.ts` types `constraints?: ConstraintsSection` (one `Record`); `02` §1 specifies + `constraints?: ConstraintSection[]`. A spec can be bounded by several NFRs; a single object can't + express that. `validators.ts` already defends against **both** array and record forms — a tell the + shape was unsettled. +- **Change.** `constraints?: ConstraintSection[]` (typed per D1: `{ flavor?, statement, target?, + measurableBy? }`). Update `order-latency-constraint.spec.ts` to author an array. **Simplify** the + validator: drop the dual array/record handling — one shape only. +- **Files.** `sections.ts`, `order-latency-constraint.spec.ts`, `validators.ts`. +- **Verify.** `constraint` floor test still passes; validator no longer branches on `Array.isArray`. -## §4b — Concept-base refinements (proposed; route to `docs/concept`, not code) - -The founding review surfaced two wording imprecisions in the ratified base. The **code is already correct** in both -cases; these are language tightenings to consider against the base (terminology is ratified, so we *flag* rather -than silently edit — `AGENTS.md` working discipline). No code change. - -- ⊳ **R1 — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4).** The synthesis - pins this as a *mild internal inconsistency in the same doc*: `ubiquitous-language.md` §2 says anchors carry - "identity only," while §4's `claim` table calls `anchored` "a human binding" — and a binding (it emits a - `satisfies`/`verifies` edge) is more than identity. Suggested unified phrasing: *"an anchor says 'this code - location is the implementation/test binding for this Spec ID'; binding only, never system-truth content — never - behavior, rationale, readiness, acceptance criteria, or delivery facts."* Substance is already present and the - code already conforms (anchors hold only `id`/`label`/target; `@ts-expect-error` proves the rest is rejected) — - this is a wording harmonization, not a model change. -- ⊳ **R2 — "no consumer reads source directly" → permit source *links*, forbid independent re-parsing.** The - principle (`03`/`05`/`06`) is right, but a Design Review linking to source locations *recorded in the graph* is - legitimate. Suggested: *"Consumers may link to source locations recorded in the graph; consumers must not - independently parse source to derive their own model."* Matters when the Slice 4 Design Review lands. - -## §4c — Forward-looking acceptance criteria, seeded by the full-MVP review (route to `07`) - -Not Phase-0 work; recorded here so the full-scope lens isn't lost, to fold into the roadmap (`docs/concept/07`) and -the relevant slice's "done." Each is honesty-posture-aligned. Ordering reflects the synthesis's priority. - -- **① Authoring ergonomics — the headline risk; elevate to a named Slice-2 concern.** The synthesis calls this the - feedback's highest-value idea and notes there is *no authoring-ergonomics workstream anywhere in `00`–`07`* (the - MVP CLI is just `build`/`validate`). The first lever is **typed sections (D1)** for autocomplete + shape - guardrails; then great error messages and `sdp validate --watch`; later `sdp new spec` / `sdp explain`. Threads - back to the §0 anti-padding corollary: make dishonesty fail without rewarding low-signal filler. (Was framed as - Slice 5; the synthesis pulls the *typed-sections* half forward to Slice 2 because it is also D1.) -- **② Golden-graph fixture — at Slice 1; keep it distinct from `--check-clean`.** The synthesis corrects a - conflation of *determinism* and *correctness* — adopt **both**, labeled distinctly: - - **Determinism self-check** (`03` §2): rebuild twice, assert **byte-identical** — explicitly a self-comparison, - **never** a diff against a committed `generated/` artifact (`generated/` is gitignored, L8). - - **Correctness oracle:** a committed `fixtures/order-management/expected/graph.json` ("did the extractor produce - the *right* graph," not just the *same* graph) — legitimate because it lives in `fixtures/`, not `generated/`. - - Heed the sub-list: make paths **repo-relative / POSIX**, and decide consciously whether **line numbers** enter - the golden (deterministic, but they make the oracle brittle to unrelated edits). -- **③ Derived-readiness banner in the MVP view — at Slice 4, but blocked on H2.** *"Stated readiness: ready · - Structural floor reached: defined · Problem: blocking open question."* Teaches the core honesty concept (stated, - then checked), and is cheaply enabled by H5 (a floor evaluator that reports *which* clause fails is the banner's - substrate). **Do not pull it forward before H2** — today the floor reads open questions from the wrong section, so - the banner would *confidently display the wrong thing* (a UI proposal exposing the model bug). -- **④ `implemented` is a UI hazard — at Slice 4, view-label only.** Model semantics are already settled (§4b / - DECISIONS MD-7: binding/existence, never liveness). Keep the internal fact name `implemented` (it powers the - `implemented ∧ ¬ready` drift query), but render binding language in views: *"Implementation binding: present / - Verifier binding: present / Runtime observation: not tracked."* -- **`coverage-unknown` — already a settled model commitment; make it Slice-4 acceptance.** Per DECISIONS MD-7 / - `07` §4 this is *decided*, not open: file-level blast-radius reports changed-but-unanchored files as - `coverage-unknown`, never silently under-reporting (honest "impact is bounded" vs false "impact is complete"). The - only add is promoting it from design note → explicit Slice-4 acceptance criterion. -- **Adopt the founding review's MVP acceptance checklist** (spec extraction · anchor extraction · claim honesty · - readiness honesty · delivery facts · traceability · determinism · view) as the roadmap's acceptance suite, mapped - across Slices 1–5 — with three synthesis sharpenings: (a) *"ready spec with blocking open questions fails"* is the - regression test to add **after** H2 (and is locked early by H8); (b) extend *"rejects non-static envelope fields"* - to *"the example fixture survives static extraction with **no dropped sections**"* (envelope is clean today; - sections aren't — H1); (c) the checklist's *"extracts one api anchor"* is the H10 gap. +### 🟡 H5 — Trim the readiness-floor validator (complexity masking a simple design) · gated on **D3** +- **Finding (verified).** `validators.ts` is 453 lines for three checks. Clause ids are enumerated in + **four** places — `readinessFloors` data, the `ReadinessClauseId` union, the + `toSupportedReadinessClauseId` switch, and the `evaluateClause` switch (add a clause → edit 3–4 spots; + miss one and it **silently skips**). `authoredPaths` is **decorative** — referenced only by its + declaration + tests, never by the evaluator, so the "which field does this clause check" fact lives in + two places, free to drift. +- **Change.** Per **D3** (recommendation: table-as-single-source-of-truth) — the evaluator reads + `authoredPaths` + a few generic predicates, killing the 4× enumeration. Safe once sections are typed. +- **Verify.** Same floor test outcomes; clause-id list defined exactly once; no decorative metadata. + +### 🟠 H4 — Referential integrity is blind to section-embedded refs · gated on **D1 + D2**; likely Slice 1 +- **Finding.** `validateDanglingReferences` checks relations, pack members, `modelRefs`, anchors — but + not refs inside sections. Yet `create-order.spec.ts` puts real refs there + (`behavior.rules:[ref(...)]`, `behavior.examples:[ref(...)]`). A typo there escapes the check; `05` + §2.1 wants *every* referenced ID to resolve. +- **Why deferred-ish.** The proper fix belongs with the extractor, which must know *structurally* where + refs can live — impossible while sections are `unknown`. Typing `behavior` (D1) + resolving the + `behavior.rules` shape (**D2**) makes both the check and the extractor tractable. +- **Change (when scheduled).** Extend referential integrity to typed ref-bearing section fields; align + the example to the resolved D2. + +### 🟠 H10 — The example under-proves the generic-anchor claim · gated on **D6**; pairs with Slice 2 +- **Finding.** *Generic* anchors should bind **any** code location, but the example has only `impl` + + `test` anchors; the docs' own example (`04` §5) includes a route anchor `api:orders.post`. The tracer + bullet never exercises the genericity claim — the strongest proof is binding a **non-class location** + (a route/endpoint). +- **Change (when scheduled).** Add an `api`/route anchor to `examples/checkout-v1` under the DSL shape + resolved in **D6**; it should extract to a `satisfies` edge with `claim:"anchored"` like any other. + +### Carried review backlog (post-split adversarial review · small, ride Wave B / Slice 1) + +Captured so the signal survives — the review lives in gitignored `.tmp-scratch/`. None block the split; +each is small, decision-free, or doc-level. +- **F2 — `ref` is a spec-only brand wearing a generic name.** `ids.ts` exports `specId as ref`, so + `ref()` rejects `pack:` / `doc:` targets. Harmless today (every call site wants a spec); add a doc + note, and revisit when `doc:`-target relations (`decidedBy` → external ADR) / pack-targeting arrive. +- **F3 — `validateAuthoredModel` mislabels its aggregate `family`.** It returns `family:"conformance"` + but folds in `honesty` readiness-floor findings (individual findings carry the correct family). The two + families are load-bearing (`05` §1); give the aggregate a neutral / no single family. Tiny. +- **F4 — `modelRefs` integrity doesn't enforce `kind:"model"`.** `02` §3 / `05` §4 say `modelRefs` + "always points at standalone `kind:"model"` specs," but `validateDanglingReferences` only checks the + ref *resolves*. Needs the kind — likely Slice-1/3 (graph) territory; named so prose doesn't + over-promise vs. the check. +- **Cross-refs (owned elsewhere).** F1 → **D7** (plan 03, base §6 refinement — de-pads the example and + the H8 constraint fixture in Wave B). F6 → **R3** (`DECISIONS.md`, doc reconciliation). **H2's + *direction* is determined** (`intent.openQuestions`, review §3.3) — it can be decoupled from D1 and + executed independently in Wave B. **F7 — DONE**: the H9 envelope-defense caveat (excess-property / + inline-literal scope) is now recorded in `test/builders.typecheck.ts`. --- ## §5 — Explicitly out of scope (still deferred) -Unchanged from the roadmap; this pass does **not** introduce any of them: the `ts-morph` extractor · `graph.json` / -graph emission · the graph-level validator gate · `--check-clean` · reader / views / Design Review · architecture -rules · custom team rules · `--lenient` · derived-readiness banner · runtime `observed` path · MCP surface · -self-hosting the Protocol's own repo. Full typing of `design`/`decision`/`ui` sections stays deferred per D1. +The `ts-morph` extractor · `graph.json` / graph emission · the graph-level validator gate · +`--check-clean` · reader / views / Design Review · architecture rules · custom team rules · `--lenient` +· derived-readiness banner · runtime `observed` path · MCP surface · self-hosting the Protocol's own +repo. Full typing of `design`/`decision`/`ui` sections stays deferred per D1. ---- +## §6 — Sequencing -## §6 — Sequencing (first cut — to iterate) - -A loose ordering, not a contract; we will refine once D1–D4 are settled. - -1. **Quick, decision-free wins:** H1 (static example), H6 (tsup), H9 (compile-time honesty fixtures), the remaining - H7 bit (`.prettierignore` for `.tmp-scratch`). -2. **Regression net:** H8 — author the active should-fail/should-pass fixtures now (all except the in-section - delivery-fact one, which is gated on D1); stub the deferred extractor-era fixtures. -3. **Resolve D1** (how much to type) → then H3 (`constraints` array), the typed `intent`/`behavior` shapes, and the - H8 `invalid-hand-authored-delivery-fact-in-section` fixture flips to failing. -4. **With typed sections in place:** H2 (open-questions home) and H5 (collapse the floor validator, D3). -5. **Resolve D2** → H4 (section-ref referential integrity), likely folded into the Slice 1 extractor work. -6. **Resolve D4** as a written direction before Slice 3; **R1/R2** routed to `docs/concept` (no code). §4c items - routed to `07`. No code this pass for §4b/§4c. - -**Done gate for the pass:** `npm run check` green; §1 baseline invariants intact; the example contains only static -literals; the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for typed sections **and** locked by -the H8 fixture; the floor clause-id list is defined exactly once; `dist/index.js` carries no shebang; the H8 active -fixture suite passes; H9 `@ts-expect-error` honesty fixtures are in place. - ---- +1. **Wave A** — ✅ done (H1, H6, H7, H8-active, H9). +2. **Resolve decisions** — the fresh grilling session (`plans/03`): D2 → D1 → D3, plus D4 (direction) and + D6 (shape); ratify R1/R2 on the base. +3. **Wave B** — with typed sections in place: H3, then H2 (+ flip its gated H8 fixture), then H5/D3, then + H4; H10 with Slice-2 anchor extraction. -## §7 — Open decisions to resolve (collected) - -| # | Decision | Recommendation | -|---|---|---| -| **D1** | How much to type sections now | Type `intent`, `behavior`, `constraints`, `model`, `verification`; leave `design`/`decision`/`ui` open | -| **D2** | `behavior.rules` — prose vs ref list; inline-vs-promote duality | Resolve `02` §3 "Section ⟷ kind" before typing `behavior` | -| **D3** | Collapse direction for the floor validator | Table-as-single-source-of-truth (path-driven predicates) once D1 lands | -| **D4** | `AuthoredModel` future vs the one graph (P2) | Decide migrate-to-`GraphSchema` vs documented pre-graph lint; execute Slice 1/3 | -| **R1** | Tighten "anchor = identity only" → "binding assertion only" (`§4b`) | Adopt against the ratified base; code already conforms | -| **R2** | Tighten "no consumer reads source" → links-ok, re-parse-no (`§4b`) | Adopt before Slice 4 Design Review | -| **D5** | Pull the derived-readiness banner into the MVP view? (`§4c`) | Yes if cheap — but **only after H2**; H5 already produces the substrate | -| **D6** | Generic-anchor DSL shape — `codeAnchor` over `impl`/`api`/`component` vs sibling builders (`H10`) | Generalize the `satisfies` anchor; pairs with Slice-2 anchor extraction | -| (H7) | Untrack already-committed `.sisyphus/` files? | `git rm --cached` if runner state shouldn't stay in history | +**Done gate for Wave B:** the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for +typed sections **and** locked by the (now-active) H8 fixture; open questions read from +`intent.openQuestions`; `constraints` is an array; the floor clause-id list is defined exactly once; the +example still contains only static literals; `npm run check` green. diff --git a/plans/03-decision-resolution-and-base-reconciliation.md b/plans/03-decision-resolution-and-base-reconciliation.md new file mode 100644 index 0000000..9e9f870 --- /dev/null +++ b/plans/03-decision-resolution-and-base-reconciliation.md @@ -0,0 +1,220 @@ +# Session 3 (next) — Decision Resolution & Base Reconciliation (the grilling agenda) + +> **Status: PLAN-ONLY · the agenda for a FRESH grilling session.** This document collects the open +> decisions that the post-Session-1 review surfaced and frames them for resolution **against the +> ratified base** (`docs/concept/ubiquitous-language.md`), in dependency order. It deliberately does +> **not** pre-resolve them — that is the grilling session's job. +> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 + the Phase-0 +> hardening **Wave A** committed and green (see `plans/02-phase0-hardening.md`). +> +> **Why this exists.** Splitting the post-Session-1 work into three homes was the unlock: the +> *execution* of the decision-free fixes lives in plan 02 (Wave A, done); the *decisions* those fixes' +> siblings depend on live **here**; the concept-wording and roadmap items were routed to +> `docs/concept` (see §4, §6). This separation is what lets the decision work happen in a clean, +> fresh session rather than tangled into an execution diff. +> +> **Absorbed review.** This agenda absorbs the **post-split adversarial review** (a fresh-session report, +> archived in gitignored `.tmp-scratch/04-post-split-adversarial-review.md`; its durable findings are +> folded here so they survive in tracked form). Its highest-value contribution is **D7** (the kind-blind +> floor forces the example to pad); it also **widens D2** (F5), adds **D8** (the `.spec.ts` collision), +> proposes **R3** (F6), and assesses several items as **resolvable now** (§3a). + +--- + +## §0 — Why a separate, fresh session (not a continuation) + +- **Grilling needs a fresh context.** Resolving these decisions well means stress-testing each against + the ratified language with full attention — the `grill-with-docs` discipline (challenge the plan + against the domain model, sharpen terminology, update `docs/concept` + `DECISIONS.md` inline as + decisions crystallise). A fresh session is a hard requirement for that. +- **This is design-on-the-base, not coding.** The pivotal decision (D1) *looks* like a TypeScript + choice but is a **representation of base §2/§3**. It must be settled on the base first; the types are + then a faithful projection, not a fresh source of truth. + +## §1 — The method (base-first, not code-first) + +The standing trap: typing a section in code *before* its canonical shape is settled in the base bakes +any drift into the type system, where it is expensive to move. So the method is strict: + +1. **Settle the canonical section shapes on the base** (§2/§3 of `ubiquitous-language.md`): what fields + each floor/extractor-bearing section *has*, and which are required by which readiness floor (P7: + *types describe shape; validators decide completeness*). +2. **Reconcile wording** (R1/R2, §4) against the base in the same pass — terminology is ratified, so we + ratify changes, never silently edit. +3. **Record the decision** in `DECISIONS.md` (an `MD-8` / D-series entry with rationale). +4. **Only then** does plan 02's Wave B type the code as a projection of the now-settled base. + +Corollary (the §0 anti-bloat thesis, carried from plan 02): every resolution must **shrink the +contract or hold it flat**, never grow it. A readiness floor is *a floor to clear, never a quota to +fill* — type sections to give authors guardrails + autocomplete, not to train them to pad specs. + +## §2 — The root tension (carried from plan 02 §2 — the driver behind D1–D3) + +`src/model/sections.ts` types every section as `Record`. The base (**L9**, `02` §3) +intends **typed-but-optional** sections. Open bags collapse "optional" into "unknown," at three costs: + +1. **The tracer bullet proves less than it claims** — the example typechecks against `unknown`, so + "it compiles" says nothing about the DSL capturing section *shape* (the session's headline goal). +2. **The authoring-shape honesty check is bypassable** — `behavior: { "has-verifier": true }` + typechecks today and no validator catches it (verified; locked by a gated H8 fixture in plan 02). +3. **Validators string-probe at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a + section field name silently passes the floor. + +> **The headline forward risk is authoring ergonomics, not graph theory.** If authoring feels heavy, +> authors (human **and** agent) avoid the system or overfit specs to satisfy tooling. Untyped sections +> give no autocomplete and no shape guardrails — the heavy-authoring loop. (H1 was the first symptom in +> the repo: an agent reached for `Object.fromEntries(...) as Record` instead of a +> plain literal — fixed in Wave A.) So typing the floor-bearing sections is both the honesty fix **and** +> the single highest-value adoption lever. This is why §4c routes "authoring ergonomics" to `07` as a +> named Slice-2 concern. + +## §3 — Open decisions, in dependency order + +``` +D2 (behavior.rules: prose vs ref list?) ──┐ +canonical section shapes (base §2/§3) ──┴──► D1 (type which sections, how) + │ + ┌─────────────────────────┼───────────────────────────────┐ + D3 (collapse floor) H2/H3 field shapes H8 gated fixtures flip active +D4 (AuthoredModel vs the one graph) — write a direction now, execute at Slice 1/3 +D6 (generic-anchor DSL shape) — base-determined; record as decided (§3a) +D7 (kind-aware `defined` floor) — sequences with D1; de-pads the example ← review F1 +D8 (.spec.ts file-extension collision) — Representation-level; before adopters ← review 1.3 +``` + +### D2 — prose-vs-ref duality across `behavior.rules` AND `behavior.examples` (gates typing `behavior`) +- **Tension.** The docs model `behavior.rules` as **prose strings** and `examples` as refs; the example + currently puts **refs** in `rules` *and* promotes the same rules to standalone `kind:"rule"` specs + (`refines` create-order) — the linkage exists twice, once invisibly. **The same duality is live in + `behavior.examples`** (review F5): `create-order.spec.ts` puts **refs** in `examples`, while + `create-order-valid-cart.spec.ts` puts **prose** in `examples` *and also* carries structured + `given/when/then` (the prose entry is redundant with its own GWT). So `behavior.examples` is used two + incompatible ways in the same example. +- **Resolve** the inline-vs-promote duality (`02` §3 "Section ⟷ kind") for **both** fields: prose + + relations to promoted specs, or typed ref lists with referential-integrity coverage. Must resolve + before `behavior` is typed (D1), or typing it ossifies whichever shape the first author happened to + pick. + +### D1 — how much to type sections now (the linchpin) +- **Recommendation (carried, to ratify or revise):** type the **five sections the floors and the + extractor depend on** — `intent`, `behavior`, `constraints`, `model`, `verification` — and leave + `design` / `decision` / `ui` as open bags so the unsettled surfaces keep breathing. This closes the + honesty bypass for object literals and gives authors guardrails, without over-committing volatile + surfaces. +- **Alternatives to weigh:** type all eight now; or type none and add a runtime authoring-shape + validator instead. +- **Unblocks:** H3 (`constraints[]` shape), H2 (open-questions home), H4 (section-ref integrity), the + gated H8 fixture `invalid-hand-authored-delivery-fact-in-section`, and the H9 in-section note. + +### D3 — collapse direction for the floor validator (depends on D1) +- **Tension.** `validators.ts` is 453 lines for three checks; clause ids are enumerated in **four** + places and `authoredPaths` in the floor data is **decorative** (read only by its declaration + tests, + never by the evaluator — verified). +- **Recommendation (carried):** make the **table the single source of truth** — the evaluator reads + `authoredPaths` + a few generic predicates (safe once sections are typed), killing the 4× + enumeration. Alternative: shrink the table, let the evaluator be the spec. + +### D7 — make the `defined` floor kind-aware (highest-value resolvable item · review F1) **[NEW]** +- **Finding (currently unowned by D1–D6).** The `defined` floor requires `rules-and-or-examples` for + **every** kind (`readiness-floor.ts`); overlays only *add* requirements, never *relax* the base. So a + `decision` / `model` / `constraint` / `contract` / `workflow` spec cannot reach `defined` on its + *natural* content — and the tracer bullet pads three specs with throwaway `behavior.rules` purely to + clear the floor (`order-lifecycle`, `order-model`, `order-latency-constraint`), which + `checkout-v1.test.ts` then asserts is green. The canonical example embodies the exact anti-pattern + **P4's anti-padding corollary forbids**, and an AI author will learn to sprinkle `behavior.rules` onto + decision records. +- **Proof it is a bug, not taste.** At `scoped` the evidence clause is + `rules-examples-or-**constraints**` (constraints alone are valid evidence); at `defined` it silently + drops `constraints` → `rules-and-or-examples`. The maturity ladder is **non-monotonic in the evidence + it accepts** — a constraint honestly `scoped` on its target suddenly needs behavior content to mature + to `defined`. This half-delivers the `02`/P8 "kind is a true subtype that *changes* required detail" + promise: at the floor level `kind` only ever *adds*. +- **Resolution (shrinks the contract).** Make the `defined` evidence clause **defer to the kind**: for a + kind with an overlay (constraint→target, model→terms, example→GWT) or a natural truth section + (decision→`decision`), that overlay/section *is* the evidence; require `rules-and-or-examples` only for + the behavior-family kinds (`behavior`, `rule`, `workflow`). The overlay machinery already exists — this + is "base evidence clause is kind-conditional," not new infrastructure. Sequences with D1 (typed + sections make the predicate cheap). +- **Why here, not plan 02.** It is a base §6 refinement (the floor table is the model), so it moves + *with* the base in the grilling pass — and resolving it de-pads the canonical example (drop three + rules), a visible quality signal. Wave B then re-authors the example + the H8 + `invalid-defined-constraint-without-target` fixture (which itself currently pads — review §3.1). + +### D4 — `AuthoredModel` seam vs the one graph (write a direction now; execute Slice 1/3) +- **Tension (P2).** Validators are designed to run over **the one graph** ("no consumer maintains a + parallel model"). When the extractor lands, either (a) validators migrate to consume `GraphSchema` + and `AuthoredModel` retires, or (b) `AuthoredModel` stays as an explicit, documented *pre-graph + authoring lint*. Avoid two validation code paths drifting apart. +- **Action:** decide the **direction** in this session even though execution is Slice 1/3. + +### D6 — generic-anchor DSL shape (decide now; pairs with Slice-2 anchor extraction) +- **Tension.** The whole point of *generic* anchors is binding **any** code location, but the builders + are namespace-locked (`anchorImplementation` → `impl:`, `specTest` → `test:`). Generic binding + implies the `satisfies` anchor should accept the implementation-flavored code namespaces + (`impl` / `api` / `component`); `api:` already parses in `ids.ts` — only a builder + branded id is + missing. +- **Decide:** generalize `anchorImplementation` to a `codeAnchor` over those namespaces, or add sibling + builders. (The example gap this exposes — H10 — is owned by plan 02, executed at Slice 2.) + +### D8 — the `.spec.ts` file-extension collision (Representation-level; currently implicit · review 1.3) **[NEW]** +- **Finding.** The extractor reads `/specs/**/*.spec.ts` (`03`/`04` §5), but `*.spec.ts` is *the* default + test glob for Vitest/Jest/Mocha. This repo dodges it (`vitest.config.ts` narrows to + `test/**/*.test.ts`), but a **consuming** project using the standard `**/*.spec.ts` for its tests will + have its runner try to execute Spec-primitive files as test suites — a baffling failure for a new + adopter. The model name `Spec` is well-chosen and stays; only the *file serialization* inherits foreign + semantics (the test runner stands in for one of the three readers). +- **Decide (before any real adopter):** keep `.spec.ts` with a documented test-glob exclusion, or move to + a collision-free extension (`.sdp.ts`, `.spec.sdp.ts`). Representation-level, not a model change. + +## §3a — Review-assessed: resolvable now (fast-track, don't re-litigate) + +The post-split adversarial review judged four items **already determined by the base** — they need +ratification, not deliberation. Recorded so the grilling session spends its energy on the genuinely open +D1 / D2 / D3 / D4 / D7 / D8: +- **R1, R2** (and **R3**, §4) — explicitly "code already conforms; wording tightening." Zero reversal + risk; ratify and apply to the base, don't grill. +- **H2 *direction*** — the open-questions home is unambiguously `intent.openQuestions` (`02` §3 + the + `04` worked example). The *fix* (read from `intent.openQuestions`) is correct **regardless of D1**; D1 + only shortens the predicate. Decouple it: H2 can be fixed and its gated H8 fixture flipped without + waiting on the typed-sections grill (execution still lands in plan 02 Wave B). +- **D6** — anchors are *generic* by definition; `ids.ts` already parses `api:` / `component:`; only a + builder + branded id is missing. Record `codeAnchor` over `impl` / `api` / `component` as **decided** + (sibling-builders alternative noted and rejected for surface bloat). + +**Does anything need *reversing*?** The review's answer: **no load-bearing bet** (one-primitive model, +authored/derived `claim` boundary, checked-never-gated, git-as-event-log, agent-surface) needs reversal. +Only refinements: D7 (floor kind-blindness), D8 (`.spec.ts`), and the R-series doc reconciliations — the +recurring shape being *the code is more honest than the docs; reconcile the docs down to it*. + +## §4 — Concept-base refinements to ratify (R1, R2, R3) + +Recorded as **PROPOSED** in `docs/concept/DECISIONS.md` (the code already conforms in all three cases; +these are wording tightenings, so we flag rather than silently edit the ratified base). Ratify and apply +them to the base in this session: + +- **R1** — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4). +- **R2** — "no consumer reads source directly" → permit source *links* recorded in the graph, forbid + independent re-parsing. +- **R3** (review F6) — reconcile `04`'s `specTest` signature to **binding-only**. `04` §2 documents + `specTest(id, { verifies, run })` with an executing `run` callback; the code (`anchors.ts`) is + identity-only (`{ id, label?, verifies }`, **no `run`**), and the code is *more* correct — a binding + carrying `run` would couple the graph binding to execution, contradicting "records that an enabled + verifier exists, never that it ran" (MD-7). Doc fidelity bug; reconcile the doc down to the code. + +## §5 — What this session unblocks (the Wave-B execution backlog, owned by plan 02) + +Once D1–D3 (and D2) land and the base is reconciled, plan 02's **Wave B** becomes execution-ready: +H3 (`constraints[]`), H2 (open-questions → `intent.openQuestions`), H5/D3 (collapse the floor +validator), H4 (section-ref referential integrity, likely folded into the Slice-1 extractor), and the +two gated H8 fixtures flip from `it.todo` to active. H10 (the api/route anchor in the example) executes +with Slice-2 anchor extraction under the D6 shape. + +## §6 — Definition of done for this session + +- **D1, D2, D3, D4, D6, D7, D8 resolved** and recorded in `DECISIONS.md` (rationale + what each + unblocks); the §3a fast-track items (R1/R2/R3, H2-direction, D6) ratified with minimal litigation. +- **Base §2/§3 and §6 reconciled** to the resolved section shapes + the kind-aware `defined` floor (D7); + **R1/R2/R3 ratified** and applied to the base. +- **Plan 02 Wave B rewritten execution-ready** with the now-locked field shapes (no ⊳ markers left), + including the example de-padding that D7 unlocks. +- No code changed in this session (it is PLAN-ONLY); the code follows in the Wave-B execution session. From b0f902adb616da1c8f711f3ec3619906e8344d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Mon, 8 Jun 2026 01:16:18 +0200 Subject: [PATCH 08/29] Introduce WIP fomalization plan --- ...zation-gap-strategy-and-pre-grill-folds.md | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 plans/04-formalization-gap-strategy-and-pre-grill-folds.md diff --git a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md new file mode 100644 index 0000000..f6b8545 --- /dev/null +++ b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md @@ -0,0 +1,241 @@ +# Session 4 — Formalization: gap strategy + pre-grill folds (re-entry context) + +> **Status: PLAN-ONLY · DRAFT · the re-entry point.** This document exists to bootstrap context fast +> after a break — it is the formalized residue of a long design arc that hit the ceiling of what a +> chat thread can resolve ("no substrate to grow the decision → formalize"). Read §0 first; the rest +> is reference. +> **Date:** 2026-06-08 · **Branch:** `feature/mvp-init` · **Repo state:** green; two commits since +> Session 1 — `478cc00` (Phase-0 hardening Wave A) and `c8a7ce0` (plan split + adversarial-review +> absorption). Working tree otherwise clean. + +--- + +## §0 — Re-entry in one screen + +**Where the code is:** Slice 0 (the protocol as typed code) is built, green, and hardened (Wave A +done). No extractor, no graph, no engine yet. + +**What happened this arc (after Wave A landed):** +1. A fresh **adversarial review** (run from a bespoke prompt with a 3-view structure) read the whole + concept + code + plans and came back high-signal. Its findings were absorbed into tracked docs. +2. We studied **gen-1 (`@libar-dev/architect`)'s full formal-spec** as *evidence, not template*. The + takeaway: gen-1 is proof that gen-2's specific rejections are correct (its FSM softened to advisory, + its tag taxonomy was repeatedly amputated, its artifacts migrate-and-delete). Lineage confirmed; no + patterns transferred. +3. We converged on **how to manage gen-2's own delivery in the gap** before the engine exists — see §2, + the **crippled graph**. + +**What's next (in order):** two scoped **fresh** fold sessions (§3) → then the **grill** on a lean base ++ a six-item agenda. This session's job was only to *formalize* that — the folds and the grill run cold, +in fresh sessions, not in a loaded one. Copy-paste launch prompts are in §5. + +**The one decision still genuinely open and yours:** how far the crippled graph goes, and exactly what +its two instances are (§2). Parked deliberately — it needs substrate to grow, not more chat. + +--- + +## §1 — The frame we locked (the meta-discipline) + +These are the operating rules the whole arc converged on. They govern every next step. + +- **Typed code is the destination; markdown is temporary scaffold.** Decisions, specs, the UL — in the + finished Protocol these are typed instances in the graph, rendered by projections. Every markdown doc + here is a stand-in for substrate that doesn't exist yet. Don't over-invest in perfecting it. +- **Fold in fresh, scoped sessions — never in a loaded one.** Folding is real editing work; it deserves + the same clean-context discipline as the grill and the review. The planning lives here; the execution + goes to fresh sessions. +- **Chat has a ceiling.** When a decision "won't converge," it's the medium signalling it needs + substrate. Formalize onto a floor the decision can stand on, rather than talking in a circle. +- **Lineage is evidence, not template.** gen-1 taught us the problem in production; we transfer *lessons* + (what it got wrong / walked back), never its mechanisms. +- **Communicate in meaning, not codes.** The `D-this / H-that` handles are poor-man's node IDs — noise + without a graph/projection to give them meaning. They are tolerable only inside a doc that defines them + inline (see the ledger, §6). In prose, lead with meaning. + +--- + +## §2 — Gap strategy: the crippled graph (LEANING DIRECTION — not designed) + +**The problem this solves.** gen-2 should manage its own delivery, but it can't yet (no engine, and +forcing the engine to serve its own navel too early warps its design). gen-1 is off-limits (its patterns +would spill — its whole *shape* is the idioms gen-2 rejected). Classic PRDs are cut until MVP maturity. +So there is a real gap with no native tool in it. + +**The leaning answer (yours, this arc): a crippled graph.** +- **Two instances**, both **local in the repo** — explicitly *not* "real production." +- A **release process + state-copy ("import production")** boundary between them. +- The graph is **deliberately crippled — designed minimal**, not the full MVP engine. + +**Why it threads the needle.** It captures the dogfooding benefit *early* while the +release/state-copy boundary acts as a **firewall** against the engine bending to serve its own +self-hosting need. "Both local" keeps it free of real operational/stability burden. It is a form of +self-hosting, but *bounded and isolated*, which is what makes "early" safe. + +**The downside to design against.** A broken/undesigned process. gen-1's instability came not from being +*small* but from being **undesigned** — "a small doc-generation experiment local to the repo, no +stability." The discipline here is the inverse: **design the cripple.** Bounded, intentional, +stable-within-its-limits — never an experiment left to sprawl. + +**Open questions to grow on substrate (named, not answered — do NOT resolve in chat):** +- What *are* the two instances? (e.g. dogfood/meta-process vs. engine-dev; or a "production" snapshot vs. + a working copy.) +- What exactly does "release / import production / state-copy" copy, and in which direction? +- How does this relate to the **AuthoredModel-vs-one-graph seam** (a grill decision) and to the + self-hosting-timing rule ("don't move the process onto the graph too early")? +- What is the *minimal* engine slice that makes a crippled graph possible at all (extract → graph, no + more)? + +> This is parked at *direction altitude* on purpose. It needs the minimal engine substrate before it can +> be designed; designing it in prose would just reproduce the loop we exited. + +--- + +## §3 — Pre-grill folds (consolidated inventory) + +The grill must open onto **(a) a self-consistent base** and **(b) an agenda of only genuinely-open +decisions.** Today neither holds. Two fresh fold sessions fix that. (Naming note: these fold sessions are +**Fold-A / Fold-B** to avoid colliding with the *review's* findings F1–F7, which are different things — +see the ledger, §6.) + +### Fold-A — the UL restructure (big; careful; its own fresh session) +The current `docs/concept/ubiquitous-language.md` is a **design-synthesis document wearing a UL hat** — an +artifact of the 8-hour grill that had to invent *and* name the model in one breath. A true Evans UL is +lean. Split the one doc into its three real concerns: +- **Glossary** (lean): `Term · Definition · Aliases-to-avoid`, grouped by concept area, + relationships + + a worked dialogue + flagged ambiguities. The §0–§8 *terms* distilled to one-liners. +- **Design** (the exposition): rehome the §0–§8 model/rubric/principle reasoning. *Open judgment call:* + into the existing `00`–`07` (dedup — leaner; they're already derived from it) **or** a new dedicated + design-synthesis doc above `00`–`07`. Lean: into `00`–`07`. Confirm the real overlap first. +- **Rationale**: already `DECISIONS.md`. +- **Re-aim every pointer** that calls `ubiquitous-language.md` "the model + terminology, read first" — + AGENTS.md nav table, README, cross-refs, and `plans/03`'s base-first method (model → `00`–`07`, terms → + the lean glossary). +- **Absorb the two anchor/source wording reconciliations** along the way (they live in the §2/§4 you're + rewriting): "anchor = identity only" vs "anchored = a binding" → unify to *binding assertion only*; and + "no consumer reads source" → *links-into-the-graph OK, independent re-parse forbidden*. +- **Discipline:** this is **distillation + rehoming, not re-deciding.** Terms and model don't change — + only where they live. So it does *not* need a grill, but it does need fresh, careful scope and a + pointer-integrity pass. + +### Fold-B — the resolvable-now sweep (small) +Fold the already-determined items out of the grill's path: +- **Reconcile the `04` doc's `specTest` signature to binding-only** (no executing `run` callback; the + code already conforms — doc fidelity fix). +- **Record the generic-anchor shape as decided**: generalize `anchorImplementation` into a `codeAnchor` + over `impl` / `api` / `component`; sibling-builders alternative noted and rejected. (Execution rides + the Slice-2 anchor work + the example's missing api-anchor.) +- **Record the open-questions home as decided**: the honesty check must read `intent.openQuestions` (not + `design`/`decision`). Direction is determined; *execution stays Wave B* (it's a code change). +- **Slim `plans/03`** to the **six genuinely-open** decisions (§6 ledger): type-sections, + prose-vs-ref-incl-examples, collapse-the-floor-validator, AuthoredModel-vs-graph, kind-aware-floor, + `.spec.ts`-collision. Collapse the "resolvable-now" section to a one-line pointer. + +### Optional — archive the artifacts +The adversarial **review**, its **prompt**, and the **gen-1 study** currently live in gitignored +`.tmp-scratch/`; they vanish on a clean clone. If they're worth keeping, move them into a tracked +`reviews/` (or `docs/reviews/`). 2.5h + 8h + this arc of signal otherwise lost to git. + +### Then — the grill +A fresh `grill-with-docs` session on `plans/03`, now opening onto a lean base + a tight six-item agenda. +It resolves the genuinely-open decisions against the base, ratifies inline, and rewrites `plans/02`'s +Wave B execution-ready. + +--- + +## §4 — Sequencing & done-criteria + +1. **Fold-A** (UL restructure) — fresh session. *Done when:* lean glossary exists; exposition rehomed; + every pointer re-aimed; the two wording reconciliations applied; nothing in the model *changed*, only + relocated; repo still coherent (no dangling cross-refs). +2. **Fold-B** (resolvable-now sweep) — fresh session (can follow Fold-A or run independently; it doesn't + touch the UL doc except the plans/03 slim). *Done when:* `04` reconciled; two decisions recorded; + `plans/03` down to six open decisions. +3. **(optional) archive** the scratch artifacts. +4. **Grill** — fresh `grill-with-docs` on `plans/03`. *Done when:* the six decisions resolved + recorded; + base reconciled; `plans/02` Wave B execution-ready. +5. **Then** Wave B execution, and — gated on a minimal engine — the **crippled graph** (§2) graduates + from direction to design. + +> Fold-A precedes the grill for a real reason, not tidiness: the grill *edits the base* (typed sections → +> §2/§3; kind-aware floor → §6). If the model has already moved into `00`–`07`, the grill knows its edits +> land in the design docs, not the glossary. Restructure-first tells the grill *where to write*. + +--- + +## §5 — Scoped session prompts (copy-paste to launch cold) + +### Fold-A prompt +> Scoped doc-restructure, no model changes. Read `docs/concept/ubiquitous-language.md`, `00`–`07`, +> `README.md`, `AGENTS.md`, and `plans/03` + `plans/04`. The current `ubiquitous-language.md` is a +> design-synthesis doc wearing a UL hat; split it into a **lean Evans glossary** (Term · Definition · +> Aliases-to-avoid, grouped, + relationships + a worked dialogue + flagged ambiguities) and **rehome the +> §0–§8 exposition into `00`–`07`** (dedup against them; decide consciously vs. a new design-synthesis +> doc). Re-aim every pointer that treats the UL doc as "the model, read first." While rewriting §2/§4, +> apply the two wording reconciliations (anchor = *binding assertion only*; consumers may *link to* graph +> source but not *re-parse* it). **Constraint: distillation + rehoming only — do not re-decide the model; +> terms and meanings stay identical, only their home changes.** End green: no dangling cross-references. + +### Fold-B prompt +> Small, decision-free sweep. Read `plans/03`, `plans/04`, `docs/concept/DECISIONS.md`, and +> `docs/concept/04-authoring-and-binding.md`. (1) Reconcile the `04` doc's `specTest` description to a +> binding-only signature (no executing `run` callback — the code in `src/model/anchors.ts` already +> conforms). (2) In `DECISIONS.md`, record two items as **decided**: the generic-anchor DSL shape +> (`codeAnchor` over `impl`/`api`/`component`; siblings rejected) and the open-questions canonical home +> (`intent.openQuestions`; note execution stays Wave B in `plans/02`). (3) Slim `plans/03` to the six +> genuinely-open decisions and collapse its "resolvable-now" (§3a) to a one-line pointer. + +### Grill kickoff (after the folds) +> `grill-with-docs` on `plans/03` — six open decisions, against the now-lean base. Ratify inline; rewrite +> `plans/02` Wave B execution-ready. + +--- + +## §6 — Decision ledger in meaning (so the codes resolve) + +The antidote to code-noise: every handle, decoded. Two separate code-spaces — don't conflate them. + +**Open decisions (the grill agenda after Fold-B):** + +| Handle | Meaning | Home | +|---|---|---| +| **D1** | How much to *type* sections now (lean: type the five floor/extractor-bearing ones — intent, behavior, constraints, model, verification; leave design/decision/ui open). The linchpin. | grill | +| **D2** | The prose-vs-ref-list duality, across **both** `behavior.rules` and `behavior.examples`. Gates typing `behavior`. | grill | +| **D3** | Collapse the readiness-floor validator (clause-ids enumerated 4×; `authoredPaths` is decorative). Lean: table = single source of truth. | grill | +| **D4** | The `AuthoredModel` seam vs. the one graph — pre-graph lint that retires, or stays a documented lint? (Interacts with the crippled graph, §2.) | grill | +| **D7** | Make the `defined` floor **kind-aware** — today it forces behavior-rules onto decision/model/constraint specs, so the example *pads*. The review's top finding; shrinks the contract; de-pads the example. | grill | +| **D8** | The `.spec.ts` file-extension collides with the JS test-runner glob — adoption landmine. Keep-with-exclusion vs. a collision-free extension. Representation-level. | grill | + +**Resolvable-now (folded out by Fold-B — already determined):** + +| Handle | Meaning | Disposition | +|---|---|---| +| **R1** | Anchor "identity only" (§2) vs "a binding" (§4) → unify to *binding assertion only*. | apply in Fold-A | +| **R2** | "No consumer reads source" → links-into-graph OK, independent re-parse forbidden. | apply in Fold-A | +| **R3** | `04` doc's `specTest` → binding-only (no `run`); code already conforms. | apply in Fold-B | +| **D6** | Generic-anchor shape → `codeAnchor` over impl/api/component. | record in Fold-B; exec Slice 2 | +| **H2-dir** | Open-questions home = `intent.openQuestions`. | record in Fold-B; exec Wave B | + +**Wave B (in `plans/02`, decision-gated; execute after the grill):** H2 (open-questions home fix), H3 +(`constraints` → array), H4 (section-ref referential integrity), H5 (trim floor validator), H10 (api +anchor in the example). Plus the carried review backlog: the `ref`-naming note, the aggregate-`family` +mislabel, and the `modelRefs`-must-be-`kind:"model"` check. + +**Fold sessions:** **Fold-A** = UL restructure · **Fold-B** = resolvable-now sweep. *(These are NOT the +review's findings F1–F7 — that F-space is unrelated: review F1 = the kind-blind floor, now **D7**; F5 = +the examples duality, folded into **D2**; F6 = the `specTest` doc, now **R3**; F2/F3/F4 = the carried +backlog above; F7 = done in Wave A.)* + +--- + +## §7 — Guardrails for the next sessions (what NOT to do) + +- **Don't self-host too early / don't force the engine to serve its own process.** The crippled graph is + bounded and isolated *for this reason*. +- **Don't rebuild gen-1.** A "capable scaffold" *is* gen-1 — a 15-doc formal spec with a churning + taxonomy and an FSM. The scaffold stays a deliberate non-system. +- **Don't perfect the markdown.** It's temporary substrate; the graph replaces it. Lean glossary, not a + beautiful one. +- **Don't grill the resolvable-now items.** They need ratification, not deliberation (Fold-B handles + them). Putting a no-op in front of a grill is workflow-gating in miniature. +- **Don't fold in a loaded session.** Fresh + scoped, always. +- **Don't re-decide the model during Fold-A.** Distillation + rehoming only. From 4bc56453e951c52fc79c4aa34be8f4bfdac8dfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 03:32:36 +0200 Subject: [PATCH 09/29] Fold-A: restructure the UL into a lean glossary; rehome the model into 00-07; apply R1/R2 - ubiquitous-language.md is now the lean Evans glossary: terms, descriptor values, relations, a worked dialogue, flagged ambiguities, the term ledger. Terms and meanings unchanged - only their home. The header maps the old 'base s-n' references for DECISIONS.md and plan history. - Rehomed the only UL-unique exposition into the design docs: the discipline ~ kind/section mapping (06 s6), the UML alignment + constrainedBy/decidedBy granularity rationale (02 s6), spec-may-belong-to-many-packs (02 s4). - Ratified + applied R1 (anchor = binding assertion only - glossary, 01, 04 s2) and R2 (source links OK, independent re-parse forbidden - 01 P2, 03 s4); DECISIONS.md R1/R2 flipped to ACCEPTED/applied. - Re-aimed every pointer: AGENTS.md nav, concept README (glossary row), DECISIONS.md companion note + legacy D1-D6 table homes, live base-section refs in 03/06/07. --- AGENTS.md | 4 +- .../01-founding-principles-and-invariants.md | 4 +- docs/concept/02-core-model.md | 4 +- docs/concept/03-the-one-graph.md | 4 +- docs/concept/04-authoring-and-binding.md | 4 +- docs/concept/06-consumers-and-projections.md | 18 +- .../07-mvp-roadmap-and-open-questions.md | 2 +- docs/concept/DECISIONS.md | 54 +- docs/concept/README.md | 1 + docs/concept/ubiquitous-language.md | 525 +++++++----------- 10 files changed, 245 insertions(+), 375 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 51585c6..0b63f55 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,9 +42,9 @@ Progressive disclosure — start at the top, follow the pointers down. | Look here | What you get | Read | |---|---|---| -| `docs/concept/ubiquitous-language.md` | **the vocabulary & the model** — the ratified base (§0–§8, all `[SETTLED]`); our bounded context and sole source of truth for terminology | **first, always** | +| `docs/concept/ubiquitous-language.md` | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology. The **model exposition** was rehomed into `00`–`07` (2026-06-10 Fold-A; the glossary header maps the old `base §n` refs) | **first, always** | | `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/00`–`07` (+ README) | **the technical design** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model, the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | +| `docs/concept/00`–`07` (+ README) | **the technical design and the model** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model (`02` — the primitive, descriptors, sections, relations), the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | | `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (MD-1/2/4/5/7, the D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code | diff --git a/docs/concept/01-founding-principles-and-invariants.md b/docs/concept/01-founding-principles-and-invariants.md index 5e6111b..a33deb8 100644 --- a/docs/concept/01-founding-principles-and-invariants.md +++ b/docs/concept/01-founding-principles-and-invariants.md @@ -19,7 +19,7 @@ If a successor kept only the essentials, the design stands or falls on these. Ea **Principle · CORE.** There is exactly one authoritative store: committed source in the repo. Every other artifact — the graph, diagrams, dashboards, views, any export — is a pure function of it, never hand-authored or hand-edited. There is no "is the graph in sync?" question because the graph is *defined* as a function of the repo. *(Founding Principle #1, #2, #4.)* ### P2 — One derived read model -**Principle · CORE.** A single canonical graph is the sole input to every projection, validator, and query. No consumer reads source directly or maintains a parallel model. (The extractor that *builds* the graph reads source — that is the producer, not a downstream consumer.) +**Principle · CORE.** A single canonical graph is the sole input to every projection, validator, and query. No consumer reads source directly or maintains a parallel model. (The extractor that *builds* the graph reads source — that is the producer, not a downstream consumer.) Consumers may *link to* source locations recorded in the graph; what is forbidden is independently *re-parsing* source to derive a model of their own (DECISIONS R2). ### P3 — Deterministic derivation **Principle · CORE.** Derivation is reproducible: `graph = f(repo)` and `view = f(graph)`, with stable ordering (specs sorted by ID, edges by `(from, type, to)`) and non-determinism (timestamps, hashes) excluded from the semantic comparison. Delete `generated/` and rebuild byte-identically. Without determinism, "derived" is unfalsifiable and the no-second-store rule cannot be enforced. @@ -55,7 +55,7 @@ If a successor kept only the essentials, the design stands or falls on these. Ea **Principle · CORE.** There is a clean division of epistemic labour, and the two halves are never confused: -- **Humans assert intent and bindings.** Relations, stated readiness, decisions — and an in-code **anchor** that points code → a spec ID (identity only, never intent). Authored and taken at face value until a validator can check them. +- **Humans assert intent and bindings.** Relations, stated readiness, decisions — and an in-code **anchor** that points code → a spec ID (a binding assertion only, never intent — DECISIONS R1). Authored and taken at face value until a validator can check them. - **Machines assert structure.** Calls, imports, route wiring — derived and distrusted as *intent*. P9 and P10 are corollaries of this boundary. So is the rule that **delivery facts are derived, not authored**: a spec's realization (`implemented` / `has-verifier` / `observed`) is **derived** by the pipeline from graph edges and runtime, never authored by hand — authoring one is a violation because it would drift from reality. *(In the MVP this is structural only — `has-verifier` means a linked, enabled verifying spec/test exists; test run results are not ingested, they are CI's. The point stands: "did it actually happen" is structurally separate from "what we state should happen".)* diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index f32a79c..47e4466 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -238,7 +238,7 @@ export const CheckoutV1 = pack({ }); ``` -Membership is authored on the `Pack` manifest; the `belongsTo` edge is **derived** from it (it carries `claim:"declared"`, because it is a deterministic re-expression of the declared manifest — see §6 and `04`). A pack is checked for **coherence** (referenced terms and `modelRefs` resolve, membership resolves, no duplicate members) — *not* for the completeness of any individual member, and *not* for "duplicated intent" (a pack has no truth of its own to duplicate). This supports "a large coherent group of low-detail specs" as a first-class state. +A spec may belong to **many** packs. Membership is authored on the `Pack` manifest; the `belongsTo` edge is **derived** from it (it carries `claim:"declared"`, because it is a deterministic re-expression of the declared manifest — see §6 and `04`). A pack is checked for **coherence** (referenced terms and `modelRefs` resolve, membership resolves, no duplicate members) — *not* for the completeness of any individual member, and *not* for "duplicated intent" (a pack has no truth of its own to duplicate). This supports "a large coherent group of low-detail specs" as a first-class state. **Two grouping mechanisms stay distinct:** *refinement* (a parent spec → its children — authored truth that happens to have descendants) vs *the aggregate* (the `Pack` — a cross-cutting review collection with no truth of its own, which may span or sub-slice the refinement hierarchy). A `Pack` is the unit a **Design Review** (`06`) renders "in context." @@ -288,6 +288,8 @@ MVP relation vocabulary (a Representation; extensible): `supersedes` is permitted **only** on decision specs, as a current authored statement about two records that both still exist in the repo — not as graph-resident history (see git is the event log in `01`). +Two notes carried from the language ratification: the verb forms align with UML where a standard stereotype exists (`refines` ≈ «refine», `dependsOn` ≈ UML *Dependency*, `decidedBy` ≈ «trace», `verifies` ≈ «verify») — adopted nouns, per the governing rubric. And `constrainedBy` / `decidedBy` are deliberately kept **distinct** from a generic `dependsOn`: "bounded by an NFR" and "shaped by a decision" are high-value, separately-queryable intents that a generic dependency edge would flatten. + **Derived edges — never authored:** - `satisfies` (code → spec) — emitted from an **anchor** in source; carries `claim:"anchored"`. This is the inverse of the old `satisfiedBy`: the direction is **code → spec**, and it is **derived from anchors**, never hand-authored. diff --git a/docs/concept/03-the-one-graph.md b/docs/concept/03-the-one-graph.md index cbfbf23..914024b 100644 --- a/docs/concept/03-the-one-graph.md +++ b/docs/concept/03-the-one-graph.md @@ -60,7 +60,7 @@ Every edge in the graph has a fixed contract: where it comes from, what `claim` | `belongsTo` | Primitive → Pack | derived (from the manifest) | declared (inherited) | `Pack` manifest `specs[]` | **error** if member missing/duplicate | — | — | | calls / imports | CodeNode → CodeNode | derived (analysis) | inferred | — (machine) | **advisory** — never errors on its own | — | feeds the impact graph only | -**Delivery facts are node facts, not edges.** `implemented` / `has-verifier` / `observed` are computed *from* the `satisfies` / `verifies` / runtime edges above and shown as badges on the node (`02` §2; base §4b) — they are never authored and never themselves edges. +**Delivery facts are node facts, not edges.** `implemented` / `has-verifier` / `observed` are computed *from* the `satisfies` / `verifies` / runtime edges above and shown as badges on the node (`02` §2) — they are never authored and never themselves edges. --- @@ -102,7 +102,7 @@ Rules that keep this honest: ## 4. Regenerability and the no-second-store rule (P1, P2) - `generated/` is gitignored and disposable (L8). Delete it, run `sdp build`, get the same bytes back. -- No consumer reads source directly or keeps a parallel model (P2). The view reads the graph; the trace query reads the graph; an AI agent reads the graph (as JSON). One read model, many readers. +- No consumer reads source directly or keeps a parallel model (P2). The view reads the graph; the trace query reads the graph; an AI agent reads the graph (as JSON). One read model, many readers. Consumers may *link to* source locations recorded in the graph (a Design Review linking to the anchored file/line is legitimate); what is forbidden is independently *re-parsing* source to derive a parallel model (DECISIONS R2). - The MVP graph is a **single JSON file** plus an in-memory graph for queries. A property-graph database is deferred until measured traversal pain — and the schema is designed to map cleanly to one later, so the deferral has forethought. There is no "is the graph in sync with the repo?" question, because the graph is *defined* as a function of the repo. The only thing to trust is `git` and the code. diff --git a/docs/concept/04-authoring-and-binding.md b/docs/concept/04-authoring-and-binding.md index 3b5e778..11cfcfa 100644 --- a/docs/concept/04-authoring-and-binding.md +++ b/docs/concept/04-authoring-and-binding.md @@ -80,9 +80,9 @@ export const _anchor = anchorImplementation({ id: "impl:orders.create-order-use- The three syntaxes are interchangeable Representations; the *binding* is the thing. A team picks one style. -### Anchors are read-only pointers — never carry intent (P9/P10) +### Anchors assert a binding — never intent (P9/P10) -An anchor does exactly one thing: bind a code location to a graph ID and its structural bindings (`component`, `satisfies`, `implements`, and — aspirationally — `handles`/`emits`). It is **forbidden** from carrying spec-level sections (intent, behaviour, readiness, verification). This asymmetry is load-bearing: +An anchor says exactly one thing: *"this code location is the implementation/test **binding** for this Spec ID"* — a binding assertion only, never system-truth content (DECISIONS R1). It binds a code location to a graph ID and its structural bindings (`component`, `satisfies`, `implements`, and — aspirationally — `handles`/`emits`). It is **forbidden** from carrying anything spec-level: behavior, rationale, readiness, acceptance criteria, or delivery facts. This asymmetry is load-bearing: - **Intent stays centralized** in the spec files, never scattered through code comments. - Anchors produce **anchored**-`claim` edges, distinct from **declared** relations (P9). diff --git a/docs/concept/06-consumers-and-projections.md b/docs/concept/06-consumers-and-projections.md index 520c283..0bfb723 100644 --- a/docs/concept/06-consumers-and-projections.md +++ b/docs/concept/06-consumers-and-projections.md @@ -62,7 +62,7 @@ This is the `claim` taxonomy (P9) elevated to two consumable surfaces: the curat - 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. -> MVP boundary (decided, not a sequencing call): the curated surface is MVP, and so is **file-level** impact — `git diff` → `byFile` → a curated-graph walk of `refines`/`dependsOn`/`satisfies`/`verifies` yields changeset blast-radius **without a symbol index**. The **exhaustive** impact graph — symbol-level identity, `bySymbol`, cross-package find-all-usages — is the `inferred` surface and stays **aspirational** (base §7). So MVP impact rides the curated graph + file-level reach; symbol-level reach is deferred. This is a boundary on the impact graph's *depth*, not a reopening of the two-surface model. Because it rides anchors + curated edges, it answers *which anchored specs a changeset touches*: **a changed file with no anchor maps to no spec and is surfaced as an explicit `coverage-unknown` item, never silently dropped** — file-level reach can under-report for shared or unanchored code, and the answer says so rather than implying a complete reach set. +> MVP boundary (decided, not a sequencing call): the curated surface is MVP, and so is **file-level** impact — `git diff` → `byFile` → a curated-graph walk of `refines`/`dependsOn`/`satisfies`/`verifies` yields changeset blast-radius **without a symbol index**. The **exhaustive** impact graph — symbol-level identity, `bySymbol`, cross-package find-all-usages — is the `inferred` surface and stays **aspirational** (the glossary's *impact graph* entry). So MVP impact rides the curated graph + file-level reach; symbol-level reach is deferred. This is a boundary on the impact graph's *depth*, not a reopening of the two-surface model. Because it rides anchors + curated edges, it answers *which anchored specs a changeset touches*: **a changed file with no anchor maps to no spec and is surfaced as an explicit `coverage-unknown` item, never silently dropped** — file-level reach can under-report for shared or unanchored code, and the answer says so rather than implying a complete reach set. --- @@ -83,7 +83,7 @@ The **`reader`** is the *component* behind the surface: joins and `claim`/taxono **Freeze only** the small irreducible set — a *universal bridge* or an *irreducible cross-source join* — the things an agent hand-rolling would get wrong: -- **Entry adapters** — the grep→graph bridge. Real work starts from a **string** ("rate limiter"), a **file** (`src/...`), or a **changeset** (`git diff`); the graph is keyed by name. `findByConcept(str)` and `byFile(path)` bridge from what-the-agent-has to what-the-graph-knows and are **MVP** (both resolve off the curated graph + anchors, no symbol index). `bySymbol(sym)` is the same shape but resolves through the **exhaustive impact graph**, so it is frozen-in-*shape* yet **aspirational** in build (§2 boundary, base §7). *(grep is an entry-point problem, not a context problem.)* +- **Entry adapters** — the grep→graph bridge. Real work starts from a **string** ("rate limiter"), a **file** (`src/...`), or a **changeset** (`git diff`); the graph is keyed by name. `findByConcept(str)` and `byFile(path)` bridge from what-the-agent-has to what-the-graph-knows and are **MVP** (both resolve off the curated graph + anchors, no symbol index). `bySymbol(sym)` is the same shape but resolves through the **exhaustive impact graph**, so it is frozen-in-*shape* yet **aspirational** in build (§2 boundary; the glossary's *impact graph* entry). *(grep is an entry-point problem, not a context problem.)* - **Blast-radius** over a changeset (impact + at-risk specs). The **file-level** form (`git diff` → `byFile` → curated-graph walk) is **MVP**; **symbol-level** exhaustive reach rides the aspirational impact graph. The MVP form **reports uncovered (unanchored) changed files explicitly** (a `coverage-unknown` signal) — honest coverage, never a silently-small answer (§2). - **Irreducible joins** — e.g. the multi-hop `spec → satisfies → … → invariants/scenarios` bridge with maturity/`claim` decode. Freeze because it is a true cross-source join, not a thin walk. @@ -91,7 +91,7 @@ Everything else (single-field traversals, group-bys, the maturity ladder) stays > Context efficiency is a measured win, not a hope: keeping the data in-process and returning only conclusions ran a multi-probe session at a measured fraction of the tokens of a grep/verb-API equivalent. Freezing answers is expensive both as bytes on disk and as tokens in context. -**Aspirational (named, deferred):** `bySymbol` and symbol-level / cross-package reach (they ride the exhaustive impact graph, base §7); token-budgeted self-contained slices (`per-pack`, `change-impact-`); the **MCP surface** (§7) exposing a read-only window; GraphRAG retrieval for very large graphs. All stay inside the read-only gate (§4). +**Aspirational (named, deferred):** `bySymbol` and symbol-level / cross-package reach (they ride the exhaustive impact graph — §2 boundary); token-budgeted self-contained slices (`per-pack`, `change-impact-`); the **MCP surface** (§7) exposing a read-only window; GraphRAG retrieval for very large graphs. All stay inside the read-only gate (§4). --- @@ -154,6 +154,18 @@ The MVP human view *is* the Design Review's relationship slice: a single derived | **baseline** | a **named approved snapshot** (≈ a git tag over a set of `ready` `Spec`s); the **git tag is the approval artifact** (a signed tag carries approver + approved-at), so approval provenance is **git-native**, not an authored fact. Vocabulary + optional projection. **Realized** — as a trivial git-tag projection, **no dedicated MVP slice or machinery**. | | **phase / iteration / milestone** | **descriptive vocabulary** with optional roadmap / now-next-later projections. Never a gate or enforced sequence. | +The **discipline ≈ kind/section mapping** (how the Protocol supports the disciplines-and-phases picture without its gates): + +- **Requirements** → `behavior` `Spec`s (+ the Capability Map projection) +- **Analysis & Design** → `design` sections + Decision Records +- **Test** → `example` `Spec`s + `verifies` relations +- **Deployment** → `observed` / evidence nodes +- **Config & Change Management** → git (the source of truth) +- **Project Management** → `Pack`s + roadmap projections +- **Business Modeling** → `model` `Spec`s + +The Protocol imposes no particular delivery style (iterative or sequential), and none of these terms is ever modeled as an additional authored truth-primitive, relation, or validator. + A classic disciplines × phases × iterations distribution chart becomes a **Mermaid / analytical projection** — a view of how authored and derived activity distributes across the graph, never a plan the system enforces. This is what lets `00`'s non-goal be honest: the Protocol *adopts the delivery nouns as projections* and *rejects only the gating FSM/sprint-state*. --- diff --git a/docs/concept/07-mvp-roadmap-and-open-questions.md b/docs/concept/07-mvp-roadmap-and-open-questions.md index e9331e3..f34d78e 100644 --- a/docs/concept/07-mvp-roadmap-and-open-questions.md +++ b/docs/concept/07-mvp-roadmap-and-open-questions.md @@ -67,7 +67,7 @@ These are out of the first slice. Each is genuinely deferred, and the model in ` Deferred deliberately; recorded so they are not lost. None blocks the MVP. - **Derived-readiness banner timing.** Floor enforcement (the stated rung is checked) is MVP; the explicit "stated `defined`, derived `scoped`" banner can be a fast follow. *(`05` §3.)* -- **Impact-graph depth (resolved, recorded here for traceability).** The boundary is decided, not open: **file-level** impact ships in the MVP (`git diff` → `byFile` → a curated-graph walk gives changeset blast-radius with no symbol index, surfacing an explicit `coverage-unknown` item for any changed file that has no anchor so a too-small set is never mistaken for complete), while the **exhaustive** impact graph — `bySymbol`, symbol-level identity, cross-package find-all-usages, drift/fan-in tooling — is deferred (Iterate). What remains genuinely open is only *when* the exhaustive graph earns its way in, driven by measured pain (§5), never the MVP boundary itself. *(`06` §2; base §7.)* +- **Impact-graph depth (resolved, recorded here for traceability).** The boundary is decided, not open: **file-level** impact ships in the MVP (`git diff` → `byFile` → a curated-graph walk gives changeset blast-radius with no symbol index, surfacing an explicit `coverage-unknown` item for any changed file that has no anchor so a too-small set is never mistaken for complete), while the **exhaustive** impact graph — `bySymbol`, symbol-level identity, cross-package find-all-usages, drift/fan-in tooling — is deferred (Iterate). What remains genuinely open is only *when* the exhaustive graph earns its way in, driven by measured pain (§5), never the MVP boundary itself. *(`06` §2.)* - **Inline-vs-centralized anchor semantics.** Anchors carry no intent in the MVP. How much *structural* semantics an anchor may carry (beyond `id`/`satisfies`/`component`) is left configurable later. *(`04` §2.)* - **Graph-DB timing.** File-based until measured traversal pain; the schema is designed to map to a property graph later. *(`03` §4.)* - **Trace-link recovery.** Permitted later only as an assistive *suggestion* engine (the impact graph's "propose candidates" assist role), never a declared edge — bounded permanently by P10. *(`01`, `06` §2.)* diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 8b0783b..1847815 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -3,7 +3,9 @@ > A why-focused log of hard-to-reverse decisions about **building the Protocol itself** — distinct from *in-system* > decision records (a project's own `kind:"decision"` instances). Its job is to **recapture rationale** so choices > stay visible and don't re-smooth into "false settledness." Entries are short and **status-tagged** -> (`ACCEPTED` / `PROPOSED` / `SUPERSEDED`). Companion to `docs/concept/ubiquitous-language.md` (the language base). +> (`ACCEPTED` / `PROPOSED` / `SUPERSEDED`). Companion to `docs/concept/ubiquitous-language.md` (the ratified +> glossary; since the 2026-06-10 Fold-A restructure the model exposition lives in `00`–`07` — historical +> `base §n` references in the entries below resolve via the §-map in the glossary header). --- @@ -99,22 +101,25 @@ derived"), not reversals of direction. home for approval provenance is **signed git tags** (identity + approved-at, already in git) — not a new authored primitive. -### Proposed base refinements (R-series) [PROPOSED 2026-06-07 · awaiting grilling ratification] +### Base refinements (R-series) [R1/R2 ACCEPTED & applied 2026-06-10 (Fold-A) · R3 see below] **Context.** The post-Session-1 founding-ideation review surfaced two wording imprecisions in the ratified base. The **code already conforms** in both cases; these are language tightenings, so per the working discipline -(terminology is ratified — *flag*, don't silently edit) they are recorded here as **PROPOSED** and routed to the -fresh decision-resolution session (`plans/03-decision-resolution-and-base-reconciliation.md` §4) to ratify and -apply against `docs/concept/ubiquitous-language.md`. They are **not** model changes. -- **R1 — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4).** §2 says anchors - carry "identity only," while §4's `claim` table calls `anchored` "a human binding" — and a binding (it emits a - `satisfies`/`verifies` edge) is more than bare identity. Proposed unified phrasing: *"an anchor says 'this code - location is the implementation/test binding for this Spec ID'; binding only, never system-truth content — never - behavior, rationale, readiness, acceptance criteria, or delivery facts."* The code already conforms (anchors hold +(terminology is ratified — *flag*, don't silently edit) they were recorded as **PROPOSED** and assessed by the +post-split adversarial review as *already determined* (ratification, not deliberation). They were ratified and +applied in the **pre-grill fold session (2026-06-10)** — R1/R2 during the Fold-A base restructure, R3 in Fold-B — +rather than spending grill time on no-op-on-code wording. They are **not** model changes. +- **R1 — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4).** [ACCEPTED · + applied] §2 said anchors carry "identity only," while §4's `claim` table called `anchored` "a human binding" — + and a binding (it emits a `satisfies`/`verifies` edge) is more than bare identity. Unified phrasing, now in the + glossary's `anchor` entry, `01` (epistemic boundary), and `04` §2: *"an anchor says 'this code location is the + implementation/test binding for this Spec ID'; binding only, never system-truth content — never behavior, + rationale, readiness, acceptance criteria, or delivery facts."* The code already conforms (anchors hold only `id`/`label`/target; `@ts-expect-error` proves the rest is rejected). -- **R2 — "no consumer reads source directly" → permit source *links*, forbid independent re-parsing.** The - principle (`03`/`05`/`06`) is right, but a Design Review linking to source locations *recorded in the graph* is - legitimate. Proposed: *"Consumers may link to source locations recorded in the graph; consumers must not - independently parse source to derive their own model."* Matters when the Slice-4 Design Review lands. +- **R2 — "no consumer reads source directly" → permit source *links*, forbid independent re-parsing.** [ACCEPTED · + applied] The principle (`03`/`05`/`06`) is right, but a Design Review linking to source locations *recorded in + the graph* is legitimate. Now stated in `01` P2 and `03` §4: *"Consumers may link to source locations recorded + in the graph; consumers must not independently parse source to derive their own model."* Matters when the + Slice-4 Design Review lands. - **R3 — reconcile `04`'s `specTest` signature to binding-only.** `04` §2 documents `specTest(id, { verifies, run })` with an executing `run` callback; the implementation (`src/model/anchors.ts`) is identity-only (`{ id, label?, verifies }`, **no `run`**). The code is the *more* faithful one: a binding anchor carrying @@ -137,18 +142,21 @@ brief have since been **deleted** (consolidated); the **sole canonical base is ## Structural-decision shorthand (D1–D6) [ACCEPTED · relocated here when the cleanup plan was retired, 2026-06-07] -> These six labels come from the original structural-decisions pass. Their *content* is canonical in -> `docs/concept/ubiquitous-language.md`; this table is kept for permanent traceability so any historical `(Dn)` -> reference still resolves now that the single-use cleanup plan (which previously held it) is gone. +> These six labels come from the original structural-decisions pass. Their *content* is canonical in the +> design docs (`00`–`07` — since the Fold-A restructure rehomed the base's model exposition there); this table +> is kept for permanent traceability so any historical `(Dn)` reference still resolves now that the +> single-use cleanup plan (which previously held it) is gone. **Do not confuse this legacy D-space with the +> open-decision handles D1–D8 used by `plans/02`/`plans/03`** — those name the post-Session-1 grill agenda +> (typed sections, prose-vs-ref, floor collapse, …), a different code-space; in prose, lead with meaning. | Label | One line | Canonical in | |---|---|---| -| **D1** | readiness (design maturity, authored) is separate from delivery facts (derived) | base §2 + §4b | -| **D2** | `02` carries explicit typed **sections**, trimmed to essence (branded-ID strings; open `model` list) | base §2 | -| **D3** | `Pack` is a reified grouping/aggregate (not folded into `Spec`); membership single-sourced on a manifest | base §2 boundary | -| **D4** | **Design Review** is the flagship curated projection — the context where a human decides to state `ready` (sharpened by MD-7; the floor is checked, the review is human practice) | base §7 | -| **D5** | the **agent surface** = a visible typed graph the agent *scripts* (no verb wall); `reader` = thin loader | base §7 | -| **D6** | the **MCP surface** = integration for user-facing apps (designed-in, deferred build, shape TBD) | base §7 | +| **D1** | readiness (design maturity, authored) is separate from delivery facts (derived) | `02` §2 | +| **D2** | `02` carries explicit typed **sections**, trimmed to essence (branded-ID strings; open `model` list) | `02` §3 | +| **D3** | `Pack` is a reified grouping/aggregate (not folded into `Spec`); membership single-sourced on a manifest | `02` §4 | +| **D4** | **Design Review** is the flagship curated projection — the context where a human decides to state `ready` (sharpened by MD-7; the floor is checked, the review is human practice) | `06` §5 | +| **D5** | the **agent surface** = a visible typed graph the agent *scripts* (no verb wall); `reader` = thin loader | `06` §3 | +| **D6** | the **MCP surface** = integration for user-facing apps (designed-in, deferred build, shape TBD) | `06` §7 | ## Measured evidence — figures behind the generalized doc prose [recorded 2026-06-07] diff --git a/docs/concept/README.md b/docs/concept/README.md index 5f0132b..6b17288 100644 --- a/docs/concept/README.md +++ b/docs/concept/README.md @@ -34,6 +34,7 @@ Every load-bearing claim is named as one or the other, on purpose — so a Repre | # | Document | What it covers | |---|---|---| | — | [README](./README.md) | This index: definition, Founding Principle, reading guide, the MVP legend. | +| — | [Ubiquitous Language](./ubiquitous-language.md) | The ratified **glossary**: terms, descriptor values, relations, a worked dialogue, flagged ambiguities. The terminology base for every doc below. | | 00 | [Vision, Scope & MVP Boundary](./00-vision-scope-and-mvp-boundary.md) | The honest full ambition **and** the sharp MVP line, in one place. | | 01 | [Founding Principles & Invariants](./01-founding-principles-and-invariants.md) | The load-bearing laws, each tagged Principle/Representation and CORE/ASPIRATIONAL. Git-as-event-log. The `claim` epistemics. | | 02 | [Core Model](./02-core-model.md) | The `Spec` primitive, the three descriptors (`kind` · `altitude` · `readiness`), sections, delivery facts, stable IDs, relations. | diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index 7e02743..15f2189 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -1,358 +1,205 @@ -# Ubiquitous Language — Libar Software Delivery Protocol (ratified canonical base) +# Ubiquitous Language — Glossary (ratified) -> **Status: RATIFIED CANONICAL BASE.** Built section-by-section in the language grill; now the sole canonical -> source for terminology and the model. -> Sources triangulated during ratification: the concept docs + JTBD stories + established software-delivery method -> vocabulary + the **pm-skills** PM/requirements vocabulary. +> **Status: RATIFIED · LEAN GLOSSARY.** The terminology base of the Libar Software Delivery Protocol — our +> bounded context's vocabulary: **one concept → one word**; the rest are *aliases to avoid*. Built +> section-by-section in the language grill; ratified 2026-06-07. > -> **Method (do not skip):** *define the concept → contextualize it → accept the definition → only then attach a -> term.* Names were parked until their concept locked. Each substantive block below is now tagged **[SETTLED]**. +> **Restructured 2026-06-10 (Fold-A).** This document now carries **terms only**. The model exposition that +> used to live here (§0–§8) was rehomed into the design docs — terms and meanings did **not** change in that +> move, only where they live. Rationale stays in `DECISIONS.md`. For historical `base §n` references (in +> `DECISIONS.md` and the plans), the old sections map to: +> §0 thesis · §1 meta-levels → `00` §2 · §2 primitive & boundary → `02` §1–§4 · §3 relations → `02` §6 · +> §4 claims & the authored/derived split → `01` + `03` §3 · §4b delivery facts → `02` §2 · +> §5 the one graph → `03` · §6 validation & honesty → `05` · §7 surfaces & projections → `06` · +> §8 delivery-process execution → `06` §6. ## Governing rubric [SETTLED] -- **Adopt the established delivery *noun*** for any concept the Protocol shares with the industry. Coin/keep a distinct - word only for a genuine differentiator — and then the word must *name the difference*. + +- **Adopt the established delivery *noun*** for any concept the Protocol shares with the industry. Coin/keep a + distinct word only for a genuine differentiator — and then the word must *name the difference*. - **Reject process *state-machine / lifecycle gating*** (sprint/ticket FSM, mandatory phase gates). Adopt the process *nouns*, not the process *gates*. - **Two naming tests:** (a) carry epistemic status where it matters (authored vs derived, claimed vs computed); - (b) concrete & unambiguous to all three readers — typed code/CLI, the coding agent, and a Studio user. One - concept → one word; the rest are *aliases to avoid*. - -## §0 — Thesis: what we are building [SETTLED · frame confirmed] · (the meta-model is named **the Protocol**; the modeled activity stays the **software-delivery process** — MD-5 resolved) -Libar Software Delivery Protocol is a **typed, executable meta-model of the software-delivery process.** Teams -author delivery intent as **`Spec` instances** — the Protocol's one authored truth-primitive — and author only -two other non-truth constructs, **`Pack`** and **`anchor`**; the meta-model — **code in the repo** — -deterministically checks **conformance and honesty** and derives **one graph**; -every other artifact (views, Mermaid diagrams, API references, context bundles) is a **projection** of that graph. - -The innovation is not *executable specs* (BDD has those) — it is an **executable, self-validating delivery -protocol**: the building blocks are code, and instances **conform** to it (conformance checked, never gated). - -> **Naming note:** the meta-model is named **the Protocol** (product: **Libar Software Delivery Protocol**) — -> "protocol" captures the conformance-contract aspect MD-1 guards. The modeled **software-delivery process** (the -> activity teams perform) keeps "process," as does the rejected **process state-machine**. See `docs/concept/DECISIONS.md` MD-5. - -**Two honesty guardrails on "self-validating" (so we don't re-import what we rejected):** -1. **Checks conformance & honesty — not content-quality, not workflow.** The meta-model deterministically checks - *well-formedness* (required detail present, references resolve, readiness *stated* honestly, authored vs derived - kept separate). It must not adjudicate whether a design is *good* (human/agent judgment) and must not gate - *workflow* (the lifecycle-FSM trap, already rejected). It is a **contract instances conform to** — invariants over the - graph, not a controller. -2. **"Deterministically validated," not "provably correct."** Conformance to the typed meta-model is provable; - real-world correctness of the design is not. Claim the former, never the latter. - -> §0 frame **ratified 2026-06-07** (the thesis + both guardrails + the three levels); **naming resolved -> 2026-06-07** — the meta-model is **the Protocol**, the modeled activity stays the **software-delivery process** (MD-5). - -## §1 — The meta-levels [SETTLED] -Three levels, kept clean (MOF-style meta-levels, made executable): - -| Level | What lives here | Role | When built | -|---|---|---|---| -| **Protocol (the meta-model)** | the primitive, its descriptors, the specialization classes, the relation set, the validators — **as typed code in this repo** | **defines the conformance contract** | **Phase 0 of the MVP** | -| **Authored model** | a project's authored intent (its instances) — instances of the classes above | **conforms (checked)** | per project | -| **Derived facts** | machine truth about the authored model (code realises it · a verifier exists · runtime observed it) | **derived, never authored** | per project | - -- **Phase 0 = build the meta-model as code** — not a detour: the extractor, the graph schema, and every validator - already presuppose it. -- **Self-hosting (later, not Phase 0):** the Protocol's own repo is an authored model that conforms to its own - meta-model — literal bootstrap self-hosting. A milestone, not a Phase-0 claim. - -## §2 — The primitive (`Spec`) & its boundary [SETTLED] -**Concept:** the one kind of thing a human authors directly — a durable, **enrich-in-place** statement of intended -truth about the system being built, made more precise *in place* (never migrated to a different artifact type) -until it is precise enough to implement. It is the only authored **truth**-primitive; everything the machine -reports about it is **derived**. (Two *other* things are authored but are not truth-primitives — see the boundary.) - -Positioned by **three descriptors** (lowercase literals in code, recognized display labels). The familiar delivery -nouns are **named coordinates**, not separate authored types — the one-primitive bet, every recognizable noun -preserved: - -| Descriptor (field) | Behaves like | Values | + (b) concrete & unambiguous to all three readers — typed code/CLI, the coding agent, and a Studio user. + +## The meta-levels (MOF-style meta-levels, made executable → `00` §2) + +| Term | Definition | Aliases to avoid | |---|---|---| -| **`kind`** — category of truth | a **true subtype** (discriminated union — changes required detail + validation) | the 8 below | -| **`altitude`** *(was `abstraction`)* — size | a **position** — same shape, different scope | `epic` → `feature` → `story` | -| **`readiness`** — design maturity | a **position** — same shape, different completeness | `idea` → `scoped` → `defined` → `ready` (`ready` has a **readiness floor**, §6) | +| **the Protocol** | the meta-model — the primitive, descriptors, relations, and validators **as typed code**; the conformance contract instances conform to (Phase 0 builds it) | "the process" (reserved, below) · "governs"/"polices" (it defines a contract) | +| **software-delivery process** | the modeled activity teams perform — what the Protocol is a meta-model *of* | — ("process" survives **only** here, in "delivery-process execution," and in the rejected *process state-machine*) | +| **authored model** | a project's authored instances; **conforms** — conformance checked, never workflow-gated | — | +| **derived facts** | machine truth about the authored model (code realises it · a verifier exists · runtime observed it) — derived, never authored | — | + +Self-hosting — the Protocol's own repo conforming to its own meta-model — is a later milestone, never a +Phase-0 claim (`00` §3). -**`kind` — the 8 specializations** (literal → display): +## The primitive & its descriptors (→ `02` §1–§2) -| `kind` | display | note | +| Term | Definition | Aliases to avoid | |---|---|---| -| `behavior` | Use Case / Behavior | broader literal; "Use Case" is the familiar display | -| `workflow` | Workflow | multi-step process / journey | -| `example` | Example / Scenario | a **Scenario** is a low-altitude `example` that `refines`/`verifies` a Story — *not* an altitude | -| `rule` | Business Rule | | -| `constraint` | Constraint (NFR) | **NFR** is a *flavor* (performance / quality / security / …), not a separate kind | -| `model` | Domain Model | | -| `decision` | Decision Record | | -| `contract` | Contract | | - -- `kind` is the genuine specialization; **`epic`/`feature`/`story` are altitude positions, not subtypes.** A - **Scenario** is not an altitude — it is a low-altitude `example`-kind primitive that `refines`/`verifies` a Story. -- **`capability`** and **domain / bounded-context** are *not* kinds or altitudes — they are **projections** (a - *Capability Map* over high-altitude `behavior` `Spec`s) and/or **groupings (packs)**. Epic is the altitude - ceiling; above-Epic (initiative/theme) defers, additive later. -- *"Executable Spec"* = an `example` that **has a verifier** (a delivery fact), not a readiness rung. -- The delivery nouns are first-class in the **language and in Studio surfaces/projections** — never separate - authored truth-primitives. - -### The envelope & sections [SETTLED] -A `Spec`'s **envelope** — `id` · `title` · `kind` · `altitude` · `readiness` · `relations` — is the stable, -minimal outer shape; it changes almost never. All other detail lives in optional, typed **sections** — the -**extension surface**: the system grows by adding sections (or enum members), never by reshaping the envelope. - -Sections (all optional; each carries one concern, mapping to a discipline): `intent` · `behavior` · `constraints` -· `model` · `design` · `decision` · `verification` · `ui`. A **readiness floor** (§6) requires the right sections -to *state* a given rung. - -**Section ⟷ kind — when a concern is inline vs. its own primitive [SETTLED].** Three sections -(`constraints` · `model` · `decision`) have a same-named `kind` twin, so the same concern can live two ways. The -trigger: keep it **inline as a section** when it is **local detail** of its host `Spec`; **promote it to a -standalone `Spec` of the matching `kind`** (linked by `constrainedBy` / `decidedBy` / `modelRefs`) when it is -**referenced by >1 `Spec`, or needs its own identity / lifecycle / review.** `modelRefs` on a `Pack` always points -at standalone `kind:"model"` specs — shared vocabulary is never inlined twice. *(The core-model doc `02` states -this once for builders; it is not scattered across the other docs.)* - -> **Name note:** **`section`** replaces the disliked **"Facet"** — **locked** (blessed 2026-06-07; chosen over -> `aspect`/`facet` for cross-audience legibility: "the Design section of this Spec" reads naturally to code, the -> coding agent, and a Studio user). - -### The boundary — what is *not* the primitive [SETTLED · concept] -A human authors **three** things; only the first states system truth. Everything else is **derived**. - -**Authored, but not the truth-primitive:** -- **the `Pack`** *(the grouping / review unit; was "SpecPack")* — a named authored **aggregate** over `Spec` - instances; the recurring unit of review/ideation (a `Spec` is always assessed in the context of its related set). - It states **no truth of its own** — it *references* model `Spec`s, never owns vocabulary. A `Spec` may belong to **many**; - membership is authored on a manifest, and the membership edge is **derived** from it. Distinct from *refinement* - (below). It is the unit a **Design Review** renders "in context." -- **the anchor** *(was "marker")* — a human-written pointer **in source code** to a `Spec`'s **ID**. Carries - **identity only, never intent**; lives in code, not spec files; yields an **`anchored`**-claim binding edge. - -**Two grouping mechanisms, kept distinct:** *refinement* (a parent `Spec` → its children — authored truth that -happens to have descendants) vs *the aggregate* (the cross-cutting review collection — no truth of its own; may -span or sub-slice the refinement hierarchy). - -**Derived, never authored:** code/structure/runtime nodes (impl · api · component · test · …) and the derived -edges/facts (`satisfies`, `has-verifier`, `observed`, derived membership). A `Spec` *links to* these; it -never authors them. - -## §3 — Relations [SETTLED · concept] · verb-form names ratified -**Concept:** an **authored, typed, directed edge** between two authored `Spec`s — a *human-asserted* connection -(the intent half of the graph). Distinct from *derived* edges (binding, membership, structural). - -**Authored relations** (verb forms read correctly as directed edges; UML-aligned): - -| Relation | Direction | Means | Anchor | -|---|---|---|---| -| **refines** | child → parent | a more precise child; also how altitude descends (Story refines Feature refines Epic) | UML «refine» | -| **dependsOn** | dependent → dependency | needs another `Spec` to hold | UML *Dependency* | -| **constrainedBy** | bounded → constraint | bounded by a rule / NFR / policy `Spec` (a *typed* dependency) | — | -| **decidedBy** | shaped → decision | shaped by a Decision Record (a decision `Spec`; `doc:` only for a genuinely external ADR) | UML «trace» | -| **verifies** | verifier → target | an example/scenario verifies a target — *also* emitted derived from a test **anchor** (**same type, different `claim`, never collapsed**) | UML «verify» | -| **supersedes** | new → old | current forward-pointer between two Decision Records that both still exist (not history — git holds that) | — | +| **`Spec`** | the one authored truth-primitive: a durable, **enrich-in-place** statement of intended system truth, never migrated to a different artifact type | "model element" · `Requirement`/`ImplementedRequirement` splits | +| **envelope** | the stable minimal outer shape — `id` · `title` · `kind` · `altitude` · `readiness` · `relations`; changes almost never (L9) | — | +| **`kind`** | the category of truth — a **true subtype** (changes required detail + validation); 8 values, below | "genus/species" | +| **`altitude`** | size/scope — a **position**: `epic → feature → story`; `epic` is the ceiling (above-epic defers) | "abstraction" | +| **`readiness`** | design maturity — a **position**: `idea → scoped → defined → ready`; **stated** by the author, checked against a floor | "status" · "candidate" (FSM imprint) · readiness is never "claimed" | -**Derived (mechanics in §4/§5):** `belongsTo` (→ grouping, from the manifest) · `satisfies` (code → `Spec`, -from the anchor). **Dropped:** `exemplifies` (= example-kind + `refines` + `verifies`). +**`kind` — the 8 specializations** (literal → display): `behavior` → Use Case / Behavior · `workflow` → +Workflow · `example` → Example / Scenario · `rule` → Business Rule · `constraint` → Constraint (NFR) · +`model` → Domain Model · `decision` → Decision Record · `contract` → Contract. -Granularity calls (decided): **`constrainedBy` and `decidedBy` kept distinct** — "bounded by an NFR" and "shaped -by a decision" are high-value, separately-queryable intents that a generic `dependsOn` would flatten. +The familiar delivery nouns are **named coordinates on the one primitive, never separate authored types**: -## §4 — The authored / derived split [SETTLED · concept; names locked: `anchor`, `claim`] -**The boundary** *(working name "epistemic boundary"; a friendlier Studio name is a minor open item)*: humans -assert **intent**, machines assert **structure**, and the two are **never confused** — the single most -load-bearing invariant the Protocol enforces. +| Label | Is | Is not | +|---|---|---| +| **Scenario** | a low-altitude `example`-kind `Spec` that `refines`/`verifies` a Story | an altitude or a kind | +| **NFR** | a `constraint` with a measurable `target`; the *flavor* (performance / security / …) lives on the section | a separate kind | +| **Executable Spec** | an `example` that **has a verifier** (a delivery fact) | a readiness rung | +| **capability / domain** | a **projection** (Capability Map over high-altitude `behavior` `Spec`s) and/or a **`Pack` grouping** | a kind or an altitude | -**Everything in the graph is a `claim`**, and every node/edge carries *what kind of claim it is*. Claims are -**never collapsed** (a `declared` truth is never silently "satisfied" by an `inferred` fact): +## Sections (→ `02` §3) -| `claim` | What it is | Authority | Lives in | -|---|---|---|---| -| **declared** | human-authored *intent* (relations, readiness, decisions) | authoritative intent | spec / grouping files | -| **anchored** | a human *binding* — an **anchor** points code → a `Spec`'s ID | authoritative binding (no intent) | source code | -| **inferred** | machine-derived *structure* (calls, imports, symbols) | advisory — never authoritative | nowhere (derived) | - -- **`anchor`** *(locked; was "marker")* — the in-code pointer, e.g. `// anchor: spec:orders.create-order`. Carries - **identity only, never intent**; emits an **`anchored`** edge (`satisfies`, `verifies`). -- **`claim`** *(locked; replaces "provenance")* — names a fact's **epistemic status**, not its ancestry. -- **Umbrella:** **authored** = human claims (`declared` + `anchored`); **derived** = machine output. -- **No 4th `claim`; computed-from-authored edges inherit their source's `claim`.** The enum stays exactly - `declared / anchored / inferred`. `inferred` is the *advisory* kind — derived by **analysis** (calls, imports). - An edge *computed deterministically from an authored source* is a derivation **mechanism, not a claim - category**: it **inherits the source's `claim`**. Worked case: **`belongsTo` carries `claim:"declared"`** — it - is a deterministic re-expression of the `Pack`'s authored (declared) manifest, so it inherits that authority; - it is **not** advisory `inferred`. (Mechanics in §5.) - -> **Naming side-effect, honored:** since `claim` now names the origin class, **readiness is "stated/asserted" by -> the author — not "claimed."** Reserve "claim" for the `claim` class. (Clears the one collision.) - -## §4b — Delivery facts (derived realization signals) [SETTLED] -**Delivery facts** are derived truths about a `Spec`'s *realization* — computed from graph edges, shown as badges, -**never authored** (authoring one is a §6 honesty violation). They are distinct from `readiness`: a delivery fact -is what the machine *observes*; readiness is what the human *states*. - -| Delivery fact | Derived when | Means — and does **not** mean | +| Term | Definition | Aliases to avoid | |---|---|---| -| **`implemented`** | ≥1 `satisfies` edge (from an `anchor`) resolves to the Spec | code **claims** to realise it — *not* that it works, is reachable, or is live | -| **`has-verifier`** | ≥1 `verifies` edge from an enabled verifier resolves to the Spec | a verifier **exists** — *not* that it passed (pass/fail is CI's, not the graph's) | -| **`observed`** *(aspirational)* | runtime evidence links to the Spec's target | seen live in production / telemetry | - -> **"Enabled verifier" and the liveness boundary.** These facts are about **binding and existence, never -> liveness.** `has-verifier` derives from an *enabled verifier* — a verifying `example`/scenario backed by a -> **linked, resolvable test anchor** (§4/§5) — where "enabled" means **structurally bound, not runner-executed**: -> whether that test is skipped, quarantined, or glob-excluded is **CI's concern, exactly as pass/fail is**. -> `implemented` likewise records that code *claims* to realise the Spec (an `anchored` binding), **not** that it -> is reachable or live. Runtime liveness is the *separate, higher* **`observed`** fact (aspirational). The ladder -> is deliberate — **`implemented`** (a binding exists) → **`has-verifier`** (a verifier exists) → **`observed`** -> (it ran / was seen live) — and a misbound anchor (e.g. on dead code) is a human honesty error caught in review, -> not something the linkage check can detect: the graph checks *binding*, never *liveness*. - -**The payoff queries** (possible only because readiness and delivery facts are *separate* axes): -- `ready ∧ ¬implemented` → the **build backlog** (designed, not yet built). -- `implemented ∧ ¬ready` → the **drift alarm** (code ran ahead of the design). - -**Readiness floor vs delivery fact** (keep this crisp in the cleanup): a readiness floor (§6) checks the -**structure required to *derive* a fact**, never the fact itself. Claiming `ready` may require that bindings -*resolve* (so `implemented` is *derivable*) — it never requires that the Spec *is* `implemented`. Readiness is a -claim about the design; delivery facts are observations about the code. - -## §5 — The read side: the one graph [SETTLED] - -**The one graph** is the single *derived* read model of the entire delivery intent. It is a flat, typed graph of **nodes** and **edges** — a deterministic snapshot-projection of the repo at a specific commit. It is the **sole** input that every consumer reads: - -- No consumer (coding agent, Studio surface, diagram generator, API reference, context bundle, etc.) ever reads source directly. -- There is no second store or parallel model. -- Every view, diagram, and agent surface is a pure **projection** of this graph (see §7). - -**Derivation** is strictly deterministic: `graph = f(repo)`. The graph is fully **regenerable** — delete it and rebuild it byte-identically from the same commit. This is the mechanical heart of the honesty guarantee in §0 and §4. - -**The producer** is the **`extractor`**. It is the *only* component that reads source. Its responsibilities are: -- Parse authored `Spec`s, anchors, packs, and authored relations. -- Perform the deterministic derivation that produces nodes, edges, and `claim` values. -- Emit a validation report (conformance + honesty failures). -- Produce the graph artifact that everything downstream consumes. - -**Node and edge typing** keeps concerns separate: -- Every node carries `nodeType` (e.g. `Primitive`, `Pack`, `Anchor`, `CodeNode`, `RuntimeObserved`, …). -- `Primitive` nodes additionally carry `specKind` (the truth-category from §2: Decision Record, Use Case, NFR, etc.). -- This split prevents the old single `kind` field from colliding between structural class and domain truth-category. - -The one graph is the concrete realisation of the classic distinction between *model element* (truth) and *view element* (projection), made executable and self-validating. All conformance checks, all surfaces, and all agent behaviour flow from this single derived artifact. - -## §6 — Validation & honesty (conformance) [SETTLED] -The deterministic machinery that makes the protocol **self-validating** (§0). Checks run over the one -graph; an **error fails the build**, a **gap informs**. The meta-model defines the contract; **instances conform**; -these checks verify conformance. *(Conformance here means conformance to the typed meta-model; for a spec's own -implementation tier, use CORE/ASPIRATIONAL.)* - -| Family | Asks | Examples | +| **section** | the typed, optional detail-slice of a `Spec` — the **extension surface**; one concern each: `intent` · `behavior` · `constraints` · `model` · `design` · `decision` · `verification` · `ui` | "Facet" · "aspect" | +| **section ⟷ kind duality** | `constraints`/`model`/`decision` have a same-named `kind` twin: keep **inline** when local detail; **promote** to a standalone `Spec` when shared or needing its own identity/lifecycle/review | — | + +## The other authored things (no system truth — → `02` §4, `04` §2) + +| Term | Definition | Aliases to avoid | |---|---|---| -| **conformance checks** | "is this *well-formed* against the meta-model?" | referential integrity (every ID resolves) · `claim` separation never collapsed (§4) · node/edge typing valid · grouping **coherence** (members + `modelRefs` resolve; no duplicate members) · **ambiguity-is-loud** (duplicate IDs / true conflicts fail, never auto-resolve) | -| **honesty checks** | "is this *not pretending* to be something it isn't?" | **authoring-shape honesty** (no hand-authored `satisfies` / `anchored` / `inferred` / delivery-facts) · **honest readiness** (a *stated* rung is structurally earned) | - -The **honesty** family is a real differentiator: most tools do only conformance; the "not faking derived truth" -layer is what makes the graph trustworthy for agents and humans alike. - -**Terms:** -- **`validator`** — one individual deterministic check (the boring, correct industry noun); validators group into - the two families above. -- **`readiness floor`** *(was "readiness profile")* — the **minimum structural requirement to *state* a readiness - rung**. A floor to clear, **never a quota to fill or a score** (significance governs detail — no tier-filling). - `ready` is thus the floor cleared **plus a human's `declared` statement** — *that a review actually occurred is - not a fact the graph records or a validator checks* (that would be the workflow-gating §0 guardrail 1 forbids); - where approval provenance matters (a baseline, §8) it is **git-native** (§5), not an authored primitive. -- **`gap`** — a surfaced absence (e.g. a `ready` `Spec` with no verifier). Informative, **never a gate**. -- **`orphan`** — a `Spec` with no relations and nothing pointing at it. Informative. - -## §7 — Surfaces & projections [SETTLED] -**`projection`** — a pure function of the graph producing a consumer artifact: disposable, regenerable, **never a -second source**. *Everything* consumer-facing is a projection (a *view element*). - -| Surface | What it is | Notes | +| **`Pack`** | the authored grouping / review **aggregate** over `Spec`s; states **no truth of its own**; its `framing` is a plain descriptive note; a spec may belong to many packs; membership lives on the manifest, the `belongsTo` edge is derived from it | "SpecPack" | +| **anchor** | a human-written pointer **in source code**: *"this code location is the implementation/test **binding** for this Spec ID"* — a binding assertion only, never system-truth content (no behavior, rationale, readiness, acceptance criteria, or delivery facts); yields `anchored`-claim edges | "marker" | + +**Two grouping mechanisms, kept distinct:** *refinement* (parent `Spec` → children — authored truth with +descendants) vs *the aggregate* (the `Pack` — a cross-cutting review collection, no truth of its own). + +## Claims & the authored/derived split (→ `01`, `03` §3) + +| Term | Definition | Aliases to avoid | |---|---|---| -| **Design Review** | the flagship curated review: a `Spec`/pack rendered *in context* — neighbors, relations, `claim`/delivery badges, auto-generated **design questions** + a **findings** table | the **context in which a human decides** to state `ready` (validators check only the structural floor, §6 — the review is human practice, **never a recorded graph fact or a gate**); adopts the recognized SDLC noun | -| **agent surface** | a **visible typed graph the agent *scripts*** via a typed CLI — no verb wall; the schema *is* the contract (under-typing hides a capability) | **push** a Design-Review slice + **pull** by scripting the graph | -| **reader** *(was "handle")* | the thin typed loader: joins + `claim`/taxonomy decode done **once**, returns composable data; authors/persists nothing | a front door, not a store | -| **Mermaid projection** | logical / analytical / topological charts | live, regenerable | -| **reference projection** | interface / API reference, kept current | live | -| **context bundle** | a token-budgeted curated slice pushed to an agent | | -| **MCP surface** | integration for user-facing apps — designed-in, **deferred build**, shape TBD | distinct from the agent surface (agents *script*; apps *integrate*) | -| **impact graph** *(was "mechanical substrate"; aspirational)* | the exhaustive import/symbol structure for blast-radius / find-all-usages | divergence from the curated graph is **curation, not drift** | - -**The edit model** — **intent → agent → git → conformance checks** (§6). No patch loop, no write-back, no -codemod-from-view. -- **intent composition** — the write-affordance: compose **scoped intent**, hand it to an agent that edits source - exactly as a human would; git records it; §6 gates. The view is a process-orchestrator, not an editor. -- **scoped intent** — *what* is composed: an explicit change bounded by a `Spec` / its neighbors / a pack / - open questions. - -## §8 — Delivery-process execution [SETTLED] - -**Stance:** Delivery-process execution vocabulary is adopted from established delivery practice as **recognized nouns** and realized — where realized at all — as **projections or descriptive vocabulary** over the one graph. These terms are **never** modeled as gates, FSM states, mandatory sequences, or additional authored truth-primitives in Phase 0. The Protocol imposes no particular delivery style (iterative or sequential). - -The goal is recognition and lightweight utility without re-importing the process state-machine the rubric explicitly rejects. - -**How the classic delivery-execution terms land in the Protocol:** - -| Term | Traditional meaning | How it lands in the Protocol | -|------|--------------------------|----------------------| -| **discipline** | A concern/area of work (Requirements, Analysis & Design, Test, Deployment, …) | A **lens / projection** — filter or group `Spec`s in the one graph by `kind` or section. “Show me the Requirements discipline” = show all behavior `Spec`s (+ the Capability Map projection). Not a phase you pass through. | -| **phase** | Broad sequential lifecycle stages | Descriptive vocabulary only. Optional roadmap framing. Never a gate or enforced sequence. | -| **iteration** | One integration loop leading to a release | Optional temporal grouping. Can appear as a **roadmap / now-next-later projection**. Not enforced. | -| **milestone** | Assessment checkpoint | Optional named checkpoint (a projection over readiness and delivery state). Not a gate. | -| **release** | A delivered functional set | A **tagged set** surfaced as a projection (backed by a git tag). | -| **baseline** | Reviewed/approved, change-controlled snapshot | A **named approved snapshot** (≈ a git tag over a set of `ready` `Spec`s). The **git tag itself is the approval artifact** — a signed tag carries approver identity + approved-at commit — so approval provenance is **git-native** (§5, *git is the event log*), **not** an authored review primitive or a validator-checked property. Vocabulary + optional projection. | - -**The discipline ≈ kind/section mapping** (how the Protocol supports the disciplines-and-phases picture without its gates): - -- **Requirements** → behavior `Spec`s (+ the Capability Map projection) -- **Analysis & Design** → design section + Decision Records -- **Test** → example `Spec`s + `verifies` relations -- **Deployment** → `observed` / evidence nodes -- **Config & Change Management** → git (the source of truth) -- **Project Management** → packs + roadmap projections -- **Business Modeling** → `model` `Spec`s - -**A disciplines × phases × iterations distribution chart** becomes a **Mermaid / analytical projection** — a view showing how authored and derived activity distributes across the graph. It is never a plan the system enforces. - -**Realization level (Phase 0):** -- **Discipline-as-lens** is realized (lightweight, high-value filter over the one graph). -- **Release** and **baseline** are realized as git-tag projections. -- **Phase, iteration, and milestone** remain descriptive vocabulary with optional roadmap projections. -- None of these terms are modeled as additional authored truth-primitives, relations, or validators in Phase 0. They are adopted for recognition and lightweight utility only. - -This section demonstrates the rubric above in action: adopt the established nouns the industry already understands, but realize them only as projections and descriptive vocabulary — never as the gating machinery that was explicitly rejected. - -## Term ledger -- **Locked names:** **`Spec`** (the one authored primitive) · **`anchor`** (in-code pointer; was "marker") · **`claim`** (epistemic status; replaces - "provenance"), values `declared` / `anchored` / `inferred` · **`the graph`** / **`extractor`** (producer) · - **`conformance`** (instances *conform*; checks = conformance + honesty) · **`readiness floor`** (was "profile") - · `validator` · `gap` · `orphan` kept · **`projection`** · **`Design Review`** (flagship) · **`reader`** (was - "handle") · **`impact graph`** (was "mechanical substrate") · `agent surface` · `context bundle` · `MCP surface` - · **`intent composition`** / **`scoped intent`**. -- **`Spec` descriptor values locked:** `kind` ∈ {`behavior`, `workflow`, `example`, `rule`, `constraint`, `model`, - `decision`, `contract`} (lowercase literals + display labels) · `altitude` ∈ {`epic`, `feature`, `story`} - *(field renamed from `abstraction`)* · `readiness` ∈ {`idea`, `scoped`, `defined`, `ready`}. `capability` & - domain = **projections**; `NFR` / `Scenario` = labels, not separate kinds. -- **More locked:** **`Pack`** (the grouping / review unit; was "SpecPack") · **`section`** (the typed detail-slice - / extension surface; **replaces "Facet"** — locked 2026-06-07) · **delivery facts** (derived, never - authored): `implemented` · `has-verifier` (a verifier *exists*, not that it passed) · `observed` (aspirational). -- **Rejected terms:** `provenance` (concept kept → **`claim`**) · `marker` (→ **`anchor`**) · `FSM`/status-FSM - (implementation-leak from the Libar Application Platform) · `model element` (as the primitive's name) · - `genus`/`species` (as labels; the split is kept, described plainly) · `abstraction` (→ **`altitude`**) · - `candidate` (as a readiness rung — FSM-status imprint) · `Scenario` / `capability` / `NFR` (as separate - descriptors — they are labels / projections, not descriptors) · `Facet` (→ **`section`**) · `SpecPack` - (→ **`Pack`**). -- **Locked usage:** `authored` / `derived` (umbrella pair kept; "derived" sharpened in §4 — *inferred* vs - *computed-from-authored*) · **readiness verb** = "stated/asserted", **not** "claimed" (reserve "claim" for the - `claim` class). -- **Resolved terminology:** the meta-model defines the **contract**; **instances conform**; the checks are - **conformance checks + honesty checks**. "govern"/"validate" retired as the umbrella — the meta-model defines the - contract, it does not govern/police. -- **Naming resolved (MD-5):** the meta-model is **the Protocol** (product **Libar Software Delivery Protocol**) — - "protocol" is now both the product name and the meta-layer noun. "process" is retained only for the modeled - **software-delivery process** (the activity) and the rejected **process state-machine**. (DECISIONS MD-5.) -- **System name (resolved):** **Libar Software Delivery Protocol** — CLI **`sdp`**; npm - **`@libar-dev/software-delivery-protocol`** (single package); repo `libar-dev/software-delivery-protocol`. - Namespaces: `@libar-dev/` (OSS) vs `@libar-ai/` (commercial). Satisfies the standing criteria (evokes the - executable delivery spine; not PM-flavored; room for the OSS backend *and* the commercial Studio); "Libar - Delivery Process" / "Architect" were the disfavored alternatives. The primitive is named **`Spec`** (locked). +| **`claim`** | a fact's **epistemic status** — never its ancestry; values `declared` / `anchored` / `inferred`, **never collapsed** | "provenance" | +| **declared** | human-authored *intent* (relations, readiness, decisions) — authoritative intent | — | +| **anchored** | a human *binding* — an anchor points code → a `Spec` ID — authoritative binding, no intent | — | +| **inferred** | machine-derived *structure* (calls, imports) — advisory, never authoritative | — | +| **authored / derived** | the umbrella pair: authored = human claims (`declared` + `anchored`); derived = machine output | — | + +No 4th `claim`: an edge computed deterministically from an authored source is a derivation **mechanism, not a +claim category** — it **inherits** its source's `claim` (so `belongsTo` carries `declared`). + +## Delivery facts (derived realization signals — → `02` §2) + +| 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 | — | +| **`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 graph & extraction (→ `03`) + +| Term | Definition | Aliases to avoid | +|---|---|---| +| **the one graph** | the single *derived* read model — a flat, typed, deterministic, regenerable snapshot-projection of the repo at a commit; the **sole** input every consumer reads | "second store" (forbidden) | +| **`extractor`** | the producer — the *only* component that reads source; derives nodes, edges, `claim`s, and the validation report | — | +| **`nodeType` / `specKind`** | structural class (`Primitive`/`Pack`/`Anchor`/`CodeNode`/…) vs the truth-category on `Primitive` nodes — kept split so they never collide | a single `kind` field | +| **git is the event log** | history and prior states live in git; the graph carries only current state | audit tables / lifecycle bookkeeping in the graph | + +## Validation & honesty (→ `05`) + +| Term | Definition | Aliases to avoid | +|---|---|---| +| **`validator`** | one individual deterministic check; groups into the two families below | — | +| **conformance checks** | "is this *well-formed* against the meta-model?" — referential integrity · duplicate IDs · `claim` separation · grouping coherence · ambiguity-is-loud | — | +| **honesty checks** | "is this *not pretending*?" — authoring-shape honesty (no hand-authored derived edges/facts) · honest readiness against the floor | — | +| **readiness floor** | the **minimum structural requirement to *state*** a readiness rung — a floor to clear, **never a quota to fill** or a score | "readiness profile" | +| **`gap`** | a surfaced absence (e.g. `ready` with no verifier) — informative, never a gate | — | +| **`orphan`** | a `Spec` with no relations and nothing pointing at it — informative | — | + +`ready` = the floor cleared **plus a human's `declared` statement**; that a review occurred is never a graph +fact or a checked property (that would be workflow-gating). The honesty bound: claim **"deterministically +validated," never "provably correct."** + +## Surfaces & projections (→ `06`) + +| Term | Definition | Aliases to avoid | +|---|---|---| +| **`projection`** | a pure function of the graph producing a consumer artifact — disposable, regenerable, never a second source | — | +| **Design Review** | the flagship curated review: a `Spec`/`Pack` rendered *in context* — the context in which a human decides to state `ready`; human practice, never a recorded fact or gate | — | +| **agent surface** | a **visible typed graph the agent *scripts*** via the typed CLI — no verb wall; the schema *is* the contract (under-typing hides a capability) | a 30-verb API · raw-JSON-you-rejoin | +| **`reader`** | the thin typed loader: joins + `claim` decode done once, returns composable data; authors/persists nothing — a front door, not a store | "handle" | +| **context bundle** | a token-budgeted curated slice pushed to an agent | — | +| **MCP surface** | integration for user-facing **apps** (designed-in, deferred build) — distinct from the agent surface: agents *script*, apps *integrate* | — | +| **impact graph** *(aspirational)* | the exhaustive import/symbol structure for blast-radius / find-all-usages; divergence from the curated graph is **curation, not drift** | "mechanical substrate" | +| **intent composition** | the write-affordance: compose **scoped intent**, hand it to an agent that edits source; git records it; conformance checks gate — no patch loop | patch-back / codemod-from-view | +| **scoped intent** | *what* is composed: an explicit change bounded by a `Spec` / its neighbors / a `Pack` / open questions | — | + +## Relations (authored, typed, directed `Spec`→`Spec` edges — → `02` §6) + +| Relation | Direction | Means | Industry anchor | +|---|---|---|---| +| **refines** | child → parent | a more precise child; how altitude descends | UML «refine» | +| **dependsOn** | dependent → dependency | needs another `Spec` to hold | UML *Dependency* | +| **constrainedBy** | bounded → constraint | bounded by a rule / NFR / policy `Spec` (a *typed* dependency) | — | +| **decidedBy** | shaped → decision | shaped by a Decision Record (`doc:` only for a genuinely external ADR) | UML «trace» | +| **verifies** | verifier → target | an example/scenario verifies a target — also emitted derived from a test anchor (same type, different `claim`, never collapsed) | UML «verify» | +| **supersedes** | new → old | current forward-pointer between two Decision Records that both still exist | — | + +**Derived, never authored:** `satisfies` (code → `Spec`, from an anchor) · `belongsTo` (→ grouping, from the +manifest). **Dropped:** `exemplifies` (= `example` kind + `refines` + `verifies`). + +## Delivery-process vocabulary (adopted nouns, realized only as projections — → `06` §6) + +**discipline** (a lens/filter over the graph, not a phase you pass through) · **phase / iteration / +milestone** (descriptive vocabulary, optional roadmap projections, never gates) · **release** (a tagged set — +a git-tag projection) · **baseline** (a named approved snapshot; the **signed git tag is the approval +artifact** — approval provenance is git-native, never an authored primitive). + +## A worked dialogue (the language in use) + +> **Engineer:** Is `spec:orders.create-order` ready to implement? +> **Agent:** It *states* `defined`, and the floor checks out — rules and examples present, every constraint +> target machine-readable, no blocking open questions in `intent`. It is not `ready`: its `dependsOn` target +> `spec:payments.authorize-payment` is still `scoped`. +> **Engineer:** And the scenario under it? +> **Agent:** `spec:orders.create-order.valid-cart` is an `example` at `story` altitude that `refines` and +> `verifies` the parent. Its test anchor resolves, so it is an **enabled verifier** — the parent carries +> `has-verifier`. That edge is `anchored`; the example's own `verifies` is `declared`. Never collapsed. +> **Engineer:** Anything dishonest in the pack? +> **Agent:** One **drift alarm**: `spec:orders.order-inventory-rule` is `implemented ∧ ¬ready` — an anchor +> binds code to it, but the design isn't mature. And one `gap`, informative only: the NFR has no verifier. + +## Flagged ambiguities (named, not silently resolved) + +- **"epistemic boundary"** is a *working name* for the humans-assert-intent / machines-assert-structure + division (`01`); a friendlier Studio-facing name is a minor open item. +- **`.spec.ts`** as the Spec file extension collides with the universal JS test-runner glob — an open + Representation-level decision (the grill agenda, `plans/03`); the model name `Spec` itself is settled. +- **`ref()`** in the DSL is today a **spec-only** reference builder wearing a generic name (it rejects + `pack:`/`doc:` targets) — a doc-note item on the Wave-B backlog (`plans/02`). + +## Term ledger (locked / rejected / resolved) + +- **Locked:** `Spec` · `Pack` · `section` · `anchor` · `claim` (`declared`/`anchored`/`inferred`) · the + graph · `extractor` · `conformance` · `readiness floor` · `validator` · `gap` · `orphan` · `projection` · + `Design Review` · `reader` · `impact graph` · `agent surface` · `context bundle` · `MCP surface` · + `intent composition` / `scoped intent` · delivery facts `implemented`/`has-verifier`/`observed`. +- **Descriptor values locked:** `kind` ∈ {`behavior`,`workflow`,`example`,`rule`,`constraint`,`model`, + `decision`,`contract`} · `altitude` ∈ {`epic`,`feature`,`story`} · `readiness` ∈ + {`idea`,`scoped`,`defined`,`ready`}. +- **Rejected:** `provenance` (→ `claim`) · `marker` (→ `anchor`) · `Facet` (→ `section`) · `SpecPack` + (→ `Pack`) · `abstraction` (→ `altitude`) · `candidate` (as a readiness rung — FSM imprint) · FSM / + status-FSM · `model element` (as the primitive's name) · `genus`/`species` · `Scenario`/`capability`/`NFR` + as descriptors (labels / projections, not descriptors) · `exemplifies` (relation). +- **Locked usage:** readiness is **"stated/asserted," never "claimed"** ("claim" is reserved for the `claim` + taxonomy) · the meta-model defines the **contract**, **instances conform**; "govern"/"police" retired · + checks are **conformance checks + honesty checks**. +- **Naming (resolved — MD-5):** product **Libar Software Delivery Protocol** (short form "the Protocol"); + CLI **`sdp`**; npm **`@libar-dev/software-delivery-protocol`** (single package); repo + `libar-dev/software-delivery-protocol`; namespaces `@libar-dev/` (OSS) vs `@libar-ai/` (commercial). + "Protocol" names the meta-layer (a conformance contract, not a workflow); "process" is retained only for + the modeled activity. From e6f0a2065105b4c27470b89d97cf741b5ced69f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 03:34:12 +0200 Subject: [PATCH 10/29] Archive review artifacts from gitignored scratch into tracked reviews/ Implementation review, founding-ideation review, adversarial review + its prompt, and the fold-session prompt - ~13h of review signal that previously vanished on a clean clone (plans/04 s3 optional-archive step). README maps each artifact to where its durable findings already landed. --- reviews/01-opus-plan-implementation-review.md | 62 +++ .../02-founding-ideation-back-for-review.md | 402 ++++++++++++++++++ ...03-post-split-adverserial-review-prompt.md | 159 +++++++ reviews/04-post-split-adversarial-review.md | 134 ++++++ reviews/05-plan-finalization-prompt.md | 118 +++++ reviews/README.md | 18 + 6 files changed, 893 insertions(+) create mode 100644 reviews/01-opus-plan-implementation-review.md create mode 100644 reviews/02-founding-ideation-back-for-review.md create mode 100644 reviews/03-post-split-adverserial-review-prompt.md create mode 100644 reviews/04-post-split-adversarial-review.md create mode 100644 reviews/05-plan-finalization-prompt.md create mode 100644 reviews/README.md diff --git a/reviews/01-opus-plan-implementation-review.md b/reviews/01-opus-plan-implementation-review.md new file mode 100644 index 0000000..bacfc29 --- /dev/null +++ b/reviews/01-opus-plan-implementation-review.md @@ -0,0 +1,62 @@ +Verified end-to-end and read every source, example, test, and the implementer's own decision log. Here's my review. + +## Verdict + +**This is a strong first session.** It's fully green (`npm run check`: typecheck × 2, lint, format, 43 tests, build all pass), faithful to the ratified language, and the trust-model boundaries are respected (no extractor, no graph emission, no graph gate, delivery facts derive-only, anchors identity-only). The test discipline is genuinely good — compile-time `@ts-expect-error` fixtures enforcing anchor honesty, exact-message validator assertions, deferred-clause coverage. The scope expansion I'd flagged (working validators) turns out to be a **deliberate decision you made in the implementation interview** ("contracts + tiny validators, overriding the pre-plan's stricter 'contracts/data only' wording"), so that's sanctioned, not creep. + +That said, there are a handful of real issues — one of which I'd fix *before* Slice 1 because it sits inside the tracer bullet itself. + +--- + +## The one I'd act on before Slice 1 — the example violates static-extractability (P5) + +Both scenario specs build their `behavior` section with a runtime call: + +```ts +// examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts:5 +const thenKey = "then"; +const createOrderValidCartBehavior = Object.fromEntries([...]) as Record; +``` +(same in `create-order-invalid-cart.spec.ts:5`) + +`★ Insight ─────────────────────────────────────` +P5 says spec source must be **static, side-effect-free literals** — "a JSON file that TypeScript happens to validate" — because the Slice 1 `ts-morph` extractor reifies it *without executing it*. `Object.fromEntries([...])` is a function call the extractor can't statically evaluate. Per the two-tier rule (`04` §1), a non-static *section* isn't a build error — it's **silently dropped with a warning** (L3). So under the next slice, these two scenarios lose their `given/when/then`, and then the graph-level `example` readiness floor (which *requires* given/when/then at `defined`+) flips them from "valid" to an honesty violation. +`─────────────────────────────────────────────────` + +This is the sharp irony: the tracer bullet exists to *de-risk the extractor*, and it's the one place that plants a landmine for it. It also perfectly illustrates the architectural seam below — the Session-1 authored-model validator sees `given/when/then` (because `Object.fromEntries` already ran at module load), but the static extractor won't. The fix is trivial (a plain object literal); the systemic fix is the deferred `sdp/spec-static` lint rule (`04` §1). I'd also add a guard so Slice 1's "done" includes *the example survives static extraction byte-for-byte.* Everything else under `examples/**/specs` (7 files) is clean static literals — this is isolated to the two scenario specs. + +--- + +## Real findings (Slice 1/2 territory, not blockers) + +**1. Sections are `Record` — the biggest design divergence.** `src/model/sections.ts:14-23` types every section as an open bag. + +`★ Insight ─────────────────────────────────────` +P7 ("types describe shape; validators decide *completeness*") is about which fields are *required* for a readiness — **not** about whether fields are typed at all. The design intent (L9, `02` §3) is **typed-but-optional** sections (`intent.outcome?: string`, `constraints: ConstraintSection[]`, …), with validators deciding which are required. `Record` collapses "optional" into "unknown," which has three downstream costs. +`─────────────────────────────────────────────────` +- The tracer bullet's section coverage is **hollow** — the example typechecks against `unknown`, so "it compiles" proves nothing about the DSL capturing section shapes (the headline goal of the session). +- The readiness-floor validator is forced to **string-probe paths at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a section field name silently passes the floor. +- It enables finding #2. + +I'd type at least the sections the floors and extractor depend on (`intent`, `behavior`, `constraints`, `model`, `verification`) in Slice 1/2. + +**2. Honesty bug — blocking open questions checked in the wrong section.** `validators.ts:199-206` (and `readiness-floor.ts:89-92,104-106`) read `spec.design.openQuestions` and `spec.decision.openQuestions`. The canonical home is **`intent.openQuestions`** (`02` §3: "openQuestions may be flagged blocking to prevent stating a readiness past `defined`"; the `04` worked example puts it under `intent`). As written, a spec with a blocking `intent.openQuestions` can state `defined`/`ready` and the honesty check won't catch it — and `design`/`decision` don't even canonically carry `openQuestions`. This is the genuine honesty differentiator missing its target. + +**3. `constraints` is a single object, not an array.** `sections.ts:18` has `constraints?: ConstraintsSection` (one Record); `02` §1 specifies `constraints?: ConstraintSection[]`. A spec can have several NFRs (perf + security + …); the single-object shape can't express that. (Tellingly, `validators.ts:139-165` defends against *both* array and record forms — a sign the shape was unsettled.) + +**4. Referential integrity is blind to section-embedded refs.** `validateDanglingReferences` (`validators.ts:363-414`) checks relations, pack members, modelRefs, and anchors — but `behavior.examples: [ref(...)]` and `behavior.rules: [ref(...)]` (see `create-order.spec.ts:22-26`) live inside the untyped section, so a dangling `ref` there escapes the check. `05` §2.1 wants *every* referenced ID to resolve. This is a direct consequence of #1. (Minor sub-note: the docs model `behavior.rules` as **prose strings** and only `examples` as refs; putting refs in `rules` is a modeling deviation.) + +--- + +## Minor / hygiene + +- **tsup banner leaks a shebang onto the library entry.** `dist/index.js` starts with `#!/usr/bin/env node` — because `tsup.config.ts:51-53` applies `banner.js` to *all* entries. This directly contradicts the implementer's own recorded decision (`decisions.md:1`: "use a source-file shebang for the CLI … so the library build stays clean"). Harmless in practice but wrong; the cleanest fix is no banner + keep the source shebang only on `src/cli/sdp.ts`, or split the CLI into its own tsup build. +- **`vitest-test.mjs` wrapper** (`package.json:24` → `node ./vitest-test.mjs`) exists only to allow `npm test -- --run x` without double-`--run`. It works, but it's a non-standard moving part; `"test": "vitest run"` is the conventional choice unless that ergonomic matters to you. +- **`.sisyphus/` will be committed** (only `.sisyphus/evidence/` is gitignored). The notepads/boulder.json are agent-runner working state — decide whether you want that in history or gitignore the whole dir. Note also that `plans/` is now gitignored, so the plan I saved is local-only (deliberate per your `.gitignore` comment). +- `package.json` lacks `description`/`license`/`repository` — fine for now, matters before any publish. + +--- + +## Forward-looking: the AuthoredModel seam + +The validators consume an `AuthoredModel` DTO, which is sanctioned and well-fenced (honest doc-comments, `deferredInSession1` clauses). The thing to decide *before* Slice 3: the design says validators run over **the one graph** (P2: "no consumer … maintains a parallel model"). When the extractor lands, either (a) these validators migrate to consume `GraphSchema`, with `AuthoredModel` retired, or (b) `AuthoredModel` stays as an explicit *pre-graph authoring lint* with a documented reason. What you want to avoid is two parallel validation code paths drifting apart — finding #1 above is already a preview of that drift (authored-model truth ≠ extracted-graph truth). diff --git a/reviews/02-founding-ideation-back-for-review.md b/reviews/02-founding-ideation-back-for-review.md new file mode 100644 index 0000000..fe24601 --- /dev/null +++ b/reviews/02-founding-ideation-back-for-review.md @@ -0,0 +1,402 @@ +Yes. My honest read: **this is much stronger than the discussion we had.** It feels like you did not merely summarize the earlier idea; you extracted the load-bearing design laws, cut the seductive extras, and turned the original “architecture graph from annotated code” idea into something much more defensible: **an executable software-delivery meta-model with a repo-native authored model and a deterministic graph projection.** + +The biggest improvement is that the docs no longer center on tools like decorators, Fastify, Effect, Awilix, Gherkin, or HTML. Those are now correctly treated as **surfaces, adapters, or projections**, not the essence. The essence is: one `Spec` primitive, one graph, honest `claim`s, deterministic derivation, conformance/honesty validation, and git as the event log. That is the right spine. + +My overall verdict: + +> **I would build this direction. I would not pivot. But I would tighten a few terms and MVP acceptance tests before writing much code.** + +--- + +# What is excellent + +## 1. The reframing from “architecture graph tool” to “software delivery protocol” is correct + +The earlier version risked sounding like “annotated code + graph + docs.” This version says something more durable: **the Protocol is a typed, executable meta-model of the software-delivery process**, with three clean levels: protocol/meta-model, authored model, and derived facts. That is a much better framing because it explains why this is not just another code-indexing, BDD, ADR, or documentation tool. + +The wording “deterministically validated, never provably correct” is also very good. It protects the idea from overclaiming. The docs consistently distinguish conformance/honesty checks from design-quality judgment or workflow gating, which is essential if this is going to remain useful rather than become a disguised process engine. + +## 2. The `Spec` primitive is the right center + +The `Spec` design solves the awkwardness we were discussing around “requirements,” “unimplemented specs,” and maturity transitions. The model now says: a `Spec` is one persistent, enrich-in-place statement of desired system truth; it can start vague and become detailed without changing artifact type. That is the key idea. + +The three descriptors are also the right separation: + +```txt +kind = category of truth +altitude = size / scope +readiness = design maturity +``` + +That cleanly avoids several traps: “Scenario” is not an altitude, NFR is not a separate primitive, capability/domain are not squeezed into the same axis, and implementation/verification are not fake maturity rungs. The docs explicitly separate readiness from derived delivery facts like `implemented`, `has-verifier`, and `observed`, which enables the valuable queries `ready ∧ ¬implemented` and `implemented ∧ ¬ready`. + +This is probably the strongest conceptual part of the design. + +## 3. The `claim` taxonomy is the real differentiator + +The declared / anchored / inferred split is excellent. + +```txt +declared = human-authored intent +anchored = human binding from code to spec ID +inferred = machine-derived structure +``` + +This is what makes the system meaningfully different from a documentation generator, a language server, or a graph extractor. The docs are clear that inference is never authoritative and that inferred structure can assist impact analysis or curation but must not densify the curated architecture. + +The line “the Protocol knows which edges are intent a human asserted, which are a binding a human anchored, and which are structure a machine guessed” is the soul of the system. + +## 4. The MVP cut is disciplined + +The MVP boundary is mostly right: one bounded context, roughly 8–12 specs, TypeScript DSL, generic anchors, `ts-morph` extraction, graph JSON, conformance/honesty checks, one generated view, and bidirectional spec↔test trace. + +The deferrals are also right. Cutting runtime-composition depth, Gherkin, harnesses, patch-back, rich projections, architecture enforcement, and deep impact graph keeps the MVP from collapsing under its own ambition. + +Especially good: **Fastify/Awilix/Effect are no longer in the core.** The docs say the MVP binds code to intent generically, and framework-specific runtime composition becomes an adapter later. That is the right correction. + +## 5. “Git is the event log” is a strong simplifying law + +This is a very useful invariant. It prevents the graph from becoming a pseudo-database with history, superseded states, approval bookkeeping, lifecycle records, or audit trails. The current graph is a projection of the repo at a commit; history is reconstructed by checking out prior commits and regenerating. + +That decision removes a huge amount of accidental complexity. + +## 6. Agent-first consumption is a good strategic choice + +The docs correctly identify the agent surface as a typed graph the agent scripts, not a giant verb API and not raw JSON that every agent must rejoin. That is an important design stance. + +This is also where the product can become genuinely differentiated. Lots of tools generate docs. Fewer tools generate a **trustworthy, typed, claim-aware context model** that coding agents can use without hallucinating architecture from raw files. + +--- + +# What I would tighten before implementation + +## 1. Clarify “anchor carries identity only” + +This is the one phrase that could become confusing. + +The docs say an anchor carries identity only, never intent. Conceptually that is right: the intent lives in the `Spec`. But an anchor also emits a `satisfies` or `verifies` edge, which is more than “identity” in the ordinary sense. It is a **binding assertion**. + +I would sharpen the wording to: + +> An anchor carries **binding only, never spec intent**. It may say “this code location is the implementation/test binding for this Spec ID,” but it must never carry behavior, rationale, readiness, acceptance criteria, or delivery facts. + +That removes a possible contradiction while preserving the epistemic boundary. + +Current direction: + +```txt +anchor = identity only, never intent +``` + +Suggested direction: + +```txt +anchor = binding assertion only, never system-truth content +``` + +That better matches the `anchored` claim model. + +## 2. Make Phase 0 aggressively small + +“Phase 0 — protocol as code” is correct, but it is also the biggest place where scope can quietly expand. + +I would define Phase 0 as only this: + +```txt +- Spec, Pack, Anchor TypeScript types +- descriptor enums +- relation builders +- graph node/edge schema +- validator interface +- 3–5 validator fixtures +- one canonical example spec fixture +``` + +Do not build a beautiful protocol framework in Phase 0. Build the smallest executable conformance contract that Slice 1 needs. + +A good Phase 0 “done” test would be: + +```bash +pnpm test fixtures/protocol +``` + +with fixtures like: + +```txt +valid-minimal-idea-spec +invalid-duplicate-id +invalid-non-static-id +invalid-ready-with-blocking-question +invalid-hand-authored-delivery-fact +``` + +## 3. Add a golden graph fixture immediately + +The graph determinism principle is central: `graph = f(repo)`, stable ordering, disposable `generated/`, and byte-identical rebuilds. + +Before building the whole extractor, I would create one tiny fixture repo and a golden expected graph: + +```txt +fixtures/order-management/ + specs/ + src/ + test/ + expected/graph.json +``` + +Then every extractor change must pass: + +```bash +sdp build fixtures/order-management +diff generated/graph.json fixtures/order-management/expected/graph.json +``` + +This will force hard decisions early: + +```txt +path normalization +line number stability +sort order +schema versioning +unknown references +warning/error format +claim inheritance +delivery fact derivation +``` + +Without this, determinism can remain an aspiration longer than it should. + +## 4. Treat “implemented” as a UI hazard + +The docs correctly define `implemented` as “a binding exists,” not that the code works, is reachable, or is live. The Decision diary also records an adversarial clarification that delivery facts are binding/existence, never liveness. + +But users will still misread “implemented.” + +I would keep the internal delivery fact as: + +```ts +"implemented" +``` + +because it enables the valuable `implemented ∧ ¬ready` drift query. + +But in generated views, I would display it as something like: + +```txt +Implemented: yes — anchored binding exists +Verifier: yes — linked verifier exists, result owned by CI +Observed: no — runtime evidence not available +``` + +or: + +```txt +Implementation binding: present +Verifier binding: present +Runtime observation: not tracked +``` + +That way the model keeps its useful terminology, but the UI does not overpromise. + +## 5. Move the derived-readiness banner into MVP if cheap + +The docs say floor enforcement is MVP, while an explicit “stated vs derived readiness” banner can be a fast follow. + +I would include the minimal banner in the MVP view if it is not expensive: + +```txt +Stated readiness: ready +Structural floor reached: defined +Problem: blocking open question exists +``` + +This is not just polish. It teaches users the most important honesty concept: readiness is stated, then checked. If the Design Review view is the first human-facing surface, this banner will make the system’s value obvious. + +## 6. Make `coverage-unknown` a first-class MVP output + +The Decision diary correctly says file-level blast-radius must report unanchored changed files as `coverage-unknown`, never silently under-report. + +I would make that part of the Slice 4 acceptance criteria, not just a design note. + +For example: + +```json +{ + "impact": { + "touchedSpecs": ["spec:orders.create-order"], + "coverageUnknown": [ + { + "file": "src/shared/money.ts", + "reason": "changed file has no anchor" + } + ] + } +} +``` + +This matters because the MVP does not include the full inferred impact graph. So the honest thing is not “impact analysis is complete”; it is “impact analysis is bounded and tells you where coverage is unknown.” + +That is very aligned with the whole honesty posture. + +## 7. Keep `Pack` truthless, but defend it with types + +The `Pack` design is good: it is a grouping/review aggregate, not a truth primitive. It has framing, model refs, and members, but does not state system truth. + +However, this will be tempting to violate. People will want to put outcomes, acceptance criteria, risks, and requirements directly on packs. + +I would make the type intentionally restrictive: + +```ts +type Pack = { + id: PackId; + title: string; + framing?: string; // plain note, not truth + modelRefs?: SpecId[]; + specs: SpecRef[]; +}; +``` + +No `intent`, no `behavior`, no `constraints`, no `readiness`. + +The moment `Pack` starts having truth-bearing sections, the one-primitive model gets weakened. + +## 8. Be careful with “no consumer reads source directly” + +The principle is right: every projection, validator, query, agent surface, and view should consume the graph, not source. + +But source links in views are still useful. The wording should make clear: + +```txt +Consumers may link to source locations recorded in the graph. +Consumers must not independently parse source to derive their own model. +``` + +That distinction will matter for the generated Design Review. + +--- + +# The biggest remaining design risk + +The biggest risk is **authoring ergonomics**, not graph theory. + +The system assumes people will happily author static TypeScript specs that behave like “JSON files that TypeScript happens to validate.” That is a good technical constraint, and the docs state it clearly. + +But if authoring feels heavy, users will either avoid the system or overfit specs to satisfy validators. + +So the MVP should optimize the authoring loop early: + +```txt +sdp new spec +sdp new example +sdp explain spec:orders.create-order +sdp validate --watch +great error messages +copy-pasteable examples +minimal boilerplate +``` + +The docs already warn against tier-filling: significance governs detail, and the Nth instance of an established shape earns a reference, not a re-derivation. That is exactly right. + +Now the implementation has to preserve that ethos. Validators should make dishonesty fail, but they should not train people or agents to pad specs with low-signal content. + +--- + +# What I would build first + +I would not start with the CLI framework, UI, HTML, Gherkin, or runtime adapters. + +I would build this exact sequence: + +```txt +1. Create the Order Management fixture specs first. +2. Implement the TypeScript DSL only enough to express those specs. +3. Implement static extraction for specs and packs. +4. Emit the flat graph JSON. +5. Add anchors for one implementation and one test. +6. Derive satisfies/verifies edges and delivery facts. +7. Add the first validators. +8. Add golden graph determinism tests. +9. Add the reader with byId, byFile, relatedSpecs, impactFromFiles. +10. Generate the simplest Design Review HTML/Markdown view. +``` + +The roadmap already says to write the example specs and anchored code first because that forces usability before the extractor is “finished.” I strongly agree with that. + +--- + +# Suggested MVP acceptance checklist + +I would make the MVP pass these concrete tests: + +```txt +Spec extraction +- Extracts 8–12 specs from /specs. +- Extracts one pack. +- Extracts declared refines/dependsOn/verifies/decidedBy edges. +- Rejects non-static envelope fields. + +Anchor extraction +- Extracts one impl anchor. +- Extracts one api anchor or implementation anchor. +- Extracts one test anchor. +- Emits satisfies and verifies edges with claim: anchored. + +Claim honesty +- Declared, anchored, and inferred claims remain visually and structurally distinct. +- Hand-authored delivery facts fail validation. +- Hand-authored satisfies edges fail validation. + +Readiness honesty +- A valid idea spec passes. +- A scoped spec without relation fails. +- A defined constraint without machine-readable target fails. +- A ready spec with blocking open questions fails. +- A ready spec with unresolved dependencies fails. + +Delivery facts +- A spec with a resolving satisfies edge shows implemented. +- A spec with an enabled verifier shows has-verifier. +- No pass/fail result appears in graph. + +Traceability +- Query: what implements this spec? +- Query: what verifies this spec? +- Query: what spec does this test cover? + +Determinism +- Delete generated output. +- Run build twice. +- Graph output is byte-identical. + +View +- Shows spec tree. +- Shows per-spec details. +- Shows readiness and delivery badges. +- Shows declared vs anchored edges distinctly. +- Shows source links. +- Shows coverage-unknown for changed unanchored files. +``` + +This would prove the thesis without accidentally building the aspirational product. + +--- + +# One thing I would not change + +I would **not** reintroduce Gherkin, Effect, Awilix, Fastify composition, rich HTML Studio, patch-back, or runtime observations into the MVP. + +The docs correctly keep them designed-for but deferred. The MVP should prove the trust model first. If the trust model works, all of those surfaces can attach later. If the trust model does not work, none of the surfaces matter. + +--- + +# My final take + +This is now a coherent product architecture, not just a clever technical mechanism. + +The strongest sentence in the whole set is essentially: + +> **Specs are code; the graph is derived; the `claim` stays honest; git is the event log.** + +That slogan is backed by an actual model: `Spec`, `Pack`, `anchor`, one graph, three descriptors, claim taxonomy, delivery facts, conformance/honesty checks, Design Review, agent surface, and a realistic MVP slice. + +My main advice is: **build the smallest executable version before adding any new surface.** The current docs are already rich enough. The next risk is not missing design; it is adding one more attractive idea before the protocol proves itself on the Order Management fixture. diff --git a/reviews/03-post-split-adverserial-review-prompt.md b/reviews/03-post-split-adverserial-review-prompt.md new file mode 100644 index 0000000..5578e58 --- /dev/null +++ b/reviews/03-post-split-adverserial-review-prompt.md @@ -0,0 +1,159 @@ +**Adversarial design review without making changes in the repo:** + +Please help me review the delicate design work and decision-making completed and the "divide and conquer" approach executed in the last session by providing your fresh perspective and adversarial looks from a few different perspectives. + +Once you complete the review with sufficient depth and understanding, please provide your feedback here in the session without making any changes in the repo. + +--- + +### View 1: Holistic MVP design context + +Please gain holistic unerstanding of carefully crafted MVP design. + +1.1 Absorb the initial design of our MPC/concept from these specs: + +- `docs/concept/README.md` +- `docs/concept/00-vision-scope-and-mvp-boundary.md` +- `docs/concept/01-founding-principles-and-invariants.md` +- `docs/concept/02-core-model.md` +- `docs/concept/03-the-one-graph.md` +- `docs/concept/04-authoring-and-binding.md` +- `docs/concept/05-validation-and-honesty.md` +- `docs/concept/06-consumers-and-projections.md` + +1.2 Ubiquitous language and many important decisions deserve a carefull look: + +- `docs/concept/ubiquitous-language.md` + +1.3 Implementation plans + +MVP implementation sequencing: + +- `docs/concept/07-mvp-roadmap-and-open-questions.md` + +### View 2: Phase 0 - implementation bootstraping + +2.1 Implementation plan for Phase 0: + +- `plans/01-session-1-bootstrap-phase0.md`. + +2.2 Plase 0 implementation + +Phease 0 is also currently the only implemented phase: + +``` +src +├── cli +│ └── sdp.ts +├── graph +│ └── schema.ts +├── ids.ts +├── index.ts +├── model +│ ├── anchors.ts +│ ├── descriptors.ts +│ ├── pack.ts +│ ├── relations.ts +│ ├── sections.ts +│ └── spec.ts +└── validate + ├── authored-model.ts + ├── contracts.ts + ├── readiness-floor.ts + └── validators.ts + +test +├── bootstrap.test.ts +├── bootstrap.typecheck.ts +├── builders.test.ts +├── builders.typecheck.ts +├── checkout-v1.test.ts +├── cli.test.ts +├── descriptors.test.ts +├── descriptors.typecheck.ts +├── fixtures +│ └── authored-model.fixtures.ts +├── fixtures.test.ts +├── graph-schema.test.ts +├── graph-schema.typecheck.ts +├── ids.test.ts +├── ids.typecheck.ts +├── readiness.test.ts +├── readiness.typecheck.ts +└── validators.test.ts + +examples +├── bootstrap-alias.ts +└── checkout-v1 + ├── model.ts + ├── specs + │ ├── checkout.pack.ts + │ ├── decisions + │ │ └── order-lifecycle.spec.ts + │ └── orders + │ ├── create-order-invalid-cart.spec.ts + │ ├── create-order-valid-cart.spec.ts + │ ├── create-order.spec.ts + │ ├── order-inventory-rule.spec.ts + │ ├── order-latency-constraint.spec.ts + │ ├── order-management.spec.ts + │ ├── order-model.spec.ts + │ └── order-total-rule.spec.ts + ├── src + │ └── orders + │ └── create-order.use-case.ts + └── test + └── orders + └── create-order.valid-cart.test.ts +``` + +### View 3: Turn on your Inner Critic + +Reviewing implementation of Phase 0 took multiple perpectives: +- look from implemented Phase 0 foundations and layering unimplemented designs on top +- and the other way around + +This produced essential but not easy to tackle review context. To make changes managable we did some splitting. Please provide your feedback after taking a look at each of the spilts. + +3.1 Split 1: Addressable and addressed + +Review implemented code, plese. + +```markdown +| Item | What landed | Verified by | +| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| **H1** | Both scenario specs are now plain static object literals — no `Object.fromEntries`/`thenKey`/casts | `grep` clean across `examples/`; the example will survive static extraction in Slice 1 | +| **H6** | Deleted the tsup banner + strip-plugin + post-build hook; rely on esbuild preserving the CLI entry's source shebang | `dist/index.js` has no shebang; `dist/cli/sdp.js` has exactly one; `--help` exits 0 | +| **H7** | `.tmp-scratch/**` added to `.prettierignore` | `format:check` green | +| **H8** | New `test/fixtures/` suite: 4 **active** should-pass/should-fail fixtures + 2 `it.todo` stubs for the gated cases | 6 fixtures run (4 pass, 2 todo) | +| **H9** | `@ts-expect-error` fixtures proving the `Spec` envelope rejects `implemented`/`has-verifier` and `Pack` rejects `intent`/`readiness`/`constraints` | `typecheck` *consumes* them — an unused directive would fail the build | + +The two `it.todo` stubs are the deliberate seam: `invalid-ready-with-blocking-question` flips active when **H2** lands, and `invalid-hand-authored-delivery-fact-in-section` flips when **D1** lands. They encode the Wave-B target without baking in the current buggy behavior. +``` + +3.2 Split 2: Decision possible and made + +It would be great to get more things to this split, from the Split 3 and decisions to be made in general. Hope no decisions will need reversing. + +```markdown +- **`plans/02-phase0-hardening.md`** → slimmed to **execution-only** (197 lines). Wave A marked ✅ done with verification notes; Wave B (H2, H3, H4, H5, H10) keeps its full technical spec but is explicitly **decision-gated** with a pointer to plan 03. +``` + +3.3 Split 3: Decision needed + +If there are decisions which stick out as resolvable, that would be genuenely valuble feedback. For the rest we will execute our second session guided by `grill-me-with-docs` skill. + +This document and all of the naming and decision making was produced by first grilling session: `docs/concept/ubiquitous-language.md` +Grilling with docs is relentless but worth it! It took me 8 hours to answer all Claude's qustions. But results are fatastic. + +```markdown +- **`plans/03-decision-resolution-and-base-reconciliation.md`** (new) → the **fresh-session grilling agenda**: the root tension, the dependency graph (`D2 → D1 → D3`, plus D4 direction and D6 shape), the base-first method, R1/R2, and what each decision unblocks. This is the "item 1" landing zone — anything tangled went here rather than getting force-resolved. +- **`docs/concept/DECISIONS.md`** → R1/R2 recorded as **PROPOSED** (flagged, not silently applied to the ratified base). +- **`docs/concept/07`** → §4c acceptance criteria folded in as §6, mapped across Slices 1–5. + +`★ Insight ─────────────────────────────────────` +The split is the real product here. Plan 02 fused three work-kinds that obey different rules: code hardening is *reversible and test-gated*; decisions are *representation work that must be ratified against the base*; concept/roadmap items are *single-canonical-source edits*. Keeping them in one doc meant the safe code fixes were hostage to the unresolved decisions, and the decisions risked being "resolved" inside an execution diff with no grilling. Un-fusing them is what makes the next session a clean, fresh grilling session — exactly the in-system discipline the Protocol itself is built on (a Spec is always reviewed in the context of its related set, never mid-edit). +`─────────────────────────────────────────────────` + +**Nothing is committed** — per the project's "commit only when asked" rule I left the working tree staged for your review. The next session is now unblocked: open `plans/03` in a fresh context and run the grilling pass (`grill-with-docs` is the natural fit — it'll ratify D1–D6 + R1/R2 and update the base inline), then Wave B becomes execution-ready. +``` diff --git a/reviews/04-post-split-adversarial-review.md b/reviews/04-post-split-adversarial-review.md new file mode 100644 index 0000000..757b459 --- /dev/null +++ b/reviews/04-post-split-adversarial-review.md @@ -0,0 +1,134 @@ +I've now absorbed the full concept (README + `00`–`07` + ubiquitous-language + DECISIONS), the three plans, and read every Phase-0 source file, the tracer-bullet example, and the test suites. Here is my adversarial review — feedback only, no repo changes made. + +--- + +## Overall read + +This is unusually disciplined work. The concept is internally coherent to a degree I rarely see: the principle/representation split, the CORE/ASPIRATIONAL split, the authored/derived (`claim`) boundary, and the "checked, never workflow-gated" guardrail are applied *consistently* across nine documents and then faithfully projected into types. The DECISIONS.md ↔ ubiquitous-language.md separation, and the three-way split of the hardening plan, are themselves a clean application of the Protocol's own thesis (review in context, never mid-edit). The code is honest about its own seams (`deferredInSession1`, the `it.todo` stubs, the H9 `@ts-expect-error` notes that *call out* the bypass they don't yet close). + +So my job here is to push hard on the few places where the polish hides a real tension. I found one finding that I think is more important than anything currently on the D1–D6 / H-list, several smaller fidelity gaps, and a set of decisions that I believe are **already resolvable now** (which is exactly what you asked for in Split 3). + +Let me organize by your three views, then end with the "what's resolvable / what might need reversing" answer you specifically asked for. + +--- + +## View 1 — Holistic design: adversarial probes + +The strengths are real and I won't pad the review by re-listing them. Four adversarial angles: + +### 1.1 The tracer bullet proves materially less than the prose claims — and the gap is concentrated exactly on the marquee feature + +Every spec in `checkout-v1` is at `readiness: "defined"`. **Nothing is `ready`.** That means the entire `ready` floor — the most complex floor, the one carrying the "honest readiness" differentiator — has *zero* live exercise. Worse, all three of its non-trivial clauses (`all-relations-resolve`, `depends-on-and-refines-targets-are-defined`, `anchors-resolve`) are `deferredInSession1: true`, so even the *data* for the headline check is inert and untested-as-behavior. + +Combined with the fact that determinism (P3), the one-graph (P1/P2), and `claim`-never-collapsed (P9) are all unexercised until the extractor lands, the honest status is: **the trust model ships as types and prose, but the parts that make it falsifiable do not yet run.** This is *expected* at Slice 0 — but the docs (e.g. `07` "the entire trust model ships at MVP") read as if the guarantee is already load-bearing. I'd recommend the roadmap explicitly mark "the `ready` honesty path has no coverage until a spec in the example reaches `ready` AND the deferred clauses activate" as a named Slice-3 risk, and add at least one `ready` spec to the tracer bullet at that point. Right now a reader of `07` would overestimate what's proven. + +### 1.2 "`kind` is a true subtype that changes required detail" is only half-delivered + +`02`/P8 promise that `kind` behaves like a discriminated union — "changes required detail + validation." But the `05` §6 floor table implements `kind`-variation only as **additive overlays** (constraint→target, example→GWT, model→terms). The *base* `defined` floor requires `rules-and-or-examples` for **every** kind. So `kind` never *replaces* required detail; it only *adds*. This is the root of finding F1 in View 2 — and it means the discriminated-union framing is, at the floor level, aspirational. That's worth saying plainly in the base rather than letting the example paper over it. + +### 1.3 `Spec` + `.spec.ts` collides head-on with the universal JS test-file convention — an adoption landmine + +The extractor is specified to read `/specs/**/*.spec.ts` (`03` §"what the extractor reads", `04` §5). But `*.spec.ts` is *the* default test glob for Vitest/Jest/Mocha across the JS ecosystem. In *this* repo you've dodged it — `vitest.config.ts` narrows `include` to `["test/**/*.test.ts"]`, so the example's `.spec.ts` files aren't picked up. But a **consuming** project that uses the standard `**/*.spec.ts` for its tests (overwhelmingly common) will have its test runner try to execute Spec-primitive files as test suites. They'll either error or "pass" emptily, and the failure will be baffling to a new adopter. + +This isn't reachable from inside the ratified language (the *name* `Spec` is well-chosen for the model), but the **file-extension representation** deserves a conscious decision before adoption: `.spec.ts` for the primitive vs. something collision-free (`.sdp.ts`, `.spec.sdp.ts`, a `specs/` dir excluded from the test glob by convention with documented setup). I'd flag this as a Representation-level decision that is currently implicit and risky. + +`★ Insight ─────────────────────────────────────` +This is the classic "ubiquitous language wins the model but loses the toolchain" trap: the domain term (`Spec`) is correct, but its *serialization* (`.spec.ts`) inherits semantics from a foreign bounded context (the JS test runner). The Protocol's own principle — "carry epistemic status where it matters, unambiguous to all three readers" — argues for disambiguating the *file*, because one of the three readers (the test runner, a stand-in for CI tooling) already reads `.spec.ts` as "test." +`─────────────────────────────────────────────────` + +### 1.4 Minor: the front-loaded design risks ossification, but the split discipline is the right antidote + +A year of design before an engine is a real bet against learning-by-building. The mitigation you've chosen — keep the base ratified, route every new pressure through a *flagged* PROPOSED entry + a fresh grilling session rather than silent edits — is the correct one, and the R1/R2 handling proves it works. No action needed; I'm noting it so the bet is named. + +--- + +## View 2 — Phase 0 implementation: adversarial code review + +Fidelity is high and the trust-model boundaries hold (anchors identity-only and proven so by `@ts-expect-error`; delivery facts are derived-only types; `src/` is domain-neutral; the two-edge verifier semantics match `02`). The known items (H2 wrong section, H3 single-vs-array, H5 453-line/4×-enumeration, `authoredPaths` decorative) are all real — I confirmed each in the source. Below are the findings that are **new** (not on the H/D list), ordered by importance. + +### F1 — The kind-blind `defined` floor forces the example to *pad*, violating P4's anti-padding rule (highest-value finding) + +The `defined` floor requires `rules-and-or-examples` (`readiness-floor.ts:79`, enforced via `hasBehaviorRulesOrExamples` at `validators.ts:127`). There is **no** kind exemption — overlays add requirements, they never relax the base. Consequence: a `decision`, `model`, `constraint`, `contract`, or `workflow` spec cannot reach `defined` on its *natural* content. The tracer bullet demonstrates the workaround three times: + +- `order-lifecycle.spec.ts` (`kind: decision`) — real content is in `decision: {…}`, but carries `behavior.rules: [...]` solely to clear the floor. +- `order-model.spec.ts` (`kind: model`) — real content is `model.terms`, but carries a meta-rule `behavior.rules: ["The authored slice uses shared term definitions instead of inlining conflicting vocabulary."]`. +- `order-latency-constraint.spec.ts` (`kind: constraint`) — real content is `constraints`, but carries `behavior.rules: ["The create-order path stays inside a machine-readable latency budget."]`, duplicating the constraint statement. + +And `checkout-v1.test.ts:8` asserts `validateAuthoredModel(checkoutV1Model).findings` equals `[]` — so those padded rules are **load-bearing for the green build.** Remove any one of them and the floor fails. The canonical example of the Protocol therefore embodies the exact anti-pattern P4's corollary forbids ("padding a spec to 'fill a level' destroys signal… AI authors especially replicate the prevailing detail level even where it does not fit"). An AI author following this example will learn to sprinkle `behavior.rules` onto decision records. + +There's a sharper internal inconsistency that proves it's a bug, not a taste call: at `scoped`, the evidence clause is `rules-examples-or-**constraints**` (`constraints` alone is valid evidence). At `defined`, the clause silently drops `constraints` and becomes `rules-and-or-examples`. So a constraint spec can be honestly `scoped` on its target, but maturing it to `defined` suddenly demands behavior content it structurally doesn't have. The maturity ladder is not monotonic in the evidence it accepts. + +**This is resolvable now and is not owned by any current decision.** The fix is to make the `defined` evidence clause *defer to the kind*: for a kind that has an overlay (constraint→target, model→terms, example→GWT) or a natural truth section (decision→`decision`), the overlay/section *is* the evidence; require `rules-and-or-examples` only for the behavior-family kinds (`behavior`, `rule`, `workflow`). The overlay machinery already exists — the change is "base evidence clause is kind-conditional," not new infrastructure. I'd add this as a first-class item to `plans/03` (it's a base §6 refinement, so it belongs in the grilling pass, and it shrinks the contract by removing the cross-kind padding pressure). See View 3.3. + +### F2 — `ref` is a spec-only brand wearing a generic name + +`ids.ts:141`: `export { specId as ref }`. So `ref("...")` requires the `spec:` namespace and brands `SpecId`. It reads as "a reference to anything," but it can't reference a `pack:` or a `doc:`. Today every call site happens to want a spec, so it's harmless — but it's a latent trap for the agent-author audience (the docs lean on `ref()` heavily). At minimum the name deserves a doc note ("`ref` = a spec reference"); better, when `doc:`-target relations (`decidedBy` to an external ADR) or pack-targeting needs arrive, `ref` will quietly reject them with a confusing namespace error. + +### F3 — `validateAuthoredModel` mislabels its aggregate `family` + +`validators.ts:445` returns the composed report with `family: "conformance"`, but it folds in `validateReadinessFloors` findings, which are `honesty`. Individual findings carry the correct family, so nothing downstream is *wrong* yet — but the top-level `family` on the aggregate report is dishonest by the Protocol's own standard (the two families are a load-bearing distinction in `05` §1). Either the aggregate shouldn't carry a single `family`, or it should be a neutral value. Small, but this codebase holds itself to "don't let one kind masquerade as another," so it's worth fixing for consistency. + +### F4 — `modelRefs` referential check doesn't enforce `kind:"model"` + +`02` §3 and `05` §4 both state `modelRefs` on a `Pack` "always points at standalone `kind:"model"` specs." `validateDanglingReferences` (`validators.ts:399`) only checks that each `modelRef` resolves to *some* spec — a `modelRef` pointing at a `behavior` spec passes. This is a coherence gap that the base explicitly claims is closed. Probably Slice-1/3 territory (needs the graph), but it should be named; today the prose over-promises relative to the check. + +### F5 — D2's prose-vs-ref ambiguity also infects `behavior.examples`, but the plan scopes D2 only to `rules` + +`plans/03` frames D2 narrowly as "`behavior.rules`: prose vs ref list." But the same unresolved duality is live in `behavior.examples` *right now*: `create-order.spec.ts:23` puts **refs** in `examples` (`[ref("spec:orders.create-order.valid-cart"), …]`), while `create-order-valid-cart.spec.ts:14` puts **prose** in `examples` (`["Valid cart becomes an order with the computed total."]`) *and also* carries structured `given/when/then`. So `behavior.examples` is used two incompatible ways in the same example, and the prose entry in the scenario is redundant with its own GWT. D2 must cover `examples` too, or typing `behavior` (D1) will bake in whichever shape the first author happened to pick. Recommend widening D2's statement in `plans/03` §3 to "the prose-vs-ref duality across `behavior.rules` **and** `behavior.examples`." + +### F6 — `specTest` doc↔code divergence (the code is the more faithful one) + +`04` §2 documents `specTest("test:…", { verifies, run: async () => {…} })` — a positional id plus a `run` callback that *executes* the test. The implementation (`anchors.ts:23`, used at `create-order.valid-cart.test.ts`) is a single identity-only object — `{ id, label?, verifies }`, **no `run`.** The code is *more* correct: a binding anchor that carried `run` would couple the graph binding to execution, which contradicts "the graph records that an enabled verifier exists, never that it ran" (`04` §2 itself, MD-7). So this is a **doc fidelity bug, not a code gap** — reconcile `04` to a binding-only `specTest` signature (this is a clean R-series candidate; the code already conforms, which is the R1/R2 pattern exactly). + +### F7 — Note on the H9 envelope defense (not a defect, a caveat to record) + +The `@ts-expect-error` proofs in `builders.typecheck.ts` that the `Spec` envelope rejects top-level `implemented`/`has-verifier` rely on TypeScript's **excess-property checking**, which only fires on object literals passed directly. Because P5 mandates inline literals, this is sound *in practice* — but the guarantee is "rejected for inline literals," not "rejected structurally." Worth one sentence in the test file so a future reader doesn't over-trust it (the in-section bypass note is already there; this is its envelope-level sibling). + +--- + +## View 3 — the split (the meta-product), and the resolvable decisions + +### 3.1 Split 1 (addressable & addressed) — the code review + +The Wave-A landings are genuinely well-executed and I verified each holds: H1 (static literals — confirmed no `Object.fromEntries`/`as Record` anywhere in `examples/`), H6 (shebang only on the CLI entry), H8 (four active fixtures, each pinning one validator outcome via `validatorId`/`relatedId`), H9 (the compile-time honesty proofs). The `it.todo` seam is the right mechanism — it encodes the Wave-B target without baking current buggy behavior into a green test. One small thing: the H8 `invalid-defined-constraint-without-target` fixture *itself* pads the constraint with `behavior.rules` (`authored-model.fixtures.ts:112`) to isolate the target failure — which is more evidence for F1: even your fixture author had to pad to write the test. + +### 3.2 Split 2 (decisions made) — is everything correctly filed? + +The three-way split (code = reversible/test-gated; decisions = ratify-against-base; concept/roadmap = single-canonical-source) is the right structural move and I'd keep it. The `★ Insight` you wrote about "un-fusing the three work-kinds" is correct. My only filing critique is that **two items currently sitting in the "to be decided" pile (Split 3) don't actually need a grilling session** — they're already determined by the base. Moving them to "decided" is exactly the enrichment you asked for. See 3.3. + +### 3.3 Split 3 (decisions to be made) — what is *already resolvable now* + +You asked specifically: "if there are decisions which stick out as resolvable, that would be genuinely valuable feedback," and "hope no decisions will need reversing." Here's my honest answer. + +**Resolvable now — promote from "to decide" → "decided" (low risk, base already determines them):** + +1. **R1 and R2** — both are explicitly "the code already conforms; wording tightening." They are not model changes; they carry zero reversal risk. They don't need a *grilling* session, only ratification. I'd ratify them immediately and let the grilling session focus its energy on the genuinely open D1/D2/D3. (Keeping them queued behind a fresh session is over-process for a no-op-on-code change.) + +2. **H2's *direction*** (open-questions canonical home = `intent.openQuestions`). The base is unambiguous: `02` §3 and the `04` worked example both put `openQuestions` under `intent`. The plan gates H2 on D1 — but that conflates two things. The *fix* (read from `intent.openQuestions` instead of `design`/`decision` at `validators.ts:200`) is correct **regardless of how sections get typed**; D1 only makes the predicate *shorter*. This is a faithfulness bug with a determined answer; it can be fixed and its gated H8 fixture flipped without waiting on the D1 grilling. Decouple it. + +3. **D6 (generic-anchor DSL shape).** The base already forces the answer: anchors are *generic* by definition (`04` §2, P-level), `ids.ts` already parses `api:`/`component:`, and the only thing missing is a builder + branded id. "Generalize `anchorImplementation` into a `codeAnchor` over `impl`/`api`/`component`" is the base-implied shape, not an open choice. I'd record it as decided (with the trivial alternative — sibling builders — noted and rejected for surface bloat) rather than re-litigating it. + +**New decision that *should* be on the agenda but isn't (the one that "sticks out"):** + +4. **F1 — make the `defined` floor kind-aware.** This is the highest-value resolvable item and it's currently *unowned* by any of D1–D6 or H2–H10. It's a base §6 refinement (so it belongs in the grilling pass, not in a code-only diff), it directly serves the anti-padding thesis, and it *shrinks* the contract (removes the cross-kind behavior-padding pressure). Resolving it will also clean up the example (drop three padded `behavior.rules`) — which is a visible quality signal for the canonical tracer bullet. I'd add it to `plans/03` §3 as **D7**, sequenced with D1 (typed sections make the kind-conditional evidence clause cheap to express). + +**Genuinely open — leave for the grilling session (correctly filed):** D1 (how much to type now), D2 (prose-vs-ref — *widened to include `examples`*, per F5), D3 (floor-validator collapse, depends on D1), D4 (`AuthoredModel` seam vs the one graph — this is a real architectural fork worth the deliberation). + +**Does anything need *reversing*?** — Your honest answer: **no load-bearing bet needs reversal.** The one-primitive model, the authored/derived `claim` boundary, "checked never gated," git-as-event-log, and the agent-surface stance all hold up under adversarial pressure. What needs **refinement** (not reversal) is narrower: +- the §6 `defined` floor's kind-blindness (F1 / proposed D7) — a refinement of a representation, not a reversal of a principle; +- the `.spec.ts` file-extension convention (View 1.3) — a Representation-level reconsideration, the model name `Spec` stays; +- the `04` `specTest` signature (F6) — a doc reconciliation toward what the code already does. + +`★ Insight ─────────────────────────────────────` +The pattern across F1, H2, and F6 is one signal: the *implementation* is currently more honest than the *base/docs* in several places (code conforms; prose over-claims). That's the inverse of the usual drift and it's a healthy sign — but it means the grilling session's real job is **reconciling docs down to the code's honesty**, not designing new behavior. R1/R2 already proved this is the dominant mode; F1 is the one exception where *both* the base and the code need to move together. +`─────────────────────────────────────────────────` + +--- + +## The three things I'd act on first + +1. **Add D7 to `plans/03`: kind-aware `defined` floor** (F1) — highest value, currently unowned, fixes the canonical example's padding, serves the anti-padding principle directly. +2. **Decouple/ratify the determined items** — R1, R2, H2-direction, and D6 don't need to wait on the D1 grilling; promoting them shrinks the open-decision surface so the grilling session concentrates on D1/D2/D3/D4. +3. **Make a conscious call on `.spec.ts`** before any real adopter hits the test-runner glob collision, and widen D2 to cover `behavior.examples` (F5) so typing `behavior` doesn't ossify the wrong shape. + +Everything else (F3, F4, F6, F7, the `ref` naming) is small and can ride the Wave-B execution session. Nothing I found undermines the foundation — the work is strong, and the few real cracks are all the kind you can fix while the cement is still wet. \ No newline at end of file diff --git a/reviews/05-plan-finalization-prompt.md b/reviews/05-plan-finalization-prompt.md new file mode 100644 index 0000000..e05ffaa --- /dev/null +++ b/reviews/05-plan-finalization-prompt.md @@ -0,0 +1,118 @@ +**Design review and finalization of MVP plans:** + +Review the delicate design work and decision-making completed and saved as plans. +Make sure you do this work with sufficient depth and understanding. +Finalize provided plans and fold/clean-up everything that can be done and completed before the grill-me-with-docs session. + +--- + +### View 1: Holistic MVP design context + +Please gain holistic unerstanding of carefully crafted MVP design. + +1.1 Absorb the initial design of our MPC/concept from these specs: + +- `docs/concept/README.md` +- `docs/concept/00-vision-scope-and-mvp-boundary.md` +- `docs/concept/01-founding-principles-and-invariants.md` +- `docs/concept/02-core-model.md` +- `docs/concept/03-the-one-graph.md` +- `docs/concept/04-authoring-and-binding.md` +- `docs/concept/05-validation-and-honesty.md` +- `docs/concept/06-consumers-and-projections.md` + +1.2 Ubiquitous language and many important decisions deserve a carefull look: + +- `docs/concept/ubiquitous-language.md` + +1.3 Implementation plans + +MVP implementation sequencing: + +- `docs/concept/07-mvp-roadmap-and-open-questions.md` + +### View 2: Phase 0 - implementation bootstraping + +2.1 Implementation plan for Phase 0: + +- `plans/01-session-1-bootstrap-phase0.md`. + +2.2 Plase 0 implementation + +Phease 0 is also currently the only implemented phase: + +``` +src +├── cli +│ └── sdp.ts +├── graph +│ └── schema.ts +├── ids.ts +├── index.ts +├── model +│ ├── anchors.ts +│ ├── descriptors.ts +│ ├── pack.ts +│ ├── relations.ts +│ ├── sections.ts +│ └── spec.ts +└── validate + ├── authored-model.ts + ├── contracts.ts + ├── readiness-floor.ts + └── validators.ts + +test +├── bootstrap.test.ts +├── bootstrap.typecheck.ts +├── builders.test.ts +├── builders.typecheck.ts +├── checkout-v1.test.ts +├── cli.test.ts +├── descriptors.test.ts +├── descriptors.typecheck.ts +├── fixtures +│ └── authored-model.fixtures.ts +├── fixtures.test.ts +├── graph-schema.test.ts +├── graph-schema.typecheck.ts +├── ids.test.ts +├── ids.typecheck.ts +├── readiness.test.ts +├── readiness.typecheck.ts +└── validators.test.ts + +examples +├── bootstrap-alias.ts +└── checkout-v1 + ├── model.ts + ├── specs + │ ├── checkout.pack.ts + │ ├── decisions + │ │ └── order-lifecycle.spec.ts + │ └── orders + │ ├── create-order-invalid-cart.spec.ts + │ ├── create-order-valid-cart.spec.ts + │ ├── create-order.spec.ts + │ ├── order-inventory-rule.spec.ts + │ ├── order-latency-constraint.spec.ts + │ ├── order-management.spec.ts + │ ├── order-model.spec.ts + │ └── order-total-rule.spec.ts + ├── src + │ └── orders + │ └── create-order.use-case.ts + └── test + └── orders + └── create-order.valid-cart.test.ts +``` + +### View 3: Planned work + +The upcoming work is split into addressable and addressed + +3.1 Split 1: Addressable and addressed, decision possible and made and decisions required + +- `plans/02-phase0-hardening.md` +- `plans/03-decision-resolution-and-base-reconciliation.md` +- `plans/04-formalization-gap-strategy-and-pre-grill-folds.md` diff --git a/reviews/README.md b/reviews/README.md new file mode 100644 index 0000000..8c250aa --- /dev/null +++ b/reviews/README.md @@ -0,0 +1,18 @@ +# Reviews — session artifacts (tracked) + +Review reports and the prompts that produced them, moved here from the gitignored `.tmp-scratch/` so the +signal survives a clean clone (the optional-archive step of `plans/04` §3). These are **session artifacts, +not canonical docs**: their durable findings were folded into the tracked plans and `docs/concept/DECISIONS.md` +at the time; keep reading those for current truth. Like every markdown here, they are temporary scaffold — +in the finished Protocol, review artifacts become graph projections. + +| File | What it is | Durable findings landed in | +|---|---|---| +| `01-opus-plan-implementation-review.md` | post-Session-1 implementation review (found H1 — the example's P5 violation — and the Wave-A backlog) | `plans/02` Wave A (executed) | +| `02-founding-ideation-back-for-review.md` | the founding-ideation review of the concept set ("I would build this direction") — surfaced the R1/R2 wording imprecisions | `DECISIONS.md` R-series | +| `03-post-split-adverserial-review-prompt.md` | the bespoke 3-view prompt that produced review 04 | — | +| `04-post-split-adversarial-review.md` | the post-split adversarial review (F1–F7; D7 kind-aware floor, D8 `.spec.ts` collision, the resolvable-now assessment) | `plans/03` agenda + `DECISIONS.md` | +| `05-plan-finalization-prompt.md` | the prompt that launched the pre-grill fold session (2026-06-10: Fold-A, Fold-B, this archive) | `plans/04` §3–§4 (executed) | + +The gen-1 (`@libar-dev/architect`) formal-spec study from the same arc was chat-only; its takeaway is +formalized in `plans/04` §0/§2 ("lineage is evidence, not template" — no patterns transferred). From 463808925295057cab3e7f579f6327c2160bb021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 03:36:25 +0200 Subject: [PATCH 11/29] Fold-B: resolvable-now sweep - specTest binding-only (R3), MD-8/MD-9 recorded, plans/03 slimmed to six - docs/concept/04 s2 now shows the binding-only specTest signature the code already has (no executing run callback); DECISIONS R3 flipped to ACCEPTED/applied. - DECISIONS.md gains the 2026-06-10 fold-session block: MD-8 (generic-anchor shape: one codeAnchor over impl/api/component; sibling builders rejected; execution Slice 2 / plan 02 H10) and MD-9 (open-questions canonical home = intent.openQuestions; execution stays plan 02 Wave B / H2). - plans/03 slimmed to the six genuinely-open grill decisions (D1 D2 D3 D4 D7 D8); the resolvable-now section collapsed to a folded-out pointer; base references re-aimed to the post-Fold-A homes (02 s1/s3, 05 s3, glossary). --- docs/concept/04-authoring-and-binding.md | 16 +- docs/concept/DECISIONS.md | 47 ++- ...sion-resolution-and-base-reconciliation.md | 300 ++++++++---------- 3 files changed, 177 insertions(+), 186 deletions(-) diff --git a/docs/concept/04-authoring-and-binding.md b/docs/concept/04-authoring-and-binding.md index 11cfcfa..929ad1a 100644 --- a/docs/concept/04-authoring-and-binding.md +++ b/docs/concept/04-authoring-and-binding.md @@ -90,15 +90,21 @@ An anchor says exactly one thing: *"this code location is the implementation/tes ### Test binding — the `verifies` trace (CORE) -A test declares which spec it verifies, via an anchor or a thin wrapper: +A test declares which spec it verifies via a **binding-only test anchor** — identity plus the `verifies` +target, never an executing callback (DECISIONS R3: a binding that carried a `run` body would couple the +graph binding to execution, contradicting "the graph records that an enabled verifier *exists*, never that +it ran"). The test body itself stays an ordinary runner test beside the anchor: ```ts -import { specTest } from "@libar-dev/software-delivery-protocol"; +import { ref, specTest, testAnchorId } from "@libar-dev/software-delivery-protocol"; -specTest("test:orders.create-order.valid-cart", { - verifies: "spec:orders.create-order.valid-cart", - run: async () => { /* ... real test ... */ }, +export const createOrderValidCartTest = specTest({ + id: testAnchorId("test:orders.create-order.valid-cart"), + label: "valid cart verifies the create-order happy path", + verifies: ref("spec:orders.create-order.valid-cart"), }); + +// ... the real test (plain Vitest/Jest/etc.) lives alongside ... ``` Here the test `verifies` the **example** it backs (`spec:orders.create-order.valid-cart`); that test anchor is exactly what makes the example an **enabled verifier**, so the example's own `verifies` edge can confer `has-verifier` on the parent it targets (the direct, per-spec, non-transitive rule in `02` §2, *Verifier semantics*). This produces the bidirectional spec↔test trace that is a core MVP deliverable: query "what verifies this spec?" and "what does this test cover?" from the graph. The test's *result and its runner status* (pass/fail, skipped, quarantined, glob-excluded) are operational — CI's, never in the graph; the graph records only that an enabled verifier — a **resolvable test binding** — *exists*, never that it ran (the derived `has-verifier` delivery fact, `02` §2). diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 1847815..b596846 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -120,13 +120,13 @@ rather than spending grill time on no-op-on-code wording. They are **not** model the graph* is legitimate. Now stated in `01` P2 and `03` §4: *"Consumers may link to source locations recorded in the graph; consumers must not independently parse source to derive their own model."* Matters when the Slice-4 Design Review lands. -- **R3 — reconcile `04`'s `specTest` signature to binding-only.** `04` §2 documents `specTest(id, { verifies, - run })` with an executing `run` callback; the implementation (`src/model/anchors.ts`) is identity-only - (`{ id, label?, verifies }`, **no `run`**). The code is the *more* faithful one: a binding anchor carrying - `run` would couple the graph binding to execution, contradicting "the graph records that an enabled verifier - *exists*, never that it ran" (§4b / MD-7). So this is a **doc fidelity bug, not a code gap** — reconcile `04` - down to a binding-only signature. Same pattern as R1/R2 (the code already conforms). Surfaced by the - post-Session-1 adversarial review (F6). +- **R3 — reconcile `04`'s `specTest` signature to binding-only.** [ACCEPTED · applied 2026-06-10 (Fold-B)] + `04` §2 documented `specTest(id, { verifies, run })` with an executing `run` callback; the implementation + (`src/model/anchors.ts`) is identity-only (`{ id, label?, verifies }`, **no `run`**). The code is the *more* + faithful one: a binding anchor carrying `run` would couple the graph binding to execution, contradicting + "the graph records that an enabled verifier *exists*, never that it ran" (§4b / MD-7). A **doc fidelity bug, + not a code gap** — `04` §2 now shows the binding-only signature matching the code. Same pattern as R1/R2 + (the code already conforms). Surfaced by the post-Session-1 adversarial review (F6). ### Scope note — relationship to the prior plan & brief **Adopt-the-nouns reversal (kept for the record).** An earlier brief held "the Protocol is **not** a PM tool; do **not** @@ -140,6 +140,39 @@ brief have since been **deleted** (consolidated); the **sole canonical base is --- +## 2026-06-10 — Session: pre-grill folds (Fold-A · Fold-B · archive) + +> The fold session prescribed by `plans/04` §3: restructure the language base (Fold-A — the UL doc became +> the lean glossary; model exposition rehomed into `00`–`07`), ratify-and-apply the R-series (above), fold +> the already-determined items out of the grill agenda (Fold-B — the two entries below), and archive the +> review artifacts into tracked `reviews/`. The grill (`plans/03`) now opens onto a lean base and only +> genuinely-open decisions. + +### MD-8 — Generic-anchor DSL shape: one `codeAnchor` over the implementation-flavored code namespaces [ACCEPTED 2026-06-10] +**Decision.** Generalize `anchorImplementation` into a **`codeAnchor`** builder (plus branded id) accepting the +implementation-flavored code namespaces — **`impl` / `api` / `component`** — so a *generic* anchor can bind any +code location (class, function, route, module) as the base requires. +**Why / alternatives rejected.** Anchors are generic *by definition* (`04` §2 — the binding is the thing, +framework- and location-neutral), and the ID grammar already parses any lowercase namespace; only the builder + +branded id are namespace-locked today. The base therefore already forces this answer — the post-split +adversarial review assessed it "resolvable now," so it is recorded here rather than spending grill time. +*Rejected:* per-namespace sibling builders (`anchorApi`, `anchorComponent`, …) — surface bloat for zero +expressive gain. +**Execution.** Rides Slice-2 anchor extraction, together with the example's missing api/route anchor +(`plans/02` H10). *(This is the item the plans' open-decision code-space called "D6" — not the legacy +structural D6 below; lead with meaning.)* + +### MD-9 — Open-questions canonical home: `intent.openQuestions` [ACCEPTED 2026-06-10] +**Decision.** Blocking open questions live in **`intent.openQuestions`** — the honesty check (no stated +`defined`/`ready` with a blocking open question) must read them there. +**Why.** The base is unambiguous (`02` §3: `intent` carries `openQuestions`, flaggable `blocking`; the `04` +worked example authors them under `intent`). The Session-1 pre-plan drifted — the implemented floor data reads +`design.openQuestions` / `decision.openQuestions`, so a doc-following author's blocking question never fires +the marquee honesty check. The fix direction is determined **regardless of how sections get typed** (the +typed-sections grill decision only shortens the predicate), so it is recorded now rather than gated on it. +**Execution.** Stays `plans/02` Wave B (H2): read from `intent.openQuestions`, update the floor data's +`authoredPaths`, flip the gated should-fail fixture active. + ## Structural-decision shorthand (D1–D6) [ACCEPTED · relocated here when the cleanup plan was retired, 2026-06-07] > These six labels come from the original structural-decisions pass. Their *content* is canonical in the diff --git a/plans/03-decision-resolution-and-base-reconciliation.md b/plans/03-decision-resolution-and-base-reconciliation.md index 9e9f870..156d242 100644 --- a/plans/03-decision-resolution-and-base-reconciliation.md +++ b/plans/03-decision-resolution-and-base-reconciliation.md @@ -1,85 +1,83 @@ -# Session 3 (next) — Decision Resolution & Base Reconciliation (the grilling agenda) - -> **Status: PLAN-ONLY · the agenda for a FRESH grilling session.** This document collects the open -> decisions that the post-Session-1 review surfaced and frames them for resolution **against the -> ratified base** (`docs/concept/ubiquitous-language.md`), in dependency order. It deliberately does -> **not** pre-resolve them — that is the grilling session's job. -> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 + the Phase-0 -> hardening **Wave A** committed and green (see `plans/02-phase0-hardening.md`). +# The grilling agenda — Decision Resolution & Base Reconciliation + +> **Status: PLAN-ONLY · the agenda for a FRESH `grill-with-docs` session.** This document holds the **six +> genuinely-open decisions** the post-Session-1 reviews surfaced, framed for resolution **against the base** +> (terms: `docs/concept/ubiquitous-language.md`, the lean glossary; model: `docs/concept/00`–`07`), in +> dependency order. It deliberately does **not** pre-resolve them — that is the grilling session's job. +> **Date:** 2026-06-07 · **slimmed 2026-06-10 (pre-grill folds)** · **Branch:** `feature/mvp-init` · +> **Repo state:** Session 1 + hardening Wave A + the Fold-A/Fold-B folds + the `reviews/` archive, all green. > -> **Why this exists.** Splitting the post-Session-1 work into three homes was the unlock: the -> *execution* of the decision-free fixes lives in plan 02 (Wave A, done); the *decisions* those fixes' -> siblings depend on live **here**; the concept-wording and roadmap items were routed to -> `docs/concept` (see §4, §6). This separation is what lets the decision work happen in a clean, -> fresh session rather than tangled into an execution diff. -> -> **Absorbed review.** This agenda absorbs the **post-split adversarial review** (a fresh-session report, -> archived in gitignored `.tmp-scratch/04-post-split-adversarial-review.md`; its durable findings are -> folded here so they survive in tracked form). Its highest-value contribution is **D7** (the kind-blind -> floor forces the example to pad); it also **widens D2** (F5), adds **D8** (the `.spec.ts` collision), -> proposes **R3** (F6), and assesses several items as **resolvable now** (§3a). +> **Folded out — do not re-litigate (the former §3a/§4).** The resolvable-now items left this agenda in the +> 2026-06-10 fold session: **R1/R2/R3** ratified and applied to the base, and two items recorded **ACCEPTED** +> in `DECISIONS.md` — the **generic-anchor DSL shape** (`codeAnchor` over `impl`/`api`/`component`, MD-8; +> execution rides Slice 2 / plan 02 H10) and the **open-questions canonical home** (`intent.openQuestions`, +> MD-9; execution stays plan 02 Wave B / H2). The source reviews are tracked in `reviews/`. --- ## §0 — Why a separate, fresh session (not a continuation) -- **Grilling needs a fresh context.** Resolving these decisions well means stress-testing each against - the ratified language with full attention — the `grill-with-docs` discipline (challenge the plan - against the domain model, sharpen terminology, update `docs/concept` + `DECISIONS.md` inline as - decisions crystallise). A fresh session is a hard requirement for that. -- **This is design-on-the-base, not coding.** The pivotal decision (D1) *looks* like a TypeScript - choice but is a **representation of base §2/§3**. It must be settled on the base first; the types are - then a faithful projection, not a fresh source of truth. +- **Grilling needs a fresh context.** Resolving these decisions well means stress-testing each against the + ratified language with full attention — the `grill-with-docs` discipline (challenge the plan against the + domain model, sharpen terminology, update `docs/concept` + `DECISIONS.md` inline as decisions crystallise). + A fresh session is a hard requirement for that. +- **This is design-on-the-base, not coding.** The pivotal decision (D1) *looks* like a TypeScript choice but + is a **representation of the canonical section shapes** (`02` §1/§3). It must be settled on the base first; + the types are then a faithful projection, not a fresh source of truth. ## §1 — The method (base-first, not code-first) -The standing trap: typing a section in code *before* its canonical shape is settled in the base bakes -any drift into the type system, where it is expensive to move. So the method is strict: - -1. **Settle the canonical section shapes on the base** (§2/§3 of `ubiquitous-language.md`): what fields - each floor/extractor-bearing section *has*, and which are required by which readiness floor (P7: - *types describe shape; validators decide completeness*). -2. **Reconcile wording** (R1/R2, §4) against the base in the same pass — terminology is ratified, so we - ratify changes, never silently edit. -3. **Record the decision** in `DECISIONS.md` (an `MD-8` / D-series entry with rationale). +The standing trap: typing a section in code *before* its canonical shape is settled in the base bakes any +drift into the type system, where it is expensive to move. So the method is strict: + +1. **Settle the canonical section shapes in the core model** (`02` §1/§3): what fields each + floor/extractor-bearing section *has*, and which are required by which readiness floor (`05` §3) — + P7: *types describe shape; validators decide completeness*. +2. **Reconcile wording in the same pass** — terminology is ratified (the glossary), so we ratify changes, + never silently edit; the grill writes its edits into the design docs (`02`/`05`/`06`) and, for term + changes, the glossary. +3. **Record each decision** in `DECISIONS.md` (an MD-series entry with rationale — lead with meaning, the + D-handles below are session-local shorthand). 4. **Only then** does plan 02's Wave B type the code as a projection of the now-settled base. -Corollary (the §0 anti-bloat thesis, carried from plan 02): every resolution must **shrink the -contract or hold it flat**, never grow it. A readiness floor is *a floor to clear, never a quota to -fill* — type sections to give authors guardrails + autocomplete, not to train them to pad specs. +Corollary (the anti-bloat thesis, carried from plan 02 §0): every resolution must **shrink the contract or +hold it flat**, never grow it. A readiness floor is *a floor to clear, never a quota to fill* — type sections +to give authors guardrails + autocomplete, not to train them to pad specs. ## §2 — The root tension (carried from plan 02 §2 — the driver behind D1–D3) -`src/model/sections.ts` types every section as `Record`. The base (**L9**, `02` §3) -intends **typed-but-optional** sections. Open bags collapse "optional" into "unknown," at three costs: +`src/model/sections.ts` types every section as `Record`. The base (**L9**, `02` §3) intends +**typed-but-optional** sections. Open bags collapse "optional" into "unknown," at three costs: -1. **The tracer bullet proves less than it claims** — the example typechecks against `unknown`, so - "it compiles" says nothing about the DSL capturing section *shape* (the session's headline goal). -2. **The authoring-shape honesty check is bypassable** — `behavior: { "has-verifier": true }` - typechecks today and no validator catches it (verified; locked by a gated H8 fixture in plan 02). -3. **Validators string-probe at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a - section field name silently passes the floor. +1. **The tracer bullet proves less than it claims** — the example typechecks against `unknown`, so "it + compiles" says nothing about the DSL capturing section *shape* (the session's headline goal). +2. **The authoring-shape honesty check is bypassable** — `behavior: { "has-verifier": true }` typechecks + today and no validator catches it (verified; locked by a gated H8 fixture in plan 02). +3. **Validators string-probe at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a section + field name silently passes the floor. -> **The headline forward risk is authoring ergonomics, not graph theory.** If authoring feels heavy, -> authors (human **and** agent) avoid the system or overfit specs to satisfy tooling. Untyped sections -> give no autocomplete and no shape guardrails — the heavy-authoring loop. (H1 was the first symptom in -> the repo: an agent reached for `Object.fromEntries(...) as Record` instead of a -> plain literal — fixed in Wave A.) So typing the floor-bearing sections is both the honesty fix **and** -> the single highest-value adoption lever. This is why §4c routes "authoring ergonomics" to `07` as a -> named Slice-2 concern. +> **The headline forward risk is authoring ergonomics, not graph theory.** If authoring feels heavy, authors +> (human **and** agent) avoid the system or overfit specs to satisfy tooling. Untyped sections give no +> autocomplete and no shape guardrails — the heavy-authoring loop. (H1 was the first symptom in the repo: an +> agent reached for `Object.fromEntries(...) as Record` instead of a plain literal — fixed +> in Wave A.) So typing the floor-bearing sections is both the honesty fix **and** the single highest-value +> adoption lever. This is why `07` §6 names "authoring ergonomics" as a Slice-2 concern. -## §3 — Open decisions, in dependency order +## §3 — The six open decisions, in dependency order + +> Handle hygiene: D1–D8 below are the *open-decision* code-space used by plans 02/03 — **not** the legacy +> structural D1–D6 table in `DECISIONS.md`. D5 was never assigned; D6 (generic-anchor shape) was folded out +> as MD-8. In prose, lead with meaning. ``` -D2 (behavior.rules: prose vs ref list?) ──┐ -canonical section shapes (base §2/§3) ──┴──► D1 (type which sections, how) - │ - ┌─────────────────────────┼───────────────────────────────┐ - D3 (collapse floor) H2/H3 field shapes H8 gated fixtures flip active +D2 (prose vs refs in behavior.rules AND behavior.examples) ──┐ +canonical section shapes (02 §1/§3) ──┴──► D1 (type which sections, how) + │ + ┌───────────────────────────────────────┼──────────────────────────────┐ + D3 (collapse floor) H2/H3 field shapes H8 gated fixtures flip active D4 (AuthoredModel vs the one graph) — write a direction now, execute at Slice 1/3 -D6 (generic-anchor DSL shape) — base-determined; record as decided (§3a) -D7 (kind-aware `defined` floor) — sequences with D1; de-pads the example ← review F1 -D8 (.spec.ts file-extension collision) — Representation-level; before adopters ← review 1.3 +D7 (kind-aware `defined` floor) — sequences with D1; de-pads the example +D8 (.spec.ts file-extension collision) — Representation-level; before adopters ``` ### D2 — prose-vs-ref duality across `behavior.rules` AND `behavior.examples` (gates typing `behavior`) @@ -91,130 +89,84 @@ D8 (.spec.ts file-extension collision) — Representation-level; before adopters `given/when/then` (the prose entry is redundant with its own GWT). So `behavior.examples` is used two incompatible ways in the same example. - **Resolve** the inline-vs-promote duality (`02` §3 "Section ⟷ kind") for **both** fields: prose + - relations to promoted specs, or typed ref lists with referential-integrity coverage. Must resolve - before `behavior` is typed (D1), or typing it ossifies whichever shape the first author happened to - pick. + relations to promoted specs, or typed ref lists with referential-integrity coverage. Must resolve before + `behavior` is typed (D1), or typing it ossifies whichever shape the first author happened to pick. ### D1 — how much to type sections now (the linchpin) -- **Recommendation (carried, to ratify or revise):** type the **five sections the floors and the - extractor depend on** — `intent`, `behavior`, `constraints`, `model`, `verification` — and leave - `design` / `decision` / `ui` as open bags so the unsettled surfaces keep breathing. This closes the - honesty bypass for object literals and gives authors guardrails, without over-committing volatile - surfaces. -- **Alternatives to weigh:** type all eight now; or type none and add a runtime authoring-shape - validator instead. -- **Unblocks:** H3 (`constraints[]` shape), H2 (open-questions home), H4 (section-ref integrity), the - gated H8 fixture `invalid-hand-authored-delivery-fact-in-section`, and the H9 in-section note. +- **Recommendation (carried, to ratify or revise):** type the **five sections the floors and the extractor + depend on** — `intent`, `behavior`, `constraints`, `model`, `verification` — and leave `design` / + `decision` / `ui` as open bags so the unsettled surfaces keep breathing. This closes the honesty bypass + for object literals and gives authors guardrails, without over-committing volatile surfaces. +- **Alternatives to weigh:** type all eight now; or type none and add a runtime authoring-shape validator + instead. +- **Unblocks:** H3 (`constraints[]` shape), H2 (open-questions execution — direction already decided, MD-9), + H4 (section-ref integrity), the gated H8 fixture `invalid-hand-authored-delivery-fact-in-section`, and the + H9 in-section note. ### D3 — collapse direction for the floor validator (depends on D1) -- **Tension.** `validators.ts` is 453 lines for three checks; clause ids are enumerated in **four** - places and `authoredPaths` in the floor data is **decorative** (read only by its declaration + tests, - never by the evaluator — verified). +- **Tension.** `validators.ts` is 453 lines for three checks; clause ids are enumerated in **four** places + and `authoredPaths` in the floor data is **decorative** (read only by its declaration + tests, never by + the evaluator — verified). - **Recommendation (carried):** make the **table the single source of truth** — the evaluator reads - `authoredPaths` + a few generic predicates (safe once sections are typed), killing the 4× - enumeration. Alternative: shrink the table, let the evaluator be the spec. - -### D7 — make the `defined` floor kind-aware (highest-value resolvable item · review F1) **[NEW]** -- **Finding (currently unowned by D1–D6).** The `defined` floor requires `rules-and-or-examples` for - **every** kind (`readiness-floor.ts`); overlays only *add* requirements, never *relax* the base. So a - `decision` / `model` / `constraint` / `contract` / `workflow` spec cannot reach `defined` on its - *natural* content — and the tracer bullet pads three specs with throwaway `behavior.rules` purely to - clear the floor (`order-lifecycle`, `order-model`, `order-latency-constraint`), which - `checkout-v1.test.ts` then asserts is green. The canonical example embodies the exact anti-pattern - **P4's anti-padding corollary forbids**, and an AI author will learn to sprinkle `behavior.rules` onto - decision records. -- **Proof it is a bug, not taste.** At `scoped` the evidence clause is - `rules-examples-or-**constraints**` (constraints alone are valid evidence); at `defined` it silently - drops `constraints` → `rules-and-or-examples`. The maturity ladder is **non-monotonic in the evidence - it accepts** — a constraint honestly `scoped` on its target suddenly needs behavior content to mature - to `defined`. This half-delivers the `02`/P8 "kind is a true subtype that *changes* required detail" - promise: at the floor level `kind` only ever *adds*. + `authoredPaths` + a few generic predicates (safe once sections are typed), killing the 4× enumeration. + Alternative: shrink the table, let the evaluator be the spec. + +### D7 — make the `defined` floor kind-aware (highest-value item · review F1) +- **Finding.** The `defined` floor requires `rules-and-or-examples` for **every** kind + (`readiness-floor.ts`); overlays only *add* requirements, never *relax* the base. So a `decision` / + `model` / `constraint` / `contract` / `workflow` spec cannot reach `defined` on its *natural* content — + and the tracer bullet pads three specs with throwaway `behavior.rules` purely to clear the floor + (`order-lifecycle`, `order-model`, `order-latency-constraint`), which `checkout-v1.test.ts` then asserts + is green. The canonical example embodies the exact anti-pattern **P4's anti-padding corollary forbids**, + and an AI author will learn to sprinkle `behavior.rules` onto decision records. +- **Proof it is a bug, not taste.** At `scoped` the evidence clause is `rules-examples-or-**constraints**` + (constraints alone are valid evidence); at `defined` it silently drops `constraints` → + `rules-and-or-examples`. The maturity ladder is **non-monotonic in the evidence it accepts** — a + constraint honestly `scoped` on its target suddenly needs behavior content to mature to `defined`. This + half-delivers the `02`/P8 "kind is a true subtype that *changes* required detail" promise: at the floor + level `kind` only ever *adds*. - **Resolution (shrinks the contract).** Make the `defined` evidence clause **defer to the kind**: for a kind with an overlay (constraint→target, model→terms, example→GWT) or a natural truth section (decision→`decision`), that overlay/section *is* the evidence; require `rules-and-or-examples` only for - the behavior-family kinds (`behavior`, `rule`, `workflow`). The overlay machinery already exists — this - is "base evidence clause is kind-conditional," not new infrastructure. Sequences with D1 (typed - sections make the predicate cheap). -- **Why here, not plan 02.** It is a base §6 refinement (the floor table is the model), so it moves - *with* the base in the grilling pass — and resolving it de-pads the canonical example (drop three - rules), a visible quality signal. Wave B then re-authors the example + the H8 + the behavior-family kinds (`behavior`, `rule`, `workflow`). The overlay machinery already exists — this is + "base evidence clause is kind-conditional," not new infrastructure. Sequences with D1 (typed sections make + the predicate cheap). +- **Why here, not plan 02.** It is a refinement of the floor table — the model (`05` §3) — so it moves + *with* the base in the grilling pass; and resolving it de-pads the canonical example (drop three rules), a + visible quality signal. Wave B then re-authors the example + the H8 `invalid-defined-constraint-without-target` fixture (which itself currently pads — review §3.1). ### D4 — `AuthoredModel` seam vs the one graph (write a direction now; execute Slice 1/3) -- **Tension (P2).** Validators are designed to run over **the one graph** ("no consumer maintains a - parallel model"). When the extractor lands, either (a) validators migrate to consume `GraphSchema` - and `AuthoredModel` retires, or (b) `AuthoredModel` stays as an explicit, documented *pre-graph - authoring lint*. Avoid two validation code paths drifting apart. +- **Tension (P2).** Validators are designed to run over **the one graph** ("no consumer maintains a parallel + model"). When the extractor lands, either (a) validators migrate to consume `GraphSchema` and + `AuthoredModel` retires, or (b) `AuthoredModel` stays as an explicit, documented *pre-graph authoring + lint*. Avoid two validation code paths drifting apart. (Interacts with the crippled-graph gap strategy, + `plans/04` §2.) - **Action:** decide the **direction** in this session even though execution is Slice 1/3. -### D6 — generic-anchor DSL shape (decide now; pairs with Slice-2 anchor extraction) -- **Tension.** The whole point of *generic* anchors is binding **any** code location, but the builders - are namespace-locked (`anchorImplementation` → `impl:`, `specTest` → `test:`). Generic binding - implies the `satisfies` anchor should accept the implementation-flavored code namespaces - (`impl` / `api` / `component`); `api:` already parses in `ids.ts` — only a builder + branded id is - missing. -- **Decide:** generalize `anchorImplementation` to a `codeAnchor` over those namespaces, or add sibling - builders. (The example gap this exposes — H10 — is owned by plan 02, executed at Slice 2.) - -### D8 — the `.spec.ts` file-extension collision (Representation-level; currently implicit · review 1.3) **[NEW]** +### D8 — the `.spec.ts` file-extension collision (Representation-level; currently implicit · review 1.3) - **Finding.** The extractor reads `/specs/**/*.spec.ts` (`03`/`04` §5), but `*.spec.ts` is *the* default - test glob for Vitest/Jest/Mocha. This repo dodges it (`vitest.config.ts` narrows to - `test/**/*.test.ts`), but a **consuming** project using the standard `**/*.spec.ts` for its tests will - have its runner try to execute Spec-primitive files as test suites — a baffling failure for a new - adopter. The model name `Spec` is well-chosen and stays; only the *file serialization* inherits foreign - semantics (the test runner stands in for one of the three readers). -- **Decide (before any real adopter):** keep `.spec.ts` with a documented test-glob exclusion, or move to - a collision-free extension (`.sdp.ts`, `.spec.sdp.ts`). Representation-level, not a model change. - -## §3a — Review-assessed: resolvable now (fast-track, don't re-litigate) - -The post-split adversarial review judged four items **already determined by the base** — they need -ratification, not deliberation. Recorded so the grilling session spends its energy on the genuinely open -D1 / D2 / D3 / D4 / D7 / D8: -- **R1, R2** (and **R3**, §4) — explicitly "code already conforms; wording tightening." Zero reversal - risk; ratify and apply to the base, don't grill. -- **H2 *direction*** — the open-questions home is unambiguously `intent.openQuestions` (`02` §3 + the - `04` worked example). The *fix* (read from `intent.openQuestions`) is correct **regardless of D1**; D1 - only shortens the predicate. Decouple it: H2 can be fixed and its gated H8 fixture flipped without - waiting on the typed-sections grill (execution still lands in plan 02 Wave B). -- **D6** — anchors are *generic* by definition; `ids.ts` already parses `api:` / `component:`; only a - builder + branded id is missing. Record `codeAnchor` over `impl` / `api` / `component` as **decided** - (sibling-builders alternative noted and rejected for surface bloat). - -**Does anything need *reversing*?** The review's answer: **no load-bearing bet** (one-primitive model, -authored/derived `claim` boundary, checked-never-gated, git-as-event-log, agent-surface) needs reversal. -Only refinements: D7 (floor kind-blindness), D8 (`.spec.ts`), and the R-series doc reconciliations — the -recurring shape being *the code is more honest than the docs; reconcile the docs down to it*. - -## §4 — Concept-base refinements to ratify (R1, R2, R3) - -Recorded as **PROPOSED** in `docs/concept/DECISIONS.md` (the code already conforms in all three cases; -these are wording tightenings, so we flag rather than silently edit the ratified base). Ratify and apply -them to the base in this session: - -- **R1** — harmonize "anchor carries identity only" (§2) with "anchored = a human binding" (§4). -- **R2** — "no consumer reads source directly" → permit source *links* recorded in the graph, forbid - independent re-parsing. -- **R3** (review F6) — reconcile `04`'s `specTest` signature to **binding-only**. `04` §2 documents - `specTest(id, { verifies, run })` with an executing `run` callback; the code (`anchors.ts`) is - identity-only (`{ id, label?, verifies }`, **no `run`**), and the code is *more* correct — a binding - carrying `run` would couple the graph binding to execution, contradicting "records that an enabled - verifier exists, never that it ran" (MD-7). Doc fidelity bug; reconcile the doc down to the code. - -## §5 — What this session unblocks (the Wave-B execution backlog, owned by plan 02) - -Once D1–D3 (and D2) land and the base is reconciled, plan 02's **Wave B** becomes execution-ready: -H3 (`constraints[]`), H2 (open-questions → `intent.openQuestions`), H5/D3 (collapse the floor -validator), H4 (section-ref referential integrity, likely folded into the Slice-1 extractor), and the -two gated H8 fixtures flip from `it.todo` to active. H10 (the api/route anchor in the example) executes -with Slice-2 anchor extraction under the D6 shape. - -## §6 — Definition of done for this session - -- **D1, D2, D3, D4, D6, D7, D8 resolved** and recorded in `DECISIONS.md` (rationale + what each - unblocks); the §3a fast-track items (R1/R2/R3, H2-direction, D6) ratified with minimal litigation. -- **Base §2/§3 and §6 reconciled** to the resolved section shapes + the kind-aware `defined` floor (D7); - **R1/R2/R3 ratified** and applied to the base. -- **Plan 02 Wave B rewritten execution-ready** with the now-locked field shapes (no ⊳ markers left), - including the example de-padding that D7 unlocks. + test glob for Vitest/Jest/Mocha. This repo dodges it (`vitest.config.ts` narrows to `test/**/*.test.ts`), + but a **consuming** project using the standard `**/*.spec.ts` for its tests will have its runner try to + execute Spec-primitive files as test suites — a baffling failure for a new adopter. The model name `Spec` + is well-chosen and stays; only the *file serialization* inherits foreign semantics (the test runner stands + in for one of the three readers). +- **Decide (before any real adopter):** keep `.spec.ts` with a documented test-glob exclusion, or move to a + collision-free extension (`.sdp.ts`, `.spec.sdp.ts`). Representation-level, not a model change. + +## §4 — What this session unblocks (the Wave-B execution backlog, owned by plan 02) + +Once D1–D3 land and the base is reconciled, plan 02's **Wave B** becomes execution-ready: H3 +(`constraints[]`), H2 (open-questions execution under MD-9), H5/D3 (collapse the floor validator), H4 +(section-ref referential integrity, likely folded into the Slice-1 extractor), the example de-padding that D7 +unlocks, and the two gated H8 fixtures flip from `it.todo` to active. H10 (the api/route anchor in the +example) executes with Slice-2 anchor extraction under the MD-8 `codeAnchor` shape. + +## §5 — Definition of done for this session + +- **D1, D2, D3, D4, D7, D8 resolved** and recorded in `DECISIONS.md` (rationale + what each unblocks). +- **The core model and floor reconciled** to the resolved section shapes (`02` §1/§3) and the kind-aware + `defined` floor (`05` §3, per D7); any term changes land in the glossary. +- **Plan 02 Wave B rewritten execution-ready** with the now-locked field shapes, including the example + de-padding that D7 unlocks. - No code changed in this session (it is PLAN-ONLY); the code follows in the Wave-B execution session. From b60542c92b822748701e8621fe3a9f9d4a408b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 03:37:13 +0200 Subject: [PATCH 12/29] chore: exempt archived reviews/ from prettier (verbatim historical records) --- .prettierignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.prettierignore b/.prettierignore index 5cf7ec7..027aaef 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,6 @@ jtbd-stories/** plans/** .sisyphus/** .tmp-scratch/** + +# Archived review artifacts - verbatim historical records, not format-policed +reviews/ From 8d010dec36fc940e7fdb3a5cdccc75d3b2c5ac59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 03:40:28 +0200 Subject: [PATCH 13/29] Finalize plans 02/04 + AGENTS nav after the pre-grill folds - plans/02: header notes the 2026-06-10 folds; H2 direction decided (MD-9), H10 shape decided (MD-8); R-series marked ratified+applied; review pointer re-aimed to tracked reviews/; sequencing gains the done folds step. - plans/04: status + s0/s3/s4 marked executed (Fold-A, Fold-B, archive); fold prompts marked executed; grill kickoff is the live prompt; s6 resolvable-now ledger flipped to done with landing sites. - AGENTS.md: DECISIONS row covers MD-1..MD-9 + R-series; reviews/ nav row. --- AGENTS.md | 3 +- plans/02-phase0-hardening.md | 40 +++++---- ...zation-gap-strategy-and-pre-grill-folds.md | 83 +++++++++++-------- 3 files changed, 73 insertions(+), 53 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0b63f55..9c32a01 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,8 +45,9 @@ Progressive disclosure — start at the top, follow the pointers down. | `docs/concept/ubiquitous-language.md` | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology. The **model exposition** was rehomed into `00`–`07` (2026-06-10 Fold-A; the glossary header maps the old `base §n` refs) | **first, always** | | `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/00`–`07` (+ README) | **the technical design and the model** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model (`02` — the primitive, descriptors, sections, relations), the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | -| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (MD-1/2/4/5/7, the D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | +| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the MD-series MD-1…MD-9, the ratified R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code | +| `reviews/` | **archived session reviews** (implementation, founding-ideation, adversarial + prompts) — durable findings already folded into plans/DECISIONS; read for provenance | rarely | > The concept docs intentionally carry implementation detail (TS shapes, DSL, graph JSON): they double as the > implementation spec until the code can hold it. A clean concept/representation split is a recorded future diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index 34afe30..ba53947 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -7,6 +7,9 @@ > proper homes (see "Where the rest went," below). > **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 (`eb6bf2a`) + Wave A > green (`npm run check`: typecheck ×2, lint, format, **47 tests + 2 todo**, build). +> **2026-06-10 folds:** R1/R2/R3 ratified & applied; the anchor shape (MD-8 `codeAnchor`) and the +> open-questions home (MD-9 `intent.openQuestions`) recorded **decided** — H2's direction and H10's shape +> are no longer open; Wave B's typed-section fixes remain blocked on D1/D2/D3 (the grill, `plans/03`). This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the extractor can safely build on."* It stays inside the Slice 0 boundary — **no extractor, no graph @@ -15,10 +18,11 @@ doesn't inherit a landmine. **Where the rest went (the re-home).** This plan was originally one document fusing three kinds of work; they now live where each gets the right rigor: -- **Open decisions (D1–D4, D6)** → `plans/03-decision-resolution-and-base-reconciliation.md` — the - fresh-session grilling agenda. Wave B below is **blocked on these**. -- **Concept-base wording (R1, R2)** → `docs/concept/DECISIONS.md` (recorded **PROPOSED**), to ratify in - the grilling session. +- **Open decisions** → `plans/03-decision-resolution-and-base-reconciliation.md` — the grilling agenda, + now slimmed to the six genuinely-open (D1–D4, D7, D8). Wave B's typed-section fixes are **blocked on + D1/D2/D3**; the former D6 (anchor shape) was folded out as **decided** (DECISIONS MD-8). +- **Concept-base wording (R1, R2, R3)** → `docs/concept/DECISIONS.md` — **ratified & applied 2026-06-10** + in the pre-grill folds. - **Forward-looking acceptance criteria** → `docs/concept/07` §6 (mapped across Slices 1–5). --- @@ -118,7 +122,7 @@ Quick, contract-shrinking, reversible wins. All landed and verified; `npm run ch Execution-ready specs, held until the named decision lands in `plans/03-decision-resolution-and-base-reconciliation.md`. **Sequence after D1/D2/D3.** -### 🟠 H2 — Honesty check points at the wrong section (open questions) · gated on **D1** (+ home decision) +### 🟠 H2 — Honesty check points at the wrong section (open questions) · direction **decided** (MD-9); execution-ready independent of D1 - **Finding.** `validators.ts` (`hasNoBlockingOpenQuestions`) and `readiness-floor.ts` read `spec.design.openQuestions` / `spec.decision.openQuestions`. The canonical home is **`intent.openQuestions`** (`02` §3; the `04` worked example puts it under `intent`). @@ -166,18 +170,18 @@ Execution-ready specs, held until the named decision lands in - **Change (when scheduled).** Extend referential integrity to typed ref-bearing section fields; align the example to the resolved D2. -### 🟠 H10 — The example under-proves the generic-anchor claim · gated on **D6**; pairs with Slice 2 +### 🟠 H10 — The example under-proves the generic-anchor claim · shape **decided** (MD-8 `codeAnchor`); pairs with Slice 2 - **Finding.** *Generic* anchors should bind **any** code location, but the example has only `impl` + `test` anchors; the docs' own example (`04` §5) includes a route anchor `api:orders.post`. The tracer bullet never exercises the genericity claim — the strongest proof is binding a **non-class location** (a route/endpoint). -- **Change (when scheduled).** Add an `api`/route anchor to `examples/checkout-v1` under the DSL shape - resolved in **D6**; it should extract to a `satisfies` edge with `claim:"anchored"` like any other. +- **Change (when scheduled).** Add an `api`/route anchor to `examples/checkout-v1` under the **MD-8 + `codeAnchor`** shape; it should extract to a `satisfies` edge with `claim:"anchored"` like any other. ### Carried review backlog (post-split adversarial review · small, ride Wave B / Slice 1) -Captured so the signal survives — the review lives in gitignored `.tmp-scratch/`. None block the split; -each is small, decision-free, or doc-level. +Captured so the signal survives — the review is tracked in `reviews/04-post-split-adversarial-review.md`. +None block the split; each is small, decision-free, or doc-level. - **F2 — `ref` is a spec-only brand wearing a generic name.** `ids.ts` exports `specId as ref`, so `ref()` rejects `pack:` / `doc:` targets. Harmless today (every call site wants a spec); add a doc note, and revisit when `doc:`-target relations (`decidedBy` → external ADR) / pack-targeting arrive. @@ -188,10 +192,10 @@ each is small, decision-free, or doc-level. "always points at standalone `kind:"model"` specs," but `validateDanglingReferences` only checks the ref *resolves*. Needs the kind — likely Slice-1/3 (graph) territory; named so prose doesn't over-promise vs. the check. -- **Cross-refs (owned elsewhere).** F1 → **D7** (plan 03, base §6 refinement — de-pads the example and - the H8 constraint fixture in Wave B). F6 → **R3** (`DECISIONS.md`, doc reconciliation). **H2's - *direction* is determined** (`intent.openQuestions`, review §3.3) — it can be decoupled from D1 and - executed independently in Wave B. **F7 — DONE**: the H9 envelope-defense caveat (excess-property / +- **Cross-refs (owned elsewhere).** F1 → **D7** (plan 03, a floor-table refinement — `05` §3 — de-pads the + example and the H8 constraint fixture in Wave B). F6 → **R3 — applied 2026-06-10** (Fold-B; `04` §2 is + now binding-only). **H2's *direction* is decided** (MD-9: `intent.openQuestions`) — execution is + decoupled from D1 and lands in Wave B. **F7 — DONE**: the H9 envelope-defense caveat (excess-property / inline-literal scope) is now recorded in `test/builders.typecheck.ts`. --- @@ -206,9 +210,11 @@ repo. Full typing of `design`/`decision`/`ui` sections stays deferred per D1. ## §6 — Sequencing 1. **Wave A** — ✅ done (H1, H6, H7, H8-active, H9). -2. **Resolve decisions** — the fresh grilling session (`plans/03`): D2 → D1 → D3, plus D4 (direction) and - D6 (shape); ratify R1/R2 on the base. -3. **Wave B** — with typed sections in place: H3, then H2 (+ flip its gated H8 fixture), then H5/D3, then +2. **Pre-grill folds (2026-06-10)** — ✅ done: R1/R2/R3 ratified & applied; the anchor shape (MD-8) and the + open-questions home (MD-9) recorded decided; `plans/03` slimmed to the six open decisions. +3. **Resolve decisions** — the fresh grilling session (`plans/03`): D2 → D1 → D3, plus D4 (direction), + D7 (kind-aware floor), D8 (file extension). +4. **Wave B** — with typed sections in place: H3, then H2 (+ flip its gated H8 fixture), then H5/D3, then H4; H10 with Slice-2 anchor extraction. **Done gate for Wave B:** the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for diff --git a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md index f6b8545..4ca6b60 100644 --- a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md +++ b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md @@ -1,12 +1,13 @@ # Session 4 — Formalization: gap strategy + pre-grill folds (re-entry context) -> **Status: PLAN-ONLY · DRAFT · the re-entry point.** This document exists to bootstrap context fast -> after a break — it is the formalized residue of a long design arc that hit the ceiling of what a -> chat thread can resolve ("no substrate to grow the decision → formalize"). Read §0 first; the rest -> is reference. -> **Date:** 2026-06-08 · **Branch:** `feature/mvp-init` · **Repo state:** green; two commits since -> Session 1 — `478cc00` (Phase-0 hardening Wave A) and `c8a7ce0` (plan split + adversarial-review -> absorption). Working tree otherwise clean. +> **Status: PLAN-ONLY · §3 EXECUTED 2026-06-10 (Fold-A · Fold-B · archive) · §2 still the parked +> direction.** This document exists to bootstrap context fast after a break — it is the formalized residue +> of a long design arc that hit the ceiling of what a chat thread can resolve ("no substrate to grow the +> decision → formalize"). Read §0 first; the rest is reference. +> **Date:** 2026-06-08 · folds executed 2026-06-10 · **Branch:** `feature/mvp-init` · **Repo state:** +> green; the pre-grill folds are committed (UL → lean glossary + rehome; R1/R2/R3 applied; MD-8/MD-9 +> recorded; `plans/03` slimmed to six; reviews archived in `reviews/`). **Next: the grill** (§5 kickoff +> prompt; agenda `plans/03`). --- @@ -25,9 +26,9 @@ done). No extractor, no graph, no engine yet. 3. We converged on **how to manage gen-2's own delivery in the gap** before the engine exists — see §2, the **crippled graph**. -**What's next (in order):** two scoped **fresh** fold sessions (§3) → then the **grill** on a lean base -+ a six-item agenda. This session's job was only to *formalize* that — the folds and the grill run cold, -in fresh sessions, not in a loaded one. Copy-paste launch prompts are in §5. +**What's next:** ~~two scoped **fresh** fold sessions (§3)~~ **the folds and the archive are DONE +(2026-06-10, one fresh fold session)** → now the **grill** on the lean base + the six-item agenda +(`plans/03`). The grill runs cold, in a fresh session; its kickoff prompt is in §5. **The one decision still genuinely open and yours:** how far the crippled graph goes, and exactly what its two instances are (§2). Parked deliberately — it needs substrate to grow, not more chat. @@ -97,7 +98,13 @@ decisions.** Today neither holds. Two fresh fold sessions fix that. (Naming note **Fold-A / Fold-B** to avoid colliding with the *review's* findings F1–F7, which are different things — see the ledger, §6.) -### Fold-A — the UL restructure (big; careful; its own fresh session) +### Fold-A — the UL restructure — ✅ DONE 2026-06-10 +*Executed as planned:* `ubiquitous-language.md` is now the lean glossary (terms · relations · worked +dialogue · flagged ambiguities · term ledger, with an old-§ map in the header); the exposition was rehomed +**into `00`–`07`** (the lean option won — measured overlap was ~95%, so rehoming was mostly dedup plus a few +genuine insertions: the discipline ≈ kind/section mapping into `06` §6, UML alignment + relation-granularity +rationale into `02` §6, many-packs membership into `02` §4); R1/R2 applied; every pointer re-aimed +(AGENTS.md, concept README, DECISIONS.md). Original scope kept below for the record. The current `docs/concept/ubiquitous-language.md` is a **design-synthesis document wearing a UL hat** — an artifact of the 8-hour grill that had to invent *and* name the model in one breath. A true Evans UL is lean. Split the one doc into its three real concerns: @@ -117,7 +124,10 @@ lean. Split the one doc into its three real concerns: only where they live. So it does *not* need a grill, but it does need fresh, careful scope and a pointer-integrity pass. -### Fold-B — the resolvable-now sweep (small) +### Fold-B — the resolvable-now sweep — ✅ DONE 2026-06-10 +*Executed as planned:* `04` §2 reconciled to the binding-only `specTest` (R3 applied); the generic-anchor +shape and the open-questions home recorded **ACCEPTED** in `DECISIONS.md` (MD-8 / MD-9); `plans/03` slimmed +to the six genuinely-open decisions. Original scope kept below for the record. Fold the already-determined items out of the grill's path: - **Reconcile the `04` doc's `specTest` signature to binding-only** (no executing `run` callback; the code already conforms — doc fidelity fix). @@ -130,10 +140,10 @@ Fold the already-determined items out of the grill's path: prose-vs-ref-incl-examples, collapse-the-floor-validator, AuthoredModel-vs-graph, kind-aware-floor, `.spec.ts`-collision. Collapse the "resolvable-now" section to a one-line pointer. -### Optional — archive the artifacts -The adversarial **review**, its **prompt**, and the **gen-1 study** currently live in gitignored -`.tmp-scratch/`; they vanish on a clean clone. If they're worth keeping, move them into a tracked -`reviews/` (or `docs/reviews/`). 2.5h + 8h + this arc of signal otherwise lost to git. +### Optional — archive the artifacts — ✅ DONE 2026-06-10 +All five scratch artifacts (both earlier reviews, the adversarial review + its prompt, the fold-session +prompt) moved into tracked **`reviews/`** with a README mapping each to where its durable findings landed. +The gen-1 study was chat-only; its takeaway is formalized in §0/§2 here. ### Then — the grill A fresh `grill-with-docs` session on `plans/03`, now opening onto a lean base + a tight six-item agenda. @@ -144,15 +154,13 @@ Wave B execution-ready. ## §4 — Sequencing & done-criteria -1. **Fold-A** (UL restructure) — fresh session. *Done when:* lean glossary exists; exposition rehomed; - every pointer re-aimed; the two wording reconciliations applied; nothing in the model *changed*, only - relocated; repo still coherent (no dangling cross-refs). -2. **Fold-B** (resolvable-now sweep) — fresh session (can follow Fold-A or run independently; it doesn't - touch the UL doc except the plans/03 slim). *Done when:* `04` reconciled; two decisions recorded; +1. ~~**Fold-A** (UL restructure)~~ — ✅ DONE 2026-06-10: lean glossary; exposition rehomed into `00`–`07`; + pointers re-aimed; R1/R2 applied; nothing in the model changed, only relocated; repo coherent. +2. ~~**Fold-B** (resolvable-now sweep)~~ — ✅ DONE 2026-06-10: `04` reconciled (R3); MD-8/MD-9 recorded; `plans/03` down to six open decisions. -3. **(optional) archive** the scratch artifacts. -4. **Grill** — fresh `grill-with-docs` on `plans/03`. *Done when:* the six decisions resolved + recorded; - base reconciled; `plans/02` Wave B execution-ready. +3. ~~**(optional) archive**~~ — ✅ DONE 2026-06-10: `reviews/` tracked. +4. **Grill** — fresh `grill-with-docs` on `plans/03`. ← **NEXT.** *Done when:* the six decisions resolved + + recorded; base reconciled; `plans/02` Wave B execution-ready. 5. **Then** Wave B execution, and — gated on a minimal engine — the **crippled graph** (§2) graduates from direction to design. @@ -164,7 +172,10 @@ Wave B execution-ready. ## §5 — Scoped session prompts (copy-paste to launch cold) -### Fold-A prompt +> The Fold-A / Fold-B prompts below were **executed 2026-06-10** and are kept for the record; the live one +> is the **grill kickoff**. + +### Fold-A prompt — executed ✅ > Scoped doc-restructure, no model changes. Read `docs/concept/ubiquitous-language.md`, `00`–`07`, > `README.md`, `AGENTS.md`, and `plans/03` + `plans/04`. The current `ubiquitous-language.md` is a > design-synthesis doc wearing a UL hat; split it into a **lean Evans glossary** (Term · Definition · @@ -175,7 +186,7 @@ Wave B execution-ready. > source but not *re-parse* it). **Constraint: distillation + rehoming only — do not re-decide the model; > terms and meanings stay identical, only their home changes.** End green: no dangling cross-references. -### Fold-B prompt +### Fold-B prompt — executed ✅ > Small, decision-free sweep. Read `plans/03`, `plans/04`, `docs/concept/DECISIONS.md`, and > `docs/concept/04-authoring-and-binding.md`. (1) Reconcile the `04` doc's `specTest` description to a > binding-only signature (no executing `run` callback — the code in `src/model/anchors.ts` already @@ -184,9 +195,11 @@ Wave B execution-ready. > (`intent.openQuestions`; note execution stays Wave B in `plans/02`). (3) Slim `plans/03` to the six > genuinely-open decisions and collapse its "resolvable-now" (§3a) to a one-line pointer. -### Grill kickoff (after the folds) -> `grill-with-docs` on `plans/03` — six open decisions, against the now-lean base. Ratify inline; rewrite -> `plans/02` Wave B execution-ready. +### Grill kickoff (the folds are done — this is the live prompt) +> `grill-with-docs` on `plans/03` — six open decisions (D1 typed-sections · D2 prose-vs-ref incl. +> `examples` · D3 floor-validator collapse · D4 AuthoredModel-vs-graph · D7 kind-aware `defined` floor · +> D8 `.spec.ts` collision), against the now-lean base (terms: the glossary; model: `00`–`07`). Ratify +> inline; record in `DECISIONS.md`; rewrite `plans/02` Wave B execution-ready. --- @@ -205,15 +218,15 @@ The antidote to code-noise: every handle, decoded. Two separate code-spaces — | **D7** | Make the `defined` floor **kind-aware** — today it forces behavior-rules onto decision/model/constraint specs, so the example *pads*. The review's top finding; shrinks the contract; de-pads the example. | grill | | **D8** | The `.spec.ts` file-extension collides with the JS test-runner glob — adoption landmine. Keep-with-exclusion vs. a collision-free extension. Representation-level. | grill | -**Resolvable-now (folded out by Fold-B — already determined):** +**Resolvable-now (folded out — all ✅ done 2026-06-10):** | Handle | Meaning | Disposition | |---|---|---| -| **R1** | Anchor "identity only" (§2) vs "a binding" (§4) → unify to *binding assertion only*. | apply in Fold-A | -| **R2** | "No consumer reads source" → links-into-graph OK, independent re-parse forbidden. | apply in Fold-A | -| **R3** | `04` doc's `specTest` → binding-only (no `run`); code already conforms. | apply in Fold-B | -| **D6** | Generic-anchor shape → `codeAnchor` over impl/api/component. | record in Fold-B; exec Slice 2 | -| **H2-dir** | Open-questions home = `intent.openQuestions`. | record in Fold-B; exec Wave B | +| **R1** | Anchor "identity only" vs "a binding" → unified to *binding assertion only*. | ✅ applied in Fold-A (glossary, `01`, `04` §2) | +| **R2** | "No consumer reads source" → links-into-graph OK, independent re-parse forbidden. | ✅ applied in Fold-A (`01` P2, `03` §4) | +| **R3** | `04` doc's `specTest` → binding-only (no `run`); code already conforms. | ✅ applied in Fold-B (`04` §2) | +| **D6** | Generic-anchor shape → `codeAnchor` over impl/api/component. | ✅ recorded **MD-8**; exec Slice 2 (plan 02 H10) | +| **H2-dir** | Open-questions home = `intent.openQuestions`. | ✅ recorded **MD-9**; exec Wave B (plan 02 H2) | **Wave B (in `plans/02`, decision-gated; execute after the grill):** H2 (open-questions home fix), H3 (`constraints` → array), H4 (section-ref referential integrity), H5 (trim floor validator), H10 (api From 24db2ad12678a9f6db1ea1358d8c5f756a471f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 04:26:16 +0200 Subject: [PATCH 14/29] Grill: D1-D4/D7/D8 resolved as MD-10..MD-15; base reconciled; Wave B execution-ready MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DECISIONS.md: six grill resolutions ratified (content-only behavior sections, the typing law, kind-conditional evidence floor, table-as-truth, one validation path, .sdp.ts extension) - Base reconciled inline: 02 (duality laws, typing law, no-status rule), 05 (one-path rule; two-part floor + per-kind evidence table), 04/03/00/jtbd-01 (.sdp.ts, content-only example), glossary (ledger updates) - plans/03 = executed agenda-of-record with resolution table; plans/02 §4 rewritten as four ordered Wave B steps (B1-B4); plans/04 marks the grill done - Polish: .sisyphus untracked; package.json description + Apache-2.0 --- .sisyphus/boulder.json | 93 ---------- .../session-1-bootstrap-phase0/decisions.md | 16 -- .../session-1-bootstrap-phase0/issues.md | 2 - .../session-1-bootstrap-phase0/learnings.md | 26 --- .../session-1-bootstrap-phase0/problems.md | 0 AGENTS.md | 2 +- .../00-vision-scope-and-mvp-boundary.md | 4 +- docs/concept/02-core-model.md | 24 ++- docs/concept/03-the-one-graph.md | 4 +- docs/concept/04-authoring-and-binding.md | 13 +- docs/concept/05-validation-and-honesty.md | 36 +++- docs/concept/DECISIONS.md | 123 +++++++++++++ docs/concept/ubiquitous-language.md | 11 +- jtbd-stories/01-capture-and-evolve-intent.md | 2 +- package.json | 2 + plans/02-phase0-hardening.md | 173 ++++++++++-------- ...sion-resolution-and-base-reconciliation.md | 43 +++-- ...zation-gap-strategy-and-pre-grill-folds.md | 52 +++--- 18 files changed, 340 insertions(+), 286 deletions(-) delete mode 100644 .sisyphus/boulder.json delete mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/decisions.md delete mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/issues.md delete mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/learnings.md delete mode 100644 .sisyphus/notepads/session-1-bootstrap-phase0/problems.md diff --git a/.sisyphus/boulder.json b/.sisyphus/boulder.json deleted file mode 100644 index 8ba7e02..0000000 --- a/.sisyphus/boulder.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "active_plan": "/Users/darkomijic/dev-projects/libar-omni-concept/.sisyphus/plans/session-1-bootstrap-phase0.md", - "started_at": "2026-06-07T08:37:00.722Z", - "session_ids": [ - "ses_15ec5956ffferI4mLRGUxHmpHl", - "ses_15ec3ee46ffehXzpSwd3K8j4Zr", - "ses_15ec3ee1affevHWWGHKoDAVqu3", - "ses_15ec3edf3ffe1Ah0U1FSV1dLaF", - "ses_15ec3edb1ffe1af15nigJWwPBs" - ], - "plan_name": "session-1-bootstrap-phase0", - "session_origins": { - "ses_15ec5956ffferI4mLRGUxHmpHl": "direct", - "ses_15ec3ee46ffehXzpSwd3K8j4Zr": "appended", - "ses_15ec3ee1affevHWWGHKoDAVqu3": "appended", - "ses_15ec3edf3ffe1Ah0U1FSV1dLaF": "appended", - "ses_15ec3edb1ffe1af15nigJWwPBs": "appended" - }, - "task_sessions": { - "todo:1": { - "task_key": "todo:1", - "task_label": "1", - "task_title": "Bootstrap package, TypeScript toolchain, scripts, ignore rules, and CI", - "session_id": "ses_15ebd033effeo6aTUdkGjlW0L6", - "agent": "Sisyphus-Junior", - "category": "", - "updated_at": "2026-06-07T09:02:47.924Z" - }, - "todo:2": { - "task_key": "todo:2", - "task_label": "2", - "task_title": "Implement stable ID primitives and the initial public barrel", - "session_id": "ses_15eac1fbcffebFQ40GOZqcPrQB", - "agent": "Sisyphus-Junior", - "category": "", - "updated_at": "2026-06-07T09:18:38.067Z" - }, - "todo:3": { - "task_key": "todo:3", - "task_label": "3", - "task_title": "Implement descriptor literals, display labels, and optional section types", - "session_id": "ses_15e9ccdbbffeaw9jWvCtbxsuiC", - "agent": "Sisyphus-Junior", - "category": "", - "updated_at": "2026-06-07T09:28:04.998Z" - }, - "todo:4": { - "task_key": "todo:4", - "task_label": "4", - "task_title": "Implement Spec, Pack, relation, and anchor DSL builders", - "session_id": "ses_15e9531e3ffekXUe31TT6CW7Xn", - "agent": "Sisyphus-Junior", - "category": "", - "updated_at": "2026-06-07T09:43:03.044Z" - }, - "todo:6": { - "task_key": "todo:6", - "task_label": "6", - "task_title": "Define validation contracts, authored model seam, and readiness-floor data", - "session_id": "ses_15e876d0fffesZMAX5rB4WiqzO", - "agent": "Sisyphus-Junior", - "category": "", - "updated_at": "2026-06-07T09:54:18.121Z" - }, - "todo:7": { - "task_key": "todo:7", - "task_label": "7", - "task_title": "Implement tiny pure authored-layer validators with TDD", - "session_id": "ses_15e7c6fd0ffe9SvHbGztylfVNB", - "agent": "Sisyphus-Junior", - "category": "deep", - "updated_at": "2026-06-07T10:08:44.954Z" - }, - "todo:9": { - "task_key": "todo:9", - "task_label": "9", - "task_title": "Author valid `examples/checkout-v1` tracer bullet and integration gates", - "session_id": "ses_15e6f938affePvKDW62drjb224", - "agent": "Sisyphus-Junior", - "category": "deep", - "updated_at": "2026-06-07T10:29:02.184Z" - }, - "final-wave:f1": { - "task_key": "final-wave:f1", - "task_label": "F1", - "task_title": "Plan Compliance Audit — oracle", - "session_id": "ses_15e548982ffeAvvV8VYPLpxbtB", - "agent": "oracle", - "category": "", - "updated_at": "2026-06-07T10:44:49.621Z" - } - } -} \ No newline at end of file diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/decisions.md b/.sisyphus/notepads/session-1-bootstrap-phase0/decisions.md deleted file mode 100644 index 5dd2c49..0000000 --- a/.sisyphus/notepads/session-1-bootstrap-phase0/decisions.md +++ /dev/null @@ -1,16 +0,0 @@ -- Use a source-file shebang for the CLI entry instead of a tsup banner so the library build stays clean; if a banner is ever needed, split the CLI build from the library build. -- Resolve the package name to `src/index.ts` in Vitest via top-level `resolve.alias`; do not rely on `test.alias` for this path. -- Keep the source shebang in `src/cli/sdp.ts` and let tsup post-process the emitted CLI file back to one hashbang line after build, so the source stays executable-friendly without shipping a duplicated shebang. -- Let `examples/**` compile only through `tsconfig.examples.json`, with its own `paths` mapping and an explicit empty `exclude`, so the example can prove the public package alias without loosening the root program. -- Keep `src/index.ts` as a Phase 0 barrel only: export the ID parser/formatter helpers, `ref`, and the branded ID helpers from `src/ids.ts`—nothing model-specific yet. -- Use a tiny `vitest-test.mjs` wrapper so the package can accept `npm test -- --run ids` without double-injecting `--run` while preserving the plain `npm test` behavior. -- Keep the `#` suffix grammar narrow (`` only, no dotted subpaths) so the ID parser stays aligned with the concept docs and rejects malformed examples early. -- Model the descriptor/section layer as optional, shape-only TypeScript: literal arrays define the canonical values, and section containers stay fully optional so idea-stage fixtures typecheck without readiness-based promotion rules. -- Keep `SpecSections` as an optional interface of optional section payloads, with each payload using a permissive `Record` shape so the model stays shape-only and lint-clean. -- Extend `src/index.ts` into the Phase 0 builder barrel by exporting the new `spec`/`pack`/relation/anchor helpers alongside IDs and existing schema exports, so Task 4 adds the authored DSL surface without regressing already-present graph-schema typecheck consumers. -- Keep `anchorImplementation()` singular at the builder layer: the identity-only implementation anchor binds one target `SpecId` via `satisfies`, not an array, matching the Task 4/Task 9 contract and avoiding premature many-target semantics in Phase 0. -- Keep the graph schema as a pure, inert module at `src/graph/schema.ts`, and re-export it from the package barrel without adding extractor, writer, or reader behavior. -- Model the Task 6 validation seam as pure authored-layer contracts plus inert readiness-floor data: `AuthoredModel` is only in-memory collections, while graph-shaped `ready` checks stay represented as `deferredInSession1` clauses instead of executable enforcement. -- Keep the CLI stub isolated from the validator/runtime layer: manual `process.argv` parsing only, exact fixed help/error strings, and no filesystem or source-walking imports in `src/cli/sdp.ts`. - -- Keep validateAuthoredModel as a pre-graph aggregator over duplicate-id, dangling-reference, and readiness-floor findings, with individual finding families preserved even though the top-level report stays on the conformance surface for Session 1. diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/issues.md b/.sisyphus/notepads/session-1-bootstrap-phase0/issues.md deleted file mode 100644 index c1b4220..0000000 --- a/.sisyphus/notepads/session-1-bootstrap-phase0/issues.md +++ /dev/null @@ -1,2 +0,0 @@ - -- `lsp_diagnostics` can lag behind file rewrites on `src/index.ts`/`test/ids.test.ts`, reporting stale pre-edit syntax until the workspace refreshes; CLI checks (`typecheck`, `test`, `lint`, `build`) were the reliable source of truth here. diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/learnings.md b/.sisyphus/notepads/session-1-bootstrap-phase0/learnings.md deleted file mode 100644 index 272ef50..0000000 --- a/.sisyphus/notepads/session-1-bootstrap-phase0/learnings.md +++ /dev/null @@ -1,26 +0,0 @@ -- tsup: prefer one ESM-only library build with named entries, `dts: true`, and a real `#!/usr/bin/env node` shebang in `src/cli.ts`; tsup docs say hashbang entries become executable. -- Vitest: package-name aliases belong at top-level `resolve.alias`, not under `test.alias`; `test.alias` is for mock/non-existent modules. -- typescript-eslint: current strict flat-config stack is `strictTypeChecked` + `stylisticTypeChecked` + `parserOptions.projectService: true`. -- package.json: use `type: "module"`, `exports` for the library entry, and `bin` for the CLI; keep `files: ["dist"]` so only build output ships. -- Typed linting with `projectService: true` needs the root TS config files (`tsup.config.ts`, `vitest.config.ts`) included in `tsconfig.json`, or ESLint will refuse to analyze them. -- Prettier `--check .` on this repo needs a `.prettierignore` for the docs/plans/notepad surface so bootstrap verification stays focused on the toolchain files. -- When tsup outputs both a source shebang and a banner shebang, the emitted CLI needs a tiny post-build normalization step to collapse it back to one executable hashbang. -- `tsconfig.examples.json` must explicitly reset `exclude` to `[]` after extending the root config, otherwise the inherited `examples` exclusion leaves the example project empty and TS18003 fires. -- A bootstrap Vitest file can meaningfully prove the package surface by checking both `import "@libar-dev/software-delivery-protocol"` and the package.json `bin`/`exports` shape. -- `npm test -- --run ids` needs a small wrapper script when the package script already bakes in `--run`; otherwise Vitest sees `--run` twice and exits early. -- Named export tests for the public barrel are easiest against `../src/index.js` before the package is built; the alias-only bootstrap test can stay a namespace import. -- The stable-ID parser should accept any lowercase namespace shape and defer broader namespace policy; helper functions (`specId`, `packId`, `implAnchorId`, `testAnchorId`) enforce their own prefixes. -- The optional `#` suffix is a single segment, not another dotted path; treating it like the main path would accept malformed IDs such as `spec:orders.create-order#valid.cart`. -- Canonical descriptor literals should stay separate from display labels: `SpecKind` is the authored value set, while labels live in a dedicated lookup so `Scenario`/`NFR` remain display language only. -- For optional section bags, `Record` fits the open-ended payload while the exported container shape stays as an `interface`; that combination satisfies the lint rules without introducing index signatures. -- Bootstrap schema guards should import `../src/index.js` instead of the package name when they need new source exports; package-name imports can still resolve to stale dist declarations during `tsc --noEmit`. -- Readiness-floor coverage is easiest to keep stable when the floor data exposes clause IDs as inert metadata; tests can then assert the canonical rungs and Session 1 deferred graph-shaped `ready` clauses without coupling to validator logic that does not exist yet. -- The minimal `sdp` stub can stay fully import-free and deterministic: export a pure `runSdpCli(args)` helper, write directly to `process.stdout` / `process.stderr`, and gate auto-execution on the built `dist/cli/sdp.js` path so tests can import the module safely. - -- Validator TDD stayed stable when readiness-floor enforcement flattened the local idea→scoped→defined clauses for a stated rung while skipping and every ready clause; that keeps Session 1 checks authored-layer only without duplicating graph validation. - -- Validator TDD stayed stable when readiness-floor enforcement flattened the local idea-to-scoped-to-defined clauses for a stated rung while skipping the ready defined-floor placeholder and all Session 1 deferred ready clauses; that keeps the checks authored-layer only without duplicating graph validation. - -- When the authored example must keep concept-aligned `*.spec.ts` filenames under `examples/`, Vitest needs an explicit `test.include` such as `test/**/*.test.ts`; otherwise it will collect authored spec files as empty test suites and fail with `No test suite found`. -- On current TypeScript, `tsconfig.examples.json` needs `compilerOptions.ignoreDeprecations: "6.0"` alongside its package-alias `paths` mapping, or `tsc --noEmit -p tsconfig.examples.json` fails on the deprecated `baseUrl` setting before the example code is even checked. -- TypeScript 5.9 rejects `ignoreDeprecations: "6.0"` in `tsconfig.examples.json` with TS5103, so the examples config should stay compatible with the actually installed compiler version instead of pinning a forward-looking deprecation override. diff --git a/.sisyphus/notepads/session-1-bootstrap-phase0/problems.md b/.sisyphus/notepads/session-1-bootstrap-phase0/problems.md deleted file mode 100644 index e69de29..0000000 diff --git a/AGENTS.md b/AGENTS.md index 9c32a01..6b654d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,7 @@ Progressive disclosure — start at the top, follow the pointers down. | `docs/concept/ubiquitous-language.md` | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology. The **model exposition** was rehomed into `00`–`07` (2026-06-10 Fold-A; the glossary header maps the old `base §n` refs) | **first, always** | | `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/00`–`07` (+ README) | **the technical design and the model** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model (`02` — the primitive, descriptors, sections, relations), the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | -| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the MD-series MD-1…MD-9, the ratified R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | +| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the MD-series MD-1…MD-15, the ratified R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code | | `reviews/` | **archived session reviews** (implementation, founding-ideation, adversarial + prompts) — durable findings already folded into plans/DECISIONS; read for provenance | rarely | diff --git a/docs/concept/00-vision-scope-and-mvp-boundary.md b/docs/concept/00-vision-scope-and-mvp-boundary.md index 0947679..86ba89c 100644 --- a/docs/concept/00-vision-scope-and-mvp-boundary.md +++ b/docs/concept/00-vision-scope-and-mvp-boundary.md @@ -51,12 +51,12 @@ The MVP proves the founding principle — *one regenerable graph derived from th - **Typed `Spec` DSL** in TypeScript: `spec()`, `pack()`, sections, relations, the three descriptors (`kind` · `altitude` · `readiness`), enrichment-in-place, refinement into child specs. One primitive, stable IDs, no artifact migration. - **Generic source anchors** that bind any code location (class, function, route, module) to a spec ID + optional component. Framework-neutral — *how* the runtime is wired is an extractor detail, not a job. -- **The `ts-morph` one-graph extractor**: one canonical, regenerable graph from `/specs/**/*.spec.ts` + anchors + basic test discovery, with honest edge `claim`s. +- **The `ts-morph` one-graph extractor**: one canonical, regenerable graph from `/specs/**/*.sdp.ts` + anchors + basic test discovery, with honest edge `claim`s. - **Core conformance + honesty checks**: referential integrity (no dangling ID references), duplicate-ID detection, honest readiness (against the readiness floor), orphan detection, `verifies` linkage from tests to specs, and authoring-shape honesty (no hand-authored derived edges or delivery facts). CI fails on errors. - **One generated read-only view**: a derived, regenerable human-readable projection (spec tree + per-spec detail with readiness, relations, impact list, source links). - **Bidirectional spec↔test trace**: query "what verifies this spec?" and "what does this test cover?" from the graph. -**The MVP write-path is "edit TypeScript + git."** No patch loop, no codemod. You change a `.spec.ts` file or an anchor, re-run `sdp build`, and the view updates. CI rejects PRs that break links or state readiness the spec has not earned. +**The MVP write-path is "edit TypeScript + git."** No patch loop, no codemod. You change a `.sdp.ts` spec file or an anchor, re-run `sdp build`, and the view updates. CI rejects PRs that break links or state readiness the spec has not earned. **"Done" for the MVP:** you can write specs in TS, anchor implementing code and tests with stable IDs, run `sdp build`, get a graph that passes the conformance + honesty checks, and open a generated view that correctly shows intent, implementation links, verification status, and impact — and CI rejects the PR if any of it is broken or incomplete. Delete `generated/` and rebuild identically. diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index 47e4466..ed23027 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -82,8 +82,8 @@ Altitude is a clean 3-rung ladder. `epic` is the ceiling; above-epic framing (in ```ts type SpecReadiness = | "idea" // title + minimal intent; open questions allowed - | "scoped" // intent, terms, parent relation; rules or examples expected - | "defined" // rules and/or examples present; NFR targets measurable + | "scoped" // intent + a relation; the kind's natural evidence *present* (kind-conditional floor, `05` §3) + | "defined" // the kind's natural evidence *complete* (structured GWT, measurable targets, the written choice…) | "ready"; // implementation-ready: clears the readiness floor (`05`), stated by a human (typically out of a Design Review, `06`) — the floor is checked; the review is practice, not a recorded fact ``` @@ -128,7 +128,7 @@ Sections carry the detail. They are the **extension surface**: the system grows | Section | Carries | Notes | |---|---|---| | `intent` | actor, problem, outcome, value, risks, assumptions, open questions | `openQuestions` may be flagged `blocking` to prevent stating a readiness past `defined`. | -| `behavior` | examples (Given/When/Then), rules, flows | An example progresses from prose → structured steps → a verifier. | +| `behavior` | rules (prose), examples (prose or structured Given/When/Then), flows | **Content only — never refs** (the duality rule below). An example entry matures *in place*: prose → a structured `{ given, when, then }` entry → (promoted) a child `example` spec backed by a verifier. | | `constraints[]` | a `flavor` (quality / security / performance / compliance / operational / policy), a statement, an optional `target`, optional `measurableBy` | A `performance` constraint with a measurable `target` is an NFR. `target` must be machine-readable (`p95 < 300ms`, not "fast enough") to state `defined`+. | | `model` | domain terms and concepts (vocabulary only) | Used for pack-level coherence checks. `DomainConcept.kind` is an open, illustrative list, not a closed enum. | | `design` | components, ports, dependencies, decisions, tradeoffs | Referenced by ID; decision bodies are linked, not parsed for semantics. | @@ -136,12 +136,23 @@ Sections carry the detail. They are the **extension surface**: the system grows | `verification` | mode (manual / reviewed / contract / executable) + criteria | A verifying test *existing and enabled* is the derived `has-verifier` delivery fact (§2), not an authored field here. Pass/fail is **not** in the graph — it is CI's, operational. | | `ui` | references to component stories, design-tool nodes, visual baselines, accessibility status | **Aspirational.** Always links, never owns or renders. | +The `decision` section carries **no `status` field** (MD-11): a decision's adoption arc is the envelope's `readiness` (raised → explored → written → ratified), checked against the floor like any spec; replacement is the `supersedes` relation; a *rejected* path is not a truth-spec at all — it lives in the chosen decision's `alternatives` / `consequences`. One concept, one place. + +### The typing law — which sections have typed shapes (MD-11) + +**Every floor-bearing section is typed, as a closed shape.** A section is *floor-bearing* when a readiness-floor clause reads it as evidence (`05` §3): `intent`, `behavior`, `constraints`, `model`, `verification`, `decision`. Closed means no index signature — which is what closes the in-section honesty bypass (`behavior: { "has-verifier": true }`) at the type level, and gives authors (human and agent) autocomplete + shape guardrails, the highest-value adoption lever. The genuinely-unsettled surfaces — `design`, `ui` — stay **open bags** so they keep breathing (L9). The criterion outlives the list: when a floor clause starts reading a section, that section gets typed. + ### Section ⟷ kind — when a concern is inline vs. its own primitive -Three sections — `constraints`, `model`, `decision` — have a same-named `kind` twin, so the same concern can live two ways. **The rule** (stated here once, not scattered across the other docs): +Five section concerns have a same-named (or same-natured) `kind` twin, so the same concern can live two ways: `constraints` ⟷ `constraint`, `model` ⟷ `model`, `decision` ⟷ `decision`, and `behavior.rules` / `behavior.examples` ⟷ the `rule` / `example` kinds. **The rule** (stated here once, not scattered across the other docs): - Keep it **inline as a section** when it is **local detail** of its host `Spec`. -- **Promote it to a standalone `Spec`** of the matching `kind` (linked by `constrainedBy` / `decidedBy` / `modelRefs`) when it is **referenced by more than one `Spec`, or needs its own identity / lifecycle / review.** +- **Promote it to a standalone `Spec`** of the matching `kind` when it is **referenced by more than one `Spec`, or needs its own identity / lifecycle / review.** The promoted spec is linked by **relations** — `constrainedBy` / `decidedBy` / `modelRefs` for the truth-section twins; `refines` (+ `verifies` for verifying examples) from the promoted `rule` / `example` child to its parent. + +Two laws make the duality safe (ratified with the 2026-06-10 grill, DECISIONS MD-10): + +- **Sections carry content; relations carry linkage.** A section never holds a spec ref — `behavior.rules` and `behavior.examples` are prose / structured entries only. "Which examples does this spec have?" is a reverse-edge query over the children's `refines`/`verifies`, never an authored list. +- **Promotion is exclusive.** An entry lives inline **or** as a promoted child — never both. Promotion *moves* the content out; nothing is left behind to drift. (The readiness floor counts promoted children as evidence, `05` §3 — promotion never costs a spec its earned rung.) `modelRefs` on a `Pack` always points at standalone `kind:"model"` specs — shared vocabulary is never inlined twice. @@ -175,7 +186,8 @@ export const CreateOrder = spec({ altitude: "feature", readiness: "ready", intent: { actor: "customer", outcome: "turn a valid cart into an order", value: "customers can complete purchases" }, - behavior: { examples: [ref("spec:orders.create-order.valid-cart")] }, + // content only — the valid-cart example is a promoted child (it refines + verifies this spec); no refs here + behavior: { rules: ["only valid carts can become orders", "creating an order emits OrderCreated"] }, constraints: [ { flavor: "performance", statement: "order creation is fast enough for checkout", target: "p95 < 300ms" }, ], diff --git a/docs/concept/03-the-one-graph.md b/docs/concept/03-the-one-graph.md index 914024b..bcfb3da 100644 --- a/docs/concept/03-the-one-graph.md +++ b/docs/concept/03-the-one-graph.md @@ -22,7 +22,7 @@ Two pure steps: `graph = f(repo)` and `output = f(graph)`. The extractor is the ### What the extractor reads -- **Typed spec files** (`/specs/**/*.spec.ts`) — the declared layer: specs, packs, relations. +- **Typed spec files** (`/specs/**/*.sdp.ts`) — the declared layer: specs, packs, relations. - **Source-code anchors** — the anchored layer: an **anchor** binds a code location to a spec ID and minimal structural facts (component, satisfies, implements). Anchors carry *no* intent (see `04`). - **Structural facts** — the inferred layer: `ts-morph`-derived structure (which file defines which `impl`, basic test discovery linking `test:*` to the specs they `verify`). In the MVP this layer is kept minimal and advisory. @@ -32,7 +32,7 @@ The graph is **flat**: arrays of nodes and arrays of edges. Hierarchy and contai { "schemaVersion": "0.1.0", "nodes": [ - { "id": "spec:orders.create-order", "nodeType": "Primitive", "specKind": "behavior", "altitude": "feature", "readiness": "ready", "deliveryFacts": ["implemented"], "claim": "declared", "file": "specs/orders/create-order.spec.ts" }, + { "id": "spec:orders.create-order", "nodeType": "Primitive", "specKind": "behavior", "altitude": "feature", "readiness": "ready", "deliveryFacts": ["implemented"], "claim": "declared", "file": "specs/orders/create-order.sdp.ts" }, { "id": "impl:orders.create-order-use-case", "nodeType": "CodeNode", "claim": "anchored", "file": "src/orders/create-order.use-case.ts", "line": 12 } ], "edges": [ diff --git a/docs/concept/04-authoring-and-binding.md b/docs/concept/04-authoring-and-binding.md index 929ad1a..7be7125 100644 --- a/docs/concept/04-authoring-and-binding.md +++ b/docs/concept/04-authoring-and-binding.md @@ -8,10 +8,10 @@ Realises **P5** (statically extractable), **P6** (ID-linked), **P9/P10** (anchor ## 1. The TypeScript Spec DSL — canonical (CORE) -Specs are authored as typed TypeScript in `/specs/**/*.spec.ts`. The DSL is a thin set of helpers (`spec`, `pack`, relation builders) over the `Spec` shape from `02`. +Specs are authored as typed TypeScript in `/specs/**/*.sdp.ts` — the Protocol's own compound extension (MD-15; the `.stories.tsx` pattern), deliberately **not** `.spec.ts`, which every JS test runner's default glob would try to execute. The DSL is a thin set of helpers (`spec`, `pack`, relation builders) over the `Spec` shape from `02`. ```ts -import { spec, refines, dependsOn, verifies, ref } from "@libar-dev/software-delivery-protocol"; +import { spec, refines, dependsOn } from "@libar-dev/software-delivery-protocol"; export const CreateOrder = spec({ id: "spec:orders.create-order", @@ -26,8 +26,9 @@ export const CreateOrder = spec({ openQuestions: ["should stock reservation happen before or after order creation?"], }, behavior: { + // content only — never refs (02 §3): a promoted example is a child spec that refines/verifies this one rules: ["only valid carts can become orders", "creating an order emits OrderCreated"], - examples: [ref("spec:orders.create-order.valid-cart")], + examples: ["an expired payment card is declined before any order is created"], }, relations: [refines("spec:orders.order-management"), dependsOn("spec:payments.authorize-payment")], }); @@ -143,9 +144,9 @@ Modules that declare controls + an `expected()` model + `coverage()`, rendered a ``` /specs - checkout.pack.ts - orders/create-order.spec.ts - payments/authorize-payment.spec.ts + checkout.pack.sdp.ts + orders/create-order.sdp.ts + payments/authorize-payment.sdp.ts /src orders/ create-order.use-case.ts // anchored: impl:orders.create-order-use-case diff --git a/docs/concept/05-validation-and-honesty.md b/docs/concept/05-validation-and-honesty.md index 324908b..d519e40 100644 --- a/docs/concept/05-validation-and-honesty.md +++ b/docs/concept/05-validation-and-honesty.md @@ -27,7 +27,7 @@ An **error fails the build**; a **gap informs**. This is the one sense in which | Layer | Checks | Status | |---|---|---| -| Types | structural shape of `Spec` (all sections optional, branded IDs) | CORE | +| Types | structural shape of `Spec` (all sections optional, branded IDs; floor-bearing sections closed-typed — the typing law, `02` §3 / MD-11) | CORE | | Schema | the graph JSON validates against its schema | CORE | | Graph validators | cross-file conformance + honesty invariants (§2) | CORE | | Architecture rules | forbidden-dependency, layer boundaries, domain purity | ASPIRATIONAL | @@ -39,7 +39,9 @@ Types describe **shape**; validators decide **completeness** (P7). Completeness ## 2. The MVP graph validators (CORE) -These are the non-negotiable core. CI fails on any error. They split across the two families: +These are the non-negotiable core. CI fails on any error. They split across the two families. + +**They run over the one graph — there is exactly one validation path** (MD-14): source → extract (static reification, P5) → graph → checks; `sdp validate` is `sdp build` + checks. Validating any *evaluated* form (importing spec modules and checking the resulting objects) would check a phantom — a non-static expression evaluates to a value on import but is dropped by static reification, so the checks could pass a spec the graph doesn't actually hold. The Session-1 pre-graph `AuthoredModel` is a stand-in that retires into (at most) an extractor-internal shape when the extractor lands; it is never a second public validation seam. Authoring-time feedback is the type system's job (typed sections, `02` §3) plus the `sdp/spec-static` lint — not a parallel validator path. **Conformance checks:** @@ -69,18 +71,34 @@ Two cross-cutting honesty rules apply to all validators: A **readiness floor** is the **minimum structural requirement to *state* a readiness rung** — a floor to clear, **never a quota to fill or a score** (significance governs detail — no tier-filling; `01`, P4 corollary). The floors are the mechanism (Principle); the specific thresholds are config a team can override. -| Readiness | Floor (minimum structure to *state* the rung) | +The floor has two parts: **kind-blind structural clauses** (the same for every kind) and **one evidence clause that is kind-conditional** (MD-12) — `kind` is a true subtype, and the floor is where that changes what is required, in both directions (it can *relax* as well as add). + +| Readiness | Kind-blind structural clauses (every kind) | |---|---| | `idea` | id, title, kind, altitude; `intent.outcome` **or** a parent relation | -| `scoped` | `intent.outcome`; at least one relation; one of `behavior.rules` / `behavior.examples` / `constraints` | -| `defined` | rules and/or examples; any constraint has a machine-readable `target`; no *blocking* open questions | +| `scoped` | `intent.outcome`; at least one relation; **the kind's evidence is *present*** (table below) | +| `defined` | **the kind's evidence is *complete*** (table below); no *blocking* open questions (`intent.openQuestions`, MD-9) | | `ready` | the `defined` floor **and**: no blocking open questions; all relations resolve; every `dependsOn`/`refines` target is itself **≥ `defined`**; any anchors present *resolve* (so `implemented` is *derivable*) | -A small, **kind-aware** requirement rides on the base floor (full per-kind overlays are deferred): +**The per-kind evidence table.** Each kind names its **natural evidence**; `scoped` requires it *present* (prose acceptable), `defined` requires it *complete* where the kind defines a stronger form. There is no separate overlay mechanism — this table is the whole kind-aware story: + +| `kind` | `scoped` — evidence present | `defined` — evidence complete | +|---|---|---| +| `behavior` / `workflow` | rules / examples / flows / constraints — inline, **or promoted** (a refining `rule`/`example` child, or a `constrainedBy` target) | rules and/or examples (inline or promoted children); constraints alone no longer suffice | +| `rule` | its statement in `behavior.rules` | same — a rule's content *is* its statement | +| `example` | an examples entry (prose fine) | ≥ 1 **structured** `{ given, when, then }` entry | +| `constraint` | `constraints[]` non-empty | every entry has a machine-readable `target` | +| `model` | `model.terms` non-empty | same — a vocabulary either has terms or it doesn't | +| `decision` | `decision` section present (context / alternatives may precede the choice) | `decision.decision` — the chosen option — is written | +| `contract` *(interim)* | as `behavior` (examples read naturally as sample payloads) | as `behavior` — **named deferral:** when a dedicated contract section lands, the typing law (`02` §3) pulls it in and this row repoints to it | + +Three laws bound the table: + +- **Monotonic by construction.** Every `defined` cell implies its `scoped` cell — clearing the higher rung always clears the lower. (The pre-MD-12 floor failed this for `constraint`: its natural evidence stopped counting between `scoped` and `defined`.) +- **Promotion-neutral.** Promoted forms count wherever one exists (`02` §3) — de-composing a spec into children never costs it its earned rung. +- **Convergence is honest.** Where the rungs converge (`rule`, `model`), the floor refuses to become a quota ("more terms," "more rules" — the tier-filling P4 forbids); those rungs then differ only by the author's stated confidence plus the kind-blind clauses. -- `constraint` → a parseable, machine-readable `target` before `defined`+; -- `example` → structured `given` / `when` / `then` before `defined`+; -- `model` → term definitions present. +> **Representation note (MD-13).** In code, the floor *table* is the single source of truth: rows carry the clause id, description, and a named predicate; the evaluator is one generic loop and the clause-id type is derived from the table. The tables above and the data in `readiness-floor.ts` are intended to be reviewable as mirror images. **The `ready` floor is earned, not asserted** — and it is **not** a delivery fact. The floor may require that anchors *resolve* (so `implemented` is *derivable*); it **never** requires the spec to *be* `implemented`. Readiness is a *stated position* about the design; delivery facts are observations about the code (`02` §2). Higher floors degrade gracefully if their structural inputs are absent. diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index b596846..b46a257 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -173,6 +173,129 @@ typed-sections grill decision only shortens the predicate), so it is recorded no **Execution.** Stays `plans/02` Wave B (H2): read from `intent.openQuestions`, update the floor data's `authoredPaths`, flip the gated should-fail fixture active. +## 2026-06-10 — Session: the grill (decision resolution on the lean base) + +> The fresh `grill-with-docs` session prescribed by `plans/04` §5, agenda `plans/03`: the six genuinely-open +> decisions (plans' open-decision handles D1–D4, D7, D8 — not the legacy structural D-space below), resolved +> against the lean base, ratified inline. Base edits land in `02`/`04`/`05`; terms in the glossary; Wave B +> (`plans/02`) rewritten execution-ready at the end. + +### MD-10 — `behavior` sections carry content only; linkage lives in relations [ACCEPTED 2026-06-10] +**Decision.** `behavior.rules` and `behavior.examples` hold **content, never spec refs**: rules are prose; +an examples entry is prose **or** a structured `{ given, when, then }` object (the entry matures *in place* — +prose → structured GWT). The section ⟷ kind duality rule (`02` §3) extends to `rule`/`example`: **promotion is +exclusive** — promoting an entry to a standalone `rule`/`example` spec *moves* the content out, and the only +linkage is the child's `refines`/`verifies` relation (a reverse-edge query, never an authored ref list). +**Why / alternatives rejected.** The repo had the same two fields authored three incompatible ways (refs in +`create-order`; prose + redundant flat GWT in `valid-cart`; prose/refs mixed in the docs), and the ref form +double-linked the same fact (parent's `behavior.rules: [ref(rule)]` *and* the rule's `refines(parent)`) with +nothing naming the linkage of record — latent drift, against ambiguity-is-loud (L2). Content-only applies the +already-ratified duality rule instead of inventing shape. *Rejected:* a `prose | ref` union (formalizes the +duality, every consumer branches, double-linkage stays legal); refs-only (every two-line rule costs a full +spec — the heavy-authoring loop `plans/03` §2 warns about). +**Consequences.** GWT's canonical home is **nested in the examples entry** (the flat `behavior.given/when/then` +the floor overlay reads today is Session-1 drift — reconciled in Wave B). Section-embedded referential +integrity (plan 02 H4) **dissolves for `behavior`** — there are no refs to check. The readiness floor must +count promoted children as evidence so promotion never costs a spec its earned rung (MD-12/D7, `05` §3). +**Execution.** Wave B: type `BehaviorSection` to this shape (D1), re-author the example (drop ref lists from +`create-order`; drop `valid-cart`'s redundant prose entry; nest its GWT), repoint the `example` overlay clause. + +### MD-11 — The typing law: floor-bearing sections are closed-typed; `decision.status` rejected [ACCEPTED 2026-06-10] +**Decision.** Sections get typed by a **criterion, not a list**: *every floor-bearing section* — one a +readiness-floor clause reads as evidence — gets a **closed typed shape** (no index signature). Today that is +six: `intent`, `behavior` (per MD-10), `constraints[]` (array, per `02` §1 / plan 02 H3), `model`, +`verification`, and `decision` (pulled in because the kind-aware floor, MD-12, makes it the natural evidence +of `decision`-kind specs). `design` and `ui` stay open bags — genuinely-unsettled surfaces keep breathing +(L9). The criterion outlives the list: a section becomes typed when a floor clause starts reading it. +**Why / alternatives rejected.** Closed shapes are simultaneously the honesty fix (the in-section bypass +`behavior: { "has-verifier": true }` stops typechecking — the type level closes what the H8 gated fixture +locks) and the single highest-value adoption lever (autocomplete + shape guardrails against the +heavy-authoring loop, `plans/03` §2). *Rejected:* the carried "five" list (same intent, but a list goes stale +the moment the floor moves — D7 proved it immediately); all eight (commits architecture/UI surfaces that are +not settled); none + a runtime authoring-shape validator (no guardrails, the tracer bullet keeps proving +nothing about shape). +**`decision.status` is rejected vocabulary** (glossary ledger): the adoption arc is the envelope's +`readiness` (raised → explored → written → ratified, floor-checked); replacement is `supersedes`; a rejected +path is not a truth-spec — it lives in the chosen decision's `alternatives`/`consequences`. The example's +`decision.status: "accepted"` is Session-1 drift, cleaned in Wave B. +**Execution.** Wave B types the six in `sections.ts`, drops the `constraints` record-form handling +(validators stop string-probing), re-authors the example, and flips the gated H8 fixture +`invalid-hand-authored-delivery-fact-in-section` active. + +### MD-12 — The readiness floor's evidence clause is kind-conditional at both rungs; overlays dissolve [ACCEPTED 2026-06-10] +**Decision.** The floor splits into **kind-blind structural clauses** (envelope at `idea`; `intent.outcome` + +≥1 relation at `scoped`; no blocking open questions at `defined`+; resolution clauses at `ready`) plus **one +kind-conditional evidence clause**: `scoped` = the kind's natural evidence is *present* (prose acceptable), +`defined` = the evidence is *complete* where the kind defines a stronger form (structured GWT for `example`, +machine-readable `target` for `constraint`, the written choice for `decision`). The separate overlay +mechanism is dissolved into the per-kind evidence table (`05` §3). Three bounding laws: monotonic by +construction (every `defined` cell implies its `scoped` cell), promotion-neutral (promoted forms count — +MD-10), convergence is honest (`rule`/`model` rungs converge rather than become quotas). `contract` maps to +the behavior-family row as a **documented interim** with a named deferral: when a dedicated contract section +lands, the typing law (MD-11) pulls it in and the row repoints. +**Why / alternatives rejected.** The review's top finding (F1), extended one rung down by the grill: the +plan's defined-only sketch would NOT have de-padded the example, because floor clauses are cumulative and +`scoped`'s kind-blind `rules-examples-or-constraints` clause is what `order-model` (`model.terms` counts for +nothing) and `order-lifecycle` (the `decision` section counts for nothing) were padding for. The old floor +was also non-monotonic for `constraint` (its natural evidence stopped counting between `scoped` and +`defined`) — half-delivering the P8 "kind changes required detail" promise: at the floor, `kind` only ever +*added*. *Rejected:* defined-only (leaves the padding one rung lower); dropping evidence below `ready` +(guts the honest-readiness differentiator — `defined` would stop meaning anything checkable). +**Consequences.** The canonical example de-pads — the throwaway `behavior.rules` on `order-lifecycle`, +`order-model`, and `order-latency-constraint` are deleted, and `checkout-v1.test.ts` stops asserting padding +is green. An AI author no longer learns to sprinkle `behavior.rules` onto decision records. +**Execution.** Wave B rewrites `readiness-floor.ts` as the per-kind evidence table (with D3's +table-as-single-source collapse), de-pads the example, and re-authors the H8 fixture +`invalid-defined-constraint-without-target` (which itself currently pads). + +### MD-13 — The floor table in code is the single source of truth; the evaluator is generic [ACCEPTED 2026-06-10] +**Decision.** `readiness-floor.ts` becomes the one home of the floor: each row carries `{ clauseId, +description, predicate }` (a named predicate from a small library, with paths as arguments where presence +*is* the check); the per-kind evidence map (MD-12) lives beside the clauses; the clause-id union is +**derived** from the table via `typeof`, never re-enumerated; the evaluator is one generic loop. Evidence +predicates take `(spec, model)` — promotion-neutrality (MD-10/MD-12) needs the authored model to see +refining children. Decorative metadata is banned: `authoredPaths` either becomes a real predicate argument +or is deleted. +**Why / alternatives rejected.** 453 lines for three checks; clause ids enumerated in four places (add a +clause → edit 3–4 spots; miss the switch and it **silently skips** — a validator that silently stops firing +is exactly what `05` §5 exists to prevent); `authoredPaths` was verified decorative. Table-as-truth also +buys doc↔code fidelity: `05` §3's tables and the data table are reviewable as mirror images. *Rejected:* +evaluator-as-truth (the doc table would mirror a switch; the sync burden survives); minimal trim (the +silent-skip failure mode survives). +**Execution.** Wave B (plan 02 H5), together with the MD-12 floor rewrite — one change, since the table +being rewritten is the table being collapsed. + +### MD-14 — One validation path, through the one graph; `AuthoredModel` retires as a public seam [ACCEPTED 2026-06-10 · direction; executes Slice 1/3] +**Decision.** When the extractor lands, validators consume **the extractor's output** — one path: source → +extract (static reification, P5) → graph (in memory) → conformance + honesty checks; `sdp validate` = +`sdp build` + checks. `AuthoredModel` is demoted to (at most) an extractor-internal intermediate — never a +second public validation seam, never a "pre-graph lint" mode. Authoring-time feedback is owned by typed +sections + `tsc` (MD-11) and the `sdp/spec-static` lint, not by a parallel validator path. +**Why / alternatives rejected.** Sharper than the "two paths drift" worry: the two ingestion modes can +**disagree** — the Session-1 harness validates *imported, evaluated* spec objects, while the extractor +*statically reifies without executing* (`04` §1). A non-static expression evaluates to a value on import but +drops in reification, so import-path validation can pass a spec the graph doesn't hold — honesty checks +validating a phantom. The protocol's truth is what source *statically states*, not what it *evaluates to*. +This also makes the crippled-graph gap strategy's minimal slice ("extract → graph, no more," `plans/04` §2) +sufficient for self-validation. *Rejected:* a documented dual path (the disagreement above, institutionalized). +**Execution.** Slice 1 (extractor feeds the floor checks) / Slice 3 (the full gate); until then the Session-1 +harness stands in, honestly fenced (the `authored-model.ts` doc-comment already says so). + +### MD-15 — Authored Spec files carry the `.sdp.ts` extension [ACCEPTED 2026-06-10] +**Decision.** Spec files are `/specs/**/*.sdp.ts` (packs: `*.pack.sdp.ts`). The Protocol's own compound +extension — the `.stories.tsx` pattern: tool-branded, collision-free, tooling-scopeable. The model name +`Spec` is untouched (it was always settled); only the file serialization changes. +**Why / alternatives rejected.** `*.spec.ts` is *the* default test glob of the JS ecosystem (Vitest: +`**/*.{test,spec}.?(c|m)[jt]s(x)`; Jest/Mocha conventions match). An adopter on runner defaults gets their +runner executing Spec-primitive files — Vitest fails files with no test suites, so first contact with the +Protocol is a baffling CI failure. This repo's own narrowed `vitest.config.ts` was the dodge that proved the +landmine. Decided now, while the rename costs nine example files and zero adopters. *Rejected:* keep + +documented exclusion (pushes a config edit onto every adopter, and IDE test explorers keep mislisting spec +files); `.spec.sdp.ts` (collision-free but verbose self-restatement); directory-convention-only (identity +lives only in the path — future colocated specs would carry no marker). +**Execution.** Wave B renames the example files and this repo's docs/globs; the extractor (Slice 1) ships +reading `*.sdp.ts` from day one. + ## Structural-decision shorthand (D1–D6) [ACCEPTED · relocated here when the cleanup plan was retired, 2026-06-07] > These six labels come from the original structural-decisions pass. Their *content* is canonical in the diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index 15f2189..e83737d 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -62,7 +62,7 @@ The familiar delivery nouns are **named coordinates on the one primitive, never | Term | Definition | Aliases to avoid | |---|---|---| | **section** | the typed, optional detail-slice of a `Spec` — the **extension surface**; one concern each: `intent` · `behavior` · `constraints` · `model` · `design` · `decision` · `verification` · `ui` | "Facet" · "aspect" | -| **section ⟷ kind duality** | `constraints`/`model`/`decision` have a same-named `kind` twin: keep **inline** when local detail; **promote** to a standalone `Spec` when shared or needing its own identity/lifecycle/review | — | +| **section ⟷ kind duality** | `constraints`/`model`/`decision` — and `behavior.rules`/`behavior.examples` vs the `rule`/`example` kinds — have a `kind` twin: keep **inline** when local detail; **promote** to a standalone `Spec` when shared or needing its own identity/lifecycle/review. **Sections carry content, relations carry linkage** (never a ref inside a section); **promotion is exclusive** (inline XOR promoted — content moves out, MD-10) | — | ## The other authored things (no system truth — → `02` §4, `04` §2) @@ -177,8 +177,6 @@ artifact** — approval provenance is git-native, never an authored primitive). - **"epistemic boundary"** is a *working name* for the humans-assert-intent / machines-assert-structure division (`01`); a friendlier Studio-facing name is a minor open item. -- **`.spec.ts`** as the Spec file extension collides with the universal JS test-runner glob — an open - Representation-level decision (the grill agenda, `plans/03`); the model name `Spec` itself is settled. - **`ref()`** in the DSL is today a **spec-only** reference builder wearing a generic name (it rejects `pack:`/`doc:` targets) — a doc-note item on the Wave-B backlog (`plans/02`). @@ -194,10 +192,15 @@ artifact** — approval provenance is git-native, never an authored primitive). - **Rejected:** `provenance` (→ `claim`) · `marker` (→ `anchor`) · `Facet` (→ `section`) · `SpecPack` (→ `Pack`) · `abstraction` (→ `altitude`) · `candidate` (as a readiness rung — FSM imprint) · FSM / status-FSM · `model element` (as the primitive's name) · `genus`/`species` · `Scenario`/`capability`/`NFR` - as descriptors (labels / projections, not descriptors) · `exemplifies` (relation). + as descriptors (labels / projections, not descriptors) · `exemplifies` (relation) · `status` (on the + `decision` section — FSM imprint; adoption arc = `readiness`, replacement = `supersedes`, rejected paths = + `alternatives`/`consequences` — MD-11). - **Locked usage:** readiness is **"stated/asserted," never "claimed"** ("claim" is reserved for the `claim` taxonomy) · the meta-model defines the **contract**, **instances conform**; "govern"/"police" retired · checks are **conformance checks + honesty checks**. +- **Resolved (MD-15):** authored Spec files carry the **`.sdp.ts`** extension (never `.spec.ts`, which every + JS test-runner default glob executes); the model name `Spec` itself was always settled — only the file + serialization changed. - **Naming (resolved — MD-5):** product **Libar Software Delivery Protocol** (short form "the Protocol"); CLI **`sdp`**; npm **`@libar-dev/software-delivery-protocol`** (single package); repo `libar-dev/software-delivery-protocol`; namespaces `@libar-dev/` (OSS) vs `@libar-ai/` (commercial). diff --git a/jtbd-stories/01-capture-and-evolve-intent.md b/jtbd-stories/01-capture-and-evolve-intent.md index 9025aa8..fd08215 100644 --- a/jtbd-stories/01-capture-and-evolve-intent.md +++ b/jtbd-stories/01-capture-and-evolve-intent.md @@ -16,7 +16,7 @@ The job here is to get a thought into the system and let it grow without ever fo **Acceptance criteria:** 1. A spec can be created at `readiness: "idea"` with only `id`, `title`, `kind`, `altitude`, and either `intent.outcome` or a parent relation — nothing else is required. -2. The new spec lives in `/specs/**/*.spec.ts` as committed code, immediately part of the single source of truth — no status field, ticket, or external tool is needed for it to "exist." +2. The new spec lives in `/specs/**/*.sdp.ts` as committed code, immediately part of the single source of truth — no status field, ticket, or external tool is needed for it to "exist." 3. Open questions can be attached (`intent.openQuestions`) without resolving them and without blocking capture; only questions explicitly marked `blocking` constrain later promotion. 4. The spec is valid at its lowest readiness — the build never demands rules, anchors, or tests to accept an idea. 5. The spec source is static, side-effect-free data (a "JSON file that TypeScript happens to validate"), so the extractor reifies it deterministically. diff --git a/package.json b/package.json index 2a75d0a..369a841 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "name": "@libar-dev/software-delivery-protocol", "version": "0.0.0", + "description": "Libar Software Delivery Protocol — a typed, executable, self-validating meta-model of the software-delivery process: author Specs as code, derive one graph, check conformance and honesty.", + "license": "Apache-2.0", "type": "module", "files": [ "dist" diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index ba53947..a02373f 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -1,15 +1,15 @@ # Phase 0 Hardening — Fidelity & Simplification Pass (post-Session-1) -> **Status: EXECUTION · Wave A done; Wave B blocked on plan 03 decisions.** This is the **code -> hardening** half of the post-Session-1 review — fidelity corrections and net complexity reductions on -> the already-implemented Phase 0. The decisions these fixes' siblings depend on, the concept-base -> wording tightenings, and the roadmap acceptance criteria were **split out of this plan** into their -> proper homes (see "Where the rest went," below). +> **Status: EXECUTION · Wave A done · Wave B EXECUTION-READY (unblocked 2026-06-10 — the grill resolved +> all gates, MD-10…MD-15).** This is the **code hardening** half of the post-Session-1 review — fidelity +> corrections and net complexity reductions on the already-implemented Phase 0. The decisions these fixes' +> siblings depend on, the concept-base wording tightenings, and the roadmap acceptance criteria were +> **split out of this plan** into their proper homes (see "Where the rest went," below). > **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 (`eb6bf2a`) + Wave A > green (`npm run check`: typecheck ×2, lint, format, **47 tests + 2 todo**, build). -> **2026-06-10 folds:** R1/R2/R3 ratified & applied; the anchor shape (MD-8 `codeAnchor`) and the -> open-questions home (MD-9 `intent.openQuestions`) recorded **decided** — H2's direction and H10's shape -> are no longer open; Wave B's typed-section fixes remain blocked on D1/D2/D3 (the grill, `plans/03`). +> **2026-06-10 folds:** R1/R2/R3 ratified & applied; MD-8 (`codeAnchor`) and MD-9 (`intent.openQuestions`) +> recorded decided. **2026-06-10 grill:** D1/D2/D3/D4/D7/D8 resolved as **MD-10…MD-15** — §4 below is +> rewritten execution-ready against them; run it as the next code session. This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the extractor can safely build on."* It stays inside the Slice 0 boundary — **no extractor, no graph @@ -56,9 +56,9 @@ they now live where each gets the right rigor: ## §2 — The root tension (drives the Wave-B fidelity fixes) `src/model/sections.ts` types every section as `Record`; the base (**L9**, `02` §3) -intends **typed-but-optional** sections. This is the linchpin — its resolution (**D1**) is owned by -**plan 03 §2–§3**, where the full framing and the authoring-ergonomics rationale live. Wave B cannot -execute its field-shape fixes until D1/D2 land there. +intends **typed-but-optional** sections. This was the linchpin — **resolved 2026-06-10**: MD-11 (the typing +law — floor-bearing sections are closed-typed; six today) on MD-10's content-only `behavior` shape. §4/B1 +carries the locked field shapes. --- @@ -117,66 +117,72 @@ Quick, contract-shrinking, reversible wins. All landed and verified; `npm run ch --- -## §4 — Wave B (blocked on plan 03 decisions) — fidelity fixes, DECISION-GATED - -Execution-ready specs, held until the named decision lands in -`plans/03-decision-resolution-and-base-reconciliation.md`. **Sequence after D1/D2/D3.** - -### 🟠 H2 — Honesty check points at the wrong section (open questions) · direction **decided** (MD-9); execution-ready independent of D1 -- **Finding.** `validators.ts` (`hasNoBlockingOpenQuestions`) and `readiness-floor.ts` read - `spec.design.openQuestions` / `spec.decision.openQuestions`. The canonical home is - **`intent.openQuestions`** (`02` §3; the `04` worked example puts it under `intent`). -- **Impact.** A doc-following author who flags a `blocking` question in `intent.openQuestions` can still - state `defined`/`ready` and the honesty check **won't fire** — the marquee differentiator aimed at the - wrong target. (Plan↔concept drift introduced by the Session-1 pre-plan; the implementation faithfully - followed it.) -- **Change.** Read open questions from `intent.openQuestions`; update the floor data `authoredPaths`. A - typed `IntentSection` (D1) shrinks the predicate to a few lines. -- **Files.** `validators.ts`, `readiness-floor.ts`; `test/readiness.test.ts` / `test/validators.test.ts`. -- **Verify.** Flip the gated H8 fixture `invalid-ready-with-blocking-question` to active: a spec with - `intent.openQuestions:[{blocking:true}]` stating `defined` **fails**; with `blocking:false`/absent - **passes**. - -### 🟠 H3 — `constraints` must be an array, not a single object · gated on **D1** -- **Finding.** `sections.ts` types `constraints?: ConstraintsSection` (one `Record`); `02` §1 specifies - `constraints?: ConstraintSection[]`. A spec can be bounded by several NFRs; a single object can't - express that. `validators.ts` already defends against **both** array and record forms — a tell the - shape was unsettled. -- **Change.** `constraints?: ConstraintSection[]` (typed per D1: `{ flavor?, statement, target?, - measurableBy? }`). Update `order-latency-constraint.spec.ts` to author an array. **Simplify** the - validator: drop the dual array/record handling — one shape only. -- **Files.** `sections.ts`, `order-latency-constraint.spec.ts`, `validators.ts`. -- **Verify.** `constraint` floor test still passes; validator no longer branches on `Array.isArray`. - -### 🟡 H5 — Trim the readiness-floor validator (complexity masking a simple design) · gated on **D3** -- **Finding (verified).** `validators.ts` is 453 lines for three checks. Clause ids are enumerated in - **four** places — `readinessFloors` data, the `ReadinessClauseId` union, the - `toSupportedReadinessClauseId` switch, and the `evaluateClause` switch (add a clause → edit 3–4 spots; - miss one and it **silently skips**). `authoredPaths` is **decorative** — referenced only by its - declaration + tests, never by the evaluator, so the "which field does this clause check" fact lives in - two places, free to drift. -- **Change.** Per **D3** (recommendation: table-as-single-source-of-truth) — the evaluator reads - `authoredPaths` + a few generic predicates, killing the 4× enumeration. Safe once sections are typed. -- **Verify.** Same floor test outcomes; clause-id list defined exactly once; no decorative metadata. - -### 🟠 H4 — Referential integrity is blind to section-embedded refs · gated on **D1 + D2**; likely Slice 1 -- **Finding.** `validateDanglingReferences` checks relations, pack members, `modelRefs`, anchors — but - not refs inside sections. Yet `create-order.spec.ts` puts real refs there - (`behavior.rules:[ref(...)]`, `behavior.examples:[ref(...)]`). A typo there escapes the check; `05` - §2.1 wants *every* referenced ID to resolve. -- **Why deferred-ish.** The proper fix belongs with the extractor, which must know *structurally* where - refs can live — impossible while sections are `unknown`. Typing `behavior` (D1) + resolving the - `behavior.rules` shape (**D2**) makes both the check and the extractor tractable. -- **Change (when scheduled).** Extend referential integrity to typed ref-bearing section fields; align - the example to the resolved D2. - -### 🟠 H10 — The example under-proves the generic-anchor claim · shape **decided** (MD-8 `codeAnchor`); pairs with Slice 2 -- **Finding.** *Generic* anchors should bind **any** code location, but the example has only `impl` + - `test` anchors; the docs' own example (`04` §5) includes a route anchor `api:orders.post`. The tracer - bullet never exercises the genericity claim — the strongest proof is binding a **non-class location** - (a route/endpoint). -- **Change (when scheduled).** Add an `api`/route anchor to `examples/checkout-v1` under the **MD-8 - `codeAnchor`** shape; it should extract to a `satisfies` edge with `claim:"anchored"` like any other. +## §4 — Wave B — ✅ UNBLOCKED 2026-06-10 (the grill resolved every gate: MD-10…MD-15) — EXECUTION-READY + +One execution session, four steps in order; `npm run check` green after each. The decision pointers are +the contract — when a shape question comes up mid-execution, the MD entry wins, then the base (`02`/`05`). + +### B1 — Type the six floor-bearing sections (MD-11 + MD-10; absorbs old H3) +- **`sections.ts`** — closed shapes (no index signature); fields optional per P7 (types describe shape, + validators decide completeness): + - `IntentSection { actor?; problem?; outcome?; value?; risks?: string[]; assumptions?: string[]; + openQuestions?: (string | { question: string; blocking?: boolean })[] }` + - `BehaviorSection { rules?: string[]; examples?: (string | { given: string[]; when: string[]; + then: string[] })[]; flows?: string[] }` — **content only, never refs** (MD-10) + - `constraints?: ConstraintSection[]` with `ConstraintSection { flavor?; statement: string; target?; + measurableBy? }` — the array shape (old H3); **drop the validator's dual array/record handling** + - `ModelSection { terms?: Record }` (richer concepts stay deferred) + - `VerificationSection { mode?: "manual" | "reviewed" | "contract" | "executable"; criteria?: string[] }` + - `DecisionSection { context?: string; decision?: string; rationale?: string[]; alternatives?: string[]; + consequences?: string[] }` — **no `status`** (MD-11; rejected vocabulary) + - `design` / `ui` stay open bags (`Record`). +- **Verify:** the in-section honesty bypass `behavior: { "has-verifier": true }` now **fails to typecheck** + — so the gated H8 fixture `invalid-hand-authored-delivery-fact-in-section` lands as a `@ts-expect-error` + compile-time fixture beside the H9 set (not a runtime fixture); `order-latency-constraint` authors a + constraints **array**. + +### B2 — Rewrite the floor as the kind-conditional table (MD-12 + MD-13 + MD-9; absorbs old H2 + H5) +- **`readiness-floor.ts` becomes THE table**, mirroring `05` §3 row-for-row: kind-blind structural clauses + + the per-kind evidence map (`scoped` = evidence *present*, `defined` = evidence *complete*). Rows carry + `{ id, description, predicate }` — named predicates with a `(spec, model)` signature (promotion-neutrality + needs the model: behavior-family evidence counts refining `rule`/`example` children and `constrainedBy` + targets). The clause-id union is **derived** (`typeof`); delete the overlay mechanism, the hand-written + `ReadinessClauseId` union, `toSupportedReadinessClauseId`, and decorative `authoredPaths`. +- **Old H2 lands inside this step** (MD-9): blocking open questions read from `intent.openQuestions` — the + typed `IntentSection` makes the predicate a few lines. Flip the gated H8 fixture + `invalid-ready-with-blocking-question` active: `intent.openQuestions: [{ blocking: true }]` stating + `defined` **fails**; `blocking: false`/absent **passes**. +- The `example`-kind evidence reads **structured `behavior.examples` entries** (MD-10), not the flat + `behavior.given/when/then` the Session-1 overlay probed. +- **Verify:** same outcomes for the non-padding fixtures; clause list defined exactly once; `model`- and + `decision`-kind fixtures reach `scoped`/`defined` on natural content alone. + +### B3 — De-pad and re-author the example (MD-10/11/12 — the visible quality signal) +- **Delete the throwaway `behavior.rules`** from `order-lifecycle`, `order-model`, + `order-latency-constraint` — each now clears the floor on its natural evidence (`decision` section · + `model.terms` · `constraints.target`). +- **`create-order`**: drop both ref lists from `behavior` (the rules/examples children already + `refines`/`verifies` it — promotion-neutral evidence); keep inline prose only if it states something no + child does. +- **`create-order-valid-cart` / `-invalid-cart`**: drop the redundant prose `examples` entry; author the + GWT as one **structured `examples` entry** (nested, per MD-10). +- **`order-lifecycle`**: drop `decision.status: "accepted"` (MD-11 — the adoption arc is `readiness`). +- **Re-author the H8 fixture `invalid-defined-constraint-without-target`** (it currently pads with + `behavior.rules` itself) and re-assert `checkout-v1.test.ts` green on the de-padded example. + +### B4 — The `.sdp.ts` rename (MD-15) +- `examples/checkout-v1/specs/**/*.spec.ts` → `*.sdp.ts`; `checkout.pack.ts` → `checkout.pack.sdp.ts`. +- Update `tsconfig.examples.json` includes and any glob that names `.spec.ts`; confirm vitest still sees + only `test/**/*.test.ts` (the config narrowing stays, but is no longer load-bearing for the collision). + +### Dissolved / re-homed by the grill +- **H4 (section-embedded ref integrity) is dissolved for `behavior`** (MD-10): refs cannot exist in + sections, so there is nothing to check — the cheapest validator is the one a model rule makes + unnecessary. What remains of H4 is only **F4** (`modelRefs` targets must be `kind:"model"`), which rides + the Slice-1/3 graph validators under the one-path rule (MD-14). +- **H10 unchanged**: the api/route anchor rides Slice-2 anchor extraction under MD-8 `codeAnchor`. +- **`AuthoredModel`** needs no Wave-B work: it stays the honestly-fenced Session-1 stand-in until the + extractor lands, then retires per MD-14 (Slice 1/3 — not this wave). ### Carried review backlog (post-split adversarial review · small, ride Wave B / Slice 1) @@ -205,19 +211,24 @@ None block the split; each is small, decision-free, or doc-level. The `ts-morph` extractor · `graph.json` / graph emission · the graph-level validator gate · `--check-clean` · reader / views / Design Review · architecture rules · custom team rules · `--lenient` · derived-readiness banner · runtime `observed` path · MCP surface · self-hosting the Protocol's own -repo. Full typing of `design`/`decision`/`ui` sections stays deferred per D1. +repo. Typing of `design`/`ui` stays deferred per the typing law (MD-11 — they become typed when a floor +clause reads them); a dedicated `contract` section is a named deferral (MD-12). ## §6 — Sequencing 1. **Wave A** — ✅ done (H1, H6, H7, H8-active, H9). 2. **Pre-grill folds (2026-06-10)** — ✅ done: R1/R2/R3 ratified & applied; the anchor shape (MD-8) and the open-questions home (MD-9) recorded decided; `plans/03` slimmed to the six open decisions. -3. **Resolve decisions** — the fresh grilling session (`plans/03`): D2 → D1 → D3, plus D4 (direction), - D7 (kind-aware floor), D8 (file extension). -4. **Wave B** — with typed sections in place: H3, then H2 (+ flip its gated H8 fixture), then H5/D3, then - H4; H10 with Slice-2 anchor extraction. - -**Done gate for Wave B:** the honesty bypass (`behavior: { "has-verifier": true }`) is rejected for -typed sections **and** locked by the (now-active) H8 fixture; open questions read from -`intent.openQuestions`; `constraints` is an array; the floor clause-id list is defined exactly once; the -example still contains only static literals; `npm run check` green. +3. **Resolve decisions** — ✅ done (2026-06-10 grill): all six resolved and recorded as **MD-10…MD-15**; + the base reconciled inline; H7's leftovers finalized (`.sisyphus/` untracked; `package.json` + description + Apache-2.0). +4. **Wave B** — ← **NEXT** (the next code session): B1 (typed sections) → B2 (the floor table) → B3 + (de-pad + re-author the example) → B4 (the `.sdp.ts` rename); H10 rides Slice-2 anchor extraction. + +**Done gate for Wave B:** the in-section honesty bypass (`behavior: { "has-verifier": true }`) **fails to +typecheck**, locked by a `@ts-expect-error` fixture; blocking open questions read from +`intent.openQuestions` with the H8 fixture active; `constraints` is a typed array (no dual-shape +validator); the floor is one kind-conditional table mirroring `05` §3, clause ids defined exactly once, no +decorative metadata; the example is **de-padded** (no `behavior.rules` on `decision`/`model`/`constraint` +specs, no section refs, no `decision.status`, GWT nested) and every spec file is `*.sdp.ts`; the example +still contains only static literals; `npm run check` green. diff --git a/plans/03-decision-resolution-and-base-reconciliation.md b/plans/03-decision-resolution-and-base-reconciliation.md index 156d242..f98b383 100644 --- a/plans/03-decision-resolution-and-base-reconciliation.md +++ b/plans/03-decision-resolution-and-base-reconciliation.md @@ -1,11 +1,19 @@ # The grilling agenda — Decision Resolution & Base Reconciliation -> **Status: PLAN-ONLY · the agenda for a FRESH `grill-with-docs` session.** This document holds the **six -> genuinely-open decisions** the post-Session-1 reviews surfaced, framed for resolution **against the base** -> (terms: `docs/concept/ubiquitous-language.md`, the lean glossary; model: `docs/concept/00`–`07`), in -> dependency order. It deliberately does **not** pre-resolve them — that is the grilling session's job. -> **Date:** 2026-06-07 · **slimmed 2026-06-10 (pre-grill folds)** · **Branch:** `feature/mvp-init` · -> **Repo state:** Session 1 + hardening Wave A + the Fold-A/Fold-B folds + the `reviews/` archive, all green. +> **Status: ✅ EXECUTED 2026-06-10 — the grill ran; all six decisions resolved and ratified (MD-10…MD-15).** +> The base was reconciled inline (`02`/`03`/`04`/`05` + the glossary), and `plans/02` Wave B was rewritten +> execution-ready. This document is kept as the agenda-of-record; the resolutions live in +> `docs/concept/DECISIONS.md`. **Date:** 2026-06-07 · slimmed 2026-06-10 (pre-grill folds) · **grilled +> 2026-06-10** · **Branch:** `feature/mvp-init`. +> +> | Handle | Resolution | Recorded | +> |---|---|---| +> | **D2** | `behavior.rules`/`examples` carry **content only** (prose / structured GWT, maturing in place); linkage lives in relations; promotion is exclusive | **MD-10** | +> | **D1** | **The typing law**: every floor-bearing section is closed-typed — six today (`intent`, `behavior`, `constraints[]`, `model`, `verification`, `decision`); `design`/`ui` stay open bags; `decision.status` rejected | **MD-11** | +> | **D7** | The floor's evidence clause is **kind-conditional at both `scoped` and `defined`** (present → complete); the overlay mechanism dissolves into the per-kind table; `contract` interim = behavior-family row | **MD-12** | +> | **D3** | The floor **table is the single source of truth**: named predicates `(spec, model)`, derived clause-id union, one generic evaluator, no decorative metadata | **MD-13** | +> | **D4** | **One validation path** through the one graph (extract → graph → checks); `AuthoredModel` retires as a public seam (direction now; executes Slice 1/3) | **MD-14** | +> | **D8** | Spec files carry the **`.sdp.ts`** extension (collision-free with JS test-runner globs; the `.stories.tsx` pattern) | **MD-15** | > > **Folded out — do not re-litigate (the former §3a/§4).** The resolvable-now items left this agenda in the > 2026-06-10 fold session: **R1/R2/R3** ratified and applied to the base, and two items recorded **ACCEPTED** @@ -162,11 +170,18 @@ Once D1–D3 land and the base is reconciled, plan 02's **Wave B** becomes execu unlocks, and the two gated H8 fixtures flip from `it.todo` to active. H10 (the api/route anchor in the example) executes with Slice-2 anchor extraction under the MD-8 `codeAnchor` shape. -## §5 — Definition of done for this session - -- **D1, D2, D3, D4, D7, D8 resolved** and recorded in `DECISIONS.md` (rationale + what each unblocks). -- **The core model and floor reconciled** to the resolved section shapes (`02` §1/§3) and the kind-aware - `defined` floor (`05` §3, per D7); any term changes land in the glossary. -- **Plan 02 Wave B rewritten execution-ready** with the now-locked field shapes, including the example - de-padding that D7 unlocks. -- No code changed in this session (it is PLAN-ONLY); the code follows in the Wave-B execution session. +## §5 — Definition of done for this session — ✅ ALL MET 2026-06-10 + +- ✅ **D1, D2, D3, D4, D7, D8 resolved** and recorded in `DECISIONS.md` as **MD-10…MD-15** (rationale + what + each unblocks). The grill went two steps *past* the agenda where the evidence forced it: D7 covers + **`scoped` too** (the kind-blind scoped clause was what the padding actually cleared), and D1 became a + **criterion** ("floor-bearing ⟹ typed") rather than a list — which pulled `decision` into the typed set + and rejected `decision.status` as FSM vocabulary. +- ✅ **The core model and floor reconciled**: `02` §2/§3 (duality laws, typing law, readiness comments, + worked examples), `05` §2/§3 (one validation path; the two-part floor + per-kind evidence table), + `04` §1/§5 + `00`/`03`/`jtbd-01` (content-only DSL example, `.sdp.ts`); term changes in the glossary + (duality entry, `status` rejection, `.sdp.ts` resolution). +- ✅ **Plan 02 Wave B rewritten execution-ready** (locked shapes, de-padding, the `.sdp.ts` rename, the H4 + dissolution). +- ✅ No `src/`/`test/` code changed (PLAN-ONLY held); the only non-doc touches were the two H7 leftovers the + session was asked to finalize (`.sisyphus/` untracked; `package.json` description + Apache-2.0). diff --git a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md index 4ca6b60..2d46837 100644 --- a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md +++ b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md @@ -1,13 +1,14 @@ # Session 4 — Formalization: gap strategy + pre-grill folds (re-entry context) -> **Status: PLAN-ONLY · §3 EXECUTED 2026-06-10 (Fold-A · Fold-B · archive) · §2 still the parked -> direction.** This document exists to bootstrap context fast after a break — it is the formalized residue -> of a long design arc that hit the ceiling of what a chat thread can resolve ("no substrate to grow the -> decision → formalize"). Read §0 first; the rest is reference. -> **Date:** 2026-06-08 · folds executed 2026-06-10 · **Branch:** `feature/mvp-init` · **Repo state:** -> green; the pre-grill folds are committed (UL → lean glossary + rehome; R1/R2/R3 applied; MD-8/MD-9 -> recorded; `plans/03` slimmed to six; reviews archived in `reviews/`). **Next: the grill** (§5 kickoff -> prompt; agenda `plans/03`). +> **Status: PLAN-ONLY · §3 EXECUTED 2026-06-10 (Fold-A · Fold-B · archive) · §4 step 4 (the grill) +> EXECUTED 2026-06-10 — all six decisions resolved, MD-10…MD-15 · §2 still the parked direction.** This +> document exists to bootstrap context fast after a break — it is the formalized residue of a long design +> arc that hit the ceiling of what a chat thread can resolve ("no substrate to grow the decision → +> formalize"). Read §0 first; the rest is reference. +> **Date:** 2026-06-08 · folds executed 2026-06-10 · grill executed 2026-06-10 · **Branch:** +> `feature/mvp-init` · **Repo state:** green; folds + grill landed (lean glossary; R1/R2/R3 + MD-8…MD-15; +> `plans/02` Wave B execution-ready). **Next: Wave B execution** (`plans/02` §4), then — gated on a +> minimal engine — the crippled graph (§2). --- @@ -27,8 +28,9 @@ done). No extractor, no graph, no engine yet. the **crippled graph**. **What's next:** ~~two scoped **fresh** fold sessions (§3)~~ **the folds and the archive are DONE -(2026-06-10, one fresh fold session)** → now the **grill** on the lean base + the six-item agenda -(`plans/03`). The grill runs cold, in a fresh session; its kickoff prompt is in §5. +(2026-06-10)** → ~~the grill~~ **the grill is DONE (2026-06-10): all six decisions resolved as +MD-10…MD-15, base reconciled, `plans/02` Wave B execution-ready** → now **Wave B execution** +(`plans/02` §4), then Slice 1. **The one decision still genuinely open and yours:** how far the crippled graph goes, and exactly what its two instances are (§2). Parked deliberately — it needs substrate to grow, not more chat. @@ -159,10 +161,12 @@ Wave B execution-ready. 2. ~~**Fold-B** (resolvable-now sweep)~~ — ✅ DONE 2026-06-10: `04` reconciled (R3); MD-8/MD-9 recorded; `plans/03` down to six open decisions. 3. ~~**(optional) archive**~~ — ✅ DONE 2026-06-10: `reviews/` tracked. -4. **Grill** — fresh `grill-with-docs` on `plans/03`. ← **NEXT.** *Done when:* the six decisions resolved + - recorded; base reconciled; `plans/02` Wave B execution-ready. -5. **Then** Wave B execution, and — gated on a minimal engine — the **crippled graph** (§2) graduates - from direction to design. +4. ~~**Grill**~~ — ✅ DONE 2026-06-10: all six resolved + recorded (**MD-10…MD-15**); base reconciled + (`02`/`03`/`04`/`05` + glossary + `00`/`jtbd-01` for the `.sdp.ts` rename); `plans/02` Wave B rewritten + execution-ready; H7's optional leftovers finalized. +5. **Then** Wave B execution ← **NEXT**, and — gated on a minimal engine — the **crippled graph** (§2) + graduates from direction to design. (MD-14's one-path rule makes the minimal engine slice — extract → + graph — sufficient for self-validation, exactly what §2 needs.) > Fold-A precedes the grill for a real reason, not tidiness: the grill *edits the base* (typed sections → > §2/§3; kind-aware floor → §6). If the model has already moved into `00`–`07`, the grill knows its edits @@ -195,11 +199,13 @@ Wave B execution-ready. > (`intent.openQuestions`; note execution stays Wave B in `plans/02`). (3) Slim `plans/03` to the six > genuinely-open decisions and collapse its "resolvable-now" (§3a) to a one-line pointer. -### Grill kickoff (the folds are done — this is the live prompt) +### Grill kickoff — executed ✅ 2026-06-10 (kept for the record) > `grill-with-docs` on `plans/03` — six open decisions (D1 typed-sections · D2 prose-vs-ref incl. > `examples` · D3 floor-validator collapse · D4 AuthoredModel-vs-graph · D7 kind-aware `defined` floor · > D8 `.spec.ts` collision), against the now-lean base (terms: the glossary; model: `00`–`07`). Ratify > inline; record in `DECISIONS.md`; rewrite `plans/02` Wave B execution-ready. +> *(Outcome: MD-10…MD-15; resolution table in `plans/03`'s header. The live next prompt is Wave B — +> `plans/02` §4.)* --- @@ -207,16 +213,16 @@ Wave B execution-ready. The antidote to code-noise: every handle, decoded. Two separate code-spaces — don't conflate them. -**Open decisions (the grill agenda after Fold-B):** +**Open decisions — ✅ all RESOLVED in the 2026-06-10 grill (resolution table: `plans/03` header; rationale: `DECISIONS.md`):** -| Handle | Meaning | Home | +| Handle | Meaning | Resolved as | |---|---|---| -| **D1** | How much to *type* sections now (lean: type the five floor/extractor-bearing ones — intent, behavior, constraints, model, verification; leave design/decision/ui open). The linchpin. | grill | -| **D2** | The prose-vs-ref-list duality, across **both** `behavior.rules` and `behavior.examples`. Gates typing `behavior`. | grill | -| **D3** | Collapse the readiness-floor validator (clause-ids enumerated 4×; `authoredPaths` is decorative). Lean: table = single source of truth. | grill | -| **D4** | The `AuthoredModel` seam vs. the one graph — pre-graph lint that retires, or stays a documented lint? (Interacts with the crippled graph, §2.) | grill | -| **D7** | Make the `defined` floor **kind-aware** — today it forces behavior-rules onto decision/model/constraint specs, so the example *pads*. The review's top finding; shrinks the contract; de-pads the example. | grill | -| **D8** | The `.spec.ts` file-extension collides with the JS test-runner glob — adoption landmine. Keep-with-exclusion vs. a collision-free extension. Representation-level. | grill | +| **D1** | How much to *type* sections now. The linchpin. | **MD-11** — the typing law: floor-bearing ⟹ closed-typed (six, incl. `decision`; `status` rejected) | +| **D2** | The prose-vs-ref-list duality, across **both** `behavior.rules` and `behavior.examples`. | **MD-10** — content only; relations carry linkage; promotion exclusive; GWT nests in entries | +| **D3** | Collapse the readiness-floor validator (clause-ids 4×; decorative `authoredPaths`). | **MD-13** — table = single source of truth; named predicates `(spec, model)`; derived clause-id union | +| **D4** | The `AuthoredModel` seam vs. the one graph. (Interacts with the crippled graph, §2.) | **MD-14** — one validation path via the graph; `AuthoredModel` retires as a public seam (exec Slice 1/3) | +| **D7** | Make the floor **kind-aware** — the example *pads* to clear kind-blind clauses. | **MD-12** — evidence kind-conditional at **both** `scoped` and `defined`; overlays dissolve; example de-pads | +| **D8** | The `.spec.ts` extension collides with the JS test-runner glob. | **MD-15** — `.sdp.ts` (the `.stories.tsx` pattern) | **Resolvable-now (folded out — all ✅ done 2026-06-10):** From 2d44b0ffe28c156fed83068b1bbf06f3ce89b71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 04:31:14 +0200 Subject: [PATCH 15/29] Consistency + consolidation pass after the fold/grill sessions Consistency fixes: - plans/02 H7: leftovers marked finalized (were done in the grill, still listed open) - 02 $1/$3: model section aligned to the locked B1 shape (ModelSection, terms-only, richer concepts a named deferral) - was DomainModelSection/DomainConcept - 04 $2: anchor snippet now points at the ratified MD-8 codeAnchor shape (Slice 2) - AGENTS.md: status + 'where we are now' updated (Wave B next, then Slice 1) Consolidation (no information lost - rationale lives in DECISIONS.md MD entries, provenance in git history and reviews/): - plans/01 -> executed stub keeping the two setup rationales (paths alias, examples/ home) - plans/02 -> Wave A compressed to one-liners (handles + fixture inventory kept), header and sequencing de-dated; $4 Wave B contract untouched - plans/03 -> agenda-of-record: resolution table + method + root tension ($2 kept as the MD-10/MD-11 citation target); per-decision detail folded into its MD entries - plans/04 -> re-entry + frame + crippled-graph direction kept; executed fold/grill scaffolding and prompts stubbed; $6 decoder slimmed to the F-space map --- AGENTS.md | 16 +- docs/concept/02-core-model.md | 4 +- docs/concept/04-authoring-and-binding.md | 2 + plans/01-session-1-bootstrap-phase0.md | 210 ++--------------- plans/02-phase0-hardening.md | 124 +++------- ...sion-resolution-and-base-reconciliation.md | 200 +++------------- ...zation-gap-strategy-and-pre-grill-folds.md | 219 ++++-------------- 7 files changed, 147 insertions(+), 628 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6b654d5..dd4c796 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,9 +9,10 @@ This repo is the **concept made right before the engine exists**. The concept sy 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. -> **Status:** concept ratified and locked; the repo is now in its **first implementation sessions**, starting with -> **Phase 0 — the protocol as typed code**. The live per-session build plan is in **`plans/`**; the slice roadmap is -> **`docs/concept/07`**. (`plans/` is the source of truth for "what now.") +> **Status:** concept ratified and locked; **Slice 0 (Phase 0 — the protocol as typed code) is built, green, and +> hardened** (Wave A done; all post-Session-1 decisions resolved, DECISIONS MD-8…MD-15). The live per-session build +> plan is in **`plans/`**; the slice roadmap is **`docs/concept/07`**. (`plans/` is the source of truth for "what +> now.") ## The frame @@ -70,10 +71,11 @@ read it before writing code. | **4** | The agent surface (the `reader` — entry adapters + impact) + the Design Review / one generated read-only view — both fully derived. | | **5** | Polish: the `sdp` CLI (`build` · `validate` · maybe `explain`/`search`), error messages, the documented example, a clean-repo determinism test. | -**Where we are now (`plans/`):** **Session 1** = Slice 0 + the repo/toolchain bootstrap, authoring the -`checkout-v1` example against the DSL **so it typechecks** — pure types, no I/O, deterministic by construction; -stops *before* the extractor. **Session 2** = Slice 1, the `ts-morph` extractor (deterministic rebuild P3 + -graceful partial extraction L3); session 1's example becomes its first real input. +**Where we are now (`plans/`):** Session 1 (Slice 0 + bootstrap) is done and hardened; the post-Session-1 +decisions are all resolved (grill 2026-06-10, MD-10…MD-15). **Next session = Wave B** (`plans/02` §4): typed +floor-bearing sections → the kind-conditional floor table → de-pad the example → the `.sdp.ts` rename. **Then** +Slice 1, the `ts-morph` extractor (deterministic rebuild P3 + graceful partial extraction L3); the hardened +example becomes its first real input. > **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the DSL and extractor are > forced to be usable before they are finished. If the example doesn't typecheck, fix the DSL — not the example. diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index ed23027..392ce34 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -22,7 +22,7 @@ type Spec = { intent?: IntentSection; behavior?: BehaviorSection; constraints?: ConstraintSection[]; - model?: DomainModelSection; + model?: ModelSection; design?: DesignSection; decision?: DecisionSection; verification?: VerificationSection; @@ -130,7 +130,7 @@ Sections carry the detail. They are the **extension surface**: the system grows | `intent` | actor, problem, outcome, value, risks, assumptions, open questions | `openQuestions` may be flagged `blocking` to prevent stating a readiness past `defined`. | | `behavior` | rules (prose), examples (prose or structured Given/When/Then), flows | **Content only — never refs** (the duality rule below). An example entry matures *in place*: prose → a structured `{ given, when, then }` entry → (promoted) a child `example` spec backed by a verifier. | | `constraints[]` | a `flavor` (quality / security / performance / compliance / operational / policy), a statement, an optional `target`, optional `measurableBy` | A `performance` constraint with a measurable `target` is an NFR. `target` must be machine-readable (`p95 < 300ms`, not "fast enough") to state `defined`+. | -| `model` | domain terms and concepts (vocabulary only) | Used for pack-level coherence checks. `DomainConcept.kind` is an open, illustrative list, not a closed enum. | +| `model` | domain terms (vocabulary only) — `terms: Record` | Used for pack-level coherence checks. Richer concept structures (typed concepts, attributes) are a **named deferral**; when one lands, the typing law below pulls it into the closed shape. | | `design` | components, ports, dependencies, decisions, tradeoffs | Referenced by ID; decision bodies are linked, not parsed for semantics. | | `decision` | context, chosen option, rationale, alternatives, consequences | The inline form of a decision; promote to a `kind:"decision"` spec when shared (see the duality rule below). | | `verification` | mode (manual / reviewed / contract / executable) + criteria | A verifying test *existing and enabled* is the derived `has-verifier` delivery fact (§2), not an authored field here. Pass/fail is **not** in the graph — it is CI's, operational. | diff --git a/docs/concept/04-authoring-and-binding.md b/docs/concept/04-authoring-and-binding.md index 7be7125..abe3eb7 100644 --- a/docs/concept/04-authoring-and-binding.md +++ b/docs/concept/04-authoring-and-binding.md @@ -80,6 +80,8 @@ export const _anchor = anchorImplementation({ id: "impl:orders.create-order-use- ``` The three syntaxes are interchangeable Representations; the *binding* is the thing. A team picks one style. +The builder generalizes to **`codeAnchor`** over the implementation-flavored code namespaces (`impl` / `api` / +`component`) — ratified MD-8, lands with Slice-2 anchor extraction; `anchorImplementation` is the Session-1 name. ### Anchors assert a binding — never intent (P9/P10) diff --git a/plans/01-session-1-bootstrap-phase0.md b/plans/01-session-1-bootstrap-phase0.md index 7611e99..5184f1e 100644 --- a/plans/01-session-1-bootstrap-phase0.md +++ b/plans/01-session-1-bootstrap-phase0.md @@ -1,195 +1,19 @@ # Session 1 — Bootstrap + Phase 0 (the protocol as typed code) -> **Status: SCOPED · awaiting detailed implementation plan.** This document fixes the *scope and shape* of the -> very first setup/implementation session. A fully detailed implementation plan (file-by-file contents) will be -> authored next and reviewed against this scope before execution. -> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state at planning time:** docs-only; npm not yet -> initialized. - -This is the first *code* session of the MVP. It implements **Slice 0** of the roadmap (`docs/concept/07` §1) — -*Phase 0, the protocol as code* — plus the repo/toolchain bootstrap that must precede any code, and it authors the -running example against the new DSL as a usability tracer bullet. It deliberately **stops before the `ts-morph` -extractor** (Slice 1). - ---- - -## §0 — How this fits - -The roadmap (`07` §1) sequences the build as Slices 0→5. Slice 0 is explicitly *the foundation, not a detour*: the -extractor, the graph schema, and every validator presuppose the typed meta-model. So the first session is "bootstrap -the repo, then build Slice 0," with the running example written first per the roadmap's tip: - -> *"write the example specs and anchored code first — it forces the DSL and extractor to be usable before they are -> finished."* (`07` §1) - -There is a clean complexity seam between Slice 0 (pure types + thin builders + the read-model schema — no I/O, -deterministic by construction) and Slice 1 (the `ts-morph` extractor, where source-reading, determinism P3, and -graceful-partial-extraction L3 enter). Keeping the extractor out of session 1 is what makes session 1 bounded and -green-at-the-end. - ---- - -## §1 — Decisions locked - -**Scope (selected):** Bootstrap the package + build Phase 0 (the typed meta-model + DSL) + author the -`checkout-v1` example specs against it so they typecheck. No extractor, no validator firing logic this session. - -**Toolchain (selected):** **npm · ESM · TypeScript strict · Vitest · tsup · ESLint + Prettier · Node ≥ 20.** - -Two cross-cutting setup decisions made here (override in the detailed plan if preferred): - -1. **The example imports the package by name, via a `paths` alias.** The fixture must - `import { spec } from "@libar-dev/software-delivery-protocol"` — the *public* DX — not `../../src`. Pre-publish, - a `tsconfig.examples.json` maps the package name → `src/index.ts`. If this alias is wrong, the example silently - typechecks against internal paths and the tracer bullet is defeated; verifying the public barrel re-exports - everything the example needs is part of "done." -2. **The running example lives under `examples/checkout-v1/`, not `/specs/`.** The design's repo-shape (`04` §5) - puts `/specs` at a *consuming project's* root; the Protocol's own repo self-hosting is an explicit *later - milestone* (base §1). A clearly-labeled fixture avoids prematurely claiming self-hosting. - ---- - -## §2 — Session goal & "Done" gates - -**Goal:** a buildable `@libar-dev/software-delivery-protocol` package whose typed meta-model (the protocol *as -code*) is complete enough that the `checkout-v1` example authors against it and compiles. - -| Gate | Command | -|---|---| -| Package + DSL typecheck | `npm run typecheck` (src) | -| Example specs compile against the public barrel | `tsc --noEmit -p tsconfig.examples.json` | -| Unit tests pass | `npm test` (Vitest) | -| Build emits JS + `.d.ts` | `npm run build` (tsup) | -| Lint clean | `npm run lint` | -| CLI runs | `npx sdp --help` (build/validate print "not implemented — Slice 1/3") | - ---- - -## §3 — Part A: Repo & toolchain bootstrap - -| File | Purpose | -|---|---| -| `package.json` | name `@libar-dev/software-delivery-protocol`, `"type":"module"`, `exports` map, `bin: { sdp }`, scripts (`build`/`test`/`typecheck`/`lint`/`format`), `engines.node ">=20"` | -| `tsconfig.json` | strict, `moduleResolution: "bundler"`, target ES2022, `declaration`, `isolatedModules`, `noUncheckedIndexedAccess` | -| `tsconfig.examples.json` | extends base, includes `examples/**`, `paths` alias → `src/index.ts`, `noEmit` | -| `tsup.config.ts` | ESM build, `dts: true`, CLI shebang banner | -| `vitest.config.ts` | node env | -| `eslint.config.js` + Prettier | flat config | -| `.gitignore` | append `node_modules/`, `dist/`, `generated/` (L8 — generated is disposable) | -| `.github/workflows/ci.yml` | install → typecheck (both tsconfigs) → lint → test → build | - ---- - -## §4 — Part B: Phase 0 — the protocol as typed code - -``` -src/ - index.ts # public barrel (the agent/author-facing surface) - ids.ts # branded SpecId/PackId/AnchorId; grammar :#; parse/format; ref() - model/ - descriptors.ts # SpecKind(8) · SpecAltitude(epic→feature→story) · SpecReadiness(idea→scoped→defined→ready) + display labels - sections.ts # intent·behavior·constraints[]·model·design·decision·verification·ui (all optional — P7) - spec.ts # Spec envelope type + spec() builder - pack.ts # Pack type + pack() builder (framing = plain note, not truth — 02 §4) - relations.ts # refines·dependsOn·constrainedBy·decidedBy·verifies·supersedes builders (all claim:"declared") - anchors.ts # anchorImplementation()/specTest() — identity-only, intent-forbidden (P9/P10) - graph/ - schema.ts # read-model types: Node union (Primitive/Pack/Anchor/CodeNode…), Edge{from,type,to,claim}, deliveryFacts, schemaVersion "0.1.0" - validate/ - contracts.ts # Validator iface, ValidatorFamily(conformance|honesty), Severity, Finding, ValidationReport - readiness-floor.ts# the 05 §3 floor table as data (declared, NOT enforced yet) - cli/ - sdp.ts # bin entry; --help; build/validate => "not implemented (Slice 1/3)" -``` - -Each piece is type/contract-only and pure — no I/O — so it is deterministic by construction. The graph schema and -validator contracts ship now (everything downstream presupposes them) but stay inert until the extractor (Slice 1) -and validators (Slice 3) arrive. - -**Envelope discipline (L9):** the `Spec` envelope (`id · title · kind · altitude · readiness · relations`) is the -stability contract; all detail lives in optional `sections`. The session must not encode completeness in conditional -types — *types describe shape; validators decide completeness* (P7). - ---- - -## §5 — Part C: The running example (the tracer bullet) - -`pack:checkout-v1`, Order Management, ~8–12 specs (`07` §1): - -``` -examples/checkout-v1/ - specs/ checkout.pack.ts · orders/order-management.spec.ts · orders/create-order.spec.ts - · 2–3 scenarios · 1–2 rules · 1 NFR(constraint) · decisions/order-lifecycle.spec.ts (~8–12 specs) - src/ orders/create-order.use-case.ts # anchored: impl:orders.create-order-use-case - test/ orders/create-order.valid-cart.test.ts # specTest verifies spec:orders.create-order.valid-cart -``` - -These compile against the **public barrel**. If they do not typecheck, the DSL is not usable — we fix the DSL, not -the example. - ---- - -## §6 — Part D: Tests (Vitest) - -- `ids.test.ts` — parse/format round-trip; reject malformed IDs; `ref()` branding. -- `builders.test.ts` — `spec()`/`pack()`/relation builders produce the expected plain objects. -- Example typecheck wired as a CI step (the `tsconfig.examples.json` gate). - ---- - -## §7 — Deferred (NOT this session) - -`ts-morph` extractor + `graph.json` (Slice 1) · validator firing logic + CI graph-gate (Slice 3) · reader / agent -surface + view (Slice 4) · determinism `--check-clean` (needs the extractor) · validator self-test fixtures -(`05` §5, needs validators). - ---- - -## §8 — Suggested commits (small, on `feature/mvp-init`) - -1. `chore: bootstrap package + tsconfig + vitest/tsup/eslint/prettier + gitignore` -2. `chore: CI (typecheck, lint, test, build)` -3. `feat: stable IDs — branded types, grammar, parse, ref` -4. `feat: descriptors + sections types` -5. `feat: Spec/Pack/relations/anchors DSL builders` -6. `feat: graph read-model schema types` -7. `feat: validator + report contracts + readiness-floor table (data)` -8. `feat: sdp CLI stub` -9. `test: ids + builders` -10. `feat(examples): checkout-v1 running example + typecheck gate` - ---- - -## §9 — Risks & gotchas - -- **Self-referencing import alias** (§1.1) — the single most likely thing to get subtly wrong; verify the example - resolves the package *name*, not `src`. -- **Over-reach into Slice 1** — resist adding any source-reading / `ts-morph` here; the green seam is "types compile, - example compiles," nothing runtime-derived. -- **Encoding completeness in types** — keep all sections optional; readiness floors are *data* this session, enforced - later (P7, P8). - ---- - -## §10 — Traceability - -| Session element | Anchored in | -|---|---| -| Slice 0 = protocol as code | `07` §1; `00` §3; base §1 | -| `Spec` envelope + 3 descriptors + sections | `02` §1–§3; P4, P7, P8, L9 | -| Stable IDs grammar | `02` §5; P6 | -| Relations + claim:"declared" | `02` §6; `03` edge contract; P9 | -| Anchors identity-only | `04` §2; P9, P10 | -| Graph read-model schema types | `03` §1 | -| Validator/report contracts + two families | `05` §1–§2 | -| Readiness-floor table (data) | `05` §3 | -| Running example as tracer bullet | `07` §1 tip; `04` §5 | -| `generated/` disposable / gitignored | L8; `03` §4 | - ---- - -## §11 — Next session preview - -**Session 2 = Slice 1:** the `ts-morph` one-graph extractor reading `examples/checkout-v1/specs/**` + anchors, -emitting `graph.json` (nodes + declared relations), with the deterministic-rebuild discipline (P3) and graceful -partial extraction (L3). The example authored in session 1 becomes its first real input. +> **Status: ✅ EXECUTED (Session 1, `eb6bf2a`).** Implemented Slice 0 (`docs/concept/07` §1) — the typed +> meta-model as code (IDs · descriptors · sections · `Spec`/`Pack`/relations/anchors builders · graph +> schema · validator contracts · floor table as data · `sdp` CLI stub) — plus the repo/toolchain bootstrap +> (npm · ESM · TS strict · Vitest · tsup · ESLint/Prettier · Node ≥ 20), and authored the `checkout-v1` +> example against the DSL as the tracer bullet. Stopped before the extractor (Slice 1). The detailed +> file-by-file plan this document used to carry is in git history; the result is the repo itself +> (`src/`, `examples/`, `test/`). What followed: hardening + decisions in `plans/02`. + +Two setup decisions worth remembering — they explain the repo's shape: + +1. **The example imports the package by name** (`@libar-dev/software-delivery-protocol`) via a + `tsconfig.examples.json` `paths` alias → `src/index.ts` — the *public* DX, never `../../src`. If the + alias is wrong, the example silently typechecks against internals and the tracer bullet is defeated; + the public barrel re-exporting everything the example needs is part of "done." +2. **The running example lives under `examples/checkout-v1/`, not `/specs/`.** `04` §5's `/specs` shape is + a *consuming project's* root; the Protocol's own repo self-hosting is an explicit later milestone — a + clearly-labeled fixture avoids prematurely claiming it. diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index a02373f..c2a43e1 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -1,29 +1,14 @@ # Phase 0 Hardening — Fidelity & Simplification Pass (post-Session-1) -> **Status: EXECUTION · Wave A done · Wave B EXECUTION-READY (unblocked 2026-06-10 — the grill resolved -> all gates, MD-10…MD-15).** This is the **code hardening** half of the post-Session-1 review — fidelity -> corrections and net complexity reductions on the already-implemented Phase 0. The decisions these fixes' -> siblings depend on, the concept-base wording tightenings, and the roadmap acceptance criteria were -> **split out of this plan** into their proper homes (see "Where the rest went," below). -> **Date:** 2026-06-07 · **Branch:** `feature/mvp-init` · **Repo state:** Session 1 (`eb6bf2a`) + Wave A -> green (`npm run check`: typecheck ×2, lint, format, **47 tests + 2 todo**, build). -> **2026-06-10 folds:** R1/R2/R3 ratified & applied; MD-8 (`codeAnchor`) and MD-9 (`intent.openQuestions`) -> recorded decided. **2026-06-10 grill:** D1/D2/D3/D4/D7/D8 resolved as **MD-10…MD-15** — §4 below is -> rewritten execution-ready against them; run it as the next code session. - -This plan covers the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the -extractor can safely build on."* It stays inside the Slice 0 boundary — **no extractor, no graph -emission, no graph gate** — except where a fix must be *designed now* so the extractor (Slice 1) -doesn't inherit a landmine. - -**Where the rest went (the re-home).** This plan was originally one document fusing three kinds of work; -they now live where each gets the right rigor: -- **Open decisions** → `plans/03-decision-resolution-and-base-reconciliation.md` — the grilling agenda, - now slimmed to the six genuinely-open (D1–D4, D7, D8). Wave B's typed-section fixes are **blocked on - D1/D2/D3**; the former D6 (anchor shape) was folded out as **decided** (DECISIONS MD-8). -- **Concept-base wording (R1, R2, R3)** → `docs/concept/DECISIONS.md` — **ratified & applied 2026-06-10** - in the pre-grill folds. -- **Forward-looking acceptance criteria** → `docs/concept/07` §6 (mapped across Slices 1–5). +> **Status: EXECUTION · Wave A ✅ done · Wave B EXECUTION-READY — run §4 as the next code session.** +> Code hardening on the already-implemented Phase 0: fidelity corrections + net complexity reductions, +> covering the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the +> extractor can safely build on."* Stays inside the Slice 0 boundary — **no extractor, no graph +> emission, no graph gate** — except where a fix must be *designed now* so Slice 1 doesn't inherit a +> landmine. Every decision this plan was gated on is **resolved**: R1/R2/R3 applied; **MD-8/MD-9** +> (pre-grill folds) and **MD-10…MD-15** (the 2026-06-10 grill, agenda `plans/03`) recorded in +> `docs/concept/DECISIONS.md`. Forward-looking acceptance criteria live in `docs/concept/07` §6. +> **Branch:** `feature/mvp-init` · **Repo state:** Session 1 + Wave A, `npm run check` green. --- @@ -53,67 +38,39 @@ they now live where each gets the right rigor: **Regression gate:** `npm run check` stays green; all invariants above still hold. -## §2 — The root tension (drives the Wave-B fidelity fixes) +## §2 — The root tension — ✅ RESOLVED (MD-11 on MD-10) `src/model/sections.ts` types every section as `Record`; the base (**L9**, `02` §3) -intends **typed-but-optional** sections. This was the linchpin — **resolved 2026-06-10**: MD-11 (the typing -law — floor-bearing sections are closed-typed; six today) on MD-10's content-only `behavior` shape. §4/B1 -carries the locked field shapes. +intends **typed-but-optional** sections. Resolved by the typing law (MD-11 — floor-bearing ⟹ +closed-typed; six today) on MD-10's content-only `behavior` shape. §4/B1 carries the locked field shapes. --- ## §3 — Wave A (decision-free) — ✅ DONE -Quick, contract-shrinking, reversible wins. All landed and verified; `npm run check` green. - -### ✅ H1 — Make the example statically extractable (P5) -- **Was.** Both scenario specs built `behavior` via `Object.fromEntries([...]) as Record` (+ a `thenKey` indirection) — not statically evaluable, so the Slice-1 `ts-morph` extractor - (which reifies without executing, `04` §1) would silently drop the section and flip the `example` - floor from valid → honesty violation. -- **Done.** Replaced both with plain static object literals. -- **Verified.** `grep -rn "Object.fromEntries\|thenKey\|as Record" examples/` → none; example typecheck - + tests green. -- **Forward hook (DEFER, named).** Slice 1's "done" must include *the example survives static extraction - byte-for-byte*; the `sdp/spec-static` lint rule (`04` §1) is the earlier backstop. - -### ✅ H6 — Simplify the tsup build; stop leaking a shebang onto the library entry -- **Was.** `dist/index.js` started with `#!/usr/bin/env node` because `banner.js` applied to **all** - entries, compensated by an esbuild strip-plugin + an `onSuccess` shebang-normalise hook. -- **Done.** Removed the banner, the strip-plugin, and the hook; rely on esbuild preserving the source - shebang on the CLI **entry** only. -- **Verified.** `dist/index.js` first line is `// src/ids.ts` (no shebang); `dist/cli/sdp.js` has - exactly one `#!/usr/bin/env node`; `node dist/cli/sdp.js --help` exits 0. - -### ✅ H7 — Hygiene (`.prettierignore`) -- **Done.** Added `.tmp-scratch/**` to `.prettierignore` so `format:check` no longer trips on scratch. -- **Still open (low, optional).** Already-committed `.sisyphus/` files (from `eb6bf2a`) remain tracked - despite the ignore; `git rm --cached .sisyphus` would untrack them if wanted. `package.json` lacks - `description`/`license`/`repository` — note for the publish checklist. The `vitest-test.mjs` wrapper - is optional ergonomics. - -### ✅ H8 (active) — A should-fail / should-pass validator fixture suite (`05` §5) -- **Done.** `test/fixtures/authored-model.fixtures.ts` + `test/fixtures.test.ts` with the **active** - (decision-free) fixtures, each pinning one validator outcome: - - `valid-minimal-idea-spec` → passes. - - `invalid-duplicate-id` → `conformance/duplicate-ids`. - - `invalid-scoped-without-relation` → `honesty/readiness-floor` (`at-least-one-relation`). - - `invalid-defined-constraint-without-target` → `honesty/readiness-floor` - (`constraint-machine-readable-target`). -- **Gated stubs (inherited checklist, `it.todo`):** `invalid-ready-with-blocking-question` (flips active - with **H2**) · `invalid-hand-authored-delivery-fact-in-section` (flips active with **D1**). The - extractor-era fixtures (`invalid-non-static-id`, `invalid-non-static-section`, - `invalid-hand-authored-satisfies-edge`, `invalid-ready-with-unresolved-dependency`, - `invalid-ready-with-target-below-defined`) remain named for Slice 1+. - -### ✅ H9 — Lock the type-level honesty defenses with compile-time fixtures -- **Done.** Added `@ts-expect-error` fixtures in `test/builders.typecheck.ts`: the `Spec` envelope - rejects a top-level `implemented` / `has-verifier`; the `Pack` rejects `intent` / `readiness` / - `constraints`. `npm run typecheck` consumes them (an unused directive would fail the build, so they - *prove* the defenses fire). -- **Note (tracked with D1).** The **in-section** bypass (`behavior: { "has-verifier": true }`) is *not* - closed by these — it is closed only by typing sections (D1, plan 03) and locked by the gated H8 - fixture. +All landed and verified; `npm run check` green. One line each — the full was/done/verified detail lives +in git history (provenance: `reviews/01`). + +- **H1 — the example is statically extractable (P5).** The `Object.fromEntries(...) as Record` + + `thenKey` indirection replaced with plain static literals. **Forward hook (Slice 1):** "done" must + include *the example survives static extraction byte-for-byte*; the `sdp/spec-static` lint (`04` §1) + is the earlier backstop. +- **H6 — tsup build simplified.** The shebang ships only on the CLI entry (no banner / strip-plugin / + `onSuccess` hook); `dist/index.js` is shebang-free. +- **H7 — hygiene.** `.tmp-scratch/**` prettier-ignored. The optional leftovers were **finalized in the + grill session**: `.sisyphus/` untracked; `package.json` carries description + Apache-2.0. +- **H8 — the validator fixture suite (`05` §5).** `test/fixtures/authored-model.fixtures.ts` + + `fixtures.test.ts`; four **active** fixtures, each pinning one validator outcome + (`valid-minimal-idea-spec` · `invalid-duplicate-id` · `invalid-scoped-without-relation` · + `invalid-defined-constraint-without-target`). **Gated stubs (`it.todo`), flipped by Wave B:** + `invalid-ready-with-blocking-question` (B2) · `invalid-hand-authored-delivery-fact-in-section` (B1 — + lands as a compile-time fixture). The extractor-era fixtures stay named for Slice 1+ + (`invalid-non-static-id` · `invalid-non-static-section` · `invalid-hand-authored-satisfies-edge` · + `invalid-ready-with-unresolved-dependency` · `invalid-ready-with-target-below-defined`). +- **H9 — type-level honesty defenses.** `@ts-expect-error` fixtures in `test/builders.typecheck.ts`: the + `Spec` envelope rejects top-level delivery facts; the `Pack` rejects `intent`/`readiness`/`constraints`. + The **in-section** bypass (`behavior: { "has-verifier": true }`) is closed only by B1's typed sections; + the envelope-defense caveat (excess-property / inline-literal scope, F7) is recorded in the file. --- @@ -216,14 +173,9 @@ clause reads them); a dedicated `contract` section is a named deferral (MD-12). ## §6 — Sequencing -1. **Wave A** — ✅ done (H1, H6, H7, H8-active, H9). -2. **Pre-grill folds (2026-06-10)** — ✅ done: R1/R2/R3 ratified & applied; the anchor shape (MD-8) and the - open-questions home (MD-9) recorded decided; `plans/03` slimmed to the six open decisions. -3. **Resolve decisions** — ✅ done (2026-06-10 grill): all six resolved and recorded as **MD-10…MD-15**; - the base reconciled inline; H7's leftovers finalized (`.sisyphus/` untracked; `package.json` - description + Apache-2.0). -4. **Wave B** — ← **NEXT** (the next code session): B1 (typed sections) → B2 (the floor table) → B3 - (de-pad + re-author the example) → B4 (the `.sdp.ts` rename); H10 rides Slice-2 anchor extraction. +Wave A ✅ → pre-grill folds ✅ (R1–R3 applied; MD-8/MD-9) → the grill ✅ (MD-10…MD-15; H7 leftovers +finalized) → **Wave B ← NEXT** (one code session: B1 → B2 → B3 → B4); H10 rides Slice-2 anchor +extraction. **Done gate for Wave B:** the in-section honesty bypass (`behavior: { "has-verifier": true }`) **fails to typecheck**, locked by a `@ts-expect-error` fixture; blocking open questions read from diff --git a/plans/03-decision-resolution-and-base-reconciliation.md b/plans/03-decision-resolution-and-base-reconciliation.md index f98b383..d9e4546 100644 --- a/plans/03-decision-resolution-and-base-reconciliation.md +++ b/plans/03-decision-resolution-and-base-reconciliation.md @@ -2,9 +2,9 @@ > **Status: ✅ EXECUTED 2026-06-10 — the grill ran; all six decisions resolved and ratified (MD-10…MD-15).** > The base was reconciled inline (`02`/`03`/`04`/`05` + the glossary), and `plans/02` Wave B was rewritten -> execution-ready. This document is kept as the agenda-of-record; the resolutions live in -> `docs/concept/DECISIONS.md`. **Date:** 2026-06-07 · slimmed 2026-06-10 (pre-grill folds) · **grilled -> 2026-06-10** · **Branch:** `feature/mvp-init`. +> execution-ready. This document is the **agenda-of-record**; the resolutions — each with the full tension, +> rationale, and rejected alternatives — live in `docs/concept/DECISIONS.md` (the detailed per-decision +> agenda text was folded into those entries and removed here). **Branch:** `feature/mvp-init`. > > | Handle | Resolution | Recorded | > |---|---|---| @@ -15,173 +15,45 @@ > | **D4** | **One validation path** through the one graph (extract → graph → checks); `AuthoredModel` retires as a public seam (direction now; executes Slice 1/3) | **MD-14** | > | **D8** | Spec files carry the **`.sdp.ts`** extension (collision-free with JS test-runner globs; the `.stories.tsx` pattern) | **MD-15** | > -> **Folded out — do not re-litigate (the former §3a/§4).** The resolvable-now items left this agenda in the -> 2026-06-10 fold session: **R1/R2/R3** ratified and applied to the base, and two items recorded **ACCEPTED** -> in `DECISIONS.md` — the **generic-anchor DSL shape** (`codeAnchor` over `impl`/`api`/`component`, MD-8; -> execution rides Slice 2 / plan 02 H10) and the **open-questions canonical home** (`intent.openQuestions`, -> MD-9; execution stays plan 02 Wave B / H2). The source reviews are tracked in `reviews/`. +> **Folded out — do not re-litigate.** The resolvable-now items left this agenda in the 2026-06-10 fold +> session: **R1/R2/R3** ratified and applied to the base, plus **MD-8** (generic-anchor shape `codeAnchor`; +> execution rides Slice 2 / plan 02 H10) and **MD-9** (open-questions home `intent.openQuestions`; execution +> Wave B / plan 02 H2). The source reviews are tracked in `reviews/`. +> +> **Handle hygiene:** D1–D8 here are the *open-decision* code-space used by plans 02/03 — **not** the legacy +> structural D1–D6 table in `DECISIONS.md`. D5 was never assigned; D6 folded out as MD-8. In prose, lead +> with meaning. --- -## §0 — Why a separate, fresh session (not a continuation) - -- **Grilling needs a fresh context.** Resolving these decisions well means stress-testing each against the - ratified language with full attention — the `grill-with-docs` discipline (challenge the plan against the - domain model, sharpen terminology, update `docs/concept` + `DECISIONS.md` inline as decisions crystallise). - A fresh session is a hard requirement for that. -- **This is design-on-the-base, not coding.** The pivotal decision (D1) *looks* like a TypeScript choice but - is a **representation of the canonical section shapes** (`02` §1/§3). It must be settled on the base first; - the types are then a faithful projection, not a fresh source of truth. - -## §1 — The method (base-first, not code-first) - -The standing trap: typing a section in code *before* its canonical shape is settled in the base bakes any -drift into the type system, where it is expensive to move. So the method is strict: - -1. **Settle the canonical section shapes in the core model** (`02` §1/§3): what fields each - floor/extractor-bearing section *has*, and which are required by which readiness floor (`05` §3) — - P7: *types describe shape; validators decide completeness*. -2. **Reconcile wording in the same pass** — terminology is ratified (the glossary), so we ratify changes, - never silently edit; the grill writes its edits into the design docs (`02`/`05`/`06`) and, for term - changes, the glossary. -3. **Record each decision** in `DECISIONS.md` (an MD-series entry with rationale — lead with meaning, the - D-handles below are session-local shorthand). -4. **Only then** does plan 02's Wave B type the code as a projection of the now-settled base. +## §1 — The method (base-first, not code-first) — kept as reusable discipline -Corollary (the anti-bloat thesis, carried from plan 02 §0): every resolution must **shrink the contract or -hold it flat**, never grow it. A readiness floor is *a floor to clear, never a quota to fill* — type sections -to give authors guardrails + autocomplete, not to train them to pad specs. +The standing trap: typing a section in code *before* its canonical shape is settled in the base bakes drift +into the type system, where it is expensive to move. The method used (and to reuse for any future grill): +**(1)** settle the canonical shapes in the core model (`02`), reconciling wording in the same pass (terms are +ratified — never silently edited); **(2)** record each decision as an MD entry in `DECISIONS.md` (lead with +meaning, handles are session-local); **(3)** only then let the code type the shapes as a faithful projection. +Grilling needs a **fresh session** — and every resolution must **shrink the contract or hold it flat** (a +readiness floor is a floor to clear, never a quota to fill). -## §2 — The root tension (carried from plan 02 §2 — the driver behind D1–D3) +## §2 — The root tension (the driver behind D1–D3; resolved by MD-10/MD-11) -`src/model/sections.ts` types every section as `Record`. The base (**L9**, `02` §3) intends -**typed-but-optional** sections. Open bags collapse "optional" into "unknown," at three costs: - -1. **The tracer bullet proves less than it claims** — the example typechecks against `unknown`, so "it - compiles" says nothing about the DSL capturing section *shape* (the session's headline goal). -2. **The authoring-shape honesty check is bypassable** — `behavior: { "has-verifier": true }` typechecks - today and no validator catches it (verified; locked by a gated H8 fixture in plan 02). -3. **Validators string-probe at runtime** (`spec.behavior.given`, `spec.intent.outcome`); a typo in a section - field name silently passes the floor. +`src/model/sections.ts` types every section as `Record`; the base (**L9**, `02` §3) intends +**typed-but-optional** sections. Open bags collapse "optional" into "unknown": the tracer bullet proves less +than it claims, the in-section honesty bypass typechecks, and validators string-probe at runtime. > **The headline forward risk is authoring ergonomics, not graph theory.** If authoring feels heavy, authors > (human **and** agent) avoid the system or overfit specs to satisfy tooling. Untyped sections give no -> autocomplete and no shape guardrails — the heavy-authoring loop. (H1 was the first symptom in the repo: an -> agent reached for `Object.fromEntries(...) as Record` instead of a plain literal — fixed -> in Wave A.) So typing the floor-bearing sections is both the honesty fix **and** the single highest-value -> adoption lever. This is why `07` §6 names "authoring ergonomics" as a Slice-2 concern. - -## §3 — The six open decisions, in dependency order - -> Handle hygiene: D1–D8 below are the *open-decision* code-space used by plans 02/03 — **not** the legacy -> structural D1–D6 table in `DECISIONS.md`. D5 was never assigned; D6 (generic-anchor shape) was folded out -> as MD-8. In prose, lead with meaning. - -``` -D2 (prose vs refs in behavior.rules AND behavior.examples) ──┐ -canonical section shapes (02 §1/§3) ──┴──► D1 (type which sections, how) - │ - ┌───────────────────────────────────────┼──────────────────────────────┐ - D3 (collapse floor) H2/H3 field shapes H8 gated fixtures flip active -D4 (AuthoredModel vs the one graph) — write a direction now, execute at Slice 1/3 -D7 (kind-aware `defined` floor) — sequences with D1; de-pads the example -D8 (.spec.ts file-extension collision) — Representation-level; before adopters -``` - -### D2 — prose-vs-ref duality across `behavior.rules` AND `behavior.examples` (gates typing `behavior`) -- **Tension.** The docs model `behavior.rules` as **prose strings** and `examples` as refs; the example - currently puts **refs** in `rules` *and* promotes the same rules to standalone `kind:"rule"` specs - (`refines` create-order) — the linkage exists twice, once invisibly. **The same duality is live in - `behavior.examples`** (review F5): `create-order.spec.ts` puts **refs** in `examples`, while - `create-order-valid-cart.spec.ts` puts **prose** in `examples` *and also* carries structured - `given/when/then` (the prose entry is redundant with its own GWT). So `behavior.examples` is used two - incompatible ways in the same example. -- **Resolve** the inline-vs-promote duality (`02` §3 "Section ⟷ kind") for **both** fields: prose + - relations to promoted specs, or typed ref lists with referential-integrity coverage. Must resolve before - `behavior` is typed (D1), or typing it ossifies whichever shape the first author happened to pick. - -### D1 — how much to type sections now (the linchpin) -- **Recommendation (carried, to ratify or revise):** type the **five sections the floors and the extractor - depend on** — `intent`, `behavior`, `constraints`, `model`, `verification` — and leave `design` / - `decision` / `ui` as open bags so the unsettled surfaces keep breathing. This closes the honesty bypass - for object literals and gives authors guardrails, without over-committing volatile surfaces. -- **Alternatives to weigh:** type all eight now; or type none and add a runtime authoring-shape validator - instead. -- **Unblocks:** H3 (`constraints[]` shape), H2 (open-questions execution — direction already decided, MD-9), - H4 (section-ref integrity), the gated H8 fixture `invalid-hand-authored-delivery-fact-in-section`, and the - H9 in-section note. - -### D3 — collapse direction for the floor validator (depends on D1) -- **Tension.** `validators.ts` is 453 lines for three checks; clause ids are enumerated in **four** places - and `authoredPaths` in the floor data is **decorative** (read only by its declaration + tests, never by - the evaluator — verified). -- **Recommendation (carried):** make the **table the single source of truth** — the evaluator reads - `authoredPaths` + a few generic predicates (safe once sections are typed), killing the 4× enumeration. - Alternative: shrink the table, let the evaluator be the spec. - -### D7 — make the `defined` floor kind-aware (highest-value item · review F1) -- **Finding.** The `defined` floor requires `rules-and-or-examples` for **every** kind - (`readiness-floor.ts`); overlays only *add* requirements, never *relax* the base. So a `decision` / - `model` / `constraint` / `contract` / `workflow` spec cannot reach `defined` on its *natural* content — - and the tracer bullet pads three specs with throwaway `behavior.rules` purely to clear the floor - (`order-lifecycle`, `order-model`, `order-latency-constraint`), which `checkout-v1.test.ts` then asserts - is green. The canonical example embodies the exact anti-pattern **P4's anti-padding corollary forbids**, - and an AI author will learn to sprinkle `behavior.rules` onto decision records. -- **Proof it is a bug, not taste.** At `scoped` the evidence clause is `rules-examples-or-**constraints**` - (constraints alone are valid evidence); at `defined` it silently drops `constraints` → - `rules-and-or-examples`. The maturity ladder is **non-monotonic in the evidence it accepts** — a - constraint honestly `scoped` on its target suddenly needs behavior content to mature to `defined`. This - half-delivers the `02`/P8 "kind is a true subtype that *changes* required detail" promise: at the floor - level `kind` only ever *adds*. -- **Resolution (shrinks the contract).** Make the `defined` evidence clause **defer to the kind**: for a - kind with an overlay (constraint→target, model→terms, example→GWT) or a natural truth section - (decision→`decision`), that overlay/section *is* the evidence; require `rules-and-or-examples` only for - the behavior-family kinds (`behavior`, `rule`, `workflow`). The overlay machinery already exists — this is - "base evidence clause is kind-conditional," not new infrastructure. Sequences with D1 (typed sections make - the predicate cheap). -- **Why here, not plan 02.** It is a refinement of the floor table — the model (`05` §3) — so it moves - *with* the base in the grilling pass; and resolving it de-pads the canonical example (drop three rules), a - visible quality signal. Wave B then re-authors the example + the H8 - `invalid-defined-constraint-without-target` fixture (which itself currently pads — review §3.1). - -### D4 — `AuthoredModel` seam vs the one graph (write a direction now; execute Slice 1/3) -- **Tension (P2).** Validators are designed to run over **the one graph** ("no consumer maintains a parallel - model"). When the extractor lands, either (a) validators migrate to consume `GraphSchema` and - `AuthoredModel` retires, or (b) `AuthoredModel` stays as an explicit, documented *pre-graph authoring - lint*. Avoid two validation code paths drifting apart. (Interacts with the crippled-graph gap strategy, - `plans/04` §2.) -- **Action:** decide the **direction** in this session even though execution is Slice 1/3. - -### D8 — the `.spec.ts` file-extension collision (Representation-level; currently implicit · review 1.3) -- **Finding.** The extractor reads `/specs/**/*.spec.ts` (`03`/`04` §5), but `*.spec.ts` is *the* default - test glob for Vitest/Jest/Mocha. This repo dodges it (`vitest.config.ts` narrows to `test/**/*.test.ts`), - but a **consuming** project using the standard `**/*.spec.ts` for its tests will have its runner try to - execute Spec-primitive files as test suites — a baffling failure for a new adopter. The model name `Spec` - is well-chosen and stays; only the *file serialization* inherits foreign semantics (the test runner stands - in for one of the three readers). -- **Decide (before any real adopter):** keep `.spec.ts` with a documented test-glob exclusion, or move to a - collision-free extension (`.sdp.ts`, `.spec.sdp.ts`). Representation-level, not a model change. - -## §4 — What this session unblocks (the Wave-B execution backlog, owned by plan 02) - -Once D1–D3 land and the base is reconciled, plan 02's **Wave B** becomes execution-ready: H3 -(`constraints[]`), H2 (open-questions execution under MD-9), H5/D3 (collapse the floor validator), H4 -(section-ref referential integrity, likely folded into the Slice-1 extractor), the example de-padding that D7 -unlocks, and the two gated H8 fixtures flip from `it.todo` to active. H10 (the api/route anchor in the -example) executes with Slice-2 anchor extraction under the MD-8 `codeAnchor` shape. - -## §5 — Definition of done for this session — ✅ ALL MET 2026-06-10 - -- ✅ **D1, D2, D3, D4, D7, D8 resolved** and recorded in `DECISIONS.md` as **MD-10…MD-15** (rationale + what - each unblocks). The grill went two steps *past* the agenda where the evidence forced it: D7 covers - **`scoped` too** (the kind-blind scoped clause was what the padding actually cleared), and D1 became a - **criterion** ("floor-bearing ⟹ typed") rather than a list — which pulled `decision` into the typed set - and rejected `decision.status` as FSM vocabulary. -- ✅ **The core model and floor reconciled**: `02` §2/§3 (duality laws, typing law, readiness comments, - worked examples), `05` §2/§3 (one validation path; the two-part floor + per-kind evidence table), - `04` §1/§5 + `00`/`03`/`jtbd-01` (content-only DSL example, `.sdp.ts`); term changes in the glossary - (duality entry, `status` rejection, `.sdp.ts` resolution). -- ✅ **Plan 02 Wave B rewritten execution-ready** (locked shapes, de-padding, the `.sdp.ts` rename, the H4 - dissolution). -- ✅ No `src/`/`test/` code changed (PLAN-ONLY held); the only non-doc touches were the two H7 leftovers the - session was asked to finalize (`.sisyphus/` untracked; `package.json` description + Apache-2.0). +> autocomplete and no shape guardrails — the **heavy-authoring loop**. (H1 was the first symptom: an agent +> reached for `Object.fromEntries(...) as Record` instead of a plain literal.) So typing the +> floor-bearing sections is both the honesty fix **and** the single highest-value adoption lever; `07` §6 +> names "authoring ergonomics" as a Slice-2 concern. + +## §3 — Outcome — ✅ all six resolved (rationale: `DECISIONS.md` MD-10…MD-15) + +The grill went two steps *past* the agenda where the evidence forced it: **D7 covers `scoped` too** (the +kind-blind scoped clause was what the padding actually cleared), and **D1 became a criterion** ("floor-bearing +⟹ typed") rather than a list — which pulled `decision` into the typed set and rejected `decision.status` as +FSM vocabulary. Side effect: H4 (section-ref integrity) mostly **dissolved** under D2 — no refs in sections +means nothing to check; only the `modelRefs`-kind check survives (Slice 1/3). Execution is owned by `plans/02` +§4 (Wave B, B1–B4). No `src/`/`test/` code changed in the grill session itself (PLAN-ONLY held). diff --git a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md index 2d46837..bc1d59e 100644 --- a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md +++ b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md @@ -1,39 +1,29 @@ # Session 4 — Formalization: gap strategy + pre-grill folds (re-entry context) -> **Status: PLAN-ONLY · §3 EXECUTED 2026-06-10 (Fold-A · Fold-B · archive) · §4 step 4 (the grill) -> EXECUTED 2026-06-10 — all six decisions resolved, MD-10…MD-15 · §2 still the parked direction.** This -> document exists to bootstrap context fast after a break — it is the formalized residue of a long design -> arc that hit the ceiling of what a chat thread can resolve ("no substrate to grow the decision → -> formalize"). Read §0 first; the rest is reference. -> **Date:** 2026-06-08 · folds executed 2026-06-10 · grill executed 2026-06-10 · **Branch:** -> `feature/mvp-init` · **Repo state:** green; folds + grill landed (lean glossary; R1/R2/R3 + MD-8…MD-15; -> `plans/02` Wave B execution-ready). **Next: Wave B execution** (`plans/02` §4), then — gated on a -> minimal engine — the crippled graph (§2). +> **Status: §3 (folds) and §4 (grill) ✅ EXECUTED 2026-06-10 · §2 (the crippled graph) still the parked +> direction — the one genuinely open item here.** This document was the formalized residue of a long design +> arc; its executed scaffolding has been folded down (outcomes live in `DECISIONS.md`, `plans/03`'s +> resolution table, and `reviews/`). What remains: the re-entry summary, the operating frame, and the parked +> gap strategy. **Branch:** `feature/mvp-init` · **Next: Wave B execution** (`plans/02` §4), then — gated on +> a minimal engine — the crippled graph (§2). --- ## §0 — Re-entry in one screen -**Where the code is:** Slice 0 (the protocol as typed code) is built, green, and hardened (Wave A -done). No extractor, no graph, no engine yet. +**Where the code is:** Slice 0 (the protocol as typed code) is built, green, and hardened (Wave A done). +No extractor, no graph, no engine yet. -**What happened this arc (after Wave A landed):** -1. A fresh **adversarial review** (run from a bespoke prompt with a 3-view structure) read the whole - concept + code + plans and came back high-signal. Its findings were absorbed into tracked docs. -2. We studied **gen-1 (`@libar-dev/architect`)'s full formal-spec** as *evidence, not template*. The - takeaway: gen-1 is proof that gen-2's specific rejections are correct (its FSM softened to advisory, - its tag taxonomy was repeatedly amputated, its artifacts migrate-and-delete). Lineage confirmed; no - patterns transferred. -3. We converged on **how to manage gen-2's own delivery in the gap** before the engine exists — see §2, - the **crippled graph**. +**What happened this arc:** a fresh adversarial review (high-signal; archived `reviews/04`) → the gen-1 +(`@libar-dev/architect`) formal-spec study (*evidence, not template*: gen-1 is proof gen-2's rejections are +correct — its FSM softened to advisory, its tag taxonomy repeatedly amputated, its artifacts +migrate-and-delete; no patterns transferred; chat-only, formalized here) → the pre-grill folds (lean +glossary, R1–R3, MD-8/MD-9) → the grill (MD-10…MD-15; base reconciled; `plans/02` Wave B execution-ready). -**What's next:** ~~two scoped **fresh** fold sessions (§3)~~ **the folds and the archive are DONE -(2026-06-10)** → ~~the grill~~ **the grill is DONE (2026-06-10): all six decisions resolved as -MD-10…MD-15, base reconciled, `plans/02` Wave B execution-ready** → now **Wave B execution** -(`plans/02` §4), then Slice 1. +**What's next:** **Wave B execution** (`plans/02` §4), then Slice 1. -**The one decision still genuinely open and yours:** how far the crippled graph goes, and exactly what -its two instances are (§2). Parked deliberately — it needs substrate to grow, not more chat. +**The one decision still genuinely open and yours:** how far the crippled graph goes, and exactly what its +two instances are (§2). Parked deliberately — it needs substrate to grow, not more chat. --- @@ -53,7 +43,7 @@ These are the operating rules the whole arc converged on. They govern every next (what it got wrong / walked back), never its mechanisms. - **Communicate in meaning, not codes.** The `D-this / H-that` handles are poor-man's node IDs — noise without a graph/projection to give them meaning. They are tolerable only inside a doc that defines them - inline (see the ledger, §6). In prose, lead with meaning. + inline. In prose, lead with meaning. --- @@ -83,166 +73,47 @@ stable-within-its-limits — never an experiment left to sprawl. - What *are* the two instances? (e.g. dogfood/meta-process vs. engine-dev; or a "production" snapshot vs. a working copy.) - What exactly does "release / import production / state-copy" copy, and in which direction? -- How does this relate to the **AuthoredModel-vs-one-graph seam** (a grill decision) and to the - self-hosting-timing rule ("don't move the process onto the graph too early")? -- What is the *minimal* engine slice that makes a crippled graph possible at all (extract → graph, no - more)? +- How does this relate to the **one-validation-path rule** (MD-14) and to the self-hosting-timing rule + ("don't move the process onto the graph too early")? Note: MD-14 already makes the *minimal* engine + slice — extract → graph, no more — sufficient for self-validation, exactly what this strategy needs. > This is parked at *direction altitude* on purpose. It needs the minimal engine substrate before it can > be designed; designing it in prose would just reproduce the loop we exited. --- -## §3 — Pre-grill folds (consolidated inventory) - -The grill must open onto **(a) a self-consistent base** and **(b) an agenda of only genuinely-open -decisions.** Today neither holds. Two fresh fold sessions fix that. (Naming note: these fold sessions are -**Fold-A / Fold-B** to avoid colliding with the *review's* findings F1–F7, which are different things — -see the ledger, §6.) - -### Fold-A — the UL restructure — ✅ DONE 2026-06-10 -*Executed as planned:* `ubiquitous-language.md` is now the lean glossary (terms · relations · worked -dialogue · flagged ambiguities · term ledger, with an old-§ map in the header); the exposition was rehomed -**into `00`–`07`** (the lean option won — measured overlap was ~95%, so rehoming was mostly dedup plus a few -genuine insertions: the discipline ≈ kind/section mapping into `06` §6, UML alignment + relation-granularity -rationale into `02` §6, many-packs membership into `02` §4); R1/R2 applied; every pointer re-aimed -(AGENTS.md, concept README, DECISIONS.md). Original scope kept below for the record. -The current `docs/concept/ubiquitous-language.md` is a **design-synthesis document wearing a UL hat** — an -artifact of the 8-hour grill that had to invent *and* name the model in one breath. A true Evans UL is -lean. Split the one doc into its three real concerns: -- **Glossary** (lean): `Term · Definition · Aliases-to-avoid`, grouped by concept area, + relationships + - a worked dialogue + flagged ambiguities. The §0–§8 *terms* distilled to one-liners. -- **Design** (the exposition): rehome the §0–§8 model/rubric/principle reasoning. *Open judgment call:* - into the existing `00`–`07` (dedup — leaner; they're already derived from it) **or** a new dedicated - design-synthesis doc above `00`–`07`. Lean: into `00`–`07`. Confirm the real overlap first. -- **Rationale**: already `DECISIONS.md`. -- **Re-aim every pointer** that calls `ubiquitous-language.md` "the model + terminology, read first" — - AGENTS.md nav table, README, cross-refs, and `plans/03`'s base-first method (model → `00`–`07`, terms → - the lean glossary). -- **Absorb the two anchor/source wording reconciliations** along the way (they live in the §2/§4 you're - rewriting): "anchor = identity only" vs "anchored = a binding" → unify to *binding assertion only*; and - "no consumer reads source" → *links-into-the-graph OK, independent re-parse forbidden*. -- **Discipline:** this is **distillation + rehoming, not re-deciding.** Terms and model don't change — - only where they live. So it does *not* need a grill, but it does need fresh, careful scope and a - pointer-integrity pass. - -### Fold-B — the resolvable-now sweep — ✅ DONE 2026-06-10 -*Executed as planned:* `04` §2 reconciled to the binding-only `specTest` (R3 applied); the generic-anchor -shape and the open-questions home recorded **ACCEPTED** in `DECISIONS.md` (MD-8 / MD-9); `plans/03` slimmed -to the six genuinely-open decisions. Original scope kept below for the record. -Fold the already-determined items out of the grill's path: -- **Reconcile the `04` doc's `specTest` signature to binding-only** (no executing `run` callback; the - code already conforms — doc fidelity fix). -- **Record the generic-anchor shape as decided**: generalize `anchorImplementation` into a `codeAnchor` - over `impl` / `api` / `component`; sibling-builders alternative noted and rejected. (Execution rides - the Slice-2 anchor work + the example's missing api-anchor.) -- **Record the open-questions home as decided**: the honesty check must read `intent.openQuestions` (not - `design`/`decision`). Direction is determined; *execution stays Wave B* (it's a code change). -- **Slim `plans/03`** to the **six genuinely-open** decisions (§6 ledger): type-sections, - prose-vs-ref-incl-examples, collapse-the-floor-validator, AuthoredModel-vs-graph, kind-aware-floor, - `.spec.ts`-collision. Collapse the "resolvable-now" section to a one-line pointer. - -### Optional — archive the artifacts — ✅ DONE 2026-06-10 -All five scratch artifacts (both earlier reviews, the adversarial review + its prompt, the fold-session -prompt) moved into tracked **`reviews/`** with a README mapping each to where its durable findings landed. -The gen-1 study was chat-only; its takeaway is formalized in §0/§2 here. - -### Then — the grill -A fresh `grill-with-docs` session on `plans/03`, now opening onto a lean base + a tight six-item agenda. -It resolves the genuinely-open decisions against the base, ratifies inline, and rewrites `plans/02`'s -Wave B execution-ready. +## §3 — Pre-grill folds — ✅ DONE 2026-06-10 ---- - -## §4 — Sequencing & done-criteria +Executed as planned, in fresh scoped sessions. **Fold-A** (UL restructure): `ubiquitous-language.md` became +the lean Evans glossary; the §0–§8 model exposition rehomed into `00`–`07` (~95% measured overlap — mostly +dedup); R1/R2 applied; every pointer re-aimed. **Fold-B** (resolvable-now sweep): `04` §2 reconciled to +binding-only `specTest` (R3); MD-8/MD-9 recorded; `plans/03` slimmed to the six open decisions. **Archive:** +the five scratch artifacts moved into tracked `reviews/` (see its README); the gen-1 study was chat-only, +formalized in §0/§2 here. *(Naming note: Fold-A/Fold-B are fold sessions — **not** the review's findings +F1–F7; see §6.)* -1. ~~**Fold-A** (UL restructure)~~ — ✅ DONE 2026-06-10: lean glossary; exposition rehomed into `00`–`07`; - pointers re-aimed; R1/R2 applied; nothing in the model changed, only relocated; repo coherent. -2. ~~**Fold-B** (resolvable-now sweep)~~ — ✅ DONE 2026-06-10: `04` reconciled (R3); MD-8/MD-9 recorded; - `plans/03` down to six open decisions. -3. ~~**(optional) archive**~~ — ✅ DONE 2026-06-10: `reviews/` tracked. -4. ~~**Grill**~~ — ✅ DONE 2026-06-10: all six resolved + recorded (**MD-10…MD-15**); base reconciled - (`02`/`03`/`04`/`05` + glossary + `00`/`jtbd-01` for the `.sdp.ts` rename); `plans/02` Wave B rewritten - execution-ready; H7's optional leftovers finalized. -5. **Then** Wave B execution ← **NEXT**, and — gated on a minimal engine — the **crippled graph** (§2) - graduates from direction to design. (MD-14's one-path rule makes the minimal engine slice — extract → - graph — sufficient for self-validation, exactly what §2 needs.) +## §4 — Sequencing — ✅ DONE through the grill -> Fold-A precedes the grill for a real reason, not tidiness: the grill *edits the base* (typed sections → -> §2/§3; kind-aware floor → §6). If the model has already moved into `00`–`07`, the grill knows its edits -> land in the design docs, not the glossary. Restructure-first tells the grill *where to write*. +Fold-A → Fold-B → archive → **grill** all executed 2026-06-10 (grill outcome: MD-10…MD-15; base reconciled; +`plans/02` Wave B rewritten execution-ready; H7 leftovers finalized). **Then → Wave B execution ← NEXT**, +and — gated on a minimal engine — the crippled graph (§2) graduates from direction to design. ---- +## §5 — Scoped session prompts (Fold-A · Fold-B · the grill kickoff) -## §5 — Scoped session prompts (copy-paste to launch cold) - -> The Fold-A / Fold-B prompts below were **executed 2026-06-10** and are kept for the record; the live one -> is the **grill kickoff**. - -### Fold-A prompt — executed ✅ -> Scoped doc-restructure, no model changes. Read `docs/concept/ubiquitous-language.md`, `00`–`07`, -> `README.md`, `AGENTS.md`, and `plans/03` + `plans/04`. The current `ubiquitous-language.md` is a -> design-synthesis doc wearing a UL hat; split it into a **lean Evans glossary** (Term · Definition · -> Aliases-to-avoid, grouped, + relationships + a worked dialogue + flagged ambiguities) and **rehome the -> §0–§8 exposition into `00`–`07`** (dedup against them; decide consciously vs. a new design-synthesis -> doc). Re-aim every pointer that treats the UL doc as "the model, read first." While rewriting §2/§4, -> apply the two wording reconciliations (anchor = *binding assertion only*; consumers may *link to* graph -> source but not *re-parse* it). **Constraint: distillation + rehoming only — do not re-decide the model; -> terms and meanings stay identical, only their home changes.** End green: no dangling cross-references. - -### Fold-B prompt — executed ✅ -> Small, decision-free sweep. Read `plans/03`, `plans/04`, `docs/concept/DECISIONS.md`, and -> `docs/concept/04-authoring-and-binding.md`. (1) Reconcile the `04` doc's `specTest` description to a -> binding-only signature (no executing `run` callback — the code in `src/model/anchors.ts` already -> conforms). (2) In `DECISIONS.md`, record two items as **decided**: the generic-anchor DSL shape -> (`codeAnchor` over `impl`/`api`/`component`; siblings rejected) and the open-questions canonical home -> (`intent.openQuestions`; note execution stays Wave B in `plans/02`). (3) Slim `plans/03` to the six -> genuinely-open decisions and collapse its "resolvable-now" (§3a) to a one-line pointer. - -### Grill kickoff — executed ✅ 2026-06-10 (kept for the record) -> `grill-with-docs` on `plans/03` — six open decisions (D1 typed-sections · D2 prose-vs-ref incl. -> `examples` · D3 floor-validator collapse · D4 AuthoredModel-vs-graph · D7 kind-aware `defined` floor · -> D8 `.spec.ts` collision), against the now-lean base (terms: the glossary; model: `00`–`07`). Ratify -> inline; record in `DECISIONS.md`; rewrite `plans/02` Wave B execution-ready. -> *(Outcome: MD-10…MD-15; resolution table in `plans/03`'s header. The live next prompt is Wave B — -> `plans/02` §4.)* +This section held the copy-paste prompts that launched each cold session; all three were executed +2026-06-10 and the prompt texts removed. The fold-session prompt is archived verbatim as +`reviews/05-plan-finalization-prompt.md`; the grill's outcome is `plans/03`'s resolution table. --- -## §6 — Decision ledger in meaning (so the codes resolve) - -The antidote to code-noise: every handle, decoded. Two separate code-spaces — don't conflate them. - -**Open decisions — ✅ all RESOLVED in the 2026-06-10 grill (resolution table: `plans/03` header; rationale: `DECISIONS.md`):** - -| Handle | Meaning | Resolved as | -|---|---|---| -| **D1** | How much to *type* sections now. The linchpin. | **MD-11** — the typing law: floor-bearing ⟹ closed-typed (six, incl. `decision`; `status` rejected) | -| **D2** | The prose-vs-ref-list duality, across **both** `behavior.rules` and `behavior.examples`. | **MD-10** — content only; relations carry linkage; promotion exclusive; GWT nests in entries | -| **D3** | Collapse the readiness-floor validator (clause-ids 4×; decorative `authoredPaths`). | **MD-13** — table = single source of truth; named predicates `(spec, model)`; derived clause-id union | -| **D4** | The `AuthoredModel` seam vs. the one graph. (Interacts with the crippled graph, §2.) | **MD-14** — one validation path via the graph; `AuthoredModel` retires as a public seam (exec Slice 1/3) | -| **D7** | Make the floor **kind-aware** — the example *pads* to clear kind-blind clauses. | **MD-12** — evidence kind-conditional at **both** `scoped` and `defined`; overlays dissolve; example de-pads | -| **D8** | The `.spec.ts` extension collides with the JS test-runner glob. | **MD-15** — `.sdp.ts` (the `.stories.tsx` pattern) | - -**Resolvable-now (folded out — all ✅ done 2026-06-10):** - -| Handle | Meaning | Disposition | -|---|---|---| -| **R1** | Anchor "identity only" vs "a binding" → unified to *binding assertion only*. | ✅ applied in Fold-A (glossary, `01`, `04` §2) | -| **R2** | "No consumer reads source" → links-into-graph OK, independent re-parse forbidden. | ✅ applied in Fold-A (`01` P2, `03` §4) | -| **R3** | `04` doc's `specTest` → binding-only (no `run`); code already conforms. | ✅ applied in Fold-B (`04` §2) | -| **D6** | Generic-anchor shape → `codeAnchor` over impl/api/component. | ✅ recorded **MD-8**; exec Slice 2 (plan 02 H10) | -| **H2-dir** | Open-questions home = `intent.openQuestions`. | ✅ recorded **MD-9**; exec Wave B (plan 02 H2) | - -**Wave B (in `plans/02`, decision-gated; execute after the grill):** H2 (open-questions home fix), H3 -(`constraints` → array), H4 (section-ref referential integrity), H5 (trim floor validator), H10 (api -anchor in the example). Plus the carried review backlog: the `ref`-naming note, the aggregate-`family` -mislabel, and the `modelRefs`-must-be-`kind:"model"` check. +## §6 — Decoder (so historical codes resolve) -**Fold sessions:** **Fold-A** = UL restructure · **Fold-B** = resolvable-now sweep. *(These are NOT the -review's findings F1–F7 — that F-space is unrelated: review F1 = the kind-blind floor, now **D7**; F5 = -the examples duality, folded into **D2**; F6 = the `specTest` doc, now **R3**; F2/F3/F4 = the carried -backlog above; F7 = done in Wave A.)* +Two separate code-spaces — don't conflate them. **Open decisions D1–D8** (all resolved): the resolution table +is `plans/03`'s header; rationale in `DECISIONS.md` MD-10…MD-15. **Resolvable-now** (all folded out): R1/R2/R3 ++ MD-8 (`codeAnchor`, exec Slice 2) + MD-9 (`intent.openQuestions`, exec Wave B) in `DECISIONS.md`. +**Fold-A / Fold-B** = the fold sessions (§3). **The review's F-space is unrelated to all of these:** F1 = the +kind-blind floor → D7/MD-12 · F5 = the examples duality → D2/MD-10 · F6 = the `specTest` doc → R3 · +F2/F3/F4 = the carried backlog (`plans/02` §4) · F7 = done in Wave A. --- @@ -254,7 +125,3 @@ backlog above; F7 = done in Wave A.)* taxonomy and an FSM. The scaffold stays a deliberate non-system. - **Don't perfect the markdown.** It's temporary substrate; the graph replaces it. Lean glossary, not a beautiful one. -- **Don't grill the resolvable-now items.** They need ratification, not deliberation (Fold-B handles - them). Putting a no-op in front of a grill is workflow-gating in miniature. -- **Don't fold in a loaded session.** Fresh + scoped, always. -- **Don't re-decide the model during Fold-A.** Distillation + rehoming only. From 907c75aafbbdd88aced13afd02d9a530729b7d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 04:57:09 +0200 Subject: [PATCH 16/29] Wave B executed: typed sections, kind-conditional floor table, de-padded example, .sdp.ts (MD-9..MD-15) B1: six floor-bearing sections closed-typed (MD-11 on MD-10); in-section honesty bypass fails typecheck, pinned as a compile-time fixture B2: readiness-floor.ts is THE table (MD-12/MD-13): kind-blind clauses + per-kind evidence map, derived clause-id union, one generic evaluator; blocking open questions read intent.openQuestions (MD-9) B3: example de-padded; promoted children are the floor evidence; GWT nested; no decision.status; fixtures re-authored + 4 new B4: spec files renamed *.sdp.ts (MD-15) F2/F3 backlog closed (ref() doc note; aggregate report carries no single family). plans/02 retired to a done-record; plans/04, AGENTS, UL, 02, 07 status/wording reconciled. npm run check green. --- AGENTS.md | 16 +- docs/concept/02-core-model.md | 2 +- .../07-mvp-roadmap-and-open-questions.md | 17 +- docs/concept/ubiquitous-language.md | 3 +- examples/checkout-v1/model.ts | 20 +- ...{checkout.pack.ts => checkout.pack.sdp.ts} | 0 ...fecycle.spec.ts => order-lifecycle.sdp.ts} | 8 +- ...ec.ts => create-order-invalid-cart.sdp.ts} | 21 +- ...spec.ts => create-order-valid-cart.sdp.ts} | 25 +- ...eate-order.spec.ts => create-order.sdp.ts} | 10 +- ...le.spec.ts => order-inventory-rule.sdp.ts} | 0 ...pec.ts => order-latency-constraint.sdp.ts} | 16 +- ...gement.spec.ts => order-management.sdp.ts} | 0 ...order-model.spec.ts => order-model.sdp.ts} | 5 - ...l-rule.spec.ts => order-total-rule.sdp.ts} | 0 plans/02-phase0-hardening.md | 238 ++++-------- ...zation-gap-strategy-and-pre-grill-folds.md | 16 +- src/ids.ts | 6 + src/model/sections.ts | 80 +++- src/validate/contracts.ts | 7 +- src/validate/readiness-floor.ts | 355 ++++++++++++++---- src/validate/validators.ts | 324 +--------------- test/builders.typecheck.ts | 28 +- test/fixtures.test.ts | 10 +- test/fixtures/authored-model.fixtures.ts | 156 +++++++- test/readiness.test.ts | 160 +++++--- test/validators.test.ts | 17 +- 27 files changed, 816 insertions(+), 724 deletions(-) rename examples/checkout-v1/specs/{checkout.pack.ts => checkout.pack.sdp.ts} (100%) rename examples/checkout-v1/specs/decisions/{order-lifecycle.spec.ts => order-lifecycle.sdp.ts} (80%) rename examples/checkout-v1/specs/orders/{create-order-invalid-cart.spec.ts => create-order-invalid-cart.sdp.ts} (65%) rename examples/checkout-v1/specs/orders/{create-order-valid-cart.spec.ts => create-order-valid-cart.sdp.ts} (59%) rename examples/checkout-v1/specs/orders/{create-order.spec.ts => create-order.sdp.ts} (74%) rename examples/checkout-v1/specs/orders/{order-inventory-rule.spec.ts => order-inventory-rule.sdp.ts} (100%) rename examples/checkout-v1/specs/orders/{order-latency-constraint.spec.ts => order-latency-constraint.sdp.ts} (65%) rename examples/checkout-v1/specs/orders/{order-management.spec.ts => order-management.sdp.ts} (100%) rename examples/checkout-v1/specs/orders/{order-model.spec.ts => order-model.sdp.ts} (87%) rename examples/checkout-v1/specs/orders/{order-total-rule.spec.ts => order-total-rule.sdp.ts} (100%) diff --git a/AGENTS.md b/AGENTS.md index dd4c796..901b4e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,9 +10,9 @@ language, and the JTBD stories together **are the spec** — the product's own t design*) applied to itself: there is no separate PRD. > **Status:** concept ratified and locked; **Slice 0 (Phase 0 — the protocol as typed code) is built, green, and -> hardened** (Wave A done; all post-Session-1 decisions resolved, DECISIONS MD-8…MD-15). The live per-session build -> plan is in **`plans/`**; the slice roadmap is **`docs/concept/07`**. (`plans/` is the source of truth for "what -> now.") +> hardened — hardening complete** (Waves A + B done; all post-Session-1 decisions resolved *and executed*, +> DECISIONS MD-8…MD-15). The live per-session build plan is in **`plans/`**; the slice roadmap is +> **`docs/concept/07`**. (`plans/` is the source of truth for "what now.") ## The frame @@ -71,11 +71,11 @@ read it before writing code. | **4** | The agent surface (the `reader` — entry adapters + impact) + the Design Review / one generated read-only view — both fully derived. | | **5** | Polish: the `sdp` CLI (`build` · `validate` · maybe `explain`/`search`), error messages, the documented example, a clean-repo determinism test. | -**Where we are now (`plans/`):** Session 1 (Slice 0 + bootstrap) is done and hardened; the post-Session-1 -decisions are all resolved (grill 2026-06-10, MD-10…MD-15). **Next session = Wave B** (`plans/02` §4): typed -floor-bearing sections → the kind-conditional floor table → de-pad the example → the `.sdp.ts` rename. **Then** -Slice 1, the `ts-morph` extractor (deterministic rebuild P3 + graceful partial extraction L3); the hardened -example becomes its first real input. +**Where we are now (`plans/`):** Phase 0 is complete — Session 1 (Slice 0 + bootstrap) plus both hardening +waves (`plans/02`, now a done-record): typed floor-bearing sections, the kind-conditional floor table, +the de-padded example, the `.sdp.ts` spec files. **Next session = Slice 1**, the `ts-morph` extractor +(deterministic rebuild P3 + graceful partial extraction L3; reads `*.sdp.ts` from day one — MD-15); the +hardened example becomes its first real input. > **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the DSL and extractor are > forced to be usable before they are finished. If the example doesn't typecheck, fix the DSL — not the example. diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index 392ce34..48cecf9 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -127,7 +127,7 @@ Sections carry the detail. They are the **extension surface**: the system grows | Section | Carries | Notes | |---|---|---| -| `intent` | actor, problem, outcome, value, risks, assumptions, open questions | `openQuestions` may be flagged `blocking` to prevent stating a readiness past `defined`. | +| `intent` | actor, problem, outcome, value, risks, assumptions, open questions | `openQuestions` may be flagged `blocking` to prevent stating `defined` or `ready` (MD-9). | | `behavior` | rules (prose), examples (prose or structured Given/When/Then), flows | **Content only — never refs** (the duality rule below). An example entry matures *in place*: prose → a structured `{ given, when, then }` entry → (promoted) a child `example` spec backed by a verifier. | | `constraints[]` | a `flavor` (quality / security / performance / compliance / operational / policy), a statement, an optional `target`, optional `measurableBy` | A `performance` constraint with a measurable `target` is an NFR. `target` must be machine-readable (`p95 < 300ms`, not "fast enough") to state `defined`+. | | `model` | domain terms (vocabulary only) — `terms: Record` | Used for pack-level coherence checks. Richer concept structures (typed concepts, attributes) are a **named deferral**; when one lands, the typing law below pulls it into the closed shape. | diff --git a/docs/concept/07-mvp-roadmap-and-open-questions.md b/docs/concept/07-mvp-roadmap-and-open-questions.md index f34d78e..7fa6547 100644 --- a/docs/concept/07-mvp-roadmap-and-open-questions.md +++ b/docs/concept/07-mvp-roadmap-and-open-questions.md @@ -97,20 +97,21 @@ land in the roadmap at the right altitude. Ordering reflects the synthesis's pri - **① Authoring ergonomics — the headline forward risk; a named Slice-2 concern.** There is *no authoring-ergonomics workstream* anywhere in `00`–`07` today (the MVP CLI is just `build`/`validate`), yet if authoring feels heavy, authors (human **and** agent) avoid the system or overfit specs to satisfy tooling. The - first lever is **typed sections** (autocomplete + shape guardrails); then great error messages and `sdp validate - --watch`; later `sdp new spec` / `sdp explain`. Threads back to the anti-padding rule: make *dishonesty* fail - without rewarding low-signal filler (a floor to clear, never a quota to fill). + first lever — **typed sections** (autocomplete + shape guardrails) — **landed in the Phase-0 hardening + (MD-11)**; the next are great error messages and `sdp validate --watch`; later `sdp new spec` / + `sdp explain`. Threads back to the anti-padding rule: make *dishonesty* fail without rewarding + low-signal filler (a floor to clear, never a quota to fill). - **② Golden-graph fixture — at Slice 1; keep it distinct from `--check-clean`.** Adopt **both**, labeled distinctly: a **determinism self-check** (`03` §2 — rebuild twice, assert **byte-identical**; a self-comparison, **never** a diff against a committed `generated/` artifact, which is gitignored, L8) **and** a **correctness oracle** (a committed `fixtures/order-management/expected/graph.json` — "did the extractor produce the *right* graph," legitimate because it lives in `fixtures/`, not `generated/`). Make paths **repo-relative / POSIX**, and decide consciously whether **line numbers** enter the golden (deterministic, but brittle to unrelated edits). -- **③ Derived-readiness banner in the MVP view — at Slice 4, but blocked on the open-questions-home fix (H2).** - *"Stated readiness: ready · Structural floor reached: defined · Problem: blocking open question."* Teaches the - core honesty concept (stated, then checked); cheaply enabled by a floor evaluator that reports *which* clause - fails. **Do not pull it forward before H2** — today the floor reads open questions from the wrong section, so the - banner would confidently display the wrong thing. +- **③ Derived-readiness banner in the MVP view — at Slice 4; its blocker is cleared.** *"Stated readiness: + ready · Structural floor reached: defined · Problem: blocking open question."* Teaches the core honesty + concept (stated, then checked); cheaply enabled by the floor evaluator, which reports *which* clause + fails. The old blocker — the floor reading open questions from the wrong section — was fixed in the + Phase-0 hardening (MD-9: the floor reads `intent.openQuestions`). - **④ `implemented` is a UI hazard — at Slice 4, view-label only.** Model semantics are settled (DECISIONS MD-7: binding/existence, never liveness). Keep the internal fact name `implemented` (it powers the `implemented ∧ ¬ready` drift query), but render binding language in views: *"Implementation binding: present / Verifier binding: diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index e83737d..98f59c3 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -178,7 +178,8 @@ artifact** — approval provenance is git-native, never an authored primitive). - **"epistemic boundary"** is a *working name* for the humans-assert-intent / machines-assert-structure division (`01`); a friendlier Studio-facing name is a minor open item. - **`ref()`** in the DSL is today a **spec-only** reference builder wearing a generic name (it rejects - `pack:`/`doc:` targets) — a doc-note item on the Wave-B backlog (`plans/02`). + `pack:`/`doc:` targets) — documented on the export (`src/ids.ts`); revisit when `doc:`-target + relations or pack-targeting arrive. ## Term ledger (locked / rejected / resolved) diff --git a/examples/checkout-v1/model.ts b/examples/checkout-v1/model.ts index a5f666b..af8d69e 100644 --- a/examples/checkout-v1/model.ts +++ b/examples/checkout-v1/model.ts @@ -1,15 +1,15 @@ import type { AuthoredModel } from "@libar-dev/software-delivery-protocol"; -import { checkoutV1Pack } from "./specs/checkout.pack.js"; -import { orderLifecycleDecisionSpec } from "./specs/decisions/order-lifecycle.spec.js"; -import { createOrderInvalidCartSpec } from "./specs/orders/create-order-invalid-cart.spec.js"; -import { createOrderValidCartSpec } from "./specs/orders/create-order-valid-cart.spec.js"; -import { createOrderSpec } from "./specs/orders/create-order.spec.js"; -import { orderInventoryRuleSpec } from "./specs/orders/order-inventory-rule.spec.js"; -import { orderLatencyConstraintSpec } from "./specs/orders/order-latency-constraint.spec.js"; -import { orderManagementSpec } from "./specs/orders/order-management.spec.js"; -import { orderModelSpec } from "./specs/orders/order-model.spec.js"; -import { orderTotalRuleSpec } from "./specs/orders/order-total-rule.spec.js"; +import { checkoutV1Pack } from "./specs/checkout.pack.sdp.js"; +import { orderLifecycleDecisionSpec } from "./specs/decisions/order-lifecycle.sdp.js"; +import { createOrderInvalidCartSpec } from "./specs/orders/create-order-invalid-cart.sdp.js"; +import { createOrderValidCartSpec } from "./specs/orders/create-order-valid-cart.sdp.js"; +import { createOrderSpec } from "./specs/orders/create-order.sdp.js"; +import { orderInventoryRuleSpec } from "./specs/orders/order-inventory-rule.sdp.js"; +import { orderLatencyConstraintSpec } from "./specs/orders/order-latency-constraint.sdp.js"; +import { orderManagementSpec } from "./specs/orders/order-management.sdp.js"; +import { orderModelSpec } from "./specs/orders/order-model.sdp.js"; +import { orderTotalRuleSpec } from "./specs/orders/order-total-rule.sdp.js"; import { createOrderUseCaseAnchor } from "./src/orders/create-order.use-case.js"; import { createOrderValidCartTest } from "./test/orders/create-order.valid-cart.test.js"; diff --git a/examples/checkout-v1/specs/checkout.pack.ts b/examples/checkout-v1/specs/checkout.pack.sdp.ts similarity index 100% rename from examples/checkout-v1/specs/checkout.pack.ts rename to examples/checkout-v1/specs/checkout.pack.sdp.ts diff --git a/examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts b/examples/checkout-v1/specs/decisions/order-lifecycle.sdp.ts similarity index 80% rename from examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts rename to examples/checkout-v1/specs/decisions/order-lifecycle.sdp.ts index 4775ef9..432db5d 100644 --- a/examples/checkout-v1/specs/decisions/order-lifecycle.spec.ts +++ b/examples/checkout-v1/specs/decisions/order-lifecycle.sdp.ts @@ -10,20 +10,14 @@ export const orderLifecycleDecisionSpec = spec({ outcome: "Decide when checkout-v1 may create an order.", value: "The authored example has one stable lifecycle rule for success and rejection paths.", }, - behavior: { - rules: [ - "Order creation happens only after cart validation and inventory checks succeed.", - "Rejected carts do not create partial orders.", - ], - }, decision: { - status: "accepted", decision: "Create orders only after cart validation confirms non-empty input and sufficient inventory.", rationale: [ "The valid-cart and invalid-cart examples need one consistent gate.", "Rejecting before persistence keeps the tracer bullet small and internally consistent.", ], + consequences: ["Rejected carts never create partial orders."], }, relations: [refines(specId("spec:orders.create-order"))], }); diff --git a/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts b/examples/checkout-v1/specs/orders/create-order-invalid-cart.sdp.ts similarity index 65% rename from examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts rename to examples/checkout-v1/specs/orders/create-order-invalid-cart.sdp.ts index d995f59..4b58962 100644 --- a/examples/checkout-v1/specs/orders/create-order-invalid-cart.spec.ts +++ b/examples/checkout-v1/specs/orders/create-order-invalid-cart.sdp.ts @@ -12,15 +12,18 @@ export const createOrderInvalidCartSpec = spec({ "The authored example captures the rejection path without adding invalid fixtures to the model.", }, behavior: { - examples: ["Out-of-stock or empty carts are rejected before an order is created."], - given: [ - "A cart is empty or contains at least one item without available inventory.", - "The cart is submitted for order creation.", - ], - when: ["The create-order use case validates the cart."], - then: [ - "No order is created.", - "The caller receives a validation error explaining why the cart is invalid.", + examples: [ + { + given: [ + "A cart is empty or contains at least one item without available inventory.", + "The cart is submitted for order creation.", + ], + when: ["The create-order use case validates the cart."], + then: [ + "No order is created.", + "The caller receives a validation error explaining why the cart is invalid.", + ], + }, ], }, verification: { diff --git a/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts b/examples/checkout-v1/specs/orders/create-order-valid-cart.sdp.ts similarity index 59% rename from examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts rename to examples/checkout-v1/specs/orders/create-order-valid-cart.sdp.ts index 75048ba..a4d1528 100644 --- a/examples/checkout-v1/specs/orders/create-order-valid-cart.spec.ts +++ b/examples/checkout-v1/specs/orders/create-order-valid-cart.sdp.ts @@ -11,17 +11,20 @@ export const createOrderValidCartSpec = spec({ value: "The authored example demonstrates the happy path for create-order.", }, behavior: { - examples: ["Valid cart becomes an order with the computed total."], - given: [ - "A customer has a cart with one or more line items.", - "Every cart item is in stock.", - "Each line item has a positive quantity and a unit price.", - ], - when: ["The customer submits the cart for order creation."], - then: [ - "An order is created.", - "The order total equals the sum of quantity multiplied by unit price for each line item.", - "The order contains the original cart lines.", + examples: [ + { + given: [ + "A customer has a cart with one or more line items.", + "Every cart item is in stock.", + "Each line item has a positive quantity and a unit price.", + ], + when: ["The customer submits the cart for order creation."], + then: [ + "An order is created.", + "The order total equals the sum of quantity multiplied by unit price for each line item.", + "The order contains the original cart lines.", + ], + }, ], }, verification: { diff --git a/examples/checkout-v1/specs/orders/create-order.spec.ts b/examples/checkout-v1/specs/orders/create-order.sdp.ts similarity index 74% rename from examples/checkout-v1/specs/orders/create-order.spec.ts rename to examples/checkout-v1/specs/orders/create-order.sdp.ts index 9999fb4..3bf16fe 100644 --- a/examples/checkout-v1/specs/orders/create-order.spec.ts +++ b/examples/checkout-v1/specs/orders/create-order.sdp.ts @@ -1,7 +1,6 @@ import { constrainedBy, decidedBy, - ref, refines, spec, specId, @@ -18,13 +17,8 @@ export const createOrderSpec = spec({ outcome: "Turn a valid cart into an order.", value: "Customers can complete purchases without the example modeling the rest of checkout.", }, - behavior: { - rules: [ref("spec:orders.order-total-rule"), ref("spec:orders.order-inventory-rule")], - examples: [ - ref("spec:orders.create-order.valid-cart"), - ref("spec:orders.create-order.invalid-cart"), - ], - }, + // No behavior section: the rules and examples are promoted children — their refines/verifies + // relations are the linkage of record (MD-10), and promoted evidence clears the floor. relations: [ refines(specId("spec:orders.order-management")), constrainedBy(specId("spec:orders.order-latency-constraint")), diff --git a/examples/checkout-v1/specs/orders/order-inventory-rule.spec.ts b/examples/checkout-v1/specs/orders/order-inventory-rule.sdp.ts similarity index 100% rename from examples/checkout-v1/specs/orders/order-inventory-rule.spec.ts rename to examples/checkout-v1/specs/orders/order-inventory-rule.sdp.ts diff --git a/examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts b/examples/checkout-v1/specs/orders/order-latency-constraint.sdp.ts similarity index 65% rename from examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts rename to examples/checkout-v1/specs/orders/order-latency-constraint.sdp.ts index 453f570..f3f5a64 100644 --- a/examples/checkout-v1/specs/orders/order-latency-constraint.spec.ts +++ b/examples/checkout-v1/specs/orders/order-latency-constraint.sdp.ts @@ -10,14 +10,12 @@ export const orderLatencyConstraintSpec = spec({ outcome: "Keep create-order fast enough for interactive checkout.", value: "Customers are not left waiting after submitting a valid cart.", }, - behavior: { - rules: ["The create-order path stays inside a machine-readable latency budget."], - }, - constraints: { - statement: "Create-order should respond within the checkout latency budget.", - target: "latency.p95.lt:250ms", - unit: "ms", - percentile: 95, - }, + constraints: [ + { + flavor: "performance", + statement: "Create-order should respond within the checkout latency budget.", + target: "latency.p95.lt:250ms", + }, + ], relations: [refines(specId("spec:orders.create-order"))], }); diff --git a/examples/checkout-v1/specs/orders/order-management.spec.ts b/examples/checkout-v1/specs/orders/order-management.sdp.ts similarity index 100% rename from examples/checkout-v1/specs/orders/order-management.spec.ts rename to examples/checkout-v1/specs/orders/order-management.sdp.ts diff --git a/examples/checkout-v1/specs/orders/order-model.spec.ts b/examples/checkout-v1/specs/orders/order-model.sdp.ts similarity index 87% rename from examples/checkout-v1/specs/orders/order-model.spec.ts rename to examples/checkout-v1/specs/orders/order-model.sdp.ts index 06413a5..a81661e 100644 --- a/examples/checkout-v1/specs/orders/order-model.spec.ts +++ b/examples/checkout-v1/specs/orders/order-model.sdp.ts @@ -10,11 +10,6 @@ export const orderModelSpec = spec({ outcome: "Define the core terms used by the checkout-v1 order-management slice.", value: "Specs, code, and tests use the same vocabulary for carts, orders, and inventory.", }, - behavior: { - rules: [ - "The authored slice uses shared term definitions instead of inlining conflicting vocabulary.", - ], - }, model: { terms: { cart: "A customer-selected set of line items that has not yet become an order.", diff --git a/examples/checkout-v1/specs/orders/order-total-rule.spec.ts b/examples/checkout-v1/specs/orders/order-total-rule.sdp.ts similarity index 100% rename from examples/checkout-v1/specs/orders/order-total-rule.spec.ts rename to examples/checkout-v1/specs/orders/order-total-rule.sdp.ts diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index c2a43e1..73c669b 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -1,167 +1,97 @@ # Phase 0 Hardening — Fidelity & Simplification Pass (post-Session-1) -> **Status: EXECUTION · Wave A ✅ done · Wave B EXECUTION-READY — run §4 as the next code session.** -> Code hardening on the already-implemented Phase 0: fidelity corrections + net complexity reductions, -> covering the gap between *"Slice 0 is green"* and *"Slice 0 is a faithful, lean foundation the -> extractor can safely build on."* Stays inside the Slice 0 boundary — **no extractor, no graph -> emission, no graph gate** — except where a fix must be *designed now* so Slice 1 doesn't inherit a -> landmine. Every decision this plan was gated on is **resolved**: R1/R2/R3 applied; **MD-8/MD-9** -> (pre-grill folds) and **MD-10…MD-15** (the 2026-06-10 grill, agenda `plans/03`) recorded in -> `docs/concept/DECISIONS.md`. Forward-looking acceptance criteria live in `docs/concept/07` §6. -> **Branch:** `feature/mvp-init` · **Repo state:** Session 1 + Wave A, `npm run check` green. +> **Status: ✅ EXECUTED — Wave A done · Wave B done 2026-06-10 · `npm run check` green.** +> Retired to a done-record: the outcomes live in the code, the rationale in `docs/concept/DECISIONS.md` +> (R1–R3, MD-8…MD-15), the provenance in git history and `reviews/`. Still live here: the baseline +> invariants (§1 — the standing regression gate), the carry-forwards riding later slices (§4), and the +> deferred scope (§5). Forward-looking acceptance criteria live in `docs/concept/07` §6. +> **Branch:** `feature/mvp-init` · **Next: Slice 1 — the `ts-morph` extractor** (`docs/concept/07`); +> the hardened example is its first real input. --- -## §0 — Framing commitments (carried from `AGENTS.md` + the founding-ideation review) +## §0 — Framing commitments (carried into the next slices) -- **This is not over-perfection.** Every item is either (a) a divergence from the ratified base - (`ubiquitous-language.md`, `02`, `04`, `05`) or (b) a *removal* of complexity that masks the design. - General code-quality/type-coverage polish stays deferred ("let the design breathe"). -- **Tracer-bullet discipline.** The example must keep forcing the DSL to be usable — and, from Slice 1 - on, must survive *static* extraction. H1 (done) was exactly where it would have broken that. -- **Phase 0 stays aggressively small.** *"Build the smallest executable conformance contract that Slice - 1 needs."* The standing test against every item: **does it shrink the contract or grow it?** A - readiness floor is **a floor to clear, never a quota to fill** (`05` §3, P4). +- Every item was (a) a divergence from the ratified base or (b) a *removal* of complexity masking the + design — never general code-quality polish ("let the design breathe"). +- **Tracer-bullet discipline.** The example keeps forcing the DSL to be usable — and, from Slice 1 on, + must survive *static* extraction. "Done" for Slice 1 includes *the example survives static + extraction byte-for-byte*; the `sdp/spec-static` lint (`04` §1) is the earlier backstop. +- **Phase 0 stays aggressively small.** The standing test against every item: does it shrink the + conformance contract or grow it? A readiness floor is **a floor to clear, never a quota to fill**. -## §1 — Baseline: what is correct and must not regress +## §1 — Baseline invariants (verified through Wave B — the standing regression gate) - Trust-model boundaries intact: no extractor, no `graph.json`, no graph gate; delivery facts are - **derived-only types**; anchors are **identity-only** (`@ts-expect-error` fixtures prove they reject - readiness/intent/facts). + **derived-only types**; anchors are **identity-only** (`@ts-expect-error` fixtures prove rejection). - `graph/schema.ts` faithful and inert: `nodeType` vs `specKind` split, `claim` on every node/edge, delivery facts on `PrimitiveNode`, authored vs derived edge types separated. -- IDs (`02` §5): grammar + branding only, namespace policy correctly deferred. -- Relations (`02` §6): direction + `claim:"declared"` exactly per the table; verb-form names correct. -- Core `src/` is domain-neutral (zero order/checkout names). The example's two-edge verifier semantics +- IDs (`02` §5): grammar + branding only, namespace policy correctly deferred. Relations (`02` §6): + direction + `claim:"declared"` exactly per the table; verb-form names correct. +- Core `src/` is domain-neutral (zero order/checkout names); the example's two-edge verifier semantics match `02` "Verifier semantics" precisely. -- `AuthoredModel` honestly fenced (doc-comment + `deferredInSession1` clauses). +- `AuthoredModel` honestly fenced (doc-comment); it retires into (at most) an extractor-internal shape + when the extractor lands (MD-14, Slice 1/3) — never a second public validation seam. **Regression gate:** `npm run check` stays green; all invariants above still hold. -## §2 — The root tension — ✅ RESOLVED (MD-11 on MD-10) - -`src/model/sections.ts` types every section as `Record`; the base (**L9**, `02` §3) -intends **typed-but-optional** sections. Resolved by the typing law (MD-11 — floor-bearing ⟹ -closed-typed; six today) on MD-10's content-only `behavior` shape. §4/B1 carries the locked field shapes. - ---- +## §2 — The root tension — ✅ RESOLVED (MD-11 on MD-10) · executed as §4/B1 ## §3 — Wave A (decision-free) — ✅ DONE -All landed and verified; `npm run check` green. One line each — the full was/done/verified detail lives -in git history (provenance: `reviews/01`). - -- **H1 — the example is statically extractable (P5).** The `Object.fromEntries(...) as Record` + - `thenKey` indirection replaced with plain static literals. **Forward hook (Slice 1):** "done" must - include *the example survives static extraction byte-for-byte*; the `sdp/spec-static` lint (`04` §1) - is the earlier backstop. -- **H6 — tsup build simplified.** The shebang ships only on the CLI entry (no banner / strip-plugin / - `onSuccess` hook); `dist/index.js` is shebang-free. -- **H7 — hygiene.** `.tmp-scratch/**` prettier-ignored. The optional leftovers were **finalized in the - grill session**: `.sisyphus/` untracked; `package.json` carries description + Apache-2.0. -- **H8 — the validator fixture suite (`05` §5).** `test/fixtures/authored-model.fixtures.ts` + - `fixtures.test.ts`; four **active** fixtures, each pinning one validator outcome - (`valid-minimal-idea-spec` · `invalid-duplicate-id` · `invalid-scoped-without-relation` · - `invalid-defined-constraint-without-target`). **Gated stubs (`it.todo`), flipped by Wave B:** - `invalid-ready-with-blocking-question` (B2) · `invalid-hand-authored-delivery-fact-in-section` (B1 — - lands as a compile-time fixture). The extractor-era fixtures stay named for Slice 1+ - (`invalid-non-static-id` · `invalid-non-static-section` · `invalid-hand-authored-satisfies-edge` · - `invalid-ready-with-unresolved-dependency` · `invalid-ready-with-target-below-defined`). -- **H9 — type-level honesty defenses.** `@ts-expect-error` fixtures in `test/builders.typecheck.ts`: the - `Spec` envelope rejects top-level delivery facts; the `Pack` rejects `intent`/`readiness`/`constraints`. - The **in-section** bypass (`behavior: { "has-verifier": true }`) is closed only by B1's typed sections; - the envelope-defense caveat (excess-property / inline-literal scope, F7) is recorded in the file. - ---- - -## §4 — Wave B — ✅ UNBLOCKED 2026-06-10 (the grill resolved every gate: MD-10…MD-15) — EXECUTION-READY - -One execution session, four steps in order; `npm run check` green after each. The decision pointers are -the contract — when a shape question comes up mid-execution, the MD entry wins, then the base (`02`/`05`). - -### B1 — Type the six floor-bearing sections (MD-11 + MD-10; absorbs old H3) -- **`sections.ts`** — closed shapes (no index signature); fields optional per P7 (types describe shape, - validators decide completeness): - - `IntentSection { actor?; problem?; outcome?; value?; risks?: string[]; assumptions?: string[]; - openQuestions?: (string | { question: string; blocking?: boolean })[] }` - - `BehaviorSection { rules?: string[]; examples?: (string | { given: string[]; when: string[]; - then: string[] })[]; flows?: string[] }` — **content only, never refs** (MD-10) - - `constraints?: ConstraintSection[]` with `ConstraintSection { flavor?; statement: string; target?; - measurableBy? }` — the array shape (old H3); **drop the validator's dual array/record handling** - - `ModelSection { terms?: Record }` (richer concepts stay deferred) - - `VerificationSection { mode?: "manual" | "reviewed" | "contract" | "executable"; criteria?: string[] }` - - `DecisionSection { context?: string; decision?: string; rationale?: string[]; alternatives?: string[]; - consequences?: string[] }` — **no `status`** (MD-11; rejected vocabulary) - - `design` / `ui` stay open bags (`Record`). -- **Verify:** the in-section honesty bypass `behavior: { "has-verifier": true }` now **fails to typecheck** - — so the gated H8 fixture `invalid-hand-authored-delivery-fact-in-section` lands as a `@ts-expect-error` - compile-time fixture beside the H9 set (not a runtime fixture); `order-latency-constraint` authors a - constraints **array**. - -### B2 — Rewrite the floor as the kind-conditional table (MD-12 + MD-13 + MD-9; absorbs old H2 + H5) -- **`readiness-floor.ts` becomes THE table**, mirroring `05` §3 row-for-row: kind-blind structural clauses - + the per-kind evidence map (`scoped` = evidence *present*, `defined` = evidence *complete*). Rows carry - `{ id, description, predicate }` — named predicates with a `(spec, model)` signature (promotion-neutrality - needs the model: behavior-family evidence counts refining `rule`/`example` children and `constrainedBy` - targets). The clause-id union is **derived** (`typeof`); delete the overlay mechanism, the hand-written - `ReadinessClauseId` union, `toSupportedReadinessClauseId`, and decorative `authoredPaths`. -- **Old H2 lands inside this step** (MD-9): blocking open questions read from `intent.openQuestions` — the - typed `IntentSection` makes the predicate a few lines. Flip the gated H8 fixture - `invalid-ready-with-blocking-question` active: `intent.openQuestions: [{ blocking: true }]` stating - `defined` **fails**; `blocking: false`/absent **passes**. -- The `example`-kind evidence reads **structured `behavior.examples` entries** (MD-10), not the flat - `behavior.given/when/then` the Session-1 overlay probed. -- **Verify:** same outcomes for the non-padding fixtures; clause list defined exactly once; `model`- and - `decision`-kind fixtures reach `scoped`/`defined` on natural content alone. - -### B3 — De-pad and re-author the example (MD-10/11/12 — the visible quality signal) -- **Delete the throwaway `behavior.rules`** from `order-lifecycle`, `order-model`, - `order-latency-constraint` — each now clears the floor on its natural evidence (`decision` section · - `model.terms` · `constraints.target`). -- **`create-order`**: drop both ref lists from `behavior` (the rules/examples children already - `refines`/`verifies` it — promotion-neutral evidence); keep inline prose only if it states something no - child does. -- **`create-order-valid-cart` / `-invalid-cart`**: drop the redundant prose `examples` entry; author the - GWT as one **structured `examples` entry** (nested, per MD-10). -- **`order-lifecycle`**: drop `decision.status: "accepted"` (MD-11 — the adoption arc is `readiness`). -- **Re-author the H8 fixture `invalid-defined-constraint-without-target`** (it currently pads with - `behavior.rules` itself) and re-assert `checkout-v1.test.ts` green on the de-padded example. - -### B4 — The `.sdp.ts` rename (MD-15) -- `examples/checkout-v1/specs/**/*.spec.ts` → `*.sdp.ts`; `checkout.pack.ts` → `checkout.pack.sdp.ts`. -- Update `tsconfig.examples.json` includes and any glob that names `.spec.ts`; confirm vitest still sees - only `test/**/*.test.ts` (the config narrowing stays, but is no longer load-bearing for the collision). - -### Dissolved / re-homed by the grill -- **H4 (section-embedded ref integrity) is dissolved for `behavior`** (MD-10): refs cannot exist in - sections, so there is nothing to check — the cheapest validator is the one a model rule makes - unnecessary. What remains of H4 is only **F4** (`modelRefs` targets must be `kind:"model"`), which rides - the Slice-1/3 graph validators under the one-path rule (MD-14). -- **H10 unchanged**: the api/route anchor rides Slice-2 anchor extraction under MD-8 `codeAnchor`. -- **`AuthoredModel`** needs no Wave-B work: it stays the honestly-fenced Session-1 stand-in until the - extractor lands, then retires per MD-14 (Slice 1/3 — not this wave). - -### Carried review backlog (post-split adversarial review · small, ride Wave B / Slice 1) - -Captured so the signal survives — the review is tracked in `reviews/04-post-split-adversarial-review.md`. -None block the split; each is small, decision-free, or doc-level. -- **F2 — `ref` is a spec-only brand wearing a generic name.** `ids.ts` exports `specId as ref`, so - `ref()` rejects `pack:` / `doc:` targets. Harmless today (every call site wants a spec); add a doc - note, and revisit when `doc:`-target relations (`decidedBy` → external ADR) / pack-targeting arrive. -- **F3 — `validateAuthoredModel` mislabels its aggregate `family`.** It returns `family:"conformance"` - but folds in `honesty` readiness-floor findings (individual findings carry the correct family). The two - families are load-bearing (`05` §1); give the aggregate a neutral / no single family. Tiny. -- **F4 — `modelRefs` integrity doesn't enforce `kind:"model"`.** `02` §3 / `05` §4 say `modelRefs` - "always points at standalone `kind:"model"` specs," but `validateDanglingReferences` only checks the - ref *resolves*. Needs the kind — likely Slice-1/3 (graph) territory; named so prose doesn't - over-promise vs. the check. -- **Cross-refs (owned elsewhere).** F1 → **D7** (plan 03, a floor-table refinement — `05` §3 — de-pads the - example and the H8 constraint fixture in Wave B). F6 → **R3 — applied 2026-06-10** (Fold-B; `04` §2 is - now binding-only). **H2's *direction* is decided** (MD-9: `intent.openQuestions`) — execution is - decoupled from D1 and lands in Wave B. **F7 — DONE**: the H9 envelope-defense caveat (excess-property / - inline-literal scope) is now recorded in `test/builders.typecheck.ts`. - ---- +One line each — the full was/done/verified detail lives in git history (provenance: `reviews/01`). + +- **H1** — the example is statically extractable (P5): plain static literals only. +- **H6** — tsup build simplified; the shebang ships only on the CLI entry. +- **H7** — hygiene: scratch dirs prettier-ignored, `.sisyphus/` untracked, `package.json` metadata. +- **H8** — the validator fixture suite (`05` §5): systematic should-pass / should-fail fixtures, each + pinning one validator outcome. The two Wave-B gated stubs are now **active** (§4/B1–B2); the + extractor-era fixture names for Slice 1+ are recorded in + `test/fixtures/authored-model.fixtures.ts`'s doc comment. +- **H9** — type-level honesty defenses: `@ts-expect-error` envelope fixtures in + `test/builders.typecheck.ts` (the excess-property caveat is recorded there); the in-section bypass + was closed by §4/B1. + +## §4 — Wave B — ✅ DONE 2026-06-10 (one session: B1 → B2 → B3 → B4) + +The decision pointers were the contract; the MD entries hold the rationale and the full shapes. + +- **B1 (MD-11 + MD-10; absorbed old H3)** — the six floor-bearing sections are closed-typed in + `src/model/sections.ts` (`intent` · content-only `behavior` · `constraints[]` array · `model` · + `verification` · `decision` with no `status`); `design`/`ui` stay open bags. The in-section honesty + bypass (`behavior: { "has-verifier": true }`) **fails to typecheck**, locked by the compile-time + fixture `invalid-hand-authored-delivery-fact-in-section` in `test/builders.typecheck.ts`. +- **B2 (MD-12 + MD-13 + MD-9; absorbed old H2 + H5)** — `src/validate/readiness-floor.ts` is THE + floor table, mirroring `05` §3 row-for-row: kind-blind clauses + the per-kind evidence map + (`kindEvidence`); rows carry `{ id, description, predicate }` with `(spec, model)` signatures + (promotion-neutral); the clause-id union is derived (`typeof`); the evaluator is one generic loop; + the overlay mechanism, the hand-written id union, and decorative `authoredPaths` are deleted. + Blocking open questions read `intent.openQuestions` (old H2, MD-9); + `invalid-ready-with-blocking-question` is active. +- **B3 (MD-10/11/12)** — the example is de-padded: no `behavior.rules` on the + `decision`/`model`/`constraint` specs, no section refs on `create-order` (its promoted children are + the floor evidence), GWT nested as structured `examples` entries, no `decision.status`. The padded + constraint fixture was re-authored; new should-pass fixtures prove `model`/`decision` kinds clear + `scoped`/`defined` on natural content alone. +- **B4 (MD-15)** — every example spec file is `*.sdp.ts` (pack: `checkout.pack.sdp.ts`); vitest still + sees only `test/**/*.test.ts` (the config narrowing is no longer load-bearing for the collision). + +**Done gate — verified:** every clause of the contract held; the example contains only static +literals; `npm run check` green (typecheck ×2 · lint · format · tests · build). + +### Carried review backlog — closed or re-homed + +- **F2 ✅** — `ref()`'s spec-only nature is documented on the export (`src/ids.ts`); revisit when + `doc:`-target relations or pack-targeting arrive (the flag stays in the glossary). +- **F3 ✅** — the aggregate `validateAuthoredModel` report no longer claims a single `family` + (`ValidationReport.family` is optional; the aggregate id is `authored-model`); each finding carries + its own family. +- **F4 → Slice 1/3** — `modelRefs` targets must be `kind:"model"`: rides the graph validators under + the one-path rule (MD-14). +- **H10 → Slice 2** — the example's missing api/route anchor rides MD-8 `codeAnchor` generic-anchor + extraction. +- **H4 — dissolved** for `behavior` (MD-10): refs cannot exist in sections, so there is nothing to + check; what remains is exactly F4 above. ## §5 — Explicitly out of scope (still deferred) @@ -171,16 +101,8 @@ The `ts-morph` extractor · `graph.json` / graph emission · the graph-level val repo. Typing of `design`/`ui` stays deferred per the typing law (MD-11 — they become typed when a floor clause reads them); a dedicated `contract` section is a named deferral (MD-12). -## §6 — Sequencing - -Wave A ✅ → pre-grill folds ✅ (R1–R3 applied; MD-8/MD-9) → the grill ✅ (MD-10…MD-15; H7 leftovers -finalized) → **Wave B ← NEXT** (one code session: B1 → B2 → B3 → B4); H10 rides Slice-2 anchor -extraction. +## §6 — Sequencing — ✅ COMPLETE -**Done gate for Wave B:** the in-section honesty bypass (`behavior: { "has-verifier": true }`) **fails to -typecheck**, locked by a `@ts-expect-error` fixture; blocking open questions read from -`intent.openQuestions` with the H8 fixture active; `constraints` is a typed array (no dual-shape -validator); the floor is one kind-conditional table mirroring `05` §3, clause ids defined exactly once, no -decorative metadata; the example is **de-padded** (no `behavior.rules` on `decision`/`model`/`constraint` -specs, no section refs, no `decision.status`, GWT nested) and every spec file is `*.sdp.ts`; the example -still contains only static literals; `npm run check` green. +Wave A ✅ → pre-grill folds ✅ (R1–R3; MD-8/MD-9) → the grill ✅ (MD-10…MD-15) → **Wave B ✅ +(2026-06-10)**. Phase 0 hardening is closed. **Next session = Slice 1** (the extractor, reading +`*.sdp.ts` from day one — MD-15); H10 rides Slice-2 anchor extraction. diff --git a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md index bc1d59e..6e5aded 100644 --- a/plans/04-formalization-gap-strategy-and-pre-grill-folds.md +++ b/plans/04-formalization-gap-strategy-and-pre-grill-folds.md @@ -4,15 +4,15 @@ > direction — the one genuinely open item here.** This document was the formalized residue of a long design > arc; its executed scaffolding has been folded down (outcomes live in `DECISIONS.md`, `plans/03`'s > resolution table, and `reviews/`). What remains: the re-entry summary, the operating frame, and the parked -> gap strategy. **Branch:** `feature/mvp-init` · **Next: Wave B execution** (`plans/02` §4), then — gated on -> a minimal engine — the crippled graph (§2). +> gap strategy. **Branch:** `feature/mvp-init` · **Next: Slice 1 (the extractor)** — Wave B executed +> 2026-06-10 (`plans/02` §4) — then, gated on a minimal engine, the crippled graph (§2). --- ## §0 — Re-entry in one screen -**Where the code is:** Slice 0 (the protocol as typed code) is built, green, and hardened (Wave A done). -No extractor, no graph, no engine yet. +**Where the code is:** Slice 0 (the protocol as typed code) is built, green, and hardened (Waves A and +B done). No extractor, no graph, no engine yet. **What happened this arc:** a fresh adversarial review (high-signal; archived `reviews/04`) → the gen-1 (`@libar-dev/architect`) formal-spec study (*evidence, not template*: gen-1 is proof gen-2's rejections are @@ -20,7 +20,8 @@ correct — its FSM softened to advisory, its tag taxonomy repeatedly amputated, migrate-and-delete; no patterns transferred; chat-only, formalized here) → the pre-grill folds (lean glossary, R1–R3, MD-8/MD-9) → the grill (MD-10…MD-15; base reconciled; `plans/02` Wave B execution-ready). -**What's next:** **Wave B execution** (`plans/02` §4), then Slice 1. +**What's next:** **Slice 1 — the extractor** (Wave B executed 2026-06-10; `plans/02` is its +done-record), then — on the minimal engine's substrate — the crippled graph (§2). **The one decision still genuinely open and yours:** how far the crippled graph goes, and exactly what its two instances are (§2). Parked deliberately — it needs substrate to grow, not more chat. @@ -95,8 +96,9 @@ F1–F7; see §6.)* ## §4 — Sequencing — ✅ DONE through the grill Fold-A → Fold-B → archive → **grill** all executed 2026-06-10 (grill outcome: MD-10…MD-15; base reconciled; -`plans/02` Wave B rewritten execution-ready; H7 leftovers finalized). **Then → Wave B execution ← NEXT**, -and — gated on a minimal engine — the crippled graph (§2) graduates from direction to design. +`plans/02` Wave B rewritten execution-ready; H7 leftovers finalized) → **Wave B executed ✅ (2026-06-10)**. +**Next: Slice 1 (the extractor)** — on its substrate the crippled graph (§2) graduates from direction +to design. ## §5 — Scoped session prompts (Fold-A · Fold-B · the grill kickoff) diff --git a/src/ids.ts b/src/ids.ts index c3a3954..7f6421d 100644 --- a/src/ids.ts +++ b/src/ids.ts @@ -138,4 +138,10 @@ export function testAnchorId(value: string): TestAnchorId { return requireNamespace<"TestAnchorId">(value, "test") as TestAnchorId; } +/** + * `ref()` is today a spec-only reference builder wearing a generic name: it is `specId` aliased, so + * it rejects `pack:` / `doc:` targets (F2, post-split adversarial review). Harmless while every call + * site wants a spec; revisit when `doc:`-target relations (`decidedBy` → an external ADR) or + * pack-targeting arrive. + */ export { specId as ref }; diff --git a/src/model/sections.ts b/src/model/sections.ts index 4d0de7c..44f9897 100644 --- a/src/model/sections.ts +++ b/src/model/sections.ts @@ -11,21 +11,87 @@ export const SPEC_SECTION_NAMES = [ export type SpecSectionName = (typeof SPEC_SECTION_NAMES)[number]; +/** + * The typing law (MD-11): every floor-bearing section — one a readiness-floor clause reads as + * evidence (`05` §3) — gets a closed typed shape (no index signature). Closed shapes are what reject + * the in-section honesty bypass (`behavior: { "has-verifier": true }`) at the type level. The + * genuinely-unsettled surfaces (`design`, `ui`) stay open bags so they keep breathing (L9). The + * criterion outlives the list: a section becomes typed when a floor clause starts reading it. + * + * All fields are optional: types describe shape, validators decide completeness (P7). + */ export type SpecSectionContent = Record; -export type IntentSection = SpecSectionContent; -export type BehaviorSection = SpecSectionContent; -export type ConstraintsSection = SpecSectionContent; -export type ModelSection = SpecSectionContent; +/** An open question is prose, or an object flaggable `blocking` (MD-9: blocks `defined`+). */ +export type IntentOpenQuestion = + | string + | { readonly question: string; readonly blocking?: boolean }; + +export interface IntentSection { + readonly actor?: string; + readonly problem?: string; + readonly outcome?: string; + readonly value?: string; + readonly risks?: readonly string[]; + readonly assumptions?: readonly string[]; + readonly openQuestions?: readonly IntentOpenQuestion[]; +} + +/** A structured Given/When/Then entry — the `defined`-complete form of an example (MD-10). */ +export interface GivenWhenThen { + readonly given: readonly string[]; + readonly when: readonly string[]; + readonly then: readonly string[]; +} + +/** An examples entry matures in place: prose → structured GWT → (promoted) a child `example` spec. */ +export type BehaviorExample = string | GivenWhenThen; + +/** + * Content only, never refs (MD-10): rules are prose; promotion to a standalone `rule`/`example` + * spec moves the content out, and the only linkage is the child's `refines`/`verifies` relation. + */ +export interface BehaviorSection { + readonly rules?: readonly string[]; + readonly examples?: readonly BehaviorExample[]; + readonly flows?: readonly string[]; +} + +export interface ConstraintSection { + readonly flavor?: string; + readonly statement: string; + readonly target?: string; + readonly measurableBy?: string; +} + +export interface ModelSection { + readonly terms?: Readonly>; +} + export type DesignSection = SpecSectionContent; -export type DecisionSection = SpecSectionContent; -export type VerificationSection = SpecSectionContent; + +/** No `status` field (MD-11): the adoption arc is the envelope's `readiness`; replacement is `supersedes`. */ +export interface DecisionSection { + readonly context?: string; + readonly decision?: string; + readonly rationale?: readonly string[]; + readonly alternatives?: readonly string[]; + readonly consequences?: readonly string[]; +} + +export type VerificationMode = "manual" | "reviewed" | "contract" | "executable"; + +export interface VerificationSection { + readonly mode?: VerificationMode; + readonly criteria?: readonly string[]; +} + export type UiSection = SpecSectionContent; export interface SpecSections { readonly intent?: IntentSection; readonly behavior?: BehaviorSection; - readonly constraints?: ConstraintsSection; + readonly constraints?: readonly ConstraintSection[]; readonly model?: ModelSection; readonly design?: DesignSection; readonly decision?: DecisionSection; diff --git a/src/validate/contracts.ts b/src/validate/contracts.ts index 8f5e691..73fdfbc 100644 --- a/src/validate/contracts.ts +++ b/src/validate/contracts.ts @@ -16,7 +16,12 @@ export interface Finding { export interface ValidationReport { readonly validatorId: string; - readonly family: ValidatorFamily; + /** + * One individual validator belongs to exactly one family. An aggregate report that composes both + * families omits this — the two families are load-bearing (`05` §1), so an aggregate never + * mislabels itself with a single one; each finding carries its own. + */ + readonly family?: ValidatorFamily; readonly findings: readonly Finding[]; } diff --git a/src/validate/readiness-floor.ts b/src/validate/readiness-floor.ts index 386401e..cf2783e 100644 --- a/src/validate/readiness-floor.ts +++ b/src/validate/readiness-floor.ts @@ -1,25 +1,244 @@ +import { SPEC_READINESS } from "../model/descriptors.js"; import type { SpecKind, SpecReadiness } from "../model/descriptors.js"; +import type { Spec } from "../model/spec.js"; +import type { AuthoredModel } from "./authored-model.js"; -export interface ReadinessClause { +/** + * The readiness floor — the single source of truth (MD-13), mirroring `05` §3 row-for-row: kind-blind + * structural clauses (`readinessFloors`) plus one kind-conditional evidence clause read from the + * per-kind evidence table (`kindEvidence`, MD-12) — `scoped` requires the kind's natural evidence + * *present* (prose acceptable), `defined` requires it *complete* where the kind defines a stronger + * form. The clause-id union is derived from the table, the evaluator is one generic loop, and + * evidence predicates take `(spec, model)` because promotion-neutrality (MD-10) needs the authored + * model to count refining children. + */ + +export type ReadinessPredicate = (spec: Spec, model: AuthoredModel) => boolean; + +export interface ActiveReadinessClause { readonly id: string; readonly description: string; - readonly authoredPaths?: readonly string[]; - readonly deferredInSession1?: true; + readonly predicate: ReadinessPredicate; } +/** Graph-shaped clauses that execute over the one graph (MD-14, Slice 1/3); the Session-1 evaluator skips them. */ +export interface DeferredReadinessClause { + readonly id: string; + readonly description: string; + readonly deferredInSession1: true; +} + +export type ReadinessClause = ActiveReadinessClause | DeferredReadinessClause; + export interface ReadinessFloor { readonly readiness: SpecReadiness; readonly clauses: readonly ReadinessClause[]; } -export type ReadinessOverlayKind = Extract; +export interface KindEvidenceCell { + readonly description: string; + readonly predicate: ReadinessPredicate; +} -export interface ReadinessKindOverlay { - readonly kind: ReadinessOverlayKind; - readonly appliesAtOrAbove: Extract; - readonly clauses: readonly ReadinessClause[]; +export interface KindEvidenceRow { + /** `scoped` — the kind's natural evidence is *present* (prose acceptable). */ + readonly scoped: KindEvidenceCell; + /** `defined` — the evidence is *complete* where the kind defines a stronger form. */ + readonly defined: KindEvidenceCell; +} + +/* ----- the named predicate library ----- */ + +function hasNonEmptyString(value: unknown): boolean { + return typeof value === "string" && value.trim().length > 0; +} + +function hasEntries(value: readonly unknown[] | undefined): boolean { + return (value?.length ?? 0) > 0; +} + +function hasSpecId(spec: Spec): boolean { + return hasNonEmptyString(spec.id); +} + +function hasTitle(spec: Spec): boolean { + return hasNonEmptyString(spec.title); +} + +function hasKind(spec: Spec): boolean { + return hasNonEmptyString(spec.kind); +} + +function hasAltitude(spec: Spec): boolean { + return hasNonEmptyString(spec.altitude); +} + +function hasIntentOutcome(spec: Spec): boolean { + return hasNonEmptyString(spec.intent?.outcome); +} + +function hasParentRelation(spec: Spec): boolean { + return (spec.relations ?? []).some((relation) => relation.type === "refines"); +} + +function hasIntentOutcomeOrParentRelation(spec: Spec): boolean { + return hasIntentOutcome(spec) || hasParentRelation(spec); +} + +function hasAtLeastOneRelation(spec: Spec): boolean { + return hasEntries(spec.relations); +} + +/** Blocking open questions live in `intent.openQuestions` (MD-9); only an entry flagged `blocking: true` blocks. */ +function hasNoBlockingOpenQuestions(spec: Spec): boolean { + return !(spec.intent?.openQuestions ?? []).some( + (entry) => typeof entry !== "string" && entry.blocking === true, + ); +} + +function hasInlineRulesOrExamples(spec: Spec): boolean { + return hasEntries(spec.behavior?.rules) || hasEntries(spec.behavior?.examples); +} + +function hasInlineBehaviorEvidence(spec: Spec): boolean { + return hasInlineRulesOrExamples(spec) || hasEntries(spec.behavior?.flows); +} + +/** Promotion-neutral evidence (MD-10/MD-12): a promoted child counts wherever an inline entry would. */ +function hasRefiningRuleOrExampleChild(spec: Spec, model: AuthoredModel): boolean { + return model.specs.some( + (child) => + (child.kind === "rule" || child.kind === "example") && + (child.relations ?? []).some( + (relation) => relation.type === "refines" && relation.target === spec.id, + ), + ); +} + +function hasConstrainedByRelation(spec: Spec): boolean { + return (spec.relations ?? []).some((relation) => relation.type === "constrainedBy"); +} + +function hasStructuredExampleEntry(spec: Spec): boolean { + return (spec.behavior?.examples ?? []).some( + (entry) => + typeof entry !== "string" && + entry.given.length > 0 && + entry.when.length > 0 && + entry.then.length > 0, + ); +} + +function hasConstraintEntries(spec: Spec): boolean { + return hasEntries(spec.constraints); +} + +function constraintTargetsAreMachineReadable(spec: Spec): boolean { + return ( + hasConstraintEntries(spec) && + (spec.constraints ?? []).every((entry) => hasNonEmptyString(entry.target)) + ); +} + +function hasModelTerms(spec: Spec): boolean { + return spec.model?.terms !== undefined && Object.keys(spec.model.terms).length > 0; +} + +function hasDecisionSection(spec: Spec): boolean { + return spec.decision !== undefined && Object.keys(spec.decision).length > 0; +} + +function hasWrittenDecision(spec: Spec): boolean { + return hasNonEmptyString(spec.decision?.decision); } +/* ----- the per-kind evidence table (MD-12; mirrors `05` §3) ----- */ + +const behaviorFamilyEvidence: KindEvidenceRow = { + scoped: { + description: + "rules, examples, flows, or constraints — inline, or promoted (a refining rule/example child, or a constrainedBy target)", + predicate: (spec, model) => + hasInlineBehaviorEvidence(spec) || + hasConstraintEntries(spec) || + hasConstrainedByRelation(spec) || + hasRefiningRuleOrExampleChild(spec, model), + }, + defined: { + description: + "rules and/or examples, inline or promoted children — constraints alone no longer suffice", + predicate: (spec, model) => + hasInlineRulesOrExamples(spec) || hasRefiningRuleOrExampleChild(spec, model), + }, +}; + +export const kindEvidence = { + behavior: behaviorFamilyEvidence, + workflow: behaviorFamilyEvidence, + example: { + scoped: { + description: "an examples entry (prose acceptable)", + predicate: (spec) => hasEntries(spec.behavior?.examples), + }, + defined: { + description: "at least one structured { given, when, then } examples entry", + predicate: hasStructuredExampleEntry, + }, + }, + rule: { + scoped: { + description: "its statement in behavior.rules", + predicate: (spec) => hasEntries(spec.behavior?.rules), + }, + defined: { + description: "its statement in behavior.rules — a rule's content is its statement", + predicate: (spec) => hasEntries(spec.behavior?.rules), + }, + }, + constraint: { + scoped: { + description: "a non-empty constraints[] section", + predicate: hasConstraintEntries, + }, + defined: { + description: "every constraints[] entry carries a machine-readable target", + predicate: constraintTargetsAreMachineReadable, + }, + }, + model: { + scoped: { + description: "non-empty model.terms", + predicate: hasModelTerms, + }, + defined: { + description: "non-empty model.terms — a vocabulary either has terms or it doesn't", + predicate: hasModelTerms, + }, + }, + decision: { + scoped: { + description: + "the decision section is present (context / alternatives may precede the choice)", + predicate: hasDecisionSection, + }, + defined: { + description: "decision.decision — the chosen option — is written", + predicate: hasWrittenDecision, + }, + }, + // Documented interim (MD-12): when a dedicated contract section lands, the typing law (MD-11) + // pulls it in and this row repoints to it. + contract: behaviorFamilyEvidence, +} as const satisfies Record; + +const kindEvidencePresent: ReadinessPredicate = (spec, model) => + kindEvidence[spec.kind].scoped.predicate(spec, model); + +const kindEvidenceComplete: ReadinessPredicate = (spec, model) => + kindEvidence[spec.kind].defined.predicate(spec, model); + +/* ----- the kind-blind structural clauses (mirrors `05` §3) ----- */ + export const readinessFloors = { idea: { readiness: "idea", @@ -27,27 +246,27 @@ export const readinessFloors = { { id: "id", description: "Envelope includes a stable spec id.", - authoredPaths: ["id"], + predicate: hasSpecId, }, { id: "title", description: "Envelope includes a human-readable title.", - authoredPaths: ["title"], + predicate: hasTitle, }, { id: "kind", description: "Envelope states the authored spec kind.", - authoredPaths: ["kind"], + predicate: hasKind, }, { id: "altitude", description: "Envelope states the authored altitude.", - authoredPaths: ["altitude"], + predicate: hasAltitude, }, { id: "intent.outcome-or-parent-relation", description: "Spec states intent.outcome or declares a parent relation via refines.", - authoredPaths: ["intent.outcome", "relations"], + predicate: hasIntentOutcomeOrParentRelation, }, ], }, @@ -57,18 +276,17 @@ export const readinessFloors = { { id: "intent.outcome", description: "Spec states the intended outcome.", - authoredPaths: ["intent.outcome"], + predicate: hasIntentOutcome, }, { id: "at-least-one-relation", description: "Spec declares at least one authored relation.", - authoredPaths: ["relations"], + predicate: hasAtLeastOneRelation, }, { - id: "rules-examples-or-constraints", - description: - "Spec includes at least one of behavior.rules, behavior.examples, or constraints.", - authoredPaths: ["behavior.rules", "behavior.examples", "constraints"], + id: "kind-evidence-present", + description: "The kind's natural evidence is present (per-kind evidence table).", + predicate: kindEvidencePresent, }, ], }, @@ -76,90 +294,71 @@ export const readinessFloors = { readiness: "defined", clauses: [ { - id: "rules-and-or-examples", - description: "Spec includes rules and/or examples.", - authoredPaths: ["behavior.rules", "behavior.examples"], - }, - { - id: "constraint-targets-are-machine-readable", - description: "Any authored constraint detail includes a machine-readable target.", - authoredPaths: ["constraints.*.target"], + id: "kind-evidence-complete", + description: "The kind's natural evidence is complete (per-kind evidence table).", + predicate: kindEvidenceComplete, }, { id: "no-blocking-open-questions", - description: "Spec has no blocking open questions.", - authoredPaths: ["design.openQuestions", "decision.openQuestions"], + description: "Spec has no blocking open question in intent.openQuestions.", + predicate: hasNoBlockingOpenQuestions, }, ], }, ready: { readiness: "ready", clauses: [ - { - id: "defined-floor", - description: "Spec satisfies the defined readiness floor.", - }, - { - id: "no-blocking-open-questions", - description: "Spec has no blocking open questions.", - authoredPaths: ["design.openQuestions", "decision.openQuestions"], - }, { id: "all-relations-resolve", description: "All authored relations resolve to known targets.", - authoredPaths: ["relations"], deferredInSession1: true, }, { id: "depends-on-and-refines-targets-are-defined", description: "Every dependsOn and refines target is at least defined.", - authoredPaths: ["relations"], deferredInSession1: true, }, { id: "anchors-resolve", description: "Any authored anchors present resolve.", - authoredPaths: ["anchors"], deferredInSession1: true, }, ], }, } as const satisfies Record; -export const readinessKindOverlays = { - constraint: { - kind: "constraint", - appliesAtOrAbove: "defined", - clauses: [ - { - id: "constraint-machine-readable-target", - description: - "Constraint specs need a parseable, machine-readable target before defined and ready.", - authoredPaths: ["constraints.target"], - }, - ], - }, - example: { - kind: "example", - appliesAtOrAbove: "defined", - clauses: [ - { - id: "example-given-when-then", - description: - "Example specs need structured given, when, and then detail before defined and ready.", - authoredPaths: ["behavior.given", "behavior.when", "behavior.then"], - }, - ], - }, - model: { - kind: "model", - appliesAtOrAbove: "defined", - clauses: [ - { - id: "model-term-definitions", - description: "Model specs need term definitions before defined and ready.", - authoredPaths: ["model.terms"], - }, - ], - }, -} as const satisfies Record; +/** Derived from the table — never re-enumerated (MD-13). */ +export type ReadinessClauseId = (typeof readinessFloors)[SpecReadiness]["clauses"][number]["id"]; + +export interface ReadinessFloorFailure { + readonly clauseId: ReadinessClauseId; + readonly description: string; +} + +/** + * The one generic evaluator (MD-13): floors are cumulative, so every clause at or below the stated + * rung must hold; deferred (graph-shaped) clauses are skipped until the extractor lands. + */ +export function evaluateReadinessFloor( + spec: Spec, + model: AuthoredModel, +): readonly ReadinessFloorFailure[] { + const statedIndex = SPEC_READINESS.indexOf(spec.readiness); + const failures: ReadinessFloorFailure[] = []; + + for (const readiness of SPEC_READINESS.slice(0, statedIndex + 1)) { + for (const clause of readinessFloors[readiness].clauses) { + if ("deferredInSession1" in clause) { + continue; + } + + if (clause.predicate(spec, model)) { + continue; + } + + failures.push({ clauseId: clause.id, description: clause.description }); + } + } + + return failures; +} diff --git a/src/validate/validators.ts b/src/validate/validators.ts index e5c6690..8c74615 100644 --- a/src/validate/validators.ts +++ b/src/validate/validators.ts @@ -1,52 +1,25 @@ import type { AnchorId, PackId, SpecId } from "../ids.js"; -import type { Spec } from "../model/spec.js"; -import type { SpecReadiness } from "../model/descriptors.js"; import type { AuthoredModel } from "./authored-model.js"; import type { Finding, ValidationReport, ValidatorFamily } from "./contracts.js"; -import { readinessFloors, readinessKindOverlays } from "./readiness-floor.js"; +import { evaluateReadinessFloor } from "./readiness-floor.js"; const duplicateIdsValidatorId = "conformance/duplicate-ids"; const danglingReferencesValidatorId = "conformance/dangling-references"; const readinessFloorValidatorId = "honesty/readiness-floor"; -const authoredModelValidatorId = "conformance/authored-model"; - -const readinessOrder = [ - "idea", - "scoped", - "defined", - "ready", -] as const satisfies readonly SpecReadiness[]; +const authoredModelValidatorId = "authored-model"; type AuthoredId = SpecId | PackId | AnchorId; -type ReadinessClauseId = - | "id" - | "title" - | "kind" - | "altitude" - | "intent.outcome-or-parent-relation" - | "intent.outcome" - | "at-least-one-relation" - | "rules-examples-or-constraints" - | "rules-and-or-examples" - | "constraint-targets-are-machine-readable" - | "no-blocking-open-questions" - | "constraint-machine-readable-target" - | "example-given-when-then" - | "model-term-definitions"; - -type OverlayKind = keyof typeof readinessKindOverlays; - function createReport( validatorId: string, - family: ValidatorFamily, findings: readonly Finding[], + family?: ValidatorFamily, ): ValidationReport { - return { - validatorId, - family, - findings, - }; + if (family === undefined) { + return { validatorId, findings }; + } + + return { validatorId, family, findings }; } function createFinding( @@ -76,266 +49,6 @@ function collectAuthoredIds(model: AuthoredModel): readonly AuthoredId[] { ]; } -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -function hasNonEmptyString(value: unknown): value is string { - return typeof value === "string" && value.trim().length > 0; -} - -function hasPresentValue(value: unknown): boolean { - if (value === undefined || value === null) { - return false; - } - - if (typeof value === "string") { - return value.trim().length > 0; - } - - if (Array.isArray(value)) { - return value.length > 0; - } - - if (isRecord(value)) { - return Object.keys(value).length > 0; - } - - return true; -} - -function hasIntentOutcome(spec: Spec): boolean { - return isRecord(spec.intent) && hasNonEmptyString(spec.intent.outcome); -} - -function hasParentRelation(spec: Spec): boolean { - return spec.relations?.some((relation) => relation.type === "refines") ?? false; -} - -function hasAtLeastOneRelation(spec: Spec): boolean { - return (spec.relations?.length ?? 0) > 0; -} - -function hasBehaviorRules(spec: Spec): boolean { - return isRecord(spec.behavior) && hasPresentValue(spec.behavior.rules); -} - -function hasBehaviorExamples(spec: Spec): boolean { - return isRecord(spec.behavior) && hasPresentValue(spec.behavior.examples); -} - -function hasBehaviorRulesOrExamples(spec: Spec): boolean { - return hasBehaviorRules(spec) || hasBehaviorExamples(spec); -} - -function hasRulesExamplesOrConstraints(spec: Spec): boolean { - return hasBehaviorRules(spec) || hasBehaviorExamples(spec) || hasPresentValue(spec.constraints); -} - -function hasConstraintTarget(value: unknown): boolean { - return isRecord(value) && hasPresentValue(value.target); -} - -function constraintsHaveMachineReadableTargets(spec: Spec): boolean { - if (spec.constraints === undefined) { - return true; - } - - const { constraints } = spec; - - if (Array.isArray(constraints)) { - return constraints.every((entry) => hasConstraintTarget(entry)); - } - - if (!isRecord(constraints)) { - return false; - } - - if (hasPresentValue(constraints.target)) { - return true; - } - - const entries = Object.values(constraints); - - if (entries.length === 0) { - return false; - } - - return entries.every((entry) => hasConstraintTarget(entry)); -} - -function isBlockingOpenQuestion(value: unknown): boolean { - if (value === undefined || value === null) { - return false; - } - - if (Array.isArray(value)) { - return value.some((entry) => isBlockingOpenQuestion(entry)); - } - - if (typeof value === "string") { - return value.trim().length > 0; - } - - if (typeof value === "boolean") { - return value; - } - - if (typeof value === "number") { - return true; - } - - if (!isRecord(value)) { - return true; - } - - if ("blocking" in value) { - return value.blocking === true; - } - - return Object.keys(value).length > 0; -} - -function hasNoBlockingOpenQuestions(spec: Spec): boolean { - const designOpenQuestions = isRecord(spec.design) ? spec.design.openQuestions : undefined; - const decisionOpenQuestions = isRecord(spec.decision) ? spec.decision.openQuestions : undefined; - - return ( - !isBlockingOpenQuestion(designOpenQuestions) && !isBlockingOpenQuestion(decisionOpenQuestions) - ); -} - -function hasStructuredExampleDetail(spec: Spec): boolean { - if (!isRecord(spec.behavior)) { - return false; - } - - return ( - hasPresentValue(spec.behavior.given) && - hasPresentValue(spec.behavior.when) && - hasPresentValue(spec.behavior.then) - ); -} - -function hasModelTermDefinitions(spec: Spec): boolean { - return isRecord(spec.model) && hasPresentValue(spec.model.terms); -} - -function evaluateClause(spec: Spec, clauseId: ReadinessClauseId): boolean { - switch (clauseId) { - case "id": - return hasNonEmptyString(spec.id); - case "title": - return hasNonEmptyString(spec.title); - case "kind": - return hasNonEmptyString(spec.kind); - case "altitude": - return hasNonEmptyString(spec.altitude); - case "intent.outcome-or-parent-relation": - return hasIntentOutcome(spec) || hasParentRelation(spec); - case "intent.outcome": - return hasIntentOutcome(spec); - case "at-least-one-relation": - return hasAtLeastOneRelation(spec); - case "rules-examples-or-constraints": - return hasRulesExamplesOrConstraints(spec); - case "rules-and-or-examples": - return hasBehaviorRulesOrExamples(spec); - case "constraint-targets-are-machine-readable": - return constraintsHaveMachineReadableTargets(spec); - case "no-blocking-open-questions": - return hasNoBlockingOpenQuestions(spec); - case "constraint-machine-readable-target": - return constraintsHaveMachineReadableTargets(spec); - case "example-given-when-then": - return hasStructuredExampleDetail(spec); - case "model-term-definitions": - return hasModelTermDefinitions(spec); - } -} - -function readinessIndex(readiness: SpecReadiness): number { - return readinessOrder.indexOf(readiness); -} - -function isOverlayKind(kind: Spec["kind"]): kind is OverlayKind { - return kind in readinessKindOverlays; -} - -function toSupportedReadinessClauseId(clauseId: string): ReadinessClauseId | undefined { - switch (clauseId) { - case "id": - case "title": - case "kind": - case "altitude": - case "intent.outcome-or-parent-relation": - case "intent.outcome": - case "at-least-one-relation": - case "rules-examples-or-constraints": - case "rules-and-or-examples": - case "constraint-targets-are-machine-readable": - case "no-blocking-open-questions": - case "constraint-machine-readable-target": - case "example-given-when-then": - case "model-term-definitions": - return clauseId; - default: - return undefined; - } -} - -function getRequiredReadinessClauseIds(spec: Spec): readonly ReadinessClauseId[] { - const clauseIds: ReadinessClauseId[] = []; - const seen = new Set(); - - for (let index = 0; index <= readinessIndex(spec.readiness); index += 1) { - const readiness = readinessOrder[index]; - - if (readiness === undefined) { - continue; - } - - for (const clause of readinessFloors[readiness].clauses) { - if ("deferredInSession1" in clause || clause.id === "defined-floor") { - continue; - } - - const supportedClauseId = toSupportedReadinessClauseId(clause.id); - - if (supportedClauseId === undefined) { - continue; - } - - if (seen.has(supportedClauseId)) { - continue; - } - - seen.add(supportedClauseId); - clauseIds.push(supportedClauseId); - } - } - - const overlay = isOverlayKind(spec.kind) ? readinessKindOverlays[spec.kind] : undefined; - - if ( - overlay !== undefined && - readinessIndex(spec.readiness) >= readinessIndex(overlay.appliesAtOrAbove) - ) { - for (const clause of overlay.clauses) { - const supportedClauseId = toSupportedReadinessClauseId(clause.id); - - if (supportedClauseId === undefined || seen.has(supportedClauseId)) { - continue; - } - - seen.add(supportedClauseId); - clauseIds.push(supportedClauseId); - } - } - - return clauseIds; -} - export function validateDuplicateIds(model: AuthoredModel): ValidationReport { const seen = new Set(); const emitted = new Set(); @@ -357,7 +70,7 @@ export function validateDuplicateIds(model: AuthoredModel): ValidationReport { ); } - return createReport(duplicateIdsValidatorId, "conformance", findings); + return createReport(duplicateIdsValidatorId, findings, "conformance"); } export function validateDanglingReferences(model: AuthoredModel): ValidationReport { @@ -410,37 +123,34 @@ export function validateDanglingReferences(model: AuthoredModel): ValidationRepo appendMissingReference(anchor.id, anchor.verifies, "verifies"); } - return createReport(danglingReferencesValidatorId, "conformance", findings); + return createReport(danglingReferencesValidatorId, findings, "conformance"); } export function validateReadinessFloors(model: AuthoredModel): ValidationReport { const findings: Finding[] = []; for (const authoredSpec of model.specs) { - for (const clauseId of getRequiredReadinessClauseIds(authoredSpec)) { - if (evaluateClause(authoredSpec, clauseId)) { - continue; - } - + for (const failure of evaluateReadinessFloor(authoredSpec, model)) { findings.push( createFinding( readinessFloorValidatorId, "honesty", - `Spec "${authoredSpec.id}" states readiness "${authoredSpec.readiness}" but does not satisfy authored clause "${clauseId}".`, + `Spec "${authoredSpec.id}" states readiness "${authoredSpec.readiness}" but does not satisfy floor clause "${failure.clauseId}": ${failure.description}`, authoredSpec.id, - clauseId, + failure.clauseId, "readiness", ), ); } } - return createReport(readinessFloorValidatorId, "honesty", findings); + return createReport(readinessFloorValidatorId, findings, "honesty"); } /** * Pre-graph authored-layer validation only. This composes the tiny Session 1 authored-model checks and is not the - * Slice 3 graph validator gate. + * Slice 3 graph validator gate. The aggregate spans both check families, so it carries no single + * `family` of its own — each finding states its family (`conformance` or `honesty`). */ export function validateAuthoredModel(model: AuthoredModel): ValidationReport { const findings = [ @@ -449,5 +159,5 @@ export function validateAuthoredModel(model: AuthoredModel): ValidationReport { ...validateReadinessFloors(model).findings, ]; - return createReport(authoredModelValidatorId, "conformance", findings); + return createReport(authoredModelValidatorId, findings); } diff --git a/test/builders.typecheck.ts b/test/builders.typecheck.ts index eeb337a..d583a37 100644 --- a/test/builders.typecheck.ts +++ b/test/builders.typecheck.ts @@ -77,9 +77,7 @@ specTest({ // The Spec envelope rejects hand-authored delivery facts at the top level: delivery facts are // derived-only (base §4b). Caveat: the guarantee is "rejected for inline object literals" — it relies // on TypeScript's excess-property checking, which only fires on literals passed directly. Sound in -// practice because P5 mandates static inline literals, but it is not a structural guarantee. Note too: -// the in-section bypass (`behavior: { "has-verifier": true }`) is NOT closed by these and stays open -// until sections are typed (D1, plan 03 / Wave B). +// practice because P5 mandates static inline literals, but it is not a structural guarantee. spec({ id: specId("spec:orders.create-order"), title: "Customer creates an order", @@ -100,6 +98,30 @@ spec({ "has-verifier": true, }); +// invalid-hand-authored-delivery-fact-in-section (the H8 fixture, landed compile-time): the +// in-section bypass is closed by the typed floor-bearing sections (MD-11) — a closed BehaviorSection +// has no index signature, so a smuggled delivery fact no longer typechecks. +spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + // @ts-expect-error sections are closed-typed (MD-11) — no hand-authored delivery fact inside a section + behavior: { "has-verifier": true }, +}); + +// The same closure holds for the other floor-bearing sections (MD-11's criterion, not a list). +spec({ + id: specId("spec:orders.create-order"), + title: "Customer creates an order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + // @ts-expect-error the decision section carries no status field (MD-11 — rejected vocabulary) + decision: { decision: "Validate before creating.", status: "accepted" }, +}); + // The Pack stays truthless (base §2 boundary): it states no system truth, so it rejects // truth-bearing fields — intent, readiness, and constraints all belong on a Spec, never a Pack. pack({ diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts index 159e1ec..c2634b4 100644 --- a/test/fixtures.test.ts +++ b/test/fixtures.test.ts @@ -24,10 +24,8 @@ describe("authored-model validator fixtures (should-pass / should-fail regressio }); } - // Gated fixtures — the inherited checklist for Wave B (plan 03). They are deliberately not active - // yet because the behavior they assert does not exist in Session-1 code: - // - the open-questions home is still design/decision, not intent (H2); - // - a hand-authored delivery fact inside an untyped section still typechecks and is uncaught (D1). - it.todo("invalid-ready-with-blocking-question fails once H2 reads intent.openQuestions"); - it.todo("invalid-hand-authored-delivery-fact-in-section fails once sections are typed (D1)"); + // The Wave-B gated stubs resolved (plans/02 §4): `invalid-ready-with-blocking-question` is active + // above (MD-9 — the floor reads intent.openQuestions); `invalid-hand-authored-delivery-fact-in- + // section` is a compile-time `@ts-expect-error` fixture in `builders.typecheck.ts` (MD-11 — closed + // section types reject it before any validator runs). }); diff --git a/test/fixtures/authored-model.fixtures.ts b/test/fixtures/authored-model.fixtures.ts index 2bd3482..cc32ccc 100644 --- a/test/fixtures/authored-model.fixtures.ts +++ b/test/fixtures/authored-model.fixtures.ts @@ -6,12 +6,12 @@ import type { AuthoredModel } from "../../src/index.js"; * (`05` §5 "Validator self-testing"). Each fixture pins a single validator outcome so a regression * that silently stops a validator firing is itself caught. * - * Scope: only the fixtures that exercise *current, decision-free* behavior live here as ACTIVE. - * Two further fixtures from the hardening plan are gated on later work and are tracked as `it.todo` - * stubs in `fixtures.test.ts`: - * - `invalid-ready-with-blocking-question` — gated on H2 (open-questions canonical home) - * - `invalid-hand-authored-delivery-fact-in-section` — gated on D1 (typed sections) - * Both flip to ACTIVE in Wave B (see `plans/03-decision-resolution-and-base-reconciliation.md`). + * The Wave-B compile-time twin lives in `test/builders.typecheck.ts`: a hand-authored delivery fact + * inside a section (`invalid-hand-authored-delivery-fact-in-section`) is rejected by the closed + * section types (MD-11), so it is pinned as a `@ts-expect-error` fixture, not a runtime one. + * The extractor-era fixtures stay named for Slice 1+ (`plans/02` §3 H8): `invalid-non-static-id` · + * `invalid-non-static-section` · `invalid-hand-authored-satisfies-edge` · + * `invalid-ready-with-unresolved-dependency` · `invalid-ready-with-target-below-defined`. */ export interface ValidatorFixture { readonly name: string; @@ -109,9 +109,9 @@ const invalidDefinedConstraintWithoutTarget: ValidatorFixture = { altitude: "story", readiness: "defined", intent: { outcome: "Keep create-order fast enough for interactive checkout." }, - behavior: { rules: ["The create-order path stays inside a latency budget."] }, - // A defined constraint must carry a machine-readable target; this one omits it. - constraints: { statement: "Create-order should respond quickly." }, + // A defined constraint's evidence must be complete: every entry carries a machine-readable + // target; this one omits it (and clears scoped on the entry's presence alone — no padding). + constraints: [{ statement: "Create-order should respond quickly." }], relations: [refines(specId("spec:orders.create-order"))], }), ], @@ -120,13 +120,149 @@ const invalidDefinedConstraintWithoutTarget: ValidatorFixture = { }, expect: { validatorId: "honesty/readiness-floor", - relatedId: "constraint-machine-readable-target", + relatedId: "kind-evidence-complete", }, }; +const invalidReadyWithBlockingQuestion: ValidatorFixture = { + name: "invalid-ready-with-blocking-question", + model: { + specs: [ + spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "idea", + intent: { outcome: "Own the order lifecycle for checkout." }, + }), + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "ready", + intent: { + outcome: "Turn a valid cart into an order.", + // Blocking open questions live in intent.openQuestions (MD-9); one flagged blocking + // prevents honestly stating defined or ready. + openQuestions: [ + { question: "Which payment provider gates order creation?", blocking: true }, + ], + }, + behavior: { rules: ["Only valid carts become orders."] }, + relations: [refines(specId("spec:orders.order-management"))], + }), + ], + packs: [], + anchors: [], + }, + expect: { validatorId: "honesty/readiness-floor", relatedId: "no-blocking-open-questions" }, +}; + +const validDefinedWithNonBlockingQuestion: ValidatorFixture = { + name: "valid-defined-with-non-blocking-question", + model: { + specs: [ + spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "idea", + intent: { outcome: "Own the order lifecycle for checkout." }, + }), + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "defined", + intent: { + outcome: "Turn a valid cart into an order.", + // Open questions that are prose or flagged blocking: false never block a stated rung. + openQuestions: [ + "Should gift carts ride this use case?", + { question: "Do we surface partial-inventory hints?", blocking: false }, + ], + }, + behavior: { rules: ["Only valid carts become orders."] }, + relations: [refines(specId("spec:orders.order-management"))], + }), + ], + packs: [], + anchors: [], + }, + expect: "pass", +}; + +// MD-12's de-padding proof: model- and decision-kind specs clear scoped/defined on their natural +// evidence alone — no throwaway behavior.rules. +const validDefinedModelOnNaturalEvidence: ValidatorFixture = { + name: "valid-defined-model-on-natural-evidence", + model: { + specs: [ + spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "idea", + intent: { outcome: "Own the order lifecycle for checkout." }, + }), + spec({ + id: specId("spec:orders.order-model"), + title: "Order-management domain vocabulary", + kind: "model", + altitude: "story", + readiness: "defined", + intent: { outcome: "Define the core order terms." }, + model: { terms: { cart: "A customer-selected set of line items." } }, + relations: [refines(specId("spec:orders.order-management"))], + }), + ], + packs: [], + anchors: [], + }, + expect: "pass", +}; + +const validDefinedDecisionOnNaturalEvidence: ValidatorFixture = { + name: "valid-defined-decision-on-natural-evidence", + model: { + specs: [ + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + intent: { outcome: "Turn a valid cart into an order." }, + }), + spec({ + id: specId("spec:decisions.order-lifecycle"), + title: "Order lifecycle keeps validation before creation", + kind: "decision", + altitude: "feature", + readiness: "defined", + intent: { outcome: "Decide when an order may be created." }, + decision: { decision: "Create orders only after cart validation succeeds." }, + relations: [refines(specId("spec:orders.create-order"))], + }), + ], + packs: [], + anchors: [], + }, + expect: "pass", +}; + export const activeValidatorFixtures: readonly ValidatorFixture[] = [ validMinimalIdeaSpec, invalidDuplicateId, invalidScopedWithoutRelation, invalidDefinedConstraintWithoutTarget, + invalidReadyWithBlockingQuestion, + validDefinedWithNonBlockingQuestion, + validDefinedModelOnNaturalEvidence, + validDefinedDecisionOnNaturalEvidence, ]; diff --git a/test/readiness.test.ts b/test/readiness.test.ts index 4f8c0de..fbdf26b 100644 --- a/test/readiness.test.ts +++ b/test/readiness.test.ts @@ -1,11 +1,21 @@ import { describe, expect, it } from "vitest"; import { + SPEC_KINDS, + evaluateReadinessFloor, + kindEvidence, readinessFloors, - readinessKindOverlays, + refines, + spec, + specId, validationSeverities, validatorFamilies, } from "../src/index.js"; +import type { AuthoredModel, Spec } from "../src/index.js"; + +function modelOf(...specs: readonly Spec[]): AuthoredModel { + return { specs, packs: [], anchors: [] }; +} describe("readiness and validation contracts", () => { it("exports the canonical validator families and severities", () => { @@ -13,7 +23,7 @@ describe("readiness and validation contracts", () => { expect(validationSeverities).toEqual(["error", "warning"]); }); - it("defines all canonical readiness floors as inert data", () => { + it("defines the kind-blind structural clauses as one table (MD-13), clause ids defined exactly once", () => { expect(Object.keys(readinessFloors)).toEqual(["idea", "scoped", "defined", "ready"]); expect(readinessFloors.idea.clauses.map((clause) => clause.id)).toEqual([ @@ -27,82 +37,110 @@ describe("readiness and validation contracts", () => { expect(readinessFloors.scoped.clauses.map((clause) => clause.id)).toEqual([ "intent.outcome", "at-least-one-relation", - "rules-examples-or-constraints", + "kind-evidence-present", ]); expect(readinessFloors.defined.clauses.map((clause) => clause.id)).toEqual([ - "rules-and-or-examples", - "constraint-targets-are-machine-readable", + "kind-evidence-complete", "no-blocking-open-questions", ]); expect(readinessFloors.ready.clauses.map((clause) => clause.id)).toEqual([ - "defined-floor", - "no-blocking-open-questions", "all-relations-resolve", "depends-on-and-refines-targets-are-defined", "anchors-resolve", ]); }); - it("marks graph-shaped ready clauses as deferred in Session 1", () => { - const deferredClauses = readinessFloors.ready.clauses - .filter( - ( - clause, - ): clause is Extract< - (typeof readinessFloors.ready.clauses)[number], - { deferredInSession1: true } - > => "deferredInSession1" in clause, - ) - .map((clause) => clause.id); - - expect(deferredClauses).toEqual([ - "all-relations-resolve", - "depends-on-and-refines-targets-are-defined", - "anchors-resolve", - ]); + it("keeps the graph-shaped ready clauses deferred in Session 1 (MD-14)", () => { + expect(readinessFloors.ready.clauses.every((clause) => "deferredInSession1" in clause)).toBe( + true, + ); }); - it("defines kind-aware overlays for constraint, example, and model", () => { - expect(Object.keys(readinessKindOverlays)).toEqual(["constraint", "example", "model"]); - - expect(readinessKindOverlays.constraint).toEqual({ - kind: "constraint", - appliesAtOrAbove: "defined", - clauses: [ - { - id: "constraint-machine-readable-target", - description: - "Constraint specs need a parseable, machine-readable target before defined and ready.", - authoredPaths: ["constraints.target"], - }, - ], - }); + it("covers every kind in the evidence table; workflow and contract ride the behavior row (MD-12)", () => { + expect(Object.keys(kindEvidence).sort()).toEqual([...SPEC_KINDS].sort()); + expect(kindEvidence.workflow).toBe(kindEvidence.behavior); + // Documented interim: the contract row repoints when a dedicated contract section lands. + expect(kindEvidence.contract).toBe(kindEvidence.behavior); + }); - expect(readinessKindOverlays.example).toEqual({ - kind: "example", - appliesAtOrAbove: "defined", - clauses: [ - { - id: "example-given-when-then", - description: - "Example specs need structured given, when, and then detail before defined and ready.", - authoredPaths: ["behavior.given", "behavior.when", "behavior.then"], - }, - ], + it("counts promoted children as evidence — promotion never costs an earned rung (MD-10/MD-12)", () => { + const parent = spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "defined", + intent: { outcome: "Turn a valid cart into an order." }, + // No inline behavior content — the promoted rule child below is the evidence. + relations: [refines(specId("spec:orders.order-management"))], }); - - expect(readinessKindOverlays.model).toEqual({ - kind: "model", - appliesAtOrAbove: "defined", - clauses: [ - { - id: "model-term-definitions", - description: "Model specs need term definitions before defined and ready.", - authoredPaths: ["model.terms"], - }, - ], + const promotedRule = spec({ + id: specId("spec:orders.order-total-rule"), + title: "Order total matches cart math", + kind: "rule", + altitude: "story", + readiness: "defined", + intent: { outcome: "Keep totals deterministic." }, + behavior: { rules: ["The order total is the sum of all line subtotals."] }, + relations: [refines(specId("spec:orders.create-order"))], }); + + expect(evaluateReadinessFloor(parent, modelOf(parent, promotedRule))).toEqual([]); + + expect( + evaluateReadinessFloor(parent, modelOf(parent)).map((failure) => failure.clauseId), + ).toEqual(["kind-evidence-present", "kind-evidence-complete"]); + }); + + it("keeps the constraint floor monotonic: an untargeted entry clears scoped, not defined (MD-12)", () => { + const constraintAt = (readiness: Spec["readiness"]): Spec => + spec({ + id: specId("spec:orders.order-latency-constraint"), + title: "Create-order latency budget", + kind: "constraint", + altitude: "story", + readiness, + intent: { outcome: "Keep create-order fast enough for interactive checkout." }, + constraints: [{ statement: "Create-order should respond within the checkout budget." }], + relations: [refines(specId("spec:orders.create-order"))], + }); + + const scoped = constraintAt("scoped"); + expect(evaluateReadinessFloor(scoped, modelOf(scoped))).toEqual([]); + + const defined = constraintAt("defined"); + expect( + evaluateReadinessFloor(defined, modelOf(defined)).map((failure) => failure.clauseId), + ).toEqual(["kind-evidence-complete"]); + }); + + it("requires a structured GWT entry for a defined example; prose clears scoped only (MD-10)", () => { + const exampleWith = (examples: NonNullable["examples"]): Spec => + spec({ + id: specId("spec:orders.create-order.valid-cart"), + title: "Valid cart creates an order", + kind: "example", + altitude: "story", + readiness: "defined", + intent: { outcome: "Show that a valid cart can become an order." }, + behavior: { examples }, + relations: [refines(specId("spec:orders.create-order"))], + }); + + const prose = exampleWith(["Valid cart becomes an order with the computed total."]); + expect( + evaluateReadinessFloor(prose, modelOf(prose)).map((failure) => failure.clauseId), + ).toEqual(["kind-evidence-complete"]); + + const structured = exampleWith([ + { + given: ["A customer has a valid cart."], + when: ["The customer submits the cart."], + then: ["An order is created."], + }, + ]); + expect(evaluateReadinessFloor(structured, modelOf(structured))).toEqual([]); }); }); diff --git a/test/validators.test.ts b/test/validators.test.ts index 9d03ca5..2b053cc 100644 --- a/test/validators.test.ts +++ b/test/validators.test.ts @@ -171,11 +171,9 @@ describe("validators", () => { createBehaviorSpec(specId("spec:orders.create-order"), "ready", { intent: { outcome: "turn a valid cart into an order" }, relations: [dependsOn(ref("spec:orders.order-management"))], - constraints: { - latencyBudget: { - target: "p95 < 200ms", - }, - }, + // An inline constraint clears the scoped evidence rung, but a defined behavior spec needs + // rules and/or examples — constraints alone no longer suffice (MD-12). + constraints: [{ statement: "order creation stays fast", target: "p95 < 200ms" }], }), ], packs: [], @@ -190,10 +188,10 @@ describe("validators", () => { family: "honesty", severity: "error", subjectId: "spec:orders.create-order", - relatedId: "rules-and-or-examples", + relatedId: "kind-evidence-complete", path: "readiness", message: - 'Spec "spec:orders.create-order" states readiness "ready" but does not satisfy authored clause "rules-and-or-examples".', + 'Spec "spec:orders.create-order" states readiness "ready" but does not satisfy floor clause "kind-evidence-complete": The kind\'s natural evidence is complete (per-kind evidence table).', }, ]); }); @@ -226,8 +224,9 @@ describe("validators", () => { anchors: [], }); - expect(report.validatorId).toBe("conformance/authored-model"); - expect(report.family).toBe("conformance"); + expect(report.validatorId).toBe("authored-model"); + // The aggregate spans both check families, so it carries no single family of its own (F3). + expect(report.family).toBeUndefined(); expect(report.findings).toEqual([]); }); From 47cf2daa5c4d1c0d424c1446f46752e317a21481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:15:19 +0200 Subject: [PATCH 17/29] Post-Wave-B adversarial pass (MD-16): promoted evidence must be carried; runtime authoring-shape check; doc: targets named deferral MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Floor (high): a promoted rule/example child counts only when it carries its kind's evidence; a constrainedBy edge counts only when it resolves to a constraint-kind spec with non-empty constraints[]. Empty stub children and wrong-kind edges no longer clear a parent's floor. Fixtures: invalid-defined-behavior-with-empty-promoted-child, invalid-scoped-behavior-with-non-constraint-constrained-by. 2. Honesty (medium): validateAuthoringShape is the Session-1 stand-in for 05 §2 check 5 — delivery-fact keys smuggled into the envelope or any section (incl. via non-fresh objects past excess-property checking) now fail at runtime. Fixture: invalid-hand-authored-delivery-fact-in-section (runtime twin). 3. doc: targets (medium): explicit named deferral where the affordance is promised (02 §6, glossary, ids.ts/relations.ts notes); F2 stays closed as the doc-note it was scoped to be. Recorded as DECISIONS MD-16; plans/02 + AGENTS updated. npm run check green. --- AGENTS.md | 9 +- docs/concept/02-core-model.md | 2 + docs/concept/05-validation-and-honesty.md | 2 +- docs/concept/DECISIONS.md | 36 +++++++ docs/concept/ubiquitous-language.md | 3 +- plans/02-phase0-hardening.md | 16 +++- src/model/relations.ts | 5 + src/validate/readiness-floor.ts | 52 +++++++--- src/validate/validators.ts | 72 ++++++++++++++ test/builders.typecheck.ts | 11 ++- test/fixtures.test.ts | 5 +- test/fixtures/authored-model.fixtures.ts | 112 +++++++++++++++++++++- test/readiness.test.ts | 15 +++ 13 files changed, 312 insertions(+), 28 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 901b4e8..16595be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,9 +10,10 @@ language, and the JTBD stories together **are the spec** — the product's own t design*) applied to itself: there is no separate PRD. > **Status:** concept ratified and locked; **Slice 0 (Phase 0 — the protocol as typed code) is built, green, and -> hardened — hardening complete** (Waves A + B done; all post-Session-1 decisions resolved *and executed*, -> DECISIONS MD-8…MD-15). The live per-session build plan is in **`plans/`**; the slice roadmap is -> **`docs/concept/07`**. (`plans/` is the source of truth for "what now.") +> hardened — hardening complete** (Waves A + B done plus the post-execution adversarial pass; all +> post-Session-1 decisions resolved *and executed*, DECISIONS MD-8…MD-16). The live per-session build plan is +> in **`plans/`**; the slice roadmap is **`docs/concept/07`**. (`plans/` is the source of truth for "what +> now.") ## The frame @@ -46,7 +47,7 @@ Progressive disclosure — start at the top, follow the pointers down. | `docs/concept/ubiquitous-language.md` | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology. The **model exposition** was rehomed into `00`–`07` (2026-06-10 Fold-A; the glossary header maps the old `base §n` refs) | **first, always** | | `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/00`–`07` (+ README) | **the technical design and the model** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model (`02` — the primitive, descriptors, sections, relations), the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | -| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the MD-series MD-1…MD-15, the ratified R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | +| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the MD-series MD-1…MD-16, the ratified R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code | | `reviews/` | **archived session reviews** (implementation, founding-ideation, adversarial + prompts) — durable findings already folded into plans/DECISIONS; read for provenance | rarely | diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index 48cecf9..23ff310 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -302,6 +302,8 @@ MVP relation vocabulary (a Representation; extensible): Two notes carried from the language ratification: the verb forms align with UML where a standard stereotype exists (`refines` ≈ «refine», `dependsOn` ≈ UML *Dependency*, `decidedBy` ≈ «trace», `verifies` ≈ «verify») — adopted nouns, per the governing rubric. And `constrainedBy` / `decidedBy` are deliberately kept **distinct** from a generic `dependsOn`: "bounded by an NFR" and "shaped by a decision" are high-value, separately-queryable intents that a generic dependency edge would flatten. +> **`doc:` targets are a named deferral (MD-16).** The DSL's relation builders and `ref()` accept only `spec:` targets today, so `decidedBy` → an external `doc:` ADR is designed-for but not yet representable; until the need arrives, an external ADR is referenced from the decision spec's body, not by a typed edge. (The glossary's flagged-ambiguities entry and the notes in `src/ids.ts` / `src/model/relations.ts` carry the same flag.) + **Derived edges — never authored:** - `satisfies` (code → spec) — emitted from an **anchor** in source; carries `claim:"anchored"`. This is the inverse of the old `satisfiedBy`: the direction is **code → spec**, and it is **derived from anchors**, never hand-authored. diff --git a/docs/concept/05-validation-and-honesty.md b/docs/concept/05-validation-and-honesty.md index d519e40..c1d3867 100644 --- a/docs/concept/05-validation-and-honesty.md +++ b/docs/concept/05-validation-and-honesty.md @@ -95,7 +95,7 @@ The floor has two parts: **kind-blind structural clauses** (the same for every k Three laws bound the table: - **Monotonic by construction.** Every `defined` cell implies its `scoped` cell — clearing the higher rung always clears the lower. (The pre-MD-12 floor failed this for `constraint`: its natural evidence stopped counting between `scoped` and `defined`.) -- **Promotion-neutral.** Promoted forms count wherever one exists (`02` §3) — de-composing a spec into children never costs it its earned rung. +- **Promotion-neutral.** Promoted forms count wherever one exists (`02` §3) — de-composing a spec into children never costs it its earned rung. The converse honesty bound (MD-16): a promoted form counts **only when it itself carries its kind's evidence** — promotion *moves content out* (`02` §3), so an empty stub child, or a `constrainedBy` edge that does not resolve to a `constraint`-kind spec carrying its constraints, is not evidence. - **Convergence is honest.** Where the rungs converge (`rule`, `model`), the floor refuses to become a quota ("more terms," "more rules" — the tier-filling P4 forbids); those rungs then differ only by the author's stated confidence plus the kind-blind clauses. > **Representation note (MD-13).** In code, the floor *table* is the single source of truth: rows carry the clause id, description, and a named predicate; the evaluator is one generic loop and the clause-id type is derived from the table. The tables above and the data in `readiness-floor.ts` are intended to be reviewable as mirror images. diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index b46a257..2a97db9 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -296,6 +296,42 @@ lives only in the path — future colocated specs would carry no marker). **Execution.** Wave B renames the example files and this repo's docs/globs; the extractor (Slice 1) ships reading `*.sdp.ts` from day one. +## 2026-06-10 — Session: Wave B execution + post-execution adversarial pass + +### MD-16 — Honesty-sharpenings from the post-Wave-B adversarial pass: promoted evidence must be carried; authoring-shape gets its runtime stand-in; `doc:` targets are an explicit deferral [ACCEPTED 2026-06-10] +**Context.** An adversarial review (Codex) of the executed Wave B challenged three honesty surfaces: +(1) the promotion-neutral floor counted *any* refining `rule`/`example` child and *any* `constrainedBy` +edge as evidence — an empty stub child or a wrong-kind edge could clear a parent's `scoped`/`defined`; +(2) MD-11's closed sections rely on TypeScript's excess-property check, which fires only on fresh +literals — a section assembled through an intermediate variable smuggles a delivery fact past `tsc`, +and nothing caught it at runtime before Slice 3; (3) the base's `decidedBy` → external `doc:` ADR +affordance (`02` §6) is unrepresentable in the DSL, making the F2 doc-note close-out read stronger +than its scope. +**Decision.** All three resolved as honesty clarifications consistent with the settled decisions: +1. **Promoted evidence counts only when the promoted spec itself carries its kind's evidence** (its + `scoped` cell in the per-kind table): a refining `rule`/`example` child must hold its + statement/entry; a `constrainedBy` edge counts only when it resolves in the model to a + `constraint`-kind spec with non-empty `constraints[]` (the section ⟷ kind twin of inline + `constraints`). Follows from MD-10 — promotion *moves content out*, so an empty child is not a + promotion but a stub. *Rejected:* requiring a minimum child **readiness** (a cross-spec readiness + gate below `ready` — the base reserves cross-spec rungs for the deferred `ready` clauses, and + readiness is the author's statement, not structural evidence); counting `rule`-kind `constrainedBy` + targets in the constraints-evidence slot (the duality twin of `constraints` is `constraint`; + additive to relax later if real usage wants it). +2. **Authoring-shape honesty (`05` §2, check 5) gets its Session-1 runtime stand-in now**: + `validateAuthoringShape` fails any spec or pack that hand-authors a delivery-fact key + (`implemented` / `has-verifier` / `observed`) at the envelope or inside any section. Not a second + validation path (MD-14): it is an MVP honesty check implemented early on the same stand-in harness + the readiness floor already rides, and it migrates onto the graph with the rest. The type-level + closure (MD-11) stays the ergonomics lever; its fresh-literal scope is now recorded for sections + exactly as F7 recorded it for the envelope, and the bypass is pinned twice (compile-time + + runtime fixtures). +3. **`doc:`-target relations are an explicit named deferral**, stated where the affordance is promised + (`02` §6, the glossary's flagged ambiguities, and doc notes in `ids.ts`/`relations.ts`): the DSL + stays `spec:`-target-only until the external-ADR need arrives. F2 stays closed *as the doc-note it + was scoped to be*; implementing a `DocId` target type now was rejected (no call site wants it, and + the shape should be decided when `doc:`/pack-targeting actually arrive). + ## Structural-decision shorthand (D1–D6) [ACCEPTED · relocated here when the cleanup plan was retired, 2026-06-07] > These six labels come from the original structural-decisions pass. Their *content* is canonical in the diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index 98f59c3..fdd5fbc 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -178,7 +178,8 @@ artifact** — approval provenance is git-native, never an authored primitive). - **"epistemic boundary"** is a *working name* for the humans-assert-intent / machines-assert-structure division (`01`); a friendlier Studio-facing name is a minor open item. - **`ref()`** in the DSL is today a **spec-only** reference builder wearing a generic name (it rejects - `pack:`/`doc:` targets) — documented on the export (`src/ids.ts`); revisit when `doc:`-target + `pack:`/`doc:` targets) — documented on the export (`src/ids.ts`). Consequently `decidedBy` → an + external `doc:` ADR is a **named deferral** (MD-16, stated in `02` §6); revisit when `doc:`-target relations or pack-targeting arrive. ## Term ledger (locked / rejected / resolved) diff --git a/plans/02-phase0-hardening.md b/plans/02-phase0-hardening.md index 73c669b..20f72ee 100644 --- a/plans/02-phase0-hardening.md +++ b/plans/02-phase0-hardening.md @@ -81,8 +81,10 @@ literals; `npm run check` green (typecheck ×2 · lint · format · tests · bui ### Carried review backlog — closed or re-homed -- **F2 ✅** — `ref()`'s spec-only nature is documented on the export (`src/ids.ts`); revisit when - `doc:`-target relations or pack-targeting arrive (the flag stays in the glossary). +- **F2 ✅ (as scoped — a doc note)** — `ref()`'s spec-only nature is documented on the export + (`src/ids.ts`), and the consequence — `decidedBy` → external `doc:` ADR is not yet representable — + is an explicit named deferral in `02` §6 (MD-16); revisit when `doc:`-target relations or + pack-targeting arrive (the flag stays in the glossary). - **F3 ✅** — the aggregate `validateAuthoredModel` report no longer claims a single `family` (`ValidationReport.family` is optional; the aggregate id is `authored-model`); each finding carries its own family. @@ -93,6 +95,16 @@ literals; `npm run check` green (typecheck ×2 · lint · format · tests · bui - **H4 — dissolved** for `behavior` (MD-10): refs cannot exist in sections, so there is nothing to check; what remains is exactly F4 above. +### Post-execution adversarial pass — ✅ folded in same-day (MD-16) + +A post-Wave-B adversarial review (Codex) challenged three honesty surfaces; all three were resolved +and landed 2026-06-10 (rationale in MD-16): promoted evidence counts only when the promoted spec +itself carries its kind's evidence (an empty stub child or a wrong-kind/dangling `constrainedBy` +edge no longer clears a floor — regression fixtures added); `validateAuthoringShape` is the +Session-1 runtime stand-in for the `05` §2 authoring-shape honesty check (a delivery fact smuggled +into a section via a non-fresh object now fails at runtime, not only at the type level); and +`doc:`-target relations are an explicit named deferral (`02` §6). + ## §5 — Explicitly out of scope (still deferred) The `ts-morph` extractor · `graph.json` / graph emission · the graph-level validator gate · diff --git a/src/model/relations.ts b/src/model/relations.ts index 24414a4..d498c1b 100644 --- a/src/model/relations.ts +++ b/src/model/relations.ts @@ -13,6 +13,11 @@ export type SpecRelationType = (typeof SPEC_RELATION_TYPES)[number]; export interface SpecRelation { readonly type: TType; + /** + * Authored relation targets are `spec:`-only today. The base reserves `decidedBy` → `doc:` for a + * genuinely external ADR (`02` §6), but that target type is a named deferral until the need + * arrives (MD-16) — see also the `ref()` note in `ids.ts`. + */ readonly target: SpecId; readonly claim: "declared"; } diff --git a/src/validate/readiness-floor.ts b/src/validate/readiness-floor.ts index cf2783e..f0212c8 100644 --- a/src/validate/readiness-floor.ts +++ b/src/validate/readiness-floor.ts @@ -104,19 +104,49 @@ function hasInlineBehaviorEvidence(spec: Spec): boolean { return hasInlineRulesOrExamples(spec) || hasEntries(spec.behavior?.flows); } -/** Promotion-neutral evidence (MD-10/MD-12): a promoted child counts wherever an inline entry would. */ -function hasRefiningRuleOrExampleChild(spec: Spec, model: AuthoredModel): boolean { +/** Typed accessor into the evidence table — returns the cell at its declared (2-arg) predicate type. */ +function evidenceCell(kind: SpecKind, rung: keyof KindEvidenceRow): KindEvidenceCell { + return kindEvidence[kind][rung]; +} + +/** + * Promotion-neutral evidence (MD-10/MD-12), bounded by MD-16: a promoted child counts wherever an + * inline entry would — but only when the child itself carries its kind's evidence. Promotion moves + * content out (MD-10), so an empty stub child is not a promotion and never clears a parent's floor. + */ +function hasPromotedRuleOrExampleEvidence(spec: Spec, model: AuthoredModel): boolean { return model.specs.some( (child) => (child.kind === "rule" || child.kind === "example") && + evidenceCell(child.kind, "scoped").predicate(child, model) && (child.relations ?? []).some( (relation) => relation.type === "refines" && relation.target === spec.id, ), ); } -function hasConstrainedByRelation(spec: Spec): boolean { - return (spec.relations ?? []).some((relation) => relation.type === "constrainedBy"); +/** + * The constrainedBy evidence slot is the promoted twin of the inline `constraints` section (`02` §3 + * duality), so it counts only when the edge resolves in the model to a `constraint`-kind spec that + * carries its own evidence (MD-16). A dangling or wrong-kind target is not evidence. + */ +function hasConstrainedByConstraintEvidence(spec: Spec, model: AuthoredModel): boolean { + const targets = new Set( + (spec.relations ?? []) + .filter((relation) => relation.type === "constrainedBy") + .map((relation) => relation.target), + ); + + if (targets.size === 0) { + return false; + } + + return model.specs.some( + (candidate) => + targets.has(candidate.id) && + candidate.kind === "constraint" && + evidenceCell("constraint", "scoped").predicate(candidate, model), + ); } function hasStructuredExampleEntry(spec: Spec): boolean { @@ -157,18 +187,18 @@ function hasWrittenDecision(spec: Spec): boolean { const behaviorFamilyEvidence: KindEvidenceRow = { scoped: { description: - "rules, examples, flows, or constraints — inline, or promoted (a refining rule/example child, or a constrainedBy target)", + "rules, examples, flows, or constraints — inline, or promoted (a refining rule/example child, or a constrainedBy-linked constraint, each carrying its own evidence)", predicate: (spec, model) => hasInlineBehaviorEvidence(spec) || hasConstraintEntries(spec) || - hasConstrainedByRelation(spec) || - hasRefiningRuleOrExampleChild(spec, model), + hasConstrainedByConstraintEvidence(spec, model) || + hasPromotedRuleOrExampleEvidence(spec, model), }, defined: { description: - "rules and/or examples, inline or promoted children — constraints alone no longer suffice", + "rules and/or examples — inline or promoted children carrying their evidence; constraints alone no longer suffice", predicate: (spec, model) => - hasInlineRulesOrExamples(spec) || hasRefiningRuleOrExampleChild(spec, model), + hasInlineRulesOrExamples(spec) || hasPromotedRuleOrExampleEvidence(spec, model), }, }; @@ -232,10 +262,10 @@ export const kindEvidence = { } as const satisfies Record; const kindEvidencePresent: ReadinessPredicate = (spec, model) => - kindEvidence[spec.kind].scoped.predicate(spec, model); + evidenceCell(spec.kind, "scoped").predicate(spec, model); const kindEvidenceComplete: ReadinessPredicate = (spec, model) => - kindEvidence[spec.kind].defined.predicate(spec, model); + evidenceCell(spec.kind, "defined").predicate(spec, model); /* ----- the kind-blind structural clauses (mirrors `05` §3) ----- */ diff --git a/src/validate/validators.ts b/src/validate/validators.ts index 8c74615..0680908 100644 --- a/src/validate/validators.ts +++ b/src/validate/validators.ts @@ -1,10 +1,13 @@ +import { deliveryFactNames } from "../graph/schema.js"; import type { AnchorId, PackId, SpecId } from "../ids.js"; +import { SPEC_SECTION_NAMES } from "../model/sections.js"; import type { AuthoredModel } from "./authored-model.js"; import type { Finding, ValidationReport, ValidatorFamily } from "./contracts.js"; import { evaluateReadinessFloor } from "./readiness-floor.js"; const duplicateIdsValidatorId = "conformance/duplicate-ids"; const danglingReferencesValidatorId = "conformance/dangling-references"; +const authoringShapeValidatorId = "honesty/authoring-shape"; const readinessFloorValidatorId = "honesty/readiness-floor"; const authoredModelValidatorId = "authored-model"; @@ -126,6 +129,74 @@ export function validateDanglingReferences(model: AuthoredModel): ValidationRepo return createReport(danglingReferencesValidatorId, findings, "conformance"); } +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +/** + * Authoring-shape honesty (`05` §2, check 5), stood in at the authored layer until the extractor + * lands (MD-16): no spec or pack hand-authors a delivery fact. The typed sections (MD-11) reject + * this for inline literals at `tsc` time, but TypeScript's excess-property check fires only on + * fresh literals — a section assembled through an intermediate variable slips past it, and this + * runtime check is what catches it. + */ +export function validateAuthoringShape(model: AuthoredModel): ValidationReport { + const findings: Finding[] = []; + + const appendSmuggledFact = (subjectId: string, factName: string, path: string): void => { + findings.push( + createFinding( + authoringShapeValidatorId, + "honesty", + `"${subjectId}" hand-authors the derived delivery fact "${factName}" at "${path}" — delivery facts are derived, never authored.`, + subjectId, + factName, + path, + ), + ); + }; + + const scanCarrier = (subjectId: string, carrier: unknown, basePath: string): void => { + if (!isRecord(carrier)) { + return; + } + + for (const factName of deliveryFactNames) { + if (factName in carrier) { + appendSmuggledFact( + subjectId, + factName, + basePath === "" ? factName : `${basePath}.${factName}`, + ); + } + } + }; + + for (const authoredSpec of model.specs) { + const specRecord = authoredSpec as unknown as Record; + scanCarrier(authoredSpec.id, specRecord, ""); + + for (const sectionName of SPEC_SECTION_NAMES) { + const section = specRecord[sectionName]; + + if (Array.isArray(section)) { + for (const [index, entry] of section.entries()) { + scanCarrier(authoredSpec.id, entry, `${sectionName}[${String(index)}]`); + } + continue; + } + + scanCarrier(authoredSpec.id, section, sectionName); + } + } + + for (const authoredPack of model.packs) { + scanCarrier(authoredPack.id, authoredPack, ""); + } + + return createReport(authoringShapeValidatorId, findings, "honesty"); +} + export function validateReadinessFloors(model: AuthoredModel): ValidationReport { const findings: Finding[] = []; @@ -156,6 +227,7 @@ export function validateAuthoredModel(model: AuthoredModel): ValidationReport { const findings = [ ...validateDuplicateIds(model).findings, ...validateDanglingReferences(model).findings, + ...validateAuthoringShape(model).findings, ...validateReadinessFloors(model).findings, ]; diff --git a/test/builders.typecheck.ts b/test/builders.typecheck.ts index d583a37..1426bc6 100644 --- a/test/builders.typecheck.ts +++ b/test/builders.typecheck.ts @@ -98,9 +98,14 @@ spec({ "has-verifier": true, }); -// invalid-hand-authored-delivery-fact-in-section (the H8 fixture, landed compile-time): the -// in-section bypass is closed by the typed floor-bearing sections (MD-11) — a closed BehaviorSection -// has no index signature, so a smuggled delivery fact no longer typechecks. +// invalid-hand-authored-delivery-fact-in-section, the compile-time twin (MD-11): the in-section +// bypass is closed for inline literals by the typed floor-bearing sections — a closed +// BehaviorSection has no index signature, so a smuggled delivery fact no longer typechecks. The +// same excess-property caveat as the envelope defenses above applies: this fires only on fresh +// literals. A section assembled through an intermediate variable slips past tsc and is caught at +// runtime by `honesty/authoring-shape` (MD-16; the runtime twin lives in the fixture suite); +// structurally, non-static section authoring is rejected by static reification (P5) and the +// `sdp/spec-static` lint when the extractor lands (Slice 1). spec({ id: specId("spec:orders.create-order"), title: "Customer creates an order", diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts index c2634b4..66d1b7c 100644 --- a/test/fixtures.test.ts +++ b/test/fixtures.test.ts @@ -26,6 +26,7 @@ describe("authored-model validator fixtures (should-pass / should-fail regressio // The Wave-B gated stubs resolved (plans/02 §4): `invalid-ready-with-blocking-question` is active // above (MD-9 — the floor reads intent.openQuestions); `invalid-hand-authored-delivery-fact-in- - // section` is a compile-time `@ts-expect-error` fixture in `builders.typecheck.ts` (MD-11 — closed - // section types reject it before any validator runs). + // section` is pinned twice — a compile-time `@ts-expect-error` fixture in `builders.typecheck.ts` + // (MD-11, the fresh-literal path) and the active runtime fixture above (MD-16, the non-fresh + // object that slips past excess-property checking). }); diff --git a/test/fixtures/authored-model.fixtures.ts b/test/fixtures/authored-model.fixtures.ts index cc32ccc..c070621 100644 --- a/test/fixtures/authored-model.fixtures.ts +++ b/test/fixtures/authored-model.fixtures.ts @@ -1,4 +1,4 @@ -import { refines, spec, specId } from "../../src/index.js"; +import { constrainedBy, refines, spec, specId } from "../../src/index.js"; import type { AuthoredModel } from "../../src/index.js"; /** @@ -6,9 +6,10 @@ import type { AuthoredModel } from "../../src/index.js"; * (`05` §5 "Validator self-testing"). Each fixture pins a single validator outcome so a regression * that silently stops a validator firing is itself caught. * - * The Wave-B compile-time twin lives in `test/builders.typecheck.ts`: a hand-authored delivery fact - * inside a section (`invalid-hand-authored-delivery-fact-in-section`) is rejected by the closed - * section types (MD-11), so it is pinned as a `@ts-expect-error` fixture, not a runtime one. + * The hand-authored-delivery-fact bypass is pinned twice, on purpose (MD-11 + MD-16): the + * compile-time twin in `test/builders.typecheck.ts` proves the closed section types reject it for + * inline literals; the runtime fixture here proves `honesty/authoring-shape` catches the non-fresh + * object that slips past TypeScript's excess-property check. * The extractor-era fixtures stay named for Slice 1+ (`plans/02` §3 H8): `invalid-non-static-id` · * `invalid-non-static-section` · `invalid-hand-authored-satisfies-edge` · * `invalid-ready-with-unresolved-dependency` · `invalid-ready-with-target-below-defined`. @@ -256,6 +257,106 @@ const validDefinedDecisionOnNaturalEvidence: ValidatorFixture = { expect: "pass", }; +// The runtime twin of the compile-time bypass fixture (MD-16): excess-property checking fires only +// on fresh literals, so a section assembled through an intermediate variable smuggles a delivery +// fact past tsc — the authoring-shape honesty check is what catches it. +const smuggledBehaviorSection = { + rules: ["Only valid carts become orders."], + "has-verifier": true, +}; + +const invalidHandAuthoredDeliveryFactInSection: ValidatorFixture = { + name: "invalid-hand-authored-delivery-fact-in-section", + model: { + specs: [ + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "idea", + intent: { outcome: "Turn a valid cart into an order." }, + behavior: smuggledBehaviorSection, + }), + ], + packs: [], + anchors: [], + }, + expect: { validatorId: "honesty/authoring-shape", relatedId: "has-verifier" }, +}; + +// MD-16's promoted-evidence bound: an empty stub child is not a promotion — promotion moves content +// out (MD-10), so a child carrying no evidence of its own never clears the parent's floor. +const invalidDefinedBehaviorWithEmptyPromotedChild: ValidatorFixture = { + name: "invalid-defined-behavior-with-empty-promoted-child", + model: { + specs: [ + spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "idea", + intent: { outcome: "Own the order lifecycle for checkout." }, + }), + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "defined", + intent: { outcome: "Turn a valid cart into an order." }, + // No inline behavior content — the only would-be evidence is the empty stub child below. + relations: [refines(specId("spec:orders.order-management"))], + }), + spec({ + id: specId("spec:orders.order-total-rule"), + title: "Order total matches cart math", + kind: "rule", + altitude: "story", + readiness: "idea", + // The stub: a rule child with no behavior.rules statement. It clears its own idea floor + // (parent relation present) but contributes no evidence to the parent. + relations: [refines(specId("spec:orders.create-order"))], + }), + ], + packs: [], + anchors: [], + }, + expect: { validatorId: "honesty/readiness-floor", relatedId: "kind-evidence-present" }, +}; + +// MD-16's constrainedBy bound: the evidence slot is the promoted twin of the inline constraints +// section, so an edge to a non-constraint spec is not constraints evidence. +const invalidScopedBehaviorWithNonConstraintConstrainedBy: ValidatorFixture = { + name: "invalid-scoped-behavior-with-non-constraint-constrained-by", + model: { + specs: [ + spec({ + id: specId("spec:orders.order-management"), + title: "Order management", + kind: "behavior", + altitude: "epic", + readiness: "idea", + intent: { outcome: "Own the order lifecycle for checkout." }, + }), + spec({ + id: specId("spec:orders.create-order"), + title: "Create order", + kind: "behavior", + altitude: "feature", + readiness: "scoped", + intent: { outcome: "Turn a valid cart into an order." }, + // The edge resolves, but to a behavior spec — not a constraint carrying its evidence. + relations: [constrainedBy(specId("spec:orders.order-management"))], + }), + ], + packs: [], + anchors: [], + }, + expect: { validatorId: "honesty/readiness-floor", relatedId: "kind-evidence-present" }, +}; + export const activeValidatorFixtures: readonly ValidatorFixture[] = [ validMinimalIdeaSpec, invalidDuplicateId, @@ -265,4 +366,7 @@ export const activeValidatorFixtures: readonly ValidatorFixture[] = [ validDefinedWithNonBlockingQuestion, validDefinedModelOnNaturalEvidence, validDefinedDecisionOnNaturalEvidence, + invalidHandAuthoredDeliveryFactInSection, + invalidDefinedBehaviorWithEmptyPromotedChild, + invalidScopedBehaviorWithNonConstraintConstrainedBy, ]; diff --git a/test/readiness.test.ts b/test/readiness.test.ts index fbdf26b..ab01c70 100644 --- a/test/readiness.test.ts +++ b/test/readiness.test.ts @@ -92,6 +92,21 @@ describe("readiness and validation contracts", () => { expect( evaluateReadinessFloor(parent, modelOf(parent)).map((failure) => failure.clauseId), ).toEqual(["kind-evidence-present", "kind-evidence-complete"]); + + // An empty stub child is not a promotion (MD-16): promotion moves content out (MD-10), so a + // rule child with no statement of its own contributes no evidence. + const stubRule = spec({ + id: specId("spec:orders.order-total-rule"), + title: "Order total matches cart math", + kind: "rule", + altitude: "story", + readiness: "idea", + relations: [refines(specId("spec:orders.create-order"))], + }); + + expect( + evaluateReadinessFloor(parent, modelOf(parent, stubRule)).map((failure) => failure.clauseId), + ).toEqual(["kind-evidence-present", "kind-evidence-complete"]); }); it("keeps the constraint floor monotonic: an untargeted entry clears scoped, not defined (MD-12)", () => { From 5b467ee7e145d485f963622b9694750e30edb61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:35:29 +0200 Subject: [PATCH 18/29] =?UTF-8?q?De-temporalize=20code=20(plan=2005=20?= =?UTF-8?q?=C2=A71):=20deferredInSession1=20->=20evaluatedOver:'graph';=20?= =?UTF-8?q?comments=20state=20invariants,=20not=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validate/authored-model.ts | 4 +++- src/validate/readiness-floor.ts | 22 +++++++++++++--------- src/validate/validators.ts | 7 ++++--- test/fixtures.test.ts | 6 ------ test/fixtures/authored-model.fixtures.ts | 13 +++++++------ test/readiness.test.ts | 10 ++++++---- test/readiness.typecheck.ts | 2 +- test/validators.test.ts | 2 +- 8 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/validate/authored-model.ts b/src/validate/authored-model.ts index 0866afe..894be07 100644 --- a/src/validate/authored-model.ts +++ b/src/validate/authored-model.ts @@ -3,7 +3,9 @@ import type { Pack } from "../model/pack.js"; import type { Spec } from "../model/spec.js"; /** - * Session 1 authored-layer DTO for pre-graph checks; not persisted, not a graph, and not the Slice 3 validation gate. + * Pre-graph authored-layer DTO — the stand-in harness until the extractor lands (one validation + * path, MD-14): not persisted, not a graph, and never a second public validation seam (not the + * Slice 3 gate). */ export interface AuthoredModel { readonly specs: readonly Spec[]; diff --git a/src/validate/readiness-floor.ts b/src/validate/readiness-floor.ts index f0212c8..d09ca48 100644 --- a/src/validate/readiness-floor.ts +++ b/src/validate/readiness-floor.ts @@ -21,14 +21,17 @@ export interface ActiveReadinessClause { readonly predicate: ReadinessPredicate; } -/** Graph-shaped clauses that execute over the one graph (MD-14, Slice 1/3); the Session-1 evaluator skips them. */ -export interface DeferredReadinessClause { +/** + * Graph-shaped clauses — they resolve across the one graph (one validation path, MD-14; executes + * Slice 1/3) and carry no pre-graph predicate, so the pre-graph evaluator skips them. + */ +export interface GraphReadinessClause { readonly id: string; readonly description: string; - readonly deferredInSession1: true; + readonly evaluatedOver: "graph"; } -export type ReadinessClause = ActiveReadinessClause | DeferredReadinessClause; +export type ReadinessClause = ActiveReadinessClause | GraphReadinessClause; export interface ReadinessFloor { readonly readiness: SpecReadiness; @@ -341,17 +344,17 @@ export const readinessFloors = { { id: "all-relations-resolve", description: "All authored relations resolve to known targets.", - deferredInSession1: true, + evaluatedOver: "graph", }, { id: "depends-on-and-refines-targets-are-defined", description: "Every dependsOn and refines target is at least defined.", - deferredInSession1: true, + evaluatedOver: "graph", }, { id: "anchors-resolve", description: "Any authored anchors present resolve.", - deferredInSession1: true, + evaluatedOver: "graph", }, ], }, @@ -367,7 +370,8 @@ export interface ReadinessFloorFailure { /** * The one generic evaluator (MD-13): floors are cumulative, so every clause at or below the stated - * rung must hold; deferred (graph-shaped) clauses are skipped until the extractor lands. + * rung must hold; graph-shaped clauses carry no pre-graph predicate and are skipped until the + * extractor lands (Slice 1/3). */ export function evaluateReadinessFloor( spec: Spec, @@ -378,7 +382,7 @@ export function evaluateReadinessFloor( for (const readiness of SPEC_READINESS.slice(0, statedIndex + 1)) { for (const clause of readinessFloors[readiness].clauses) { - if ("deferredInSession1" in clause) { + if ("evaluatedOver" in clause) { continue; } diff --git a/src/validate/validators.ts b/src/validate/validators.ts index 0680908..e71e898 100644 --- a/src/validate/validators.ts +++ b/src/validate/validators.ts @@ -219,9 +219,10 @@ export function validateReadinessFloors(model: AuthoredModel): ValidationReport } /** - * Pre-graph authored-layer validation only. This composes the tiny Session 1 authored-model checks and is not the - * Slice 3 graph validator gate. The aggregate spans both check families, so it carries no single - * `family` of its own — each finding states its family (`conformance` or `honesty`). + * Pre-graph authored-layer validation only. This composes the pre-graph authored-model checks and + * is not the Slice 3 graph validator gate (one validation path, MD-14). The aggregate spans both + * check families, so it carries no single `family` of its own — each finding states its family + * (`conformance` or `honesty`). */ export function validateAuthoredModel(model: AuthoredModel): ValidationReport { const findings = [ diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts index 66d1b7c..80f7d8b 100644 --- a/test/fixtures.test.ts +++ b/test/fixtures.test.ts @@ -23,10 +23,4 @@ describe("authored-model validator fixtures (should-pass / should-fail regressio expect(matching.length).toBeGreaterThan(0); }); } - - // The Wave-B gated stubs resolved (plans/02 §4): `invalid-ready-with-blocking-question` is active - // above (MD-9 — the floor reads intent.openQuestions); `invalid-hand-authored-delivery-fact-in- - // section` is pinned twice — a compile-time `@ts-expect-error` fixture in `builders.typecheck.ts` - // (MD-11, the fresh-literal path) and the active runtime fixture above (MD-16, the non-fresh - // object that slips past excess-property checking). }); diff --git a/test/fixtures/authored-model.fixtures.ts b/test/fixtures/authored-model.fixtures.ts index c070621..53337b1 100644 --- a/test/fixtures/authored-model.fixtures.ts +++ b/test/fixtures/authored-model.fixtures.ts @@ -2,15 +2,16 @@ import { constrainedBy, refines, spec, specId } from "../../src/index.js"; import type { AuthoredModel } from "../../src/index.js"; /** - * Systematic should-pass / should-fail fixtures for the Session-1 authored-layer validators + * Systematic should-pass / should-fail fixtures for the pre-graph authored-layer validators * (`05` §5 "Validator self-testing"). Each fixture pins a single validator outcome so a regression * that silently stops a validator firing is itself caught. * - * The hand-authored-delivery-fact bypass is pinned twice, on purpose (MD-11 + MD-16): the - * compile-time twin in `test/builders.typecheck.ts` proves the closed section types reject it for - * inline literals; the runtime fixture here proves `honesty/authoring-shape` catches the non-fresh - * object that slips past TypeScript's excess-property check. - * The extractor-era fixtures stay named for Slice 1+ (`plans/02` §3 H8): `invalid-non-static-id` · + * The hand-authored-delivery-fact bypass is pinned twice, on purpose (the typing law MD-11 + + * carried evidence MD-16): the compile-time twin in `test/builders.typecheck.ts` proves the closed + * section types reject it for inline literals; the runtime fixture here proves + * `honesty/authoring-shape` catches the non-fresh object that slips past TypeScript's + * excess-property check. + * The extractor-era fixtures stay named, awaiting Slice 1+: `invalid-non-static-id` · * `invalid-non-static-section` · `invalid-hand-authored-satisfies-edge` · * `invalid-ready-with-unresolved-dependency` · `invalid-ready-with-target-below-defined`. */ diff --git a/test/readiness.test.ts b/test/readiness.test.ts index ab01c70..6d98234 100644 --- a/test/readiness.test.ts +++ b/test/readiness.test.ts @@ -52,10 +52,12 @@ describe("readiness and validation contracts", () => { ]); }); - it("keeps the graph-shaped ready clauses deferred in Session 1 (MD-14)", () => { - expect(readinessFloors.ready.clauses.every((clause) => "deferredInSession1" in clause)).toBe( - true, - ); + it("marks the ready clauses graph-shaped — evaluated over the one graph, never the pre-graph harness (one validation path, MD-14)", () => { + expect( + readinessFloors.ready.clauses.every( + (clause) => "evaluatedOver" in clause && clause.evaluatedOver === "graph", + ), + ).toBe(true); }); it("covers every kind in the evidence table; workflow and contract ride the behavior row (MD-12)", () => { diff --git a/test/readiness.typecheck.ts b/test/readiness.typecheck.ts index f952803..45ff22f 100644 --- a/test/readiness.typecheck.ts +++ b/test/readiness.typecheck.ts @@ -66,7 +66,7 @@ const invalidAuthoredModel: AuthoredModel = { specs: [], packs: [], anchors: [], - // @ts-expect-error Session 1 authored models stay in-memory only. + // @ts-expect-error the pre-graph authored model is an in-memory DTO — it carries no source-file bookkeeping. sourceFiles: [], }; diff --git a/test/validators.test.ts b/test/validators.test.ts index 2b053cc..2776349 100644 --- a/test/validators.test.ts +++ b/test/validators.test.ts @@ -196,7 +196,7 @@ describe("validators", () => { ]); }); - it("ignores Session 1 deferred ready checks for target readiness and anchor resolution", () => { + it("skips graph-shaped ready clauses pre-graph (target readiness and anchor resolution wait for the extractor)", () => { const report = validateReadinessFloors({ specs: [ createBehaviorSpec(specId("spec:orders.create-order"), "ready", { From 01f5e1427115b4a01ad62edb2615110fe28d494c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:37:00 +0200 Subject: [PATCH 19/29] =?UTF-8?q?De-temporalize=20docs=20(plan=2005=20?= =?UTF-8?q?=C2=A72):=20cite=20decisions=20not=20events=20in=2002/04/05/07;?= =?UTF-8?q?=20AGENTS.md=20status=20->=20one=20pointer=20at=20plans/;=20glo?= =?UTF-8?q?ssary=20header=20shrinks,=20base-=C2=A7=20map=20moves=20to=20DE?= =?UTF-8?q?CISIONS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 17 ++++------------- docs/concept/02-core-model.md | 2 +- docs/concept/04-authoring-and-binding.md | 3 ++- docs/concept/05-validation-and-honesty.md | 2 +- .../07-mvp-roadmap-and-open-questions.md | 2 +- docs/concept/DECISIONS.md | 10 ++++++++-- docs/concept/ubiquitous-language.md | 13 +++---------- 7 files changed, 20 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 16595be..c076dc1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,11 +9,8 @@ This repo is the **concept made right before the engine exists**. The concept sy 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. -> **Status:** concept ratified and locked; **Slice 0 (Phase 0 — the protocol as typed code) is built, green, and -> hardened — hardening complete** (Waves A + B done plus the post-execution adversarial pass; all -> post-Session-1 decisions resolved *and executed*, DECISIONS MD-8…MD-16). The live per-session build plan is -> in **`plans/`**; the slice roadmap is **`docs/concept/07`**. (`plans/` is the source of truth for "what -> now.") +> **Status:** concept ratified and locked. Build state and "what now" live in **`plans/`** — read the +> highest-numbered plan's status header; the slice roadmap is **`docs/concept/07`**. ## The frame @@ -44,10 +41,10 @@ Progressive disclosure — start at the top, follow the pointers down. | Look here | What you get | Read | |---|---|---| -| `docs/concept/ubiquitous-language.md` | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology. The **model exposition** was rehomed into `00`–`07` (2026-06-10 Fold-A; the glossary header maps the old `base §n` refs) | **first, always** | +| `docs/concept/ubiquitous-language.md` | **the vocabulary** — the ratified lean glossary (terms · relations · a worked dialogue · flagged ambiguities); sole source of truth for terminology; the model exposition lives in `00`–`07` | **first, always** | | `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/00`–`07` (+ README) | **the technical design and the model** — 9 principle-led docs: vision & MVP boundary, founding principles (P1–P10), core model (`02` — the primitive, descriptors, sections, relations), the one graph, authoring & binding, validation & honesty, consumers, roadmap | to know *how* it works | -| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the MD-series MD-1…MD-16, the ratified R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | +| `docs/concept/DECISIONS.md` | **the why-log** for building the Protocol itself (the ratified-name registry + the MD-series, the R-series, the legacy D1–D6 shorthand, measured evidence) — rationale + open tensions | when a choice looks arbitrary | | `plans/` | **the build plan** — what each implementation session does, and why | before writing code | | `reviews/` | **archived session reviews** (implementation, founding-ideation, adversarial + prompts) — durable findings already folded into plans/DECISIONS; read for provenance | rarely | @@ -72,12 +69,6 @@ read it before writing code. | **4** | The agent surface (the `reader` — entry adapters + impact) + the Design Review / one generated read-only view — both fully derived. | | **5** | Polish: the `sdp` CLI (`build` · `validate` · maybe `explain`/`search`), error messages, the documented example, a clean-repo determinism test. | -**Where we are now (`plans/`):** Phase 0 is complete — Session 1 (Slice 0 + bootstrap) plus both hardening -waves (`plans/02`, now a done-record): typed floor-bearing sections, the kind-conditional floor table, -the de-padded example, the `.sdp.ts` spec files. **Next session = Slice 1**, the `ts-morph` extractor -(deterministic rebuild P3 + graceful partial extraction L3; reads `*.sdp.ts` from day one — MD-15); the -hardened example becomes its first real input. - > **Tracer-bullet discipline.** Author the example specs and anchored code *first*, so the DSL and extractor are > forced to be usable before they are finished. If the example doesn't typecheck, fix the DSL — not the example. diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index 23ff310..5d2dd34 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -149,7 +149,7 @@ Five section concerns have a same-named (or same-natured) `kind` twin, so the sa - Keep it **inline as a section** when it is **local detail** of its host `Spec`. - **Promote it to a standalone `Spec`** of the matching `kind` when it is **referenced by more than one `Spec`, or needs its own identity / lifecycle / review.** The promoted spec is linked by **relations** — `constrainedBy` / `decidedBy` / `modelRefs` for the truth-section twins; `refines` (+ `verifies` for verifying examples) from the promoted `rule` / `example` child to its parent. -Two laws make the duality safe (ratified with the 2026-06-10 grill, DECISIONS MD-10): +Two laws make the duality safe (content-only sections — DECISIONS MD-10): - **Sections carry content; relations carry linkage.** A section never holds a spec ref — `behavior.rules` and `behavior.examples` are prose / structured entries only. "Which examples does this spec have?" is a reverse-edge query over the children's `refines`/`verifies`, never an authored list. - **Promotion is exclusive.** An entry lives inline **or** as a promoted child — never both. Promotion *moves* the content out; nothing is left behind to drift. (The readiness floor counts promoted children as evidence, `05` §3 — promotion never costs a spec its earned rung.) diff --git a/docs/concept/04-authoring-and-binding.md b/docs/concept/04-authoring-and-binding.md index abe3eb7..7eb8952 100644 --- a/docs/concept/04-authoring-and-binding.md +++ b/docs/concept/04-authoring-and-binding.md @@ -81,7 +81,8 @@ export const _anchor = anchorImplementation({ id: "impl:orders.create-order-use- The three syntaxes are interchangeable Representations; the *binding* is the thing. A team picks one style. The builder generalizes to **`codeAnchor`** over the implementation-flavored code namespaces (`impl` / `api` / -`component`) — ratified MD-8, lands with Slice-2 anchor extraction; `anchorImplementation` is the Session-1 name. +`component`) — the generic `codeAnchor` decision (MD-8), landing with Slice-2 anchor extraction; until then the +DSL ships the narrower `anchorImplementation`. ### Anchors assert a binding — never intent (P9/P10) diff --git a/docs/concept/05-validation-and-honesty.md b/docs/concept/05-validation-and-honesty.md index c1d3867..7f3a938 100644 --- a/docs/concept/05-validation-and-honesty.md +++ b/docs/concept/05-validation-and-honesty.md @@ -41,7 +41,7 @@ Types describe **shape**; validators decide **completeness** (P7). Completeness These are the non-negotiable core. CI fails on any error. They split across the two families. -**They run over the one graph — there is exactly one validation path** (MD-14): source → extract (static reification, P5) → graph → checks; `sdp validate` is `sdp build` + checks. Validating any *evaluated* form (importing spec modules and checking the resulting objects) would check a phantom — a non-static expression evaluates to a value on import but is dropped by static reification, so the checks could pass a spec the graph doesn't actually hold. The Session-1 pre-graph `AuthoredModel` is a stand-in that retires into (at most) an extractor-internal shape when the extractor lands; it is never a second public validation seam. Authoring-time feedback is the type system's job (typed sections, `02` §3) plus the `sdp/spec-static` lint — not a parallel validator path. +**They run over the one graph — there is exactly one validation path** (MD-14): source → extract (static reification, P5) → graph → checks; `sdp validate` is `sdp build` + checks. Validating any *evaluated* form (importing spec modules and checking the resulting objects) would check a phantom — a non-static expression evaluates to a value on import but is dropped by static reification, so the checks could pass a spec the graph doesn't actually hold. The pre-graph `AuthoredModel` is a stand-in that retires into (at most) an extractor-internal shape when the extractor lands (Slice 1); it is never a second public validation seam. Authoring-time feedback is the type system's job (typed sections, `02` §3) plus the `sdp/spec-static` lint — not a parallel validator path. **Conformance checks:** diff --git a/docs/concept/07-mvp-roadmap-and-open-questions.md b/docs/concept/07-mvp-roadmap-and-open-questions.md index 7fa6547..114fdca 100644 --- a/docs/concept/07-mvp-roadmap-and-open-questions.md +++ b/docs/concept/07-mvp-roadmap-and-open-questions.md @@ -88,7 +88,7 @@ The point of the principle-led core is that each of these slots in cleanly, with --- -## 6. Forward-looking acceptance criteria (seeded by the post-Session-1 full-MVP review) +## 6. Forward-looking acceptance criteria (seeded by the Phase-0 full-MVP review) Recorded here so the full-scope lens isn't lost; each is honesty-posture-aligned and maps to a slice's "done." These came out of the Phase-0 hardening review and were routed here (rather than into that code-only plan) so they diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 2a97db9..caf22c9 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -4,8 +4,14 @@ > decision records (a project's own `kind:"decision"` instances). Its job is to **recapture rationale** so choices > stay visible and don't re-smooth into "false settledness." Entries are short and **status-tagged** > (`ACCEPTED` / `PROPOSED` / `SUPERSEDED`). Companion to `docs/concept/ubiquitous-language.md` (the ratified -> glossary; since the 2026-06-10 Fold-A restructure the model exposition lives in `00`–`07` — historical -> `base §n` references in the entries below resolve via the §-map in the glossary header). +> glossary; the model exposition lives in `00`–`07`). +> +> **Decoding historical `base §n` references** (used by older entries below and the archived `reviews/`): +> the restructured base's old sections map to — +> §0 thesis · §1 meta-levels → `00` §2 · §2 primitive & boundary → `02` §1–§4 · §3 relations → `02` §6 · +> §4 claims & the authored/derived split → `01` + `03` §3 · §4b delivery facts → `02` §2 · +> §5 the one graph → `03` · §6 validation & honesty → `05` · §7 surfaces & projections → `06` · +> §8 delivery-process execution → `06` §6. --- diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index fdd5fbc..6b01ca1 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -1,17 +1,10 @@ # Ubiquitous Language — Glossary (ratified) > **Status: RATIFIED · LEAN GLOSSARY.** The terminology base of the Libar Software Delivery Protocol — our -> bounded context's vocabulary: **one concept → one word**; the rest are *aliases to avoid*. Built -> section-by-section in the language grill; ratified 2026-06-07. +> bounded context's vocabulary: **one concept → one word**; the rest are *aliases to avoid*. > -> **Restructured 2026-06-10 (Fold-A).** This document now carries **terms only**. The model exposition that -> used to live here (§0–§8) was rehomed into the design docs — terms and meanings did **not** change in that -> move, only where they live. Rationale stays in `DECISIONS.md`. For historical `base §n` references (in -> `DECISIONS.md` and the plans), the old sections map to: -> §0 thesis · §1 meta-levels → `00` §2 · §2 primitive & boundary → `02` §1–§4 · §3 relations → `02` §6 · -> §4 claims & the authored/derived split → `01` + `03` §3 · §4b delivery facts → `02` §2 · -> §5 the one graph → `03` · §6 validation & honesty → `05` · §7 surfaces & projections → `06` · -> §8 delivery-process execution → `06` §6. +> This document carries **terms only**; the model exposition lives in the design docs (`00`–`07`), +> rationale in `DECISIONS.md`. ## Governing rubric [SETTLED] From f6e43323a1754a19b7556c06181137e7ac471c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:38:42 +0200 Subject: [PATCH 20/29] =?UTF-8?q?DECISIONS.md=20ratified-name=20registry?= =?UTF-8?q?=20(plan=2005=20=C2=A73);=20bare=20MD-n=20refs=20in=2002/05/07/?= =?UTF-8?q?glossary=20now=20lead=20with=20meaning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/concept/02-core-model.md | 4 +-- docs/concept/05-validation-and-honesty.md | 2 +- .../07-mvp-roadmap-and-open-questions.md | 6 ++--- docs/concept/DECISIONS.md | 25 +++++++++++++++++++ docs/concept/ubiquitous-language.md | 2 +- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/docs/concept/02-core-model.md b/docs/concept/02-core-model.md index 5d2dd34..5cf69ed 100644 --- a/docs/concept/02-core-model.md +++ b/docs/concept/02-core-model.md @@ -127,7 +127,7 @@ Sections carry the detail. They are the **extension surface**: the system grows | Section | Carries | Notes | |---|---|---| -| `intent` | actor, problem, outcome, value, risks, assumptions, open questions | `openQuestions` may be flagged `blocking` to prevent stating `defined` or `ready` (MD-9). | +| `intent` | actor, problem, outcome, value, risks, assumptions, open questions | `openQuestions` may be flagged `blocking` to prevent stating `defined` or `ready` (the open-questions home, MD-9). | | `behavior` | rules (prose), examples (prose or structured Given/When/Then), flows | **Content only — never refs** (the duality rule below). An example entry matures *in place*: prose → a structured `{ given, when, then }` entry → (promoted) a child `example` spec backed by a verifier. | | `constraints[]` | a `flavor` (quality / security / performance / compliance / operational / policy), a statement, an optional `target`, optional `measurableBy` | A `performance` constraint with a measurable `target` is an NFR. `target` must be machine-readable (`p95 < 300ms`, not "fast enough") to state `defined`+. | | `model` | domain terms (vocabulary only) — `terms: Record` | Used for pack-level coherence checks. Richer concept structures (typed concepts, attributes) are a **named deferral**; when one lands, the typing law below pulls it into the closed shape. | @@ -136,7 +136,7 @@ Sections carry the detail. They are the **extension surface**: the system grows | `verification` | mode (manual / reviewed / contract / executable) + criteria | A verifying test *existing and enabled* is the derived `has-verifier` delivery fact (§2), not an authored field here. Pass/fail is **not** in the graph — it is CI's, operational. | | `ui` | references to component stories, design-tool nodes, visual baselines, accessibility status | **Aspirational.** Always links, never owns or renders. | -The `decision` section carries **no `status` field** (MD-11): a decision's adoption arc is the envelope's `readiness` (raised → explored → written → ratified), checked against the floor like any spec; replacement is the `supersedes` relation; a *rejected* path is not a truth-spec at all — it lives in the chosen decision's `alternatives` / `consequences`. One concept, one place. +The `decision` section carries **no `status` field** (rejected by the typing law, MD-11): a decision's adoption arc is the envelope's `readiness` (raised → explored → written → ratified), checked against the floor like any spec; replacement is the `supersedes` relation; a *rejected* path is not a truth-spec at all — it lives in the chosen decision's `alternatives` / `consequences`. One concept, one place. ### The typing law — which sections have typed shapes (MD-11) diff --git a/docs/concept/05-validation-and-honesty.md b/docs/concept/05-validation-and-honesty.md index 7f3a938..f87bd2a 100644 --- a/docs/concept/05-validation-and-honesty.md +++ b/docs/concept/05-validation-and-honesty.md @@ -77,7 +77,7 @@ The floor has two parts: **kind-blind structural clauses** (the same for every k |---|---| | `idea` | id, title, kind, altitude; `intent.outcome` **or** a parent relation | | `scoped` | `intent.outcome`; at least one relation; **the kind's evidence is *present*** (table below) | -| `defined` | **the kind's evidence is *complete*** (table below); no *blocking* open questions (`intent.openQuestions`, MD-9) | +| `defined` | **the kind's evidence is *complete*** (table below); no *blocking* open questions (`intent.openQuestions` — the open-questions home, MD-9) | | `ready` | the `defined` floor **and**: no blocking open questions; all relations resolve; every `dependsOn`/`refines` target is itself **≥ `defined`**; any anchors present *resolve* (so `implemented` is *derivable*) | **The per-kind evidence table.** Each kind names its **natural evidence**; `scoped` requires it *present* (prose acceptable), `defined` requires it *complete* where the kind defines a stronger form. There is no separate overlay mechanism — this table is the whole kind-aware story: diff --git a/docs/concept/07-mvp-roadmap-and-open-questions.md b/docs/concept/07-mvp-roadmap-and-open-questions.md index 114fdca..d653fc8 100644 --- a/docs/concept/07-mvp-roadmap-and-open-questions.md +++ b/docs/concept/07-mvp-roadmap-and-open-questions.md @@ -98,7 +98,7 @@ land in the roadmap at the right altitude. Ordering reflects the synthesis's pri authoring-ergonomics workstream* anywhere in `00`–`07` today (the MVP CLI is just `build`/`validate`), yet if authoring feels heavy, authors (human **and** agent) avoid the system or overfit specs to satisfy tooling. The first lever — **typed sections** (autocomplete + shape guardrails) — **landed in the Phase-0 hardening - (MD-11)**; the next are great error messages and `sdp validate --watch`; later `sdp new spec` / + (the typing law, MD-11)**; the next are great error messages and `sdp validate --watch`; later `sdp new spec` / `sdp explain`. Threads back to the anti-padding rule: make *dishonesty* fail without rewarding low-signal filler (a floor to clear, never a quota to fill). - **② Golden-graph fixture — at Slice 1; keep it distinct from `--check-clean`.** Adopt **both**, labeled @@ -111,12 +111,12 @@ land in the roadmap at the right altitude. Ordering reflects the synthesis's pri ready · Structural floor reached: defined · Problem: blocking open question."* Teaches the core honesty concept (stated, then checked); cheaply enabled by the floor evaluator, which reports *which* clause fails. The old blocker — the floor reading open questions from the wrong section — was fixed in the - Phase-0 hardening (MD-9: the floor reads `intent.openQuestions`). + Phase-0 hardening (the open-questions home, MD-9 — the floor reads `intent.openQuestions`). - **④ `implemented` is a UI hazard — at Slice 4, view-label only.** Model semantics are settled (DECISIONS MD-7: binding/existence, never liveness). Keep the internal fact name `implemented` (it powers the `implemented ∧ ¬ready` drift query), but render binding language in views: *"Implementation binding: present / Verifier binding: present / Runtime observation: not tracked."* -- **`coverage-unknown` — already a settled model commitment (MD-7 / §4 above); make it Slice-4 acceptance.** +- **`coverage-unknown` — already a settled model commitment (binding, never liveness — MD-7 / §4 above); make it Slice-4 acceptance.** File-level blast-radius reports changed-but-unanchored files as `coverage-unknown`, never silently under-reporting. The only add is promoting it from design note → explicit Slice-4 acceptance criterion. - **The MVP acceptance checklist, mapped across Slices 1–5:** spec extraction · anchor extraction · claim honesty · diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index caf22c9..76d556e 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -13,6 +13,31 @@ > §5 the one graph → `03` · §6 validation & honesty → `05` · §7 surfaces & projections → `06` · > §8 delivery-process execution → `06` §6. +## The ratified-name registry + +The name is the primary handle; the ID is the breadcrumb. **In prose, lead with meaning — write "the +typing law (MD-11)," never bare "MD-n."** (Generalizes MD-8's own footnote.) *Curation* records each +entry's standing against the ADR three-part test (hard to reverse · surprising without context · a +real trade-off — see the working discipline in `AGENTS.md`); *durable* entries become `kind:"decision"` +specs at the post-Slice-1 fold, under the future spec id reserved here. + +| ID | Ratified name | Curation | Future spec id | +|---|---|---|---| +| MD-1 | the executable meta-model | durable | `spec:protocol.decisions.executable-meta-model` | +| MD-2 | adopt the nouns, reject the gates | durable | `spec:protocol.decisions.adopt-the-nouns` | +| MD-4 | one primitive, named coordinates | durable | `spec:protocol.decisions.one-primitive` | +| MD-5 | the protocol naming | durable | `spec:protocol.decisions.protocol-naming` | +| MD-7 | binding, never liveness | durable | `spec:protocol.decisions.binding-not-liveness` | +| MD-8 | the generic `codeAnchor` | folds at Slice 2 → doc-comment on the `codeAnchor` builder | — | +| MD-9 | the open-questions home | folds at the fold (lives in `sections.ts`, the floor, `02` §3) | — | +| MD-10 | content-only sections | durable | `spec:protocol.decisions.content-only-sections` | +| MD-11 | the typing law | durable | `spec:protocol.decisions.typing-law` | +| MD-12 | the kind-conditional floor | durable | `spec:protocol.decisions.kind-conditional-floor` | +| MD-13 | floor-table-as-truth | folds (lives in `05` §3's Representation note + the `readiness-floor.ts` header) | — | +| MD-14 | one validation path | durable | `spec:protocol.decisions.one-validation-path` | +| MD-15 | the `.sdp.ts` extension | durable | `spec:protocol.decisions.sdp-ts-extension` | +| MD-16 | carried evidence | durable | `spec:protocol.decisions.carried-evidence` | + --- ## 2026-06-06 — Session: reframe + language base diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index 6b01ca1..c615f50 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -196,7 +196,7 @@ artifact** — approval provenance is git-native, never an authored primitive). - **Resolved (MD-15):** authored Spec files carry the **`.sdp.ts`** extension (never `.spec.ts`, which every JS test-runner default glob executes); the model name `Spec` itself was always settled — only the file serialization changed. -- **Naming (resolved — MD-5):** product **Libar Software Delivery Protocol** (short form "the Protocol"); +- **Naming (resolved — the protocol naming, MD-5):** product **Libar Software Delivery Protocol** (short form "the Protocol"); CLI **`sdp`**; npm **`@libar-dev/software-delivery-protocol`** (single package); repo `libar-dev/software-delivery-protocol`; namespaces `@libar-dev/` (OSS) vs `@libar-ai/` (commercial). "Protocol" names the meta-layer (a conformance contract, not a workflow); "process" is retained only for From c9d5b73d0dd8c707b0d6f5ae5dbe7053b786219e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:39:16 +0200 Subject: [PATCH 21/29] =?UTF-8?q?Curation=20annotations=20+=20admission=20?= =?UTF-8?q?bar=20(plan=2005=20=C2=A74):=20R-series=20RETIRE-AT-FOLD,=20D-t?= =?UTF-8?q?able=20fold=20note,=20three-part=20test=20in=20working=20discip?= =?UTF-8?q?line,=20'pre-graph'=20ratified=20in=20the=20glossary=20ledger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 6 ++++++ docs/concept/DECISIONS.md | 8 ++++++++ docs/concept/ubiquitous-language.md | 4 +++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index c076dc1..99f6725 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -98,6 +98,12 @@ Every doc honours both — never mistake one half for the other: 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 the base**, not current usage. +- **DECISIONS.md admits sparingly, and reads by name.** An entry must pass the ADR three-part test — *hard to + reverse · surprising without context · a real trade-off*. If the "Why" says the base already forced the + answer, it's drift repair, not a decision — its paper trail is git + the plan done-record. Authoring + guidance only, never a validator (checks police conformance and honesty, never content-quality). In prose, + lead with meaning: "the typing law (MD-11)," never bare MD-n (the registry at the top of `DECISIONS.md` + holds the ratified names). - **Naming is resolved — use these names.** Product **Libar Software Delivery Protocol** (short form "the Protocol"); CLI **`sdp`**; npm **`@libar-dev/software-delivery-protocol`** (single package); repo `libar-dev/software-delivery-protocol`. Namespaces: `@libar-dev/` (OSS) vs `@libar-ai/` (commercial). diff --git a/docs/concept/DECISIONS.md b/docs/concept/DECISIONS.md index 76d556e..2bc5604 100644 --- a/docs/concept/DECISIONS.md +++ b/docs/concept/DECISIONS.md @@ -133,6 +133,11 @@ home for approval provenance is **signed git tags** (identity + approved-at, alr primitive. ### Base refinements (R-series) [R1/R2 ACCEPTED & applied 2026-06-10 (Fold-A) · R3 see below] + +> **[RETIRE-AT-FOLD]** — pure change-log by the three-part test (the entries' own words: "the code already +> conforms"); substance fully absorbed into the glossary, `01`, `03`, `04`. Kept until the decision-spec fold +> because `04` §2 cites "DECISIONS R1"/"R3" live. + **Context.** The post-Session-1 founding-ideation review surfaced two wording imprecisions in the ratified base. The **code already conforms** in both cases; these are language tightenings, so per the working discipline (terminology is ratified — *flag*, don't silently edit) they were recorded as **PROPOSED** and assessed by the @@ -371,6 +376,9 @@ than its scope. > single-use cleanup plan (which previously held it) is gone. **Do not confuse this legacy D-space with the > open-decision handles D1–D8 used by `plans/02`/`plans/03`** — those name the post-Session-1 grill agenda > (typed sections, prose-vs-ref, floor collapse, …), a different code-space; in prose, lead with meaning. +> **[KEEP for traceability]** — at the decision-spec fold, **D3** (Pack reified), **D5** (the agent surface +> scripts the graph), and **D6** (the MCP-deferred *no*) become decision specs of their own; the table +> retires when the artifacts citing `(Dn)` do. | Label | One line | Canonical in | |---|---|---| diff --git a/docs/concept/ubiquitous-language.md b/docs/concept/ubiquitous-language.md index c615f50..69a302c 100644 --- a/docs/concept/ubiquitous-language.md +++ b/docs/concept/ubiquitous-language.md @@ -192,7 +192,9 @@ artifact** — approval provenance is git-native, never an authored primitive). `alternatives`/`consequences` — MD-11). - **Locked usage:** readiness is **"stated/asserted," never "claimed"** ("claim" is reserved for the `claim` taxonomy) · the meta-model defines the **contract**, **instances conform**; "govern"/"police" retired · - checks are **conformance checks + honesty checks**. + checks are **conformance checks + honesty checks** · **pre-graph** = upstream of graph derivation in the + one validation path (the authored layer before the extractor runs) — fences stand-in checks, never a + second validation path (one validation path, MD-14). - **Resolved (MD-15):** authored Spec files carry the **`.sdp.ts`** extension (never `.spec.ts`, which every JS test-runner default glob executes); the model name `Spec` itself was always settled — only the file serialization changed. From 953f98c5cf5ec83798a3b8e49356c20cfc0c729f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:40:26 +0200 Subject: [PATCH 22/29] =?UTF-8?q?Wire=20check:temporal=20guard=20into=20np?= =?UTF-8?q?m=20run=20check=20(plan=2005=20=C2=A75):=20banned=20temporal=20?= =?UTF-8?q?tokens=20fail=20the=20gate;=20DECISIONS.md/plans/reviews=20exem?= =?UTF-8?q?pt=20by=20genre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 369a841..c025ae8 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", - "check": "npm run typecheck && npm run typecheck:examples && npm run lint && npm run format:check && npm test && npm run build" + "check:temporal": "! git grep -nE 'Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}' -- src test examples AGENTS.md 'docs/concept/*.md' ':(exclude)docs/concept/DECISIONS.md'", + "check": "npm run check:temporal && npm run typecheck && npm run typecheck:examples && npm run lint && npm run format:check && npm test && npm run build" }, "devDependencies": { "@eslint/js": "^9.0.0", From 5a96b269c39a794ba27329b00fa3e51c1550211e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:42:06 +0200 Subject: [PATCH 23/29] Plan 05 done-record + lint fix: simplify graph-clause assertion (literal type carries the value); plans/05 is now the sole 'what next' home --- ...-temporalization-and-decisions-registry.md | 280 ++++++++++++++++++ test/readiness.test.ts | 6 +- 2 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 plans/05-de-temporalization-and-decisions-registry.md diff --git a/plans/05-de-temporalization-and-decisions-registry.md b/plans/05-de-temporalization-and-decisions-registry.md new file mode 100644 index 0000000..82e08da --- /dev/null +++ b/plans/05-de-temporalization-and-decisions-registry.md @@ -0,0 +1,280 @@ +# Plan 05 — De-temporalization + the DECISIONS.md ratified-name registry + +> **Status: ✅ EXECUTED 2026-06-10** — all six work items landed as one commit per item on +> `feature/mvp-init`; `npm run check` green end-to-end including the new `check:temporal` guard. +> Two small execution deviations, both deliberate: `04` §1's MD-15 citation was left as-is (compliant — +> the sentence already states the `.sdp.ts` extension and the `.stories.tsx` pattern, so prefixing the +> registry name would restate the sentence's own subject), and the readiness test's assertion was +> simplified to the `in`-check (ESLint `no-unnecessary-condition`: the `evaluatedOver: "graph"` literal +> type already guarantees the value — the type system carries what the runtime comparison restated). +> +> **Next session: Slice 1 — the `ts-morph` extractor** (deterministic rebuild P3 + graceful partial +> extraction L3; reads `*.sdp.ts` from day one — the `.sdp.ts` extension, MD-15); the hardened example +> becomes its first real input. + +## Context + +Two external reports found the same disease with two symptoms: **temporal information leaking into +timeless artifacts** (calendar-named identifiers like `deferredInSession1`, history-narration comments, +AGENTS.md restating build status three times) and **unreadable bare `MD-n` decision references**. Both +were validated against the repo — every file:line claim confirmed (with current line numbers; a few +extra instances found that the reports missed). The unifying principle is the repo's own ratified law +applied to itself: **git is the event log** — durable artifacts carry current truth; `plans/`, +`reviews/`, DECISIONS.md, and git carry history. Names bind to capabilities, not calendars, so they +expire themselves. + +**Validation verdicts worth recording:** + +- Report 1: all code/doc claims CONFIRMED. Refinement: the proposed rename `requiresGraph: true` is + imprecise — the clauses don't need a graph *object*, their truth is *graph-shaped* (relation + resolution, transitive target readiness, anchor resolution). Use `evaluatedOver: "graph"` instead. +- Report 2: the ADR three-part-test classification holds, with one gap — **MD-16 was never + classified**. It is durable (same family as MD-7: adversarial honesty sharpenings with real rejected + alternatives). +- The §-map move is *better* justified than Report 1 argued: DECISIONS.md entries cite bare `§4b`, + `§6`, `§0 guardrail 1` (MD-7, R1) — its only live readers are inside DECISIONS.md itself (plus 2 + archived refs in `reviews/`). +- "Organic naming" is only partial: "the typing law (MD-11)" is real, but ~22 bare references remain + in `docs/concept` + AGENTS.md. + +**Ratified choices (asked & answered):** the temporal guard IS wired into `npm run check`; R1–R3 are +annotated `[RETIRE-AT-FOLD]`, not deleted (04 §2 cites them live); the glossary's "ratified +2026-06-07" date is dropped (zero guard exemptions). + +## Standing rules (apply throughout; encode in the guard) + +- **Rule 1 — name by capability, not calendar.** `deferredInSession1` → `evaluatedOver: "graph"`; + "Session-1 evaluator" → "pre-graph evaluator". +- **Rule 2 — state the invariant, not the event.** Comments say what holds and why (cite the + decision); the event lives in git/plans/reviews. +- **Rule 3 — status lives in exactly one place:** `plans/` (the highest-numbered plan's status + header). AGENTS.md points, never restates. +- **Rule 4 — migration scaffolding lives with the artifact that needs it:** the `base §n` map moves + into DECISIONS.md's header. +- **Allowed forward-pointers:** `Slice N` / `Phase 0` (roadmap-relative capability names, defined in + `07` + the AGENTS.md slice table) and meaning-led `MD-n` citations. **Banned** in the swept set: + `Session N`, `Wave X`, `Fold-X`, ISO dates, numbered plan-file refs (`plans/0N`). `plans/` as a + directory pointer is fine. +- **Exempt by genre (never scrubbed):** `docs/concept/DECISIONS.md` (dated diary), `plans/`, + `reviews/`, git history, the measured-evidence table. + +--- + +## 1. Code de-temporalization (M) + +### 1a. The rename — `src/validate/readiness-floor.ts` + +Only graph-shaped clauses carry a marker; active clauses get **no** `evaluatedOver: "authored"` field +(under one-validation-path MD-14 *every* clause eventually evaluates over the graph, so labeling the +structural clauses "authored" would itself rot at Slice 1/3). + +- Lines 24–29: `DeferredReadinessClause` → `GraphReadinessClause`; field + `readonly deferredInSession1: true` → `readonly evaluatedOver: "graph"`. Doc comment → + *"Graph-shaped clauses — they resolve across the one graph (one validation path, MD-14; executes + Slice 1/3) and carry no pre-graph predicate, so the pre-graph evaluator skips them."* +- Line 31: union member renames with it (`ActiveReadinessClause | GraphReadinessClause`). +- Lines 344/349/354: `deferredInSession1: true,` → `evaluatedOver: "graph",` (three clause literals). +- Line 381: guard `if ("deferredInSession1" in clause)` → `if ("evaluatedOver" in clause)` (same + `in`-narrowing pattern). +- Lines 369–370 evaluator comment: "deferred (graph-shaped) clauses are skipped until the extractor + lands" → "graph-shaped clauses carry no pre-graph predicate and are skipped until the extractor + lands (Slice 1/3)". +- `ReadinessClauseId` (derived via `typeof`, MD-13) is unaffected — clause ids don't change. The type + is exported via `export *` from `src/index.ts` but referenced nowhere else in src/ — blast radius is + this file + two tests. Renaming is free at v0.0.0 with zero adopters; doing it before Slice 1 is + the point. + +### 1b. Tests tracking the rename + +- `test/readiness.test.ts:55–59` — name → *"marks the ready clauses graph-shaped — evaluated over the + one graph, never the pre-graph harness (one validation path, MD-14)"*; assertion → + `clauses.every((clause) => "evaluatedOver" in clause && clause.evaluatedOver === "graph")`. +- `test/validators.test.ts:199` — name → *"skips graph-shaped ready clauses pre-graph (target + readiness and anchor resolution wait for the extractor)"*. Body unchanged. (No snapshot/name-keyed + tooling — renames are safe; the old `in`-assertions fail loudly until updated, so the rename can't + land half-done.) + +### 1c. Comment sweep (Rule 2 rewrites) + +- `src/validate/authored-model.ts:5–7` → *"Pre-graph authored-layer DTO — the stand-in harness until + the extractor lands (one validation path, MD-14): not persisted, not a graph, and never a second + public validation seam (not the Slice 3 gate)."* +- `src/validate/validators.ts:221–225` → *"Pre-graph authored-layer validation only. This composes + the pre-graph authored-model checks and is not the Slice 3 graph validator gate (MD-14). …"* (rest + unchanged). +- `test/readiness.typecheck.ts:69` → *"the pre-graph authored model is an in-memory DTO — it carries + no source-file bookkeeping."* +- `test/fixtures/authored-model.fixtures.ts:5` → *"fixtures for the pre-graph authored-layer + validators"*; line 13: drop the `(plans/02 §3 H8)` ref — the fixture list is the durable content. +- `test/fixtures.test.ts:27–31` — **delete the comment block.** Its durable content (the bypass + pinned twice: compile-time MD-11 twin + runtime MD-16 twin) already lives in + `authored-model.fixtures.ts`'s module doc-comment; the rest is Wave-B activation narration. +- **Unchanged by decision:** `src/cli/sdp.ts:10,44` ("Not implemented yet (Slice 1: extractor)" — + allowed forward-pointer, pinned by `test/cli.test.ts`); `test/builders.typecheck.ts:101–108` + (meaning-led MD refs + Slice-1 pointer); the remaining ~42 src/test MD refs (already meaning-led — + a mechanical sweep is churn without payoff). + +**Proof:** `npm run typecheck && npm test` green; +`grep -rn "Session\|deferredInSession\|Wave\|plans/0" src test examples` → nothing. + +## 2. Docs de-temporalization (M) + +### 2a. Four concept-doc rewrites (cite the decision, drop the event) + +- `02-core-model.md:152`: "(ratified with the 2026-06-10 grill, DECISIONS MD-10)" → + "(content-only sections — DECISIONS MD-10)". +- `04-authoring-and-binding.md:84`: "— ratified MD-8, lands with Slice-2 anchor extraction; + `anchorImplementation` is the Session-1 name." → "— the generic `codeAnchor` decision (MD-8), + landing with Slice-2 anchor extraction; until then the DSL ships the narrower + `anchorImplementation`." +- `05-validation-and-honesty.md:44`: "The Session-1 pre-graph `AuthoredModel` is a stand-in…" → + "The pre-graph `AuthoredModel` is a stand-in that retires into (at most) an extractor-internal + shape when the extractor lands (Slice 1)…". +- `07-mvp-roadmap-and-open-questions.md:91`: heading "(seeded by the post-Session-1 full-MVP + review)" → "(seeded by the Phase-0 full-MVP review)" — matches the section body's existing + "Phase-0 hardening" usage. + +### 2b. AGENTS.md — status lives in one place + +- **Status blockquote (lines 12–16)** becomes, in full: *"**Status:** concept ratified and locked. + Build state and 'what now' live in **`plans/`** — read the highest-numbered plan's status header; + the slice roadmap is **`docs/concept/07`**."* +- **"Where we are now" paragraph (lines 75–79): delete entirely.** The latest plan's status header + carries "next session" (existing convention); keep the tracer-bullet blockquote that follows. +- **"Where to look" glossary row (line 47):** drop the Fold-A/date parenthetical and the §-map + mention → "…sole source of truth for terminology; the model exposition lives in `00`–`07`". +- **DECISIONS row (line 50):** "the MD-series MD-1…MD-16" (rots at MD-17) → "the ratified-name + registry + the MD-series, the R-series, the legacy D1–D6 shorthand, measured evidence". + +### 2c. Glossary header — shrink, relocate the §-map + +`docs/concept/ubiquitous-language.md:1–14`: + +- Status line: drop "Built section-by-section in the language grill; ratified 2026-06-07." + (`Status: RATIFIED` stays; provenance lives in git + DECISIONS.md's dated headings). +- The "Restructured 2026-06-10 (Fold-A)" paragraph + §-map → one timeless sentence: *"This document + carries terms only; the model exposition lives in the design docs (`00`–`07`), rationale in + `DECISIONS.md`."* +- The §-map relocates **verbatim** into the DECISIONS.md header (item 3a) — where its live readers + are (MD-7's §4b/§6/§7/§8, R1's §2/§4; it still decodes the 2 archived refs in `reviews/`). + +## 3. DECISIONS.md — registry + reference convention (M) + +### 3a. The ratified-name registry (new table under the header blockquote) + +Header blockquote absorbs the §-map (from 2c). Registry columns: **ID · Ratified name · Curation · +Future spec id**: + +| ID | Ratified name | Curation | Future spec id | +|---|---|---|---| +| MD-1 | the executable meta-model | durable | `spec:protocol.decisions.executable-meta-model` | +| MD-2 | adopt the nouns, reject the gates | durable | `spec:protocol.decisions.adopt-the-nouns` | +| MD-4 | one primitive, named coordinates | durable | `spec:protocol.decisions.one-primitive` | +| MD-5 | the protocol naming | durable | `spec:protocol.decisions.protocol-naming` | +| MD-7 | binding, never liveness | durable | `spec:protocol.decisions.binding-not-liveness` | +| MD-8 | the generic `codeAnchor` | folds at Slice 2 → doc-comment on the builder | — | +| MD-9 | the open-questions home | folds at fold time (lives in `sections.ts`, the floor, `02` §3) | — | +| MD-10 | content-only sections | durable | `spec:protocol.decisions.content-only-sections` | +| MD-11 | the typing law | durable | `spec:protocol.decisions.typing-law` | +| MD-12 | the kind-conditional floor | durable | `spec:protocol.decisions.kind-conditional-floor` | +| MD-13 | floor-table-as-truth | folds (lives in `05` §3 Representation note + `readiness-floor.ts` header) | — | +| MD-14 | one validation path | durable | `spec:protocol.decisions.one-validation-path` | +| MD-15 | the `.sdp.ts` extension | durable | `spec:protocol.decisions.sdp-ts-extension` | +| MD-16 | carried evidence | durable (classification added here — Report 2's gap) | `spec:protocol.decisions.carried-evidence` | + +Below the table, the convention line: *"In prose, lead with meaning — write 'the typing law +(MD-11),' never bare 'MD-n.' (Generalizes MD-8's own footnote.)"* + +### 3b. The bare-reference sweep (~22 sites, docs/concept + AGENTS.md) + +Compliance rule: a citation whose sentence already states the decision's content is compliant (many +are — the registry names were lifted from the docs' own phrasing). Mandatory rewrites, the genuinely +bare ones: + +- `02:130` "(MD-9)" → "(the open-questions home, MD-9)"; `02:139` "(MD-11)" → "(rejected by the + typing law, MD-11)". +- `05:80` → "(`intent.openQuestions` — the open-questions home, MD-9)". +- `04:11` → "(the `.sdp.ts`-extension decision, MD-15 — the `.stories.tsx` pattern)". +- glossary `:206` "(resolved — MD-5)" → "(the protocol naming, MD-5)"; check `:65/:182/:199/:203` + against the rule (most carry meaning inline already). +- `07:101/:114/:115/:119` — align to "name (MD-n)" form (`:115` already complies). +- AGENTS.md bare refs disappear with the 2b deletions/rewrites. +- **No src/test sweep** (already meaning-led). + +## 4. Curation annotations + the admission bar + "pre-graph" (S) + +- **Annotate, don't delete.** R-series intro gets `[RETIRE-AT-FOLD — substance absorbed into + glossary/01/03/04; cited live from 04 §2]`. The D1–D6 table intro gets `[KEEP for traceability; at + fold time D3, D5, D6 become decision specs]`. No entry text is deleted this session. +- **AGENTS.md working discipline** gains one bullet (after "Terminology is ratified…"): + *"**DECISIONS.md admits sparingly, and reads by name.** An entry must pass the ADR three-part test — + hard to reverse · surprising without context · a real trade-off. Authoring guidance only, never a + validator (checks police conformance and honesty, never content-quality). In prose, lead with + meaning: 'the typing law (MD-11),' never bare MD-n."* +- **Ratify "pre-graph"** in the glossary's **"Locked usage"** ledger line: *"pre-graph = upstream of + graph derivation in the one validation path (the authored layer before the extractor runs) — fences + stand-in checks, never a second validation path (one validation path, MD-14)"*. Glossary, not + AGENTS.md: it names a *pipeline position* (like `extractor`/`validator`), not a build era — and a + second terminology ledger would violate the repo's own discipline. + +## 5. The guard — `check:temporal` in `npm run check` (S) + +In `package.json` (no new files): + +```json +"check:temporal": "! git grep -nE 'Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}' -- src test examples AGENTS.md 'docs/concept/*.md' ':(exclude)docs/concept/DECISIONS.md'", +"check": "npm run check:temporal && npm run typecheck && …" +``` + +- **File set:** `src/`, `test/`, `examples/`, `AGENTS.md`, `docs/concept/` minus `DECISIONS.md` + (dated diary, exempt by genre). `plans/`/`reviews/` out (temporal by genre). `CLAUDE.md` symlinks + to AGENTS.md — covered. +- **Patterns:** session/wave/fold tokens, the old flag name (regression tripwire for 1a), numbered + plan-file refs, ISO dates. **Deliberately absent:** `Slice [0-9]`/`Phase 0` (allowed + forward-pointers) and `MD-n` (citations encouraged). +- Runs **first** in `check` (fastest step, fail fast). After items 1–3 land this produces zero hits + (verified against the current tree: the only date in `docs/concept/0*` is `02:152`, fixed in 2a; + the glossary date is dropped in 2c). +- Known accepted limitation (note beside the script in this plan's done-record): `!`-negation also + inverts a git-grep usage error (exit 128) into a pass — acceptable for a single-developer repo. +- Future false-positive path: if a Slice-1 fixture legitimately needs an ISO date (an order + timestamp), narrow the date pattern or add a targeted pathspec exclude *in the same commit*, never + by widening the genre exemptions. + +## 6. Verification (S) + +1. `npm run check` — full gate green (now including `check:temporal`). +2. `npm run check:temporal` standalone — zero hits. +3. Cross-ref spot checks: (a) the DECISIONS.md header §-map decodes MD-7's §4b/§6/§7/§8 and R1's + §2/§4 without leaving the file; (b) `04` §2's "DECISIONS R1"/"R3" citations still resolve; (c) + AGENTS.md no longer mentions the glossary-header map; (d) the glossary header carries no `base §n` + machinery; (e) every `(MD-n)` in `docs/concept/0*` + glossary has a name to its left (eyeball — + not machine-enforced, to keep the guard purely temporal-token-shaped). +4. Write this plan's done-record header at session end (Status: executed; **Next: Slice 1 — the + `ts-morph` extractor**) — now the *only* place carrying "what next" (Rule 3). + +## 7. Explicit non-goals + +- **The decision-spec fold** (DECISIONS entries → `kind:"decision"` `.sdp.ts` specs) — deferred until + after Slice 1, as both reports recommend: it becomes the extractor's second corpus (~10 durable + MD specs + D3/D5/D6; Execution lines and status tags dissolve into derived facts and `readiness` + per the model's own rules). This session only prepares the ground (the registry's future-spec-id + column). +- **No DECISIONS.md entry deletion** (incl. R1–R3 — annotated instead; see item 4). +- **No scrubbing of `plans/`, `reviews/`, git history, or the measured-evidence table** — temporal by + genre. +- **No src/test MD-reference sweep** beyond comments already being edited. +- **`jtbd-stories/`** — validated clean; untouched. +- **CLI strings and Slice-N forward-pointers** — untouched by decision, not omission. + +## Risks + +- **§-map relocation:** AGENTS.md's glossary row and DECISIONS.md's header both currently point at + the glossary-header map — both are rewritten this session (2b, 3a); verification 3 covers the + dangling-pointer risk. +- **Glossary date drop:** mildly lossy on the artifact itself; accepted (provenance in git + + DECISIONS.md). +- **`GraphReadinessClause` is exported API surface** (`export *` from `src/index.ts`) — free to + rename at v0.0.0 with zero adopters. +- **Test renames:** safe (no snapshot/name-keyed tooling); stale assertions fail loudly until + updated. diff --git a/test/readiness.test.ts b/test/readiness.test.ts index 6d98234..7af0070 100644 --- a/test/readiness.test.ts +++ b/test/readiness.test.ts @@ -53,11 +53,7 @@ describe("readiness and validation contracts", () => { }); it("marks the ready clauses graph-shaped — evaluated over the one graph, never the pre-graph harness (one validation path, MD-14)", () => { - expect( - readinessFloors.ready.clauses.every( - (clause) => "evaluatedOver" in clause && clause.evaluatedOver === "graph", - ), - ).toBe(true); + expect(readinessFloors.ready.clauses.every((clause) => "evaluatedOver" in clause)).toBe(true); }); it("covers every kind in the evidence table; workflow and contract ride the behavior row (MD-12)", () => { From d1e5fd803e030e6552e96cc9794ac4181711952e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:50:32 +0200 Subject: [PATCH 24/29] Harden the temporal guard (post-review): fail-closed Node wrapper replaces !-negation; dedicated CI step (workflow never ran npm run check); all three exit paths verified --- .github/workflows/ci.yml | 3 ++ check-temporal.mjs | 42 +++++++++++++++++++ package.json | 2 +- ...-temporalization-and-decisions-registry.md | 26 ++++++++---- 4 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 check-temporal.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54beca5..89d3dae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Temporal guard + run: npm run check:temporal + - name: Typecheck run: npm run typecheck diff --git a/check-temporal.mjs b/check-temporal.mjs new file mode 100644 index 0000000..47c8d4c --- /dev/null +++ b/check-temporal.mjs @@ -0,0 +1,42 @@ +import { spawnSync } from "node:child_process"; + +// The temporal-token guard (plans/05 §5): durable artifacts carry current truth, so calendar and +// session tokens are banned from code and the timeless docs. `docs/concept/DECISIONS.md` is exempt +// by genre (a dated diary); `plans/` and `reviews/` are outside the swept set for the same reason. +// `Slice N` / `Phase 0` (roadmap-relative capability names) and `MD-n` citations are allowed. +const pattern = + "Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}"; +const pathspecs = [ + "src", + "test", + "examples", + "AGENTS.md", + "docs/concept/*.md", + ":(exclude)docs/concept/DECISIONS.md", +]; + +const result = spawnSync("git", ["grep", "-nE", pattern, "--", ...pathspecs], { + encoding: "utf8", +}); + +if (result.error !== undefined) { + throw result.error; +} + +// `git grep` exit codes: 1 = no matches (the only pass), 0 = matches found, anything else = the +// command itself failed. A guard that errors must never pass — fail closed. +if (result.status === 1) { + process.exit(0); +} + +if (result.status === 0) { + console.error("check:temporal — banned temporal tokens found:\n"); + console.error(result.stdout); + process.exit(1); +} + +console.error(`check:temporal — git grep failed (exit ${result.status}); failing closed.`); +if (result.stderr !== "") { + console.error(result.stderr); +} +process.exit(1); diff --git a/package.json b/package.json index c025ae8..130be42 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", - "check:temporal": "! git grep -nE 'Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}' -- src test examples AGENTS.md 'docs/concept/*.md' ':(exclude)docs/concept/DECISIONS.md'", + "check:temporal": "node ./check-temporal.mjs", "check": "npm run check:temporal && npm run typecheck && npm run typecheck:examples && npm run lint && npm run format:check && npm test && npm run build" }, "devDependencies": { diff --git a/plans/05-de-temporalization-and-decisions-registry.md b/plans/05-de-temporalization-and-decisions-registry.md index 82e08da..35865ed 100644 --- a/plans/05-de-temporalization-and-decisions-registry.md +++ b/plans/05-de-temporalization-and-decisions-registry.md @@ -8,6 +8,14 @@ > simplified to the `in`-check (ESLint `no-unnecessary-condition`: the `evaluatedOver: "graph"` literal > type already guarantees the value — the type system carries what the runtime comparison restated). > +> **Post-execution adversarial pass (Codex)** — two valid findings, both fixed: (1) CI never ran the +> guard (the workflow fans out into discrete steps and skips `npm run check`) → a dedicated +> "Temporal guard" CI step now runs first; (2) the `!`-negated shell form was fail-open (a git-grep +> usage error also inverted to a pass) → replaced by the `check-temporal.mjs` Node wrapper, which +> passes **only** on git-grep exit 1 and fails closed on everything else. All three paths verified: +> clean tree passes, a seeded banned token fails listing the match, and a forced git-grep error +> fails closed. +> > **Next session: Slice 1 — the `ts-morph` extractor** (deterministic rebuild P3 + graceful partial > extraction L3; reads `*.sdp.ts` from day one — the `.sdp.ts` extension, MD-15); the hardened example > becomes its first real input. @@ -219,12 +227,13 @@ bare ones: ## 5. The guard — `check:temporal` in `npm run check` (S) -In `package.json` (no new files): - -```json -"check:temporal": "! git grep -nE 'Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}' -- src test examples AGENTS.md 'docs/concept/*.md' ':(exclude)docs/concept/DECISIONS.md'", -"check": "npm run check:temporal && npm run typecheck && …" -``` +As built (revised by the post-execution adversarial pass): the guard is the root-level +`check-temporal.mjs` Node wrapper (the `vitest-test.mjs` idiom), invoked as +`"check:temporal": "node ./check-temporal.mjs"` — first in `npm run check` **and** as a dedicated +"Temporal guard" step in `.github/workflows/ci.yml` (the workflow fans out into discrete steps and +never runs `npm run check`, so the script-level wiring alone never reached the merge gate). The +wrapper passes **only** on git-grep exit 1 (searched, found nothing); exit 0 fails listing the +matches; any other exit fails closed. - **File set:** `src/`, `test/`, `examples/`, `AGENTS.md`, `docs/concept/` minus `DECISIONS.md` (dated diary, exempt by genre). `plans/`/`reviews/` out (temporal by genre). `CLAUDE.md` symlinks @@ -235,8 +244,9 @@ In `package.json` (no new files): - Runs **first** in `check` (fastest step, fail fast). After items 1–3 land this produces zero hits (verified against the current tree: the only date in `docs/concept/0*` is `02:152`, fixed in 2a; the glossary date is dropped in 2c). -- Known accepted limitation (note beside the script in this plan's done-record): `!`-negation also - inverts a git-grep usage error (exit 128) into a pass — acceptable for a single-developer repo. +- ~~Known accepted limitation: `!`-negation inverts a git-grep usage error into a pass.~~ Rejected + by the adversarial pass — a guard that errors must never pass (the MD-13 silent-skip lesson, + applied to the guard itself); the Node wrapper fails closed. - Future false-positive path: if a Slice-1 fixture legitimately needs an ISO date (an order timestamp), narrow the date pattern or add a targeted pathspec exclude *in the same commit*, never by widening the genre exemptions. From caa324ba6b4a9be826344f893c24a3406f997079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 05:55:30 +0200 Subject: [PATCH 25/29] Guard round 2 (post-review): sweep inverts to all tracked files minus genre exemptions; guard's own plan-ref comment removed; self-exemption is use-mention only --- check-temporal.mjs | 21 +++++++++++-------- ...-temporalization-and-decisions-registry.md | 14 +++++++++---- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/check-temporal.mjs b/check-temporal.mjs index 47c8d4c..ff94633 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -1,18 +1,21 @@ import { spawnSync } from "node:child_process"; -// The temporal-token guard (plans/05 §5): durable artifacts carry current truth, so calendar and -// session tokens are banned from code and the timeless docs. `docs/concept/DECISIONS.md` is exempt -// by genre (a dated diary); `plans/` and `reviews/` are outside the swept set for the same reason. -// `Slice N` / `Phase 0` (roadmap-relative capability names) and `MD-n` citations are allowed. +// 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 tracked +// file. Only the temporal-by-genre artifacts are exempt: the dated decision diary, the per-session +// plan done-records, the archived reviews — plus the machine-generated lockfile (derived, not +// authored prose) and this file itself, which must *mention* the tokens it bans (use–mention: the +// pattern below literally contains one). `Slice N` / `Phase 0` (roadmap-relative capability names) +// and `MD-n` citations are allowed. const pattern = "Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}"; const pathspecs = [ - "src", - "test", - "examples", - "AGENTS.md", - "docs/concept/*.md", + ".", ":(exclude)docs/concept/DECISIONS.md", + ":(exclude)plans", + ":(exclude)reviews", + ":(exclude)package-lock.json", + ":(exclude)check-temporal.mjs", ]; const result = spawnSync("git", ["grep", "-nE", pattern, "--", ...pathspecs], { diff --git a/plans/05-de-temporalization-and-decisions-registry.md b/plans/05-de-temporalization-and-decisions-registry.md index 35865ed..9c0815d 100644 --- a/plans/05-de-temporalization-and-decisions-registry.md +++ b/plans/05-de-temporalization-and-decisions-registry.md @@ -14,7 +14,11 @@ > usage error also inverted to a pass) → replaced by the `check-temporal.mjs` Node wrapper, which > passes **only** on git-grep exit 1 and fails closed on everything else. All three paths verified: > clean tree passes, a seeded banned token fails listing the match, and a forced git-grep error -> fails closed. +> fails closed. A second pass then caught the guard's own blind spot: the sweep listed subtrees +> instead of subtracting exemptions, so root/tooling files — including the guard itself, whose +> header cited a numbered plan — escaped scanning. The sweep now covers **all tracked files minus +> the genre exemptions**; re-verified on all three paths, including a seeded token in the +> previously-unscanned `ci.yml`. > > **Next session: Slice 1 — the `ts-morph` extractor** (deterministic rebuild P3 + graceful partial > extraction L3; reads `*.sdp.ts` from day one — the `.sdp.ts` extension, MD-15); the hardened example @@ -235,9 +239,11 @@ never runs `npm run check`, so the script-level wiring alone never reached the m wrapper passes **only** on git-grep exit 1 (searched, found nothing); exit 0 fails listing the matches; any other exit fails closed. -- **File set:** `src/`, `test/`, `examples/`, `AGENTS.md`, `docs/concept/` minus `DECISIONS.md` - (dated diary, exempt by genre). `plans/`/`reviews/` out (temporal by genre). `CLAUDE.md` symlinks - to AGENTS.md — covered. +- **File set (as built, second round):** **all tracked files**, minus the genre exemptions — + `docs/concept/DECISIONS.md` (dated diary), `plans/` (done-records), `reviews/` (archive), + `package-lock.json` (machine-generated, not authored prose), and `check-temporal.mjs` itself + (use–mention: its pattern must name the tokens it bans). Subtractive, never additive — new files + are guarded by default. - **Patterns:** session/wave/fold tokens, the old flag name (regression tripwire for 1a), numbered plan-file refs, ISO dates. **Deliberately absent:** `Slice [0-9]`/`Phase 0` (allowed forward-pointers) and `MD-n` (citations encouraged). From 3d1d6b0b820e8c93d1b263d546fa9d06aa50d857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 06:01:02 +0200 Subject: [PATCH 26/29] Guard round 3 (post-review): self-exemption narrowed to line-level use-mention (guard's own file is swept); plans/[0-9]+ un-caps the numbered-plan ban --- check-temporal.mjs | 39 +++++++++++-------- ...-temporalization-and-decisions-registry.md | 16 +++++--- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/check-temporal.mjs b/check-temporal.mjs index ff94633..757eba5 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -4,18 +4,16 @@ import { spawnSync } from "node:child_process"; // (session/wave/fold handles, ISO dates, numbered plan-file refs) are banned from every tracked // file. Only the temporal-by-genre artifacts are exempt: the dated decision diary, the per-session // plan done-records, the archived reviews — plus the machine-generated lockfile (derived, not -// authored prose) and this file itself, which must *mention* the tokens it bans (use–mention: the -// pattern below literally contains one). `Slice N` / `Phase 0` (roadmap-relative capability names) -// and `MD-n` citations are allowed. +// authored prose). `Slice N` / `Phase 0` (roadmap-relative capability names) and `MD-n` citations +// are allowed. const pattern = - "Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/0[0-9]|20[0-9]{2}-[0-9]{2}-[0-9]{2}"; + "Session[ -][0-9]|Wave[- ][A-Z]|Fold-[A-Z]|deferredInSession|plans/[0-9]+|20[0-9]{2}-[0-9]{2}-[0-9]{2}"; const pathspecs = [ ".", ":(exclude)docs/concept/DECISIONS.md", ":(exclude)plans", ":(exclude)reviews", ":(exclude)package-lock.json", - ":(exclude)check-temporal.mjs", ]; const result = spawnSync("git", ["grep", "-nE", pattern, "--", ...pathspecs], { @@ -26,20 +24,29 @@ if (result.error !== undefined) { throw result.error; } -// `git grep` exit codes: 1 = no matches (the only pass), 0 = matches found, anything else = the -// command itself failed. A guard that errors must never pass — fail closed. -if (result.status === 1) { - process.exit(0); +// `git grep` exit codes: 1 = no matches, 0 = matches found, anything else = the command itself +// failed. A guard that errors must never pass — fail closed. +if (result.status !== 0 && result.status !== 1) { + console.error(`check:temporal — git grep failed (exit ${result.status}); failing closed.`); + if (result.stderr !== "") { + console.error(result.stderr); + } + process.exit(1); } -if (result.status === 0) { +// This file is swept like every other; its single allowance is line-level use–mention: the guard +// must name the tokens it bans, so the one line that *is* the pattern literal is permitted. Any +// other matching line here — a comment, a header — is a violation like any file's. (If this file +// is renamed, the allowance stops matching and the pattern line fails loudly: fail closed.) +const matches = result.status === 0 ? result.stdout.split("\n").filter(Boolean) : []; +const violations = matches.filter( + (line) => !(line.startsWith("check-temporal.mjs:") && line.includes(pattern)), +); + +if (violations.length > 0) { console.error("check:temporal — banned temporal tokens found:\n"); - console.error(result.stdout); + console.error(violations.join("\n")); process.exit(1); } -console.error(`check:temporal — git grep failed (exit ${result.status}); failing closed.`); -if (result.stderr !== "") { - console.error(result.stderr); -} -process.exit(1); +process.exit(0); diff --git a/plans/05-de-temporalization-and-decisions-registry.md b/plans/05-de-temporalization-and-decisions-registry.md index 9c0815d..d48ca02 100644 --- a/plans/05-de-temporalization-and-decisions-registry.md +++ b/plans/05-de-temporalization-and-decisions-registry.md @@ -18,7 +18,11 @@ > instead of subtracting exemptions, so root/tooling files — including the guard itself, whose > header cited a numbered plan — escaped scanning. The sweep now covers **all tracked files minus > the genre exemptions**; re-verified on all three paths, including a seeded token in the -> previously-unscanned `ci.yml`. +> previously-unscanned `ci.yml`. A third pass narrowed the guard's self-exemption from file-level +> to **line-level use–mention** (only the line that *is* the pattern literal is allowed; any other +> line in the guard is swept like any file's) and un-capped the numbered-plan pattern +> (`plans/[0-9]+` — the zero-padded form would have stopped guarding at plan 10). Verified: a +> banned token seeded into the guard's own comments fails the check. > > **Next session: Slice 1 — the `ts-morph` extractor** (deterministic rebuild P3 + graceful partial > extraction L3; reads `*.sdp.ts` from day one — the `.sdp.ts` extension, MD-15); the hardened example @@ -239,11 +243,11 @@ never runs `npm run check`, so the script-level wiring alone never reached the m wrapper passes **only** on git-grep exit 1 (searched, found nothing); exit 0 fails listing the matches; any other exit fails closed. -- **File set (as built, second round):** **all tracked files**, minus the genre exemptions — - `docs/concept/DECISIONS.md` (dated diary), `plans/` (done-records), `reviews/` (archive), - `package-lock.json` (machine-generated, not authored prose), and `check-temporal.mjs` itself - (use–mention: its pattern must name the tokens it bans). Subtractive, never additive — new files - are guarded by default. +- **File set (as built, final):** **all tracked files**, minus the genre exemptions — + `docs/concept/DECISIONS.md` (dated diary), `plans/` (done-records), `reviews/` (archive), and + `package-lock.json` (machine-generated, not authored prose). The guard sweeps its own file too; + its single allowance is line-level use–mention (only the pattern-literal line, which must name + the tokens it bans). Subtractive, never additive — new files are guarded by default. - **Patterns:** session/wave/fold tokens, the old flag name (regression tripwire for 1a), numbered plan-file refs, ISO dates. **Deliberately absent:** `Slice [0-9]`/`Phase 0` (allowed forward-pointers) and `MD-n` (citations encouraged). From 2d04aff4814cf5c13ce00a6e0cea2577b651cab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 06:05:07 +0200 Subject: [PATCH 27/29] Guard round 4 (post-review): self-allowance is exact-line equality, at most once; copied-regex smuggle and duplicated literal verified caught --- check-temporal.mjs | 20 +++++++++++++------ ...-temporalization-and-decisions-registry.md | 7 +++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/check-temporal.mjs b/check-temporal.mjs index 757eba5..4b3f47a 100644 --- a/check-temporal.mjs +++ b/check-temporal.mjs @@ -35,13 +35,21 @@ if (result.status !== 0 && result.status !== 1) { } // This file is swept like every other; its single allowance is line-level use–mention: the guard -// must name the tokens it bans, so the one line that *is* the pattern literal is permitted. Any -// other matching line here — a comment, a header — is a violation like any file's. (If this file -// is renamed, the allowance stops matching and the pattern line fails loudly: fail closed.) +// must name the tokens it bans, so exactly one line — the pattern literal, alone on its line — is +// permitted. The predicate is exact-content equality, so a copied regex with anything else beside +// it on the line, or a second copy of the literal, is a violation like any other. (If this file is +// renamed, the allowance stops matching and the pattern line fails loudly: fail closed.) const matches = result.status === 0 ? result.stdout.split("\n").filter(Boolean) : []; -const violations = matches.filter( - (line) => !(line.startsWith("check-temporal.mjs:") && line.includes(pattern)), -); +const allowedSelfLine = `"${pattern}";`; +let selfAllowanceUsed = false; +const violations = matches.filter((line) => { + const selfMatch = /^check-temporal\.mjs:\d+:(.*)$/.exec(line); + if (selfMatch !== null && selfMatch[1].trim() === allowedSelfLine && !selfAllowanceUsed) { + selfAllowanceUsed = true; + return false; + } + return true; +}); if (violations.length > 0) { console.error("check:temporal — banned temporal tokens found:\n"); diff --git a/plans/05-de-temporalization-and-decisions-registry.md b/plans/05-de-temporalization-and-decisions-registry.md index d48ca02..332e9e8 100644 --- a/plans/05-de-temporalization-and-decisions-registry.md +++ b/plans/05-de-temporalization-and-decisions-registry.md @@ -21,8 +21,11 @@ > previously-unscanned `ci.yml`. A third pass narrowed the guard's self-exemption from file-level > to **line-level use–mention** (only the line that *is* the pattern literal is allowed; any other > line in the guard is swept like any file's) and un-capped the numbered-plan pattern -> (`plans/[0-9]+` — the zero-padded form would have stopped guarding at plan 10). Verified: a -> banned token seeded into the guard's own comments fails the check. +> (`plans/[0-9]+` — the zero-padded form would have stopped guarding at plan 10). A fourth pass +> tightened the allowance predicate to **exact-line equality with an at-most-one rule** — a copied +> regex with anything else beside it on the line, or a second copy of the literal, is a violation. +> Verified: a banned token in the guard's own comments, the copied-regex smuggle, and the +> duplicated literal all fail the check. > > **Next session: Slice 1 — the `ts-morph` extractor** (deterministic rebuild P3 + graceful partial > extraction L3; reads `*.sdp.ts` from day one — the `.sdp.ts` extension, MD-15); the hardened example From f16159fa5a4d1efeceb4b093aea78b6f291d6472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 06:15:01 +0200 Subject: [PATCH 28/29] Phase 0 wrap-up (internal review): ref() doc-comment cites the decision (carried evidence, MD-16) not the review event; LICENSE (Apache-2.0) + package repository field land --- LICENSE | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 + src/ids.ts | 6 +- 3 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/package.json b/package.json index 130be42..5719366 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "version": "0.0.0", "description": "Libar Software Delivery Protocol — a typed, executable, self-validating meta-model of the software-delivery process: author Specs as code, derive one graph, check conformance and honesty.", "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/libar-dev/software-delivery-protocol.git" + }, "type": "module", "files": [ "dist" diff --git a/src/ids.ts b/src/ids.ts index 7f6421d..14ecc70 100644 --- a/src/ids.ts +++ b/src/ids.ts @@ -140,8 +140,8 @@ export function testAnchorId(value: string): TestAnchorId { /** * `ref()` is today a spec-only reference builder wearing a generic name: it is `specId` aliased, so - * it rejects `pack:` / `doc:` targets (F2, post-split adversarial review). Harmless while every call - * site wants a spec; revisit when `doc:`-target relations (`decidedBy` → an external ADR) or - * pack-targeting arrive. + * it rejects `pack:` / `doc:` targets — a named deferral (carried evidence, MD-16). Harmless while + * every call site wants a spec; revisit when `doc:`-target relations (`decidedBy` → an external ADR) + * or pack-targeting arrive. */ export { specId as ref }; From 8e6753d76d6f53f273178aae4bf6efb897423789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Darko=20Mijic=CC=81?= Date: Wed, 10 Jun 2026 06:18:40 +0200 Subject: [PATCH 29/29] =?UTF-8?q?Fix=20CI=20typecheck=20hermeticity:=20map?= =?UTF-8?q?=20the=20package=20name=20to=20src/index.ts=20in=20tsconfig=20p?= =?UTF-8?q?aths=20=E2=80=94=20locally=20tsc=20resolved=20the=20self-import?= =?UTF-8?q?=20through=20exports=20->=20stale=20dist/,=20which=20a=20fresh?= =?UTF-8?q?=20checkout=20(CI)=20never=20has;=20same=20idiom=20as=20tsconfi?= =?UTF-8?q?g.examples.json=20and=20the=20vitest=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 9ae8b1f..3b01981 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,10 @@ "skipLibCheck": true, "resolveJsonModule": true, "types": ["node"], - "noEmit": true + "noEmit": true, + "paths": { + "@libar-dev/software-delivery-protocol": ["./src/index.ts"] + } }, "include": ["src/**/*.ts", "test/**/*.ts", "tsup.config.ts", "vitest.config.ts"], "exclude": ["dist", "generated", "examples"]