@codeyam-editor/codeyam-editor 0.1.8 → 0.1.9

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 (35) hide show
  1. package/npm/cloud.js +361 -18
  2. package/npm/editor-dev.js +24 -2
  3. package/npm/editor.js +15 -5
  4. package/npm/fleet-add-failure.js +355 -21
  5. package/npm/fleet-branch.js +124 -1
  6. package/npm/fleet-build-data.js +12 -1
  7. package/npm/fleet-cleanup.js +84 -3
  8. package/npm/fleet-datastore-health.js +107 -0
  9. package/npm/fleet-revision.js +108 -0
  10. package/npm/fleet-run-setup.js +66 -0
  11. package/npm/fleet-session-activity.js +162 -0
  12. package/npm/fleet-vm-readiness.js +57 -16
  13. package/npm/launch-config.js +126 -2
  14. package/npm/scenario-check.js +697 -34
  15. package/npm/scenario-interactivity.js +477 -23
  16. package/npm/scenario-issues.js +61 -0
  17. package/npm/scenario-playwright.js +67 -14
  18. package/npm/utils.js +262 -22
  19. package/package.json +5 -5
  20. package/ui/dist/assets/{ContainerInspector-DSVZvEkB.js → ContainerInspector-C7I9wctz.js} +1 -1
  21. package/ui/dist/assets/EditorShell-9aYMeOdZ.js +330 -0
  22. package/ui/dist/assets/EditorShell-CE8iG_EM.css +1 -0
  23. package/ui/dist/assets/ScenarioDataPanel-CLwkrex0.js +18 -0
  24. package/ui/dist/assets/{ScenarioDataPanelFullPage-Cyk6efiI.js → ScenarioDataPanelFullPage-DT0TZC4M.js} +1 -1
  25. package/ui/dist/assets/ScenarioRenderer-Bsarhcrl.js +21 -0
  26. package/ui/dist/assets/index-BRoAenc1.css +1 -0
  27. package/ui/dist/assets/{index-BPLKgwon.js → index-iMR5siiG.js} +9 -9
  28. package/ui/dist/index.html +2 -2
  29. package/ui/dist/preview-subdir-demo/index.html +62 -0
  30. package/ui/dist/preview-subdir-demo/sibling.html +48 -0
  31. package/ui/dist/assets/EditorShell-DnXdHrar.js +0 -256
  32. package/ui/dist/assets/EditorShell-FvbXyqqD.css +0 -1
  33. package/ui/dist/assets/ScenarioDataPanel-6qJE-0lA.js +0 -15
  34. package/ui/dist/assets/ScenarioRenderer-Uyt54vHI.js +0 -21
  35. package/ui/dist/assets/index-DKpgJLo3.css +0 -1
@@ -71,6 +71,12 @@
71
71
  // 'cli-missing' — the editor is up but the build-agent CLI isn't installed
72
72
  // (`providerAuth.cliInstalled === false`): a provisioning
73
73
  // fault, not a usable VM.
74
+ // 'cli-broken' — the CLI IS installed but will not execute
75
+ // (`providerAuth.cliRunnable === false`): a provisioning
76
+ // fault whose remedy is repair/reinstall, not a fresh
77
+ // install and not re-authentication. Distinct from
78
+ // 'cli-missing' because sending an operator to "install it"
79
+ // when it is already installed wastes the incident.
74
80
  // 'auth-unsettled' — the auth probe is wedged at `unknown` (timing out — the
75
81
  // exact VM5 auth shape): we can't even tell if login is
76
82
  // needed. A provisioning fault distinct from a clean
@@ -89,6 +95,7 @@ const READINESS_STATES = [
89
95
  'broker-down',
90
96
  'ws-dead',
91
97
  'cli-missing',
98
+ 'cli-broken',
92
99
  'auth-unsettled',
93
100
  'needs-login',
94
101
  'ready',
@@ -100,23 +107,41 @@ const fpub = require('./fleet-public-url');
100
107
 
101
108
  // Classify the build-agent usability axis from the editor's own `providerAuth`
102
109
  // subtree (vm: Readiness Verifies Real Usability). control-api emits the SAME
103
- // `providerAuth` (with `cliInstalled` + settled `state`) on BOTH /api/config and
104
- // /api/session-info via build_provider_auth_json, so the dashboard reads it
105
- // straight off the gathered VM record — no re-derivation. Returns one verdict:
110
+ // `providerAuth` (with `cliInstalled` + `cliRunnable` + settled `state`) on BOTH
111
+ // /api/config and /api/session-info via build_provider_auth_json, so the
112
+ // dashboard reads it straight off the gathered VM record — no re-derivation.
113
+ // Returns one verdict:
106
114
  // 'cli-missing' — `cliInstalled === false`: a provisioning fault.
107
- // 'auth-unsettled' — CLI installed but `state === 'unknown'` (probe wedged /
108
- // timing out — the VM5 shape): a provisioning fault.
109
- // 'needs-login' — CLI installed, `state === 'needs-login'`: a LEGITIMATE,
115
+ // 'cli-broken' — CLI installed but `cliRunnable === false`: it resolves
116
+ // and will not execute. A provisioning fault.
117
+ // 'auth-unsettled' — CLI installed and runnable (or unprobed) but
118
+ // `state === 'unknown'` (probe wedged / timing out — the
119
+ // VM5 shape): a provisioning fault.
120
+ // 'needs-login' — CLI usable, `state === 'needs-login'`: a LEGITIMATE,
110
121
  // actionable state, not a fault.
111
- // 'ready' — CLI installed, `state === 'authenticated'`: usable.
122
+ // 'ready' — CLI usable, `state === 'authenticated'`: usable.
112
123
  // 'unknown' — providerAuth absent (older editor / probe never landed),
113
124
  // or any other shape: absence of evidence, never a fault.
114
- // Only an EXPLICIT `cliInstalled === false` / `state === 'unknown'` is a fault;
115
- // missing fields stay 'unknown' so an older editor never reads as broken.
125
+ // Only an EXPLICIT `cliInstalled === false` / `cliRunnable === false` /
126
+ // `state === 'unknown'` is a fault; missing fields stay 'unknown' so an older
127
+ // editor never reads as broken.
128
+ //
129
+ // The `cli-broken` check sits BEFORE the state checks deliberately, and that
130
+ // ordering IS the fix. A CLI that will not execute cannot have authenticated,
131
+ // so `state: 'authenticated'` alongside `cliRunnable: false` is not a
132
+ // contradiction to be resolved in the state's favour — it is precisely the
133
+ // false green observed on VM-2 and VM-7 through the whole 2026-08-19/20 npm
134
+ // publish race, where the card read `cliInstalled: true, state: "authenticated"`
135
+ // while both boxes were unusable and the breakage was found only by ssh.
136
+ //
137
+ // `cliRunnable` is deliberately NOT required to be `true` to proceed: an older
138
+ // editor server omits it entirely (`undefined`), and treating that as a fault
139
+ // would mark every not-yet-upgraded VM in the fleet broken.
116
140
  function classifyAgentUsability(providerAuth) {
117
141
  if (!providerAuth || typeof providerAuth !== 'object') return 'unknown';
118
142
  if (providerAuth.cliInstalled === false) return 'cli-missing';
119
143
  if (providerAuth.cliInstalled !== true) return 'unknown';
144
+ if (providerAuth.cliRunnable === false) return 'cli-broken';
120
145
  if (providerAuth.state === 'unknown') return 'auth-unsettled';
121
146
  if (providerAuth.state === 'needs-login') return 'needs-login';
122
147
  if (providerAuth.state === 'authenticated') return 'ready';
@@ -126,8 +151,13 @@ function classifyAgentUsability(providerAuth) {
126
151
  // Map the agent-usability verdict to the tri-state the rostering gate
127
152
  // (fleet-cleanup's reapVerdict) consumes, mirroring `projectWired`'s
128
153
  // true|false|null contract so a flaky probe can never strand a healthy VM:
129
- // false — a PROVISIONING FAULT ('cli-missing' / 'auth-unsettled'): the box is
130
- // not usable, so don't roster it as healthy (the VM5 false-green).
154
+ // false — a PROVISIONING FAULT ('cli-missing' / 'cli-broken' /
155
+ // 'auth-unsettled'): the box is not usable, so don't roster it as
156
+ // healthy (the VM5 false-green). 'cli-broken' joins the fault set
157
+ // because a CLI that will not execute makes the VM exactly as
158
+ // unusable as one that was never installed — the two differ only in
159
+ // the remedy an operator should reach for, not in whether the box can
160
+ // do work.
131
161
  // true — usable enough to roster: 'ready', or a legitimate 'needs-login'
132
162
  // (the user must auth, but that's an expected state, not a
133
163
  // half-provisioned box that should be held back).
@@ -135,7 +165,13 @@ function classifyAgentUsability(providerAuth) {
135
165
  // evidence must NEVER block rostering, exactly like reapVerdict's null
136
166
  // tolerance for projectWired.
137
167
  function agentRosterReady(verdict) {
138
- if (verdict === 'cli-missing' || verdict === 'auth-unsettled') return false;
168
+ if (
169
+ verdict === 'cli-missing' ||
170
+ verdict === 'cli-broken' ||
171
+ verdict === 'auth-unsettled'
172
+ ) {
173
+ return false;
174
+ }
139
175
  if (verdict === 'ready' || verdict === 'needs-login') return true;
140
176
  return null;
141
177
  }
@@ -206,12 +242,17 @@ function deriveVmReadiness(v, ctx) {
206
242
  if (v.publicWsHealth === 'unwired' && !(bh && bh.editor === 'healthy')) return 'ws-dead';
207
243
  }
208
244
  // 6. Agent usability: the box is up and the WS path carries frames, but the
209
- // build agent itself may not be usable — CLI not installed, or the auth
210
- // probe wedged at 'unknown'. A legitimate 'needs-login' surfaces as its own
211
- // actionable state (Authenticate), distinct from a provisioning fault.
212
- // Absence of providerAuth (older editor / unprobed) falls through to ready.
245
+ // build agent itself may not be usable — CLI not installed, CLI installed
246
+ // but not executable, or the auth probe wedged at 'unknown'. A legitimate
247
+ // 'needs-login' surfaces as its own actionable state (Authenticate),
248
+ // distinct from a provisioning fault. Absence of providerAuth (older
249
+ // editor / unprobed) falls through to ready.
213
250
  const usability = classifyAgentUsability(v.providerAuth);
214
251
  if (usability === 'cli-missing') return 'cli-missing';
252
+ // Surfaced as its own readiness state rather than folded into 'cli-missing':
253
+ // the card's whole job here is to send an operator to the right remedy, and
254
+ // "reinstall the broken CLI" is not "install the missing one".
255
+ if (usability === 'cli-broken') return 'cli-broken';
215
256
  if (usability === 'auth-unsettled') return 'auth-unsettled';
216
257
  if (usability === 'needs-login') return 'needs-login';
217
258
  // 6b. Preview-host IAP gap: the editor surface grants access (the URL routes
@@ -119,6 +119,79 @@ function validateLaunchConfig(cfg) {
119
119
  return { ok: true };
120
120
  }
121
121
 
122
+ /** The branch refs a launch config asks git to resolve, as
123
+ * `[{ field, repo, branch }]` — one entry per branch-carrying field that is
124
+ * actually populated. This is the input to a ref-existence preflight; it lives
125
+ * beside normalizeLaunchConfig (rather than in a second parser at the add
126
+ * endpoint or in cloud.js) because it reads the SAME canonical field names that
127
+ * normalizer produces, so a field rename can never leave the two disagreeing.
128
+ *
129
+ * BOTH fields are reported, deliberately. In the 2026-08-25 incident the dead
130
+ * ref sat in `clientBranch` AND `editorBranch`, and it was `clientBranch` that
131
+ * actually killed the bootstrap — an editor-only check would have missed it.
132
+ *
133
+ * A greenfield add (`newProjectName`, no `clientRepo`) has no client repo to
134
+ * resolve a branch against, so its clientBranch is skipped rather than probed
135
+ * against a repo URL that does not exist. `editorRepo` is passed in because the
136
+ * launch config carries an editor BRANCH but not an editor REPO — the repo is
137
+ * the caller's (cloud:up resolves it via resolveEditorConfig; the dashboard
138
+ * holds it in REPO). Pure; no I/O. */
139
+ function branchRefTargets(cfg, { editorRepo } = {}) {
140
+ const c = cfg || {};
141
+ const targets = [];
142
+ if (c.clientRepo && c.clientBranch) {
143
+ targets.push({ field: "clientBranch", repo: c.clientRepo, branch: c.clientBranch });
144
+ }
145
+ if (editorRepo && c.editorBranch) {
146
+ targets.push({ field: "editorBranch", repo: editorRepo, branch: c.editorBranch });
147
+ }
148
+ return targets;
149
+ }
150
+
151
+ /** Partition branchRefTargets by whether each ref still resolves, using an
152
+ * INJECTED probe so the decision logic is unit-tested with no network and no
153
+ * git. `refExists({ repo, branch })` returns true when the ref is present,
154
+ * false when it is provably gone.
155
+ *
156
+ * A probe that cannot reach the remote at all must NOT report false — a network
157
+ * or credential fault is not evidence a branch was deleted, and treating it as
158
+ * such would refuse every add the moment GitHub blipped. The probe signals that
159
+ * by returning `null`/`undefined`, which this treats as "unknown, let it
160
+ * through": the classifier arm downstream still catches a genuinely dead ref,
161
+ * at the cost this preflight normally saves. Fail-open is the right default
162
+ * here precisely because the preflight is an OPTIMIZATION over a failure that
163
+ * is already handled, never the only line of defence. Pure; no I/O. */
164
+ function missingBranchRefs(targets, refExists) {
165
+ const missing = [];
166
+ for (const t of targets || []) {
167
+ if (refExists(t) === false) missing.push(t);
168
+ }
169
+ return missing;
170
+ }
171
+
172
+ /** Operator-facing copy for a failed branch-ref preflight. States the OUTCOME
173
+ * first (nothing was created), then the mechanism, then the remedy — and never
174
+ * the "a partial instance may exist — reconcile or destroy it" language the
175
+ * generic provisioning failure uses, which is what made the real incident
176
+ * confusing: at preflight time no instance, disk, NEG or backend service has
177
+ * been created, so there is nothing to reconcile and nothing to destroy.
178
+ *
179
+ * Names which FIELD carried the dead ref, because the two are configured in
180
+ * different places and an operator who only corrects one re-issues a request
181
+ * that fails identically. Pure; no I/O. */
182
+ function missingBranchRefMessage(missing) {
183
+ const list = (missing || [])
184
+ .map((m) => `${m.field}: branch '${m.branch}' is not on ${m.repo}`)
185
+ .join("; ");
186
+ return (
187
+ `Branch no longer exists on origin — ${list}. ` +
188
+ "Nothing was provisioned: this is checked before any instance is created, so " +
189
+ "there is no VM, disk or backend service to clean up. This repo prunes branches " +
190
+ "when they merge, so a stored branch name expires silently. Re-add on a live " +
191
+ "branch (the current fleet branch, or any branch that still exists on origin)."
192
+ );
193
+ }
194
+
122
195
  /** Pre-flight the operator-host CONFIG a dashboard Add depends on, BEFORE
123
196
  * spawning the ~20-min cloud:up. Pure classifier: it takes the ALREADY-resolved
124
197
  * zone, project, and GitHub token (the side-effecting resolution — env lookups,
@@ -522,11 +595,42 @@ function recentlyLaunchedWithin(ts, nowMs, graceMs) {
522
595
  * variant(@branch) fallback.
523
596
  * - source "live" when editorIdentity drove editorLabel, else
524
597
  * "history" — the affordance the card uses to mark inferred
525
- * data. Pure; no I/O. */
598
+ * data.
599
+ * - deployedBranch the branch the fleet ADVANCED this VM to, from the editor's
600
+ * `editorDeployment` (which reads the record the refresh script
601
+ * writes at install time); null when the editor reports none.
602
+ * - deployedTag the full artifact tag behind that branch, for the hover.
603
+ * - deployedSource "live" when the editor reported a deployment, else
604
+ * "unknown".
605
+ * - deployedState the card's three-way presentation verdict, resolved HERE
606
+ * rather than in the browser so the rule is unit-tested in one
607
+ * place and index.html carries no copy of it (the compromise
608
+ * groupVmsByProject's own comment apologizes for, avoidable
609
+ * here because this value rides in the /data payload):
610
+ * "agrees" deployment and provenance name the same branch
611
+ * "differs" they disagree — the EXPECTED plan-only-retag
612
+ * state, to be shown as a pair, never badged
613
+ * "unknown" no deployment reported
614
+ *
615
+ * editorLabel and deployedBranch answer DIFFERENT questions and are routinely
616
+ * expected to disagree. editorLabel is build provenance — what source the
617
+ * binary was compiled from — and stays exactly as it was; it is the right
618
+ * answer to "what code is this actually running?" and is what fragility #33's
619
+ * fix was built for. deployedBranch is the deployment target. After a plan-only
620
+ * advance, where fleet-advance-to.sh correctly retags the base branch's CI
621
+ * binary, provenance `main` + deployment `editor-improvements-78` is the
622
+ * CORRECT state, so the card must present the pair rather than badge it.
623
+ *
624
+ * A missing deployment resolves to null / "unknown" and is NEVER filled in from
625
+ * cfg.editorBranch, though that value is right there: it is written at
626
+ * reconfigure and is not updated by a binary refresh, so it is stale for
627
+ * precisely this question — the same live-over-history discipline the rest of
628
+ * this function applies, and the same `source` honesty. Pure; no I/O. */
526
629
  function resolveCardIdentity(live, historyCfg) {
527
630
  const cfg = historyCfg || {};
528
631
  const lcp = live && live.clientProject;
529
632
  const lei = live && live.editorIdentity;
633
+ const led = live && live.editorDeployment;
530
634
 
531
635
  const historyName = cfg.clientRepo ? repoBasename(cfg.clientRepo) : (cfg.newProjectName || "");
532
636
  const project = (lcp && lcp.name) || historyName || null;
@@ -547,7 +651,24 @@ function resolveCardIdentity(live, historyCfg) {
547
651
  source = "history";
548
652
  }
549
653
 
550
- return { project, clientBranch, editorLabel, source };
654
+ const deployedBranch = (led && led.branch) || null;
655
+ const deployedTag = (led && led.tag) || null;
656
+ const deployedSource = deployedBranch || deployedTag ? "live" : "unknown";
657
+ let deployedState;
658
+ if (!deployedBranch) deployedState = "unknown";
659
+ else if (deployedBranch === editorLabel) deployedState = "agrees";
660
+ else deployedState = "differs";
661
+
662
+ return {
663
+ project,
664
+ clientBranch,
665
+ editorLabel,
666
+ source,
667
+ deployedBranch,
668
+ deployedTag,
669
+ deployedSource,
670
+ deployedState,
671
+ };
551
672
  }
552
673
 
553
674
  /** Sticky-merge a VM poll's `clientProject` against the last-known-good value
@@ -1215,6 +1336,9 @@ module.exports = {
1215
1336
  EMPTY_CONFIG,
1216
1337
  normalizeLaunchConfig,
1217
1338
  validateLaunchConfig,
1339
+ branchRefTargets,
1340
+ missingBranchRefs,
1341
+ missingBranchRefMessage,
1218
1342
  addPreflight,
1219
1343
  isVariantProvisionable,
1220
1344
  isVariantRebuildable,