@odla-ai/chapter 0.10.1 → 0.11.0
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 +21 -3
- package/dist/index.cjs +12 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +17 -3
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +7 -0
- package/dist/worker/index.d.ts +7 -0
- package/dist/worker/index.js +17 -3
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,9 +165,20 @@ These bite silently — a smoke test won't catch them:
|
|
|
165
165
|
- **Disclaimer acknowledgement.** A truthy `disclaimerAck` on the submit body
|
|
166
166
|
(boolean or the string a plain form posts) stamps `disclaimerAckAt` from the
|
|
167
167
|
*server* clock; no ack leaves the attr absent, and a client-supplied
|
|
168
|
-
`disclaimerAckAt` is ignored.
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
`disclaimerAckAt` is ignored.
|
|
169
|
+
|
|
170
|
+
Unlike most data loss this is **unreconstructible** — you cannot later
|
|
171
|
+
determine whether someone checked a box — so the submit response always echoes
|
|
172
|
+
`disclaimerAckAt` (a number, or `null` when nothing was recorded). Assert it in
|
|
173
|
+
one integration test and a join page that quietly stops posting the flag can't
|
|
174
|
+
go unnoticed.
|
|
175
|
+
|
|
176
|
+
**If the disclaimer is a compliance record, set
|
|
177
|
+
`application: { requireDisclaimerAck: true }`** and a submit with no ack is a
|
|
178
|
+
400 instead of a row with no consent. It defaults to `false` so an upgrade
|
|
179
|
+
never starts rejecting traffic; enabling it fails *deterministically* on your
|
|
180
|
+
first test submit, not intermittently in production. Expect this to default to
|
|
181
|
+
`true` at 1.0.
|
|
171
182
|
- **CRM projection points.** chapter projects the person on application submit
|
|
172
183
|
(`projectApplicant`), not on booking or on webhook status change. If you mirror
|
|
173
184
|
pipeline stage into the CRM, keep those routes.
|
|
@@ -177,6 +188,13 @@ These bite silently — a smoke test won't catch them:
|
|
|
177
188
|
|
|
178
189
|
### Install + scope notes
|
|
179
190
|
|
|
191
|
+
- **Your first admin is seeded in odla Studio, by hand — and the email must be
|
|
192
|
+
lowercased.** Neither `admins` nor `superAdmins` is ever written by a worker
|
|
193
|
+
route or a provisioning seed; that is deliberate, so nothing running in the app
|
|
194
|
+
(or injected into a page) can grant admin. The gate looks the email up
|
|
195
|
+
lowercased, so a row saved as `Ada@Example.com` matches nothing and the account
|
|
196
|
+
silently isn't an admin — with no error to tell you. Save it lowercase, then
|
|
197
|
+
confirm by signing in, not by looking at the row.
|
|
180
198
|
- **`@odla-ai/auth-clerk` is not a chapter peer.** It is deliberately absent from
|
|
181
199
|
this package's manifest, because only one entry imports it: the worker verifies
|
|
182
200
|
JWTs with `jose` via `ctx.verifyUser`, and `@odla-ai/chapter/ui/member` is
|
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
findApplicationRef: () => findApplicationRef,
|
|
46
46
|
firstPaymentPatch: () => firstPaymentPatch,
|
|
47
47
|
getVaultSecret: () => getVaultSecret,
|
|
48
|
+
hasDisclaimerAck: () => hasDisclaimerAck,
|
|
48
49
|
introIdempotencyKey: () => introIdempotencyKey,
|
|
49
50
|
isAdminRole: () => isAdminRole,
|
|
50
51
|
isAlreadySent: () => isAlreadySent,
|
|
@@ -486,9 +487,13 @@ function resolveApplication(a) {
|
|
|
486
487
|
optional,
|
|
487
488
|
maxLen: a?.maxLen ?? {},
|
|
488
489
|
defaultMaxLen: a?.defaultMaxLen ?? 2e3,
|
|
489
|
-
bodyCap: a?.bodyCap ?? 32768
|
|
490
|
+
bodyCap: a?.bodyCap ?? 32768,
|
|
491
|
+
requireDisclaimerAck: a?.requireDisclaimerAck ?? false
|
|
490
492
|
};
|
|
491
493
|
}
|
|
494
|
+
function hasDisclaimerAck(fields) {
|
|
495
|
+
return fields.disclaimerAck === true || fields.disclaimerAck === "true";
|
|
496
|
+
}
|
|
492
497
|
var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["email", "firstName", "lastName"]);
|
|
493
498
|
function applicantProfile(chapter, fields) {
|
|
494
499
|
const profile = {};
|
|
@@ -511,6 +516,10 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
511
516
|
const cap = app.maxLen[f] ?? app.defaultMaxLen;
|
|
512
517
|
if (typeof v === "string" && v.length > cap) return { ok: false, error: `${f} exceeds ${cap} characters` };
|
|
513
518
|
}
|
|
519
|
+
const acked = hasDisclaimerAck(fields);
|
|
520
|
+
if (app.requireDisclaimerAck && !acked) {
|
|
521
|
+
return { ok: false, error: "disclaimerAck is required" };
|
|
522
|
+
}
|
|
514
523
|
const id2 = opts.newId();
|
|
515
524
|
const row = { id: id2, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
516
525
|
for (const f of [...app.required, ...app.optional]) {
|
|
@@ -518,12 +527,12 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
518
527
|
}
|
|
519
528
|
if (fields.focus !== void 0) row.focus = fields.focus;
|
|
520
529
|
if (opts.groupId) row.groupId = opts.groupId;
|
|
521
|
-
if (
|
|
530
|
+
if (acked) row.disclaimerAckAt = opts.now;
|
|
522
531
|
const { duplicate } = await db.transact(
|
|
523
532
|
[{ t: "update", ns: "applications", id: id2, attrs: row }],
|
|
524
533
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
525
534
|
);
|
|
526
|
-
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial };
|
|
535
|
+
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
527
536
|
}
|
|
528
537
|
function joinConfig(group, paymentsReady2) {
|
|
529
538
|
return {
|