@tokenoftrust/storefront-runner 1.3.4-rc.4 → 1.4.1
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/apps/storefront/astro.config.mjs +15 -0
- package/apps/storefront/dev-plugins/dev-publish.mjs +55 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +86 -7
- package/apps/storefront/drizzle.config.apps.ts +13 -0
- package/apps/storefront/env.d.ts +10 -0
- package/apps/storefront/migrations/README.md +13 -7
- package/apps/storefront/migrations-apps/0000_fast_millenium_guard.sql +129 -0
- package/apps/storefront/migrations-apps/0001_woozy_lyja.sql +22 -0
- package/apps/storefront/migrations-apps/meta/0000_snapshot.json +843 -0
- package/apps/storefront/migrations-apps/meta/0001_snapshot.json +990 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +20 -0
- package/apps/storefront/package.json +12 -1
- package/apps/storefront/perf/README.md +64 -0
- package/apps/storefront/perf/assert-budgets.ts +159 -0
- package/apps/storefront/playwright.config.ts +23 -0
- package/apps/storefront/public/js/dashboard-apps.js +173 -0
- package/apps/storefront/public/shared/commerce-marketing.css +221 -0
- package/apps/storefront/src/components/CollectionCard.astro +1 -0
- package/apps/storefront/src/components/ProductCard.astro +1 -0
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +3562 -0
- package/apps/storefront/src/components/apps/AppWidgetFrame.astro +30 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +6 -3
- package/apps/storefront/src/components/chrome/SiteFooter.astro +10 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +10 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +3 -2
- package/apps/storefront/src/components/content/Callout.astro +75 -0
- package/apps/storefront/src/components/content/NeedsReviewCallout.astro +66 -0
- package/apps/storefront/src/components/content/ProseSections.astro +121 -0
- package/apps/storefront/src/components/content/ProseToc.astro +34 -0
- package/apps/storefront/src/components/content/RichText.astro +44 -0
- package/apps/storefront/src/components/content/TrustStrip.astro +46 -0
- package/apps/storefront/src/components/home/Hero.astro +14 -0
- package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +295 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +42 -21
- package/apps/storefront/src/components/islands/VariantSelector.tsx +62 -12
- package/apps/storefront/src/components/plp/FacetSidebar.astro +2 -2
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +1 -0
- package/apps/storefront/src/config/compliance/rulesets.ts +79 -0
- package/apps/storefront/src/config/storeName.ts +34 -0
- package/apps/storefront/src/layouts/Layout.astro +97 -23
- package/apps/storefront/src/lib/activity/alerts.ts +428 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +85 -0
- package/apps/storefront/src/lib/activity/deployVersion.ts +127 -0
- package/apps/storefront/src/lib/activity/ingest.ts +188 -0
- package/apps/storefront/src/lib/activity/ingestAuth.ts +105 -0
- package/apps/storefront/src/lib/activity/killSwitch.ts +80 -0
- package/apps/storefront/src/lib/activity/query.ts +403 -0
- package/apps/storefront/src/lib/activity/recordActivity.ts +105 -0
- package/apps/storefront/src/lib/activity/store.ts +122 -0
- package/apps/storefront/src/lib/activity/uiActor.ts +150 -0
- package/apps/storefront/src/lib/activity/workerCommit.ts +70 -0
- package/apps/storefront/src/lib/analytics/budgets.json +69 -0
- package/apps/storefront/src/lib/analytics/lighthouseReport.ts +109 -0
- package/apps/storefront/src/lib/analytics/perfBudgets.ts +452 -0
- package/apps/storefront/src/lib/analytics/rumAlert.ts +179 -0
- package/apps/storefront/src/lib/analytics/webVitals.ts +269 -0
- package/apps/storefront/src/lib/apps/adminService.ts +106 -0
- package/apps/storefront/src/lib/apps/adminSession.ts +205 -0
- package/apps/storefront/src/lib/apps/apiAuth.ts +91 -0
- package/apps/storefront/src/lib/apps/apiRoute.ts +35 -0
- package/apps/storefront/src/lib/apps/catalogMapper.ts +39 -0
- package/apps/storefront/src/lib/apps/credentials.ts +153 -0
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +156 -0
- package/apps/storefront/src/lib/apps/healthAggregate.ts +66 -0
- package/apps/storefront/src/lib/apps/orders/attributionService.ts +206 -0
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +28 -0
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +203 -0
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +100 -0
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +117 -0
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +91 -0
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +181 -0
- package/apps/storefront/src/lib/apps/registryService.ts +579 -0
- package/apps/storefront/src/lib/apps/scopes.ts +79 -0
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +121 -0
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +148 -0
- package/apps/storefront/src/lib/apps/widgets/eligibility.ts +18 -0
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +52 -0
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +84 -0
- package/apps/storefront/src/lib/apps/widgets/placements.ts +57 -0
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +111 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +119 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +21 -2
- package/apps/storefront/src/lib/auth/loginGate.ts +102 -18
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +219 -0
- package/apps/storefront/src/lib/auth/route.ts +60 -5
- package/apps/storefront/src/lib/auth/session.ts +8 -0
- package/apps/storefront/src/lib/auth/stepUpChallenge.ts +107 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +208 -0
- package/apps/storefront/src/lib/blog/provider.ts +39 -0
- package/apps/storefront/src/lib/blog/types.ts +26 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +39 -1
- package/apps/storefront/src/lib/chrome/model.ts +11 -0
- package/apps/storefront/src/lib/compliance/enforcement.ts +95 -0
- package/apps/storefront/src/lib/content/callout.ts +78 -0
- package/apps/storefront/src/lib/content/index.ts +27 -0
- package/apps/storefront/src/lib/content/needsReview.ts +58 -0
- package/apps/storefront/src/lib/content/prose.ts +186 -0
- package/apps/storefront/src/lib/content/richtext.ts +76 -0
- package/apps/storefront/src/lib/content/trustStrip.ts +74 -0
- package/apps/storefront/src/lib/content-edit/client.ts +70 -14
- package/apps/storefront/src/lib/d1/catalog.ts +12 -0
- package/apps/storefront/src/lib/d1/schema-apps.ts +267 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +8 -2
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +65 -115
- package/apps/storefront/src/lib/dev/cockpitStore.ts +65 -0
- package/apps/storefront/src/lib/dev/previewStatus.ts +112 -0
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +241 -0
- package/apps/storefront/src/lib/dev/subjectReissue.ts +67 -0
- package/apps/storefront/src/lib/dev/vcBinding.ts +80 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +10 -10
- package/apps/storefront/src/lib/env.ts +63 -0
- package/apps/storefront/src/lib/jsonld.ts +12 -17
- package/apps/storefront/src/lib/membership/eligibility.ts +37 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +302 -0
- package/apps/storefront/src/lib/privacy/emailHint.ts +13 -5
- package/apps/storefront/src/lib/publish/apex-readiness.ts +337 -0
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +269 -0
- package/apps/storefront/src/lib/publish/domainState.ts +351 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +369 -0
- package/apps/storefront/src/lib/rawChrome.ts +34 -3
- package/apps/storefront/src/lib/storyblok/content-model.ts +34 -2
- package/apps/storefront/src/lib/storyblok/provider.ts +11 -4
- package/apps/storefront/src/lib/subscription/model.ts +114 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +3 -3
- package/apps/storefront/src/lib/tot/query.ts +32 -0
- package/apps/storefront/src/lib/webhooks/cloudflareQueueDispatcher.ts +82 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +245 -0
- package/apps/storefront/src/lib/webhooks/deliveryMapper.ts +34 -0
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +668 -0
- package/apps/storefront/src/lib/webhooks/dispatcher.ts +168 -0
- package/apps/storefront/src/lib/webhooks/emit.ts +167 -0
- package/apps/storefront/src/lib/webhooks/endpointGuard.ts +135 -0
- package/apps/storefront/src/lib/webhooks/events.ts +98 -0
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +49 -0
- package/apps/storefront/src/middleware/index.ts +45 -13
- package/apps/storefront/src/pages/404.astro +21 -9
- package/apps/storefront/src/pages/[...slug].astro +36 -2
- package/apps/storefront/src/pages/admin/ops-timeline.astro +420 -0
- package/apps/storefront/src/pages/admin.astro +183 -9
- package/apps/storefront/src/pages/api/activity.ts +137 -0
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +73 -0
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +86 -0
- package/apps/storefront/src/pages/api/apps/admin/health.ts +44 -0
- package/apps/storefront/src/pages/api/apps/admin/install.ts +125 -0
- package/apps/storefront/src/pages/api/apps/admin/list.ts +26 -0
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +79 -0
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +80 -0
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +98 -0
- package/apps/storefront/src/pages/api/apps/admin/update.ts +126 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +70 -0
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +51 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +183 -0
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +87 -0
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +162 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +39 -0
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +49 -0
- package/apps/storefront/src/pages/api/apps/v1/health.ts +32 -0
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +59 -0
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +48 -0
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +73 -0
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +21 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +74 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +47 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +48 -1
- package/apps/storefront/src/pages/api/auth/step-up-send.ts +57 -0
- package/apps/storefront/src/pages/api/auth/step-up-verify.ts +129 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +23 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +11 -0
- package/apps/storefront/src/pages/api/compliance/preflight.ts +206 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +30 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +54 -0
- package/apps/storefront/src/pages/api/test/dev-session.ts +133 -0
- package/apps/storefront/src/pages/auth/login.astro +202 -45
- package/apps/storefront/src/pages/auth/magic.astro +75 -43
- package/apps/storefront/src/pages/blog/[slug].astro +107 -0
- package/apps/storefront/src/pages/blog/index.astro +98 -0
- package/apps/storefront/src/pages/capabilities.astro +8 -0
- package/apps/storefront/src/pages/cockpit.astro +470 -69
- package/apps/storefront/src/pages/collections/[handle].astro +8 -0
- package/apps/storefront/src/pages/collections/index.astro +10 -2
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +119 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +5 -0
- package/apps/storefront/src/pages/index.astro +57 -0
- package/apps/storefront/src/pages/llms.txt.ts +31 -10
- package/apps/storefront/src/pages/products/[handle].astro +100 -9
- package/apps/storefront/src/pages/sitemap.xml.ts +21 -4
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +198 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +7 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -0
- package/apps/storefront/src/pages/style-guide/index.astro +10 -0
- package/apps/storefront/src/styles/fonts.css +54 -0
- package/apps/storefront/src/styles/global.css +22 -2
- package/apps/storefront/src/themes/schema.ts +3 -25
- package/apps/storefront/tsconfig.json +1 -1
- package/apps/storefront/vitest.config.ts +4 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/activity/README.md +146 -0
- package/packages/public-runtime/src/activity/catalog.ts +501 -0
- package/packages/public-runtime/src/activity/event.ts +168 -0
- package/packages/public-runtime/src/activity/index.ts +16 -0
- package/packages/public-runtime/src/activity/redaction.ts +263 -0
- package/packages/public-runtime/src/candidate-index.ts +324 -0
- package/packages/public-runtime/src/checkout.ts +94 -2
- package/packages/public-runtime/src/compliance/evaluate.ts +265 -0
- package/packages/public-runtime/src/compliance/evidence-signals.ts +81 -0
- package/packages/public-runtime/src/compliance/index.ts +22 -0
- package/packages/public-runtime/src/compliance/pact-report.ts +94 -0
- package/packages/public-runtime/src/compliance/profile.ts +198 -0
- package/packages/public-runtime/src/compliance/ruleset.ts +117 -0
- package/packages/public-runtime/src/compliance/verification.ts +81 -0
- package/packages/public-runtime/src/csp.ts +27 -3
- package/packages/public-runtime/src/customization-reconcile.ts +35 -0
- package/packages/public-runtime/src/customization-runtime.ts +8 -0
- package/packages/public-runtime/src/customization-versioning.ts +17 -0
- package/packages/public-runtime/src/extension-contract.ts +2 -1
- package/packages/public-runtime/src/hash.ts +25 -0
- package/packages/public-runtime/src/index.ts +12 -0
- package/packages/public-runtime/src/membership.ts +353 -0
- package/packages/public-runtime/src/product.ts +24 -2
- package/packages/public-runtime/src/review-trust-proof.ts +194 -0
- package/packages/public-runtime/src/tenant-assets.ts +40 -5
- package/packages/public-runtime/src/tenant.ts +236 -0
- package/packages/public-runtime/src/widget-postmessage.ts +205 -0
- package/scripts/dev/publish.mjs +158 -0
- package/scripts/dev/transient-files.mjs +2 -1
- package/tenants/home/public/fonts/inter-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/inter-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/tenants/home/public/fonts/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-800-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-500-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-600-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-700-normal.woff2 +0 -0
- package/tenants/home/public/fonts/plus-jakarta-sans-latin-ext-800-normal.woff2 +0 -0
- package/tenants/home/public/pages/storefront.css +23 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +0 -169
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// ActivityEvent — the versioned actor×action telemetry envelope (card D0).
|
|
3
|
+
//
|
|
4
|
+
// One event = "an ACTOR performed an ACTION, here is the OUTCOME." It is the
|
|
5
|
+
// single shape every operational-activity emitter produces and every sink
|
|
6
|
+
// stores, across the whole storefront platform (dev/CLI first, then merchant/
|
|
7
|
+
// admin/agent). Downstream cards import this directly:
|
|
8
|
+
// D1 server recordActivity() · D2 deploy provenance · D3 CLI emitActivity
|
|
9
|
+
// D4 ingest+store · D5 query/timeline UI · D6/D7 alerts.
|
|
10
|
+
//
|
|
11
|
+
// TWO ZONES, and the split is load-bearing:
|
|
12
|
+
// 1. CORE fields (everything except `payload`) — always safe to store. They
|
|
13
|
+
// carry NO free-text and NO third-party PII by construction: ids are
|
|
14
|
+
// opaque, error is a low-cardinality CLASS, action is an allowlisted
|
|
15
|
+
// catalog key. A core field is never redacted because it can never hold a
|
|
16
|
+
// secret.
|
|
17
|
+
// 2. `payload` (args / rendered) — the extensible, ALWAYS-REDACTED zone.
|
|
18
|
+
// Whatever a call site puts here is default-DENIED: only the keys the
|
|
19
|
+
// action's allowlist names survive, and even those are canary-scanned.
|
|
20
|
+
// See redaction.ts. You cannot construct an event whose payload skipped
|
|
21
|
+
// redaction — use createActivityEvent() (redaction.ts), not a bare object.
|
|
22
|
+
//
|
|
23
|
+
// This package is the node-free, dependency-free edge base (see index.ts): the
|
|
24
|
+
// envelope stays pure data + pure functions so server, edge, and the JS CLI
|
|
25
|
+
// mirror can all consume it. ActionKey lives in catalog.ts (type-only import
|
|
26
|
+
// here — no runtime cycle).
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
import type { ActionKey } from "./catalog.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Envelope contract version. Bump ONLY on a breaking change to a CORE field
|
|
32
|
+
* (add/remove/retype). Adding a new action key, sink, or allowlist entry is NOT
|
|
33
|
+
* breaking and does NOT bump this. Sinks store this so a reader can migrate old
|
|
34
|
+
* rows. Kept as a literal-typed const so `v` is `1`, not `number`.
|
|
35
|
+
*/
|
|
36
|
+
export const ACTIVITY_SCHEMA_VERSION = 1 as const;
|
|
37
|
+
export type ActivitySchemaVersion = typeof ACTIVITY_SCHEMA_VERSION;
|
|
38
|
+
|
|
39
|
+
/** WHO acted. Deliberately a small closed set — ingest can reject a mismatch. */
|
|
40
|
+
export type ActorKind = "dev" | "merchant" | "admin" | "agent" | "system";
|
|
41
|
+
|
|
42
|
+
/** WHERE the event originated. */
|
|
43
|
+
export type ActivitySource = "cli" | "server" | "ui";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Invoked/result semantics. `invoked` = the action started (fire-and-forget or
|
|
47
|
+
* the open half of an invoke/result pair); the terminal statuses close it.
|
|
48
|
+
* `refused` is distinct from `failed`: the platform DECLINED the action (a
|
|
49
|
+
* capability/authz gate said no) — it never ran — vs it ran and errored.
|
|
50
|
+
*/
|
|
51
|
+
export type ActivityStatus = "invoked" | "succeeded" | "failed" | "refused";
|
|
52
|
+
|
|
53
|
+
/** Which store(s) an action lands in. The store decision is per-action policy,
|
|
54
|
+
* declared on each catalog entry — see catalog.ts ActionSpec.sinks and
|
|
55
|
+
* DECISIONS.md §8. `logs` is universal; `analytics` = high-volume/query-light;
|
|
56
|
+
* `timeline` = low-volume/queryable ops UI. */
|
|
57
|
+
export type ActivitySink = "logs" | "analytics" | "timeline";
|
|
58
|
+
|
|
59
|
+
export interface ActivityActor {
|
|
60
|
+
kind: ActorKind;
|
|
61
|
+
/**
|
|
62
|
+
* OPAQUE, stable actor id. NEVER a raw email / phone / name — the core zone is
|
|
63
|
+
* unredacted, so a raw PII id here would defeat the whole contract. Emitters
|
|
64
|
+
* pseudonymize before constructing the event (a stable hash of the email, an
|
|
65
|
+
* install id, an app-domain, "system"). The canonical pseudonymization salt is
|
|
66
|
+
* an OPEN QUESTION owned by D1/D4 (see redaction.ts header); until it lands,
|
|
67
|
+
* emitters use an already-opaque handle (install id, hashed email, app domain).
|
|
68
|
+
*/
|
|
69
|
+
id: string;
|
|
70
|
+
/**
|
|
71
|
+
* Optional secondary opaque reference (e.g. a hashed email used to correlate a
|
|
72
|
+
* dev across CLI + UI). Still core, still MUST be non-reversible / non-PII.
|
|
73
|
+
*/
|
|
74
|
+
ref?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ActivityScope {
|
|
78
|
+
/** Tenant / store id (tenant_id or app domain). Core, non-secret. */
|
|
79
|
+
tenantId?: string;
|
|
80
|
+
/** ShipLoop change/candidate id, when the action is change-scoped. */
|
|
81
|
+
changeId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Correlation id spanning an invite→terminal→problem span. Matches the
|
|
84
|
+
* existing `traceId` already threaded through the dev bridge + runtime record,
|
|
85
|
+
* so a CLI event and the server event it triggered share one trace.
|
|
86
|
+
*/
|
|
87
|
+
traceId?: string;
|
|
88
|
+
/**
|
|
89
|
+
* BUILD PROVENANCE (card D2). The git commit SHA the running server/edge build
|
|
90
|
+
* was cut from — stamped by the emitter onto every SERVER-sourced event so the
|
|
91
|
+
* query surface (D5) can derive **deploy skew**: is the build that produced this
|
|
92
|
+
* event == the pushed umbrella tip, or is a stale worker still serving? Core,
|
|
93
|
+
* non-secret by construction (a 40-hex git sha, never PII/free-text), so it is
|
|
94
|
+
* never redacted. Absent on `cli`/`ui`-sourced events and on any build with no
|
|
95
|
+
* git context (reported as "unknown" by the stamper, see the storefront's
|
|
96
|
+
* workerCommit() accessor). This is `worker_commit` in the D2 vocabulary — the
|
|
97
|
+
* value the standalone `deploy.version_observed` action also carries in payload.
|
|
98
|
+
*/
|
|
99
|
+
workerCommit?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ActivityOutcome {
|
|
103
|
+
status: ActivityStatus;
|
|
104
|
+
/**
|
|
105
|
+
* Stable, LOW-CARDINALITY error class — never a raw exception message (which
|
|
106
|
+
* can carry secrets/PII and blows up cardinality). e.g. "capability_denied",
|
|
107
|
+
* "forge_5xx", "node_too_old", "not_found". Free-text detail, if truly needed,
|
|
108
|
+
* goes through payload.rendered and its allowlist, never here.
|
|
109
|
+
*/
|
|
110
|
+
errorClass?: string;
|
|
111
|
+
/** Wall-clock duration of the action in ms, when known. */
|
|
112
|
+
durationMs?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The extensible, ALWAYS-REDACTED payload zone. Both lanes are default-DENY:
|
|
117
|
+
* only keys named by the action's allowlist survive redaction; every other key
|
|
118
|
+
* is DROPPED (never stored, never "logged raw then redacted later").
|
|
119
|
+
*/
|
|
120
|
+
export interface ActivityPayload {
|
|
121
|
+
/** Structured machine input to the action (e.g. { command, exitCode }). */
|
|
122
|
+
args?: Record<string, unknown>;
|
|
123
|
+
/** Human-readable strings for a UI timeline (e.g. { description, reason }). */
|
|
124
|
+
rendered?: Record<string, string>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The wire + storage envelope. Field names are short because this is written at
|
|
129
|
+
* volume (Analytics Engine blobs, Workers Logs lines, D1 columns).
|
|
130
|
+
*/
|
|
131
|
+
export interface ActivityEvent {
|
|
132
|
+
/** Envelope contract version (see ACTIVITY_SCHEMA_VERSION). */
|
|
133
|
+
v: ActivitySchemaVersion;
|
|
134
|
+
/** Unique event id (UUID v4). Idempotency key for the ingest/store path. */
|
|
135
|
+
id: string;
|
|
136
|
+
/** ISO 8601 UTC timestamp, emit clock. */
|
|
137
|
+
at: string;
|
|
138
|
+
actor: ActivityActor;
|
|
139
|
+
/** ALLOWLISTED catalog key — never a free string. See catalog.ts. */
|
|
140
|
+
action: ActionKey;
|
|
141
|
+
source: ActivitySource;
|
|
142
|
+
scope: ActivityScope;
|
|
143
|
+
outcome: ActivityOutcome;
|
|
144
|
+
/** Post-redaction payload. Absent when the action carries no allowlisted args. */
|
|
145
|
+
payload?: ActivityPayload;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** True when `at` is a parseable ISO-8601 instant. Cheap ingest guard (D4). */
|
|
149
|
+
export function isIsoTimestamp(at: unknown): at is string {
|
|
150
|
+
return typeof at === "string" && !Number.isNaN(Date.parse(at));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Node-free UUID v4. Uses the platform crypto (Workers + Node ≥19 global), with
|
|
154
|
+
* a getRandomValues fallback so the edge bundle never imports `node:crypto`. */
|
|
155
|
+
export function newEventId(): string {
|
|
156
|
+
const c: Crypto | undefined = (globalThis as { crypto?: Crypto }).crypto;
|
|
157
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
158
|
+
const b = new Uint8Array(16);
|
|
159
|
+
(c?.getRandomValues ? c.getRandomValues(b) : fillMathRandom(b));
|
|
160
|
+
b[6] = ((b[6] as number) & 0x0f) | 0x40;
|
|
161
|
+
b[8] = ((b[8] as number) & 0x3f) | 0x80;
|
|
162
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
|
|
163
|
+
return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function fillMathRandom(b: Uint8Array): void {
|
|
167
|
+
for (let i = 0; i < b.length; i++) b[i] = Math.floor(Math.random() * 256);
|
|
168
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// @tot/public-runtime/activity — the operational activity telemetry contract.
|
|
3
|
+
//
|
|
4
|
+
// Card D0: the actor×action ActivityEvent envelope, the allowlisted action
|
|
5
|
+
// catalog, and the default-deny redaction contract. This is the FOUNDATION the
|
|
6
|
+
// downstream cards import directly — nothing here does I/O or picks a store; it
|
|
7
|
+
// is pure types + pure functions so server, edge, and the CLI's JS mirror can
|
|
8
|
+
// all consume it. The store DECISION and cost model live in the repo-root
|
|
9
|
+
// DECISIONS.md §8; the per-action routing lives on each catalog entry (sinks).
|
|
10
|
+
//
|
|
11
|
+
// See ./README.md for the envelope shape, catalog rules, redaction contract,
|
|
12
|
+
// and the CLI-mirror keep-in-step obligation for card D3.
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
export * from "./event.js";
|
|
15
|
+
export * from "./catalog.js";
|
|
16
|
+
export * from "./redaction.js";
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Redaction contract for the activity payload zone (card D0).
|
|
3
|
+
//
|
|
4
|
+
// This is the surface card D8 (adversarial PII/secret-leak review) will attack,
|
|
5
|
+
// so the posture is deliberately conservative. Three layers, in order:
|
|
6
|
+
//
|
|
7
|
+
// 1. DEFAULT-DENY ALLOWLIST. For a given action, ONLY the top-level payload
|
|
8
|
+
// keys named in its ActionSpec (argsAllow / renderedAllow) are eligible to
|
|
9
|
+
// be stored. Every other key is DROPPED — not stored, not logged, not
|
|
10
|
+
// "kept raw and redacted later". There is no path that persists an
|
|
11
|
+
// un-allowlisted field.
|
|
12
|
+
//
|
|
13
|
+
// 2. PRIMITIVE-ONLY VALUES. An allowlisted value must be a string / number /
|
|
14
|
+
// boolean. Objects, arrays, functions are DROPPED — nested structures are
|
|
15
|
+
// where secrets hide, and we will not walk them. (If a future action
|
|
16
|
+
// genuinely needs structured args, it flattens them at the call site into
|
|
17
|
+
// named primitive keys and allowlists each.)
|
|
18
|
+
//
|
|
19
|
+
// 3. CANARY SCAN. Even an allowlisted primitive is scanned for secret/PII
|
|
20
|
+
// shapes (JWTs, bearer tokens, API keys, private keys, emails, card/phone
|
|
21
|
+
// numbers) AND secret-looking KEY NAMES. A hit replaces the value with a
|
|
22
|
+
// redaction marker and is REPORTED (the fact of the hit, never the value) so
|
|
23
|
+
// D6/D7 can alert and D8 can audit. A secret-named key is dropped outright.
|
|
24
|
+
//
|
|
25
|
+
// The safe constructor createActivityEvent() runs all three before an event is
|
|
26
|
+
// ever returned — you cannot build an event whose payload skipped redaction.
|
|
27
|
+
//
|
|
28
|
+
// OPEN QUESTION (owned by D1/D4): the canonical actor-id pseudonymization salt.
|
|
29
|
+
// This module scrubs PII that leaks into the PAYLOAD, but actor.id in the CORE
|
|
30
|
+
// zone is the emitter's responsibility to pseudonymize. A shared, salted,
|
|
31
|
+
// non-reversible hash helper (with the salt as a Worker secret) should land with
|
|
32
|
+
// D1's recordActivity()/D4's ingest — do NOT bake a weak unsalted hash here.
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
import {
|
|
35
|
+
ACTIVITY_SCHEMA_VERSION,
|
|
36
|
+
newEventId,
|
|
37
|
+
type ActivityEvent,
|
|
38
|
+
type ActivityActor,
|
|
39
|
+
type ActivityScope,
|
|
40
|
+
type ActivityOutcome,
|
|
41
|
+
type ActivityPayload,
|
|
42
|
+
type ActivitySource,
|
|
43
|
+
} from "./event.js";
|
|
44
|
+
import { getActionSpec, type ActionKey, type ActionSpec } from "./catalog.js";
|
|
45
|
+
|
|
46
|
+
/** Marker stored in place of a value that tripped a canary (never the raw value). */
|
|
47
|
+
export const REDACTION_MARKER = "«redacted»";
|
|
48
|
+
/** Cap on a stored rendered/arg string — a stored operational value is never long;
|
|
49
|
+
* capping bounds row/blob size and shrinks the blast radius of a missed secret. */
|
|
50
|
+
export const MAX_VALUE_LEN = 512;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Secret/PII VALUE canaries — deny-by-default even inside an allowlisted field.
|
|
54
|
+
* Conservative on purpose: a false positive costs one redacted ops field; a false
|
|
55
|
+
* negative is a leaked secret. Ordered roughly most- to least-specific. Named so
|
|
56
|
+
* a report can say WHICH canary fired (the label), never the matched text.
|
|
57
|
+
*/
|
|
58
|
+
export const VALUE_CANARIES: readonly { label: string; re: RegExp }[] = [
|
|
59
|
+
{ label: "private_key", re: /-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----/ },
|
|
60
|
+
{ label: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+/ },
|
|
61
|
+
{ label: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/i },
|
|
62
|
+
{ label: "aws_access_key", re: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
63
|
+
{ label: "gh_token", re: /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}/ },
|
|
64
|
+
{ label: "slack_token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}/ },
|
|
65
|
+
{ label: "openai_key", re: /\bsk-[A-Za-z0-9]{20,}/ },
|
|
66
|
+
{ label: "email", re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/ },
|
|
67
|
+
{ label: "card_number", re: /\b(?:\d[ -]?){13,19}\b/ },
|
|
68
|
+
{ label: "phone", re: /\b\+?\d[\d ().-]{9,}\d\b/ },
|
|
69
|
+
// High-entropy long token: ≥28 chars of base64url with no spaces. Last resort.
|
|
70
|
+
{ label: "high_entropy", re: /(?:^|[^A-Za-z0-9_-])[A-Za-z0-9_-]{28,}(?:$|[^A-Za-z0-9_-])/ },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Secret-looking KEY NAMES — dropped outright even if (mistakenly) allowlisted.
|
|
75
|
+
* Defense in depth: an allowlist edit that names one of these can never leak.
|
|
76
|
+
*/
|
|
77
|
+
// NB: matched as a substring of the key name, so entries must be specific enough
|
|
78
|
+
// not to eat legitimate fields — e.g. a bare `code` would swallow `exitCode`.
|
|
79
|
+
// Single-use codes are VALUES (caught by the value canaries + the CLI's own
|
|
80
|
+
// --code redaction), not key names, so `code` is deliberately NOT here.
|
|
81
|
+
export const KEY_NAME_CANARY = /(?:secret|token|passwd|password|authorization|api[_-]?key|private[_-]?key|credential|cookie|session|bearer|jwt)/i;
|
|
82
|
+
|
|
83
|
+
export interface RedactionFieldHit {
|
|
84
|
+
lane: "args" | "rendered";
|
|
85
|
+
key: string;
|
|
86
|
+
/** Why the field was scrubbed/dropped. */
|
|
87
|
+
reason: "not_allowlisted" | "non_primitive" | "key_name_canary" | "value_canary" | "unknown_action";
|
|
88
|
+
/** The canary label, when reason === "value_canary". Never the matched text. */
|
|
89
|
+
canary?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface RedactionReport {
|
|
93
|
+
/** Fields kept (after passing all three layers). */
|
|
94
|
+
keptArgs: string[];
|
|
95
|
+
keptRendered: string[];
|
|
96
|
+
/** Fields dropped or value-redacted, with the reason (for D8 audit / D6 alerts). */
|
|
97
|
+
hits: RedactionFieldHit[];
|
|
98
|
+
/** True if any value-canary fired — the signal an alert rule watches. */
|
|
99
|
+
canaryTripped: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isPrimitive(v: unknown): v is string | number | boolean {
|
|
103
|
+
const t = typeof v;
|
|
104
|
+
return t === "string" || t === "number" || t === "boolean";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Scan a stringified value against the value canaries. Returns the first label hit. */
|
|
108
|
+
export function scanValueCanary(value: string): string | undefined {
|
|
109
|
+
for (const c of VALUE_CANARIES) if (c.re.test(value)) return c.label;
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* CORE-zone identifier canaries — the STRUCTURED secret/PII shapes only.
|
|
115
|
+
*
|
|
116
|
+
* The core zone (actor.id / actor.ref, and the scope ids) is UNREDACTED by design and
|
|
117
|
+
* its contract is "opaque, never raw PII/secret". Unlike a payload value, a core id is
|
|
118
|
+
* SUPPOSED to be a high-entropy opaque handle (a salted hash `h_…`, a UUID install id,
|
|
119
|
+
* an app domain). So this scan deliberately EXCLUDES three VALUE_CANARIES that cannot
|
|
120
|
+
* distinguish a leak from a legitimate opaque id:
|
|
121
|
+
* • `high_entropy` — every opaque id is high-entropy by construction;
|
|
122
|
+
* • `card_number` / `phone` — a numeric hash / UUID segment is a bare digit run,
|
|
123
|
+
* structurally identical to a card/phone; scanning for them would reject legitimate
|
|
124
|
+
* numeric ids (e.g. an all-hex-digit hash, a digit-heavy UUID segment).
|
|
125
|
+
* What REMAINS are the STRUCTURED shapes that never occur in an opaque id but ARE the
|
|
126
|
+
* accidental-PII/secret an emitter most plausibly forgets to pseudonymize: an `@` email,
|
|
127
|
+
* a `eyJ…` JWT, a `Bearer …` header, `AKIA…`/`ghp_…`/`xox…`/`sk-…` cloud keys, a
|
|
128
|
+
* `-----BEGIN … PRIVATE KEY-----`. Used by D4 ingest to enforce the "core carries no
|
|
129
|
+
* PII by construction" invariant the payload contract already enforces.
|
|
130
|
+
*/
|
|
131
|
+
const CORE_IDENTIFIER_EXCLUDED = new Set(["high_entropy", "card_number", "phone"]);
|
|
132
|
+
const CORE_IDENTIFIER_CANARIES: readonly { label: string; re: RegExp }[] = VALUE_CANARIES.filter(
|
|
133
|
+
(c) => !CORE_IDENTIFIER_EXCLUDED.has(c.label),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Scan a CORE-zone identifier (actor.id / actor.ref / scope id) for a RAW, STRUCTURED
|
|
138
|
+
* secret/PII SHAPE. Returns the canary label on a hit, else undefined. Excludes the
|
|
139
|
+
* high-entropy / card / phone canaries so a legitimate opaque id (hash / UUID / domain)
|
|
140
|
+
* is never flagged, while a raw email / JWT / bearer / cloud-key / private-key still is.
|
|
141
|
+
* Length-capped like a payload value.
|
|
142
|
+
*/
|
|
143
|
+
export function scanCoreIdentifier(value: string): string | undefined {
|
|
144
|
+
const s = value.slice(0, MAX_VALUE_LEN);
|
|
145
|
+
for (const c of CORE_IDENTIFIER_CANARIES) if (c.re.test(s)) return c.label;
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function redactLane(
|
|
150
|
+
lane: "args" | "rendered",
|
|
151
|
+
raw: Record<string, unknown> | undefined,
|
|
152
|
+
allow: readonly string[],
|
|
153
|
+
hits: RedactionFieldHit[],
|
|
154
|
+
): Record<string, string | number | boolean> | undefined {
|
|
155
|
+
if (!raw) return undefined;
|
|
156
|
+
const allowSet = new Set(allow);
|
|
157
|
+
const out: Record<string, string | number | boolean> = {};
|
|
158
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
159
|
+
// Layer 3b: secret-named key — drop regardless of allowlist.
|
|
160
|
+
if (KEY_NAME_CANARY.test(key)) {
|
|
161
|
+
hits.push({ lane, key, reason: "key_name_canary" });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
// Layer 1: default-deny allowlist.
|
|
165
|
+
if (!allowSet.has(key)) {
|
|
166
|
+
hits.push({ lane, key, reason: "not_allowlisted" });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Layer 2: primitives only.
|
|
170
|
+
if (!isPrimitive(value)) {
|
|
171
|
+
hits.push({ lane, key, reason: "non_primitive" });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// Layer 3a: value canary scan on the stringified, length-capped value.
|
|
175
|
+
const str = String(value).slice(0, MAX_VALUE_LEN);
|
|
176
|
+
const canary = scanValueCanary(str);
|
|
177
|
+
if (canary) {
|
|
178
|
+
hits.push({ lane, key, reason: "value_canary", canary });
|
|
179
|
+
out[key] = REDACTION_MARKER;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
out[key] = typeof value === "string" ? str : value;
|
|
183
|
+
}
|
|
184
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Apply the redaction contract to a raw payload for a given action. Pure. Returns
|
|
189
|
+
* the redacted payload (safe to store) plus a report of what was kept/dropped and
|
|
190
|
+
* whether a canary tripped. An unknown action yields an empty payload (deny-all).
|
|
191
|
+
*/
|
|
192
|
+
export function redactPayload(
|
|
193
|
+
action: string,
|
|
194
|
+
raw: ActivityPayload | undefined,
|
|
195
|
+
): { payload: ActivityPayload | undefined; report: RedactionReport } {
|
|
196
|
+
const hits: RedactionFieldHit[] = [];
|
|
197
|
+
const spec: ActionSpec | undefined = getActionSpec(action);
|
|
198
|
+
if (!spec) {
|
|
199
|
+
if (raw?.args) for (const k of Object.keys(raw.args)) hits.push({ lane: "args", key: k, reason: "unknown_action" });
|
|
200
|
+
if (raw?.rendered) for (const k of Object.keys(raw.rendered)) hits.push({ lane: "rendered", key: k, reason: "unknown_action" });
|
|
201
|
+
return { payload: undefined, report: { keptArgs: [], keptRendered: [], hits, canaryTripped: false } };
|
|
202
|
+
}
|
|
203
|
+
const args = redactLane("args", raw?.args, spec.argsAllow, hits);
|
|
204
|
+
const renderedRaw = redactLane("rendered", raw?.rendered, spec.renderedAllow, hits);
|
|
205
|
+
// rendered is declared as string-valued; coerce (canary marker + capped strings
|
|
206
|
+
// are already strings, primitives stringify safely).
|
|
207
|
+
const rendered = renderedRaw
|
|
208
|
+
? Object.fromEntries(Object.entries(renderedRaw).map(([k, v]) => [k, String(v)]))
|
|
209
|
+
: undefined;
|
|
210
|
+
const payload: ActivityPayload | undefined = args || rendered ? { ...(args ? { args } : {}), ...(rendered ? { rendered } : {}) } : undefined;
|
|
211
|
+
return {
|
|
212
|
+
payload,
|
|
213
|
+
report: {
|
|
214
|
+
keptArgs: args ? Object.keys(args) : [],
|
|
215
|
+
keptRendered: rendered ? Object.keys(rendered) : [],
|
|
216
|
+
hits,
|
|
217
|
+
canaryTripped: hits.some((h) => h.reason === "value_canary"),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface CreateActivityEventInput {
|
|
223
|
+
action: ActionKey;
|
|
224
|
+
actor: ActivityActor;
|
|
225
|
+
source: ActivitySource;
|
|
226
|
+
outcome: ActivityOutcome;
|
|
227
|
+
scope?: ActivityScope;
|
|
228
|
+
/** RAW payload — WILL be redacted before the event is returned. */
|
|
229
|
+
payload?: ActivityPayload;
|
|
230
|
+
/** Override the timestamp (tests / replaying a reported client event). */
|
|
231
|
+
at?: string;
|
|
232
|
+
/** Override the id (idempotent re-ingest). Defaults to a fresh UUID. */
|
|
233
|
+
id?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The SAFE constructor — the only sanctioned way to build an ActivityEvent.
|
|
238
|
+
* Fills v/id/at, and runs the raw payload through redactPayload() so the returned
|
|
239
|
+
* event's payload is already default-denied + canary-scanned. Returns the event
|
|
240
|
+
* plus the redaction report (D4 ingest logs the report / emits
|
|
241
|
+
* `system.ingest.rejected` if a canary tripped; D1 recordActivity() can ignore it).
|
|
242
|
+
* Throws only on an out-of-catalog action (a programming error — deny by default).
|
|
243
|
+
*/
|
|
244
|
+
export function createActivityEvent(input: CreateActivityEventInput): {
|
|
245
|
+
event: ActivityEvent;
|
|
246
|
+
report: RedactionReport;
|
|
247
|
+
} {
|
|
248
|
+
const spec = getActionSpec(input.action);
|
|
249
|
+
if (!spec) throw new Error(`activity: unknown action "${input.action}" (not in catalog)`);
|
|
250
|
+
const { payload, report } = redactPayload(input.action, input.payload);
|
|
251
|
+
const event: ActivityEvent = {
|
|
252
|
+
v: ACTIVITY_SCHEMA_VERSION,
|
|
253
|
+
id: input.id ?? newEventId(),
|
|
254
|
+
at: input.at ?? new Date().toISOString(),
|
|
255
|
+
actor: input.actor,
|
|
256
|
+
action: input.action,
|
|
257
|
+
source: input.source,
|
|
258
|
+
scope: input.scope ?? {},
|
|
259
|
+
outcome: input.outcome,
|
|
260
|
+
...(payload ? { payload } : {}),
|
|
261
|
+
};
|
|
262
|
+
return { event, report };
|
|
263
|
+
}
|