Skip to content

Commit ffbaafe

Browse files
Yaraslautclaude
andcommitted
crm: state what the rung actually ships, and what it does not
The README and LADDER.md described crm as "7a under construction". Both halves are in fact built, server-side, and three things the README asks for are not there -- which the documents did not say, so a reader had to discover them. - crm/README.md gains a "What is not built" section naming all three with the reason each was left: no client at all (crm is the only built rung with neither `gui/` nor `gui_wasm/`, which also leaves the explicit-submit form mode and the quote-lines child-table control unbuilt); lists ship unpaginated, so the cursor-stability test the strain points call for does not exist; and the deleted-custom-field lifecycle has one of its three named arrival paths under test, with the offline path structurally unreachable today and journal replay simply untested. - The definition of done is now annotated bullet by bullet -- one met, one partly met, one not met -- instead of reading as a list of intentions. - LADDER.md's two crm passages say "built server-side, no client" and point at that section, rather than "7a under construction". Documentation only; no behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6k1MQAd5wVAU1tac8qFaY
1 parent 14a4ae8 commit ffbaafe

2 files changed

Lines changed: 92 additions & 23 deletions

File tree

examples/LADDER.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ finished deliverables (requirements studies whose sharpest content the
1515
spikes convert into CI at a fraction of construction cost); building any of
1616
them is a separate decision taken *after* rung 4 with the
1717
[finding pipeline](FINDINGS.md) scoreboard in hand — **crm (rung 7) was
18-
green-lit for 7a construction on 2026-08-28** by direct decision rather than
18+
green-lit for construction on 2026-08-28** by direct decision rather than
1919
waiting on that scoreboard review, once its own defining framework question
20-
was independently answered by the extension-bag spike (below). Ledger
21-
(rung 5) remains the strongest *unbuilt* candidate — the only other annex
22-
rung with a genuinely app-shaped core; forge's framework content still ships
23-
as its load script against synthetic models. The program's
20+
was independently answered by the extension-bag spike (below). Both halves
21+
(7a and 7b) are now **built server-side, with no client** — see
22+
[`crm/README.md`](crm/README.md)'s "What is not built" for that gap and two
23+
smaller ones. Ledger (rung 5) remains the strongest *unbuilt* candidate —
24+
the only other annex rung with a genuinely app-shaped core; forge's
25+
framework content still ships as its load script against synthetic models.
26+
The program's
2427
product is **findings fixed, not apps shipped** — see
2528
[`FINDINGS.md`](FINDINGS.md) for what counts, triage, the fix budget, exit
2629
criteria, and the demotion policy.
@@ -83,10 +86,10 @@ later rungs consume earlier answers (5 reuses 4's cascade-journaling answer,
8386
| 8* | [`forge`](forge) | [Gogs](https://github.com/gogs/gogs), [Gitea/Forgejo](https://github.com/go-gitea/gitea), GitLab architecture | Everything at once: orgs/permissions, notifications at scale, webhooks, out-of-protocol sidecars |
8487

8588
\* = design annex: README is the deliverable; construction is a post-rung-4
86-
decision (ledger under construction; lims built; forge → load script; crm 7a
87-
under construction — its own defining framework question was independently
88-
resolved by the extension-bag spike, so it carries no `*` here even though
89-
7b's go/no-go gate is still separate).
89+
decision (ledger under construction; lims built; forge → load script; crm
90+
built server-side, no client — its own defining framework question was
91+
independently resolved by the extension-bag spike, which is why it carries
92+
no `*` here; 7b's go/no-go gate was passed on that answer).
9093

9194
## Cross-cutting stress map
9295

examples/crm/README.md

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
# crm — rung 7 of the [application ladder](../LADDER.md)
22

3-
**Status: 7a under construction** (green-lit 2026-08-28 by direct decision,
4-
ahead of a formal ladder-wide findings-scoreboard review — see
5-
[`../LADDER.md`](../LADDER.md) for the program's general post-rung-4 gate).
6-
This README remains the design record; the rung's defining framework
7-
question (runtime custom fields) was already answered by the standalone
8-
**extension-bag spike** (complete — see
3+
**Status: server-side complete, no client.** Build order steps 1–10 and
4+
7b's three follow-on items are built and tested (`src/`, `include/`,
5+
`tests/`). crm is the only built rung with no `gui/` and no `gui_wasm/`, so the
6+
parts of the definition of done that name a rendering client are not met.
7+
Two smaller items also fall short of what this README asks for: lists ship
8+
unpaginated, and the deleted-field lifecycle has one of its three arrival
9+
paths under test. All three are set out in
10+
[What is not built](#what-is-not-built), and the definition of done below is
11+
annotated bullet by bullet.
12+
13+
Construction was green-lit 2026-08-28 by direct decision, ahead of a formal
14+
ladder-wide findings-scoreboard review — see [`../LADDER.md`](../LADDER.md)
15+
for the program's general post-rung-4 gate. This README remains the design
16+
record; the rung's defining framework question (runtime custom fields) was
17+
already answered by the standalone **extension-bag spike** (complete — see
918
[`EXTENSION-BAG-SPIKE.md`](EXTENSION-BAG-SPIKE.md): yes, reachable today with
1019
no framework change). A mini-Salesforce: accounts, contacts, leads,
1120
opportunities in a pipeline, quotes with exact pricing, per-field
1221
permissions, field-level audit history — and, as the endgame, runtime custom
1322
fields. This rung tests whether morph can carry *metadata-driven* production
1423
business software, the defining property of the Salesforce/SAP class.
1524

16-
Per review, the rung is split: **7a** = steps 1–8 (a conventional CRM on
25+
Per review, the rung was split: **7a** = steps 1–8 (a conventional CRM on
1726
compiled types), **7b** = steps 9–10 (runtime custom fields), with an
1827
explicit **go/no-go gate** between them — the extension-bag question has a
19-
different risk profile, and a negative answer must not stall the ladder.
28+
different risk profile, and a negative answer must not stall the ladder. The
29+
gate was passed on the spike's answer, and both halves are built.
2030

2131
## Reference implementations
2232

@@ -590,11 +600,67 @@ execute → warnings + confirmation token → re-execute; and **record merge**
590600
instances — two attached handler sets, one survivor), the hardest
591601
journal + instance-directory interaction in the ladder.
592602

603+
## What is not built
604+
605+
Three things this README asks for that the rung does not currently have.
606+
Each is listed with why it was left, so the gap is a decision on the record
607+
rather than an omission a reader has to discover.
608+
609+
- **No client — no `gui/`, no `gui_wasm/`.** Every other built rung ships a
610+
`gui/`; pastebin, bookmarks and polls also ship a `gui_wasm/`. The
611+
server-side halves of the form story *are* built and tested —
612+
`crm::gui::crmSchemasJson()` serves every form action's schema, and
613+
`crm::gui::updateAccountSchemaJsonFor()` shapes it per caller — but
614+
nothing consumes them. Two "expected strain points" below therefore stay
615+
open, because only a renderer can confront them: the **explicit-submit /
616+
presenter-gated form mode** (which that section requires *before any form
617+
ships*), and the **child-table control for quote lines**. The recursive
618+
line-item validation those lines also need does exist, server-side, in
619+
`QuoteModel` (`test_quote_model.cpp`).
620+
- **Lists are unpaginated.** Step 1 asks for filters/pagination and the
621+
strain points make keyset-cursor lists the ladder idiom, with a test for
622+
cursor stability while another client renames or deletes rows mid-walk.
623+
`ListAccounts`' own doc comment (`dto/account_dto.hpp`) records the
624+
decision taken instead: no rung has a cursor convention yet, and step 1
625+
stays consistent with the ladder rather than inventing the first one
626+
alone. Filters ship; pagination and its stability test do not.
627+
- **The deleted-field lifecycle has one of three arrival paths under
628+
test.** The strain points name three ways a write naming a just-deleted
629+
custom field can arrive: a stale open form, a queued offline edit, and
630+
journal replay of an old payload. All three would pass through the single
631+
enforcement point `validateCustomFields()`, and the live-submit path is
632+
tested ("Submitting a deleted field's key is rejected, not silently
633+
dropped or stored"). The other two are not exercised. The offline path is
634+
structurally unreachable today — the outbox carries only
635+
`QueuedOpportunityUpdate` and custom fields exist only on `Account` — and
636+
replay is worth a test of its own rather than an argument, because §8
637+
already records that replay mutates rows directly instead of going
638+
through `execute()`.
639+
640+
The two review-added features in the **"7-later" bucket** (duplicate
641+
detection on create, record merge) are deferred by the delivery review
642+
itself, not by this build, and neither gates 7a or 7b.
643+
593644
## Definition of done
594645

595-
- A rep works a lead → conversion → opportunity → quote → won, entirely on
596-
generated forms, on desktop and WASM, local and remote.
597-
- A second user with a restricted role sees the same records with fields
598-
hidden/read-only, enforced server-side.
599-
- An admin adds a custom field at runtime; existing clients render it on
600-
next schema fetch; its values persist, validate, and journal.
646+
Current state per bullet. The rung is server-complete; the two bullets that
647+
name a rendering client are unmet for the reason above.
648+
649+
- **Not met** — no client. *A rep works a lead → conversion → opportunity →
650+
quote → won, entirely on generated forms, on desktop and WASM, local and
651+
remote.* Every action behind that flow is built and tested end to end
652+
(`test_lead_model.cpp`, `test_convert_lead.cpp`, `test_pipeline.cpp`,
653+
`test_quote_model.cpp`), and the forms are served; there is no renderer to
654+
work them on, on either platform.
655+
- **Met.** *A second user with a restricted role sees the same records with
656+
fields hidden/read-only, enforced server-side.* The served schema is
657+
shaped per caller (`test_authz_schema.cpp`) **and** the field is re-checked
658+
independently on write, so a client that ignores `x-readonly` is still
659+
refused (`test_account_authz.cpp`); history is redacted on the same terms
660+
(`test_account_history.cpp`).
661+
- **Partly met.** *An admin adds a custom field at runtime; existing clients
662+
render it on next schema fetch; its values persist, validate, and journal.*
663+
The schema grows to carry the new field, and its values persist, validate
664+
and journal (`test_custom_fields.cpp`, `test_custom_fields_7b.cpp`). The
665+
render-on-next-fetch half is untested, for the same reason as the first
666+
bullet.

0 commit comments

Comments
 (0)