create-ailk 0.18.0 → 0.19.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/LICENSE +6 -5
- package/NOTICE +2 -0
- package/dist/add-module.js +4 -1
- package/dist/category-profile.d.ts +10 -0
- package/dist/category-profile.js +21 -0
- package/dist/derive-architecture.d.ts +5 -1
- package/dist/derive-architecture.js +8 -3
- package/dist/lib/license-key-files.d.ts +6 -3
- package/dist/lib/license-key-files.js +8 -2
- package/dist/lib/scaffold-render-sweep.d.ts +9 -0
- package/dist/lib/scaffold-render-sweep.js +24 -3
- package/dist/license-set.d.ts +1 -1
- package/dist/license-set.js +13 -8
- package/dist/module-architecture.js +119 -0
- package/dist/programmatic.js +3 -0
- package/dist/spoke-checkout.d.ts +11 -3
- package/dist/spoke-checkout.js +62 -12
- package/dist/starter-grid.d.ts +1 -1
- package/dist/surfaces.js +140 -1
- package/dist/template-manifest.d.ts +4 -3
- package/dist/topology.d.ts +7 -2
- package/dist/topology.js +60 -3
- package/package.json +5 -4
- package/templates/.env.example +3 -3
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/apps/api/CLAUDE.md +25 -7
- package/templates/apps/api/LICENSE +6 -5
- package/templates/apps/api/NOTICE +2 -0
- package/templates/apps/api/jest.config.cjs +4 -29
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +9 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +38 -0
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +5 -4
- package/templates/apps/api/src/lib/stripe.ts +81 -0
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +266 -39
- package/templates/apps/api/src/middleware/auth.ts +206 -46
- package/templates/apps/api/src/routes/leads/index.ts +4 -0
- package/templates/apps/api/src/server.ts +107 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +58 -0
- package/templates/apps/api/src/services/lead-routing.ts +30 -2
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/.env.example +10 -0
- package/templates/apps/mcp/CLAUDE.md +9 -4
- package/templates/apps/mcp/LICENSE +6 -5
- package/templates/apps/mcp/NOTICE +2 -0
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +198 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +15 -3
- package/templates/apps/mcp/__tests__/boot.test.ts +78 -1
- package/templates/apps/mcp/__tests__/http-transport.test.ts +291 -2
- package/templates/apps/mcp/__tests__/request_callback.test.ts +69 -7
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +69 -8
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +69 -7
- package/templates/apps/mcp/jest.config.cjs +3 -44
- package/templates/apps/mcp/src/abuse/index.ts +117 -21
- package/templates/apps/mcp/src/boot.ts +5 -23
- package/templates/apps/mcp/src/http-transport.ts +93 -73
- package/templates/apps/mcp/src/index.ts +2 -0
- package/templates/apps/mcp/src/request-context.ts +52 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +9 -1
- package/templates/apps/mcp/src/tools/request_callback.ts +9 -1
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +9 -1
- package/templates/apps/web/LICENSE +6 -5
- package/templates/apps/web/NOTICE +2 -0
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +288 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +568 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +272 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +11 -13
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +115 -16
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +305 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-popups.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +126 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +163 -72
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +451 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/security.test.tsx +139 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +20 -390
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/support.test.tsx +16 -11
- package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +1 -1
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +114 -50
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +24 -45
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +253 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +145 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +100 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +179 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +514 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +288 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +123 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +177 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing-source.ts +147 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/request-quote.tsx +229 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/schedule-service.tsx +276 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/send-message.tsx +128 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/billing-anchor.tsx +27 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/deletion-request.tsx +132 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +131 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/profile-form.tsx +104 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/profile-source.ts +73 -0
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +18 -8
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +4 -6
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +4 -1
- package/templates/apps/web/app/[locale]/(authed)/account/new-actions.ts +219 -0
- package/templates/apps/web/app/[locale]/(authed)/account/new-menu.tsx +340 -0
- package/templates/apps/web/app/[locale]/(authed)/account/new-popups.tsx +107 -0
- package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +57 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +85 -26
- package/templates/apps/web/app/[locale]/(authed)/account/profile-actions.ts +116 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/page.tsx +80 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +13 -354
- package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +108 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-in-method.ts +37 -0
- package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +9 -10
- package/templates/apps/web/app/[locale]/layout.tsx +23 -4
- package/templates/apps/web/app/api/auth/hub/__tests__/continuation.test.ts +143 -0
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +62 -10
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +22 -7
- package/templates/apps/web/app/api/auth/hub/continuation.ts +73 -0
- package/templates/apps/web/app/api/auth/hub/continue/[ref]/__tests__/route.test.ts +188 -0
- package/templates/apps/web/app/api/auth/hub/continue/[ref]/route.ts +53 -0
- package/templates/apps/web/app/robots.txt/route.ts +10 -4
- package/templates/apps/web/auth.ts +5 -1
- package/templates/apps/web/lib/__tests__/account-config.test.ts +258 -46
- package/templates/apps/web/lib/account-config.ts +188 -52
- package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +143 -0
- package/templates/apps/web/lib/hub-session/index.ts +1 -0
- package/templates/apps/web/lib/hub-session/reseal.ts +90 -0
- package/templates/apps/web/lib/hub-session/seal.ts +13 -9
- package/templates/apps/web/lib/topology.generated.ts +24 -0
- package/templates/apps/web/messages/en.json +156 -35
- package/templates/database/CHANGELOG.md +21 -0
- package/templates/database/LICENSE +6 -5
- package/templates/database/NOTICE +2 -0
- package/templates/database/README.md +1 -1
- package/templates/database/__tests__/member-area.db.test.ts +15 -3
- package/templates/database/__tests__/member-contract-migration.test.ts +85 -0
- package/templates/database/__tests__/member-contract.db.test.ts +413 -0
- package/templates/database/__tests__/seed.test.ts +54 -2
- package/templates/database/inbox/schema.prisma +90 -15
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +192 -0
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +20 -0
- package/templates/database/package.json +3 -2
- package/templates/database/seed.ts +34 -2
- package/versions.json +36 -36
- package/templates/apps/api/src/__tests__/content-write-production-boot.test.ts +0 -104
- package/templates/apps/api/src/__tests__/cors.test.ts +0 -276
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +0 -279
- package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +0 -122
- package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +0 -167
- package/templates/apps/api/src/__tests__/root-modules.test.ts +0 -175
- package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +0 -117
- package/templates/apps/api/src/__tests__/server.test.ts +0 -591
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +0 -175
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +0 -293
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +0 -1620
- package/templates/apps/api/src/routes/project-listings/__tests__/directory.test.ts +0 -528
- package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +0 -198
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +0 -652
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -408
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +0 -476
- package/templates/apps/mcp/__tests__/audit.test.ts +0 -346
- package/templates/apps/mcp/__tests__/parity.test.ts +0 -325
- package/templates/apps/mcp/__tests__/workspace-surface.test.ts +0 -364
- package/templates/apps/web/app/[locale]/(authed)/account/settings/color-mode.tsx +0 -68
- package/templates/apps/web/app/[locale]/(authed)/account/settings/deletion-request.tsx +0 -55
- package/templates/apps/web/app/[locale]/(authed)/account/settings/name-form.tsx +0 -118
- package/templates/database/__tests__/build.test.ts +0 -131
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
-- #8168 — the member area data model (spec
|
|
2
|
+
-- docs/specs/8168-member-area-data-model-spec.md D1–D3, D5, D7–D9, D12).
|
|
3
|
+
--
|
|
4
|
+
-- piece_versions (inbox) — one delivery of a piece to its client:
|
|
5
|
+
-- a private library file OR an https
|
|
6
|
+
-- link, numbered from 1 per piece, with
|
|
7
|
+
-- the client's review recorded on it.
|
|
8
|
+
-- NO round count, limit or remaining
|
|
9
|
+
-- column (#8140 disposition 3).
|
|
10
|
+
-- pieces.approved_at — set with an `approved` review; freezes
|
|
11
|
+
-- review (D5).
|
|
12
|
+
-- pieces_id_person_id_workspace_id_key — a unique index over a triple the
|
|
13
|
+
-- PK already makes unique: the target of
|
|
14
|
+
-- piece_versions_piece_fk, so "a piece of
|
|
15
|
+
-- THIS Person in THIS workspace" is one
|
|
16
|
+
-- database fact (D1).
|
|
17
|
+
-- interactions.kind / .payload — `message` · `quote` · `review`, and the
|
|
18
|
+
-- stored payload of a quote or review;
|
|
19
|
+
-- `inbound` joins `outbound` (D3). An
|
|
20
|
+
-- outbound row stays a payload-free
|
|
21
|
+
-- `message` — #7877's rows, unchanged.
|
|
22
|
+
-- flow_definitions.customers_enabled — whether a signed-in client may submit
|
|
23
|
+
-- the magnet from the member area (D7).
|
|
24
|
+
-- the `request-quote` magnet — one draft `contact` magnet with
|
|
25
|
+
-- customers_enabled = true, backfilled
|
|
26
|
+
-- into EVERY existing workspace (D8).
|
|
27
|
+
--
|
|
28
|
+
-- The only new foreign key stays inside the inbox namespace (ADR 0009
|
|
29
|
+
-- invariant 1). asset_id and delivered_by name rows of other namespaces:
|
|
30
|
+
-- plain columns with format CHECKs, no foreign key. The backfill reads
|
|
31
|
+
-- "workspaces" (auth namespace) in SQL only — no relation.
|
|
32
|
+
--
|
|
33
|
+
-- ADDITIVE: every ADD COLUMN is nullable or carries a constant default, so no
|
|
34
|
+
-- existing row fails a new CHECK — every existing interaction is an outbound
|
|
35
|
+
-- `message` with no payload, which the replaced and added CHECKs admit
|
|
36
|
+
-- (interactions_body_length keeps its 1–10000 rule for a `message`). The
|
|
37
|
+
-- unique index on "pieces" builds under the file's transaction, holding a
|
|
38
|
+
-- SHARE lock on "pieces" for one scan of a table that is a few rows per
|
|
39
|
+
-- client. The backfill inserts one row per workspace and NEVER overwrites a
|
|
40
|
+
-- workspace's existing (site_id, slug) row (ON CONFLICT DO NOTHING) —
|
|
41
|
+
-- re-running the block adds nothing.
|
|
42
|
+
--
|
|
43
|
+
-- Rollout: deploy this migration BEFORE any code that writes piece_versions,
|
|
44
|
+
-- pieces.approved_at, an inbound interaction, interactions.kind/payload or
|
|
45
|
+
-- flow_definitions.customers_enabled. Code written before it never names the
|
|
46
|
+
-- new columns, so it keeps working against the migrated database.
|
|
47
|
+
--
|
|
48
|
+
-- Rollback (safe while nothing but #8168's siblings use the new surface):
|
|
49
|
+
-- DELETE FROM "flow_definitions" WHERE "slug" = 'request-quote' AND "status" = 'draft' AND "customers_enabled";
|
|
50
|
+
-- ALTER TABLE "flow_definitions" DROP COLUMN "customers_enabled";
|
|
51
|
+
-- DELETE FROM "interactions" WHERE "direction" = 'inbound';
|
|
52
|
+
-- ALTER TABLE "interactions" DROP CONSTRAINT "interactions_kind_allowed",
|
|
53
|
+
-- DROP CONSTRAINT "interactions_outbound_shape", DROP CONSTRAINT "interactions_payload_by_kind",
|
|
54
|
+
-- DROP CONSTRAINT "interactions_payload_size", DROP CONSTRAINT "interactions_body_length",
|
|
55
|
+
-- DROP CONSTRAINT "interactions_direction_allowed";
|
|
56
|
+
-- ALTER TABLE "interactions" DROP COLUMN "kind", DROP COLUMN "payload";
|
|
57
|
+
-- then re-add #7877's interactions_direction_allowed (direction = 'outbound')
|
|
58
|
+
-- and interactions_body_length (1–10000 once trimmed) from
|
|
59
|
+
-- 20261011120000_member_area;
|
|
60
|
+
-- DROP TABLE "piece_versions"; DROP INDEX "pieces_id_person_id_workspace_id_key";
|
|
61
|
+
-- ALTER TABLE "pieces" DROP COLUMN "approved_at";
|
|
62
|
+
-- The first DELETE also removes an operator's own edits to a still-draft
|
|
63
|
+
-- seeded magnet; check for those before rolling back.
|
|
64
|
+
|
|
65
|
+
-- AlterTable
|
|
66
|
+
ALTER TABLE "flow_definitions" ADD COLUMN "customers_enabled" BOOLEAN NOT NULL DEFAULT false;
|
|
67
|
+
|
|
68
|
+
-- AlterTable
|
|
69
|
+
-- Existing rows and #7877's writer (which names no kind) stay `message`.
|
|
70
|
+
ALTER TABLE "interactions" ADD COLUMN "kind" VARCHAR(16) NOT NULL DEFAULT 'message',
|
|
71
|
+
ADD COLUMN "payload" JSONB;
|
|
72
|
+
|
|
73
|
+
-- AlterTable
|
|
74
|
+
ALTER TABLE "pieces" ADD COLUMN "approved_at" TIMESTAMP(3);
|
|
75
|
+
|
|
76
|
+
-- CreateTable
|
|
77
|
+
CREATE TABLE "piece_versions" (
|
|
78
|
+
"id" TEXT NOT NULL DEFAULT ('pver_'::text || (uuidv7())::text),
|
|
79
|
+
"workspace_id" TEXT NOT NULL,
|
|
80
|
+
"person_id" TEXT NOT NULL,
|
|
81
|
+
"piece_id" TEXT NOT NULL,
|
|
82
|
+
"version" INTEGER NOT NULL,
|
|
83
|
+
"asset_id" TEXT,
|
|
84
|
+
"link" VARCHAR(2048),
|
|
85
|
+
"note" TEXT,
|
|
86
|
+
"delivered_by" TEXT NOT NULL,
|
|
87
|
+
"delivered_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
"review" VARCHAR(24),
|
|
89
|
+
"review_comment" TEXT,
|
|
90
|
+
"reviewed_at" TIMESTAMP(3),
|
|
91
|
+
|
|
92
|
+
CONSTRAINT "piece_versions_pkey" PRIMARY KEY ("id")
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
-- CreateIndex
|
|
96
|
+
-- A client's artifacts: workspace_id = $1 AND person_id = $2 ORDER BY delivered_at DESC.
|
|
97
|
+
CREATE INDEX "piece_versions_workspace_id_person_id_delivered_at_idx" ON "piece_versions"("workspace_id", "person_id", "delivered_at");
|
|
98
|
+
|
|
99
|
+
-- CreateIndex
|
|
100
|
+
-- The public asset read's "is this asset a version file" check (AC-MAA-21).
|
|
101
|
+
CREATE INDEX "piece_versions_asset_id_idx" ON "piece_versions"("asset_id");
|
|
102
|
+
|
|
103
|
+
-- CreateIndex
|
|
104
|
+
-- A version number is a fact per piece; the writer numbers, this is the backstop.
|
|
105
|
+
CREATE UNIQUE INDEX "piece_versions_piece_id_version_key" ON "piece_versions"("piece_id", "version");
|
|
106
|
+
|
|
107
|
+
-- CreateIndex
|
|
108
|
+
-- Redundant with the PK; piece_versions_piece_fk needs it as its target.
|
|
109
|
+
CREATE UNIQUE INDEX "pieces_id_person_id_workspace_id_key" ON "pieces"("id", "person_id", "workspace_id");
|
|
110
|
+
|
|
111
|
+
-- AddForeignKey
|
|
112
|
+
-- A version names a piece of THIS Person in THIS workspace (the piece's own
|
|
113
|
+
-- FK already ties it to a Person of its workspace); deleting a piece with a
|
|
114
|
+
-- version is refused.
|
|
115
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_piece_fk" FOREIGN KEY ("piece_id", "person_id", "workspace_id") REFERENCES "pieces"("id", "person_id", "workspace_id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
116
|
+
|
|
117
|
+
-- ADR-0031 rule 3 — piece_versions: pver_ (registered in
|
|
118
|
+
-- packages/ids/src/registry.ts; this migration's path is added to
|
|
119
|
+
-- migration-sync.test.ts's PK_CHECK_MIGRATIONS). Each reference column
|
|
120
|
+
-- carries the format CHECK of the entity it names, in the #7877 shape.
|
|
121
|
+
ALTER TABLE "piece_versions" ALTER COLUMN "id" TYPE text COLLATE "C";
|
|
122
|
+
ALTER TABLE "piece_versions" ALTER COLUMN "id" SET DEFAULT ('pver_'::text || (uuidv7())::text);
|
|
123
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_id_format" CHECK ("id" ~ '^pver_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
124
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_workspace_id_format" CHECK ("workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
125
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_person_id_format" CHECK ("person_id" ~ '^person_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
126
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_piece_id_format" CHECK ("piece_id" ~ '^piece_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
127
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_asset_id_format" CHECK ("asset_id" ~ '^asset_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
128
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_delivered_by_format" CHECK ("delivered_by" ~ '^user_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
129
|
+
|
|
130
|
+
-- D2 — a version's shape is held here, not by the writer alone.
|
|
131
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_version_positive"
|
|
132
|
+
CHECK ("version" >= 1);
|
|
133
|
+
-- A delivery is a file or a link: exactly one.
|
|
134
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_one_target"
|
|
135
|
+
CHECK (("asset_id" IS NULL) <> ("link" IS NULL));
|
|
136
|
+
-- An https scheme, a host with no userinfo, no whitespace anywhere — the
|
|
137
|
+
-- database floor under packages/validation's isHttpsLink (SEC-8168-5).
|
|
138
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_link_https"
|
|
139
|
+
CHECK ("link" IS NULL OR ("link" ~ '^https://[^[:space:]/?#@]+([/?#][^[:space:]]*)?$' AND char_length("link") <= 2048));
|
|
140
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_note_length"
|
|
141
|
+
CHECK ("note" IS NULL OR char_length(btrim("note")) BETWEEN 1 AND 2000);
|
|
142
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_review_allowed"
|
|
143
|
+
CHECK ("review" IS NULL OR "review" IN ('approved', 'changes-requested'));
|
|
144
|
+
-- A review and its timestamp are set together or not at all.
|
|
145
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_review_stamped"
|
|
146
|
+
CHECK (("review" IS NULL) = ("reviewed_at" IS NULL));
|
|
147
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_comment_needs_review"
|
|
148
|
+
CHECK ("review_comment" IS NULL OR "review" IS NOT NULL);
|
|
149
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_changes_need_comment"
|
|
150
|
+
CHECK ("review" IS DISTINCT FROM 'changes-requested' OR char_length(btrim(coalesce("review_comment", ''))) >= 1);
|
|
151
|
+
ALTER TABLE "piece_versions" ADD CONSTRAINT "piece_versions_comment_length"
|
|
152
|
+
CHECK ("review_comment" IS NULL OR char_length("review_comment") <= 5000);
|
|
153
|
+
|
|
154
|
+
-- D3 — the second direction and the kinds. Replaced IN PLACE under the same
|
|
155
|
+
-- names, so #7877's tests keep naming the same constraints.
|
|
156
|
+
ALTER TABLE "interactions" DROP CONSTRAINT "interactions_direction_allowed";
|
|
157
|
+
ALTER TABLE "interactions" ADD CONSTRAINT "interactions_direction_allowed"
|
|
158
|
+
CHECK ("direction" IN ('outbound', 'inbound'));
|
|
159
|
+
ALTER TABLE "interactions" ADD CONSTRAINT "interactions_kind_allowed"
|
|
160
|
+
CHECK ("kind" IN ('message', 'quote', 'review'));
|
|
161
|
+
-- An outbound row is a payload-free `message` — #7877's rows, unchanged.
|
|
162
|
+
ALTER TABLE "interactions" ADD CONSTRAINT "interactions_outbound_shape"
|
|
163
|
+
CHECK ("direction" <> 'outbound' OR ("kind" = 'message' AND "payload" IS NULL));
|
|
164
|
+
-- A quote or a review always carries its stored payload.
|
|
165
|
+
ALTER TABLE "interactions" ADD CONSTRAINT "interactions_payload_by_kind"
|
|
166
|
+
CHECK ("kind" = 'message' OR "payload" IS NOT NULL);
|
|
167
|
+
-- 64 items of a 200-character label and a 2000-character value fit with room (SEC-8168-4).
|
|
168
|
+
ALTER TABLE "interactions" ADD CONSTRAINT "interactions_payload_size"
|
|
169
|
+
CHECK ("payload" IS NULL OR octet_length("payload"::text) <= 262144);
|
|
170
|
+
-- A message body stays 1–10000 characters once trimmed; a quote or review
|
|
171
|
+
-- body may be empty; no body exceeds 10000.
|
|
172
|
+
ALTER TABLE "interactions" DROP CONSTRAINT "interactions_body_length";
|
|
173
|
+
ALTER TABLE "interactions" ADD CONSTRAINT "interactions_body_length"
|
|
174
|
+
CHECK (
|
|
175
|
+
char_length("body") <= 10000
|
|
176
|
+
AND ("kind" <> 'message' OR char_length(btrim("body")) BETWEEN 1 AND 10000)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
-- D8/D9 — the request-quote magnet in every existing workspace. The config
|
|
180
|
+
-- literal below is test-held to `requestQuoteFlowConfig`
|
|
181
|
+
-- (packages/validation/src/schemas/flow.ts) by
|
|
182
|
+
-- database/__tests__/member-contract-migration.test.ts, and the block is
|
|
183
|
+
-- re-executed verbatim by member-contract.db.test.ts to prove it idempotent.
|
|
184
|
+
-- Do not edit one without the other.
|
|
185
|
+
-- BEGIN request-quote backfill
|
|
186
|
+
INSERT INTO "flow_definitions" ("site_id", "workspace_id", "slug", "config", "kind", "title", "status", "customers_enabled", "updated_at")
|
|
187
|
+
SELECT w."id", w."id", 'request-quote',
|
|
188
|
+
'{"slug":"request-quote","defaultLocale":"en","steps":[{"key":"request","type":"field","copy":{"title":{"en":"Request a quote"}},"fields":[{"name":"need","inputType":"textarea","required":true,"copy":{"label":{"en":"What do you need?"}}},{"name":"timing","inputType":"text","copy":{"label":{"en":"When do you need it?"}}},{"name":"budget","inputType":"text","copy":{"label":{"en":"Budget range"},"help":{"en":"A rough range is fine."}}},{"name":"link","inputType":"url","copy":{"label":{"en":"Link"},"help":{"en":"A brief, a document or an example. Must start with https://."}}}]}]}'::jsonb,
|
|
189
|
+
'contact', 'Request a quote', 'draft', true, CURRENT_TIMESTAMP
|
|
190
|
+
FROM "workspaces" w
|
|
191
|
+
ON CONFLICT ("site_id", "slug") DO NOTHING;
|
|
192
|
+
-- END request-quote backfill
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- #8185 — record, at the moment the hub writes it, that a lead is a spoke's
|
|
2
|
+
-- live paid checkout (spec docs/specs/8185-spoke-checkout-purchases-spec.md
|
|
3
|
+
-- D1, D6, D10).
|
|
4
|
+
--
|
|
5
|
+
-- leads.spoke_checkout — set true by apps/api/src/services/lead-routing.ts
|
|
6
|
+
-- only for a spoke-keyed, unrelayed submit passing
|
|
7
|
+
-- the #7877 checkout plan rule's predicate. Written
|
|
8
|
+
-- at creation, never updated. NOT NULL with a
|
|
9
|
+
-- constant default, so existing rows read false and
|
|
10
|
+
-- nothing is rewritten; they are deliberately NOT
|
|
11
|
+
-- backfilled (D6: no stored fact proves an older
|
|
12
|
+
-- lead's provenance).
|
|
13
|
+
--
|
|
14
|
+
-- Rollout order (D10): apply this migration to a deployment's database BEFORE
|
|
15
|
+
-- deploying the API that writes the column. The old API is unaffected by it;
|
|
16
|
+
-- the new API's marked create fails without it.
|
|
17
|
+
--
|
|
18
|
+
-- Rollback: ALTER TABLE "leads" DROP COLUMN "spoke_checkout";
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "leads" ADD COLUMN "spoke_checkout" BOOLEAN NOT NULL DEFAULT false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@working-theory/database",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"inbox",
|
|
17
17
|
"ops",
|
|
18
18
|
"tenancy.ts",
|
|
19
|
-
"prisma.config.ts"
|
|
19
|
+
"prisma.config.ts",
|
|
20
|
+
"NOTICE"
|
|
20
21
|
],
|
|
21
22
|
"description": "AI Launch Kit database schema (single-tenant Prisma)",
|
|
22
23
|
"type": "module",
|
|
@@ -25,6 +25,12 @@ import "dotenv/config";
|
|
|
25
25
|
|
|
26
26
|
import { basename } from "node:path";
|
|
27
27
|
|
|
28
|
+
import {
|
|
29
|
+
REQUEST_QUOTE_FLOW_SLUG,
|
|
30
|
+
REQUEST_QUOTE_FLOW_TITLE,
|
|
31
|
+
requestQuoteFlowConfig,
|
|
32
|
+
} from "@working-theory/validation";
|
|
33
|
+
|
|
28
34
|
import type { PrismaClient } from "./generated/client/client.js";
|
|
29
35
|
import {
|
|
30
36
|
DEFAULT_ORG_ID,
|
|
@@ -47,14 +53,14 @@ export {
|
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
/**
|
|
50
|
-
* The DI surface `provisionDefaultWorkspace` needs — just the
|
|
56
|
+
* The DI surface `provisionDefaultWorkspace` needs — just the three Prisma
|
|
51
57
|
* delegates it touches. A `Pick` off the real `PrismaClient` type (not a
|
|
52
58
|
* hand-rolled shape), so a real client satisfies it by construction and the
|
|
53
59
|
* DB-free unit test can pass a lightweight stub with no live Postgres.
|
|
54
60
|
*/
|
|
55
61
|
export type DefaultWorkspacePrismaClient = Pick<
|
|
56
62
|
PrismaClient,
|
|
57
|
-
"organization" | "workspace"
|
|
63
|
+
"organization" | "workspace" | "flowDefinition"
|
|
58
64
|
>;
|
|
59
65
|
|
|
60
66
|
/**
|
|
@@ -62,6 +68,12 @@ export type DefaultWorkspacePrismaClient = Pick<
|
|
|
62
68
|
* yet; no-op (no create/upsert calls) once any Workspace is present — covering
|
|
63
69
|
* both single-tenant re-run idempotency and a multi-tenant deployment that
|
|
64
70
|
* already has real Workspaces.
|
|
71
|
+
*
|
|
72
|
+
* #8168 D8 (C4) — the default workspace also gets its `request-quote` magnet.
|
|
73
|
+
* A fresh install runs `migrate deploy` BEFORE this seed, so the migration's
|
|
74
|
+
* backfill finds no workspace and seeds nothing; this is where the default
|
|
75
|
+
* workspace's magnet comes from. Insert-if-absent: an `upsert` with an empty
|
|
76
|
+
* `update`, so an existing `(site_id, slug)` row is never overwritten.
|
|
65
77
|
*/
|
|
66
78
|
export async function provisionDefaultWorkspace(
|
|
67
79
|
prisma: DefaultWorkspacePrismaClient,
|
|
@@ -97,6 +109,26 @@ export async function provisionDefaultWorkspace(
|
|
|
97
109
|
update: {},
|
|
98
110
|
});
|
|
99
111
|
|
|
112
|
+
await prisma.flowDefinition.upsert({
|
|
113
|
+
where: {
|
|
114
|
+
siteId_slug: {
|
|
115
|
+
siteId: DEFAULT_WORKSPACE_ID,
|
|
116
|
+
slug: REQUEST_QUOTE_FLOW_SLUG,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
create: {
|
|
120
|
+
siteId: DEFAULT_WORKSPACE_ID,
|
|
121
|
+
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
122
|
+
slug: REQUEST_QUOTE_FLOW_SLUG,
|
|
123
|
+
config: requestQuoteFlowConfig,
|
|
124
|
+
kind: "contact",
|
|
125
|
+
title: REQUEST_QUOTE_FLOW_TITLE,
|
|
126
|
+
status: "draft",
|
|
127
|
+
customersEnabled: true,
|
|
128
|
+
},
|
|
129
|
+
update: {},
|
|
130
|
+
});
|
|
131
|
+
|
|
100
132
|
return { created: true };
|
|
101
133
|
}
|
|
102
134
|
|
package/versions.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@working-theory/admin-ui": "0.
|
|
3
|
-
"@working-theory/aeo": "0.
|
|
4
|
-
"@working-theory/agent-context": "0.
|
|
2
|
+
"@working-theory/admin-ui": "0.39.0",
|
|
3
|
+
"@working-theory/aeo": "0.39.0",
|
|
4
|
+
"@working-theory/agent-context": "0.39.0",
|
|
5
5
|
"@working-theory/agent-contract": "0.1.0",
|
|
6
6
|
"@working-theory/agent-memory": "0.0.0",
|
|
7
7
|
"@working-theory/ai": "0.1.0",
|
|
8
|
-
"@working-theory/analytics": "0.
|
|
9
|
-
"@working-theory/api-client": "0.
|
|
8
|
+
"@working-theory/analytics": "0.39.0",
|
|
9
|
+
"@working-theory/api-client": "0.39.0",
|
|
10
10
|
"@working-theory/cda-filesystem": "0.0.0",
|
|
11
|
-
"@working-theory/cli": "0.
|
|
12
|
-
"@working-theory/common": "0.
|
|
11
|
+
"@working-theory/cli": "0.39.0",
|
|
12
|
+
"@working-theory/common": "0.39.0",
|
|
13
13
|
"@working-theory/consent": "0.1.0",
|
|
14
|
-
"@working-theory/content-adapters": "0.
|
|
15
|
-
"@working-theory/content-projection": "0.
|
|
16
|
-
"@working-theory/email": "0.
|
|
14
|
+
"@working-theory/content-adapters": "0.39.0",
|
|
15
|
+
"@working-theory/content-projection": "0.39.0",
|
|
16
|
+
"@working-theory/email": "0.39.0",
|
|
17
17
|
"@working-theory/env": "0.0.0",
|
|
18
|
-
"@working-theory/eslint-config": "0.
|
|
19
|
-
"@working-theory/fumadocs-schema": "0.
|
|
20
|
-
"@working-theory/ids": "0.
|
|
21
|
-
"@working-theory/jsonld": "0.
|
|
22
|
-
"@working-theory/license": "0.
|
|
23
|
-
"@working-theory/metadata": "0.
|
|
24
|
-
"@working-theory/observability": "0.
|
|
25
|
-
"@working-theory/page-renderer": "0.
|
|
26
|
-
"@working-theory/plugin-mcp": "0.1.
|
|
27
|
-
"@working-theory/schema": "0.
|
|
28
|
-
"@working-theory/schema-pack-registry": "0.
|
|
29
|
-
"@working-theory/schema-validator": "0.
|
|
30
|
-
"@working-theory/sections-registry": "0.
|
|
31
|
-
"@working-theory/site-config": "0.
|
|
32
|
-
"@working-theory/spoke-checkout": "0.
|
|
33
|
-
"@working-theory/stripe-client": "0.
|
|
34
|
-
"@working-theory/templates": "0.
|
|
35
|
-
"@working-theory/theme": "0.
|
|
36
|
-
"@working-theory/ui": "0.
|
|
37
|
-
"@working-theory/validation": "0.
|
|
38
|
-
"@working-theory/web-i18n": "0.
|
|
18
|
+
"@working-theory/eslint-config": "0.39.0",
|
|
19
|
+
"@working-theory/fumadocs-schema": "0.39.0",
|
|
20
|
+
"@working-theory/ids": "0.12.0",
|
|
21
|
+
"@working-theory/jsonld": "0.39.0",
|
|
22
|
+
"@working-theory/license": "0.39.0",
|
|
23
|
+
"@working-theory/metadata": "0.39.0",
|
|
24
|
+
"@working-theory/observability": "0.39.0",
|
|
25
|
+
"@working-theory/page-renderer": "0.39.0",
|
|
26
|
+
"@working-theory/plugin-mcp": "0.1.3",
|
|
27
|
+
"@working-theory/schema": "0.39.0",
|
|
28
|
+
"@working-theory/schema-pack-registry": "0.39.0",
|
|
29
|
+
"@working-theory/schema-validator": "0.39.0",
|
|
30
|
+
"@working-theory/sections-registry": "0.39.0",
|
|
31
|
+
"@working-theory/site-config": "0.39.0",
|
|
32
|
+
"@working-theory/spoke-checkout": "0.5.0",
|
|
33
|
+
"@working-theory/stripe-client": "0.39.0",
|
|
34
|
+
"@working-theory/templates": "0.39.0",
|
|
35
|
+
"@working-theory/theme": "0.39.0",
|
|
36
|
+
"@working-theory/ui": "0.39.0",
|
|
37
|
+
"@working-theory/validation": "0.39.0",
|
|
38
|
+
"@working-theory/web-i18n": "0.39.0",
|
|
39
39
|
"@working-theory/billing-client": "0.0.0",
|
|
40
40
|
"@working-theory/branding": "0.0.0",
|
|
41
41
|
"@working-theory/license-client": "0.0.0",
|
|
42
42
|
"@working-theory/license-manifest": "0.0.0",
|
|
43
43
|
"@working-theory/partner-session": "0.0.0",
|
|
44
44
|
"@working-theory/preview-protocol": "0.0.0",
|
|
45
|
-
"@working-theory/ui-commerce": "0.23.
|
|
46
|
-
"@working-theory/ui-docs-blog": "0.23.
|
|
47
|
-
"@working-theory/ui-scheduling": "0.23.
|
|
48
|
-
"@working-theory/ui-signal-collectors": "0.25.
|
|
45
|
+
"@working-theory/ui-commerce": "0.23.17",
|
|
46
|
+
"@working-theory/ui-docs-blog": "0.23.17",
|
|
47
|
+
"@working-theory/ui-scheduling": "0.23.17",
|
|
48
|
+
"@working-theory/ui-signal-collectors": "0.25.6",
|
|
49
49
|
"@working-theory/web": "0.0.0",
|
|
50
50
|
"@working-theory/api": "1.0.0",
|
|
51
51
|
"@working-theory/mcp": "0.0.0",
|
|
52
|
-
"@working-theory/database": "0.
|
|
52
|
+
"@working-theory/database": "0.39.0"
|
|
53
53
|
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file content-write-production-boot.test.ts
|
|
3
|
-
* @description A production build with `content-write` enabled boots without
|
|
4
|
-
* pino-pretty (#7502).
|
|
5
|
-
*
|
|
6
|
-
* `services/revalidation.ts` is loaded only by the content-write routes, and it
|
|
7
|
-
* used to build its logger with `createLogger({ service })` alone. The
|
|
8
|
-
* observability package read a missing `environment` as development and asked
|
|
9
|
-
* pino for a `pino-pretty` transport, which a production install does not carry:
|
|
10
|
-
* pino threw `unable to determine transport target for "pino-pretty"` at module
|
|
11
|
-
* load, `build()` rejected, and every route — `/health` included — answered 500.
|
|
12
|
-
*
|
|
13
|
-
* "Without pino-pretty installed" is simulated at the pino boundary: the mock
|
|
14
|
-
* below throws pino's own error whenever a `pino-pretty` transport is requested,
|
|
15
|
-
* and passes every other call through to the real pino. pino is mocked by its
|
|
16
|
-
* resolved path because `apps/api` does not depend on it directly — only
|
|
17
|
-
* `@working-theory/observability` does.
|
|
18
|
-
*
|
|
19
|
-
* Mirrors `module-exclusion.test.ts`'s external-boundary mocks (CORS,
|
|
20
|
-
* rate-limit, email).
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { FastifyInstance } from "fastify";
|
|
24
|
-
|
|
25
|
-
jest.mock(
|
|
26
|
-
require.resolve("pino", {
|
|
27
|
-
paths: [`${__dirname}/../../../../packages/observability`],
|
|
28
|
-
}),
|
|
29
|
-
() => {
|
|
30
|
-
const realPino = jest.requireActual(
|
|
31
|
-
require.resolve("pino", {
|
|
32
|
-
paths: [`${__dirname}/../../../../packages/observability`],
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
35
|
-
const withoutPinoPretty = (
|
|
36
|
-
options?: { transport?: { target?: string } },
|
|
37
|
-
...rest: unknown[]
|
|
38
|
-
) => {
|
|
39
|
-
if (options?.transport?.target === "pino-pretty") {
|
|
40
|
-
throw new Error(
|
|
41
|
-
'unable to determine transport target for "pino-pretty"',
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return realPino(options, ...rest);
|
|
45
|
-
};
|
|
46
|
-
// pino exports itself as `default` and as `pino` too; both are replaced.
|
|
47
|
-
return Object.assign(withoutPinoPretty, realPino, {
|
|
48
|
-
default: withoutPinoPretty,
|
|
49
|
-
pino: withoutPinoPretty,
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
jest.mock("@fastify/cors", () => {
|
|
55
|
-
return jest.fn().mockResolvedValue(undefined);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
jest.mock("@fastify/rate-limit", () => {
|
|
59
|
-
return jest.fn().mockResolvedValue(undefined);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
jest.mock("@working-theory/email", () => ({
|
|
63
|
-
sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
describe("production boot with content-write enabled (#7502)", () => {
|
|
67
|
-
const prior = {
|
|
68
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
69
|
-
AILK_ENABLED_MODULES: process.env.AILK_ENABLED_MODULES,
|
|
70
|
-
};
|
|
71
|
-
let app: FastifyInstance;
|
|
72
|
-
|
|
73
|
-
beforeAll(async () => {
|
|
74
|
-
process.env.NODE_ENV = "production";
|
|
75
|
-
// content-write registers only alongside a tenancy module (server.ts).
|
|
76
|
-
process.env.AILK_ENABLED_MODULES = "multi-tenant,content-write";
|
|
77
|
-
// The config and the observability singleton read NODE_ENV at import, so
|
|
78
|
-
// the server is loaded only after the environment is set.
|
|
79
|
-
const { build } = await import("../server");
|
|
80
|
-
app = await build();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
afterAll(async () => {
|
|
84
|
-
await app?.close();
|
|
85
|
-
for (const [key, value] of Object.entries(prior)) {
|
|
86
|
-
if (value === undefined) delete process.env[key];
|
|
87
|
-
else process.env[key] = value;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("answers /health with 200", async () => {
|
|
92
|
-
const res = await app.inject({ method: "GET", url: "/health" });
|
|
93
|
-
expect(res.statusCode).toBe(200);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("registers the content-write routes, so revalidation.ts was loaded", async () => {
|
|
97
|
-
// Registered and session-less: 401, where an unregistered route is a 404.
|
|
98
|
-
const res = await app.inject({
|
|
99
|
-
method: "POST",
|
|
100
|
-
url: "/v1/workspaces/wksp_00000000-0000-7000-8000-0000000000aa/content",
|
|
101
|
-
});
|
|
102
|
-
expect(res.statusCode).toBe(401);
|
|
103
|
-
});
|
|
104
|
-
});
|