@nextcommerce/campaigns-os 1.43.2 → 1.47.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 (80) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +798 -5103
  3. package/README.md +33 -12
  4. package/agents/claude/CLAUDE.md +1 -1
  5. package/agents/codex/AGENTS.md +1 -1
  6. package/agents/copilot/copilot-instructions.md +1 -1
  7. package/agents/cursor/campaigns-os.mdc +1 -1
  8. package/campaign-spec/dist/rules/analytics-contract-shape.d.ts +2 -2
  9. package/campaign-spec/dist/rules/analytics-contract-shape.js +2 -2
  10. package/campaign-spec/dist/rules/store-profile-shape.d.ts +5 -1
  11. package/campaign-spec/dist/rules/store-profile-shape.js +8 -10
  12. package/campaign-spec/dist/types.d.ts +2 -2
  13. package/compatibility.json +1 -1
  14. package/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
  15. package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
  16. package/contracts/commerce-surface-catalog.json +1204 -129
  17. package/contracts/effects.v1.json +1179 -116
  18. package/contracts/orientation-reason-codes.v1.json +7 -0
  19. package/contracts/release-ledger.json +2515 -5919
  20. package/contracts/supported-surface.json +7 -4
  21. package/contracts/template-brand-contract.shared-commerce.v0.json +3 -3
  22. package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
  23. package/docs/brand-theme-bridge.md +81 -0
  24. package/docs/build-packet.md +180 -23
  25. package/docs/campaigns-os-build-flow.md +3 -3
  26. package/docs/design-source-package.md +73 -0
  27. package/docs/effects.md +50 -8
  28. package/docs/gateway-login.md +3 -0
  29. package/docs/local-setup.md +7 -4
  30. package/docs/orientation-contract-reference.md +42 -2
  31. package/docs/polish-evidence.md +74 -0
  32. package/docs/qa-and-test-orders.md +118 -14
  33. package/docs/release-ledger-authoring-guide.md +64 -4
  34. package/docs/runtime-readiness.md +1 -1
  35. package/docs/sdk-storage-compatibility.md +1 -1
  36. package/docs/skills-revision.md +10 -10
  37. package/docs/supported-surface.md +2 -2
  38. package/docs/versioning.md +4 -1
  39. package/package.json +1 -1
  40. package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
  41. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
  42. package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
  43. package/skills/campaign-readback-classification/SKILL.md +3 -3
  44. package/skills/campaign-run-evidence/SKILL.md +7 -6
  45. package/skills/contribution-intake/SKILL.md +3 -3
  46. package/skills/next-campaigns-build/SKILL.md +7 -6
  47. package/skills/next-campaigns-os/SKILL.md +7 -7
  48. package/skills/next-campaigns-os/references/session-intake.md +9 -3
  49. package/skills/next-campaigns-os-setup/SKILL.md +5 -5
  50. package/skills/next-campaigns-polish/SKILL.md +28 -9
  51. package/skills/next-campaigns-qa/SKILL.md +7 -4
  52. package/skills.json +10 -10
  53. package/src/brand-theme.mjs +320 -20
  54. package/src/built-script-syntax.mjs +116 -15
  55. package/src/built-site-scope.mjs +16 -4
  56. package/src/cli.mjs +280 -46
  57. package/src/commercial-parity.mjs +48 -2
  58. package/src/deviation.mjs +13 -1
  59. package/src/diagnostic.mjs +6 -2
  60. package/src/doctor/checks.mjs +319 -81
  61. package/src/doctor/inspect.mjs +55 -13
  62. package/src/doctor/source-provenance.mjs +184 -0
  63. package/src/invocation.mjs +4 -0
  64. package/src/live-campaign-refs.mjs +466 -0
  65. package/src/login.mjs +2 -2
  66. package/src/page-kit-store-profile.mjs +69 -12
  67. package/src/page-kit-sync.mjs +31 -12
  68. package/src/progress-node.mjs +3 -1
  69. package/src/qa-analytics-parity.mjs +37 -2
  70. package/src/qa-binding-evidence.mjs +4 -2
  71. package/src/qa-browser.mjs +612 -40
  72. package/src/qa-commercial-parity.mjs +48 -5
  73. package/src/qa-node.mjs +122 -7
  74. package/src/qa-test-order-topology.mjs +148 -0
  75. package/src/sdk-markup.mjs +32 -7
  76. package/src/sdk-storage-compatibility.mjs +3 -2
  77. package/src/source-html-intake.mjs +116 -0
  78. package/src/stage-record.mjs +551 -0
  79. package/src/tooling-setup.mjs +9 -0
  80. package/src/upsell-selector-scope.mjs +112 -2
@@ -652,8 +652,8 @@ function recurrenceEquivalent(claim, truth) {
652
652
  if (moneyToCents(claim.amount) !== moneyToCents(truth.amount)) return false;
653
653
  if (claim.interval_count === truth.interval_count && claim.interval === truth.interval) return true;
654
654
 
655
- // Uzzle's accepted monthly disclosure is the one narrow proven equivalence:
656
- // its API cadence is expressed as 30 days. Do not generalize rate math.
655
+ // One merchant's accepted monthly disclosure is the one narrow proven
656
+ // equivalence: its API cadence is expressed as 30 days. Do not generalize rate math.
657
657
  return (claim.interval_count === 1 && claim.interval === "month"
658
658
  && truth.interval_count === 30 && truth.interval === "day")
659
659
  || (truth.interval_count === 1 && truth.interval === "month"
@@ -872,6 +872,52 @@ export function serializeCommercialFindings(findingsValue, options = {}) {
872
872
  return { assertions, omittedFindingCount };
873
873
  }
874
874
 
875
+ export const RECURRING_CLAIM_ABSENT_CODE = "commercial_parity.recurring_claim_absent";
876
+
877
+ /**
878
+ * A subscription package a page renders with no recurring claim extracted for
879
+ * it (#533). The diff above is silent when there is nothing to compare, which
880
+ * is exactly how a page whose rebill copy could not be read used to pass. This
881
+ * is incomplete evidence, not a mismatch: it names the package, never a price.
882
+ * `subscriptionRefsByPage` maps a page id to the refs the spec says recur;
883
+ * a capture carries the package refs its page renders as
884
+ * `rendered_package_refs`. A capture that failed extraction is skipped — that
885
+ * failure is already its own issue — and so is one with no page id: there is
886
+ * no page to hold its subscriptions against, and it is never keyed as "null".
887
+ */
888
+ export function recurringClaimAbsences(capturesValue, subscriptionRefsByPage) {
889
+ const absences = [];
890
+ const seen = new Set();
891
+ partitionCaptures(capturesValue).valid.forEach((capture) => {
892
+ if (!present(capture.page_id)) return;
893
+ const pageId = String(capture.page_id);
894
+ const subscriptions = subscriptionRefsByPage?.get?.(pageId);
895
+ if (!subscriptions?.size) return;
896
+ const claimed = new Set(capture.recurrence_claims.map((claim) => String(claim.package_id)));
897
+ array(capture.rendered_package_refs).map(String).forEach((ref) => {
898
+ const key = `${pageId}\u0000${ref}`;
899
+ if (!subscriptions.has(ref) || claimed.has(ref) || seen.has(key)) return;
900
+ seen.add(key);
901
+ absences.push({ type: "recurring-claim-absent", page_id: pageId, ...(present(capture.url) ? { url: String(capture.url) } : {}), package_id: ref });
902
+ });
903
+ });
904
+ return absences;
905
+ }
906
+
907
+ export function recurringClaimAbsentAssertions(absences, { maxAssertions = Number.POSITIVE_INFINITY } = {}) {
908
+ return array(absences).slice(0, Math.max(0, maxAssertions)).map((absence) => ({
909
+ id: `${RECURRING_CLAIM_ABSENT_CODE}:${component(absence.page_id)}:package-${component(absence.package_id)}`,
910
+ family: "pricing",
911
+ page: absence.page_id || "campaign",
912
+ ...(absence.url ? { url: absence.url } : {}),
913
+ status: "warn",
914
+ severity: "warn",
915
+ expected: `a readable recurring (rebill) claim for subscription package ${absence.package_id}`,
916
+ actual: `no recurring claim extracted for package ${absence.package_id}`,
917
+ evidence: { type: absence.type, code: RECURRING_CLAIM_ABSENT_CODE, package_id: absence.package_id },
918
+ }));
919
+ }
920
+
875
921
  /**
876
922
  * Build the runner-facing commercial evidence section and flat QA assertions.
877
923
  * `countsOnly` retains matched array lengths without walking claim elements.
package/src/deviation.mjs CHANGED
@@ -17,6 +17,17 @@ export const DEVIATION_JOURNAL_REL_PATH = ".campaign-runtime/agent-deviations.js
17
17
  // (doctor, next, findings, telemetry, run, validate-*) never deviate.
18
18
  export const TRACKED_STAGE_COMMANDS = Object.freeze(new Set(["start", "prepare-build", "theme", "polish", "qa", "run-record"]));
19
19
 
20
+ // Setup and metadata subcommands of a tracked command. They produce no stage
21
+ // output, so running one outside the recommendation is not a detour:
22
+ // qa install-browser — installs the package-owned QA browser binary;
23
+ // qa policy set — edits the packet's QA policy fields (and the
24
+ // assembly report's mirror of them);
25
+ // qa resolve — read-only diagnostic: prints the derived QA targets
26
+ // and, unless --no-probe, probes their reachability.
27
+ // Every other subcommand of a tracked command (`qa run`, `polish capture`,
28
+ // `theme generate`, ...) is still compared against the recommendation.
29
+ export const UNTRACKED_SUBCOMMANDS = Object.freeze(new Set(["qa install-browser", "qa policy", "qa resolve"]));
30
+
20
31
  // Commands every recommendation implicitly allows for its stage. Stage work is
21
32
  // agent/skill work, so the expected command set is small and explicit.
22
33
  const EXPECTED_COMMANDS_BY_STAGE = Object.freeze({
@@ -65,8 +76,9 @@ export function buildRecommendation({ stage, status, expectedCommands, now = new
65
76
  * Compare a pipeline-advancing command against the session's last
66
77
  * recommendation. Returns a deviation entry or null.
67
78
  */
68
- export function detectDeviation({ lastRecommendation, command, argvShape = [], runId = null, deviationReason = null, now = new Date() }) {
79
+ export function detectDeviation({ lastRecommendation, command, subcommand = null, argvShape = [], runId = null, deviationReason = null, now = new Date() }) {
69
80
  if (!TRACKED_STAGE_COMMANDS.has(command)) return null;
81
+ if (subcommand && UNTRACKED_SUBCOMMANDS.has(`${command} ${subcommand}`)) return null;
70
82
  if (!lastRecommendation || !Array.isArray(lastRecommendation.expected_commands)) return null;
71
83
  if (lastRecommendation.expected_commands.includes(command)) return null;
72
84
  return {
@@ -20,7 +20,11 @@ const REASONS = new Set([
20
20
  "built_output.upsell_selector_scope", "built_output.sdk_markup.swap_with_add_to_cart",
21
21
  "built_output.sdk_markup.checkout_not_form", "built_output.sdk_markup.wrong_field_name",
22
22
  "built_output.sdk_markup.missing_selector_id_match", "built_output.script_syntax.parse_failure",
23
- "built_output.script_syntax.missing_script",
23
+ "built_output.script_syntax.missing_script", "built_output.script_syntax.unclosed_script",
24
+ "built_output.script_syntax.symlink_outside_site", "source_html.producer_provenance",
25
+ "source_html.producer_provenance.source_type", "source_html.producer_provenance.screenshot_fallback_used",
26
+ "source_html.producer_provenance.semantic_section_count", "source_html.producer_provenance.material_fingerprint",
27
+ "source_html.producer_provenance.section_exports", "source_html.producer_provenance.waiver_inert",
24
28
  ]);
25
29
  const ACTIONS = new Set([
26
30
  "repair_target", "align_store_profile", "align_sdk_version", "repair_waiver", "waive_checkpoint",
@@ -31,7 +35,7 @@ const ACTIONS = new Set([
31
35
  ]);
32
36
 
33
37
  const RECOVERY = Object.freeze({
34
- skills: { owner: "operator", action_id: "install-skills", input_needed: "Selected agent profile", instruction: "Refresh the bundled skills for that profile and restart the agent." },
38
+ skills: { owner: "operator", action_id: "install-skills", input_needed: "Selected agent profile", instruction: "Refresh the bundled skills for that profile, then read the SKILL.md files install-skills lists under Read now in the running session; restart the agent only if it cannot read them." },
35
39
  pin: { owner: "operator", action_id: "tooling.status", input_needed: "Reviewed toolkit version and lockfile", instruction: "Compare the installed package with the reviewed version; registry currency is not checked." },
36
40
  update: { owner: "operator", action_id: "tooling.status", input_needed: "Reviewed upstream revision", instruction: "Review and update the toolkit checkout using the existing worktree workflow." },
37
41
  doctor: { owner: "workflow_owner", action_id: "doctor", input_needed: "Local packet inputs and detailed doctor findings", instruction: "Inspect doctor locally and follow its existing owner, required inputs, and recovery actions." },