Developing a module on DODIL — the lifecycle
For the agent (or person) building business software for someone. Read it once before the first conversation; come back to a phase when you reach it.
The rule that shapes everything else: phases 0–3 produce documents, not code, and the
customer signs off on them before phase 4. The reference modules exist so you never start
from a blank page — not so you can skip understanding this customer. Most of them already have
their code as bricks: public git repos on erp-platform that phase 4 composes and configures:
with rows first, a brick of the customer's own when rows cannot say it, and the customer's own copy
of a brick when nothing else will. The customer's signed process and change list are the intent; an
upgrade re-applies them.
How far a customer can change it: three depths, one kept foundation
Everything is customizable, including the code (seemo, 2026-10-18). Every change is classified as one depth, drawn on the customer's diagram and signed before it is built:
| Depth | What the customer gets | Mechanism | On an upgrade |
|---|---|---|---|
| 1 Configure | a diagram edit or a chat request becomes rows | the brick's own keep_* use cases | automatic: rows survive a version bump |
| 2 Extend | behaviour the brick does not have | a capability provider in the customer's own small brick (hooks: not built yet) | the capability contract is versioned |
| 3 Own | the brick itself, changed | dodil git repo fork into the customer's org | the agent re-applies the recorded intent on the new tag; the bench proves it |
The kept foundation is refused if broken, each part for a reason: stable identifiers (process and
step keys, APQC PCF ids, event types, use-case names: re-applying intent, mining and audit join on
them); the event-log format (not built yet); shared keys and data integrity (natural keys, cross-brick
keys, exact money, versions, idempotency: DataK3 enforces none of it, the write path does); the
security model (permission names, row visibility, gateway sign-in: it fails closed); and
erp-platform itself (one pinned runtime; copies drift).
Best practices are defaults with warnings, not blocks. A deviation from a standard: or
typical: default is shown to the customer with its warning and source, signed, and then built.
BPMN is the customer's view. It is generated from the spec and the live configuration, never hand-edited, and the bricks never execute it. Stories are shown on it as paths (scenarios), and the signed diagram is the contract through test, operate and change.
0 FRAME what are they asking for? is there already a system here?
1 DISCOVER the module catalog -> the reference module's spec
2 UNDERSTAND actors, stories, workflows, entities, master data, rules
3 DESIGN map it onto the bricks -> ERP_<MODULE>.md -> sign-off
4 BUILD compose the published bricks; generate only a missing module, as a brick
5 TEST a test per story, role, invariant + the generated probes, in process and live
6 PROVISION dev before prod; merge role catalogs
7 DEPLOY git push -> CI -> registry -> CD
8 VERIFY the app reaches its own data; a real person signs in; their agent connects
9 OPERATE change -> back to 2; next module -> back to 0
Modular in design, incremental in delivery, shared in data, one app by default.
Five rules that hold in every phase
- Interactive by default. Every decision the customer can make is a short question with choices; every change you make is shown before and after. Work silently only on what was already agreed.
- The repo remembers, not you. State lives in
ERP_<MODULE>.md(status, people, decisions, changes), not in your memory or the chat. Another agent — or another person — must be able to pick up from the file alone. See Starting a session. - Test copy first, live only on an explicit yes. The customer usually cannot tell which database they are changing. You always can, and you always say. See Environments.
- Show it on localhost. Whenever something can be seen, run it locally against the test copy and let the customer look before anything goes live.
- The signed BPMN drives every change; the code follows, never the reverse. The customer signs a
DIAGRAM (never the markdown alone), and every later change is redrawn with the changes marked,
confirmed in plain words, re-signed as a new numbered version, and only then built
(
dodil://erp/phase/change). Agents forget follow-ups, and the end user can't read code. Enforced from erp-platform 0.8:erp-compose plan/migrateand CI refuse an unsigned, pending or drifted module.
Starting a session
Before anything else, every session:
- Reads every
ERP_*.mdin the repo — the Status block first, then People, Change requests, and Decisions. - Works out who it is talking to. If it is not obvious, ask one light question: "Before we continue — who am I speaking with, and what's your part in the business?"
- Brings the file up to the current template (
/modules/template.md) if it is older: adds any missing Status, People, Environments or Change requests section in the template's form, filled from what the file and the repo already say. Content stays; only the shape changes. - Opens with a three-line where we are: what is live, what is being worked on, what is waiting on the customer. In that person's words.
When the person changes
Projects outlive conversations. The developer who signed off the design hands over to the owner; the owner delegates to an office manager. Signals: the vocabulary shifts ("schema" becomes "the list"), the questions change, or they say so.
- Recalibrate, don't restart. Ask the new person the two Phase 0 calibration questions — what their part in the business is, and whether they want to weigh in or have you decide — and record both under People, with the words they use. Do this before acting on their first request.
- Retell the state in their words. A business user gets "your quotes now lock once sent", not "status transitions guard the write path". Never carry the previous person's jargon forward.
- Earlier decisions stand unless the new person changes them — and a change is a change request (Phase 9), recorded with who asked.
- Authority is theirs to state. If the new person asks for something that reverses a signed-off decision, say so plainly and ask whether they can make that call or whether someone else should confirm.
0 · FRAME
Listen for capabilities, not labels
People say "an app", "a system", "a portal", "an ERP", "something to replace the spreadsheet".
The word tells you almost nothing. "An app that tracks our customers, sends invoices and handles
support tickets" is three modules whatever it is called. Listen for the nouns and verbs of their
business and map them to candidate modules using customer_words in the catalog.
Speak their word back. If they said "app", it is their app. Never make a customer learn "ERP", "module", "master data" or "entity" — those are your working words, not theirs.
Calibrate to the person — ask about the situation, not their competence
Two questions do the job, and both feed the design:
- "What do you use for this today?" — spreadsheets, an old tool, nothing. It shows their experience and what you are replacing.
- "Should I make the technical calls and explain them as I go, or would you like to weigh in?" — asks about involvement, not skill. "You decide" is a fine answer.
The answer changes how much you explain and how many options you offer. It never changes the quality of the design underneath.
Recommend a sequence, not a big bang
Name the first module — the one that hurts most or unlocks most (first_module_fit in the
catalog) — and show the rest as a roadmap in the customer's words.
| Decision | Default |
|---|---|
| Build order | one module at a time |
| Deployment | one app serving every brick (tier 1), plus the public app only if a brick issues links; split further only for a stated reason (independent scaling, a trust boundary) |
| Data | shared from day one — one bucket, one sign-in pool, one master-data register |
The real danger for a first-time builder is not a monolith. It is module one and module two each
inventing their own customer list. So if anything on the roadmap touches money (invoicing,
purchasing, accounting), the first module references a shared business partner instead of
keeping its own customer table. Check depends_on in the catalog.
Detect an existing system — don't ask, look
| Signal | Where |
|---|---|
| apps of the same system | dodil ignite app list / app get → labels.group |
| the sign-in already in use | app get → user_pool |
| where the data lives | app get → env.BUCKET |
| what each module decided | ERP_*.md at the root of the customer repo — the most reliable signal |
If a system exists: reuse its pool, bucket, master data and repo, and read every ERP_*.md
before designing anything.
Hold the conversation, don't hand over a questionnaire
Most customers are not system builders. For them, how you ask decides whether you learn anything.
- One topic per turn, one to three questions. Never a numbered list of six open questions — a non-technical customer answers the first two and the rest are lost.
- Offer choices with a recommended default, plus "something else". "Everyone sees everything (recommended for a team of five)" is answerable; "what visibility model do you need?" is not.
- Say back what you recorded after each answer, in one line, so a wrong assumption is caught now rather than at sign-off.
- Put anything the customer must read in the message itself, before the question. A multiple-choice prompt can cover the text around it; a summary placed there goes unseen.
- Review the design in parts (who uses it and the screens → how the main thing flows → what happens on its own → what comes later), each confirmed before the next. Offer a visual page when a part is long.
- Explain a decision you made for them in their terms, one sentence: "quotes expire by date, so nothing has to run overnight".
With an experienced builder the same rules loosen: batch related questions, use their vocabulary (a process person says "workflow" and "approval step"; a developer says "schema"), and skip what they have already decided. Notice the words they use — it is a better signal than asking.
Output: a short scope note back to the customer — what you heard, the modules it maps to, the recommended first module and order, and how involved they want to be.
1 · DISCOVER
/modules/index.json → the candidate module → /modules/<id>/spec.yaml.
If the catalog says spec: pending, there is no reference vocabulary for that module yet:
Phase 2 is done from scratch with the customer, in the house style of the modules that do have
specs (the entry's start_from names them). Write the customer's design file as usual — and if the
module is one the catalog expects to exist, write its spec too, from
/modules/module.template.yaml, so the next engagement starts
from something. The spec gives you the
reference actors, stories, workflows, entities, master data, jobs and seams — a vocabulary to
correct with the customer, and customer_varies: the questions that most often change the design.
Show the reference process. For each PCF id in scope, show the brick's reference process view
(the process BPMN a brick generates from its seed configuration, e.g. brick-hr's show_playbook_bpmn;
the blog's /modules/<id>/process.bpmn once published) and the landscape of what is covered. The
customer recognises it or says "not like that"; either answer is recorded.
The reference code is for phase 4. Do not open it to decide what to build — but note
whether the spec has any: reference.repos lists the bricks (clone URL, release tag,
read_first), and a spec with no repos means phase 4 generates that module. That changes the
estimate you give the customer, not the questions you ask them.
2 · UNDERSTAND THE BUSINESS
The only creative phase, and the whole value of the engagement.
Work through the reference spec with the customer. Ask the customer_varies questions in their
language, one or two at a time. For each reference story: keep, change, or drop. Then ask what is
missing. A novice will not volunteer system stories — ask "is there anything that should happen
on its own, on a schedule, or when something else happens?"
Propose, do not interrogate. Where a question carries standard: or typical:, read it out
and ask them to correct it. "Most firms like yours pay net 30, and the EU late-payment directive
assumes 30 unless you agree otherwise — is that you?" gets a truer answer, faster, than "what are
your payment terms?" — because people rarely recall a policy on demand and can always react to
one. An open question asked of someone who has never had to write the answer down produces a
guess, and the guess becomes a config row nobody revisits.
Two kinds of answer, and never blur them:
standard:is a named authority — a directive, an accounting standard, a statute. The customer can look it up and hold you to it. Say which one, so adopt it and deliberately differ from it are both available to them. Most will adopt; the ones who differ usually have a reason worth writing into the design.typical:is what we have seen. No survey, no authority, no implication that a customer doing otherwise is wrong. It is conversation, and presenting it as more than that is how an implementation ends up defending a choice nobody ever made.
Record the answer either way — "they keep the standard" is as much a decision as departing from it, and in six months nobody remembers which was which.
2a · Actors
Who uses it and what each may do and see. Becomes the role catalog.
2b · Stories — three kinds, each with acceptance criteria
- Person — someone does something through a screen.
CRM-06 · As the commercial director, I want quotes above our discount threshold routed to me, so that margin is protected. Given a 15% threshold and an 18% quote, when the rep submits it, then it waits in my queue and the rep cannot approve it.
- System — the system acts on its own, on a schedule or an event.
CRM-10 · Every 6 hours, the system re-scores open leads. Given a lead active an hour ago, when the run completes, then its score reflects it, and running it twice changes nothing.
- Integration — the work crosses into another module, which owns it.
CRM-20 · When a quote is accepted, a draft invoice appears in invoicing for the same customer and amount.
Prefix story ids with the module (CRM-06) so they stay unambiguous in tests, commits and other
modules' designs. The Given/When/Then is the phase 5 test.
Every story with a Given/When/Then gets a path: the choices it makes at each gateway of the
process view (path: { process: hr.onboarding, choices: { Gateway_worker_type: freelancer_company } }).
2c · Workflows
For each entity with a lifecycle: states, transitions, who may move it, and gates (thresholds, approvals, locks). This is where "every customer has their own process" is captured. Play every story's path on the diagram with the customer; an unwalked branch is a question.
2d · Entities
In business terms first: meaning, natural key, key attributes, relationships, owner. Decide keys now — DataK3 has no sequences.
2e · Master data
For every entity: shared (one module is the sole writer, all others reference it) or
module-owned (table prefixed with the module: crm_, gl_). A foreign key is accepted and
never enforced, so "sole writer" is the integrity mechanism — decide it, don't assume it.
2f · Rules and controls
Every business rule and where it is enforced — almost always a guarded write path, not a database constraint.
2g · Non-functional
Volumes, concurrency (16 concurrent reads per bucket), retention, residency, consent, and who operates it after go-live.
3 · DESIGN
| From phase 2 | Becomes |
|---|---|
| Entities | tables, natural PKs; prefix = module-owned, no prefix = shared |
| Relationships that need walking | a typed graph (filter on rel) |
| "Find similar" | a VECTOR column |
| Person stories | use cases (each one a route and an MCP tool) and screens — the UI is derived from the stories |
| System stories | jobs: trigger, what runs, where it runs — Ignite has no scheduler: an always-on worker app with its own loop, or an external scheduler calling a route |
| Integration stories | a call to the owning brick's use case (in process inside one app), never a write into its tables |
| Where the customer differs from the reference | a change item at one of three depths: configure (a row the brick already takes: stages, caps, playbooks, terms), extend (a capability in their own brick), own (a fork they keep, re-applied on upgrade). Each is drawn on their diagram and signed |
| Actors | permissions <module>:<object>:<verb> |
Write ERP_<MODULE>.md
One file per module at the root of the customer's repo (ERP_CRM.md, ERP_INVOICING.md). Another
module finds it with a glob. Template: /modules/template.md. Same sections in every file.
The filename says ERP because it is for tooling. The content uses the customer's words.
Sign-off — a hard stop
Show the customer the design in plain language — their actors, their stories, their screens, what runs on its own, what is deliberately left out — and wait for a yes before phase 4.
Sign-off is three artefacts: their BPMN (processes/<process>.bpmn, generated, recorded with its
sha256), the scenario list (id, path, expected end state), and the deviation list, each
warning accepted by name. Record them in the file's Processes, Scenarios and Deviations sections and
the yes in Decisions, with the date and who approved. An approval is only ever words the customer
wrote themselves, naming the version.
4 · BUILD
Build it to the pattern: /modules/pattern.md — the anatomy of a module and of a brick. It is short, and it is what keeps the fifth module looking like the first.
Compose; do not copy. For each module, spec.yaml → reference.repos names its bricks and
erp-platform, each with an https and ssh clone URL, a release tag and read_first:
git clone --branch v0.2.0 https://git.dodil.io/dodil/erp-brick-hr.git # the tag from the spec
# read README.md and AGENTS.md before writing anything
The customer's repo depends on them; it does not contain them:
customer-repo/
ERP_HR.md ERP_CRM.md the design files
requirements.txt erp-platform and each brick, pinned to its tag:
brick-hr @ git+https://git.dodil.io/dodil/erp-brick-hr.git@v0.2.0
compose.yaml name, version, tier: 1, bricks: [{name, brick, version}], capabilities
roles.yaml role -> permissions; the pool's catalog is seeded from it
scenarios/ the cross-brick stories, SC-<MODULE>-nn (pattern §9)
Dockerfile .dodil/deploy.yaml .github/workflows/ci.yml
erp-compose check refuses table and tool collisions and version drift; plan prints both apps'
environment; migrate applies every brick's schema plus the platform's tables, once per release;
serve is the one staff app — every brick under /<brick>, one /mcp — and serve --public the
public app, only when a brick issues links. Pin every brick explicitly: a brick does not always
declare the neighbours it seams to (brick-hr needs brick-business-partner beside it).
The customer's specifics are built from the signed change list, by depth. Depth 1 is rows,
written through the bricks' own use cases (a seed script calling the routes or tools, never INSERTs
into a brick's tables). Depth 2 is a capability in the customer's own brick, wired in compose.yaml.
Depth 3 is a fork — never an unrecorded patch. A fork is a signed depth-3 change: its intent
lives in processes/*.changes.yaml, the agent re-applies it on each new tag, and the bench proves
it. A difference every customer would want is a change to the brick upstream, released as a new tag.
A module no brick covers is generated as a new brick in the same shape —
erp-platform/BRICKS.md, started from erp-platform/templates/brick-example/: domain/ (rules, plain
Python), application/ (use cases; each is a route and an MCP tool), infrastructure/ (prefixed
tables, Aggregate repositories), three test layers and a scenario catalogue. It joins
compose.yaml like any other. Generate its business logic from ERP_<MODULE>.md, not from
another module's code. Put the story id in each use case's docstring.
Transition — modules with no bricks yet. A spec whose reference.repos is empty (gl, ar,
purchasing, the pending modules) has no brick to clone: generate a new brick in the brick shape
on erp-platform (domain / application / infrastructure, use cases, Aggregate repositories, the three
test layers), from ERP_<MODULE>.md. Any reference.packages entry is a worked example to read,
never a layer to copy. The brick replaces this path once it is published.
5 · TEST
Against a dev bucket:
-
Acceptance — one test per story, from its Given/When/Then. A story without a test is unverified.
-
Access — per role: right rows, others' rows are not found, excluded actions refused.
-
Invariants — totals net to zero, locked records refuse edits, re-imports are idempotent.
-
Generated probes —
erp_platform.scenariosderives them from the use-case declarations: every role × every use case × both doors (401 / exactly 403 / served, 404 for another person's record), every idempotent command replayed, extra fields refused (mass assignment). Hand-written negatives miss the use case added last month; generated ones cannot. -
Scenarios — the cross-brick stories (
SC-<MODULE>-nn), run in process (the whole composition on SQLite, seconds) in CI and live against the dev deploy. The crm-demo composition is the bar (2026-10-17): security probes 486/486, stories 28/28. -
Paths and conformance — each scenario's recorded trace equals its signed path, and the run's events replayed on the signed BPMN score conformance fitness 1.0, with a negative control (a run that skips a signed approval) scoring below it. Not built yet: the path walker and the shared event log. Until then, check each path by hand against the diagram and say so in the report.
Every rule in pattern.md §3–§4 has a probe or a scenario that fails when it is broken; a rule
with neither is a convention.
A composition ships its OWN test, in its own folder — the bricks' suites are not it. Running each
cloned brick's tests proves the bricks, not that your compose.yaml, roles and configuration rows
work together. Add tests/ beside compose.yaml that (1) boots the composition, (2) runs the bricks'
scenario catalogues against it (erp-scenarios run <brick>/tests/scenarios/catalogue.py with your
compose file), and (3) asserts the customer's configuration is in place (read it back through the
bricks' use cases: the chase cadence, the signer, the timezone they chose). Learned 2026-10-17: a lab
build passed 31/31 hidden scenarios yet shipped no test of its own, so nothing would have caught its
own configuration breaking later.
6 · PROVISION
Environments — a test copy and a live copy, always
| Test copy (dev) | Live | |
|---|---|---|
| Bucket | <customer>-dev | <customer> |
| App | <customer>-dev, or localhost against the dev bucket | <customer> |
| Sign-in pool | <customer>-dev with test users | <customer> with real staff |
| Data | seed and test data; safe to wipe | the business's real records |
| Who changes it | you, freely, as work progresses | you, only after the customer says "go live" |
- Local
.envpoints at the test copy, never at live. Live credentials exist only as secret references in.dodil/deploy.yaml. A local process that can write to live is how a customer's records get damaged by a test. - Say which one, every time. "I've added the field on your test copy"; "this will change your live app". The customer should never have to ask.
- Reading live is allowed, writing is not — reading real counts ("you have 214 sent quotes") makes impact feedback concrete.
- Preview on localhost first.
uvicorn main:app --reloadplus the UI dev server against the test copy is the fastest way to let the customer see a change. Give them the URL and what to click. Deploy the dev app only when localhost is not enough (sign-in, phone, someone else needs to look).
Creating things
-
Dev first, live when the first release is approved.
-
Joining an existing system: merge the role catalog, never replace it.
appid roles setreplaces the whole catalog; a module that writes only its own roles silently strips every other module's access.roles get→ merge →roles set --file. -
Tables:
erp-compose migratecreates each brick's prefixed tables and the platform's own; a generated brick adds only its prefixed tables and any new shared master data (with its owner recorded in the register). -
The staff app is deployed
--user-pool <pool>WITH--allow-unauthenticated: the gateway logs in optionally, strips forged identity headers and injects them when a session or bearer is present, and the app's sign-in gate (erp_platform_SignInGate) refuses every route without identity except/healthzand/.well-known/oauth-protected-resource— the RFC 9728 metadata that Claude.ai/ChatGPT connector discovery and the browser sign-in redirect need anonymously (crm-demo v4–v7, 2026-10-16/17: an anonymous/lead/leadsis a 401 from the app, the metadata a 200). Gateway-levelpublic_invoke=falseis right only for an app that serves no MCP and no browser sign-in redirect. -
Two service accounts when there is a public app. The staff app's:
k3.editorscoped to the bucket (+ignite.model-userif a brick calls Models). The public app's: its own,k3.editoronly, no Models role, no connector credential — it is the one on the internet with no sign-in. The public app has no pool,public_invoke=true, and never migrates (erp-compose planprints both environments). -
A pool with no
redirect_uriscannot log anybody in. Attachinguser_poolto an app is half the job: the gateway's front door only opens for a callback the pool allows, so every app in the pool needs its callback added to the allowlist —https://<app>-<org>-<port>.ignite.dodil.cloud/.dodil/auth/callbackand
appid settings setnow MERGES the flags you pass into the stored settings (CLI help, 2026-10-18; it used to replace the block), but the redirect list is a value of its own: read it first and pass every app's callback, not only the new one. Adding the fifth module means five URIs in that list, not one. A pool missing them answers{"error":"redirect_uri is not in the pool's allowlist (settings.redirect_uris)"}which arrives at the person trying to sign in, not at you.
-
Create at least one user, and say so. With
allow_signup: false— the right setting for a business system — nobody can create their own account. A pool with a correct allowlist and no users still admits no one. Invite them (no password; they set one through the reset flow). -
Bricks in one app call each other in process, as the caller — no service client. Only bricks deployed as separate apps (tier 2–3) need one per calling app: a dodil-appid service client with a role holding only what that call needs.
7 · DEPLOY
git push → CI → registry → CD from .dodil/deploy.yaml. There is no separate deploy command.
8 · VERIFY
- The app reaches its own data — ship a
POST /self-check that opens the bucket with the app's own credential;ignite invokecalls it. A public-URL 401 proves only the gateway is up. - The gateway strips forged identity headers.
- A real person signs in, once per role. You cannot do this — you never set passwords — so tell
the customer it is their step, and don't call the module done until it has happened.
This is the step most easily skipped, because the other two pass without it:
ignite invokeand a service-client token both go round the browser login entirely. Five apps were deployed, self-checked, and exercised end to end through their APIs here before anyone discovered the pool had noredirect_urisand no users — the login had never once been tried. If you cannot hand someone a URL they can actually sign into, the module is not verified. - Their agent connects, as them. A person adding the app to Claude.ai or ChatGPT signs in
(
authorization_code+ PKCE) and sees exactly their own rows. Claude.ai registers itself (DCR) and arrives pending: it can authorize nobody until you approve it —dodil appid client list <pool>, thendodil appid client approve <pool> <id>(appid 0.14, 2026-10-16). A machine (a job, Claude Code) uses a service client instead; never hand a person's agent a service token. - The scenarios pass live against the deploy (
erp-scenarios run … --live). The live runner refuses a bucket not stampeddemounless given--allow, and never injects faults.
9 · OPERATE AND EVOLVE
Change requests — a conversation, then autonomous execution
Customers ask for changes in passing: "can we add a site-visit step before quoting?", "office should not see margins", "a customer can have two VAT numbers". Most touch an entity or a workflow, and most customers cannot tell what that implies. The job is to make the implication visible before anything changes, then do all of it without further hand-holding.
1 · Understand — interactively. Restate it in their words and ask one to three questions with choices. Probe the edges they will not think of:
- What about the ones that already exist? (existing records)
- Who is allowed to do this? (roles)
- What should happen automatically? (system stories)
- Does anything else depend on it? (other modules, reports, the PDF, imports)
2 · Locate. By element id on the signed diagram, then from ERP_<MODULE>.md: which stories,
workflow transitions, entities, screens, jobs and seams are touched. Read live counts where it helps.
3 · Show the impact — plain language, before any work. Send it as an ordinary chat message of its own, then ask for the go-ahead. The approval question may only refer to what the customer has already been shown: never "as described above" unless the description was actually sent, and never impact that lives only in your notes or the CR you are about to write. Get the real counts (read-only) before this message, not after the yes. This shape:
What you asked for: a Site visit step between Enquiry and Quoted.
What changes for your team
- The pipeline board gets a new column, Site visit.
- A deal can't be quoted until the visit is logged. (new rule — you asked for this)
What happens to what you already have
- Your 37 open deals in Enquiry stay where they are.
- Your 12 deals already in Quoted are not moved back.
What doesn't change: quotes, prices, the PDF, reports.
How it goes live: first on your test copy — I'll show you on localhost — then live when you say so. Nothing is deleted; undoing it means removing the column again.
Shall I go ahead?
Put the diagram before and after beside this message (added, removed and changed elements by key), so the customer sees the change where they signed the process.
Name the migration honestly: each brick's change is a versioned migration (pattern.md §11), with
its counts read from the test copy. On this platform ALTER TABLE … ADD COLUMN is the only cheap change
— and it currently unsettles reads on that table for minutes afterwards, so it happens on the test
copy first and at a quiet hour on the live one. A rename or type change is add new → backfill →
switch reads → drop old later; nothing is dropped in the same release that stops using it. If existing records need a value, say which value and why.
4 · Record. On a yes: add CR-<n> under Change requests (date, who asked, who approved,
summary, element ids), and amend the stories, workflows, entities and deviations it touches. Record
it as a new signed process version (v2, the previous hash kept as supersedes). The design
file changes first, the code second — so it never describes a system that no longer exists.
5 · Execute — autonomously. No further questions unless something contradicts what was agreed: update code, add or amend the story tests, migrate the test copy, run the whole suite, start localhost, and tell the customer what to look at.
6 · Go live — on their word. After they have seen it: export or snapshot the affected live tables, migrate live, deploy, run the self-check, and report back in one short message — what changed, how many records were touched, and how to undo it. Mark the CR live.
A change request that turns out to be a new capability ("can it also send invoices?") is not a change — it is the next module, and goes back to Phase 0.
Also
- Rollback:
ignite version rollbackreturns the app to an earlier deployed version; the data is not versioned with it. That is why step 6 exports first. - Recurring work — whatever Phase 2 named, and where it runs.
- The next module → back to Phase 0, as "joining an existing system": read every
ERP_*.mdfirst.