instar 1.3.310 → 1.3.312

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 (67) hide show
  1. package/dist/commands/init.d.ts.map +1 -1
  2. package/dist/commands/init.js +32 -0
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/commands/server.d.ts.map +1 -1
  5. package/dist/commands/server.js +8 -5
  6. package/dist/commands/server.js.map +1 -1
  7. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  8. package/dist/config/ConfigDefaults.js +17 -0
  9. package/dist/config/ConfigDefaults.js.map +1 -1
  10. package/dist/core/AgentWorktreeDetector.d.ts +27 -3
  11. package/dist/core/AgentWorktreeDetector.d.ts.map +1 -1
  12. package/dist/core/AgentWorktreeDetector.js +77 -19
  13. package/dist/core/AgentWorktreeDetector.js.map +1 -1
  14. package/dist/core/BootSelfKnowledge.d.ts +121 -0
  15. package/dist/core/BootSelfKnowledge.d.ts.map +1 -0
  16. package/dist/core/BootSelfKnowledge.js +285 -0
  17. package/dist/core/BootSelfKnowledge.js.map +1 -0
  18. package/dist/core/InstarWorktreeManager.d.ts +7 -0
  19. package/dist/core/InstarWorktreeManager.d.ts.map +1 -1
  20. package/dist/core/InstarWorktreeManager.js +47 -1
  21. package/dist/core/InstarWorktreeManager.js.map +1 -1
  22. package/dist/core/PostUpdateMigrator.d.ts +10 -0
  23. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  24. package/dist/core/PostUpdateMigrator.js +161 -0
  25. package/dist/core/PostUpdateMigrator.js.map +1 -1
  26. package/dist/core/types.d.ts +26 -0
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/core/types.js.map +1 -1
  29. package/dist/messaging/AttentionTopicGuard.d.ts +14 -0
  30. package/dist/messaging/AttentionTopicGuard.d.ts.map +1 -1
  31. package/dist/messaging/AttentionTopicGuard.js +21 -0
  32. package/dist/messaging/AttentionTopicGuard.js.map +1 -1
  33. package/dist/messaging/TelegramAdapter.d.ts +40 -3
  34. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  35. package/dist/messaging/TelegramAdapter.js +61 -11
  36. package/dist/messaging/TelegramAdapter.js.map +1 -1
  37. package/dist/scaffold/templates.d.ts.map +1 -1
  38. package/dist/scaffold/templates.js +8 -0
  39. package/dist/scaffold/templates.js.map +1 -1
  40. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  41. package/dist/scheduler/JobScheduler.js +3 -3
  42. package/dist/scheduler/JobScheduler.js.map +1 -1
  43. package/dist/server/routes.d.ts.map +1 -1
  44. package/dist/server/routes.js +137 -2
  45. package/dist/server/routes.js.map +1 -1
  46. package/dist/threadline/CollaborationSurfacer.d.ts +4 -1
  47. package/dist/threadline/CollaborationSurfacer.d.ts.map +1 -1
  48. package/dist/threadline/CollaborationSurfacer.js +1 -1
  49. package/dist/threadline/CollaborationSurfacer.js.map +1 -1
  50. package/dist/threadline/TelegramBridge.d.ts +4 -1
  51. package/dist/threadline/TelegramBridge.d.ts.map +1 -1
  52. package/dist/threadline/TelegramBridge.js +1 -1
  53. package/dist/threadline/TelegramBridge.js.map +1 -1
  54. package/dist/threadline/hubCommands.d.ts +4 -1
  55. package/dist/threadline/hubCommands.d.ts.map +1 -1
  56. package/dist/threadline/hubCommands.js +3 -3
  57. package/dist/threadline/hubCommands.js.map +1 -1
  58. package/package.json +2 -2
  59. package/scripts/lint-no-direct-destructive.js +4 -0
  60. package/scripts/lint-no-unfunneled-topic-creation.js +129 -0
  61. package/src/data/builtin-manifest.json +73 -65
  62. package/src/scaffold/templates.ts +8 -0
  63. package/src/templates/scripts/secret-get.mjs +143 -0
  64. package/upgrades/1.3.311.md +20 -0
  65. package/upgrades/1.3.312.md +31 -0
  66. package/upgrades/side-effects/bounded-notification-surface.md +34 -0
  67. package/upgrades/side-effects/session-boot-self-knowledge.md +78 -0
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * secret-get.mjs — read ONE secret value from the agent's encrypted SecretStore
4
+ * vault and stream it to stdout for piping. The retrieval affordance promised by
5
+ * the Session Boot Self-Knowledge block (docs/specs/session-boot-self-knowledge.md):
6
+ * a session that boots knowing `github_token` is in the vault uses THIS script to
7
+ * fetch it — never `node -e` ad-hoc reads, never asking the user to re-send it.
8
+ *
9
+ * Containment contract (sibling of secret-drop-retrieve.mjs, same rules):
10
+ * - The VALUE goes to stdout ONLY — single write, no trailing newline — so it
11
+ * can pipe straight into the consuming command without ever being echoed
12
+ * into a terminal, chat, or transcript.
13
+ * - ALL diagnostics go to stderr and are limited to key NAMES, lengths, and
14
+ * error categories — never values.
15
+ * - On ANY error, stdout receives ZERO value bytes (stderr-only, non-zero exit).
16
+ *
17
+ * Usage:
18
+ * node .instar/scripts/secret-get.mjs <keyPath>
19
+ * → prints the value to stdout. Pipe it: `... github_token | gh auth login --with-token`
20
+ *
21
+ * node .instar/scripts/secret-get.mjs --names
22
+ * → prints vault key paths + value lengths to stderr; nothing to stdout.
23
+ *
24
+ * node .instar/scripts/secret-get.mjs <keyPath> --run -- <cmd...>
25
+ * → runs <cmd> with the value piped to its stdin (atomic handoff — the value
26
+ * never touches a shell variable or the transcript). Exits with <cmd>'s code.
27
+ *
28
+ * Vault access:
29
+ * Loads the instar SecretStore implementation from the local install and reads
30
+ * the vault at `.instar/secrets/config.secrets.enc` relative to cwd (run from
31
+ * the agent home). Keychain-backed master key by default — the same read path
32
+ * the server uses.
33
+ *
34
+ * Exit codes:
35
+ * 0 — value printed (or --run command succeeded, or --names listed)
36
+ * 1 — key not found, vault absent/undecryptable, or --run command failed
37
+ * 2 — usage error (missing args, cannot resolve the instar dist)
38
+ */
39
+
40
+ import * as fs from 'node:fs';
41
+ import * as path from 'node:path';
42
+ import { spawnSync } from 'node:child_process';
43
+ import { createRequire } from 'node:module';
44
+
45
+ const args = process.argv.slice(2);
46
+
47
+ // `<keyPath> --run -- <cmd...>`: everything after the first `--` (which must
48
+ // follow `--run`) is the command to receive the value on stdin.
49
+ const runIdx = args.indexOf('--run');
50
+ const dashIdx = args.indexOf('--');
51
+ let runCmd = null;
52
+ if (runIdx !== -1) {
53
+ if (dashIdx === -1 || dashIdx < runIdx || dashIdx === args.length - 1) {
54
+ process.stderr.write('usage: secret-get.mjs <keyPath> --run -- <cmd...>\n');
55
+ process.exit(2);
56
+ }
57
+ runCmd = args.slice(dashIdx + 1);
58
+ }
59
+ const positional = (dashIdx === -1 ? args : args.slice(0, dashIdx)).filter((a) => !a.startsWith('--'));
60
+ const namesOnly = args.includes('--names');
61
+ const keyPath = positional[0];
62
+
63
+ if (!namesOnly && !keyPath) {
64
+ process.stderr.write('usage: secret-get.mjs <keyPath> | secret-get.mjs --names\n');
65
+ process.exit(2);
66
+ }
67
+
68
+ // Resolve the instar dist: deployed agents run from the shadow-install; the dev
69
+ // checkout falls back to its own dist. Never print resolution paths on success.
70
+ const require = createRequire(import.meta.url);
71
+ const candidates = [
72
+ path.resolve('.instar/shadow-install/node_modules/instar/dist/core/SecretStore.js'),
73
+ path.resolve('dist/core/SecretStore.js'),
74
+ ];
75
+ let SecretStore = null;
76
+ for (const c of candidates) {
77
+ if (fs.existsSync(c)) {
78
+ try {
79
+ ({ SecretStore } = require(c));
80
+ break;
81
+ } catch {
82
+ // try the next candidate; details are not value-bearing but stay off stdout
83
+ }
84
+ }
85
+ }
86
+ if (!SecretStore) {
87
+ process.stderr.write('secret-get: cannot resolve the instar SecretStore module (run from the agent home)\n');
88
+ process.exit(2);
89
+ }
90
+
91
+ const stateDir = path.resolve('.instar');
92
+ if (!fs.existsSync(path.join(stateDir, 'secrets', 'config.secrets.enc'))) {
93
+ process.stderr.write('secret-get: no vault on this machine (.instar/secrets/config.secrets.enc absent)\n');
94
+ process.exit(1);
95
+ }
96
+
97
+ let secrets;
98
+ try {
99
+ secrets = new SecretStore({ stateDir }).read();
100
+ } catch {
101
+ process.stderr.write(
102
+ 'secret-get: vault exists but could not be decrypted (master-key mismatch?). ' +
103
+ 'Do NOT repair/rotate/delete — surface to the operator.\n',
104
+ );
105
+ process.exit(1);
106
+ }
107
+
108
+ // Flatten to dot-notation leaves — names + lengths only, mirroring the vault's
109
+ // own get/set addressing. Values never leave this function except via stdout.
110
+ function leaves(obj, prefix = '') {
111
+ const out = [];
112
+ for (const [k, v] of Object.entries(obj)) {
113
+ const p = prefix ? `${prefix}.${k}` : k;
114
+ if (v && typeof v === 'object' && !Array.isArray(v)) out.push(...leaves(v, p));
115
+ else out.push([p, v]);
116
+ }
117
+ return out;
118
+ }
119
+ const entries = leaves(secrets);
120
+
121
+ if (namesOnly) {
122
+ for (const [name, value] of entries) {
123
+ process.stderr.write(`${name} (${String(value ?? '').length} chars)\n`);
124
+ }
125
+ if (entries.length === 0) process.stderr.write('(vault is empty)\n');
126
+ process.exit(0);
127
+ }
128
+
129
+ const hit = entries.find(([name]) => name === keyPath);
130
+ if (!hit) {
131
+ process.stderr.write(`secret-get: no key "${keyPath}" in the vault. Known keys:\n`);
132
+ for (const [name] of entries) process.stderr.write(` ${name}\n`);
133
+ process.exit(1);
134
+ }
135
+ const value = typeof hit[1] === 'string' ? hit[1] : JSON.stringify(hit[1]);
136
+
137
+ if (runCmd) {
138
+ const r = spawnSync(runCmd[0], runCmd.slice(1), { input: value, stdio: ['pipe', 'inherit', 'inherit'] });
139
+ process.exit(r.status ?? 1);
140
+ }
141
+
142
+ process.stdout.write(value);
143
+ process.exit(0);
@@ -0,0 +1,20 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ New `src/core/BootSelfKnowledge.ts` (block builder: sanitization, depth-2 collapse, alphabetical + capped + byte-bounded rendering, mtime+size-keyed module cache); three routes in `routes.ts`; one fetch block in `getSessionStartHook()`; `selfKnowledge` config surface (defaults backfilled by `migrateConfig`, `enabled` left unset for the developmentAgent gate); CLAUDE.md template section + `migrateClaudeMd` parity; `secret-get.mjs` shipped via `migrateScripts` + init. Spec: `docs/specs/session-boot-self-knowledge.md` (converged, 3 iterations, cross-model codex-cli:gpt-5.5).
9
+
10
+ ## What to Tell Your User
11
+
12
+ Nothing user-visible yet — the feature ships dark on the fleet (live on the development agent for the bake). When the fleet flip lands it gets its own note: "your agent now remembers what credentials it holds across sessions — it won't ask you to re-send a key it already has."
13
+
14
+ ## Summary of New Capabilities
15
+
16
+ - `GET /self-knowledge/session-context` (Bearer; `enabled ?? !!developmentAgent`): a bounded, sanitized `<session-self-knowledge>` block with the agent's vault secret NAMES (never values; same `secretKeyPaths()` derivation as `/secrets/sync-status`, depth-capped) + self-asserted operational facts. `?full=1` bypasses the display caps. A vault that exists but won't decrypt is reported honestly as DECRYPT-FAILED with hands-off guidance — never as an empty vault.
17
+ - `POST/DELETE /self-knowledge/facts`: agent-driven writer for durable per-machine operational facts (auto-stamped `{fact, updatedAt, machine}`; duplicate/cap/ambiguity guarded; atomic temp+rename config write).
18
+ - `.instar/scripts/secret-get.mjs` (always-overwrite installed): hardened vault retrieval — value streams to stdout for piping straight into the consuming command, names/diagnostics to stderr, zero value bytes on any error path.
19
+ - The session-start hook injects the block at every boot (fail-open: dark/unreachable/version-skew → silent skip), placed after the org-intent and preferences blocks.
20
+ - Structural test guard: `MasterKeyManager` forces the file key under vitest — no test can ever read or overwrite the machine-global OS keychain master key again (the 2026-06-05 bifurcated-key incident class is closed).
@@ -0,0 +1,31 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ `createForumTopic`/`findOrCreateForumTopic` gained an optional `opts?: { origin, label }` parameter (back-compatible; omitted = budgeted `auto`); known callsites declare origins (`user`: hub-commands "open this"/"tie", `/new`, dashboard create-topic/session routes; `system`: Lifeline, Dashboard, Updates, Attention, flood-notice, agent-health lane; labeled `auto`: attention items, job topics, threadline bridge, collaboration surfacer). CLAUDE.md template gains a "Bounded Notification Surface" section via `migrateClaudeMd` (content-sniffed, parity-tracked). Detector resolver gained a `cwd` test seam (its tests silently depended on the running machine's checkout at `process.cwd()`).
9
+
10
+ resolveAgentHome (the worktree-create agent-home guard) gains one narrow acceptance path: a home outside ~/.instar/agents/ is accepted iff the agent registry's recorded entry path (realpath-resolved) equals it and the entry name passes the existing charset clamp. The agent name comes from the registry entry, never the directory. Every prior refusal is preserved verbatim; planted files inside the candidate remain non-evidence.
11
+
12
+ ## What to Tell Your User
13
+
14
+ Your agent can no longer spam your Telegram group with a wall of topics, no matter what goes wrong inside it. After the third topic-flood incident (a boot-time checker misread shared state and raised 110 false alarms at once), the limit now lives inside the one function that creates topics — every automatic topic creation is budgeted by default, floods collapse into one summary notice, and a CI test fails any future build that could flood. Genuine emergencies still always get their own topic.
15
+
16
+ Agents set up before the worktree convention (their home under ~/Documents/Projects/ or similar) can now use the standard worktree-create command like every other agent, instead of hand-building checkouts — which was the main source of their build failures.
17
+
18
+ ## Summary of New Capabilities
19
+
20
+ - `TelegramAdapter.createForumTopic` now enforces a last-resort `topicCreationBudget` (per-label 8 + global 12 per 10-min window, config-tunable via `messaging[].config.topicCreationBudget`) on every `origin: 'auto'` creation — and `'auto'` is the DEFAULT, so a future caller that never heard of the budget is still bounded. `'user'` (human-initiated) and `'system'` (fixed create-once infra topics) are exempt. Overflow throws `TopicFloodBudgetError` — the same failure shape as a Telegram 429, which every caller already survives.
21
+ - `AgentWorktreeDetector` enumerates safe roots from the DISK (`~/.instar/agents/*/.worktrees`) instead of the racy shared agent registry (the flood's root cause: a lost-update window + a silent parse-failure→empty-list fallback made the agent's own worktrees look misplaced), and aggregates N findings into ONE summary attention item with a stable feature-scoped `sourceContext` (the per-item unique paths were what dodged the 2026-05-28 per-source budget).
22
+ - `tests/integration/notification-flood-burst-invariant.test.ts`: the requirement-as-test — 1,000-item bursts through the real pipeline with shipped-default budgets must create ≤ budget topics, covering both the unique-source and unique-label dodges; proves no item is ever dropped and HIGH/URGENT always get their own topic even mid-flood.
23
+ - `scripts/lint-no-unfunneled-topic-creation.js` (in `pnpm lint`): no raw `createForumTopic` Bot-API call outside the budgeted funnel (SafeGitExecutor-style closed allowlist).
24
+ - `migrateWorktreeMisplacedFloodItems`: purges the stale per-path `worktree-misplaced:*` false-positive items from every flooded agent's attention store on update (idempotent, atomic; the new `worktree-misplaced-summary:*` format is kept).
25
+ - Constitution: new Building standard **"Bounded Notification Surface"** + catalog entry **P17** (any user-facing notification surface needs a chokepoint budget + a burst test before it ships).
26
+
27
+ - Legacy-home agents (registry-path-verified) get first-class worktree creation at `<legacyHome>/.worktrees/` — inside their own granted territory.
28
+
29
+ ## Evidence
30
+
31
+ Found live in apprenticeship cycle 10 (2026-06-05): Codey's first command on a new build assignment — `instar worktree create codey/unit-suite-hermetic --base JKHeadley/main` — was refused with "is not under the instar agents root" despite his home being registered with a live heartbeat at exactly that path. 6 new unit tests cover both sides (env-var route, walk-up route, symlinked registration, planted-files-refused, hostile-name-refused, dangling-path-refused); the original hostile-AGENT.md refusal test passes unchanged. 49/49 green in the file.
@@ -0,0 +1,34 @@
1
+ # Side-Effects Review — Bounded Notification Surface (topic-flood invariant)
2
+
3
+ **Version / slug:** `bounded-notification-surface`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (Tier 1; operator-directed incident remediation, approval in topic 11960)`
7
+
8
+ ## Summary of the change
9
+
10
+ Three-layer structural fix for the 2026-06-05 topic-spam flood (the third such incident): (1) `AgentWorktreeDetector` enumerates safe roots from the disk instead of the racy agent registry and aggregates N misplaced worktrees into ONE attention item; (2) `TelegramAdapter.createForumTopic` — the topic-creation chokepoint — enforces a last-resort `topicCreationBudget` on every `origin: 'auto'` creation, with 'auto' as the default so undeclared callers are bounded; (3) a burst-invariant integration test + a funnel lint pin the bound in CI. A `PostUpdateMigrator` migration purges stale per-path flood items fleet-wide. The standard joins the constitution as "Bounded Notification Surface" (P17), pending operator ratification at PR review.
11
+
12
+ ## Decision-point inventory
13
+
14
+ One new decision point: `createForumTopic` may now REFUSE an `origin:'auto'` creation past the budget (throws `TopicFloodBudgetError`). It is a delivery shaper in the same class as `AttentionTopicGuard` — it never withholds critical notices (HIGH/URGENT attention items pass `origin:'system'`), never drops an item (attention items are still stored; the existing coalesce path is untouched), and its failure shape (a thrown error from topic creation) is one every existing caller already handles, because Telegram 429s produce the same shape today.
15
+
16
+ ## 1. Over-block
17
+
18
+ Worst case: a legitimate feature creating >8 auto topics per label (or >12 globally) in 10 minutes gets refused; its content degrades exactly as it does under a Telegram rate limit (caller catch paths; attention items stay in the store topic-less; job notifications log the failure). Budgets are config-tunable per agent (`messaging[].config.topicCreationBudget`). User-initiated (`origin:'user'`: hub commands, /new, dashboard create) and create-once system topics (Lifeline, Dashboard, Updates, Attention, flood-notice, agent-health lane) are exempt, so no human-facing path can be over-blocked.
19
+
20
+ ## 2. Under-block
21
+
22
+ HIGH/URGENT attention items are exempt at both layers BY DESIGN (critical never coalesced) — a feature that floods at URGENT can still flood; that is the documented, deliberate trade (criticals must always land) and is unchanged from the 2026-05-28 lockdown. The lifeline's single raw `createForumTopic` call (separate process, fixed cardinality 1) is allowlisted in the lint with justification.
23
+
24
+ ## 3. Level-of-abstraction fit
25
+
26
+ The bound moved from per-feature cooperation (dodgeable — this incident proved it) to the creation primitive itself, mirroring the SafeGitExecutor/SafeFsExecutor single-funnel precedent. The detector fix removes a registry read that was the wrong ground truth for an on-disk question.
27
+
28
+ ## 4. Migration / fleet rollout
29
+
30
+ `migrateWorktreeMisplacedFloodItems` is idempotent (skips when no old-format ids present), atomic (tmp+rename), and only deletes items with the retired `worktree-misplaced:` id prefix; the new `worktree-misplaced-summary:` items are preserved. CLAUDE.md awareness ships via `migrateClaudeMd` with a content-sniff guard (Migration Parity), tracked in the feature-delivery-completeness parity test.
31
+
32
+ ## 5. Reversibility
33
+
34
+ Config off-switches: `topicCreationBudget: { enabled: false }` restores pre-change creation behavior; the detector change is signal-only (never moves/deletes worktrees). No data is destroyed anywhere except the stale flood items the migration purges — which are reconstructible from `git worktree list` if ever needed (and were 100% false positives).
@@ -0,0 +1,78 @@
1
+ # Side-Effects Review — Session Boot Self-Knowledge
2
+
3
+ **Version / slug:** `session-boot-self-knowledge`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `spec-converge multi-reviewer panel (3 rounds: security/adversarial/integration/scalability/lessons-aware internal + Standards-Conformance Gate + codex-cli:gpt-5.5 external each round)`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds the boot self-knowledge block: a bounded, sanitized `<session-self-knowledge>` context block (vault secret NAMES — never values — plus self-asserted operational facts) built server-side by the new `src/core/BootSelfKnowledge.ts`, served by `GET /self-knowledge/session-context` (with `?full=1`), written by `POST/DELETE /self-knowledge/facts`, injected by a new fetch block in `getSessionStartHook()`, retrieved-from by the new hardened `secret-get.mjs` script, and configured by the new `InstarConfig.selfKnowledge` surface (defaults via `ConfigDefaults` + `migrateConfig`; CLAUDE.md template + `migrateClaudeMd`; script via `migrateScripts` + init). Includes the structural `MasterKeyManager` VITEST constructor guard. Files: `src/core/BootSelfKnowledge.ts` (new), `src/core/SecretStore.ts`, `src/server/routes.ts`, `src/core/PostUpdateMigrator.ts`, `src/core/types.ts`, `src/config/ConfigDefaults.ts`, `src/scaffold/templates.ts`, `src/commands/init.ts`, `src/templates/scripts/secret-get.mjs` (new), plus 4 test files, the spec + ELI16 + convergence report, and the release fragment. Spec: `docs/specs/session-boot-self-knowledge.md` (converged + approved).
11
+
12
+ ## Decision-point inventory
13
+
14
+ This change adds NO decision point with blocking authority — it is a pure signal producer (read-only context injection; per `docs/signal-vs-authority.md`). Conditionals it adds are availability switches and writer-input validation, not behavior gates:
15
+
16
+ - `GET /self-knowledge/session-context` enabled-resolution (`enabled ?? !!developmentAgent`) — add — availability switch (graduated rollout), not a behavior gate.
17
+ - Facts writer validation (400 empty/oversize; 409 duplicate/cap/ambiguous/expect-mismatch) — add — input validation on an agent-driven write surface.
18
+ - Pass-through: SecretStore read path (read-only), session-start hook (additive fetch block, fail-open), config write path (new atomic helper for one array).
19
+
20
+ ---
21
+
22
+ ## 1. Over-block
23
+
24
+ The enabled-resolution 503s the route on fleet agents (flag unset, `developmentAgent` false) — by design (graduated rollout), not an over-block: the hook fail-opens silently. The facts writer rejects: empty/oversize facts (legitimate long facts >256 chars must be split — accepted cost, keeps the boot block bounded), exact duplicates, adds past the 50-fact cap, ambiguous `match` deletes, and stale `index+expect` deletes. Each 4xx carries an actionable message. No legitimate session-context READ is ever rejected beyond auth + the availability switch. No other block/allow surface.
25
+
26
+ ## 2. Under-block
27
+
28
+ - A fact that is misleading-but-validly-shaped (≤256 chars, unique) is stored and injected every boot — mitigated by the self-asserted/unverified labeling, per-index render for one-call removal, and the per-serve audit line; residual risk accepted for v1 and explicitly watched during the bake (spec §Threat model).
29
+ - The last-writer-wins window between the facts writer and the pre-existing NON-atomic config writers (PATCH /config, telemetry) remains — bounded to the handler's microseconds by re-read-before-write, pinned by the interleaving migration test; accepted and documented.
30
+ - Names already written into past transcripts are not retroactively scrubbed if the feature is later disabled.
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ Right layer. The names derivation stays in `secretKeyPaths()` (shared with `/secrets/sync-status` — no logic fork); the block-building/presentation is a new module rather than overloading sync-status (which 503s when secret-sync is dark — wrong availability semantics for a boot surface) or the SelfKnowledgeTree (LLM search over AGENT.md — different system, noted in code comments on both). The hook injection rides the existing org-intent/preferences pattern rather than inventing a new injection mechanism. Rejected alternatives (pull surfaces: /capabilities, MCP resources, memory files) are analyzed in the spec — the failure class is "agent doesn't know to look," which only push-at-boot removes.
35
+
36
+ ## 4. Signal vs authority compliance
37
+
38
+ Compliant — pure signal producer. The block is wrapped in an envelope that explicitly subordinates it to org-intent constraints, safety rules, and user instructions. The guidance line is signal-shaped ("retrieve rather than re-ask, unless you have evidence it is invalid") not absolute. A deterministic block on credential re-asks was considered and rejected as brittle-authority (spec §Why guidance stays a signal); the designed escalation if the bake shows non-compliance is a smart-gate signal feed, not a regex block. The VITEST keychain guard is a test-environment safety rail, not a runtime decision point.
39
+
40
+ ## 5. Interactions
41
+
42
+ - Coexists with the org-intent and preferences injections: placed after both in the hook (authoritative contract first); envelope states precedence. No shadowing — different routes, different envelopes.
43
+ - `/self-knowledge/*` namespace shared with the SelfKnowledgeTree routes (search/validate/health) — no path collision; comments mark which system serves which path.
44
+ - The names cache keys on the vault file path + (mtimeMs,size); secret-sync writes go through the same in-process server, so a peer-pushed secret invalidates the cache on its atomic write. No double-fire: the route is the only consumer.
45
+ - `migrateConfig`'s recursive add-missing merge interacts with operator-set `selfKnowledge` values — partial-override case pinned by test.
46
+ - The VITEST guard interacts with every existing test that constructs SecretStores — it can only make them SAFER (file-key instead of keychain); tests that explicitly pass `forceFileKey: true` (e.g. SecretMigrator's) are unchanged.
47
+
48
+ ## 6. External surfaces
49
+
50
+ - Vault key NAMES become visible in: the Bearer-gated route response, the agent's session context, and therefore on-disk session transcripts (which can travel further than vaults — debug bundles, provider retention; spec §Threat model). This is the feature's one genuinely new exposure and the reason it ships dark-fleet with the live flip as an explicit follow-up decision.
51
+ - No cross-agent or cross-machine surface changes: facts are per-machine (config doesn't sync); names reflect the local vault (which secret-sync may populate). Nothing here changes timing-sensitive behavior visible to other systems; the hook fetch is fail-open with `--max-time 4 --connect-timeout 1`.
52
+
53
+ ## 7. Rollback cost
54
+
55
+ Low. Per-agent: `selfKnowledge.sessionContext.enabled: false` (route 503s, hook silently skips — no restart needed; the flag is fresh-read). Fleet: revert the PR — no data formats change; the only state this feature writes is the `operationalFacts` array via explicit calls, which survives or is hand-removable. The VITEST guard's rollback is one constructor line. Names already in transcripts are the only non-revertible residue (documented).
56
+
57
+ ## Deferred / follow-ups (all tracked)
58
+
59
+ - Live-fleet flip (`enabled: true` in ConfigDefaults) — rides PR #800's merge or explicit approver direction (spec §Availability Resolution rule; the approver was asked directly in the approval request).
60
+ - Session-start hook's pre-existing uncapped sibling curls — framework-issues ledger `session-start-hook-uncapped-curls`.
61
+ - `/secrets/sync-status` rendering decrypt-failure as an empty vault — framework-issues ledger `sync-status-decrypt-fail-reads-empty`.
62
+ - Per-agent keychain accounts + key-id header + dual-key read fallback — pre-existing commitment from the 2026-06-05 incident (CMT lineage in topic 13481), unchanged by this PR.
63
+
64
+ ## Post-review fix round (fresh-eyes code review, 2026-06-05)
65
+
66
+ The independent code review of the feature commit found ONE real bug, fixed before PR: the names cache (keyed on the vault file) cached the `decrypt-failed` outcome — but a decrypt failure is almost always a MASTER-KEY problem (a separate file the cache key cannot see), so a recovered key kept serving the stale hands-off warning until a restart. Fix: only the healthy outcome is cached; a failed state is re-tried on every read (cheap relative to lying about recovery). Plus hardening: backticks are stripped from rendered names/facts (a hostile name can no longer break the inline-code span). Two regression tests added (decrypt-recovery-heals-without-restart; backtick-inertness).
67
+
68
+ ## CI-fix round (post-PR, 2026-06-05)
69
+
70
+ Three CI failures owned per Zero-Failure: (1) `ConversationStore.test.ts` time-bomb — the test anchored retirement at fixed `2026-05-30`, and its 25h-stale entries crossed the store's 7-day expiry on 2026-06-05 (main's last CI run squeaked under the boundary by an hour; this PR's run detonated it) — re-anchored at real now, semantics unchanged; (2) no-silent-fallbacks ratchet — the four new BootSelfKnowledge catch blocks annotated `@silent-fallback-ok` with per-catch justifications (never a baseline bump); (3) docs-coverage route floor — the three new routes documented in the site API reference + a new features page (which also satisfies the route ratchet for the existing tree routes' namespace).
71
+
72
+ ## Compaction-parity round (approver design review, 2026-06-05)
73
+
74
+ Justin's review surfaced the long-session gap: the block injected at session start survives compaction only if the summary carries it. Fixed in-PR: the compaction-recovery hook now carries the same fail-open fetch (re-injection after every compaction — refreshed, not merely preserved), with a Phase-3 e2e running the real compact-hook block against a live server. Collateral: org-intent + preferences share the boot-only gap (filed as `session-context-injectors-lack-compaction-parity`); a "Compaction Parity" constitution amendment is proposed separately. His scale concern is answered by the existing hard 2KB byte-cap (pointer-not-payload design); the AGGREGATE boot-budget concern across all injectors filed as `boot-context-aggregate-budget`.
75
+
76
+ ## Post-merge-conflict round (2026-06-05 AM)
77
+
78
+ Rebase onto the post-#848 main + CI surfaced the feature-delivery-completeness registry: the new CLAUDE.md section is now tracked in `featureSections` AND mirrored to the framework-shadow markers (`migrateFrameworkShadowCapabilities`) — Codex/Gemini agents learn the capability too (the Secret Drop lesson: an unshadowed capability gets improvised around). Local pre-push had skipped the smoke ("CI is the authority"), which is why CI caught it.