Method Packs¶
A Method Pack is the data that tells the framework what an architecture is made of:
artifact types, the relationships between them, the rules that validate them, and the
methods (practices) that produce them. The core validates packs and evaluates their rules; it
hardcodes none of their content. This document defines the pack authoring format, the fixed
constraint vocabulary the core evaluates, how selecting methods materializes the active subset,
versioning/migration, distribution, and the authoring surface (ea pack new / pack validate +
the ea-methodologist skill).
Pack identity & file¶
A pack is authored as a single pack.yaml. The repository does not store whole packs;
pack.yaml is the upstream/distribution unit — the repo stores only the materialized active
subset (see below).
pack:
id: core-ea # id pattern; unique per pack
version: 1.0.0 # SemVer; a published version is immutable
schema_version: 1 # version of THIS pack format; the core rejects unsupported ones
title: Core Enterprise Architecture
description: >
Minimal capability/principle method library.
Artifact types¶
A flat list under artifact_types. Each type is domain-agnostic — domains are assigned by
the manifest (manifest.md), never declared here.
artifact_types:
- id: capability # unique within the pack; becomes the <type> path segment
title: Business Capability
description: A stable thing the business can do.
fields: # JSON Schema (Draft 2020-12) for the type's own front-matter
type: object # fields (beyond the envelope domain/type/id/name)
required: [owner]
properties:
owner: { type: string }
maturity: { type: string, enum: [initial, managed, optimized] }
relationships:
- id: governed-by # becomes a front-matter field on `capability` artifacts
title: Governed by
target_type: principle # string, or a list of allowed type ids
cardinality: "1..*"
body: # OPTIONAL advisory body structure — guidance, not enforced
sections:
- id: statement
heading: Statement # the human heading the agent uses (## Statement)
guidance: One sentence stating what the capability is, in business terms.
- id: rationale
heading: Rationale
guidance: Why this capability matters and the value it protects or creates.
fieldsvalidates the type-specific front-matter. Because envelope fields and relationship fields live alongside type fields,fieldsMUST NOT setadditionalProperties: falseat its top level; the core validates the three key-groups as a composed union.- Reserved names. The envelope names
domain/type/id/nameare reserved — a field or relationshipidmust not shadow them.ea showadds two structural keys to an artifact's read-side response,scope(where it resolved) andbody(the verbatim markdown), so those two names are reserved for a pack field/relationship id as well. This is advisory today (no shipped pack collides) and may become a mechanical check in a future release. Should a front-matter key collide anyway, the structural keys win deterministically —show'sdata.scope/data.bodyare always the resolution scope and the body, never a field value. relationships[]declare typed edges. A relationship'sidis the front-matter field name carrying the reference target(s) on artifacts of this type.body.sections[](optional) declares the advisory prose structure of the artifact's body — ordered{id, heading, guidance}. It is guidance the agent reads to compose a well-structured body (surfaced byea type showandscaffoldguidance), not a core-enforced gate:commitneither requires the declared sections nor rejects extra ones.
Scaffolding — no templates. The deterministic scaffold op generates a new-artifact stub from
the type alone: envelope + field + relationship keys, JSON Schema defaults filled, field
descriptions surfaced as guidance, body.sections surfaced as advisory body guidance. A field
is stubbed only when it is required (or carries a default) — optional fields without a
default are omitted from the skeleton (they stay discoverable via guidance), so a typed-empty
placeholder can never violate the field schema at commit. The stub is front-matter only with an
empty body — the body is authored later at commit (judgment). There is no separate template
tree: enrich a stub by adding default/description/body.sections to the type.
Rules — the closed constraint vocabulary¶
Validation rules are data evaluated by a fixed, versioned set of constraint kinds
implemented in ea-core. There is no expression language. Constraints appear in two places,
both compiling to the same kinds:
- Inline on type/relationship declarations — field shape (JSON Schema) and relationship
cardinality/target_type. rules:list — graph / cross-artifact constraints:
rules:
- { id: caps-unique, kind: unique, applies_to: capability, params: { field: id } }
- { id: principle-used, kind: must_be_referenced, applies_to: principle, params: { via: governed-by } }
- { id: refs-resolve, kind: reference_resolves, applies_to: "*" }
- { id: decomp-acyclic, kind: acyclic, applies_to: capability, params: { relationship: decomposes-into } }
applies_to is a type id or "*". Cardinality notation: "min..max", max may be *;
shorthand "1" ≡ "1..1".
v1 constraint kinds¶
| Kind | Declared | Params | Holds when… |
|---|---|---|---|
| (field shape) | type fields (JSON Schema) |
— | the type-specific front-matter validates against the schema |
relationship_cardinality |
relationship cardinality |
{relationship, min, max} |
the count of that relationship's targets is within [min, max] |
relationship_target_type |
relationship target_type |
{relationship, target_types} |
every target is one of the allowed types |
reference_resolves |
rules |
{relationship?} (default *) |
every reference target resolves to an existing artifact (via the repository.md resolver — layering violations are reported here) |
unique |
rules |
{field, across?: scope\|repo} |
the field value is unique among applies_to artifacts within the span (default scope) |
must_be_referenced |
rules |
{via?: relationship, from_type?} |
the artifact is the target of ≥1 inbound reference (optionally of a given relationship/source type) |
acyclic |
rules |
{relationship} |
the relationship's directed graph over applies_to artifacts has no cycle |
The vocabulary is closed: a pack naming an unknown kind is rejected by pack validation.
Extending it is a deliberate core release, versioned via schema_version.
Evaluation determinism: artifacts in stable order (by path), rules by id; each violation is
one structured diagnostic ordered by (scope, artifact path, rule id); evaluation reads only the
repository + active definitions — no clock, no RNG.
Relationships ↔ artifact front-matter¶
A type's relationship id is a front-matter field on artifacts of that type; its value is a
reference target (or list) in the shape fixed in repository.md:
---
domain: business
type: capability
id: billing
name: Billing
owner: finance
governed-by: # relationship 'governed-by' (cardinality 1..*)
- { scope: org, type: principle, id: data-locality }
realized-by: [] # cardinality 0..* — empty allowed
---
Billing handles invoicing and dunning.
A relationship with max cardinality 1 may be written as a single object; the core normalizes to
a list internally. A type's relationships here are its versioned baseline — the org may layer
a repo-level relationship overlay on top (add cross-pack edges, override or disable a baseline
edge) in the Tailoring Manifest (manifest.md); the overlay merges onto the loaded
types at model-load and is enforced through this same machinery.
Methods¶
A flat list under methods. A method is a practice plus its dependency declaration — ordered
declarative data, not an interactive script.
methods:
- id: capability-mapping # the unit of selection
title: Capability Mapping
summary: Build and govern the business capability map.
requires: # dependency declaration → drives materialization
artifact_types: [capability, principle] # MUST list every type the method uses,
rules: [caps-unique, refs-resolve] # including relationship targets
steps: # ORDERED agent guidance — NOT a core-enforced gate
- guide: Capture each top-level business capability as a `capability`.
produces: capability
detail: > # OPTIONAL multi-line how-to / rationale
List the things the business can do at the top level, independent of org structure or
technology. Aim for stable nouns ("Order Management"), not projects or systems.
- guide: State the principle(s) that govern each capability.
produces: principle
requires.artifact_typesMUST be explicit and complete: every type the method authors or targets via a relationship. Materialization validates the closure; otherwise the pack is rejected. A method's rules materialize only if listed in some method'srequires.rules.stepsare guidance the agent reads to know what to author and in what order; the core does not enforce step order.producesties a step to a type.guideis the scannable headline;detailis the depth an agent can learn the method from — surfaced byea method show, while list surfaces show onlyguide.
Selection → materialization¶
Selecting a method is a guarded operation taking pack@version#method. The core computes and
writes the active subset into definitions/, which is then authoritative:
- Resolve each selected
pack@version(offline, against the local cache/search path). - Close the dependencies: union of
requires.artifact_types+requires.rulesover selected methods, plus the relationships those types declare and their inline constraints. - Validate closure completeness: every relationship
target_typereferenced by an included type is itself in the closure; every rule id resolves; no unknown kinds. - Materialize into
definitions/(one file per item, each recordingsource: { pack, version }provenance):
definitions/
methods/<method-id>.yaml
types/<type-id>.yaml
rules/<rule-id>.yaml
index.yaml # integrity lock — each active pack@version + content digest
index.yamlis the integrity lock:{ schema_version, packs: [ { pack, version, digest } ] },digest= sha256 over the resolved pack's canonical key-sorted JSON. Re-materializing a pinned version whose resolved content drifts from the locked digest is refused — a published version is immutable, so changed content is corruption, not an upgrade.- Cross-pack id collision. The store keys items by bare id while ids are only unique
within a pack. If two different packs in the selection claim the same id, materialization is
refused as a usage error (no silent last-wins) — and the message names the exact
ea manifest rename addthat fixes it. The manifest's rename overlay (manifest.md) maps a pack's published id to a local id at materialization (v1 coverskind: rule— the common engine-internal clash, e.g. two packs'refs-resolve); the rule materializes under its local id and its methods'requires.rulesare rewritten to match. Type/method renaming and per-pack store namespaces stay deferred. - The store is exactly the closure — no unused items. Writing it is a guarded, dry-run-by-default write. Note: a required cross-domain edge pulls its target type into the closure transitively, which can widen the footprint — harmless, but visible in compose-heavy packs.
Versioning & migration¶
- Packs are SemVer; a published
pack@versionis immutable. Multiple versions coexist on the pack path at<base>/<pack-id>/<version>/pack.yaml(the legacy single-version layout<base>/<pack-id>/pack.yamlis honored; its headerversionmust match). - A version is pinned only in
definitions/(provenance + index lock); the Manifest holds no selection list. - Bumping a pinned version is
ea migrate <pack>@<version>— deliberate, guarded, dry-run-by-default. It re-pins every active method of that pack (other packs grandfathered), requires the target version to resolve, re-materializes the closure, and re-types already-authored artifacts by re-validating them against the new types/rules. - Re-typing is definitions-only. The core never rewrites an artifact body. A bump that
invalidates an artifact reports the incompatibility as a non-blocking
warning(migrate.incompatibility) on the plan;validateindependently reports it as an error afterward, for the author to resolve deliberately.
Distribution & fetch¶
There is no hosted registry service. Distribution rides git and files:
- A source is a git remote or a local directory.
ea pack fetch <source> [pack@version …]brings packs into a local cache; everypack.yamlfound is validated and copied to<cache>/<id>/<version>/pack.yaml. Anypresets/<id>.yamlthe source ships ride along into the preset cache. - Fetch is the only online step. The cache (
EA_PACK_CACHE, default~/.cache/ea/packs) is auto-appended to the pack path afterEA_PACK_PATH(explicit dev path wins). Selection, materialization, andmigratethen resolve offline. Because the repo materializes its active subset, the cache is a build-time artifact — a clone of the repo validates with no cache present. - Fetch is not a guarded repository write — it populates a user-level cache (like
git fetch), needs no repo and no--confirm, and is write-once/idempotent. - Resolution is pin-native. A selection is an exact immutable
pack@version— no version range, nolatestfloat. Choosing a newer version is the guardedea migrate.
Pack validation¶
ea-core validates a pack.yaml against the Method Pack JSON Schema plus structural checks,
with a deterministic, ordered error report: supported schema_version; valid SemVer; all ids
match the pattern and are unique in their scope; every relationship target_type names a pack
type; every rule kind is in the closed vocabulary with well-formed params; every cardinality
parses; every method's requires resolves and its closure is complete; each type's fields is
itself valid JSON Schema. Pack validation is pure — no repo, no manifest needed.
Surfaced on the CLI as:
ea pack validate <path>(read verb):valid/exit 0 witherrors: []; structural errors →invalid/exit 3 with sorteddata.errors; unreadable/malformed → exit 2.ea pack new <id>(guarded write; needs no EA repo): scaffolds a minimal, schema-validpack.yamlskeleton — an illustrative, self-consistent type + rules + method with# TODOcomments — atpacks/<id>/<version>/(override with--path;--versiondefaults0.1.0). The skeleton passespack validatewith zero edits and is byte-stable. Existing target / bad id / bad version → exit 2.
Authoring surface: the ea-methodologist skill¶
The judgment layer for authoring a pack (the inverse of ea-architect — see
surfaces.md): eliciting a methodology and modelling it into the schema. One linear
flow, not a capability menu:
- Enter — from scratch, or derive from an existing pack (
ea pack show, adapt under a new id/version). - Ideate — brainstorm purpose, practitioners, core concepts into a lightweight pack brief before touching YAML.
- Model types — concepts → artifact types (fields + descriptions, relationships,
body.sections). - Build methods — ordered
steps[], eachproduces-ing a type with richdetail, completerequires— the existing schema, authored well. - Wire rules — from the closed vocabulary, learned from the core at runtime, not memory.
- Review + validate —
ea pack validateis the mandatory structural gate; on top, an advisory pack review (non-empty descriptions; no orphan types; produced types reachable;body.sectionspresent; composes into a throwaway manifest green). The review recommends; the author decides. Validity stays the core's; taste stays the skill's.
Boundaries that differ from ea-architect: the methodologist may draft pack YAML directly —
a pack is reviewed, versioned source authored once, not live operational state — but it never
declares a pack valid by inspection, and it holds no hardcoded schema/vocabulary. Guarded
pack-mutation verbs (pack add-type etc.), a core pack lint, preset authoring, and
extract-from-example-artifacts are deliberately not built (deferred pending demonstrated need).