instar 1.3.763 → 1.3.764
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.map +1 -1
- package/dist/core/PostUpdateMigrator.js +7 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/scaffold/templates.js +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +15 -5
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +1 -1
- package/upgrades/1.3.764.md +45 -0
- package/upgrades/side-effects/actionclaim-config-shape-fix.eli16.md +50 -0
- package/upgrades/side-effects/actionclaim-config-shape-fix.md +149 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ELI16 — Why the "I'll do that in 5 minutes" tracker couldn't be turned on
|
|
2
|
+
|
|
3
|
+
## The one-sentence version
|
|
4
|
+
|
|
5
|
+
There's a safety feature that watches for promises the agent makes ("I'll restart it", "I'll post
|
|
6
|
+
that in about 5 minutes") and quietly writes them down so they survive a restart — and it turned out
|
|
7
|
+
you literally could not switch it on. This change fixes the switch.
|
|
8
|
+
|
|
9
|
+
## What was broken
|
|
10
|
+
|
|
11
|
+
Think of the config file as a form. Most settings live in labeled boxes you can fill in. The switch
|
|
12
|
+
for this promise-tracker was written as "fill in the box `messaging → actionClaim → enabled`."
|
|
13
|
+
|
|
14
|
+
The problem: `messaging` isn't a box that holds other boxes. It's a **list** — one row per chat
|
|
15
|
+
platform (Telegram, Slack, WhatsApp). You can't write "actionClaim" *inside a list*; there's nowhere
|
|
16
|
+
to put it. So when the program looked for `messaging → actionClaim → enabled`, it always found
|
|
17
|
+
nothing, and "nothing" means "off." No matter what you did, the feature stayed off.
|
|
18
|
+
|
|
19
|
+
Why nobody noticed for so long: every automated test filled the form out the *wrong* way — it made
|
|
20
|
+
`messaging` a box instead of a list, so in the tests the switch worked fine. But no real install
|
|
21
|
+
looks like that. Real installs always use the list. So the tests were green while the real thing was
|
|
22
|
+
un-turn-on-able. Two sibling features have the same quirk, but they default to **on**, so nobody ever
|
|
23
|
+
had to find the switch — this promise-tracker is the first one that defaults to **off** and actually
|
|
24
|
+
needs its switch to work.
|
|
25
|
+
|
|
26
|
+
## What this change does
|
|
27
|
+
|
|
28
|
+
It moves the switch to a box that actually exists: a **top-level `actionClaim`** setting, right
|
|
29
|
+
alongside the other real settings — not buried inside the platform list. The old (broken) location
|
|
30
|
+
is still honored if anyone used it, so nothing that already worked breaks.
|
|
31
|
+
|
|
32
|
+
Two places read that switch: the live server route that receives the promise, and a small "when a
|
|
33
|
+
turn finishes" hook script. Both now look in the new top-level spot first, and both are careful never
|
|
34
|
+
to trip over the list.
|
|
35
|
+
|
|
36
|
+
## Why it's safe
|
|
37
|
+
|
|
38
|
+
- The feature is *signal-only*: it just writes a note; it never blocks or changes a message. So there
|
|
39
|
+
is no risk of it wrongly rejecting something.
|
|
40
|
+
- It still ships **off by default** — this change only makes the "on" switch reachable, it doesn't
|
|
41
|
+
turn anything on.
|
|
42
|
+
- Old configs keep working (the old location is a fallback), so it's backwards compatible.
|
|
43
|
+
- If it's ever wrong, the fix is a plain code revert — there's no saved data to clean up.
|
|
44
|
+
|
|
45
|
+
## How we know it works now
|
|
46
|
+
|
|
47
|
+
A new test fills the form out the **real** way — `messaging` as a list — and flips the new top-level
|
|
48
|
+
switch, then checks that a promise like "I'll restart the server now" actually gets written down. It
|
|
49
|
+
fails without the fix and passes with it. Older tests (the ones using the box shape) still pass, so
|
|
50
|
+
the backwards-compatibility promise holds.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Side-Effects Review — Action-Claim sentinel enablable on array-shaped `messaging`
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `actionclaim-config-shape-fix`
|
|
4
|
+
**Date:** `2026-07-04`
|
|
5
|
+
**Author:** `Echo`
|
|
6
|
+
**Second-pass reviewer:** `not required (Tier-1)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The Action-Claim Follow-Through Sentinel (including the Slack-followthrough lane from #1361)
|
|
11
|
+
gated on the config path `messaging.actionClaim.enabled`. On every real install `messaging` is a
|
|
12
|
+
JSON **array** of adapter configs, so `messaging.actionClaim.*` is unreachable — `LiveConfig`'s
|
|
13
|
+
`getNestedValue` walks `messaging`, gets the array, evaluates `array['actionClaim']` → `undefined`,
|
|
14
|
+
and returns the `false` default. Because this sentinel defaults OFF, the master gate could never be
|
|
15
|
+
set true: the feature was **structurally un-enablable in production**. CI never caught it because
|
|
16
|
+
every test wrote an object-shaped `messaging`, which no real install uses. Fix: read the config from
|
|
17
|
+
a reachable **top-level `actionClaim`** block (canonical), honoring the legacy object-shaped
|
|
18
|
+
`messaging.actionClaim` as a back-compat fallback. Files: `src/server/routes.ts` (the
|
|
19
|
+
`/action-claim/observe` reads, via a small `acGet` live-read helper), `src/core/PostUpdateMigrator.ts`
|
|
20
|
+
(the generated Stop hook's raw-file resolution + the CLAUDE-template enable-key text),
|
|
21
|
+
`src/scaffold/templates.ts` (the scaffold CLAUDE-template enable-key text), plus tests.
|
|
22
|
+
|
|
23
|
+
## Decision-point inventory
|
|
24
|
+
|
|
25
|
+
- `POST /action-claim/observe` config reads (`src/server/routes.ts`) — **modify** — master `enabled`,
|
|
26
|
+
`slack.enabled` dev-gate value, `slack.dryRun`, `perTopicCap`, `expiresHours` now resolve
|
|
27
|
+
top-level-first via `acGet`.
|
|
28
|
+
- Generated `action-claim-followthrough.js` Stop hook (`PostUpdateMigrator.getActionClaimFollowthroughHook`)
|
|
29
|
+
— **modify** — the raw-file `enabled` resolution reads top-level `cfg.actionClaim` and Array-guards
|
|
30
|
+
the legacy `cfg.messaging.actionClaim` fallback.
|
|
31
|
+
- The `messaging.actionClaim.slack.enabled` dev-gate (`resolveDevAgentGate`) — **pass-through** — value
|
|
32
|
+
now sourced via `acGet` but the `undefined → live-on-dev, dark-fleet` semantics are unchanged.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 1. Over-block
|
|
37
|
+
|
|
38
|
+
No block/allow surface — over-block not applicable. The change only affects whether a
|
|
39
|
+
signal-only, never-blocking sentinel can be turned ON. An explicit top-level `actionClaim.enabled:false`
|
|
40
|
+
correctly keeps it off (covered by a test).
|
|
41
|
+
|
|
42
|
+
## 2. Under-block
|
|
43
|
+
|
|
44
|
+
No block/allow surface — under-block not applicable. The sentinel remains dark by default (absent
|
|
45
|
+
config → `false`); the fix does not change detection precision, only config reachability.
|
|
46
|
+
|
|
47
|
+
## 3. Level-of-abstraction fit
|
|
48
|
+
|
|
49
|
+
Right layer. This is a config-resolution fix at the exact read sites that were broken; it introduces
|
|
50
|
+
no new authority and reuses the existing `LiveConfig` getter and the raw-file read in the generated
|
|
51
|
+
hook. It does not re-implement config parsing — it adds a top-level-first fallback around the
|
|
52
|
+
existing getters.
|
|
53
|
+
|
|
54
|
+
## 4. Signal vs authority compliance
|
|
55
|
+
|
|
56
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
57
|
+
|
|
58
|
+
- [x] No — this change has no block/allow surface. It changes config reachability for a signal-only
|
|
59
|
+
sentinel (the Stop hook always `exit(0)`; the observe route never blocks a message). No brittle
|
|
60
|
+
logic gains blocking authority.
|
|
61
|
+
|
|
62
|
+
## 5. Interactions
|
|
63
|
+
|
|
64
|
+
- **Shadowing:** none. The `acGet` helper reads top-level first, then falls back to the old
|
|
65
|
+
`messaging.actionClaim` path with the same default — a superset of prior behavior. Object-shaped
|
|
66
|
+
configs (existing tests) resolve identically via the fallback.
|
|
67
|
+
- **Double-fire:** none. Single read path per field; no new emitter.
|
|
68
|
+
- **Races:** none. Reads are per-request via `LiveConfig` (live, no shared mutable state added).
|
|
69
|
+
- **Feedback loops:** none.
|
|
70
|
+
|
|
71
|
+
## 6. External surfaces
|
|
72
|
+
|
|
73
|
+
- **Other agents / install base:** the generated Stop hook is regenerated on every migration
|
|
74
|
+
(`instar/` hooks are always-overwritten), so existing agents pick up the array-safe resolution on
|
|
75
|
+
their next update — no per-agent config migration required (a fresh top-level `actionClaim` enable
|
|
76
|
+
is purely additive).
|
|
77
|
+
- **External systems:** none changed. Slack/Telegram delivery paths untouched.
|
|
78
|
+
- **Persistent state:** none. No new ledger/column/file.
|
|
79
|
+
- **Operator surface:** no operator-facing action added — enabling is a config edit (dev-first soak),
|
|
80
|
+
same as before, now at a reachable key. "No operator-facing actions" — the CLAUDE-template text was
|
|
81
|
+
corrected so agents point operators at the reachable `actionClaim.enabled` key.
|
|
82
|
+
|
|
83
|
+
## 6b. Operator-surface quality
|
|
84
|
+
|
|
85
|
+
No operator surface — not applicable. No dashboard renderer, approval page, or grant/secret form is
|
|
86
|
+
touched.
|
|
87
|
+
|
|
88
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
89
|
+
|
|
90
|
+
**machine-local BY DESIGN.** Config (`.instar/config.json`) is per-machine, and the Action-Claim
|
|
91
|
+
sentinel already runs per-machine on the machine that owns the responding session (it registers under
|
|
92
|
+
that session's own bind token — slack-followthrough-generalization §4.5). This change only alters
|
|
93
|
+
*where in the local config file* the enable flag is read; it introduces no cross-machine state, no
|
|
94
|
+
user-facing notice, no durable state, and no URLs. Enabling it on one machine does not and should not
|
|
95
|
+
implicitly enable it on another — each machine's config is authoritative for its own sessions.
|
|
96
|
+
|
|
97
|
+
## 8. Rollback cost
|
|
98
|
+
|
|
99
|
+
Pure code change — revert and ship as a patch. No persistent state, no data migration, no user-visible
|
|
100
|
+
regression during the rollback window. The generated hook is regenerated on the next migration in
|
|
101
|
+
either direction. A deployed agent that had set top-level `actionClaim.enabled:true` would, on
|
|
102
|
+
rollback, simply return to un-enablable (the pre-fix state) — no cleanup needed.
|
|
103
|
+
|
|
104
|
+
## Conclusion
|
|
105
|
+
|
|
106
|
+
The review confirms a low-risk, additive config-resolution fix that makes a previously un-enablable
|
|
107
|
+
default-off sentinel actually turnable-on on real (array-`messaging`) installs, with the legacy
|
|
108
|
+
object-shape fully back-compatible. It adds the missing test coverage (array-shaped `messaging`) that
|
|
109
|
+
would have caught the original bug. Clear to ship. Follow-up worth tracking: the sibling default-ON
|
|
110
|
+
`messaging.*` sentinels (`toneGate`, `outboundAdvisory`) share the same latent unreachability but are
|
|
111
|
+
masked by their on-by-default posture — noted here, not fixed in this change (out of scope).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Second-pass review (if required)
|
|
116
|
+
|
|
117
|
+
**Reviewer:** not required (Tier-1)
|
|
118
|
+
**Independent read of the artifact: n/a**
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Evidence pointers
|
|
123
|
+
|
|
124
|
+
- `tests/integration/action-claim-config-shape.test.ts` — array-shaped `messaging` + top-level
|
|
125
|
+
`actionClaim`: enablement, tuning-knob read, explicit-false off-switch, object-shape back-compat.
|
|
126
|
+
- `tests/unit/action-claim-hook-slack.test.ts` — hook-body array-safe resolution assertion.
|
|
127
|
+
- Existing `tests/integration/action-claim-route.test.ts` (14) + `tests/e2e/action-claim-lifecycle.test.ts`
|
|
128
|
+
(4) + `tests/unit/generated-hooks-parse.test.ts` (25) + `tests/unit/migrate-actionclaim-slack-devgate.test.ts`
|
|
129
|
+
(5) all stay green; `tsc --noEmit` clean.
|
|
130
|
+
- `docs/investigations/s7-slack-delivery-repro-2026-07-04.md` — the S7 keystone investigation that
|
|
131
|
+
surfaced this bug (the merged #1361 fix could not be turned on).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Class-Closure Declaration (display-only mirror)
|
|
136
|
+
|
|
137
|
+
- **`defectClass`** — `config-unreachable-on-shape` (`novel`; nearestExistingClass:
|
|
138
|
+
`feature-un-enablable`; includes: a config gate whose dot-path is unreachable given the container's
|
|
139
|
+
real runtime shape, e.g. a key nested under an array-valued parent; excludes: a feature disabled by a
|
|
140
|
+
correctly-read explicit flag). Enters `status: "unconfirmed"` pending operator confirmation, so this
|
|
141
|
+
fix carries `closure: gap` (below), not `closure: guard`.
|
|
142
|
+
- **`closure`** — `gap` — the class-level guard (a lint that flags a `messaging.<x>` dot-path read when
|
|
143
|
+
`messaging` is array-shaped in the shipped config, or a shape-fuzzing test harness for config gates)
|
|
144
|
+
is out of scope for this fix.
|
|
145
|
+
- **`guardEvidence`** — n/a for `closure: gap`.
|
|
146
|
+
- **`gap`** — tracked as a standards-gap follow-up: "config-gate shape-reachability lint/fuzz — a
|
|
147
|
+
default-off feature gated on an unreachable dot-path is un-enablable; add a guard that fails when a
|
|
148
|
+
gate reads `messaging.<child>.*` given `messaging` ships as an array." (This change ships the direct
|
|
149
|
+
regression test for THIS feature; the class-level guard is the tracked gap.)
|