@odla-ai/chapter 0.20.0 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -39
- package/package.json +3 -2
- package/runbooks/adopt-existing.md +505 -0
- package/runbooks/greenfield.md +652 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
# Adoption runbook: convert an existing site to Chapter
|
|
2
|
+
|
|
3
|
+
Audience: an LLM or coding agent converting an existing or deployed website,
|
|
4
|
+
Worker, database, auth flow, or operating membership product.
|
|
5
|
+
|
|
6
|
+
Read this file completely before changing the application. Also read the
|
|
7
|
+
installed package `README.md`, `package.json` exports, and `dist/*.d.ts`. The
|
|
8
|
+
existing product stays live until measured Chapter parity and cutover are
|
|
9
|
+
approved.
|
|
10
|
+
|
|
11
|
+
If the repository is empty and no operating state exists, use
|
|
12
|
+
[`greenfield.md`](./greenfield.md).
|
|
13
|
+
|
|
14
|
+
## Completion means conversion, not installation
|
|
15
|
+
|
|
16
|
+
The following are canary evidence only:
|
|
17
|
+
|
|
18
|
+
- installing `@odla-ai/chapter`;
|
|
19
|
+
- writing `defineChapter()` or rendering `ChapterAdmin`;
|
|
20
|
+
- provisioning an ODLA tenant;
|
|
21
|
+
- serving a login screen or one Chapter API route;
|
|
22
|
+
- deploying a parallel Worker with copied sample data.
|
|
23
|
+
|
|
24
|
+
Adoption is complete only when the real product routes and journeys use Chapter
|
|
25
|
+
as the primary implementation, live data is reconciled and continuously
|
|
26
|
+
handled, production reads and writes have crossed explicit gates, and both
|
|
27
|
+
runtime and data rollback remain proven.
|
|
28
|
+
|
|
29
|
+
## Non-negotiable boundaries
|
|
30
|
+
|
|
31
|
+
1. Never mutate, reset, delete, detach, or repoint current production resources
|
|
32
|
+
during development adoption. Preserve Workers, Pages projects, domains,
|
|
33
|
+
databases, R2/KV, queues, Workflows, cron, webhooks, Clerk, Stripe, email,
|
|
34
|
+
Google calendars, source repositories, and immutable history.
|
|
35
|
+
2. Never print or commit secrets. Dev and production use separate credentials
|
|
36
|
+
and explicitly targeted Worker environments.
|
|
37
|
+
3. Preserve existing public URLs, content, SEO, header/navigation, responsive
|
|
38
|
+
layout, theme behavior, accessibility, and branded assets unless the human
|
|
39
|
+
approves a product change. Framework replacement is not redesign authority.
|
|
40
|
+
4. Preserve data semantics before deleting code. Chapter schema, routes, sends,
|
|
41
|
+
auth, account, payment, and scheduling defaults are hypotheses until parity
|
|
42
|
+
tests prove them.
|
|
43
|
+
5. Runtime rollback and data rollback are different. Restoring an old Worker
|
|
44
|
+
cannot recover writes accepted after Chapter became authoritative.
|
|
45
|
+
6. No silent fallback. A Chapter-primary failure is explicit and measured; a
|
|
46
|
+
legacy fallback, if approved, is named, time-bounded, and visible.
|
|
47
|
+
7. Keep ODLA rules default-deny and every admin/provider mutation
|
|
48
|
+
server-mediated.
|
|
49
|
+
|
|
50
|
+
## Phase 0 — Freeze the operating baseline
|
|
51
|
+
|
|
52
|
+
Create or extend root `MIGRATION.md`; never replace its history. Record state
|
|
53
|
+
`inventoried` only after the human approves the baseline.
|
|
54
|
+
|
|
55
|
+
### Inventory the product
|
|
56
|
+
|
|
57
|
+
- every public, member, admin, API, webhook, health, asset, feed, and 404 route;
|
|
58
|
+
- current production/dev origins, Worker/Pages names and version ids, routes,
|
|
59
|
+
DNS, compatibility settings, build automation, and rollback versions;
|
|
60
|
+
- D1/SQL/ODLA/KV/R2/files and every reader/writer;
|
|
61
|
+
- queues, Workflow definitions, cron, provider webhooks, retry/outbox behavior;
|
|
62
|
+
- Clerk app/instances, JWT claims, roles/allowlists, redirects, and first-admin
|
|
63
|
+
process;
|
|
64
|
+
- Stripe products/prices/webhooks/idempotency, Google calendars, Email Service;
|
|
65
|
+
- current schema/rules, row counts, earliest/latest timestamps, natural keys,
|
|
66
|
+
relations, projections, and owner-edited fields;
|
|
67
|
+
- public/private content boundaries and every raw HTML/Markdown/inline-JSON
|
|
68
|
+
render sink;
|
|
69
|
+
- telemetry, freshness/error alerts, deploy commands, and CI auto-deploys.
|
|
70
|
+
|
|
71
|
+
### Freeze visual and behavior evidence
|
|
72
|
+
|
|
73
|
+
Capture representative production screenshots and measured layouts at 390,
|
|
74
|
+
768, 1280, and 1440 pixels in every supported theme. Record:
|
|
75
|
+
|
|
76
|
+
- header height, wordmark/nav alignment, responsive breakpoints, page geometry;
|
|
77
|
+
- form fields/labels/errors, Clerk containment, tables/cards/rails, loading,
|
|
78
|
+
empty, error, unauthorized, and 404 states;
|
|
79
|
+
- full public route/link inventory, titles, descriptions, canonical URLs,
|
|
80
|
+
structured data, robots/sitemap/feed behavior;
|
|
81
|
+
- join, payment, booking, member, admin, CRM, email, and refund journeys.
|
|
82
|
+
|
|
83
|
+
Do not rely on visual memory. These fixtures are acceptance authority until a
|
|
84
|
+
human explicitly approves a redesign.
|
|
85
|
+
|
|
86
|
+
### Define migration budgets
|
|
87
|
+
|
|
88
|
+
Record accepted count/hash differences, maximum sync lag/newest-data age, route
|
|
89
|
+
and status parity, latency/error budget, visual tolerances, identity merge
|
|
90
|
+
policy, deterministic synthetic user/application ids, and an observation
|
|
91
|
+
window. Record both rollback plans before any Chapter write.
|
|
92
|
+
|
|
93
|
+
Maintain a migration matrix:
|
|
94
|
+
|
|
95
|
+
| Track | Legacy authority | Chapter/ODLA dev target | Current reads | Current writes | Evidence/freshness | Rollback | State |
|
|
96
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
97
|
+
| runtime/bindings | | | | | | | |
|
|
98
|
+
| membership data/CRM | | | | | | | |
|
|
99
|
+
| jobs/webhooks/outboxes | | | | | | | |
|
|
100
|
+
| Clerk/auth | | | | | | | |
|
|
101
|
+
| Stripe/payments | | | | | | | |
|
|
102
|
+
| calendar/booking | | | | | | | |
|
|
103
|
+
| email | | | | | | | |
|
|
104
|
+
| public/member/admin UI | | | | | | | |
|
|
105
|
+
| o11y/alerts | | | | | | | |
|
|
106
|
+
|
|
107
|
+
Allowed states: `not-started`, `canary`, `parallel`, `verified`,
|
|
108
|
+
`primary-dev`, `primary-prod`, `retired`, or `n/a` with a reason.
|
|
109
|
+
|
|
110
|
+
## Phase 1 — Model Chapter without deleting anything
|
|
111
|
+
|
|
112
|
+
Install the version-matched agent guidance and inspect the automation boundary:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npx @odla-ai/cli@0.17.1 setup
|
|
116
|
+
npx @odla-ai/cli@0.17.1 capabilities --json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Pin the exact known-good package matrix from the installed Chapter README.
|
|
120
|
+
For this runbook, first require
|
|
121
|
+
`npm view @odla-ai/chapter@0.20.2 version` to succeed. Install every runtime,
|
|
122
|
+
toolchain, CLI, and security package with the exact versions in that README;
|
|
123
|
+
never use `latest`, a range, or `--legacy-peer-deps`.
|
|
124
|
+
|
|
125
|
+
Write `src/chapter.config.mjs` from the inventory. Make every behavior decision
|
|
126
|
+
explicit:
|
|
127
|
+
|
|
128
|
+
- `mode`, `services`, complete light/dark brand tokens;
|
|
129
|
+
- price/currency/interval and owner-approved policy copy;
|
|
130
|
+
- email addresses/templates/debug redirect and send trigger;
|
|
131
|
+
- scheduling/timezone and pipeline stages/bookable/approvable subsets;
|
|
132
|
+
- exact application fields, caps, disclaimer requirement, Clerk-visible
|
|
133
|
+
`profileFields`, and CRM-projected `crmFields`;
|
|
134
|
+
- claim/table auth, ladder/super-admin tier;
|
|
135
|
+
- account side effect (`none`, `create`, or emailed `invite`);
|
|
136
|
+
- approve/refund policy and any network targets/field allowlists.
|
|
137
|
+
|
|
138
|
+
### Schema/rule parity gate
|
|
139
|
+
|
|
140
|
+
Before deleting old schema/rules/provisioning:
|
|
141
|
+
|
|
142
|
+
1. freeze the legacy schema and rules as reviewed test fixtures;
|
|
143
|
+
2. normalize only approved representational differences;
|
|
144
|
+
3. compare `createChapterIntegration(chapter)` namespaces, attrs, links, rules,
|
|
145
|
+
and seeds to the fixtures;
|
|
146
|
+
4. fail on missing fields, widened rules, cardinality changes, renamed natural
|
|
147
|
+
keys, new client-readable data, or unapproved seed differences;
|
|
148
|
+
5. record every intentional delta and migration mapping in `MIGRATION.md`.
|
|
149
|
+
|
|
150
|
+
`createChapterIntegration()` inserts the group seed only when absent. Existing
|
|
151
|
+
runtime owner edits do not update from later config changes. Compare the current
|
|
152
|
+
group row field by field, classify ownership, and write an explicit migration
|
|
153
|
+
for approved differences.
|
|
154
|
+
|
|
155
|
+
### Behavior-delta gate
|
|
156
|
+
|
|
157
|
+
At minimum audit:
|
|
158
|
+
|
|
159
|
+
- services default (`db`, `calendar`, `o11y`);
|
|
160
|
+
- per-field and request-body caps;
|
|
161
|
+
- account default `none` and outbound effects of `create`/`invite`;
|
|
162
|
+
- stock `JoinIsland` generates a fresh `submissionId` on each invocation, so an
|
|
163
|
+
ambiguous retry after a lost response is not idempotent even though a
|
|
164
|
+
concurrent click is disabled;
|
|
165
|
+
- Clerk-client-readable `public_metadata.profile`;
|
|
166
|
+
- application-to-CRM projection fields;
|
|
167
|
+
- required disclaimer acknowledgement and `disclaimerAckAt`;
|
|
168
|
+
- initial/bookable/approvable stages and never-backwards transitions;
|
|
169
|
+
- when each lifecycle email fires;
|
|
170
|
+
- Chapter recognizes only the literal `prod` environment as live mail; the
|
|
171
|
+
CLI-valid alias `production` follows the non-production fail-safe;
|
|
172
|
+
- approve promotion/email and refund/subscription behavior;
|
|
173
|
+
- Chapter route names and member/admin routing;
|
|
174
|
+
- Chapter's insert-only group seed and runtime-edited fields;
|
|
175
|
+
- payment readiness versus actual webhook readiness;
|
|
176
|
+
- calendar's HTTP-200 `schedulingReady: false` degradation.
|
|
177
|
+
|
|
178
|
+
The stock `applications` namespace is fixed. Its configurable form strings are
|
|
179
|
+
limited to `firstName`, `lastName`, `email`, `referral`, `referralName`,
|
|
180
|
+
`whoYouAre`, `linkedin`, `message`, `phone`, and `state`; `focus` and
|
|
181
|
+
`disclaimerAck` are special. An unknown configured field can pass config
|
|
182
|
+
validation and then fail at the database. Preserve a host schema/submit route
|
|
183
|
+
for every legacy field that cannot be mapped without loss.
|
|
184
|
+
|
|
185
|
+
The current `prices.currency` and `prices.interval` values do not control
|
|
186
|
+
storage, join config, formatting, or the Stripe subscription. Treat the live
|
|
187
|
+
Stripe Price as authoritative and make provider-side amount/currency/interval
|
|
188
|
+
equality a cutover gate.
|
|
189
|
+
|
|
190
|
+
Keep one test per accepted behavior decision so a package upgrade cannot change
|
|
191
|
+
the site silently.
|
|
192
|
+
|
|
193
|
+
### Make the dev descriptor executable
|
|
194
|
+
|
|
195
|
+
Before Phase 2, create or adapt `odla.config.mjs`. A repository with no ODLA
|
|
196
|
+
config may use the installed exact CLI's `init` command as a starting point,
|
|
197
|
+
but the reviewed result must declare:
|
|
198
|
+
|
|
199
|
+
- the existing product's one app id/name and only `envs: ["dev"]`;
|
|
200
|
+
- explicit `chapter.services`;
|
|
201
|
+
- `integrations: [createChapterIntegration(chapter)]` plus any compatible,
|
|
202
|
+
non-overlapping host integrations;
|
|
203
|
+
- the intended dev Clerk publishable key, calendar ids, o11y service, local
|
|
204
|
+
credential paths, and a null/off-route dev link.
|
|
205
|
+
|
|
206
|
+
Do not provision both a legacy inline schema and the Chapter integration for the
|
|
207
|
+
same namespaces. After the schema/rule parity gate passes, freeze the legacy
|
|
208
|
+
descriptor as a test fixture and make the reviewed Chapter integration the
|
|
209
|
+
active dev descriptor. Production configuration remains absent.
|
|
210
|
+
|
|
211
|
+
Create an off-route dev Worker entry using
|
|
212
|
+
`chapterWorker({ chapter, routes: legacyRoutes })`, wrapped with o11y, and an
|
|
213
|
+
explicit `wrangler ... --env dev` target. Keeping all legacy handlers as host
|
|
214
|
+
routes at this point changes no route ownership; it only makes the canary and
|
|
215
|
+
Chapter data surface executable.
|
|
216
|
+
|
|
217
|
+
### Add a fail-closed migration readiness route
|
|
218
|
+
|
|
219
|
+
The built-in `/api/health` is only `{ "ok": true }`; it does not touch the
|
|
220
|
+
database. Add an admin-only host route or executable command that returns `503`
|
|
221
|
+
until all of these match the acceptance manifest:
|
|
222
|
+
|
|
223
|
+
- literal tenant/app/env and Chapter/package release;
|
|
224
|
+
- expected schema/rules digest and required group/config rows;
|
|
225
|
+
- reconciled counts, natural-key collisions, relation integrity, and newest
|
|
226
|
+
source timestamp within the freshness budget;
|
|
227
|
+
- zero unhandled quarantines and bounded/zero pending outbox work;
|
|
228
|
+
- healthy continuous sync and the Chapter-primary-to-legacy rollback mirror.
|
|
229
|
+
|
|
230
|
+
Return only redacted counts/digests. This gate is allowed to fail during initial
|
|
231
|
+
canary work; it must be mechanically green before switching either reads or
|
|
232
|
+
writes, and after every rollback recovery.
|
|
233
|
+
|
|
234
|
+
## Phase 2 — Create an isolated dev canary
|
|
235
|
+
|
|
236
|
+
Run:
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
npx odla-ai doctor
|
|
240
|
+
npx odla-ai provision --dry-run
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The dev topology must be distinct from production:
|
|
244
|
+
|
|
245
|
+
- `envs: ["dev"]`, tenant `<app-id>--dev`;
|
|
246
|
+
- explicit dev Worker name and `wrangler ... --env dev`;
|
|
247
|
+
- no production route, DNS, database binding, queue, Workflow, cron, webhook,
|
|
248
|
+
Git writer, Stripe live resource, email recipient, or Google write authority;
|
|
249
|
+
- `debugEmail` enabled and Stripe test mode only;
|
|
250
|
+
- dev Clerk `pk_test_...` from the intended workspace/application;
|
|
251
|
+
- o11y wrapped around the real canary handler with safe low-cardinality labels.
|
|
252
|
+
|
|
253
|
+
Show the dry-run target, schema/rules, services, link, and secret recipients.
|
|
254
|
+
After the human approves the device code:
|
|
255
|
+
|
|
256
|
+
```sh
|
|
257
|
+
npx odla-ai provision --email <existing-odla-account> --write-dev-vars --push-secrets
|
|
258
|
+
npm run build
|
|
259
|
+
npx wrangler deploy --env dev
|
|
260
|
+
npx odla-ai smoke --env dev
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
If credential transfer alone must be retried, use the non-rotating
|
|
264
|
+
`npx odla-ai secrets push --env dev`; do not reprovision or rotate by default.
|
|
265
|
+
Verify the deployed Chapter health marker, a database-backed read, and an
|
|
266
|
+
unauthenticated private-route rejection. Record the URL and Cloudflare version
|
|
267
|
+
id. A canary advances matrix rows only to `canary`; the migration-readiness
|
|
268
|
+
route is expected to remain `503` until Phase 3 evidence passes.
|
|
269
|
+
|
|
270
|
+
## Phase 3 — Adopt data with replay and reconciliation
|
|
271
|
+
|
|
272
|
+
Skip historical backfill only when the human-approved inventory proves the
|
|
273
|
+
site has no operating data. Otherwise:
|
|
274
|
+
|
|
275
|
+
1. Write a checked-in mapping for every legacy table/collection/file and
|
|
276
|
+
relation: natural key, target namespace, attrs, omissions/nulls, edge
|
|
277
|
+
direction, ownership, tombstone/deletion behavior, and provenance.
|
|
278
|
+
2. Freeze a source snapshot or bounded high-water mark. Record counts,
|
|
279
|
+
earliest/latest timestamps, and a digest/immutable id.
|
|
280
|
+
3. Backfill in bounded deterministic chunks using stable mutation ids.
|
|
281
|
+
Persist checkpoints and a rejection ledger; each input is written, skipped
|
|
282
|
+
with reason, or failed/retryable.
|
|
283
|
+
4. Replay from an earlier checkpoint and prove no duplicate applications,
|
|
284
|
+
people, companies, meetings, email log entries, relations, or side effects.
|
|
285
|
+
5. Generate a redacted reconciliation report: counts, required attrs, duplicate
|
|
286
|
+
natural keys, dangling relations, identity collisions, status/price/role
|
|
287
|
+
differences, and newest timestamps.
|
|
288
|
+
6. Preserve human-owned fields. Automated projection omits fields it does not
|
|
289
|
+
own rather than reading and writing stale copies.
|
|
290
|
+
|
|
291
|
+
### Continuous operation
|
|
292
|
+
|
|
293
|
+
Choose one durable event boundary for production-shaped changes. New legacy
|
|
294
|
+
applications, payments, role changes, meetings, CRM edits, and emails must reach
|
|
295
|
+
the dev Chapter projection through a measured outbox/queue/replay process.
|
|
296
|
+
Record oldest pending age, attempts, quarantine, and source-to-target lag.
|
|
297
|
+
|
|
298
|
+
Backfill alone is not conversion. Advance to `continuously-synced` only after
|
|
299
|
+
normal schedules/webhooks are observed and failures replay from durable state.
|
|
300
|
+
|
|
301
|
+
### Shadow reads
|
|
302
|
+
|
|
303
|
+
For list/detail/search/admin dashboard/member status/join config, read both
|
|
304
|
+
legacy and Chapter in dev while still serving the legacy response. Compare
|
|
305
|
+
normalized status, ids, ordering, counts, fields, relations, price/policy, and
|
|
306
|
+
freshness. Log bounded diffs, never private row bodies.
|
|
307
|
+
|
|
308
|
+
At separate human checkpoints:
|
|
309
|
+
|
|
310
|
+
1. switch dev reads to Chapter and make failures explicit;
|
|
311
|
+
2. switch accepted dev writes to Chapter;
|
|
312
|
+
3. durably mirror Chapter-primary writes back to the legacy rollback authority;
|
|
313
|
+
4. simulate ODLA unavailable and rollback-mirror unavailable.
|
|
314
|
+
|
|
315
|
+
Every authenticated/admin mutator must use this authority contract or be
|
|
316
|
+
disabled. A separate Chapter demo route does not satisfy primary-dev.
|
|
317
|
+
|
|
318
|
+
## Phase 4 — Adopt the Worker and routes in safe order
|
|
319
|
+
|
|
320
|
+
Do not replace schema, worker, routes, and UI in one unreviewable change.
|
|
321
|
+
|
|
322
|
+
1. Re-assert that the active dev descriptor is the reviewed
|
|
323
|
+
`createChapterIntegration(chapter)` from Phase 1 and that the legacy
|
|
324
|
+
provisioner remains only as a parity fixture.
|
|
325
|
+
2. Start from the off-route `chapterWorker({ chapter, routes })` canary. Host
|
|
326
|
+
routes run before built-ins and receive Chapter's existing context
|
|
327
|
+
(`verifyUser`, `makeDb`, `roleFor`, `isAdmin`); do not verify JWTs twice.
|
|
328
|
+
3. Initially keep every bespoke endpoint as a host route. Alias legacy URLs to
|
|
329
|
+
Chapter behavior instead of breaking clients.
|
|
330
|
+
4. Compare built-in and legacy behavior route by route; only then remove a host
|
|
331
|
+
route that Chapter fully owns.
|
|
332
|
+
5. Keep static fallback and API handling explicit so unknown APIs cannot fall
|
|
333
|
+
through to a successful SPA document.
|
|
334
|
+
|
|
335
|
+
Test public/private method policies, body limits, idempotency, auth 401 vs 403,
|
|
336
|
+
safe redirects, webhook signatures/replay, scheduling conflict,
|
|
337
|
+
replay-deduplicated and concurrent email sends, and unavailable-provider
|
|
338
|
+
degradation. Chapter's email log prevents a later replay after success; it does
|
|
339
|
+
not serialize two concurrent sends, so preserve a stricter existing outbox or
|
|
340
|
+
provider-idempotency contract.
|
|
341
|
+
|
|
342
|
+
## Phase 5 — Adopt UI without losing the product
|
|
343
|
+
|
|
344
|
+
### Preserve first
|
|
345
|
+
|
|
346
|
+
Render the Chapter component beside the current implementation in a dev-only
|
|
347
|
+
fixture. Compare markup/behavior and the Phase 0 screenshots before deletion.
|
|
348
|
+
|
|
349
|
+
- Import `@odla-ai/ui/themes/salt/app.css` before host CSS.
|
|
350
|
+
- Map the legacy design tokens into `brand.palette` and `paletteDark`; do not
|
|
351
|
+
carry an unbounded parallel theme system.
|
|
352
|
+
- Public pages remain site-owned. Preserve their content hierarchy, imagery,
|
|
353
|
+
URLs, SEO, and responsive geometry.
|
|
354
|
+
- Replace join orchestration with `JoinIsland`, member behavior with
|
|
355
|
+
`MembersArea`, and admin with `<ChapterAdmin chapter={chapter}/>` only after
|
|
356
|
+
field/auth/action parity.
|
|
357
|
+
- The host still supplies member CSS and the Clerk wrapper around
|
|
358
|
+
`MembersArea`; Chapter does not ship a whole public stylesheet.
|
|
359
|
+
- Scope host form styles. Bare `input`, `button`, `select`, `textarea`, `label`,
|
|
360
|
+
or global box-model rules must not corrupt Clerk/vendor DOM.
|
|
361
|
+
|
|
362
|
+
### Required visual acceptance
|
|
363
|
+
|
|
364
|
+
At 390, 768, 1280, and 1440 pixels in every theme, compare:
|
|
365
|
+
|
|
366
|
+
- identical product header/navigation position between public/member/admin;
|
|
367
|
+
- wordmark, text baseline, theme control, active state, and hover/focus;
|
|
368
|
+
- join fields/errors, payment and slot picker, Clerk sign-in containment;
|
|
369
|
+
- member cards/actions and every admin section/table/drawer;
|
|
370
|
+
- no clipped labels, card text escape, accidental document scroll, or
|
|
371
|
+
horizontal overflow;
|
|
372
|
+
- loading, empty, error, unauthorized, 404, and reduced-motion states.
|
|
373
|
+
|
|
374
|
+
The human approves deployed screenshots/browser behavior. Passing CSS tests is
|
|
375
|
+
not visual parity.
|
|
376
|
+
|
|
377
|
+
## Phase 6 — Provider and authorization parity
|
|
378
|
+
|
|
379
|
+
### Clerk
|
|
380
|
+
|
|
381
|
+
Use the same Clerk application/workspace. Chapter claim mode requires session
|
|
382
|
+
claims for both email and role; hub table mode uses lowercase `admins` rows.
|
|
383
|
+
Prove existing allowed/forbidden users, role ladder, super-admin behavior,
|
|
384
|
+
malformed/expired sessions, sign-out, and return targets.
|
|
385
|
+
|
|
386
|
+
If the legacy verifier enforces JWT `aud` or `azp`, keep that host verifier in
|
|
387
|
+
front of Chapter. This Chapter release verifies issuer but does not expose or
|
|
388
|
+
enforce the CLI-provisioned Clerk audience. Add wrong-audience and
|
|
389
|
+
wrong-authorized-party fixtures; do not cut over auth until the legacy contract
|
|
390
|
+
still rejects them.
|
|
391
|
+
|
|
392
|
+
When Chapter needs server-side account/role operations, set the named
|
|
393
|
+
app-readable `clerk_secret_key` through:
|
|
394
|
+
|
|
395
|
+
```sh
|
|
396
|
+
npx odla-ai secrets set clerk_secret_key --env dev --stdin
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Do not substitute `secrets set-clerk-key`; it writes the platform-reserved
|
|
400
|
+
`$clerk_secret`, which Chapter cannot read under the named key. The generic
|
|
401
|
+
command does not validate Clerk instance prefixes: independently confirm dev
|
|
402
|
+
uses `sk_test_`. Never put `sk_live_` in dev.
|
|
403
|
+
|
|
404
|
+
### Stripe
|
|
405
|
+
|
|
406
|
+
Use test resources and an isolated dev webhook. Store `stripe_secret_key` and
|
|
407
|
+
`stripe_webhook_secret` write-only; reconcile group `stripePublishableKey` and
|
|
408
|
+
`stripePriceId`. Read that exact Stripe Price from Stripe and prove its first
|
|
409
|
+
charge, recurring amount, currency, and interval match the rendered and
|
|
410
|
+
approved contract. `paymentsReady: true` proves neither that equality nor
|
|
411
|
+
webhook readiness; Chapter's built-in money formatter is USD/dollar-specific in
|
|
412
|
+
this release. Preserve a host UI/route for non-USD or mismatched pricing. Test
|
|
413
|
+
successful payment, duplicate webhook, failure, refund, and subscription
|
|
414
|
+
cancellation before switching the UI.
|
|
415
|
+
|
|
416
|
+
### Calendar and email
|
|
417
|
+
|
|
418
|
+
Complete dev Google booking consent, reconnecting any grant from the retired
|
|
419
|
+
read-only mirror, then prove actual slots, booking, rebooking, cancellation,
|
|
420
|
+
and invitation behavior. `schedulingReady: false` with HTTP 200 is a failed
|
|
421
|
+
readiness gate, not a pass.
|
|
422
|
+
|
|
423
|
+
Use a verified dev sender plus `debugEmail`; prove every lifecycle template,
|
|
424
|
+
recipient redirect, replay deduplication, concurrent-send behavior, and the
|
|
425
|
+
selected account invitation effect. Never let dev send to imported members.
|
|
426
|
+
|
|
427
|
+
## Phase 7 — Full deployed-dev journey
|
|
428
|
+
|
|
429
|
+
Check in an executable acceptance manifest with:
|
|
430
|
+
|
|
431
|
+
- actual dev URLs and Worker version ids;
|
|
432
|
+
- deterministic synthetic member/application ids;
|
|
433
|
+
- expected routes/statuses, schema/counts/freshness, and Chapter backend marker;
|
|
434
|
+
- expected migration-readiness inputs and a `200` only when each is green;
|
|
435
|
+
- expected account, payment, booking, email, CRM, and admin outcomes;
|
|
436
|
+
- expected o11y service/release and safe trace;
|
|
437
|
+
- replay and failure-injection outcomes;
|
|
438
|
+
- visual viewport/theme fixtures.
|
|
439
|
+
|
|
440
|
+
Run the real journey:
|
|
441
|
+
|
|
442
|
+
1. browse every public route/deep link and verify SEO/404/method behavior;
|
|
443
|
+
2. submit the configured join form, including consent;
|
|
444
|
+
3. execute Stripe test payment and authoritative webhook;
|
|
445
|
+
4. book/rebook the intro call and receive the debug-routed email;
|
|
446
|
+
5. sign in as provisional/member/admin and use the real member/admin pages;
|
|
447
|
+
6. perform every admin mutation family and verify Chapter-primary audit plus
|
|
448
|
+
legacy rollback mirror;
|
|
449
|
+
7. replay the event and prove no duplicate row, charge, booking, account, or
|
|
450
|
+
email;
|
|
451
|
+
8. inject ODLA/provider/mirror failure, observe explicit failure/retry, recover;
|
|
452
|
+
9. verify freshness/continuous-sync budget and visual parity;
|
|
453
|
+
10. require the fail-closed migration-readiness gate to return `200`;
|
|
454
|
+
11. follow safe o11y evidence across the real Worker.
|
|
455
|
+
|
|
456
|
+
`doctor`, unit tests, build, CLI smoke, a login page, and a human-looking admin
|
|
457
|
+
screen are necessary but not sufficient.
|
|
458
|
+
|
|
459
|
+
## Phase 8 — Production parallel run and cutover
|
|
460
|
+
|
|
461
|
+
Do not add production until the human approves the full dev report and both
|
|
462
|
+
rollback plans.
|
|
463
|
+
|
|
464
|
+
1. Add only the literal `prod` environment using the production instance of the
|
|
465
|
+
same Clerk app, Stripe live resources, approved email sender, calendars,
|
|
466
|
+
links, and distinct secret targets. Do not use the CLI-valid alias
|
|
467
|
+
`production`: Chapter would suppress or debug-redirect lifecycle mail.
|
|
468
|
+
2. Run production build/tests/security scan, `doctor`, and
|
|
469
|
+
`provision --dry-run`; inspect CI/Workers Builds so merging cannot
|
|
470
|
+
auto-deploy the legacy production target unexpectedly.
|
|
471
|
+
3. After explicit approval, provision with `--yes` and deploy a production
|
|
472
|
+
Chapter candidate off-route. Record its version id.
|
|
473
|
+
4. Repeat historical backfill, reconciliation, continuous sync, shadow reads,
|
|
474
|
+
provider checks, and a production-safe synthetic journey against production
|
|
475
|
+
source data. Dev data is not a production backfill.
|
|
476
|
+
5. At one checkpoint, require the fail-closed readiness gate to return `200`,
|
|
477
|
+
then switch production reads. Verify freshness, auth, visual behavior, and
|
|
478
|
+
explicit failure handling while legacy writes continue.
|
|
479
|
+
6. At a second checkpoint, record a write high-water mark, require readiness
|
|
480
|
+
again, and make Chapter authoritative for writes. Keep the durable legacy
|
|
481
|
+
rollback mirror and continuously reconcile it.
|
|
482
|
+
7. Abort when write loss, unbounded lag, unexplained diff, auth failure,
|
|
483
|
+
freshness breach, or accepted error budget is crossed.
|
|
484
|
+
|
|
485
|
+
### Runtime rollback
|
|
486
|
+
|
|
487
|
+
Restore the recorded known-good Worker version plus routes, bindings, triggers,
|
|
488
|
+
and domains. Verify representative routes and health. Keep Chapter resources
|
|
489
|
+
and telemetry for investigation; do not delete them.
|
|
490
|
+
|
|
491
|
+
### Data rollback
|
|
492
|
+
|
|
493
|
+
Pause/drain new Chapter-primary work; enumerate accepted writes since the
|
|
494
|
+
checkpoint; reverse-project missing writes with stable idempotency keys;
|
|
495
|
+
reconcile queues, counts, timestamps, identities, status, meetings, charges,
|
|
496
|
+
and email; only then report legacy reads healthy. Never reset either store.
|
|
497
|
+
|
|
498
|
+
Keep both rollback capabilities through the approved observation window
|
|
499
|
+
(normally at least 72 hours). Retire each legacy reader, writer, binding,
|
|
500
|
+
webhook, job, and secret only in a separate human-approved change with a fresh
|
|
501
|
+
journey afterward.
|
|
502
|
+
|
|
503
|
+
Close `MIGRATION.md` only when every applicable track is `primary-prod` or
|
|
504
|
+
`retired`, the observation window passes, and the human confirms the existing
|
|
505
|
+
product—not merely a Chapter canary—has been converted.
|