Tailoring Manifest¶
The Tailoring Manifest (ea.manifest.yaml) is the one org-level configuration file. It holds
the domains the architecture is organized into, the archetype in force (as provenance),
framework settings, method-governance policies, and two org overlays — relationships
(cross-pack edges) and renames (cross-pack id collisions). It deliberately does not hold
a method-selection list (the active set lives in definitions/ — packs.md) or any
inline acceptance flag (acceptance is a property of the confirmed write).
File & identity¶
- Path:
ea.manifest.yamlat the repository root; its presence marks the repo root. - Exactly one Manifest per repository, shared by all projects. Projects do not tailor independently.
- Part of the single source of truth; canonical YAML emission (repository.md).
Top-level shape¶
manifest:
schema_version: 1 # version of THIS manifest format
archetype: startup-light # provenance: which preset is in force (plain value; optional)
domains:
- { id: business, title: Business, types: [capability, principle] }
- { id: data, title: Data, types: [entity] }
relationships: [] # org relationship overlay (optional)
renames: [] # org cross-pack id rename overlay (optional)
settings: {} # framework-level configuration (extension point)
policies: {} # method-governance decisions (optional)
The top-level keys are a closed set: manifest, domains, relationships, renames,
settings, policies — canonical emission in that order. A packs/methods selection list
is not permitted — the schema rejects it; the active set lives in definitions/. No
accepted markers anywhere.
Domains¶
The architecture is organized into domains, defined here as org tailoring (never in a pack).
domains:
- id: business # the <domain> path segment
title: Business
description: Business-layer architecture.
types: [capability, principle] # the artifact types permitted in this domain
- An artifact at
<scope>/<domain>/<type>/<id>.mdis legal iff<domain>is declared and<type>is listed in that domain'stypes(and is an active type). - A type may appear in more than one domain — this is how the same type can sit in different domains across organizations.
- An active type listed in no domain cannot hold artifacts (no legal path); validation flags an
artifact whose
(domain, type)pair is not permitted.
Incremental domain assignment¶
Adoption writes domains wholesale from a preset, and method add extends the active type set
without touching this section — so a type added after adoption arrives unmapped. Rather than
force a preset clone, domains is editable in place through a guarded verb family:
ea manifest domain add <id> --types t1,t2[,…] [--title T] [--description D]— append a new domain (rejected with existing ids enumerated if already declared).ea manifest domain set-types <id> --types t1,t2[,…]— replace an existing domain'stypesdeclaratively (idempotent).ea manifest domain remove <id>— drop a domain.
Each is one guarded change writing only ea.manifest.yaml. --types entries are deduplicated
(order preserved) and every entry must be an active type — an unknown type is rejected with
the active types enumerated, so "domain types ⊆ active types" holds at edit time.
Relationship overlay (cross-pack edges)¶
A pack's relationships are its versioned baseline (they live in definitions/types/ and
drive materialization closure + validation). A pack cannot declare a relationship whose
target_type lives in another pack — but the org, having adopted two packs, often wants exactly
that (e.g. a UX pack's ui-flow linking to whatever outcome layer is in force), without
either pack hard-depending on the other. The manifest carries a strict, typed relationship
overlay:
relationships:
- { id: addresses, source_type: capability, target_type: risk, cardinality: "0..*" } # add (cross-pack)
- { id: governed-by, source_type: capability, cardinality: "0..*", override: true } # override a pack edge
- { id: mitigates, source_type: control, disable: true } # disable a pack edge
Three modes per entry:
- add —
{ id, source_type, target_type, cardinality }: a new edge;(source_type, id)must not already be a pack baseline edge.target_typemay be"*"(existence-only — the type-check is skipped, only resolution + cardinality apply). - override —
{ id, source_type, cardinality, target_type?, override: true }: replaces an existing pack edge's cardinality (and optionally its target) — e.g. relaxing1..*to0..*. - disable —
{ id, source_type, disable: true }: drops an existing pack edge entirely.overrideanddisableare mutually exclusive.
The overlay is merged onto the loaded types at model-load. Because the rules engine and
scaffold iterate a type's relationships, the full stack — scaffold stub & guidance,
cardinality, target-type, and reference_resolves — enforces the overlay with no rules-engine
change. It is a validation-layer concern only: it never changes what a pack materializes.
ea type show reports the effective edges with each edge's origin (pack/overlay).
Guarded verbs: ea manifest relationship add <id> --source <type> --target <type>
--cardinality <c> [--override] · disable <id> --source <type> · remove <id>
--source <type> (undoes the tailoring; pack baseline untouched). Each guards source/target
against the active types and rejects mode mismatches at edit time.
Rename overlay (cross-pack id collisions)¶
The materialized store keys items by bare id, and ids are only unique within a pack — so two
selected packs shipping the same id (nearly every pack has a refs-resolve rule) would collide
at materialization, which refuses. The clash is an engine concern the org resolves at mix
time — not an author tax forcing every pack to pre-namespace. The manifest carries a rename
overlay mapping a pack's published id to a local id:
renames:
- { pack: lean-ux, kind: rule, from: refs-resolve, to: lux-refs-resolve }
- v1 covers
kind: rule— the only kind where a clash is both common and engine-internal (a rule id never appears in artifact paths, references, domains, or the relationship overlay). Type/method renaming would ripple into on-disk layout and references and stays deferred; thekindenum is the extension point. - Applied at materialization: the rule materializes under its local
toid, the owning methods'requires.rulesare rewritten, the collision guard runs on post-rename ids, and provenance recordssource.original_id. References are unaffected. - Guarded verbs:
ea manifest rename add --pack <id> --kind rule --from <id> --to <id>(idempotent; when the pack is already active,--fromis validated against its rules; when not yet active it is accepted as forward-looking — essential, since the clashingmethod addhasn't succeeded yet) ·rename remove --pack <id> --kind rule --from <id>. - A dead entry — one whose
fromrenamed no rule the active set materialized (a typo'dpack/from, or a target rule that left the active set) — is warned, not enforced:validateemits a non-blockingrenames.danglingwarning per dead entry (mirroringpolicy.dangling). A forward-looking entry is dead until its pack is added and is warned too.
Archetype¶
An archetype is a named preset representing a weight of practice (e.g. startup-light →
enterprise-heavy). Its role is expand-to-explicit, not live inheritance:
- Adoption (
ea manifest adopt <preset>) is a guarded operation whose plan (a) materializes the preset's methods intodefinitions/and (b) writes the preset's domains/settings into this Manifest as explicit values. Nothing is inherited by reference; after adoption the Manifest fully describes the configuration on its own.manifest.archetype: <id>is recorded purely as provenance.archetypeis optional — an org may tailor without a preset. - Evolution (
ea migrate <target-preset>) computes the delta as a monotonic three-way merge of base (the recorded archetype provenance, resolved to its preset), current (this Manifest +definitions/), and target: - Collections (domains,
domain.types, method selections) merge by union — a user's additions are never dropped; re-materialization never deletes adefinitions/file. - Scalars (
domain.title/description, settings) merge three-way: undiverged values evolve to the target; deliberately diverged values are kept, surfaced as awarning(archetype.conflict) — never silently overwritten, never a block.
Because migration only ever adds, it has no invalid verdict — always a clean guarded write.
The plan carries a structured data.delta (added/changed/conflicts/preserved).
- ea migrate dispatches two orthogonal axes: archetype-preset migration (above; writes the
Manifest) vs. pack-version migration (ea migrate <pack>@<version>, definitions-only —
packs.md). Both reuse the same guarded-write substrate.
- Presets are data, sourced/distributed by the same ea pack fetch as packs (a presets/ dir in
a source rides along into the preset cache). The Manifest never stores the preset itself — only
its expanded result.
Carried org tailoring: policies, relationships, and renames are org-authored tailoring
layered after adoption — never shipped by a preset. Both manifest adopt and archetype
migrate carry all three forward verbatim (preserve-only, no three-way merge needed), so
governance and overlay edits survive adopting or evolving an archetype.
Method governance policies¶
policies is the optional, id-keyed section recording deliberate governance decisions about
methods. It lives in the Manifest — not on the materialized method doc — because definitions/
is a pure projection of (selections × packs) recomputed on every re-materialization; an
org-authored field there would be clobbered.
policies:
methods:
control-mapping:
status: # the lifecycle facet — qualifiers nest INSIDE it
value: deprecated # v0.1 enum: { deprecated }
reason: Superseded by control-mapping-v2. # optional
Each policies.methods.<id> is a map of facet objects; a facet's qualifiers nest inside it.
v0.1 defines exactly one facet, status (strict schema). Future facets (mandate, sunset) are
added as sibling objects under a bumped schema_version.
Deprecation — blocked-for-new, grandfathered¶
A method with status.value: deprecated is governed repository-wide:
- Footprint is by type. A deprecation governs a type only when that type becomes stranded — required by ≥1 active method, every one of which is deprecated. A type a non-deprecated method still produces stays freely usable.
- Grandfathering is by presence. The gate is enforced at
ea scaffold: scaffolding a stranded type into a scope that holds none is refused (status: invalid, exit 3, diagnosticmethod.deprecated, nothing written); a scope already holding one continues.validateis not the enforcement point — every artifact it sees is, by presence, already grandfathered. - Reversible and orthogonal.
ea method deprecate <id> [--reason …]/undeprecate <id>are guarded Manifest writes; the active set is unchanged (deprecation ≠ removal).promote/migrateare not gated — they relocate or re-type existing artifacts.
Settings¶
settings is the extension point for framework-level configuration that is neither a domain nor
an archetype. A schema-validated map; unknown keys are rejected (the schema grows deliberately
with schema_version). v1 defines no required settings; the section may be {} or absent.
Deliberate acceptance without inline flags¶
Acceptance is enforced by mechanism, not by data:
- A value reaches the committed Manifest only through a confirmed, dry-run-by-default write with a content-bound token — so every committed value is, by construction, deliberately accepted. There is no "silent default in the Manifest" state.
- During tailoring, the surface presents each default and its consequence before the write; the user's confirmation of the write is the acceptance.
- The record of acceptance is version-control history — not an inline
acceptedfield.
Validation¶
Structural (schema only): supported schema_version; only the closed top-level keys (no
selection list); unique id-pattern domain ids, non-empty titles, id-pattern types entries;
id-pattern archetype; well-formed overlay entries; settings/policies validate against their
schemas.
Semantic (needs definitions/):
- Every type in any domain's
typesis an active type. - Every artifact's
(domain, type)pair is permitted by the domains. - A
policies.methodsentry naming an inactive method is stale: a non-blockingwarning(policy.dangling), never an error — removing a method must not turn a green repo red. - A relationship-overlay entry naming an inactive type, or overriding/disabling a baseline edge
that no longer exists, is dangling: a non-blocking
warning(relationship.overlay_dangling), skipped — pack churn must not redden a green repo. An internally malformed entry (add colliding with a pack edge, missing/bad cardinality,override+disabletogether, duplicate(source_type, id)) is an error (relationship.overlay_invalid) and does not apply.
Relationship to definitions/¶
The Manifest and definitions/ are disjoint, non-overlapping parts of the source of truth:
definitions/ = the active methods/types/rules + pack@version provenance; the Manifest =
domains + archetype + settings + policies + overlays. Neither duplicates the other, so there is
no drift to reconcile — the cross-checks are "domain types ⊆ active types", "policy method ids ⊆
active methods", and "overlay types ⊆ active types". Adopting an archetype writes to both as
one reviewed, guarded change.