Skip to content

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.yaml at 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>.md is legal iff <domain> is declared and <type> is listed in that domain's types (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's types declaratively (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_type may 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. relaxing 1..* to 0..*.
  • disable{ id, source_type, disable: true }: drops an existing pack edge entirely. override and disable are 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; the kind enum is the extension point.
  • Applied at materialization: the rule materializes under its local to id, the owning methods' requires.rules are rewritten, the collision guard runs on post-rename ids, and provenance records source.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, --from is validated against its rules; when not yet active it is accepted as forward-looking — essential, since the clashing method add hasn't succeeded yet) · rename remove --pack <id> --kind rule --from <id>.
  • A dead entry — one whose from renamed no rule the active set materialized (a typo'd pack/from, or a target rule that left the active set) — is warned, not enforced: validate emits a non-blocking renames.dangling warning per dead entry (mirroring policy.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-lightenterprise-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 into definitions/ 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. archetype is 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 a definitions/ file.
  • Scalars (domain.title/description, settings) merge three-way: undiverged values evolve to the target; deliberately diverged values are kept, surfaced as a warning (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, diagnostic method.deprecated, nothing written); a scope already holding one continues. validate is 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/migrate are 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 accepted field.

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 types is an active type.
  • Every artifact's (domain, type) pair is permitted by the domains.
  • A policies.methods entry naming an inactive method is stale: a non-blocking warning (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+disable together, 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.