diff --git a/repos/constitution/.claude-plugin/marketplace.json b/repos/constitution/.claude-plugin/marketplace.json new file mode 100644 index 0000000..f8f9627 --- /dev/null +++ b/repos/constitution/.claude-plugin/marketplace.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://anthropic.com/schemas/claude-code-marketplace.json", + "name": "systemfsoftware-marketplace", + "owner": { + "name": "systemfsoftware", + "url": "https://systemfsoftware.com" + }, + "plugins": [ + { + "name": "constitution", + "source": "./plugins/constitution", + "description": "Time-Traveling Stream Rules (TTSR) enforcing strict adherence to the Supreme Engineering Constitution (Articles I–V).", + "version": "0.1.0", + "author": { + "name": "systemfsoftware", + "url": "https://systemfsoftware.com" + }, + "homepage": "https://github.com/systemfsoftware/constitution", + "repository": "https://github.com/systemfsoftware/constitution", + "license": "Apache-2.0", + "keywords": [ + "constitution", + "ttsr", + "rules", + "omp", + "claude-code" + ] + } + ] +} diff --git a/repos/constitution/.gitignore b/repos/constitution/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/repos/constitution/.husky/commit-msg b/repos/constitution/.husky/commit-msg new file mode 100755 index 0000000..74f6a8c --- /dev/null +++ b/repos/constitution/.husky/commit-msg @@ -0,0 +1 @@ +deno run --allow-read --allow-env --allow-run --allow-sys npm:@commitlint/cli@21 --edit $1 diff --git a/repos/constitution/.husky/pre-commit b/repos/constitution/.husky/pre-commit new file mode 100755 index 0000000..9b938ee --- /dev/null +++ b/repos/constitution/.husky/pre-commit @@ -0,0 +1 @@ +deno task test diff --git a/repos/constitution/.omp-plugin/marketplace.json b/repos/constitution/.omp-plugin/marketplace.json new file mode 100644 index 0000000..f8f9627 --- /dev/null +++ b/repos/constitution/.omp-plugin/marketplace.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://anthropic.com/schemas/claude-code-marketplace.json", + "name": "systemfsoftware-marketplace", + "owner": { + "name": "systemfsoftware", + "url": "https://systemfsoftware.com" + }, + "plugins": [ + { + "name": "constitution", + "source": "./plugins/constitution", + "description": "Time-Traveling Stream Rules (TTSR) enforcing strict adherence to the Supreme Engineering Constitution (Articles I–V).", + "version": "0.1.0", + "author": { + "name": "systemfsoftware", + "url": "https://systemfsoftware.com" + }, + "homepage": "https://github.com/systemfsoftware/constitution", + "repository": "https://github.com/systemfsoftware/constitution", + "license": "Apache-2.0", + "keywords": [ + "constitution", + "ttsr", + "rules", + "omp", + "claude-code" + ] + } + ] +} diff --git a/repos/constitution/AGENTS.md b/repos/constitution/AGENTS.md new file mode 100644 index 0000000..bb2c79f --- /dev/null +++ b/repos/constitution/AGENTS.md @@ -0,0 +1,121 @@ +# AGENTS.md — Constitution Repository + +Single source of truth for the supreme design law of [System F Software](https://systemfsoftware.com). Consumer repos vendor via `git subtree` + symlink. This repo has no production code, no test suite, and no build step — it is two markdown documents plus their governance tooling (commit validation, agent harness): `CONSTITUTION.md`, resident in every agent's context, and `CONSTITUTION-ARTICLES.md`, retrieved on write or edit of a source file. + +@CONSTITUTION.md + +## Startup Workflow + +Before making changes: + +1. **Read this file** completely. +2. **Confirm the active task** with the user or the agent's task list. +3. **Review recent commits** with `git log --oneline -5`. +4. **Ensure current branch is not `main`** — feature branches only. If on main, create one. + +## Working Rules + +- **One task at a time.** Finish before starting the next. +- **Conventional commits required.** The commit-msg hook enforces `type(scope): description`. Run `git commit` through the hook — do not bypass with `--no-verify`. +- **Verification required.** Run the verification commands before claiming done. +- **Stay in scope.** Don't modify files unrelated to the task. Scope reduction requires explicit user approval. +- **Leave clean state.** The next session must run verification immediately. + +## Amending the Constitution + +### File Split + +- **`CONSTITUTION.md` (Resident):** Conduct rules (Article V + Governance) loaded in every session. +- **`CONSTITUTION-ARTICLES.md` (Retrieved):** Domain, boundary, verification, and organization rules (Articles I–IV) loaded on write/edit of source code. + +### Writing a Rule + +Rules are fenced YAML blocks with: `id`, `title`, `gate`, `do`, `dont`, `harm`, `check` (and optional `example`, `scope`, `layers`). + +### Minting an ID + +ID format: `CONST-`. Pick the next free number in the family (never renumber to close gaps). + +| Letter | Family | Purpose | +|---|---|---| +| `G` | Governance | Invoking constitution or resolving priority | +| `E` | Enforcement | Gate design, execution, verification | +| `P` | Purity | Decision functions and side-effect isolation | +| `D` | Domain modelling | Domain types and constraints | +| `B` | Boundary | Core/shell boundaries, effects, adapters | +| `T` | Testing | Testing strategy, mutation, properties | +| `N` | Naming & structure | Module organization and naming | +| `W` | Work discipline | Task scope, bypass declarations, reviews | +| `S` | Subtraction | Code deletion and structural simplification | + +### Changing a Rule + +- Same obligation reworded / moved: **keep ID**. +- Obligation narrowed, widened, split, or deleted: **retire old ID forever** and mint new one. +## Surface Classes + +| Surface | Files | Rule | +|---|---|---| +| **Locked** | `AGENTS.md`, `.husky/_/`, verification scripts | Read and propose changes; do not edit to make verification pass. | +| **Editable** | `deno.json`, `deno.lock`, `commitlint.config.cjs`, `.gitignore`, `.husky/` (hooks only, not `_/`) | Edit freely within the active task. | +| **Human-controlled** | `CONSTITUTION.md`, `CONSTITUTION-ARTICLES.md`, `README.md`, merging to `main`, pushing, destructive ops | Propose changes; ask the user before acting. | + +## Definition of Done + +A task is done only when ALL of the following are true: + +- [ ] Target changes are applied. +- [ ] Verification commands ran and passed. +- [ ] Commit uses conventional format (`type(scope): description`). +- [ ] Evidence recorded via the runtime memory system and task list. +- [ ] No dirty files left in the working tree. + +## Verification Commands + +```bash +deno task test # both files: schema, coverage, ids, families, dangling citations +deno run --allow-read --allow-env --allow-run npm:@commitlint/cli@21 --from HEAD~1 +``` + +After a commit that deletes, splits, merges, or re-scopes a rule — not after every edit — also run the reassignment check against the revision before it: + +```bash +deno task test --against +``` + +### Anti-Bypass Rules + +- Run the **full command**, not parts in isolation. +- Evidence must be from the **current run**, not a prior session. +- **Any failure blocks done.** Do not bypass with `--no-verify`. +- Do not suppress, skip, or disable checks to make verification pass. + +### Hallucination Prevention + +- **Read before edit:** before editing a file, read it in the current session. Do not edit from memory. +- **Verify before claim:** before saying "done," the verification command must have run and its output recorded. +- **Search before write:** before writing code that calls a library API, read the actual API surface. Do not generate from training memory. + +## Multi-Agent Ownership + +When multiple agents work in the same repo: + +- Each agent owns a disjoint file/module set. +- An agent must claim a file before editing it. +- Agents may not recursively delegate to each other. +- The one-shot verification must pass before any agent claims done. + +## End of Session + +Before ending a session: + +1. Record current state, blockers, and next steps via the runtime memory system and task list. +2. Commit with a conventional-format message once work is in a safe state. +3. Leave the repo clean — `git status` should show nothing unexpected. + +## Escalation + +- **Constitution conflict**: `CONSTITUTION.md` is already in context — reread it there, not from disk. +- **Unclear requirements**: Ask the user. +- **Verification failure**: Record via memory, flag for review, do not bypass. +- **Scope ambiguity**: Re-read this file and the Definition of Done. diff --git a/repos/constitution/CLAUDE.md b/repos/constitution/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/repos/constitution/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/repos/constitution/CONSTITUTION-ARTICLES.md b/repos/constitution/CONSTITUTION-ARTICLES.md new file mode 100644 index 0000000..f9c650f --- /dev/null +++ b/repos/constitution/CONSTITUTION-ARTICLES.md @@ -0,0 +1,294 @@ +# Constitution — Articles + +Retrieved, not resident. `CONSTITUTION.md` is supreme and always in context; these four articles are its craft law, delivered when the work reaches the artifact each one governs. Article V (conduct) in `CONSTITUTION.md` binds here unchanged. + +Deliver this file on **write or edit** of a source file, never on read: an agent that greps, or works from a plan, never fires a read trigger. The trigger condition is the law's; the mechanism that fires it — a path-scoped rule, a pre-tool gate — is the consuming harness's, and belongs in that repo's `AGENTS.md`, never here. + +--- + +## Article I — The Pure Core + +```yaml +rules: + - id: CONST-P1 + title: Purity + gate: lint + do: each domain decision is a pure function — data in, a value or typed error out + dont: + - in a decision, do I/O, throw, read a clock, or use randomness + - return an effect handle from a decision — if it needs the runtime, move the boundary, not the purity + harm: logic untestable over all inputs, untrustworthy + check: lint — decisions import no I/O or effect-runtime; mutation + - id: CONST-D1 + title: Types Before Logic + gate: type-checker + do: define types before behavior; make illegal states unrepresentable so bad data fails to compile + dont: start from functions and add types after + harm: invalid data reaches runtime; tests multiply to cover what a type could forbid + check: type-checker rejects the illegal state; review + - id: CONST-D2 + title: Each Error Its Own Variant + gate: lint + do: give every distinct failure its own tagged variant + dont: distinguish failures by a boolean or string field + harm: callers can't branch on the real failure; distinct errors collapse into one case + check: lint; review — callers branch on the variant tag, never on a field value + - id: CONST-D3 + title: No Primitive Obsession + gate: lint + do: brand every domain-meaningful value (ids, amounts, codes) as its own type + dont: pass bare text or number in a domain-significant position + harm: values transposed or misused; the type says nothing about what they are + check: lint — no bare primitives in domain signatures + - id: CONST-D4 + title: Null Is Not a State — but absence is fine for optional data + gate: lint + do: + - model mutually-exclusive states as a tagged union — one variant per state, each carrying only its valid fields + - use a plain nullable for a value absent identically in every state + dont: + - encode a state by which fields are present + - wrap such a field in Option/Maybe to "fix" it — the wrapper renames the hole, not closes it + harm: a state machine hidden in a record; the compiler can't reject invalid field combinations — the question is never "null or Option" but "a value that may not exist, or a state in disguise" + check: lint — flags an optional that correlates with the discriminant, not plain optionals; review + example: + wrong: Order { status, shippedAt?, trackingId? } — state by presence; an Option wrapper is the same defect + right: Order = Pending { placedAt } | Shipped { placedAt, shippedAt, trackingId } + fine: Customer { name, middleName? } — genuinely optional; plain nullable, no wrapper + - id: CONST-P2 + title: The Pure Core Has One Path (Cyclomatic Complexity 1) + gate: lint + do: write each core decision as a single path — choice as exhaustive dispatch over a closed type (match a tagged union), iteration as map/fold; the core is an expression, not a procedure + dont: + - in the core, use if/else, switch, ?:, or &&/|| for control + - in the core, use for/while — repetition moves into map/fold + scope: binds the pure core (decision and workflow files); the ban is on the control-flow form, not branching — a core function reads as one path yet still decides and iterates; the shell sequences steps and carries no decisions, its only structure is the sandwich (CONST-B3); the gate runs on core files, not the shell + harm: every branch is an untested path where state silently diverges — the mutator reaches it, the suite does not + check: lint — cyclomatic complexity = 1 on core files (match, map, fold are calls, not control flow, so they hold at 1; if/switch/loops raise it) + example: + wrong: if (o.kind === "Shipped") ship(o) else hold(o) — two paths in a decision + right: match(o) { Shipped -> ship, Pending -> hold } — one exhaustive dispatch over a closed type + wrong_iteration: fold over the data with a for-loop in the core — iterating the core as a procedure + right_iteration: fold(xs, 0, add) — iterate as one expression (a shell loop is fine — that's the shell) +``` + +--- + +## Article II — The Boundary + +```yaml +rules: + - id: CONST-B1 + title: Functional Core, Imperative Shell + gate: review + do: split every module into a pure core (decisions) and a thin shell (I/O); pass plain serializable data across the seam + dont: let a boundary object (handler, adapter, middleware) make a decision — it only translates external ↔ domain + harm: decisions tangled with I/O can't be tested without mocks; bugs hide in the boundary + check: review — a boundary object that needs its own test suite has logic in it; move it to the core + - id: CONST-B2 + title: Effects Are Values + gate: lint + do: return effects as lazy values (descriptions), interpreted once at the edge; attach logging, metrics, tracing as decorators on the value + dont: + - put an eager async result (promise, future, task) on the public surface + - embed a cross-cutting concern in a decision + harm: an already-started result can't be held, retried, or swapped; embedded concerns can't be turned off or composed + check: lint — no eager async result on the public surface + example: + wrong: "getUser : UserId -> " + right: "getUser : UserId -> Effect — a lazy value, interpreted once at the edge" + - id: CONST-B3 + title: The I/O Sandwich + gate: review + do: shape every outside interaction as read (impure) → transform (pure) → write (impure); the shell calls the core directly + dont: insert a layer that only passes work through without a read, transform, or write + harm: side effects leak into business logic; pass-through layers add coupling for nothing + check: review — pass-through delegation is the violation; the shell doing the read/transform/write, or sitting between transport and core, is not + example: + flow: | + read → decode → decide → shape → write + impure bread (read, write) around a thick pure filling (decode, decide, shape), no I/O between the pure steps. + read pull raw inputs — store, gateway, network, clock (impure) + decode validate raw → branded domain types (fail as data) (pure) + decide one decision over typed data → Decision | Error (pure) + shape build outputs and events from the Decision (pure) + write persist · emit · respond (impure) + wrong: read → decide → read → decide — I/O interleaved; the filling turns impure + edge: a later read that depends on an earlier decision — pre-fetch it, split into two sandwiches, or keep it openly in the shell; never fake a "pure core" around it + - id: CONST-B6 + title: The Sandwich Order Is Carried by Types + gate: type-checker + do: express an outside interaction as one phase chain — each phase's return type carries the required member the next phase's parameter demands — so the order is a consequence of the types and the compiler decides it + dont: + - hand-sequence the phases and state their order beside them; an order asserted in prose is decided by nothing + - give the phases a hierarchy — where a later phase's type is assignable to an earlier phase's parameter, an inversion still compiles + harm: an order nothing decides permits every permutation while reading as a guarantee, so the interleaved read that turns the filling impure — the defect CONST-B3 names — reaches production with the rule green + check: type-checker — composing the phases in the wrong order omits the required member, so the compiler names the phase that must come first; the sentence survives into the published declaration as that member's own name, which is what carries it into a consumer's compiler + example: + wrong: "write(decide(read(raw))) — hand-sequenced; every permutation type-checks, so the order is a comment" + right: "read : Raw -> ReadDone, decode : ReadDone -> DecodeDone, decide : DecodeDone -> DecideDone — decode cannot receive what read has not produced" + - id: CONST-B4 + title: Dependencies Point Inward + gate: lint + do: let the shell import the core; wire all implementations at one composition root + dont: let the core import the shell, the database, or the framework + harm: a decision layer chained to infrastructure can't be tested or replaced + check: import-graph lint + - id: CONST-B5 + title: Decode, Never Cast + gate: lint + do: turn outside data (bytes, serialized text, a foreign type) into a domain type via a decode returning a typed result + dont: + - assert type with an unchecked cast (`as`, `as unknown as`, `as any`) + - assert type with a suppression comment + harm: a shape nothing verified; everything downstream trusts a check that never ran + check: lint — no unchecked casts or suppression comments on outside data + example: + wrong: config := value as Config + right: "config := decode(value) : Result" + - id: CONST-P3 + title: Purity Is Per Function, Not Per Folder + gate: review + do: judge pure-versus-effectful by return type alone + dont: infer it from a folder, package, or "library versus application" + harm: a database-driver mislabeled "pure," a parser "impure," because of where it lives + check: review — return type decides; the lint behind CONST-P1 + example: + pure: "decide : Command -> Result" + effectful: "load : OrderId -> Effect — owns effects" +``` + +--- + +## Article III — Verification + +```yaml +rules: + - id: CONST-T8 + title: Test Public Functions Directly, Pure Logic with Mutation + gate: review + do: test the public API with real inputs and outputs; test internal calculation and branching logic with mutation tests (and properties only when CONST-T14 requires them); never write dedicated unit tests for code that only forwards calls between components + dont: + - write unit tests for intermediate helper functions that only pass data to other functions + - mix I/O code or adapters into the same mutation test run as pure calculation logic + - mock a dependency when only one real implementation exists + harm: unit-testing intermediate layers locks in private implementation details without catching real bugs; mocking real code gives false confidence; business rules stay tangled with I/O + check: review — every test calls either a public export or a pure decision function, never a private forwarding helper + - id: CONST-T14 + title: Properties Where the Surface Cannot Reach + gate: review + do: prove a pure decision with a property when a universal over generated input, or a refusal no generated law can express, cannot be reached from the published surface; the type is the generator + dont: + - cover the core with hand-picked example unit tests + - write a property for a decision already fully pinned from above just because the decision is important + harm: a green suite that tests only the cases you imagined; or a property farm that restates the public contract and dies with it + check: review — each authored property names the universal the public surface cannot reach + - id: CONST-T3 + title: Mutation Is the Measure + gate: mutation + do: gate a named, change-relevant mutated set at a perfect kill score; the set names the behavior it covers, and its scope is a cost decision, never a fault-majority claim; kill a survivor with a sharper property or by deleting the dead branch it exploits + dont: + - reach the number by a suppression comment + - reach the number by narrowing the mutated set after the fact + - reach the number by lowering the gate + - let an empty mutated set pass + - treat a raw mutation percentage as comparable across changes or codebases + harm: a score certifying tests that notice nothing; an empty or author-shrunk set passing vacuously + check: mutation gate (break = 100) on the declared mutated set; lint banning suppression, scope-narrowing, and an empty set + - id: CONST-T13 + title: Mutation Also Grades the Tests + gate: mutation + do: fail a run whose mutants all died if an authored property file defends nothing the rest of the suite does not; opt out in the mutation config, never by deleting the file the gate named + dont: + - treat a perfect mutant score as proof every test pulled its weight + - accuse a file that covered an unattributed kill + harm: toothless properties accumulate; deleting them to silence the gate removes the only named contract + check: mutation — the test-set verdict is part of the same run as the score + - id: CONST-T4 + title: Behavior Lives Where the Mutator Sees It + gate: lint + do: put any code that can be wrong (transform, check, branch) in a file the mutator covers + dont: place behavior in a declaration file (types, schemas, constant data), excluded from mutation + harm: a bug hidden behind a perfect score, in a file nothing mutates + check: lint — declaration files contain no behavior + - id: CONST-T9 + title: Pin the Published Contract Before You Delete a Path + gate: review + do: before removing or replacing a published operation, pin its observables (value, error variant, serialized document, process result) with examples or properties whose expected side is not the implementation under change; if the old operation still runs, compare old and new on the same published inputs until they agree, then delete old + dont: + - pin private functions + - derive expected values by running the implementation under change + - treat a mutation or property score as proof a deleted published capability still exists — those are blind to absence + - leave a persisted gold after the old path is gone unless the gold is externally authored, independently gated, and cheap to re-bless + harm: a rebuild silently drops a capability; same-session gold blesses the bug; a clean score after a delete is a silent regression + check: review — pins call only published names; each expected value names an independent source (spec clause, prior published major, second implementation, or a hand-written oracle next to the constructor) + - id: CONST-T10 + title: The Oracle Is Not the System Under Test + gate: review + do: every assertion has an oracle the SUT did not produce — a spec literal, a fixture not generated by importing the module, a law relating two views of the same value, or a second implementation; generated round-trip laws on a type cover what the type accepts and nothing it should reject, so a hand-written refusal survives beside them at any specifiable refusal boundary + dont: + - compute expected by calling the SUT + - assert collaborator call graphs + - treat generated accept-laws as full coverage of a refinement + harm: a green suite that cannot fail when the behavior is wrong; widening a refinement leaves generated laws green + check: review — plus sabotage (after green, break one core law and one published field; at least one test must go red) + - id: CONST-T11 + title: Snapshots and Differentials Are Published-Surface Oracles + gate: lint + do: snapshot only canonicalized published output; compare two implementations only of the same published operation (or a prior published major against current) + dont: + - snapshot or compare private helpers, mappers, or unexported modules + - snapshot a value small enough to be a property or a named example + harm: tests that fail on refactors callers cannot see and pass on contract breaks they can + check: lint — snapshot and differential fixtures are produced only through the package's published export map + - id: CONST-T12 + title: What a Test Does Comes from What It Calls, Not Its Filename + gate: lint + do: classify what a test is by what it imports and calls — public exports or pure logic under mutation — never by its folder, filename, or file extension + dont: decide which testing rules apply to a file based on its name or suffix + harm: renaming a test file secretly stops its rules from running while the test suite still looks complete + check: lint — no linter or test runner rules that pick tests by filename suffix + - id: CONST-E5 + title: A Gate's Key Is Recomputed, Never Reported + gate: review + do: + - key every gate on a recomputation from source bytes, a compiler verdict, or a rehash — never on a field the gated work's author supplied; when a gate reads a field, recompute that field in the same run + - treat a gate whose verdict the gated agent can produce or observe as unverified until an independent channel confirms it — an instrument the agent does not control, or review by someone who is not the gated agent + dont: + - accept a self-reported field, a presence flag, a metadata suffix, or a comment as evidence a property holds + - treat a mechanical gate's green as self-certifying + harm: a check keyed on author-supplied values passes everything and catches nothing, and the green then masks the broken invariant the gate exists to catch; an instrument correlated with the work under test can manufacture a verdict no single observer catches + check: review — each gate names the recomputation it runs and the independent channel that confirms its verdict +``` + +--- + +## Article IV — Organization + +```yaml +rules: + - id: CONST-N1 + title: Organized by What It Does + gate: review + do: organize by workflow and capability; keep code that changes together, together + dont: organize by what the system has (entities, technical layers) + harm: one change scattered across the tree + check: review — one change touches one capability subtree + - id: CONST-N2 + title: Names Scream the Domain + gate: lint + do: name files and folders for the job they do — a name must answer "of what?" + dont: + - use layer names (`core`, `shell`) + - use junk drawers (`util`, `service`, `manager`) + - use a suffix no rule keys on + harm: files no one can locate; meaningless buckets + check: filename lint — allowed suffixes; banned layer and junk-drawer names + - id: CONST-N3 + title: Fits in the Head + gate: review + do: give a module one responsibility; split it when a test needs elaborate setup (the signal it has several) + dont: accumulate unrelated concerns in one module + harm: modules no one can fully reason about; brittle, sprawling tests + check: review — fixture difficulty is the decomposition signal +``` diff --git a/repos/constitution/CONSTITUTION.md b/repos/constitution/CONSTITUTION.md new file mode 100644 index 0000000..e291ef9 --- /dev/null +++ b/repos/constitution/CONSTITUTION.md @@ -0,0 +1,85 @@ +# Constitution + +## Article V — Conduct + +```yaml +rules: + - id: CONST-G3 + title: Constitutional Violations Are Automatic P0 Failures + gate: review + do: treat every undeclared violation of this constitution during review as an automatic, non-negotiable P0 failure; reject the change unconditionally with zero appeals and no severity downgrades unless explicitly declared under CONST-W3 + dont: + - downgrade an undeclared constitutional breach to an advisory, P1, P2, or non-blocking finding + - treat a CONST-W3 declaration as optional prose — an explicit declaration in the change itself is the only legal waiver, and it must name the rule and the case + - accept a promise of follow-up repair or expedience plea to bypass an active rule without a CONST-W3 declaration + harm: constitutional rules decay into optional suggestions; agents negotiate away core architecture to ship faster; unblocked violations calcify into precedent + check: review — every undeclared constitutional violation is graded P0 and blocks approval unconditionally; any review that waives or downgrades an undeclared violation is rejected + - id: CONST-S1 + title: Depth Over Expedience + gate: review + do: fix the root cause; restructure when the design is wrong + dont: patch the symptom or bypass a boundary to ship faster + harm: the bug returns + check: review — the change names the root cause it fixes + - id: CONST-W1 + title: Scope Discipline + gate: review + do: execute accepted scope in full + dont: reduce scope mid-task because it grew complex, without the author's consent + harm: half-finished work; wasted effort second-guessing intent + check: review — delivered scope matches accepted scope + - id: CONST-S2 + title: First Principles Over Precedent + gate: review + do: justify a pattern by these principles — surrounding code is evidence of what exists, never of what is correct + dont: + - justify by "that is how it's done elsewhere" + - copy a neighbouring file as a template — code age grants no immunity + harm: unexamined defaults calcify into rules; one slop pattern seeds the next by imitation, and the average drifts down + check: review — a choice defended by precedent, or by the file next to it, is rejected + - id: CONST-S3 + title: API-First Discovery + gate: review + do: define the outside contract first, then derive use cases, decisions, and machinery beneath it; model only what a known requirement needs + dont: build a domain abstraction for a hypothetical future + harm: speculative structure that never pays off and constrains what comes after + check: review — every abstraction traces to a known requirement + - id: CONST-W2 + title: Challenge Before You Commit + gate: review + do: subject a large or irreversible choice to a deliberate challenge (another agent, a person, or rigorous self-examination), record it with the decision, judge it by the harm it names + dont: + - appeal to a tribunal or standing authority + - let a challenge become a clause quoted against a choice + harm: a costly, hard-to-reverse direction taken with no one trying to break it first + check: review — the challenge is recorded with the decision + - id: CONST-W3 + title: No Silent Bypass + gate: review + do: when you break a rule here — knowingly, or because it was wrong for this case — say so, in the open, in the change itself + dont: conceal a bypass + harm: two failures — the breach and the hiding of it; the next reader trusts a rule quietly broken + check: review — every rule breach is declared in the change that contains it + - id: CONST-S4 + title: Subtract Before You Add + gate: review + do: + - treat every line as a liability — removal is the default response to slop at every scale; adding is the exception you justify + - small — unify duplicates, make bad states unrepresentable, delete a branch instead of guarding it + - structural — when the root violates this document and breeds a bug class, rebuild the core (published contract pinned first (CONST-T9); decomposed into shippable milestones) rather than prune leaves off a rotten trunk + - distrust existing structure — assume rotten until it proves it conforms + dont: + - extend a copy-paste cluster with copy N+1 + - add a helper when removing or unifying one does the job + - patch around a rotten core to keep it alive + - treat code as sound because it compiles, is large, or is old + - mistake taste ("I'd write it differently") for rot + harm: the codebase only grows; rot survives every patch and regrows; each copied pattern seeds the next, and the average drifts down + check: review reads the net line delta — a refactor/improvement/chore that adds net lines states why and names what it deleted (features and their tests are exempt); a fix that leaves a named root violation standing is rejected; "rotten" names the invariant the core breaks; a structural rebuild ships a CONST-T9 pin on every published path it deletes + example: + wrong: add formatPhone() beside the three formatters already there + right: delete the three, keep one parameterised formatter + wrong_state: add a guard for the impossible state the record permits + right_state: delete the record; a tagged union makes the state unconstructable + +``` diff --git a/repos/constitution/LICENSE b/repos/constitution/LICENSE new file mode 100644 index 0000000..b09cd78 --- /dev/null +++ b/repos/constitution/LICENSE @@ -0,0 +1,201 @@ +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/repos/constitution/README.md b/repos/constitution/README.md new file mode 100644 index 0000000..fc3f92c --- /dev/null +++ b/repos/constitution/README.md @@ -0,0 +1,124 @@ +# Constitution + +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](LICENSE) +[![System F Software](https://img.shields.io/badge/systemfsoftware.com-constitution-black?style=flat-square)](https://systemfsoftware.com/constitution) +[![Rules: 34](https://img.shields.io/badge/rules-34%20in%20corpus-blue?style=flat-square)](CONSTITUTION.md) + +Shared engineering laws for repositories at [System F Software](https://systemfsoftware.com). + +It sets baseline requirements for clean code: a pure functional core behind a thin imperative shell, domain types before logic, mutation testing for decisions, and deleting code before writing more. Principles are stack-neutral, so they apply to any language. + +```mermaid +flowchart LR + S["systemfsoftware/constitution
Upstream Repository"] -->|git subtree| A[Consumer Repo A] + S -->|git subtree| B[Consumer Repo B] + S -->|git subtree| C[Consumer Repo C] + A -.symlink.-> S + B -.symlink.-> S + C -.symlink.-> S +``` + +--- + +## Two files, two roles + +The rules split into two files based on when an agent needs to see them: + +``` +constitution/ +├── CONSTITUTION.md # Resident: loaded on every run +└── CONSTITUTION-ARTICLES.md # On demand: retrieved when editing source files +``` + +| File | Delivery | Contents | How to load it | +| :--- | :--- | :--- | :--- | +| `CONSTITUTION.md` | **Always on** | Conduct (Article V) | Include in agent context on every turn (`@CONSTITUTION.md` in `AGENTS.md` or `CLAUDE.md`) | +| `CONSTITUTION-ARTICLES.md` | **On demand** | Articles I to IV (Pure Core, Boundaries, Testing, Project Layout) | Load via tool hook or path rule when editing source code (never on read) | + +Rules about conduct stay resident because nothing triggers them after a mistake happens. Craft rules (like how to structure a domain model or write a test) only need to load when someone touches source code. + +--- + +## Quick start + +Vendor the repository using `git subtree` and symlink both files to the project root: + +```bash +# 1. Fetch the remote into a local ref +git fetch https://github.com/systemfsoftware/constitution.git main:refs/remotes/vendor/constitution + +# 2. Add as a squashed subtree +git subtree add --prefix=vendor/constitution refs/remotes/vendor/constitution --squash \ + -m "chore: vendor shared constitution" + +# 3. Symlink both files to the repo root +ln -s vendor/constitution/CONSTITUTION.md CONSTITUTION.md +ln -s vendor/constitution/CONSTITUTION-ARTICLES.md CONSTITUTION-ARTICLES.md +``` + +If the repository is brand new, create an initial commit first (`git commit --allow-empty -m "init"`). + +### Connect to your agent harness + +1. Add `@CONSTITUTION.md` to `AGENTS.md` or `CLAUDE.md`. +2. Set up a path-scoped rule (`.claude/rules/` or `.cursor/rules/`) to provide `CONSTITUTION-ARTICLES.md` when editing source files. +3. Or add the marketplace and install the TTSR plugin to intercept violations during edits: + ```bash + omp plugin marketplace add systemfsoftware/constitution + omp plugin install constitution@systemfsoftware-marketplace + ``` + +--- + +## The articles + +| Article | File | Mode | Core rules | +| :--- | :--- | :--- | :--- | +| **I: Pure Core** | `CONSTITUTION-ARTICLES.md` | Retrieved | Pure decisions, explicit types, tagged error variants, no `null` states. | +| **II: Boundaries** | `CONSTITUTION-ARTICLES.md` | Retrieved | Functional core / imperative shell, values for effects, decode inputs rather than casting. | +| **III: Verification** | `CONSTITUTION-ARTICLES.md` | Retrieved | Observer-fit test placement, properties by narrow grant, mutation as the measure, independent oracles, pinned published contracts. | +| **IV: Organization** | `CONSTITUTION-ARTICLES.md` | Retrieved | Organize by domain responsibility, clear naming, keep modules small. | +| **V: Conduct** | `CONSTITUTION.md` | **Always on** | Zero-appeal P0 review enforcement, fix root causes, challenge decisions before committing, remove code before adding. | + +--- + +## Pulling updates + +Pull upstream changes into the subtree without changing existing symlinks: + +```bash +git subtree pull --prefix=vendor/constitution https://github.com/systemfsoftware/constitution.git main --squash \ + -m "chore: update shared constitution" +``` + +--- + +## Machine validation + +Every rule is defined in structured YAML: + +```yaml +- id: CONST-S4 + title: Subtract Before You Add + gate: review + do: treat every line as a liability — removal is the default response to slop + dont: extend a copy-paste cluster; patch around a rotten core + harm: the codebase only grows; rot survives every patch and regrows + check: review reads the net line delta; fixes that leave root violations are rejected +``` + +Run the validator to check rule IDs, schema compliance, and references across both files: + +```bash +deno task test +``` + +--- + +## Contributing + +Amendments need a written explanation, a version bump, and updates to consuming repos. See [AGENTS.md](AGENTS.md) for commit standards and testing guidelines. + +## License + +[Apache-2.0](LICENSE) (c) 2026 Ryan Lee. diff --git a/repos/constitution/commitlint.config.cjs b/repos/constitution/commitlint.config.cjs new file mode 100644 index 0000000..84dcb12 --- /dev/null +++ b/repos/constitution/commitlint.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/repos/constitution/deno.json b/repos/constitution/deno.json new file mode 100644 index 0000000..d112f13 --- /dev/null +++ b/repos/constitution/deno.json @@ -0,0 +1,10 @@ +{ + "version": "0.1.0", + "tasks": { + "test": "deno run --allow-read=CONSTITUTION.md,CONSTITUTION-ARTICLES.md --allow-run=git scripts/validate-constitution.ts", + "prepare": "git config core.hooksPath .husky" + }, + "imports": { + "@std/yaml": "jsr:@std/yaml@^1.2.0" + } +} diff --git a/repos/constitution/deno.lock b/repos/constitution/deno.lock new file mode 100644 index 0000000..e264451 --- /dev/null +++ b/repos/constitution/deno.lock @@ -0,0 +1,16 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/yaml@^1.2.0": "1.2.0" + }, + "jsr": { + "@std/yaml@1.2.0": { + "integrity": "20beb41e4983ba3437dbefac62b14061ab058e8a187596f19d28ff9035f6e6cf" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/yaml@^1.2.0" + ] + } +} diff --git a/repos/constitution/docs/plans/2026-08-29-2245-refactor-article-iii-verification-plan.md b/repos/constitution/docs/plans/2026-08-29-2245-refactor-article-iii-verification-plan.md new file mode 100644 index 0000000..c6acc20 --- /dev/null +++ b/repos/constitution/docs/plans/2026-08-29-2245-refactor-article-iii-verification-plan.md @@ -0,0 +1,432 @@ +--- +title: Article III Verification Doctrine Rehaul - Plan +type: refactor +date: 2026-08-29 +supersedes: docs/plans/2026-08-29-2210-refactor-article-iii-verification-plan.md +artifact_contract: ce-unified-plan/v1 +artifact_readiness: implementation-ready +product_contract_source: ce-plan-bootstrap +execution: code +--- + +# Article III Verification Doctrine Rehaul - Plan + +## Goal Capsule + +- **Objective:** The constitution's verification law binds only what measured evidence supports — oracle independence, observer-fit test placement, change-relevant mutation over a named non-empty set, published-contract pinning before deletion, and recomputed gate keys — and no longer prescribes the Testing Trophy, mandatory mocked composition, blanket property coverage of the core, or characterization as a standing duty. The corpus gate is Deno. A reader outside this repo can confirm it: `pnpm test` passes with 33 rules across both corpus files, and `pnpm test --against ` run after the corpus commit names exactly CONST-T1, CONST-T2, and CONST-T5 as vacated with no id reassigned. +- **Means:** The corpus gate is rewritten as a Deno script that implements the behavior the repo's own documentation specifies (vacated-id and uncompared-file reporting); Article III is replaced (vacate T1/T2/T5, reword T3, keep T4, mint T8–T14 plus CONST-E5); CONST-S4 is repointed to CONST-T9; AGENTS.md and README.md prose is reconciled (KTD1–KTD5; normative rule text in Appendix A). +- **Authority:** The H1–H12 verdict dossier and the org-mechanism specification (both delivered in the invoking conversation) govern doctrine conflicts. The corpus's own id-surgery table in AGENTS.md governs id handling. Ecosystem tooling law: Deno only for code (software-wiki harness law). The current corpus at `origin/main` (28 rules: 7 resident Article V + 21 retrieved) is the base being amended, not evidence of correctness (CONST-S2). +- **Execution profile:** One feature branch; four conventional commits in order — plan checkpoint, validator, corpus, prose — pushed together in one PR; no consumer-repo changes. +- **Stop conditions:** A gate failure not explained by the amendment's own edits stops the run. An edit whose find-pattern misses (grep still finds the old text after the edit) stops the run for repair. Implementation-time evidence contradicting a settled decision stops the run. +- **Tail ownership:** Branch shipping, PR, and CI watch belong to the calling pipeline. The org tree's watchdog and mutation-config updates belong to that repo after its next vendor pull. + +--- + +## Product Contract + +### Summary + +Replace the Python corpus gate with a Deno script implementing the full documented behavior — schema, coverage, families, gates, dangling citations, cross-revision reassignment, and vacated-id/uncompared-file reporting. Rewrite Article III of `CONSTITUTION-ARTICLES.md` so the verification doctrine matches the evidence: observer-fit placement instead of trophy widths, a narrow grant for property tests instead of a blanket mandate, mutation gated on a named change-relevant set whose run also grades the test files, published-contract pinning instead of characterization, an independent-oracle requirement with a refusal complement, and one enforcement rule (CONST-E5) binding every gate's key to a recomputation. Repoint CONST-S4's rebuild pin from the vacated CONST-T5 to the new CONST-T9, and reconcile AGENTS.md and README.md so no prose teaches the vacated doctrine or a false rule count. + +### Problem Frame + +The org's enforcing tree already implements observer-fit placement, toothless-property detection, mutation of named sets, a contract lane driven from outside the process, and import-keyed test classification. The constitution still describes a Testing Trophy with mocked composition as the widest band and mandatory characterization before every rebuild. Agents obey `check:` lines, so the stale doctrine is not inert prose — it actively instructs behavior the enforcing tree refuted. The dossier (H1–H12, 73 primary sources) adjudicates the doctrine: same-session test generation is tautology-prone without an independent oracle (H1), the two-surface count is underdetermined (H2), decision-only mutation over-claims observer-fit (H5), a decision-layer fault-majority is false as a census (H6), and gates bind through accountability while advisory surfacing is inert (H12). The constitution must state principles at that altitude and leave mechanism binding to consumer trees, because consumer repos in several languages vendor this file. + +The amendment prunes refuted doctrine and replaces it with a larger instruction surface: Article III grows from 5 to 9 rules and the corpus from 28 to 33. That growth is deliberate — each minted rule names an observer, oracle, or gate discipline the old five could not express, and the refuted doctrine is gone from every line the nine now occupy. The no-retirement id rule means each mint is permanent; that cost is accepted per rule, not in aggregate. + +### Key Decisions + +- **Vacate CONST-T1 and CONST-T5 rather than reword them.** The trophy and the characterization duty are refuted doctrine, not rewordable ones; their numbers stay vacant forever per the corpus's own id-surgery rule. (session-settled: user-directed — chosen over keeping trophy and characterization as reworded doctrine: the org tree's measured mechanisms and the dossier refuted both.) Governs R2, R3, R20. +- **The dossier adjudicates doctrine conflicts.** Where the org tree's current mechanisms and the evidence disagree, the evidence wins: mutation scope is change-relevant, not decision-only (H5); the organizing principle is oracle independence, not a surface count (H2). (session-settled: user-directed — chosen over encoding the org repo's mechanisms verbatim: H5 refuted decision-only scoping as observer-fit and H2 refuted the exhaustive two-surface claim.) Governs R5, R8. +- **CONST-E5 ships in this amendment.** The unforgeable-gate rule is in scope now, not deferred. (session-settled: user-directed — chosen over deferring gate-verification doctrine to a later amendment: user directed inclusion when the deferral was presented as default.) Governs R14. +- **Principles only — no tool names, no consumer names, no filename suffixes in rule YAML.** Mutation is an instrument class; how a tree scopes its mutant set is that tree's config. (session-settled: user-directed — chosen over naming the enforcing stack as exemplar: consumer repos in other languages vendor this file, and a named first consumer turns a house rule into law.) Governs R19. + +### Requirements + +**Corpus gate (Deno)** + +- R1. `scripts/validate-constitution.py` is deleted; a Deno script at `scripts/validate-constitution.ts` implements the same gate — the two-file corpus contract, fenced-YAML coverage accounting, the rule schema (required, optional, and gate fields), the family registry, duplicate-id detection, dangling-citation resolution across both files, and the `--against` cross-revision reassignment check — plus the vacated-id and uncompared-file reporting on the success line that AGENTS.md and `docs/solutions/architecture-patterns/the-vacuous-pass-gate-input-sets.md` already specify. Exit codes: 0 clean, 1 with named defects, 3 unmeasurable. `pnpm test` invokes the script directly; the shebang carries the permission flags (read limited to the two corpus files, run limited to `git`); the `@std/yaml` dependency is declared in `deno.json` imports. + +**Article III surgery** + +- R2. CONST-T1 is removed from `CONSTITUTION-ARTICLES.md`; the number is never reused; no corpus text prescribes trophy layers, widths, or mocked composition as doctrine. +- R3. CONST-T5 is removed; "characterization" appears nowhere in the corpus as a standing duty. +- R4. CONST-T2 is removed; its blanket property mandate does not survive in any reworded rule (see KTD1 for why the replacement takes a fresh id and a fresh title). +- R5. CONST-T3 keeps its id and title "Mutation Is the Measure" with a reworded body: the gate covers a named, non-empty, change-relevant mutated set; the `dont` list covers suppression, after-the-fact narrowing, gate-lowering, an empty set, and raw cross-project percentage comparison. +- R6. CONST-T4's text stands byte-identical. +- R7. CONST-T14 "Properties Where the Surface Cannot Reach" is minted: a property is earned when a universal over generated input, or a refusal no generated law can express, cannot be reached from the published surface; a `dont` forbids properties that restate a decision already pinned from above. +- R8. CONST-T8 "The Observer Must See the Fault Class" is minted: each behavior is enrolled in the observer that can see its failure class; `dont` entries forbid orchestrator suites, mixed mutant populations, and substitutes whose expected value derives from the single implementation they replace. +- R9. CONST-T13 "Mutation Also Grades the Tests" is minted: a run whose mutants all died fails when an authored property file defends nothing the rest of the suite does not; opting out happens in the mutation config, never by deleting the named file. +- R10. CONST-T9 "Pin the Published Contract Before You Delete a Path" is minted: observables are pinned with an expected side independent of the implementation under change; a persisted gold survives a rewrite only when externally authored, independently gated, and cheap to re-bless. +- R11. CONST-T10 "The Oracle Is Not the System Under Test" is minted: every assertion draws on an oracle the SUT did not produce, and a hand-written refusal survives beside generated accept-laws at any specifiable refusal boundary. +- R12. CONST-T11 "Snapshots and Differentials Are Published-Surface Oracles" is minted: snapshot and differential fixtures are produced only through the package's published export map. +- R13. CONST-T12 "Altitude Is What the Test Calls" is minted: a test's observer is decided by what it imports and invokes, never by filename, folder, or suffix; the check is lint. +- R14. CONST-E5 "A Gate's Key Is Recomputed, Never Reported" is minted at the end of Article III's rules block (retrieved placement — see KTD3): gates key on recomputation from source bytes, a compiler verdict, or a rehash; a gate whose verdict the gated agent can produce or observe is unverified until an independent channel confirms it. + +**Resident law** + +- R15. CONST-S4's structural-rebuild bullet cites CONST-T9 instead of CONST-T5, and its `check` clause requires a CONST-T9 pin on every published path a rebuild deletes. No other S4 text changes. + +**Prose reconciliation** + +- R16. AGENTS.md's file-choice paragraph cites CONST-T14 as its review-gated artifact-announced testing example instead of CONST-T2; the "28 of 34 rules" count sentence is recomputed against the amended corpus (33 rules) and restated with measured numbers; its vacated-id reporting claim is now backed by the gate's real behavior. +- R17. README.md's Article III row describes the new doctrine without naming the Testing Trophy, and the "Rules: 34" badge is recomputed to 33. + +**Refusals** + +- R19. No rule YAML names a tool, a consumer repository, a language stack, or a filename suffix. +- R20. No Testing Trophy diagram, width metaphor, or layer table survives anywhere in the corpus. + +### Success Criteria + +- `pnpm test` exits 0 and its success line reports the post-amendment corpus (33 rules across both files). +- After the corpus commit, `pnpm test --against ` exits 0, prints no reassignment, and names exactly CONST-T1, CONST-T2, and CONST-T5 as vacated. +- Against a pre-#9 revision, the same gate names exactly CONST-E1, CONST-E2, CONST-E3, CONST-E4, CONST-G1, and CONST-G2 as vacated. +- A probe that adds a citation to a vacated id makes the gate fail naming the dangling citation; the probe is reverted after the demonstration. +- A grep sweep over the corpus, AGENTS.md, README.md, and `scripts/` finds no reference to CONST-T1, CONST-T2, CONST-T5, "Testing Trophy", or characterization-as-duty. + +### Scope Boundaries + +In scope: `CONSTITUTION-ARTICLES.md`, `CONSTITUTION.md`, `scripts/validate-constitution.py` (deleted), `scripts/validate-constitution.ts` (new), `deno.json` (new), `package.json` (test script), `AGENTS.md`, `README.md`, and this plan. + +### Deferred to Follow-Up Work + +- The org tree's `WATCHDOG.md` police-checks "T1 = trophy, T5 = pin with characterization" (measured in the invoking conversation); it enforces vacated obligations after this amendment and must be updated in that repo after its next vendor pull. Other consumer repos are unmeasured; vacancy failure at their pulls is loud by design. A tracker ticket for this follow-up is filed by the shipping pipeline. +- Restructuring AGENTS.md's measured-count prose so future amendments drift less is a separate docs decision. + +Outside this amendment's identity: validator schema, family registry, or gate registration changes (none needed — E and T are registered, `review`/`lint`/`mutation` are registered gates); renumbering to close id gaps (forbidden by the corpus's own rule); restoring the Application section or the Preamble that commits #9/#10 removed. + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. **CONST-T2 is vacated; its replacement is minted as CONST-T14 with a distinct title.** The narrowed grant forbids what the old `do` required — blanket property coverage of the core violates the new `dont` — so the obligation narrowed, and the corpus's id-surgery table assigns a narrowed obligation a new id. The `--against` reassignment check compares titles, and only for ids present at both revisions, so it is structurally blind to a body-narrowing under a kept id; id discipline for body-level obligation change is therefore review-only, which is exactly why the narrowed grant takes a fresh id and a fresh title ("Properties Where the Surface Cannot Reach") — no reader can mistake the new rule for a light edit of the old. AGENTS.md's prose citation of CONST-T2 re-points to CONST-T14 (R16), which is likewise review-gated and artifact-announced. +- KTD2. **CONST-T3 keeps its id and title.** The reword adds only loophole-closers (empty set, after-the-fact narrowing, raw-percentage comparison) and renames the gated set from "the core" to a named change-relevant set; nothing the old text required is now forbidden, so an agent compliant with the old text still complies. Same obligation, sharper instrument hygiene. +- KTD3. **CONST-E5 is retrieved law at the end of Article III, not resident law.** Commit ebd9b10 (#9) removed the resident Application section — CONST-G1/E1–E4/G2 — with the rationale that gate accumulation had produced cargo-culted checks; recreating a resident enforcement block for one rule reopens exactly that. The resident file is now conduct-only by direction of #9, and CONST-E5 is not conduct. It belongs where the work announces it: an agent authoring or judging a gate holds a verification artifact, which is Article III's retrieval trigger. Family follows about-ness, not placement — the corpus's own precedent is CONST-P3 (purity) living in Article II. E5 is the next free E number because E1–E4, though deleted in #9, are permanently vacant. Grounding: this repo's vacuous-pass learning ("never key a gate on a value its own author supplies") and dossier H12 (enforced gates bind, advisory surfacing is inert; an instrument correlated with the work can manufacture a false verdict). The rule stops at accountability — a recomputed key plus an independent confirmation channel — because binding by a machine gate an agent merely observes is unmeasured (dossier open question Q6). +- KTD4. **Numbering follows the settled mint set; the bundle is one doctrine revision.** CONST-T8 through CONST-T13 per the specification; CONST-T14 for the property grant; CONST-E5 in the E family. T6 and T7 remain vacant alongside T1, T2, and T5; gaps are free and a citation into a gap fails loudly. The eight mints ship as one amendment deliberately: the rules are mutually dependent — T9's pinning assumes T10's oracle independence, T11's snapshots and T12's altitude both key on T8's observer placement, and CONST-E5 disciplines the gates the T-family names — so splitting them would mint rules that cite rules that do not exist yet. +- KTD5. **Four commits, pushed together as one PR: plan checkpoint, validator, corpus, prose.** The Deno gate lands before the corpus commit so the amendment it judges is verified by a gate that already reports vacancies; the corpus commit carries both constitution files; prose lands last. The intermediate states exist only on the local branch for minutes — CI and consumer repos see only the PR head. The corpus/prose divergence window never publishes, and each layer stays auditable on its own. +- KTD6. **The gate is Deno, not Python.** Ecosystem tooling law is Deno-only for code, and the user directed it for this run. The rewrite is a port, not a redesign: every check the Python gate made is preserved, and the one addition is the vacated-id/uncompared-file reporting the repo's documentation already specifies (AGENTS.md's gate paragraph; the vacuous-pass learning's success-line shape). The `@std/yaml` parser is declared in `deno.json` and imported by bare name; the shebang carries the least-privilege flags so callers invoke the script directly. + +### Id surgery map + +| Action | Id | Title | +|---|---|---| +| Vacate | CONST-T1 | The Testing Trophy | +| Vacate | CONST-T2 | Properties Over Examples (blanket mandate) | +| Vacate | CONST-T5 | Pin Behavior Before You Rebuild | +| Keep id, reword body | CONST-T3 | Mutation Is the Measure | +| Keep, byte-identical | CONST-T4 | Behavior Lives Where the Mutator Sees It | +| Mint | CONST-T8 | The Observer Must See the Fault Class | +| Mint | CONST-T9 | Pin the Published Contract Before You Delete a Path | +| Mint | CONST-T10 | The Oracle Is Not the System Under Test | +| Mint | CONST-T11 | Snapshots and Differentials Are Published-Surface Oracles | +| Mint | CONST-T12 | Altitude Is What the Test Calls | +| Mint | CONST-T13 | Mutation Also Grades the Tests | +| Mint | CONST-T14 | Properties Where the Surface Cannot Reach | +| Mint | CONST-E5 | A Gate's Key Is Recomputed, Never Reported | + +Base: 28 rules (CONSTITUTION.md 7 + CONSTITUTION-ARTICLES.md 21). Post-amendment: 28 − 3 + 8 = 33. + +### Assumptions + +- The org tree's enforcing mechanisms are taken as specified in the invoking conversation; they were measured in that repo and are not re-verified from here. +- The dossier's adjudications are taken as the doctrine authority as delivered; primary-source re-derivation is out of scope for this amendment. +- CONST-T2's vacancy (KTD1) is resolved by the corpus's own id-surgery table after the question was declined in dialogue; the user redirected nothing when authorizing autonomous shipping. +- The two inference-grade `harm` fields (the empty-set guard's catching behavior, machine-gate binding on an observing agent) are stated as rationale, not measured findings, and the rules that carry them are framed as guards and accountability requirements. +- The locked-surface guidance for verification scripts is overridden for the gate file itself by direct user direction (Python out, Deno in); the override is declared in the validator commit body. + +### Risks & Dependencies + +- Consumer repos citing CONST-T1, CONST-T2, or CONST-T5 fail loudly at their next vendor pull. That is the intended vacancy behavior — a citation into a gap resolves to nothing, which the gate surfaces by name. One consumer is known to cite them (the org tree's `WATCHDOG.md`, per the invoking conversation); its repair is the deferred follow-up, not a shim here. +- AGENTS.md's measured-count prose will drift again on the next amendment. Accepted; the amendment checklist already requires recomputation on delete/split/merge/re-scope commits. +- The first `pnpm test` run after the Deno rewrite fetches `@std/yaml` from JSR (as the old command fetched PyPI); subsequent runs are cached. + +### System-Wide Impact + +Every consumer repo's agents receive the amended doctrine at their next vendor pull; the constitution repo's own harness (AGENTS.md guidance, README's doctrine table and badge) changes with it. The gate moves from Python to Deno — consumer repos that invoke the vendored validator directly inherit a Deno dependency; those using `pnpm test` inherit it transparently. + +--- + +## Implementation Units + +### U5. Deno corpus gate + +**Goal:** The corpus gate is a Deno script implementing the full documented behavior, including vacated-id and uncompared-file reporting; the Python file is gone. + +**Requirements:** R1 + +**Dependencies:** none (executes first) + +**Files:** `scripts/validate-constitution.ts` (new), `scripts/validate-constitution.py` (deleted), `deno.json` (new), `package.json` (test script) + +**Approach:** + +1. Read the Python gate in full and port every check: two-file coverage, fenced-block extraction and per-block parse errors, declared-vs-parsed id accounting, schema fields, gate values, family registry, duplicate ids, `do`/`dont` string-or-string-list shape, `example` map-of-strings shape, cross-file dangling citations, `--against` title-keyed reassignment. +2. Add the success-line reporting: ids vacated since the revision, and corpus paths absent at it — neither fails the run; both are named. +3. Declare `@std/yaml` in `deno.json` imports; point `package.json`'s `test` script at the script path; carry permissions in the shebang (`--allow-read` limited to the two corpus files, `--allow-run=git`); mark the file executable. + +**Patterns to follow:** the reporting shape in the vacuous-pass learning ("what cannot be failed must be reported"); the Python gate's error-message voice. + +**Test scenarios:** + +- Red-first: before the rewrite, `pnpm test --against ebd9b10~1` (captured) prints only `no id reassigned` — six vacated ids unreported. +- After the rewrite, against `ebd9b10~1`: names exactly CONST-E1, CONST-E2, CONST-E3, CONST-E4, CONST-G1, CONST-G2 as vacated; exit 0. +- Against the pre-split revision `28c79e6~1`: names `CONSTITUTION-ARTICLES.md` as not compared; exit 0. +- Against the current HEAD: names nothing vacated; exit 0. +- Without `--against`: output shape unchanged from the Python gate's, exit 0. +- Dangling-citation fixture: a temporary citation to a vacated id fails the run naming it. + +**Verification:** the six scenarios pass; `deno lint` on the new script is clean. + +### U1. Article III surgery + +**Goal:** Article III states observer-fit, oracle-independence, mutation, pinning, and gate-key doctrine; the trophy and the characterization duty are gone. + +**Requirements:** R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20 + +**Dependencies:** none on U5 (independent content; ordering matters only for U4's evidence) + +**Files:** `CONSTITUTION-ARTICLES.md` + +**Approach:** + +1. Read the current Article III block before editing. +2. Replace the rules block with the Appendix A block verbatim: CONST-T8, CONST-T14, CONST-T3, CONST-T13, CONST-T4, CONST-T9, CONST-T10, CONST-T11, CONST-T12, then CONST-E5 as the terminal rule. CONST-T4's entry stays byte-identical to the current text. +3. Article III's heading and the surrounding article structure do not change. + +**Patterns to follow:** the existing rule YAML schema (`id`, `title`, `gate`, `do`, `dont`, `harm`, `check`); gate values limited to the registered set. + +**Test scenarios:** (all run at the corpus-commit boundary, after U2's S4 repoint — the dangling T5 citation in CONSTITUTION.md keeps the gate red until then) + +- Validator schema: `pnpm test` exits 0; the success line reports 33 rules. +- Dangling-citation probe: temporarily add a citation to CONST-T5 in a corpus file; the gate fails naming the dangling citation; revert the probe and confirm green. +- Stale-doctrine sweep: a grep over the corpus for `CONST-T1`, `CONST-T2`, `CONST-T5`, `Testing Trophy`, and `characterization` returns nothing. + +**Verification:** the three scenarios pass at the corpus-commit boundary. + +### U2. S4 repoint + +**Goal:** CONST-S4 pins structural rebuilds with CONST-T9 instead of the vacated CONST-T5. + +**Requirements:** R15 + +**Dependencies:** U1 (the citation target must exist) + +**Files:** `CONSTITUTION.md` + +**Approach:** + +1. Read the current CONST-S4 block before editing and confirm both find-patterns are present verbatim. +2. In the structural-rebuild bullet, replace "behaviour pinned with characterization tests first, CONST-T5" with "published contract pinned first (CONST-T9)". +3. In the `check` clause, replace "a structural rebuild ships its characterization tests" with "a structural rebuild ships a CONST-T9 pin on every published path it deletes". +4. If either find-pattern misses, stop for amendment repair — do not approximate the edit. + +**Patterns to follow:** the resident Article V rule shape; no other S4 field changes. + +**Test scenarios:** + +- A grep of `CONSTITUTION.md` for CONST-T5 returns nothing; a grep for CONST-T9 returns the S4 citation. +- `pnpm test` exits 0 at the corpus-commit boundary (with U1). + +**Verification:** both scenarios pass. + +### U3. Harness prose reconciliation + +**Goal:** The harness and front-page prose teach the amended doctrine, cite only live rules, and carry measured numbers. + +**Requirements:** R16, R17 + +**Dependencies:** U1, U2, U5 (prose must match final rule text, ids, and gate behavior) + +**Files:** `AGENTS.md`, `README.md` + +**Approach:** + +1. In AGENTS.md's file-choice paragraph, replace the CONST-T2 example citation with CONST-T14. +2. In AGENTS.md's gate-explanation paragraph, recompute the inbound-citation count and the corpus total (33) against the amended files and restate the sentence with the measured numbers; its vacated-reporting claim now names real behavior. +3. In README.md's doctrine table, rewrite the Article III row to describe observer-fit placement, properties by narrow grant, and mutation as the measure; recompute the "Rules" badge from 34 to 33. + +**Patterns to follow:** AGENTS.md's existing measured-claim style; README.md's table-row and badge shapes. + +**Test scenarios:** + +- Repo-wide grep sweep (excluding `.git`, lock files, and this plan) for CONST-T1, CONST-T2, CONST-T5, "Testing Trophy", and characterization-as-duty returns nothing. +- The recomputed AGENTS.md and README numbers match fresh counts run at implementation time, not numbers carried from this plan. + +**Verification:** both scenarios pass. + +### U4. Verification battery and commits + +**Goal:** Evidence is recorded and the work lands as four conventional commits. + +**Requirements:** all + +**Dependencies:** U5, U1, U2, U3 + +**Files:** none new + +**Approach:** + +1. Run the full Verification Contract in order. +2. Commit the plan checkpoint (`docs(plans):`) first; then the Deno gate (`feat(scripts)!:`) with the locked-surface override declared in the body; then the corpus change (U1 + U2, `refactor(constitution)!:`); then the prose (U3, `docs:`). +3. Run commitlint over all four commits. + +**Test scenarios:** + +- All four commits pass `pnpm exec commitlint --from HEAD~4`. +- The working tree is clean after the commits. + +**Verification:** the Verification Contract's exit criteria all hold. + +--- + +## Verification Contract + +| Check | Command / method | Exit criterion | +|---|---|---| +| Corpus gate | `pnpm test` | Exit 0; success line reports 33 rules across both corpus files | +| Vacancy gate | `pnpm test --against ` | Exit 0; no reassignment; names exactly CONST-T1, CONST-T2, CONST-T5 vacated | +| Backfill fixture | `pnpm test --against ebd9b10~1` | Exit 0; names exactly the six #9-removed ids vacated | +| Uncompared fixture | `pnpm test --against 28c79e6~1` | Exit 0; names `CONSTITUTION-ARTICLES.md` not compared | +| Dangling-citation probe | temporary citation to a vacated id, then `pnpm test` | Fails naming the citation; probe reverted; gate green again | +| Stale-doctrine sweep | grep corpus, AGENTS.md, README.md, `scripts/` for vacated ids and retired doctrine names | Zero matches | +| Commit format | `pnpm exec commitlint --from HEAD~4` | Exit 0 for all four commits | +| Clean state | `git status --porcelain` | Empty after commits | + +--- + +## Definition of Done + +- All requirements R1–R20 hold in the tree. +- The Verification Contract passes in full, in the current session. +- The four commits (plan, validator, corpus, prose) are separate, conventional, and pushed together in one PR. +- No consumer-repo file is touched; the org-tree watchdog follow-up remains deferred with its tracker ticket filed. +- Working tree is clean. + +--- + +## Appendix A — Article III replacement block + +Normative payload for U1. YAML block content, replacing the current Article III `rules:` sequence: + +```yaml +rules: + - id: CONST-T8 + title: The Observer Must See the Fault Class + gate: review + do: enroll each behavior in the observer that can see its failure class — a pure decision is read by properties and by mutation of that decision; a published operation is read by tests that call only exported names; a shell that only translates is read by those exported-name tests, not by a suite of its own + dont: + - give the orchestrator its own test suite — extract the decision or live with the exported-name test + - enroll adapters, codecs, or wiring in the same mutant set as decisions + - treat a substitute whose expected value derives from the single implementation it replaces as an observer + harm: the wrong instrument reports coverage and measures nothing; mocked composition certifies wiring; a fat middle grows where decisions and I/O stay tangled + check: review — every new test names the surface it binds (decision or published export) and does not import a non-exported symbol to assert it + - id: CONST-T14 + title: Properties Where the Surface Cannot Reach + gate: review + do: prove a pure decision with a property when a universal over generated input, or a refusal no generated law can express, cannot be reached from the published surface; the type is the generator + dont: + - cover the core with hand-picked example unit tests + - write a property for a decision already fully pinned from above just because the decision is important + harm: a green suite that tests only the cases you imagined; or a property farm that restates the public contract and dies with it + check: review — each authored property names the universal the public surface cannot reach + - id: CONST-T3 + title: Mutation Is the Measure + gate: mutation + do: gate a named, change-relevant mutated set at a perfect kill score; the set names the behavior it covers, and its scope is a cost decision, never a fault-majority claim; kill a survivor with a sharper property or by deleting the dead branch it exploits + dont: + - reach the number by a suppression comment + - reach the number by narrowing the mutated set after the fact + - reach the number by lowering the gate + - let an empty mutated set pass + - treat a raw mutation percentage as comparable across changes or codebases + harm: a score certifying tests that notice nothing; an empty or author-shrunk set passing vacuously + check: mutation gate (break = 100) on the declared mutated set; lint banning suppression, scope-narrowing, and an empty set + - id: CONST-T13 + title: Mutation Also Grades the Tests + gate: mutation + do: fail a run whose mutants all died if an authored property file defends nothing the rest of the suite does not; opt out in the mutation config, never by deleting the file the gate named + dont: + - treat a perfect mutant score as proof every test pulled its weight + - accuse a file that covered an unattributed kill + harm: toothless properties accumulate; deleting them to silence the gate removes the only named contract + check: mutation — the test-set verdict is part of the same run as the score + - id: CONST-T4 + title: Behavior Lives Where the Mutator Sees It + gate: lint + do: put any code that can be wrong (transform, check, branch) in a file the mutator covers + dont: place behavior in a declaration file (types, schemas, constant data), excluded from mutation + harm: a bug hidden behind a perfect score, in a file nothing mutates + check: lint — declaration files contain no behavior + - id: CONST-T9 + title: Pin the Published Contract Before You Delete a Path + gate: review + do: before removing or replacing a published operation, pin its observables (value, error variant, serialized document, process result) with examples or properties whose expected side is not the implementation under change; if the old operation still runs, compare old and new on the same published inputs until they agree, then delete old + dont: + - pin private functions + - derive expected values by running the implementation under change + - treat a mutation or property score as proof a deleted published capability still exists — those are blind to absence + - leave a persisted gold after the old path is gone unless the gold is externally authored, independently gated, and cheap to re-bless + harm: a rebuild silently drops a capability; same-session gold blesses the bug; a clean score after a delete is a silent regression + check: review — pins call only published names; each expected value names an independent source (spec clause, prior published major, second implementation, or a hand-written oracle next to the constructor) + - id: CONST-T10 + title: The Oracle Is Not the System Under Test + gate: review + do: every assertion has an oracle the SUT did not produce — a spec literal, a fixture not generated by importing the module, a law relating two views of the same value, or a second implementation; generated round-trip laws on a type cover what the type accepts and nothing it should reject, so a hand-written refusal survives beside them at any specifiable refusal boundary + dont: + - compute expected by calling the SUT + - assert collaborator call graphs + - treat generated accept-laws as full coverage of a refinement + harm: a green suite that cannot fail when the behavior is wrong; widening a refinement leaves generated laws green + check: review — plus sabotage: after green, break one core law and one published field; at least one test must go red + - id: CONST-T11 + title: Snapshots and Differentials Are Published-Surface Oracles + gate: lint + do: snapshot only canonicalized published output; compare two implementations only of the same published operation (or a prior published major against current) + dont: + - snapshot or compare private helpers, mappers, or unexported modules + - snapshot a value small enough to be a property or a named example + harm: tests that fail on refactors callers cannot see and pass on contract breaks they can + check: lint — snapshot and differential fixtures are produced only through the package's published export map + - id: CONST-T12 + title: Altitude Is What the Test Calls + gate: lint + do: decide a test's observer from what it imports and invokes — published names or a decision under mutation — never from a filename, folder, or suffix + dont: key which doctrine applies to a test on a label the author can rename + harm: a rename silently un-enrolls the file from its observer; the absence reads as coverage + check: lint — no rule that selects tests by filename suffix + - id: CONST-E5 + title: A Gate's Key Is Recomputed, Never Reported + gate: review + do: + - key every gate on a recomputation from source bytes, a compiler verdict, or a rehash — never on a field the gated work's author supplied; when a gate reads a field, recompute that field in the same run + - treat a gate whose verdict the gated agent can produce or observe as unverified until an independent channel confirms it — an instrument the agent does not control, or review by someone who is not the gated agent + dont: + - accept a self-reported field, a presence flag, a metadata suffix, or a comment as evidence a property holds + - treat a mechanical gate's green as self-certifying + harm: a check keyed on author-supplied values passes everything and catches nothing, and the green then masks the broken invariant the gate exists to catch; an instrument correlated with the work under test can manufacture a verdict no single observer catches + check: review — each gate names the recomputation it runs and the independent channel that confirms its verdict +``` + +--- + +## Appendix B — CONST-S4 edits + +Normative payload for U2, verified verbatim against the current corpus. Two edits, complete and final: + +- Structural-rebuild bullet: "behaviour pinned with characterization tests first, CONST-T5" becomes "published contract pinned first (CONST-T9)". +- Check clause: "a structural rebuild ships its characterization tests" becomes "a structural rebuild ships a CONST-T9 pin on every published path it deletes". + +--- + +## Sources & Research + +- H1–H12 verdict dossier (delivered in the invoking conversation): doctrine authority. Load-bearing verdicts: H1 (same-session contamination), H2 (oracle independence over surface count), H5 (change-relevant mutation; decision-only refuted; empty-set guard load-bearing), H7 (refusal complement), H8 (seam authority), H11 (external-gated gold), H12 (gates bind; accountability reading). Independently spot-confirmed against primary literature (arXiv:2607.05139, arXiv:2410.21136, EMSE'22 commit-relevant mutants). +- Org-mechanism specification (delivered in the invoking conversation): the enforcing tree's mechanisms, the mint set CONST-T8 through CONST-T13, and the measured consumer citation (org tree WATCHDOG.md). +- Python gate `scripts/validate-constitution.py`, read in full: the port source — schema fields, family registry, cross-file citation resolution, title-keyed `--against` reassignment over ids present at both revisions. +- AGENTS.md: id-surgery table (obligation identity), family registry, file-choice rule, the gate-explanation paragraph whose counts R16 recomputes and whose vacated-reporting claim the Deno gate makes true. +- `docs/solutions/architecture-patterns/the-vacuous-pass-gate-input-sets.md`: grounding for CONST-E5 and the `--against` reporting shape — a gate keyed on author-supplied values certifies nothing; what cannot be failed must be reported. +- Tooling: Deno-only ecosystem law (software-wiki harness convention); `@std/yaml` (`jsr:@std/yaml`, single-document `parse`, safe for untrusted input — docs.deno.com/runtime/reference/std/yaml). +- Corpus base: `origin/main` at dd5722a (28 rules; commits ebd9b10/#9 and dd5722a/#10 removed the Application section and the Preamble). diff --git a/repos/constitution/docs/plans/2026-08-30-0100-feat-omp-constitution-ttsr-plugin-plan.md b/repos/constitution/docs/plans/2026-08-30-0100-feat-omp-constitution-ttsr-plugin-plan.md new file mode 100644 index 0000000..287e44d --- /dev/null +++ b/repos/constitution/docs/plans/2026-08-30-0100-feat-omp-constitution-ttsr-plugin-plan.md @@ -0,0 +1,49 @@ +--- +title: "feat(plugin): create OMP constitution TTSR rules plugin" +date: 2026-08-30 +category: feature +artifact_contract: ce-unified-plan/v1 +artifact_readiness: implementation-ready +execution: code +product_contract_source: ce-plan +--- + +# Goal Capsule + +Add a standard OMP plugin package (`@systemfsoftware/omp-plugin-constitution` under `packages/omp-plugin/` or `plugins/constitution/`) that defines Time-Traveling Stream Rules (TTSR) in markdown (`rules/*.md`). The rules trigger on edits/writes to source code and tests, enforcing strict adherence to the Supreme Engineering Constitution (`CONSTITUTION.md` and `CONSTITUTION-ARTICLES.md` Articles I–V). Any violation in review or source generation is flagged immediately with automatic P0 severity and actionable remediation. + +# Product Contract + +## 1. Scope and Objective +- Package an OMP plugin adhering to Claude/OMP plugin discovery specifications (`.omp-plugin/plugin.json`, `rules/`, `package.json`, `README.md`). +- Define targeted TTSR stream rules (`rules/*.md`) covering: + 1. `constitution-pure-core.md`: Enforces Article I (purity, tagged error variants, branded types, cyclomatic complexity 1, no primitive obsession, no boolean/status string errors, no null states). + 2. `constitution-boundary.md`: Enforces Article II (functional core / imperative shell, effects as lazy values, I/O sandwich order, inward dependencies, decode never unchecked cast `as any`/`as unknown`). + 3. `constitution-verification.md`: Enforces Article III (public export API testing, mutation testing on decisions, narrow property grants, independent oracles, no mocked intermediate single-implementation glue code, no tautological/characterization tests). + 4. `constitution-conduct-and-review.md`: Enforces Article V & Governance (automatic P0 on constitutional breaches with zero appeals unless declared under CONST-W3, root cause over expedient patch, subtract before add, scope discipline). +- Each rule carries frontmatter with `condition`, `scope` (`tool:edit(...)`, `tool:write(...)`, `tool:ast_edit(...)`), and `interruptMode: tool-only` or `always`. +- Update project manifest (`package.json`) and root test validation to ensure all rules are valid YAML frontmatter and tested against `omp ttsr test` (or regex validator). + +## 2. Requirements & Invariants +- **R1 (Plugin Discovery)**: Plugin manifest `.omp-plugin/plugin.json` and `.claude-plugin/plugin.json` properly identify the plugin name, version, and author metadata. +- **R2 (TTSR Rule Format)**: Every rule file in `rules/` has valid YAML frontmatter with `description`, `condition`, `scope`, and markdown instructions quoting the exact rule IDs (`CONST-P1`, `CONST-D1`, `CONST-B1`, `CONST-T8`, `CONST-G3`, etc.). +- **R3 (Zero Hallucination / Grounded)**: Rule text cites exact IDs and principles from the repository's `CONSTITUTION.md` and `CONSTITUTION-ARTICLES.md`. +- **R4 (Verification)**: `pnpm test` (and any new plugin test scripts) must execute cleanly, validating all YAML blocks across the repository. + +# Implementation Units + +- [ ] **U1: Scaffold OMP Plugin Manifest & Directory Structure** + - **Goal**: Create the plugin layout under `plugins/constitution/` or root plugin metadata with `.omp-plugin/plugin.json`, `.claude-plugin/plugin.json`, and `package.json`. + - **Files**: `plugins/constitution/.omp-plugin/plugin.json`, `plugins/constitution/.claude-plugin/plugin.json`, `plugins/constitution/package.json`, `plugins/constitution/README.md`. + +- [ ] **U2: Implement Article I & II TTSR Rules (Core & Boundary)** + - **Goal**: Author `rules/constitution-pure-core.md` and `rules/constitution-boundary.md` with precise regex and ast conditions for unchecked casts (`as any`, `as unknown`), eager async promises on domain signatures, mutable control flow in pure core, and missing error tagging. + - **Files**: `plugins/constitution/rules/constitution-pure-core.md`, `plugins/constitution/rules/constitution-boundary.md`. + +- [ ] **U3: Implement Article III & V TTSR Rules (Verification & Review P0)** + - **Goal**: Author `rules/constitution-verification.md` and `rules/constitution-conduct-review.md` covering mocked single implementations, tautological assertions, private helper unit tests, and the mandatory P0 automatic review rejection. + - **Files**: `plugins/constitution/rules/constitution-verification.md`, `plugins/constitution/rules/constitution-conduct-review.md`. + +- [ ] **U4: Wire Validation, Test Battery, and Documentation** + - **Goal**: Update root `scripts/validate-constitution.ts` (or add plugin test scripts) to validate the plugin rules, update `README.md` and `AGENTS.md` to document the OMP plugin installation and usage. + - **Files**: `scripts/validate-constitution.ts`, `README.md`, `AGENTS.md`, `package.json`. diff --git a/repos/constitution/docs/solutions/architecture-patterns/the-vacuous-pass-gate-input-sets.md b/repos/constitution/docs/solutions/architecture-patterns/the-vacuous-pass-gate-input-sets.md new file mode 100644 index 0000000..9796243 --- /dev/null +++ b/repos/constitution/docs/solutions/architecture-patterns/the-vacuous-pass-gate-input-sets.md @@ -0,0 +1,152 @@ +--- +title: A gate can go green because it stopped looking +date: 2026-08-18 +category: architecture-patterns +module: constitution corpus validator +problem_type: architecture_pattern +component: tooling +severity: high +applies_when: + - A gate's input set is named in the gate rather than derived from the tree + - A single-artifact check is widened to cover several artifacts + - A merge-blocking check reports a count it computed itself +tags: [gate-design, vacuous-pass, verification, fail-closed, known-bad-fixture] +--- + +# A gate can go green because it stopped looking + +## Context + +The constitution's format gate validated one markdown file whose path was a module-level +constant. The law was then split across two files. The gate's logic was untouched and +entirely correct — it parsed, it compared declared rule ids against parsed rule ids, it +found no discrepancy, it exited 0. It was reporting on a third of the rules. + +Nothing in the output distinguished that from a healthy corpus. The same run over the +whole corpus and the run over a third of it print the same shape, differing only in a +number nobody had a baseline for. + +Two further shapes of the same defect surfaced during review of the fix, both after the +input set had been widened to a tuple of paths: + +- A path present in the tuple but **absent from disk** was caught by a hard failure. A + path present, parsing cleanly, and declaring **zero rules** was not — emptying the + resident half to a preamble plus two `placeholder: true` blocks printed a valid line + with a smaller count and exited 0. +- The cross-revision arm tolerated a path that did not exist at the older revision, + because a newly created file legitimately has nothing to compare. That tolerance also + swallowed a path **renamed** in the same commit that re-scoped a rule: the older + revision had no such path, so the rule's retitle went uncompared and the run reported + no reassignment. + +## Guidance + +**Assert the corpus, not only the contents.** A check over a subset is +indistinguishable from a check over the whole unless the gate says which inputs it +measured. Three rules follow. + +1. **A missing input is a hard failure, never a smaller pass.** Absence of an expected + input is a defect in the gate's own configuration, and it must exit non-zero rather + than validate what remains. + +2. **Presence is not contribution.** An input that resolves, parses, and yields nothing + scores exactly like one that yields its half. Require every declared input to + contribute at least one unit of the thing being validated. This is a recomputation + from the bytes — *did this file produce a rule?* — not a number the author supplies. + +3. **What cannot be failed must be reported.** Some gaps are legal and failing them + would fire on correct work: a deliberate deletion, a genuinely new input with no + history. Name them on the success line — which inputs were not compared, which + identifiers vacated — so the reader sees the reduced coverage instead of inferring + full coverage from a green exit. + +**Never key a gate on a value its own author supplies.** The tempting fix for shape 1 is +a pinned expected count. That is a field the author writes, so the gate never runs on the +case it exists to catch. Prefer a key the gate recomputes: a digest over current bytes, a +compiler verdict, a re-derivation from the tree. + +**Prove the widening with a known-bad fixture.** A gate that has only ever been run +against a healthy input has demonstrated that it can print a success line. Run it against +an input that must fail, and read the failure text. Each defect above was found by a +fixture, not by reasoning about the code. + +## Why This Matters + +The failure is silent by construction and lands in the one place nobody reads twice: +output that already says everything is fine. Downstream, the green result is then cited as +evidence the invariant holds — so the gate does not merely fail to catch the defect, it +actively certifies its absence. + +The documentation compounds the harm. Prose asserting a guarantee the gate does not +implement ("a rule dropped in a move fails the gate") trains a maintainer to skip the +manual review the gate is not doing. Two independent reviewers caught that sentence here; +it had been written in the same change that widened the gate, by the author most convinced +the gate was now sound. + +## When to Apply + +- Widening any check from one artifact to several — the moment the input set becomes a + collection, it becomes a thing that can silently shrink. +- Any gate that prints a count it computed itself, with no baseline the reader can check. +- Any check whose failure path is reachable only by a state the repository never normally + reaches; that path has almost certainly never run. +- Reviewing a claim about what a gate enforces. Run the fixture; do not read the code and + agree with it. + +## Examples + +Before — a single named input. Correct, and silently correct about a third of the corpus +once the corpus grew: + +```python +PATH = "ONE_FILE.md" +text = open(PATH, encoding="utf-8").read() +# ... one file's worth of checking, exit 0 +``` + +After — the input set is a collection, absence is fatal, and emptiness is fatal: + +```python +PATHS = ("RESIDENT.md", "RETRIEVED.md") + +for p in PATHS: + try: + texts[p] = open(p, encoding="utf-8").read() + except FileNotFoundError: + fail([f"{p}: missing — half a corpus scores exactly like a whole one"]) + +# ... after parsing, every declared input must have produced something +for p in PATHS: + if p not in contributors: + errors.append(f"{p}: parses but declares no rule") +``` + +And what cannot be failed is stated rather than omitted: + +``` +valid: 34 rules across 6 yaml blocks in 2 files, 9 families; + no id reassigned since ; + not compared, absent at : RETRIEVED.md; + 1 id(s) vacated since : CONST-N3 +``` + +The fixture battery that found all of it — each must fail, and the failure text is the +artifact worth keeping: + +| Fixture | Required result | +|---|---| +| one corpus path absent from disk | fail, naming the path | +| one corpus path present but declaring nothing | fail, naming the path | +| an identifier duplicated across two paths | fail on the duplicate | +| a rule deleted while a citation to it survives | fail on the dangling citation | +| a malformed fence in the second path | fail, naming that path and a path-local block index | +| a rule deleted with nothing citing it | pass, and name the vacated identifier | +| a path renamed alongside a rule retitle | pass, and name the uncompared path | + +## Related + +- The gate discussed here is the constitution corpus validator invoked by the repository's + `test` script; its module docstring carries the same argument at the point of use. +- `CONST-E1` (Prefer the Gate) and `CONST-E3` (A Gate Earns Its Place) are the rules that + make a gate the final word and price its false-positive budget; this learning is the + counterweight — a gate that cannot fail is not enforcement, it is a certificate. diff --git a/repos/constitution/docs/solutions/architecture-patterns/ttsr-regex-rules-flag-translation-backreferences.md b/repos/constitution/docs/solutions/architecture-patterns/ttsr-regex-rules-flag-translation-backreferences.md new file mode 100644 index 0000000..c8c4acc --- /dev/null +++ b/repos/constitution/docs/solutions/architecture-patterns/ttsr-regex-rules-flag-translation-backreferences.md @@ -0,0 +1,31 @@ +--- +title: TTSR regex rules require explicit flag translation and generic backreferences +date: 2026-08-30 +category: architecture-patterns +module: omp-plugin-constitution +problem_type: architecture_pattern +component: tooling +severity: medium +applies_when: + - Authoring Time-Traveling Stream Rules (TTSR) markdown files with condition regexes + - Writing validators for stream rules with PCRE inline flags like `(?i)` or `(?ims)` + - Matching self-referential or tautological anti-patterns across tool streams +root_cause: tool_interface +tags: [ttsr, regex, omp, stream-rules, flag-translation, backreference] +--- + +# TTSR regex rules require explicit flag translation and generic backreferences + +## Context + +When creating an OMP plugin providing Time-Traveling Stream Rules (TTSR), rules are authored as Markdown documents with YAML frontmatter containing `condition` regex patterns and `scope` tool selectors. + +During implementation and review of the constitution plugin, two critical regex compilation and matching traps emerged: +1. **PCRE Inline Flags:** Authors frequently write `(?i)` or `(?ims)` in condition strings. Standard JavaScript / V8 `RegExp` engines reject leading `(?i)` group syntax as invalid groups (`SyntaxError: Invalid group`). The OMP capability layer translates leading PCRE flag groups (`/^\(\?([ims]+)\)/`) into native `RegExp` flags, but a custom validator that passes raw strings to `new RegExp(pattern)` fails valid rules, while failing to reject unsupported inline combinations (`(?imsu)` or mid-pattern flags). +2. **Literal vs Generic Anti-Pattern Matching:** Condition patterns targeting self-referential statements (e.g., asserting `expect(fn()).toBe(fn())`) fail completely when hardcoded to literal identifiers. To detect tautologies generically across all codebases without false negatives, condition regexes must use capturing groups and backreferences (`expect\(\s*(\w+)\s*\([^)]*\)\s*\)\.to(?:Be|Equal)\(\s*\1\s*\([^)]*\)\s*\)`). + +## Guidance + +- When validating TTSR condition regexes, implement the exact OMP inline flag translation (`/^\(\?([ims]+)\)/`) and reject unsupported flags explicitly before compiling. +- For anti-pattern rules that detect identical calls or operands on both sides of an operator, use regex backreferences (`\1`) rather than example identifiers. +- Validate the expected rule set strictly by exact filename or ID rather than loosely testing directory file counts. diff --git a/repos/constitution/docs/solutions/workflow-issues/plans-freeze-observations-about-a-moving-base.md b/repos/constitution/docs/solutions/workflow-issues/plans-freeze-observations-about-a-moving-base.md new file mode 100644 index 0000000..4ebe0db --- /dev/null +++ b/repos/constitution/docs/solutions/workflow-issues/plans-freeze-observations-about-a-moving-base.md @@ -0,0 +1,34 @@ +--- +title: Plans freeze observations about a moving base +date: 2026-08-30 +category: workflow-issues +module: constitution amendment workflow +problem_type: workflow_issue +component: development_workflow +severity: medium +applies_when: + - A plan names a base revision and its verification rows carry expectations measured at research time + - Commits land on the branch between plan freeze and the first implementation unit + - A fixture expectation in a plan contradicts what the gate prints at execution time +root_cause: stale planning baseline +tags: [plan-freeze, base-drift, fixtures, measured-expectations, review-findings] +--- + +# Plans freeze observations about a moving base + +## Context + +During the Article III verification amendment, the plan was researched and frozen against base `dd5722a`. Six commits landed on the branch before the implementation units started. By execution time, plan rows that named the pre-split tree ("exactly six vacated ids at this revision", "the uncompared-file clause fires at revision X") described a tree that no longer existed: the corpus split and corpus-commit had moved the vacated-id set to nine and moved the uncompared-file boundary to an earlier revision. The multi-agent review wave flagged the unsatisfiable rows; execution had already recorded the honest outputs by running the gate and keeping what it printed. + +The defect is not that the plan was wrong when written — it was measured against the base it named. The defect is that nothing re-derived the expectations when the base moved, and the plan's freeze rule (byte-identical after start) made correcting the rows in place the wrong move. + +## Guidance + +- At the first implementation unit, re-run every plan verification command against the current head. Record divergent outputs in the run record as measured facts; do not edit frozen plan rows to match. +- Derive fixture expectations at execution time from the gate's actual output. A number carried from research time is a hypothesis about the terminal; the run's own execution is the terminal. +- When a plan row and a measured output disagree, trust the measurement, note the divergence in the run record, and let the review (not the plan edit) carry the correction. Two unsatisfiable rows in this session were caught exactly this way — by a reviewer executing the named revision and comparing against the plan's text. +- Reviewers verify plan claims by execution against named revisions, not by re-reading the plan. That stance is what surfaced the drift; keep it in every review brief. + +## Applicability + +This applies to any planned change whose plan encodes observations about tree state (counts, absence boundaries, report shapes) rather than only intent. A plan that names only invariants and lets execution derive numbers has no rows to go stale. Where the plan must carry concrete expectations (fixtures for a gate), scope each expectation to the revision it was measured at, so a moved base invalidates the row loudly instead of silently. diff --git a/repos/constitution/plugins/constitution/.claude-plugin/plugin.json b/repos/constitution/plugins/constitution/.claude-plugin/plugin.json new file mode 100644 index 0000000..aa00a23 --- /dev/null +++ b/repos/constitution/plugins/constitution/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "@systemfsoftware/omp-plugin-constitution", + "version": "0.1.0", + "description": "OMP Plugin providing Time-Traveling Stream Rules (TTSR) enforcing strict adherence to the Supreme Engineering Constitution.", + "author": { + "name": "systemfsoftware", + "url": "https://systemfsoftware.com" + }, + "homepage": "https://github.com/systemfsoftware/constitution", + "repository": "https://github.com/systemfsoftware/constitution", + "license": "Apache-2.0", + "keywords": [ + "omp", + "plugin", + "constitution", + "rules", + "ttsr", + "architecture", + "clean-code" + ] +} diff --git a/repos/constitution/plugins/constitution/.omp-plugin/plugin.json b/repos/constitution/plugins/constitution/.omp-plugin/plugin.json new file mode 100644 index 0000000..aa00a23 --- /dev/null +++ b/repos/constitution/plugins/constitution/.omp-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "@systemfsoftware/omp-plugin-constitution", + "version": "0.1.0", + "description": "OMP Plugin providing Time-Traveling Stream Rules (TTSR) enforcing strict adherence to the Supreme Engineering Constitution.", + "author": { + "name": "systemfsoftware", + "url": "https://systemfsoftware.com" + }, + "homepage": "https://github.com/systemfsoftware/constitution", + "repository": "https://github.com/systemfsoftware/constitution", + "license": "Apache-2.0", + "keywords": [ + "omp", + "plugin", + "constitution", + "rules", + "ttsr", + "architecture", + "clean-code" + ] +} diff --git a/repos/constitution/plugins/constitution/README.md b/repos/constitution/plugins/constitution/README.md new file mode 100644 index 0000000..fbb7390 --- /dev/null +++ b/repos/constitution/plugins/constitution/README.md @@ -0,0 +1,29 @@ +# OMP Plugin — Supreme Engineering Constitution TTSR Rules + +Time-Traveling Stream Rules (TTSR) plugin for Oh My Pi (OMP) and Claude Code that enforces strict adherence to the [System F Software Engineering Constitution](https://github.com/systemfsoftware/constitution). + +## Overview + +This plugin installs active stream rules that intercept edits, writes, and reviews violating constitutional articles: +- **`rules/constitution-pure-core.md` (Article I):** Intercepts impure logic (exceptions, I/O in decisions), primitive obsession, procedural control flow (cyclomatic complexity > 1 in core), and unvalidated states. +- **`rules/constitution-boundary.md` (Article II):** Intercepts decisions in boundary adapters, eager async promises on public domain surfaces, unchecked type casts (`as any`), and interleaved I/O sandwiches. +- **`rules/constitution-verification.md` (Article III):** Intercepts mocks on single implementations, tautological/characterization tests, private helper unit tests, and dependent oracles. +- **`rules/constitution-conduct-review.md` (Article V & Governance):** Enforces mandatory **P0** automatic failure on all undeclared constitutional violations with zero appeals and zero waivers (`CONST-G3`). + +## Installation + +Install in OMP: + +```bash +omp plugin install @systemfsoftware/omp-plugin-constitution +``` + +Or link directly in local development: + +```bash +omp plugin link ./plugins/constitution +``` + +## License + +Apache-2.0 diff --git a/repos/constitution/plugins/constitution/rules/constitution-boundary.md b/repos/constitution/plugins/constitution/rules/constitution-boundary.md new file mode 100644 index 0000000..6ff81fb --- /dev/null +++ b/repos/constitution/plugins/constitution/rules/constitution-boundary.md @@ -0,0 +1,33 @@ +--- +description: "Fires on edits/writes that violate Article II (The Boundary) of the Constitution: decisions inside boundary handlers, eager async promises on public surfaces, unchecked type casts, or pass-through delegation layers." +condition: + - '(?\(\s*\(\s*(?:resolve|reject)' +scope: + - 'tool:edit(**/*.{ts,tsx,js,jsx,rs,py,go})' + - 'tool:write(**/*.{ts,tsx,js,jsx,rs,py,go})' + - 'tool:ast_edit(**/*.{ts,tsx,js,jsx,rs,py,go})' +interruptMode: tool-only +--- + +# Constitutional Interruption: Article II — The Boundary Violation + +You are authoring code that violates **Article II (The Boundary)** of the Supreme Engineering Constitution (`CONSTITUTION-ARTICLES.md`): + +1. **Functional Core, Imperative Shell (`CONST-B1`):** Boundary handlers, adapters, and middleware translate external data ↔ domain types. + - **DO NOT** make domain decisions inside boundary objects. Boundary layers that require complex logic testing belong in the pure core. + +2. **Effects Are Lazy Values (`CONST-B2`):** Return side-effects as lazy descriptions (`Effect`, IO values), interpreted once at the application edge. + - **DO NOT** put eager async results (e.g., bare `Promise`) directly on public domain surfaces. + +3. **The I/O Sandwich (`CONST-B3` & `CONST-B6`):** Outside interactions follow `read` (impure) → `transform` (pure) → `write` (impure). + - **DO NOT** interleave I/O inside transformation logic. + - **DO NOT** add useless pass-through layers that only forward calls without transforming, reading, or writing. + +4. **Dependencies Point Inward (`CONST-B4`):** The shell imports the core, never vice versa. Wire all implementations at one composition root. + +5. **Decode, Never Cast (`CONST-B5`):** Validate incoming serialized or foreign data into branded domain types with a typed decoder. + - **DO NOT** use unchecked type casting (`as any`, `as unknown`, `as Type`) or suppression comments (`@ts-ignore`, `@ts-expect-error`) to bypass validation. + +Remediate the boundary violation before re-issuing this write. diff --git a/repos/constitution/plugins/constitution/rules/constitution-conduct-review.md b/repos/constitution/plugins/constitution/rules/constitution-conduct-review.md new file mode 100644 index 0000000..5e1663c --- /dev/null +++ b/repos/constitution/plugins/constitution/rules/constitution-conduct-review.md @@ -0,0 +1,37 @@ +--- +description: "Fires during review or implementation when constitutional rules are treated as optional, bypassed without declared CONST-W3 justification, or downgraded from P0 severity." +condition: + - '(?i)(?:downgrade|waive|ignore|skip)\s+(?:the\s+)?constitution' + - '(?ims)severity\s*:\s*(?:P1|P2|P3|advisory)\b.{0,200}?\bCONST-[A-Z][0-9]+\b' + - '(?ims)\bCONST-[A-Z][0-9]+\b.{0,200}?\bseverity\s*:\s*(?:P1|P2|P3|advisory)\b' +scope: + - 'tool:edit(**/*)' + - 'tool:write(**/*)' + - 'tool:ast_edit(**/*)' +interruptMode: tool-only +--- + +# Constitutional Interruption: Constitutional Review & Conduct Violation + +You are attempting to waive, downgrade, or silently bypass the Supreme Engineering Constitution (`CONSTITUTION.md`): + +1. **Automatic P0 Severity on All Violations (`CONST-G3`):** + - Every undeclared violation of the constitution during review is an **automatic, non-negotiable P0 failure**. + - **ZERO APPEALS, NO SEVERITY DOWNGRADES, NO WAIVERS.** + - **DO NOT** downgrade a constitutional violation to an advisory, P1, P2, or non-blocking finding. + - **DO NOT** approve or merge any change while an undeclared constitutional violation exists. + +2. **No Silent Bypass (`CONST-W3`):** + - The **ONLY** legal exception to any rule is an explicit, declared bypass stated in the open, in the change itself, naming the exact rule ID and the specific reason why it was wrong or impossible for this case. + - Undeclared breaches cannot be waived by reviewer discretion. + +3. **Depth Over Expedience (`CONST-S1`):** + - Fix the root cause. Restructure when the design is wrong. Do not patch symptoms to ship faster. + +4. **Subtract Before You Add (`CONST-S4`):** + - Treat every line as a liability. Delete dead branches and replace complex records with unconstructable tagged unions before adding new abstractions. + +5. **First Principles Over Precedent (`CONST-S2`):** + - Neighboring code is evidence of what exists, never of what is correct. Justify all patterns by first principles. + +Block the change or declare the bypass explicitly under `CONST-W3`. diff --git a/repos/constitution/plugins/constitution/rules/constitution-pure-core.md b/repos/constitution/plugins/constitution/rules/constitution-pure-core.md new file mode 100644 index 0000000..857ec9e --- /dev/null +++ b/repos/constitution/plugins/constitution/rules/constitution-pure-core.md @@ -0,0 +1,33 @@ +--- +description: "Fires on edits/writes that violate Article I (The Pure Core) of the Constitution: impure decisions doing I/O or throwing, unvalidated primitives in domain models, unbranched boolean/string error distinctions, or procedural control flow." +condition: + - '(?i)throw\s+new\s+Error\(' + - '(?i)(?:class|interface)\s+\w+\s*\{[^}]*?\b(?:success|isError)\s*:\s*\b(?:true|false|boolean\b)' + - '(?i)type\s+\w+\s*=\s*\{[^}]*?\b(?:success|isError)\s*:\s*\b(?:true|false|boolean\b)' +scope: + - 'tool:edit(**/*.{ts,tsx,js,jsx,rs,py,go})' + - 'tool:write(**/*.{ts,tsx,js,jsx,rs,py,go})' + - 'tool:ast_edit(**/*.{ts,tsx,js,jsx,rs,py,go})' +interruptMode: tool-only +--- + +# Constitutional Interruption: Article I — The Pure Core Violation + +You are authoring code that violates **Article I (The Pure Core)** of the Supreme Engineering Constitution (`CONSTITUTION-ARTICLES.md`): + +1. **Pure Decisions (`CONST-P1`):** Every domain decision must be a pure function (data in, value or typed error out). + - **DO NOT** perform I/O, read system clocks, use randomness, or `throw` exceptions inside decision logic. + - Return errors as typed values. + +2. **Types Before Logic (`CONST-D1` & `CONST-D4`):** Make illegal states unrepresentable. + - **DO NOT** encode states by field presence (e.g. `Order { status, shippedAt?, trackingId? }`). Model mutually exclusive states as a tagged union. + - **DO NOT** use unchecked casts (`as any`, `as unknown`). + +3. **Distinct Tagged Errors (`CONST-D2`):** Give every failure its own tagged variant. + - **DO NOT** distinguish errors by a `success: boolean` flag or `message: string`. Callers must branch exhaustively on the variant tag. + +4. **No Primitive Obsession (`CONST-D3`):** Brand domain-significant numbers, IDs, and codes. + +5. **Single-Path Core (`CONST-P2`):** Pure decisions are expressions, not procedures (cyclomatic complexity = 1). Use exhaustive pattern matching / fold / map rather than procedural `if/else`, loops, or `switch` statements in pure decision modules. + +Remediate the violation before re-issuing this write. diff --git a/repos/constitution/plugins/constitution/rules/constitution-verification.md b/repos/constitution/plugins/constitution/rules/constitution-verification.md new file mode 100644 index 0000000..0815fa4 --- /dev/null +++ b/repos/constitution/plugins/constitution/rules/constitution-verification.md @@ -0,0 +1,42 @@ +--- +description: "Fires on edits/writes that violate Article III (Verification) of the Constitution: mocking the only real implementation, authoring tautological/characterization tests, testing intermediate private forwarding helpers, or computing expected test values by invoking the SUT." +condition: + - '(?i)(?:vi|jest)\.mock\(\s*["\x27]\.\.?/' + - '(?i)(?:sinon|td)\.replace\(\s*["\x27]\.\.?/' + - '(?i)expect\(\s*(\w+)\s*\([^)]*\)\s*\)\.to(?:Be|Equal|StrictEqual)\(\s*\1\s*\([^)]*\)\s*\)' + - '(?i)\bcharacterization\b' + - '(?i)\bgolden[\s_-]+master\b' +scope: + - 'tool:edit(**/*.{test,spec}.{ts,tsx,js,jsx,rs,py,go})' + - 'tool:write(**/*.{test,spec}.{ts,tsx,js,jsx,rs,py,go})' + - 'tool:ast_edit(**/*.{test,spec}.{ts,tsx,js,jsx,rs,py,go})' +interruptMode: tool-only +--- + +# Constitutional Interruption: Article III — Verification Violation + +You are authoring tests that violate **Article III (Verification)** of the Supreme Engineering Constitution (`CONSTITUTION-ARTICLES.md`): + +1. **Test Public Exports Directly, Pure Logic with Mutation (`CONST-T8`):** + - Test the public API with real inputs and outputs. + - **DO NOT** write unit tests for intermediate helper functions that only pass data to other functions. + - **DO NOT** mock a dependency when only one real version of it exists. + - **DO NOT** mix adapters or I/O code into the same mutation test run as pure calculation logic. + +2. **Properties by Narrow Grant (`CONST-T14`):** + - Pure decisions earn property tests only when a universal invariant over generated inputs cannot be fully pinned from the public surface. + - **DO NOT** spray property tests across decisions already covered from the exported surface. + +3. **Mutation Is the Measure (`CONST-T3` & `CONST-T13`):** + - Gate change-relevant pure logic at a 100% mutation kill score over a named, non-empty set. + - Never suppress mutants or shrink the mutated set after the fact. + +4. **Independent Oracles (`CONST-T10` & `CONST-T9`):** + - Every test assertion must have an oracle the SUT did not produce (spec literal, second implementation, law relating two views). + - **DO NOT** compute expected values by calling the implementation under test. + - **DO NOT** write tautological / characterization tests that simply record current output and assert it. + +5. **Snapshots on Public Exports Only (`CONST-T11`):** + - Snapshot only canonicalized, published output formats through the package's published export map. + +Fix the test architecture before proceeding. diff --git a/repos/constitution/scripts/validate-constitution.ts b/repos/constitution/scripts/validate-constitution.ts new file mode 100755 index 0000000..bf507e0 --- /dev/null +++ b/repos/constitution/scripts/validate-constitution.ts @@ -0,0 +1,288 @@ +#!/usr/bin/env -S deno run --allow-read=CONSTITUTION.md,CONSTITUTION-ARTICLES.md --allow-run=git +/** + * Validate the constitution corpus against constitution-rule/v1. + * + * Gate for CONST-E1 applied reflexively: the constitution's own format must fail a + * command, not a cited clause. Validates every fenced ```yaml block against + * hardcoded schema fields (required_fields, optional_fields, gate_values). + * + * Coverage is checked before schema. A rule the parser never reaches cannot be + * validated, and an unterminated fence silently removes every rule after it from + * the block — so counting ids in the raw text and comparing against ids parsed + * out of blocks is the only way this gate can report on what it did NOT see. + * Without that comparison a green run means "no rule I happened to parse was + * malformed", which is not the claim the gate is making. + * + * The corpus is two files — the resident law and the retrieved articles — and the + * union is the unit every check runs over. Ids are unique across it and citations + * resolve across it: CONST-S4 cites CONST-T9, which lives in the other file. Point + * this at one file and the coverage comparison above still passes, on a third of + * the rules, which is precisely the vacuous pass it exists to prevent. A file that + * is merely absent is not the only shape of that pass: a file present and parsing + * but declaring no rule scores identically, so every path in PATHS must contribute + * at least one rule of its own. + * + * There is no backwards compatibility and no retirement ledger. A deleted rule + * leaves its number vacant and a citation to it resolves to nothing, which is a + * loud failure and needs no gate. Vacancy is named, not enforced: --against lists + * ids vacated since the revision and corpus files absent at it on the success + * line, because a green line that silently measured less is the vacuous pass this + * gate exists to prevent. The one identifier defect that is NOT loud is an id that + * survives while its rule changes underneath it: every citation keeps resolving, + * to the wrong rule. No single revision can see that, so `--against ` + * recomputes it from git. + * + * Exit 0 clean, 1 with a named defect list, 3 unmeasurable — no identifiers matched + * at all, reported distinctly because an id pattern that matches nothing scores a + * healthy corpus and an id-free one identically. + */ +import { parse } from "@std/yaml"; + +const PATHS = ["CONSTITUTION.md", "CONSTITUTION-ARTICLES.md"] as const; + +const ID_RE = /^CONST-[A-Z]\d+$/; +const ID_IN_TEXT_RE = /^\s*- id:\s*(\S+)\s*$/gm; +const TITLE_IN_TEXT_RE = /^\s*- id:\s*(\S+)\s*\n\s*title:\s*(.+?)\s*$/gm; +const CITE_RE = /\bCONST-[A-Z]\d+\b/g; + +// A family letter names what a rule is ABOUT, never where it sits. Adding a +// letter here is half the change; the other half is the registry in AGENTS.md. +const FAMILIES: Record = { + "G": "Governance", + "E": "Enforcement", + "P": "Purity", + "D": "Domain modelling", + "B": "Boundary", + "T": "Testing", + "N": "Naming & structure", + "W": "Work discipline", + "S": "Subtraction", +}; + +const REQUIRED_FIELDS = ["id", "title", "gate", "do", "dont", "harm", "check"]; +const OPTIONAL_FIELDS = ["scope", "example", "layers"]; +const GATE_VALUES: Record = { + "lint": true, + "type-checker": true, + "mutation": true, + "review": true, +}; +const KNOWN_FAMILIES = Object.keys(FAMILIES).sort().join(", "); +const KNOWN_GATES = Object.keys(GATE_VALUES).sort().join(", "); + +type Rule = Record; + +function fail(errors: string[]): never { + for (const e of errors) console.log(`FAIL ${e}`); + Deno.exit(1); +} + +function titlesFrom(text: string): Map { + const titles = new Map(); + for (const m of text.matchAll(TITLE_IN_TEXT_RE)) titles.set(m[1], m[2]); + return titles; +} + +const againstIndex = Deno.args.indexOf("--against"); +const againstEquals = Deno.args.find((a) => a.startsWith("--against=")); +const against = againstIndex >= 0 + ? Deno.args[againstIndex + 1] + : againstEquals?.slice("--against=".length); +const againstRequested = against !== undefined || againstIndex >= 0 || + againstEquals !== undefined; +if (againstRequested && (against === undefined || against.length === 0)) { + fail([ + "--against requires a revision (form: --against or --against=) — a silently skipped comparison is the vacuous pass this gate exists to prevent", + ]); +} +const strayAgainst = Deno.args.find((a) => + a.startsWith("--against") && a !== "--against" && !a.startsWith("--against=") +); +if (strayAgainst !== undefined) { + fail([`unknown flag '${strayAgainst}' — did you mean --against ?`]); +} + +const errors: string[] = []; + +const texts: Record = {}; +for (const p of PATHS) { + try { + texts[p] = await Deno.readTextFile(p); + } catch { + fail([ + `${p}: missing — the corpus is both files, and half a corpus scores exactly like a whole one`, + ]); + } +} + +const blocks: Array<{ path: string; index: number; body: string }> = []; +for (const [p, t] of Object.entries(texts)) { + const found = [...t.matchAll(/```yaml\n([\s\S]*?)```/g)]; + if (found.length === 0) { + errors.push(`${p}: no fenced yaml rule blocks found`); + } + found.forEach((m, j) => blocks.push({ path: p, index: j, body: m[1] })); +} +if (blocks.length === 0) { + fail([...errors, "no fenced yaml rule blocks found in any corpus file"]); +} + +const rules: Rule[] = []; +for (const b of blocks) { + try { + const doc = parse(b.body) as { rules?: Rule[] } | null; + rules.push(...(doc?.rules ?? [])); + } catch (e) { + errors.push( + `${b.path} block ${b.index}: YAML parse error: ${(e as Error).message}`, + ); + } +} + +const parsedIds = rules.map((r) => String(r.id)); +const declaredIds = Object.values(texts).flatMap((t) => + [...t.matchAll(ID_IN_TEXT_RE)].map((m) => m[1]) +); +const parsedIdSet = new Set(parsedIds); +const uncovered = declaredIds.filter((i) => !parsedIdSet.has(i)); +if (uncovered.length > 0) { + errors.push( + `${uncovered.length} rule(s) declared in the corpus but never parsed into a yaml block: [${uncovered.join(", ")}] — check for an unterminated \`\`\`yaml fence`, + ); +} + +if (declaredIds.length === 0) { + console.log( + "UNMEASURABLE: no rule identifiers matched — the corpus is empty, or the id syntax moved", + ); + Deno.exit(3); +} + +const seen = new Set(); +for (const r of rules) { + const rid = String(r.id ?? ""); + for (const f of REQUIRED_FIELDS) { + if (!(f in r)) errors.push(`${rid}: missing required field '${f}'`); + } + const unknown = Object.keys(r).filter((k) => + !REQUIRED_FIELDS.includes(k) && !OPTIONAL_FIELDS.includes(k) + ); + if (unknown.length > 0) { + errors.push(`${rid}: unknown fields [${unknown.sort().join(", ")}]`); + } + if (!ID_RE.test(rid)) { + errors.push(`${rid}: id does not match ${ID_RE.source}`); + } else if (!Object.hasOwn(FAMILIES, rid["CONST-".length])) { + errors.push( + `${rid}: family '${rid["CONST-".length]}' is not registered — known families are [${KNOWN_FAMILIES}]`, + ); + } + if (seen.has(rid)) errors.push(`${rid}: duplicate id`); + seen.add(rid); + if (typeof r.gate !== "string" || !Object.hasOwn(GATE_VALUES, r.gate)) { + errors.push(`${rid}: gate '${String(r.gate)}' not in [${KNOWN_GATES}]`); + } + for (const f of ["do", "dont"] as const) { + const v = r[f]; + const shaped = typeof v === "string" || + (Array.isArray(v) && v.every((x) => typeof x === "string")); + if (!shaped) errors.push(`${rid}: '${f}' must be a string or list of strings`); + } + const ex = r.example; + if (ex !== undefined && ex !== null) { + const shaped = typeof ex === "object" && !Array.isArray(ex) && + Object.values(ex).every((v) => typeof v === "string"); + if (!shaped) errors.push(`${rid}: 'example' must be a map of strings`); + } +} + +const cites: Record> = {}; +for (const [p, t] of Object.entries(texts)) { + cites[p] = new Set([...t.matchAll(CITE_RE)].map((m) => m[0])); +} +const allCited = new Set(); +for (const s of Object.values(cites)) { + for (const c of s) allCited.add(c); +} +for (const cited of [...allCited].sort()) { + if (seen.has(cited)) continue; + for (const p of PATHS) { + if (cites[p]?.has(cited)) { + errors.push(`dangling citation: '${cited}' is cited in ${p} but names no rule`); + } + } +} + +async function checkAgainst( + rev: string, + errors: string[], + liveTitles: Map, +): Promise<{ vacated: string[]; uncompared: string[] }> { + const oldTitles = new Map(); + const uncompared: string[] = []; + try { + const results = await Promise.all(PATHS.map(async (p) => { + const cmd = new Deno.Command("git", { + args: ["show", `${rev}:${p}`], + stdout: "piped", + stderr: "piped", + }); + const out = await cmd.output(); + if (!out.success) return { p, absent: true, titles: [] }; + return { p, absent: false, titles: [...titlesFrom(new TextDecoder().decode(out.stdout))] }; + })); + for (const r of results) { + if (r.absent) { + uncompared.push(r.p); + continue; + } + for (const [rid, title] of r.titles) oldTitles.set(rid, title); + } + } catch (e) { + errors.push(`--against ${rev}: git is not runnable (${(e as Error).message})`); + return { vacated: [], uncompared: [] }; + } + + if (oldTitles.size === 0) { + errors.push( + `--against ${rev}: no rules found in any corpus file at that revision — wrong rev, or every file was renamed`, + ); + return { vacated: [], uncompared: [] }; + } + + for (const [rid, oldTitle] of oldTitles) { + const live = liveTitles.get(rid); + if (live !== undefined && live !== oldTitle) { + errors.push( + `reassigned id: '${rid}' named "${oldTitle}" at ${rev} and names "${live}" now — every citation to it resolves to a different rule`, + ); + } + } + + const vacated = [...oldTitles.keys()].filter((rid) => !liveTitles.has(rid)) + .sort(); + return { vacated, uncompared: uncompared.sort() }; +} + +let vacated: string[] = []; +let uncompared: string[] = []; +if (against !== undefined) { + const liveTitles = new Map(); + for (const t of Object.values(texts)) { + for (const [rid, title] of titlesFrom(t)) liveTitles.set(rid, title); + } + ({ vacated, uncompared } = await checkAgainst(against, errors, liveTitles)); +} + +if (errors.length > 0) fail(errors); + +const suffix = against !== undefined ? `; no id reassigned since ${against}` : ""; +console.log( + `valid: ${rules.length} rules across ${blocks.length} yaml blocks in ${Object.keys(texts).length} files, ${Object.keys(FAMILIES).length} families${suffix}`, +); +if (against !== undefined && uncompared.length > 0) { + console.log(` not compared, absent at ${against}: ${uncompared.join(", ")}`); +} +if (against !== undefined && vacated.length > 0) { + console.log(` ${vacated.length} id(s) vacated since ${against}: ${vacated.join(", ")}`); +} diff --git a/subtrees.toml b/subtrees.toml new file mode 100644 index 0000000..4e66a8d --- /dev/null +++ b/subtrees.toml @@ -0,0 +1,5 @@ +[[repos]] +name = "constitution" +url = "https://github.com/systemfsoftware/constitution" +branch = "main" +description = "Shared engineering laws for clean code, testing, and project organization at System F Software"