instar 1.3.705 → 1.3.706
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +16 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +20 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/guardAcceptedFallbacks.d.ts +48 -0
- package/dist/monitoring/guardAcceptedFallbacks.d.ts.map +1 -0
- package/dist/monitoring/guardAcceptedFallbacks.js +106 -0
- package/dist/monitoring/guardAcceptedFallbacks.js.map +1 -0
- package/dist/monitoring/guardManifest.d.ts +25 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +48 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/monitoring/guardPostureView.d.ts +23 -0
- package/dist/monitoring/guardPostureView.d.ts.map +1 -1
- package/dist/monitoring/guardPostureView.js +66 -0
- package/dist/monitoring/guardPostureView.js.map +1 -1
- package/dist/monitoring/probes/GuardPostureProbe.d.ts +13 -2
- package/dist/monitoring/probes/GuardPostureProbe.d.ts.map +1 -1
- package/dist/monitoring/probes/GuardPostureProbe.js +143 -40
- package/dist/monitoring/probes/GuardPostureProbe.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +46 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/data/state-coherence-registry.json +18 -0
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.706.md +80 -0
- package/upgrades/side-effects/g3-dark-but-load-bearing-guards.md +143 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: minor -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The guard-posture inventory (`GET /guards`) can now express that a DARK feature
|
|
9
|
+
is load-bearing — that a critical path depends on it — instead of treating it as
|
|
10
|
+
an ordinary optional dark feature that sits quiet while the path it should guard
|
|
11
|
+
runs unguarded. This is the guard-layer arm of the ratified "A Dark Feature Guards
|
|
12
|
+
Nothing" standard, and it closes the gap the 2026-07-01 silent-loss postmortem
|
|
13
|
+
named (operator message delivery depended on dark guards, and nothing surfaced it).
|
|
14
|
+
|
|
15
|
+
A guard declared `loadBearing` in the manifest now carries a `criticalPath` label
|
|
16
|
+
on every row, and when it sits silently unguarded it is classified one of three
|
|
17
|
+
ways:
|
|
18
|
+
- `loadBearingGap` — LOUD. A critical path is unguarded. It alerts on its OWN
|
|
19
|
+
attention episode track (`guard-posture-loadbearing:ep-N`), so a long-lived gap
|
|
20
|
+
can never mask an acute load-shed on another guard.
|
|
21
|
+
- `loadBearingSoaking` — a dry-run guard graduating within a bounded, code-constant
|
|
22
|
+
soak window. Surfaced on `/guards` only, no alert; it LAPSES to a loud gap if it
|
|
23
|
+
stalls past the window.
|
|
24
|
+
- `loadBearingAccepted` — an owned, PIN-authenticated operator acceptance is on
|
|
25
|
+
record. Full suppression + a visible accepted-risk row.
|
|
26
|
+
|
|
27
|
+
New route (dashboard-PIN-gated, `owner` + `reason` both required):
|
|
28
|
+
`POST`/`DELETE /guards/:key/accept-fallback`. The initial curated load-bearing set
|
|
29
|
+
is small and conservative: the durable inbound-message queue and the stranded-topic
|
|
30
|
+
sentinel, both on the operator-message-delivery critical path.
|
|
31
|
+
|
|
32
|
+
The classifier stays observe-only — it never gates, blocks, or disables anything,
|
|
33
|
+
and `/guards` is byte-identical for a consumer that ignores the new fields. Rollback
|
|
34
|
+
lever for the alert: `monitoring.guardPostureProbe.alertLoadBearingGaps` (the
|
|
35
|
+
classification stays either way).
|
|
36
|
+
|
|
37
|
+
## What to Tell Your User
|
|
38
|
+
|
|
39
|
+
I can now tell you when a critical safety system is turned off but something
|
|
40
|
+
important actually depends on it — instead of it quietly staying dark. If that
|
|
41
|
+
happens I will flag it clearly and give you three ways to resolve it: turn the
|
|
42
|
+
guard on, let it finish a short grace period while it is being rolled out, or have
|
|
43
|
+
you formally accept the risk (which I record with your name and reason so it is a
|
|
44
|
+
real decision, not a shrug). Accepting a risk needs your dashboard PIN — I cannot
|
|
45
|
+
do it on your behalf. If you ever ask "why is a critical guard flagged as a gap?",
|
|
46
|
+
that is this feature at work.
|
|
47
|
+
|
|
48
|
+
## Summary of New Capabilities
|
|
49
|
+
|
|
50
|
+
- `GET /guards` rows gain `loadBearing`, `criticalPath`, `loadBearingGap`,
|
|
51
|
+
`loadBearingSoaking`, `loadBearingAccepted`, and `acceptedFallbackReason`; the
|
|
52
|
+
summary and the capacity heartbeat gain `loadBearingGapKeys`,
|
|
53
|
+
`loadBearingSoakingKeys`, and `loadBearingAcceptedKeys`.
|
|
54
|
+
- `POST /guards/:key/accept-fallback` (dashboard-PIN-gated; `owner` + `reason`
|
|
55
|
+
required) records an owned per-machine acceptance; `DELETE` revokes it and
|
|
56
|
+
reopens the gap.
|
|
57
|
+
- The GuardPostureProbe raises a load-bearing gap on its own bounded, per-episode
|
|
58
|
+
attention topic, separate from the acute load-shed track (no masking).
|
|
59
|
+
- Per-machine by design: accepting a risk on one machine never silences a peer's
|
|
60
|
+
gap; a peer's critical-path label is looked up from the local, fleet-uniform
|
|
61
|
+
manifest.
|
|
62
|
+
|
|
63
|
+
## Evidence
|
|
64
|
+
|
|
65
|
+
Before: on an agent where a load-bearing feature ships dark (e.g. the inbound-message
|
|
66
|
+
queue with `enabled:false`), `GET /guards` reported it identically to any optional
|
|
67
|
+
dark feature — an `off:dark-default` row with no signal that a critical path depended
|
|
68
|
+
on it, so nothing ever surfaced the exposure (the postmortem's silent-loss shape).
|
|
69
|
+
|
|
70
|
+
After (verified over the real HTTP route + real disk in the Tier-3 e2e): the same
|
|
71
|
+
dark guard's `/guards` row now carries `loadBearing:true`, a `criticalPath` string,
|
|
72
|
+
and `loadBearingGap:true`, and the summary lists it under `loadBearingGapKeys`.
|
|
73
|
+
`POST /guards/:key/accept-fallback` with a valid PIN + owner + reason flips the row to
|
|
74
|
+
`loadBearingAccepted` (gap cleared, reason visible); `DELETE` reopens the gap, and both
|
|
75
|
+
survive a fresh-server "reboot" reading the same durable per-machine record. The
|
|
76
|
+
masking regression drives the REAL `createAttentionItem` funnel and asserts an acute
|
|
77
|
+
off-runtime-divergent guard surfaces while a load-bearing-gap episode has been open —
|
|
78
|
+
and an inert-lever test proves the funnel dedups by item id, so the separate episode
|
|
79
|
+
track (not a healthKey change) is what carries the fix. `npx tsc --noEmit` clean and
|
|
80
|
+
`npm run lint` exit 0.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Side-Effects Review — G3 Dark-but-Load-Bearing Guard Classification
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `g3-dark-but-load-bearing-guards`
|
|
4
|
+
**Date:** `2026-07-01`
|
|
5
|
+
**Author:** Echo (autonomous)
|
|
6
|
+
**Spec:** `docs/specs/g3-dark-but-load-bearing-guards.md` (review-convergence 2026-07-02, 6 iterations, cross-model codex GPT-5.5; approved:true)
|
|
7
|
+
**Parent principle:** A Dark Feature Guards Nothing (STANDARDS-REGISTRY.md, ratified 2026-07-01 PR #1316)
|
|
8
|
+
**Second-pass reviewer:** not-required (Tier 2; observe-only classifier + one PIN-gated suppress route; no new gating/blocking decision, byte-identical `/guards` for a consumer ignoring the new fields)
|
|
9
|
+
|
|
10
|
+
## Summary of the change
|
|
11
|
+
|
|
12
|
+
The `/guards` inventory could not express "this dark feature is load-bearing" — a
|
|
13
|
+
feature that ships DARK but that a CRITICAL PATH depends on was indistinguishable
|
|
14
|
+
from a genuinely-optional dark feature. It sat quiet while the path it should guard
|
|
15
|
+
ran unguarded (the 2026-07-01 silent-loss postmortem's case study: operator message
|
|
16
|
+
delivery depended on dark guards, and nothing surfaced it).
|
|
17
|
+
|
|
18
|
+
G3 adds an optional `loadBearing`/`criticalPath`/`soakWindowDays`/`declaredLoadBearingAt`
|
|
19
|
+
manifest declaration and classifies a load-bearing guard in a silent-unguarded posture
|
|
20
|
+
into ONE of three states — `loadBearingGap` (loud, its OWN attention channel),
|
|
21
|
+
`loadBearingSoaking` (graduate arm, no attention item), `loadBearingAccepted` (owned,
|
|
22
|
+
PIN-authenticated operator acceptance). `criticalPath` travels on EVERY anomaly of a
|
|
23
|
+
load-bearing guard. Pure classifier, observe-only — it never gates.
|
|
24
|
+
|
|
25
|
+
### Files added
|
|
26
|
+
- `src/monitoring/guardAcceptedFallbacks.ts` — ALL disk I/O for the per-machine
|
|
27
|
+
operator-accept store (`state/guard-accepted-fallbacks.json`, keyed
|
|
28
|
+
`<machineId>:<guardKey>`). Keeps the classifier PURE: the caller reads/scopes
|
|
29
|
+
once and threads the map in. Missing/corrupt file ⇒ empty map (safe direction).
|
|
30
|
+
- Tests: `tests/unit/monitoring/guard-posture-loadbearing.test.ts`,
|
|
31
|
+
`tests/unit/monitoring/guard-posture-probe-loadbearing.test.ts`,
|
|
32
|
+
`tests/integration/guards-accept-fallback-route.test.ts`,
|
|
33
|
+
`tests/e2e/guards-loadbearing-lifecycle.test.ts`,
|
|
34
|
+
`tests/unit/PostUpdateMigrator-guardLoadBearingSection.test.ts`.
|
|
35
|
+
|
|
36
|
+
### Files modified
|
|
37
|
+
- `src/monitoring/guardManifest.ts` — 4 optional fields; the two NEW manifest lints
|
|
38
|
+
(`validateGuardManifest`); the curated initial load-bearing set (inboundQueue +
|
|
39
|
+
strandedTopicSentinel, both operator-message-delivery critical-path, 30-day soak
|
|
40
|
+
window declared 2026-07-01).
|
|
41
|
+
- `src/monitoring/guardPostureView.ts` — the SIX new row fields + three-state
|
|
42
|
+
precedence (accepted → soaking-within-window → gap) on a threaded `now`; both
|
|
43
|
+
`deriveGuardRow` and `buildGuardInventory` stay PURE (accept map threaded via
|
|
44
|
+
`DeriveInput`/`opts`); `ROW_FIELD_ALLOWLIST` extended with all six by name; summary
|
|
45
|
+
+ heartbeat gain the three key-lists.
|
|
46
|
+
- `src/monitoring/probes/GuardPostureProbe.ts` — the SEPARATE `load-bearing-gap`
|
|
47
|
+
episode track (own `openEpisodeId`/`episodeEmitted`/anomalies + item-id namespace
|
|
48
|
+
`guard-posture-loadbearing:ep-N`); acute close condition tests `currentAcute` (not
|
|
49
|
+
the shared set); `load-bearing-gap` class pushed in both evaluate paths; criticalPath
|
|
50
|
+
on all load-bearing anomalies; `alertLoadBearingGaps` sub-flag (default-on).
|
|
51
|
+
- `src/core/types.ts` — the 3 optional key-list fields on `GuardPostureSummary`.
|
|
52
|
+
- `src/server/routes.ts` — GET /guards threads the accept map + now; the PIN-gated
|
|
53
|
+
`POST`/`DELETE /guards/:key/accept-fallback` route (owner+reason required).
|
|
54
|
+
- `src/commands/server.ts` — all 3 `buildGuardInventory` call sites thread the accept
|
|
55
|
+
map + now (route, probe `getLocalPosture`, heartbeat `selfGuardPosture`); the probe's
|
|
56
|
+
`alertLoadBearingGaps` sub-flag resolved from config (default true).
|
|
57
|
+
- `src/scaffold/templates.ts` + `src/core/PostUpdateMigrator.ts` — Agent Awareness +
|
|
58
|
+
Migration Parity (content-sniffed on `loadBearingGap`).
|
|
59
|
+
- `src/data/state-coherence-registry.json` — the new `guard-accepted-fallbacks`
|
|
60
|
+
store category + a `resolution`-class retention policy.
|
|
61
|
+
|
|
62
|
+
## Decision-point inventory
|
|
63
|
+
|
|
64
|
+
- **Added — one PIN-gated route** (`POST/DELETE /guards/:key/accept-fallback`): the
|
|
65
|
+
ONLY new decision surface. It SUPPRESSES a safety SIGNAL for a named operator; it
|
|
66
|
+
never allows/blocks an action. Gated by `checkMandatePin` (Know Your Principal — a
|
|
67
|
+
Bearer token cannot accept a safety risk); `owner` AND `reason` both REQUIRED (400
|
|
68
|
+
otherwise); a non-load-bearing key is 404.
|
|
69
|
+
- **Added — classifier flags**: `deriveGuardRow` sets orthogonal FLAGS; the nine
|
|
70
|
+
`GuardEffectiveState` values + the normative precedence table are UNCHANGED. No new
|
|
71
|
+
effective state, so the nine-state contract cannot regress.
|
|
72
|
+
- **Added — probe anomaly class** `load-bearing-gap` on its OWN episode track.
|
|
73
|
+
Observe-only: the probe only ever calls the attention funnel; never gates.
|
|
74
|
+
- No agent-to-user message-flow gate is added.
|
|
75
|
+
|
|
76
|
+
## Roll-up across the seven review dimensions
|
|
77
|
+
|
|
78
|
+
1. **Over-block**: none. The classifier adds flags; `/guards` is byte-identical for a
|
|
79
|
+
consumer ignoring the new fields. The accept route only suppresses a signal — it
|
|
80
|
+
loosens nothing and blocks nothing. The alert has a rollback sub-flag
|
|
81
|
+
(`alertLoadBearingGaps:false`) that keeps the classification.
|
|
82
|
+
2. **Under-block**: none. No gate was loosened. A load-bearing dark guard is now MORE
|
|
83
|
+
visible, not less. Soaking only defers the alert within a bounded, code-constant
|
|
84
|
+
window; a typo'd `declaredLoadBearingAt` falls to the LOUD gap (safe direction).
|
|
85
|
+
3. **Masking**: the round-3/round-4 fix. `load-bearing-gap` is a designed long-lived
|
|
86
|
+
anomaly; running it on a SEPARATE episode track (its own item-id namespace) means a
|
|
87
|
+
week-open gap can never hold the acute track's episode open. The regression test
|
|
88
|
+
drives the REAL `createAttentionItem` funnel and asserts an acute off-runtime-divergent
|
|
89
|
+
surfaces while the lb episode is open; the inert-lever test proves the funnel dedups
|
|
90
|
+
by ID, so a healthKey-only split would leave masking intact.
|
|
91
|
+
4. **Multi-machine**: classification is per-machine (an accept on machine A never
|
|
92
|
+
silences a peer's gap — proven by test). Manifest fields are fleet-uniform constants;
|
|
93
|
+
a peer's criticalPath is looked up from the local manifest. The heartbeat carries the
|
|
94
|
+
three key-lists (Array.isArray-guarded for an un-upgraded peer). The 3rd-site fix: the
|
|
95
|
+
heartbeat compute threads the local accept map, so an accepted guard never ships to
|
|
96
|
+
peers as a false gap.
|
|
97
|
+
5. **Purity / I/O layering**: `deriveGuardRow` AND `buildGuardInventory` gain no `fs`.
|
|
98
|
+
The caller reads the accept file ONCE per inventory build and threads the scoped map;
|
|
99
|
+
the new `guardAcceptedFallbacks.ts` module owns all disk I/O. One accept-file read per
|
|
100
|
+
inventory build, never per guard.
|
|
101
|
+
6. **Bounded Notification Surface**: each track surfaces as ONE bounded, coalesced,
|
|
102
|
+
per-episode class-level forum topic (two class-level topics max, never per-guard).
|
|
103
|
+
Soaking pushes NO attention item.
|
|
104
|
+
7. **Migration Parity / Agent Awareness**: the manifest ships as code (reaches existing
|
|
105
|
+
agents automatically); the CLAUDE.md vocabulary + accept route reach existing agents
|
|
106
|
+
via a content-sniffed `migrateClaudeMd` addendum; new agents get it in `generateClaudeMd`.
|
|
107
|
+
Migration idempotency + parity covered by a dedicated test.
|
|
108
|
+
|
|
109
|
+
## Rollout / rollback
|
|
110
|
+
|
|
111
|
+
Classification + route + separate-track wiring ship ALWAYS-ON as pure observability
|
|
112
|
+
(additive, never gating). The probe ALERT on `load-bearing-gap` rides the existing
|
|
113
|
+
GuardPostureProbe enablement + the `monitoring.guardPostureProbe.alertLoadBearingGaps`
|
|
114
|
+
sub-flag (default-on; soak windows are code constants). Rollback = set the sub-flag
|
|
115
|
+
false; `/guards` keeps the classification. The accept store is a per-machine JSON file;
|
|
116
|
+
DELETE scopes to the operator record only, never the manifest soak constant.
|
|
117
|
+
|
|
118
|
+
## Test evidence
|
|
119
|
+
|
|
120
|
+
`npx tsc --noEmit` clean; full `npm run lint` exit 0. New tests green: 21 unit
|
|
121
|
+
(classification/lints/allowlist/key-lists/purity), 9 probe-track unit (separate track,
|
|
122
|
+
masking regression against the real funnel, inert-lever guard, soaking-silent,
|
|
123
|
+
criticalPath, heartbeat array-guard, per-machine independence, alert rollback), 7
|
|
124
|
+
integration (PIN gate, owner-required, accept clears gap, DELETE-revoke survives reboot),
|
|
125
|
+
3 e2e (feature-alive: six fields + accept route mounted). Existing guard/posture suites
|
|
126
|
+
(monitoring 260, guard integration+e2e 51, server+migrator 27, parity+discoverability 170)
|
|
127
|
+
all pass.
|
|
128
|
+
|
|
129
|
+
## Post-review fixes (2026-07-02)
|
|
130
|
+
|
|
131
|
+
- Rebased onto JKHeadley/main after PR #1317 (silent-loss fix) merged. #1317's
|
|
132
|
+
rejected-enumeration + wiring gate on `routes.ts`/`server.ts`/`types.ts` and G3's
|
|
133
|
+
additive edits compose cleanly (adjacent, non-overlapping edits; tsc + `npm run lint`
|
|
134
|
+
clean post-rebase; the guard/posture suite re-run green).
|
|
135
|
+
- CI's full sharded suite caught two mechanical guard-tracking failures the loaded-box
|
|
136
|
+
local run (exit 144) missed. Neither weakens a guard:
|
|
137
|
+
1. `no-silent-fallbacks` (492 > baseline 491): `readAcceptedFallbacks`'s
|
|
138
|
+
missing/corrupt-file catch in `guardAcceptedFallbacks.ts` is annotated
|
|
139
|
+
`@silent-fallback-ok` — an empty map is the SAFE direction (no phantom accept can
|
|
140
|
+
suppress a real load-bearing gap). Threshold NOT bumped; count back to 491.
|
|
141
|
+
2. `feature-delivery-completeness`: the migrator's new `loadBearingGap` content-sniff
|
|
142
|
+
section is registered in `legacyMigratorSections` (migrator-only behavioral
|
|
143
|
+
awareness, also emitted inline by `generateClaudeMd` so a fresh init self-matches).
|