instar 1.3.494 → 1.3.496

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;
@@ -809,6 +823,12 @@
809
823
  .mnd-btn { padding: 6px 14px; cursor: pointer; }
810
824
  .mnd-btn-danger { color: #f85149; }
811
825
  .mnd-dead-note { font-size: 13px; color: var(--text-dim); font-style: italic; }
826
+ .mnd-grants-head { font-size: 13px; color: var(--text-dim); }
827
+ .mnd-grants { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.7; }
828
+ .mnd-grant-details { font-size: 13px; }
829
+ .mnd-grant-summary { cursor: pointer; color: var(--text-dim); }
830
+ .mnd-grant-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 8px 0 4px; }
831
+ .mnd-grant-field { min-width: 130px; max-width: 100%; padding: 6px 8px; }
812
832
  .mnd-notice { min-height: 20px; font-size: 14px; color: #3fb950; }
813
833
  .mnd-issue-form { display: flex; flex-direction: column; gap: 14px; max-width: 640px; padding: 12px 0; }
814
834
  .mnd-issue-form label { display: flex; flex-direction: column; gap: 4px; font-size: 15px; color: var(--text-bright); }
@@ -3697,6 +3717,10 @@
3697
3717
  // Kept separate from `sessions` because the WebSocket replaces that array
3698
3718
  // with LOCAL sessions on every broadcast — merging would be wiped each tick.
3699
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 = [];
3700
3724
  let selfMachineNickname = null;
3701
3725
  let activeSession = null;
3702
3726
  let activeMachineId = null; // non-null when the active session streams from a peer (§2.2)
@@ -3958,6 +3982,9 @@
3958
3982
  empty.style.display = 'flex';
3959
3983
  // Clear any existing session items
3960
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);
3961
3988
  return;
3962
3989
  }
3963
3990
 
@@ -4045,6 +4072,37 @@
4045
4072
  ${telemetryHtml}
4046
4073
  `;
4047
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
+ }
4048
4106
  }
4049
4107
 
4050
4108
  // ── Terminal ──────────────────────────────────────────────
@@ -4600,6 +4658,15 @@
4600
4658
  remoteSessions = (j.sessions || []).filter(s => s.remote === true && (s.status === 'running' || s.status === 'starting'));
4601
4659
  renderSessionList();
4602
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 */ }
4603
4670
  }
4604
4671
  function startPoolSessionsPolling() {
4605
4672
  if (poolPollTimer) return;
@@ -20,6 +20,28 @@ const AUTHORITIES_TEMPLATE = JSON.stringify([
20
20
  { action: 'sign-code-review', bounds: { artifact: 'migration-port', mutual: true } },
21
21
  ], null, 2);
22
22
 
23
+ // Mirrors FLOOR_ACTIONS in src/permissions/RolePolicy.ts — the enumerated
24
+ // never-discretionary actions a user grant can lift. Kept in sync by the
25
+ // dashboard-mandatesTab test, which compares this list against the source enum.
26
+ const FLOOR_ACTIONS = [
27
+ 'money-movement',
28
+ 'prod-deploy',
29
+ 'credential-access',
30
+ 'destructive-data',
31
+ 'external-send',
32
+ 'grant-authority',
33
+ ];
34
+
35
+ // Mobile-first: the operator picks a duration, never types a timestamp.
36
+ // The submit handler clamps to the mandate's own expiry (a grant can never
37
+ // outlive the mandate that carries it — the server enforces the same rule).
38
+ const GRANT_DURATIONS = [
39
+ { label: '15 minutes', minutes: 15 },
40
+ { label: '1 hour', minutes: 60 },
41
+ { label: '4 hours', minutes: 240 },
42
+ { label: '24 hours', minutes: 1440 },
43
+ ];
44
+
23
45
  function esc(s) {
24
46
  return String(s ?? '').replace(/[&<>"']/g, (c) => ({
25
47
  '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
@@ -39,7 +61,57 @@ function fmtWhen(iso) {
39
61
  } catch { return String(iso); }
40
62
  }
41
63
 
42
- export function renderMandates(list) {
64
+ function userLabel(slackUserId, slackUsers) {
65
+ const u = (slackUsers || []).find((x) => x.slackUserId === slackUserId);
66
+ return u ? `${u.name} (${slackUserId})` : slackUserId;
67
+ }
68
+
69
+ /** The grants a mandate already carries, in operator language. */
70
+ export function renderGrants(m, slackUsers) {
71
+ const grants = Array.isArray(m.grants) ? m.grants : [];
72
+ if (grants.length === 0) return '';
73
+ const rows = grants.map((g) => {
74
+ const expired = Date.parse(g.expiresAt) < Date.now();
75
+ const badge = expired
76
+ ? '<span class="mnd-badge mnd-dead">expired</span>'
77
+ : '<span class="mnd-badge mnd-ok">active</span>';
78
+ return `<li>${badge} <strong>${esc(userLabel(g.grantedTo, slackUsers))}</strong> may <code>${esc(g.floorAction)}</code> until ${fmtWhen(g.expiresAt)} — authorized by ${esc(g.authorizedBy)}</li>`;
79
+ }).join('');
80
+ return `<div class="mnd-grants-head">User grants this mandate carries:</div><ul class="mnd-grants">${rows}</ul>`;
81
+ }
82
+
83
+ /**
84
+ * The add-grant form for an ACTIVE mandate. Mobile-first by design (the
85
+ * 2026-06-12 lesson, instar#1080): the operator PICKS a person and a duration —
86
+ * the only thing typed is the PIN. A free-text Slack-id input appears only
87
+ * when the user registry has nobody to offer.
88
+ */
89
+ export function renderGrantForm(m, slackUsers) {
90
+ const users = (slackUsers || []).filter((u) => u.slackUserId);
91
+ const granteeField = users.length > 0
92
+ ? `<select class="mnd-grant-field" data-grant-user="${esc(m.id)}">${
93
+ users.map((u) => `<option value="${esc(u.slackUserId)}">${esc(u.name)}${u.orgRole ? ` — ${esc(u.orgRole)}` : ''}</option>`).join('')
94
+ }</select>`
95
+ : `<input type="text" class="mnd-grant-field" data-grant-user="${esc(m.id)}" placeholder="Slack user id (e.g. U0…)" />`;
96
+ const actionField = `<select class="mnd-grant-field" data-grant-action="${esc(m.id)}">${
97
+ FLOOR_ACTIONS.map((a) => `<option value="${a}"${a === 'prod-deploy' ? ' selected' : ''}>${a}</option>`).join('')
98
+ }</select>`;
99
+ const durationField = `<select class="mnd-grant-field" data-grant-duration="${esc(m.id)}">${
100
+ GRANT_DURATIONS.map((d) => `<option value="${d.minutes}"${d.minutes === 60 ? ' selected' : ''}>${d.label}</option>`).join('')
101
+ }</select>`;
102
+ return `<details class="mnd-grant-details"><summary class="mnd-grant-summary">Grant a user a floor action (PIN required)</summary>
103
+ <div class="mnd-grant-row">
104
+ ${granteeField}
105
+ ${actionField}
106
+ ${durationField}
107
+ <input type="password" class="mnd-pin" data-grant-pin="${esc(m.id)}" placeholder="PIN" autocomplete="off" />
108
+ <button class="mnd-btn" data-grant="${esc(m.id)}">Grant</button>
109
+ </div>
110
+ <span class="mnd-hint">Lifts the picked person to ONE floor action for the picked window. Signed into this mandate by your PIN — revoking the mandate voids it; it can never outlive the mandate.</span>
111
+ </details>`;
112
+ }
113
+
114
+ export function renderMandates(list, slackUsers = []) {
43
115
  if (!Array.isArray(list) || list.length === 0) {
44
116
  return '<div class="mnd-empty">No mandates issued. The gate is deny-by-default: every delegated agent action refuses until you issue one.</div>';
45
117
  }
@@ -60,6 +132,7 @@ export function renderMandates(list) {
60
132
  <button class="mnd-btn mnd-btn-danger" data-revoke="${esc(m.id)}">Revoke</button>
61
133
  </div>`
62
134
  : `<div class="mnd-dead-note">${m.revoked ? `revoked ${fmtWhen(m.revoked.at)} — ${esc(m.revoked.reason)}` : `expired ${fmtWhen(m.expiresAt)}`}</div>`;
135
+ const grantUi = state === 'active' ? renderGrantForm(m, slackUsers) : '';
63
136
  return `<div class="mnd-card">
64
137
  <div class="mnd-card-head">
65
138
  <span class="mnd-scope">${esc(m.scope)}</span>
@@ -68,6 +141,8 @@ export function renderMandates(list) {
68
141
  </div>
69
142
  <div class="mnd-meta">id <code>${esc(m.id)}</code> · agents <code>${esc(shortFp(m.agents?.[0]))}</code> + <code>${esc(shortFp(m.agents?.[1]))}</code> · by ${esc(m.author)} · expires ${fmtWhen(m.expiresAt)}</div>
70
143
  <ul class="mnd-authorities">${authorities}</ul>
144
+ ${renderGrants(m, slackUsers)}
145
+ ${grantUi}
71
146
  ${revokeUi}
72
147
  </div>`;
73
148
  }).join('');
@@ -129,12 +204,18 @@ export function createController({ doc, els, fetchImpl }) {
129
204
 
130
205
  let refreshErrorShown = false;
131
206
  let autoOpenedIssueForm = false;
207
+ // Last-fetched state the grant submit handler needs: the mandate list (for
208
+ // the expiry clamp) — kept here, never re-derived from the DOM.
209
+ let lastMandates = [];
132
210
 
133
211
  async function refresh() {
134
212
  try {
135
- const [mand, audit] = await Promise.all([
213
+ const [mand, audit, users] = await Promise.all([
136
214
  fetchJson('/mandate'),
137
215
  fetchJson('/mandate/audit?limit=200'),
216
+ // Registered Slack users feed the grant form's person picker. A failure
217
+ // here must never take down the tab — the form degrades to a text input.
218
+ fetchJson('/permissions/users').catch(() => ({ status: 0, body: null })),
138
219
  ]);
139
220
  if (mand.status === 503) {
140
221
  els.list.innerHTML = '<div class="mnd-empty">Mandate engine unavailable on this server (older version or init failure).</div>';
@@ -143,7 +224,9 @@ export function createController({ doc, els, fetchImpl }) {
143
224
  return;
144
225
  }
145
226
  const mandates = mand.body?.mandates;
146
- els.list.innerHTML = renderMandates(mandates);
227
+ lastMandates = Array.isArray(mandates) ? mandates : [];
228
+ const slackUsers = users.status === 200 && Array.isArray(users.body?.users) ? users.body.users : [];
229
+ els.list.innerHTML = renderMandates(mandates, slackUsers);
147
230
  els.audit.innerHTML = renderAudit(audit.body);
148
231
  if (els.stamp) els.stamp.textContent = 'updated ' + new Date().toLocaleTimeString();
149
232
  // Nothing issued yet → the issue form IS the page's call to action;
@@ -155,6 +238,7 @@ export function createController({ doc, els, fetchImpl }) {
155
238
  // A persistent refresh error from a server restart-gap heals itself.
156
239
  if (refreshErrorShown) { clearNote(); refreshErrorShown = false; }
157
240
  wireRevokeButtons();
241
+ wireGrantButtons();
158
242
  } catch (e) {
159
243
  refreshErrorShown = true;
160
244
  note('refresh failed: ' + (e?.message ?? e) + ' — retrying automatically.', 'error');
@@ -184,6 +268,66 @@ export function createController({ doc, els, fetchImpl }) {
184
268
  });
185
269
  }
186
270
 
271
+ function wireGrantButtons() {
272
+ els.list.querySelectorAll('[data-grant]').forEach((btn) => {
273
+ btn.onclick = async () => {
274
+ const id = btn.getAttribute('data-grant');
275
+ const userEl = els.list.querySelector(`[data-grant-user="${id}"]`);
276
+ const actionEl = els.list.querySelector(`[data-grant-action="${id}"]`);
277
+ const durEl = els.list.querySelector(`[data-grant-duration="${id}"]`);
278
+ const pinEl = els.list.querySelector(`[data-grant-pin="${id}"]`);
279
+ const grantedTo = (userEl?.value ?? '').trim();
280
+ const floorAction = actionEl?.value ?? 'prod-deploy';
281
+ const pin = pinEl?.value ?? '';
282
+ const problems = [];
283
+ if (!grantedTo) problems.push('• Pick (or type) who the grant is for.');
284
+ if (!pin) problems.push('• Type your dashboard PIN — granting a floor action is a human action; agent credentials are refused.');
285
+ if (problems.length > 0) {
286
+ note('Not granted — fix the following first:\n' + problems.join('\n'), 'error');
287
+ return;
288
+ }
289
+ // A grant can never outlive its mandate — clamp client-side so the
290
+ // operator's pick always succeeds (the server enforces the same rule
291
+ // by rejection; rejection is a worse experience than a shorter window).
292
+ const minutes = Number(durEl?.value ?? 60) || 60;
293
+ const mandate = lastMandates.find((m) => m.id === id);
294
+ const mandateExpiryMs = mandate ? Date.parse(mandate.expiresAt) : NaN;
295
+ let expiryMs = Date.now() + minutes * 60_000;
296
+ let clamped = false;
297
+ if (!isNaN(mandateExpiryMs) && expiryMs > mandateExpiryMs) { expiryMs = mandateExpiryMs; clamped = true; }
298
+ btn.disabled = true;
299
+ try {
300
+ const { status, body } = await fetchJson(`/mandate/${encodeURIComponent(id)}/grants`, {
301
+ method: 'POST',
302
+ headers: { 'Content-Type': 'application/json' },
303
+ body: JSON.stringify({
304
+ pin,
305
+ grants: [{
306
+ floorAction,
307
+ grantedTo,
308
+ authorizedBy: 'operator (dashboard PIN)',
309
+ expiresAt: new Date(expiryMs).toISOString(),
310
+ }],
311
+ }),
312
+ });
313
+ if (status === 201) {
314
+ note(`✓ Grant signed — ${grantedTo} may ${floorAction} until ${fmtWhen(new Date(expiryMs).toISOString())}${clamped ? ' (shortened to the mandate’s own expiry — a grant can never outlive its mandate)' : ''}.`, 'success');
315
+ await refresh();
316
+ } else {
317
+ note(`Not granted — the server refused: ${body?.error ?? `HTTP ${status}`}`, 'error');
318
+ }
319
+ } catch (e) {
320
+ // A network failure must neither strand the typed PIN (cleared in
321
+ // finally) nor fail silently.
322
+ note(`Not granted — request failed: ${e?.message ?? e}. Nothing was signed; try again.`, 'error');
323
+ } finally {
324
+ if (pinEl) pinEl.value = ''; // never retain the PIN — on ANY path
325
+ btn.disabled = false;
326
+ }
327
+ };
328
+ });
329
+ }
330
+
187
331
  // Validate EVERYTHING client-side and report every problem at once —
188
332
  // never POST a request we know the server will refuse, and never let the
189
333
  // operator discover a missing field one transient error at a time.
@@ -1 +1 @@
1
- {"version":3,"file":"PostUpdateMigrator.d.ts","sourceRoot":"","sources":["../../src/core/PostUpdateMigrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PostUpdateMigrator.d.ts","sourceRoot":"","sources":["../../src/core/PostUpdateMigrator.ts"],"names":[],"mappings":"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"}
@@ -5027,6 +5027,24 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
5027
5027
  patched = true;
5028
5028
  result.upgraded.push('CLAUDE.md: added Coordination Mandate awareness section');
5029
5029
  }
5030
+ // Phone-first floor grants (Mobile-Complete Operator Actions, instar#1080)
5031
+ // — agents that already carry the Coordination Mandate section must learn
5032
+ // to point operators at the Mandates-tab grant form, never at a terminal
5033
+ // command. Content-sniffed on the bullet's distinctive lead; inserted
5034
+ // inside the existing section when its anchor line is intact, appended
5035
+ // otherwise so a hand-edited section still gains the guidance.
5036
+ if (content.includes('/mandate/evaluate') && !content.includes('User floor-action grants are phone-first')) {
5037
+ const grantBullet = `- **User floor-action grants are phone-first.** When the operator needs to grant a USER a floor action (e.g. "let Mia prod-deploy for an hour"), the Mandates tab carries a grant form on every active mandate: pick the person (from the registered-user list), pick the action and duration, type the PIN, tap Grant. Send them the dashboard link — NEVER a terminal command or a hand-built API call (Mobile-Complete Operator Actions). The grant is signed into the mandate, clamped to the mandate's expiry, and voided by revoking the mandate.`;
5038
+ const grantAnchor = 'point them at the dashboard **Mandates tab** (issue/revoke forms + the decision audit live there).';
5039
+ if (content.includes(grantAnchor)) {
5040
+ content = content.replace(grantAnchor, grantAnchor + '\n' + grantBullet);
5041
+ }
5042
+ else {
5043
+ content += '\n' + grantBullet + '\n';
5044
+ }
5045
+ patched = true;
5046
+ result.upgraded.push('CLAUDE.md: added phone-first floor-grant guidance to the Coordination Mandate section');
5047
+ }
5030
5048
  // ReviewExchange protocol (coordination-mandate spec §7 G2.3) — Agent
5031
5049
  // Awareness backfill. Content-sniffed on the distinctive route prefix.
5032
5050
  if (!content.includes('/review-exchange')) {