@nextcommerce/campaigns-os 1.50.0 → 1.52.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +426 -0
  2. package/agents/claude/CLAUDE.md +2 -2
  3. package/agents/codex/AGENTS.md +1 -1
  4. package/agents/copilot/copilot-instructions.md +1 -1
  5. package/agents/cursor/campaigns-os.mdc +1 -1
  6. package/campaign-spec/dist/rules/campaign-metadata.d.ts +5 -1
  7. package/campaign-spec/dist/rules/campaign-metadata.js +9 -2
  8. package/campaign-spec/dist/rules/design-source-shape.js +13 -3
  9. package/campaign-spec/dist/rules/sdk-version.js +2 -1
  10. package/compatibility.json +1 -1
  11. package/contracts/commerce-surface-catalog.json +26 -46
  12. package/contracts/effects.v1.json +81 -2
  13. package/contracts/release-ledger.json +906 -0
  14. package/contracts/supported-surface.json +2 -2
  15. package/contracts/template-brand-contract.shared-commerce.v0.json +2 -2
  16. package/contracts/template-slot-manifest.shared-content-core.v0.json +24 -0
  17. package/docs/build-packet.md +93 -9
  18. package/docs/campaign-build-brief.md +25 -1
  19. package/docs/effects.md +6 -0
  20. package/docs/local-setup.md +1 -1
  21. package/docs/orientation-contract-reference.md +1 -1
  22. package/docs/polish-evidence.md +10 -0
  23. package/docs/qa-and-test-orders.md +45 -4
  24. package/docs/runtime-readiness.md +1 -1
  25. package/docs/sdk-storage-compatibility.md +1 -1
  26. package/docs/skills-revision.md +10 -10
  27. package/package.json +1 -1
  28. package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
  29. package/skills/campaign-readback-classification/SKILL.md +3 -3
  30. package/skills/campaign-run-evidence/SKILL.md +3 -3
  31. package/skills/contribution-intake/SKILL.md +3 -3
  32. package/skills/next-campaigns-build/SKILL.md +3 -3
  33. package/skills/next-campaigns-os/SKILL.md +4 -4
  34. package/skills/next-campaigns-os/references/session-intake.md +7 -3
  35. package/skills/next-campaigns-os-setup/SKILL.md +3 -3
  36. package/skills/next-campaigns-polish/SKILL.md +3 -3
  37. package/skills/next-campaigns-qa/SKILL.md +6 -5
  38. package/skills.json +10 -10
  39. package/src/adapter-decision-contract.mjs +1 -1
  40. package/src/brand-theme.mjs +12 -0
  41. package/src/build-brief.mjs +68 -21
  42. package/src/built-site-scope.mjs +39 -6
  43. package/src/built-smoke-qc.mjs +1117 -0
  44. package/src/campaign-identity.mjs +36 -2
  45. package/src/cart-placeholders.mjs +730 -0
  46. package/src/cli.mjs +310 -34
  47. package/src/commercial-journey.mjs +65 -4
  48. package/src/commercial-parity.mjs +6 -1
  49. package/src/doctor/checks.mjs +291 -24
  50. package/src/doctor/inspect.mjs +53 -2
  51. package/src/doctor/next-step.mjs +1 -1
  52. package/src/invocation.mjs +2 -1
  53. package/src/local-preview-policy.mjs +1 -1
  54. package/src/local-proof.mjs +4 -1
  55. package/src/polish-browser.mjs +218 -1
  56. package/src/polish-capture.mjs +1 -1
  57. package/src/polish-media-weight.mjs +492 -0
  58. package/src/polish-node.mjs +96 -4
  59. package/src/progress-node.mjs +5 -1
  60. package/src/qa-browser.mjs +308 -96
  61. package/src/qa-content-params.mjs +889 -0
  62. package/src/qa-node.mjs +104 -12
  63. package/src/qa-order-bump.mjs +22 -1
  64. package/src/qa-policy-links.mjs +1019 -0
  65. package/src/qa-tracking-params.mjs +1389 -0
  66. package/src/qa-url-privacy.mjs +168 -0
  67. package/src/qc-accept.mjs +446 -0
  68. package/src/qc-check-registry.mjs +83 -0
  69. package/src/qc-results.mjs +1049 -0
  70. package/src/sdk-attribute-index.mjs +71 -0
  71. package/src/sdk-markup.mjs +2 -2
  72. package/src/sdk-storage-compatibility.mjs +63 -3
  73. package/src/source-prep.mjs +37 -7
  74. package/src/stage-record.mjs +56 -17
@@ -0,0 +1,83 @@
1
+ // The one map from a QC check id to the check module that re-derives its
2
+ // results. The QC readers
3
+ // (src/qc-results.mjs) reach every check's rules through here, so a check
4
+ // lands by adding its module, never by editing the readers.
5
+ //
6
+ // Each entry names a module specifier, imported lazily and relative to this
7
+ // file, and the export the reader calls:
8
+ // - QA checks: `rederiveQcResult(observation)` returns a Derived result
9
+ // ({check, subject, result, reason_code, members, accept_eligible, coverage,
10
+ // state}) or null when the observation cannot be re-derived.
11
+ // - Polish checks: `MEDIA_WEIGHT_QC_RULES` is {thresholds, vocabulary,
12
+ // evaluate(cell, thresholds) => Derived[]}.
13
+ // - Doctor checks recompute from the built HTML on every read and are called
14
+ // by doctor itself; their entries name the evaluator for completeness.
15
+ //
16
+ // The table holds only the shipped QC checks. A stand-in check never comes
17
+ // from here: tests pass theirs in-process (qcStandIns).
18
+ export const QC_CHECK_REGISTRY = Object.freeze({
19
+ // Tracking params reach the order
20
+ "tracking.url": Object.freeze({ leg: "qa", unit: "1.1", module: "./qa-tracking-params.mjs", rederive: "rederiveQcResult" }),
21
+ "tracking.order": Object.freeze({ leg: "qa", unit: "1.1", module: "./qa-tracking-params.mjs", rederive: "rederiveQcResult" }),
22
+ "tracking.tag": Object.freeze({ leg: "qa", unit: "1.1", module: "./qa-tracking-params.mjs", rederive: "rederiveQcResult" }),
23
+ // Content params
24
+ content_param: Object.freeze({ leg: "qa", unit: "1.2", module: "./qa-content-params.mjs", rederive: "rederiveQcResult" }),
25
+ // Media weight and oversizing
26
+ "media.weight": Object.freeze({ leg: "polish", unit: "1.3", module: "./polish-media-weight.mjs", rederive: "MEDIA_WEIGHT_QC_RULES" }),
27
+ "media.oversize": Object.freeze({ leg: "polish", unit: "1.3", module: "./polish-media-weight.mjs", rederive: "MEDIA_WEIGHT_QC_RULES" }),
28
+ // Policy links
29
+ "policy.presence": Object.freeze({ leg: "qa", unit: "1.4", module: "./qa-policy-links.mjs", rederive: "rederiveQcResult" }),
30
+ "policy.availability": Object.freeze({ leg: "qa", unit: "1.4", module: "./qa-policy-links.mjs", rederive: "rederiveQcResult" }),
31
+ // Cart placeholders
32
+ cart_placeholders: Object.freeze({ leg: "doctor", unit: "1.5", module: "./cart-placeholders.mjs", rederive: "evaluateCartPlaceholders" }),
33
+ // Built-output smoke checks
34
+ smoke_qc: Object.freeze({ leg: "doctor", unit: "1.6", module: "./built-smoke-qc.mjs", rederive: "evaluateSmokeQc" }),
35
+ });
36
+
37
+ export const qcChecksForLeg = (leg) => Object.keys(QC_CHECK_REGISTRY).filter((check) => QC_CHECK_REGISTRY[check].leg === leg);
38
+
39
+ // A module "does not exist yet" only when Node reports ERR_MODULE_NOT_FOUND
40
+ // for that exact specifier. A missing transitive import names another URL,
41
+ // and a syntax error or a throw at load has no such code: those read as a
42
+ // failed load, which the readers turn into evidence_not_reproducible, never
43
+ // not_captured_by_this_version and never pass.
44
+ function isMissingModule(error, href) {
45
+ if (error?.code !== "ERR_MODULE_NOT_FOUND") return false;
46
+ if (typeof error.url === "string") return error.url === href;
47
+ const message = String(error.message || "");
48
+ return message.startsWith(`Cannot find module '${new URL(href).pathname}' imported from `);
49
+ }
50
+
51
+ let loaded = null;
52
+
53
+ // Loads every registered module of the given legs once per process and
54
+ // returns { "<check>": {status: "loaded", rederive} | {status: "missing"} |
55
+ // {status: "failed", error} }. `importer` resolves a specifier relative to
56
+ // this file; it exists so the missing/failed distinction can be exercised.
57
+ export async function loadQcRederivers({ legs = ["qa", "polish"], registry = QC_CHECK_REGISTRY, importer = (specifier) => import(specifier) } = {}) {
58
+ const useCache = registry === QC_CHECK_REGISTRY;
59
+ const cache = useCache && loaded ? loaded : {};
60
+ const modules = new Map();
61
+ for (const [check, entry] of Object.entries(registry)) {
62
+ if (!legs.includes(entry.leg) || Object.hasOwn(cache, check)) continue;
63
+ const href = new URL(entry.module, import.meta.url).href;
64
+ if (!modules.has(href)) {
65
+ modules.set(href, importer(entry.module).then(
66
+ (module) => ({ module }),
67
+ (error) => ({ error, missing: isMissingModule(error, href) }),
68
+ ));
69
+ }
70
+ const outcome = await modules.get(href);
71
+ if (outcome.missing) cache[check] = Object.freeze({ status: "missing" });
72
+ else if (outcome.error) cache[check] = Object.freeze({ status: "failed", error: String(outcome.error?.message || outcome.error) });
73
+ else if (outcome.module?.[entry.rederive] == null) cache[check] = Object.freeze({ status: "failed", error: `${entry.module} has no export ${entry.rederive}` });
74
+ else cache[check] = Object.freeze({ status: "loaded", rederive: outcome.module[entry.rederive] });
75
+ }
76
+ if (useCache) loaded = cache;
77
+ return Object.freeze({ ...cache });
78
+ }
79
+
80
+ // The rederivers loaded so far in this process, or null before the first
81
+ // load. Synchronous readers fall back to this; a check not loaded yet reads
82
+ // as not re-derivable (evidence_not_reproducible), never as pass.
83
+ export const loadedQcRederivers = () => (loaded ? Object.freeze({ ...loaded }) : null);