@tokenoftrust/cli 1.4.0-rc.13 → 1.4.0-rc.15
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/bin/tot.mjs +9 -8
- package/package.json +1 -1
- package/src/commands/accept.mjs +225 -159
- package/src/commands/pr.mjs +194 -7
- package/src/commands/ship.mjs +267 -1147
- package/src/plan.mjs +58 -29
package/src/plan.mjs
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* The shared "operation plan" affordance (unit U10) — the load-bearing
|
|
3
3
|
* cross-cutting requirement from decision `operator-verb-and-hosting-model`:
|
|
4
4
|
* every MUTATING operator verb (build / accept / ship / retire) must STATE
|
|
5
|
-
* EXACTLY what it will do — which PR
|
|
6
|
-
* (preview / live) are touched, and their URLs — and get an
|
|
7
|
-
* before acting.
|
|
5
|
+
* EXACTLY what it will do — which PR is queued/integrated/deployed, which
|
|
6
|
+
* deploy targets (preview / live) are touched, and their URLs — and get an
|
|
7
|
+
* explicit confirm before acting. (`accept` now queue-integrates a PR into the
|
|
8
|
+
* `preview` aggregate — unit b08 — rather than merging it to main.) No silent multi-step mutations, in either surface (the CLI
|
|
8
9
|
* here, and `AdminPublishTab.astro`'s confirm dialog, which renders the same
|
|
9
10
|
* shape of plan text server-side/inline).
|
|
10
11
|
*
|
|
@@ -14,13 +15,14 @@
|
|
|
14
15
|
* half — print the plan, then gate on an explicit yes (reusing `prompt.mjs`'s
|
|
15
16
|
* TTY-safe `promptYesNo`; a non-TTY without `--yes` never silently proceeds).
|
|
16
17
|
*
|
|
17
|
-
* SHIP
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* SHIP has ONE meaning (unit b10 — supersedes the retired context-dependent
|
|
19
|
+
* ship, decision `ship-context-dependent-semantics`): it publishes the
|
|
20
|
+
* tenant's CURRENT GREEN AGGREGATE — the batch of PRs that integrated
|
|
21
|
+
* cleanly — to live. No merge, no PR/candidate targeting, no developer-vs-
|
|
22
|
+
* operator branching. The plan states the pinned aggregate sha, its
|
|
23
|
+
* content-addressed artifact digest, every included PR, the rollback target,
|
|
24
|
+
* and the go-live paywall verdict, so the human reviews EXACTLY what "green"
|
|
25
|
+
* means before confirming.
|
|
24
26
|
*
|
|
25
27
|
* Dependency-free (no imports besides the sibling `prompt.mjs`).
|
|
26
28
|
*/
|
|
@@ -52,6 +54,11 @@ function targetLabel({ pr, changeId }) {
|
|
|
52
54
|
* endpoint?: string|null,
|
|
53
55
|
* targets?: { preview?: string|null, live?: string|null },
|
|
54
56
|
* context?: "developer"|"operator",
|
|
57
|
+
* pinnedSha?: string|null,
|
|
58
|
+
* artifactDigest?: string|null,
|
|
59
|
+
* includedPrs?: Array<{ prNumber?: number|null, changeId?: string|null, headSha?: string|null }>,
|
|
60
|
+
* rollbackTarget?: { receiptId: string, aggregateSha: string } | null,
|
|
61
|
+
* paywall?: { allowed: boolean, message?: string|null } | null,
|
|
55
62
|
* }} params
|
|
56
63
|
* @returns {string[]} plan lines (no leading/trailing blank line)
|
|
57
64
|
*/
|
|
@@ -64,7 +71,14 @@ export function planForAction({
|
|
|
64
71
|
endpoint = null,
|
|
65
72
|
targets = {},
|
|
66
73
|
context = "operator",
|
|
74
|
+
pinnedSha = null,
|
|
75
|
+
artifactDigest = null,
|
|
76
|
+
includedPrs = null,
|
|
77
|
+
rollbackTarget = null,
|
|
78
|
+
paywall = null,
|
|
67
79
|
}) {
|
|
80
|
+
void context; // retained param — no action currently branches on it (ship, the
|
|
81
|
+
// last one that did, is now ONE meaning; kept so a future action can opt in).
|
|
68
82
|
const label = targetLabel({ pr, changeId });
|
|
69
83
|
const lines = [`${titleFor(action)} plan:`];
|
|
70
84
|
if (tenant) lines.push(` tenant: ${tenant}`);
|
|
@@ -82,21 +96,43 @@ export function planForAction({
|
|
|
82
96
|
break;
|
|
83
97
|
}
|
|
84
98
|
case "accept": {
|
|
85
|
-
|
|
99
|
+
// Accept now means QUEUE-INTEGRATE-INTO-PREVIEW (unit b08), NOT merge-to-main:
|
|
100
|
+
// the candidate lands in the protected `preview` aggregate (serialized merge →
|
|
101
|
+
// rebuild → combined-evidence gate), and the aggregate goes live only later via
|
|
102
|
+
// `tot ship`. So the plan states the integration, never a merge or a go-live.
|
|
103
|
+
const into = tenant ? `${tenant}'s preview aggregate` : "the preview aggregate";
|
|
104
|
+
lines.push(
|
|
105
|
+
` effect: queue ${label} for integration into ${into} — NO merge to main, NO go-live.`,
|
|
106
|
+
);
|
|
86
107
|
break;
|
|
87
108
|
}
|
|
88
109
|
case "ship": {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
// ONE meaning (b10): publish the tenant's CURRENT GREEN AGGREGATE to live.
|
|
111
|
+
// No merge — b09's orchestrator ships the already-materialized, already-
|
|
112
|
+
// reviewed digest. State exactly WHAT that is: the pinned sha, the
|
|
113
|
+
// content-addressed artifact digest, every included PR, the rollback
|
|
114
|
+
// target, and the go-live paywall verdict — so the human reviews the
|
|
115
|
+
// real content of "green" before confirming.
|
|
116
|
+
lines.push(
|
|
117
|
+
` effect: publish the current green aggregate to live${targets.live ? ` (${targets.live})` : ""} — no merge.`,
|
|
118
|
+
);
|
|
119
|
+
if (pinnedSha) lines.push(` pinned sha: ${pinnedSha}`);
|
|
120
|
+
if (artifactDigest) lines.push(` artifact digest: ${artifactDigest}`);
|
|
121
|
+
if (Array.isArray(includedPrs)) {
|
|
122
|
+
lines.push(` included PRs (${includedPrs.length}):`);
|
|
123
|
+
for (const p of includedPrs) {
|
|
124
|
+
const prLabel = p && p.prNumber != null ? `#${p.prNumber}` : p?.changeId || "(no PR)";
|
|
125
|
+
const shortSha = p && p.headSha ? ` ${String(p.headSha).slice(0, 8)}` : "";
|
|
126
|
+
lines.push(` - ${prLabel}${shortSha}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
lines.push(
|
|
130
|
+
rollbackTarget
|
|
131
|
+
? ` rollback to: ${rollbackTarget.aggregateSha} (receipt ${rollbackTarget.receiptId})`
|
|
132
|
+
: " rollback to: (none — first-ever ship)",
|
|
133
|
+
);
|
|
134
|
+
if (paywall && paywall.allowed === false) {
|
|
135
|
+
lines.push(` ⚠ paywall: ${paywall.message || "go-live is blocked by the storefront subscription gate"}`);
|
|
100
136
|
}
|
|
101
137
|
break;
|
|
102
138
|
}
|
|
@@ -121,13 +157,6 @@ function titleFor(action) {
|
|
|
121
157
|
return action ? action[0].toUpperCase() + action.slice(1) : "Operation";
|
|
122
158
|
}
|
|
123
159
|
|
|
124
|
-
/** Render "preview (<url>) + live (<url>)", degrading gracefully when a URL is unknown. Pure. */
|
|
125
|
-
function deployTargetsLine(targets = {}) {
|
|
126
|
-
const preview = targets.preview ? `preview (${targets.preview})` : "preview";
|
|
127
|
-
const live = targets.live ? `live (${targets.live})` : "live";
|
|
128
|
-
return `${preview} + ${live}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
160
|
/**
|
|
132
161
|
* Print a plan and gate on an explicit confirm — the CLI half of the shared
|
|
133
162
|
* affordance. Prints every line, a trailing blank line, then:
|