feat: declare the Service Intent metamodel once and parse every example with it - #77
Closed
ExtraToast wants to merge 1 commit into
Closed
ExtraToast wants to merge 1 commit into
ExtraToast wants to merge 1 commit into
Conversation
…le with it The Service Intent language had four statements of itself and no declaration: a class diagram kept by hand in three notations, a closed-vocabulary table, a set of field tables, and prose for 61 error codes. Nothing joined them, so a disagreement between any two was invisible: each artefact was internally consistent. The checks that existed read YAML by indentation, which could see neither a closed vocabulary nor a union nor where in a document a defect was. Declare the language once, as the wire schemas docs/architecture.md prescribes, and parse every Service Intent document in the repository against it. - src/wire/service-intent/ is the metamodel: one export per class of chapter 10's diagram, collected in one enumerable record, plus the concrete syntax (YAML, and the placeholder grammar the env files are written in, which had a table of four sources and no grammar until now). Unions are unions: a grant is discriminated on engine per 0085, a probe is http or tcp. The seventeen closed vocabularies are enums there and nowhere else in code. - src/domain/service-intent/ is the abstract syntax and the well-formedness rules. A rule is a pure function from one parsed document to a diagnostic list, registered with its code, the class it constrains, its Essential OCL placement and its chapter anchor: the shape chapter 40 already gives the estate-wide invariants. Eleven rules are registered; the rules that need a second document name the input they are missing instead. - src/application/parse-service-intent.ts is what "conforms to" means: YAML, then the metamodel, then the mapper, then every rule, each stage reporting everything it finds. - scripts/lint-intent.ts parses all eleven documents and five env files. A refusal fixture must fail with exactly the code its expect header names, and the committed JSON Schema must be what the metamodel generates. Settled here, per the epic: stateful is deleted from the language, from chapter 10 and its class diagram, from chapter 20's authority table and from the examples; the object kind derives from lifecycle and volumes. A Workload has zero or more env files. The indentation-based checks in the simplification and diagram-consistency tests are replaced by checks over the parsed model. Three example defects the parser found and this fixes: the postgres claim declared no size, the two cutover fixtures declared an engine over a reconstructible volume that derives no backup, and the negative fixtures carried a Service-level alertClass that 0021 replaced. Two places where the specification disagreed with itself are repaired in the spec's own favour: E_NON_KV_DELIVERY narrows to transit, which is what the field table, the Delivery section and 0085 all say and only the validation table did not; and chapter 10's Asset class drew a `substitute` map that its own Assets section and every worked example do not carry. Closes #38
| it("includes the auth worked example, parsed into the domain model", () => { | ||
| const file = join( | ||
| REPOSITORY, | ||
| "spec/v1/examples/auth/auth.domain.yml".replaceAll("/", "/"), |
| "expect: E_ENGINE_WITHOUT_DURABILITY\ndomain: fixture", | ||
| ) | ||
| .replace("runtime: node", "runtime: node\n engine: valkey") | ||
| .replace("provides", "provides") |
ExtraToast
marked this pull request as draft
September 14, 2026 09:46
Contributor
Author
|
Parked as a draft. The model's design is not settled, so landing the first |
This was referenced Sep 14, 2026
Contributor
Author
|
Superseded by the three pull requests that rewrote this work against the renamed model, with the model-driven implementation beside it: #112 (the minimal case end to end), #113 (every worked example, the descriptor and the JSON Schema) and #114 (refusals, their diagnostics oracles and the OCL invariants, closing #38). Closing. |
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 #38 (parent #35)
What this branch does
The Service Intent metamodel is declared once, in
src/wire/service-intent/, and every example in the repository is parsed against it. A document either conforms, or fails with the error code the specification says it should. This is the repository's firstsrc/.The judgements later tickets inherit
The wire schema is the metamodel. One exported Zod const per class of chapter 10's diagram, named as the diagram names it, collected in
METAMODEL, with the 17 closed vocabularies beside it inCLOSED_VOCABULARIES.docs/architecture.md#the-wire-boundaryalready made the wire schema the single source of the runtime check, the TypeScript type and the JSON Schema; this makes it the source of the diagram and the tables as well.Declaring it in the domain was rejected, and the reason matters for #41 and #42: the authoring shape carries exactly what the domain drops, a
providesmap, an omittedenginemeaningkv, an absent block meaning none. Generating the authoring language from the domain would put the authoring vocabulary back in the core, which is what ADR 0066 separated.A code is a registry entry, never a schema refinement. A rule with an
E_code lives inSERVICE_INTENT_RULEScarrying{code, context, placement, anchor, states, evaluate}; a rule the specification gives no code stays in the schema and reportskind: "schema". The sharp case isE_ALERT_CLASS_WITHOUT_SIGNAL: Zod could express "whole or absent", but folding it in would refuse the same document while reportingschema, losing the code the fixture names. SoObservability.scrapeis deliberately optional, which is what makes the half-declared block representable and therefore refusable by name. Codes are unique, contexts are metamodel class names, anchors resolve against real chapter 10 headings, all asserted. No code was invented.Conformance is four ordered steps. It parses as YAML, it is an instance of the metamodel (strict objects, so an unknown key fails, with a document path), it maps into the domain totally, and then every registered rule runs. All of them, never short-circuiting. An empty diagnostic list means it conforms. Semantics is not evaluated. Every domain node carries
at, its document path, so a diagnostic's address is a property of the model rather than of the code that found the defect.What one document cannot decide is exported data, not prose.
NOT_DECIDED_BY_ONE_DOCUMENTholds 21 codes, each naming the input that is missing: the composed union, the Platform document, the node contract, the images lock, the ClusterState snapshot, the rendered set. A test holds it disjoint from the registry.E_UNKNOWN_SURFACEis deliberately left unimplemented and listed there, so #39 keeps its ticket whole.All 11 rules are
inv. ThePlacementtype already carriesderiveandpre, so the registry's shape does not change when derivation arrives.Three defects the parser found in committed examples
data.domain.yml'spostgres-datavolume declared nosize, which chapter 10 requires. Set to the100Giits own committed rendered PVC already carries.reconstructiblevolume, so each carried two defects and had stopped isolating one.alertClassthat ADR 0021 replaced with theobservabilityblock, so they would have failed schema validation long before reaching the identity check they exist for.Where the specification disagreed with itself
E_NON_KV_DELIVERY: chapter 10's field table, its Delivery section and ADR 0085 all say atransitgrant isselfonly. Only chapter 10's own validation table still said "a non-KV grant". The stale row is what I fixed.Asset.substitute: drawn in the class diagram, absent from the chapter's own Assets section, its field tables and every example. Removed from the mermaid and the SVG.apiVersionandkind: stated normatively, carried byminimal, all four refusals and the Platform document, absent from four others. Made required and added, because the alternative legalises a document that does not say which language it is written in.E_DUPLICATE_ROUTEversusE_DUPLICATE_ROUTE_MATCH: chapter 10 line 1088 says the first, its normative table says the second, and chapter 40 and ADR 0093 still say the first. Register every model rule with its context, placement, OCL statement and fixture #44 explicitly owns this cleanup, so I implemented the normative table's code and fixed only the one contradicting sentence inside chapter 10. Chapter 40 and 0093 are left for Register every model rule with its context, placement, OCL statement and fixture #44's diff to carry whole.What the rebase changed
src/exists now, solint:boundariesstops skipping and enforces for the first time: all 17 rules live, 14 modules, 32 dependencies, no violations.That flips one ledger row.
RULE-043, generated artifacts are committed and CI fails on a regeneration diff, was pending because nothing generated anything yet. The intent gate is that first generator: it regeneratesspec/v1/schemas/service-intent.schema.jsonfrom the metamodel and fails on a diff. Pending drops 19 to 18.Two pending reasons that this branch made false were repaired in place rather than flipped, since nothing enforces either rule yet. Three more (
RULE-024,RULE-025,RULE-041) now have code to constrain and are satisfied by it, but nothing enforces them, so they stay pending rather than claim a proof they do not have. That is #30's call.REQ-015in the behaviour ledger, count to 15.RULE-061for the intent gate, count to 61. ADR0105. Fifteen gates indocs/architecture.md, seven of them sharing thecontractsjob.Ratchet
Statements 98.31 to 98.78, branches 92.43 to 94.16, functions 100, lines 98.19 to 98.68. Every module under
src/is at 100% statements, lines and functions.Verification
Eleven codes, each from a base fixture with one mutation so each isolates one defect. Two examples:
Breaking the
authworked example at the case chapter 10 names,auth-uilistening on 8080:Restored, and the gate is green again.
npm run verifyclean on the rebased tree: 92 ADR files, 159 link files, 15 ledger rows, 61 rules, 33 doc claims, no leaks, no dependency violations, 373 tests.actionlint1.7.7, the version CI pins, exit 0.