instar 1.3.495 → 1.3.497
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/dashboard/index.html +61 -0
- package/package.json +1 -1
- package/skills/instar-dev/templates/side-effects-artifact.md +1 -0
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/{1.3.495.md → 1.3.496.md} +37 -0
- package/upgrades/1.3.497.md +24 -0
- package/upgrades/side-effects/mobile-complete-operator-actions.md +67 -0
- package/upgrades/side-effects/multi-machine-seamlessness-ws42.md +59 -0
package/dashboard/index.html
CHANGED
|
@@ -217,6 +217,20 @@
|
|
|
217
217
|
already names the machine, so a slightly dimmed row is enough hint. */
|
|
218
218
|
.session-item.remote { opacity: 0.9; cursor: pointer; }
|
|
219
219
|
.session-item.remote:hover { opacity: 1; }
|
|
220
|
+
/* WS4.2: explicit per-machine state when a pool machine has no session
|
|
221
|
+
tiles — an idle machine must never look identical to a broken one.
|
|
222
|
+
Plain flex row; inherits the sidebar width (mobile-safe). */
|
|
223
|
+
.machine-status-row {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: 8px;
|
|
227
|
+
padding: 8px 12px;
|
|
228
|
+
font-size: 11px;
|
|
229
|
+
color: var(--text-dim, #8b8fa3);
|
|
230
|
+
border-top: 1px dashed rgba(139, 143, 163, 0.25);
|
|
231
|
+
}
|
|
232
|
+
.machine-status-row.offline .machine-status-text { color: #f87171; }
|
|
233
|
+
.machine-status-row.online .machine-status-text { opacity: 0.85; }
|
|
220
234
|
|
|
221
235
|
.type-badge {
|
|
222
236
|
padding: 1px 6px;
|
|
@@ -3703,6 +3717,10 @@
|
|
|
3703
3717
|
// Kept separate from `sessions` because the WebSocket replaces that array
|
|
3704
3718
|
// with LOCAL sessions on every broadcast — merging would be wiped each tick.
|
|
3705
3719
|
let remoteSessions = [];
|
|
3720
|
+
// WS4.2 (multi-machine-seamlessness spec, F7): pool machine inventory for the
|
|
3721
|
+
// per-machine empty-state strip. Populated only when the pool is enabled with
|
|
3722
|
+
// 2+ machines — single-machine agents never see the strip (strict no-op).
|
|
3723
|
+
let poolMachinesView = [];
|
|
3706
3724
|
let selfMachineNickname = null;
|
|
3707
3725
|
let activeSession = null;
|
|
3708
3726
|
let activeMachineId = null; // non-null when the active session streams from a peer (§2.2)
|
|
@@ -3964,6 +3982,9 @@
|
|
|
3964
3982
|
empty.style.display = 'flex';
|
|
3965
3983
|
// Clear any existing session items
|
|
3966
3984
|
list.querySelectorAll('.session-item').forEach(el => el.remove());
|
|
3985
|
+
// WS4.2: even with zero sessions anywhere, each pool machine still gets
|
|
3986
|
+
// an explicit state row — an idle machine must never look broken.
|
|
3987
|
+
renderMachineStatusStrip(list, all);
|
|
3967
3988
|
return;
|
|
3968
3989
|
}
|
|
3969
3990
|
|
|
@@ -4051,6 +4072,37 @@
|
|
|
4051
4072
|
${telemetryHtml}
|
|
4052
4073
|
`;
|
|
4053
4074
|
}
|
|
4075
|
+
|
|
4076
|
+
// WS4.2: per-machine state strip after the tiles.
|
|
4077
|
+
renderMachineStatusStrip(list, all);
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
// WS4.2 (multi-machine-seamlessness spec, F7 — 2026-06-12 live incident):
|
|
4081
|
+
// a machine with zero running sessions used to render as NOTHING, identical
|
|
4082
|
+
// to a broken or unreachable machine. Render an explicit state row per pool
|
|
4083
|
+
// machine that has no session tiles: "online — no active sessions" when its
|
|
4084
|
+
// heartbeat is live, "not reachable — last seen <t>" when it is not. Pool
|
|
4085
|
+
// disabled or single-machine → poolMachinesView is empty → zero rows, zero
|
|
4086
|
+
// behavior change (single-machine strict no-op, spec invariant 6).
|
|
4087
|
+
function machineStatusInfo(m) {
|
|
4088
|
+
if (m.online) return { cls: 'online', text: 'online — no active sessions' };
|
|
4089
|
+
const seen = m.selfReportedLastSeen ? new Date(m.selfReportedLastSeen).getTime() : null;
|
|
4090
|
+
const rel = seen ? fmtRelTime(seen) : 'unknown';
|
|
4091
|
+
return { cls: 'offline', text: `not reachable — last seen ${rel}` };
|
|
4092
|
+
}
|
|
4093
|
+
function renderMachineStatusStrip(list, allSessions) {
|
|
4094
|
+
list.querySelectorAll('.machine-status-row').forEach(el => el.remove());
|
|
4095
|
+
if (!poolMachinesView.length) return;
|
|
4096
|
+
const busy = new Set(allSessions.map(s => s.machineNickname || s.machineId).filter(Boolean));
|
|
4097
|
+
for (const m of poolMachinesView) {
|
|
4098
|
+
const label = m.nickname || m.machineId || 'unnamed machine';
|
|
4099
|
+
if (busy.has(m.nickname) || busy.has(m.machineId)) continue; // has tiles — badge already names it
|
|
4100
|
+
const info = machineStatusInfo(m);
|
|
4101
|
+
const row = document.createElement('div');
|
|
4102
|
+
row.className = `machine-status-row ${info.cls}`;
|
|
4103
|
+
row.innerHTML = `<span class="machine-badge">${escapeHtml(label)}</span><span class="machine-status-text">${escapeHtml(info.text)}</span>`;
|
|
4104
|
+
list.appendChild(row);
|
|
4105
|
+
}
|
|
4054
4106
|
}
|
|
4055
4107
|
|
|
4056
4108
|
// ── Terminal ──────────────────────────────────────────────
|
|
@@ -4606,6 +4658,15 @@
|
|
|
4606
4658
|
remoteSessions = (j.sessions || []).filter(s => s.remote === true && (s.status === 'running' || s.status === 'starting'));
|
|
4607
4659
|
renderSessionList();
|
|
4608
4660
|
} catch { /* best-effort — peers may be offline; next tick retries */ }
|
|
4661
|
+
// WS4.2: refresh the machine inventory on the same cadence so the sessions
|
|
4662
|
+
// view can say "online — no active sessions" vs "not reachable" per machine.
|
|
4663
|
+
try {
|
|
4664
|
+
const pool = await apiFetch('/pool');
|
|
4665
|
+
poolMachinesView = (pool && pool.enabled && Array.isArray(pool.machines) && pool.machines.length >= 2)
|
|
4666
|
+
? pool.machines
|
|
4667
|
+
: [];
|
|
4668
|
+
renderSessionList();
|
|
4669
|
+
} catch { /* best-effort — strip simply stays absent until the next tick */ }
|
|
4609
4670
|
}
|
|
4610
4671
|
function startPoolSessionsPolling() {
|
|
4611
4672
|
if (poolPollTimer) return;
|
package/package.json
CHANGED
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
- External systems (Telegram, Slack, GitHub, Cloudflare, etc.)?
|
|
96
96
|
- Persistent state (databases, ledgers, memory files)?
|
|
97
97
|
- Timing or runtime conditions we don't fully control?
|
|
98
|
+
- **Operator surface (Mobile-Complete Operator Actions):** does every operator-facing action this change adds or touches have a phone-completable surface — a dashboard form or a link the agent can send? A PIN-gated or approval-class route with no human surface is an incomplete feature, not a finished API (the 2026-06-12 floor-grant lesson: the route was correct, signed, audited — and laptop-bound). "No operator-facing actions" is a valid answer; an API-only operator action is not.
|
|
98
99
|
|
|
99
100
|
[Specific findings. "The response format for 422 changes — callers parsing the `issue` field will still see a non-empty string. Verified in telegram-reply.sh." "No external surface changes" is also valid if true.]
|
|
100
101
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-12T21:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-12T21:39:24.602Z",
|
|
5
|
+
"instarVersion": "1.3.497",
|
|
6
6
|
"entryCount": 201,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -9,10 +9,31 @@ The PIN-gated user→agent floor-grant route (`POST /mandate/:id/grants`) finall
|
|
|
9
9
|
|
|
10
10
|
Agent awareness ships both ways: the CLAUDE.md template gains a "User floor-action grants are phone-first" bullet (send the operator the dashboard link, NEVER a terminal command), and a `PostUpdateMigrator` migration inserts the same bullet into existing agents' Coordination Mandate section (anchored insertion, append fallback, idempotent). `CapabilityIndex` entries updated for both routes.
|
|
11
11
|
|
|
12
|
+
The dashboard sessions view rendered NOTHING for a pool machine with zero running
|
|
13
|
+
sessions — visually identical to a machine that is offline or unreachable. Live
|
|
14
|
+
incident 2026-06-12 (topic 13481): minutes after the pool-tile status filter shipped,
|
|
15
|
+
an idle-but-healthy Mac Mini disappeared from the sessions view entirely and the
|
|
16
|
+
operator reasonably read it as a regression.
|
|
17
|
+
|
|
18
|
+
The sessions view now renders an explicit state row for every pool machine that has
|
|
19
|
+
no session tiles: **"online — no active sessions"** when its heartbeat is live, or
|
|
20
|
+
**"not reachable — last seen <t>"** when it is not. Data comes from the existing
|
|
21
|
+
`GET /pool` machine inventory on the existing 15-second pool poll — no new routes, no
|
|
22
|
+
server changes. Machines that have session tiles get no redundant row (their tiles
|
|
23
|
+
already carry the machine badge). Pool disabled or single-machine: the strip is never
|
|
24
|
+
rendered — strict no-op, locked by test. This is WS4.2 of the converged
|
|
25
|
+
multi-machine-seamlessness spec (`docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md`,
|
|
26
|
+
3-round convergence, 91 findings addressed), whose audit and ELI16 companion ship in
|
|
27
|
+
this same PR.
|
|
28
|
+
|
|
12
29
|
## What to Tell Your User
|
|
13
30
|
|
|
14
31
|
- "You can now grant a teammate a time-boxed floor action (like a 1-hour prod-deploy) straight from the dashboard on your phone — pick the person, the action, and the duration, type your PIN, tap Grant. No terminal, no laptop."
|
|
15
32
|
|
|
33
|
+
- "Your dashboard's session list now tells you the state of every machine, even the
|
|
34
|
+
quiet ones — an idle machine says 'online — no active sessions' instead of showing
|
|
35
|
+
nothing, so you can always tell a resting machine from a broken one."
|
|
36
|
+
|
|
16
37
|
## Summary of New Capabilities
|
|
17
38
|
|
|
18
39
|
| Capability | How to Use |
|
|
@@ -20,6 +41,10 @@ Agent awareness ships both ways: the CLAUDE.md template gains a "User floor-acti
|
|
|
20
41
|
| Phone-first floor-action grants | Dashboard → Mandates tab → any active mandate → "Grant a user a floor action" |
|
|
21
42
|
| Registered-user person picker | Automatic (`GET /permissions/users` feeds it) |
|
|
22
43
|
|
|
44
|
+
| Capability | How to Use |
|
|
45
|
+
|-----------|-----------|
|
|
46
|
+
| Per-machine empty-state in the sessions view | Automatic — open the dashboard sessions list on any multi-machine pool |
|
|
47
|
+
|
|
23
48
|
## Evidence
|
|
24
49
|
|
|
25
50
|
- Unit: `tests/unit/dashboard-mandateGrantForm.test.ts` (12 tests — pick-don't-type rendering, PIN discipline incl. never-retained, expiry clamp, enum-drift pin, XSS, registry-failure degradation) + `tests/unit/PostUpdateMigrator-floorGrantPhoneFirst.test.ts` (4 tests — fresh install, anchored insertion, hand-edited fallback, idempotency).
|
|
@@ -27,3 +52,15 @@ Agent awareness ships both ways: the CLAUDE.md template gains a "User floor-acti
|
|
|
27
52
|
- E2E: `tests/e2e/coordination-mandate-lifecycle.test.ts` (+3 — route alive + Bearer-gated on the production boot path; the form's EXACT payload signs a grant end-to-end; Bearer-without-PIN structurally refused).
|
|
28
53
|
- All 24 pre-existing Mandates-tab tests + the full mandate-area suite (109 tests) green.
|
|
29
54
|
- Side-effects artifact: `upgrades/side-effects/mandate-grant-form.md`.
|
|
55
|
+
|
|
56
|
+
- New test file `tests/unit/dashboard-machineEmptyState.test.ts` (6 tests, at-rest
|
|
57
|
+
HTML/JS assertions following the established dashboard test pattern) — proven
|
|
58
|
+
failing 6/6 against the pre-change file, all pass on the fix. Covers: the
|
|
59
|
+
single-machine no-op gate, both honest state strings, the zero-sessions branch,
|
|
60
|
+
no-duplicate-rows reconciliation, no redundant rows for busy machines, and HTML
|
|
61
|
+
escaping of machine-provided strings.
|
|
62
|
+
- `tests/integration/pool-routes.test.ts` extended to lock the `/pool` contract field
|
|
63
|
+
(`selfReportedLastSeen`) the strip consumes.
|
|
64
|
+
- Side-effects artifact: `upgrades/side-effects/multi-machine-seamlessness-ws42.md`.
|
|
65
|
+
- Spec: `docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md` (converged + approved) with
|
|
66
|
+
convergence report `docs/specs/reports/multi-machine-seamlessness-convergence.md`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The Standards Registry (`docs/STANDARDS-REGISTRY.md`, Interaction section) gains a new operator-ratified standard: **Mobile-Complete Operator Actions** — every action that needs the operator (approvals, grants, credential submissions, decisions, PIN-gated authority) must be completable from a phone via the dashboard or a sent link; a terminal command, file edit, or laptop-only step in an operator loop is a defect. Earned from the 2026-06-12 floor-grant incident (Slack live-test scenario 8/8: a correct, PIN-gated, signed route that was laptop-bound because it shipped API-only), ratified by operator directive (Justin, topic 22367). The entry records the sharper sub-lesson too: the outbound advisory blocked the raw-CLI message and the agent complied in format only — guards catch format, the constitution states substance.
|
|
9
|
+
|
|
10
|
+
Review-time enforcement ships with it: the side-effects artifact template (`skills/instar-dev/templates/side-effects-artifact.md`, question 6 — External surfaces) now explicitly asks whether every operator-facing action the change adds or touches has a phone-completable surface. The crystallizing incident's conversion (the Mandates-tab grant form, instar#1080/PR #1082) is the standard's first applied-through artifact; the durable generalization (one-time Operator Approval Links) is tracked to go through `/spec-converge`.
|
|
11
|
+
|
|
12
|
+
## What to Tell Your User
|
|
13
|
+
|
|
14
|
+
None — internal change (no user-facing surface).
|
|
15
|
+
|
|
16
|
+
## Summary of New Capabilities
|
|
17
|
+
|
|
18
|
+
None — internal change (no user-facing surface).
|
|
19
|
+
|
|
20
|
+
## Evidence
|
|
21
|
+
|
|
22
|
+
- The registry entry follows the constitution's established format (Rule / In practice / Earned from / Ratified by / Traces to the goal / Applied through) with the operator's ratifying directive quoted.
|
|
23
|
+
- Registry-parsing guards stay green: `tests/unit/standards-enforcement-auditor.test.ts` (incl. the zero-dangling-refs canary — every guard the entry cites exists on disk), `tests/unit/standards-conformance-gate.test.ts`, and the deferral scan (the entry's known-open generalization carries a tracked marker).
|
|
24
|
+
- Side-effects artifact: `upgrades/side-effects/mobile-complete-operator-actions.md`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Side-Effects Review — Constitution: Mobile-Complete Operator Actions
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `mobile-complete-operator-actions`
|
|
4
|
+
**Date:** `2026-06-12`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `not required (documentation + review-template change; no runtime surface, no decision points)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds the operator-ratified **Mobile-Complete Operator Actions** standard to `docs/STANDARDS-REGISTRY.md` (Interaction section) and its review-time enforcement hook: an operator-surface bullet in the side-effects template's question 6 (`skills/instar-dev/templates/side-effects-artifact.md`). Earned from the 2026-06-12 floor-grant incident; ratified by operator directive (Justin, topic 22367). No runtime code changes.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
No decision points touched. The registry is read by the spec-review conformance gate and the standards-enforcement auditor — both observe/classify; neither gains new blocking logic from this entry. The template change adds a QUESTION future authors must answer in writing; the pre-commit hook's artifact checks are unchanged (it verifies artifact existence/coverage, not per-question structure).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Over-block
|
|
19
|
+
|
|
20
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
21
|
+
|
|
22
|
+
Nothing is blocked by this change. The new template question could be answered "No operator-facing actions" by any change without one — no legitimate change gains friction beyond one written sentence. No issue identified.
|
|
23
|
+
|
|
24
|
+
## 2. Under-block
|
|
25
|
+
|
|
26
|
+
**What failure modes does this still miss?**
|
|
27
|
+
|
|
28
|
+
The standard's enforcement is review-time prose (the template question) — an author can answer it wrongly and ship an API-only operator action anyway; the conformance audit will classify this standard as `spec-only` strength until a structural ratchet exists (the named candidate: a UI-surface map for PIN-class routes). That gap is the Standards Enforcement Coverage system working as designed — it surfaces which standards are wishes — and the registry entry names the intended ratchet honestly rather than claiming structural enforcement it doesn't have.
|
|
29
|
+
|
|
30
|
+
## 3. Level-of-abstraction fit
|
|
31
|
+
|
|
32
|
+
**Is this at the right layer?**
|
|
33
|
+
|
|
34
|
+
Yes. The 2026-06-12 incident proved a gate alone is the wrong layer: the outbound advisory fired and the substance still shipped laptop-bound. A constitutional entry is the layer that states substance; gates and ratchets grow toward it (and the spec-review conformance gate starts checking drafts against it immediately, since it reads the registry).
|
|
35
|
+
|
|
36
|
+
## 4. Signal vs authority compliance
|
|
37
|
+
|
|
38
|
+
**Does this hold blocking authority with brittle logic?**
|
|
39
|
+
|
|
40
|
+
No blocking authority is added anywhere. Reference reviewed: `docs/signal-vs-authority.md`. No issue identified.
|
|
41
|
+
|
|
42
|
+
## 5. Interactions
|
|
43
|
+
|
|
44
|
+
**Does it shadow another check, get shadowed, double-fire, race with adjacent cleanup?**
|
|
45
|
+
|
|
46
|
+
- Registry parsers verified green against the new entry: `standards-enforcement-auditor` (including the zero-dangling-refs canary — every guard the entry cites must exist on disk, which is why this PR lands AFTER the grant-form PR that contains `GET /permissions/users`), `standards-conformance-gate`, and the pre-commit deferral scan (the entry's known-open generalization carries a `tracked:` marker).
|
|
47
|
+
- The entry sits beside "No Manual Work (user *or* agent)" as a sibling, not a duplicate: that standard says interactions must be automatic/channel-borne; this one pins the DEVICE bar for the human half of those interactions.
|
|
48
|
+
|
|
49
|
+
## 6. External surfaces
|
|
50
|
+
|
|
51
|
+
**Does this change anything visible outside the immediate code path?**
|
|
52
|
+
|
|
53
|
+
- The registry is read by the spec-review conformance gate, the runtime Usher, and the standards-enforcement auditor — all gain one more standard to surface/classify. That is the intended effect.
|
|
54
|
+
- The template change reaches future instar authors only (the template is copied at artifact-writing time; existing artifacts are untouched).
|
|
55
|
+
- **Operator surface (the new question, answered for this change itself):** this change adds no operator-facing actions. The standard it documents was applied to the incident's own surface in PR #1082.
|
|
56
|
+
|
|
57
|
+
## 7. Rollback cost
|
|
58
|
+
|
|
59
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
60
|
+
|
|
61
|
+
Revert the docs commit. No state, no migrations, no runtime behavior. The conformance gate simply stops checking drafts against the entry. Trivial rollback.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Second-pass review
|
|
66
|
+
|
|
67
|
+
Not required — documentation + review-template change with no runtime surface or decision points (per the skill's Phase 5 trigger list). The artifact's "documentation-level impact" conclusion is the valid outcome the skill explicitly names for this class of change.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Side-Effects Review — WS4.2 per-machine empty-state strip (dashboard sessions view)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md (converged 2026-06-12, 3 iterations; approved)
|
|
4
|
+
**Change:** dashboard/index.html (+ tests). The sessions view renders an explicit state
|
|
5
|
+
row for every pool machine that has no session tiles: "online — no active sessions"
|
|
6
|
+
when its heartbeat is live, "not reachable — last seen <t>" when not. Data: the
|
|
7
|
+
existing `GET /pool` machines array, fetched on the existing 15s pool poll cadence.
|
|
8
|
+
Closes audit finding F7 (2026-06-12 live incident: idle Mini rendered as nothing and
|
|
9
|
+
read as a regression).
|
|
10
|
+
|
|
11
|
+
## 1. Over-block
|
|
12
|
+
No issue identified — the change blocks nothing. It is a read-only presentation
|
|
13
|
+
addition; no input is rejected anywhere.
|
|
14
|
+
|
|
15
|
+
## 2. Under-block
|
|
16
|
+
No issue identified — there is no blocking surface. Honesty boundary worth naming:
|
|
17
|
+
`online:false` cannot distinguish "deliberately shut down" from "network-unreachable",
|
|
18
|
+
so the row says "not reachable — last seen <t>" (data-honest) rather than claiming to
|
|
19
|
+
know which. The spec's three-state wording maps onto the two states the data supports;
|
|
20
|
+
this is recorded as the deliberate build decision the converged spec's round-3
|
|
21
|
+
adversarial pass classified as adequately constrained.
|
|
22
|
+
|
|
23
|
+
## 3. Level-of-abstraction fit
|
|
24
|
+
Right layer. The dashboard already consumes `/pool` (Machines tab) and
|
|
25
|
+
`/sessions?scope=pool` (tiles); the strip is pure client-side composition of data both
|
|
26
|
+
endpoints already serve. No new route, no server change. A server-side "empty
|
|
27
|
+
machines" field would duplicate client-known state at a worse layer.
|
|
28
|
+
|
|
29
|
+
## 4. Signal vs authority compliance
|
|
30
|
+
Compliant by vacuity: the change introduces NO decision point — it gates no flow,
|
|
31
|
+
filters no message, constrains no behavior. (Phase-1 principle check recorded the
|
|
32
|
+
same conclusion in the build transcript.)
|
|
33
|
+
|
|
34
|
+
## 5. Interactions
|
|
35
|
+
- The strip renders AFTER session tiles and clears its own rows per render — no
|
|
36
|
+
interference with tile add/remove reconciliation (verified by test: no duplicate
|
|
37
|
+
accumulation).
|
|
38
|
+
- The zero-sessions early-return branch now also renders the strip; the existing
|
|
39
|
+
#emptyState banner still shows alongside it (intended: "no sessions" + per-machine
|
|
40
|
+
states are complementary, not contradictory).
|
|
41
|
+
- A machine WITH tiles gets no row (its tiles' machine badge already names it) —
|
|
42
|
+
prevents double-labeling.
|
|
43
|
+
- The extra `GET /pool` per 15s poll tick matches the Machines tab's existing call
|
|
44
|
+
pattern and is auth'd identically (apiFetch). Failure is swallowed best-effort: the
|
|
45
|
+
strip simply stays absent until the next tick — degraded view, never an error loop.
|
|
46
|
+
|
|
47
|
+
## 6. External surfaces
|
|
48
|
+
None beyond the operator's own PIN-gated dashboard. No new data is exposed: nickname,
|
|
49
|
+
online, lastSeen are already rendered on the Machines tab. Machine-provided strings
|
|
50
|
+
are escaped before DOM injection (tested). Single-machine agents: `poolMachinesView`
|
|
51
|
+
is populated only when the pool is enabled with 2+ machines — strict no-op, tested.
|
|
52
|
+
|
|
53
|
+
## 7. Rollback cost
|
|
54
|
+
Trivial: revert the dashboard/index.html hunk (static asset; no data, no migration,
|
|
55
|
+
no state). Ships with the next release; rollback is a one-commit revert and re-release.
|
|
56
|
+
|
|
57
|
+
## Second-pass review
|
|
58
|
+
Not required — no block/allow decisions, no session lifecycle, no
|
|
59
|
+
gate/sentinel/watchdog surface. (Phase-5 trigger list consulted; none match.)
|