111 โ€” Directory types declare their layout

Type: reference/types/adr.md ยท Status: accepted

Status: accepted Date: 2026-10-06 Amends: ADR 095: the layout, not the schema, owns membership; the schema keeps the manifest's instance metadata and hashes.

Amended by: ADR 114. A role may declare verifies, a relation kind the engine covers only by a judgment of the handed version; the sentence below that verifications are not members predates ADR 113. Amended 2026-10-10: the engine and validation vocabulary calls a directory-artifact instance an artifact, not a set: "artifact validation" below reads for "whole-set validation", and the standard handler is artifact_check. The type name agentic-system-analysis-set and the analysis types' "analysis set" prose keep the old word until a retained set is next regenerated and the type is relocated. Amended 2026-10-10: the memory member types are relocated to agentic-system-memory-report and agentic-system-memory-profile, so every member type shares the agentic-system- prefix (ADR 116). Archived sets keep the old type paths in their members' frontmatter: their bytes are pinned, so they stay frozen history and are not re-pinned. Amended 2026-10-10: an identity source may name run instead of a role: the member's listed fields must equal the run's values, its parameters and run-id. Draft validation inside an engine run checks them; validation outside a run, including at publication, has no run values and skips them. run is therefore not a role name, and a run-bound field is no relation, so the engine requires no judgment over it. The analysis binds the boundary's run-id and the memory report's source-identity this way (ADR 117).

Context

ADR 095 let a directory type's JSON Schema own membership. The schema could name files and require them, but it could not say how members relate: who supplies a member's identity, whose declarations a member may cite. Those relations lived in Python registered per type path, and the facts about which files make the analysis set were repeated in four modules and the schema.

The analysis set's declaration source, the boundary, was written at the run root outside the artifact. Code substituted it for the absent overview before synthesis, by path convention, in the workflow and in one type rule. Nothing in the type said the boundary belonged to the set.

Decision

This adopts the proposal "Directory types declare their layout", now archived, in full.

A directory type spec may carry layout in its frontmatter. A layout has membership (open or closed), roles and required. Each role names one direct child path, the document type expected there, identity (a list of sources, each a role and the fields this member repeats from it) and cites (the roles whose declarations its references resolve against). required lists the roles every instance has under always; by names a discriminating role and field and lists, per value, the roles an instance with that value requires and admits. When a layout declares a discriminator, a value's list is also the limit of what that instance admits.

The layout owns membership: a file is a member when it matches a role. The schema still receives {manifest, members} but constrains only the manifest. Hashes stay manifest metadata: a supplied hash binds, and a type that pins its members says so in a type rule, not in the layout.

The validator applies the layout generically: membership, expected types, requiredness and identity. A type rule computes what needs a grammar, such as record declarations and references, and reads citation scope from the layout. Every layout or rule finding names the role it belongs to, or no role when it concerns the artifact as a whole. ValidationRun.artifact_findings returns them; a caller that wants one member's findings filters by role, and may place a candidate's bytes at its role's path through content overrides.

Layout and type-rule checks run over the members present even when the schema or a member fails. An incomplete working instance therefore gets its absent required members as findings and its relations checked as far as they reach. A working instance may carry a type-only manifest from its first write; it fails artifact validation until it is finished, with no phase concept.

The analysis set declares a layout with seven roles. The boundary is a member, written at output/boundary.md as the run's first member, and is the identity source and the declarer of sources. The overview repeats the boundary's fields and disposition; its Boundary and evidence and Source register sections must equal the boundary's and declare nothing. The profile cites only the three analyst reports. Publication copies the boundary with the other members. Sets retained before this decision have no boundary and are regenerated by a fresh run, not migrated.

Considered alternatives

Layout in the schema file under a reserved keyword. It would hide a layout vocabulary inside a JSON Schema document whose validator ignores it.

Layout in a separate file named by the type spec. One more file and resolution step, with no separation the frontmatter does not already give while one directory type exists.

All relations in code keyed by role. A member-level lookup would still need the code to know what a role relates to; the layout would be the schema's filename properties renamed.

A constraint language. Rejected for the reasons in ADR 095.

Hashing as a layout property. Content identity is not structure, and a role-level hash flag would need a phase concept to let working instances validate.

Nested paths in one artifact. Deferred until a type needs a subdirectory of members. The one use found, reaching the boundary by moving the manifest to the run root, would draw the artifact around the run directory rather than the product.

Nested directory artifacts. Left aside, not designed. The analysis run shows the shape that may be wanted, a run directory that is itself an artifact holding its process files and, as a member, the product artifact it publishes. That is composition of artifacts, a different question from layout.

Manifest at the run root, or the boundary left outside with an overview fallback. The first makes process files tolerated non-members and reshapes the retained tree; the second keeps the declaration source outside the definition.

Consequences

A reader of a directory type spec sees what an instance contains and how its members relate. The analysis set validates its boundary agreement, scoped citations and completeness by disposition from the declaration, before synthesis and without the overview. The memory-profile type rule keeps only the profile's own content rules; its references resolve in the set rule. Code reads member paths from the layout through analysis_layout().

The analysis workflow asks about set relations only through directory validation of the working instance. An analyst, reconciliation or profile candidate is placed at its role's path without being written, and acceptance and self-check deliver that role's findings, marked [set] to distinguish them from workflow checks such as declaration prefixes, quotation anchors and run-state agreement. The round-close check keeps every finding of the roles that exist at round close, dropping absent members. Verifications and the synthesis are not members; their references resolve within what the overview may cite.

The retained set produced before this decision fails validation until a fresh run replaces it. Runs started before this decision, with their boundary at the run root, do not resume. Deterministic success establishes the declared structure, not analytical truth or semantic support.