create-ailk 0.13.0 → 0.15.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 +12 -4
- package/component-catalog.json +250 -36
- package/component-catalog.md +10 -6
- package/dist/add-module.d.ts +7 -0
- package/dist/add-module.js +34 -2
- package/dist/add-route.js +18 -2
- package/dist/apply-design.d.ts +32 -2
- package/dist/apply-design.js +155 -22
- package/dist/cli.js +24 -3
- package/dist/derive-architecture.d.ts +15 -2
- package/dist/derive-architecture.js +34 -6
- package/dist/design-compile.d.ts +70 -2
- package/dist/design-compile.js +97 -1
- package/dist/design-home.d.ts +74 -0
- package/dist/design-home.js +256 -0
- package/dist/design-package.d.ts +39 -2
- package/dist/design-package.js +224 -9
- package/dist/design-presets.d.ts +17 -1
- package/dist/design-presets.js +17 -5
- package/dist/design-scaffold.d.ts +6 -25
- package/dist/design-scaffold.js +26 -74
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/lib/chrome-readers.d.ts +21 -0
- package/dist/lib/chrome-readers.js +100 -0
- package/dist/lib/design-checks.d.ts +12 -1
- package/dist/lib/design-checks.js +38 -2
- package/dist/lib/extract-module-bundle.d.ts +11 -0
- package/dist/lib/extract-module-bundle.js +36 -6
- package/dist/lib/license-key-files.d.ts +6 -1
- package/dist/lib/license-key-files.js +9 -4
- package/dist/lib/module-license-gate.d.ts +9 -0
- package/dist/lib/module-license-gate.js +18 -0
- package/dist/module-architecture.d.ts +9 -0
- package/dist/module-architecture.js +6 -0
- package/dist/parse-args.d.ts +22 -0
- package/dist/parse-args.js +73 -0
- package/dist/programmatic.d.ts +20 -0
- package/dist/programmatic.js +180 -35
- package/dist/site-chrome.d.ts +42 -0
- package/dist/site-chrome.js +140 -2
- package/dist/site-fonts.d.ts +21 -0
- package/dist/site-fonts.js +54 -0
- package/dist/site-manifest.d.ts +12 -5
- package/dist/site-manifest.js +25 -11
- package/dist/site-roster.d.ts +16 -0
- package/dist/site-roster.js +81 -0
- package/dist/starter-grid.js +15 -0
- package/dist/surfaces.js +12 -0
- package/dist/sync-routes.js +11 -2
- package/dist/team-profiles.d.ts +29 -0
- package/dist/team-profiles.js +43 -0
- package/dist/topology.d.ts +131 -0
- package/dist/topology.js +533 -0
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
- package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
- package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
- package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
- package/site-templates/designs/minimalism/DESIGN.md +166 -36
- package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
- package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
- package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
- package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
- package/site-templates/marketing-site-free/ailk-template.json +1 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
- package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
- package/site-templates/shapes/organization-service/ailk-template.json +1 -1
- package/templates/README.md +2 -0
- package/templates/apps/api/CLAUDE.md +6 -1
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +5 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +10 -0
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +114 -1
- package/templates/apps/api/src/lib/__tests__/site.test.ts +54 -0
- package/templates/apps/api/src/lib/agent-store.ts +95 -3
- package/templates/apps/api/src/lib/site.ts +37 -0
- package/templates/apps/api/src/lib/spoke-store.ts +18 -2
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +4 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +22 -0
- package/templates/apps/api/src/middleware/auth.ts +3 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +23 -0
- package/templates/apps/api/src/openapi/spec.ts +38 -2
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +50 -2
- package/templates/apps/api/src/routes/checkout/sessions.ts +7 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +275 -0
- package/templates/apps/api/src/routes/leads/index.ts +69 -6
- package/templates/apps/api/src/routes/webhooks/README.md +39 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +366 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +61 -1
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +199 -0
- package/templates/apps/api/src/services/default-tenant.ts +41 -7
- package/templates/apps/api/src/services/lead-routing.ts +148 -29
- package/templates/apps/api/src/services/public-site-resolver.ts +48 -0
- package/templates/apps/mcp/__tests__/index.test.ts +5 -5
- package/templates/apps/mcp/package.json +1 -1
- package/templates/apps/mcp/src/index.ts +6 -4
- package/templates/apps/mcp/src/lib/content-adapter.ts +2 -2
- package/templates/apps/mcp/src/lib/topology.generated.ts +24 -0
- package/templates/apps/web/CLAUDE.md +12 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
- package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +66 -14
- package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +163 -0
- package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +68 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
- package/templates/apps/web/app/[locale]/layout.tsx +212 -57
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/page.tsx +37 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +5 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
- package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
- package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/not-found.tsx +47 -0
- package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +89 -1
- package/templates/apps/web/app/openapi.json/route.ts +69 -1
- package/templates/apps/web/instrumentation.ts +7 -4
- package/templates/apps/web/lib/__tests__/closing-footer-path.test.ts +96 -0
- package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +290 -0
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +52 -0
- package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
- package/templates/apps/web/lib/closing-footer-path.ts +50 -0
- package/templates/apps/web/lib/profile-back-link.ts +51 -0
- package/templates/apps/web/lib/relay.ts +206 -2
- package/templates/apps/web/lib/site-brand.tsx +22 -1
- package/templates/apps/web/lib/site-chrome.ts +15 -0
- package/templates/apps/web/lib/site-fonts.ts +15 -0
- package/templates/apps/web/lib/site-logo.tsx +39 -1
- package/templates/apps/web/lib/site-theme-head.ts +33 -0
- package/templates/apps/web/lib/site-theme.ts +13 -1
- package/templates/apps/web/lib/topology.generated.ts +23 -0
- package/templates/apps/web/messages/en.json +7 -1
- package/templates/apps/web/package.json +1 -1
- package/templates/database/CHANGELOG.md +32 -0
- package/templates/database/__tests__/agents.db.test.ts +65 -0
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +92 -0
- package/templates/database/auth/schema.prisma +23 -0
- package/templates/database/inbox/schema.prisma +13 -0
- package/templates/database/jest.config.cjs +7 -11
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +35 -0
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +20 -0
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +33 -0
- package/templates/database/package.json +1 -1
- package/versions.json +35 -34
- package/site-templates/designs/minimalism/sample/README.md +0 -48
- package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
- package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
- package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lead-idempotency-key.db.test.ts
|
|
3
|
+
*
|
|
4
|
+
* LIVE-DATABASE proof of the lead idempotency key (#7869, spec
|
|
5
|
+
* docs/specs/7869-lead-idempotency-key-spec.md D8, D10 cases 15–17; AC-4).
|
|
6
|
+
* Requires DATABASE_URL pointing at a Postgres with the migrations applied —
|
|
7
|
+
* the whole suite SKIPS when DATABASE_URL is unset, keeping the workspace
|
|
8
|
+
* Jest run DB-free (the repo convention; the `apps/api Route Tests
|
|
9
|
+
* (Postgres)` workflow exercises it).
|
|
10
|
+
*
|
|
11
|
+
* Uses the `pg` driver directly, as `spoke-registry.db.test.ts` does. What it
|
|
12
|
+
* proves that a mock cannot — the unique index
|
|
13
|
+
* `leads_workspace_id_idempotency_key_key` itself:
|
|
14
|
+
* 15. One key twice in one workspace: the second insert fails (23505).
|
|
15
|
+
* 16. One key in two workspaces: both inserts succeed.
|
|
16
|
+
* 17. Two keyless (NULL) leads in one workspace: both succeed.
|
|
17
|
+
*/
|
|
18
|
+
import { Client } from "pg";
|
|
19
|
+
|
|
20
|
+
const describeDb = process.env.DATABASE_URL ? describe : describe.skip;
|
|
21
|
+
|
|
22
|
+
describeDb("the lead idempotency key (#7869, live DB)", () => {
|
|
23
|
+
let db: Client;
|
|
24
|
+
let workspaceA: string;
|
|
25
|
+
let workspaceB: string;
|
|
26
|
+
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
db = new Client({ connectionString: process.env.DATABASE_URL });
|
|
29
|
+
await db.connect();
|
|
30
|
+
|
|
31
|
+
const org = (
|
|
32
|
+
await db.query<{ id: string }>(
|
|
33
|
+
`INSERT INTO "organizations" ("name", "slug", "updated_at")
|
|
34
|
+
VALUES ('Lead Idempotency Org', 'idem7869-org', now()) RETURNING "id"`,
|
|
35
|
+
)
|
|
36
|
+
).rows[0]!;
|
|
37
|
+
const workspace = async (slug: string) =>
|
|
38
|
+
(
|
|
39
|
+
await db.query<{ id: string }>(
|
|
40
|
+
`INSERT INTO "workspaces" ("organization_id", "name", "slug", "updated_at")
|
|
41
|
+
VALUES ($1, $2, $2, now()) RETURNING "id"`,
|
|
42
|
+
[org.id, slug],
|
|
43
|
+
)
|
|
44
|
+
).rows[0]!.id;
|
|
45
|
+
workspaceA = await workspace("idem7869-ws-a");
|
|
46
|
+
workspaceB = await workspace("idem7869-ws-b");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterAll(async () => {
|
|
50
|
+
await db.query(`DELETE FROM "leads" WHERE "source" = 'idem7869'`);
|
|
51
|
+
await db.query(`DELETE FROM "organizations" WHERE "slug" = 'idem7869-org'`);
|
|
52
|
+
await db.end();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/** Insert one lead; the database mints its id. */
|
|
56
|
+
const insertLead = (workspaceId: string, idempotencyKey: string | null) =>
|
|
57
|
+
db.query(
|
|
58
|
+
`INSERT INTO "leads" ("site_id", "workspace_id", "source", "email", "payload", "idempotency_key")
|
|
59
|
+
VALUES ($1, $1, 'idem7869', 'visitor@example.com', '{}', $2)`,
|
|
60
|
+
[workspaceId, idempotencyKey],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
it("case 15 — one key twice in one workspace: the second insert fails with 23505", async () => {
|
|
64
|
+
await insertLead(workspaceA, "idem7869-key-same-workspace");
|
|
65
|
+
await expect(
|
|
66
|
+
insertLead(workspaceA, "idem7869-key-same-workspace"),
|
|
67
|
+
).rejects.toMatchObject({
|
|
68
|
+
code: "23505",
|
|
69
|
+
constraint: "leads_workspace_id_idempotency_key_key",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("case 16 — the same key in two workspaces: both inserts succeed", async () => {
|
|
74
|
+
await insertLead(workspaceA, "idem7869-key-two-workspaces");
|
|
75
|
+
await insertLead(workspaceB, "idem7869-key-two-workspaces");
|
|
76
|
+
const { rows } = await db.query<{ n: string }>(
|
|
77
|
+
`SELECT count(*) AS n FROM "leads" WHERE "idempotency_key" = 'idem7869-key-two-workspaces'`,
|
|
78
|
+
);
|
|
79
|
+
expect(rows[0]!.n).toBe("2");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("case 17 — two keyless leads in one workspace: both succeed (NULLs are distinct)", async () => {
|
|
83
|
+
await insertLead(workspaceA, null);
|
|
84
|
+
await insertLead(workspaceA, null);
|
|
85
|
+
const { rows } = await db.query<{ n: string }>(
|
|
86
|
+
`SELECT count(*) AS n FROM "leads"
|
|
87
|
+
WHERE "source" = 'idem7869' AND "workspace_id" = $1 AND "idempotency_key" IS NULL`,
|
|
88
|
+
[workspaceA],
|
|
89
|
+
);
|
|
90
|
+
expect(rows[0]!.n).toBe("2");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -106,6 +106,9 @@ model Workspace {
|
|
|
106
106
|
spokes Spoke[]
|
|
107
107
|
// #3591 — the agents bound to this workspace.
|
|
108
108
|
agents Agent[]
|
|
109
|
+
// #7803 — the people who last activated this workspace (User.lastWorkspaceId).
|
|
110
|
+
// Read and written only by apps/api/src/services/tenant-context.ts.
|
|
111
|
+
lastActiveUsers User[] @relation("UserLastWorkspace")
|
|
109
112
|
|
|
110
113
|
@@unique([organizationId, slug])
|
|
111
114
|
@@index([organizationId])
|
|
@@ -167,6 +170,16 @@ enum AgentPermissionLevel {
|
|
|
167
170
|
/// deleting the creator does not delete the agents they made for others.
|
|
168
171
|
/// `apps/api/src/lib/agent-store.ts` is the only file that names
|
|
169
172
|
/// `prisma.agent`.
|
|
173
|
+
///
|
|
174
|
+
/// `grants` (#7864, spec docs/specs/7864-agent-key-grants-spec.md D2, D3)
|
|
175
|
+
/// lists the permissions an owner or admin granted this agent one by one —
|
|
176
|
+
/// only the five `@working-theory/validation` names in
|
|
177
|
+
/// `AGENT_GRANTABLE_PERMISSIONS` (content-edit, content-publish, assets-write,
|
|
178
|
+
/// magnets-manage, meeting-types-manage), held to that set by the
|
|
179
|
+
/// `agents_grants_allowed` CHECK. A grant never raises the ceiling above. The
|
|
180
|
+
/// agent holds a stored grant only while its person's own role holds it too:
|
|
181
|
+
/// the tenant guards clamp it on every request, so a demoted person's agent
|
|
182
|
+
/// keeps the row but loses the permission.
|
|
170
183
|
model Agent {
|
|
171
184
|
id String @id @default(dbgenerated("('agent_'::text || (uuidv7())::text)")) @db.Text
|
|
172
185
|
organizationId String @map("organization_id")
|
|
@@ -174,6 +187,7 @@ model Agent {
|
|
|
174
187
|
responsibleMembershipId String @map("responsible_membership_id")
|
|
175
188
|
name String @db.VarChar(80)
|
|
176
189
|
permissionLevel AgentPermissionLevel @default(member) @map("permission_level")
|
|
190
|
+
grants String[] @default([]) @map("grants")
|
|
177
191
|
enabled Boolean @default(true)
|
|
178
192
|
credentialHash String @unique @map("credential_hash") @db.Char(64)
|
|
179
193
|
credentialPrefix String @map("credential_prefix") @db.VarChar(24)
|
|
@@ -374,7 +388,16 @@ model User {
|
|
|
374
388
|
memberships Membership[]
|
|
375
389
|
sentInvitations Invitation[] @relation("sent_invitations")
|
|
376
390
|
|
|
391
|
+
// #7803 — the workspace this person last activated, so a new sign-in lands
|
|
392
|
+
// there. Written only by activateWorkspace after its membership grant; read
|
|
393
|
+
// only by listTenancy, which reports it only when the workspace is in the
|
|
394
|
+
// caller's own listing (apps/api/src/services/tenant-context.ts). Deleting
|
|
395
|
+
// the workspace clears it. Never a Better Auth field, so no client can set it.
|
|
396
|
+
lastWorkspaceId String? @map("last_workspace_id")
|
|
397
|
+
lastWorkspace Workspace? @relation("UserLastWorkspace", fields: [lastWorkspaceId], references: [id], onDelete: SetNull)
|
|
398
|
+
|
|
377
399
|
@@unique([billingProvider, billingCustomerId])
|
|
400
|
+
@@index([lastWorkspaceId])
|
|
378
401
|
@@map("users")
|
|
379
402
|
}
|
|
380
403
|
|
|
@@ -63,6 +63,19 @@ model Lead {
|
|
|
63
63
|
promotedUserId String? @map("promoted_user_id")
|
|
64
64
|
promotedAt DateTime? @map("promoted_at")
|
|
65
65
|
|
|
66
|
+
// #7869 (roadmap D5) — the caller's `Idempotency-Key` header on
|
|
67
|
+
// `POST /v1/leads/submit`, stored verbatim so a retried write finds the
|
|
68
|
+
// first lead. Written only by `apps/api/src/services/lead-routing.ts`'s
|
|
69
|
+
// submit branch and read only by its two scoped lookups. Untrusted,
|
|
70
|
+
// client-supplied text — never echo it. Nullable, no default: every
|
|
71
|
+
// pre-#7869 row and every keyless lead reads NULL, and Postgres treats
|
|
72
|
+
// NULLs as distinct under the unique below. Same shape as
|
|
73
|
+
// `FlowCheckout.idempotencyKey` (#4912).
|
|
74
|
+
idempotencyKey String? @map("idempotency_key")
|
|
75
|
+
|
|
76
|
+
// #7869 — the idempotency guarantee, scoped per workspace so a key can
|
|
77
|
+
// never reach another tenant's row.
|
|
78
|
+
@@unique([workspaceId, idempotencyKey])
|
|
66
79
|
@@index([siteId])
|
|
67
80
|
@@index([createdAt])
|
|
68
81
|
@@index([email])
|
|
@@ -6,26 +6,22 @@ module.exports = {
|
|
|
6
6
|
testPathIgnorePatterns: ["/node_modules/", "/generated/"],
|
|
7
7
|
moduleFileExtensions: ["ts", "js", "json"],
|
|
8
8
|
moduleNameMapper: {
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// identical mapping. The published dist is ESM-only and this package's
|
|
12
|
-
// CJS ts-jest transform can't load it; database/flows/presets.ts's test
|
|
13
|
-
// needs the real flowConfigSchema value at runtime.
|
|
14
|
-
"^@working-theory/validation$": "<rootDir>/../packages/validation/src/index.ts",
|
|
15
|
-
// ../tenancy.ts's `isSentinel` import from @working-theory/ids became a
|
|
16
|
-
// VALUE import (#5876 spec D2/D11) — no longer type-only, so it DOES need
|
|
17
|
-
// a mapping now, same reason as the validation mapping above.
|
|
18
|
-
"^@working-theory/ids$": "<rootDir>/../packages/ids/src/index.ts",
|
|
9
|
+
// @working-theory/validation and @working-theory/ids resolve from the
|
|
10
|
+
// installed packages; their ESM dist is converted by the transform below.
|
|
19
11
|
"^(\\.{1,2}/.*)\\.js$": "$1",
|
|
20
12
|
},
|
|
13
|
+
transformIgnorePatterns: [
|
|
14
|
+
"/node_modules/(?!(\\.pnpm/)?@working-theory(?:\\+[^/]*?@?[^/]*?|/[^/]+)(/|$))",
|
|
15
|
+
],
|
|
21
16
|
transform: {
|
|
22
|
-
"^.+\\.
|
|
17
|
+
"^.+\\.[cm]?[tj]sx?$": [
|
|
23
18
|
"ts-jest",
|
|
24
19
|
{
|
|
25
20
|
tsconfig: {
|
|
26
21
|
target: "ES2020",
|
|
27
22
|
module: "commonjs",
|
|
28
23
|
lib: ["ES2020"],
|
|
24
|
+
allowJs: true,
|
|
29
25
|
declaration: false,
|
|
30
26
|
strict: true,
|
|
31
27
|
esModuleInterop: true,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- #7803 — sign-in lands in the user's last workspace (spec
|
|
2
|
+
-- docs/specs/7803-sign-in-last-workspace-spec.md D1).
|
|
3
|
+
--
|
|
4
|
+
-- users.last_workspace_id (auth) — the workspace this person last activated.
|
|
5
|
+
-- Written only by activateWorkspace after its membership
|
|
6
|
+
-- grant; read only by listTenancy, which reports it only
|
|
7
|
+
-- when the workspace is in the caller's own listing
|
|
8
|
+
-- (apps/api/src/services/tenant-context.ts). Deleting the
|
|
9
|
+
-- workspace sets it null.
|
|
10
|
+
--
|
|
11
|
+
-- PURELY ADDITIVE: one nullable column with no default, its index, one FK to
|
|
12
|
+
-- "workspaces" and the ADR-0031 format CHECK on the new reference column. No
|
|
13
|
+
-- backfill runs and no existing row changes, so there is no pre-assertion
|
|
14
|
+
-- block — this migration cannot lose a row. A NULL passes the CHECK.
|
|
15
|
+
--
|
|
16
|
+
-- Rollout: deploy this migration BEFORE the API code that reads the column
|
|
17
|
+
-- (SEC-7803-3). Being nullable, it is safe to apply ahead of the code.
|
|
18
|
+
--
|
|
19
|
+
-- Rollback: ALTER TABLE "users" DROP COLUMN "last_workspace_id"; (this drops
|
|
20
|
+
-- the index, the FK and the CHECK with it). Safe while nothing but #7803's own
|
|
21
|
+
-- code reads it.
|
|
22
|
+
|
|
23
|
+
-- AlterTable
|
|
24
|
+
ALTER TABLE "users" ADD COLUMN "last_workspace_id" TEXT;
|
|
25
|
+
|
|
26
|
+
-- CreateIndex
|
|
27
|
+
CREATE INDEX "users_last_workspace_id_idx" ON "users"("last_workspace_id");
|
|
28
|
+
|
|
29
|
+
-- AddForeignKey
|
|
30
|
+
-- A deleted workspace clears every pointer to it.
|
|
31
|
+
ALTER TABLE "users" ADD CONSTRAINT "users_last_workspace_id_fkey" FOREIGN KEY ("last_workspace_id") REFERENCES "workspaces"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
32
|
+
|
|
33
|
+
-- ADR-0031 rule 3 — the reference column carries the format CHECK of the
|
|
34
|
+
-- entity it names (wksp_), in the exact pattern the agents migration uses.
|
|
35
|
+
ALTER TABLE "users" ADD CONSTRAINT "users_last_workspace_id_format" CHECK ("last_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}$');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- #7869 (spoke-pattern roadmap Phase 1, D5) — Lead idempotency key: the
|
|
2
|
+
-- caller's optional `Idempotency-Key` header on POST /v1/leads/submit, so a
|
|
3
|
+
-- retried lead write (a client retry, a spoke's fulfilment replay) finds the
|
|
4
|
+
-- first lead instead of writing a second. Mirrors the column-add shape of
|
|
5
|
+
-- 20260906000000_add_flow_checkout_idempotency (one AlterTable, one unique
|
|
6
|
+
-- index under the Prisma section headers).
|
|
7
|
+
--
|
|
8
|
+
-- The column is nullable with no default, so no pre-existing row needs a
|
|
9
|
+
-- backfill and none collides on the new unique — Postgres treats NULLs as
|
|
10
|
+
-- distinct. The unique is scoped by workspace_id: one key in two workspaces
|
|
11
|
+
-- is two leads.
|
|
12
|
+
--
|
|
13
|
+
-- Rollback: ALTER TABLE "leads" DROP COLUMN "idempotency_key"; (the index
|
|
14
|
+
-- goes with it).
|
|
15
|
+
|
|
16
|
+
-- AlterTable
|
|
17
|
+
ALTER TABLE "leads" ADD COLUMN "idempotency_key" TEXT;
|
|
18
|
+
|
|
19
|
+
-- CreateIndex
|
|
20
|
+
CREATE UNIQUE INDEX "leads_workspace_id_idempotency_key_key" ON "leads"("workspace_id", "idempotency_key");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- #7864 — grant an agent key five named abilities (spec
|
|
2
|
+
-- docs/specs/7864-agent-key-grants-spec.md D2).
|
|
3
|
+
--
|
|
4
|
+
-- agents.grants (auth) — the permissions an owner or admin granted this
|
|
5
|
+
-- agent one by one. Written only by setServedAgentGrants
|
|
6
|
+
-- (apps/api/src/lib/agent-store.ts, the PUT
|
|
7
|
+
-- …/agents/:agentId/grants route); read by the auth
|
|
8
|
+
-- preHandler's key lookup and the agent list. The tenant
|
|
9
|
+
-- guards hold an agent to the grants its person's own role
|
|
10
|
+
-- also holds, so a stored grant never outranks the person.
|
|
11
|
+
--
|
|
12
|
+
-- The CHECK names the same five permissions as @working-theory/validation's
|
|
13
|
+
-- AGENT_GRANTABLE_PERMISSIONS; database/__tests__/agents.db.test.ts reads
|
|
14
|
+
-- this file and fails if the two lists differ (SEC-7864-G).
|
|
15
|
+
--
|
|
16
|
+
-- PURELY ADDITIVE: one NOT NULL column with an empty-array default and its
|
|
17
|
+
-- CHECK. No backfill runs: every existing agent takes the default, no
|
|
18
|
+
-- grants, and so behaves exactly as before. No existing row changes beyond
|
|
19
|
+
-- the default fill, so there is no pre-assertion block.
|
|
20
|
+
--
|
|
21
|
+
-- Rollout: deploy this migration BEFORE the API code that reads the column.
|
|
22
|
+
-- Code without the column fails closed — its key lookup errors and every
|
|
23
|
+
-- agent is answered 401 (SEC-7864-F).
|
|
24
|
+
--
|
|
25
|
+
-- Rollback: ALTER TABLE "agents" DROP COLUMN "grants"; (this drops the CHECK
|
|
26
|
+
-- with it). Safe while nothing but #7864's own code reads it.
|
|
27
|
+
|
|
28
|
+
-- AlterTable
|
|
29
|
+
ALTER TABLE "agents" ADD COLUMN "grants" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];
|
|
30
|
+
|
|
31
|
+
-- Only the five grantable permissions may be stored.
|
|
32
|
+
ALTER TABLE "agents" ADD CONSTRAINT "agents_grants_allowed"
|
|
33
|
+
CHECK ("grants" <@ ARRAY['content-edit','content-publish','assets-write','magnets-manage','meeting-types-manage']::TEXT[]);
|
package/versions.json
CHANGED
|
@@ -1,52 +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.37.0",
|
|
3
|
+
"@working-theory/aeo": "0.37.0",
|
|
4
|
+
"@working-theory/agent-context": "0.37.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.37.0",
|
|
9
|
+
"@working-theory/api-client": "0.37.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.37.0",
|
|
12
|
+
"@working-theory/common": "0.37.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.37.0",
|
|
15
|
+
"@working-theory/content-projection": "0.37.0",
|
|
16
|
+
"@working-theory/email": "0.37.0",
|
|
17
17
|
"@working-theory/env": "0.0.0",
|
|
18
|
-
"@working-theory/eslint-config": "0.
|
|
19
|
-
"@working-theory/fumadocs-schema": "0.
|
|
18
|
+
"@working-theory/eslint-config": "0.37.0",
|
|
19
|
+
"@working-theory/fumadocs-schema": "0.37.0",
|
|
20
20
|
"@working-theory/ids": "0.10.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/
|
|
33
|
-
"@working-theory/
|
|
34
|
-
"@working-theory/
|
|
35
|
-
"@working-theory/
|
|
36
|
-
"@working-theory/
|
|
37
|
-
"@working-theory/
|
|
21
|
+
"@working-theory/jsonld": "0.37.0",
|
|
22
|
+
"@working-theory/license": "0.37.0",
|
|
23
|
+
"@working-theory/metadata": "0.37.0",
|
|
24
|
+
"@working-theory/observability": "0.37.0",
|
|
25
|
+
"@working-theory/page-renderer": "0.37.0",
|
|
26
|
+
"@working-theory/plugin-mcp": "0.1.2",
|
|
27
|
+
"@working-theory/schema": "0.37.0",
|
|
28
|
+
"@working-theory/schema-pack-registry": "0.37.0",
|
|
29
|
+
"@working-theory/schema-validator": "0.37.0",
|
|
30
|
+
"@working-theory/sections-registry": "0.37.0",
|
|
31
|
+
"@working-theory/site-config": "0.37.0",
|
|
32
|
+
"@working-theory/spoke-checkout": "0.2.0",
|
|
33
|
+
"@working-theory/stripe-client": "0.37.0",
|
|
34
|
+
"@working-theory/templates": "0.37.0",
|
|
35
|
+
"@working-theory/theme": "0.37.0",
|
|
36
|
+
"@working-theory/ui": "0.37.0",
|
|
37
|
+
"@working-theory/validation": "0.37.0",
|
|
38
|
+
"@working-theory/web-i18n": "0.37.0",
|
|
38
39
|
"@working-theory/billing-client": "0.0.0",
|
|
39
40
|
"@working-theory/branding": "0.0.0",
|
|
40
41
|
"@working-theory/license-client": "0.0.0",
|
|
41
42
|
"@working-theory/license-manifest": "0.0.0",
|
|
42
43
|
"@working-theory/partner-session": "0.0.0",
|
|
43
44
|
"@working-theory/preview-protocol": "0.0.0",
|
|
44
|
-
"@working-theory/ui-commerce": "0.23.
|
|
45
|
-
"@working-theory/ui-docs-blog": "0.23.
|
|
46
|
-
"@working-theory/ui-scheduling": "0.23.
|
|
47
|
-
"@working-theory/ui-signal-collectors": "0.25.
|
|
45
|
+
"@working-theory/ui-commerce": "0.23.15",
|
|
46
|
+
"@working-theory/ui-docs-blog": "0.23.15",
|
|
47
|
+
"@working-theory/ui-scheduling": "0.23.15",
|
|
48
|
+
"@working-theory/ui-signal-collectors": "0.25.4",
|
|
48
49
|
"@working-theory/web": "0.0.0",
|
|
49
50
|
"@working-theory/api": "1.0.0",
|
|
50
51
|
"@working-theory/mcp": "0.0.0",
|
|
51
|
-
"@working-theory/database": "0.
|
|
52
|
+
"@working-theory/database": "0.37.0"
|
|
52
53
|
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# The `minimalism` design's home-deck sample
|
|
2
|
-
|
|
3
|
-
> Narrative moved here from the retired minimalism template's `TEMPLATE-MANIFEST.md` when
|
|
4
|
-
> that tree retired (spec 6892 D4/D5) — `minimalism` is a design, not a template, so it
|
|
5
|
-
> carries no generated manifest of its own. This file is NOT installed onto a scaffold
|
|
6
|
-
> (`installDesignSample` copies only `content/**` and `public/**`); it documents the sample for
|
|
7
|
-
> maintainers of this design.
|
|
8
|
-
|
|
9
|
-
The second Pro template (#5589) that first wore this design was a reports-first site whose home
|
|
10
|
-
is a **nine-slide scroll-snap deck**, shaped after the reference captured in
|
|
11
|
-
`docs/research/bondcap-capture/`. Structure and design are the reference; **the copy is not** —
|
|
12
|
-
every line in `content/en/HomePage/home.mdx` is placeholder English of the same shape and length.
|
|
13
|
-
|
|
14
|
-
`layout.home: "deck"` (the `minimalism` design's own layout knob) makes every direct section
|
|
15
|
-
child of `home.mdx` one full-viewport snap target, in order. The mark is hidden on slide 1 (the
|
|
16
|
-
deck-hero landing state) and revealed from slide 2 on.
|
|
17
|
-
|
|
18
|
-
| # | Slide | Section | Ground | Action |
|
|
19
|
-
| --- | ----------------- | -------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------- |
|
|
20
|
-
| 1 | Landing | `hero` (`backdrop`) | gradient band + backdrop | `{Primary CTA} →` → `/reports`, centered; 44px scroll cue. |
|
|
21
|
-
| 2 | Tagline | `content-block` (`align: center`, `fill: viewport`) | light | — |
|
|
22
|
-
| 3 | Our community | `logoCloud` (`actions`) | light | `Refer someone →` → `/referral`, bottom-centered at 44px |
|
|
23
|
-
| 4 | Reports headline | `content-block` (`align: center`, `fill: viewport`) | light | — |
|
|
24
|
-
| 5 | Special reports | `reports-list` (`list`, `tone: on-dark`) | dark (`gradient-dark`) | four dated titles → `/reports/<slug>` |
|
|
25
|
-
| 6 | Favorites | `reports-list` (`list`, `tone: on-dark`) | dark (`gradient-dark`) | year-only titles → `/reports/<slug>` |
|
|
26
|
-
| 7 | Services headline | `content-block` (`align: center`, `fill: viewport`) | light | `Hire me →` → `/meet` |
|
|
27
|
-
| 8 | Partners | `team` (`with-large-images`, `hoverReveal`, `phoneColumns: 2`) | light | `About us →` → `/about`, bottom-centered at 44px |
|
|
28
|
-
| 9 | Closing | `cta` (`minimal`) | light | `Contact me →` → `/contact`, centered |
|
|
29
|
-
|
|
30
|
-
## The site principle
|
|
31
|
-
|
|
32
|
-
Every slide's action sits at the bottom, centered, at 44px or larger; grids are 2-up on a phone.
|
|
33
|
-
This is held by bounded section props on the sections above, never by a `className` override or
|
|
34
|
-
hand-rolled markup (`.claude/rules/frontend.md` § Chrome Composes Blocks).
|
|
35
|
-
|
|
36
|
-
## The 11 SVGs
|
|
37
|
-
|
|
38
|
-
`public/hero-backdrop.svg`, `public/wordmark-placeholder.svg`, three `public/logo-placeholder-*.svg`
|
|
39
|
-
and six `public/avatar-placeholder-*.svg` — all placeholders, referenced only from this sample's
|
|
40
|
-
`home.mdx` (the avatars are also referenced by the retired bespoke `AboutPage` seed, which did not
|
|
41
|
-
move with this design — its team slide is the only remaining consumer). They ride with the design
|
|
42
|
-
because the deck uses all 11; a shape/design pair that does not install this sample never ships
|
|
43
|
-
them (D4).
|
|
44
|
-
|
|
45
|
-
## Copy density
|
|
46
|
-
|
|
47
|
-
This design declares `copy: { density: "sparse" }` in its `DESIGN.md` — its pages, this deck
|
|
48
|
-
included, carry far fewer words than a standard site.
|