instar 1.3.277 → 1.3.278
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 +104 -0
- package/dashboard/mandates.js +205 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +44 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/scaffold/templates.js +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +19 -19
- package/src/scaffold/templates.ts +1 -1
- package/upgrades/1.3.278.md +36 -0
- package/upgrades/side-effects/mandates-dashboard-tab.md +56 -0
package/dashboard/index.html
CHANGED
|
@@ -790,6 +790,35 @@
|
|
|
790
790
|
display: none !important;
|
|
791
791
|
}
|
|
792
792
|
|
|
793
|
+
/* Mandates tab */
|
|
794
|
+
.mnd-card { border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
795
|
+
.mnd-card-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
796
|
+
.mnd-scope { font-size: 17px; font-weight: 600; color: var(--text-bright); }
|
|
797
|
+
.mnd-badge { font-size: 12px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); }
|
|
798
|
+
.mnd-ok { color: #3fb950; border-color: #3fb95055; }
|
|
799
|
+
.mnd-bad { color: #f85149; border-color: #f8514955; font-weight: 700; }
|
|
800
|
+
.mnd-deny { color: #d29922; border-color: #d2992255; }
|
|
801
|
+
.mnd-dead { color: var(--text-dim); }
|
|
802
|
+
.mnd-meta { font-size: 13px; color: var(--text-dim); }
|
|
803
|
+
.mnd-authorities { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.7; }
|
|
804
|
+
.mnd-empty { font-size: 16px; color: var(--text-dim); line-height: 1.6; padding: 12px 0; }
|
|
805
|
+
.mnd-revoke-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
806
|
+
.mnd-pin { width: 110px; } .mnd-reason { flex: 1; min-width: 160px; }
|
|
807
|
+
.mnd-btn { padding: 6px 14px; cursor: pointer; }
|
|
808
|
+
.mnd-btn-danger { color: #f85149; }
|
|
809
|
+
.mnd-dead-note { font-size: 13px; color: var(--text-dim); font-style: italic; }
|
|
810
|
+
.mnd-notice { min-height: 20px; font-size: 14px; color: #3fb950; }
|
|
811
|
+
.mnd-notice-err { color: #f85149; }
|
|
812
|
+
.mnd-issue-form { display: flex; flex-direction: column; gap: 10px; max-width: 640px; padding: 12px 0; }
|
|
813
|
+
.mnd-issue-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--text-dim); }
|
|
814
|
+
.mnd-issue-form input, .mnd-issue-form textarea { font: inherit; padding: 6px 8px; }
|
|
815
|
+
.mnd-issue-form textarea { font-family: ui-monospace, monospace; font-size: 12px; }
|
|
816
|
+
.mnd-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
817
|
+
.mnd-table th, .mnd-table td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
818
|
+
.mnd-reason-cell { color: var(--text-dim); max-width: 420px; }
|
|
819
|
+
.mnd-audit-head { display: flex; gap: 12px; align-items: center; margin-bottom: 8px; }
|
|
820
|
+
.mnd-dim { font-size: 13px; color: var(--text-dim); }
|
|
821
|
+
|
|
793
822
|
/* ── process-health tab (PROCESS-HEALTH-DASHBOARD-TAB-SPEC §4.1) ──
|
|
794
823
|
Large, readable, calm. No monospace, no tables, no alarm colors.
|
|
795
824
|
Measurable bars: headline ≥24px, section headers ≥20px, body ≥17px,
|
|
@@ -2652,6 +2681,7 @@
|
|
|
2652
2681
|
<button class="tab" data-tab="process-health" onclick="switchTab('process-health')">Process Health</button>
|
|
2653
2682
|
<button class="tab" data-tab="preferences-learning" onclick="switchTab('preferences-learning')">Preferences</button>
|
|
2654
2683
|
<button class="tab" data-tab="machines" onclick="switchTab('machines')">Machines</button>
|
|
2684
|
+
<button class="tab" data-tab="mandates" onclick="switchTab('mandates')">Mandates</button>
|
|
2655
2685
|
</nav>
|
|
2656
2686
|
</div>
|
|
2657
2687
|
<div class="vital-signs" id="vitalSigns">
|
|
@@ -3200,6 +3230,37 @@
|
|
|
3200
3230
|
</details>
|
|
3201
3231
|
</div>
|
|
3202
3232
|
|
|
3233
|
+
<!-- mandates tab (coordination-mandate spec, decision 2A — the operator's PIN-gated surface) -->
|
|
3234
|
+
<div id="mandatesPanel" class="ph-root" style="display:none;flex-direction:column;padding:28px;gap:24px;overflow-y:auto">
|
|
3235
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
|
3236
|
+
<h2 class="ph-title">Coordination Mandates</h2>
|
|
3237
|
+
<div id="mndStamp" class="ph-stamp" aria-live="polite"></div>
|
|
3238
|
+
</div>
|
|
3239
|
+
<p class="ph-intro">Your bounded "permission slips" for autonomous agent-to-agent work. The slip — never the agent — is the authorizer. Issuing and revoking require <strong>your dashboard PIN</strong>; agent credentials alone are refused by design. With no slip issued, every delegated action is denied.</p>
|
|
3240
|
+
<div id="mndNotice" class="mnd-notice" aria-live="polite"></div>
|
|
3241
|
+
<section class="ph-section">
|
|
3242
|
+
<h3 class="ph-h">Issued mandates</h3>
|
|
3243
|
+
<div id="mndList"></div>
|
|
3244
|
+
</section>
|
|
3245
|
+
<details class="ph-detail">
|
|
3246
|
+
<summary class="ph-detail-summary">Issue a new mandate (PIN required)</summary>
|
|
3247
|
+
<div class="mnd-issue-form">
|
|
3248
|
+
<label>Scope <input type="text" id="mndIssueScope" value="feedback-migration" /></label>
|
|
3249
|
+
<label>Agent A fingerprint <input type="text" id="mndIssueAgentA" placeholder="full routing fingerprint" /></label>
|
|
3250
|
+
<label>Agent B fingerprint <input type="text" id="mndIssueAgentB" placeholder="full routing fingerprint" /></label>
|
|
3251
|
+
<label>Authorities (JSON) <textarea id="mndIssueAuthorities" rows="9" spellcheck="false"></textarea></label>
|
|
3252
|
+
<label>Expires <input type="datetime-local" id="mndIssueExpires" /></label>
|
|
3253
|
+
<label>Your dashboard PIN <input type="password" id="mndIssuePin" autocomplete="off" placeholder="typed at issuance, never stored" /></label>
|
|
3254
|
+
<button id="mndIssueBtn" class="mnd-btn">Issue mandate</button>
|
|
3255
|
+
</div>
|
|
3256
|
+
</details>
|
|
3257
|
+
<section class="ph-section">
|
|
3258
|
+
<h3 class="ph-h">Decision audit</h3>
|
|
3259
|
+
<p class="ph-h-sub">Every allow and deny, hash-chained. A broken chain means tampering — treat it as an incident.</p>
|
|
3260
|
+
<div id="mndAudit"></div>
|
|
3261
|
+
</section>
|
|
3262
|
+
</div>
|
|
3263
|
+
|
|
3203
3264
|
<!-- preferences-learning tab (Correction & Preference Learning Sentinel §10 Slice-2) -->
|
|
3204
3265
|
<div id="preferencesLearningPanel" class="ph-root" style="display:none;flex-direction:column;padding:28px;gap:24px;overflow-y:auto">
|
|
3205
3266
|
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
|
@@ -4543,6 +4604,13 @@
|
|
|
4543
4604
|
onActivate: () => { if (typeof startMachines === 'function') startMachines(); },
|
|
4544
4605
|
onDeactivate: () => { if (typeof stopMachines === 'function') stopMachines(); },
|
|
4545
4606
|
},
|
|
4607
|
+
{
|
|
4608
|
+
id: 'mandates',
|
|
4609
|
+
panels: ['mandatesPanel'],
|
|
4610
|
+
display: ['flex'],
|
|
4611
|
+
onActivate: () => { if (typeof startMandates === 'function') startMandates(); },
|
|
4612
|
+
onDeactivate: () => { if (typeof stopMandates === 'function') stopMandates(); },
|
|
4613
|
+
},
|
|
4546
4614
|
{
|
|
4547
4615
|
id: 'secrets',
|
|
4548
4616
|
panels: ['secretsPanel'],
|
|
@@ -8474,6 +8542,42 @@
|
|
|
8474
8542
|
if (__phController) __phController.stop();
|
|
8475
8543
|
}
|
|
8476
8544
|
|
|
8545
|
+
// ── Mandates tab (coordination-mandate spec, decision 2A) ──
|
|
8546
|
+
// Same lazy dynamic-import pattern as Process Health. The PIN typed in this
|
|
8547
|
+
// tab is sent once per action and never stored anywhere.
|
|
8548
|
+
let __mndController = null;
|
|
8549
|
+
let __mndModule = null;
|
|
8550
|
+
async function startMandates() {
|
|
8551
|
+
if (!__mndModule) {
|
|
8552
|
+
try { __mndModule = await import('/dashboard/mandates.js'); }
|
|
8553
|
+
catch (e) { console.error('[mandates] module load failed', e); return; }
|
|
8554
|
+
}
|
|
8555
|
+
if (!__mndController) {
|
|
8556
|
+
const els = {
|
|
8557
|
+
list: document.getElementById('mndList'),
|
|
8558
|
+
audit: document.getElementById('mndAudit'),
|
|
8559
|
+
notice: document.getElementById('mndNotice'),
|
|
8560
|
+
stamp: document.getElementById('mndStamp'),
|
|
8561
|
+
issueScope: document.getElementById('mndIssueScope'),
|
|
8562
|
+
issueAgentA: document.getElementById('mndIssueAgentA'),
|
|
8563
|
+
issueAgentB: document.getElementById('mndIssueAgentB'),
|
|
8564
|
+
issueAuthorities: document.getElementById('mndIssueAuthorities'),
|
|
8565
|
+
issueExpires: document.getElementById('mndIssueExpires'),
|
|
8566
|
+
issuePin: document.getElementById('mndIssuePin'),
|
|
8567
|
+
issueBtn: document.getElementById('mndIssueBtn'),
|
|
8568
|
+
};
|
|
8569
|
+
const fetchImpl = (url, opts = {}) => fetch(url, {
|
|
8570
|
+
...opts,
|
|
8571
|
+
headers: { 'Authorization': `Bearer ${token}`, ...(opts.headers || {}) },
|
|
8572
|
+
});
|
|
8573
|
+
__mndController = __mndModule.createController({ doc: document, els, fetchImpl });
|
|
8574
|
+
}
|
|
8575
|
+
__mndController.start();
|
|
8576
|
+
}
|
|
8577
|
+
function stopMandates() {
|
|
8578
|
+
if (__mndController) __mndController.stop();
|
|
8579
|
+
}
|
|
8580
|
+
|
|
8477
8581
|
// ── Preferences tab (Correction & Preference Learning Sentinel §10 Slice-2) ──
|
|
8478
8582
|
// Same lazy dynamic-import pattern as Process Health: the controller lives in
|
|
8479
8583
|
// this (classic) scope so it can read `token`; the module ships the pure
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mandates.js — the Mandates dashboard tab (coordination-mandate spec, decision 2A).
|
|
3
|
+
*
|
|
4
|
+
* The OPERATOR's surface for the Coordination Mandate engine: see every mandate
|
|
5
|
+
* (with live authorship verification), issue a new one, revoke one, and read the
|
|
6
|
+
* hash-chained decision audit. Issuance + revocation require the dashboard PIN —
|
|
7
|
+
* typed here at action time, sent once, NEVER stored (no localStorage, no module
|
|
8
|
+
* state). The Bearer token alone cannot perform either; that is the engine's
|
|
9
|
+
* design, and this tab is the human-authenticated surface it points at.
|
|
10
|
+
*
|
|
11
|
+
* Same shape as process-health.js / preferences-learning.js: pure renderers +
|
|
12
|
+
* createController({ doc, els, fetchImpl }) so index.html stays a thin shim.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const REFRESH_MS = 30_000;
|
|
16
|
+
|
|
17
|
+
// The A/A/B first-mandate shape, prefilled so the operator edits rather than authors.
|
|
18
|
+
const AUTHORITIES_TEMPLATE = JSON.stringify([
|
|
19
|
+
{ action: 'exchange-read-credential', bounds: { credentialScope: 'read-only', onMachine: true } },
|
|
20
|
+
{ action: 'sign-code-review', bounds: { artifact: 'migration-port', mutual: true } },
|
|
21
|
+
], null, 2);
|
|
22
|
+
|
|
23
|
+
function esc(s) {
|
|
24
|
+
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
25
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
26
|
+
}[c]));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function shortFp(fp) {
|
|
30
|
+
const s = String(fp ?? '');
|
|
31
|
+
return s.length > 12 ? s.slice(0, 8) + '…' : s;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fmtWhen(iso) {
|
|
35
|
+
if (!iso) return '—';
|
|
36
|
+
try {
|
|
37
|
+
const d = new Date(iso);
|
|
38
|
+
return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
39
|
+
} catch { return String(iso); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function renderMandates(list) {
|
|
43
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
44
|
+
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
|
+
}
|
|
46
|
+
return list.map((m) => {
|
|
47
|
+
const expired = Date.parse(m.expiresAt) < Date.now();
|
|
48
|
+
const state = m.revoked ? 'revoked' : expired ? 'expired' : 'active';
|
|
49
|
+
const stateCls = state === 'active' ? 'mnd-ok' : 'mnd-dead';
|
|
50
|
+
const authBadge = m.authorshipValid
|
|
51
|
+
? '<span class="mnd-badge mnd-ok">authorship verified</span>'
|
|
52
|
+
: '<span class="mnd-badge mnd-bad">AUTHORSHIP INVALID</span>';
|
|
53
|
+
const authorities = (m.authorities || []).map((a) =>
|
|
54
|
+
`<li><code>${esc(a.action)}</code> — bounds <code>${esc(JSON.stringify(a.bounds))}</code>${a.requiresCondition ? ` — requires <code>${esc(a.requiresCondition)}</code>` : ''}</li>`,
|
|
55
|
+
).join('');
|
|
56
|
+
const revokeUi = state === 'active'
|
|
57
|
+
? `<div class="mnd-revoke-row">
|
|
58
|
+
<input type="password" class="mnd-pin" data-revoke-pin="${esc(m.id)}" placeholder="PIN" autocomplete="off" />
|
|
59
|
+
<input type="text" class="mnd-reason" data-revoke-reason="${esc(m.id)}" placeholder="reason" />
|
|
60
|
+
<button class="mnd-btn mnd-btn-danger" data-revoke="${esc(m.id)}">Revoke</button>
|
|
61
|
+
</div>`
|
|
62
|
+
: `<div class="mnd-dead-note">${m.revoked ? `revoked ${fmtWhen(m.revoked.at)} — ${esc(m.revoked.reason)}` : `expired ${fmtWhen(m.expiresAt)}`}</div>`;
|
|
63
|
+
return `<div class="mnd-card">
|
|
64
|
+
<div class="mnd-card-head">
|
|
65
|
+
<span class="mnd-scope">${esc(m.scope)}</span>
|
|
66
|
+
<span class="mnd-badge ${stateCls}">${state}</span>
|
|
67
|
+
${authBadge}
|
|
68
|
+
</div>
|
|
69
|
+
<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
|
+
<ul class="mnd-authorities">${authorities}</ul>
|
|
71
|
+
${revokeUi}
|
|
72
|
+
</div>`;
|
|
73
|
+
}).join('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function renderAudit(payload) {
|
|
77
|
+
const entries = payload?.entries ?? [];
|
|
78
|
+
const chainOk = payload?.chain?.ok;
|
|
79
|
+
const chainBadge = chainOk === false
|
|
80
|
+
? '<span class="mnd-badge mnd-bad">CHAIN BROKEN — possible tampering</span>'
|
|
81
|
+
: '<span class="mnd-badge mnd-ok">chain verified</span>';
|
|
82
|
+
if (entries.length === 0) {
|
|
83
|
+
return `<div class="mnd-audit-head">${chainBadge}</div><div class="mnd-empty">No decisions recorded yet.</div>`;
|
|
84
|
+
}
|
|
85
|
+
const rows = entries.slice(-25).reverse().map((e) =>
|
|
86
|
+
`<tr>
|
|
87
|
+
<td>${fmtWhen(e.ts)}</td>
|
|
88
|
+
<td><span class="mnd-badge ${e.decision === 'allow' ? 'mnd-ok' : 'mnd-deny'}">${esc(e.decision)}</span></td>
|
|
89
|
+
<td><code>${esc(e.action)}</code></td>
|
|
90
|
+
<td><code>${esc(shortFp(e.agentFp))}</code></td>
|
|
91
|
+
<td class="mnd-reason-cell">${esc(e.reason)}</td>
|
|
92
|
+
</tr>`,
|
|
93
|
+
).join('');
|
|
94
|
+
return `<div class="mnd-audit-head">${chainBadge}<span class="mnd-dim">${entries.length} total decisions · newest first</span></div>
|
|
95
|
+
<table class="mnd-table"><thead><tr><th>when</th><th>decision</th><th>action</th><th>agent</th><th>reason</th></tr></thead><tbody>${rows}</tbody></table>`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createController({ doc, els, fetchImpl }) {
|
|
99
|
+
let timer = null;
|
|
100
|
+
let running = false;
|
|
101
|
+
|
|
102
|
+
async function fetchJson(url, opts) {
|
|
103
|
+
const res = await fetchImpl(url, opts);
|
|
104
|
+
let body = null;
|
|
105
|
+
try { body = await res.json(); } catch { body = null; }
|
|
106
|
+
return { status: res.status, body };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function note(msg, isError) {
|
|
110
|
+
if (!els.notice) return;
|
|
111
|
+
els.notice.textContent = msg;
|
|
112
|
+
els.notice.className = isError ? 'mnd-notice mnd-notice-err' : 'mnd-notice';
|
|
113
|
+
if (msg) setTimeout(() => { if (els.notice.textContent === msg) els.notice.textContent = ''; }, 8000);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function refresh() {
|
|
117
|
+
try {
|
|
118
|
+
const [mand, audit] = await Promise.all([
|
|
119
|
+
fetchJson('/mandate'),
|
|
120
|
+
fetchJson('/mandate/audit?limit=200'),
|
|
121
|
+
]);
|
|
122
|
+
if (mand.status === 503) {
|
|
123
|
+
els.list.innerHTML = '<div class="mnd-empty">Mandate engine unavailable on this server (older version or init failure).</div>';
|
|
124
|
+
els.audit.innerHTML = '';
|
|
125
|
+
if (els.stamp) els.stamp.textContent = '';
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
els.list.innerHTML = renderMandates(mand.body?.mandates);
|
|
129
|
+
els.audit.innerHTML = renderAudit(audit.body);
|
|
130
|
+
if (els.stamp) els.stamp.textContent = 'updated ' + new Date().toLocaleTimeString();
|
|
131
|
+
wireRevokeButtons();
|
|
132
|
+
} catch (e) {
|
|
133
|
+
note('refresh failed: ' + (e?.message ?? e), true);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function wireRevokeButtons() {
|
|
138
|
+
els.list.querySelectorAll('[data-revoke]').forEach((btn) => {
|
|
139
|
+
btn.onclick = async () => {
|
|
140
|
+
const id = btn.getAttribute('data-revoke');
|
|
141
|
+
const pinEl = els.list.querySelector(`[data-revoke-pin="${id}"]`);
|
|
142
|
+
const reasonEl = els.list.querySelector(`[data-revoke-reason="${id}"]`);
|
|
143
|
+
const pin = pinEl?.value ?? '';
|
|
144
|
+
if (!pin) { note('Type your dashboard PIN to revoke — revocation is a human action.', true); return; }
|
|
145
|
+
btn.disabled = true;
|
|
146
|
+
try {
|
|
147
|
+
const { status, body } = await fetchJson(`/mandate/${encodeURIComponent(id)}/revoke`, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: { 'Content-Type': 'application/json' },
|
|
150
|
+
body: JSON.stringify({ pin, reason: reasonEl?.value || 'operator revocation (dashboard)' }),
|
|
151
|
+
});
|
|
152
|
+
if (pinEl) pinEl.value = ''; // never retain the PIN
|
|
153
|
+
if (status === 200) { note(`Mandate ${id} revoked.`); await refresh(); }
|
|
154
|
+
else note(body?.error ?? `revoke failed (${status})`, true);
|
|
155
|
+
} finally { btn.disabled = false; }
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function issue() {
|
|
161
|
+
const pin = els.issuePin?.value ?? '';
|
|
162
|
+
if (!pin) { note('Type your dashboard PIN — issuance is a human action; agent credentials are refused.', true); return; }
|
|
163
|
+
let authorities;
|
|
164
|
+
try { authorities = JSON.parse(els.issueAuthorities?.value || '[]'); }
|
|
165
|
+
catch { note('Authorities must be valid JSON (array of { action, bounds }).', true); return; }
|
|
166
|
+
const payload = {
|
|
167
|
+
pin,
|
|
168
|
+
scope: els.issueScope?.value?.trim(),
|
|
169
|
+
agents: [els.issueAgentA?.value?.trim(), els.issueAgentB?.value?.trim()],
|
|
170
|
+
authorities,
|
|
171
|
+
expiresAt: els.issueExpires?.value ? new Date(els.issueExpires.value).toISOString() : '',
|
|
172
|
+
};
|
|
173
|
+
els.issueBtn.disabled = true;
|
|
174
|
+
try {
|
|
175
|
+
const { status, body } = await fetchJson('/mandate/issue', {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
body: JSON.stringify(payload),
|
|
179
|
+
});
|
|
180
|
+
if (els.issuePin) els.issuePin.value = ''; // never retain the PIN
|
|
181
|
+
if (status === 201) {
|
|
182
|
+
note(`Mandate ${body?.mandate?.id ?? ''} issued.`);
|
|
183
|
+
await refresh();
|
|
184
|
+
} else {
|
|
185
|
+
note(body?.error ?? `issue failed (${status})`, true);
|
|
186
|
+
}
|
|
187
|
+
} finally { els.issueBtn.disabled = false; }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function start() {
|
|
191
|
+
if (running) return;
|
|
192
|
+
running = true;
|
|
193
|
+
if (els.issueAuthorities && !els.issueAuthorities.value) els.issueAuthorities.value = AUTHORITIES_TEMPLATE;
|
|
194
|
+
if (els.issueBtn) els.issueBtn.onclick = issue;
|
|
195
|
+
refresh();
|
|
196
|
+
timer = setInterval(refresh, REFRESH_MS);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function stop() {
|
|
200
|
+
running = false;
|
|
201
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { start, stop, refresh };
|
|
205
|
+
}
|
|
@@ -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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"}
|
|
@@ -4066,6 +4066,50 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
|
|
|
4066
4066
|
patched = true;
|
|
4067
4067
|
result.upgraded.push('CLAUDE.md: added Resource Usage (CPU + memory) awareness section');
|
|
4068
4068
|
}
|
|
4069
|
+
// Coordination Mandate engine (docs/specs/coordination-mandate.md) — Agent
|
|
4070
|
+
// Awareness backfill for the deny-by-default A2A authority gate (#788).
|
|
4071
|
+
// Existing agents updating in place must learn to evaluate-before-acting and
|
|
4072
|
+
// that issuance/revocation are PIN-gated human actions. Content-sniffed on
|
|
4073
|
+
// the distinctive evaluate route.
|
|
4074
|
+
if (!content.includes('/mandate/evaluate')) {
|
|
4075
|
+
const section = `
|
|
4076
|
+
**Coordination Mandate** — Your operator's "permission slip" for autonomous agent-to-agent work. Instead of approving every step of a multi-agent project, the operator issues ONE bounded, expiring, revocable mandate (from the dashboard Mandates tab, behind their PIN) delegating SPECIFIC authorities to a SPECIFIC pair of agents. The mandate — never you — is the authorizer: requester ≠ authorizer is preserved. Deny-by-default: with no mandate issued, every check denies.
|
|
4077
|
+
- **Before any A2A action under a mandate** (PROACTIVE — this is the trigger): check it: \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/mandate/evaluate -H 'Content-Type: application/json' -d '{"action":"sign-code-review","params":{"artifact":"migration-port","mutual":true},"agentFp":"<your-fingerprint>","mandateId":"<id>"}'\` → \`{ decision: "allow"|"deny", reason }\`. A deny means STOP — do not retry around it or escalate to a human-bypass; the bounds are the operator's.
|
|
4078
|
+
- Inspect: \`GET /mandate\` (each with live \`authorshipValid\`) · \`GET /mandate/:id\` · \`GET /mandate/audit\` (every decision, hash-chained — \`chain.ok:false\` means tampering; surface it immediately).
|
|
4079
|
+
- **You cannot issue or revoke mandates.** \`POST /mandate/issue\` and \`POST /mandate/:id/revoke\` require the operator's dashboard PIN — your Bearer token is structurally insufficient. NEVER ask the user to paste their PIN into chat; point them at the dashboard **Mandates tab** (issue/revoke forms + the decision audit live there).
|
|
4080
|
+
- Every evaluation (allow AND deny) is audited. Act as if the audit is read by the operator — because it is. (Spec: \`docs/specs/coordination-mandate.md\`.)
|
|
4081
|
+
`;
|
|
4082
|
+
content += '\n' + section;
|
|
4083
|
+
patched = true;
|
|
4084
|
+
result.upgraded.push('CLAUDE.md: added Coordination Mandate awareness section');
|
|
4085
|
+
}
|
|
4086
|
+
// ReviewExchange protocol (coordination-mandate spec §7 G2.3) — Agent
|
|
4087
|
+
// Awareness backfill. Content-sniffed on the distinctive route prefix.
|
|
4088
|
+
if (!content.includes('/review-exchange')) {
|
|
4089
|
+
const section = `
|
|
4090
|
+
**ReviewExchange (autonomous code review)** — The structured way two mandate-named agents sign off a code review WITHOUT the operator relaying. One exchange = one review package, content-addressed (\`packageSha256\` fixed at creation), moving linearly: proposed → delivered → verdict-recorded → complete (or changes-requested — rework is a NEW exchange). BOTH sign-offs (the peer's authenticated approve-verdict AND your countersignature) are evaluated through the mandate gate's \`sign-code-review\` authority before acceptance; every accepted signature carries the audit hash of the gate decision that authorized it.
|
|
4091
|
+
- Create: \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/review-exchange -H 'Content-Type: application/json' -d '{"mandateId":"<id>","artifact":"migration-port","packageRef":"docs/...-review-package.md","packageSha256":"<sha256 of the package>","parties":["<your-fp>","<peer-fp>"]}'\`
|
|
4092
|
+
- Drive it: \`POST /review-exchange/:id/delivered\` (after you actually sent the package over Threadline — record the message ref as evidence) → \`POST /review-exchange/:id/peer-verdict\` (the peer's authenticated verdict; approve = their sign-off, mandate-gated) → \`POST /review-exchange/:id/sign\` (your countersignature, mandate-gated → complete).
|
|
4093
|
+
- **When to use** (PROACTIVE — this is the trigger): the moment a mandate with \`sign-code-review\` exists and you need a peer agent's review of work in its scope, drive it through an exchange — NEVER improvise a sign-off in chat prose (an unrecorded "LGTM" over Threadline is not a sign-off; the gate-audited exchange is). A 403 on a sign step means the mandate denied it — STOP, do not work around it.
|
|
4094
|
+
- Inspect: \`GET /review-exchange\` · \`GET /review-exchange/:id\` (signatures + audit hashes).
|
|
4095
|
+
`;
|
|
4096
|
+
content += '\n' + section;
|
|
4097
|
+
patched = true;
|
|
4098
|
+
result.upgraded.push('CLAUDE.md: added ReviewExchange awareness section');
|
|
4099
|
+
}
|
|
4100
|
+
// Cutover-readiness checker (coordination-mandate spec §7 G2.4) — Agent
|
|
4101
|
+
// Awareness backfill. Content-sniffed on the distinctive route prefix.
|
|
4102
|
+
if (!content.includes('/cutover-readiness')) {
|
|
4103
|
+
const section = `
|
|
4104
|
+
**Cutover Readiness** — When a migration (or any one-way cutover) is gated on objective conditions, this is the read surface for "is everything up to the door green?" — composed from REAL durable state (the persisted import integrity report + the durable zero-divergence parity window with a freshness bound), never from anyone's assertion.
|
|
4105
|
+
- Check: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/cutover-readiness\` → \`{ ready, door: "manual-operator-click", integrity, parity }\`.
|
|
4106
|
+
- Feed the parity window with a live check: \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/cutover-readiness/parity-pass\` — the server fetches + compares server-side; you only trigger it. A failed check records nothing.
|
|
4107
|
+
- **The door is NOT yours**: \`ready: true\` means the conditions are green — it is NEVER an instruction to flip. The cutover click belongs to the operator. NEVER present \`ready\` to the user as "I can cut over now"; present it as "everything up to your click is green."
|
|
4108
|
+
`;
|
|
4109
|
+
content += '\n' + section;
|
|
4110
|
+
patched = true;
|
|
4111
|
+
result.upgraded.push('CLAUDE.md: added Cutover Readiness awareness section');
|
|
4112
|
+
}
|
|
4069
4113
|
if (patched) {
|
|
4070
4114
|
try {
|
|
4071
4115
|
fs.writeFileSync(claudeMdPath, content);
|