instar 1.3.34 → 1.3.36
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/README.md +1 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +53 -3
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +15 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +15 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/featureRolloutScan.d.ts +23 -0
- package/dist/core/featureRolloutScan.d.ts.map +1 -1
- package/dist/core/featureRolloutScan.js +102 -0
- package/dist/core/featureRolloutScan.js.map +1 -1
- package/dist/core/types.d.ts +45 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/AgentTelegramComms.d.ts +77 -0
- package/dist/messaging/AgentTelegramComms.d.ts.map +1 -1
- package/dist/messaging/AgentTelegramComms.js +108 -0
- package/dist/messaging/AgentTelegramComms.js.map +1 -1
- package/dist/monitoring/ReleaseReadinessSentinel.d.ts +172 -0
- package/dist/monitoring/ReleaseReadinessSentinel.d.ts.map +1 -0
- package/dist/monitoring/ReleaseReadinessSentinel.js +315 -0
- package/dist/monitoring/ReleaseReadinessSentinel.js.map +1 -0
- package/dist/monitoring/releaseReadinessWiring.d.ts +54 -0
- package/dist/monitoring/releaseReadinessWiring.d.ts.map +1 -0
- package/dist/monitoring/releaseReadinessWiring.js +241 -0
- package/dist/monitoring/releaseReadinessWiring.js.map +1 -0
- 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 +1 -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 +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +55 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/analyze-release.js +221 -0
- package/scripts/upgrade-guide-validator.mjs +120 -0
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates/jobs/instar/release-readiness-check.md +24 -0
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.35.md +44 -0
- package/upgrades/1.3.36.md +39 -0
- package/upgrades/side-effects/agent-telegram-comms-send.md +71 -0
- package/upgrades/side-effects/release-readiness-visibility-pr2a.md +27 -0
- package/upgrades/side-effects/release-readiness-visibility-pr2b.md +26 -0
- package/upgrades/side-effects/release-readiness-visibility-pr2c.md +37 -0
- package/upgrades/side-effects/release-readiness-visibility-pr3.md +33 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: minor -->
|
|
4
|
+
<!-- Valid values: patch, minor, major -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The **Release-Readiness Visibility** spec (`docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md`) shipped fully across three PRs (#433, #442, #443). Two silent self-driving-loop gaps are closed:
|
|
9
|
+
|
|
10
|
+
1. **Auto-draft for upgrade notes (Layer A).** `scripts/analyze-release.js` gained a `--draft-guide` mode that turns the classified commit-range into a starter `upgrades/NEXT.md`. Drafted content carries `auto-draft-unreviewed` markers; both `scripts/upgrade-guide-validator.mjs` (via the new `autoDraftReviewIssues` block in `validateGuideContent`) and `.github/workflows/publish.yml` refuse to ship while any marker remains. A reviewer clears each by replacing the marker with `<!-- reviewed-by: <name> @ <ISO-date> :hash=<sha256> -->` — the SHA captures the canonicalized section at review time, so a later edit invalidates the receipt and re-blocks publish.
|
|
11
|
+
|
|
12
|
+
2. **Release-readiness watchdog (Layer B).** A new `ReleaseReadinessSentinel` in `src/monitoring/ReleaseReadinessSentinel.ts`. When `monitoring.releaseReadiness.enabled: true` and the install has an analyzable instar git repo (`isAnalyzableRepo` in `src/monitoring/releaseReadinessWiring.ts`), the sentinel is constructed at server boot. The off-by-default `release-readiness-check` cron job drives it via `POST /release-readiness/tick`. The sentinel evaluates canonical `main` (bounded fetch, no `--depth=1` because that shallows the local repo — caught by the real-I/O E2E) and surfaces a stalled release as exactly ONE deduped, age-escalating Attention item keyed on the oldest unreleased commit SHA. Fail-loud: every evaluation error raises a low-priority Attention item, never a silent catch. New routes (bearer-gated): `GET /release-readiness`, `POST /release-readiness/tick`, `POST /release-readiness/rollback` (raises HIGH attention + audits + `rollbackHistory[]` — never a silent kill), `POST /release-readiness/enable`. State file: `.instar/state/release-readiness.json`.
|
|
13
|
+
|
|
14
|
+
3. **Canonical-ref scan for FeatureRolloutReconciler (Layer C).** `src/core/featureRolloutScan.ts` gained `scanSpecArtifactsCanonical` and `scanSpecArtifactsWithCanonical`. When `featureRollout.canonicalRefScan: true` and a canonical remote is configured, the spec scan reads `docs/specs/` and `.instar/instar-dev-traces/` from canonical `main` directly via `SafeGitExecutor` (`ls-tree` + `show`). A spec present on main is merged by construction — the old `approved && traceExists` inference is replaced with real ancestry. On any failure (no remote, network, parse) → degradation event + graceful fallback to the local scan. Never throws into boot.
|
|
15
|
+
|
|
16
|
+
The fix path for an existing silent stall: the watchdog opens an Attention item; auto-draft fills NEXT.md but BOTH publish gates refuse it until a human reviews each section (replacing the marker with a hash-locked receipt); rollback flips a state-file kill-switch loudly.
|
|
17
|
+
|
|
18
|
+
## What to Tell Your User
|
|
19
|
+
|
|
20
|
+
- **I'll notice if my own ship is stuck**: when changes pile up and a release stops going out, I now raise a single, calm flag on my attention list — and the longer it sits, the louder that flag gets. You don't have to remember to check whether I'm actually shipping; I'll tell you when I'm not.
|
|
21
|
+
- **The release notes draft themselves**: instead of waiting for someone to write release notes from scratch, the toolchain now sketches a starter version from the change list. A human still has to read and sign off on each section before it can publish — the safety net stays in place; it just isn't a blank page anymore.
|
|
22
|
+
- **A merged feature can no longer be invisible to my own systems**: my feature board used to read whatever was on my laptop, which sometimes meant brand-new merged features didn't show up. It can now read the real shared copy directly, so the newest work stops being the most-hidden work.
|
|
23
|
+
|
|
24
|
+
This change is off by default everywhere. I'll dogfood it on myself first.
|
|
25
|
+
|
|
26
|
+
## Summary of New Capabilities
|
|
27
|
+
|
|
28
|
+
| Capability | How to Use |
|
|
29
|
+
|-----------|-----------|
|
|
30
|
+
| Release-readiness watchdog | `GET /release-readiness` (status) · `POST /release-readiness/tick` (run once) · `POST /release-readiness/rollback` (loud disable) · `POST /release-readiness/enable` |
|
|
31
|
+
| Auto-draft an upgrade guide | `node scripts/analyze-release.js --draft-guide` (writes `upgrades/NEXT.md` with unreviewed markers) |
|
|
32
|
+
| Canonical-ref spec scan | Set `featureRollout.canonicalRefScan: true` and `featureRollout.canonicalRemote` in `.instar/config.json` |
|
|
33
|
+
| Enable the watchdog | Set `monitoring.releaseReadiness.enabled: true` in `.instar/config.json`; the `release-readiness-check` job runs every 6h (still ships disabled — flip it on per agent) |
|
|
34
|
+
|
|
35
|
+
## Evidence
|
|
36
|
+
|
|
37
|
+
The Layer B real-I/O E2E (`tests/e2e/release-readiness-live.test.ts`) reproduces the original silent-stall shape against a fixture instar repo (real git fetch + real `analyze-release.js` subprocess + real `merge-base`) and asserts that exactly one Attention item is raised for an aged, blocked backlog. The Layer A unit tests (`tests/unit/analyze-release-draft-guide.test.ts` and `tests/unit/upgrade-guide-autodraft-review.test.ts`) reproduce the placeholder-text bypass adversarial review caught (the analyzer's `'Review the commit for specifics'` fallback that previously could pass the section-presence gate) and confirm the new validator blocks it. The Layer C unit test (`tests/unit/featureRolloutScan-canonical.test.ts`) reproduces the exact bug — a spec deleted from the local working tree but committed to main — and confirms the canonical scan still detects it as merged. ~64 tests added across three layered tiers, all green.
|
|
38
|
+
|
|
39
|
+
Additionally, this very NEXT.md authoring revealed the meta-failure: the two PRs above merged but their publishes silently skipped because **NEXT.md didn't exist** — exactly the silent-skip mechanism Layer A + Layer B were built to surface. This guide is the unblock; once it publishes, the watchdog this guide describes goes live and can catch the same shape next time.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Side-Effects Review — agent-to-agent Telegram comms: send side + audit (PR 2b)
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/MENTOR-LIVE-READINESS-SPEC.md` §Fix 2a "Sender side" + anti-loop #1 +
|
|
4
|
+
round-2 adversarial F5 (token-leak scrub). PR 2 of the staged build, part b.
|
|
5
|
+
**Change:** Extend `src/messaging/AgentTelegramComms.ts` with `sendAgentMessage` (the
|
|
6
|
+
adapter-agnostic send function), a `SendAuditRow` schema + appender hook, and `scrubToken`
|
|
7
|
+
(redacts the bot token from any error surface before it leaves the module). I/O still
|
|
8
|
+
**injected** — fully unit-testable; no caller invokes it yet (dark). PR 3 wires it.
|
|
9
|
+
**Files:** `src/messaging/AgentTelegramComms.ts` (additive), `tests/unit/messaging/AgentTelegramComms.test.ts` (+5).
|
|
10
|
+
|
|
11
|
+
## What changed
|
|
12
|
+
|
|
13
|
+
- `sendAgentMessage(input, deps)` — mints/uses `a2aId` (UUID-or-supplied), defaults
|
|
14
|
+
`correlationId` to `id` (prompts self-correlate; replies thread on the prompt's id),
|
|
15
|
+
formats the marker via `formatMarker`, calls the injected `deps.send(topicId, text)`,
|
|
16
|
+
and writes ONE audit row (always — on `ok`, `failed`, or `role-refused`). Returns
|
|
17
|
+
`{ok, a2aId, sentMessageId?, reason?}`.
|
|
18
|
+
- **Runtime anti-loop guard** (spec §Fix 2a anti-loop #1): refuses any `role` not in
|
|
19
|
+
`deps.allowedRoles` — NO send attempted, NO marker formed, audit row with
|
|
20
|
+
`result: 'role-refused'`. This makes the import-surface lint (deferred to PR 3) a
|
|
21
|
+
backup rather than the only defense; the runtime guard always applies.
|
|
22
|
+
- `scrubToken(s, token)` — redacts the bot token (full + secret-portion) from any
|
|
23
|
+
string before it leaves the module. Wired into every error/audit path in
|
|
24
|
+
`sendAgentMessage` (round-2 adversarial F5: Telegram 401 response bodies sometimes
|
|
25
|
+
echo the token).
|
|
26
|
+
|
|
27
|
+
## The seven questions
|
|
28
|
+
|
|
29
|
+
1. **Over-block.** The runtime role-refusal is exact — a caller that legitimately needs
|
|
30
|
+
role X passes it in `allowedRoles`. No false-positive refusal.
|
|
31
|
+
2. **Under-block.** Every send path writes an audit row (ok / failed / role-refused) — no
|
|
32
|
+
silent drop. Token scrub runs on every reason-string surface (format errors, send
|
|
33
|
+
errors). The audit row schema explicitly forbids secrets (verified in the artifact).
|
|
34
|
+
3. **Level-of-abstraction fit.** I/O injected (`send`, `appendAudit`, `now`, `mintId`,
|
|
35
|
+
`botToken`, `fromBotId`, `toBotId`). The function is the wire-level send/marker/audit
|
|
36
|
+
logic; storage choice (JSONL vs SQLite) is the caller's (PR 3 picks).
|
|
37
|
+
4. **Signal vs authority.** Send is the authority on "did the message leave my process";
|
|
38
|
+
the result is exact (`ok` only when the adapter returns a messageId). Audit row is the
|
|
39
|
+
signal trail. No silent best-effort.
|
|
40
|
+
5. **Interactions — dark, but the module surface is shared with PR 1 + the existing
|
|
41
|
+
`decideRoute`.** The send side adds NEW exports (sendAgentMessage, scrubToken,
|
|
42
|
+
SendAuditRow, etc.) — existing imports unaffected. Dark today.
|
|
43
|
+
6. **External surfaces.** None new in this PR. The audit row schema is the wire format
|
|
44
|
+
for future log readers (Stage-B forensics will read it in PR 3).
|
|
45
|
+
7. **Rollback cost.** Trivial — revert removes additive exports + tests. No data, no
|
|
46
|
+
migration. PR 1 / PR 2a are not touched.
|
|
47
|
+
|
|
48
|
+
## Testing
|
|
49
|
+
|
|
50
|
+
- 5 new unit tests (25 total in the file, all green):
|
|
51
|
+
- **send + audit happy path**: marker round-trips through `parseMarker` (the sender's
|
|
52
|
+
output is the receiver's input — closes the wire-format integration informally);
|
|
53
|
+
`result: 'ok'` audit row with `sentMessageId` + correct fields.
|
|
54
|
+
- **correlation threading**: explicit `correlationId` lands in the marker's `corr=`
|
|
55
|
+
and in the audit row (prompt↔reply linkage via Telegram chat history alone, per
|
|
56
|
+
Codey's round-2 design point).
|
|
57
|
+
- **ANTI-LOOP runtime refusal**: a role not in `allowedRoles` → no send, role-refused
|
|
58
|
+
audit row with explanatory reason. Proves the import-lint isn't the only defense.
|
|
59
|
+
- **send-error with token in body**: adapter throws an error containing the bot token
|
|
60
|
+
→ result + audit reason both scrubbed (no token leak through any surface).
|
|
61
|
+
- **scrubToken unit**: full-token + secret-portion + no-token-present + undefined-token
|
|
62
|
+
coverage.
|
|
63
|
+
- `tsc --noEmit` clean.
|
|
64
|
+
|
|
65
|
+
## Migration parity
|
|
66
|
+
|
|
67
|
+
None — additive exports to a dark module. No config, no routes, no agent-installed file
|
|
68
|
+
changes. PR 3 wires the receiver-handler at `server.ts`, the role handlers, the audit
|
|
69
|
+
ledger persistence (JSONL append via SafeFsExecutor), and the processed-id store (SQLite
|
|
70
|
+
per the convention note flagged in PR 1's artifact); migration (file-outbox retirement,
|
|
71
|
+
dead-config removal) lands there per the spec's §Migration parity.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Side-Effects Review — Release-Readiness Visibility, PR-2a (Layer A auto-draft + publish-gate amendment)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §4.1 (converged + approved).
|
|
4
|
+
**Scope:** the auto-draft half of PR-2 — `analyze-release.js --draft-guide`, the `upgrade-guide-validator.mjs` review gate, and the `publish.yml` skip-predicate amendment. (Layer B — the ReleaseReadinessSentinel + routes + job — lands in a following commit on this same branch.)
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
|
|
8
|
+
- **`scripts/analyze-release.js`** — new `--draft-guide` mode. Builds `upgrades/NEXT.md` from the already-computed `generateChangeDescriptions`, structured into the required sections + a seeded `<!-- bump: -->`. Every section carries an `auto-draft-unreviewed` marker. Commit-message text is sanitized (HTML comments stripped, length-capped, leading `#`/`---` escaped) so a crafted message can't forge markers or break section bounds. Two write modes: full draft when the guide is absent/pristine-template; additive uncovered-delta block (never clobbers human content) otherwise. Idempotent — coverage is measured against human content only, so the delta block doesn't oscillate. Race-guarded against publish-finalize via the `upgrades/{version}.md` existence check.
|
|
9
|
+
- **`scripts/upgrade-guide-validator.mjs`** — `autoDraftReviewIssues()` (wired into `validateGuideContent`): blocks while any `auto-draft-unreviewed` marker remains; validates `reviewed-by` receipts (required `:hash=<sha256>` matching the canonicalized section, ≤30-day window). `canonicalizeSectionForHash` / `sectionReviewHash` exported for reuse + tests.
|
|
10
|
+
- **`.github/workflows/publish.yml`** — skip predicate also sets `skip=true` when `auto-draft-unreviewed` is present (keeps silent-skip semantics for the unreviewed case; the sentinel surfaces it as a signal in Layer B).
|
|
11
|
+
|
|
12
|
+
## Side-effects analysis
|
|
13
|
+
|
|
14
|
+
**Over/under-reach.** The validator change adds block conditions that fire ONLY on content the auto-drafter produces (`auto-draft-unreviewed` markers) or on malformed `reviewed-by` receipts. A hand-written guide that never uses these markers is completely unaffected — existing guides and the existing finalization flow are untouched. `--draft-guide` is opt-in (no existing caller passes it); the default `analyze-release.js` behavior is byte-identical.
|
|
15
|
+
|
|
16
|
+
**Defeating-the-gate (the iter-1 adversarial finding).** The whole point: a fully auto-drafted guide PASSES every prior check (sections present, length, no template placeholders, evidence-section present) but is BLOCKED by the new unreviewed-marker rule. Verified: drafting then validating returns the unreviewed-marker issue; replacing markers with correct hash-locked receipts clears it; wrong-hash / missing-hash / stale receipts re-block. So auto-fill removes the blank-guide root cause without shipping un-reviewed notes.
|
|
17
|
+
|
|
18
|
+
**Signal vs authority.** The gate amendment teaches the EXISTING publish gate (which already has blocking authority) a new recognize-and-block condition; it introduces no new low-context detector with veto power. Consistent with §5.
|
|
19
|
+
|
|
20
|
+
**Spec deviation (documented).** §4.1.3 also describes an intra-host O_EXCL advisory lock. It is intentionally omitted: releasing it needs a destructive `fs.unlink`, forbidden by `lint-no-direct-destructive` in this dependency-free, test-copyable script, and the draft job is single-runner via the multi-machine lease so concurrent local drafters don't occur. The cross-host guarantee (the one §4.1.3 calls the real guarantee) is fully preserved via the `{version}.md` existence check. The full "marker stripped without a receipt" detection remains the tracked Phase-2 git-diff CI check per §4.1.1.
|
|
21
|
+
|
|
22
|
+
**Rollback.** Revert restores prior `analyze-release.js` / validator / workflow. No state, config, or migration introduced by PR-2a.
|
|
23
|
+
|
|
24
|
+
## Testing
|
|
25
|
+
|
|
26
|
+
- Unit (19 green): `analyze-release-draft-guide.test.ts` (8 — full draft, template overwrite, never-clobber merge, idempotency, fully-covered no-op, finalize-race skip, no stray lock, HTML-comment sanitization); `upgrade-guide-autodraft-review.test.ts` (7 — marker blocks, block-marker blocks, correct receipt passes, hash-mismatch/missing-hash/stale block, validateGuideContent surfaces it); `analyze-release-ref-flag.test.ts` (4, from PR-1).
|
|
27
|
+
- Integration/E2E for the readiness pipeline land with Layer B (the routes + the "feature is alive" E2E that reproduces the original stall).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Side-Effects Review — Release-Readiness Visibility, PR-2b (Layer B core: sentinel + I/O wiring)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §4.2 (converged + approved).
|
|
4
|
+
**Scope of THIS commit:** the dependency-injected `ReleaseReadinessSentinel` (pure decision logic) and `releaseReadinessWiring` (its real git/analyzer/state/Attention I/O bridge), plus their tests. **This commit does NOT yet wire the sentinel into server startup or add the HTTP routes** — so on this commit the sentinel is constructed nowhere (it is not yet live). The server construction + `GET /release-readiness` + `POST /release-readiness/rollback` + config defaults + job template + migrations + integration/E2E land in the following commit on this same (unmerged) branch. Calling this out explicitly to avoid the PR #334 failure mode (sentinels merged as dead code with a false "wired" claim) — nothing here is claimed to be live.
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
|
|
8
|
+
- **`src/monitoring/ReleaseReadinessSentinel.ts`** — EventEmitter with injected deps. `tick()` (detect → surface → auto-resolve → reap), `resolveEpisodesInRange()` (publish-finalize hook; ancestry-based so it's correct under oldest-SHA churn), `priorityForAge()`, `reap()`. Blocked-predicate is decoupled from NEXT.md state (commits-with-blocked-guide OR coverage-gaps) so Layer A's auto-draft can't silence the alarm. One deduped signal keyed on the oldest-unreleased-commit SHA; age-scaled priority; 12h hysteresis; fail-loud (every evaluation error → low-priority Attention, never a silent catch). Tier 0.
|
|
9
|
+
- **`src/monitoring/releaseReadinessWiring.ts`** — `buildReleaseReadinessDeps()` (real I/O), `isAnalyzableRepo()` (repo-gate), `resolveCanonicalRemote()` (allow-list), `loadReadinessState`/`saveReadinessState` (atomic via temp+rename — no destructive-lint verb), `makeAttentionPoster`.
|
|
10
|
+
|
|
11
|
+
## Side-effects analysis
|
|
12
|
+
|
|
13
|
+
**Reach.** Both modules are new, imported by nothing yet. Zero runtime effect on any existing path at this commit. No config, route, job, or migration is introduced here.
|
|
14
|
+
|
|
15
|
+
**Repo-gated dev-environment scope (spec refinement, documented).** The sentinel analyzes the instar git repo via `analyze-release.js`. That repo only exists in the dev/maintainer environment (Echo's agent home IS the instar checkout). A plain npm-installed agent has no instar repo to analyze. `isAnalyzableRepo()` is the gate the server will consult: on a non-analyzable install the sentinel is simply not constructed (inert) — it never posts a spurious Attention item about a missing repo. This is consistent with the spec's "ships off by default; Echo dogfoods first," and makes explicit a reality the spec under-specified.
|
|
16
|
+
|
|
17
|
+
**Security / input safety.** All git goes through `SafeGitExecutor` (execFileSync, no shell). The canonical-remote allow-list is anchored on `github.com` host (`^(https://github\.com/|git@github\.com:)JKHeadley/instar(\.git)?$`) — a look-alike host like `git@evil.com:JKHeadley/instar.git` does NOT match (iter-3 adversarial V3); a configured override to a non-canonical URL is permitted but flips `canonicalRemoteOverridden`, which the server will surface as a HIGH-priority signal. The analyzer is spawned via `execFile(process.execPath, [...])` (argv array, no shell). State load tolerates corruption (returns a fresh state rather than throwing into the tick).
|
|
18
|
+
|
|
19
|
+
**Fail-open vs fail-loud.** The sentinel's contract is fail-loud: `tick()`'s top-level catch and the per-stage `failLoud()` convert fetch/analyzer errors into low-priority Attention items (deduped per failure episode). This is verified by unit tests, and is the core anti-pattern guard (a silent catch would re-create the bug being fixed).
|
|
20
|
+
|
|
21
|
+
**Rollback.** Deleting the two modules + their tests fully reverts; nothing imports them yet.
|
|
22
|
+
|
|
23
|
+
## Testing
|
|
24
|
+
|
|
25
|
+
- Unit (21 green): `ReleaseReadinessSentinel.test.ts` (13 — thresholds, silent-below-threshold, single deduped signal, escalation, coverage-gap blocking, decoupled-from-guide, auto-resolve, fail-loud×2, hysteresis, resolveEpisodesInRange, stale-reap, disabled-no-op); `releaseReadinessWiring.test.ts` (8 wiring-integrity — allow-list, repo-gate, override detection, auto-detect, state round-trip+corruption, all deps are real callables, guideBlocksPublish truth table).
|
|
26
|
+
- Integration (HTTP routes) + E2E (feature-alive: reproduce the original stall → one Attention item) land with the server-wiring commit, which is where they become meaningful (a route/E2E can't exist before the route does).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Side-Effects Review — Release-Readiness Visibility, PR-2c (Layer B wired live)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §§4.2 / 4.3.1 / 4.2.7 / 7 / 10.
|
|
4
|
+
**Scope of this commit:** make Layer B actually live — server construction (gated on an analyzable instar repo + config), the four HTTP routes, the off-by-default cron job, config defaults + migrations (config + CLAUDE.md), and the integration + real-I/O E2E that prove it. Plus the four new rollback/enable/snapshot/disabled methods on the sentinel that PR-2b's commit couldn't fully ship without the routes.
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
|
|
8
|
+
- **`src/commands/server.ts`** — constructs `ReleaseReadinessSentinel` IFF `config.monitoring.releaseReadiness.enabled` AND `isAnalyzableRepo(repoPath)` (default repoPath = process.cwd()). NOT `start()`ed — the off-by-default cron job is the cadence (one tick per cron). On an inert install (no instar repo or feature off) the sentinel is null and the routes 503 — never a spurious signal.
|
|
9
|
+
- **`src/server/AgentServer.ts` + `src/server/routes.ts`** — `releaseReadinessSentinel` plumbed through `AgentServerOptions` → `RouteContext`. Four routes added: `GET /release-readiness` (read-only snapshot + `X-Readiness-Source` header), `POST /release-readiness/tick` (the cron entry point), `POST /release-readiness/rollback` (loud — raises a HIGH Attention item + audits, can't silently mute the alarm; iter-3 V5), `POST /release-readiness/enable` (re-arm).
|
|
10
|
+
- **`src/monitoring/ReleaseReadinessSentinel.ts`** — added `disabled` to `ReadinessState`, `tick()` no-ops when disabled (writes a `tick-skipped-disabled` audit), and added `snapshot()` / `rollback({sessionId, sourceIp})` / `enable()` methods. Rollback resolves all open episodes, persists the `rollbackHistory` audit, and posts the HIGH "alarm disabled" Attention item before saving state.
|
|
11
|
+
- **`src/monitoring/releaseReadinessWiring.ts`** — **production-bug fix discovered by the E2E**: the spec's `--depth=1` fetch SHALLOWS the local repo, which breaks `git log <tag>..ref` in `analyze-release.js` (commit count silently becomes 0 → the sentinel goes silent on a real backlog — the exact failure mode the spec exists to fix). Dropped `--depth=1`; `--no-tags --no-recurse-submodules` keeps incremental fetches bounded without shallowing. **This is the kind of bug only the E2E could catch — unit tests with stubbed deps would have passed.**
|
|
12
|
+
- **`src/core/types.ts`** — `MonitoringConfig.releaseReadiness` block (kill switch + thresholds + hysteresis + TTL + fetch timeout + canonical-remote + repoPath overrides).
|
|
13
|
+
- **`src/config/ConfigDefaults.ts`** — default block under `monitoring.releaseReadiness` (ships OFF). Existing agents pick it up automatically: `applyDefaults` adds missing keys, never overwrites — migration parity is automatic.
|
|
14
|
+
- **`src/core/PostUpdateMigrator.ts`** — `migrateClaudeMd` appends a Release Readiness section to existing agents' CLAUDE.md, content-sniffed on the `/release-readiness` marker (Agent Awareness Standard).
|
|
15
|
+
- **`src/scaffold/templates.ts`** — new agents get the same paragraph in the rendered CLAUDE.md.
|
|
16
|
+
- **`src/scaffold/templates/jobs/instar/release-readiness-check.md`** — declarative cron job, `enabled: false`, schedule `0 */6 * * *`, model `haiku`. Body curls `POST /release-readiness/tick`; reports nothing (the sentinel owns all signalling).
|
|
17
|
+
|
|
18
|
+
## Side-effects analysis
|
|
19
|
+
|
|
20
|
+
**Reach.** All new code is gated. The sentinel is constructed only when (a) config enables it AND (b) `isAnalyzableRepo(repoPath)` returns true. On an npm-installed agent with no instar repo, the sentinel stays null and the routes 503 — zero behavior change. The job ships `enabled: false`. The CLAUDE.md migration is content-sniffed (idempotent).
|
|
21
|
+
|
|
22
|
+
**Rollback semantics (iter-3 V5).** `POST /release-readiness/rollback` cannot be a silent kill: it ALWAYS raises a HIGH-priority Attention item ("Release-readiness alarm disabled by session X at T"), writes a `rollbackHistory[]` entry to the readiness state, and emits a `rollback` audit line. A compromised/confused session calling rollback is loudly visible. `enable` re-arms cleanly.
|
|
23
|
+
|
|
24
|
+
**Production bug caught.** The `--depth=1` shallow-fetch bug (now fixed) would have silently broken the watchdog on every install after the first fetch — the alarm would have been *quiet for the exact reason it was built*. Caught by the real-I/O E2E (fixture git repo + real `analyze-release.js` subprocess + real fetch). Without that test, we would have shipped the failure mode the spec exists to fix.
|
|
25
|
+
|
|
26
|
+
**Sentinel-vs-cadence design.** Spec §4.2.1 said "host: a new declarative job." The class also exposes `start()`/`stop()` (self-ticking via setInterval) for tests + alt wiring. The SERVER does NOT call `start()` — the cron job is the single cadence driver, calling `POST /release-readiness/tick`. This avoids double-ticking and gives operators full control via the job's `enabled` flag.
|
|
27
|
+
|
|
28
|
+
**Migration parity.** Config defaults: handled automatically by `applyDefaults` (existing agents get the missing `monitoring.releaseReadiness` block on next migration). CLAUDE.md: explicit `migrateClaudeMd` block (Agent Awareness). Job template: shipped via the existing `InstallBuiltinJobs` always-overwrite path (operator `enabled` override is preserved per-slug). All three migration-parity standard surfaces covered.
|
|
29
|
+
|
|
30
|
+
**Rollback if reverted.** Reverting this PR removes the sentinel construction, routes, job template, config block, and CLAUDE.md mention. On existing agents post-revert: the config key + CLAUDE.md section become orphaned but harmless (no code references them). The state file `.instar/state/release-readiness.json` is left in place (cheap, ignored).
|
|
31
|
+
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
Full layered coverage, all green (47 tests across 7 files this PR):
|
|
35
|
+
- **Unit (40):** `analyze-release-ref-flag` (4), `analyze-release-draft-guide` (8), `upgrade-guide-autodraft-review` (7 — marker blocks, hash-locked receipts pass/block on tamper/age/missing-hash), `ReleaseReadinessSentinel` (13 — thresholds, dedupe, escalation, decoupled-from-guide, auto-resolve, fail-loud, hysteresis, resolveEpisodesInRange, stale-reap), `releaseReadinessWiring` (8 — allow-list, repo-gate, override detection, state round-trip, deps-are-real, guide-blocks truth table).
|
|
36
|
+
- **Integration (5):** real routes + real sentinel + controllable deps. Proves: route is 200 not 503 (alive), 503 when sentinel null (correctly inert), blocked+aged backlog → exactly one Attention signal (the original silent-stall bug reproduced + surfaced), clean backlog → no signal, rollback is loud and reversible.
|
|
37
|
+
- **E2E (2):** real `fixture` instar repo + real canonical remote + REAL git fetch + REAL `analyze-release.js` subprocess + REAL `merge-base`. Drives `sentinel.tick()` end-to-end and proves the wiring works against real I/O. Caught the `--depth=1` shallowing bug.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Side-Effects Review — Release-Readiness Visibility, PR-3 (Layer C canonical-ref scan)
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/RELEASE-READINESS-VISIBILITY-SPEC.md §4.3 (converged + approved).
|
|
4
|
+
**Scope:** the third and final PR of the spec — fix the second silent self-driving loop. The `FeatureRolloutReconciler`'s spec scanner today reads the LOCAL working tree, so a freshly-merged spec that isn't on the developer's current branch (or whose trace receipts have been cleaned up) is invisible — the reconciler silently skips exactly the newest work. This PR adds a canonical-ref scan that reads `docs/specs/` and `.instar/instar-dev-traces/` from canonical `main` directly. Feature-flagged off; falls back to the local scan on any failure with a single degradation event; never throws into boot.
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
|
|
8
|
+
- **`src/core/featureRolloutScan.ts`** — new exports:
|
|
9
|
+
- `scanSpecArtifactsCanonical(opts)` — sync canonical scan. Bounded fetch (`--no-tags --no-recurse-submodules`, no `--depth` — matches Layer B's --depth=1 lesson that shallowing the local repo breaks downstream git operations). Enumerates spec + trace blobs via `git ls-tree` and reads contents via `git show <sha>:<path>`. Returns `SpecArtifact[]` with `merged: true` by construction (a spec on main IS merged — the local scan's inferred `approved && traceExists` shortcut is replaced with real ancestry).
|
|
10
|
+
- `scanSpecArtifactsWithCanonical(repoRoot, opts)` — the repo-gated, feature-flagged wrapper. When `canonicalRefScanEnabled` is false → calls the original local scanner. When the flag is on but `canonicalRemote` is missing → degradation + local fallback. When the canonical scan throws → degradation + local fallback. Boot-safe by construction.
|
|
11
|
+
- **`src/core/types.ts`** — new top-level `featureRollout` config block (`canonicalRefScan?: boolean` + `canonicalRemote?: string` + `fetchTimeoutMs?: number`). Default off.
|
|
12
|
+
- **`src/commands/server.ts`** — `FeatureRolloutReconciler` now constructs its `listSpecArtifacts` via the gated wrapper, passing config + an `onDegradation` log sink. With the flag off (the default) and the wrapper trivially routing to the local scan, behavior on every existing install is byte-identical.
|
|
13
|
+
|
|
14
|
+
## Side-effects analysis
|
|
15
|
+
|
|
16
|
+
**Reach.** Default off → every existing install runs the exact local scan from before, unchanged. New code paths only activate when an operator flips `featureRollout.canonicalRefScan: true`. The reconciler's call shape is unchanged (still sync, still returns `SpecArtifact[]`).
|
|
17
|
+
|
|
18
|
+
**The bug being fixed (proved by test).** Layer C's signature unit test: write an approved spec, commit + push to a canonical bare remote, then DELETE the file from the local working tree. The original local scanner doesn't see it (the file is gone). `scanSpecArtifactsWithCanonical` with the flag on + canonical remote configured DOES see it — `merged: true` by construction. That is the exact failure mode this layer exists to fix.
|
|
19
|
+
|
|
20
|
+
**Security / input safety.** All git through `SafeGitExecutor.run` (execFileSync, no shell). The canonical-remote allow-list lives on the Layer B side (release-readiness wiring) — Layer C accepts the remote NAME (already validated upstream) and uses it as a single argv token to `git fetch`. Trace JSON is parsed with a `try/catch` per blob; a malformed trace is skipped, never crashes the scan.
|
|
21
|
+
|
|
22
|
+
**Fail-loud / fail-safe.** Every failure path (no flag, no remote, scan throws) routes through `onDegradation(reason)` once per scan with a structured reason string, then returns the local-scan fallback. The reconciler's sync contract is preserved; the function NEVER throws into the boot path or the every-6h reconcile timer.
|
|
23
|
+
|
|
24
|
+
**Cross-PR alignment.** The Layer B production-bug fix from PR-2 (drop `--depth=1`) was carried through here verbatim. The `featureRollout.canonicalRemote` setting is INDEPENDENT of `releaseReadiness.canonicalRemote` — Layer C doesn't depend on PR-2 having landed; if both ship, an operator can either configure one for both or set them independently.
|
|
25
|
+
|
|
26
|
+
**Spec deferrals (per spec §4.3.3).** The skip-signal escalation (K=1 first tick then K=3, degradation → low-priority Attention after K consecutive deserved-skips) and the per-tick `lastProcessedCommit` cursor are mentioned in the spec but are NOT in this commit's scope. The canonical scan as-shipped already removes the silent-skip ROOT cause (the scan now sees what main has, not what the local tree has). The skip-signal + cursor are an additional observability enhancement on top — tracked here for a follow-up: <!-- tracked: release-readiness-visibility-pr3-followups -->. The follow-up does not block this PR's correctness — Layer C as-shipped is a self-contained improvement.
|
|
27
|
+
|
|
28
|
+
**Rollback.** Revert removes the canonical-scan path; the reconciler returns to using the local-only scanner. The `featureRollout` config key becomes orphaned but harmless.
|
|
29
|
+
|
|
30
|
+
## Testing
|
|
31
|
+
|
|
32
|
+
- **Unit (5):** flag off → local fallback; flag on + no remote → degradation + local fallback; **the headline test — a spec deleted from the local tree but committed to main is still detected**; trace + spec joined by `specPath` (canonical-only); bad remote → ONE degradation + local fallback.
|
|
33
|
+
- All run against real fixture git repos with `SafeGitExecutor`; no stubs of git. Layer C's real-I/O coverage is built into the unit tests themselves (the wrapper has trivial branching; the canonical scan IS the I/O).
|