Skip to content

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.
  • fields validates the type-specific front-matter. Because envelope fields and relationship fields live alongside type fields, fields MUST NOT set additionalProperties: false at its top level; the core validates the three key-groups as a composed union.
  • Reserved names. The envelope names domain/type/id/name are reserved — a field or relationship id must not shadow them. ea show adds two structural keys to an artifact's read-side response, scope (where it resolved) and body (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's data.scope/data.body are always the resolution scope and the body, never a field value.
  • relationships[] declare typed edges. A relationship's id is 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 by ea type show and scaffold guidance), not a core-enforced gate: commit neither 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_types MUST 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's requires.rules.
  • steps are guidance the agent reads to know what to author and in what order; the core does not enforce step order. produces ties a step to a type. guide is the scannable headline; detail is the depth an agent can learn the method from — surfaced by ea method show, while list surfaces show only guide.

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:

  1. Resolve each selected pack@version (offline, against the local cache/search path).
  2. Close the dependencies: union of requires.artifact_types + requires.rules over selected methods, plus the relationships those types declare and their inline constraints.
  3. Validate closure completeness: every relationship target_type referenced by an included type is itself in the closure; every rule id resolves; no unknown kinds.
  4. Materialize into definitions/ (one file per item, each recording source: { 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.yaml is 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 add that fixes it. The manifest's rename overlay (manifest.md) maps a pack's published id to a local id at materialization (v1 covers kind: rule — the common engine-internal clash, e.g. two packs' refs-resolve); the rule materializes under its local id and its methods' requires.rules are 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@version is immutable. Multiple versions coexist on the pack path at <base>/<pack-id>/<version>/pack.yaml (the legacy single-version layout <base>/<pack-id>/pack.yaml is honored; its header version must 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; validate independently 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; every pack.yaml found is validated and copied to <cache>/<id>/<version>/pack.yaml. Any presets/<id>.yaml the 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 after EA_PACK_PATH (explicit dev path wins). Selection, materialization, and migrate then 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, no latest float. Choosing a newer version is the guarded ea 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 with errors: []; structural errors → invalid/exit 3 with sorted data.errors; unreadable/malformed → exit 2.
  • ea pack new <id> (guarded write; needs no EA repo): scaffolds a minimal, schema-valid pack.yaml skeleton — an illustrative, self-consistent type + rules + method with # TODO comments — at packs/<id>/<version>/ (override with --path; --version defaults 0.1.0). The skeleton passes pack validate with 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:

  1. Enter — from scratch, or derive from an existing pack (ea pack show, adapt under a new id/version).
  2. Ideate — brainstorm purpose, practitioners, core concepts into a lightweight pack brief before touching YAML.
  3. Model types — concepts → artifact types (fields + descriptions, relationships, body.sections).
  4. Build methods — ordered steps[], each produces-ing a type with rich detail, complete requires — the existing schema, authored well.
  5. Wire rules — from the closed vocabulary, learned from the core at runtime, not memory.
  6. Review + validateea pack validate is the mandatory structural gate; on top, an advisory pack review (non-empty descriptions; no orphan types; produced types reachable; body.sections present; 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).