@nextcommerce/campaigns-os 1.33.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/AGENTS.md +204 -0
- package/CHANGELOG.md +5002 -0
- package/CONTEXT.md +685 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +368 -0
- package/agents/claude/CLAUDE.md +32 -0
- package/agents/codex/AGENTS.md +27 -0
- package/agents/copilot/copilot-instructions.md +14 -0
- package/agents/cursor/campaigns-os.mdc +13 -0
- package/bin/campaigns-os.mjs +38 -0
- package/campaign-spec/README.md +138 -0
- package/campaign-spec/dist/analytics-vocabulary.d.ts +47 -0
- package/campaign-spec/dist/analytics-vocabulary.js +74 -0
- package/campaign-spec/dist/index.d.ts +40 -0
- package/campaign-spec/dist/index.js +77 -0
- package/campaign-spec/dist/normalize.d.ts +22 -0
- package/campaign-spec/dist/normalize.js +41 -0
- package/campaign-spec/dist/routing.d.ts +190 -0
- package/campaign-spec/dist/routing.js +263 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/analytics-contract-shape.js +303 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.d.ts +42 -0
- package/campaign-spec/dist/rules/assembly-hints-shape.js +191 -0
- package/campaign-spec/dist/rules/campaign-metadata.d.ts +14 -0
- package/campaign-spec/dist/rules/campaign-metadata.js +40 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.d.ts +31 -0
- package/campaign-spec/dist/rules/checkout-has-success-url.js +66 -0
- package/campaign-spec/dist/rules/cycle-detection.d.ts +12 -0
- package/campaign-spec/dist/rules/cycle-detection.js +141 -0
- package/campaign-spec/dist/rules/design-source-shape.d.ts +29 -0
- package/campaign-spec/dist/rules/design-source-shape.js +142 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.d.ts +11 -0
- package/campaign-spec/dist/rules/downsell-without-upsell.js +40 -0
- package/campaign-spec/dist/rules/exit-intent-validation.d.ts +23 -0
- package/campaign-spec/dist/rules/exit-intent-validation.js +147 -0
- package/campaign-spec/dist/rules/funnel-count.d.ts +9 -0
- package/campaign-spec/dist/rules/funnel-count.js +37 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.d.ts +22 -0
- package/campaign-spec/dist/rules/funnel-hypothesis-length.js +60 -0
- package/campaign-spec/dist/rules/funnel-identity.d.ts +15 -0
- package/campaign-spec/dist/rules/funnel-identity.js +57 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.d.ts +20 -0
- package/campaign-spec/dist/rules/funnel-weight-sum.js +66 -0
- package/campaign-spec/dist/rules/index.d.ts +69 -0
- package/campaign-spec/dist/rules/index.js +131 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.d.ts +13 -0
- package/campaign-spec/dist/rules/offer-ref-integrity.js +59 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.d.ts +12 -0
- package/campaign-spec/dist/rules/package-pricing-sanity.js +42 -0
- package/campaign-spec/dist/rules/page-count.d.ts +11 -0
- package/campaign-spec/dist/rules/page-count.js +31 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.d.ts +14 -0
- package/campaign-spec/dist/rules/page-id-uniqueness.js +47 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.d.ts +8 -0
- package/campaign-spec/dist/rules/promo-code-input-validation.js +126 -0
- package/campaign-spec/dist/rules/promo-codes-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/promo-codes-shape.js +187 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.d.ts +33 -0
- package/campaign-spec/dist/rules/route-field-ignored-for-page-type.js +81 -0
- package/campaign-spec/dist/rules/route-target-resolves.d.ts +32 -0
- package/campaign-spec/dist/rules/route-target-resolves.js +112 -0
- package/campaign-spec/dist/rules/schema-version.d.ts +21 -0
- package/campaign-spec/dist/rules/schema-version.js +53 -0
- package/campaign-spec/dist/rules/sdk-version.d.ts +26 -0
- package/campaign-spec/dist/rules/sdk-version.js +96 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-countries-shape.js +30 -0
- package/campaign-spec/dist/rules/shipping-methods-present.d.ts +10 -0
- package/campaign-spec/dist/rules/shipping-methods-present.js +26 -0
- package/campaign-spec/dist/rules/store-profile-shape.d.ts +30 -0
- package/campaign-spec/dist/rules/store-profile-shape.js +127 -0
- package/campaign-spec/dist/rules/thank-you-requirement.d.ts +16 -0
- package/campaign-spec/dist/rules/thank-you-requirement.js +50 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.d.ts +28 -0
- package/campaign-spec/dist/rules/unknown-top-level-fields.js +114 -0
- package/campaign-spec/dist/rules/upsell-has-packages.d.ts +9 -0
- package/campaign-spec/dist/rules/upsell-has-packages.js +35 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.d.ts +10 -0
- package/campaign-spec/dist/rules/upsell-routing-complete.js +45 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.d.ts +11 -0
- package/campaign-spec/dist/rules/upsell-without-checkout.js +44 -0
- package/campaign-spec/dist/rules/variant-labels-shape.d.ts +28 -0
- package/campaign-spec/dist/rules/variant-labels-shape.js +86 -0
- package/campaign-spec/dist/sdk-version-parse.d.ts +43 -0
- package/campaign-spec/dist/sdk-version-parse.js +62 -0
- package/campaign-spec/dist/types.d.ts +674 -0
- package/campaign-spec/dist/types.js +40 -0
- package/campaign-spec/package.json +12 -0
- package/compatibility.json +25 -0
- package/contracts/agent-relevant-change-policy.v1.json +111 -0
- package/contracts/brand-theme-source-defaults.figma-sections-export.v0.json +32 -0
- package/contracts/brand-theme-target-tokens.next-core.v0.json +65 -0
- package/contracts/campaign-cart-checkout-field-contract.v0.json +45 -0
- package/contracts/campaign-cart-sdk-support-policy.v0.json +11 -0
- package/contracts/commerce-surface-catalog.json +2452 -0
- package/contracts/fixtures/orientation/canonicalization/v1.json +34 -0
- package/contracts/fixtures/orientation/envelope/current.json +95 -0
- package/contracts/fixtures/orientation/envelope/freshness_unknown.json +97 -0
- package/contracts/fixtures/orientation/envelope/legacy_baseline.json +95 -0
- package/contracts/fixtures/orientation/envelope/orientation_available.json +136 -0
- package/contracts/fixtures/orientation/envelope/recovered_interrupted_update.json +136 -0
- package/contracts/fixtures/orientation/envelope/refused.json +100 -0
- package/contracts/fixtures/orientation/envelope/restart_required.json +137 -0
- package/contracts/fixtures/orientation/envelope/updated.json +135 -0
- package/contracts/fixtures/orientation/hostile-target/README.md +61 -0
- package/contracts/fixtures/orientation/hostile-target/manifest.json +82 -0
- package/contracts/fixtures/orientation/hostile-target/repo/CHANGELOG.md +18 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/intended.mjs +12 -0
- package/contracts/fixtures/orientation/hostile-target/repo/bin/tripwire.mjs +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/release-ledger.json +48 -0
- package/contracts/fixtures/orientation/hostile-target/repo/contracts/supported-surface.json +17 -0
- package/contracts/fixtures/orientation/hostile-target/repo/docs/example-contract.md +13 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-checkout +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/post-merge +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hooks/pre-commit +5 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/package.json +15 -0
- package/contracts/fixtures/orientation/hostile-target/repo/hostile-dependency-tripwire/tripwire.mjs +9 -0
- package/contracts/fixtures/orientation/hostile-target/repo/package.json +20 -0
- package/contracts/fixtures/orientation/hostile-target/repo/schemas/example.v0.schema.json +15 -0
- package/contracts/fixtures/orientation/release-gate/cases.json +1073 -0
- package/contracts/fixtures/runtime-recipe/accept/current.json +299 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +294 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +51 -0
- package/contracts/fixtures/runtime-recipe/manifest.json +85 -0
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +297 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/missing-required-field.json +251 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +310 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +299 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +299 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/assembly-report.json +180 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/build-context.json +115 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/doctor-output.json +29 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/.campaign-runtime/qa-verdict.json +27 -0
- package/contracts/fixtures/sidecar-bundle/production-shaped/campaign-runtime.build.json +141 -0
- package/contracts/migration-sidecar-bundle.v0.json +149 -0
- package/contracts/orientation-limits.v1.json +41 -0
- package/contracts/orientation-reason-codes.v1.json +196 -0
- package/contracts/private-template-sources.json +8 -0
- package/contracts/release-ledger.json +4598 -0
- package/contracts/reserved-skill-names.json +13 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +299 -0
- package/contracts/supported-surface.json +180 -0
- package/contracts/template-brand-contract.apollo-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.apollo.v0.json +27 -0
- package/contracts/template-brand-contract.demeter.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.olympus-mv-two-step.v0.json +28 -0
- package/contracts/template-brand-contract.olympus.v0.json +27 -0
- package/contracts/template-brand-contract.shared-commerce.v0.json +190 -0
- package/contracts/template-brand-contract.shop-single-step.v0.json +27 -0
- package/contracts/template-brand-contract.shop-three-step.v0.json +29 -0
- package/contracts/template-slot-manifest.apollo-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.apollo.v0.json +12 -0
- package/contracts/template-slot-manifest.demeter.v0.json +30 -0
- package/contracts/template-slot-manifest.olympus-mv-single-step.v0.json +14 -0
- package/contracts/template-slot-manifest.olympus-mv-two-step.v0.json +15 -0
- package/contracts/template-slot-manifest.olympus.v0.json +12 -0
- package/contracts/template-slot-manifest.shared-content-core.v0.json +4254 -0
- package/contracts/template-slot-manifest.shop-single-step.v0.json +47 -0
- package/contracts/template-slot-manifest.shop-three-step.v0.json +33 -0
- package/docs/brand-theme-bridge.md +159 -0
- package/docs/build-packet.md +1300 -0
- package/docs/campaign-build-brief.md +145 -0
- package/docs/campaign-standardization-report.md +329 -0
- package/docs/campaigns-os-build-flow.md +117 -0
- package/docs/design-source-package.md +784 -0
- package/docs/legacy-migration.md +58 -0
- package/docs/migration-sidecar-bundle.md +139 -0
- package/docs/orientation-contract-reference.md +1220 -0
- package/docs/polish-evidence.md +502 -0
- package/docs/qa-and-test-orders.md +1691 -0
- package/docs/release-ledger-authoring-guide.md +274 -0
- package/docs/runtime-readiness.md +211 -0
- package/docs/supported-surface.md +83 -0
- package/docs/versioning.md +55 -0
- package/docs/workflow-findings-sidecar.md +588 -0
- package/package.json +135 -0
- package/prompts/first-build.md +27 -0
- package/prompts/friction-log.md +30 -0
- package/schemas/campaign-build-brief.v1.schema.json +149 -0
- package/schemas/campaign-design-source-package.v0.schema.json +697 -0
- package/schemas/campaign-runtime-assembly-report.v0.schema.json +390 -0
- package/schemas/campaign-runtime-build-context.v0.schema.json +339 -0
- package/schemas/campaign-runtime-build-packet.v0.schema.json +452 -0
- package/schemas/campaign-spec.v4.schema.json +582 -0
- package/schemas/campaigns-os-doctor-output.v0.schema.json +43 -0
- package/schemas/campaigns-os-legacy-migration-inventory.v0.schema.json +112 -0
- package/schemas/campaigns-os-legacy-provisioning-plan.v0.schema.json +38 -0
- package/schemas/campaigns-os-legacy-provisioning-receipt.v0.schema.json +57 -0
- package/schemas/campaigns-os-qa-verdict-sidecar.v0.schema.json +150 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +438 -0
- package/schemas/campaigns-os-release-ledger.v1.schema.json +162 -0
- package/schemas/campaigns-os-run-record.v0.schema.json +343 -0
- package/schemas/campaigns-os-runtime-recipe.v1.schema.json +313 -0
- package/schemas/campaigns-os-sidecar-bundle-conformance.v0.schema.json +78 -0
- package/schemas/campaigns-os-tooling-orientation.v1.schema.json +381 -0
- package/schemas/campaigns-os-workflow-finding.v0.schema.json +134 -0
- package/schemas/source-html-manifest.v0.schema.json +252 -0
- package/skills/next-campaigns-build/SKILL.md +73 -0
- package/skills/next-campaigns-os/SKILL.md +101 -0
- package/skills/next-campaigns-os/references/session-intake.md +160 -0
- package/skills/next-campaigns-os-setup/SKILL.md +22 -0
- package/skills/next-campaigns-polish/SKILL.md +145 -0
- package/skills/next-campaigns-qa/SKILL.md +92 -0
- package/skills.json +56 -0
- package/skills.sh +64 -0
- package/src/adapter-decision-contract.mjs +333 -0
- package/src/brand-theme.mjs +1151 -0
- package/src/browser-launch.mjs +79 -0
- package/src/build-brief.mjs +781 -0
- package/src/built-site-scope.mjs +312 -0
- package/src/campaign-ecosystem.mjs +734 -0
- package/src/campaign-identity.mjs +405 -0
- package/src/campaign-workspace.mjs +127 -0
- package/src/checkpoint-waiver.mjs +302 -0
- package/src/cli.mjs +13442 -0
- package/src/commercial-journey.mjs +1119 -0
- package/src/commercial-parity.mjs +965 -0
- package/src/consent.mjs +347 -0
- package/src/content-residue.mjs +322 -0
- package/src/deadline.mjs +81 -0
- package/src/design-source-package.mjs +2604 -0
- package/src/deviation.mjs +107 -0
- package/src/doctor-check-registry.mjs +49 -0
- package/src/doctor-sidecar.mjs +106 -0
- package/src/finding-cause.mjs +557 -0
- package/src/findings.mjs +326 -0
- package/src/fs-identity.mjs +68 -0
- package/src/gate-actions.mjs +105 -0
- package/src/html-scan.mjs +53 -0
- package/src/install-mode.mjs +272 -0
- package/src/legacy-migration.d.ts +128 -0
- package/src/legacy-migration.mjs +510 -0
- package/src/lifecycle.mjs +338 -0
- package/src/local-proof.mjs +401 -0
- package/src/map-pin-writeback.mjs +210 -0
- package/src/orchestration-stage-contract.mjs +81 -0
- package/src/package-install-fixture.mjs +33 -0
- package/src/page-kit-build-summary.mjs +175 -0
- package/src/page-kit-campaign-config.mjs +57 -0
- package/src/page-kit-sdk-version.mjs +392 -0
- package/src/page-kit-store-profile.mjs +369 -0
- package/src/page-kit-sync.mjs +162 -0
- package/src/polish-browser.mjs +867 -0
- package/src/polish-capture.mjs +1094 -0
- package/src/polish-deadline.mjs +51 -0
- package/src/polish-gate.mjs +739 -0
- package/src/polish-node.mjs +639 -0
- package/src/polish-page-load.mjs +1100 -0
- package/src/private-template-source.mjs +237 -0
- package/src/proof-policy.mjs +82 -0
- package/src/qa-analytics-correctness.mjs +307 -0
- package/src/qa-analytics-errors.mjs +38 -0
- package/src/qa-analytics-parity.mjs +699 -0
- package/src/qa-binding-evidence.mjs +140 -0
- package/src/qa-browser.mjs +6608 -0
- package/src/qa-cart-entry.mjs +406 -0
- package/src/qa-commercial-parity.mjs +641 -0
- package/src/qa-node.mjs +3620 -0
- package/src/qa-order-bump.mjs +381 -0
- package/src/qa-parity-capture.mjs +428 -0
- package/src/qa-parity-fixture.mjs +359 -0
- package/src/qa-publish.mjs +362 -0
- package/src/qa-purchase-data-layer.mjs +263 -0
- package/src/qa-route-probe.mjs +272 -0
- package/src/qa-sidecar.mjs +188 -0
- package/src/qa-test-order-topology.mjs +207 -0
- package/src/qa-url-privacy.mjs +13 -0
- package/src/qa-verdict-discovery.mjs +192 -0
- package/src/qa-verdict-publish.mjs +105 -0
- package/src/qa-verdict.mjs +287 -0
- package/src/remit.mjs +388 -0
- package/src/repo-scan.mjs +83 -0
- package/src/route-identity.mjs +133 -0
- package/src/run-record-closeout.mjs +229 -0
- package/src/run-record.mjs +839 -0
- package/src/run-session.mjs +226 -0
- package/src/runtime-state-ignore.mjs +113 -0
- package/src/sdk-attribute-index.mjs +212 -0
- package/src/sdk-markup.mjs +358 -0
- package/src/sdk-meta-tags.mjs +52 -0
- package/src/shell-token.mjs +7 -0
- package/src/sidecar-bundle.mjs +399 -0
- package/src/source-asset-crawl.mjs +469 -0
- package/src/source-html-intake.mjs +627 -0
- package/src/source-html-manifest.mjs +276 -0
- package/src/source-prep.mjs +284 -0
- package/src/spec-derive-store.mjs +431 -0
- package/src/spec-derive.mjs +514 -0
- package/src/spec-fetch.mjs +66 -0
- package/src/spec-hash.mjs +48 -0
- package/src/spec-identity.mjs +27 -0
- package/src/stage-ledger.mjs +509 -0
- package/src/standardization-report.mjs +1297 -0
- package/src/template-brand-contract.mjs +473 -0
- package/src/template-freshness.mjs +196 -0
- package/src/template-reference.mjs +81 -0
- package/src/template-slot-manifest.mjs +150 -0
- package/src/text-safety.mjs +62 -0
- package/src/theme-gate.mjs +185 -0
- package/src/upsell-selector-scope.mjs +299 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const FINGERPRINT_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
4
|
+
const PLACEHOLDER_HUMANS = new Set([
|
|
5
|
+
"agent",
|
|
6
|
+
"anonymous",
|
|
7
|
+
"automation",
|
|
8
|
+
"bot",
|
|
9
|
+
"campaigns-os",
|
|
10
|
+
"ci",
|
|
11
|
+
"cli flag",
|
|
12
|
+
"cli_flag",
|
|
13
|
+
"github actions",
|
|
14
|
+
"github-actions",
|
|
15
|
+
"github_actions",
|
|
16
|
+
"ai",
|
|
17
|
+
"none",
|
|
18
|
+
"n/a",
|
|
19
|
+
"na",
|
|
20
|
+
"operator",
|
|
21
|
+
"system",
|
|
22
|
+
"unknown",
|
|
23
|
+
]);
|
|
24
|
+
const AUTOMATION_IDENTITY_TOKENS = new Set([
|
|
25
|
+
"actions",
|
|
26
|
+
"agent",
|
|
27
|
+
"ai",
|
|
28
|
+
"automation",
|
|
29
|
+
"bot",
|
|
30
|
+
"ci",
|
|
31
|
+
"codex",
|
|
32
|
+
"daemon",
|
|
33
|
+
"github",
|
|
34
|
+
"operator",
|
|
35
|
+
"runner",
|
|
36
|
+
"service",
|
|
37
|
+
"system",
|
|
38
|
+
"workflow",
|
|
39
|
+
]);
|
|
40
|
+
const AUTOMATION_COMPACT_ALIASES = new Set([
|
|
41
|
+
"automation",
|
|
42
|
+
"bot",
|
|
43
|
+
"ai",
|
|
44
|
+
"ci",
|
|
45
|
+
"githubactions",
|
|
46
|
+
"campaignsos",
|
|
47
|
+
"claudecode",
|
|
48
|
+
"cliflag",
|
|
49
|
+
"codexagent",
|
|
50
|
+
"automationrunner",
|
|
51
|
+
"systemoperator",
|
|
52
|
+
"githubactionsrunner",
|
|
53
|
+
"servicedaemon",
|
|
54
|
+
"workflowrunner",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
function isPlainObject(value) {
|
|
58
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function canonicalize(value) {
|
|
62
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
63
|
+
if (isPlainObject(value)) {
|
|
64
|
+
return Object.fromEntries(
|
|
65
|
+
Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function canonicalJson(value) {
|
|
72
|
+
return JSON.stringify(canonicalize(value));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isValidTimestamp(value) {
|
|
76
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) return false;
|
|
77
|
+
const parsed = new Date(value);
|
|
78
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function checkpointStateFingerprint({ scope, subject, state }) {
|
|
82
|
+
const payload = canonicalJson({ scope, subject, state });
|
|
83
|
+
return `sha256:${createHash("sha256").update(payload).digest("hex")}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The one attribution rule every waiver lane shares: a named human (no
|
|
87
|
+
// placeholder or automation identity), a non-empty reason, canonical
|
|
88
|
+
// timestamps, and an expiry that lies after the waive instant. Checkpoint
|
|
89
|
+
// waivers additionally require a bound (expiry or review condition); the theme
|
|
90
|
+
// lane records an expiry when one is given but does not demand one. `label`
|
|
91
|
+
// prefixes every message so each command names itself in its own refusal.
|
|
92
|
+
export function validateWaiverAttribution({
|
|
93
|
+
reason,
|
|
94
|
+
waivedBy,
|
|
95
|
+
now = new Date().toISOString(),
|
|
96
|
+
expiresAt = null,
|
|
97
|
+
reviewCondition = null,
|
|
98
|
+
requireBound = true,
|
|
99
|
+
label = "Checkpoint waiver",
|
|
100
|
+
} = {}) {
|
|
101
|
+
if (typeof reason !== "string" || !reason.trim()) {
|
|
102
|
+
throw new Error(`${label} requires a non-empty reason.`);
|
|
103
|
+
}
|
|
104
|
+
if (!isNamedHuman(waivedBy)) {
|
|
105
|
+
throw new Error(`${label} requires --waived-by with the named human who approved it; placeholders are not accepted.`);
|
|
106
|
+
}
|
|
107
|
+
if (!isValidTimestamp(now)) throw new Error(`${label} requires a valid waived_at timestamp.`);
|
|
108
|
+
if (expiresAt != null && !isValidTimestamp(expiresAt)) {
|
|
109
|
+
throw new Error(`${label} expires_at must be a valid timestamp when provided.`);
|
|
110
|
+
}
|
|
111
|
+
if (reviewCondition != null && (typeof reviewCondition !== "string" || reviewCondition.trim() === "")) {
|
|
112
|
+
throw new Error(`${label} review_condition must be non-empty when provided.`);
|
|
113
|
+
}
|
|
114
|
+
if (requireBound && expiresAt == null && reviewCondition == null) {
|
|
115
|
+
throw new Error(`${label} requires at least one of expires_at or review_condition.`);
|
|
116
|
+
}
|
|
117
|
+
if (expiresAt != null && Date.parse(expiresAt) <= Date.parse(now)) {
|
|
118
|
+
throw new Error(`${label} expires_at must be later than waived_at.`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
reason: reason.trim(),
|
|
122
|
+
waived_by: waivedBy.trim().replace(/\s+/g, " "),
|
|
123
|
+
waived_at: new Date(now).toISOString(),
|
|
124
|
+
...(expiresAt == null ? {} : { expires_at: new Date(expiresAt).toISOString() }),
|
|
125
|
+
...(reviewCondition == null ? {} : { review_condition: reviewCondition.trim() }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isNamedHuman(value) {
|
|
130
|
+
if (typeof value !== "string") return false;
|
|
131
|
+
const normalized = value.trim().replace(/\s+/g, " ");
|
|
132
|
+
const letters = normalized.match(/\p{L}/gu) || [];
|
|
133
|
+
if (letters.length < 2) return false;
|
|
134
|
+
const lower = normalized.toLocaleLowerCase("en-US");
|
|
135
|
+
const compact = lower.replace(/[^\p{L}\p{N}]+/gu, "");
|
|
136
|
+
const tokens = lower.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
137
|
+
if (PLACEHOLDER_HUMANS.has(lower)) return false;
|
|
138
|
+
if (AUTOMATION_COMPACT_ALIASES.has(compact)) return false;
|
|
139
|
+
if (tokens.some((token) => AUTOMATION_IDENTITY_TOKENS.has(token))) return false;
|
|
140
|
+
// Claude is also a real one-word human name; reject the obvious product
|
|
141
|
+
// identity without outlawing people named Claude.
|
|
142
|
+
return !(tokens.includes("claude") && tokens.includes("code"));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function validWaiverRecord(record) {
|
|
146
|
+
if (!(isPlainObject(record)
|
|
147
|
+
&& typeof record.scope === "string"
|
|
148
|
+
&& record.scope.trim() !== ""
|
|
149
|
+
&& isPlainObject(record.subject)
|
|
150
|
+
&& FINGERPRINT_PATTERN.test(record.state_fingerprint || "")
|
|
151
|
+
&& typeof record.reason === "string"
|
|
152
|
+
&& record.reason.trim() !== ""
|
|
153
|
+
&& isNamedHuman(record.waived_by)
|
|
154
|
+
&& isValidTimestamp(record.waived_at))) return false;
|
|
155
|
+
|
|
156
|
+
const hasExpiry = record.expires_at != null;
|
|
157
|
+
const hasReviewCondition = record.review_condition != null;
|
|
158
|
+
if (!hasExpiry && !hasReviewCondition) return false;
|
|
159
|
+
if (hasExpiry && (!isValidTimestamp(record.expires_at) || Date.parse(record.expires_at) <= Date.parse(record.waived_at))) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (hasReviewCondition && (typeof record.review_condition !== "string" || record.review_condition.trim() === "")) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function assessCheckpointWaivers(waivers, checkpoint, { now = new Date().toISOString() } = {}) {
|
|
169
|
+
const assessment = {
|
|
170
|
+
active: null,
|
|
171
|
+
stale: [],
|
|
172
|
+
foreign: [],
|
|
173
|
+
malformed: [],
|
|
174
|
+
expired: [],
|
|
175
|
+
};
|
|
176
|
+
const records = Array.isArray(waivers) ? waivers : [];
|
|
177
|
+
const checkpointSubject = canonicalJson(checkpoint?.subject || {});
|
|
178
|
+
const nowMs = Date.parse(now);
|
|
179
|
+
let activeTime = Number.NEGATIVE_INFINITY;
|
|
180
|
+
|
|
181
|
+
for (const record of records) {
|
|
182
|
+
// Top-level Assembly Report waivers[] is shared by every checkpoint.
|
|
183
|
+
// Other scopes are not foreign/malformed Store Profile history; they are
|
|
184
|
+
// outside this evaluator entirely and must not create noise here.
|
|
185
|
+
if (!isPlainObject(record) || record.scope !== checkpoint?.scope) continue;
|
|
186
|
+
if (!validWaiverRecord(record)) {
|
|
187
|
+
assessment.malformed.push(record);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (canonicalJson(record.subject) !== checkpointSubject) {
|
|
191
|
+
assessment.foreign.push(record);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (record.state_fingerprint !== checkpoint?.state_fingerprint) {
|
|
195
|
+
assessment.stale.push(record);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (record.expires_at != null && Date.parse(record.expires_at) <= nowMs) {
|
|
199
|
+
assessment.expired.push(record);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const waivedAt = Date.parse(record.waived_at);
|
|
203
|
+
if (waivedAt >= activeTime) {
|
|
204
|
+
assessment.active = record;
|
|
205
|
+
activeTime = waivedAt;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return assessment;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Gate/readback/verdict artifacts must never echo arbitrary fields from an
|
|
212
|
+
// Assembly Report waiver record. This projection is checkpoint-generic so each
|
|
213
|
+
// registered gate can publish the same bounded attribution contract while the
|
|
214
|
+
// raw history remains private to evaluation.
|
|
215
|
+
export function projectCheckpointWaiver(record, checkpoint) {
|
|
216
|
+
if (!validWaiverRecord(record)
|
|
217
|
+
|| !isPlainObject(checkpoint)
|
|
218
|
+
|| typeof checkpoint.scope !== "string"
|
|
219
|
+
|| !isPlainObject(checkpoint.subject)
|
|
220
|
+
|| !FINGERPRINT_PATTERN.test(checkpoint.state_fingerprint || "")) return null;
|
|
221
|
+
return {
|
|
222
|
+
scope: checkpoint.scope,
|
|
223
|
+
subject: canonicalize(checkpoint.subject),
|
|
224
|
+
state_fingerprint: checkpoint.state_fingerprint,
|
|
225
|
+
reason: record.reason.trim(),
|
|
226
|
+
waived_by: record.waived_by.trim().replace(/\s+/g, " "),
|
|
227
|
+
waived_at: record.waived_at,
|
|
228
|
+
...(record.expires_at == null ? {} : { expires_at: record.expires_at }),
|
|
229
|
+
...(record.review_condition == null ? {} : { review_condition: record.review_condition.trim() }),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function projectCheckpointWaiverAssessment(assessment, checkpoint) {
|
|
234
|
+
return {
|
|
235
|
+
active: projectCheckpointWaiver(assessment?.active, checkpoint),
|
|
236
|
+
inert_counts: {
|
|
237
|
+
stale: Array.isArray(assessment?.stale) ? assessment.stale.length : 0,
|
|
238
|
+
foreign: Array.isArray(assessment?.foreign) ? assessment.foreign.length : 0,
|
|
239
|
+
malformed: Array.isArray(assessment?.malformed) ? assessment.malformed.length : 0,
|
|
240
|
+
expired: Array.isArray(assessment?.expired) ? assessment.expired.length : 0,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function createCheckpointWaiver(checkpoint, {
|
|
246
|
+
reason,
|
|
247
|
+
waivedBy,
|
|
248
|
+
now = new Date().toISOString(),
|
|
249
|
+
expiresAt = null,
|
|
250
|
+
reviewCondition = null,
|
|
251
|
+
} = {}) {
|
|
252
|
+
if (typeof checkpoint?.scope !== "string" || !checkpoint.scope.trim()) {
|
|
253
|
+
throw new Error("Checkpoint waiver needs a non-empty scope.");
|
|
254
|
+
}
|
|
255
|
+
if (!isPlainObject(checkpoint?.subject)) {
|
|
256
|
+
throw new Error("Checkpoint waiver needs a subject object.");
|
|
257
|
+
}
|
|
258
|
+
if (!FINGERPRINT_PATTERN.test(checkpoint?.state_fingerprint || "")) {
|
|
259
|
+
throw new Error("Checkpoint waiver needs a current state fingerprint.");
|
|
260
|
+
}
|
|
261
|
+
const attribution = validateWaiverAttribution({ reason, waivedBy, now, expiresAt, reviewCondition });
|
|
262
|
+
return {
|
|
263
|
+
scope: checkpoint.scope,
|
|
264
|
+
subject: canonicalize(checkpoint.subject),
|
|
265
|
+
state_fingerprint: checkpoint.state_fingerprint,
|
|
266
|
+
...attribution,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function appendCheckpointWaiver(report, waiver) {
|
|
271
|
+
const base = isPlainObject(report) ? report : {};
|
|
272
|
+
return {
|
|
273
|
+
...base,
|
|
274
|
+
waivers: [...(Array.isArray(base.waivers) ? base.waivers : []), waiver],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function createCheckpointRegistry(entries) {
|
|
279
|
+
if (!Array.isArray(entries)) throw new Error("Checkpoint registry entries must be an array.");
|
|
280
|
+
const registry = {};
|
|
281
|
+
for (const [index, entry] of entries.entries()) {
|
|
282
|
+
if (!isPlainObject(entry) || typeof entry.id !== "string" || !entry.id.trim()) {
|
|
283
|
+
throw new Error(`Checkpoint registry entry ${index} needs a non-empty id.`);
|
|
284
|
+
}
|
|
285
|
+
if (typeof entry.evaluate !== "function") {
|
|
286
|
+
throw new Error(`Checkpoint registry entry "${entry.id.trim()}" needs an evaluate function.`);
|
|
287
|
+
}
|
|
288
|
+
const id = entry.id.trim();
|
|
289
|
+
if (Object.hasOwn(registry, id)) throw new Error(`Checkpoint registry has duplicate id "${id}".`);
|
|
290
|
+
registry[id] = Object.freeze({ id, evaluate: entry.evaluate });
|
|
291
|
+
}
|
|
292
|
+
return Object.freeze(registry);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function evaluateCheckpointRegistry(registry, id, context) {
|
|
296
|
+
const entry = registry?.[id];
|
|
297
|
+
if (!entry) {
|
|
298
|
+
const registered = Object.keys(registry || {});
|
|
299
|
+
throw new Error(`Unknown checkpoint gate "${id}"; registered gates: ${registered.length ? registered.join(", ") : "(none)"}.`);
|
|
300
|
+
}
|
|
301
|
+
return entry.evaluate(context);
|
|
302
|
+
}
|