@nextcommerce/campaigns-os 1.43.2 → 1.46.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 (72) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +648 -5103
  3. package/README.md +32 -11
  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/contracts/archive/CHANGELOG.2026-09-30.md +5111 -0
  14. package/contracts/archive/release-ledger.2026-09-30.json +5068 -0
  15. package/contracts/effects.v1.json +1176 -113
  16. package/contracts/orientation-reason-codes.v1.json +7 -0
  17. package/contracts/release-ledger.json +2345 -6087
  18. package/contracts/supported-surface.json +7 -4
  19. package/contracts/template-slot-manifest.shared-content-core.v0.json +403 -0
  20. package/docs/brand-theme-bridge.md +81 -0
  21. package/docs/build-packet.md +158 -21
  22. package/docs/campaigns-os-build-flow.md +3 -3
  23. package/docs/design-source-package.md +73 -0
  24. package/docs/effects.md +50 -8
  25. package/docs/gateway-login.md +3 -0
  26. package/docs/local-setup.md +1 -1
  27. package/docs/orientation-contract-reference.md +42 -2
  28. package/docs/polish-evidence.md +74 -0
  29. package/docs/qa-and-test-orders.md +99 -13
  30. package/docs/release-ledger-authoring-guide.md +64 -4
  31. package/docs/runtime-readiness.md +1 -1
  32. package/docs/sdk-storage-compatibility.md +1 -1
  33. package/docs/skills-revision.md +10 -10
  34. package/docs/supported-surface.md +2 -2
  35. package/docs/versioning.md +4 -1
  36. package/package.json +1 -1
  37. package/schemas/campaigns-os-release-ledger.v1.schema.json +32 -2
  38. package/schemas/campaigns-os-tooling-orientation.v1.schema.json +1 -0
  39. package/skills/campaign-lifecycle-orientation/SKILL.md +16 -5
  40. package/skills/campaign-readback-classification/SKILL.md +3 -3
  41. package/skills/campaign-run-evidence/SKILL.md +7 -6
  42. package/skills/contribution-intake/SKILL.md +3 -3
  43. package/skills/next-campaigns-build/SKILL.md +7 -6
  44. package/skills/next-campaigns-os/SKILL.md +7 -7
  45. package/skills/next-campaigns-os/references/session-intake.md +9 -3
  46. package/skills/next-campaigns-os-setup/SKILL.md +5 -5
  47. package/skills/next-campaigns-polish/SKILL.md +28 -9
  48. package/skills/next-campaigns-qa/SKILL.md +7 -4
  49. package/skills.json +10 -10
  50. package/src/brand-theme.mjs +320 -20
  51. package/src/built-site-scope.mjs +16 -4
  52. package/src/cli.mjs +280 -46
  53. package/src/commercial-parity.mjs +48 -2
  54. package/src/deviation.mjs +13 -1
  55. package/src/diagnostic.mjs +5 -2
  56. package/src/doctor/checks.mjs +320 -81
  57. package/src/doctor/inspect.mjs +55 -13
  58. package/src/doctor/source-provenance.mjs +184 -0
  59. package/src/invocation.mjs +4 -0
  60. package/src/live-campaign-refs.mjs +466 -0
  61. package/src/login.mjs +2 -2
  62. package/src/page-kit-store-profile.mjs +69 -12
  63. package/src/page-kit-sync.mjs +31 -12
  64. package/src/progress-node.mjs +3 -1
  65. package/src/qa-browser.mjs +538 -28
  66. package/src/qa-commercial-parity.mjs +48 -5
  67. package/src/qa-node.mjs +122 -7
  68. package/src/qa-test-order-topology.mjs +148 -0
  69. package/src/sdk-markup.mjs +72 -8
  70. package/src/source-html-intake.mjs +116 -0
  71. package/src/stage-record.mjs +551 -0
  72. package/src/upsell-selector-scope.mjs +112 -2
@@ -21,6 +21,11 @@ Three layers must all be satisfied:
21
21
  `campaigns-os polish capture` from the current packet, report, served build,
22
22
  mapped routes, and fixed desktop/mobile viewports.
23
23
 
24
+ Record all three with `campaigns-os record polish` (§7) rather than editing
25
+ the report by hand: it stamps the stage-record fields from doctor's current
26
+ state, keeps the captured `page_load`, and writes nothing unless this gate would
27
+ pass on the result.
28
+
24
29
  ## 1. Stage-record requirements
25
30
 
26
31
  The gate only applies once assembly is complete
@@ -500,3 +505,72 @@ current value.
500
505
  Polish evidence certifies the polish pass only — it is not QA and does not
501
506
  certify launch readiness (`docs/qa-and-test-orders.md` owns the QA proof
502
507
  stack).
508
+
509
+ ## 7. Recording with `record polish`
510
+
511
+ ```bash
512
+ campaigns-os record polish --packet <campaign-runtime.build.json> --evidence <polish-evidence.json> [--report <json>] [--dry-run] [--json]
513
+ ```
514
+
515
+ Run it after `campaigns-os polish capture`, against the build `campaigns-os
516
+ record build` recorded. The `--evidence` file is a JSON object with these keys;
517
+ any other key is refused:
518
+
519
+ | Key | Required | Written to |
520
+ |---|---|---|
521
+ | `status` | no (default `completed`) | `stages.polish.status`; `completed`, `completed_with_warnings`, `blocked` or `skipped` |
522
+ | `evidence` | for `completed*` | `stages.polish.evidence`, the seven categories of §2. Leave out `visual_review.page_load`: the value `polish capture` recorded is kept, and a file that carries one is refused. Without it, a `blocked` or `skipped` record keeps the evidence already on the report. |
523
+ | `blockers` | for `blocked` only | `stages.polish.blockers`: a non-empty array of `{"code", "message"}` objects. |
524
+ | `skip_reason` | for `skipped` only | `stages.polish.skip_reason`: a non-empty string. |
525
+ | `repair_loop_defect` | no | `report.theme.repair_loop_defect`: `null`, or the first brand-layer repair-loop defect as an object (`code`, `message`, `path`, `detail`). A non-null defect needs a recorded `report.theme`. |
526
+
527
+ The command itself stamps `performed_by: "next-campaigns-polish"`,
528
+ `source_build_fingerprint` (doctor's `derived.build_output_fingerprint.value`,
529
+ which must equal the recorded `stages.assembly.build_fingerprint`),
530
+ `source_package_material_fingerprint` when the report fingerprints a Design
531
+ Source Package, and `completed_at` (not for `blocked`). It then validates the
532
+ report it would write against
533
+ `schemas/campaign-runtime-assembly-report.v0.schema.json` and doctor's report
534
+ checks and, for a `completed*` status, evaluates this gate and the hidden
535
+ eager-media checkpoint over it exactly as doctor does. A `blocked` or `skipped`
536
+ record keeps this gate blocked, so `next` stays at Polish and QA stays blocked. Any failure is printed by field or gate code
537
+ (for example `repair_loop_defect must be null or an object ... (got string)`, or
538
+ `polish.evidence_incomplete` with its per-field problems), the command exits
539
+ non-zero, and nothing is written. `--dry-run` runs every check and writes
540
+ nothing. Like `record setup` and `record build`, it refuses a report that is not
541
+ bound to the packet (`docs/build-packet.md`, "Recording stage completion") and
542
+ output that changes while it records.
543
+
544
+ A complete file:
545
+
546
+ ```json
547
+ {
548
+ "status": "completed",
549
+ "evidence": {
550
+ "visual_review": {
551
+ "screenshots": ["qa-output/polish/landing-desktop.png", "qa-output/polish/landing-mobile.png"]
552
+ },
553
+ "brand_review": {
554
+ "logo_checked": true,
555
+ "favicon": { "status": "confirmed_non_template" },
556
+ "colors": ["#1f4d3a"],
557
+ "brand_bleed": { "cleared": true }
558
+ },
559
+ "checkout_review": {
560
+ "field_labels": "initial field labels and placeholders are legible on desktop and mobile",
561
+ "phone_alignment": "checked",
562
+ "payment_display": "checked",
563
+ "bump_compare_price_rule": "no equal or no-discount compare price renders"
564
+ },
565
+ "template_residue_review": {
566
+ "next_blue": "not found",
567
+ "starter_favicon": { "status": "confirmed_non_template" },
568
+ "lorem": "not found"
569
+ },
570
+ "commerce_flow_review": "direct-entry package selection reviewed",
571
+ "issues": [],
572
+ "commands": ["next-campaigns-polish", "campaigns-os polish capture"]
573
+ },
574
+ "repair_loop_defect": null
575
+ }
576
+ ```
@@ -246,12 +246,18 @@ before QA with the relevant gate ID:
246
246
  ```bash
247
247
  campaigns-os checkpoint waive \
248
248
  --packet campaign-runtime.build.json \
249
- --gate <page_kit.store_profile|page_kit.sdk_version|polish.hidden_eager_media|built_output.upsell_selector_scope> \
249
+ --gate <page_kit.store_profile|page_kit.sdk_version|polish.hidden_eager_media|built_output.upsell_selector_scope|source_html.producer_provenance> \
250
+ [--page <page_id>] \
250
251
  --reason "<why>" \
251
252
  --waived-by "<named human>" \
252
253
  --review-condition "<specific re-evaluation trigger>"
253
254
  ```
254
255
 
256
+ `--page` is required for `source_html.producer_provenance`, which is waived one
257
+ Figma-typed page at a time (see the
258
+ [Design Source Package](./design-source-package.md) hand-written HTML route),
259
+ and refused for the other gates.
260
+
255
261
  Legacy source/theme/QA waiver commands and artifact lanes remain in place until
256
262
  those gates are registered. Store Profile evidence includes only the governed
257
263
  nine-field matrix plus status, normalized slug, relative target path,
@@ -432,7 +438,8 @@ work that lands with the receiver's connection contract. This section
432
438
  documents the semantics of the stamps the receiver already applies.
433
439
 
434
440
  Add `--browser --test-order common` for the normal proof pass: first-party
435
- Playwright browser checks plus the default typed-card order sample. If the
441
+ Playwright browser checks plus the default `common` typed-card orders
442
+ (described under "Test Orders" below). If the
436
443
  browser binary is missing, the CLI will prompt you to run
437
444
  `npm run qa:install-browser`:
438
445
 
@@ -452,6 +459,30 @@ machine-checkable `agentContract.qaStructure` selectors in the commerce surface
452
459
  catalog. If the family contract is silent, the assertion returns
453
460
  `manual_review`, not `pass`; if declared required structure is missing, it
454
461
  soft-fails with warning severity so the verdict becomes `ready_with_exceptions`.
462
+ The checkout wrapper and page composition are source-owned, so QA checks what
463
+ the checkout does rather than family class names. Family shell is a fixed list:
464
+ `.checkout-wrapper`, `.checkout-layout__left`, `.checkout-layout__right`,
465
+ `.checkout__layout`, `.checkout__column--left`, `.checkout__column--right` and
466
+ the family include's `[data-next-component="shipping-field-row"]` marker. When
467
+ family shell is all that is missing, the row is `warn` if the checkout also
468
+ passes three behaviour checks: a `<form data-next-checkout="form">` exists;
469
+ `email`, `fname`, `lname`, `country`, `address1`, `city`, `province` and
470
+ `postal` are each an input, select or textarea carrying that
471
+ `data-next-checkout-field` inside the form, not a `type="hidden"` input or a
472
+ disabled control (visibility is not required, so a field hidden until a country
473
+ is chosen still counts); and a cart-summary total
474
+ (`[data-next-display="cart.total"]` or
475
+ `[data-next-cart-summary] .order-totals__value--total`) is visible with text.
476
+ If any behaviour check fails, or no checkout form is found, the row stays
477
+ `fail`. Any other missing selector is always `fail`: the SDK selectors
478
+ (`[data-next-checkout="form"]`, `[os-checkout-payment]`,
479
+ `[data-next-cart-summary]`, `[data-next-bundle-slots-for]`) and any class the
480
+ list does not name, such as a hosted payment field class. `evidence.behaviour` records
481
+ the three checks and each `evidence.checks[]` entry carries `kind`
482
+ (`family_shell` or `sdk_wiring`).
483
+ The upsell and checkout bundle price checks count the SDK's
484
+ `[data-next-bundle-display*='price']` alongside the contract's price rows; a
485
+ hidden, zero-size or empty bundle-display node does not count.
455
486
  Promoted template families must also have
456
487
  `contracts/template-brand-contract.<family>.v0.json`; QA emits a blocker if the
457
488
  selected family is missing its brand/residue/pricing contract instead of
@@ -1107,13 +1138,64 @@ npm run campaigns-os -- qa run \
1107
1138
  --test-order common
1108
1139
  ```
1109
1140
 
1110
- The default mode is **`common`** (also what bare `--test-order` runs): at most
1111
- four shapes from the selected checkout's declared topology — the checkout
1112
- baseline, first-offer `accept` and `decline` when `expected_next_url` reaches an
1113
- upsell/downsell, and the shortest declared path that actually reaches a
1114
- receipt/thank-you page. The receipt path is deduplicated when it is already
1115
- `accept` or `decline`; Campaigns OS never invents a receipt path from offer
1116
- count alone. This is the everyday QA sample.
1141
+ The default mode is **`common`** (also what bare `--test-order` runs). It
1142
+ starts from the selected checkout's declared topology:
1143
+
1144
+ - When every actual terminal path (the `full` set, checkout baseline included)
1145
+ fits under the flood cap (`--max-test-orders`, default `6`), `common` runs
1146
+ them all. The run says so on stderr, and on a funnel with offer pages the
1147
+ coverage row below records
1148
+ `order_path_depth_effective: "full"` with `order_path_depth_reason:
1149
+ "under_cap"`.
1150
+ - Above the cap, or when the topology cannot be walked exhaustively, `common`
1151
+ runs the sample: the checkout baseline, first-offer `accept` and `decline`
1152
+ when `expected_next_url` reaches an upsell/downsell, and the shortest
1153
+ declared path that actually reaches a receipt/thank-you page (deduplicated
1154
+ when it is already `accept` or `decline`; Campaigns OS never invents a
1155
+ receipt path from offer count alone). It then adds, for each offer or
1156
+ downsell page whose decline no planned path clicks yet, the shortest actual
1157
+ terminal path that clicks it, until the plan reaches the cap. Pages still
1158
+ left out are named on stderr and in the coverage row. The sample itself is
1159
+ never trimmed, so a `--max-test-orders` below it is refused before launch as
1160
+ before.
1161
+
1162
+ A page counts as covered only when a path clicks its **decline** control.
1163
+ Reaching a page, or clicking only its accept, does not count: a broken decline
1164
+ link strands the shopper even when accept works.
1165
+
1166
+ Every browser test-order run whose funnels include offer pages records
1167
+ `browser-test-order:upsell-action-coverage`, read from the clicks the runner
1168
+ actually made in orders it placed, not from the plan. It lists the offer pages
1169
+ of every funnel in the run, including funnels the orders do not drive, and a
1170
+ click credits only the funnel whose order made it, never another funnel's page
1171
+ at the same URL. The row is conservative: it is `pass` or `warn` only when
1172
+ coverage is certain, and `manual_review` in every other case.
1173
+
1174
+ Coverage is certain when all of these hold: at least one order was placed;
1175
+ every funnel lists its pages; every page is either a known non-offer type
1176
+ (checkout, landing, thank-you and the like) or an upsell/downsell page with its
1177
+ own absolute http(s) URL that no other page shares; every planned order belongs
1178
+ to exactly one funnel (its checkout is that funnel's checkout and it was
1179
+ planned over that funnel's page list); and every click an order recorded lands
1180
+ on a declared offer page of that order's own funnel. Then the row is `warn`
1181
+ (severity `warn`) naming each page whose decline no order of its funnel
1182
+ clicked, and each funnel no order ran through, or `pass` when every page's
1183
+ decline was clicked.
1184
+
1185
+ Otherwise the row is `manual_review` (severity `warn`) naming the pages not
1186
+ proved clicked through, with `evidence.reason`: `test_orders_off` for a
1187
+ `--test-order off` run, `no_order_recorded` when no order was placed (an
1188
+ attempt that failed before an order reference counts as none), `no_topology`
1189
+ when no funnel topology reached the check, or `not_assessable` for the rest.
1190
+ `evidence.not_assessable[]` names each page that cannot be matched to a click,
1191
+ with a reason: `no_url` or `unresolvable_url`, `shared_url` (the URL belongs to
1192
+ another page too), `unknown_page_type` (neither an offer type nor a known
1193
+ non-offer type), or `no_pages` (a funnel with no page list).
1194
+ `evidence.uncertainty[]` lists the run-level reasons: `unattributed_plan` (a
1195
+ plan that does not match exactly one funnel's checkout and page list),
1196
+ `unattributed_click` (a click with no usable URL, or from such a plan's order)
1197
+ and `undeclared_click` (an order clicked a page its funnel does not declare).
1198
+ `evidence.pages[]` gives `accept_clicked` / `decline_clicked` per page.
1117
1199
 
1118
1200
  Other modes: `checkout` (base order redirect only), `accept`/`decline` (click the
1119
1201
  rendered control on the first upsell page), `both` (two fresh orders for those
@@ -1547,8 +1629,11 @@ npm run campaigns-os -- qa run \
1547
1629
  ```
1548
1630
 
1549
1631
  `--max-test-orders` (default `6`) is an **accidental-flood guard, not a permission
1550
- gate**. A single checkout's `common` sample always stays under it, though tier
1551
- expansion can exceed it. If `full` expands past the cap, the command stops before
1632
+ gate**. A single checkout's `common` plan always keeps its checkout,
1633
+ first-offer accept/decline and shortest-receipt sample. If that sample alone
1634
+ exceeds the cap (a `--max-test-orders` below it), the run is refused before
1635
+ browser launch, as before. Otherwise the added decline paths stop at the cap.
1636
+ Tier expansion can exceed it. If `full` expands past the cap, the command stops before
1552
1637
  browser launch, prints the planned count, lists the planned paths (up to 40 ids;
1553
1638
  past that the remainder is counted, never cut silently, and `--select-package
1554
1639
  <ref[:qty]>` lists one tier's paths), and names the exact `--max-test-orders <count>` raise. For example, a linear three-offer graph has
@@ -1729,8 +1814,9 @@ use the declared topology instead of a single happy path:
1729
1814
 
1730
1815
  1. Checkout-only with the base cart.
1731
1816
  2. Checkout-only with the base cart plus bump when the bump is in scope.
1732
- 3. Base cart through the checkout/first-action sample plus the shortest real
1733
- receipt path (`--test-order common` covers up to four deduplicated shapes).
1817
+ 3. Base cart through `--test-order common`: every actual terminal path when
1818
+ they fit under the cap, otherwise the checkout/first-action sample, the
1819
+ shortest real receipt path and one decline path per uncovered offer page.
1734
1820
  4. Base plus bump cart through the same sample matrix when bump behavior is
1735
1821
  launch-relevant.
1736
1822
  5. Use `full` when you want every actual terminal path, raising the flood cap to
@@ -146,7 +146,7 @@ incomplete, append a correction:
146
146
  "amends": "RL-0007",
147
147
  "amendment_reason": "RL-0007 was recorded as compatible; it removed a documented guarantee.",
148
148
  "surface_version": null,
149
- "changelog_section": "1.15.0+agent.1",
149
+ "changelog_section": "1.16.0+agent.1",
150
150
  "compatibility": "breaking",
151
151
  "migration": "Stop relying on the removed guarantee; see docs/build-packet.md.",
152
152
  "agent_impact": "Treat the 1.15.0 packet doc change as breaking, not compatible.",
@@ -154,6 +154,10 @@ incomplete, append a correction:
154
154
  }
155
155
  ```
156
156
 
157
+ The amendment corrects a 1.15.0 entry, but its own section is a new one at the
158
+ very top of `CHANGELOG.md`, numbered on the release on top when it is written
159
+ (`1.16.0` here). It is never inserted under the older release it corrects.
160
+
157
161
  An amendment is the only entry kind whose change items may map to no changed
158
162
  path in its own range, because it corrects meaning rather than moving bytes.
159
163
 
@@ -171,13 +175,25 @@ entry currently holding the link, may re-link a section; any other second link
171
175
  still fails the one-to-one rule. Say in `amendment_reason` what changed in the
172
176
  section and why.
173
177
 
178
+ This works only for a section still in the live `CHANGELOG.md`. An archived
179
+ section cannot be corrected in place: archive files are never edited, and each
180
+ is pinned by its SHA-256 in `baseline_floor.archives`, so any change to one
181
+ fails the gate. Correct archived history with a new live amendment entry that
182
+ links its own new section at the top of `CHANGELOG.md`, and say in
183
+ `amendment_reason` what is wrong in the archived section.
184
+
174
185
  `scripts/check-changelog-structure.mjs` (part of `npm run check`) refuses the
175
186
  marker lines outright, in `CHANGELOG.md` and under `docs/`, and also holds the
176
187
  section layout: identifiers unique, `+agent.N` sections in one run directly
177
188
  above their release with N descending (newest first), and every ledger
178
- `changelog_section` naming a section that exists. Insert a new `+agent.N`
179
- section at the top of its release's run, not directly above the release
180
- heading.
189
+ `changelog_section` naming a section that exists.
190
+
191
+ A new section always goes at the very top of `CHANGELOG.md`: either a new
192
+ release, or a `+agent.N` section numbered on the current top release (one above
193
+ its highest N, or `+agent.1` if it has none). Never add a `+agent.N` section
194
+ under an older release. Given `--base`, both `check-changelog-structure.mjs` and
195
+ the release-ledger gate refuse any new section that sits below a section base
196
+ already had.
181
197
 
182
198
  ## Running the gate
183
199
 
@@ -274,3 +290,47 @@ a quiet raise.
274
290
 
275
291
  Raising a limit is a reviewed policy change: advance `limits_version`, and the
276
292
  change owes its own ledger entry like anything else.
293
+
294
+ ## Rotating the baseline
295
+
296
+ When the live ledger and changelog approach a bound, rotate them at a reviewed
297
+ cut instead of raising a limit. The first rotation (2026-09-30, `RL-0190`)
298
+ moved `RL-0001` through `RL-0124`; the ledger's `baseline_floor` records it.
299
+
300
+ 1. Pick the cut: the last entry to archive. Everything up to it moves, and so
301
+ does the changelog from the first section those entries link down to the end
302
+ of the file, including sections no entry links. If a kept entry amends an
303
+ archived one, or links a section in that tail, move the cut earlier until the
304
+ pair is on one side. Never later.
305
+ 2. Run the rotation through `rotateLedger` in `scripts/orientation-contract.mjs`
306
+ with a new dated pair under `contracts/archive/`, e.g.
307
+ `release-ledger.<date>.json` and `CHANGELOG.<date>.md`. It writes the entries
308
+ byte-for-byte (original `sequence` and hashes kept) and the changelog tail
309
+ verbatim, appends the pair and its SHA-256 to `baseline_floor.archives`, and
310
+ moves `last_archived_id`, `last_archived_sequence` and `first_kept_id`. It
311
+ refuses a cut that splits a pair.
312
+ 3. Never edit an existing archive file, and never reuse a date: each rotation
313
+ adds a new pair. Add both new files to `named` in
314
+ `contracts/supported-surface.json`.
315
+ 4. Record the rotation as its own entry, named in `baseline_floor.rotation_entry`,
316
+ with a change item for each new archive file and a CHANGELOG section of its
317
+ own. Its `agent_impact` tells consumers which baseline is now too old.
318
+
319
+ The gate accepts a base entry missing from the live ledger only when the head
320
+ floor covers it, the new archive holds it canonical-JSON-identical, and the floor
321
+ moved with a new rotation entry in the same range. Any other deletion, an
322
+ archive copy that differs from base, a floor that moves without a rotation
323
+ entry, a floor that moves back or is rewritten, an edited archive file, and a
324
+ cut that archives an entry while an entry that was live at base stays live and
325
+ amends it (the same refusal `rotateLedger` gives) all fail. A new entry added
326
+ after the rotation may still amend an archived entry, linking its own section
327
+ in the live changelog: that is how archived history is corrected. The live changelog and each archive changelog must each be well-formed on
328
+ their own, a section id may appear in only one of them, a section present
329
+ at base must still be in one of them, and the live changelog followed by the
330
+ archives, newest rotation first, must read as the base's sections in order
331
+ with new sections only at the top of the live file. So every live section is
332
+ newer than every archived one, and an archived section never moves back
333
+ (`check-changelog-structure.mjs --base` and the `--base` release-ledger gate).
334
+ The archive files are not mandatory
335
+ orientation reads and are not measured; a consumer whose reviewed baseline is older than the floor refuses
336
+ with `baseline_below_floor` and adopts a newer reviewed baseline.
@@ -8,7 +8,7 @@
8
8
 
9
9
  How a checkout of this repository at one commit becomes a usable installed runtime, and how a consumer decides whether a prepared one is still trustworthy. Everything below is generated from `contracts/runtime-recipe.campaigns-os-node-v1.json`, which is the only authority for these values.
10
10
 
11
- Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.43.2`.
11
+ Recipe kind `campaigns-os-node-v1`, revision `1.0.2`, validated by `schemas/campaigns-os-runtime-recipe.v1.schema.json` (`Campaigns OS Runtime Recipe v1`). Supported surface at generation time: `1.46.0`.
12
12
 
13
13
  ## What this is
14
14
 
@@ -8,7 +8,7 @@ npx --no-install campaigns-os sdk storage-check --target . --target-sdk 0.4.38 -
8
8
 
9
9
  Omit `--json` for the concise human report. Exit 0 means source-compatible; exit 2 means incompatible or unknown; invalid arguments or manifests exit 1. No merchant files or pins are rewritten. This is independent of doctor's built HTML markup check.
10
10
 
11
- The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply the generated `storage-migrations.v1.json` from the SDK contract change (campaign-cart #102); until that change is published, this command does **not** imply a released SDK tag contains that file. The report records the full file SHA-256, SDK version, supported target range, registry/extractor/input digest, and Git provenance. When the supplied bytes equal their repository HEAD blob, provenance is `verified-git-blob` with commit and repository path. Proposed, modified, or copied manifests are labeled `unverified-local-file`; the label never certifies a release. Review/pin SDK provenance separately before acting on findings. Targets outside the manifest's supported SDK range report unknown.
11
+ The manifest is generated and owned by Campaign Cart, from its storage registry. The scanner has no independent SDK key list and fetches no remote code. Supply `docs/compatibility/storage-migrations.v1.json` from an unmodified checkout of a Campaign Cart release tag, v0.4.40 or later. The report records the full file SHA-256, SDK version, supported target range, registry/extractor/input digest, and Git provenance. When the supplied bytes equal their repository HEAD blob, provenance is `verified-git-blob` with commit and repository path. Proposed, modified, or copied manifests are labeled `unverified-local-file`; the label never certifies a release. Review/pin SDK provenance separately before acting on findings. Targets outside the manifest's supported SDK range report unknown.
12
12
 
13
13
  `--target` must name the Git root. `--scope` is mandatory: comma-separated literal repository-relative directory/file paths; use `.` only when the complete repository is intended. Add shared JavaScript directories explicitly. `--exclude` uses the same syntax and records explicit exclusions. Archives receive no implicit exemption. Only Git-tracked `.html`, `.htm`, `.js`, `.mjs`, and `.cjs` files in the selected scope are scanned, using current working-tree bytes and per-file digests; untracked files and built dependencies are outside this evidence. A selected HTML page's local script outside the selected files reports unknown. Relative scripts affected by an HTML `<base href>` also report unknown; review their actual dependency paths. Remote SDK and third-party scripts are not fetched or analyzed.
14
14
 
@@ -16,7 +16,7 @@ that the copy on disk moved.
16
16
  `skills.json` carries one top-level field:
17
17
 
18
18
  ```json
19
- "bundle_revision": "1.43.2+skills.1"
19
+ "bundle_revision": "1.46.0+skills.1"
20
20
  ```
21
21
 
22
22
  The spelling is `<package version>+skills.<n>`:
@@ -25,7 +25,7 @@ The spelling is `<package version>+skills.<n>`:
25
25
  skills ship with (`check-skill-versions.mjs` fails if the two disagree);
26
26
  - `<n>` is a plain counter, not a semver component. It says "this is the *n*th
27
27
  skill-text revision published against that package version" and it **resets
28
- with the prefix**. `1.43.2+skills.1` is therefore ahead of `1.40.0+skills.7`.
28
+ with the prefix**. `1.46.0+skills.1` is therefore ahead of `1.40.0+skills.7`.
29
29
 
30
30
  It is one identity for the bundle as a whole, on purpose. Per-skill versions
31
31
  still exist and still gate per-skill changes, but an agent that loaded one skill
@@ -37,7 +37,7 @@ The first body line of every bundled `SKILL.md`, immediately after the
37
37
  frontmatter, is exactly:
38
38
 
39
39
  ```
40
- Bundle revision: 1.43.2+skills.1
40
+ Bundle revision: 1.46.0+skills.1
41
41
  ```
42
42
 
43
43
  followed by a short paragraph telling the agent to run the check below at the
@@ -48,7 +48,7 @@ text the agent is actually reading, not from a file it would have to go and open
48
48
  ## The check
49
49
 
50
50
  ```bash
51
- npx --no-install campaigns-os tooling status --skills-revision 1.43.2+skills.1
51
+ npx --no-install campaigns-os tooling status --skills-revision 1.46.0+skills.1
52
52
  ```
53
53
 
54
54
  The value is compared against the bundle revision of the **CLI the command runs
@@ -90,20 +90,20 @@ reports the choice as `skills.scope` (`requested`, `installed_platforms`, or
90
90
  "revision_check": "match",
91
91
  "skills_revision": {
92
92
  "status": "match",
93
- "requested": "1.43.2+skills.1",
93
+ "requested": "1.46.0+skills.1",
94
94
  "spelling": "bundle",
95
- "on_disk": "1.43.2+skills.1",
95
+ "on_disk": "1.46.0+skills.1",
96
96
  "on_disk_skill": null,
97
- "message": "match (1.43.2+skills.1)"
97
+ "message": "match (1.46.0+skills.1)"
98
98
  }
99
99
  ```
100
100
 
101
101
  The text view prints one named line, as a header above the rest of the status:
102
102
 
103
103
  ```
104
- Skills revision: match (1.43.2+skills.1)
105
- Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.43.2+skills.1 — start a fresh session
106
- Skills revision: unchecked (on disk 1.43.2+skills.1)
104
+ Skills revision: match (1.46.0+skills.1)
105
+ Skills revision: mismatch: loaded 1.39.0+skills.1, on disk 1.46.0+skills.1 — start a fresh session
106
+ Skills revision: unchecked (on disk 1.46.0+skills.1)
107
107
  ```
108
108
 
109
109
  `unchecked` is the state when the flag is absent. It is not an error — an
@@ -14,7 +14,7 @@ implementation detail, however stable it looks.
14
14
  | Surface | Contract | Change discipline |
15
15
  |---|---|---|
16
16
  | `schemas/*.schema.json` (all of them) | The portable contract catalog: CampaignSpec, Design Source Package, Build Packet, Build Context, Assembly Report, Doctor Output, sidecar-bundle conformance, Run Record, Workflow Finding, Build Brief, Source-HTML Manifest, Tooling Orientation, Release Ledger, QA Verdict, the QA Verdict sidecar projection, Runtime Recipe, and the legacy-migration inventory/plan/receipt trio. | Hashed. Any content change requires updating the recorded hash **and** bumping `surface_version` in the same PR. A shape change that alters meaning gets a new schema-version const — one version identifier must never cover two shapes (the 2026-08 assembly-report drift is the incident this rule encodes). Additions to an open `v0` schema are expected and consumers must tolerate unknown fields; the security-sensitive legacy-migration schemas are closed, so additions there require a new lineage. 1.28.0 (RL entry `surface_version: 1.28.0`, breaking) removed the two required Build Packet booleans `qa.test_orders_allowed` and `qa.sandbox_test_card_confirmed` (nothing read them; test orders run from `--test-order <mode>` alone), added `local-serve` to the `deploy.target` enum, and added the optional `remit_result` / `remit_base_kind` fields to the Run Record. 1.30.0 (additive) added the optional `data_layer` record to the QA Verdict's `test_orders[]` entries — the order's `dl_purchase` reading (#325). 1.33.0 (additive) added the optional `qa_verdict_publish` block to the Run Record — which verdict was posted to the QA portal, by `qa run` or `qa publish`, and what the portal answered, in the `remit_result` vocabulary (#328). |
17
- | CLI commands: `start`, `prepare-build`, `build`, `polish`, `checkpoint`, `page-kit`, `spec`, `doctor`, `bundle`, `next`, `theme`, `tooling`, `install-skills`, `install-agent-context`, `validate-assembly-report`, `telemetry`, `standardize`, `qa`, `findings`, `run-record`, `run`, `sdk`, `demo`, `login`, `logout` | Scriptable entry points. The founding list (1.0.0) was the argv surface Campaigns Agent's remit fixture pins; 1.25.0 adds the partner entry path the public guides instruct — `install-skills` (skill install), `tooling status` (preflight), `next --packet` (the runtime cursor) — plus `theme`, `install-agent-context`, `validate-assembly-report`, and `telemetry`, since consent is part of the partner contract. `validate-build-packet`, formerly an undocumented and unsupported alias of `doctor`, was removed in 1.25.0+agent.7 (RL-0036) and now gets the unknown-command error; use `doctor`. `standardization-report`, a supported but redundant second spelling of `standardize` — same flags, same output, same exit codes — was removed in 1.27.0 and now gets the same unknown-command error; use `standardize`. Removing a supported command is a breaking change and is why that release moved the minor. `bundle check` validates the canonical JSON readback set and makes QA required only under `--require-qa`. `polish capture` owns page-load evidence production. `checkpoint waive` currently accepts four registered gates: `page_kit.store_profile`, `page_kit.sdk_version`, `polish.hidden_eager_media`, and `built_output.upsell_selector_scope`. `page-kit sync` (added in 1.29.0) writes the CampaignSpec's Store Profile fields and SDK pin into the target's `_data/campaigns.json` entry — the repair the first two gates name — and is the only `page-kit` subcommand. `spec derive` (added in 1.31.0) is the reverse write for the repo-derived field class (#432): the target's SDK pin, page routes and analytics ids into the packet's local CampaignSpec; it is the only `spec` subcommand, and the `page_kit.sdk_version.repo_newer` advisory names it; its `--write-map` flag (1.33.0+agent.2) also records the derived pin in the saved Map's Build hints field through the proxy Worker, never moving a Map pin backwards. `qa publish` (added in 1.33.0) posts an already-stored verdict to the QA portal without a re-run or an order, refusing a stale `spec_hash` or a verdict its Run Record already records as published (#328). Within Polish, only the broader Source Freshness waiver remains on its existing report lane; theme and QA decisions also retain their existing lanes. | Any change to this list — adding, renaming, or removing a command — bumps `surface_version` in the same PR, and since 1.25.0 `check-supported-surface.mjs --base` enforces that (before, only hashed and named entries owed a bump, so `checkpoint` landed unbumped). Subcommands, registered gates, and flags may grow freely beneath a listed command. Removing a listed command from dispatch fails the gate outright. Do not infer support for an unregistered checkpoint from the top-level command. |
17
+ | CLI commands: `start`, `prepare-build`, `build`, `polish`, `checkpoint`, `page-kit`, `spec`, `doctor`, `bundle`, `next`, `theme`, `tooling`, `install-skills`, `install-agent-context`, `validate-assembly-report`, `telemetry`, `standardize`, `qa`, `findings`, `run-record`, `run`, `sdk`, `demo`, `login`, `logout` | Scriptable entry points. The founding list (1.0.0) was the argv surface Campaigns Agent's remit fixture pins; 1.25.0 adds the partner entry path the public guides instruct — `install-skills` (skill install), `tooling status` (preflight), `next --packet` (the runtime cursor) — plus `theme`, `install-agent-context`, `validate-assembly-report`, and `telemetry`, since consent is part of the partner contract. `validate-build-packet`, formerly an undocumented and unsupported alias of `doctor`, was removed in 1.25.0+agent.7 (RL-0036) and now gets the unknown-command error; use `doctor`. `standardization-report`, a supported but redundant second spelling of `standardize` — same flags, same output, same exit codes — was removed in 1.27.0 and now gets the same unknown-command error; use `standardize`. Removing a supported command is a breaking change and is why that release moved the minor. `bundle check` validates the canonical JSON readback set and makes QA required only under `--require-qa`. `polish capture` owns page-load evidence production. `checkpoint waive` currently accepts five registered gates: `page_kit.store_profile`, `page_kit.sdk_version`, `polish.hidden_eager_media`, `built_output.upsell_selector_scope`, and `source_html.producer_provenance` (per page, with `--page <page_id>`). `page-kit sync` (added in 1.29.0) writes the CampaignSpec's Store Profile fields and SDK pin into the target's `_data/campaigns.json` entry — the repair the first two gates name — and is the only `page-kit` subcommand. `spec derive` (added in 1.31.0) is the reverse write for the repo-derived field class (#432): the target's SDK pin, page routes and analytics ids into the packet's local CampaignSpec; it is the only `spec` subcommand, and the `page_kit.sdk_version.repo_newer` advisory names it; its `--write-map` flag (1.33.0+agent.2) also records the derived pin in the saved Map's Build hints field through the proxy Worker, never moving a Map pin backwards. `qa publish` (added in 1.33.0) posts an already-stored verdict to the QA portal without a re-run or an order, refusing a stale `spec_hash` or a verdict its Run Record already records as published (#328). Within Polish, only the broader Source Freshness waiver remains on its existing report lane; theme and QA decisions also retain their existing lanes. | Any change to this list — adding, renaming, or removing a command — bumps `surface_version` in the same PR, and since 1.25.0 `check-supported-surface.mjs --base` enforces that (before, only hashed and named entries owed a bump, so `checkpoint` landed unbumped). Subcommands, registered gates, and flags may grow freely beneath a listed command. Removing a listed command from dispatch fails the gate outright. Do not infer support for an unregistered checkpoint from the top-level command. |
18
18
  | `sdk storage-check` and `docs/sdk-storage-compatibility.md` | Read-only AST compatibility report for explicitly scoped tracked campaign HTML/JS before an SDK bump; consumes the supplied SDK-owned manifest and records its SHA-256 and verified or unverified local Git provenance. Incompatible and unknown findings exit 2; a clean scan is static source evidence only. | Additive supported CLI and named documentation; no automatic merchant integration repair. |
19
19
  | `bin/campaigns-os.mjs` (`campaigns-os`) | The CLI entry itself. | Declared in `package.json` `bin`; the gate fails if it disappears. |
20
20
  | Package export `./campaign-spec` | The versioned campaign-spec rule registry, consumed as `@nextcommerce/campaigns-os` (pinned by consumers' lockfiles; lockstep policy — ADR-003 in the ops repo). | Behavior-guarded from the consumer side by their contract tests; the export path itself is gated here. |
@@ -24,7 +24,7 @@ implementation detail, however stable it looks.
24
24
  | Contract docs: `CONTEXT.md`, `docs/campaigns-os-build-flow.md`, `docs/build-packet.md`, `docs/migration-sidecar-bundle.md`, `docs/design-source-package.md`, `docs/campaign-build-brief.md`, `docs/campaign-standardization-report.md`, `docs/brand-theme-bridge.md`, `docs/qa-and-test-orders.md`, `docs/legacy-migration.md`, `docs/versioning.md`, `docs/workflow-findings-sidecar.md`, this file | Named entry points consumers pin for context. | Content evolves freely; the path must keep existing. |
25
25
  | `skills.json` + `skills/` + `skills.sh` | Versioned skill packages and their installer. | Governed by `check-skill-versions.mjs` (parity + bump gate + reserved external names). `skills.json` ships in the npm pack as of surface 1.0.0. As of 1.40.0 the manifest also carries `bundle_revision` (`<package version>+skills.<n>`), one identity for the bundle that every `SKILL.md` states on its first body line and that `campaigns-os tooling status --skills-revision <value>` checks against the bundle on disk; it must advance whenever any bundled skill changes (`--base` mode), and `docs/skills-revision.md` is its prose. |
26
26
  | `compatibility.json` | The published compatibility statement. | Named; must keep existing. |
27
- | Orientation contract: `AGENTS.md`, `CHANGELOG.md`, `contracts/release-ledger.json`, `contracts/agent-relevant-change-policy.v1.json`, `contracts/orientation-limits.v1.json`, `contracts/orientation-reason-codes.v1.json`, `docs/orientation-contract-reference.md`, `docs/release-ledger-authoring-guide.md`, `contracts/fixtures/orientation/canonicalization/v1.json` | The declarative data a consumer reads from Git objects to decide whether a commit is safe to work against, without executing anything from this repository. Schema `campaigns-os-tooling-orientation/v1`; ledger schema `campaigns-os-release-ledger/v1`. The canonicalization fixture gives independent implementations exact ledger and changelog bytes plus their expected SHA-256 digests. Entry point: [`AGENTS.md`](../AGENTS.md). | Named. The ledger is append-only: a correction ships as a new amendment entry, never an edit. `scripts/check-release-ledger.mjs` enforces the two-way gate; `docs/orientation-contract-reference.md` and the canonicalization fixture are generated together, and CI fails if either is stale. |
27
+ | Orientation contract: `AGENTS.md`, `CHANGELOG.md`, `contracts/release-ledger.json`, `contracts/archive/release-ledger.2026-09-30.json`, `contracts/archive/CHANGELOG.2026-09-30.md`, `contracts/agent-relevant-change-policy.v1.json`, `contracts/orientation-limits.v1.json`, `contracts/orientation-reason-codes.v1.json`, `docs/orientation-contract-reference.md`, `docs/release-ledger-authoring-guide.md`, `contracts/fixtures/orientation/canonicalization/v1.json` | The declarative data a consumer reads from Git objects to decide whether a commit is safe to work against, without executing anything from this repository. Schema `campaigns-os-tooling-orientation/v1`; ledger schema `campaigns-os-release-ledger/v1`. The canonicalization fixture gives independent implementations exact ledger and changelog bytes plus their expected SHA-256 digests. Entry point: [`AGENTS.md`](../AGENTS.md). | Named. The ledger is append-only: a correction ships as a new amendment entry, never an edit. A baseline rotation moves older entries and changelog sections unchanged into dated files under `contracts/archive/`, which never change once merged and are not mandatory orientation reads. `scripts/check-release-ledger.mjs` enforces the two-way gate; `docs/orientation-contract-reference.md` and the canonicalization fixture are generated together, and CI fails if either is stale. |
28
28
  | Orientation fixtures: `contracts/fixtures/orientation/envelope/*.json`, `contracts/fixtures/orientation/hostile-target/**` (as named) | The bytes a consumer's parser validates against: one envelope per terminal outcome, plus a hostile target carrying Git hooks, an executable file, and npm lifecycle scripts for proving a reader executes nothing. The hostile target carries a second invariant for the runtime recipe: preparing it must run the recipe's own two steps and no lifecycle script reachable from them. | Named. Regenerate the envelopes with `npm run generate:orientation-docs`. Fixtures under `contracts/fixtures/` that are **not** named here — including the legacy-migration conformance corpus — are this repo's own test data and are not supported. |
29
29
  | Runtime recipe: `contracts/runtime-recipe.campaigns-os-node-v1.json` | The declarative description of how a checkout at one commit becomes a usable installed runtime: exact commands, accepted tool ranges, per-step network policy, the enumerated input set, the mandatory output checks, and the enforced bounds. This repository publishes it; the consumer bootstrap executes it. Guide: [`docs/runtime-readiness.md`](runtime-readiness.md). | **Hashed**, deliberately unlike the orientation policy contracts beside it. Any change to commands, network policy, tool versions, inputs, or output verification is an agent-relevant release event, and only a hashed entry makes such a change require `surface_version` to advance in the same PR. A recipe whose commands can change without a version bump is not a contract. |
30
30
  | Runtime-recipe fixtures: `contracts/fixtures/runtime-recipe/**` (as named), `docs/runtime-readiness.md` | Accept and single-mutation reject documents a consumer's parser validates against, the prepared-runtime states its output checks must distinguish, and the generated guide. | Named. All of it is generated — regenerate with `npm run generate:runtime-docs`; CI fails on a stale copy. |
@@ -42,7 +42,10 @@ consumer needs to know about an agent-relevant change even when
42
42
  `CHANGELOG.md` narrates them, and the two are checked against each other in both
43
43
  directions. Reason codes and semantic classes are append-only vocabularies —
44
44
  renaming or removing one is a breaking change. Raising a limit advances
45
- `limits_version` and owes its own ledger entry.
45
+ `limits_version` and owes its own ledger entry. Rotating the baseline (moving
46
+ old entries and their sections into a dated `contracts/archive/` pair under the
47
+ ledger's `baseline_floor`) changes no limit and no schema id; it is recorded as
48
+ its own ledger entry.
46
49
 
47
50
  The runtime recipe carries two version identifiers because they gate different
48
51
  things. The **kind** names what an installed consumer must already understand in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextcommerce/campaigns-os",
3
- "version": "1.43.2",
3
+ "version": "1.46.0",
4
4
  "description": "Toolkit for agent-assisted NEXT campaign builds.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -16,11 +16,41 @@
16
16
  },
17
17
  "entries": {
18
18
  "type": "array",
19
- "description": "Ordered oldest to newest. sequence is strictly increasing by 1 from 1, and date is non-decreasing.",
19
+ "description": "Ordered oldest to newest. sequence is strictly increasing by 1 from 1, and date is non-decreasing. When baseline_floor is present, the entries up to the floor live in the archive it names and this array starts at baseline_floor.last_archived_sequence + 1.",
20
20
  "items": { "$ref": "#/$defs/entry" }
21
- }
21
+ },
22
+ "baseline_floor": { "$ref": "#/$defs/baseline_floor" }
22
23
  },
23
24
  "$defs": {
25
+ "baseline_floor": {
26
+ "type": "object",
27
+ "additionalProperties": true,
28
+ "description": "Present after a baseline rotation. Entries up to and including last_archived_id moved byte-for-byte, and the changelog from the first section they link to the end of the file moved verbatim, into the dated archive files listed in archives; they keep their original sequence and hashes, so each archived entry's changelog_sha256 verifies against the archive changelog. The archive files are not mandatory orientation reads and count against no orientation limit. A consumer whose reviewed baseline's newest entry is older than last_archived_id refuses with refusal_reason_code and adopts a newer reviewed baseline. The floor only moves forward, only with a new rotation entry, and an archive file never changes once merged; a later rotation appends a new archive pair.",
29
+ "required": ["archives", "last_archived_id", "last_archived_sequence", "first_kept_id", "rotation_entry", "refusal_reason_code"],
30
+ "properties": {
31
+ "archives": {
32
+ "type": "array",
33
+ "minItems": 1,
34
+ "description": "Every archive pair, oldest rotation first. Append-only.",
35
+ "items": {
36
+ "type": "object",
37
+ "additionalProperties": true,
38
+ "required": ["ledger_path", "ledger_sha256", "changelog_path", "changelog_sha256"],
39
+ "properties": {
40
+ "ledger_path": { "type": "string", "pattern": "^contracts/archive/[^/]+\\.json$", "description": "Archived entries, same schema_version and shape as this file." },
41
+ "ledger_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "SHA-256 over the exact bytes of ledger_path." },
42
+ "changelog_path": { "type": "string", "pattern": "^contracts/archive/[^/]+\\.md$", "description": "The changelog tail that starts at the first section the archived entries link, verbatim and in order, same heading format." },
43
+ "changelog_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "SHA-256 over the exact bytes of changelog_path." }
44
+ }
45
+ }
46
+ },
47
+ "last_archived_id": { "type": "string", "pattern": "^RL-[0-9]{4}$", "description": "The newest archived entry." },
48
+ "last_archived_sequence": { "type": "integer", "minimum": 1, "description": "Its sequence. The first live entry's sequence is this plus one." },
49
+ "first_kept_id": { "type": "string", "pattern": "^RL-[0-9]{4}$", "description": "The first entry still in this file." },
50
+ "rotation_entry": { "type": "string", "pattern": "^RL-[0-9]{4}$", "description": "The live entry that recorded the rotation which set this floor." },
51
+ "refusal_reason_code": { "type": "string", "minLength": 1, "description": "The reason code, from contracts/orientation-reason-codes.v1.json, a consumer refuses with when its reviewed baseline is below the floor." }
52
+ }
53
+ },
24
54
  "entry": {
25
55
  "type": "object",
26
56
  "additionalProperties": true,
@@ -350,6 +350,7 @@
350
350
  "enum": [
351
351
  "already_current",
352
352
  "ahead_of_upstream",
353
+ "baseline_below_floor",
353
354
  "checkout_acquisition_failed",
354
355
  "checkout_already_exists",
355
356
  "checkout_missing",
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: campaign-lifecycle-orientation
3
- version: 1.0.9
3
+ version: 1.0.17
4
4
  description: Orient a reader to the Campaigns OS lifecycle artifacts a run has already emitted, without advancing any stage or changing any state.
5
5
  ---
6
6
 
7
- Bundle revision: 1.43.2+skills.1
8
- Run `npx --no-install campaigns-os tooling status --skills-revision 1.43.2+skills.1`
7
+ Bundle revision: 1.46.0+skills.1
8
+ Run `npx --no-install campaigns-os tooling status --skills-revision 1.46.0+skills.1`
9
9
  from the campaign's Page Kit folder, where it runs the project's pinned copy and
10
10
  never installs one, at the start of each task. Start a fresh session if it
11
11
  reports `mismatch`: this text is already in your context and is never re-read
@@ -34,6 +34,15 @@ cite `src/` or `scripts/`: those are implementation and may change without a
34
34
  supported-surface bump, so a reader cannot check them and a rename would not
35
35
  reach this text. `docs/supported-surface.md` is the prose twin of that list.
36
36
 
37
+ `CHANGELOG.md` and `contracts/release-ledger.json` hold recent history. When
38
+ the ledger declares a `baseline_floor`, the older entries and the sections they
39
+ link sit in the dated `contracts/archive/` files it names: cite those for
40
+ history, but they are not part of an orientation read. An orientation whose
41
+ reviewed baseline is older than the floor is refused with
42
+ `baseline_below_floor`, and the remedy is to adopt a newer reviewed baseline.
43
+ That is a reviewed change for an authorized human, not something to do in a
44
+ session.
45
+
37
46
  Identity comes from the tool, not from a file beside the session.
38
47
  `campaigns-os tooling status --json` (tier `B`: its only write is the
39
48
  command-lifecycle journal) reports the install mode, the version, and a source
@@ -90,8 +99,10 @@ proceeds. It checks the packet, its CampaignSpec, the artifacts and the built
90
99
  output, and its ordered check registry records what ran. When blocking errors
91
100
  exist the result is not OK, names the errors, and points the next step at
92
101
  collecting or correcting input. `campaigns-os doctor --packet <packet> --json`
93
- (tier `none`: inspection is the default, and without `--write` it leaves the
94
- target byte-identical) is the inspection form.
102
+ (tier `A`: inspection is the default, and without `--write` it leaves the
103
+ target byte-identical, but once the site is built and a public campaign key
104
+ resolves it makes one read-only request for the live campaign) is the
105
+ inspection form.
95
106
 
96
107
  Read doctor's recorded result, not a process exit code. Exit codes are knowable
97
108
  only from implementation files this skill may not cite, so do not branch on one.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: campaign-readback-classification
3
- version: 1.0.9
3
+ version: 1.0.17
4
4
  description: Classify a selected campaign from the readback projection's v2 fields and write a read-only handoff without turning diagnosis into permission.
5
5
  ---
6
6
 
7
- Bundle revision: 1.43.2+skills.1
8
- Run `npx --no-install campaigns-os tooling status --skills-revision 1.43.2+skills.1`
7
+ Bundle revision: 1.46.0+skills.1
8
+ Run `npx --no-install campaigns-os tooling status --skills-revision 1.46.0+skills.1`
9
9
  from the campaign's Page Kit folder, where it runs the project's pinned copy and
10
10
  never installs one, at the start of each task. Start a fresh session if it
11
11
  reports `mismatch`: this text is already in your context and is never re-read