dodil/erp-method

node_modules/.cache/erp-method/v0.1.15/modules/module.template.yaml

# module.yaml — the skeleton for a new module source.
#
# Copy this to modules/<id>/module.yaml, fill it in, and run `npm run check:modules` (it enforces
# most of what follows). The page at /modules/<id>/ and the agent spec at /modules/<id>/spec.yaml
# are both generated from this one file — never write either by hand.
#
# Two rules decide whether this file is any good:
#   1. every story carries its acceptance criteria, because that is the test somebody writes later
#   2. everything a customer is likely to argue about is a config_row, not a constant in code
#
# The anatomy this must conform to: /modules/pattern.md

id: <kebab-case-id>                  # must match the directory name
name: <what a customer would call it — "Invoicing & collections", not "AR">
short: <one or two words for a breadcrumb>
version: 1
maturity: planned                    # planned | reference | tested | deployed — see /modules/pattern.md §14
summary: >-
  <one or two sentences, in the customer's language, saying what this covers and what it spares
  them. No platform vocabulary.>

facets:
  domain: <sales | finance | it-operations | people | legal | master-data | ...>
  industries: [any]                  # or the ones an overlay exists for
  customer_words:                    # how people ASK for it — an agent matches a request on these
    - "<a sentence a customer would actually say>"
  replaces: [<spreadsheets, the incumbent product, ...>]
  pillars: [sql]                     # add graph / vector only if the module genuinely needs them
  first_module_fit: <foundation | high | medium | low>
  depends_on:
    - module: business-partner
      why: <what breaks without it>
      required: <true | recommended | optional>
  feeds: [<modules that consume this one's work>]
  owns_master_data: [<shared entities this module is the sole writer of>]
  references_master_data: [<shared entities it reads but never writes>]

actors:
  - id: <role_id>
    name: <the job, in their words>
    does: <what they do here>
    sees: <everything | only what they own | a named subset>
    permissions: [<namespace>:<object>:<verb>]
  - id: system
    name: The system
    does: <scheduled and event-driven work>
    sees: everything, through its own service account
    permissions: []

# person = someone acts through a screen · system = it happens on its own · integration = it
# crosses into another module, which OWNS it. Ids are prefixed so they stay unambiguous in tests,
# commits and other modules' design files.
stories:
  - id: <PREFIX>-01
    kind: person
    actor: <role_id>
    story: As a <actor>, I want <capability>, so that <business reason>.
    given: <the starting state, concretely — with numbers where numbers matter>
    when: <the action>
    then: <the observable outcome, including what is REFUSED and what the refusal says>
    screen: <screen id>
    path: { process: <module>.<process key>, choices: { <Gateway_key>: <outcome key> } }   # the walk on the process view
    reference: "<implemented | partial — what is missing | not built>"
    customer_varies: "<optional — the part of this that differs per customer>"
  - id: <PREFIX>-10
    kind: system
    actor: system
    trigger: "<every 6 hours | when X happens | on read (derived)>"
    story: <what the system does, and why>
    given: <...>
    when: <...>
    then: <..., and what happens when it runs twice>
    reference: <...>
  - id: <PREFIX>-20
    kind: integration
    actor: <role_id | system>
    trigger: <when it fires>
    story: <what crosses the boundary>
    given: <...>
    when: <...>
    then: <...>
    owner: <the module that owns the write — never this one, if the data is theirs>
    reference: <...>

# The business processes this module implements, keyed on APQC PCF ids (L3-L4). The process view a
# customer sees and signs is generated from the providing brick's seed configuration. Element keys
# are frozen once published: a renamed concept gets a new key (pattern.md §6).
processes:
  - key: <process key, e.g. onboarding>
    pcf_id: <e.g. 10469>
    pcf_name: <the APQC name>
    reference_names: [<vendor end-to-end names as reference points only, e.g. "Hire to Retire">]
    brick: <the brick that provides it>

workflows:                            # the LIFECYCLE view: one entity's states (a drill-down, not the customer's view)
  - entity: <entity_id>
    states: [<...>]
    transitions:
      - { from: <state>, to: <state>, by: <actor>, gate: <condition>, requires: <field>, effect: <what else happens> }
    note: <anything derived rather than stored>

entities:
  - id: <entity_id>
    meaning: <in business terms — what it IS, not what it stores>
    natural_key: <the real-world identifier; there are no sequences on this platform>
    key_attributes: [<...>]
    relationships: [<"-> other_entity">]
    master_data: "<module-owned | SHARED — sole writer | references <module>>"
    reference_table: <prefix>_<plural>

master_data:
  owns: [<tables this module is the ONLY writer of>]
  references:
    - entity: <shared entity>
      owner: <module>
      how: <the join column>
      if_absent: <what the customer loses, stated plainly>
  warning: >-
    <the duplication this module would cause if the shared record is skipped>

jobs:                                 # one per system story. Ignite has no scheduler.
  - story: <PREFIX>-10
    trigger: <...>
    runs: <what>
    where_options: [derived on read, in the same request, always-on worker (--reserved 1 --max-replicas 1), external scheduler]
    idempotent: "<how — the key that makes a second run a no-op>"

seams:                                # one per integration story
  - story: <PREFIX>-20
    with: <module>
    owner: <module that owns the write>
    rule: "<X calls Y's route; it never writes Y's tables>"

screens:                              # derived from the person stories, not invented later
  - { id: <screen_id>, name: <what the tab says>, stories: [<PREFIX>-01], actors: [<role_id>] }
  - { id: settings, name: Settings, stories: [], actors: [<admin role>],
      note: "every config_row below lives here" }

permissions:
  namespace: <short form — may be shorter than the id (business-partner -> bp)>
  catalog:
    - <namespace>:<object>:<verb>
  row_visibility:
    rule: "<everyone sees everything is a legitimate answer — ask and record it>"
    enforced: <where>
    not_found_not_forbidden: true

# The test for each: could the customer's office manager change it on a settings screen?
config_rows:
  - { table: <table>, changes: "<what the customer edits, in their words>" }
in_code_not_rows:
  - "<the rules that must not become configuration, and why>"

tests:
  acceptance: "one per story id above, named for it"
  access:
    - "<per role: what they can see, what is refused>"
  invariants:
    - "<what must always hold — totals, idempotence, frozen records, currency separation>"

scale:
  reads: "the tables reader serves 16 concurrent reads; <what this module's wide reads are>"
  hot_paths: [<...>]
  workers: <what must run pinned warm, or "none">
  volumes: <the size this shape is good for, and what to do above it>

platform_constraints:                 # optional: where an open platform fault shapes the design
  - constraint: <...>
    today: <the workaround, and what to tell the customer>

customer_varies:                      # the Phase 2 questions, in the customer's language
  - question: <a question a business owner can answer>
    changes: <what in this spec it changes>
    # Give them something to REACT to. A business user can rarely recall a policy on demand and
    # can always correct one — so read these out rather than asking an open question and
    # waiting. Two kinds, never to be confused:
    standard: >-                       # optional. A NAMED authority they can look up and hold
      <a directive, accounting standard or statute, named precisely enough to find — e.g.
      "EU VAT Directive 2006/112/EC art. 226". If you cannot cite it, delete this key: calling
      convention a standard is how an implementation ends up defending a choice nobody made.>
    typical:                           # optional. Arrangements we have SEEN. No authority.
      <a kind of business>: <what businesses like that commonly do>
      <another kind>: <...>
      warning: <optional — the one that costs money to get wrong later>

variation_points:                     # customer_varies joined to the node it configures
  - node: "<Task_<process>_<key> or Gateway_<key>>"
    op: <on_off | narrow | re_time | re_lane | row_value>
    default: <the reference behaviour>
    depth: <1 | 2 | 3>
    guideline: "<warn - why the default is the best practice, with its standard or typical source>"   # or
    requirement: "<refuse - the foundation rule and its reason>"

reference:
  # The code this spec is built from: PUBLIC git repos, anonymous clone, one release tag each.
  # Fetch with `git clone --depth 1 --branch <tag> <https>`; read_first lists what to open first.
  # role: brick = this module's code; platform = the runtime every brick builds on (erp-platform);
  # ui = the screens' components (erp-ui). `npm run check:packages` clones each at its tag and
  # checks its schema files against the same rules as a package. Prefer repos over `packages`.
  repos:
    - name: <repo-name>                 # brick-<module>, erp-platform, erp-ui
      role: brick                       # brick | platform | ui
      https: https://git.dodil.io/dodil/<repo-name>.git
      ssh: git@git.dodil.io:dodil/<repo-name>.git
      tag: v0.1.0                       # a release tag, v<semver> - never a branch
      read_first: [README.md, AGENTS.md]
  packages:                             # the OLD code/ tarballs; leave empty once repos cover it
    - { id: <package-id>, what: "<what it contains>", url: /code/<package-id>/ }
  contracts: []
  build_guide: "</library/<post-id>>"   # or null
  known_gaps:
    - "<what the reference does NOT do — state it plainly; a hidden gap costs somebody a day>"