docs: make the chapters agree and name the three-model pipeline - #115
Merged
Merged
Conversation
Wording fixes for rules the model has already decided, so a reader of any one chapter meets the rule in force. No model change. - Chapter 20 said hardening was "one platform posture plus declared exceptions" and derived `securityContext` "from `hardening` and its declared exceptions", above its own "Hardening has no exception surface either". 0016 deleted the exception vocabulary; the authority row and the derived-mechanics bullet now say so and name E_HARDENING_UNMET, and CONTEXT.md's Hardening Class loses its "named exceptions". - "a Context", "the pinned context" and "a context" meaning the Platform Intent are gone from chapters 10, 16 and 60. - Chapter 10 carried a truncated copy of "Writing a derived value as a literal is a build error" in front of the sentence. Removed. - Chapter 60's onboarding step said an Application carries `alertClass`. It carries an optional `observability` block, whole or absent. Its platform-facts paragraph listed an observability policy the Platform document does not have; it now lists the monitor cadence. The worked platform example's README named the same non-existent block, and now names `monitors`. - Chapter 10 pointed at chapter 14's probe policy for the scrape cadence. The monitor cadence has its own section. - CONTEXT.md's Alert Class said how an alert "should be delivered". Delivery is routing, which belongs to the monitoring stack. It states urgency. - Chapter 16's derivation-map mermaid labelled the Platform Intent a layer-2 input and carried a Flux health timeout class, a Flux Kustomization, a reachability entry and edge-catalog nodes the drawn SVGs do not. The catalogs reach the ConfigMap, as the deliverables SVG draws it, and the Kubernetes Service node the rename had relabelled "Application" is "Service" again. - The file layout and the worked examples disagreed. The project file is `platform/<project>.project.yml`, the suffix naming its `kind`, which every example and refusal fixture already spells; the chapter, chapter 14's foundation files, the publish workflow and the examples' header comments now say it too. A Process's env files live in a directory named for the Process, so the cluster overlay has somewhere to sit: four flat example env files move, and data's is filed under its Process `postgres`. - Chapter 00's example table and its overview diagram named a `projects/` directory, flat env files and a `platform.yml` that do not exist.
The three layers are three models, each joined to the next by a transformation. They are not metalevels: no layer is a type model of the layer below it, and "meta-model" claims that relation. - 0003's file name, chapter 00's heading and its anchor, the overview diagram's file name and embedded diagram name, CONTEXT.md's section and the README now say "three-model pipeline". 0003's decision and its H1 are unchanged; its Why records the rename. - CONTEXT.md defines "Three-model pipeline" and "Metamodel", the latter kept for a language definition only, and says why "meta-model" is retired. - The anchor, 0003's normative pointer, the register row and every link at the old file names move in this one commit, so lint:adrs and lint:links resolve throughout. That includes 0105, 0116 and the model-driven 0109, which cite 0003 and postdate the first draft of this change.
ExtraToast
added a commit
that referenced
this pull request
Sep 15, 2026
The claim workflow read every bare #NNN in a title or body as a claim,
labelling any issue merely mentioned ("follows #115", "blocked by #39",
"unlike #41"), the exact shape of the labelling incident this fixes. It
also released a claimed issue's parent on every child close, and depended
on gh issue view's --json parent field without confirming it exists on
the runner.
scripts/claim-issue.ts (test/claim-issue.test.ts, no gh call) now extracts
candidates from a GitHub closing keyword (closes, fixes, resolves, and
their inflections) immediately naming an issue, or the branch name's
leading number, stripping fenced code blocks first. The workflow calls it
for the candidate list and keeps every gh call in its own bash: on claim,
the parent is claimed too if unclaimed; on release, only the named issue
is released, never the parent, which stays a human's call or a later
workflow's. Dropped synchronize from the triggers, since a push re-runs
the same extraction over the same fields and can only repeat what already
holds.
Confirmed gh issue view --json parent is real and supported: added in gh
CLI 2.94.0 (2026-06-10 changelog), and the ubuntu-latest runner image
ships 2.100.0. A gh issue view failure is now logged with its real error
text rather than assumed to mean "not an issue".
Coverage rises again with the new file's own full coverage (statements
99.09%, branches 96.32%, functions 100%, lines 99.01%), still above
main's floor; vitest.config.ts and README.md carry the measured values.
ExtraToast
added a commit
that referenced
this pull request
Sep 15, 2026
* docs: route every agent through AGENTS.md Add AGENTS.md as the one instruction file every coding agent reads: the estate pointer, the repository map, setup, every npm script, the testing strategy, code style, the Boundaries section, the repository skills, the claiming procedure, and the moved emf/ and MDE coursework sections. CLAUDE.md becomes an import of it plus Claude-specific notes only. * docs: configure repository skills and agent tooling Four skills (adr, spec-change, gate-triage, new-rule) live in .agents/skills, and .claude/skills is a committed symlink to that directory (git ls-files -s reads mode 120000). .claude/settings.json holds a read-only permission allowlist plus the one labelled write the claim procedure needs, and enables the mattpocock-skills, typescript-lsp and drawio plugins. .mcp.json holds context7. docs/agents/ configures the mattpocock-skills engineering skills: the GitHub issue tracker with cross-repository references written in full, the estate status: labels mapped to the five canonical triage roles, and the single-context domain doc layout with spec/v1 normative over an ADR. * test: fail when a script escapes AGENTS.md's script list Add lint:agents (scripts/lint-agents.ts), wired into the contracts CI job and npm run verify, with a fixture (test/agents-contract.test.ts) proving it fires on an undocumented script. Ledger rows REQ-029 and RULE-069 record it, and docs/architecture.md's Gates table gains a row and its count moves to nineteen, per 0102 and 0104. The new gate's own coverage (96%/94.73%/100%/95.83%) carries the same one-line command-guard gap every sibling gate script already does; the suite's aggregate moves to 98.98%/96.06%/100%/98.89%, so the ratchet in vitest.config.ts and README's quoted numbers move with it. * feat(ci): claim the issue a pull request names A non-gating workflow that adds status: in-progress (and removes status: ready-for-agent) on the issue a pull request's title, body or branch name references, and on its parent, when the pull request opens; releases the claim when it closes. Labels only: the project board's own Status field needs a token scope the default GITHUB_TOKEN does not carry, and the job says so in its own log rather than claiming a fix it cannot make. * docs: extend CONTRIBUTING.md with change shape and release flow Below the existing contribution policy: the shape of a change, when a change takes a decision, Conventional Commits, what runs where across the five non-gating workflows beside ci.yml, and release candidates and releasing. * test: recompute the coverage ratchet after rebasing onto main REQ-029 collided with #118's own row landed on main; this branch's AGENTS.md script-list requirement moves to REQ-030. #118 also moved the coverage baseline, so the ratchet in vitest.config.ts and README's quoted numbers are recomputed against the rebased tree rather than the pre-rebase measurement. * test: hold the ratchet at main's floor after the AGENTS.md gate Cover lint-agents.ts's own entrypoint guard and default output parameter with an in-process module reload, the way test/package-contents-contract.test.ts and test/secret-scan-contract.test.ts already do for theirs, so the new gate is the first to leave nothing uncovered. Rebased onto main again (#119, docs-only); REQ-030 and RULE-069 remain free. Every metric now sits above main's floor (99.06/96.17/100/98.97): statements 99.07%, branches 96.25%, functions 100%, lines 98.99%, and vitest.config.ts and README.md carry the measured values. * fix(ci): claim only what closes, fixes or resolves a pull request The claim workflow read every bare #NNN in a title or body as a claim, labelling any issue merely mentioned ("follows #115", "blocked by #39", "unlike #41"), the exact shape of the labelling incident this fixes. It also released a claimed issue's parent on every child close, and depended on gh issue view's --json parent field without confirming it exists on the runner. scripts/claim-issue.ts (test/claim-issue.test.ts, no gh call) now extracts candidates from a GitHub closing keyword (closes, fixes, resolves, and their inflections) immediately naming an issue, or the branch name's leading number, stripping fenced code blocks first. The workflow calls it for the candidate list and keeps every gh call in its own bash: on claim, the parent is claimed too if unclaimed; on release, only the named issue is released, never the parent, which stays a human's call or a later workflow's. Dropped synchronize from the triggers, since a push re-runs the same extraction over the same fields and can only repeat what already holds. Confirmed gh issue view --json parent is real and supported: added in gh CLI 2.94.0 (2026-06-10 changelog), and the ubuntu-latest runner image ships 2.100.0. A gh issue view failure is now logged with its real error text rather than assumed to mean "not an issue". Coverage rises again with the new file's own full coverage (statements 99.09%, branches 96.32%, functions 100%, lines 99.01%), still above main's floor; vitest.config.ts and README.md carry the measured values. * fix(test): keep agent configuration out of the mutation sandbox Stryker copies the repository into its sandbox before running, and its copyfile call dies on .claude/skills: a symlink to a directory, which copyfile refuses outright (EISDIR). ignorePatterns (a real, documented Stryker option, confirmed against node_modules/@stryker-mutator/core's own stryker-schema.json for the pinned 10.0.0) now excludes /.claude and /.agents, neither of which the mutation run needs: Claude Code configuration and skill content, not src/. The symlink itself is untouched; only the sandbox copy skips it. * test: hold the ratchet at main's floor after rebasing onto #120 #120 (the Platform document reader) took REQ-030 for an unrelated behaviour and added REQ-031; this branch's AGENTS.md script-list requirement moves to REQ-032, the ledger now states 32 rows. #120 added no npm script, so AGENTS.md's script list needed no change (lint:agents confirms all 22 scripts still resolve). Coverage rises with #120's own new, fully-tested code: statements 99.15%, branches 96.49%, functions 100%, lines 99.08%, still above main's floor (99.06/96.17/100/98.97, which #120 left untouched); vitest.config.ts and README.md carry the measured values.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #36
Closes #51
Parent #35
What this branch does
Two passes over the specification, in the vocabulary #100 landed: the model chapters stop contradicting themselves, and the "three-layer meta-model" becomes the three-model pipeline.
The chapters agree with themselves (#36)
Each of these was a sentence that said one thing while the chapter, the record or the worked example said another.
securityContextrow andCONTEXT.md's Hardening Class now say there is no per-control relaxation, and nameE_HARDENING_UNMET.cluster-deploy-contextOCI artifact are left alone, as is historical prose in ADRs.alertClass; it is an optionalobservabilityblock, whole or absent. Its platform-facts paragraph listed an observability policy the Platform document does not have; it lists the monitor cadence. Chapter 10's scrape cadence linked a probe section when chapter 14 has its own## Monitor cadence, whichlint:linkscould not catch because the wrong anchor exists. The platform example README described areceiversblock whereplatform.intent.ymlhasmonitors.CONTEXT.md's Alert Class described routing; it describes urgency.Applicationwhere it means a Kubernetes Service, a leftover of the rename; it readsService.platform/<project>.ymlwhile every example, refusal fixture and suffix-selecting test uses.project.yml. The rule isplatform/<project>.project.yml, the suffix naming the file'skind. Four flat env files move toenv/<process>/base.env, so the per-cluster overlay has a directory to sit in.data's goes underpostgres/, the Process, rather than the Application id its header already disagreed with. Chapter 00's examples table and overview diagram named paths that do not exist; they name the ones that do.Every comment edit in a YAML or env file keeps that file's line count, so no line reference elsewhere shifts. The suffix-selecting tests still select what they should: 14
.project.ymlfiles for the diagram test, 4 cases for the Project Intent test, and the 4 worked files for the EMF worked-examples test.The three-model pipeline (#51)
"Meta-model" claimed something the decision does not. The intent models, the Resolved Deployment and the Deliverable Set sit at one metalevel: each is transformed into the next, and none is a type model of another. A meta-model names a stack of metalevels, which this pipeline does not have.
CONTEXT.mdnow defines Three-model pipeline and Metamodel apart, and says why the old name is retired.docs/adr/model/0003-three-layer-meta-model.mdbecomes0003-three-model-pipeline.md, with a dated note in its Why. Its decision and H1 are unchanged.## The meta-modelbecomes## The three-model pipeline. It was 0003'snormative:target, so the frontmatter and the register's normative column move to#the-three-model-pipelinein the same commit, andlint:adrsresolves it.00-overview-three-model-pipeline.drawio.svg, and its embedded name follows.The only remaining occurrences of the old name are the three sentences that say it is retired.
Verification
npm run lint:adrs: 96 files clean.node scripts/lint-adrs.ts emf: 10 files clean.npm run lint:links: 178 files clean.npm run verifyexit 0: 24 requirement rows, 64 rules with 11 pending, 32 doc claims, 466 tests.The EMF Maven and Tycho job runs in CI only. The one
emf/change here is prose in ADR 0109.Left for follow-ups
spec/v1/examples/negative/*/intent*/do not carry.project.yml. They stand in for published fragments rather than a repository'splatform/tree, and renaming them would pull them into the worked-examples tests, where one is expected to fail a parse. That wants its own change.spec/v1/examples/platform/README.mdstill says "the Traefik Application" where it means a Service, and has no row for thehardening:block the platform document carries.spec/v1/examples/data/rendered/README.mdwritesalertClass: none, and chapter 10 says Alert Class has nononemember.