@pithy-sh/core 0.12.0 → 0.14.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 +54 -0
- package/dist/audit/auditEvent.js +1 -1
- package/dist/audit/chain.d.ts +289 -0
- package/dist/audit/chain.d.ts.map +1 -0
- package/dist/audit/chain.js +400 -0
- package/dist/audit/citations.d.ts +52 -0
- package/dist/audit/citations.d.ts.map +1 -0
- package/dist/audit/citations.js +13 -0
- package/dist/audit/composed.d.ts +18 -0
- package/dist/audit/composed.d.ts.map +1 -0
- package/dist/audit/composed.js +28 -0
- package/dist/capability/bindings.d.ts +22 -0
- package/dist/capability/bindings.d.ts.map +1 -1
- package/dist/capability/bindings.js +32 -2
- package/dist/capability/capability.d.ts +133 -15
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/capability/capability.js +17 -2
- package/dist/capability/erasure.d.ts +75 -0
- package/dist/capability/erasure.d.ts.map +1 -0
- package/dist/capability/erasure.js +2 -0
- package/dist/capability/manifest.d.ts +5 -0
- package/dist/capability/manifest.d.ts.map +1 -1
- package/dist/capability/personColumns.d.ts +28 -0
- package/dist/capability/personColumns.d.ts.map +1 -0
- package/dist/capability/personColumns.js +66 -0
- package/dist/capability/personRows.d.ts +62 -0
- package/dist/capability/personRows.d.ts.map +1 -0
- package/dist/capability/personRows.js +59 -0
- package/dist/capability/purge.d.ts +113 -4
- package/dist/capability/purge.d.ts.map +1 -1
- package/dist/capability/purge.js +138 -5
- package/dist/capability/settings.d.ts +10 -0
- package/dist/capability/settings.d.ts.map +1 -1
- package/dist/capability/vectorIndex.d.ts +36 -0
- package/dist/capability/vectorIndex.d.ts.map +1 -0
- package/dist/capability/vectorIndex.js +25 -0
- package/dist/cli/commandCatalog.generated.d.ts +434 -4
- package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
- package/dist/cli/commandCatalog.generated.js +455 -2
- package/dist/cli/commandFacts.d.ts +8 -5
- package/dist/cli/commandFacts.d.ts.map +1 -1
- package/dist/cli/commandFacts.js +12 -3
- package/dist/consent/purpose.d.ts +70 -0
- package/dist/consent/purpose.d.ts.map +1 -0
- package/dist/consent/purpose.js +97 -0
- package/dist/consent/reader.d.ts +99 -0
- package/dist/consent/reader.d.ts.map +1 -0
- package/dist/consent/reader.js +33 -0
- package/dist/controlPlane/audit/actions.d.ts +15 -4
- package/dist/controlPlane/audit/actions.d.ts.map +1 -1
- package/dist/controlPlane/audit/actions.js +16 -5
- package/dist/controlPlane/capability.d.ts +10 -0
- package/dist/controlPlane/capability.d.ts.map +1 -1
- package/dist/controlPlane/capability.js +56 -8
- package/dist/controlPlane/context.d.ts +4 -0
- package/dist/controlPlane/context.d.ts.map +1 -1
- package/dist/controlPlane/context.js +3 -2
- package/dist/controlPlane/data/connection.d.ts +21 -2
- package/dist/controlPlane/data/connection.d.ts.map +1 -1
- package/dist/controlPlane/data/connection.js +13 -4
- package/dist/controlPlane/data/connections.d.ts +94 -0
- package/dist/controlPlane/data/connections.d.ts.map +1 -0
- package/dist/controlPlane/data/connections.js +151 -0
- package/dist/controlPlane/data/hold.d.ts +46 -0
- package/dist/controlPlane/data/hold.d.ts.map +1 -0
- package/dist/controlPlane/data/hold.js +36 -0
- package/dist/controlPlane/data/oidcTrust.d.ts +122 -0
- package/dist/controlPlane/data/oidcTrust.d.ts.map +1 -0
- package/dist/controlPlane/data/oidcTrust.js +173 -0
- package/dist/controlPlane/data/tables.d.ts +4 -0
- package/dist/controlPlane/data/tables.d.ts.map +1 -1
- package/dist/controlPlane/data/tables.js +9 -4
- package/dist/controlPlane/data/trustFirstCall.d.ts +16 -0
- package/dist/controlPlane/data/trustFirstCall.d.ts.map +1 -0
- package/dist/controlPlane/data/trustFirstCall.js +25 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts +165 -2
- package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
- package/dist/controlPlane/discovery/adminRoute.js +32 -8
- package/dist/controlPlane/discovery/routeInput.d.ts +23 -0
- package/dist/controlPlane/discovery/routeInput.d.ts.map +1 -0
- package/dist/controlPlane/discovery/routeInput.js +80 -0
- package/dist/controlPlane/discovery/toolName.d.ts +21 -0
- package/dist/controlPlane/discovery/toolName.d.ts.map +1 -0
- package/dist/controlPlane/discovery/toolName.js +25 -0
- package/dist/controlPlane/erasure/attest.d.ts +78 -0
- package/dist/controlPlane/erasure/attest.d.ts.map +1 -0
- package/dist/controlPlane/erasure/attest.js +164 -0
- package/dist/controlPlane/erasure/read.d.ts +39 -0
- package/dist/controlPlane/erasure/read.d.ts.map +1 -0
- package/dist/controlPlane/erasure/read.js +140 -0
- package/dist/controlPlane/error/errors.d.ts +61 -2
- package/dist/controlPlane/error/errors.d.ts.map +1 -1
- package/dist/controlPlane/error/errors.js +13 -0
- package/dist/controlPlane/evidence/derive.d.ts +124 -0
- package/dist/controlPlane/evidence/derive.d.ts.map +1 -0
- package/dist/controlPlane/evidence/derive.js +343 -0
- package/dist/controlPlane/evidence/mapping.d.ts +85 -0
- package/dist/controlPlane/evidence/mapping.d.ts.map +1 -0
- package/dist/controlPlane/evidence/mapping.js +114 -0
- package/dist/controlPlane/evidence/pack.d.ts +46 -0
- package/dist/controlPlane/evidence/pack.d.ts.map +1 -0
- package/dist/controlPlane/evidence/pack.js +124 -0
- package/dist/controlPlane/http/connections.d.ts +96 -0
- package/dist/controlPlane/http/connections.d.ts.map +1 -0
- package/dist/controlPlane/http/connections.js +109 -0
- package/dist/controlPlane/http/guard.d.ts +23 -2
- package/dist/controlPlane/http/guard.d.ts.map +1 -1
- package/dist/controlPlane/http/guard.js +103 -7
- package/dist/controlPlane/http/handlers.d.ts +212 -10
- package/dist/controlPlane/http/handlers.d.ts.map +1 -1
- package/dist/controlPlane/http/handlers.js +117 -26
- package/dist/controlPlane/http/holds.d.ts +57 -0
- package/dist/controlPlane/http/holds.d.ts.map +1 -0
- package/dist/controlPlane/http/holds.js +98 -0
- package/dist/controlPlane/http/presence.d.ts +27 -0
- package/dist/controlPlane/http/presence.d.ts.map +1 -0
- package/dist/controlPlane/http/presence.js +67 -0
- package/dist/controlPlane/http/processing.d.ts +73 -0
- package/dist/controlPlane/http/processing.d.ts.map +1 -0
- package/dist/controlPlane/http/processing.js +17 -0
- package/dist/controlPlane/http/responses.d.ts +927 -3
- package/dist/controlPlane/http/responses.d.ts.map +1 -1
- package/dist/controlPlane/http/responses.js +283 -7
- package/dist/controlPlane/http/routes.d.ts +12 -2
- package/dist/controlPlane/http/routes.d.ts.map +1 -1
- package/dist/controlPlane/http/routes.js +182 -5
- package/dist/controlPlane/http/schemas.d.ts +63 -0
- package/dist/controlPlane/http/schemas.d.ts.map +1 -1
- package/dist/controlPlane/http/schemas.js +39 -1
- package/dist/controlPlane/http/verify.d.ts.map +1 -1
- package/dist/controlPlane/http/verify.js +13 -1
- package/dist/controlPlane/http/verifyTrust.d.ts +76 -0
- package/dist/controlPlane/http/verifyTrust.d.ts.map +1 -0
- package/dist/controlPlane/http/verifyTrust.js +75 -0
- package/dist/controlPlane/migrations/0004_connection_trusts.d.ts +15 -0
- package/dist/controlPlane/migrations/0004_connection_trusts.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0004_connection_trusts.js +23 -0
- package/dist/controlPlane/migrations/0005_holds.d.ts +12 -0
- package/dist/controlPlane/migrations/0005_holds.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0005_holds.js +26 -0
- package/dist/controlPlane/processing/derive.d.ts +77 -0
- package/dist/controlPlane/processing/derive.d.ts.map +1 -0
- package/dist/controlPlane/processing/derive.js +206 -0
- package/dist/controlPlane/processing/read.d.ts +10 -0
- package/dist/controlPlane/processing/read.d.ts.map +1 -0
- package/dist/controlPlane/processing/read.js +89 -0
- package/dist/controlPlane/scope/kind.d.ts +46 -0
- package/dist/controlPlane/scope/kind.d.ts.map +1 -0
- package/dist/controlPlane/scope/kind.js +52 -0
- package/dist/controlPlane/scope/scope.d.ts +58 -3
- package/dist/controlPlane/scope/scope.d.ts.map +1 -1
- package/dist/controlPlane/scope/scope.js +59 -4
- package/dist/controlPlane/token/jws.js +1 -1
- package/dist/controlPlane/token/mint.js +1 -1
- package/dist/controlPlane/wire.d.ts +23 -0
- package/dist/controlPlane/wire.d.ts.map +1 -1
- package/dist/controlPlane/wire.js +24 -1
- package/dist/createBackend.d.ts +13 -0
- package/dist/createBackend.d.ts.map +1 -1
- package/dist/createBackend.js +11 -3
- package/dist/createEntrypoint.d.ts +14 -1
- package/dist/createEntrypoint.d.ts.map +1 -1
- package/dist/createEntrypoint.js +19 -5
- package/dist/error/http.d.ts +216 -0
- package/dist/error/http.d.ts.map +1 -1
- package/dist/error/payload.d.ts +432 -0
- package/dist/error/payload.d.ts.map +1 -1
- package/dist/error/payload.js +144 -0
- package/dist/error/pithyError.d.ts +22 -0
- package/dist/error/pithyError.d.ts.map +1 -1
- package/dist/error/pithyError.js +31 -1
- package/dist/http/routeSpec.d.ts +46 -1
- package/dist/http/routeSpec.d.ts.map +1 -1
- package/dist/http/routeSpec.js +39 -5
- package/dist/http/validation.d.ts +11 -2
- package/dist/http/validation.d.ts.map +1 -1
- package/dist/http/validation.js +20 -3
- package/dist/http/verification.d.ts.map +1 -1
- package/dist/http/verification.js +1 -1
- package/dist/i18n/translator.d.ts +2 -1
- package/dist/i18n/translator.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/logger/record.d.ts +126 -0
- package/dist/logger/record.d.ts.map +1 -1
- package/dist/mcp/annotations.d.ts +20 -0
- package/dist/mcp/annotations.d.ts.map +1 -0
- package/dist/mcp/annotations.js +23 -0
- package/dist/mcp/tools.d.ts +75 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +118 -0
- package/dist/openapi/document.d.ts.map +1 -1
- package/dist/openapi/document.js +8 -26
- package/dist/openapi/operations.d.ts +35 -0
- package/dist/openapi/operations.d.ts.map +1 -0
- package/dist/openapi/operations.js +37 -0
- package/dist/presence/channel.d.ts +11 -0
- package/dist/presence/channel.d.ts.map +1 -0
- package/dist/presence/channel.js +65 -0
- package/dist/presence/declaration.d.ts +154 -0
- package/dist/presence/declaration.d.ts.map +1 -0
- package/dist/presence/declaration.js +164 -0
- package/dist/presence/durableObject.d.ts +61 -0
- package/dist/presence/durableObject.d.ts.map +1 -0
- package/dist/presence/durableObject.js +257 -0
- package/dist/presence/frames.d.ts +166 -0
- package/dist/presence/frames.d.ts.map +1 -0
- package/dist/presence/frames.js +90 -0
- package/dist/presence/http/guard.d.ts +11 -0
- package/dist/presence/http/guard.d.ts.map +1 -0
- package/dist/presence/http/guard.js +20 -0
- package/dist/presence/http/responses.d.ts +31 -0
- package/dist/presence/http/responses.d.ts.map +1 -0
- package/dist/presence/http/responses.js +34 -0
- package/dist/presence/http/routes.d.ts +24 -0
- package/dist/presence/http/routes.d.ts.map +1 -0
- package/dist/presence/http/routes.js +127 -0
- package/dist/presence/http/schemas.d.ts +29 -0
- package/dist/presence/http/schemas.d.ts.map +1 -0
- package/dist/presence/http/schemas.js +28 -0
- package/dist/presence/names.d.ts +23 -0
- package/dist/presence/names.d.ts.map +1 -0
- package/dist/presence/names.js +22 -0
- package/dist/presence/room.d.ts +95 -0
- package/dist/presence/room.d.ts.map +1 -0
- package/dist/presence/room.js +105 -0
- package/dist/presence/ticket.d.ts +55 -0
- package/dist/presence/ticket.d.ts.map +1 -0
- package/dist/presence/ticket.js +109 -0
- package/dist/privacy/classify.js +1 -1
- package/dist/privacy/encrypted.js +1 -1
- package/dist/processing/activity.d.ts +134 -0
- package/dist/processing/activity.d.ts.map +1 -0
- package/dist/processing/activity.js +161 -0
- package/dist/processing/composed.d.ts +18 -0
- package/dist/processing/composed.d.ts.map +1 -0
- package/dist/processing/composed.js +28 -0
- package/dist/processing/vocabulary.d.ts +26 -0
- package/dist/processing/vocabulary.d.ts.map +1 -0
- package/dist/processing/vocabulary.js +35 -0
- package/dist/queue/consumer.d.ts +59 -0
- package/dist/queue/consumer.d.ts.map +1 -0
- package/dist/queue/consumer.js +46 -0
- package/dist/retention/composed.d.ts +37 -0
- package/dist/retention/composed.d.ts.map +1 -0
- package/dist/retention/composed.js +66 -0
- package/dist/retention/holds.d.ts +61 -0
- package/dist/retention/holds.d.ts.map +1 -0
- package/dist/retention/holds.js +107 -0
- package/dist/retention/holdsPurge.d.ts +22 -0
- package/dist/retention/holdsPurge.d.ts.map +1 -0
- package/dist/retention/holdsPurge.js +50 -0
- package/dist/retention/policy.d.ts +132 -0
- package/dist/retention/policy.d.ts.map +1 -0
- package/dist/retention/policy.js +197 -0
- package/dist/session/rotated.d.ts +34 -0
- package/dist/session/rotated.d.ts.map +1 -0
- package/dist/session/rotated.js +25 -0
- package/dist/test-utils/connectionFixtures.d.ts +13 -0
- package/dist/test-utils/connectionFixtures.d.ts.map +1 -0
- package/dist/test-utils/connectionFixtures.js +58 -0
- package/dist/test-utils/interleave.d.ts +12 -0
- package/dist/test-utils/interleave.d.ts.map +1 -0
- package/dist/test-utils/interleave.js +26 -0
- package/dist/testWorker.d.ts +8 -0
- package/dist/testWorker.d.ts.map +1 -0
- package/dist/testWorker.js +9 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workflow/dispatchRoute.js +1 -1
- package/package.json +1 -1
- package/src/audit/auditEvent.ts +1 -1
- package/src/audit/chain.ts +551 -0
- package/src/audit/citations.ts +63 -0
- package/src/audit/composed.ts +30 -0
- package/src/capability/bindings.ts +61 -0
- package/src/capability/capability.ts +166 -18
- package/src/capability/erasure.ts +82 -0
- package/src/capability/personColumns.ts +83 -0
- package/src/capability/personRows.ts +125 -0
- package/src/capability/purge.ts +208 -8
- package/src/capability/settings.ts +7 -0
- package/src/capability/vectorIndex.ts +60 -0
- package/src/cli/commandCatalog.generated.ts +488 -2
- package/src/cli/commandFacts.ts +12 -3
- package/src/cloudflare-test.d.ts +3 -1
- package/src/consent/purpose.ts +160 -0
- package/src/consent/reader.ts +134 -0
- package/src/controlPlane/audit/actions.ts +15 -4
- package/src/controlPlane/capability.ts +99 -20
- package/src/controlPlane/context.ts +10 -2
- package/src/controlPlane/data/connection.ts +16 -2
- package/src/controlPlane/data/connections.ts +231 -0
- package/src/controlPlane/data/hold.ts +52 -0
- package/src/controlPlane/data/oidcTrust.ts +249 -0
- package/src/controlPlane/data/tables.ts +7 -0
- package/src/controlPlane/data/trustFirstCall.ts +37 -0
- package/src/controlPlane/discovery/adminRoute.ts +71 -9
- package/src/controlPlane/discovery/routeInput.ts +86 -0
- package/src/controlPlane/discovery/toolName.ts +23 -0
- package/src/controlPlane/erasure/attest.ts +205 -0
- package/src/controlPlane/erasure/read.ts +169 -0
- package/src/controlPlane/error/errors.ts +69 -2
- package/src/controlPlane/evidence/derive.ts +426 -0
- package/src/controlPlane/evidence/mapping.ts +118 -0
- package/src/controlPlane/evidence/pack.ts +190 -0
- package/src/controlPlane/http/connections.ts +129 -0
- package/src/controlPlane/http/guard.ts +178 -10
- package/src/controlPlane/http/handlers.ts +172 -40
- package/src/controlPlane/http/holds.ts +108 -0
- package/src/controlPlane/http/presence.ts +71 -0
- package/src/controlPlane/http/processing.ts +19 -0
- package/src/controlPlane/http/responses.ts +590 -7
- package/src/controlPlane/http/routes.ts +302 -3
- package/src/controlPlane/http/schemas.ts +126 -0
- package/src/controlPlane/http/verify.ts +19 -2
- package/src/controlPlane/http/verifyTrust.ts +176 -0
- package/src/controlPlane/migrations/0004_connection_trusts.ts +30 -0
- package/src/controlPlane/migrations/0005_holds.ts +40 -0
- package/src/controlPlane/processing/derive.ts +359 -0
- package/src/controlPlane/processing/read.ts +102 -0
- package/src/controlPlane/scope/kind.ts +63 -0
- package/src/controlPlane/scope/scope.ts +65 -3
- package/src/controlPlane/wire.ts +26 -0
- package/src/createBackend.ts +31 -6
- package/src/createEntrypoint.ts +34 -2
- package/src/error/payload.ts +302 -0
- package/src/error/pithyError.ts +42 -0
- package/src/http/routeSpec.ts +75 -5
- package/src/http/validation.ts +35 -3
- package/src/http/verification.ts +3 -1
- package/src/i18n/translator.ts +2 -1
- package/src/index.ts +2 -0
- package/src/mcp/annotations.ts +38 -0
- package/src/mcp/tools.ts +220 -0
- package/src/openapi/document.ts +8 -28
- package/src/openapi/operations.ts +62 -0
- package/src/presence/channel.ts +46 -0
- package/src/presence/declaration.ts +283 -0
- package/src/presence/durableObject.ts +287 -0
- package/src/presence/frames.ts +153 -0
- package/src/presence/http/guard.ts +23 -0
- package/src/presence/http/responses.ts +63 -0
- package/src/presence/http/routes.ts +165 -0
- package/src/presence/http/schemas.ts +39 -0
- package/src/presence/names.ts +45 -0
- package/src/presence/room.ts +145 -0
- package/src/presence/ticket.ts +145 -0
- package/src/processing/activity.ts +289 -0
- package/src/processing/composed.ts +33 -0
- package/src/processing/vocabulary.ts +43 -0
- package/src/queue/consumer.ts +104 -0
- package/src/retention/composed.ts +75 -0
- package/src/retention/holds.ts +175 -0
- package/src/retention/holdsPurge.ts +50 -0
- package/src/retention/policy.ts +305 -0
- package/src/session/rotated.ts +53 -0
- package/src/test-utils/connectionFixtures.ts +58 -0
- package/src/test-utils/interleave.ts +31 -0
- package/src/testWorker.ts +13 -0
- package/src/version.generated.ts +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import { type ErasingPurgeContribution, type PurgeContext } from "./purge";
|
|
5
|
+
/**
|
|
6
|
+
* The purge contribution most capabilities need, written once (#814): rows in D1 that name a person by a column,
|
|
7
|
+
* exported on an access request and deleted by a purge.
|
|
8
|
+
*
|
|
9
|
+
* `@pithy-sh/rating`'s ratings, `@pithy-sh/leaderboard`'s entries, `@pithy-sh/matchmaking`'s friendships — each is
|
|
10
|
+
* a table whose rows carry the person's id in a column, and nothing else to do with them than read them out and
|
|
11
|
+
* delete them. A capability with more to do — bytes in R2, an address rather than an id, a row that must be
|
|
12
|
+
* anonymized — writes its own contribution with the same types.
|
|
13
|
+
*
|
|
14
|
+
* **One transaction.** The deletes are one `d1.batch()`, in the order the tables are listed, so a failure leaves
|
|
15
|
+
* every row where it was and the retried step erases the lot. **Idempotent**: a second run deletes nothing.
|
|
16
|
+
*/
|
|
17
|
+
/** One table a person's rows are in, and how they are found and exported. */
|
|
18
|
+
export interface PersonTable {
|
|
19
|
+
/** The table's camelCase name, as the capability's tables map keys it. */
|
|
20
|
+
readonly table: string;
|
|
21
|
+
/** Its row schema. Rows are decoded through it, so the export carries the app shape and never SQLite's. */
|
|
22
|
+
readonly schema: z.ZodObject;
|
|
23
|
+
/** The columns that name the person. A row is theirs when any of them is the subject's id. */
|
|
24
|
+
readonly columns: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* Columns a row must also hold exactly — `{ subjectType: "user" }`, where one id column names people and
|
|
27
|
+
* organizations both and only a person's rows are theirs.
|
|
28
|
+
*/
|
|
29
|
+
readonly match?: Readonly<Record<string, string>>;
|
|
30
|
+
/** The key the rows are exported under — `ratings`, `entries`. */
|
|
31
|
+
readonly as: string;
|
|
32
|
+
/** A column to read them in, so an export reads the same twice. */
|
|
33
|
+
readonly orderBy?: string;
|
|
34
|
+
/** Columns the export leaves out — a credential, or another person's data the row carries beside theirs. */
|
|
35
|
+
readonly omit?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* A per-row redaction, for a credential that sits in a column only some rows hold it in — a Google Play purchase
|
|
38
|
+
* token keys a purchase row on that rail alone. It is handed the decoded row, before `omit` drops any column, and
|
|
39
|
+
* returns the row to export.
|
|
40
|
+
*/
|
|
41
|
+
readonly redact?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
/** What {@link personRowsPurge} is built from. */
|
|
44
|
+
export interface PersonRowsPurgeInput {
|
|
45
|
+
/** The capability, for the fault a missing binding raises. */
|
|
46
|
+
readonly capability: string;
|
|
47
|
+
/** The one sentence the purge records with this step. */
|
|
48
|
+
readonly summary: string;
|
|
49
|
+
/** The D1 binding the tables live in. */
|
|
50
|
+
readonly database?: string;
|
|
51
|
+
/** The tables, in the order a purge deletes from them — children before the rows they reference. */
|
|
52
|
+
readonly tables: readonly PersonTable[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The export half alone: every row naming the person in `tables`, decoded, under each table's key. For a
|
|
56
|
+
* contribution that **retains** its rows (#814) — `@pithy-sh/payments` keeps a purchase for tax law, and still
|
|
57
|
+
* answers for it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function personRowsExport(input: Omit<PersonRowsPurgeInput, "summary">): (context: PurgeContext) => Promise<Record<string, unknown>>;
|
|
60
|
+
/** Build a user contribution that exports, then on a purge deletes, every row naming the person in `tables`. */
|
|
61
|
+
export declare function personRowsPurge(input: PersonRowsPurgeInput): ErasingPurgeContribution;
|
|
62
|
+
//# sourceMappingURL=personRows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"personRows.d.ts","sourceRoot":"","sources":["../../src/capability/personRows.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAE,KAAK,wBAAwB,EAAc,KAAK,YAAY,EAAgB,MAAM,SAAS,CAAC;AAErG;;;;;;;;;;;GAWG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2GAA2G;IAC3G,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,kEAAkE;IAClE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,4GAA4G;IAC5G,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7E;AAED,kDAAkD;AAClD,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAiBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,GAC3C,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmB7D;AAED,gHAAgH;AAChH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,wBAAwB,CAmBrF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { exportRows, purgeBinding } from "./purge.js";
|
|
4
|
+
import { createDatabase } from "../data/db.js";
|
|
5
|
+
import { sql } from "kysely";
|
|
6
|
+
//#region src/capability/personRows.ts
|
|
7
|
+
/** `(column = id OR column = id …) AND match…` over a table's person columns. */
|
|
8
|
+
function theirs({ columns, match }, id) {
|
|
9
|
+
return (eb) => eb.and([eb.or(columns.map((column) => eb(sql.ref(column), "=", id))), ...Object.entries(match ?? {}).map(([column, value]) => eb(sql.ref(column), "=", value))]);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The export half alone: every row naming the person in `tables`, decoded, under each table's key. For a
|
|
13
|
+
* contribution that **retains** its rows (#814) — `@pithy-sh/payments` keeps a purchase for tax law, and still
|
|
14
|
+
* answers for it.
|
|
15
|
+
*/
|
|
16
|
+
function personRowsExport(input) {
|
|
17
|
+
const database = input.database ?? "DB";
|
|
18
|
+
return async ({ subject, env }) => {
|
|
19
|
+
const db = createDatabase(purgeBinding(env, database, input.capability), {});
|
|
20
|
+
const read = await Promise.all(input.tables.map(async (table) => {
|
|
21
|
+
let query = db.selectFrom(table.table).selectAll().where(theirs(table, subject.id));
|
|
22
|
+
if (table.orderBy) query = query.orderBy(sql.ref(table.orderBy));
|
|
23
|
+
const rows = (await query.execute()).map((row) => {
|
|
24
|
+
const parsed = table.schema.parse(row);
|
|
25
|
+
const decoded = table.redact ? table.redact(parsed) : parsed;
|
|
26
|
+
for (const column of table.omit ?? []) delete decoded[column];
|
|
27
|
+
return decoded;
|
|
28
|
+
});
|
|
29
|
+
return [table.as, exportRows(rows)];
|
|
30
|
+
}));
|
|
31
|
+
return Object.fromEntries(read);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Build a user contribution that exports, then on a purge deletes, every row naming the person in `tables`. */
|
|
35
|
+
function personRowsPurge(input) {
|
|
36
|
+
const database = input.database ?? "DB";
|
|
37
|
+
const open = (env) => {
|
|
38
|
+
const d1 = purgeBinding(env, database, input.capability);
|
|
39
|
+
return {
|
|
40
|
+
d1,
|
|
41
|
+
db: createDatabase(d1, {})
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
subject: "user",
|
|
46
|
+
summary: input.summary,
|
|
47
|
+
erase: async ({ subject, env }) => {
|
|
48
|
+
const { d1, db } = open(env);
|
|
49
|
+
const statements = input.tables.map((table) => {
|
|
50
|
+
const compiled = db.deleteFrom(table.table).where(theirs(table, subject.id)).compile();
|
|
51
|
+
return d1.prepare(compiled.sql).bind(...compiled.parameters);
|
|
52
|
+
});
|
|
53
|
+
if (statements.length > 0) await d1.batch(statements);
|
|
54
|
+
},
|
|
55
|
+
export: personRowsExport(input)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { personRowsExport, personRowsPurge };
|
|
@@ -20,6 +20,21 @@ import { z } from "zod";
|
|
|
20
20
|
* a step is retried after a failure that may have come after its rows were already gone, and erasing
|
|
21
21
|
* nothing must then succeed.
|
|
22
22
|
*
|
|
23
|
+
* ## The same contribution answers an export (#622)
|
|
24
|
+
*
|
|
25
|
+
* An access request arrives before an erasure, not after. So a contribution may also say what this capability
|
|
26
|
+
* holds about the subject, on the read side: `export` answers one JSON object, and the control plane's export
|
|
27
|
+
* route assembles one document from every composed contribution, in the same order the purge runs them. A
|
|
28
|
+
* contribution with no `export` is listed as holding rows it did not say how to export, which is the honest
|
|
29
|
+
* answer and the one an operator can act on.
|
|
30
|
+
*
|
|
31
|
+
* ## Or it keeps them, and says why (#814)
|
|
32
|
+
*
|
|
33
|
+
* Not every row about a person should go. A payment record is kept for tax law, a suppression has to outlive the
|
|
34
|
+
* person or the unsubscribe stops holding, and the audit trail is the record of who acted. So a contribution has a
|
|
35
|
+
* third disposition beside erase and anonymize: `retain`, with a one-sentence basis written for the person. The
|
|
36
|
+
* purge records that step as retained and erases nothing of it; the export carries the rows and the basis.
|
|
37
|
+
*
|
|
23
38
|
* ## An adopter declares one too
|
|
24
39
|
*
|
|
25
40
|
* On their own `app` capability, for their own tables, with the same types. The contract is exported for
|
|
@@ -27,9 +42,11 @@ import { z } from "zod";
|
|
|
27
42
|
*/
|
|
28
43
|
/**
|
|
29
44
|
* The kinds of subject a purge erases. Closed, so a typo is refused where it is written rather than becoming
|
|
30
|
-
* a contribution no purge ever asks for. `organization`
|
|
45
|
+
* a contribution no purge ever asks for. `organization` joined with organization state (#623): a capability
|
|
46
|
+
* holding rows scoped to a tenant declares one for it, and the organization purge runs it.
|
|
31
47
|
*/
|
|
32
48
|
export declare const PurgeSubject: z.ZodEnum<{
|
|
49
|
+
organization: "organization";
|
|
33
50
|
user: "user";
|
|
34
51
|
}>;
|
|
35
52
|
export type PurgeSubject = z.output<typeof PurgeSubject>;
|
|
@@ -42,23 +59,68 @@ export interface PurgeContext {
|
|
|
42
59
|
};
|
|
43
60
|
/** The Worker's bindings for this run, from which the contribution opens its own database. */
|
|
44
61
|
readonly env: Record<string, unknown>;
|
|
62
|
+
/**
|
|
63
|
+
* The subject's email address, read on demand from the capability that owns the subject — `null` when it has
|
|
64
|
+
* none, or no longer has one (#814). For a capability that holds rows by address rather than by id: a tester's
|
|
65
|
+
* membership, a suppression, an invitation.
|
|
66
|
+
*
|
|
67
|
+
* **A function, never a value**, so the address is read inside the step that needs it and is never carried in
|
|
68
|
+
* a Workflow's journal, where a purge would otherwise leave it behind. Absent where nobody can say, and a
|
|
69
|
+
* contribution that needs it then finds nothing by address.
|
|
70
|
+
*/
|
|
71
|
+
readonly address?: () => Promise<string | null>;
|
|
45
72
|
}
|
|
46
|
-
/**
|
|
47
|
-
|
|
73
|
+
/** What every contribution says, whichever its disposition. */
|
|
74
|
+
interface PurgeContributionBase {
|
|
48
75
|
/** The kind of subject this erases. */
|
|
49
76
|
readonly subject: PurgeSubject;
|
|
50
77
|
/**
|
|
51
|
-
* One sentence on what is erased or
|
|
78
|
+
* One sentence on what is erased, anonymized or kept — `Deletes the user's uploads and their stored bytes.`
|
|
52
79
|
* Recorded with the step that ran it, so the trail says what a purge did and not only that it ran.
|
|
53
80
|
*/
|
|
54
81
|
readonly summary: string;
|
|
82
|
+
/**
|
|
83
|
+
* What this capability holds about the subject, as one JSON object — the read side of `erase` (#622). It
|
|
84
|
+
* answers an access request, so it carries the subject's own data and never a credential: no session token,
|
|
85
|
+
* no provider token, no push token. Optional on an erasing contribution: one without it is listed in the
|
|
86
|
+
* export as rows it erases and did not say how to export. A throw costs this capability's entry, never the
|
|
87
|
+
* document.
|
|
88
|
+
*/
|
|
89
|
+
readonly export?: (context: PurgeContext) => Promise<Record<string, unknown>>;
|
|
90
|
+
}
|
|
91
|
+
/** A contribution that erases or anonymizes the subject's rows. */
|
|
92
|
+
export interface ErasingPurgeContribution extends PurgeContributionBase {
|
|
55
93
|
/**
|
|
56
94
|
* Erase or anonymize this subject's rows. **Idempotent**: run again after it succeeded, or after a failure
|
|
57
95
|
* that came once the rows were gone, it must succeed having found nothing to do. Throwing fails this step
|
|
58
96
|
* and only this step.
|
|
59
97
|
*/
|
|
60
98
|
readonly erase: (context: PurgeContext) => Promise<void>;
|
|
99
|
+
/** Never set on an erasing contribution. */
|
|
100
|
+
readonly retain?: undefined;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A contribution that **keeps** the subject's rows through a purge, and says why (#814). A payment record kept
|
|
104
|
+
* for tax law, a suppression that must outlive the person or the unsubscribe stops holding, the audit trail of
|
|
105
|
+
* who acted. The purge records the step as retained and never erases a row of it; the export carries the rows
|
|
106
|
+
* and the basis, because rows that are kept are rows that are answered for.
|
|
107
|
+
*/
|
|
108
|
+
export interface RetainingPurgeContribution extends PurgeContributionBase {
|
|
109
|
+
/** Why the rows are kept, in one sentence written for the person — `Kept for tax records.` */
|
|
110
|
+
readonly retain: {
|
|
111
|
+
readonly basis: string;
|
|
112
|
+
};
|
|
113
|
+
/** Required: what is kept is exported. */
|
|
114
|
+
readonly export: (context: PurgeContext) => Promise<Record<string, unknown>>;
|
|
115
|
+
/** Never set on a retaining contribution. */
|
|
116
|
+
readonly erase?: undefined;
|
|
61
117
|
}
|
|
118
|
+
/** One capability's answer to "what happens to this subject's rows": erased, anonymized, or kept and why. */
|
|
119
|
+
export type PurgeContribution = ErasingPurgeContribution | RetainingPurgeContribution;
|
|
120
|
+
/** What a purge does with a contribution's rows: `erase` (or anonymize), or `retain`. */
|
|
121
|
+
export type PurgeDisposition = "erase" | "retain";
|
|
122
|
+
/** Whether a contribution keeps the subject's rows rather than erasing them. */
|
|
123
|
+
export declare function isRetained(contribution: PurgeContribution): contribution is RetainingPurgeContribution;
|
|
62
124
|
/** One step of a purge: which capability it runs, and the contribution that capability declared. */
|
|
63
125
|
export interface PurgeStep {
|
|
64
126
|
/** The step's name — the capability's, so a retried step names who failed. */
|
|
@@ -77,4 +139,51 @@ export declare function purgeSteps(capabilities: readonly {
|
|
|
77
139
|
readonly name: string;
|
|
78
140
|
readonly purge?: readonly PurgeContribution[];
|
|
79
141
|
}[], subject: PurgeSubject): PurgeStep[];
|
|
142
|
+
/** What every export entry says: the capability, its sentence, and what a purge does with its rows. */
|
|
143
|
+
interface PurgeExportEntryBase {
|
|
144
|
+
readonly capability: string;
|
|
145
|
+
readonly summary: string;
|
|
146
|
+
/** `erase` — a purge erases or anonymizes these rows — or `retain`, a purge keeps them (#814). */
|
|
147
|
+
readonly disposition: PurgeDisposition;
|
|
148
|
+
/** For `retain`, why the rows are kept, in the sentence the contribution wrote for the person. */
|
|
149
|
+
readonly basis?: string;
|
|
150
|
+
}
|
|
151
|
+
/** What one step's export answered: its data, that it declared none, or that it could not be read. */
|
|
152
|
+
export type PurgeExportEntry = (PurgeExportEntryBase & {
|
|
153
|
+
readonly state: "exported";
|
|
154
|
+
readonly data: Record<string, unknown>;
|
|
155
|
+
}) | (PurgeExportEntryBase & {
|
|
156
|
+
readonly state: "not_exported";
|
|
157
|
+
}) | (PurgeExportEntryBase & {
|
|
158
|
+
readonly state: "unavailable";
|
|
159
|
+
});
|
|
160
|
+
/**
|
|
161
|
+
* Read every step's export for one subject — the export document's body, in step order (#622).
|
|
162
|
+
*
|
|
163
|
+
* **Each read is guarded on its own and they run concurrently**, the shape the user pane's sub-reads have
|
|
164
|
+
* (#380): one capability whose table will not read costs its own entry as `unavailable`, and the rest of the
|
|
165
|
+
* document still answers the request. What the read threw is not carried — it names a query and a table, and
|
|
166
|
+
* the document crosses a trust boundary. An answer that is not one object is `unavailable` too, because the
|
|
167
|
+
* document promises an object per capability and a client validates it.
|
|
168
|
+
*/
|
|
169
|
+
export declare function purgeExports(steps: readonly PurgeStep[], context: PurgeContext): Promise<PurgeExportEntry[]>;
|
|
170
|
+
/**
|
|
171
|
+
* One binding a contribution opens off a run's env — or a wiring fault naming it and the capability (#814). The
|
|
172
|
+
* purge and the export both run in the adopter's Worker, so a missing binding is that Worker's wiring, and the
|
|
173
|
+
* fault says which binding to add rather than failing deep inside a query.
|
|
174
|
+
*/
|
|
175
|
+
export declare function purgeBinding<T>(env: Record<string, unknown>, name: string, capability: string, kind?: string): T;
|
|
176
|
+
/**
|
|
177
|
+
* Rows as an export carries them (#814): plain JSON, a `Date` as its ISO-8601 string — the shape the export
|
|
178
|
+
* document crosses the wire in, so what a contribution answers is what a reader parses, and a test compares
|
|
179
|
+
* exactly that.
|
|
180
|
+
*/
|
|
181
|
+
export declare function exportRows<T>(rows: readonly T[]): Record<string, unknown>[];
|
|
182
|
+
/**
|
|
183
|
+
* Every path in an export's data whose field name holds a credential — empty for an export that carries none.
|
|
184
|
+
* Each capability's export test asserts it empty, so a column added later with a token in it fails the build
|
|
185
|
+
* rather than reaching an access request (#814).
|
|
186
|
+
*/
|
|
187
|
+
export declare function credentialFields(value: unknown, path?: string): string[];
|
|
188
|
+
export {};
|
|
80
189
|
//# sourceMappingURL=purge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/capability/purge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB
|
|
1
|
+
{"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/capability/purge.ts"],"names":[],"mappings":"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"}
|
package/dist/capability/purge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { ValidationError } from "../error/pithyError.js";
|
|
3
|
+
import { InternalError, ValidationError } from "../error/pithyError.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/capability/purge.ts
|
|
6
6
|
/**
|
|
@@ -22,6 +22,21 @@ import { z } from "zod";
|
|
|
22
22
|
* a step is retried after a failure that may have come after its rows were already gone, and erasing
|
|
23
23
|
* nothing must then succeed.
|
|
24
24
|
*
|
|
25
|
+
* ## The same contribution answers an export (#622)
|
|
26
|
+
*
|
|
27
|
+
* An access request arrives before an erasure, not after. So a contribution may also say what this capability
|
|
28
|
+
* holds about the subject, on the read side: `export` answers one JSON object, and the control plane's export
|
|
29
|
+
* route assembles one document from every composed contribution, in the same order the purge runs them. A
|
|
30
|
+
* contribution with no `export` is listed as holding rows it did not say how to export, which is the honest
|
|
31
|
+
* answer and the one an operator can act on.
|
|
32
|
+
*
|
|
33
|
+
* ## Or it keeps them, and says why (#814)
|
|
34
|
+
*
|
|
35
|
+
* Not every row about a person should go. A payment record is kept for tax law, a suppression has to outlive the
|
|
36
|
+
* person or the unsubscribe stops holding, and the audit trail is the record of who acted. So a contribution has a
|
|
37
|
+
* third disposition beside erase and anonymize: `retain`, with a one-sentence basis written for the person. The
|
|
38
|
+
* purge records that step as retained and erases nothing of it; the export carries the rows and the basis.
|
|
39
|
+
*
|
|
25
40
|
* ## An adopter declares one too
|
|
26
41
|
*
|
|
27
42
|
* On their own `app` capability, for their own tables, with the same types. The contract is exported for
|
|
@@ -29,9 +44,14 @@ import { z } from "zod";
|
|
|
29
44
|
*/
|
|
30
45
|
/**
|
|
31
46
|
* The kinds of subject a purge erases. Closed, so a typo is refused where it is written rather than becoming
|
|
32
|
-
* a contribution no purge ever asks for. `organization`
|
|
47
|
+
* a contribution no purge ever asks for. `organization` joined with organization state (#623): a capability
|
|
48
|
+
* holding rows scoped to a tenant declares one for it, and the organization purge runs it.
|
|
33
49
|
*/
|
|
34
|
-
const PurgeSubject = z.enum(["user"]).describe("The kind of subject a purge erases. `user` is one person's account in `@pithy-sh/auth`, identified by its user id.");
|
|
50
|
+
const PurgeSubject = z.enum(["user", "organization"]).describe("The kind of subject a purge erases. `user` is one person's account in `@pithy-sh/auth`, identified by its user id. `organization` is one tenant in `@pithy-sh/organization`, identified by its organization id.");
|
|
51
|
+
/** Whether a contribution keeps the subject's rows rather than erasing them. */
|
|
52
|
+
function isRetained(contribution) {
|
|
53
|
+
return contribution.retain !== void 0;
|
|
54
|
+
}
|
|
35
55
|
/**
|
|
36
56
|
* Refuse a capability's purge contributions that no purge could run as declared: an unknown subject, two for
|
|
37
57
|
* one subject, or one with nothing to record. Called by `defineCapability`, so the refusal names the capability
|
|
@@ -54,12 +74,36 @@ function assertPurgeContributions(capability, contributions) {
|
|
|
54
74
|
message: `The ${capability} capability's ${subject.data} purge has no summary.`,
|
|
55
75
|
action: "Say in one sentence what it erases. The purge records it with the step."
|
|
56
76
|
});
|
|
77
|
+
if (contribution.export !== void 0 && typeof contribution.export !== "function") throw new ValidationError({
|
|
78
|
+
message: `The ${capability} capability's ${subject.data} purge has an export that is not a function.`,
|
|
79
|
+
action: "Give it an `export` that answers what this capability holds about the subject, or leave it out."
|
|
80
|
+
});
|
|
81
|
+
if (contribution.retain !== void 0) {
|
|
82
|
+
assertRetention(capability, subject.data, contribution);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
57
85
|
if (typeof contribution.erase !== "function") throw new ValidationError({
|
|
58
86
|
message: `The ${capability} capability's ${subject.data} purge has no erase function.`,
|
|
59
|
-
action: "Give it an `erase` that removes or anonymizes the subject's rows."
|
|
87
|
+
action: "Give it an `erase` that removes or anonymizes the subject's rows, or a `retain` that says why it keeps them."
|
|
60
88
|
});
|
|
61
89
|
}
|
|
62
90
|
}
|
|
91
|
+
/** Refuse a retaining contribution the person could not be answered with: no basis, no export, or an erase too. */
|
|
92
|
+
function assertRetention(capability, subject, contribution) {
|
|
93
|
+
const basis = contribution.retain?.basis;
|
|
94
|
+
if (typeof basis !== "string" || basis.trim() === "") throw new ValidationError({
|
|
95
|
+
message: `The ${capability} capability's ${subject} purge retains the rows with no basis.`,
|
|
96
|
+
action: "Say in one sentence, written for the person, why they are kept — `Kept for tax records.`"
|
|
97
|
+
});
|
|
98
|
+
if (typeof contribution.export !== "function") throw new ValidationError({
|
|
99
|
+
message: `The ${capability} capability's ${subject} purge retains the rows and has no export.`,
|
|
100
|
+
action: "Give it an `export`: rows that are kept are rows an access request is answered with."
|
|
101
|
+
});
|
|
102
|
+
if (contribution.erase !== void 0) throw new ValidationError({
|
|
103
|
+
message: `The ${capability} capability's ${subject} purge both retains and erases the rows.`,
|
|
104
|
+
action: "Declare one disposition: `retain` with its basis, or `erase`."
|
|
105
|
+
});
|
|
106
|
+
}
|
|
63
107
|
/** The steps a purge of `subject` runs, in composition order — libraries first, the adopter's `app` last. */
|
|
64
108
|
function purgeSteps(capabilities, subject) {
|
|
65
109
|
const steps = [];
|
|
@@ -72,5 +116,94 @@ function purgeSteps(capabilities, subject) {
|
|
|
72
116
|
}
|
|
73
117
|
return steps;
|
|
74
118
|
}
|
|
119
|
+
/** Whether an export answered what the contract promises: one plain object. */
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Read every step's export for one subject — the export document's body, in step order (#622).
|
|
125
|
+
*
|
|
126
|
+
* **Each read is guarded on its own and they run concurrently**, the shape the user pane's sub-reads have
|
|
127
|
+
* (#380): one capability whose table will not read costs its own entry as `unavailable`, and the rest of the
|
|
128
|
+
* document still answers the request. What the read threw is not carried — it names a query and a table, and
|
|
129
|
+
* the document crosses a trust boundary. An answer that is not one object is `unavailable` too, because the
|
|
130
|
+
* document promises an object per capability and a client validates it.
|
|
131
|
+
*/
|
|
132
|
+
async function purgeExports(steps, context) {
|
|
133
|
+
return Promise.all(steps.map(async ({ step, contribution }) => {
|
|
134
|
+
const base = isRetained(contribution) ? {
|
|
135
|
+
capability: step,
|
|
136
|
+
summary: contribution.summary,
|
|
137
|
+
disposition: "retain",
|
|
138
|
+
basis: contribution.retain.basis
|
|
139
|
+
} : {
|
|
140
|
+
capability: step,
|
|
141
|
+
summary: contribution.summary,
|
|
142
|
+
disposition: "erase"
|
|
143
|
+
};
|
|
144
|
+
const read = contribution.export;
|
|
145
|
+
if (read === void 0) return {
|
|
146
|
+
...base,
|
|
147
|
+
state: "not_exported"
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
const data = await read(context);
|
|
151
|
+
return isRecord(data) ? {
|
|
152
|
+
...base,
|
|
153
|
+
state: "exported",
|
|
154
|
+
data
|
|
155
|
+
} : {
|
|
156
|
+
...base,
|
|
157
|
+
state: "unavailable"
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return {
|
|
161
|
+
...base,
|
|
162
|
+
state: "unavailable"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* One binding a contribution opens off a run's env — or a wiring fault naming it and the capability (#814). The
|
|
169
|
+
* purge and the export both run in the adopter's Worker, so a missing binding is that Worker's wiring, and the
|
|
170
|
+
* fault says which binding to add rather than failing deep inside a query.
|
|
171
|
+
*/
|
|
172
|
+
function purgeBinding(env, name, capability, kind = "D1 database") {
|
|
173
|
+
const binding = env[name];
|
|
174
|
+
if (typeof binding !== "object" || binding === null) throw new InternalError({
|
|
175
|
+
message: `The ${capability} capability's ${kind} is not bound, so its purge step could not run.`,
|
|
176
|
+
action: `Bind the ${kind} named ${name} to the Worker that runs the purge.`,
|
|
177
|
+
detail: `${capability} purge contribution found no ${name} binding on the env`
|
|
178
|
+
});
|
|
179
|
+
return binding;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Rows as an export carries them (#814): plain JSON, a `Date` as its ISO-8601 string — the shape the export
|
|
183
|
+
* document crosses the wire in, so what a contribution answers is what a reader parses, and a test compares
|
|
184
|
+
* exactly that.
|
|
185
|
+
*/
|
|
186
|
+
function exportRows(rows) {
|
|
187
|
+
return JSON.parse(JSON.stringify(rows));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A field name that holds a credential — a token, a secret, a password, a digest of one, a sealed value. What an
|
|
191
|
+
* export never carries (#814): an access request is answered with what is held, never with the means to act as
|
|
192
|
+
* the person or to reach what they shared.
|
|
193
|
+
*/
|
|
194
|
+
const CREDENTIAL_FIELD = /secret|token|password|credential|digest|private_?key|encrypted/i;
|
|
195
|
+
/**
|
|
196
|
+
* Every path in an export's data whose field name holds a credential — empty for an export that carries none.
|
|
197
|
+
* Each capability's export test asserts it empty, so a column added later with a token in it fails the build
|
|
198
|
+
* rather than reaching an access request (#814).
|
|
199
|
+
*/
|
|
200
|
+
function credentialFields(value, path = "") {
|
|
201
|
+
if (Array.isArray(value)) return value.flatMap((item, index) => credentialFields(item, `${path}[${index}]`));
|
|
202
|
+
if (typeof value !== "object" || value === null) return [];
|
|
203
|
+
return Object.entries(value).flatMap(([key, inner]) => {
|
|
204
|
+
const here = path ? `${path}.${key}` : key;
|
|
205
|
+
return [...CREDENTIAL_FIELD.test(key) ? [here] : [], ...credentialFields(inner, here)];
|
|
206
|
+
});
|
|
207
|
+
}
|
|
75
208
|
//#endregion
|
|
76
|
-
export { PurgeSubject, assertPurgeContributions, purgeSteps };
|
|
209
|
+
export { PurgeSubject, assertPurgeContributions, credentialFields, exportRows, isRetained, purgeBinding, purgeExports, purgeSteps };
|
|
@@ -142,6 +142,16 @@ export interface SettingsAccountReader {
|
|
|
142
142
|
* Asked by secrets of the manager token the runtime token superseded, which nothing revokes for you.
|
|
143
143
|
*/
|
|
144
144
|
accountToken(name: string): Promise<boolean>;
|
|
145
|
+
/**
|
|
146
|
+
* **Whether an R2 bucket's objects under `prefix` are under an indefinite lock (#822)** — `locked`, `unlocked`, or
|
|
147
|
+
* `missing` when no bucket of that name exists on the account. Asked by audit, whose checkpoints are evidence only
|
|
148
|
+
* while nothing can replace them. Optional, so a reader built before the question existed still compiles; a
|
|
149
|
+
* check asking a reader without it reports the environment unanswered rather than guessing.
|
|
150
|
+
*/
|
|
151
|
+
r2BucketLock?(request: {
|
|
152
|
+
bucket: string;
|
|
153
|
+
prefix: string;
|
|
154
|
+
}): Promise<"locked" | "unlocked" | "missing">;
|
|
145
155
|
}
|
|
146
156
|
/** What {@link SettingsAccountReader.appRows} is asked. */
|
|
147
157
|
export interface AppRowsRequest {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/capability/settings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,mFAAmF;AACnF,eAAO,MAAM,eAAe;;;;;;iBAiC+D,CAAC;AAC5F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,6FAA6F;AAC7F,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,4GAA4G;AAC5G,MAAM,WAAW,oBAAoB;IACnC,gGAAgG;IAChG,OAAO,EAAE,MAAM,CAAC;IAChB,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,WAAW,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC1C,+GAA+G;IAC/G,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC;;;;;;;OAOG;IACH,WAAW,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC7D;;;;;;;OAOG;IACH,gBAAgB,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClG;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/capability/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* A Vectorize index a capability owns and re-embeds itself — the contribution `pithy vector reprocess` reads.
|
|
5
|
+
*
|
|
6
|
+
* ## Why a contribution
|
|
7
|
+
*
|
|
8
|
+
* `@pithy-sh/vector` re-embeds the indexes an adopter declares in `vector({ indexes })`. A capability that
|
|
9
|
+
* builds an index of its own — `@pithy-sh/support`'s semantic message index — uses vector's public modules to
|
|
10
|
+
* embed and to re-embed, and runs vector's reprocess orchestration in a Workflow it hosts, over its own corpus.
|
|
11
|
+
* What it cannot do is teach vector's CLI that the index exists: capabilities depend on core seams, never on
|
|
12
|
+
* each other. So it says so here, on its own `Capability`, the way it states its migrations and its purge, and
|
|
13
|
+
* `pithy vector reprocess` reads every composed declaration — in a project that never composes `vector()` too.
|
|
14
|
+
*
|
|
15
|
+
* ## What the CLI does with one
|
|
16
|
+
*
|
|
17
|
+
* It starts `reprocessJob`, one of this capability's own `workflows`, under the deployed name every Workflow
|
|
18
|
+
* gets — `<project>-<env>-<capability>-<job>` — with the parameters every reprocess takes: `{ index, all }`.
|
|
19
|
+
* A model change, a repair and a full rebuild (`--all`) are then the same three cases they are for any index.
|
|
20
|
+
*/
|
|
21
|
+
export interface VectorIndexContribution {
|
|
22
|
+
/**
|
|
23
|
+
* The index's name as `pithy vector reprocess --index` takes it — `support-messages`. Unique across the
|
|
24
|
+
* project, and never one of the adopter's own `vector()` index keys.
|
|
25
|
+
*/
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/** The job, in this capability's `workflows`, that re-embeds the index. */
|
|
28
|
+
readonly reprocessJob: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Refuse a declaration no reprocess could run: a duplicate name, or a job the capability does not register.
|
|
32
|
+
* Called by `defineCapability`, so the mistake is named where it is written rather than on the day somebody
|
|
33
|
+
* changes a model.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertVectorIndexContributions(capability: string, indexes: readonly VectorIndexContribution[], jobs: readonly string[]): void;
|
|
36
|
+
//# sourceMappingURL=vectorIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectorIndex.d.ts","sourceRoot":"","sources":["../../src/capability/vectorIndex.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,uBAAuB,EAAE,EAC3C,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,IAAI,CAiBN"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { InternalError } from "../error/pithyError.js";
|
|
4
|
+
//#region src/capability/vectorIndex.ts
|
|
5
|
+
/**
|
|
6
|
+
* Refuse a declaration no reprocess could run: a duplicate name, or a job the capability does not register.
|
|
7
|
+
* Called by `defineCapability`, so the mistake is named where it is written rather than on the day somebody
|
|
8
|
+
* changes a model.
|
|
9
|
+
*/
|
|
10
|
+
function assertVectorIndexContributions(capability, indexes, jobs) {
|
|
11
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const index of indexes) {
|
|
13
|
+
if (seen.has(index.name)) throw new InternalError({
|
|
14
|
+
message: `The ${capability} capability declares the vector index \`${index.name}\` twice.`,
|
|
15
|
+
detail: `vectorIndexes on ${capability} repeats ${index.name}`
|
|
16
|
+
});
|
|
17
|
+
seen.add(index.name);
|
|
18
|
+
if (!jobs.includes(index.reprocessJob)) throw new InternalError({
|
|
19
|
+
message: `The ${capability} capability's vector index \`${index.name}\` names a reprocess job it does not register.`,
|
|
20
|
+
detail: `vectorIndexes on ${capability}: no workflow job ${index.reprocessJob}; registered: ${jobs.join(", ") || "none"}`
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { assertVectorIndexContributions };
|