instar 0.28.110 → 0.28.112
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/proposals.html +418 -0
- package/dist/remediation/NovelFailureReviewer.d.ts +212 -0
- package/dist/remediation/NovelFailureReviewer.d.ts.map +1 -0
- package/dist/remediation/NovelFailureReviewer.js +605 -0
- package/dist/remediation/NovelFailureReviewer.js.map +1 -0
- package/dist/remediation/Remediator.d.ts.map +1 -1
- package/dist/remediation/Remediator.js +1 -0
- package/dist/remediation/Remediator.js.map +1 -1
- package/dist/remediation/TrustElevationSource.d.ts +8 -1
- package/dist/remediation/TrustElevationSource.d.ts.map +1 -1
- package/dist/remediation/TrustElevationSource.js +8 -1
- package/dist/remediation/TrustElevationSource.js.map +1 -1
- package/dist/remediation/audit/AuditProjection.d.ts +7 -0
- package/dist/remediation/audit/AuditProjection.d.ts.map +1 -1
- package/dist/remediation/audit/AuditProjection.js +16 -0
- package/dist/remediation/audit/AuditProjection.js.map +1 -1
- package/dist/remediation/audit/AuditWriter.d.ts +8 -0
- package/dist/remediation/audit/AuditWriter.d.ts.map +1 -1
- package/dist/remediation/audit/AuditWriter.js +5 -1
- package/dist/remediation/audit/AuditWriter.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +24 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes/remediation-proposals.d.ts +114 -0
- package/dist/server/routes/remediation-proposals.d.ts.map +1 -0
- package/dist/server/routes/remediation-proposals.js +350 -0
- package/dist/server/routes/remediation-proposals.js.map +1 -0
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +31 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/0.28.111.md +43 -0
- package/upgrades/0.28.112.md +28 -0
- package/upgrades/side-effects/s1-novel-failure-reviewer.md +79 -0
- package/upgrades/side-effects/s2-dashboard-proposals.md +76 -0
- package/upgrades/side-effects/s3-promotion-gate.md +111 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Proposals routes — Tier-3 S-2 of the Self-Healing Remediator v2.
|
|
3
|
+
*
|
|
4
|
+
* Spec anchor: docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md
|
|
5
|
+
* §A10 — outstanding-proposal cap (≤ 3 visible at any time + queued behind).
|
|
6
|
+
* §A13 — Config + telemetry + dashboard taxonomy. Proposals is a sub-section
|
|
7
|
+
* of the existing Remediation tab. All four `/remediation/proposals*`
|
|
8
|
+
* routes require bearer auth + `X-Instar-Request: 1`. Redacted view
|
|
9
|
+
* by default; full `reason.full` field shown only at `collaborative`
|
|
10
|
+
* trust (matches v1's `/remediation/attempts` redaction rule).
|
|
11
|
+
* §A26 — Proposal dismiss requires `collaborative` trust + ≤ 10/hour
|
|
12
|
+
* per-principal rate-limit. Every dismiss is audit-logged with the
|
|
13
|
+
* principal identity (callers wire the audit-writer; this route
|
|
14
|
+
* emits structured events / route-level diagnostics only).
|
|
15
|
+
* §A48 — Path-limited proposal fetch shape (id-keyed JSON files under
|
|
16
|
+
* `.instar/remediation/proposals-<machineId>/<proposalId>.json`).
|
|
17
|
+
* §A57 Tier-3 — Dashboard Proposals sub-section + auth-gated routes.
|
|
18
|
+
*
|
|
19
|
+
* Routes:
|
|
20
|
+
* GET /remediation/proposals — list outstanding proposals
|
|
21
|
+
* (visible + queued).
|
|
22
|
+
* GET /remediation/proposals/:id — detail view of a proposal.
|
|
23
|
+
* POST /remediation/proposals/:id/dismiss — user dismisses (no promotion).
|
|
24
|
+
* Requires `collaborative` trust.
|
|
25
|
+
*
|
|
26
|
+
* Redaction rule: at less than `collaborative` trust, the response omits
|
|
27
|
+
* `forensic.rawResponse` and any `sampleEvents[].reason.full` field. At
|
|
28
|
+
* `collaborative` trust, both fields are included verbatim. The proposal
|
|
29
|
+
* file on disk always carries redacted `reason.redacted` per S-1; this
|
|
30
|
+
* route enforces redaction at the response boundary regardless of disk
|
|
31
|
+
* shape, so a future migration adding `reason.full` does not silently
|
|
32
|
+
* leak the field through.
|
|
33
|
+
*
|
|
34
|
+
* Reads proposals from every `proposals-<machineId>/` directory under
|
|
35
|
+
* `<stateDir>/remediation/`. The store is per-machine (A14) but the
|
|
36
|
+
* dashboard surfaces a fleet-wide list; merging happens here at the read
|
|
37
|
+
* boundary. The `producingAgentId` field on each proposal disambiguates
|
|
38
|
+
* cross-machine duplicates per §A60.
|
|
39
|
+
*
|
|
40
|
+
* Dismiss is implemented as a status-mutation in place (status: 'outstanding'
|
|
41
|
+
* → 'dismissed'). The proposal is NOT deleted — auditability requires the
|
|
42
|
+
* file to remain for post-hoc forensic review. The outstanding-3 cap
|
|
43
|
+
* counts only `status === 'outstanding'` entries.
|
|
44
|
+
*/
|
|
45
|
+
import fs from 'node:fs';
|
|
46
|
+
import path from 'node:path';
|
|
47
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
48
|
+
const PROPOSAL_DIR_PREFIX = 'proposals-';
|
|
49
|
+
const PROPOSAL_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
|
|
50
|
+
const DISMISS_RATE_LIMIT_PER_HOUR = 10;
|
|
51
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
52
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
53
|
+
function requireUserIntent(req, res) {
|
|
54
|
+
const header = String(req.header('x-instar-request') ?? '').trim();
|
|
55
|
+
if (header !== '1') {
|
|
56
|
+
res.status(400).json({
|
|
57
|
+
error: 'X-Instar-Request: 1 header required (user-intent attestation)',
|
|
58
|
+
reason: 'missing-user-intent',
|
|
59
|
+
});
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function listProposalDirs(remediationDir) {
|
|
65
|
+
if (!fs.existsSync(remediationDir))
|
|
66
|
+
return [];
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = fs.readdirSync(remediationDir);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const name of entries) {
|
|
76
|
+
if (!name.startsWith(PROPOSAL_DIR_PREFIX))
|
|
77
|
+
continue;
|
|
78
|
+
const abs = path.join(remediationDir, name);
|
|
79
|
+
try {
|
|
80
|
+
const stat = fs.statSync(abs);
|
|
81
|
+
if (stat.isDirectory())
|
|
82
|
+
out.push(abs);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Skip unreadable entries.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function readProposalsFromDir(dir) {
|
|
91
|
+
let files;
|
|
92
|
+
try {
|
|
93
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const f of files) {
|
|
100
|
+
try {
|
|
101
|
+
const raw = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
102
|
+
const parsed = JSON.parse(raw);
|
|
103
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.proposalId === 'string') {
|
|
104
|
+
parsed.sourceMachineDir = path.basename(dir);
|
|
105
|
+
out.push(parsed);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Skip corrupt entries; A26 forensic copy lives in llm-raw-*.jsonl.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
function loadAllProposals(stateDir) {
|
|
115
|
+
const remediationDir = path.join(stateDir, 'remediation');
|
|
116
|
+
const dirs = listProposalDirs(remediationDir);
|
|
117
|
+
const all = [];
|
|
118
|
+
for (const d of dirs) {
|
|
119
|
+
all.push(...readProposalsFromDir(d));
|
|
120
|
+
}
|
|
121
|
+
// Sort oldest-first to make the visible-3 deterministic.
|
|
122
|
+
all.sort((a, b) => (a.generatedAt ?? 0) - (b.generatedAt ?? 0));
|
|
123
|
+
return all;
|
|
124
|
+
}
|
|
125
|
+
function findProposalById(stateDir, id) {
|
|
126
|
+
const remediationDir = path.join(stateDir, 'remediation');
|
|
127
|
+
const dirs = listProposalDirs(remediationDir);
|
|
128
|
+
for (const d of dirs) {
|
|
129
|
+
const candidate = path.join(d, `${id}.json`);
|
|
130
|
+
if (fs.existsSync(candidate)) {
|
|
131
|
+
try {
|
|
132
|
+
const raw = fs.readFileSync(candidate, 'utf8');
|
|
133
|
+
const parsed = JSON.parse(raw);
|
|
134
|
+
if (parsed && typeof parsed === 'object' && parsed.proposalId === id) {
|
|
135
|
+
parsed.sourceMachineDir = path.basename(d);
|
|
136
|
+
return { proposal: parsed, filePath: candidate };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Skip — treated as not-found.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Apply the §A13 redaction rule: at less than `collaborative` trust, strip
|
|
148
|
+
* `forensic.rawResponse` and any `sampleEvents[].reason.full` field. Returns
|
|
149
|
+
* a defensive copy — the on-disk JSON is never mutated.
|
|
150
|
+
*/
|
|
151
|
+
function redactForViewer(proposal, hasCollaborative) {
|
|
152
|
+
if (hasCollaborative) {
|
|
153
|
+
return { ...proposal, redactionApplied: false };
|
|
154
|
+
}
|
|
155
|
+
const copy = {
|
|
156
|
+
...proposal,
|
|
157
|
+
redactionApplied: true,
|
|
158
|
+
};
|
|
159
|
+
if (Array.isArray(copy.sampleEvents)) {
|
|
160
|
+
copy.sampleEvents = copy.sampleEvents.map((e) => {
|
|
161
|
+
if (!e || !e.reason)
|
|
162
|
+
return e;
|
|
163
|
+
const { full: _omit, ...rest } = e.reason;
|
|
164
|
+
return { ...e, reason: rest };
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (copy.forensic && typeof copy.forensic === 'object') {
|
|
168
|
+
const { rawResponse: _omit, ...rest } = copy.forensic;
|
|
169
|
+
copy.forensic = rest;
|
|
170
|
+
}
|
|
171
|
+
return copy;
|
|
172
|
+
}
|
|
173
|
+
function makeDismissLimiter(now) {
|
|
174
|
+
const buckets = new Map();
|
|
175
|
+
return function check(principal) {
|
|
176
|
+
const t = now();
|
|
177
|
+
let bucket = buckets.get(principal);
|
|
178
|
+
if (!bucket) {
|
|
179
|
+
bucket = { timestamps: [] };
|
|
180
|
+
buckets.set(principal, bucket);
|
|
181
|
+
}
|
|
182
|
+
// Drop expired timestamps.
|
|
183
|
+
while (bucket.timestamps.length > 0 && bucket.timestamps[0] <= t - HOUR_MS) {
|
|
184
|
+
bucket.timestamps.shift();
|
|
185
|
+
}
|
|
186
|
+
if (bucket.timestamps.length >= DISMISS_RATE_LIMIT_PER_HOUR) {
|
|
187
|
+
return {
|
|
188
|
+
allowed: false,
|
|
189
|
+
retryAfterMs: bucket.timestamps[0] + HOUR_MS - t,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
bucket.timestamps.push(t);
|
|
193
|
+
return { allowed: true };
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function principalKey(req) {
|
|
197
|
+
// Bearer-only model: every authed call is the configured principal.
|
|
198
|
+
// We still partition by IP for defence-in-depth against future
|
|
199
|
+
// multi-principal expansion. `'auth'` fallback handles tests that mount
|
|
200
|
+
// the routes without a real socket (supertest).
|
|
201
|
+
return `${req.ip || req.socket?.remoteAddress || 'auth'}`;
|
|
202
|
+
}
|
|
203
|
+
// ── Public API ───────────────────────────────────────────────────────
|
|
204
|
+
/**
|
|
205
|
+
* Register the three Tier-3 S-2 Dashboard Proposals routes on the given
|
|
206
|
+
* Express app. The routes assume bearer-auth middleware is already mounted
|
|
207
|
+
* UPSTREAM of this registration — registration order is the caller's
|
|
208
|
+
* responsibility.
|
|
209
|
+
*/
|
|
210
|
+
export function registerRemediationProposalsRoutes(opts) {
|
|
211
|
+
const { app, stateDir, trustSource } = opts;
|
|
212
|
+
const now = opts.now ?? (() => Date.now());
|
|
213
|
+
const emit = (event, payload) => {
|
|
214
|
+
try {
|
|
215
|
+
opts.onEvent?.({ event, payload });
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Observability MUST never throw (matches NovelFailureReviewer convention).
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const dismissLimiter = makeDismissLimiter(now);
|
|
222
|
+
// GET /remediation/proposals — list (visible + queued).
|
|
223
|
+
app.get('/remediation/proposals', (req, res) => {
|
|
224
|
+
if (!requireUserIntent(req, res))
|
|
225
|
+
return;
|
|
226
|
+
let proposals = [];
|
|
227
|
+
try {
|
|
228
|
+
proposals = loadAllProposals(stateDir);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
// Missing proposals-<machineId>/ directory is handled by
|
|
232
|
+
// loadAllProposals → returns []. Any other error is a 500.
|
|
233
|
+
res.status(500).json({ error: err.message });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const hasCollaborative = trustSource.hasCollaborativeTrust();
|
|
237
|
+
const outstanding = proposals.filter((p) => p.status === 'outstanding');
|
|
238
|
+
const dismissed = proposals.filter((p) => p.status === 'dismissed');
|
|
239
|
+
const promoted = proposals.filter((p) => p.status === 'promoted');
|
|
240
|
+
// Visible-3 + queued behind (§A10).
|
|
241
|
+
const visible = outstanding.slice(0, 3).map((p) => redactForViewer(p, hasCollaborative));
|
|
242
|
+
const queued = outstanding.slice(3).map((p) => redactForViewer(p, hasCollaborative));
|
|
243
|
+
emit('remediation.dashboard.proposals-listed', {
|
|
244
|
+
visibleCount: visible.length,
|
|
245
|
+
queuedCount: queued.length,
|
|
246
|
+
dismissedCount: dismissed.length,
|
|
247
|
+
promotedCount: promoted.length,
|
|
248
|
+
hasCollaborativeTrust: hasCollaborative,
|
|
249
|
+
});
|
|
250
|
+
res.json({
|
|
251
|
+
visible,
|
|
252
|
+
queued,
|
|
253
|
+
dismissed: dismissed.map((p) => redactForViewer(p, hasCollaborative)),
|
|
254
|
+
promoted: promoted.map((p) => redactForViewer(p, hasCollaborative)),
|
|
255
|
+
trust: { hasCollaborative },
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
// GET /remediation/proposals/:id — detail.
|
|
259
|
+
app.get('/remediation/proposals/:id', (req, res) => {
|
|
260
|
+
if (!requireUserIntent(req, res))
|
|
261
|
+
return;
|
|
262
|
+
const id = String(req.params.id ?? '');
|
|
263
|
+
if (!PROPOSAL_ID_REGEX.test(id)) {
|
|
264
|
+
res.status(400).json({ error: 'invalid proposalId format', reason: 'invalid-id' });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const found = findProposalById(stateDir, id);
|
|
268
|
+
if (!found) {
|
|
269
|
+
res.status(404).json({ error: 'proposal not found', proposalId: id });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const hasCollaborative = trustSource.hasCollaborativeTrust();
|
|
273
|
+
const view = redactForViewer(found.proposal, hasCollaborative);
|
|
274
|
+
emit('remediation.dashboard.proposal-fetched', {
|
|
275
|
+
proposalId: id,
|
|
276
|
+
hasCollaborativeTrust: hasCollaborative,
|
|
277
|
+
});
|
|
278
|
+
res.json({ proposal: view, trust: { hasCollaborative } });
|
|
279
|
+
});
|
|
280
|
+
// POST /remediation/proposals/:id/dismiss — collaborative-only (§A26).
|
|
281
|
+
app.post('/remediation/proposals/:id/dismiss', (req, res) => {
|
|
282
|
+
if (!requireUserIntent(req, res))
|
|
283
|
+
return;
|
|
284
|
+
const id = String(req.params.id ?? '');
|
|
285
|
+
if (!PROPOSAL_ID_REGEX.test(id)) {
|
|
286
|
+
res.status(400).json({ error: 'invalid proposalId format', reason: 'invalid-id' });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!trustSource.hasCollaborativeTrust()) {
|
|
290
|
+
emit('remediation.dashboard.dismiss-refused', {
|
|
291
|
+
proposalId: id,
|
|
292
|
+
reason: 'trust-level-below-collaborative',
|
|
293
|
+
});
|
|
294
|
+
res.status(403).json({
|
|
295
|
+
error: 'dismiss requires collaborative trust',
|
|
296
|
+
reason: 'trust-level-below-collaborative',
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const principal = principalKey(req);
|
|
301
|
+
const rl = dismissLimiter(principal);
|
|
302
|
+
if (!rl.allowed) {
|
|
303
|
+
emit('remediation.dashboard.dismiss-refused', {
|
|
304
|
+
proposalId: id,
|
|
305
|
+
reason: 'rate-limited',
|
|
306
|
+
principal,
|
|
307
|
+
});
|
|
308
|
+
res.status(429).json({
|
|
309
|
+
error: `dismiss rate limit exceeded (max ${DISMISS_RATE_LIMIT_PER_HOUR}/hour per principal)`,
|
|
310
|
+
retryAfterMs: rl.retryAfterMs,
|
|
311
|
+
reason: 'rate-limited',
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const found = findProposalById(stateDir, id);
|
|
316
|
+
if (!found) {
|
|
317
|
+
res.status(404).json({ error: 'proposal not found', proposalId: id });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (found.proposal.status === 'dismissed') {
|
|
321
|
+
// Idempotent — second dismiss is a no-op, returns current state.
|
|
322
|
+
res.json({ proposal: redactForViewer(found.proposal, true), dismissed: false, alreadyDismissed: true });
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (found.proposal.status === 'promoted') {
|
|
326
|
+
res.status(409).json({
|
|
327
|
+
error: 'cannot dismiss a promoted proposal',
|
|
328
|
+
reason: 'already-promoted',
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const next = {
|
|
333
|
+
...found.proposal,
|
|
334
|
+
status: 'dismissed',
|
|
335
|
+
};
|
|
336
|
+
try {
|
|
337
|
+
fs.writeFileSync(found.filePath, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
res.status(500).json({ error: err.message });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
emit('remediation.dashboard.proposal-dismissed', {
|
|
344
|
+
proposalId: id,
|
|
345
|
+
principal,
|
|
346
|
+
});
|
|
347
|
+
res.json({ proposal: redactForViewer(next, true), dismissed: true });
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=remediation-proposals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remediation-proposals.js","sourceRoot":"","sources":["../../../src/server/routes/remediation-proposals.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -29,6 +29,7 @@ import crypto from 'node:crypto';
|
|
|
29
29
|
import { execSync } from 'node:child_process';
|
|
30
30
|
import { fileURLToPath } from 'node:url';
|
|
31
31
|
import { checkEli16Overview } from './eli16-overview-check.mjs';
|
|
32
|
+
import { verifyProposalDerivedRunbooks } from '../skills/instar-dev/scripts/verify-proposal-derived-runbook.mjs';
|
|
32
33
|
|
|
33
34
|
const __filename = fileURLToPath(import.meta.url);
|
|
34
35
|
const __dirname = path.dirname(__filename);
|
|
@@ -329,10 +330,39 @@ if (staged.includes(spec) && !staged.includes(eli16Rel)) {
|
|
|
329
330
|
);
|
|
330
331
|
}
|
|
331
332
|
|
|
333
|
+
// ─── Step 8: proposal-derived runbook gate (S-3) ─────────────────────────
|
|
334
|
+
// Per SELF-HEALING-REMEDIATOR-V2-SPEC §A11/§A22/§A32, runbook source files
|
|
335
|
+
// emitted by the SystemReviewer proposal pipeline must:
|
|
336
|
+
// 1. Reference a proposal that actually exists in
|
|
337
|
+
// .instar/remediation/proposals-*/<id>.json on this checkout, AND
|
|
338
|
+
// 2. Carry a __producingAgentId const matching the proposal's
|
|
339
|
+
// producingAgentId field.
|
|
340
|
+
// The CI gate (C-1) re-verifies at PR-merge time with full fleet visibility.
|
|
341
|
+
// This commit-time gate catches author mistakes before the PR is pushed.
|
|
342
|
+
|
|
343
|
+
const promotionGateResult = verifyProposalDerivedRunbooks({
|
|
344
|
+
repoRoot: ROOT,
|
|
345
|
+
files: staged,
|
|
346
|
+
});
|
|
347
|
+
if (!promotionGateResult.ok) {
|
|
348
|
+
blockCommit(
|
|
349
|
+
inScopeFiles,
|
|
350
|
+
[
|
|
351
|
+
'Proposal-derived runbook gate refused this commit:',
|
|
352
|
+
` ${promotionGateResult.reason}`,
|
|
353
|
+
'',
|
|
354
|
+
'See SELF-HEALING-REMEDIATOR-V2-SPEC.md §A11, §A22, §A32 for the rationale.',
|
|
355
|
+
'The proposal pipeline (Tier-3 S-1) must emit __proposalDerivedFrom +',
|
|
356
|
+
'__producingAgentId together, and the matching proposal JSON must be',
|
|
357
|
+
'present at .instar/remediation/proposals-<machineId>/<id>.json.',
|
|
358
|
+
].join('\n'),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
332
362
|
// ─── Pass ────────────────────────────────────────────────────────────────
|
|
333
363
|
|
|
334
364
|
console.error(
|
|
335
|
-
`[instar-dev-precommit] OK — trace ${path.basename(validTrace.entry.file)} covers ${inScopeFiles.length} in-scope file(s), artifact ${validTrace.trace.artifactPath} verified, spec ${spec} is converged + approved, ELI16 overview ${eli16Rel} present (${eli16Result.charCount} chars).`,
|
|
365
|
+
`[instar-dev-precommit] OK — trace ${path.basename(validTrace.entry.file)} covers ${inScopeFiles.length} in-scope file(s), artifact ${validTrace.trace.artifactPath} verified, spec ${spec} is converged + approved, ELI16 overview ${eli16Rel} present (${eli16Result.charCount} chars), promotion-gate: ${promotionGateResult.reason}.`,
|
|
336
366
|
);
|
|
337
367
|
process.exit(0);
|
|
338
368
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-05-
|
|
5
|
-
"instarVersion": "0.28.
|
|
4
|
+
"generatedAt": "2026-05-16T18:53:49.177Z",
|
|
5
|
+
"instarVersion": "0.28.112",
|
|
6
6
|
"entryCount": 188,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -1416,7 +1416,7 @@
|
|
|
1416
1416
|
"type": "subsystem",
|
|
1417
1417
|
"domain": "server",
|
|
1418
1418
|
"sourcePath": "src/server/AgentServer.ts",
|
|
1419
|
-
"contentHash": "
|
|
1419
|
+
"contentHash": "510f556002273a0ba882a9db9cb99a22b5d96eeb1362570d5e23e12de23bb55f",
|
|
1420
1420
|
"since": "2025-01-01"
|
|
1421
1421
|
},
|
|
1422
1422
|
"subsystem:session-manager": {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: minor -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
Tier-3 step two of the self-healing remediator is live. The Dashboard now has a Remediation Proposals view, and three new HTTP endpoints back it.
|
|
8
|
+
|
|
9
|
+
What lands today:
|
|
10
|
+
|
|
11
|
+
- A list endpoint that surfaces every pending NovelFailureReviewer proposal on disk. Up to three appear in the visible section; any extras sit silently in the queued section per the outstanding-three cap. Bearer auth and an `X-Instar-Request` header are required, matching the convention used by other user-authoritative endpoints.
|
|
12
|
+
- A detail endpoint for a single proposal. At the collaborative trust level the full LLM forensic record and raw reason text come through. Below collaborative trust the route strips the untrusted fields before they reach the response — the dashboard always renders the LLM-summarized portion inside an "Untrusted LLM-summarized content" frame, prefixed `[REVIEW NEEDED]` per the spec rendering rule.
|
|
13
|
+
- A dismiss endpoint that lets a collaborative-trust user clear a proposal from the outstanding-three cap. Each principal is limited to ten dismissals per hour. Dismiss is idempotent — re-dismissing a proposal returns the current state instead of erroring.
|
|
14
|
+
- A standalone dashboard page at `/dashboard/proposals.html` that reads from the three endpoints. The page can be linked from the existing Remediation tab once Tier-2 lands; until then it stands on its own.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
Your agent can now show you which novel failures it has noticed but does not yet know how to fix. From this release on, when the NovelFailureReviewer runs and finds a recurring un-recognized error, it writes a proposal to disk. You can review those proposals in your dashboard at the new Remediation Proposals page, and dismiss the ones that turn out to be noise.
|
|
19
|
+
|
|
20
|
+
The visible list shows three at a time so you are never asked to review a wall of items. Anything beyond three sits silently in the queued section and is shown once you clear space. Dismissing requires the collaborative trust level — the same level you set for other write-side dashboard actions.
|
|
21
|
+
|
|
22
|
+
## Summary of New Capabilities
|
|
23
|
+
|
|
24
|
+
| Capability | How to Use |
|
|
25
|
+
|-----------|-----------|
|
|
26
|
+
| Pending-proposal list at `/remediation/proposals` | Bearer auth + `X-Instar-Request: 1`. |
|
|
27
|
+
| Proposal detail at `/remediation/proposals/:id` | Same auth shape; full fields shown only at collaborative trust. |
|
|
28
|
+
| Proposal dismiss at `/remediation/proposals/:id/dismiss` | Collaborative trust + bearer + `X-Instar-Request: 1`. Ten dismissals per hour per principal. |
|
|
29
|
+
| Dashboard Proposals page at `/dashboard/proposals.html` | Open in a browser, paste your bearer token, click Refresh. |
|
|
30
|
+
|
|
31
|
+
## Evidence
|
|
32
|
+
|
|
33
|
+
Twelve new integration tests in `tests/integration/remediation-proposals-routes.test.ts` cover the nine spec-required cases plus three bonus checks (404 on unknown id, 400 on invalid id format, three-visible cap with queued overflow). All twelve pass. The full TypeScript build passes clean (`tsc --noEmit` returns zero).
|
|
34
|
+
|
|
35
|
+
Side-effects review for this slice is in `upgrades/side-effects/s2-dashboard-proposals.md`. Second-pass review was marked not-required by the spec gate procedure — the routes are read-mostly with one write path (dismiss) whose authority is gated through `TrustElevationSource.hasCollaborativeTrust()`, the same gate used by S-1's in-process dismiss path.
|
|
36
|
+
|
|
37
|
+
The new route file is `src/server/routes/remediation-proposals.ts` (about three hundred sixty lines). Wiring into `AgentServer.ts` adds eight lines guarded by a try/catch so a misconfigured trust source cannot prevent the server from starting. A one-line visibility change to `TrustElevationSource.hasCollaborativeTrust()` makes the existing private method public so route handlers can consult it directly — this is the same change S-1 makes on its branch, deliberately duplicated so either order of merge produces a clean rebase.
|
|
38
|
+
|
|
39
|
+
## What is outstanding
|
|
40
|
+
|
|
41
|
+
The Remediation tab itself is not yet on main — it ships with a separate Tier-2 slice. Until then the proposals page lives at its own URL rather than nested inside the tab. The dashboard navigation will gain a Remediation entry pointing at this page once the parent tab lands.
|
|
42
|
+
|
|
43
|
+
The route file mounts the trust source from `config.autonomyProfile` directly. Once the Remediator bootstrap is wired into `AgentServer`, the routes should consume that shared instance instead, so config-flip events take effect without a server restart. That swap is mechanical and tracked in the same Tier-3 follow-up that wires the rest of the Remediator into the server lifecycle.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
Ships S-3 of Tier-3 from the self-healing remediator spec — the **commit-time** half of the promotion gate that pairs with the **PR-merge-time** half shipped in C-1 (PR #204).
|
|
8
|
+
|
|
9
|
+
A "proposal-derived runbook" is a runbook source file that the SystemReviewer's clustering pipeline (Tier-3 S-1, not yet shipped) emits when it spots a repeating failure pattern that warrants a new automated remediation. Those files carry two special markers: `__proposalDerivedFrom = '<proposalId>'` and `__producingAgentId = '<agentId>'`. Before this release, nothing checked those markers locally — the only enforcement happened in CI at PR review.
|
|
10
|
+
|
|
11
|
+
This release adds a second check at commit time, inside the `/instar-dev` skill's pre-commit hook:
|
|
12
|
+
|
|
13
|
+
- If you stage a runbook source under `src/remediation/runbooks/` that carries `__proposalDerivedFrom`, the hook reads the proposal JSON at `.instar/remediation/proposals-<machineId>/<proposalId>.json`, verifies the agent IDs match, and (if a signature and pubkey are present) verifies the Ed25519 signature.
|
|
14
|
+
- If anything is missing or mismatched, the commit is refused with a structured message pointing at the proposal pipeline as the responsible surface.
|
|
15
|
+
- Runbooks without the markers — every runbook on main today — pass through with no work.
|
|
16
|
+
|
|
17
|
+
This catches author mistakes before a PR is pushed. The CI gate (C-1) remains the authoritative check at PR-merge time because only CI can verify signing identity for the different-principal rule.
|
|
18
|
+
|
|
19
|
+
## What to Tell Your User
|
|
20
|
+
|
|
21
|
+
No behavior change for users. This is a developer-side guardrail that prevents a malformed runbook from being committed in the first place. The user-visible promotion flow (proposal review and approval via the dashboard or Telegram) still works the same way.
|
|
22
|
+
|
|
23
|
+
## Summary of New Capabilities
|
|
24
|
+
|
|
25
|
+
| Capability | How to Use |
|
|
26
|
+
|-----------|-----------|
|
|
27
|
+
| Commit-time promotion gate for proposal-derived runbooks | Automatic; runs inside `/instar-dev` pre-commit |
|
|
28
|
+
| `verify-proposal-derived-runbook.mjs` CLI | `node skills/instar-dev/scripts/verify-proposal-derived-runbook.mjs --files <csv>` |
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Side-Effects Review — S-1 NovelFailureReviewer
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `s1-novel-failure-reviewer`
|
|
4
|
+
**Date:** 2026-05-16
|
|
5
|
+
**Author:** echo
|
|
6
|
+
**Second-pass reviewer:** not required (Tier-3 LLM-driven module; proposes only, cannot promote)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds `src/remediation/NovelFailureReviewer.ts` — the bottom-up signature-discovery module per spec §A18, §A10, §A26, §A57 Tier-3. Watches the audit projection for `no-matching-runbook` entries, clusters by signature, summarizes recurring patterns via a Haiku-class LLM, and emits proposals for human approval. Cannot author runbooks itself — proposals require an `/instar-dev` commit + spec-converge approval to become runbooks. 17 unit tests cover all behaviors. Small extensions to `Remediator.ts`, `TrustElevationSource.ts`, `audit/AuditProjection.ts`, and `audit/AuditWriter.ts` expose the read-only APIs the reviewer needs.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `src/remediation/NovelFailureReviewer.ts` (new) — **add** — clustering + LLM summarization + proposal generation. Propose-only authority.
|
|
15
|
+
- `src/remediation/TrustElevationSource.ts` (modified) — **modify** — adds a `dismissProposal` predicate path requiring `collaborative` trust per §A26.
|
|
16
|
+
- `src/remediation/audit/AuditProjection.ts` (modified) — **modify** — exposes `readUnmatchedSince(cursor)` for the reviewer's tick loop.
|
|
17
|
+
- `src/remediation/audit/AuditWriter.ts` (modified) — **modify** — emits `remediation.novel-failure-reviewer.*` events through the same writer.
|
|
18
|
+
- `src/remediation/Remediator.ts` (modified) — **modify** — one-line API surface for cluster-counter introspection.
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
23
|
+
|
|
24
|
+
- LLM output that's schema-valid but conservative (e.g., suggests `UNKNOWN_NETWORK_FAILURE`) is accepted regardless of whether it's the *best* code — the gate is structural, not quality. This is intentional.
|
|
25
|
+
- Collisions with existing runbook prefilters are auto-rejected (no slot consumed per §A26 R3). False-positive collisions on common substrings would mean a legitimate proposal is suppressed; mitigated by the strict prefilter-equality check (not substring match).
|
|
26
|
+
|
|
27
|
+
## 2. Under-block
|
|
28
|
+
|
|
29
|
+
**What failure modes does this still miss?**
|
|
30
|
+
|
|
31
|
+
- An LLM that returns schema-valid output containing injected misleading text past the URL/code-fence/imperative-verb filter. The 200/400 char limits + tier-3 human-review boundary contain damage; rendering is in a clearly-marked "Untrusted LLM-summarized content" frame.
|
|
32
|
+
- Coordinated audit-log pollution: if an attacker can inject enough no-matching-runbook entries to cross the 3-occurrence × 2-lifetime × 14-day threshold for a chosen signature, they can shape a proposal. Mitigated by §A12 audit-token enforcement (forged entries route to `audit-rejected.jsonl`, not `audit-projection`); the reviewer reads only verified entries.
|
|
33
|
+
|
|
34
|
+
## 3. Level-of-abstraction fit
|
|
35
|
+
|
|
36
|
+
**Is this at the right layer?**
|
|
37
|
+
|
|
38
|
+
Yes. The reviewer reads audit projection (read-only signal) and emits proposals (writes to a per-machine proposals directory). It cannot mutate the runbook registry, cannot dispatch attempts, cannot suppress alerts. The line between "execute" and "propose" is structural — the reviewer's only write surface is `proposals-<machineId>/` and the cluster-counters file. Promotion requires crossing a different authority boundary (`/instar-dev` commit + spec-converge).
|
|
39
|
+
|
|
40
|
+
## 4. Signal vs authority compliance
|
|
41
|
+
|
|
42
|
+
**Does this hold blocking authority with brittle logic, or does it produce a signal?**
|
|
43
|
+
|
|
44
|
+
Pure signal-producer. LLM-summarized proposals are explicitly marked as untrusted human-review artifacts. The runbook registry validator (in F-8) is the authority; the reviewer feeds candidate signatures into a human-gated approval path. Compliant with `docs/signal-vs-authority.md`.
|
|
45
|
+
|
|
46
|
+
## 5. Interactions
|
|
47
|
+
|
|
48
|
+
**Does it shadow another check, get shadowed, double-fire, race?**
|
|
49
|
+
|
|
50
|
+
- **Cluster threshold + per-signature persistence (§A47):** counters live at `.instar/remediation/cluster-counters-<machineId>.json`. LRU-capped at 500 distinct signatures. 1k-entry tail kept for forensic display. No race with the audit writer (read-only consumer; writes its own state file).
|
|
51
|
+
- **Outstanding-proposal cap (≤3, §A10):** queue overflow is silent. New proposals beyond the cap are tracked in counters but not LLM-summarized until a slot frees.
|
|
52
|
+
- **LLM monthly budget (§A65):** pauses LLM calls when cumulative spend ≥ budget. Backoff on LLM failure: 1h → 6h → 24h.
|
|
53
|
+
|
|
54
|
+
## 6. External surfaces
|
|
55
|
+
|
|
56
|
+
**Does it change anything visible to other agents, other users, other systems?**
|
|
57
|
+
|
|
58
|
+
- New state files under `.instar/remediation/`: `cluster-counters-<machineId>.json`, `proposals-<machineId>/<proposalId>.json`, `llm-raw-<machineId>.jsonl` (30-day TTL per §A26 R4).
|
|
59
|
+
- Per §A14 backup/sync taxonomy: cluster-counters and llm-raw are per-machine, NOT git-synced. proposals are git-synced read-only history (Dashboard S-2 surfaces them).
|
|
60
|
+
- LLM call: outbound to the configured `llmModel` (default haiku-class). Subject to §A65 monthly budget cap.
|
|
61
|
+
|
|
62
|
+
## 7. Rollback cost
|
|
63
|
+
|
|
64
|
+
**If this turns out wrong, what's the back-out?**
|
|
65
|
+
|
|
66
|
+
- Delete `src/remediation/NovelFailureReviewer.ts` + tests.
|
|
67
|
+
- Revert the small API additions in `TrustElevationSource.ts`, `AuditProjection.ts`, `AuditWriter.ts`, `Remediator.ts` (each <15 lines).
|
|
68
|
+
- The Tier-3 dashboard sub-section (S-2, future PR) is the only consumer. No runtime path depends on this module yet.
|
|
69
|
+
- State files at `.instar/remediation/cluster-counters-*` and `proposals-*/` are forensic-only; safe to delete.
|
|
70
|
+
|
|
71
|
+
## Trust elevation
|
|
72
|
+
|
|
73
|
+
`dismissProposal` requires `collaborative` trust + audit-logged principal identity. Per-agent dismiss rate-limit: 10/hour (enforced in the reviewer module).
|
|
74
|
+
|
|
75
|
+
## Side-effects on adjacent systems
|
|
76
|
+
|
|
77
|
+
- No HTTP routes, no Telegram surfaces, no dashboard wiring yet (S-2 is the consumer).
|
|
78
|
+
- Audit projection: read-only consumer; reviewer's own writes go to `audit-projection` via the standard AuditWriter path (verified-append per §A12).
|
|
79
|
+
- Backup/sync: A14 paths honored. F-7's `addGitignoreEntry` step already includes `cluster-counters-*` and `llm-raw-*`.
|