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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.495",
3
+ "version": "1.3.497",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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:22:33.499Z",
5
- "instarVersion": "1.3.495",
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.)