instar 1.3.449 → 1.3.451
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/.claude/skills/autonomous/SKILL.md +42 -2
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +26 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +17 -6
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +47 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +8 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +10 -0
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/monitoring/GrowthMilestoneAnalyst.d.ts +169 -0
- package/dist/monitoring/GrowthMilestoneAnalyst.d.ts.map +1 -0
- package/dist/monitoring/GrowthMilestoneAnalyst.js +500 -0
- package/dist/monitoring/GrowthMilestoneAnalyst.js.map +1 -0
- package/dist/permissions/AmbientContributionGate.d.ts +100 -0
- package/dist/permissions/AmbientContributionGate.d.ts.map +1 -1
- package/dist/permissions/AmbientContributionGate.js +85 -3
- package/dist/permissions/AmbientContributionGate.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +41 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +14 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +5 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +77 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.450.md +24 -0
- package/upgrades/1.3.451.md +57 -0
- package/upgrades/side-effects/autonomous-legitimate-stops.md +108 -0
- package/upgrades/side-effects/growth-milestone-analyst-dev-gate.md +99 -0
- package/upgrades/side-effects/growth-milestone-analyst.md +92 -0
- package/upgrades/side-effects/slack-livetest-cleanups.md +75 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Added **`GrowthMilestoneAnalyst`** — a new proactive growth & milestone analyst
|
|
9
|
+
(`src/monitoring/`). It composes the existing tracking surfaces (`InitiativeTracker`
|
|
10
|
+
rollout stages + staleness, `ApprovalLedger` approve-vs-change, `CorrectionLedger`
|
|
11
|
+
recurrence) into one opinionated digest with five explicit notify-rules:
|
|
12
|
+
|
|
13
|
+
- **R1** promotion-ready (a feature past its incubation window that proved itself)
|
|
14
|
+
- **R2** incubation-expired-unproven (past window, never proved itself → extend/repair/retire)
|
|
15
|
+
- **R3** initiative-stalling (reuses `tracker.digest()`)
|
|
16
|
+
- **R4** spec approve-vs-change pattern
|
|
17
|
+
- **R5** recurring-correction pattern
|
|
18
|
+
|
|
19
|
+
The key lever is a TIGHT incubation window (3d low-risk / 7d standard) whose **expiry
|
|
20
|
+
is itself the trigger**, so a feature can never be silently left behind. Promotion
|
|
21
|
+
requires real proof-of-life, never elapsed time alone (a feature whose evidence
|
|
22
|
+
source isn't wired is surfaced honestly as `proved:'unknown'` and can never be
|
|
23
|
+
promotion-ready).
|
|
24
|
+
|
|
25
|
+
Wires four read-only routes — `GET /growth/digest`, `GET /growth/findings`,
|
|
26
|
+
`GET /growth/status`, `POST /growth/tick` — plus a `monitoring.growthAnalyst` config
|
|
27
|
+
block (defaults OFF). Ships **DARK**: this slice computes and exposes findings only;
|
|
28
|
+
it does NOT send to Telegram. Cadence/event-delivery and enabling the muted
|
|
29
|
+
analyzers ride a later, flood-sensitive slice.
|
|
30
|
+
|
|
31
|
+
## What to Tell Your User
|
|
32
|
+
|
|
33
|
+
Nothing yet — this ships dark and disabled by default, and is agent-only. It does not
|
|
34
|
+
message the user, change any existing behavior, or turn on any new surface until it
|
|
35
|
+
is explicitly enabled in a later promotion slice. When asked "is anything checking on
|
|
36
|
+
my initiatives, feature maturity, or patterns?", you can note the analyst exists and
|
|
37
|
+
is being rolled out carefully from dark to live, and that you will be able to read its
|
|
38
|
+
computed digest once it is turned on.
|
|
39
|
+
|
|
40
|
+
## Summary of New Capabilities
|
|
41
|
+
|
|
42
|
+
- New monitoring component `GrowthMilestoneAnalyst` (observe-only; no new sensors).
|
|
43
|
+
- New read routes `GET /growth/digest|findings|status`, `POST /growth/tick`
|
|
44
|
+
(503 when disabled — the dark default).
|
|
45
|
+
- New config block `monitoring.growthAnalyst` (ships disabled; tunable incubation
|
|
46
|
+
windows, proof-of-life threshold, per-rule flags) — auto-applies to existing
|
|
47
|
+
agents via ConfigDefaults deep-merge.
|
|
48
|
+
- CLAUDE.md template documents the routes (Agent Awareness Standard).
|
|
49
|
+
|
|
50
|
+
## Evidence
|
|
51
|
+
|
|
52
|
+
Not a bug fix and not reproducible in dev — this is a new, dark-by-default capability,
|
|
53
|
+
so there is no before/after to reproduce. It changes no existing behavior: with the
|
|
54
|
+
flag off (the default) every new route returns 503 and nothing else references the
|
|
55
|
+
analyst. Behavior is covered by 41 tests across all three tiers (unit semantic
|
|
56
|
+
coverage on both sides of every window/proof boundary, integration route gating,
|
|
57
|
+
wiring against a real InitiativeTracker, and an e2e "feature is alive" lifecycle).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Side-Effects Review — Autonomous "Legitimate Stop Conditions" allowlist
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `autonomous-legitimate-stops`
|
|
4
|
+
**Date:** `2026-06-09`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `not required`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds a new top-level section to the autonomous skill's instruction file — **"## Legitimate Stop Conditions (the ONLY valid reasons to exit)"** — enumerating the only three valid reasons a pre-approved autonomous session may exit: (a) a genuine hard external blocker the agent cannot resolve, (b) duration expiry, (c) the completion condition/promise genuinely met. It pairs that with an explicit NON-stops table (reversible decisions, milestones, late-hour, "needs your steer/opinion," "good stopping point," quiet off-ramp-with-no-reply) and reinforces the existing Anti-Patterns list with two new entries ("This Needs Your Steer" and "Quiet Off-Ramp"). Files touched: `.claude/skills/autonomous/SKILL.md` (the bundled shipping source — new agents get it via `init`/`installAutonomousSkill`), `src/core/PostUpdateMigrator.ts` (bumps the existing `migrateAutonomousStopHookTopicKeyed` SKILL.md marker from `PER-TOPIC (setup-race hardening)` to `LEGITIMATE_STOP_CONDITIONS` so existing agents receive the section on update), and `tests/unit/PostUpdateMigrator-autonomousStopHook.test.ts` (four new migration tests). This is prompt/skill CONTENT plus its Migration-Parity migration — NOT a change to any decision-point code logic.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
The change touches **no runtime decision point.** It does NOT modify the autonomous stop hook's blocking logic (`autonomous-stop-hook.sh`), the completion-condition judge, the emergency-stop check, or any gate/sentinel/watchdog. The stop hook continues to decide exit exactly as before; this change only edits the prose the agent reads and adds a content-sniffing migration that re-deploys that prose to existing installs.
|
|
15
|
+
|
|
16
|
+
- `migrateAutonomousStopHookTopicKeyed` (SKILL.md upgrade arm) — modify (marker bump only) — re-deploys the bundled SKILL.md to existing agents whose installed copy lacks the new sentinel.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
23
|
+
|
|
24
|
+
No block/allow surface — over-block not applicable. The migration's `upgrade()` helper writes a file only when the installed copy lacks the new marker AND matches the stock fingerprint (`ALL_TASKS_COMPLETE`); a customized SKILL.md is left untouched (reported as `skipped`), so it cannot "over-write" operator customizations.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Under-block
|
|
29
|
+
|
|
30
|
+
**What failure modes does this still miss?**
|
|
31
|
+
|
|
32
|
+
No block/allow surface — under-block not applicable. As prose guidance, the section is advisory: the structural stop hook is what actually enforces continuation. An agent could still rationalize an early stop despite the guidance — but that is the stop-hook's job to catch (out of scope here, tracked as the separate Tier-2 stop-hook logic change). This change strengthens the guidance the agent reads each iteration; it does not claim to be the enforcement layer.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 3. Level-of-abstraction fit
|
|
37
|
+
|
|
38
|
+
**Is this at the right layer?**
|
|
39
|
+
|
|
40
|
+
Yes. This is skill CONTENT (prompt-level guidance), which is the correct layer for "here are the only valid reasons to exit." The structural enforcement (the stop hook) is a separate, lower layer that this content complements. The migration rides the EXISTING `migrateAutonomousStopHookTopicKeyed` path — the established, correct home for autonomous SKILL.md content updates — rather than introducing a parallel migration. The marker-bump mechanism is the same content-sniffing pattern already used for the prior SKILL.md fixes (per-topic write, registration-path fix), so it reuses rather than re-implements.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 4. Signal vs authority compliance
|
|
45
|
+
|
|
46
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
47
|
+
|
|
48
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
49
|
+
|
|
50
|
+
- [x] No — this change has no block/allow surface.
|
|
51
|
+
|
|
52
|
+
This is documentation/skill-content plus an idempotent file-deployment migration. It makes no allow/block decision, holds no authority over message flow or session lifecycle, and adds no brittle detector. The migration only writes a markdown file under content-sniffing guards.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 5. Interactions
|
|
57
|
+
|
|
58
|
+
**Does this interact with existing checks, recovery paths, or infrastructure?**
|
|
59
|
+
|
|
60
|
+
- **Shadowing:** None. The migration's SKILL.md arm shares the same `upgrade()` helper as the stop-hook and setup-script arms within `migrateAutonomousStopHookTopicKeyed`; each arm targets a distinct file path, so they cannot shadow each other. The marker bump on the SKILL.md arm does not affect the hook/setup arms (independent markers).
|
|
61
|
+
- **Double-fire:** None. `installAutonomousSkill()` (init path) is install-if-missing and won't overwrite; the migration is the single content-update path. They do not both write the same file in the same run (init only writes when absent).
|
|
62
|
+
- **Races:** None. The migration runs synchronously inside the PostUpdateMigrator sequence at update time; no shared concurrent state.
|
|
63
|
+
- **Feedback loops:** None.
|
|
64
|
+
|
|
65
|
+
One real interaction verified: the existing test `PostUpdateMigrator-autonomousStopHook.test.ts` already exercises this migration; the marker bump from `PER-TOPIC (setup-race hardening)` to `LEGITIMATE_STOP_CONDITIONS` required no change to the existing hook/setup test cases (those assert their own markers, not the SKILL.md marker), and the new SKILL.md tests were added alongside. Verified the bundled SKILL.md contains BOTH the new marker (so `next.includes(marker)` passes and the upgrade actually writes) AND the fingerprint.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 6. External surfaces
|
|
70
|
+
|
|
71
|
+
**Does this change anything visible outside the immediate code path?**
|
|
72
|
+
|
|
73
|
+
- Other agents on the same machine: only their autonomous SKILL.md content updates on their next PostUpdateMigrator run — the intended Migration-Parity effect.
|
|
74
|
+
- Other users of the install base: same — they get the new guidance on update.
|
|
75
|
+
- External systems (Telegram, Slack, GitHub, Cloudflare): none.
|
|
76
|
+
- Persistent state: none beyond rewriting the one markdown file the migration targets (only when stale + stock).
|
|
77
|
+
- Timing/runtime conditions: none.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 7. Rollback cost
|
|
82
|
+
|
|
83
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
84
|
+
|
|
85
|
+
Pure content change — revert the SKILL.md edit and the marker bump, ship as the next patch. No persistent state to clean up, no data migration, no agent-state repair. Existing agents that already received the re-deployed SKILL.md would simply receive the reverted version on a subsequent marker bump (or keep the harmless prose section). No user-visible regression during the rollback window.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Conclusion
|
|
90
|
+
|
|
91
|
+
This review confirms the change is content-only with no decision-point surface. It adds the operator-requested "Legitimate Stop Conditions" allowlist + NON-stops table to the autonomous skill, ships it to new agents via the bundled source, and reaches existing agents via the established content-sniffing SKILL.md migration arm (marker bump, idempotent, customization-preserving). No design changes were required by the review. Clear to ship pending human review.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Second-pass review (if required)
|
|
96
|
+
|
|
97
|
+
**Reviewer:** not required
|
|
98
|
+
|
|
99
|
+
Tier 1, content-only, no block/allow or session-lifecycle decision logic touched — the Phase 5 second-pass triggers do not apply.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Evidence pointers
|
|
104
|
+
|
|
105
|
+
- `npx tsc --noEmit` → exit 0 (clean).
|
|
106
|
+
- `npx vitest run tests/unit/PostUpdateMigrator-autonomousStopHook.test.ts` → 12 passed (8 prior + 4 new SKILL.md cases).
|
|
107
|
+
- `npx vitest run tests/unit/autonomous-skill-deployment.test.ts tests/unit/migration-parity.test.ts tests/unit/PostUpdateMigrator-autonomousHookPath.test.ts tests/unit/autonomous-stop-hook-idle-backoff.test.ts` → 52 passed.
|
|
108
|
+
- Bundled SKILL.md verified to contain both `LEGITIMATE_STOP_CONDITIONS` (marker) and `ALL_TASKS_COMPLETE` (fingerprint).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Side-Effects Review — GrowthMilestoneAnalyst dev-agent gate correction
|
|
2
|
+
|
|
3
|
+
**Change:** Make the GrowthMilestoneAnalyst honor the standard `developmentAgent`
|
|
4
|
+
dark-feature gate instead of a hardcoded `enabled: false`. The original slice-1
|
|
5
|
+
implementation shipped dark for **every** agent (including dev agents like echo),
|
|
6
|
+
which contradicts the established convention used by secret-sync, boot
|
|
7
|
+
self-knowledge, the resource sampler, warm-session A2A, etc.: dark-features OMIT
|
|
8
|
+
`enabled` from their config default and resolve `enabled ?? !!config.developmentAgent`
|
|
9
|
+
at runtime so the dev agent (the dogfooding ground) runs them LIVE while the fleet
|
|
10
|
+
stays DARK.
|
|
11
|
+
|
|
12
|
+
**Files:**
|
|
13
|
+
- `src/config/ConfigDefaults.ts` — removed the hardcoded `enabled: false` from the
|
|
14
|
+
`monitoring.growthAnalyst` block (and rewrote the comment to describe the gate).
|
|
15
|
+
- `src/server/AgentServer.ts` — construction now resolves
|
|
16
|
+
`growthAnalystEnabled = monitoring?.growthAnalyst?.enabled ?? !!developmentAgent`,
|
|
17
|
+
and feeds that resolved value into `resolveGrowthSettings({ ...block, enabled })`
|
|
18
|
+
so `GET /growth/status` honestly reports `enabled: true` on a dev agent. The
|
|
19
|
+
`resolveGrowthSettings` call is now optional-chained so the gate can be true even
|
|
20
|
+
with no `monitoring.growthAnalyst` block present (dev agent, defaults only).
|
|
21
|
+
- `tests/unit/growth-analyst-gate-wiring.test.ts` — new wiring-integrity tests
|
|
22
|
+
(config omits `enabled`, migration parity, both-sides resolution, source-level
|
|
23
|
+
gate assertions).
|
|
24
|
+
|
|
25
|
+
## 1. Over-block — what legitimate inputs does this reject that it shouldn't?
|
|
26
|
+
None. The change ENABLES the feature on dev agents; it never rejects an input. An
|
|
27
|
+
operator who explicitly sets `monitoring.growthAnalyst.enabled: false` still
|
|
28
|
+
force-darks the feature even on a dev agent (explicit value wins via `??`), and an
|
|
29
|
+
explicit `true` still enables it fleet-wide — both preserved and tested.
|
|
30
|
+
|
|
31
|
+
## 2. Under-block — what failure modes does this still miss?
|
|
32
|
+
The analyst remains slice-1: it COMPUTES + serves the digest over `/growth/*` but
|
|
33
|
+
does NOT message the user on a schedule (that's the deliberately-separate next
|
|
34
|
+
slice). So a dev agent now runs the analyst live but still won't proactively speak
|
|
35
|
+
until the messaging slice lands. This is by design (avoid swinging from too-quiet
|
|
36
|
+
to too-noisy), not a regression introduced here. No new under-block.
|
|
37
|
+
|
|
38
|
+
## 3. Level-of-abstraction fit — right layer?
|
|
39
|
+
Yes. The gate resolution lives in `AgentServer` construction exactly where every
|
|
40
|
+
other dark-feature gate resolves (`standard_development_agent_dark_feature_gate`),
|
|
41
|
+
and the default omission lives in `ConfigDefaults` exactly like warmSessionA2A and
|
|
42
|
+
boot self-knowledge. No new layer; it joins the existing one.
|
|
43
|
+
|
|
44
|
+
## 4. Signal vs authority compliance (docs/signal-vs-authority.md)
|
|
45
|
+
Compliant. The GrowthMilestoneAnalyst is a SIGNAL-PRODUCER: it reads rollout
|
|
46
|
+
stages, the approval ledger, and the correction ledger and computes findings. It
|
|
47
|
+
holds NO blocking authority — it never filters a message, blocks an action, or
|
|
48
|
+
gates information flow. This change only determines WHETHER that signal-producer
|
|
49
|
+
runs on a given agent; it adds no brittle-check-with-authority. The routes remain
|
|
50
|
+
read-only.
|
|
51
|
+
|
|
52
|
+
## 5. Interactions — shadowing, double-fire, races?
|
|
53
|
+
None. The construction is guarded by its own try/catch (failure is non-fatal and
|
|
54
|
+
null-instances the analyst → `/growth/*` 503-stubs). No PostUpdateMigrator entry
|
|
55
|
+
writes `growthAnalyst`, so the gate is the sole resolver — there's no migration
|
|
56
|
+
that could write `enabled` to disk and shadow the runtime gate. The
|
|
57
|
+
`resolveGrowthSettings({ ...block, enabled })` merge does not mutate config.
|
|
58
|
+
|
|
59
|
+
## 6. External surfaces — visible to other agents/users/systems?
|
|
60
|
+
On the dev agent only, `/growth/digest`, `/growth/findings`, `/growth/status`,
|
|
61
|
+
`POST /growth/tick` flip from 503 to 200 after the next server boot (Claude Code
|
|
62
|
+
loads config once at session/server start — running processes pick it up on
|
|
63
|
+
restart). Fleet agents are unaffected (still 503). No agent-to-agent surface, no
|
|
64
|
+
Telegram surface (the analyst does not message in this slice). The
|
|
65
|
+
`/growth/status` `enabled` field now reads `true` on a dev agent (was a latent
|
|
66
|
+
`false` had the routes ever been forced live) — this is the honesty fix.
|
|
67
|
+
|
|
68
|
+
## 7. Rollback cost — back-out if wrong?
|
|
69
|
+
Trivial and reversible. Two ways: (a) operator sets
|
|
70
|
+
`monitoring.growthAnalyst.enabled: false` in `.instar/config.json` and restarts —
|
|
71
|
+
force-darks it immediately, no code change; (b) git revert of this commit restores
|
|
72
|
+
the hardcoded `enabled: false`. No data migration, no state repair — the analyst's
|
|
73
|
+
only durable artifact is `state/growth-milestone-analyst/stage-journal.json`, which
|
|
74
|
+
is observe-only and self-heals (a missing/stale journal just recomputes on the next
|
|
75
|
+
observe). Worst case is a dev agent serving an unused read route.
|
|
76
|
+
|
|
77
|
+
## No deferrals
|
|
78
|
+
This change is complete: the gate is wired, the default is corrected, the status
|
|
79
|
+
honesty is fixed, and the wiring tests cover both sides. Nothing is deferred.
|
|
80
|
+
|
|
81
|
+
## Second-pass review (independent)
|
|
82
|
+
**VERDICT: Concur with the review.** An independent reviewer audited the diff,
|
|
83
|
+
the analyst source, the routes, and the migrators:
|
|
84
|
+
- Read-only/signal-only confirmed: `computeFindings`/`buildDigest`/`getStatus`
|
|
85
|
+
never gate on `settings.enabled`; `isEnabled()` only feeds the informational
|
|
86
|
+
status field; routes guard solely on `if (!ctx.growthMilestoneAnalyst)` — so
|
|
87
|
+
constructing the analyst is sufficient to make it live, and the component holds
|
|
88
|
+
no blocking authority.
|
|
89
|
+
- Gate matches the established convention (byte-identical to the resource
|
|
90
|
+
sampler's `?? !!options.config.developmentAgent`); explicit `enabled` wins both
|
|
91
|
+
ways; both sides tested.
|
|
92
|
+
- No fleet leak / no disk write: non-dev agents resolve to false → null → 503;
|
|
93
|
+
grep of PostUpdateMigrator returned zero `growthAnalyst` references, so no
|
|
94
|
+
migration writes `enabled` to disk; `applyDefaults` only backfills missing
|
|
95
|
+
fields (operator `enabled:false` survives).
|
|
96
|
+
- No optional-chaining/merge bug: `resolveGrowthSettings({ ...(block ?? {}),
|
|
97
|
+
enabled })` is safe when the block is absent and cannot throw; the spread sets
|
|
98
|
+
`enabled` last so it deterministically wins; no needed field dropped.
|
|
99
|
+
- All seven side-effects claims match the code; new test file passes 6/6.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Side-Effects Review — Growth & Milestone Analyst (slice 1)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `growth-milestone-analyst`
|
|
4
|
+
**Date:** `2026-06-08`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Tier:** `1` (ships dark, flag-off, compute + read-only, fully reversible)
|
|
7
|
+
**Second-pass reviewer:** `not required`
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
Adds `GrowthMilestoneAnalyst` (`src/monitoring/`) — a pure observer that composes
|
|
12
|
+
the existing `InitiativeTracker` (rollout stages + staleness), `ApprovalLedger`
|
|
13
|
+
(approve-vs-change), and `CorrectionLedger` (recurrence) into one digest with five
|
|
14
|
+
explicit notify-rules (R1 promotion-ready, R2 incubation-expired-unproven, R3
|
|
15
|
+
initiative-stalling, R4 spec-pattern, R5 correction-pattern). It keeps ONE piece of
|
|
16
|
+
internal bookkeeping — a stage-observation journal — so "days in stage" is robust
|
|
17
|
+
where the rollout engines do not stamp it. Wires four read routes (`GET
|
|
18
|
+
/growth/digest|findings|status`, `POST /growth/tick`), a `monitoring.growthAnalyst`
|
|
19
|
+
config block (defaults OFF), and the AgentServer construction (guarded; null →
|
|
20
|
+
routes 503). Ships DARK: compute + read only, no Telegram sending.
|
|
21
|
+
|
|
22
|
+
## Decision-point inventory
|
|
23
|
+
|
|
24
|
+
- **`classifyRollout`** (the window-expiry verdict): in-window / at-boundary /
|
|
25
|
+
expired × proved / unproved / unknown × stage. Both sides of every edge are unit
|
|
26
|
+
tested.
|
|
27
|
+
- **Per-rule enable gates** (`settings.rules.*`): each rule suppresses iff its flag
|
|
28
|
+
is false.
|
|
29
|
+
- **Construction gate** in AgentServer: analyst built only when
|
|
30
|
+
`monitoring.growthAnalyst.enabled === true` AND a tracker + stateDir exist.
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
|
|
34
|
+
**What legitimate inputs does this change reject?** Nothing user-facing is rejected.
|
|
35
|
+
The only "rejection" is structural: when the feature is OFF (the default), every
|
|
36
|
+
`/growth/*` route returns 503 — by design, mirroring the established dark-feature
|
|
37
|
+
contract (e.g. `correctionLearning`). No existing route, job, message, or gate
|
|
38
|
+
changes behavior. The analyst never blocks, delays, or rewrites anything.
|
|
39
|
+
|
|
40
|
+
## 2. Under-block
|
|
41
|
+
|
|
42
|
+
**What does this still miss?** Three deliberate gaps, all deferred to later slices:
|
|
43
|
+
(a) no Telegram/event delivery — it computes but does not speak, so a real milestone
|
|
44
|
+
won't reach the operator until the sending slice; (b) proof-of-life
|
|
45
|
+
(`evidenceCounter`) is unwired, so every expiry currently classifies as
|
|
46
|
+
R2-unknown rather than R1 until a per-feature activation source is plumbed — this is
|
|
47
|
+
surfaced honestly as `proved:'unknown'`, never masked; (c) the muted analyzers
|
|
48
|
+
(`correctionLearning`, `failureLearning`) are NOT enabled here — that flood-sensitive
|
|
49
|
+
step is its own slice.
|
|
50
|
+
|
|
51
|
+
## 3. Level-of-abstraction fit
|
|
52
|
+
|
|
53
|
+
**Right layer?** Yes. The analyst lives in `src/monitoring/` beside the other
|
|
54
|
+
observe-only components (TokenLedger, ResourceLedger). It reads through the engines'
|
|
55
|
+
public methods (`tracker.list/digest`, `approvalLedger.summarize`,
|
|
56
|
+
`correctionLedger.list`) rather than reaching into their storage. The stage journal
|
|
57
|
+
is analyst-private state under `stateDir/state/growth-milestone-analyst/`.
|
|
58
|
+
|
|
59
|
+
## 4. Reversibility
|
|
60
|
+
|
|
61
|
+
Fully reversible: the feature ships behind `monitoring.growthAnalyst.enabled`
|
|
62
|
+
(default false). Disabling it (or removing the construction block) returns the
|
|
63
|
+
system to byte-identical prior behavior — the routes 503, nothing else references
|
|
64
|
+
the analyst. The only persisted artifact is a small JSON journal that is pruned and
|
|
65
|
+
self-heals.
|
|
66
|
+
|
|
67
|
+
## 5. Anti-flood discipline (relevant because this REVERSES an over-silence)
|
|
68
|
+
|
|
69
|
+
The whole point is to speak more, so overshooting into a new flood is the risk to
|
|
70
|
+
guard. This slice does not send at all. The digest is structurally ONE object — a
|
|
71
|
+
burst of N window-expiries aggregates into one digest with counts, never N messages
|
|
72
|
+
(unit-tested at N=500). When the sending slice lands it must route through the
|
|
73
|
+
existing budget-guarded attention/post-update surfaces and aggregate, never create
|
|
74
|
+
one topic per feature. That guardrail is stated here so the next slice inherits it.
|
|
75
|
+
|
|
76
|
+
## 6. Framework generality
|
|
77
|
+
|
|
78
|
+
Not framework-specific. The analyst is a server-side monitoring component that reads
|
|
79
|
+
instar's own ledgers/trackers; it does not spawn sessions, call an LLM, or route
|
|
80
|
+
through any agentic framework (Claude/Codex/Gemini/pi). It behaves identically
|
|
81
|
+
regardless of which framework the agent runs on. No per-framework branching exists
|
|
82
|
+
or is needed.
|
|
83
|
+
|
|
84
|
+
## 7. Migration parity
|
|
85
|
+
|
|
86
|
+
- Config defaults auto-apply to existing agents via `ConfigDefaults` +
|
|
87
|
+
`applyDefaults` deep-merge (the established no-separate-migrateConfig path for a
|
|
88
|
+
new `monitoring.*` sub-key).
|
|
89
|
+
- The CLAUDE.md template (`generateClaudeMd`) documents the routes for NEW agents.
|
|
90
|
+
- A `migrateClaudeMd` content-sniff + the scheduled digest job are deferred to the
|
|
91
|
+
promotion slice — a dark, route-only feature needs no live agent-awareness
|
|
92
|
+
migration until it actually speaks.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Side-Effects Review — Slack live-test cleanups (#2 ambient-silence observability, #5 spec/type alignment)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `slack-livetest-cleanups`
|
|
4
|
+
**Date:** `2026-06-09`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `not required` (Tier-1; observability-only + doc-accuracy; no decision authority touched)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Two small cleanups the Slack-org live-test plan flagged.
|
|
11
|
+
|
|
12
|
+
**Cleanup #2 (observability):** the `AmbientContributionGate` previously only left a trace when it SPOKE (the speak-path `console.log` + the `onDecision` log fires on `speak:true`). A wrongful SILENCE left no measurable record, so the ambient false-positive (wrongful-silence) / false-negative rate could not be measured during the observe-only live test. This adds a bounded, in-memory aggregate inside the gate — per-channel `{evaluated, spoke, silent, nearMissSilent, silentByReason}` plus a bounded ring of the most-recent near-miss silences — read via a new `getStats()` method, a `SlackAdapter.getAmbientStats()` passthrough, and a read-only `GET /permissions/ambient-stats` route (reads the live `ctx.slack` instance). Files: `src/permissions/AmbientContributionGate.ts`, `src/messaging/slack/SlackAdapter.ts`, `src/server/routes.ts`, plus unit + integration tests.
|
|
13
|
+
|
|
14
|
+
**Cleanup #5 (doc accuracy):** `SLACK-ORG-INTEGRATION-SPEC.md` named a `respondMode: 'considered'` third mode, but the shipped `SlackRespondMode` type is `'all' | 'mention-only'`; ambient rides on `ambientContribution.enabledChannelIds` ON TOP of `mention-only`. The spec (§5.2 and the §15 migration-parity bullet) is rewritten to match the shipped reality — the lower-risk fix. No type value added; no code path needed it.
|
|
15
|
+
|
|
16
|
+
## Decision-point inventory
|
|
17
|
+
|
|
18
|
+
- `AmbientContributionGate.shouldSpeak` (the speak/silence decision) — **pass-through** — the verdict is computed exactly as before; the aggregate is updated AFTER the verdict is formed, inside `decide()`, wrapped in try/catch, and `decide()` returns the decision unchanged. The recording cannot alter the outcome.
|
|
19
|
+
- `GET /permissions/ambient-stats` — **add** — read-only observability surface; performs no mutation and no messaging.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Over-block
|
|
24
|
+
|
|
25
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
26
|
+
|
|
27
|
+
No block/allow surface — over-block not applicable. The aggregate is signal-only and the new route is read-only. The speak/silence decision is unchanged (a dedicated regression test asserts identical verdicts with the aggregate present).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. Under-block
|
|
32
|
+
|
|
33
|
+
**What failure modes does this still miss?**
|
|
34
|
+
|
|
35
|
+
No block/allow surface — under-block not applicable. The aggregate is an in-memory FP-measurement counter, not a gate. It is reset on restart, which is acceptable for an FP-rate-measurement surface (the durable per-decision record is the file-backed `/permissions/decisions`). Worst case is a momentary loss of accumulated counts after a restart — never an over- or under-speak.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 3. Level-of-abstraction fit
|
|
40
|
+
|
|
41
|
+
**Is this at the right layer?**
|
|
42
|
+
|
|
43
|
+
Yes. The aggregate lives ON the gate that produces the decisions it counts, so every decision (including the previously-invisible silences) is observed at the single chokepoint `decide()` — no parallel detector, no double-counting. The route reads the live `ctx.slack` adapter (already in `RouteContext`), mirroring how the existing observe-only `/permissions/*` surfaces expose internal state. A durable counter could replace the in-memory map later without changing the contract.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 4. Signal vs authority compliance
|
|
48
|
+
|
|
49
|
+
**Does this hold blocking authority with brittle logic, or produce a signal?**
|
|
50
|
+
|
|
51
|
+
Pure signal. Per `docs/signal-vs-authority.md`: the aggregate is a detector/observability surface with ZERO authority — it never blocks, delays, or rewrites a message, and it cannot change the speak/silence verdict (recording runs after the verdict and returns it unchanged; a stats bug is swallowed by try/catch). The ambient gate's own fail-to-silence authority is untouched.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 5. Interactions
|
|
56
|
+
|
|
57
|
+
**Does it shadow another check, get shadowed, double-fire, or race?**
|
|
58
|
+
|
|
59
|
+
No. The new counters are independent of the existing `onDecision` hook and the speak-path `console.log` (both retained). No timer, no shared mutable state with another component. The `recordSpoke()` rate-limit budget is a separate concern and is not affected. The new route shares the `/permissions/*` prefix, already allowlisted as dark/internal in `CapabilityIndex` (line ~1023), so it does not trip the route-discoverability scan.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 6. External surfaces
|
|
64
|
+
|
|
65
|
+
**Does it change anything visible to other agents / users / systems?**
|
|
66
|
+
|
|
67
|
+
Only the new authenticated read-only route `GET /permissions/ambient-stats` (returns `{ present: false }` when no ambient gate is attached — the default). It creates NO Telegram message and NO forum topic, so it cannot contribute to a notification flood (verified: the notification-flood burst-invariant test still passes). It is dark unless a channel is opted into ambient contribution. No timing/conversation-state dependency.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 7. Rollback cost
|
|
72
|
+
|
|
73
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
74
|
+
|
|
75
|
+
Trivial. The aggregate is in-memory (no persistence, no migration, no agent-state repair) and the route is additive. Reverting the commit removes both with no residue. Cleanup #5 is a doc edit with zero runtime effect.
|