@nextcommerce/campaigns-os 1.62.0 → 1.66.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/CHANGELOG.md +71 -0
- package/campaign-spec/dist/rules/campaign-currencies.d.ts +1 -0
- package/campaign-spec/dist/rules/campaign-currencies.js +1 -1
- package/campaign-spec/dist/rules/index.d.ts +1 -0
- package/campaign-spec/dist/rules/index.js +3 -0
- package/campaign-spec/dist/rules/package-currency-prices.d.ts +29 -0
- package/campaign-spec/dist/rules/package-currency-prices.js +137 -0
- package/campaign-spec/dist/types.d.ts +16 -0
- package/compatibility.json +1 -1
- package/contracts/effects.v1.json +129 -1
- package/contracts/fixtures/runtime-recipe/accept/current.json +1 -0
- package/contracts/fixtures/runtime-recipe/accept/minimal.json +1 -0
- package/contracts/fixtures/runtime-recipe/dist-states.json +1 -1
- package/contracts/fixtures/runtime-recipe/reject/advisory-enforcement.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/allowlist-without-hosts.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/committed-output-claim.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/engines-disagreement-warns.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/lifecycle-scripts-enabled.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-kind.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-network-policy.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-output-check.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-revision.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unknown-step-id.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unperformable-check-skipped.json +1 -0
- package/contracts/fixtures/runtime-recipe/reject/unpinned-lockfile.json +1 -0
- package/contracts/release-ledger.json +810 -0
- package/contracts/runtime-recipe.campaigns-os-node-v1.json +1 -0
- package/contracts/supported-surface.json +6 -6
- package/docs/build-packet.md +31 -5
- package/docs/campaigns-os-build-flow.md +38 -0
- package/docs/campaignspec-authoring-examples.md +31 -0
- package/docs/effects.md +8 -0
- package/docs/local-setup.md +1 -1
- package/docs/orientation-contract-reference.md +1 -1
- package/docs/qa-and-test-orders.md +44 -10
- package/docs/readback.md +8 -1
- package/docs/runtime-readiness.md +4 -3
- package/docs/skills-revision.md +10 -10
- package/docs/supported-surface.md +1 -1
- package/package.json +1 -1
- package/schemas/campaign-spec.v4.schema.json +15 -0
- package/schemas/campaign-spec.v5.schema.json +15 -0
- package/schemas/campaigns-os-qa-verdict.v0.schema.json +17 -2
- package/skills/campaign-lifecycle-orientation/SKILL.md +3 -3
- package/skills/campaign-readback-classification/SKILL.md +3 -3
- package/skills/campaign-run-evidence/SKILL.md +3 -3
- package/skills/contribution-intake/SKILL.md +3 -3
- package/skills/next-campaigns-build/SKILL.md +3 -3
- package/skills/next-campaigns-os/SKILL.md +3 -3
- package/skills/next-campaigns-os-setup/SKILL.md +3 -3
- package/skills/next-campaigns-polish/SKILL.md +3 -3
- package/skills/next-campaigns-qa/SKILL.md +4 -4
- package/skills/next-campaigns-sdk-bump/SKILL.md +3 -3
- package/skills.json +11 -11
- package/src/change-scope.mjs +25 -6
- package/src/cli.mjs +120 -7
- package/src/commercial-journey.mjs +3 -0
- package/src/commercial-parity.mjs +157 -7
- package/src/doctor/checks.mjs +28 -3
- package/src/doctor/ci.mjs +36 -1
- package/src/evidence-debt.mjs +9 -1
- package/src/input-currency.mjs +4 -2
- package/src/input-refresh.mjs +1 -1
- package/src/invocation.mjs +3 -2
- package/src/orchestration-stage-contract.mjs +3 -1
- package/src/polish-gate.mjs +10 -0
- package/src/qa-browser.mjs +46 -0
- package/src/qa-commercial-parity.mjs +173 -18
- package/src/qa-node.mjs +10 -5
- package/src/qa-verdict.mjs +3 -1
- package/src/readback.mjs +1 -0
- package/src/runtime-state-ignore.mjs +44 -19
- package/src/stage-ledger.mjs +35 -9
- package/src/stage-record.mjs +93 -14
package/src/stage-record.mjs
CHANGED
|
@@ -37,7 +37,9 @@ import { LOCAL_PROOF_BUILD_ENVIRONMENT, LOCAL_PROOF_PRODUCTION_ENVIRONMENT, isLo
|
|
|
37
37
|
import { polishCarriedForwardForLadder } from "./local-preview-policy.mjs";
|
|
38
38
|
import { isLoopbackHostname } from "./remit.mjs";
|
|
39
39
|
import { campaignRouteRoot, normalizePublicRouteSlug } from "./route-identity.mjs";
|
|
40
|
-
import { buildChangeEntry, buildCommerceSignature, nextBuildChanges } from "./change-scope.mjs";
|
|
40
|
+
import { buildChangeEntry, buildCommerceSignature, nextBuildChanges, rebuiltFromBuildWithoutSignature } from "./change-scope.mjs";
|
|
41
|
+
import { isNamedHuman } from "./checkpoint-waiver.mjs";
|
|
42
|
+
import { singleLineFragment } from "./text-safety.mjs";
|
|
41
43
|
import { assessEvidenceDebt } from "./evidence-debt.mjs";
|
|
42
44
|
import { writeJsonAtomic } from "./doctor-sidecar.mjs";
|
|
43
45
|
import { doctorPacket } from "./doctor/inspect.mjs";
|
|
@@ -54,7 +56,7 @@ import {
|
|
|
54
56
|
import { evaluateRecordedHiddenEagerMediaCheckpoint } from "./polish-node.mjs";
|
|
55
57
|
import { effectiveStageStatus, effectiveStatusIsTerminal, inputStamps, stageWriteInputs } from "./input-currency.mjs";
|
|
56
58
|
import { demoteStages, recordBrief, recordSpec } from "./input-refresh.mjs";
|
|
57
|
-
import { applyDerivedAssemblyReportSummary, archiveStageRecord, archiveSupersededRecord, assemblyReportMatchesPacket, commitAssemblyReport, inputChangeFor, polishReviewedBuild, qaExtendedBuildFingerprint, qaRecordedBuildFingerprint, restorePolishKeptWithOutput } from "./stage-ledger.mjs";
|
|
59
|
+
import { applyDerivedAssemblyReportSummary, archiveStageRecord, archiveForBaseline, archiveSupersededRecord, assemblyReportMatchesPacket, campaignHandedOff, qaEverCompleted, commitAssemblyReport, inputChangeFor, polishReviewedBuild, qaExtendedBuildFingerprint, qaRecordedBuildFingerprint, restorePolishKeptWithOutput } from "./stage-ledger.mjs";
|
|
58
60
|
import { withTargetLockSync } from "./target-lock.mjs";
|
|
59
61
|
import { commerceScopeFromScope } from "./theme-gate.mjs";
|
|
60
62
|
|
|
@@ -71,7 +73,9 @@ const BUILD_RECORD_FLAGS = Object.freeze(["build-environment", "adapter-decision
|
|
|
71
73
|
// discovery finds it).
|
|
72
74
|
const BRIEF_RECORD_FLAGS = Object.freeze(["brief"]);
|
|
73
75
|
// `record spec` binds the CampaignSpec as it is now; it takes no extra flag.
|
|
74
|
-
|
|
76
|
+
// `record baseline` accepts the current build as the starting point: who, and why.
|
|
77
|
+
const BASELINE_RECORD_FLAGS = Object.freeze(["accepted-by", "reason"]);
|
|
78
|
+
const RECORD_SUBCOMMANDS = Object.freeze([...RECORD_STAGES, "brief", "spec", "baseline"]);
|
|
75
79
|
// The page-kit environment the built output was rendered in, recorded on
|
|
76
80
|
// stages.assembly.evidence.build_environment (local proof mode builds in
|
|
77
81
|
// development; doctor and page-kit parity read it).
|
|
@@ -174,7 +178,7 @@ export function parseRecordArgs(args) {
|
|
|
174
178
|
if (!RECORD_SUBCOMMANDS.includes(stage) || args._.length !== 2) {
|
|
175
179
|
throw refused(`Use: ${cmd("record")} <${RECORD_SUBCOMMANDS.join("|")}> --packet <campaign-runtime.build.json> [--context <json>] [--report <json>] [--dry-run] [--json]; record polish also takes --evidence <polish-evidence.json>, record deploy --base-url <served url>, record build [--build-environment <${BUILD_ENVIRONMENTS.join("|")}>] [--adapter-decision <key=value[,key=value...]>], and record brief [--brief <yaml|json>].`);
|
|
176
180
|
}
|
|
177
|
-
const known = new Set([...RECORD_FLAGS, ...(stage === "polish" ? POLISH_RECORD_FLAGS : []), ...(stage === "deploy" ? DEPLOY_RECORD_FLAGS : []), ...(stage === "build" ? BUILD_RECORD_FLAGS : []), ...(stage === "brief" ? BRIEF_RECORD_FLAGS : [])]);
|
|
181
|
+
const known = new Set([...RECORD_FLAGS, ...(stage === "polish" ? POLISH_RECORD_FLAGS : []), ...(stage === "deploy" ? DEPLOY_RECORD_FLAGS : []), ...(stage === "build" ? BUILD_RECORD_FLAGS : []), ...(stage === "brief" ? BRIEF_RECORD_FLAGS : []), ...(stage === "baseline" ? BASELINE_RECORD_FLAGS : [])]);
|
|
178
182
|
const unknown = Object.keys(args).filter((key) => key !== "_" && !known.has(key));
|
|
179
183
|
if (unknown.length) {
|
|
180
184
|
throw refused(`Unknown flag${unknown.length > 1 ? "s" : ""} for record ${stage}: ${unknown.map((key) => `--${key}`).join(", ")}. Known flags: ${[...known].map((key) => `--${key}`).join(", ")}.`);
|
|
@@ -186,6 +190,10 @@ export function parseRecordArgs(args) {
|
|
|
186
190
|
throw refused(`--dry-run takes no value (got ${JSON.stringify(args["dry-run"])}); write \`--dry-run\` on its own, after the other flags.`);
|
|
187
191
|
}
|
|
188
192
|
if (Object.hasOwn(args, "json") && args.json !== true) throw refused("--json is a boolean flag and takes no value.");
|
|
193
|
+
if (stage === "baseline") {
|
|
194
|
+
if (!isNamedHuman(args["accepted-by"])) throw refused("record baseline: --accepted-by needs the named person accepting this build; placeholders and automation identities are not accepted.");
|
|
195
|
+
if (!optionalString(args.reason)) throw refused("record baseline: --reason needs the reason this build is being accepted untested.");
|
|
196
|
+
}
|
|
189
197
|
const buildEnvironment = Object.hasOwn(args, "build-environment") ? args["build-environment"] : null;
|
|
190
198
|
if (buildEnvironment !== null && !BUILD_ENVIRONMENTS.includes(buildEnvironment)) {
|
|
191
199
|
throw refused(`--build-environment must be one of: ${BUILD_ENVIRONMENTS.join(", ")} (got ${JSON.stringify(buildEnvironment)}).`);
|
|
@@ -201,6 +209,7 @@ export function parseRecordArgs(args) {
|
|
|
201
209
|
adapterDecisions,
|
|
202
210
|
briefPath: stage === "brief" && Object.hasOwn(args, "brief") ? resolve(args.brief) : null,
|
|
203
211
|
deviationReason: optionalString(args["deviation-reason"]),
|
|
212
|
+
baseline: stage === "baseline" ? { acceptedBy: args["accepted-by"].trim().replace(/\s+/g, " "), reason: args.reason.trim() } : null,
|
|
204
213
|
};
|
|
205
214
|
}
|
|
206
215
|
|
|
@@ -322,7 +331,7 @@ function composeSetup(report, context, { now, recordedBy }) {
|
|
|
322
331
|
// operator decision that applies only to the record that carried it.
|
|
323
332
|
const STAMP_FIELDS = Object.freeze(["source_brief_material", "source_spec_material_hash"]);
|
|
324
333
|
// A replaced record in any of these statuses is checked for an input change.
|
|
325
|
-
const REPLACED_COMPLETED_STATUSES = Object.freeze(["completed", "completed_with_warnings", "completed_partial"]);
|
|
334
|
+
const REPLACED_COMPLETED_STATUSES = Object.freeze(["completed", "completed_with_warnings", "completed_partial", "baseline_accepted"]);
|
|
326
335
|
|
|
327
336
|
// The stamps a completed record writes: the inputs current at this write
|
|
328
337
|
// (the absent brief value for a packet without a brief). A value that cannot
|
|
@@ -473,6 +482,73 @@ function deployAfterBuild(previousDeploy, fingerprint, now) {
|
|
|
473
482
|
return archiveSupersededRecord(demoted, previousDeploy, { by: "record build", reason: "build_output_changed", at: now });
|
|
474
483
|
}
|
|
475
484
|
|
|
485
|
+
// `record baseline`: a named person accepts a funnel from before markup hashes
|
|
486
|
+
// as the starting point for page-only changes, without testing it. Polish and
|
|
487
|
+
// QA take the status baseline_accepted, which ends the ladder but is never
|
|
488
|
+
// completed; what they held moves to history, so its purchase proof stops
|
|
489
|
+
// counting. One time per funnel: report.commerce_baseline stays after a later
|
|
490
|
+
// full QA, so this can never be a way past QA that a commerce change owes.
|
|
491
|
+
function composeBaseline(report, { now, recordedBy, fingerprint, input, inputs, packet, packetPath }) {
|
|
492
|
+
const earlier = report.commerce_baseline;
|
|
493
|
+
if (isObject(earlier)) {
|
|
494
|
+
throw refuseRecord("baseline", [`${earlier.accepted_by} accepted a baseline for this funnel at ${earlier.accepted_at}, and a funnel gets one. Run ${cmd("qa")} run for what is owed now.`]);
|
|
495
|
+
}
|
|
496
|
+
if (!campaignHandedOff(report)) {
|
|
497
|
+
throw refuseRecord("baseline", ["This funnel has no completed deploy or QA, now or in its history. A baseline is for a funnel already handed off; run the full stages."]);
|
|
498
|
+
}
|
|
499
|
+
if (!rebuiltFromBuildWithoutSignature(report.stages?.assembly)) {
|
|
500
|
+
throw refuseRecord("baseline", [`This funnel's records have markup hashes (Campaigns OS 1.62.0 or later), or ${cmd("record")} build has not run on this toolkit. Page-only changes can be read from a full QA; a baseline is only for records from before those hashes. If you have not, run ${cmd("record")} build first.`]);
|
|
501
|
+
}
|
|
502
|
+
// A baseline stands in for a QA that passed on an older toolkit, so it needs
|
|
503
|
+
// one. A blocked QA found a problem a sign-off cannot vouch for.
|
|
504
|
+
const lastQa = stageObject(report, "qa");
|
|
505
|
+
if (lastQa.status === "blocked") {
|
|
506
|
+
const blockers = (Array.isArray(lastQa.blockers) ? lastQa.blockers : []).slice(0, 3).map((blocker) => singleLineFragment(typeof blocker === "string" ? blocker : blocker?.message || blocker?.code || "")).filter(Boolean);
|
|
507
|
+
throw refuseRecord("baseline", [`The last QA was blocked${blockers.length ? ` (${blockers.join("; ")})` : ""}, so it cannot be accepted untested. Run a full ${cmd("qa")} run; once it passes, page-only changes work without a baseline.`]);
|
|
508
|
+
}
|
|
509
|
+
if (!qaEverCompleted(report)) {
|
|
510
|
+
throw refuseRecord("baseline", [`No QA has completed on this funnel, so there is no result to start from. Run a full ${cmd("qa")} run; once it passes, page-only changes work without a baseline.`]);
|
|
511
|
+
}
|
|
512
|
+
const debt = assessEvidenceDebt({ packet, packetPath, report }).qa;
|
|
513
|
+
if (debt.status !== "owed" || debt.scope !== "full") {
|
|
514
|
+
throw refuseRecord("baseline", [`A full QA is not owed (QA reads ${debt.status}${debt.scope ? `, scope ${debt.scope}` : ""}), so there is nothing to accept.`]);
|
|
515
|
+
}
|
|
516
|
+
const stamps = currentStamps(inputs);
|
|
517
|
+
// History keeps no warnings or skip reasons, and a baseline must still say
|
|
518
|
+
// what it replaced.
|
|
519
|
+
const replacedRecord = (key) => {
|
|
520
|
+
const before = stageObject(report, key);
|
|
521
|
+
return { status: before.status ?? null, blockers: before.blockers ?? [], warnings: before.warnings ?? [], ...(before.skip_reason ? { skip_reason: before.skip_reason } : {}) };
|
|
522
|
+
};
|
|
523
|
+
const replace = (key, fields) => {
|
|
524
|
+
const previous = stageObject(report, key);
|
|
525
|
+
return archiveForBaseline({
|
|
526
|
+
stage: key,
|
|
527
|
+
status: "baseline_accepted",
|
|
528
|
+
recorded_by: recordedBy,
|
|
529
|
+
checked_at: now,
|
|
530
|
+
baseline_accepted_at: now,
|
|
531
|
+
...stamps,
|
|
532
|
+
blockers: [],
|
|
533
|
+
warnings: [],
|
|
534
|
+
outputs: [],
|
|
535
|
+
...fields,
|
|
536
|
+
}, previous, { at: now });
|
|
537
|
+
};
|
|
538
|
+
return {
|
|
539
|
+
report: {
|
|
540
|
+
...report,
|
|
541
|
+
commerce_baseline: { accepted_by: input.acceptedBy, accepted_at: now, reason: input.reason, build_fingerprint: fingerprint, replaced_polish: replacedRecord("polish"), replaced_qa: replacedRecord("qa") },
|
|
542
|
+
stages: {
|
|
543
|
+
...report.stages,
|
|
544
|
+
polish: replace("polish", { source_build_fingerprint: fingerprint }),
|
|
545
|
+
qa: replace("qa", { evidence: { source_build_fingerprint: fingerprint } }),
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
context: null,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
476
552
|
function composePolish(report, { now, recordedBy, fingerprint, input, inputs = {} }) {
|
|
477
553
|
const previous = stageObject(report, "polish");
|
|
478
554
|
const completed = POLISH_COMPLETED_STATUSES.includes(input.status);
|
|
@@ -1009,7 +1085,7 @@ function doctorFacts(stage, doctor, report, packet) {
|
|
|
1009
1085
|
const binding = bindingProblems(doctor, report, packet);
|
|
1010
1086
|
if (binding.length) throw refuseRecord(stage, binding);
|
|
1011
1087
|
// The brand layer is applied to built output, so theme is checked as polish is.
|
|
1012
|
-
const ladder = ladderProblems(stage === "theme" ? "polish" : stage, doctor, report);
|
|
1088
|
+
const ladder = ladderProblems(stage === "theme" || stage === "baseline" ? "polish" : stage, doctor, report);
|
|
1013
1089
|
if (ladder.length) throw refuseRecord(stage, ladder);
|
|
1014
1090
|
if (stage === "setup") {
|
|
1015
1091
|
const outputDir = optionalString(derived.target_output_dir);
|
|
@@ -1026,13 +1102,13 @@ function doctorFacts(stage, doctor, report, packet) {
|
|
|
1026
1102
|
if (stage === "build" && derived.scaffold_required === true) {
|
|
1027
1103
|
throw refuseRecord(stage, [`Setup is still required (${derived.scaffold_reason || "Build Context scaffold.required is true"}); run ${cmd("record")} setup first.`]);
|
|
1028
1104
|
}
|
|
1029
|
-
if (stage === "polish" || stage === "theme" || stage === "deploy") {
|
|
1105
|
+
if (stage === "polish" || stage === "theme" || stage === "deploy" || stage === "baseline") {
|
|
1030
1106
|
const recorded = optionalString(report?.stages?.assembly?.build_fingerprint);
|
|
1031
1107
|
if (!String(report?.stages?.assembly?.status || "").startsWith("completed") || !recorded) {
|
|
1032
1108
|
throw refuseRecord(stage, [`Build is not recorded (stages.assembly needs a completed status and build_fingerprint); run ${cmd("record")} build first.`]);
|
|
1033
1109
|
}
|
|
1034
1110
|
if (recorded !== fingerprint) {
|
|
1035
|
-
throw refuseRecord(stage, [stage === "theme" || stage === "deploy"
|
|
1111
|
+
throw refuseRecord(stage, [stage === "theme" || stage === "deploy" || stage === "baseline"
|
|
1036
1112
|
? `The built output changed since build was recorded (recorded ${recorded}, current ${fingerprint}); run ${cmd("record")} build, then record ${stage} again.`
|
|
1037
1113
|
: `The built output changed since build was recorded (recorded ${recorded}, current ${fingerprint}); run ${cmd("record")} build, then ${cmd("polish")} capture, then record polish again.`]);
|
|
1038
1114
|
}
|
|
@@ -1143,12 +1219,12 @@ function readPacketFile(stage, packetPath) {
|
|
|
1143
1219
|
* composed or written.
|
|
1144
1220
|
*/
|
|
1145
1221
|
export function recordStageCommand(args, { now = () => new Date(), beforeLock = null, afterDoctorRead = null, probe = null } = {}) {
|
|
1146
|
-
const { stage, packetPath, evidencePath, dryRun, buildEnvironment, adapterDecisions, deviationReason } = parseRecordArgs(args);
|
|
1222
|
+
const { stage, packetPath, evidencePath, dryRun, buildEnvironment, adapterDecisions, deviationReason, baseline } = parseRecordArgs(args);
|
|
1147
1223
|
if (!existsSync(packetPath)) throw new Error(`record ${stage}: Build Packet not found at ${packetPath}; run ${cmd("start")} or ${cmd("prepare-build")} first.`);
|
|
1148
1224
|
if (stage === "deploy" && !probe) throw new Error("record deploy needs the served-route probe; run it through recordCommand.");
|
|
1149
1225
|
// Operator input, not target state: no campaigns-os writer produces it.
|
|
1150
1226
|
// For deploy, the probe of the running server (probeLocalPreview).
|
|
1151
|
-
const input = stage === "polish" ? readPolishEvidenceFile(evidencePath) : stage === "deploy" ? probe :
|
|
1227
|
+
const input = stage === "polish" ? readPolishEvidenceFile(evidencePath) : stage === "deploy" ? probe : baseline;
|
|
1152
1228
|
const sidecars = {
|
|
1153
1229
|
contextPath: args.context ? resolve(args.context) : undefined,
|
|
1154
1230
|
reportPath: args.report ? resolve(args.report) : undefined,
|
|
@@ -1169,7 +1245,7 @@ export function recordStageCommand(args, { now = () => new Date(), beforeLock =
|
|
|
1169
1245
|
const recorded = dryRun ? run() : withTargetLockSync(lockedTarget, run, { command: `record ${stage}` });
|
|
1170
1246
|
const { composed, facts, layer, reportPath, contextPath, after } = recorded;
|
|
1171
1247
|
|
|
1172
|
-
const stageKey = stage === "build" ? "assembly" : stage;
|
|
1248
|
+
const stageKey = stage === "build" ? "assembly" : stage === "baseline" ? "qa" : stage;
|
|
1173
1249
|
const writes = [...(composed.context ? [contextPath] : []), ...(composed.packet ? [packetPath] : []), reportPath];
|
|
1174
1250
|
const ready = stage === "deploy" ? [
|
|
1175
1251
|
`stages.deploy.status = completed; deploy.preview_url = ${input.url}`,
|
|
@@ -1186,6 +1262,7 @@ export function recordStageCommand(args, { now = () => new Date(), beforeLock =
|
|
|
1186
1262
|
...(stage === "build" && adapterDecisions ? Object.entries(adapterDecisions).map(([key, value]) => `report.adapter_decisions.${key} = ${value}`) : []),
|
|
1187
1263
|
...(stage === "build" ? buildChangeReadyLines(composed.report.stages.assembly) : []),
|
|
1188
1264
|
...(stage === "build" ? [`stages.polish.status = ${composed.report.stages.polish.status}`] : []),
|
|
1265
|
+
...(stage === "baseline" ? [`Polish and QA read baseline_accepted: ${baseline.acceptedBy} accepted build ${facts.fingerprint}. Nothing was tested, and no purchase is proven from here until a full ${cmd("qa")} run.`] : []),
|
|
1189
1266
|
...(stage === "build" && composed.report.stages.deploy?.required_by === "build" ? [`stages.deploy.status = ${composed.report.stages.deploy.status} (the recorded deploy probed other output; run ${cmd("record")} deploy again)`] : []),
|
|
1190
1267
|
...(composed.context ? ["Build Context scaffold.required = false"] : []),
|
|
1191
1268
|
];
|
|
@@ -1200,7 +1277,7 @@ export function recordStageCommand(args, { now = () => new Date(), beforeLock =
|
|
|
1200
1277
|
...(dryRun ? { would_write: writes } : { written: writes }),
|
|
1201
1278
|
build_fingerprint: facts.fingerprint || null,
|
|
1202
1279
|
// The manifest is kept in the report, not echoed: it lists every built file.
|
|
1203
|
-
record: stage === "theme" ? composed.report.theme : stage === "build" ? withoutKeys(composed.report.stages.assembly, ["build_manifest", "commerce_signature"]) : composed.report.stages[stageKey],
|
|
1280
|
+
record: stage === "baseline" ? composed.report.commerce_baseline : stage === "theme" ? composed.report.theme : stage === "build" ? withoutKeys(composed.report.stages.assembly, ["build_manifest", "commerce_signature"]) : composed.report.stages[stageKey],
|
|
1204
1281
|
...(stage === "build" ? { polish: composed.report.stages.polish } : {}),
|
|
1205
1282
|
...(composed.context ? { scaffold: composed.context.scaffold } : {}),
|
|
1206
1283
|
...(stage === "polish" && input.hasRepairLoopDefect ? { repair_loop_defect: input.repairLoopDefect } : {}),
|
|
@@ -1273,7 +1350,9 @@ function recordUnderLock({ stage, packetPath, sidecars, lockedTarget, input, dry
|
|
|
1273
1350
|
? composeTheme(report, { now: timestamp, recordedBy, layer })
|
|
1274
1351
|
: stage === "deploy"
|
|
1275
1352
|
? composeDeploy(report, packet, { now: timestamp, recordedBy, probe: input, fingerprint: facts.fingerprint })
|
|
1276
|
-
:
|
|
1353
|
+
: stage === "baseline"
|
|
1354
|
+
? composeBaseline(report, { now: timestamp, recordedBy, fingerprint: facts.fingerprint, input, inputs: recordInputs(doctor), packet, packetPath })
|
|
1355
|
+
: composePolish(report, { now: timestamp, recordedBy, fingerprint: facts.fingerprint, input, inputs: recordInputs(doctor) });
|
|
1277
1356
|
applyDerivedAssemblyReportSummary(next.report, recordInputs(doctor));
|
|
1278
1357
|
// The packet's one new value, deploy.preview_url, is checked by
|
|
1279
1358
|
// deployPreviewUrl; the rest of the packet is as the operator left it.
|
|
@@ -1290,7 +1369,7 @@ function recordUnderLock({ stage, packetPath, sidecars, lockedTarget, input, dry
|
|
|
1290
1369
|
// Already inside the target lock, which commitAssemblyReport re-enters.
|
|
1291
1370
|
commitAssemblyReport(workspace, compose, {
|
|
1292
1371
|
command: `record ${stage}`,
|
|
1293
|
-
staleReason: `${stage === "theme" ? "theme" : `stages.${stage === "build" ? "assembly" : stage}`} was recorded after this doctor snapshot. Re-run ${cmd("doctor")} (or next) for current state.`,
|
|
1372
|
+
staleReason: `${stage === "theme" ? "theme" : stage === "baseline" ? "a baseline" : `stages.${stage === "build" ? "assembly" : stage}`} was recorded after this doctor snapshot. Re-run ${cmd("doctor")} (or next) for current state.`,
|
|
1294
1373
|
...(dryRun ? { lock: false } : {}),
|
|
1295
1374
|
});
|
|
1296
1375
|
const after = dryRun ? null : doctorPacket(packetPath, sidecars);
|