MVP slices 1–5: the full loop — extract, bind, check, read, polish#3
Open
darko-mijic wants to merge 20 commits into
Open
MVP slices 1–5: the full loop — extract, bind, check, read, polish#3darko-mijic wants to merge 20 commits into
darko-mijic wants to merge 20 commits into
Conversation
extract({root}) reifies *.sdp.ts files by pure AST reading (no type checker, no
import following — MD-14's static reification) into Primitive/Pack nodes,
declared edges, and derived belongsTo (claim "declared"; no 4th claim), with
two-tier degradation: envelope failures are hard errors per spec, section
content drops property-by-property with warnings (L3). serialize.ts owns every
output byte (sorted nodes/edges, pinned key order, authored-order sections);
sdp build [root] [--check-clean] writes <root>/generated/graph.json
all-or-nothing. The checkout-v1 example is the first real input: model.ts hand
assembly deleted, tracer-bullet test re-pointed to extractor output (zero
findings, no dropped sections), golden oracle committed (prettier-ignored: the
serializer owns those bytes) plus the distinct determinism self-check. Schema
grows additively to 0.2.0; Finding gains optional file/line; seven on-disk
corpora pin the five extraction finding ids, duplicate-id exclusion, and the
emitted dangling edge; three reserved fixture names activate.
…ot, stale artifacts Three valid findings fixed. (1) The CLI entry guard suffix-matched argv[1] against /dist/cli/sdp.js, but Node keeps npm's .bin/sdp symlink path in argv[1], so the installed binary exited 0 silently; isCliEntrypoint now realpath-compares argv[1] against the module's own file (fail closed), unit-tested through a real symlink and verified against the built binary. (2) Committed hard-error corpora poisoned the documented default root — sdp build from the repo root swept test/fixtures/extract/*.sdp.ts and failed; discovery stays config-free (P3), so corpora are committed defused as *.sdp.ts.txt and materialized into temp dirs under their real names by test/helpers/extract-corpus.ts (the dead tsconfig/eslint corpus exclusions drop out); a regression test pins the repo root as a clean default root. (3) A failed build left the previous graph.json readable as current; hard errors and --check-clean divergence now remove the prior artifact, and the success path writes temp-then-rename. Plan 06 done-record carries the adversarial-pass record.
codeAnchor replaces anchorImplementation over impl/api/component (MD-8, folded into the builder doc-comment); the extractor sweeps source files for anchor constants by import binding, derives CodeNode/Anchor binding nodes (file + line) and anchored satisfies/verifies edges, and computes the first delivery facts (implemented / has-verifier, enabled-verifier gated per 02 §2 — binding, never liveness). The example gains H10's api anchor; schema 0.3.0; golden regenerated (13 nodes, 25 edges, zero findings); eight new defused corpora pin the anchor finding outcomes, including the new extract/misplaced-authoring warning.
…id slots only, executable verifier Three valid findings, all drift repair forced by the base (L2 / MD-10 / 04 §2): (1) unknown top-level spec/pack properties no longer vanish silently — derived graph vocabulary (delivery facts, claim, edge fields) is an envelope-tier hard error (extract/reserved-property, the extraction-layer twin of authoring-shape honesty) and anything else outside the authored shape warns-and-drops (extract/unrecognized-property); (2) id builders unwrap in id slots only — a ref() in section content drops loudly (sections carry content, relations carry linkage — MD-10); (3) the valid-cart verifier anchor gained its executable runner test beside it (04 §2) and the example's tests run in the suite (golden diff: the anchor's binding line only). A validation re-review pressed the raw-id-shaped-string case; resolved as the documented prose-is-prose boundary (closing it would police prose content, MD-1 guardrail 1), pinned by the id-shaped-string-content corpus. Five new defused corpora; 87 tests green.
validateGraph over the one graph is the sole validation seam (MD-14 executed): the ten 05 $2 validators, the active ready floor clauses, sdp validate = build + checks, AuthoredModel retired, and the CI gate on the example (0 errors, 1 standing warning). Includes the post-review hardening (Codex adversarial pass): the delivery-fact derivation rule shared via src/graph/delivery-facts.ts (one derivation path, resolving edge-contract rows), the new honesty/delivery-facts check (stated facts must equal recomputation; gaps reads the recomputed facts), and descriptor conformance (unratified specKind/altitude/readiness fail closed; the floor evaluator is total).
…ndpoints The 03 $1 edge contract's kind-typed endpoint rows were stated but unenforced: claim-separation now errors on constrainedBy targets outside rule/constraint (per 02 $6's 'a rule / NFR / policy spec' — MD-16 contemplates rule-kind targets), decidedBy targets that are not decision-kind, and supersedes endpoints (either end) that are not decision-kind. Rows evaluate only on resolving, ratified-kind endpoints (dangling stays referential integrity's finding, unratified the descriptor check's); the declared-verifies-from-an-example row stays the informative verifies-linkage warning. Pinned by DSL-fixture should-fail and should-pass tests.
The consumer half of the MVP story: createReader(graph) is the one decode path (joins, claim decode, recomputed delivery facts, derived readiness, findings — once at construction; frozen surface: findByConcept, byFile, blastRadius with coverage-unknown, specContext, packContext); the Design Review renders off the reader only (Markdown, golden-pinned); sdp view = validate + render, view directory owned wholesale. deriveReadiness rides the same floor table (MD-13); the inferred set decided empty with its consumer; graph golden byte-identical — this slice only reads.
…test-anchor rule Two findings from the Slice-4 adversarial review, both valid: - One resolving-test-anchor rule, never three. checkVerifiesLinkage and both reader decode sites accepted an anchored verifies edge without requiring its source to resolve to an Anchor node, while computeDeliveryFacts (correctly) did - so a foreign producer's off-contract edge could suppress the missing-test warning and decode an example as enabled. The rule is now one exported predicate (isResolvingTestAnchorVerify, in delivery-facts.ts) consumed by the derivation, the verifies-linkage check, and the reader - the surfaces can no longer disagree (fail closed). Regressions pin it in validators.test.ts and reader.test.ts; both verified to fail pre-fix. - Anchor docs reconciled to the landed binding contract. 04 §2, 03 §1, 07 §4, and jtbd-02 advertised component/implements (and a plural satisfies) the extractor hard-rejects; they now state the landed contract (id · optional label · one satisfies/verifies target) and name component/implements/handles/emits explicitly ASPIRATIONAL, pointing at the inline-vs-centralized open question (07 §4) - the R1/R3 pattern: the code was the more faithful artifact.
…documented example, the clean-repo determinism test (plan 10)
… cited; attribute each determinism gate Two documentation findings from the Slice-5 review, both verified against the gates (check:temporal, format:check): - The second-caller bar is now named at 06 §3, where the freeze rule is stated and where 00 §4 / 07 §1+§3 / AGENTS.md point. The term had graduated from plan vocabulary into the base with citations to a section that stated the rule but never the name - the only naming site (06 §4, "applied to writes") presupposed it. - The example README's determinism paragraph attributes each property to its actual gate: check:example gates the twice-run --check-clean self-check over the example; delete-generated/-and-rerun and the different-absolute-path (clean-repo) property are pinned in the test suite. It previously credited check:example with delete-and-rerun and omitted the location-independence property Slice 5 itself added. Review otherwise clean: all four break-it experiments re-executed and reproduced as documented, the exactly-once location rendering grep- verified across src/, no surviving "maybe" about explain/search, and the full gate green at 157 tests.
…s fail loudly on both surfaces; the namespace import form extracts; every missing envelope field reports in one pass; a repeated carrier property is loud; discovery skips dot-directories
…gates the reader's enabled decode and the Design Review's test-anchor rendering; the readiness ladder derives from SPEC_READINESS
…he parse errors covered; an error boundary keeps failures past discovery to one line, exit 1, stale artifacts removed (the injection seam, never a flag)
…nd array-carrier pins; real graph-flip assertion; id-brand pins; the empty typecheck stub deleted
…ded-ID DSL fragments, suffix-alone discovery, the schema sample, the acceptance criteria of record
…er promoted; the verifier gap reads 'designed, stated done, unverified'; the Founding Principle is stated once (concept README) and only pointed at; the cut list is named by the MVP, not by 'now'
…nd branded layer — one __brand key with disjoint unit types reduces to never, so tsc enforced nothing for the flavored ids; cross-flavor pins added; doc-comment deixis dropped
… rule — one predicate decides conferral for decode and derived facts; the builder's id contract restated statically; opaque envelope entries never double-report as missing; the duplicate-property guard reaches every object tier; shadowed and default-import bindings resolved honestly; CLI recovery never re-throws, temp twins removed, the checks ride the one-line law; residual deixis dropped
… names its ratified rungs; the wrong-builder diagnostic reads as one sentence; the discovery skip-list boundary recorded as a named deferral; the unknown-option line rides the one-line failure prefix
…orce:true swallows only ENOENT there, so generated-as-a-file raised ENOTDIR out of the one-line law; success-path removals stay raw so a stale temp tree is never renamed into place
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The MVP loop, end to end: Slices 1–5 on the Phase 0 foundation
What this PR is
Phase 0 (already on
main) made the Protocol itself typed code: theSpecprimitive, its threedescriptors (
kind·altitude·readiness), the relation set, and the validators — plus a workedexample that typechecks. What it could not yet do was anything useful: there was no way to turn
authored specs into a graph, no binding between specs and code, no CI gate over the graph, and nothing
generated to read.
This PR builds the rest of the MVP — all five vertical slices from the roadmap
(
docs/concept/07) — so the loop the conceptpromises actually runs:
Concretely, from the repo root:
That single warning is deliberate, and it is the project's honesty posture in one line:
invalid-cartdeclares that it verifies its parent, but no test anchor binds it yet — so the graph says the
spec↔test trace is incomplete instead of pretending. A surfaced absence is informative, never a gate
(the exit code stays 0).
Why it is shaped this way
Two disciplines from the concept docs drove the order of work:
each one had to extract / bind / check / render the real
checkout-v1example before it counted asdone. The slice roadmap is
docs/concept/07§1; the per-session plans are inplans/.extractor were forced to be usable before they were finished. When the example didn't extract, the
extractor was wrong — not the example.
One structural decision from the decision log shaped the architecture more than any other: one
validation path (MD-14). Validators consume the extractor's output and nothing else — source →
static reification → graph → checks. The Phase-0
AuthoredModelstand-in harness is deleted on thisbranch;
validateGraphis the sole validation seam. The truth the checks see is what sourcestatically states, never what it happens to evaluate to on import.
What landed, slice by slice
ts-morphextractor: discovers*.sdp.tsfiles, statically reifies specs and packs (no execution), derives nodes + declared edges, writesgenerated/graph.jsonsrc/extract/codeAnchor(impl:/api:/component:) andspecTestbindings extracted from implementation and test files →satisfies/verifiesedges withclaim: "anchored", and the derived delivery facts (implemented/has-verifier)src/extract/anchors.ts,src/graph/delivery-facts.tsverifieslinkage · authoring-shape honestysrc/validate/reader(a thin typed loader — joins andclaimdecode done once, entry adapters + file-level impact with explicitcoverage-unknownitems) and the Design Review (a pure, regenerable projection: index + one page per spec/pack)src/reader/,src/projections/design-review.tssdp build·validate·view, each subsuming the previous stage), one diagnostic rendering rule (every finding renders location-first from structured fields, failures stay to one line), the documented example walkthrough, and the clean-repo determinism testsrc/cli/sdp.ts,examples/checkout-v1/README.mdThe trust model ships whole
The MVP's claim is not "a spec format" — it is that the graph can be trusted. Every piece of that
posture is in this PR:
claimtaxonomy is never collapsed. Every edge carriesdeclared(human intent),anchored(a human binding from code), orinferred(machine-derived — designed-in, decoded byevery consumer, deliberately shipping empty until the impact graph produces it). An example's own
verifiesisdeclared; the test anchor's isanchored; consumers can always tell which is which.implementedandhas-verifierare computed fromresolving anchor edges. Hand-authoring one is rejected twice: the closed section types refuse it at
tsctime, and the authoring-shape honesty check refuses it at validate time even when smuggledpast the types.
structurally earned (kind-conditional evidence, blocking open questions, resolution clauses). The
Design Review renders stated beside derived and the banner fires only in the dishonest direction.
present · Runtime observation: not tracked" — the graph records that a binding exists, never that
code is live or tests passed. Pass/fail belongs to CI; liveness belongs to the deferred
observedfact.
--check-cleanruns the full pipeline twice independentlyand fails on a single divergent byte; deleting
generated/and rebuilding is byte-identical; so isthe pipeline run from a copy at a different absolute path. All three are pinned in the test suite,
and
npm run check:examplegates CI on the first.Hardened, not just landed
The commit history reads slice → adversarial review pass → hardening, five times over. A few of the
catches worth knowing about, because they show what the reviews were for:
__brandkey with disjoint unittypes reduces the intersection to
never— sotscwas enforcing nothing for the flavored anchorids. Rebuilt as a proper two-layer brand, with cross-flavor assignment pins so it cannot silently
regress.
instead of being silently skipped — a missing file must never look like a clean graph.
named like the builder cannot mint a binding.
both the reader's decode and the derived facts, so the two surfaces cannot drift apart.
the verdict, not merely that green inputs stay green.
The should-fail fixture corpus grew accordingly:
test/fixtures/extract/now covers ~25 invalid oredge-case repos (dangling refs, duplicate IDs, hand-authored delivery facts, non-static envelopes,
wrong builders, misplaced anchors, namespace imports, parse errors, …), each pinned to its exact
diagnostic.
Docs kept in lock-step
Two repair commits keep the concept docs honest against the landed surface: the DSL fragments, the
graph-schema sample, and the acceptance criteria in
docs/concept/now match the code that exists,and the JTBD stories were re-aligned to the ratified language (readiness is stated, never promoted;
the cut list is named by the MVP boundary). The worked example gained a full walkthrough —
examples/checkout-v1/README.md— including a "break it onpurpose" section that trips each check deliberately.
Verification
npm run check(the full CI gate) is green: temporal-language check → typecheck (src + examples) →lint → format → tests → build → example determinism.
(
test/fixtures/checkout-v1/expected-graph.json+ the golden Design Review tree) kept distinct fromthe determinism self-checks.
docs/concept/07§6) are each pinned by a named test: theblocking-open-question honesty failure, drops-no-sections extraction, the
api:anchor extraction,and
coverage-unknownas acceptance.Deliberately not in this PR
The cut list (
docs/concept/07§3) stands: no runtime observation or test-result ingestion (verificationin the graph is structural — a linked, enabled verifier exists), no Gherkin surface, no patch-back
loop (the edit path is intent → agent → git → checks), no MCP surface, no symbol-level impact graph,
no incremental builds. Each is recorded with rationale and a designed-in seam, so none requires
refactoring the core when it earns its way in.