@tokenoftrust/storefront-runner 1.4.0 → 1.4.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/storefront/astro.config.mjs +17 -1
- package/apps/storefront/integrations/materialize-guard.mjs +56 -0
- package/apps/storefront/migrations-apps/0001_woozy_lyja.sql +22 -0
- package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
- package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
- package/apps/storefront/migrations-apps/meta/0001_snapshot.json +990 -0
- package/apps/storefront/migrations-apps/meta/0002_snapshot.json +1160 -0
- package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +21 -0
- package/apps/storefront/package.json +0 -1
- package/apps/storefront/public/js/dashboard-team.js +38 -2
- package/apps/storefront/src/components/Seo.astro +65 -1
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +1432 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1756 -1382
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
- package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
- package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
- package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
- package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
- package/apps/storefront/src/layouts/Layout.astro +48 -3
- package/apps/storefront/src/lib/activity/alerts.ts +426 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +85 -0
- package/apps/storefront/src/lib/activity/deployVersion.ts +120 -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 +101 -0
- package/apps/storefront/src/lib/activity/store.ts +120 -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/admin/adminShell.ts +290 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
- package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
- package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
- package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
- package/apps/storefront/src/lib/apps/adminService.ts +4 -4
- package/apps/storefront/src/lib/apps/adminSession.ts +53 -44
- package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
- package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
- package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
- package/apps/storefront/src/lib/apps/credentials.ts +2 -2
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
- package/apps/storefront/src/lib/apps/healthAggregate.ts +5 -5
- package/apps/storefront/src/lib/apps/orders/attributionService.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +6 -6
- package/apps/storefront/src/lib/apps/registryService.ts +6 -6
- package/apps/storefront/src/lib/apps/scopes.ts +2 -2
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
- package/apps/storefront/src/lib/apps/widgets/eligibility.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
- package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
- package/apps/storefront/src/lib/auth/adminEntry.ts +9 -9
- package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
- package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +6 -4
- package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
- package/apps/storefront/src/lib/auth/route.ts +44 -4
- package/apps/storefront/src/lib/auth/session.ts +7 -8
- package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
- package/apps/storefront/src/lib/basePath.ts +9 -2
- package/apps/storefront/src/lib/blog/access.ts +29 -0
- package/apps/storefront/src/lib/blog/collection.ts +313 -0
- package/apps/storefront/src/lib/blog/markdown.ts +174 -0
- package/apps/storefront/src/lib/blog/provider.ts +5 -0
- package/apps/storefront/src/lib/blog/reactions.ts +87 -0
- package/apps/storefront/src/lib/blog/rss.ts +92 -0
- package/apps/storefront/src/lib/blog/teaser.ts +83 -0
- package/apps/storefront/src/lib/blog/types.ts +34 -0
- package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
- package/apps/storefront/src/lib/d1/catalog.ts +2 -2
- package/apps/storefront/src/lib/d1/schema-apps.ts +147 -14
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
- package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
- package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
- package/apps/storefront/src/lib/dev/cockpitStore.ts +1 -9
- package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
- package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
- package/apps/storefront/src/lib/edgeCache.ts +17 -0
- package/apps/storefront/src/lib/env.ts +51 -0
- package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
- package/apps/storefront/src/lib/i18n.ts +25 -0
- package/apps/storefront/src/lib/jsonld.ts +118 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +11 -11
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
- package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
- package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
- package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
- package/apps/storefront/src/lib/newsletter/send.ts +265 -0
- package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +3519 -0
- package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +5 -7
- package/apps/storefront/src/lib/publish/domainState.ts +22 -13
- package/apps/storefront/src/lib/publish/panelPolling.ts +78 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +100 -42
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -0
- package/apps/storefront/src/lib/rate/kvWindow.ts +53 -0
- package/apps/storefront/src/lib/search/index.ts +2 -2
- package/apps/storefront/src/lib/seo/alternates.ts +42 -0
- package/apps/storefront/src/lib/seo/meta.ts +65 -0
- package/apps/storefront/src/lib/social/golive.ts +212 -0
- package/apps/storefront/src/lib/social/notify.ts +32 -0
- package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
- package/apps/storefront/src/lib/the-build/provider.ts +27 -0
- package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
- package/apps/storefront/src/lib/the-build/toc.ts +95 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
- package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
- package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
- package/apps/storefront/src/middleware/index.ts +130 -2
- package/apps/storefront/src/pages/admin/ops-timeline.astro +420 -0
- package/apps/storefront/src/pages/admin.astro +52 -1206
- 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 +27 -1
- package/apps/storefront/src/pages/api/apps/admin/install.ts +26 -1
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +24 -1
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +24 -1
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +25 -1
- package/apps/storefront/src/pages/api/apps/admin/update.ts +24 -1
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +11 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +11 -0
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +20 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +12 -0
- package/apps/storefront/src/pages/api/blog/[collection]/[slug]/react.ts +104 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +11 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +30 -0
- package/apps/storefront/src/pages/api/newsletter/[collection]/subscribe.ts +146 -0
- package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
- package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +21 -2
- package/apps/storefront/src/pages/auth/login.astro +5 -4
- package/apps/storefront/src/pages/blog/[slug].astro +96 -16
- package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
- package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
- package/apps/storefront/src/pages/blog/index.astro +2 -0
- package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
- package/apps/storefront/src/pages/cockpit.astro +74 -12
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
- package/apps/storefront/src/pages/search.astro +1 -1
- package/apps/storefront/src/pages/sitemap.xml.ts +20 -0
- package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
- package/apps/storefront/src/pages/the-build/index.astro +146 -0
- package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
- package/apps/storefront/src/styles/admin.css +1080 -0
- package/apps/storefront/tsconfig.json +5 -0
- package/package.json +1 -1
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/src/activity/README.md +146 -0
- package/packages/public-runtime/src/activity/catalog.ts +553 -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 +13 -0
- package/packages/public-runtime/src/catalog-d1.ts +1 -1
- package/packages/public-runtime/src/customization-preview.ts +25 -2
- package/packages/public-runtime/src/customization-runtime.ts +125 -3
- package/packages/public-runtime/src/customization-versioning.ts +10 -0
- package/packages/public-runtime/src/index.ts +7 -0
- package/packages/public-runtime/src/static-bundle.ts +357 -0
- package/packages/public-runtime/src/tenant.ts +36 -8
- package/scripts/build/copy-tenant-assets.mjs +14 -4
- package/scripts/dev/ai-edit.mjs +1 -1
- package/scripts/dev/checkout-watch.mjs +2 -2
- package/scripts/dev/file-browser.mjs +2 -4
- package/scripts/dev/file-writer.mjs +1 -1
- package/scripts/dev/git-status.mjs +2 -2
- package/scripts/dev/locate-handler.mjs +3 -3
- package/scripts/dev/port-check.mjs +3 -3
- package/scripts/dev/publish.mjs +5 -9
- package/scripts/dev/shot.mjs +1 -1
- package/scripts/dev/unified-diff.mjs +8 -3
- package/scripts/tot-dev.mjs +6 -9
- package/apps/storefront/src/lib/webhooks/signing.ts +0 -29
- package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +0 -146
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Action catalog — the ALLOWLISTED registry of actionKeys (card D0).
|
|
3
|
+
//
|
|
4
|
+
// The `action` field of an ActivityEvent is NEVER a free string. It is one of
|
|
5
|
+
// the keys below, and each key ships a spec: which actor kinds may emit it,
|
|
6
|
+
// which source, which store sink(s) it lands in, and — critically — the
|
|
7
|
+
// default-DENY allowlist of payload fields that survive redaction for it.
|
|
8
|
+
//
|
|
9
|
+
// WHY A FIXED CATALOG. Free-string actions make the downstream query/alert
|
|
10
|
+
// surface (D5/D6/D7) intractable: you cannot build a dashboard, an alert rule,
|
|
11
|
+
// or a redaction allowlist against an open vocabulary. Adding a NEW action is a
|
|
12
|
+
// deliberate edit HERE (plus, for the CLI, the JS mirror + its keep-in-step
|
|
13
|
+
// test — see the module README), not an ad-hoc string at a call site.
|
|
14
|
+
//
|
|
15
|
+
// GROUNDING. These keys were reverse-engineered from the activity surfaces that
|
|
16
|
+
// already exist informally in this repo, so D1–D3 map onto them 1:1 rather than
|
|
17
|
+
// inventing new vocab:
|
|
18
|
+
// • CLI command dispatch + ~/.tot/activity.log → cli.command.*
|
|
19
|
+
// • obstacle beacon (obstacle-beacon.cjs) → cli.obstacle.reported
|
|
20
|
+
// • local→hosted dev bridge (dev/activityStore, runtimeStore, obstacleStore)
|
|
21
|
+
// → dev.*
|
|
22
|
+
// • preview serve / reconcile / Gitea webhook → preview.* / reconcile.* / webhook.*
|
|
23
|
+
// • deploy version observed (provenance, D2) → deploy.version_observed
|
|
24
|
+
// • ShipLoop CHANGE_STATES + admin routes
|
|
25
|
+
// (accept/reject/ship/revert/rollback/integrate/publish/invite/retire)
|
|
26
|
+
// → change.* / content.* / grant.* / preview.*
|
|
27
|
+
// • app installs (STOREFRONT_APPS_DB) → app.*
|
|
28
|
+
// • agent cohort (later) → agent.*
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
import type { ActorKind, ActivitySource, ActivitySink } from "./event.js";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Metadata for one catalog action. `argsAllow` / `renderedAllow` are the
|
|
34
|
+
* default-DENY payload allowlists consumed by redaction.ts: ONLY these top-level
|
|
35
|
+
* keys survive; everything else is dropped. An empty allowlist means the action
|
|
36
|
+
* stores no payload of that lane at all.
|
|
37
|
+
*/
|
|
38
|
+
export interface ActionSpec {
|
|
39
|
+
/** Actor kinds legitimately allowed to emit this action (ingest rejects others). */
|
|
40
|
+
actors: readonly ActorKind[];
|
|
41
|
+
/** Where the action originates. */
|
|
42
|
+
source: ActivitySource;
|
|
43
|
+
/**
|
|
44
|
+
* Store routing (the per-action half of the store decision, DECISIONS.md §8).
|
|
45
|
+
* Every action includes "logs" (universal, zero-provision firehose). High-
|
|
46
|
+
* volume/query-light actions add "analytics" (Cloudflare Analytics Engine).
|
|
47
|
+
* Low-volume/high-value lifecycle actions add "timeline" (D1, queryable ops UI).
|
|
48
|
+
*/
|
|
49
|
+
sinks: readonly ActivitySink[];
|
|
50
|
+
/** Allowlist: payload.args keys that survive redaction. Default-deny. */
|
|
51
|
+
argsAllow: readonly string[];
|
|
52
|
+
/** Allowlist: payload.rendered keys that survive redaction. Default-deny. */
|
|
53
|
+
renderedAllow: readonly string[];
|
|
54
|
+
/** One-line human description for a query legend / UI. */
|
|
55
|
+
desc: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Sink shorthands — keep the registry readable and the routing policy in one place.
|
|
59
|
+
const LOGS: readonly ActivitySink[] = ["logs"];
|
|
60
|
+
const HIGH_VOLUME: readonly ActivitySink[] = ["logs", "analytics"];
|
|
61
|
+
const LIFECYCLE: readonly ActivitySink[] = ["logs", "timeline"];
|
|
62
|
+
const PROVENANCE: readonly ActivitySink[] = ["logs", "analytics", "timeline"];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* THE CATALOG. Add a new action by adding a key here (and its JS mirror for the
|
|
66
|
+
* CLI).
|
|
67
|
+
*
|
|
68
|
+
* NAMING STANDARD (two-tier — every key is dot-namespaced; the FIRST segment is a
|
|
69
|
+
* DOMAIN from the closed set below, and there are NO un-namespaced keys):
|
|
70
|
+
*
|
|
71
|
+
* • `<domain>.<verb>` — IMPERATIVE, for an ATOMIC action that happens in one
|
|
72
|
+
* shot: `change.ship`, `grant.invite`, `store.connect`, `change.go_live`.
|
|
73
|
+
* • `<domain>.<entity>.<verb|state>` — PARTICIPLE/STATE, for a TWO-PHASE / async
|
|
74
|
+
* action that opens then closes (an invoke/result pair, or a received/terminal
|
|
75
|
+
* span): `cli.command.invoked` + `cli.command.result`, `preview.build.requested`
|
|
76
|
+
* + `preview.build.completed`, `webhook.reconcile.received` + `reconcile.terminal`.
|
|
77
|
+
*
|
|
78
|
+
* The DOMAIN prefix is stable (dashboards and alert rules glob on it) and MUST be one
|
|
79
|
+
* of the closed set: cli, dev, preview, webhook, reconcile, deploy, change, content,
|
|
80
|
+
* grant, store, app, agent, system.
|
|
81
|
+
*
|
|
82
|
+
* TWO-PHASE PAIRING GOING FORWARD: a NEW two-phase action uses `.invoked` / `.result`.
|
|
83
|
+
* The pre-existing `.requested` / `.completed` (preview.build.*) and `.received` /
|
|
84
|
+
* `.terminal` (webhook.reconcile.received + reconcile.terminal) pairs are GRANDFATHERED
|
|
85
|
+
* verbatim — do NOT rename them (alerts.ts hardcodes some of these literals).
|
|
86
|
+
*/
|
|
87
|
+
export const ACTION_CATALOG = {
|
|
88
|
+
// ── CLI (source: cli) ──────────────────────────────────────────────────────
|
|
89
|
+
// Grounded in packages/cli command dispatch + activity-log.mjs. High-volume:
|
|
90
|
+
// a developer runs dozens–hundreds of commands/day. NOTE the redaction
|
|
91
|
+
// allowlist excludes raw `args` argv (paths/emails/codes) — only the command
|
|
92
|
+
// NAME + version survive; the CLI's own activity-log.mjs already redacts
|
|
93
|
+
// --code/--token, and here we go further (deny-by-default).
|
|
94
|
+
"cli.command.invoked": {
|
|
95
|
+
actors: ["dev", "agent"], source: "cli", sinks: HIGH_VOLUME,
|
|
96
|
+
argsAllow: ["command", "subcommand", "cliVersion", "node"], renderedAllow: [],
|
|
97
|
+
desc: "A `tot` CLI command was invoked.",
|
|
98
|
+
},
|
|
99
|
+
"cli.command.result": {
|
|
100
|
+
actors: ["dev", "agent"], source: "cli", sinks: HIGH_VOLUME,
|
|
101
|
+
argsAllow: ["command", "subcommand", "cliVersion", "exitCode", "durationMs"], renderedAllow: [],
|
|
102
|
+
desc: "A `tot` CLI command finished (exit code + duration).",
|
|
103
|
+
},
|
|
104
|
+
"cli.obstacle.reported": {
|
|
105
|
+
// The obstacle beacon (obstacle-beacon.cjs) — a failed `tot start`/`tot clone`
|
|
106
|
+
// preflight. Low volume but support-valuable → also timelined.
|
|
107
|
+
actors: ["dev"], source: "cli", sinks: PROVENANCE,
|
|
108
|
+
argsAllow: ["kind", "have", "need", "cliVersion"], renderedAllow: [],
|
|
109
|
+
desc: "A local `tot start`/`tot clone` failed preflight (node-too-old, pnpm-missing, install/clone).",
|
|
110
|
+
},
|
|
111
|
+
"cli.signin.requested": {
|
|
112
|
+
actors: ["dev"], source: "cli", sinks: LOGS,
|
|
113
|
+
argsAllow: ["cliVersion"], renderedAllow: [],
|
|
114
|
+
desc: "The CLI requested a browserless sign-in code.",
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// ── Local dev loop → hosted bridge (source: cli) ───────────────────────────
|
|
118
|
+
// Grounded in dev/activityStore.ts, runtimeStore.ts. Heartbeats are the single
|
|
119
|
+
// highest-volume stream (one every few seconds while `tot dev` runs) → analytics
|
|
120
|
+
// only, never timelined. Local paths (cwd/url/editor) are DROPPED by allowlist.
|
|
121
|
+
"dev.runtime.heartbeat": {
|
|
122
|
+
actors: ["dev"], source: "cli", sinks: HIGH_VOLUME,
|
|
123
|
+
argsAllow: ["cliVersion", "runnerVersion", "os", "osVersion", "arch"], renderedAllow: [],
|
|
124
|
+
desc: "The developer's local dev loop reported a liveness heartbeat.",
|
|
125
|
+
},
|
|
126
|
+
"dev.file.changed": {
|
|
127
|
+
actors: ["dev"], source: "cli", sinks: HIGH_VOLUME,
|
|
128
|
+
// Raw file path is DROPPED (local, may carry names); only the event kind +
|
|
129
|
+
// the server-composed business description (activityStore.describeActivity)
|
|
130
|
+
// survive.
|
|
131
|
+
argsAllow: ["event"], renderedAllow: ["description"],
|
|
132
|
+
desc: "A file save was reported by the local dev loop.",
|
|
133
|
+
},
|
|
134
|
+
"dev.serve.up": {
|
|
135
|
+
actors: ["dev"], source: "cli", sinks: LOGS,
|
|
136
|
+
argsAllow: [], renderedAllow: [],
|
|
137
|
+
desc: "The developer's store came up locally.",
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// ── Preview / reconcile / webhook (source: server) ─────────────────────────
|
|
141
|
+
"preview.serve": {
|
|
142
|
+
actors: ["system"], source: "server", sinks: HIGH_VOLUME,
|
|
143
|
+
argsAllow: [], renderedAllow: [],
|
|
144
|
+
desc: "A preview environment served a request (per-request, high volume).",
|
|
145
|
+
},
|
|
146
|
+
"preview.build.requested": {
|
|
147
|
+
actors: ["admin", "dev", "system"], source: "server", sinks: LIFECYCLE,
|
|
148
|
+
argsAllow: ["changeId"], renderedAllow: [],
|
|
149
|
+
desc: "A preview build was requested.",
|
|
150
|
+
},
|
|
151
|
+
"preview.build.completed": {
|
|
152
|
+
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
153
|
+
argsAllow: ["changeId", "result"], renderedAllow: [],
|
|
154
|
+
desc: "A preview build finished (result: ok|failed).",
|
|
155
|
+
},
|
|
156
|
+
"preview.override": {
|
|
157
|
+
// D9: an AUTHENTICATED operator accepted the risk of a blocking-safety finding for a
|
|
158
|
+
// specific commit (/api/preview/override), letting the reconcile materialize past it.
|
|
159
|
+
// Cockpit-gated (signed-in real developer capability + session tenant), so the acting
|
|
160
|
+
// cohort is owner→merchant, staff→admin, or a signed-in developer→dev. The required
|
|
161
|
+
// justification is operator-authored → rendered (truncated + canary-scanned).
|
|
162
|
+
actors: ["admin", "merchant", "dev"], source: "ui", sinks: LIFECYCLE,
|
|
163
|
+
argsAllow: ["commit"], renderedAllow: ["reason"],
|
|
164
|
+
desc: "An operator overrode a blocking-safety finding for a preview commit (auditable).",
|
|
165
|
+
},
|
|
166
|
+
"webhook.reconcile.received": {
|
|
167
|
+
// Gitea candidate-reconcile webhook (api/changes/candidate-reconcile.ts). Low
|
|
168
|
+
// volume (one per PR push, NOT per request) and the lifecycle PARTNER of
|
|
169
|
+
// `reconcile.terminal` — timelined so D7's reconcile-hang watchdog can correlate
|
|
170
|
+
// "received" against "terminal" for the same changeId and detect an ABSENCE.
|
|
171
|
+
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
172
|
+
argsAllow: ["repo", "action", "branch", "changeId"], renderedAllow: [],
|
|
173
|
+
desc: "A Gitea reconcile webhook was received.",
|
|
174
|
+
},
|
|
175
|
+
"reconcile.terminal": {
|
|
176
|
+
// A reconcile reached a terminal state (reconciled|blocked|failed). Low
|
|
177
|
+
// volume, high value → timelined for the ops UI (D5).
|
|
178
|
+
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
179
|
+
argsAllow: ["changeId", "result", "evidenceStatus"], renderedAllow: [],
|
|
180
|
+
desc: "A candidate reconcile reached a terminal state.",
|
|
181
|
+
},
|
|
182
|
+
"candidate.closed": {
|
|
183
|
+
// A `pull_request` `closed` webhook drove a candidate to its TERMINAL status
|
|
184
|
+
// (candidate-reconcile.ts): `merged` (squash-merged) or `closed` (closed
|
|
185
|
+
// unmerged), so the row leaves the operator's review queue. Low volume (one
|
|
186
|
+
// per PR close), high value — the end-of-life marker for a candidate's timeline.
|
|
187
|
+
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
188
|
+
argsAllow: ["changeId", "merged", "result", "status"], renderedAllow: [],
|
|
189
|
+
desc: "A merged/closed PR webhook transitioned a candidate to a terminal state.",
|
|
190
|
+
},
|
|
191
|
+
"prebuild.dispatched": {
|
|
192
|
+
// T1 background pre-build (docs/epics/proposed/storefront-instant-static-rollback.md
|
|
193
|
+
// Addendum §B): a successful candidate reconcile fired (or failed to fire) the
|
|
194
|
+
// materialize+validate+STAGE-ONLY repository_dispatch for a static-publish
|
|
195
|
+
// tenant (candidate-reconcile.ts). Fail-open by design — this never gates the
|
|
196
|
+
// candidate reaching `ready`; recorded so a stuck/never-dispatched prebuild is
|
|
197
|
+
// observable rather than silently absent.
|
|
198
|
+
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
199
|
+
argsAllow: ["changeId", "headSha"], renderedAllow: [],
|
|
200
|
+
desc: "A candidate's background static-prebuild dispatch (stage-only) succeeded or failed.",
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// ── Deploy provenance (source: server or cli) — card D2 ────────────────────
|
|
204
|
+
"deploy.version_observed": {
|
|
205
|
+
actors: ["system", "dev"], source: "server", sinks: PROVENANCE,
|
|
206
|
+
argsAllow: ["version", "commit", "env", "observedFrom"], renderedAllow: [],
|
|
207
|
+
desc: "A deployed worker version/commit was observed (provenance).",
|
|
208
|
+
},
|
|
209
|
+
"publish.readiness_evidence.recorded": {
|
|
210
|
+
actors: ["system"], source: "server", sinks: PROVENANCE,
|
|
211
|
+
argsAllow: ["routes", "cfFailures", "statusMismatches", "targetHealthy"], renderedAllow: [],
|
|
212
|
+
desc: "Go-live readiness dual-run evidence was recorded for a tenant (u9 producer).",
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// ── ShipLoop change lifecycle (source: ui/server) ──────────────────────────
|
|
216
|
+
// 1:1 with the admin ship routes + CHANGE_STATES (change-record.ts). Low
|
|
217
|
+
// volume, high value, needs joins/point-queries → timelined. `reason` is
|
|
218
|
+
// operator-authored justification → goes through rendered (truncated +
|
|
219
|
+
// canary-scanned), never core.
|
|
220
|
+
"change.submit": {
|
|
221
|
+
actors: ["dev", "admin"], source: "server", sinks: LIFECYCLE,
|
|
222
|
+
argsAllow: ["changeId"], renderedAllow: ["title"],
|
|
223
|
+
desc: "A change/candidate was submitted for review.",
|
|
224
|
+
},
|
|
225
|
+
"change.accept": {
|
|
226
|
+
actors: ["admin"], source: "ui", sinks: LIFECYCLE,
|
|
227
|
+
argsAllow: ["changeId"], renderedAllow: [],
|
|
228
|
+
desc: "A change was accepted/approved.",
|
|
229
|
+
},
|
|
230
|
+
"change.reject": {
|
|
231
|
+
// Supersedes the interim admin slice's "candidate.reject" (recordActivity.ts).
|
|
232
|
+
actors: ["admin"], source: "ui", sinks: LIFECYCLE,
|
|
233
|
+
argsAllow: ["changeId"], renderedAllow: ["reason"],
|
|
234
|
+
desc: "A change was rejected (reason required).",
|
|
235
|
+
},
|
|
236
|
+
"change.resolve": {
|
|
237
|
+
// u3 (tot-merge-conflict-resolution-ux): one-click conflict resolution via
|
|
238
|
+
// candidate_refresh. Same admission ladder as change.reject (resolveShipSession
|
|
239
|
+
// → ship-on-behalf floor), so the same actor cohort. `strategy` is a
|
|
240
|
+
// low-cardinality enum (ours|theirs|merge), safe to render.
|
|
241
|
+
actors: ["admin"], source: "ui", sinks: LIFECYCLE,
|
|
242
|
+
argsAllow: ["changeId"], renderedAllow: ["strategy"],
|
|
243
|
+
desc: "A not-mergeable change was resolved by rebuilding it from the base tip under a strategy.",
|
|
244
|
+
},
|
|
245
|
+
"change.ship": {
|
|
246
|
+
// D6: the admin/cockpit "Publish" (go-live) handler admits the store OWNER
|
|
247
|
+
// (merchant), ToT staff (admin), a ship-on-behalf developer (dev), AND the
|
|
248
|
+
// headless Bearer/CLI operator (agent) — the actor list reflects who actually
|
|
249
|
+
// reaches POST /api/changes/ship, not a placeholder subset.
|
|
250
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
251
|
+
argsAllow: ["changeId", "version"], renderedAllow: [],
|
|
252
|
+
desc: "A change was shipped to the live channel.",
|
|
253
|
+
},
|
|
254
|
+
"change.revert": {
|
|
255
|
+
// D9: same admission ladder as change.ship/rollback (resolveShipSession) — owner
|
|
256
|
+
// (merchant), staff (admin), ship-on-behalf dev, headless operator (agent). The
|
|
257
|
+
// `/api/changes/revert` route gates on the SAME seam, so the emitter cohort matches.
|
|
258
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
259
|
+
argsAllow: ["changeId"], renderedAllow: ["reason"],
|
|
260
|
+
desc: "A change was reverted.",
|
|
261
|
+
},
|
|
262
|
+
"change.hotfix": {
|
|
263
|
+
// D9: the owner-only emergency HOTFIX lane (/api/changes/hotfix) — a DISTINCT verb
|
|
264
|
+
// from change.ship (never a `--base main` flag): a bypass-normal-flow live promote
|
|
265
|
+
// from `main` while `preview` holds other unshipped work. Same resolveShipSession
|
|
266
|
+
// ladder as ship/rollback (owner ⊇ ship-on-behalf), so the same cohort set.
|
|
267
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
268
|
+
argsAllow: ["changeId"], renderedAllow: ["reason"],
|
|
269
|
+
desc: "An emergency hotfix was released to the live channel (bypassing unshipped preview work).",
|
|
270
|
+
},
|
|
271
|
+
"change.rollback": {
|
|
272
|
+
// D6: same admission ladder as change.ship (resolveShipSession) — owner
|
|
273
|
+
// (merchant), staff (admin), ship-on-behalf dev, headless operator (agent).
|
|
274
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
275
|
+
argsAllow: ["changeId", "toVersion"], renderedAllow: ["reason"],
|
|
276
|
+
desc: "The live channel was rolled back to a prior version.",
|
|
277
|
+
},
|
|
278
|
+
"change.integrate": {
|
|
279
|
+
// D6: accept/integrate (resolveShipSession) admits the same cohort set.
|
|
280
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
281
|
+
argsAllow: ["changeId"], renderedAllow: [],
|
|
282
|
+
desc: "A change was integrated into the shared preview.",
|
|
283
|
+
},
|
|
284
|
+
"change.rebuild": {
|
|
285
|
+
// u15: retry a dead aggregate rebuild without a new merge (/api/changes/rebuild).
|
|
286
|
+
// Same admission ladder as change.integrate/revert (resolveShipSession) — the
|
|
287
|
+
// route gates on the SAME seam, so the emitter cohort matches. No changeId: a
|
|
288
|
+
// rebuild retries the tenant's most recent integration attempt, not one candidate.
|
|
289
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
290
|
+
argsAllow: [], renderedAllow: [],
|
|
291
|
+
desc: "A dead aggregate rebuild was retried without a new merge.",
|
|
292
|
+
},
|
|
293
|
+
"change.resync": {
|
|
294
|
+
// u12: advance the shared preview aggregate to an out-of-band branch tip
|
|
295
|
+
// without a candidate/<changeId> PR (/api/changes/resync). Same admission
|
|
296
|
+
// ladder as change.integrate/rebuild (resolveShipSession). No changeId: a
|
|
297
|
+
// resync pins an operator-supplied branch/sha, not one candidate.
|
|
298
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
299
|
+
argsAllow: [], renderedAllow: [],
|
|
300
|
+
desc: "The shared preview aggregate was resynced to an out-of-band branch tip.",
|
|
301
|
+
},
|
|
302
|
+
"change.transition": {
|
|
303
|
+
// Generic SDLC transition for states without a dedicated verb above
|
|
304
|
+
// (triage↔in_progress↔ready_for_review↔announced↔closed).
|
|
305
|
+
actors: ["admin", "dev", "system"], source: "server", sinks: LIFECYCLE,
|
|
306
|
+
argsAllow: ["changeId", "from", "to"], renderedAllow: ["reason"],
|
|
307
|
+
desc: "A ShipLoop change moved between SDLC states.",
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// ── Content / customization publish (source: ui/server) ────────────────────
|
|
311
|
+
"content.publish": {
|
|
312
|
+
actors: ["admin", "merchant", "dev"], source: "server", sinks: LIFECYCLE,
|
|
313
|
+
argsAllow: ["versionId", "scope"], renderedAllow: [],
|
|
314
|
+
desc: "Tenant customization/content was published to a version.",
|
|
315
|
+
},
|
|
316
|
+
"content.checkout_style.saved": {
|
|
317
|
+
// NEW (D10): a tenant's checkout styling was saved to a versioned test artifact
|
|
318
|
+
// (Settings → Checkout styling → publishToTest spine). A persistent tenant
|
|
319
|
+
// customization — low volume, high value → timelined. Actor cohort mirrors the
|
|
320
|
+
// route's own auth (resolveOwnerSession): a merchant saving their own tenant, or
|
|
321
|
+
// a ToT-staff operator "operating as" another tenant via the admin picker.
|
|
322
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
323
|
+
argsAllow: ["versionId", "scope"], renderedAllow: [],
|
|
324
|
+
desc: "A tenant's checkout styling was saved to a version.",
|
|
325
|
+
},
|
|
326
|
+
"change.go_live": {
|
|
327
|
+
// D9: renamed from the orphaned, un-namespaced `go_live` (the ONE key that broke the
|
|
328
|
+
// domain-prefix rule). The snake_case suffix is kept (the `deploy.version_observed`
|
|
329
|
+
// precedent) — only the missing `change.` domain prefix is added. Wired into
|
|
330
|
+
// /api/preview/promote (promote the current preview snapshot to live).
|
|
331
|
+
actors: ["admin", "merchant"], source: "ui", sinks: LIFECYCLE,
|
|
332
|
+
argsAllow: ["versionId"], renderedAllow: [],
|
|
333
|
+
desc: "A store's current preview was promoted to go-live.",
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
// ── Access / grants / connect (source: ui/server) ──────────────────────────
|
|
337
|
+
// Invitee identity is PII → DROPPED by allowlist; only the role/resource +
|
|
338
|
+
// an opaque hashed invitee ref (rendered) survive.
|
|
339
|
+
"grant.invite": {
|
|
340
|
+
// D6: the self-serve "Invite a Developer" handler admits an owner (merchant) or
|
|
341
|
+
// staff (admin) session, and the headless operator (agent) via the Bearer seam.
|
|
342
|
+
// D11: the SECOND invite path (grants/team-invite.ts) is session-authed by vendor
|
|
343
|
+
// MEMBERSHIP — a signed-in team member who is neither owner nor staff classifies as
|
|
344
|
+
// "dev" (see uiActor.classifyActorKind), so `dev` joins the admission set.
|
|
345
|
+
actors: ["admin", "merchant", "dev", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
346
|
+
argsAllow: ["role", "resource"], renderedAllow: ["inviteeRef"],
|
|
347
|
+
desc: "A developer/team invite was issued.",
|
|
348
|
+
},
|
|
349
|
+
"grant.approve": {
|
|
350
|
+
// D6: the browserless CLI sign-in approval is taken by whoever is signed in
|
|
351
|
+
// (owner→merchant, staff→admin, or a signed-in developer→dev).
|
|
352
|
+
actors: ["admin", "merchant", "dev"], source: "ui", sinks: LIFECYCLE,
|
|
353
|
+
argsAllow: ["role", "resource"], renderedAllow: [],
|
|
354
|
+
desc: "A pending access request / CLI sign-in was approved.",
|
|
355
|
+
},
|
|
356
|
+
"grant.revoke": {
|
|
357
|
+
// D9: the self-serve "remove a developer from my team" handler (/api/grants/revoke),
|
|
358
|
+
// the inverse of grant.invite — same resolveOwnerSession seam, so the same cohort
|
|
359
|
+
// (owner→merchant, staff→admin, headless operator→agent). The revoked developer's
|
|
360
|
+
// identity is PII → DROPPED by the allowlist; only an opaque hashed ref survives.
|
|
361
|
+
actors: ["admin", "merchant", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
362
|
+
argsAllow: ["role", "resource"], renderedAllow: ["principalRef"],
|
|
363
|
+
desc: "A developer was revoked from the owner's team.",
|
|
364
|
+
},
|
|
365
|
+
"preview.invite": {
|
|
366
|
+
// D10 wired this into POST /api/preview/invite (the DEV-4 developer-onboarding
|
|
367
|
+
// invite), a Bearer-operator (headless) route with no human session — so `agent`
|
|
368
|
+
// is added to the cohort, mirroring how grant.invite admits the headless
|
|
369
|
+
// Bearer seam. The route mints a signed repo-onboarding LINK (owner/repo/
|
|
370
|
+
// cloneUrl), not an email invite, so there is no invitee PII to hash.
|
|
371
|
+
actors: ["admin", "merchant", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
372
|
+
argsAllow: ["changeId"], renderedAllow: ["inviteeRef"],
|
|
373
|
+
desc: "A reviewer/developer was invited to a preview.",
|
|
374
|
+
},
|
|
375
|
+
"preview.retire": {
|
|
376
|
+
actors: ["admin", "merchant"], source: "ui", sinks: LIFECYCLE,
|
|
377
|
+
argsAllow: ["changeId"], renderedAllow: [],
|
|
378
|
+
desc: "A preview environment was retired.",
|
|
379
|
+
},
|
|
380
|
+
"store.connect": {
|
|
381
|
+
actors: ["merchant", "admin"], source: "ui", sinks: LIFECYCLE,
|
|
382
|
+
argsAllow: ["provider"], renderedAllow: [],
|
|
383
|
+
desc: "A store/provider was connected.",
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// ── App installs (source: server) — STOREFRONT_APPS_DB ─────────────────────
|
|
387
|
+
"app.install": {
|
|
388
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
389
|
+
argsAllow: ["appId", "appVersion", "env"], renderedAllow: [],
|
|
390
|
+
desc: "A private app was installed.",
|
|
391
|
+
},
|
|
392
|
+
"app.uninstall": {
|
|
393
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
394
|
+
argsAllow: ["appId", "env"], renderedAllow: [],
|
|
395
|
+
desc: "A private app was uninstalled.",
|
|
396
|
+
},
|
|
397
|
+
// ── App lifecycle mutations (source: server) — card D10 ─────────────────────
|
|
398
|
+
// Modeled on app.install/app.uninstall: the SAME `/api/apps/admin/*` operator
|
|
399
|
+
// cohort reaches these (a store OWNER managing their own app → merchant, ToT
|
|
400
|
+
// staff operating-as → admin), so the actor list mirrors those two, NOT a
|
|
401
|
+
// narrower placeholder. Security note (app.credential.rotated): the rotated
|
|
402
|
+
// secret NEVER enters the payload — the allowlist names only the non-sensitive
|
|
403
|
+
// install handle + rotation time (redaction would scrub a secret anyway, but the
|
|
404
|
+
// allowlist is deliberately minimal so there is nothing to scrub).
|
|
405
|
+
"app.credential.rotated": {
|
|
406
|
+
// The rotated secret NEVER enters the payload. We identify WHICH app rotated by
|
|
407
|
+
// its (appId, env) — NOT the installId, which is a UUID and would be scrubbed by
|
|
408
|
+
// the high-entropy canary anyway; appId/env survive redaction and are the same
|
|
409
|
+
// non-sensitive handle app.install/uninstall/suspend use.
|
|
410
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
411
|
+
argsAllow: ["appId", "env", "rotatedAt"], renderedAllow: [],
|
|
412
|
+
desc: "A private app's client secret was rotated.",
|
|
413
|
+
},
|
|
414
|
+
"app.suspend": {
|
|
415
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
416
|
+
argsAllow: ["appId", "env"], renderedAllow: [],
|
|
417
|
+
desc: "A private app install was suspended.",
|
|
418
|
+
},
|
|
419
|
+
"app.resume": {
|
|
420
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
421
|
+
argsAllow: ["appId", "env"], renderedAllow: [],
|
|
422
|
+
desc: "A private app install was resumed to active.",
|
|
423
|
+
},
|
|
424
|
+
"app.update": {
|
|
425
|
+
actors: ["merchant", "admin"], source: "server", sinks: LIFECYCLE,
|
|
426
|
+
argsAllow: ["appId", "env"], renderedAllow: [],
|
|
427
|
+
desc: "A private app install was updated (version/scopes/manifest/webhook).",
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
// ── Agent cohort (source: server/cli) — later ──────────────────────────────
|
|
431
|
+
// Placeholder spine so the agent cohort (a later card) has stable keys to emit
|
|
432
|
+
// against without reopening D0. Args are deliberately empty until that card
|
|
433
|
+
// defines what an agent action is allowed to record.
|
|
434
|
+
"agent.action.invoked": {
|
|
435
|
+
actors: ["agent"], source: "server", sinks: HIGH_VOLUME,
|
|
436
|
+
argsAllow: ["tool"], renderedAllow: [],
|
|
437
|
+
desc: "An automated agent invoked an action.",
|
|
438
|
+
},
|
|
439
|
+
"agent.action.result": {
|
|
440
|
+
actors: ["agent"], source: "server", sinks: HIGH_VOLUME,
|
|
441
|
+
argsAllow: ["tool", "durationMs"], renderedAllow: [],
|
|
442
|
+
desc: "An automated agent action finished.",
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
// ── D11: remaining grant/staff-ops gaps + machine/agent-driven mutations ─────
|
|
446
|
+
// Follow-on to D0–D8: wires the invite/grant paths, staff-impersonation, cache,
|
|
447
|
+
// and machine-authed (signature/idempotency/app-JWT) mutations that D6 left out.
|
|
448
|
+
// Grant/staff actions are low-volume lifecycle → timelined; the machine cohort's
|
|
449
|
+
// high-volume streams (order-forward, attribution) are analytics-only.
|
|
450
|
+
"grant.issue": {
|
|
451
|
+
// grants/index.ts POST — the owner's team grant fan-out (one grantRepoAccess per
|
|
452
|
+
// member×repo). DISTINCT from `grant.invite` (which emails a developer to establish
|
|
453
|
+
// eligibility): this issues repo access to EXISTING principals. `resolveOwnerSession`
|
|
454
|
+
// admits owner (merchant), staff (admin), and the headless Bearer operator (agent).
|
|
455
|
+
// Principals are emails/usernames (PII) → NEVER stored; only counts + the owner.
|
|
456
|
+
actors: ["merchant", "admin", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
457
|
+
argsAllow: ["resource", "granted", "failed"], renderedAllow: [],
|
|
458
|
+
desc: "A team was granted repo access (grant fan-out).",
|
|
459
|
+
},
|
|
460
|
+
"grant.provisioned": {
|
|
461
|
+
// grants/provisioned.ts — bulk re-grant of the owner's EXISTING team onto a newly
|
|
462
|
+
// provisioned repo. DISTINCT from `grant.approve` (a CLI sign-in approval): this is
|
|
463
|
+
// a merchant-driven re-grant onto a new resource. Same admission ladder as the
|
|
464
|
+
// grants surface (resolveOwnerSession → merchant primary, staff/headless too).
|
|
465
|
+
actors: ["merchant", "admin", "agent"], source: "ui", sinks: LIFECYCLE,
|
|
466
|
+
argsAllow: ["repo", "regranted", "failed"], renderedAllow: [],
|
|
467
|
+
desc: "An existing team was re-granted onto a newly provisioned repo.",
|
|
468
|
+
},
|
|
469
|
+
"dashboard.vendor_entered": {
|
|
470
|
+
// dashboard/enter-vendor.ts — a ToT-STAFF viewer enters/impersonates a vendor tenant.
|
|
471
|
+
// A classic audit event on the staff-impersonation boundary → timelined. Emitted
|
|
472
|
+
// ONLY after the staff gate passes, so the actor is always `admin` (staff); the
|
|
473
|
+
// staff email is PII and never stored (actor.id is its salted hash).
|
|
474
|
+
actors: ["admin"], source: "ui", sinks: LIFECYCLE,
|
|
475
|
+
argsAllow: ["resource", "capability"], renderedAllow: [],
|
|
476
|
+
desc: "A ToT-staff operator entered a vendor tenant (impersonation boundary).",
|
|
477
|
+
},
|
|
478
|
+
"cache.purged": {
|
|
479
|
+
// cache-purge.ts — bumps a tenant's edge page-cache version (the ETL load step calls
|
|
480
|
+
// it per tenant post-migration). Bearer-secret service call (no session) → emitted
|
|
481
|
+
// server-side as the machine caller (agent); an operator manual purge is the admin.
|
|
482
|
+
actors: ["admin", "agent"], source: "server", sinks: LIFECYCLE,
|
|
483
|
+
argsAllow: ["version"], renderedAllow: [],
|
|
484
|
+
desc: "A tenant's edge page-cache version was bumped (purge).",
|
|
485
|
+
},
|
|
486
|
+
"store.connect.completed": {
|
|
487
|
+
// domain/callback.ts — the CI completion callback for the www-domain job, the
|
|
488
|
+
// machine-side terminal PARTNER of D6's `store.connect`. Signature-authed, one per
|
|
489
|
+
// connect run (rare) → low-volume lifecycle terminal, timelined like reconcile.terminal.
|
|
490
|
+
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
491
|
+
argsAllow: ["status", "state", "action"], renderedAllow: [],
|
|
492
|
+
desc: "A store apex-domain connect/rollback CI job reported completion.",
|
|
493
|
+
},
|
|
494
|
+
"app.order.forwarded": {
|
|
495
|
+
// apps/internal/order-forward.ts — an order forwarded to a private app (the
|
|
496
|
+
// tot-foxycart reconciliation unit, HMAC-signature-authed). HIGH-VOLUME (per order)
|
|
497
|
+
// → analytics only, NEVER timelined. Customer/order ids are excluded from the
|
|
498
|
+
// allowlist; only the coarse status/env/currency dimensions survive.
|
|
499
|
+
actors: ["agent", "system"], source: "server", sinks: HIGH_VOLUME,
|
|
500
|
+
argsAllow: ["status", "env", "currency"], renderedAllow: [],
|
|
501
|
+
desc: "An order was forwarded to a private app.",
|
|
502
|
+
},
|
|
503
|
+
"app.attribution.recorded": {
|
|
504
|
+
// apps/v1/attribution.ts — an installed private app (external bot, app-JWT authed,
|
|
505
|
+
// idempotency-keyed) records an attribution. HIGH-VOLUME → analytics only. The
|
|
506
|
+
// referralCode/customerHash are excluded; only the campaign dimension survives.
|
|
507
|
+
actors: ["agent"], source: "server", sinks: HIGH_VOLUME,
|
|
508
|
+
argsAllow: ["campaign"], renderedAllow: [],
|
|
509
|
+
desc: "A private app recorded an order attribution.",
|
|
510
|
+
},
|
|
511
|
+
"webhook.delivery.replayed": {
|
|
512
|
+
// apps/{admin,v1}/webhooks/deliveries/[deliveryId]/replay.ts — a webhook delivery was
|
|
513
|
+
// re-enqueued. TWO call sites, ONE key: the admin/ops console (session-authed →
|
|
514
|
+
// admin/merchant per resolveOperatorSession) and the app-facing v1 route (app-JWT →
|
|
515
|
+
// agent). A deliberate remediation action, low-volume → timelined for audit. The
|
|
516
|
+
// deliveryId is the opaque CloudEvents wire id (non-PII); `surface` disambiguates.
|
|
517
|
+
actors: ["admin", "merchant", "agent"], source: "server", sinks: LIFECYCLE,
|
|
518
|
+
argsAllow: ["deliveryId", "surface"], renderedAllow: [],
|
|
519
|
+
desc: "A webhook delivery was replayed (re-enqueued).",
|
|
520
|
+
},
|
|
521
|
+
|
|
522
|
+
// ── Meta (source: server) ──────────────────────────────────────────────────
|
|
523
|
+
"system.ingest.rejected": {
|
|
524
|
+
// The ingest endpoint (D4) rejected an event (unknown action, actor
|
|
525
|
+
// mismatch, canary tripped). Self-telemetry so a leak attempt is visible.
|
|
526
|
+
actors: ["system"], source: "server", sinks: HIGH_VOLUME,
|
|
527
|
+
argsAllow: ["reason", "attemptedAction"], renderedAllow: [],
|
|
528
|
+
desc: "An inbound activity event was rejected at ingest.",
|
|
529
|
+
},
|
|
530
|
+
} as const satisfies Record<string, ActionSpec>;
|
|
531
|
+
|
|
532
|
+
/** The allowlisted set of action keys. `action` on an ActivityEvent must be one. */
|
|
533
|
+
export type ActionKey = keyof typeof ACTION_CATALOG;
|
|
534
|
+
|
|
535
|
+
/** All action keys as a runtime array (for ingest validation, tests, docs). */
|
|
536
|
+
export const ACTION_KEYS = Object.keys(ACTION_CATALOG) as ActionKey[];
|
|
537
|
+
|
|
538
|
+
/** Type guard: is this string an allowlisted action key? Deny-by-default. */
|
|
539
|
+
export function isActionKey(v: unknown): v is ActionKey {
|
|
540
|
+
return typeof v === "string" && Object.prototype.hasOwnProperty.call(ACTION_CATALOG, v);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** The spec for an action key. Returns undefined for an unknown key (caller
|
|
544
|
+
* treats undefined as "reject" — never as "allow everything"). */
|
|
545
|
+
export function getActionSpec(action: string): ActionSpec | undefined {
|
|
546
|
+
return isActionKey(action) ? ACTION_CATALOG[action] : undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** May this actor kind emit this action? Ingest (D4) uses this to reject spoofed
|
|
550
|
+
* cohorts (e.g. a `dev`-sourced token claiming an `admin` lifecycle action). */
|
|
551
|
+
export function isActorAllowed(action: ActionKey, actor: ActorKind): boolean {
|
|
552
|
+
return (ACTION_CATALOG[action].actors as readonly ActorKind[]).includes(actor);
|
|
553
|
+
}
|