instar 1.3.747 → 1.3.748
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/PostUpdateMigrator.d.ts +16 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +64 -5
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +16 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/conversationBindGate.d.ts +62 -0
- package/dist/core/conversationBindGate.d.ts.map +1 -0
- package/dist/core/conversationBindGate.js +83 -0
- package/dist/core/conversationBindGate.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
- package/dist/monitoring/CommitmentTracker.js +4 -1
- package/dist/monitoring/CommitmentTracker.js.map +1 -1
- package/dist/scaffold/templates.js +1 -1
- package/dist/server/routes.d.ts +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +129 -88
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +1 -1
- package/upgrades/{1.3.747.md → 1.3.748.md} +16 -0
- package/upgrades/side-effects/action-claim-dryrun-audit-mkdir-fix.md +45 -0
- package/upgrades/side-effects/slack-followthrough-generalization.md +49 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Side-effects review — action-claim dryRun audit mkdir fix
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
|
|
5
|
+
`src/server/routes.ts`, `POST /action-claim/observe` dry-run branch (minted-id
|
|
6
|
+
Slack lane, §8.1): add `fs.mkdirSync(path.dirname(logPath), { recursive: true })`
|
|
7
|
+
immediately before the best-effort `fs.appendFileSync` that writes the
|
|
8
|
+
`would-register` audit line to `logs/action-claim-observe.jsonl`.
|
|
9
|
+
|
|
10
|
+
## Files touched
|
|
11
|
+
|
|
12
|
+
- `src/server/routes.ts` (one added line inside the existing dry-run `try` block)
|
|
13
|
+
|
|
14
|
+
## Side-effects analysis
|
|
15
|
+
|
|
16
|
+
- **Filesystem**: now creates the `logs/` directory if absent (idempotent,
|
|
17
|
+
`recursive: true`). Previously the append silently failed via the swallowing
|
|
18
|
+
`catch` when the directory did not exist. No new files beyond the audit log
|
|
19
|
+
the feature already intended to write.
|
|
20
|
+
- **Response contract**: unchanged. The route returns the identical
|
|
21
|
+
`{ observed: true, registered: false, dryRun: true, wouldRegister: true, ... }`
|
|
22
|
+
body. No status-code change; never-500 guarantee preserved (the `mkdirSync`
|
|
23
|
+
is inside the same `try/catch`).
|
|
24
|
+
- **Live path**: untouched — the non-dry-run branch calls `record()` and never
|
|
25
|
+
reaches this code.
|
|
26
|
+
- **Migration / config / hooks**: none. No config keys, no settings, no CLAUDE.md
|
|
27
|
+
template surface, no operator-facing change.
|
|
28
|
+
- **Security**: none. Directory is created under the agent's own state tree with
|
|
29
|
+
default permissions, same as the sibling audit writers.
|
|
30
|
+
- **Concurrency**: `mkdirSync(..., { recursive: true })` is safe to call
|
|
31
|
+
repeatedly and from concurrent requests (no-op when the dir exists).
|
|
32
|
+
|
|
33
|
+
## Test impact
|
|
34
|
+
|
|
35
|
+
Fixes `tests/integration/action-claim-route.test.ts` → "dryRun → would-register
|
|
36
|
+
audit line, NO row", which failed in clean CI environments because
|
|
37
|
+
`<os-tmpdir>/logs/` did not pre-exist. No test was modified or skipped; the code
|
|
38
|
+
was fixed to satisfy the existing assertion.
|
|
39
|
+
|
|
40
|
+
## Causal autopsy
|
|
41
|
+
|
|
42
|
+
Origin: new-code. The dry-run audit-write branch was introduced by this same PR
|
|
43
|
+
(slack-followthrough-generalization) and shipped with the missing `mkdirSync`.
|
|
44
|
+
The failure is a latent bug in that new code, masked locally by a leaked
|
|
45
|
+
`logs/` directory in the OS temp dir.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Side-Effects Review — Slack Follow-Through Generalization (Phase 2.3)
|
|
2
|
+
|
|
3
|
+
Spec: `docs/specs/slack-followthrough-generalization.md` (converged + approved). ELI16: `docs/specs/slack-followthrough-generalization.eli16.md`.
|
|
4
|
+
|
|
5
|
+
**What shipped:** the registration TRIGGER that makes a promise born in a Slack conversation register a durable commitment (bound to the conversation's minted id), so the already-live increment-2 PromiseBeacon + `deliverToConversation` funnel carry the follow-through across restarts — the way Telegram already works. Six code seams: (1) `detectTimePromise` hedge fix; (2) a shared `verifyConversationBind` §7 helper (refactor — `POST /commitments` now calls it, behavior-identical, golden test green); (3) `/action-claim/observe` gains the negative-id bind gate + slack dev-gate/dryRun read + Lane-A precedence + Lane-B time-promise predicate + both-prefix cap + dryRun audit sink; (4) `INSTAR_CONVERSATION_ID` spawn env with the 1:1 lifeline guard; (5) the generalized Stop hook (INSTAR_CONVERSATION_ID-only, bind-token header, no ≥20-char floor, 16KB clamp); (6) `messaging.actionClaim.slack` dev-gate registration + array-safe migration + CLAUDE.md template (both scaffold + migrator).
|
|
6
|
+
|
|
7
|
+
## Signal-vs-authority (Phase 1 answer)
|
|
8
|
+
**Does this involve a decision point?** YES — two. (a) The **registration trigger** is a DECISION to open durable state, but it is **SIGNAL-ONLY**: it runs at turn end, AFTER the Slack reply already went out; it can never block/delay/rewrite a message. It produces a durable commitment (a signal the existing beacon consumes). (b) The **bind gate** is a real blocking AUTHORITY, but it is NOT new brittle logic — it REUSES the deployed §7 `verifyConversationBind` (MAC-verified token, deterministic), factored into one helper both routes share. No brittle check gained blocking authority. Compliant.
|
|
9
|
+
|
|
10
|
+
## The 8 questions
|
|
11
|
+
|
|
12
|
+
1. **Over-block (rejects legitimate input it shouldn't?)** — The bind gate refuses a minted-id registration whose token is missing/invalid/foreign. The only "legitimate" caller of a minted id is the owning Slack session (its spawn-env token) — a refusal there means the caller genuinely lacks authority (fail-closed by design). A refusal is signal-only (the hook ignores the response) → a refused registration is an untracked promise (status-quo), never a user-visible error. No legitimate message is blocked (registration is post-send).
|
|
13
|
+
|
|
14
|
+
2. **Under-block (failure modes still missed?)** — By design (accepted recall trade, precision-over-recall): (a) Slack DMs folded into the shared `lifeline` session are deliberately untracked (they carry no `INSTAR_CONVERSATION_ID`); (b) a promise phrased outside the two deterministic classifiers; (c) a promise buried past the 16KB hook clamp; (d) a legacy dedicated session spawned before this change (no `INSTAR_CONVERSATION_ID`) until it respawns. All are safe misses (untracked = status quo), never mis-delivery. §10-Q1/Q5 track recall follow-ups.
|
|
15
|
+
|
|
16
|
+
3. **Level-of-abstraction fit** — Correct layer: the responding session's own post-turn Stop hook (generalizes the Telegram model exactly), server-side classify+record+bind on the owning machine, delivery on the existing funnel. Alternatives (in-process send-chokepoint classifier; the `commitment-detection` job) were considered and rejected in spec §11 — they add new machinery and don't generalize the Telegram model. No smarter gate is bypassed.
|
|
17
|
+
|
|
18
|
+
4. **Signal vs authority compliance** — Yes (see above). The trigger is signal-only; the authority is the reused deterministic §7 gate. `docs/signal-vs-authority.md` honored.
|
|
19
|
+
|
|
20
|
+
5. **Interactions (shadow / shadowed / double-fire / race)** — (a) **Lane A precedence** prevents double-registration: a dual-signal turn ("I'll deploy in 10 min") registers ONE actionclaim row (integration-tested). (b) **Shared cap** now counts BOTH `actionclaim:` + `timepromise:` prefixes against one budget (was actionclaim-only) — a Lane-B row can no longer escape the cap. (c) The `POST /commitments` refactor is behavior-identical (the §7 golden test `conversation-registry-routes.test.ts` passes unchanged). (d) Lane B relies on `record()`'s INTERNAL auto-arm; the route runs `detectTimePromise` on the SAME `message.slice(0,500)` `record()` sees, so "predicate fired" ⟺ "beacon armed" — no divergence. (e) The generalized hook is shared with the Telegram path; adding Lane B improves Telegram time-promise recall once the master flag graduates (spec §8.4, intended).
|
|
21
|
+
|
|
22
|
+
6. **External surfaces** — (a) A registered commitment's PromiseBeacon heartbeat is delivered into the user's Slack thread — but ONLY when `conversationIdentity.followThrough` graduates (delivery gate, separate); until then the §6.1 dark-window attention item marks it undeliverable (existing machinery). (b) The hook posts a bind-token header now (all sessions) — validated by the shared helper; a token-bearing positive (Telegram) id rides the existing R6-minor-4 arm (a strict improvement, no regression). (c) A new best-effort `logs/action-claim-observe.jsonl` dryRun audit file. Timing/state: the whole feature is dev-gated dark (`messaging.actionClaim.slack`) AND requires the master `messaging.actionClaim.enabled` — dark on the fleet.
|
|
23
|
+
|
|
24
|
+
7. **Multi-machine posture (Cross-Machine Coherence)** — **Machine-local BY DESIGN, structurally.** Registration happens on the machine that OWNS the minted conversation (the Slack-fronting Mini): the Slack session spawns there, its Stop hook posts to that machine's localhost, its bind token verifies only against that machine's secret (a peer would 403 — different secret), and `record()`'s binder resolves the minted id against the LOCAL registry (a peer throws `conversation-bind-unresolvable` → 409). Write-admission refuses a minted-id write on a non-owner. A conversation that later moves machines carries its denormalized `boundTuple`; PromiseBeacon re-resolves the live owner at speak time (increment-2 machinery, unchanged). No new replication path needed — registration correctly follows the owner.
|
|
25
|
+
|
|
26
|
+
8. **Rollback cost** — Trivial, no data migration. Kill switches (read live, no restart): `messaging.actionClaim.slack.dryRun:true` → observe-only; `messaging.actionClaim.enabled:false` (fleet default) → the hook `exit(0)`s before any POST. The `POST /commitments` refactor is behavior-identical (revertible in isolation). No durable schema change (commitments are existing rows; the only new externalKey prefix is `timepromise:`, which auto-expires). Back-out = revert the branch or flip the flag.
|
|
27
|
+
|
|
28
|
+
## Reconciliation notes (grounded deviations from the spec, all documented)
|
|
29
|
+
- **No `messaging.actionClaim.slack.dryRun` config write (spec §8.5 asked for one).** Real installs have `messaging` as an ARRAY of adapter configs, so `messaging.actionClaim.slack.enabled` resolves `undefined` — exactly what `resolveDevAgentGate` wants (live-on-dev/dark-fleet) — and the route defaults `dryRun` to `true` when absent. Correct dev-gated-dryRun-first behavior is delivered with ZERO config write; writing a dotted key into an array would corrupt it. Migration parity is satisfied by construction; the added `migrateConfigActionClaimSlackDevGate` is a defensive, array-safe #1001 strip (a no-op on the array shape).
|
|
30
|
+
- **Inherited (not introduced) risk:** the action-claim sentinel reads its master flag at `messaging.actionClaim.enabled`, but real `messaging` is an array — so the master flag is enabled via an object-shaped override (as the tests do). This is the EXISTING sentinel's config path, unchanged by this build; flagged for operator awareness before the dev soak.
|
|
31
|
+
- **Keystone doc-fix owed (§10-Q6):** durable-conversation-identity's minor-3 mislabels "the action-claim observer" as in-process; the reconciliation is in spec §4.3. A one-line keystone edit is owed when the build PR touches the shared helper.
|
|
32
|
+
|
|
33
|
+
## Tests (all three tiers, both sides of every boundary)
|
|
34
|
+
- Unit: `detect-time-promise-hedge` (9), `conversation-bind-gate` (9, all arms), `action-claim-hook-slack` (7, hook semantics), `migrate-actionclaim-slack-devgate` (5).
|
|
35
|
+
- Integration: `action-claim-route` Slack lane (7 new: register/bind, no-token 403+attention, foreign 403, lane precedence single-row, shared cap both-prefix, dryRun audit, slack-lane-dark, positive-unaffected).
|
|
36
|
+
- E2E: `action-claim-lifecycle` Slack minted-id feature-ALIVE (bound + beacon-armed) + no-token-403.
|
|
37
|
+
- Regression: §7 golden test (`conversation-registry-routes`) green (refactor parity); `generated-hooks-parse` green (hook still parses); `devGatedFeatures-wiring` (145) green (new gate entry conforms). tsc clean, lint clean.
|
|
38
|
+
|
|
39
|
+
## Phase 5 — Second-pass review (independent reviewer)
|
|
40
|
+
|
|
41
|
+
An independent reviewer audited all five high-risk areas against the ACTUAL code (not the prose):
|
|
42
|
+
1. **Cross-channel mis-delivery** — clean: the hook keys ONLY on `INSTAR_CONVERSATION_ID` (no Telegram fallback; the other `INSTAR_TELEGRAM_TOPIC` refs belong to *different* hooks); `SessionManager.ts` injects the env only when `name !== 'lifeline'`, so a Slack DM folded into `lifeline` carries no env → registers nothing (safe miss).
|
|
43
|
+
2. **Signal vs authority** — registration is post-send (`exit(0)` always); the bind gate faithfully reproduces the old inline §7 branches (skip / minted-fail-closed / positive-token / legacy-fail-open + straggler), identical 403 shape. `POST /commitments` refactor behavior-identical.
|
|
44
|
+
3. **Double-registration / cap escape** — Lane A returns before Lane B (one row for "I'll deploy in 10 min"); cap counts both prefixes.
|
|
45
|
+
4. **Beacon arming** — Lane B passes no beacon fields; auto-arm fires (`beaconEnabled===undefined`); route runs `detectTimePromise` on the same `slice(0,500)`.
|
|
46
|
+
5. **Fail-closed on minted ids** — negative id requires a valid in-set token; the gate runs before the dryRun return AND `record()`.
|
|
47
|
+
Also confirmed the hedge regex uses a NON-capturing group so `numeric[1]/[2]` indices are unshifted.
|
|
48
|
+
|
|
49
|
+
**Verdict: Concur with the review.**
|