@odla-ai/chapter 0.47.10 → 0.48.1
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 +231 -19
- package/dist/{chunk-VWDEHUNI.js → chunk-KZ2AXEZW.js} +497 -204
- package/dist/chunk-KZ2AXEZW.js.map +1 -0
- package/dist/{chunk-7MEVZEL2.js → chunk-MQ4OPZCS.js} +969 -516
- package/dist/chunk-MQ4OPZCS.js.map +1 -0
- package/dist/{copy-context-Dorxp-WK.d.ts → copy-context-BveBZglM.d.ts} +18 -7
- package/dist/index.cjs +3842 -773
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +775 -90
- package/dist/index.d.ts +775 -90
- package/dist/index.js +3835 -766
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +144 -42
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +92 -2
- package/dist/testing.d.ts +92 -2
- package/dist/testing.js +144 -42
- package/dist/testing.js.map +1 -1
- package/dist/ui/admin/index.d.ts +75 -5
- package/dist/ui/admin/index.js +3 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +4 -2
- package/dist/ui/member/index.d.ts +9 -2
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +10549 -5504
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +509 -9
- package/dist/worker/index.d.ts +509 -9
- package/dist/worker/index.js +8607 -3563
- package/dist/worker/index.js.map +1 -1
- package/package.json +5 -5
- package/runbooks/adopt-existing.md +44 -8
- package/dist/chunk-7MEVZEL2.js.map +0 -1
- package/dist/chunk-VWDEHUNI.js.map +0 -1
package/README.md
CHANGED
|
@@ -88,13 +88,13 @@ Chapter-owned application, admin, CRM, payment, and scheduling behavior.
|
|
|
88
88
|
*or* an odla-db `admins` allowlist, plus a read-only `superAdmins` tier —
|
|
89
89
|
selected by `auth.source`, defaulting per mode. Escalation guards
|
|
90
90
|
(`canChangeRole`) are package-enforced.
|
|
91
|
-
- **Correctness is packaged, not per-site.**
|
|
92
|
-
(`sendTemplated`/`isAlreadySent`;
|
|
93
|
-
|
|
91
|
+
- **Correctness is packaged, not per-site.** Durably claimed email
|
|
92
|
+
(`sendTemplated`/`isAlreadySent`; ambiguous non-idempotent provider outcomes
|
|
93
|
+
require evidence-based reconciliation rather than blind resend), a non-prod
|
|
94
94
|
delivery fail-safe (`planDelivery`), stable application replay identity (one
|
|
95
95
|
UI submission key and the same resumable canonical id), status-never-backwards
|
|
96
96
|
(`canTransition`), Stripe webhook integrity (`verifyStripeSignature`) with the
|
|
97
|
-
|
|
97
|
+
verified Stripe events and refund reconciliation as the payment authority,
|
|
98
98
|
one-subscription-per-application idempotency, meetings-as-canonical booking (a
|
|
99
99
|
rebooking *reschedules* the event, preserving the Meet link), Google-edit
|
|
100
100
|
adoption (`reconcileMeetings`), and a one-way CRM projection.
|
|
@@ -381,6 +381,12 @@ The same renderer can also replace the placeholder chooser inside
|
|
|
381
381
|
`redirect_status` and `reschedule` values never choose a UI step. Supply
|
|
382
382
|
`loadResume` when a host stores the capability elsewhere.
|
|
383
383
|
|
|
384
|
+
Closed/refunded/declined applications return a scoped `409` with
|
|
385
|
+
`code: "application_closed"`; `loadJoinResume` maps this to `step: "closed"`.
|
|
386
|
+
The island shows a terminal notice without another form, checkout, or booking.
|
|
387
|
+
Unknown continuation states and mismatched application ids are rejected rather
|
|
388
|
+
than defaulting to payment. Temporary configuration failures remain retryable.
|
|
389
|
+
|
|
384
390
|
Studio and automated previews can traverse that same state machine without
|
|
385
391
|
creating an application, loading Stripe, charging a card, or booking a real
|
|
386
392
|
calendar event. Supply deterministic `simulation` boundaries and keep the
|
|
@@ -609,6 +615,24 @@ The hub builds a complete `SignupControlDocument`, calls
|
|
|
609
615
|
secret is vaulted separately on each side; it is never a Clerk token, ODLA key,
|
|
610
616
|
or CRM network secret.
|
|
611
617
|
|
|
618
|
+
For multiple authoritative runtimes sharing a data environment, set
|
|
619
|
+
`signupControl.runtimeSecrets` to an explicit map such as
|
|
620
|
+
`{ cory: "signup_control_cory", tori: "signup_control_tori" }`. Each value names
|
|
621
|
+
a different follower vault key; paired hub credentials remain separate too.
|
|
622
|
+
Unlisted runtimes and untargeted legacy deliveries then fail closed. Include
|
|
623
|
+
`deliveryTarget: { appId, environment, runtime }` beside the envelope's
|
|
624
|
+
`document` and `digest`. This transport identity is signed but excluded from
|
|
625
|
+
the document digest, so both runtimes accept or replay the same reviewed state.
|
|
626
|
+
The receipt attests the receiving runtime; the sender validates exact target,
|
|
627
|
+
revision, digest, status, and timestamp. Delivery does not follow redirects and
|
|
628
|
+
bounds receipt reads to 8 KiB and ten seconds.
|
|
629
|
+
|
|
630
|
+
Provision the additive `signupControlHeads` namespace before deploying this
|
|
631
|
+
receiver. Its transaction guard serializes concurrent first publications and
|
|
632
|
+
stale work across runtimes. The hub must likewise allocate revisions atomically
|
|
633
|
+
and freeze the document in the same transaction as its per-runtime outbox rows.
|
|
634
|
+
No process should regenerate an old revision from the current catalog on retry.
|
|
635
|
+
|
|
612
636
|
The document carries the source, exact chapter/environment/Stripe-mode target,
|
|
613
637
|
monotonic revision, ordered and available tiers, immutable Stripe Price ids,
|
|
614
638
|
the membership-page renderer document, and explicitly delegated application,
|
|
@@ -629,6 +653,12 @@ join-config, application tier validation, and checkout already read. Public
|
|
|
629
653
|
application contract, and theme. Applicant requests therefore use local
|
|
630
654
|
last-known-good state and never depend on the hub being reachable.
|
|
631
655
|
|
|
656
|
+
A successful empty tier query retains legacy single-tier compatibility.
|
|
657
|
+
Database failures, malformed responses, and missing stored prices fail closed;
|
|
658
|
+
they never invent a free membership. Retiring all stored tiers leaves no offers
|
|
659
|
+
instead of reviving the group's legacy price. Public Worker routes return
|
|
660
|
+
`503` with `tiers_unavailable` for this failure rather than charging a fallback.
|
|
661
|
+
|
|
632
662
|
#### One field contract for follower forms and CMS management
|
|
633
663
|
|
|
634
664
|
Chapter owns field identity and storage authority; a hub such as Built Not
|
|
@@ -1502,18 +1532,141 @@ These bite silently — a smoke test won't catch them:
|
|
|
1502
1532
|
method, auth, request, response shape, units, status, and header parity are
|
|
1503
1533
|
proven. Equal values with different JSON contracts are not compatible. For a
|
|
1504
1534
|
join flow, adopt `JoinIsland` end to end or keep an explicit tested adapter;
|
|
1505
|
-
do not merely repoint an existing page at `/api/join-config`. `
|
|
1535
|
+
do not merely repoint an existing page at `/api/join-config`. `JoinIsland`
|
|
1536
|
+
now posts to `/api/payments/quote?application=<id>` before exposing
|
|
1537
|
+
membership payment consent. The quote uses the application's recorded tier,
|
|
1538
|
+
not a URL selection, and includes its amount, currency, policy, and digest.
|
|
1539
|
+
Optional signed `policy.tierPolicies` overrides supply refund/trust copy for
|
|
1540
|
+
declared tier IDs. Checkout validates a supplied quote digest before provider
|
|
1541
|
+
writes; a changed quote must be shown and accepted again. The accepted quote
|
|
1542
|
+
is frozen with the checkout intent so later catalog edits cannot silently
|
|
1543
|
+
change an in-progress purchase. Hosts upgrading the complete join client can
|
|
1544
|
+
set `chapterWorker({ chapter, requirePaymentQuote: true })` to reject older
|
|
1545
|
+
requests without a digest. The default preserves old published clients; new
|
|
1546
|
+
membership `JoinIsland` clients always send the digest. Gift purchase and inert
|
|
1547
|
+
simulation flows retain their separate contracts. The standalone `PaymentStep`
|
|
1506
1548
|
obtains Stripe's `clientSecret`, `publishableKey`, and `lineItems` from
|
|
1507
1549
|
`POST /api/payments/subscription`, not from the public join-config response.
|
|
1550
|
+
Subscription setup verifies Stripe's immutable amount, currency, recurrence,
|
|
1551
|
+
quantity model, and test/live mode before creating a customer or subscription.
|
|
1552
|
+
A saved subscription is retrieved by ID on replay and checked against the
|
|
1553
|
+
customer, runtime partition, original invoice, and Price; it is never recreated.
|
|
1554
|
+
An ambiguous create with no saved provider ID stops after 23 hours with
|
|
1555
|
+
`checkout_recovery_required`, before Stripe can prune its idempotency key.
|
|
1556
|
+
Reconcile that exact purchase before retrying; do not clear its intent to force
|
|
1557
|
+
another charge.
|
|
1558
|
+
For intent-backed first invoices, the signed webhook also verifies the exact
|
|
1559
|
+
subscription, customer, paid amount, currency, and invoice status before
|
|
1560
|
+
advancing membership or applying an interview waiver. A webhook arriving
|
|
1561
|
+
before the checkout-ready write returns a retryable response. Mismatched
|
|
1562
|
+
evidence is quarantined without sending payment notices or granting access.
|
|
1563
|
+
Renewal dates come from an unambiguous recurring invoice line, not whichever
|
|
1564
|
+
line Stripe happens to list first; manual and proration/update invoices do not
|
|
1565
|
+
renew membership.
|
|
1566
|
+
|
|
1567
|
+
The internal provider adapter can freeze separate, versioned percentage-coupon
|
|
1568
|
+
terms alongside the base recurring Price. An optional extended initial term
|
|
1569
|
+
charges a full up-front invoice item and defers recurring billing to the frozen
|
|
1570
|
+
service end. This uses Stripe's trial anchor mechanically, **not** a free
|
|
1571
|
+
membership: access still requires the verified paid invoice. The coupon must
|
|
1572
|
+
match the exact app/environment/runtime and policy version; a missing coupon
|
|
1573
|
+
is provisioned idempotently and restricted to the verified membership product,
|
|
1574
|
+
so it cannot also discount a named seat.
|
|
1575
|
+
Only first-invoice and continuing coupons are supported; arbitrary timed
|
|
1576
|
+
discount expiry is rejected rather than silently becoming permanent. These
|
|
1577
|
+
adapter terms are not a public join-config override. A host may opt into
|
|
1578
|
+
`chapterWorker({ chapter, membershipAuthority: adapter.authority })`, where
|
|
1579
|
+
`adapter = createMembershipWorkerAdapter({ chapterId, secretName, makeDb,
|
|
1580
|
+
targetUrl, fetch, namedSeats })` comes from `@odla-ai/chapter/worker`.
|
|
1581
|
+
The factory wires payment, approval, refund, renewal, current access, and
|
|
1582
|
+
optional named-seat operations through the same exact runtime edge. `fetch`
|
|
1583
|
+
and `targetUrl` resolve from the host environment; an absent configured
|
|
1584
|
+
binding fails closed and never falls back to a different deployment.
|
|
1585
|
+
All callbacks must target the same network authority scope, never a local
|
|
1586
|
+
shadow ledger. They may use `requestMembershipCommercialOffer` and
|
|
1587
|
+
`requestMembershipCheckoutBinding`, `requestMembershipPayment` and
|
|
1588
|
+
`requestMembershipApproval` against an authenticated existing service,
|
|
1589
|
+
or invoke the corresponding network commands in process. This API does not
|
|
1590
|
+
require or provision a new app or Worker.
|
|
1591
|
+
|
|
1592
|
+
With that adapter configured, POST prepares a durable offer projection before
|
|
1593
|
+
returning consent terms; GET only reads an existing quote. A lost issuer reply
|
|
1594
|
+
reuses the persisted request identity. An unexpired quote survives later
|
|
1595
|
+
catalog changes. An expired quote can be replaced only before a checkout
|
|
1596
|
+
intent exists; changed terms require fresh consent. The base chapter Price
|
|
1597
|
+
must match the authority offering's amount, currency and recurrence.
|
|
1598
|
+
After consent, a durable network binding fixes the application, offer, Price
|
|
1599
|
+
and quote before any Stripe I/O. Conflicting bindings, unavailable authority,
|
|
1600
|
+
and legacy intents without an offer fail closed. An existing binding can
|
|
1601
|
+
replay after offer expiry, but does not itself assert payment or grant access.
|
|
1602
|
+
Never delete an ambiguous checkout intent to obtain a new quote or purchase.
|
|
1603
|
+
Install the chapter projection schema before deploying the updated client;
|
|
1604
|
+
install `createMembershipAuthorityIntegration()` only in the network owner's
|
|
1605
|
+
existing database (BNF), alongside its policy and verified commercial-event
|
|
1606
|
+
integrations. It creates eight deny-all collections with no activation seeds.
|
|
1607
|
+
Pair it with `createMembershipAuthorityStorage(db, allowedSecretNames)` from
|
|
1608
|
+
the Worker entry: this maps the command model's three `network*` collections
|
|
1609
|
+
to `membershipRecord`, `membershipEntitlement`, and `membershipSeat`, leaving
|
|
1610
|
+
the original shadow collections unchanged. Atomic guards and mutation IDs
|
|
1611
|
+
are preserved. Do not mix this descriptor with raw `membershipOfferEntities`
|
|
1612
|
+
or `membershipAuthorityEntities`, or use its storage without the descriptor.
|
|
1613
|
+
`createMembershipAuthorityHandler` may be mounted in the existing BNF backend;
|
|
1614
|
+
no additional app/database is required. It accepts only signed, exact-scope
|
|
1615
|
+
commands, never arbitrary database operations or role grants. The storage
|
|
1616
|
+
adapter is a defensive contract, not a security boundary against a compromised
|
|
1617
|
+
host holding its unrestricted app key. Protected superadmin changes remain
|
|
1618
|
+
exclusively in odla Studio. `memberSystemAvailability` requires explicit
|
|
1619
|
+
`bnfBackendReachable` independently of `bnfFrontendReachable` and odla health;
|
|
1620
|
+
absent backend health fails dependent workflows closed.
|
|
1621
|
+
Network policy/cohort writes must fence `membershipAuthorityState.revision`.
|
|
1622
|
+
Both the chapter and network Stripe destinations must subscribe to
|
|
1623
|
+
`invoice.payment_succeeded` before enabling the adapter. Its membership flow
|
|
1624
|
+
ignores `invoice.paid` because that event also includes out-of-band payments.
|
|
1625
|
+
The network independently verifies the event, scope, provider references and
|
|
1626
|
+
frozen amount before atomically creating a pending membership/entitlement.
|
|
1627
|
+
A chapter webhook arriving first retries without advancing paid status.
|
|
1628
|
+
Durable chapter Journey approval then obtains the network member number and
|
|
1629
|
+
active entitlement before Clerk promotion; duplicate approvals do not spend
|
|
1630
|
+
another number. Already-paid founding promises can exceed the soft target.
|
|
1631
|
+
Full renewal/refund/seat lifecycle wiring and live provider journey proof
|
|
1632
|
+
remain prerequisites for enabling this adapter in a host.
|
|
1633
|
+
|
|
1634
|
+
`drainMembershipEffects` recovers pending provider work and expires unpaid or
|
|
1635
|
+
unaccepted seats within one exact configured runtime. Pair its
|
|
1636
|
+
`deliverChapterEffect` callback with `deliverMembershipChapterEffect` and a
|
|
1637
|
+
chapter `createMembershipChapterEffectRoute({ sender, secretName, origin,
|
|
1638
|
+
readEffect: adapter.readEffect })`. The receiver reloads current network state
|
|
1639
|
+
before inviting, granting or revoking; a stale queue item cannot specify an
|
|
1640
|
+
arbitrary recipient or grant access. Missing adapters and ambiguous provider
|
|
1641
|
+
receipts remain pending. `retryChapterApprovalEffects` separately repairs
|
|
1642
|
+
durable, unfinished local approval work without starving later applicants.
|
|
1643
|
+
Neither helper installs a cron or provisions infrastructure by itself.
|
|
1644
|
+
|
|
1645
|
+
The member area exposes the authenticated purchaser's named-seat checkout
|
|
1646
|
+
only when configured. Recipient hosts pass `initialNamedSeatId` from the
|
|
1647
|
+
invitation's `seat` query parameter and an authenticated `namedSeatClaimApi`
|
|
1648
|
+
to `JoinIsland`; the recipient completes their own application and acceptance
|
|
1649
|
+
without a second subscription checkout. A primary cancellation also governs
|
|
1650
|
+
seat renewal; buying a seat does not restore canceled auto-renewal.
|
|
1651
|
+
|
|
1652
|
+
Delivery logs distinguish sent, logged-only, in-flight, uncertain and
|
|
1653
|
+
retry-approved outcomes. The authenticated operator resolution route
|
|
1654
|
+
`/api/admin/email/log/:id/resolve` records evidence against the exact attempt;
|
|
1655
|
+
it never sends mail. Confirmed non-delivery permits only the original workflow
|
|
1656
|
+
to retry its frozen payload. Non-production recipient redirection still applies.
|
|
1508
1657
|
|
|
1509
1658
|
### Install + scope notes
|
|
1510
1659
|
|
|
1511
|
-
- **Bootstrap the first admin according to the selected auth source.**
|
|
1512
|
-
|
|
1513
|
-
`
|
|
1514
|
-
|
|
1515
|
-
app-scoped role on Database → Users in odla Studio
|
|
1516
|
-
|
|
1660
|
+
- **Bootstrap the first admin according to the selected auth source.** Prefer
|
|
1661
|
+
`auth: { source: "clerk", superAdminSource: "odla" }`: the backend resolves
|
|
1662
|
+
`private_metadata.role` on each authorization check, ignores public/session
|
|
1663
|
+
roles, and fails closed if Clerk is unavailable. Grant the operator's
|
|
1664
|
+
app-scoped protected role on Database → Users in odla Studio, bound to the
|
|
1665
|
+
exact Clerk subject ID (not email). This protected grant is separate from
|
|
1666
|
+
their private Clerk `admin` role and is never imported from legacy metadata.
|
|
1667
|
+
Existing Chapter sites retain claim/public-role defaults until an explicit,
|
|
1668
|
+
coordinated migration; do not clear public roles while a peer runtime still
|
|
1669
|
+
depends on them. Hub mode defaults to table auth:
|
|
1517
1670
|
add the first operator's lowercase
|
|
1518
1671
|
email to `admins` in Studio. Neither allowlist is ever written by a worker
|
|
1519
1672
|
route or provisioning seed; that is deliberate, so the running app cannot
|
|
@@ -1541,9 +1694,17 @@ These bite silently — a smoke test won't catch them:
|
|
|
1541
1694
|
- **Roster/identity:** `GET /people` (union of `$users` + applications by email,
|
|
1542
1695
|
roles and backend-only profile signals from `clerkListUsers`),
|
|
1543
1696
|
`GET /people/access`, `POST /people/role`, `POST /crm/sync`
|
|
1544
|
-
(backfill/reproject
|
|
1697
|
+
(empty body: backfill/reproject; `{ recordId }`: repair only that person's
|
|
1698
|
+
missing application link). Targeted repair requires a unique application
|
|
1699
|
+
and CRM person match in this chapter, preserves profile and lifecycle state,
|
|
1700
|
+
and records an audited compare-and-swap update. Ambiguity fails closed. The
|
|
1701
|
+
Stage panel offers this repair when an application link is missing. Also,
|
|
1545
1702
|
`POST /clerk/private-profiles/sync?offset=0&limit=50` (bounded,
|
|
1546
1703
|
resumable migration/repair from Chapter's former public profile keys).
|
|
1704
|
+
New default chapter CRMs declare `applicationId` and `tier`; initial applicant
|
|
1705
|
+
projection preserves these links even if optional enrichment is invalid.
|
|
1706
|
+
Submission replays use the canonical stored application, and reject changed
|
|
1707
|
+
email before account provisioning, notifications, or CRM projection.
|
|
1547
1708
|
- **Pipeline/meetings:** `GET /dashboard` (flow counts, stage counts + weekly
|
|
1548
1709
|
delta, agenda, live revenue), `GET /meetings` (reconciled agenda),
|
|
1549
1710
|
`GET/PUT /scheduling`, `POST /meetings/:id/{reschedule,cancel}`,
|
|
@@ -1564,6 +1725,25 @@ These bite silently — a smoke test won't catch them:
|
|
|
1564
1725
|
}});
|
|
1565
1726
|
```
|
|
1566
1727
|
|
|
1728
|
+
Refund execution is subscription-scoped: the provider verifies the customer and
|
|
1729
|
+
the admin route's exact application/chapter/environment/runtime metadata, then
|
|
1730
|
+
resolves the subscription's first paid invoice. It never picks an arbitrary
|
|
1731
|
+
customer charge or moves to a renewal when the original charge was refunded.
|
|
1732
|
+
Modern invoice-payment records and legacy invoice charge/payment-intent fields
|
|
1733
|
+
are supported; ambiguous payments or incomplete history fail closed. Retries
|
|
1734
|
+
reuse a stable refund key and can finish cancellation even after the refund
|
|
1735
|
+
webhook updates the application. A partial `charge.refunded` event is not a
|
|
1736
|
+
terminal membership or gift event. Customer-only refund events are quarantined
|
|
1737
|
+
for reconciliation rather than guessed from a customer's latest application.
|
|
1738
|
+
Before reporting success, the refund command re-reads the exact charge and
|
|
1739
|
+
verifies a completed full refund, then reconciles the application and CRM.
|
|
1740
|
+
This also repairs older payments whose charge events lack application metadata;
|
|
1741
|
+
a pending refund is not reported as complete. Closed billing has no renewal date.
|
|
1742
|
+
New checkouts resolve legacy or modern InvoicePayments and verify/tag the exact
|
|
1743
|
+
PaymentIntent before exposing its client secret. Missing, foreign, or ambiguous
|
|
1744
|
+
payment evidence fails closed. Booking enforces the same payment and waiver
|
|
1745
|
+
eligibility as the signup wizard, including on direct endpoint requests.
|
|
1746
|
+
|
|
1567
1747
|
The escalation rules (super-admin tier, self-demotion lockout) are **not** seams
|
|
1568
1748
|
— `canChangeRole` package-enforces them so a site can't weaken them. `approve`
|
|
1569
1749
|
is the caller `onboardingInvite` was missing.
|
|
@@ -1573,18 +1753,27 @@ These bite silently — a smoke test won't catch them:
|
|
|
1573
1753
|
the odla→Clerk write half: `clerkGetUserByEmail`, `clerkGetUser`,
|
|
1574
1754
|
`clerkListUsers` (auto-paginated — never a silent 100-user cap),
|
|
1575
1755
|
`clerkSetRole`, `updateClerkUserMetadata`, and
|
|
1576
|
-
`updateClerkUserMetadataByEmail`.
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1756
|
+
`updateClerkUserMetadataByEmail`. Role helpers accept a final metadata source
|
|
1757
|
+
(`"private"` or legacy `"public"`, after the injectable `fetch`); use
|
|
1758
|
+
`clerkRoleMetadata(chapter.auth)` so readers and writers agree. An absent
|
|
1759
|
+
selected role reads as `"provisional"`. Private role writes merge only that
|
|
1760
|
+
key and remove the legacy public role, preserving private profile data.
|
|
1761
|
+
Helpers use the effective vault `clerk_secret_key`; no extra credential is
|
|
1762
|
+
needed. `planPrivateRoleMigration` is a dev-only, dry-run preflight requiring
|
|
1763
|
+
the complete active runtime inventory, paused role writers, compatible builds,
|
|
1764
|
+
and an existing protected operator grant. It rejects conflicting roles and
|
|
1765
|
+
never grants privileges or writes data. Follow the adoption runbook for
|
|
1766
|
+
coordinated migration and same-session revocation tests.
|
|
1581
1767
|
|
|
1582
1768
|
### Multi-site acceptance tests
|
|
1583
1769
|
|
|
1584
1770
|
`@odla-ai/chapter/testing` exports a schema-validated manifest, scenario planner,
|
|
1585
1771
|
public join-contract probe, serial suite runner, and text report formatter.
|
|
1586
|
-
Declare every logical chapter
|
|
1587
|
-
|
|
1772
|
+
Declare every logical chapter in the reviewed deployment scope and its exact
|
|
1773
|
+
developer runtime. Another developer's authoritative sandbox is not implicitly
|
|
1774
|
+
a deployment prerequisite or a target for writes. Keep sibling-runtime isolation
|
|
1775
|
+
tests without requiring deployment access to those siblings. Runtime identity is
|
|
1776
|
+
separate from Stripe test/live mode.
|
|
1588
1777
|
|
|
1589
1778
|
- `probeChapterJoinContracts(manifest)` reads deployed health and catalog routes.
|
|
1590
1779
|
It compares effective app/chapter/data-environment/runtime health headers with
|
|
@@ -1606,6 +1795,29 @@ manifest; runtime identity is separate from Stripe test/live mode.
|
|
|
1606
1795
|
- Evidence is copied only after validation. Extra fields and rejected payloads
|
|
1607
1796
|
are discarded; check labels must contain no personal data, and references must
|
|
1608
1797
|
be opaque provider IDs or digests, never client secrets or raw provider objects.
|
|
1798
|
+
- `assessChapterReleaseEvidence(plan, proofs)` validates a separately reviewed
|
|
1799
|
+
required-proof contract against exact source commits and dev Worker versions.
|
|
1800
|
+
Missing features must remain requirements even if current feature flags are
|
|
1801
|
+
off. Missing/duplicate proofs, wrong execution phases or runtimes, failed
|
|
1802
|
+
assertions, absent provider references, and incomplete cleanup block review.
|
|
1803
|
+
This checks evidence completeness, not whether a driver actually called a
|
|
1804
|
+
provider; real driver execution and inspectable artifacts remain mandatory.
|
|
1805
|
+
A complete result is only `readyForReview`; `productionAuthorized` is always
|
|
1806
|
+
false, and this API never deploys anything.
|
|
1807
|
+
|
|
1808
|
+
Host routes can reuse `resolveNetworkTargetTransport` from the Worker entry
|
|
1809
|
+
for public discovery as well as signed delivery. A declared service binding is
|
|
1810
|
+
authoritative: missing or invalid bindings throw `NetworkTargetTransportError`
|
|
1811
|
+
instead of falling back to a public URL. Hosts remain responsible for selecting
|
|
1812
|
+
the reviewed runtime/origin and bounding response size and duration.
|
|
1813
|
+
|
|
1814
|
+
`ChapterAdmin.renderWorkspaceFrame` replaces packaged workspace geometry with
|
|
1815
|
+
a host-owned frame. It receives the already-composed active content, workspace
|
|
1816
|
+
list (including optional icons), current account and native route/navigation
|
|
1817
|
+
helpers. Custom headers remain outside the frame; auth and active-only mounting
|
|
1818
|
+
stay in Chapter. Hosts need no per-workspace renderer wrappers or duplicate
|
|
1819
|
+
navigation registry. Without the slot, existing tab and standalone layouts stay
|
|
1820
|
+
unchanged.
|
|
1609
1821
|
|
|
1610
1822
|
### Verify from the types, not this file
|
|
1611
1823
|
|