@pithy-sh/core 0.1.2 → 0.1.4
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/dist/address/address.d.ts +57 -0
- package/dist/address/address.d.ts.map +1 -0
- package/dist/address/address.js +71 -0
- package/dist/audit/auditEvent.d.ts +89 -0
- package/dist/audit/auditEvent.d.ts.map +1 -0
- package/dist/audit/auditEvent.js +69 -0
- package/dist/audit/recorder.d.ts +18 -0
- package/dist/audit/recorder.d.ts.map +1 -0
- package/dist/audit/recorder.js +8 -0
- package/dist/capability/bindings.d.ts +62 -0
- package/dist/capability/bindings.d.ts.map +1 -0
- package/dist/capability/bindings.js +97 -0
- package/dist/capability/capability.d.ts +501 -0
- package/dist/capability/capability.d.ts.map +1 -0
- package/dist/capability/capability.js +16 -0
- package/dist/capability/client.d.ts +70 -0
- package/dist/capability/client.d.ts.map +1 -0
- package/dist/capability/client.js +86 -0
- package/dist/capability/compose.d.ts +25 -0
- package/dist/capability/compose.d.ts.map +1 -0
- package/dist/capability/compose.js +54 -0
- package/dist/capability/composition.d.ts +26 -0
- package/dist/capability/composition.d.ts.map +1 -0
- package/dist/capability/composition.js +84 -0
- package/dist/capability/config.d.ts +7 -0
- package/dist/capability/config.d.ts.map +1 -0
- package/dist/capability/config.js +14 -0
- package/dist/capability/devSecret.d.ts +32 -0
- package/dist/capability/devSecret.d.ts.map +1 -0
- package/dist/capability/devSecret.js +28 -0
- package/dist/capability/manifest.d.ts +298 -0
- package/dist/capability/manifest.d.ts.map +1 -0
- package/dist/capability/manifest.js +356 -0
- package/dist/capability/secretOrigin.d.ts +121 -0
- package/dist/capability/secretOrigin.d.ts.map +1 -0
- package/dist/capability/secretOrigin.js +134 -0
- package/dist/capability/settings.d.ts +119 -0
- package/dist/capability/settings.d.ts.map +1 -0
- package/dist/capability/settings.js +66 -0
- package/dist/capability/validateBindings.d.ts +4 -0
- package/dist/capability/validateBindings.d.ts.map +1 -0
- package/dist/capability/validateBindings.js +34 -0
- package/dist/capability/vanishingKey.d.ts +31 -0
- package/dist/capability/vanishingKey.d.ts.map +1 -0
- package/dist/capability/vanishingKey.js +79 -0
- package/dist/controlPlane/audit/actions.d.ts +70 -0
- package/dist/controlPlane/audit/actions.d.ts.map +1 -0
- package/dist/controlPlane/audit/actions.js +76 -0
- package/dist/controlPlane/capability.d.ts +47 -0
- package/dist/controlPlane/capability.d.ts.map +1 -0
- package/dist/controlPlane/capability.js +138 -0
- package/dist/controlPlane/config/config.d.ts +19 -0
- package/dist/controlPlane/config/config.d.ts.map +1 -0
- package/dist/controlPlane/config/config.js +85 -0
- package/dist/controlPlane/context.d.ts +27 -0
- package/dist/controlPlane/context.d.ts.map +1 -0
- package/dist/controlPlane/context.js +29 -0
- package/dist/controlPlane/data/connection.d.ts +79 -0
- package/dist/controlPlane/data/connection.d.ts.map +1 -0
- package/dist/controlPlane/data/connection.js +55 -0
- package/dist/controlPlane/data/keyLifecycle.d.ts +62 -0
- package/dist/controlPlane/data/keyLifecycle.d.ts.map +1 -0
- package/dist/controlPlane/data/keyLifecycle.js +126 -0
- package/dist/controlPlane/data/replay.d.ts +18 -0
- package/dist/controlPlane/data/replay.d.ts.map +1 -0
- package/dist/controlPlane/data/replay.js +20 -0
- package/dist/controlPlane/data/tables.d.ts +34 -0
- package/dist/controlPlane/data/tables.d.ts.map +1 -0
- package/dist/controlPlane/data/tables.js +37 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts +313 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -0
- package/dist/controlPlane/discovery/adminRoute.js +136 -0
- package/dist/controlPlane/discovery/configuration.d.ts +108 -0
- package/dist/controlPlane/discovery/configuration.d.ts.map +1 -0
- package/dist/controlPlane/discovery/configuration.js +187 -0
- package/dist/controlPlane/discovery/drift.d.ts +64 -0
- package/dist/controlPlane/discovery/drift.d.ts.map +1 -0
- package/dist/controlPlane/discovery/drift.js +58 -0
- package/dist/controlPlane/discovery/health.d.ts +99 -0
- package/dist/controlPlane/discovery/health.d.ts.map +1 -0
- package/dist/controlPlane/discovery/health.js +151 -0
- package/dist/controlPlane/discovery/healthSummary.d.ts +236 -0
- package/dist/controlPlane/discovery/healthSummary.d.ts.map +1 -0
- package/dist/controlPlane/discovery/healthSummary.js +278 -0
- package/dist/controlPlane/error/errors.d.ts +67 -0
- package/dist/controlPlane/error/errors.d.ts.map +1 -0
- package/dist/controlPlane/error/errors.js +79 -0
- package/dist/controlPlane/http/cors.d.ts +112 -0
- package/dist/controlPlane/http/cors.d.ts.map +1 -0
- package/dist/controlPlane/http/cors.js +192 -0
- package/dist/controlPlane/http/guard.d.ts +77 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -0
- package/dist/controlPlane/http/guard.js +142 -0
- package/dist/controlPlane/http/handlers.d.ts +179 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -0
- package/dist/controlPlane/http/handlers.js +245 -0
- package/dist/controlPlane/http/responses.d.ts +99 -0
- package/dist/controlPlane/http/responses.d.ts.map +1 -0
- package/dist/controlPlane/http/responses.js +63 -0
- package/dist/controlPlane/http/routes.d.ts +40 -0
- package/dist/controlPlane/http/routes.d.ts.map +1 -0
- package/dist/controlPlane/http/routes.js +83 -0
- package/dist/controlPlane/http/schemas.d.ts +39 -0
- package/dist/controlPlane/http/schemas.d.ts.map +1 -0
- package/dist/controlPlane/http/schemas.js +31 -0
- package/dist/controlPlane/http/verify.d.ts +84 -0
- package/dist/controlPlane/http/verify.d.ts.map +1 -0
- package/dist/controlPlane/http/verify.js +65 -0
- package/dist/controlPlane/migrations/0001_init.d.ts +51 -0
- package/dist/controlPlane/migrations/0001_init.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0001_init.js +65 -0
- package/dist/controlPlane/replay/d1Guard.d.ts +29 -0
- package/dist/controlPlane/replay/d1Guard.d.ts.map +1 -0
- package/dist/controlPlane/replay/d1Guard.js +39 -0
- package/dist/controlPlane/replay/guard.d.ts +52 -0
- package/dist/controlPlane/replay/guard.d.ts.map +1 -0
- package/dist/controlPlane/replay/guard.js +0 -0
- package/dist/controlPlane/replay/kvGuard.d.ts +99 -0
- package/dist/controlPlane/replay/kvGuard.d.ts.map +1 -0
- package/dist/controlPlane/replay/kvGuard.js +101 -0
- package/dist/controlPlane/scope/scope.d.ts +68 -0
- package/dist/controlPlane/scope/scope.d.ts.map +1 -0
- package/dist/controlPlane/scope/scope.js +63 -0
- package/dist/controlPlane/token/base64url.d.ts +10 -0
- package/dist/controlPlane/token/base64url.d.ts.map +1 -0
- package/dist/controlPlane/token/base64url.js +48 -0
- package/dist/controlPlane/token/claims.d.ts +83 -0
- package/dist/controlPlane/token/claims.d.ts.map +1 -0
- package/dist/controlPlane/token/claims.js +86 -0
- package/dist/controlPlane/token/digest.d.ts +44 -0
- package/dist/controlPlane/token/digest.d.ts.map +1 -0
- package/dist/controlPlane/token/digest.js +56 -0
- package/dist/controlPlane/token/jws.d.ts +52 -0
- package/dist/controlPlane/token/jws.d.ts.map +1 -0
- package/dist/controlPlane/token/jws.js +65 -0
- package/dist/controlPlane/token/mint.d.ts +56 -0
- package/dist/controlPlane/token/mint.d.ts.map +1 -0
- package/dist/controlPlane/token/mint.js +55 -0
- package/dist/controlPlane/wire.d.ts +122 -0
- package/dist/controlPlane/wire.d.ts.map +1 -0
- package/dist/controlPlane/wire.js +123 -0
- package/dist/createBackend.d.ts +58 -0
- package/dist/createBackend.d.ts.map +1 -0
- package/dist/createBackend.js +170 -0
- package/dist/createEntrypoint.d.ts +53 -0
- package/dist/createEntrypoint.d.ts.map +1 -0
- package/dist/createEntrypoint.js +52 -0
- package/dist/data/boundParameters.d.ts +83 -0
- package/dist/data/boundParameters.d.ts.map +1 -0
- package/dist/data/boundParameters.js +162 -0
- package/dist/data/codecs.d.ts +34 -0
- package/dist/data/codecs.d.ts.map +1 -0
- package/dist/data/codecs.js +135 -0
- package/dist/data/cursor.d.ts +81 -0
- package/dist/data/cursor.d.ts.map +1 -0
- package/dist/data/cursor.js +104 -0
- package/dist/data/databases.d.ts +55 -0
- package/dist/data/databases.d.ts.map +1 -0
- package/dist/data/databases.js +32 -0
- package/dist/data/db.d.ts +42 -0
- package/dist/data/db.d.ts.map +1 -0
- package/dist/data/db.js +24 -0
- package/dist/data/withD1Retry.d.ts +91 -0
- package/dist/data/withD1Retry.d.ts.map +1 -0
- package/dist/data/withD1Retry.js +152 -0
- package/dist/entitlement/entitlement.d.ts +118 -0
- package/dist/entitlement/entitlement.d.ts.map +1 -0
- package/dist/entitlement/entitlement.js +59 -0
- package/dist/entitlement/gateScan.d.ts +26 -0
- package/dist/entitlement/gateScan.d.ts.map +1 -0
- package/dist/entitlement/gateScan.js +96 -0
- package/dist/entitlement/require.d.ts +12 -0
- package/dist/entitlement/require.d.ts.map +1 -0
- package/dist/entitlement/require.js +123 -0
- package/dist/env/ambient.d.ts +52 -0
- package/dist/env/ambient.d.ts.map +1 -0
- package/dist/env/ambient.js +35 -0
- package/dist/env/ci.d.ts +36 -0
- package/dist/env/ci.d.ts.map +1 -0
- package/dist/env/ci.js +40 -0
- package/dist/env/stem.d.ts +23 -0
- package/dist/env/stem.d.ts.map +1 -0
- package/dist/env/stem.js +29 -0
- package/dist/error/cause.d.ts +143 -0
- package/dist/error/cause.d.ts.map +1 -0
- package/dist/error/cause.js +192 -0
- package/dist/error/client.d.ts +35 -0
- package/dist/error/client.d.ts.map +1 -0
- package/dist/error/client.js +38 -0
- package/dist/error/extend.d.ts +101 -0
- package/dist/error/extend.d.ts.map +1 -0
- package/dist/error/extend.js +76 -0
- package/dist/error/http.d.ts +1516 -0
- package/dist/error/http.d.ts.map +1 -0
- package/dist/error/http.js +72 -0
- package/dist/error/payload.d.ts +3034 -0
- package/dist/error/payload.d.ts.map +1 -0
- package/dist/error/payload.js +1110 -0
- package/dist/error/pithyError.d.ts +154 -0
- package/dist/error/pithyError.d.ts.map +1 -0
- package/dist/error/pithyError.js +197 -0
- package/dist/error/terminal.d.ts +28 -0
- package/dist/error/terminal.d.ts.map +1 -0
- package/dist/error/terminal.js +33 -0
- package/dist/http/authContext.d.ts +21 -0
- package/dist/http/authContext.d.ts.map +1 -0
- package/dist/http/authContext.js +23 -0
- package/dist/http/routeContract.d.ts +39 -0
- package/dist/http/routeContract.d.ts.map +1 -0
- package/dist/http/routeContract.js +82 -0
- package/dist/http/sameOrigin.d.ts +47 -0
- package/dist/http/sameOrigin.d.ts.map +1 -0
- package/dist/http/sameOrigin.js +24 -0
- package/dist/http/signedWebhook.d.ts +225 -0
- package/dist/http/signedWebhook.d.ts.map +1 -0
- package/dist/http/signedWebhook.js +247 -0
- package/dist/http/validation.d.ts +37 -0
- package/dist/http/validation.d.ts.map +1 -0
- package/dist/http/validation.js +14 -0
- package/dist/http/verification.d.ts +10 -0
- package/dist/http/verification.d.ts.map +1 -0
- package/dist/http/verification.js +17 -0
- package/dist/i18n/acceptLanguage.d.ts +22 -0
- package/dist/i18n/acceptLanguage.d.ts.map +1 -0
- package/dist/i18n/acceptLanguage.js +55 -0
- package/dist/i18n/catalog.d.ts +39 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +57 -0
- package/dist/i18n/locale.d.ts +74 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +109 -0
- package/dist/i18n/localeMarker.d.ts +29 -0
- package/dist/i18n/localeMarker.d.ts.map +1 -0
- package/dist/i18n/localeMarker.js +112 -0
- package/dist/i18n/match.d.ts +26 -0
- package/dist/i18n/match.d.ts.map +1 -0
- package/dist/i18n/match.js +88 -0
- package/dist/i18n/registry.d.ts +25 -0
- package/dist/i18n/registry.d.ts.map +1 -0
- package/dist/i18n/registry.js +51 -0
- package/dist/i18n/translator.d.ts +107 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +80 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/kv/kv.d.ts +166 -0
- package/dist/kv/kv.d.ts.map +1 -0
- package/dist/kv/kv.js +261 -0
- package/dist/kv/namespaces.d.ts +58 -0
- package/dist/kv/namespaces.d.ts.map +1 -0
- package/dist/kv/namespaces.js +40 -0
- package/dist/logger/local.d.ts +41 -0
- package/dist/logger/local.d.ts.map +1 -0
- package/dist/logger/local.js +45 -0
- package/dist/logger/logger.d.ts +56 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +83 -0
- package/dist/logger/record.d.ts +919 -0
- package/dist/logger/record.d.ts.map +1 -0
- package/dist/logger/record.js +79 -0
- package/dist/logger/worker.d.ts +74 -0
- package/dist/logger/worker.d.ts.map +1 -0
- package/dist/logger/worker.js +54 -0
- package/dist/migrations/batch.d.ts +10 -0
- package/dist/migrations/batch.d.ts.map +1 -0
- package/dist/migrations/batch.js +193 -0
- package/dist/migrations/bookkeeping.d.ts +24 -0
- package/dist/migrations/bookkeeping.d.ts.map +1 -0
- package/dist/migrations/bookkeeping.js +57 -0
- package/dist/migrations/owner.d.ts +41 -0
- package/dist/migrations/owner.d.ts.map +1 -0
- package/dist/migrations/owner.js +105 -0
- package/dist/migrations/registry.d.ts +36 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +70 -0
- package/dist/migrations/runner.d.ts +67 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +218 -0
- package/dist/naming/domains.d.ts +108 -0
- package/dist/naming/domains.d.ts.map +1 -0
- package/dist/naming/domains.js +156 -0
- package/dist/naming/environment.d.ts +118 -0
- package/dist/naming/environment.d.ts.map +1 -0
- package/dist/naming/environment.js +190 -0
- package/dist/naming/feature.d.ts +68 -0
- package/dist/naming/feature.d.ts.map +1 -0
- package/dist/naming/feature.js +106 -0
- package/dist/naming/limits.d.ts +229 -0
- package/dist/naming/limits.d.ts.map +1 -0
- package/dist/naming/limits.js +187 -0
- package/dist/naming/provisionScope.d.ts +91 -0
- package/dist/naming/provisionScope.d.ts.map +1 -0
- package/dist/naming/provisionScope.js +65 -0
- package/dist/naming/resource.d.ts +169 -0
- package/dist/naming/resource.d.ts.map +1 -0
- package/dist/naming/resource.js +220 -0
- package/dist/naming/resourceNames.d.ts +83 -0
- package/dist/naming/resourceNames.d.ts.map +1 -0
- package/dist/naming/resourceNames.js +102 -0
- package/dist/naming/segment.d.ts +29 -0
- package/dist/naming/segment.d.ts.map +1 -0
- package/dist/naming/segment.js +31 -0
- package/dist/projection/asRead.d.ts +72 -0
- package/dist/projection/asRead.d.ts.map +1 -0
- package/dist/projection/asRead.js +169 -0
- package/dist/projection/published.d.ts +95 -0
- package/dist/projection/published.d.ts.map +1 -0
- package/dist/projection/published.js +114 -0
- package/dist/schema/describedness.d.ts +55 -0
- package/dist/schema/describedness.d.ts.map +1 -0
- package/dist/schema/describedness.js +180 -0
- package/dist/seed/compose.d.ts +41 -0
- package/dist/seed/compose.d.ts.map +1 -0
- package/dist/seed/compose.js +51 -0
- package/dist/seed/devLogin.d.ts +50 -0
- package/dist/seed/devLogin.d.ts.map +1 -0
- package/dist/seed/devLogin.js +52 -0
- package/dist/seed/exampleIdentities.d.ts +58 -0
- package/dist/seed/exampleIdentities.d.ts.map +1 -0
- package/dist/seed/exampleIdentities.js +26 -0
- package/dist/seed/metadata.d.ts +20 -0
- package/dist/seed/metadata.d.ts.map +1 -0
- package/dist/seed/metadata.js +27 -0
- package/dist/seed/seed.d.ts +264 -0
- package/dist/seed/seed.d.ts.map +1 -0
- package/dist/seed/seed.js +37 -0
- package/dist/seed/seededRows.d.ts +26 -0
- package/dist/seed/seededRows.d.ts.map +1 -0
- package/dist/seed/seededRows.js +20 -0
- package/dist/seed/writeD1.d.ts +43 -0
- package/dist/seed/writeD1.d.ts.map +1 -0
- package/dist/seed/writeD1.js +73 -0
- package/dist/seed/writeKv.d.ts +29 -0
- package/dist/seed/writeKv.d.ts.map +1 -0
- package/dist/seed/writeKv.js +63 -0
- package/dist/semver/semver.d.ts +62 -0
- package/dist/semver/semver.d.ts.map +1 -0
- package/dist/semver/semver.js +137 -0
- package/dist/text/comments.d.ts +62 -0
- package/dist/text/comments.d.ts.map +1 -0
- package/dist/text/comments.js +161 -0
- package/dist/version.generated.d.ts +5 -0
- package/dist/version.generated.d.ts.map +1 -0
- package/dist/version.generated.js +7 -0
- package/dist/worker/health.d.ts +39 -0
- package/dist/worker/health.d.ts.map +1 -0
- package/dist/worker/health.js +41 -0
- package/dist/worker/identity.d.ts +191 -0
- package/dist/worker/identity.d.ts.map +1 -0
- package/dist/worker/identity.js +162 -0
- package/dist/workflow/bindings.d.ts +41 -0
- package/dist/workflow/bindings.d.ts.map +1 -0
- package/dist/workflow/bindings.js +53 -0
- package/dist/workflow/dispatch.d.ts +98 -0
- package/dist/workflow/dispatch.d.ts.map +1 -0
- package/dist/workflow/dispatch.js +148 -0
- package/dist/workflow/dispatchRoute.d.ts +80 -0
- package/dist/workflow/dispatchRoute.d.ts.map +1 -0
- package/dist/workflow/dispatchRoute.js +134 -0
- package/dist/workflow/faults.d.ts +97 -0
- package/dist/workflow/faults.d.ts.map +1 -0
- package/dist/workflow/faults.js +111 -0
- package/dist/workflow/host.d.ts +176 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +116 -0
- package/dist/workflow/hostEntry.d.ts +50 -0
- package/dist/workflow/hostEntry.d.ts.map +1 -0
- package/dist/workflow/hostEntry.js +24 -0
- package/dist/workflow/hostEnv.d.ts +143 -0
- package/dist/workflow/hostEnv.d.ts.map +1 -0
- package/dist/workflow/hostEnv.js +170 -0
- package/dist/workflow/loopback.d.ts +52 -0
- package/dist/workflow/loopback.d.ts.map +1 -0
- package/dist/workflow/loopback.js +76 -0
- package/dist/workflow/naming.d.ts +79 -0
- package/dist/workflow/naming.d.ts.map +1 -0
- package/dist/workflow/naming.js +138 -0
- package/dist/workflow/register.d.ts +19 -0
- package/dist/workflow/register.d.ts.map +1 -0
- package/dist/workflow/register.js +40 -0
- package/dist/workflow/schemas.d.ts +44 -0
- package/dist/workflow/schemas.d.ts.map +1 -0
- package/dist/workflow/schemas.js +43 -0
- package/dist/workflow/spec.d.ts +81 -0
- package/dist/workflow/spec.d.ts.map +1 -0
- package/dist/workflow/spec.js +0 -0
- package/dist/workflow/stepMessage.d.ts +122 -0
- package/dist/workflow/stepMessage.d.ts.map +1 -0
- package/dist/workflow/stepMessage.js +152 -0
- package/package.json +20 -9
- package/src/capability/manifest.ts +21 -0
- package/src/version.generated.ts +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email addresses: the one rule for deciding whether two strings are the same person.
|
|
3
|
+
*
|
|
4
|
+
* Four capabilities compare addresses. `auth` matches a sign-in address, `email` suppresses one,
|
|
5
|
+
* `support` links a sender by `From`, and `testers` invites one. Four rules is four chances to
|
|
6
|
+
* disagree, and a disagreement here does not present as anything about addresses — it presents as "the
|
|
7
|
+
* suppression list did not work", or as one customer with two support threads, or as an invitation
|
|
8
|
+
* nobody can accept because it was minted against `Ada@` and every session carries `ada@`.
|
|
9
|
+
*
|
|
10
|
+
* So the rule is written down once, here, and it is deliberately small.
|
|
11
|
+
*
|
|
12
|
+
* ## What it normalizes
|
|
13
|
+
*
|
|
14
|
+
* - **Surrounding whitespace.** Trimmed. An address pasted out of a spreadsheet carries it.
|
|
15
|
+
* - **Case, in both halves.** RFC 5321 says the local part is case-sensitive; no mail provider in
|
|
16
|
+
* practice treats it that way, and treating `Ada@` and `ada@` as two people splits one customer's
|
|
17
|
+
* history in half. The failure the other way — two genuinely distinct mailboxes differing only in
|
|
18
|
+
* case — does not exist in the wild.
|
|
19
|
+
*
|
|
20
|
+
* ## What it deliberately does not
|
|
21
|
+
*
|
|
22
|
+
* - **Subaddressing (`ada+shop@`) and dots in the local part.** Gmail collapses both. Most providers do
|
|
23
|
+
* not, and folding them would merge two real people on a self-hosted domain. Showing one customer as
|
|
24
|
+
* two is recoverable; showing two customers as one is not.
|
|
25
|
+
* - **Unicode normalization.** No NFC, no NFKC, no case folding beyond `toLowerCase`. NFKC in
|
|
26
|
+
* particular maps distinct codepoints onto ASCII, which is exactly how one address comes to match
|
|
27
|
+
* another that was never issued to the same person — the confusable-domain attack, performed by us,
|
|
28
|
+
* on our own comparison. An address whose bytes differ stays a different address.
|
|
29
|
+
* - **Validation.** {@link normalizeAddress} takes any string and returns its normal form. Whether a
|
|
30
|
+
* string *is* an address is a question for the boundary that accepted it — Zod, or {@link
|
|
31
|
+
* parseAddress} where the input is a mail header. A normalizer that also rejects is a normalizer
|
|
32
|
+
* whose callers stop calling it.
|
|
33
|
+
* - **IDN / punycode.** A unicode domain is not converted to its `xn--` form, or back. Both spellings
|
|
34
|
+
* are stable under this rule; a project that accepts one must accept only one, at its boundary.
|
|
35
|
+
*/
|
|
36
|
+
/** The longest address accepted. RFC 5321 caps a path at 256 octets; anything longer is malformed. */
|
|
37
|
+
export declare const MAX_ADDRESS_LENGTH = 256;
|
|
38
|
+
/**
|
|
39
|
+
* One address, in the one form every comparison in the kit is against.
|
|
40
|
+
*
|
|
41
|
+
* Trimmed and lowercased, and nothing else. Total: every string has a normal form, including the ones
|
|
42
|
+
* that are not addresses. Use {@link parseAddress} where the input is attacker-supplied text that may
|
|
43
|
+
* not contain an address at all.
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizeAddress(value: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Read one address out of a header-shaped string, or refuse.
|
|
48
|
+
*
|
|
49
|
+
* Unwraps `Ada Lovelace <ada@example.com>`, bounds the length, refuses anything that is not
|
|
50
|
+
* recognizably a single address, and returns it through {@link normalizeAddress}.
|
|
51
|
+
*
|
|
52
|
+
* Returns `undefined` rather than throwing. Inbound mail is attacker-controlled, so a malformed `From`
|
|
53
|
+
* is an expected input, not an exception — the caller decides whether that means "drop the message" or
|
|
54
|
+
* "store it with no sender", and both are reasonable.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseAddress(value: string | null | undefined): string | undefined;
|
|
57
|
+
//# sourceMappingURL=address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../src/address/address.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,sGAAsG;AACtG,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAmBjF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/address/address.ts
|
|
2
|
+
/**
|
|
3
|
+
* Email addresses: the one rule for deciding whether two strings are the same person.
|
|
4
|
+
*
|
|
5
|
+
* Four capabilities compare addresses. `auth` matches a sign-in address, `email` suppresses one,
|
|
6
|
+
* `support` links a sender by `From`, and `testers` invites one. Four rules is four chances to
|
|
7
|
+
* disagree, and a disagreement here does not present as anything about addresses — it presents as "the
|
|
8
|
+
* suppression list did not work", or as one customer with two support threads, or as an invitation
|
|
9
|
+
* nobody can accept because it was minted against `Ada@` and every session carries `ada@`.
|
|
10
|
+
*
|
|
11
|
+
* So the rule is written down once, here, and it is deliberately small.
|
|
12
|
+
*
|
|
13
|
+
* ## What it normalizes
|
|
14
|
+
*
|
|
15
|
+
* - **Surrounding whitespace.** Trimmed. An address pasted out of a spreadsheet carries it.
|
|
16
|
+
* - **Case, in both halves.** RFC 5321 says the local part is case-sensitive; no mail provider in
|
|
17
|
+
* practice treats it that way, and treating `Ada@` and `ada@` as two people splits one customer's
|
|
18
|
+
* history in half. The failure the other way — two genuinely distinct mailboxes differing only in
|
|
19
|
+
* case — does not exist in the wild.
|
|
20
|
+
*
|
|
21
|
+
* ## What it deliberately does not
|
|
22
|
+
*
|
|
23
|
+
* - **Subaddressing (`ada+shop@`) and dots in the local part.** Gmail collapses both. Most providers do
|
|
24
|
+
* not, and folding them would merge two real people on a self-hosted domain. Showing one customer as
|
|
25
|
+
* two is recoverable; showing two customers as one is not.
|
|
26
|
+
* - **Unicode normalization.** No NFC, no NFKC, no case folding beyond `toLowerCase`. NFKC in
|
|
27
|
+
* particular maps distinct codepoints onto ASCII, which is exactly how one address comes to match
|
|
28
|
+
* another that was never issued to the same person — the confusable-domain attack, performed by us,
|
|
29
|
+
* on our own comparison. An address whose bytes differ stays a different address.
|
|
30
|
+
* - **Validation.** {@link normalizeAddress} takes any string and returns its normal form. Whether a
|
|
31
|
+
* string *is* an address is a question for the boundary that accepted it — Zod, or {@link
|
|
32
|
+
* parseAddress} where the input is a mail header. A normalizer that also rejects is a normalizer
|
|
33
|
+
* whose callers stop calling it.
|
|
34
|
+
* - **IDN / punycode.** A unicode domain is not converted to its `xn--` form, or back. Both spellings
|
|
35
|
+
* are stable under this rule; a project that accepts one must accept only one, at its boundary.
|
|
36
|
+
*/
|
|
37
|
+
/** The longest address accepted. RFC 5321 caps a path at 256 octets; anything longer is malformed. */
|
|
38
|
+
const MAX_ADDRESS_LENGTH = 256;
|
|
39
|
+
/**
|
|
40
|
+
* One address, in the one form every comparison in the kit is against.
|
|
41
|
+
*
|
|
42
|
+
* Trimmed and lowercased, and nothing else. Total: every string has a normal form, including the ones
|
|
43
|
+
* that are not addresses. Use {@link parseAddress} where the input is attacker-supplied text that may
|
|
44
|
+
* not contain an address at all.
|
|
45
|
+
*/
|
|
46
|
+
function normalizeAddress(value) {
|
|
47
|
+
return value.trim().toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read one address out of a header-shaped string, or refuse.
|
|
51
|
+
*
|
|
52
|
+
* Unwraps `Ada Lovelace <ada@example.com>`, bounds the length, refuses anything that is not
|
|
53
|
+
* recognizably a single address, and returns it through {@link normalizeAddress}.
|
|
54
|
+
*
|
|
55
|
+
* Returns `undefined` rather than throwing. Inbound mail is attacker-controlled, so a malformed `From`
|
|
56
|
+
* is an expected input, not an exception — the caller decides whether that means "drop the message" or
|
|
57
|
+
* "store it with no sender", and both are reasonable.
|
|
58
|
+
*/
|
|
59
|
+
function parseAddress(value) {
|
|
60
|
+
if (typeof value !== "string") return void 0;
|
|
61
|
+
let candidate = value.trim();
|
|
62
|
+
if (candidate.length === 0 || candidate.length > 256) return void 0;
|
|
63
|
+
const angled = /<([^<>]*)>\s*$/.exec(candidate);
|
|
64
|
+
if (angled?.[1] !== void 0) candidate = angled[1].trim();
|
|
65
|
+
const at = candidate.lastIndexOf("@");
|
|
66
|
+
if (at <= 0 || at === candidate.length - 1) return void 0;
|
|
67
|
+
if (!candidate.slice(at + 1).includes(".") || /[\s,;<>"]/.test(candidate)) return void 0;
|
|
68
|
+
return normalizeAddress(candidate);
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { MAX_ADDRESS_LENGTH, normalizeAddress, parseAddress };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The action taxonomy is **federated, not centralized**: `action` is an open, namespace-validated
|
|
4
|
+
* `domain/reason` string, and each capability owns and exports its own action constants (`auth/*`,
|
|
5
|
+
* `entitlement/*`, `admin/*`) — added without touching core, the way migrations and table prefixes
|
|
6
|
+
* already federate. Core never holds a closed union of every event.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AuditAction: z.ZodString;
|
|
9
|
+
export type AuditAction = z.output<typeof AuditAction>;
|
|
10
|
+
/** The result of an audited action. A blocked authorization attempt is a first-class record. */
|
|
11
|
+
export declare const AuditOutcome: z.ZodEnum<{
|
|
12
|
+
denied: "denied";
|
|
13
|
+
failure: "failure";
|
|
14
|
+
success: "success";
|
|
15
|
+
}>;
|
|
16
|
+
export type AuditOutcome = z.output<typeof AuditOutcome>;
|
|
17
|
+
/** How serious the event is — orthogonal to outcome; drives dashboard filtering and alerting. */
|
|
18
|
+
export declare const AuditSeverity: z.ZodEnum<{
|
|
19
|
+
critical: "critical";
|
|
20
|
+
info: "info";
|
|
21
|
+
warning: "warning";
|
|
22
|
+
}>;
|
|
23
|
+
export type AuditSeverity = z.output<typeof AuditSeverity>;
|
|
24
|
+
/**
|
|
25
|
+
* What kind of principal acted. `anonymous` covers an unauthenticated request; `system` an internal job.
|
|
26
|
+
*
|
|
27
|
+
* `control-plane` is its own kind rather than a flavour of `service` because the question an adopter
|
|
28
|
+
* actually asks is "what did the management client do", separately from "what did my users do". A
|
|
29
|
+
* control-plane caller is not a user of their app at all — it holds no session and owns no user row —
|
|
30
|
+
* so folding it into `user` or `service` would make that question unanswerable from the trail.
|
|
31
|
+
*/
|
|
32
|
+
export declare const AuditActorType: z.ZodEnum<{
|
|
33
|
+
anonymous: "anonymous";
|
|
34
|
+
"control-plane": "control-plane";
|
|
35
|
+
service: "service";
|
|
36
|
+
system: "system";
|
|
37
|
+
user: "user";
|
|
38
|
+
}>;
|
|
39
|
+
export type AuditActorType = z.output<typeof AuditActorType>;
|
|
40
|
+
/** Capability-specific structured detail attached to an event; Zod-validated on write and read. */
|
|
41
|
+
export declare const AuditMetadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
42
|
+
export type AuditMetadata = z.output<typeof AuditMetadata>;
|
|
43
|
+
/**
|
|
44
|
+
* One audit event as an emitter supplies it. `occurredAt` is optional — the recorder stamps it at
|
|
45
|
+
* write time when absent. The nullable correlation fields (`actorId`, `sessionId`, request fields,
|
|
46
|
+
* resource fields) are populated from request context where available; an emitter sets only what it
|
|
47
|
+
* knows. The recorder turns this into the stored `pithy_audit_events` row.
|
|
48
|
+
*
|
|
49
|
+
* `tenant` is the one field here that names a *dimension* of the trail rather than a detail of the
|
|
50
|
+
* action, and it is on this seam rather than on the recorder's origin for the reason the origin fields
|
|
51
|
+
* are not: the recorder cannot know it. `project`, `environment` and `worker` are constant across every
|
|
52
|
+
* row a multi-tenant Worker writes, so without this the trail cannot be read per customer at all — and
|
|
53
|
+
* it must be stamped here, at write time, because the tenant of an action is a fact *at the time of the
|
|
54
|
+
* action* while membership is a fact *now*. Deriving one from the other later moves a year of history
|
|
55
|
+
* between tenants every time somebody joins or leaves.
|
|
56
|
+
*/
|
|
57
|
+
export declare const AuditEvent: z.ZodObject<{
|
|
58
|
+
action: z.ZodString;
|
|
59
|
+
outcome: z.ZodEnum<{
|
|
60
|
+
denied: "denied";
|
|
61
|
+
failure: "failure";
|
|
62
|
+
success: "success";
|
|
63
|
+
}>;
|
|
64
|
+
severity: z.ZodDefault<z.ZodEnum<{
|
|
65
|
+
critical: "critical";
|
|
66
|
+
info: "info";
|
|
67
|
+
warning: "warning";
|
|
68
|
+
}>>;
|
|
69
|
+
actorType: z.ZodEnum<{
|
|
70
|
+
anonymous: "anonymous";
|
|
71
|
+
"control-plane": "control-plane";
|
|
72
|
+
service: "service";
|
|
73
|
+
system: "system";
|
|
74
|
+
user: "user";
|
|
75
|
+
}>;
|
|
76
|
+
actorId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
77
|
+
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
78
|
+
resourceType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
+
resourceId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
80
|
+
ip: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
81
|
+
userAgent: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
82
|
+
requestId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
83
|
+
tenant: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
84
|
+
occurredAt: z.ZodOptional<z.ZodDate>;
|
|
85
|
+
metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
export type AuditEvent = z.output<typeof AuditEvent>;
|
|
88
|
+
export type AuditEventInput = z.input<typeof AuditEvent>;
|
|
89
|
+
//# sourceMappingURL=auditEvent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auditEvent.d.ts","sourceRoot":"","sources":["../../src/audit/auditEvent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB;;;;;GAKG;AACH,eAAO,MAAM,WAAW,aAM+F,CAAC;AACxH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAEvD,gGAAgG;AAChG,eAAO,MAAM,YAAY;;;;EAItB,CAAC;AACJ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAEzD,iGAAiG;AACjG,eAAO,MAAM,aAAa;;;;EAIvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;EAIxB,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D,mGAAmG;AACnG,eAAO,MAAM,aAAa,wCAIvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCiF,CAAC;AACzG,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AACrD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The action taxonomy is **federated, not centralized**: `action` is an open, namespace-validated
|
|
4
|
+
* `domain/reason` string, and each capability owns and exports its own action constants (`auth/*`,
|
|
5
|
+
* `entitlement/*`, `admin/*`) — added without touching core, the way migrations and table prefixes
|
|
6
|
+
* already federate. Core never holds a closed union of every event.
|
|
7
|
+
*/
|
|
8
|
+
const AuditAction = z.string().regex(/^[a-z][a-z0-9]*\/[a-z][a-z0-9_]*$/, "An audit action must be a namespaced `domain/reason` code: lowercase, the domain and reason separated by a single `/` (e.g. `auth/login`).").describe("A namespaced `domain/reason` action code (`auth/login`, `admin/config_changed`); the federated taxonomy.");
|
|
9
|
+
/** The result of an audited action. A blocked authorization attempt is a first-class record. */
|
|
10
|
+
const AuditOutcome = z.enum([
|
|
11
|
+
"success",
|
|
12
|
+
"failure",
|
|
13
|
+
"denied"
|
|
14
|
+
]).describe("The result of the audited action: `success`, `failure` (it was attempted and errored), or `denied` (an authorization gate blocked it). `denied` is first-class — blocked attempts are recorded, not only successes.");
|
|
15
|
+
/** How serious the event is — orthogonal to outcome; drives dashboard filtering and alerting. */
|
|
16
|
+
const AuditSeverity = z.enum([
|
|
17
|
+
"info",
|
|
18
|
+
"warning",
|
|
19
|
+
"critical"
|
|
20
|
+
]).describe("The event's severity, orthogonal to its outcome: `info` (routine), `warning` (notable), `critical` (alert-worthy). Drives dashboard filtering and alerting.");
|
|
21
|
+
/**
|
|
22
|
+
* What kind of principal acted. `anonymous` covers an unauthenticated request; `system` an internal job.
|
|
23
|
+
*
|
|
24
|
+
* `control-plane` is its own kind rather than a flavour of `service` because the question an adopter
|
|
25
|
+
* actually asks is "what did the management client do", separately from "what did my users do". A
|
|
26
|
+
* control-plane caller is not a user of their app at all — it holds no session and owns no user row —
|
|
27
|
+
* so folding it into `user` or `service` would make that question unanswerable from the trail.
|
|
28
|
+
*/
|
|
29
|
+
const AuditActorType = z.enum([
|
|
30
|
+
"user",
|
|
31
|
+
"service",
|
|
32
|
+
"system",
|
|
33
|
+
"anonymous",
|
|
34
|
+
"control-plane"
|
|
35
|
+
]).describe("The kind of principal that acted: `user` (an authenticated person), `service` (a service account or CI token), `system` (an internal job, no external actor), `anonymous` (an unauthenticated request), or `control-plane` (a management client calling in under the `control-plane` strategy — a principal outside the adopter's own user base, whose actions are answerable separately from their users').");
|
|
36
|
+
/** Capability-specific structured detail attached to an event; Zod-validated on write and read. */
|
|
37
|
+
const AuditMetadata = z.record(z.string(), z.unknown()).describe("Capability-specific structured detail (a JSON object), validated on write and read. Never put a secret, credential, or sensitive payload here — the trail is queryable and long-lived.");
|
|
38
|
+
/**
|
|
39
|
+
* One audit event as an emitter supplies it. `occurredAt` is optional — the recorder stamps it at
|
|
40
|
+
* write time when absent. The nullable correlation fields (`actorId`, `sessionId`, request fields,
|
|
41
|
+
* resource fields) are populated from request context where available; an emitter sets only what it
|
|
42
|
+
* knows. The recorder turns this into the stored `pithy_audit_events` row.
|
|
43
|
+
*
|
|
44
|
+
* `tenant` is the one field here that names a *dimension* of the trail rather than a detail of the
|
|
45
|
+
* action, and it is on this seam rather than on the recorder's origin for the reason the origin fields
|
|
46
|
+
* are not: the recorder cannot know it. `project`, `environment` and `worker` are constant across every
|
|
47
|
+
* row a multi-tenant Worker writes, so without this the trail cannot be read per customer at all — and
|
|
48
|
+
* it must be stamped here, at write time, because the tenant of an action is a fact *at the time of the
|
|
49
|
+
* action* while membership is a fact *now*. Deriving one from the other later moves a year of history
|
|
50
|
+
* between tenants every time somebody joins or leaves.
|
|
51
|
+
*/
|
|
52
|
+
const AuditEvent = z.object({
|
|
53
|
+
action: AuditAction.describe("What happened, as a namespaced `domain/reason` action code."),
|
|
54
|
+
outcome: AuditOutcome.describe("Whether the action succeeded, failed, or was denied."),
|
|
55
|
+
severity: AuditSeverity.default("info").describe("How serious the event is; defaults to `info`."),
|
|
56
|
+
actorType: AuditActorType.describe("The kind of principal that acted."),
|
|
57
|
+
actorId: z.string().nullish().describe("The acting principal's stable id (user id, service/token name); null for `system`/`anonymous`."),
|
|
58
|
+
sessionId: z.string().nullish().describe("The session this action belongs to, tying a chain of actions together; null when none."),
|
|
59
|
+
resourceType: z.string().nullish().describe("The type of resource the action targeted (e.g. `user`, `secret`); null when not resource-scoped."),
|
|
60
|
+
resourceId: z.string().nullish().describe("The id of the resource the action targeted; null when not resource-scoped."),
|
|
61
|
+
ip: z.string().nullish().describe("The client IP the request came from, for correlation; null when unknown."),
|
|
62
|
+
userAgent: z.string().nullish().describe("The client user-agent string, for correlation; null when unknown."),
|
|
63
|
+
requestId: z.string().nullish().describe("The request correlation id, tying this event to a single request/trace; null when unknown."),
|
|
64
|
+
tenant: z.string().nullish().describe("Whose action it was — the id of the tenant the action was taken *for*, in the emitter's own tenancy model. Opaque here: compared, never interpreted. **The one dimension an emitter supplies.** The recorder stamps `project`, `environment` and `worker` from the Worker's own vars and no emitter can touch them, but no var can know which customer an action belonged to, so this one comes from the call site and is exactly as trustworthy as it — set it where the tenant is already resolved and authorized, never from an unvalidated request field. Optional and nullable: a single-tenant app has no such dimension and must not invent one, and null means *not tenant-scoped* (a CLI-originated action, a fleet-wide operator action) rather than unknown. It is not `actorId`: one person can act in two tenants, so who acted does not answer for whom."),
|
|
65
|
+
occurredAt: z.date().optional().describe("When the action occurred. Optional on emit — the recorder stamps the current time when absent."),
|
|
66
|
+
metadata: AuditMetadata.nullish().describe("Capability-specific structured detail; null when there is none.")
|
|
67
|
+
}).describe("One audit event as an emitter supplies it to `emit()` — the logical event before storage.");
|
|
68
|
+
//#endregion
|
|
69
|
+
export { AuditAction, AuditActorType, AuditEvent, AuditMetadata, AuditOutcome, AuditSeverity };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AuditEventInput } from "./auditEvent";
|
|
2
|
+
/**
|
|
3
|
+
* The recorder seam. `emit()` on the request context (`c.var.emit`) takes one {@link AuditEventInput}
|
|
4
|
+
* and durably records it. `@pithy-sh/audit`'s middleware replaces the default with a real D1-backed
|
|
5
|
+
* recorder; with no audit capability installed the default is {@link noopEmit}, so an emitter can
|
|
6
|
+
* always call `emit()` without first checking whether auditing is on.
|
|
7
|
+
*
|
|
8
|
+
* `emit()` is **non-fatal by contract**: it never throws and never rejects, so an audited action is
|
|
9
|
+
* never broken by an audit write. The real recorder swallows and logs its own failures; the no-op
|
|
10
|
+
* simply resolves.
|
|
11
|
+
*/
|
|
12
|
+
export type AuditEmit = (event: AuditEventInput) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* The default recorder when no audit capability is composed: accept the event and drop it. Resolves
|
|
15
|
+
* without throwing, so calling `emit()` is always safe whether or not auditing is installed.
|
|
16
|
+
*/
|
|
17
|
+
export declare const noopEmit: AuditEmit;
|
|
18
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/audit/recorder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,SAA0B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/audit/recorder.ts
|
|
2
|
+
/**
|
|
3
|
+
* The default recorder when no audit capability is composed: accept the event and drop it. Resolves
|
|
4
|
+
* without throwing, so calling `emit()` is always safe whether or not auditing is installed.
|
|
5
|
+
*/
|
|
6
|
+
const noopEmit = async () => {};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { noopEmit };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const BindingType: z.ZodUnion<readonly [z.ZodLiteral<"d1">, z.ZodLiteral<"kv">, z.ZodLiteral<"r2">, z.ZodLiteral<"ai">, z.ZodLiteral<"vectorize">, z.ZodLiteral<"queue">, z.ZodLiteral<"ratelimit">, z.ZodLiteral<"email">, z.ZodLiteral<"secret">, z.ZodLiteral<"workflow">, z.ZodLiteral<"service">, z.ZodLiteral<"durable_object">]>;
|
|
3
|
+
export type BindingType = z.infer<typeof BindingType>;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a provision command creates the **resource** behind this kind of binding, rather than the
|
|
6
|
+
* adopter standing it up themselves.
|
|
7
|
+
*
|
|
8
|
+
* The three are the ones whose resource exists only after `pithy <capability> provision`: a `secret` is a
|
|
9
|
+
* Secrets Store entry (a `.dev.vars` string in local dev), a `workflow` runs in a deployed host Worker,
|
|
10
|
+
* and a `vectorize` binding addresses a provisioned index. `pithy add` says so in a note, at the moment
|
|
11
|
+
* the adopter is thinking about the capability.
|
|
12
|
+
*
|
|
13
|
+
* **This is not the same question as "did anything write the stanza".** Collapsing the two is what left
|
|
14
|
+
* `workflow:EMAIL_SENDER` unwritten and every route answering 500 (#258): the entry is derivable offline
|
|
15
|
+
* even though the Workflow it names is not deployed yet, so {@link isWrittenBinding} writes it and this
|
|
16
|
+
* one still reports that provisioning is what makes it work. A binding can be in both sets, and the
|
|
17
|
+
* Workflow is.
|
|
18
|
+
*
|
|
19
|
+
* `service` is not here. Its entry is also written for the adopter — by `pithy feature`, out of the
|
|
20
|
+
* target Worker's env-scoped script name — but it is wiring between an app's own Workers rather than a
|
|
21
|
+
* capability's provisioned resource, and no shipped capability declares one.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isProvisionedBinding(type: BindingType): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether `pithy add` writes this kind's `wrangler.jsonc` entry, offline, at the moment it composes the
|
|
26
|
+
* capability.
|
|
27
|
+
*
|
|
28
|
+
* Together with {@link isProvisionedBinding} this is the whole answer to "where does this binding come
|
|
29
|
+
* from" — the invariant `capabilities/requiredBindings.test.ts` states. A kind in **neither** set is a
|
|
30
|
+
* binding a capability requires and nothing supplies: the composition refuses to assemble, the error
|
|
31
|
+
* names the binding, and no command anywhere fixes it. `ratelimit` was exactly that, which is why a
|
|
32
|
+
* scaffolded project composing `auth` answered 500 on every route including `/health` (#258).
|
|
33
|
+
*
|
|
34
|
+
* Membership turns on one question only: **is every field wrangler requires derivable offline?** A rate
|
|
35
|
+
* limiter's are — the stanza is a policy with no resource behind it. A Workflow's are, because the
|
|
36
|
+
* manifest binding states the job and the exported class, and the rest is the project-scoped naming rule.
|
|
37
|
+
* A Vectorize index's `index_name` is a provisioning output and is not, so an entry carrying only
|
|
38
|
+
* `binding` would fail wrangler's validator and stop `wrangler dev` and `wrangler deploy` both — worse
|
|
39
|
+
* than no entry. A `secret` has no `wrangler.jsonc` array at all.
|
|
40
|
+
*
|
|
41
|
+
* `service` is in neither, deliberately: `pithy feature` writes it out of the target Worker's env-scoped
|
|
42
|
+
* script name, and no shipped capability declares one.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isWrittenBinding(type: BindingType): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Declares a Cloudflare binding a capability requires in the Worker env. The authoring
|
|
47
|
+
* shape lets `optional` be omitted (defaults false); `createBackend` normalizes via parse.
|
|
48
|
+
*/
|
|
49
|
+
export declare const BindingSpec: z.ZodObject<{
|
|
50
|
+
type: z.ZodUnion<readonly [z.ZodLiteral<"d1">, z.ZodLiteral<"kv">, z.ZodLiteral<"r2">, z.ZodLiteral<"ai">, z.ZodLiteral<"vectorize">, z.ZodLiteral<"queue">, z.ZodLiteral<"ratelimit">, z.ZodLiteral<"email">, z.ZodLiteral<"secret">, z.ZodLiteral<"workflow">, z.ZodLiteral<"service">, z.ZodLiteral<"durable_object">]>;
|
|
51
|
+
name: z.ZodString;
|
|
52
|
+
optional: z.ZodDefault<z.ZodBoolean>;
|
|
53
|
+
className: z.ZodOptional<z.ZodString>;
|
|
54
|
+
classModule: z.ZodOptional<z.ZodString>;
|
|
55
|
+
job: z.ZodOptional<z.ZodString>;
|
|
56
|
+
service: z.ZodOptional<z.ZodString>;
|
|
57
|
+
remote: z.ZodOptional<z.ZodBoolean>;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
export type BindingSpec = z.infer<typeof BindingSpec>;
|
|
60
|
+
/** Authoring shape for a capability's `requiredBindings`: `optional` may be omitted. */
|
|
61
|
+
export type BindingSpecInput = z.input<typeof BindingSpec>;
|
|
62
|
+
//# sourceMappingURL=bindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/capability/bindings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,WAAW,sTAmBuC,CAAC;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAU3D;AAqBD;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;iBAqFpB,CAAC;AACL,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { NAME_SEGMENT } from "../naming/segment.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/capability/bindings.ts
|
|
4
|
+
const BindingType = z.union([
|
|
5
|
+
z.literal("d1").describe("D1 SQL database (SQLite at the edge)."),
|
|
6
|
+
z.literal("kv").describe("Workers KV — eventually-consistent key/value store."),
|
|
7
|
+
z.literal("r2").describe("R2 bucket — S3-compatible object storage."),
|
|
8
|
+
z.literal("ai").describe("Workers AI — run models on Cloudflare's GPU network."),
|
|
9
|
+
z.literal("vectorize").describe("Vectorize — vector DB for embeddings and similarity search."),
|
|
10
|
+
z.literal("queue").describe("Cloudflare Queue — async message producer/consumer."),
|
|
11
|
+
z.literal("ratelimit").describe("Workers Rate Limiting — per-key request limiting."),
|
|
12
|
+
z.literal("email").describe("Email Sending binding (Cloudflare Email Service)."),
|
|
13
|
+
z.literal("secret").describe("Secret from the Secrets Store, encrypted at rest."),
|
|
14
|
+
z.literal("workflow").describe("Cloudflare Workflow — durable, multi-step execution."),
|
|
15
|
+
z.literal("service").describe("Service binding — direct RPC to another Worker."),
|
|
16
|
+
z.literal("durable_object").describe("Durable Object — a single-threaded, stateful actor with its own storage. Backed by an exported DO class; the CLI wires the namespace binding and the class migration tag.")
|
|
17
|
+
]).describe("Kind of Cloudflare resource a binding refers to.");
|
|
18
|
+
/**
|
|
19
|
+
* Whether a provision command creates the **resource** behind this kind of binding, rather than the
|
|
20
|
+
* adopter standing it up themselves.
|
|
21
|
+
*
|
|
22
|
+
* The three are the ones whose resource exists only after `pithy <capability> provision`: a `secret` is a
|
|
23
|
+
* Secrets Store entry (a `.dev.vars` string in local dev), a `workflow` runs in a deployed host Worker,
|
|
24
|
+
* and a `vectorize` binding addresses a provisioned index. `pithy add` says so in a note, at the moment
|
|
25
|
+
* the adopter is thinking about the capability.
|
|
26
|
+
*
|
|
27
|
+
* **This is not the same question as "did anything write the stanza".** Collapsing the two is what left
|
|
28
|
+
* `workflow:EMAIL_SENDER` unwritten and every route answering 500 (#258): the entry is derivable offline
|
|
29
|
+
* even though the Workflow it names is not deployed yet, so {@link isWrittenBinding} writes it and this
|
|
30
|
+
* one still reports that provisioning is what makes it work. A binding can be in both sets, and the
|
|
31
|
+
* Workflow is.
|
|
32
|
+
*
|
|
33
|
+
* `service` is not here. Its entry is also written for the adopter — by `pithy feature`, out of the
|
|
34
|
+
* target Worker's env-scoped script name — but it is wiring between an app's own Workers rather than a
|
|
35
|
+
* capability's provisioned resource, and no shipped capability declares one.
|
|
36
|
+
*/
|
|
37
|
+
function isProvisionedBinding(type) {
|
|
38
|
+
return type === "secret" || type === "workflow" || type === "vectorize";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether `pithy add` writes this kind's `wrangler.jsonc` entry, offline, at the moment it composes the
|
|
42
|
+
* capability.
|
|
43
|
+
*
|
|
44
|
+
* Together with {@link isProvisionedBinding} this is the whole answer to "where does this binding come
|
|
45
|
+
* from" — the invariant `capabilities/requiredBindings.test.ts` states. A kind in **neither** set is a
|
|
46
|
+
* binding a capability requires and nothing supplies: the composition refuses to assemble, the error
|
|
47
|
+
* names the binding, and no command anywhere fixes it. `ratelimit` was exactly that, which is why a
|
|
48
|
+
* scaffolded project composing `auth` answered 500 on every route including `/health` (#258).
|
|
49
|
+
*
|
|
50
|
+
* Membership turns on one question only: **is every field wrangler requires derivable offline?** A rate
|
|
51
|
+
* limiter's are — the stanza is a policy with no resource behind it. A Workflow's are, because the
|
|
52
|
+
* manifest binding states the job and the exported class, and the rest is the project-scoped naming rule.
|
|
53
|
+
* A Vectorize index's `index_name` is a provisioning output and is not, so an entry carrying only
|
|
54
|
+
* `binding` would fail wrangler's validator and stop `wrangler dev` and `wrangler deploy` both — worse
|
|
55
|
+
* than no entry. A `secret` has no `wrangler.jsonc` array at all.
|
|
56
|
+
*
|
|
57
|
+
* `service` is in neither, deliberately: `pithy feature` writes it out of the target Worker's env-scoped
|
|
58
|
+
* script name, and no shipped capability declares one.
|
|
59
|
+
*/
|
|
60
|
+
function isWrittenBinding(type) {
|
|
61
|
+
return type === "d1" || type === "kv" || type === "r2" || type === "ai" || type === "durable_object" || type === "ratelimit" || type === "workflow";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Declares a Cloudflare binding a capability requires in the Worker env. The authoring
|
|
65
|
+
* shape lets `optional` be omitted (defaults false); `createBackend` normalizes via parse.
|
|
66
|
+
*/
|
|
67
|
+
const BindingSpec = z.object({
|
|
68
|
+
type: BindingType.describe("Resource kind this binding provides."),
|
|
69
|
+
name: z.string().min(1).describe("Binding name expected in the Worker env (e.g. \"DB\", \"SESSIONS\")."),
|
|
70
|
+
optional: z.boolean().default(false).describe("If true, createBackend won't fail when this binding is absent."),
|
|
71
|
+
className: z.string().min(1).regex(/^[A-Za-z_$][A-Za-z0-9_$]*$/, "A className is a JavaScript identifier: letters, digits, `_` and `$`, not leading a digit.").optional().describe("The exported class this binding is backed by. For a `durable_object` binding it is the DO class (e.g. \"MultiplayerSession\") the CLI writes into `durable_objects.bindings`, the DO class migration tag, and the `export { … }` line in the Worker entry. For a `workflow` binding it is the `WorkflowEntrypoint` subclass (e.g. \"EmailSendWorkflow\") the CLI writes as `class_name` — the same value the capability's own `WorkflowSpec.className` carries. Ignored for every other kind."),
|
|
72
|
+
classModule: z.string().min(1).regex(/^(@[a-z0-9~-][a-z0-9._~-]*\/)?[a-z0-9~-][a-z0-9._~-]*(\/[A-Za-z0-9_~-][A-Za-z0-9._~-]*)*$/, "A classModule is a bare module specifier: a package, optionally with a path into it.").optional().describe("The module the class is exported from (e.g. \"@pithy-sh/multiplayer/src/session/durableObject\") — what the CLI writes the Worker entry's `export { <className> } from \"<classModule>\";` against. Required for a `durable_object` binding, because wrangler resolves `class_name` against the entry `main` names and refuses the deploy when nothing there exports it. Its own module, never the package entry point, which `pithy.config.ts` loads in Node. Ignored for every other kind."),
|
|
73
|
+
job: z.string().min(1).regex(NAME_SEGMENT, "A job is one name segment: lowercase letters, digits, and single hyphens.").optional().describe("The job within the capability this Workflow runs (e.g. \"send\") — the `<job>` segment of the deployed `<project>-<env>-<capability>-<job>` name, and the second half of the `<capability>/<job>` dispatch key. Meaningful only for a `workflow` binding, where it is what lets `pithy add` name the Workflow offline instead of leaving the binding unwritten. Ignored for every other kind."),
|
|
74
|
+
service: z.string().min(1).optional().describe("The Worker this binding calls, named as it appears in `apps/<name>/` (e.g. \"api\"). Meaningful only for a `service` binding — the CLI resolves it to that Worker's environment-scoped script name when writing `services` into wrangler.jsonc, so worker-to-worker RPC targets the right deployment per environment. Ignored for every other kind."),
|
|
75
|
+
remote: z.boolean().optional().describe("Reach the real Cloudflare resource during local development instead of a local emulation. Set it for a binding that has none — Vectorize and Workers AI both lack local simulation — so `wrangler dev` and any Workflow host, which always runs locally, still work. Left unset rather than defaulting to false, so a spec that does not care emits no flag at all. Ignored in a deployed Worker.")
|
|
76
|
+
}).describe("Declares a Cloudflare binding a capability requires in the Worker env.").check((ctx) => {
|
|
77
|
+
if (ctx.value.type === "durable_object" && ctx.value.className === void 0) ctx.issues.push({
|
|
78
|
+
code: "custom",
|
|
79
|
+
input: ctx.value,
|
|
80
|
+
path: ["className"],
|
|
81
|
+
message: `Durable Object binding "${ctx.value.name}" needs a className — the exported DO class it is backed by.`
|
|
82
|
+
});
|
|
83
|
+
if (ctx.value.type === "durable_object" && ctx.value.classModule === void 0) ctx.issues.push({
|
|
84
|
+
code: "custom",
|
|
85
|
+
input: ctx.value,
|
|
86
|
+
path: ["classModule"],
|
|
87
|
+
message: `Durable Object binding "${ctx.value.name}" needs a classModule — the module its class is exported from.`
|
|
88
|
+
});
|
|
89
|
+
if (ctx.value.type === "service" && ctx.value.service === void 0) ctx.issues.push({
|
|
90
|
+
code: "custom",
|
|
91
|
+
input: ctx.value,
|
|
92
|
+
path: ["service"],
|
|
93
|
+
message: `Service binding "${ctx.value.name}" needs a service — the Worker it calls, as named in apps/.`
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
//#endregion
|
|
97
|
+
export { BindingSpec, BindingType, isProvisionedBinding, isWrittenBinding };
|