Skip to content

Typed architecture claims

Shape

Architecture claims as text files, reviewed by people, checked by shp in CI.

Shape in 73 seconds: in a writing app, a coding agent's share button would leak private drafts, and the Shape check on the pull request stops the merge. Narrated, with captions.

Architecture decisions often live only in people’s heads, chat threads, or outdated diagrams. Code review then depends on someone noticing that a pull request hard-deletes a document’s revision history, exposes private drafts on the public feed, or breaks a dependency rule the team already agreed.

Tests check behaviour and typecheckers check types. Neither is a natural place to state a durable decision such as “revisions are append-only” or “only the publisher may serve the story endpoint”, and to keep that decision visible and enforced as the code changes.

Shape is a small language for writing those decisions down as claims, and shp is the checker that accepts or rejects them. A claim is a declaration the author asserts, such as “Revision is append-only” or “RevisionLog.appendRevision appends revisions and does nothing else”.

Claims live in .shape files under shape/. Together the files form the Shape model: the checker loads every .shape file it finds there and checks them as one model. People, and optionally coding agents, write the claims. Reviewers read them in the pull request like any other change.

Shape judges only the declared Shape model. shp check reads .shape files and, when given, a changed-file list. It never opens or runs the application source that source and evidence refs name. It does not prove that the implementation matches the claims, and it does not replace tests, typechecks, or code review.

A passing check means the claims are coherent with each other. When CI also supplies a changed-file list, a pass also covers the change-set checks described under Where Shape sits in review. Whether the claims describe the code truthfully is decided in review.

PartKeywordsWhat it claimsRead more
Resources and traitsresource, traitThe data that matters, and the effects that are finally forbidden on itEffect Model
Componentscomponent, owns, grantsWhich component owns a resource, and which effects its functions may haveEffect Model
Function summariesfn, effects complete, effects unknown, source, evidenceWhat each source function does to resources, with references to the codeEffect Model
Relations and rulesrelation, ruleHow components and resources connect, and which providers, paths, or cycles are forbiddenRelations and Graph Rules
Change governanceimplementation, binding, attestWhich source paths need a model update when they change, and which files must change togetherKeep the Model Current
Design memoryrationale, memory, guards, change, reevaluationWhy a fragile target looks the way it does, and which changes to it need a recorded reviewDesign Memory
Modules and packsmodule, importNamespaces, and shared declarations vendored under shape/vendor/Domain Packs

Suppose a writing app keeps every document’s history so that edits can always be recovered. Each time the editor saves, it appends a revision through the revision log, and revisions must never be hard-deleted. Revision is an append-only resource. RevisionLog is the component that owns it; it may append and read revisions, and its two functions claim only those effects:

module history
resource Revision : AppendOnly
component RevisionLog {
owns Revision
grants Append<Revision>
grants Read<Revision>
fn appendRevision
effects complete {
Append<Revision>
}
fn listRevisions
effects complete {
Read<Revision>
}
}

AppendOnly comes from Shape’s built-in prelude. Because Revision carries it, hard delete, truncate, and drop are finally forbidden for that resource. Saved as shape/history.shape, this model passes, and shp check prints Shape check passed.

Later, a pull request adds a job that purges old revisions. Its author adds a grant and a function to RevisionLog:

grants HardDelete<Revision>
fn purgeOldRevisions
source ts("src/history/purge.ts#purgeOldRevisions")
effects complete {
HardDelete<Revision>
evidence ts("src/history/purge.ts#purgeOldRevisions")
}

shp check now fails with exit code 1:

error: forbidden effect
RevisionLog.purgeOldRevisions emits HardDelete<Revision>.
Revision has trait AppendOnly.
AppendOnly forbids final HardDelete<Revision>.
evidence: ts("src/history/purge.ts#purgeOldRevisions")
caused by:
- shape/history.shape: effect RevisionLog.purgeOldRevisions emits HardDelete<Revision>
- shape/history.shape: resource Revision : AppendOnly
- standard prelude: trait AppendOnly forbids final HardDelete<T>

The diagnostic names the claim that failed and the declarations that caused it. The grant does not help, because a final forbid on a resource’s trait wins over any grant.

The source and evidence refs point at the purge code, but the checker never opens that file. Reviewers follow the refs to check the claim against the code, and coverage uses their paths to match changed source files to the model. The Quickstart builds this model step by step and shows how to resolve the failure.

shp check reads only the authored .shape files and changed.txt and accepts or rejects them; the application source that evidence refs point at stays outside the checker, judged by tests, typechecks, and code review.

In a pull request that touches the architecture, three parties act on the same change:

  • The author changes the code and the .shape claims that describe it. When a source file the model governs changes but its architecture does not, the author records an attest no_shape_change with a reason instead. shp author, shp ast, or a coding agent can draft the update; a draft is a suggestion until a person reviews it.
  • Reviewers read the changed claims next to the code, follow the source and evidence refs, and read any rationale, memory, or reevaluation attached to guarded targets.
  • CI runs shp check --changed-files changed.txt. It checks the whole model, requires a Shape update or attestation in the same change for each changed governed source file, and enforces bindings such as a paired docs change. On failure, the diagnostic names the failing claim and its causes, and the author changes the code, the design decision, or the claim that was wrong.

Tests, typechecks, and code review keep running beside Shape.

  • Final forbids are final. Grants, rationale, memory, reevaluation, attestations, and imports cannot waive a forbid final. See Effect Model.
  • Unknown effects must be explicit. Write effects unknown while a function’s effects are not known, because an empty effects complete {} claims the function has none. Strict shp check rejects effects unknown in authored modules, so unresolved analysis fails the check.
  • Coverage checks that the model changed, not that it is right. A function source or effect evidence ref for the changed path satisfies coverage whenever its .shape file is part of the change, and so does a no_shape_change attestation that is new relative to the base model, whether or not the claim is accurate. See Keep the Model Current.
  • Guards fire only from change declarations. A guard requires a reevaluation when a change declaration modifies or removes its target; editing the target’s declaration in place in the Shape model triggers nothing. See Design Memory.
  • Hints and drafts are advisory. shp analyze, shp ast, and shp author inform the author; only shp check decides whether the model passes. shp analyze --shape-files exits 1 when it reports a warning, but it never changes the result of shp check. See Analyzer Hints.
You want toRead
Decide whether Shape fits your teamThis page, then Design Rationale
Adopt Shape in a repositoryQuickstart, Keep the Model Current, Run Shape in CI
Write and maintain claimsEffect Model, Relations and Graph Rules, Design Memory, Language Syntax
Look up a command or an errorCLI Reference, Diagnostics, Glossary
Contribute to ShapeCONTRIBUTING.md, then Checker Pipeline