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.
Files changed (35) hide show
  1. package/dashboard/proposals.html +418 -0
  2. package/dist/remediation/NovelFailureReviewer.d.ts +212 -0
  3. package/dist/remediation/NovelFailureReviewer.d.ts.map +1 -0
  4. package/dist/remediation/NovelFailureReviewer.js +605 -0
  5. package/dist/remediation/NovelFailureReviewer.js.map +1 -0
  6. package/dist/remediation/Remediator.d.ts.map +1 -1
  7. package/dist/remediation/Remediator.js +1 -0
  8. package/dist/remediation/Remediator.js.map +1 -1
  9. package/dist/remediation/TrustElevationSource.d.ts +8 -1
  10. package/dist/remediation/TrustElevationSource.d.ts.map +1 -1
  11. package/dist/remediation/TrustElevationSource.js +8 -1
  12. package/dist/remediation/TrustElevationSource.js.map +1 -1
  13. package/dist/remediation/audit/AuditProjection.d.ts +7 -0
  14. package/dist/remediation/audit/AuditProjection.d.ts.map +1 -1
  15. package/dist/remediation/audit/AuditProjection.js +16 -0
  16. package/dist/remediation/audit/AuditProjection.js.map +1 -1
  17. package/dist/remediation/audit/AuditWriter.d.ts +8 -0
  18. package/dist/remediation/audit/AuditWriter.d.ts.map +1 -1
  19. package/dist/remediation/audit/AuditWriter.js +5 -1
  20. package/dist/remediation/audit/AuditWriter.js.map +1 -1
  21. package/dist/server/AgentServer.d.ts.map +1 -1
  22. package/dist/server/AgentServer.js +24 -0
  23. package/dist/server/AgentServer.js.map +1 -1
  24. package/dist/server/routes/remediation-proposals.d.ts +114 -0
  25. package/dist/server/routes/remediation-proposals.d.ts.map +1 -0
  26. package/dist/server/routes/remediation-proposals.js +350 -0
  27. package/dist/server/routes/remediation-proposals.js.map +1 -0
  28. package/package.json +1 -1
  29. package/scripts/instar-dev-precommit.js +31 -1
  30. package/src/data/builtin-manifest.json +3 -3
  31. package/upgrades/0.28.111.md +43 -0
  32. package/upgrades/0.28.112.md +28 -0
  33. package/upgrades/side-effects/s1-novel-failure-reviewer.md +79 -0
  34. package/upgrades/side-effects/s2-dashboard-proposals.md +76 -0
  35. package/upgrades/side-effects/s3-promotion-gate.md +111 -0
@@ -0,0 +1,76 @@
1
+ # Side-effects review — Tier-3 S-2: Dashboard Proposals routes + sub-section
2
+
3
+ **Spec**: `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` — §A10, §A13, §A26, §A48, §A57 Tier-3.
4
+
5
+ **Scope**: A new route file (`src/server/routes/remediation-proposals.ts`), a standalone dashboard page (`dashboard/proposals.html`), wiring into `AgentServer.ts`, and a one-line visibility change on `TrustElevationSource.hasCollaborativeTrust()` so the routes can consult it.
6
+
7
+ ## What changed
8
+
9
+ 1. **New file** `src/server/routes/remediation-proposals.ts` — implements the three S-2 routes:
10
+ - `GET /remediation/proposals` (list with visible-3 + queued-behind).
11
+ - `GET /remediation/proposals/:id` (detail with §A13 redaction at < `collaborative` trust).
12
+ - `POST /remediation/proposals/:id/dismiss` (§A26 — `collaborative` trust required + 10/hour rate-limit per principal).
13
+ 2. **New file** `dashboard/proposals.html` — standalone dashboard page that fetches the three routes. Renders each proposal inside an "Untrusted LLM-summarized content" frame per §A10 and prefixes titles with `[REVIEW NEEDED]`.
14
+ 3. **`src/server/AgentServer.ts`** — imports `registerRemediationProposalsRoutes` + `TrustElevationSource`, mounts the routes after the worktree routes and before the error handler. Wiring is guarded by a try/catch so a misconfigured trust source cannot prevent the server from starting (mirrors the burn-detection-system wiring convention).
15
+ 4. **`src/remediation/TrustElevationSource.ts`** — `hasCollaborativeTrust()` changes from `private` to public. Identical to the change S-1 makes on its branch; intentional duplication so the merge order doesn't matter.
16
+
17
+ ## Over-block analysis
18
+
19
+ - **Dismiss is collaborative-only.** The spec is unambiguous (§A26). The 10/hour rate-limit could feel restrictive for a user clearing a deploy-day flood, but the spec rationale (signal-suppression authority) holds — a user clearing real outstanding work hits ten in any reasonable session. The rate-limit returns a 429 with `retryAfterMs`, so the dashboard can surface a meaningful wait time.
20
+ - **`X-Instar-Request: 1` required.** Returning 400 (not 403) when the header is missing matches the existing convention in `routes.ts:14412` for `POST /shared-state/sessions/:sid/revoke`. The list route arguably could skip the header check (it's read-only), but the spec line under §A13 reads "All four require bearer auth + `X-Instar-Request: 1`" — applying it uniformly avoids drift.
21
+
22
+ ## Under-block analysis
23
+
24
+ - **No HMAC on the proposal file.** The route reads proposal JSON directly off disk without verifying the `producingAgentSignature` field defined in §A32. The spec's signature-verification surface is the CI pre-merge gate, not the dashboard read path — dashboard rendering is intended to surface every persisted proposal so a tampered one is human-visible. Adding signature verification at the read boundary would be over-block (it would silently drop tampered proposals from the dashboard, exactly what the user needs to see). The CI gate is the authority that refuses merge on signature mismatch.
25
+ - **Dismiss writes JSON in place rather than appending to an audit log.** S-1's `NovelFailureReviewer.dismissProposal()` uses the same in-place mutation pattern (`status: outstanding → dismissed`). The forensic record lives in the file itself plus the structured event emitted via `onEvent`. The route does not call `SafeFsExecutor` for the write because `fs.writeFileSync` of a single status mutation isn't a destructive operation per the SafeFsExecutor scope (which is `rm`/`unlink`/`rmdir`). Cleanup paths in the tests use `SafeFsExecutor.safeRmSync` per the spec mandate.
26
+ - **Trust source consults `config.autonomyProfile` at server-construction time.** A profile change at runtime requires a server restart for the dismiss gate to see the new value. This matches the existing pattern (the Remediator bootstrap reads the profile at init). The follow-up Tier-2/Tier-3 work that wires the Remediator into `AgentServer`'s lifecycle will swap the per-request profile read in if config-flip dynamism is required.
27
+
28
+ ## Level-of-abstraction fit
29
+
30
+ The route layer is a thin HTTP gate over file-system reads of S-1's proposal JSON. It does not duplicate S-1's persistence logic, threshold logic, or LLM-call logic — it consumes the persisted artifact. The redaction guard at the route boundary is the right level: it lets a future migration that introduces `reason.full` on disk (currently only S-1 sample-events carry `reason.redacted`) ship without leaking the new field through the response. The route's redaction is field-name-driven and applies regardless of whether the field is present on disk.
31
+
32
+ The dismiss authority is gated by `TrustElevationSource`, which is the single policy authority for non-runbook signal-suppression actions per §A26. Route handlers do not interpret trust levels themselves — they call `hasCollaborativeTrust()` and branch on the boolean. Identical pattern to S-1's in-process `dismissProposal()`.
33
+
34
+ ## Signal vs authority compliance
35
+
36
+ - **Signal layer**: the rate-limit timestamp ring, the redaction guard, the `X-Instar-Request` header check. These flag conditions without making policy decisions.
37
+ - **Authority layer**: `TrustElevationSource.hasCollaborativeTrust()` — the single gate for the dismiss write path.
38
+
39
+ No signal layer holds blocking authority; no authority layer is brittle pattern-matching. Compliant.
40
+
41
+ ## Interactions
42
+
43
+ - **S-1 (NovelFailureReviewer)**: shares the proposal JSON shape and the `proposals-<machineId>/` directory layout. Order of merge is invariant — if S-1 lands first, the routes read S-1's writes immediately. If S-2 lands first, the routes return empty lists (per the §A13 graceful-empty-dir requirement, covered by test 8) until S-1 starts writing.
44
+ - **S-1's in-process `dismissProposal()`**: writes the same on-disk field (`status: dismissed`). The route's dismiss path uses the same shape so the two surfaces are interchangeable. The route additionally enforces the 10/hour rate-limit per-principal at the HTTP boundary, which S-1's in-process call does not (it has no principal).
45
+ - **`TrustElevationSource`**: the `hasCollaborativeTrust()` visibility change is benign — it exposes a pure read of the configured profile. No mutability is introduced. S-1 makes the identical change.
46
+ - **Dashboard `index.html`**: untouched. The proposals page lives at its own URL until the Remediation tab parent lands.
47
+ - **`AgentServer.ts` startup ordering**: routes are mounted after `createWorktreeRoutes` and before `errorHandler`. The mount is guarded by try/catch matching the burn-detection convention — a route-registration failure logs a warning and does not throw.
48
+
49
+ ## Rollback cost
50
+
51
+ Trivial. Revert the four-file diff. The dashboard page is self-contained — removing it leaves no broken links because no other dashboard markup references it yet. The routes are bearer-auth-gated; removing them returns 404s that the dashboard handles gracefully (the `loadProposals()` fetch surfaces the error in the status line). The `TrustElevationSource.hasCollaborativeTrust()` visibility change is forward-compatible — if S-1 also adds the same public method, the reverts merge cleanly. If only S-2 added it, reverting reduces the surface area to S-1's identical change.
52
+
53
+ The proposal files on disk are untouched by S-2 except for the in-place status flip on dismiss. Reverting S-2 does not affect any persisted proposal beyond leaving the dismiss flag in place — exactly what a forensic audit would want.
54
+
55
+ ## Tests
56
+
57
+ Twelve integration tests in `tests/integration/remediation-proposals-routes.test.ts`:
58
+
59
+ 1. 401 without bearer auth.
60
+ 2. Bearer + header returns the list.
61
+ 3. Redacted view at < collaborative trust strips `reason.full` + `forensic.rawResponse`.
62
+ 4. Collaborative trust includes both fields verbatim.
63
+ 5. Dismiss at supervised trust returns 403 with `trust-level-below-collaborative`.
64
+ 6. Dismiss at collaborative trust mutates on-disk status to `dismissed` and the list excludes it from outstanding.
65
+ 7. Missing `X-Instar-Request` header returns 400 on all three routes.
66
+ 8. Missing `proposals-<machineId>/` directory returns 200 with empty arrays.
67
+ 9. 10/hour rate-limit returns 429 on the eleventh dismiss, with `retryAfterMs` populated.
68
+ 10. Unknown proposalId returns 404.
69
+ 11. Invalid proposalId format returns a non-2xx outcome.
70
+ 12. Visible-3 cap with two-proposal queued overflow.
71
+
72
+ All twelve pass. Full `tsc --noEmit` is clean.
73
+
74
+ ## Second-pass review
75
+
76
+ Marked not-required by the spec gate procedure. The change is read-mostly with one gated write path; both surfaces (read and write) use existing primitives (file-system read, the existing `TrustElevationSource`). No new authority is introduced; no new persistence is introduced; rollback is trivial.
@@ -0,0 +1,111 @@
1
+ # Side-Effects Review — S-3: Commit-time promotion gate for proposal-derived runbooks
2
+
3
+ **Version / slug:** `s3-promotion-gate`
4
+ **Date:** `2026-05-16`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Ships S-3 of Tier-3 from `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` — the **commit-time** promotion gate that pairs with C-1's PR-merge-time CI gate.
11
+
12
+ Per A11 / A22 / A32 / A41 / A48 / A57 Tier-3, when the `/instar-dev` skill commits a runbook source file under `src/remediation/runbooks/*.{ts,js}` that declares `__proposalDerivedFrom = '<proposalId>'`, the commit MUST:
13
+
14
+ 1. Carry a matching `__producingAgentId = '<agentId>'` const in the same source file.
15
+ 2. Reference a proposal that exists at `.instar/remediation/proposals-<machineId>/<proposalId>.json` in the current checkout.
16
+ 3. Have a proposal whose `producingAgentId` field matches the runbook's `__producingAgentId` annotation.
17
+ 4. (When pubkey is bundled and proposal carries a signature) verify the proposal's `producingAgentIdSignature` against `.instar/remediation/agent-pubkeys/<agentId>.pem`.
18
+
19
+ Otherwise the pre-commit hook refuses the commit with a structured error.
20
+
21
+ The gate is **additive** — runbook source files without `__proposalDerivedFrom` (i.e., the existing hand-authored runbooks W-1..W-7) pass through with zero work. Files outside `src/remediation/runbooks/` are not inspected.
22
+
23
+ This is the **commit-time** half. C-1 (shipped in PR #204, SHA `780b2659`) is the **PR-merge-time** half — it adds different-principal verification (GPG/sigstore OR Telegram countersignature) at PR review. Both are required per spec: this catches author mistakes before the PR is pushed; C-1 is authoritative because it has fleet-wide visibility.
24
+
25
+ Files touched:
26
+ - `skills/instar-dev/scripts/verify-proposal-derived-runbook.mjs` (new) — pure-Node verifier (no deps beyond `node:crypto`/`node:fs`/`node:path`). Exports `verifyProposalDerivedRunbooks`, `inspectRunbook`, `findProposalJson`, `verifyProposalSignature`. CLI mode at `--files <csv>`.
27
+ - `scripts/instar-dev-precommit.js` — adds Step 8 calling the verifier on the staged file list. Existing Steps 0–7 (merge skip, staged inspect, classify, bootstrap, fresh-trace, trace validation, spec tags, ELI16) unchanged.
28
+ - `skills/instar-dev/SKILL.md` — Phase 0 documentation extended with the new gate's preconditions.
29
+ - `tests/unit/verify-proposal-derived-runbook.test.ts` (new) — 12 cases covering the 6 spec scenarios plus edge cases.
30
+ - `upgrades/NEXT.md` — S-3 entry appended; existing release-notes preserved.
31
+
32
+ ## Decision-point inventory
33
+
34
+ - `inspectRunbook(repoRoot, relPath)` — **add** — reads each touched runbook source, regex-detects `__proposalDerivedFrom` and `__producingAgentId` consts. Pure structural detection (single regex per marker). Not a content classifier; it does not parse arbitrary TypeScript.
35
+ - `findProposalJson(repoRoot, proposalId)` — **add** — scans `.instar/remediation/proposals-*` directories for `<proposalId>.json`. Reads via `fs.readdirSync` + `fs.existsSync` to avoid pulling unrelated history (A48). Malformed JSON treated as not-found.
36
+ - `verifyProposalSignature(repoRoot, proposal)` — **add** — Ed25519 verify over canonical `proposalId=...\nproducingAgentId=...\nemittedAt=...` payload. Returns `'ok' | 'no-signature' | 'no-pubkey' | 'invalid'`. Only `'invalid'` blocks the commit; `'no-signature'` and `'no-pubkey'` defer to C-1 (proposal pipeline and pubkey distribution are downstream infra).
37
+ - `verifyProposalDerivedRunbooks({repoRoot, files})` — **add** — top-level decision tree: filter to runbook source paths → inspect each → presence checks → proposal lookup → agent-id match → signature verify. Short-circuits on first failure.
38
+ - `scripts/instar-dev-precommit.js` Step 8 — **add** — calls the verifier on the full staged file list (not just `inScopeFiles`, because runbooks under `src/remediation/runbooks/` are already in scope; passing the full list is a no-op for non-runbook files).
39
+ - `SKILL.md` Phase 0 — **extend** — documents the new gate's preconditions so the agent knows the contract before producing a runbook.
40
+
41
+ ## Over-block / under-block analysis
42
+
43
+ **Under-block risks (false PASS):**
44
+ - An author hand-writes `__proposalDerivedFrom = 'real-proposal'` + `__producingAgentId = 'self'` matching a legitimate-on-disk proposal that they themselves authored. The S-3 gate passes (the proposal exists, IDs match). The defense-in-depth is C-1: at PR-merge time the different-principal check refuses a PR whose signing identity matches the proposal's `producingAgentId`. This is the spec-intended layering — S-3 catches typos; C-1 catches collusion.
45
+ - A proposal without a `producingAgentIdSignature` field passes the signature step. This is by design — early proposals predate signing infra; the C-1 CI gate will surface this at PR time once the proposal pipeline lands (Tier-3 S-1).
46
+ - A proposal with a signature but no bundled pubkey at `.instar/remediation/agent-pubkeys/<agentId>.pem` passes locally. Pubkey distribution is fleet infrastructure that ships separately; C-1 has the authoritative pubkey set.
47
+
48
+ **Over-block risks (false BLOCK):**
49
+ - A hand-authored runbook (W-1..W-7) that gains a `__proposalDerivedFrom` annotation in the future without a matching proposal would block at commit-time. Mitigation: the annotation is only ever emitted by the SystemReviewer proposal pipeline (S-1, not yet shipped). Hand-authored runbooks never carry the const.
50
+ - Renaming or relocating proposals (e.g., archival to `proposals-archived-<machineId>/` per the spec's rollback line 5) would orphan the runbook reference. Mitigation: archival happens during nuclear uninstall only; runbook source is also archived in the same rollback step.
51
+ - File-encoding edge cases (BOMs, CRLF, unusual quoting) could make the regex miss the const. Mitigation: the regex tolerates single/double/backtick quoting and any of `const|let|var` with optional `export`; tests pin the byte shape.
52
+
53
+ **Bootstrap exception not needed.** The existing `BOOTSTRAP_TRIGGERS` list (`scripts/instar-dev-precommit.js:105`) covers the precommit script's own first-ship and `/spec-converge`. The new gate is additive — staged files with no `__proposalDerivedFrom` (which includes everything in this S-3 PR) pass through harmlessly.
54
+
55
+ ## Level-of-abstraction fit
56
+
57
+ - The verifier sits in `skills/instar-dev/scripts/` because it is part of the `/instar-dev` skill's enforcement surface — it has no caller outside the precommit hook. Co-locating it with `write-trace.mjs` keeps the skill's structural pieces together.
58
+ - The precommit hook adds Step 8 (after spec-tag + ELI16 checks) because the proposal-derivation check is the most expensive (fs scan of `.instar/remediation/proposals-*`). Running it last means cheap structural checks reject first.
59
+ - C-1 lives in CI because PR-merge-time needs different-principal verification (commit signing identity), which only the CI environment can determine authoritatively. S-3 doesn't need that — it only verifies the runbook + proposal shape.
60
+
61
+ ## Signal vs authority compliance
62
+
63
+ - The verifier is a **signal-producer with structural authority** — it makes a deterministic, byte-level structural check (regex match + JSON parse + Ed25519 verify), not a content judgment. Signal-vs-authority's "brittle filters don't get blocking authority" rule is for content classifiers (NLP, heuristics). Structural pre-commit gates are the canonical pattern for authority bound to verifiable invariants — same shape as the existing spec-tag and ELI16 checks.
64
+ - The "intelligent gate" layer above this is the user reviewing the side-effects artifact and PR diff. The structural gate ensures runbook source carries the metadata the intelligent gate needs to make its decision.
65
+
66
+ ## Interactions
67
+
68
+ - **Existing precommit Steps 0–7:** the new Step 8 runs AFTER trace validation, AFTER spec-tag verification, AFTER ELI16. It uses `staged` (not `inScopeFiles`) because runbooks under `src/remediation/runbooks/` are already in `inScopeFiles` per `inScope()`; passing `staged` keeps the API caller-agnostic.
69
+ - **C-1 CI gate:** complementary. S-3 verifies the runbook↔proposal binding locally; C-1 verifies signer-identity + Telegram countersignature at PR-merge time. They share the `__proposalDerivedFrom` const as the trigger marker.
70
+ - **F-5 TrustElevationSource (PR #203):** the trust-elevation logic at the API layer decides whether a high-blast-radius runbook may run. S-3 is at a different layer (source-control), so no interaction.
71
+ - **F-7 PostUpdateMigrator + AnnouncementManager (PR #210):** unaffected. Promotion events aren't yet wired into AnnouncementManager (deferred to a follow-up that ships once S-1's proposal pipeline lands).
72
+ - **Bootstrap exception list:** unchanged. The new gate's own first-ship doesn't add a runbook with `__proposalDerivedFrom`, so it passes through Step 8 naturally.
73
+
74
+ ## External surfaces
75
+
76
+ - New filesystem paths read (none written): `.instar/remediation/proposals-*/<id>.json`, `.instar/remediation/agent-pubkeys/<agentId>.pem`. Both are spec-mandated locations from §A14 / §A20.
77
+ - No new network calls, no new env-var reads, no new IPC.
78
+ - The script is pure-Node ESM — same runtime as the precommit hook. No new runtime dependency.
79
+ - CLI surface: `--files <csv>` + `--repo-root <path>` only. No interactive prompts.
80
+
81
+ ## Rollback cost
82
+
83
+ If S-3 turns out wrong in production:
84
+
85
+ - **Hot-fix path:** revert the import in `scripts/instar-dev-precommit.js` (one line). The verifier script and tests can stay; they're inert without the precommit wire-up.
86
+ - **Full revert:** remove the new script, the test file, the SKILL.md addendum, the precommit import + Step 8 block. `git revert` is sufficient; no data migration.
87
+ - **In-flight runbook PRs:** zero impact. The gate is additive; runbooks without `__proposalDerivedFrom` (every runbook on main today) pass through.
88
+ - **No agent-state repair needed.** The gate writes no state.
89
+
90
+ ## Test coverage
91
+
92
+ `tests/unit/verify-proposal-derived-runbook.test.ts` — 12 cases:
93
+
94
+ 1. Runbook without `__proposalDerivedFrom` → passes through.
95
+ 2. Runbook with `__proposalDerivedFrom` matching existing proposal + agent id → passes.
96
+ 3. Runbook with `__proposalDerivedFrom` but no `__producingAgentId` → fails.
97
+ 4. Runbook with `__proposalDerivedFrom` pointing to non-existent proposal → fails.
98
+ 5. Runbook with `__producingAgentId` whose signature doesn't verify → fails.
99
+ 6. Multiple runbooks, first failure short-circuits.
100
+ 7. Producing-agent-id mismatch between runbook and proposal → fails.
101
+ 8. Proposal exists but lacks `producingAgentId` field → fails.
102
+ 9. No pubkey on disk → passes (defers to CI gate).
103
+ 10. Proposal without signature field → passes (signature optional at commit-time).
104
+ 11. Non-runbook paths in file list are ignored.
105
+ 12. Proposal stored under a different `machineId` directory still resolves.
106
+
107
+ Tests use real Ed25519 keypairs (`crypto.generateKeyPairSync`), real fs scaffolding in `os.tmpdir()`, and `SafeFsExecutor.safeRmSync` for teardown.
108
+
109
+ ## Concur or concern
110
+
111
+ No second-pass required: the change is purely additive, structural, and dual-gated (S-3 here + C-1 in CI). The 12-test suite pins the byte-shape and decision tree.