@tokenoftrust/storefront-runner 2.0.16 → 2.0.18
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/env.d.ts +18 -0
- package/apps/storefront/migrations-apps/0012_test_destinations.sql +29 -0
- package/apps/storefront/src/components/blog/ReactionBar.astro +1 -1
- package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +365 -49
- package/apps/storefront/src/components/islands/checkoutVerificationFlow.ts +251 -0
- package/apps/storefront/src/config/compliance/rulesets.ts +18 -7
- package/apps/storefront/src/config/devTenantSeed.ts +24 -1
- package/apps/storefront/src/config/evidenceTenantRoster.ts +25 -0
- package/apps/storefront/src/config/publicationSpaceConfig.ts +25 -0
- package/apps/storefront/src/config/resolver.ts +5 -0
- package/apps/storefront/src/config/tenantAnalyticsFiles.ts +43 -0
- package/apps/storefront/src/config/tenants.ts +11 -0
- package/apps/storefront/src/layouts/Layout.astro +5 -1
- package/apps/storefront/src/lib/activity/killSwitch.ts +1 -1
- package/apps/storefront/src/lib/admin/consentSettingsClient.ts +113 -0
- package/apps/storefront/src/lib/apps/consentConfigRead.ts +24 -0
- package/apps/storefront/src/lib/apps/gtmMigrationDraft.ts +103 -0
- package/apps/storefront/src/lib/apps/routeParams.ts +45 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +25 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +1 -1
- package/apps/storefront/src/lib/compliance/enforcement.ts +25 -3
- package/apps/storefront/src/lib/d1/schema-apps.ts +44 -0
- package/apps/storefront/src/lib/env.ts +13 -111
- package/apps/storefront/src/lib/fixtures/regulated-commerce-truth/index.ts +540 -0
- package/apps/storefront/src/lib/fixtures/ws3-readonly-clone-preflight/index.ts +495 -0
- package/apps/storefront/src/lib/integration/tenantAnalyticsDeclarations.ts +198 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +1 -1
- package/apps/storefront/src/lib/rawMarketingHtml.ts +9 -1
- package/apps/storefront/src/lib/runtimeEnv.ts +124 -0
- package/apps/storefront/src/lib/verification/totVerificationClient.ts +485 -0
- package/apps/storefront/src/middleware/candidatePin.ts +35 -0
- package/apps/storefront/src/middleware/controlPlane.ts +270 -0
- package/apps/storefront/src/middleware/index.ts +100 -796
- package/apps/storefront/src/middleware/publicRender.ts +43 -0
- package/apps/storefront/src/middleware/renderChain.ts +685 -0
- package/apps/storefront/src/middleware/routeBranch.ts +160 -0
- package/apps/storefront/src/middleware/staticBundle.ts +109 -0
- package/apps/storefront/src/pages/[...slug].astro +1 -0
- package/apps/storefront/src/pages/api/compliance/preflight.ts +179 -2
- package/apps/storefront/src/pages/index.astro +1 -0
- package/apps/storefront/vitest.config.ts +5 -0
- package/package.json +1 -1
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/deliver-test-event-request.schema.json +122 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-test-destination-delivery-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/register-test-destination-request.schema.json +93 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/test-destination-delivery.schema.json +106 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/test-destination.schema.json +77 -0
- package/packages/public-runtime/schemas/publication-space/v1/manifest.schema.json +195 -0
- package/packages/public-runtime/schemas/publication-space/v1/normalized-mount.schema.json +80 -0
- package/packages/public-runtime/schemas/publication-space/v1/ref.schema.json +20 -0
- package/packages/public-runtime/schemas/publication-version/v1/pointer.schema.json +28 -0
- package/packages/public-runtime/schemas/publication-version/v1/transition-request.schema.json +44 -0
- package/packages/public-runtime/schemas/publication-version/v1/transition.schema.json +47 -0
- package/packages/public-runtime/schemas/publication-version/v1/version.schema.json +58 -0
- package/packages/public-runtime/src/activity/catalog.ts +1 -1
- package/packages/public-runtime/src/candidate-index.ts +6 -0
- package/packages/public-runtime/src/customization-reconcile.ts +8 -0
- package/packages/public-runtime/src/html-cache-policy.ts +57 -0
- package/packages/public-runtime/src/index.ts +7 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +115 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +111 -27
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +65 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +81 -0
- package/packages/public-runtime/src/integration/render-phases.ts +8 -3
- package/packages/public-runtime/src/publication-artifact.ts +203 -0
- package/packages/public-runtime/src/publication-space.ts +853 -0
- package/packages/public-runtime/src/publication-version.ts +587 -0
- package/packages/public-runtime/src/route-tier.ts +257 -0
- package/packages/public-runtime/src/static-bundle.ts +18 -5
- package/packages/public-runtime/src/tenant-assets.ts +85 -17
- package/packages/public-runtime/src/tenant.ts +70 -0
- package/packages/public-runtime/tests/conformance/fake-destination.ts +402 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/internal.json +32 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/mounted-public.json +38 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/normalized-mounts.json +39 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/ref.json +5 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/root-public.json +31 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/articles-v1.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/articles-v2.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/internal-v1.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/live-pointer.json +11 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/live-transition-request.json +14 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/live-transition.json +17 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/root-v1.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/root-v2.json +20 -0
- package/scripts/dev/ai-edit.mjs +16 -3
- package/scripts/dev/port-check.mjs +18 -3
- package/scripts/dev/publish.mjs +26 -3
- package/scripts/tenant/validate.mjs +14 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://tokenoftrust.com/schemas/publication-space/v1/ref.schema.json",
|
|
4
|
+
"title": "PublicationSpaceRefV1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["version", "tenant", "spaceId"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"version": { "const": 1 },
|
|
11
|
+
"tenant": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"pattern": "^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$"
|
|
14
|
+
},
|
|
15
|
+
"spaceId": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"pattern": "^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://tokenoftrust.com/schemas/publication-version/v1/pointer.schema.json",
|
|
4
|
+
"title": "PublicationChannelPointerV1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "tenant", "spaceId", "channel", "revision", "versionId", "sourceSha", "artifactDigest", "updatedAt"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"tenant": { "$ref": "#/$defs/identifier" },
|
|
12
|
+
"spaceId": { "$ref": "#/$defs/identifier" },
|
|
13
|
+
"channel": { "enum": ["preview", "live"] },
|
|
14
|
+
"revision": { "type": "integer", "minimum": 1 },
|
|
15
|
+
"versionId": { "$ref": "#/$defs/versionId" },
|
|
16
|
+
"sourceSha": { "$ref": "#/$defs/sha40" },
|
|
17
|
+
"artifactDigest": { "$ref": "#/$defs/sha256" },
|
|
18
|
+
"updatedAt": { "$ref": "#/$defs/instant" }
|
|
19
|
+
},
|
|
20
|
+
"$defs": {
|
|
21
|
+
"identifier": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$" },
|
|
22
|
+
"opaqueId": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._:-]{6,126}[a-z0-9])$" },
|
|
23
|
+
"versionId": { "type": "string", "pattern": "^pv_[a-z0-9](?:[a-z0-9._:-]{4,122}[a-z0-9])$" },
|
|
24
|
+
"sha40": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
|
|
25
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
26
|
+
"instant": { "type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$" }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://tokenoftrust.com/schemas/publication-version/v1/transition-request.schema.json",
|
|
4
|
+
"title": "PublicationTransitionRequestV1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"schemaVersion",
|
|
10
|
+
"transitionId",
|
|
11
|
+
"operation",
|
|
12
|
+
"tenant",
|
|
13
|
+
"spaceId",
|
|
14
|
+
"channel",
|
|
15
|
+
"targetVersionId",
|
|
16
|
+
"expectedSourceSha",
|
|
17
|
+
"expectedArtifactDigest",
|
|
18
|
+
"expectedRevision",
|
|
19
|
+
"principal",
|
|
20
|
+
"occurredAt"
|
|
21
|
+
],
|
|
22
|
+
"properties": {
|
|
23
|
+
"schemaVersion": { "const": 1 },
|
|
24
|
+
"transitionId": { "$ref": "#/$defs/opaqueId" },
|
|
25
|
+
"operation": { "enum": ["activate", "rollback"] },
|
|
26
|
+
"tenant": { "$ref": "#/$defs/identifier" },
|
|
27
|
+
"spaceId": { "$ref": "#/$defs/identifier" },
|
|
28
|
+
"channel": { "enum": ["preview", "live"] },
|
|
29
|
+
"targetVersionId": { "$ref": "#/$defs/versionId" },
|
|
30
|
+
"expectedSourceSha": { "$ref": "#/$defs/sha40" },
|
|
31
|
+
"expectedArtifactDigest": { "$ref": "#/$defs/sha256" },
|
|
32
|
+
"expectedRevision": { "type": "integer", "minimum": 0 },
|
|
33
|
+
"principal": { "$ref": "#/$defs/opaqueId" },
|
|
34
|
+
"occurredAt": { "$ref": "#/$defs/instant" }
|
|
35
|
+
},
|
|
36
|
+
"$defs": {
|
|
37
|
+
"identifier": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$" },
|
|
38
|
+
"opaqueId": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._:-]{6,126}[a-z0-9])$" },
|
|
39
|
+
"versionId": { "type": "string", "pattern": "^pv_[a-z0-9](?:[a-z0-9._:-]{4,122}[a-z0-9])$" },
|
|
40
|
+
"sha40": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
|
|
41
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
42
|
+
"instant": { "type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://tokenoftrust.com/schemas/publication-version/v1/transition.schema.json",
|
|
4
|
+
"title": "PublicationPointerTransitionV1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "transitionId", "operation", "tenant", "spaceId", "channel", "previous", "next", "principal", "occurredAt"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"transitionId": { "$ref": "#/$defs/opaqueId" },
|
|
12
|
+
"operation": { "enum": ["activate", "rollback"] },
|
|
13
|
+
"tenant": { "$ref": "#/$defs/identifier" },
|
|
14
|
+
"spaceId": { "$ref": "#/$defs/identifier" },
|
|
15
|
+
"channel": { "enum": ["preview", "live"] },
|
|
16
|
+
"previous": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/snapshot" }] },
|
|
17
|
+
"next": { "$ref": "#/$defs/snapshot" },
|
|
18
|
+
"principal": { "$ref": "#/$defs/opaqueId" },
|
|
19
|
+
"occurredAt": { "$ref": "#/$defs/instant" }
|
|
20
|
+
},
|
|
21
|
+
"allOf": [
|
|
22
|
+
{
|
|
23
|
+
"if": { "properties": { "operation": { "const": "rollback" } }, "required": ["operation"] },
|
|
24
|
+
"then": { "properties": { "previous": { "$ref": "#/$defs/snapshot" } } }
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"$defs": {
|
|
28
|
+
"identifier": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$" },
|
|
29
|
+
"opaqueId": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._:-]{6,126}[a-z0-9])$" },
|
|
30
|
+
"versionId": { "type": "string", "pattern": "^pv_[a-z0-9](?:[a-z0-9._:-]{4,122}[a-z0-9])$" },
|
|
31
|
+
"sha40": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
|
|
32
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
33
|
+
"instant": { "type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$" },
|
|
34
|
+
"snapshot": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
37
|
+
"additionalProperties": false,
|
|
38
|
+
"required": ["revision", "versionId", "sourceSha", "artifactDigest"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"revision": { "type": "integer", "minimum": 1 },
|
|
41
|
+
"versionId": { "$ref": "#/$defs/versionId" },
|
|
42
|
+
"sourceSha": { "$ref": "#/$defs/sha40" },
|
|
43
|
+
"artifactDigest": { "$ref": "#/$defs/sha256" }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://tokenoftrust.com/schemas/publication-version/v1/version.schema.json",
|
|
4
|
+
"title": "PublicationVersionV1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schemaVersion", "versionId", "tenant", "spaceId", "source", "renderer", "artifactDigest", "createdAt", "metadata"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"versionId": { "$ref": "#/$defs/versionId" },
|
|
12
|
+
"tenant": { "$ref": "#/$defs/identifier" },
|
|
13
|
+
"spaceId": { "$ref": "#/$defs/identifier" },
|
|
14
|
+
"source": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"required": ["forge", "repo", "commitSha"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"forge": { "enum": ["gitea", "github", "fixture"] },
|
|
21
|
+
"repo": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"minLength": 1,
|
|
24
|
+
"maxLength": 256,
|
|
25
|
+
"pattern": "^(?!.*\\.\\.)(?!.*//)[A-Za-z0-9](?:[A-Za-z0-9._/-]{0,254}[A-Za-z0-9])?$"
|
|
26
|
+
},
|
|
27
|
+
"commitSha": { "$ref": "#/$defs/sha40" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"renderer": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"required": ["contractVersion", "themeContractVersion"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"contractVersion": { "type": "string", "minLength": 1 },
|
|
37
|
+
"themeContractVersion": { "type": "string", "minLength": 1 }
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"artifactDigest": { "$ref": "#/$defs/sha256" },
|
|
41
|
+
"createdAt": { "$ref": "#/$defs/instant" },
|
|
42
|
+
"metadata": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"patternProperties": { "^\\$(?:schema|comment|id)$": {} },
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"required": ["buildId"],
|
|
47
|
+
"properties": { "buildId": { "$ref": "#/$defs/opaqueId" } }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"$defs": {
|
|
51
|
+
"identifier": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$" },
|
|
52
|
+
"opaqueId": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9._:-]{6,126}[a-z0-9])$" },
|
|
53
|
+
"versionId": { "type": "string", "pattern": "^pv_[a-z0-9](?:[a-z0-9._:-]{4,122}[a-z0-9])$" },
|
|
54
|
+
"sha40": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
|
|
55
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
56
|
+
"instant": { "type": "string", "format": "date-time", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -98,7 +98,7 @@ export const ACTION_CATALOG = {
|
|
|
98
98
|
},
|
|
99
99
|
"cli.command.result": {
|
|
100
100
|
actors: ["dev", "agent"], source: "cli", sinks: HIGH_VOLUME,
|
|
101
|
-
argsAllow: ["command", "subcommand", "cliVersion", "exitCode", "durationMs"], renderedAllow: [],
|
|
101
|
+
argsAllow: ["command", "subcommand", "cliVersion", "exitCode", "durationMs", "diagnosticId"], renderedAllow: [],
|
|
102
102
|
desc: "A `tot` CLI command finished (exit code + duration).",
|
|
103
103
|
},
|
|
104
104
|
"cli.obstacle.reported": {
|
|
@@ -122,6 +122,12 @@ export interface ReviewEnvironment {
|
|
|
122
122
|
* surfaced in the PR review console. Absent/empty ⇒ validated clean.
|
|
123
123
|
*/
|
|
124
124
|
findings?: PreviewStateFinding[];
|
|
125
|
+
/**
|
|
126
|
+
* Artifact paths this candidate's reconcile DROPPED from the version it published.
|
|
127
|
+
* Non-empty on a `ready` candidate means the preview renders the tenant's content
|
|
128
|
+
* MINUS these files; `lastError` names them in prose. Absent/empty ⇒ published clean.
|
|
129
|
+
*/
|
|
130
|
+
droppedArtifacts?: string[];
|
|
125
131
|
/** Auditable safety-block override applied to this candidate (who/why), if any. */
|
|
126
132
|
overriddenBy?: PreviewStateOverride | null;
|
|
127
133
|
/**
|
|
@@ -76,6 +76,14 @@ export interface PreviewState {
|
|
|
76
76
|
* Absent/empty ⇒ the tenant's files validated clean. Written by `reconcilePreview`.
|
|
77
77
|
*/
|
|
78
78
|
findings?: PreviewStateFinding[];
|
|
79
|
+
/**
|
|
80
|
+
* Artifact paths the last reconcile DROPPED from the version it published. A
|
|
81
|
+
* non-empty list on a SUCCESSFUL reconcile means the site is serving the tenant's
|
|
82
|
+
* content MINUS these files — they exist in the repo and are absent from the live
|
|
83
|
+
* version. Absent/empty ⇒ everything mapped was published. `lastError` names the
|
|
84
|
+
* same paths in prose. Written by `reconcilePreview`.
|
|
85
|
+
*/
|
|
86
|
+
droppedArtifacts?: string[];
|
|
79
87
|
/**
|
|
80
88
|
* The auditable safety-block override applied to this reconcile (who/why), when a
|
|
81
89
|
* blocking-safety finding was materialized past. Absent/null ⇒ no override.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-document HTML cache policy (ADR 0013 — `serving.htmlCachePolicy`).
|
|
3
|
+
*
|
|
4
|
+
* The browser/shared TTLs a tenant's ROUTE DOCUMENTS (HTML) are served with are a
|
|
5
|
+
* declared property of the TENANT, read by BOTH public-serving substrates (the
|
|
6
|
+
* Cloudflare Worker+R2 pointer plane and the AWS S3/CloudFront plane), so moving
|
|
7
|
+
* substrate never silently changes repeat-visit caching. This module is the ONE
|
|
8
|
+
* place the policy is turned into a `Cache-Control` string, so both planes emit a
|
|
9
|
+
* byte-identical header from one formatter.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** The declared route-document cache TTLs (all in SECONDS). Absent fields fall to {@link DEFAULT_HTML_CACHE_POLICY}. */
|
|
13
|
+
export interface HtmlCachePolicy {
|
|
14
|
+
/** `max-age` — how long a browser may reuse a route document without revalidating. */
|
|
15
|
+
browserMaxAge?: number;
|
|
16
|
+
/** `s-maxage` — how long the shared CDN cache may reuse it. */
|
|
17
|
+
sharedMaxAge?: number;
|
|
18
|
+
/** `stale-while-revalidate` — grace window a stale document may still serve while a fresh one is fetched. */
|
|
19
|
+
staleWhileRevalidate?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The route-document default when a tenant declares no `serving.htmlCachePolicy`.
|
|
24
|
+
* A 5-minute browser+shared TTL with a 1-day SWR: fresh-biased because the pointer
|
|
25
|
+
* flip republishes at a STABLE url and every publish busts the edge (digest-scoped
|
|
26
|
+
* keys self-invalidate), so a short browser ceiling is safe and the ADR's contract.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_HTML_CACHE_POLICY: Required<HtmlCachePolicy> = {
|
|
29
|
+
browserMaxAge: 300,
|
|
30
|
+
sharedMaxAge: 300,
|
|
31
|
+
staleWhileRevalidate: 86400,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render a {@link HtmlCachePolicy} to a `Cache-Control` header value. Each field
|
|
36
|
+
* falls back to {@link DEFAULT_HTML_CACHE_POLICY} INDEPENDENTLY, so a partial policy
|
|
37
|
+
* is completed rather than rejected, and `null`/`undefined` yields the full default.
|
|
38
|
+
* This is the single source of the route-document Cache-Control string for both planes.
|
|
39
|
+
*/
|
|
40
|
+
export function formatHtmlCacheControl(policy?: HtmlCachePolicy | null): string {
|
|
41
|
+
const browserMaxAge = policy?.browserMaxAge ?? DEFAULT_HTML_CACHE_POLICY.browserMaxAge;
|
|
42
|
+
const sharedMaxAge = policy?.sharedMaxAge ?? DEFAULT_HTML_CACHE_POLICY.sharedMaxAge;
|
|
43
|
+
const staleWhileRevalidate =
|
|
44
|
+
policy?.staleWhileRevalidate ?? DEFAULT_HTML_CACHE_POLICY.staleWhileRevalidate;
|
|
45
|
+
return `public, max-age=${browserMaxAge}, s-maxage=${sharedMaxAge}, stale-while-revalidate=${staleWhileRevalidate}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The internal, MATERIALIZE-ONLY response header the render emits (gated on the
|
|
50
|
+
* materialize probe request header) so the publish/materialize plane can capture the
|
|
51
|
+
* tenant's resolved route-document Cache-Control into the bundle manifest — the same
|
|
52
|
+
* capture pattern as CSP. It is NEVER emitted on a real public response.
|
|
53
|
+
*/
|
|
54
|
+
export const HTML_CACHE_POLICY_CAPTURE_HEADER = "x-tot-html-cache-policy";
|
|
55
|
+
|
|
56
|
+
/** The request header the materialize probe sends to ask the render to emit {@link HTML_CACHE_POLICY_CAPTURE_HEADER}. */
|
|
57
|
+
export const HTML_CACHE_POLICY_CAPTURE_PROBE = "x-tot-materialize-capture";
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
// preview / script types the renderer needs. Read/apply only.
|
|
16
16
|
export * from "./product.js";
|
|
17
17
|
export * from "./tenant.js";
|
|
18
|
+
export * from "./publication-space.js";
|
|
19
|
+
export * from "./publication-version.js";
|
|
20
|
+
export * from "./publication-artifact.js";
|
|
21
|
+
// The ONE route-tier authority: route -> mount -> publication space ->
|
|
22
|
+
// {visibility, authorization} -> an ADR 0013 tier. No consumer derives its own.
|
|
23
|
+
export * from "./route-tier.js";
|
|
18
24
|
export * from "./checkout.js";
|
|
19
25
|
export * from "./membership.js";
|
|
20
26
|
export * from "./checkout-orders.js";
|
|
@@ -51,6 +57,7 @@ export * from "./review-trust-proof.js";
|
|
|
51
57
|
export * from "./compliance/index.js";
|
|
52
58
|
export * from "./tenant-assets.js";
|
|
53
59
|
export * from "./static-bundle.js";
|
|
60
|
+
export * from "./html-cache-policy.js";
|
|
54
61
|
export * from "./binary-path.js";
|
|
55
62
|
export * from "./hot-standby-readiness.js";
|
|
56
63
|
export * from "./hash.js";
|
|
@@ -140,6 +140,33 @@ export function defaultConsentSnapshot(): ConsentSnapshot {
|
|
|
140
140
|
return makeSnapshot([ESSENTIAL]);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The MATERIALIZE-ONLY request header carrying a tenant's resolved consent snapshot
|
|
145
|
+
* (ADR 0013). The go-live dispatch resolves the tenant's REAL snapshot from D1 (the
|
|
146
|
+
* Worker holds that binding) and the bake pipeline sets this header on each render, so
|
|
147
|
+
* the static render inlines the real snapshot WITHOUT a consent D1 read on the
|
|
148
|
+
* public/materialize path. Never present on a real public request.
|
|
149
|
+
*/
|
|
150
|
+
export const CONSENT_SNAPSHOT_INPUT_HEADER = "x-tot-consent-snapshot";
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The HMAC-SHA256 signature (hex) over the {@link CONSENT_SNAPSHOT_INPUT_HEADER}
|
|
154
|
+
* value, carried alongside it. The go-live Worker (which holds both the tenant's real
|
|
155
|
+
* consent D1 and the signing secret) signs the snapshot; the materialize render
|
|
156
|
+
* VERIFIES it before honoring the input header. Absent/invalid ⇒ the snapshot input is
|
|
157
|
+
* rejected and consent falls back to the fail-closed default. Together with the
|
|
158
|
+
* materialize-mode binding gate this makes the input header un-forgeable: the deployed
|
|
159
|
+
* Worker never reads it at all, and even in materialize mode a snapshot without a valid
|
|
160
|
+
* signature from the go-live secret is refused.
|
|
161
|
+
*/
|
|
162
|
+
export const CONSENT_SNAPSHOT_SIG_HEADER = "x-tot-consent-snapshot-sig";
|
|
163
|
+
|
|
164
|
+
/** The Worker secret binding whose value signs/verifies the baked consent snapshot. */
|
|
165
|
+
export const CONSENT_SNAPSHOT_HMAC_SECRET_BINDING = "CONSENT_SNAPSHOT_HMAC_SECRET";
|
|
166
|
+
|
|
167
|
+
/** The Worker binding that marks a MATERIALIZE-mode render — absent on the deployed Worker. */
|
|
168
|
+
export const MATERIALIZE_MODE_BINDING = "TOT_MATERIALIZE_MODE";
|
|
169
|
+
|
|
143
170
|
/**
|
|
144
171
|
* Whether a purpose is currently eligible. `essential` is always eligible; an
|
|
145
172
|
* unknown or unapproved-and-ungranted purpose is not — so this both permits
|
|
@@ -208,6 +235,94 @@ export function parseConsentSnapshot(raw: unknown): ConsentSnapshot | null {
|
|
|
208
235
|
return canonical;
|
|
209
236
|
}
|
|
210
237
|
|
|
238
|
+
// ── Baked-snapshot signature (materialize bake wire) ────────────────────────────
|
|
239
|
+
// The go-live Worker holds the tenant's real consent D1 AND the signing secret; it
|
|
240
|
+
// signs the resolved snapshot, and the materialize render verifies before honoring the
|
|
241
|
+
// input header. Web Crypto only (crypto.subtle) so it runs unchanged on workerd, and
|
|
242
|
+
// verification is constant-time. The signature is TENANT-BOUND — it covers a
|
|
243
|
+
// length-prefixed framing of `tenantId` and `serializedSnapshot`, so neither field's
|
|
244
|
+
// content can shift a byte across the field boundary: a signed snapshot for one tenant
|
|
245
|
+
// can never be replayed as another tenant's baked input, or as a different snapshot for
|
|
246
|
+
// the same tenant.
|
|
247
|
+
|
|
248
|
+
const consentSigTextEncoder = new TextEncoder();
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The exact string the consent snapshot signature covers: each field prefixed by its
|
|
252
|
+
* own UTF-8 byte length, so no value in either field can be interpreted as a field
|
|
253
|
+
* separator.
|
|
254
|
+
*/
|
|
255
|
+
function consentSignaturePayload(tenantId: string, serializedSnapshot: string): string {
|
|
256
|
+
const tenantBytes = consentSigTextEncoder.encode(tenantId).length;
|
|
257
|
+
const snapshotBytes = consentSigTextEncoder.encode(serializedSnapshot).length;
|
|
258
|
+
return `${tenantBytes}:${tenantId}${snapshotBytes}:${serializedSnapshot}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function consentHmacKey(secret: string): Promise<CryptoKey> {
|
|
262
|
+
return crypto.subtle.importKey(
|
|
263
|
+
"raw",
|
|
264
|
+
consentSigTextEncoder.encode(secret) as unknown as BufferSource,
|
|
265
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
266
|
+
false,
|
|
267
|
+
["sign", "verify"],
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function consentSigHexToBytes(hex: string): Uint8Array | null {
|
|
272
|
+
if (hex.length === 0 || hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
|
|
273
|
+
const out = new Uint8Array(hex.length / 2);
|
|
274
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Sign a tenant's serialized consent snapshot: hex HMAC-SHA256 over the length-prefixed
|
|
280
|
+
* {@link consentSignaturePayload}. The go-live Worker calls this; the materialize render
|
|
281
|
+
* verifies with the same secret via {@link verifyConsentSnapshotSig}.
|
|
282
|
+
*/
|
|
283
|
+
export async function signConsentSnapshot(
|
|
284
|
+
secret: string,
|
|
285
|
+
tenantId: string,
|
|
286
|
+
serializedSnapshot: string,
|
|
287
|
+
): Promise<string> {
|
|
288
|
+
const key = await consentHmacKey(secret);
|
|
289
|
+
const sig = await crypto.subtle.sign(
|
|
290
|
+
"HMAC",
|
|
291
|
+
key,
|
|
292
|
+
consentSigTextEncoder.encode(
|
|
293
|
+
consentSignaturePayload(tenantId, serializedSnapshot),
|
|
294
|
+
) as unknown as BufferSource,
|
|
295
|
+
);
|
|
296
|
+
let hex = "";
|
|
297
|
+
for (const b of new Uint8Array(sig)) hex += b.toString(16).padStart(2, "0");
|
|
298
|
+
return hex;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Verify a consent snapshot signature (constant-time via `crypto.subtle.verify`). A
|
|
303
|
+
* malformed / short / absent signature returns false rather than throwing — fail-closed,
|
|
304
|
+
* so a caller can never smuggle in an unsigned or forged snapshot.
|
|
305
|
+
*/
|
|
306
|
+
export async function verifyConsentSnapshotSig(
|
|
307
|
+
secret: string,
|
|
308
|
+
tenantId: string,
|
|
309
|
+
serializedSnapshot: string,
|
|
310
|
+
signatureHex: string | null | undefined,
|
|
311
|
+
): Promise<boolean> {
|
|
312
|
+
if (!signatureHex) return false;
|
|
313
|
+
const sig = consentSigHexToBytes(signatureHex.trim());
|
|
314
|
+
if (!sig) return false;
|
|
315
|
+
const key = await consentHmacKey(secret);
|
|
316
|
+
return crypto.subtle.verify(
|
|
317
|
+
"HMAC",
|
|
318
|
+
key,
|
|
319
|
+
sig as unknown as BufferSource,
|
|
320
|
+
consentSigTextEncoder.encode(
|
|
321
|
+
consentSignaturePayload(tenantId, serializedSnapshot),
|
|
322
|
+
) as unknown as BufferSource,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
211
326
|
/** Listener invoked with the new snapshot whenever eligibility changes. */
|
|
212
327
|
export type ConsentListener = (snapshot: ConsentSnapshot) => void;
|
|
213
328
|
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
* with it (the plan carries no vendor runtime, and `planRevision` moves), so a
|
|
13
13
|
* rollback leaves no stale effect.
|
|
14
14
|
*
|
|
15
|
-
* It is deliberately narrow (ADR 0016/0023 Q6):
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
15
|
+
* It is deliberately narrow (ADR 0016/0023 Q6): on a regulated tenant the bridge
|
|
16
|
+
* enables only the declarations the surface × tier matrix permits on the regulated
|
|
17
|
+
* surface without a certification — reviewed platform-adapter/sandbox/server tiers —
|
|
18
|
+
* and REFUSES full parent authority (`elevated-parent`) until a capability-specific
|
|
19
|
+
* certification clears every clause. The regulated shared-runtime floor is
|
|
20
|
+
* non-waivable, and this bridge never opens an escape hatch around it. Promote-time
|
|
21
|
+
* enforcement still runs through the full fail-closed gate
|
|
22
|
+
* (`verifyIntegrationPromotable`); this module is the render-time tenant-eligibility
|
|
23
|
+
* decision that precedes it.
|
|
22
24
|
*/
|
|
23
25
|
import type { BrowserEnvironment } from "./environment.js";
|
|
24
26
|
import type { EffectivePlan } from "./plan.js";
|
|
@@ -34,6 +36,13 @@ import {
|
|
|
34
36
|
isRegulatedParentCertified,
|
|
35
37
|
type RegulatedParentCertification,
|
|
36
38
|
} from "./regulated-parent-cert.js";
|
|
39
|
+
import {
|
|
40
|
+
allowedTiersForCell,
|
|
41
|
+
resolveCellAuthority,
|
|
42
|
+
type CellAuthority,
|
|
43
|
+
type TenantTier,
|
|
44
|
+
} from "../integration-capabilities.js";
|
|
45
|
+
import type { ExecutionTier } from "./phases.js";
|
|
37
46
|
|
|
38
47
|
/**
|
|
39
48
|
* Whether an integration's declared consent dependency permits loading its entry
|
|
@@ -57,6 +66,11 @@ export interface InterimBridgeRefusal {
|
|
|
57
66
|
surface: AssuranceSurface;
|
|
58
67
|
/** An operator-facing explanation of the refusal and what unblocks it. */
|
|
59
68
|
message: string;
|
|
69
|
+
/**
|
|
70
|
+
* The registered feedback item tracking this gap, so a consumer can route the reader
|
|
71
|
+
* to live work without parsing the message. See {@link REGULATED_REFUSAL_FEEDBACK_REF}.
|
|
72
|
+
*/
|
|
73
|
+
feedbackRef: string;
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
/**
|
|
@@ -80,10 +94,11 @@ export interface InterimBridgeInput {
|
|
|
80
94
|
* G2: the regulated elevated-parent certification artifact for a regulated tenant.
|
|
81
95
|
* The bridge NEVER accepts a raw "certified" boolean — it runs
|
|
82
96
|
* {@link decideRegulatedParentCert} itself (via {@link isRegulatedParentCertified}),
|
|
83
|
-
* so `certifiedImmutable` has a SINGLE ORIGIN: a regulated tenant
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
97
|
+
* so `certifiedImmutable` has a SINGLE ORIGIN: a regulated tenant's full-parent
|
|
98
|
+
* authority (`elevated-parent`) declaration is enabled ONLY when the cert clears
|
|
99
|
+
* every clause (pinned, undrifted from `servedDigest`, every server-derived
|
|
100
|
+
* obligation proven). Absent/failed ⇒ the decision fails closed and that declaration
|
|
101
|
+
* stays refused. This is the WS2 forward seam, not a per-tenant bypass.
|
|
87
102
|
*/
|
|
88
103
|
regulatedCertification?: RegulatedParentCertification | null;
|
|
89
104
|
/**
|
|
@@ -101,31 +116,71 @@ export interface InterimBridgeInput {
|
|
|
101
116
|
* set), so an eligible tenant's plan is exactly the kernel plan every mount serves —
|
|
102
117
|
* the bridge adds only the tenant-eligibility gate, not a second plan shape.
|
|
103
118
|
*/
|
|
119
|
+
/**
|
|
120
|
+
* Feedback tripwire for the regulated refusal below.
|
|
121
|
+
*
|
|
122
|
+
* A tripwire is three coupled parts: a registered feedback item describing an accepted
|
|
123
|
+
* gap, a reference to it from the message emitted where execution trips over that gap,
|
|
124
|
+
* and a workstream that owns closing it. Carrying the reference in the refusal itself is
|
|
125
|
+
* what routes a reader to live work instead of a fresh diagnosis.
|
|
126
|
+
*
|
|
127
|
+
* This gate refuses a regulated tenant whose declaration requests full parent
|
|
128
|
+
* authority (`elevated-parent`) without a {@link RegulatedParentCertification} that
|
|
129
|
+
* clears every clause — an absent or failed one fails closed. Owned by the
|
|
130
|
+
* `tot-regulated-integration-certification` workstream.
|
|
131
|
+
*
|
|
132
|
+
* See docs/playbooks/feedback-tripwire.md for the generalized recipe — this is the
|
|
133
|
+
* reference implementation, not a one-off.
|
|
134
|
+
*/
|
|
135
|
+
export const REGULATED_REFUSAL_FEEDBACK_REF = "fb-1788612281954-3q8f1n" as const;
|
|
136
|
+
|
|
104
137
|
export function resolveInterimBridgePlan(input: InterimBridgeInput): InterimBridgeResult {
|
|
105
138
|
const surface = resolveObligationSurface(input.tenant);
|
|
106
139
|
|
|
107
140
|
// The certification is the SINGLE ORIGIN of `certifiedImmutable`: derive it from the
|
|
108
|
-
// decision (never a raw external flag), so a regulated tenant
|
|
109
|
-
// cert actually clears every clause. Absent/failed
|
|
141
|
+
// decision (never a raw external flag), so a regulated tenant's full-parent authority
|
|
142
|
+
// declaration is enabled only when the cert actually clears every clause. Absent/failed
|
|
143
|
+
// ⇒ false ⇒ that declaration is refused.
|
|
110
144
|
const regulatedCertified = isRegulatedParentCertified(input.regulatedCertification, {
|
|
111
145
|
tenant: input.tenant,
|
|
112
146
|
servedDigest: input.servedDigest,
|
|
113
147
|
});
|
|
114
148
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
// Drive the render-time refusal off the same surface × tier matrix the policy layer
|
|
150
|
+
// enforces (SURFACE_TENANT_TIER_MATRIX): a regulated tenant is refused only for a
|
|
151
|
+
// declaration the matrix does not permit on the regulated surface — full parent
|
|
152
|
+
// authority (`elevated-parent`) without a certification that clears every clause, never
|
|
153
|
+
// a reviewed platform-adapter/sandbox/server tier the cell already allows. A cert whose
|
|
154
|
+
// codeForm is `mutable-container`, or that has drifted/is incomplete, never clears the
|
|
155
|
+
// decision, so mutable parent code on a regulated surface stays refused.
|
|
156
|
+
// `verifyIntegrationPromotable` remains the authoritative fail-closed backstop at
|
|
157
|
+
// promote time; this only stops the bridge PRE-refusing a class the policy permits.
|
|
158
|
+
if (isRegulated(input.tenant)) {
|
|
159
|
+
const cell = resolveCellAuthority(regulatedTenantTier(input.tenant), "regulated");
|
|
160
|
+
const refused =
|
|
161
|
+
!cell ||
|
|
162
|
+
input.declarations.some((decl) =>
|
|
163
|
+
isRegulatedDeclarationRefused(decl.manifest.browser.executionTier, cell, regulatedCertified),
|
|
164
|
+
);
|
|
165
|
+
if (refused) {
|
|
166
|
+
return {
|
|
167
|
+
ok: false,
|
|
168
|
+
reason: "regulated-refused",
|
|
169
|
+
refusal: {
|
|
170
|
+
code: "regulated-tenant-not-certified",
|
|
171
|
+
surface,
|
|
172
|
+
message:
|
|
173
|
+
`This integration needs full-page access to run on your storefront. Because your ` +
|
|
174
|
+
`store is regulated, that level of access is only available once a compliance ` +
|
|
175
|
+
`certification confirms it can't reach age-verification or customer data — a ` +
|
|
176
|
+
`safeguard, not a limitation of your store. Standard analytics and platform ` +
|
|
177
|
+
`integrations, including Google Analytics 4 (GA4), work normally on a regulated ` +
|
|
178
|
+
`storefront and can be added today. To proceed, choose a standard, already-supported ` +
|
|
179
|
+
`integration, or contact support to pursue certification for this one.`,
|
|
180
|
+
feedbackRef: REGULATED_REFUSAL_FEEDBACK_REF,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
129
184
|
}
|
|
130
185
|
|
|
131
186
|
const result = resolveEffectivePlan({
|
|
@@ -136,3 +191,32 @@ export function resolveInterimBridgePlan(input: InterimBridgeInput): InterimBrid
|
|
|
136
191
|
if (!result.ok) return { ok: false, reason: "invalid-plan", violations: result.violations };
|
|
137
192
|
return { ok: true, plan: result.value };
|
|
138
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The regulated tenant tier for the matrix lookup. Both regulated tiers carry the
|
|
197
|
+
* identical `regulated` cell (`maxTier: "server"`, `elevatedParent: "cert-required"`),
|
|
198
|
+
* so the split only tracks the tenant's site type; an unset site type resolves to
|
|
199
|
+
* `regulated-commerce`, mirroring {@link resolveObligationSurface}'s commerce floor.
|
|
200
|
+
*/
|
|
201
|
+
function regulatedTenantTier(tenant: ObligationTenant): TenantTier {
|
|
202
|
+
return tenant.siteType === "marketing" ? "regulated-marketing" : "regulated-commerce";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Whether one declaration is refused on the regulated surface, per the cell's authority.
|
|
207
|
+
* Full parent authority (`elevated-parent`) is gated by the cell's elevated-parent
|
|
208
|
+
* disposition — `cert-required` enables it only behind a certification that clears every
|
|
209
|
+
* clause, and any other disposition (`prohibited`/`acknowledge`) never enables it here
|
|
210
|
+
* (fail closed). Every other tier is permitted when it is within the cell's allowed
|
|
211
|
+
* non-elevated tiers (`platform-adapter` ≤ `sandbox` ≤ `server`).
|
|
212
|
+
*/
|
|
213
|
+
function isRegulatedDeclarationRefused(
|
|
214
|
+
tier: ExecutionTier,
|
|
215
|
+
cell: CellAuthority,
|
|
216
|
+
regulatedCertified: boolean,
|
|
217
|
+
): boolean {
|
|
218
|
+
if (tier === "elevated-parent") {
|
|
219
|
+
return cell.elevatedParent === "cert-required" ? !regulatedCertified : true;
|
|
220
|
+
}
|
|
221
|
+
return !allowedTiersForCell(cell).includes(tier);
|
|
222
|
+
}
|