instar 1.3.765 → 1.3.766

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.765",
3
+ "version": "1.3.766",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "test:contract": "node scripts/run-contract-tests.js",
29
29
  "test:contract:raw": "vitest run --config vitest.contract.config.ts",
30
30
  "test:all": "vitest run && vitest run --config vitest.integration.config.ts && vitest run --config vitest.e2e.config.ts",
31
- "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-no-unbounded-llm-spawn.js && node scripts/lint-no-unregistered-self-action.js && node scripts/lint-no-unfunneled-credential-write.js && node scripts/lint-state-registry.js && node scripts/lint-store-retention-declared.js && node scripts/lint-no-wholefile-sync-read.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-sync-subprocess-chokepoint.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/lint-no-mainthread-cartographer-walk.js && node scripts/lint-scrape-fixture-realness.js && node scripts/check-codex-rule1-drift.js && node scripts/lint-routing-registry-freshness.js && node scripts/lint-no-opus-claude-cli-gating.js && node scripts/lint-model-registry-freshness.mjs",
31
+ "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-no-unbounded-llm-spawn.js && node scripts/lint-no-unregistered-self-action.js && node scripts/lint-no-unfunneled-credential-write.js && node scripts/lint-state-registry.js && node scripts/lint-store-retention-declared.js && node scripts/lint-no-wholefile-sync-read.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-sync-subprocess-chokepoint.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/lint-no-mainthread-cartographer-walk.js && node scripts/lint-scrape-fixture-realness.js && node scripts/check-codex-rule1-drift.js && node scripts/lint-routing-registry-freshness.js && node scripts/lint-no-opus-claude-cli-gating.js && node scripts/lint-model-registry-freshness.mjs && node scripts/lint-no-unreachable-messaging-gate.js",
32
32
  "lint:routing-registry": "node scripts/lint-routing-registry-freshness.js",
33
33
  "lint:no-opus-claude-cli-gating": "node scripts/lint-no-opus-claude-cli-gating.js",
34
34
  "lint:model-freshness": "node scripts/lint-model-registry-freshness.mjs",
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lint-no-unreachable-messaging-gate.js — ban a DEFAULT-OFF config gate read at a
4
+ * `messaging.<child>.*` dot-path.
5
+ *
6
+ * The 2026-07-04 incident (PR #1379): the Action-Claim / Slack-followthrough
7
+ * sentinel gated on `messaging.actionClaim.enabled`, read as
8
+ * `liveConfig.get('messaging.actionClaim.enabled', false)`. On every real install
9
+ * `messaging` is a JSON ARRAY of adapter configs, so `getNestedValue` walks the
10
+ * array, `array['actionClaim']` is undefined, and it returns the `false` default.
11
+ * Because the feature defaults OFF, the master switch could never be set true — the
12
+ * feature was structurally UN-ENABLABLE in production. CI missed it because every
13
+ * test used an OBJECT-shaped `messaging`, which no real install uses.
14
+ *
15
+ * The sibling default-ON `messaging.*` sentinels (toneGate, outboundAdvisory) share
16
+ * the same unreachability but are masked — unreachable just means they stay ON. Only
17
+ * a DEFAULT-OFF gate is un-enablable. So this lint flags exactly the un-enablable
18
+ * shape: a `.get('messaging.<...>', false)` (a dot-path under the array-valued
19
+ * `messaging`, with a `false` default). The fix is to read the config from a
20
+ * reachable TOP-LEVEL key (e.g. `actionClaim`), as #1379 did.
21
+ *
22
+ * Conservative by design: it flags only the concrete `.get('messaging.*', false)`
23
+ * shape (the primary LiveConfig gate-read), not every messaging.* access. A
24
+ * genuinely-intended exception can be suppressed inline with
25
+ * // lint-allow-messaging-gate: <reason>
26
+ * on the same or the immediately-preceding line.
27
+ *
28
+ * Exit 0 = clean. Exit 1 = at least one offending site (printed).
29
+ */
30
+
31
+ import fs from 'node:fs';
32
+ import path from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+
35
+ /**
36
+ * `.get('messaging.<child>...', false)` — optionally with a `<T>` generic and any
37
+ * whitespace. The `false` default is what makes it un-enablable (an unreachable
38
+ * default-true gate merely stays on). Matches both quote styles.
39
+ */
40
+ export const UNREACHABLE_OFF_GATE =
41
+ /\.get\s*(?:<[^>]*>)?\s*\(\s*['"`]messaging\.[^'"`]+['"`]\s*,\s*false\b/;
42
+
43
+ const SUPPRESS = /lint-allow-messaging-gate\s*:/;
44
+
45
+ /** Scan raw source text; return 1-indexed line numbers of un-suppressed offenders. */
46
+ export function scanText(text) {
47
+ const lines = text.split('\n');
48
+ const hits = [];
49
+ lines.forEach((line, i) => {
50
+ if (!UNREACHABLE_OFF_GATE.test(line)) return;
51
+ if (SUPPRESS.test(line)) return;
52
+ if (i > 0 && SUPPRESS.test(lines[i - 1])) return;
53
+ hits.push(i + 1);
54
+ });
55
+ return hits;
56
+ }
57
+
58
+ function walk(dir, out = []) {
59
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
60
+ const full = path.join(dir, e.name);
61
+ if (e.isDirectory()) {
62
+ if (e.name === 'node_modules' || e.name === 'templates') continue;
63
+ walk(full, out);
64
+ } else if (e.name.endsWith('.ts') && !e.name.endsWith('.test.ts')) {
65
+ out.push(full);
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+
71
+ function main() {
72
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
73
+ const SRC = path.join(ROOT, 'src');
74
+ const offenders = [];
75
+ for (const file of walk(SRC)) {
76
+ const rel = path.relative(ROOT, file);
77
+ const text = fs.readFileSync(file, 'utf-8');
78
+ for (const line of scanText(text)) {
79
+ offenders.push({ rel, line, text: text.split('\n')[line - 1].trim() });
80
+ }
81
+ }
82
+
83
+ if (offenders.length > 0) {
84
+ console.error(
85
+ 'lint-no-unreachable-messaging-gate: default-OFF config gate at an unreachable messaging.<child>.* dot-path.\n' +
86
+ "On a real install `messaging` is a JSON ARRAY, so `messaging.<child>.*` resolves undefined → the `false`\n" +
87
+ 'default → the feature is structurally UN-ENABLABLE (the PR #1379 bug). Read it from a reachable TOP-LEVEL\n' +
88
+ 'key instead (e.g. `actionClaim.enabled`), or suppress with `// lint-allow-messaging-gate: <reason>`.\n',
89
+ );
90
+ for (const o of offenders) console.error(` ${o.rel}:${o.line} ${o.text}`);
91
+ process.exit(1);
92
+ }
93
+ console.log('lint-no-unreachable-messaging-gate: clean');
94
+ process.exit(0);
95
+ }
96
+
97
+ // Run the CLI scan only when executed directly (not when imported by a test).
98
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
99
+ main();
100
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-04T23:42:04.602Z",
5
- "instarVersion": "1.3.765",
4
+ "generatedAt": "2026-07-04T23:55:08.310Z",
5
+ "instarVersion": "1.3.766",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,37 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Added a CI lint (`scripts/lint-no-unreachable-messaging-gate.js`, wired into the `lint` chain) that
9
+ fails the build if any source reads a **default-OFF** config gate at a `messaging.<child>.*`
10
+ dot-path. On a real install `messaging` is a JSON **array** of adapter configs, so
11
+ `messaging.<child>.*` resolves `undefined` → the `false` default → the feature is structurally
12
+ un-enablable. This is the class-level guard for the PR #1379 bug (the Action-Claim / Slack-followthrough
13
+ sentinel that couldn't be turned on). It flags only the concrete `.get('messaging.*', false)` shape,
14
+ exempts default-`true` gates, and supports an inline `// lint-allow-messaging-gate: <reason>`
15
+ suppression. Zero current offenders — a pure forward guard.
16
+
17
+ ## What to Tell Your User
18
+
19
+ Nothing proactive — this is an internal build-time safety check. It only matters to developers: it
20
+ stops a whole class of "the on-switch was written in a place the program can't read" mistakes from
21
+ ever landing again, which is the exact bug that made the promise follow-through tracker impossible to
22
+ turn on earlier. It is the automated backstop so a human doesn't have to remember that quirk.
23
+
24
+ ## Summary of New Capabilities
25
+
26
+ - CI now refuses a default-off config gate placed at an unreachable position under the messaging list.
27
+ - Points the author at the fix (move the switch to a reachable top-level setting) with a precise
28
+ file:line, or lets them suppress a deliberate exception visibly.
29
+ - No runtime effect; a build-time guard only.
30
+
31
+ ## Evidence
32
+
33
+ - `tests/unit/lint-no-unreachable-messaging-gate.test.ts` — 8 cases: flags the #1379 shape (generic +
34
+ plain, both quote styles); exempts default-true, non-messaging, and top-level `actionClaim`; honors
35
+ same-line and preceding-line suppression; the exported regex matches the incident line.
36
+ - `node scripts/lint-no-unreachable-messaging-gate.js` → `clean` on current `main` (0 offenders).
37
+ - Side-effects review: `upgrades/side-effects/lint-unreachable-messaging-gate.md`.
@@ -0,0 +1,42 @@
1
+ # ELI16 — a guard so the "un-turn-on-able switch" bug can't come back
2
+
3
+ ## The story so far
4
+
5
+ Earlier today we found a safety feature whose on-switch was written in a spot the program could never
6
+ read: the config's "messaging" section is a *list* of chat platforms, but the switch was addressed as
7
+ if it were a labeled box inside that list. Lists don't have named boxes, so the program always found
8
+ "nothing," and "nothing" means off. The feature was impossible to turn on, and every test had used a
9
+ fake box-shaped config so nobody noticed. We fixed that specific feature (PR #1379). This change makes
10
+ sure the *whole class* of that mistake can't happen again.
11
+
12
+ ## What this adds
13
+
14
+ A tiny automated check (a "lint") that runs whenever the code is built or someone tries to commit. It
15
+ scans the code for the exact broken shape: reading an **off-by-default** switch from a
16
+ `messaging.<something>` address. Because "messaging" is a list, any such address is unreadable, and an
17
+ off-by-default switch there can never be turned on — so the check fails the build and points at the
18
+ line, telling the author to move the switch to a spot that actually works (a top-level setting).
19
+
20
+ ## Why it's careful (not annoying)
21
+
22
+ - It only flags the genuinely-broken case: **off-by-default** switches. Switches that default to *on*
23
+ are left alone, because "unreadable" just means they quietly stay on — no harm.
24
+ - It only looks at the one common way switches are read (`.get("messaging.x.enabled", false)`), so it
25
+ won't cry wolf on unrelated code.
26
+ - If there's ever a real reason to keep such a line, an author can add a short "allow this on purpose"
27
+ comment and the check steps aside — but that choice is now visible in the code instead of silent.
28
+ - Right now there are **zero** offending lines (the earlier fix removed the last one), so turning this
29
+ check on breaks nothing today — it's purely a guard for the future.
30
+
31
+ ## How we know it works
32
+
33
+ Eight small tests feed the checker example lines: it correctly flags the exact bug shape (in a few
34
+ spellings), correctly ignores the safe cases (default-on switches, top-level switches, non-messaging
35
+ switches), and correctly honors the "allow this on purpose" comment. So the guard catches the mistake
36
+ without blocking legitimate code.
37
+
38
+ ## The bigger idea
39
+
40
+ This is the "Structure beats Willpower" principle in one line: instead of hoping future authors
41
+ *remember* that "messaging" is a list and won't hold a switch, the build now *refuses* the mistake
42
+ automatically. A guard that a human missed once shouldn't rely on a human not missing it again.
@@ -0,0 +1,107 @@
1
+ # Side-Effects Review — lint: ban default-off config gate at an unreachable messaging.* path
2
+
3
+ **Version / slug:** `lint-unreachable-messaging-gate`
4
+ **Date:** `2026-07-04`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `not required (Tier-1)`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `scripts/lint-no-unreachable-messaging-gate.js` (wired into the `lint` chain in `package.json`)
11
+ and its unit test. The lint fails CI if any source file reads a **default-OFF** config gate at a
12
+ `messaging.<child>.*` dot-path — the exact shape of the PR #1379 bug: on a real install `messaging`
13
+ is a JSON **array**, so `messaging.<child>.*` resolves `undefined` → the `false` default → the
14
+ feature is structurally un-enablable. It flags only the concrete `.get('messaging.*', false)`
15
+ LiveConfig gate-read shape, exempts default-`true` gates (unreachable just leaves them on), and
16
+ supports an inline `// lint-allow-messaging-gate: <reason>` suppression. Currently 0 offenders on
17
+ `main` (PR #1379 removed the last one) — this is a pure forward guard.
18
+
19
+ ## Decision-point inventory
20
+
21
+ - `scripts/lint-no-unreachable-messaging-gate.js` — **add** — a new CI lint gate.
22
+ - `package.json` `lint` script — **modify** — append the new lint to the chain.
23
+ - `tests/unit/lint-no-unreachable-messaging-gate.test.ts` — **add** — detector unit tests.
24
+
25
+ ## 1. Over-block
26
+
27
+ The regex targets `.get('messaging.<...>', false)` specifically. A legitimate default-OFF gate that
28
+ genuinely must live under `messaging` (none known) would be flagged — but the correct answer is to
29
+ move it to a reachable top-level key (the whole point), and the inline suppression exists for a
30
+ deliberate exception. Default-`true` gates and non-`messaging` paths are not flagged.
31
+
32
+ ## 2. Under-block
33
+
34
+ It only catches the `.get(path, false)` shape. A default-off gate read via a cast
35
+ (`(cfg as {...}).messaging?.x`) or a raw-file read (`cfg.messaging.x.enabled` in a generated hook)
36
+ is not caught — a known, accepted limitation (the `.get` gate is the primary and highest-frequency
37
+ pattern; the two others are rarer and harder to lint without false positives). Documented in the
38
+ lint header.
39
+
40
+ ## 3. Level-of-abstraction fit
41
+
42
+ Right layer: a build-time lint (cheap, deterministic, CI-enforced) is exactly the "Structure beats
43
+ Willpower" guard for a class that a human/CI missed once (object-shaped-messaging tests hid it). It
44
+ does not add runtime behavior or authority.
45
+
46
+ ## 4. Signal vs authority compliance
47
+
48
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
49
+
50
+ - [x] No — this change has no runtime block/allow surface. It is a build-time CI lint. Its "authority"
51
+ is failing a build on a precise, suppressible pattern — not gating any live message or action.
52
+
53
+ ## 5. Interactions
54
+
55
+ - **Shadowing:** none — it's an independent step appended to the end of the `lint` chain.
56
+ - **Double-fire:** none.
57
+ - **Races:** none — pure file scan at build time.
58
+ - **Feedback loops:** none.
59
+
60
+ ## 6. External surfaces
61
+
62
+ - **Install base / agents:** none at runtime — a CI/dev-time lint only.
63
+ - **External systems / persistent state:** none.
64
+ - **Operator surface:** no operator-facing action.
65
+
66
+ ## 6b. Operator-surface quality
67
+
68
+ No operator surface — not applicable.
69
+
70
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
71
+
72
+ **machine-local BY DESIGN** — a build-time lint runs in CI / on a developer's checkout; it has no
73
+ runtime, no cross-machine state, no notices, no URLs. It is identical everywhere it runs by
74
+ construction.
75
+
76
+ ## 8. Rollback cost
77
+
78
+ Pure additive tooling — revert the script + the one-line `package.json` edit + the test. No
79
+ persistent state, no runtime effect, no user-visible change. Zero-cost back-out.
80
+
81
+ ## Conclusion
82
+
83
+ A small, precise, suppressible CI lint that would have caught the PR #1379 un-enablable bug at the
84
+ read site (`.get('messaging.actionClaim.enabled', false)`). 0 current offenders, so it is a pure
85
+ forward guard closing the class the operator's live-enable attempt surfaced. Clear to ship.
86
+
87
+ ## Second-pass review (if required)
88
+
89
+ **Reviewer:** not required (Tier-1)
90
+
91
+ ## Evidence pointers
92
+
93
+ - `tests/unit/lint-no-unreachable-messaging-gate.test.ts` — 8 cases: flags the #1379 shape (generic +
94
+ plain + both quote styles), exempts default-true and non-messaging and top-level `actionClaim`,
95
+ honors same-line and preceding-line suppression, regex matches the incident line.
96
+ - `node scripts/lint-no-unreachable-messaging-gate.js` → `clean` (0 offenders on current `main`).
97
+
98
+ ## Class-Closure Declaration (display-only mirror)
99
+
100
+ - **`defectClass`** — `config-unreachable-on-shape` (the class named in the PR #1379 side-effects
101
+ artifact; the guard the operator asked be built to close it).
102
+ - **`closure`** — `guard` — this lint IS the class-level guard: it structurally fails CI when a
103
+ default-off config gate is read at an unreachable `messaging.<child>.*` dot-path.
104
+ - **`guardEvidence`** — `{ enforcementType: lint, citation: scripts/lint-no-unreachable-messaging-gate.js
105
+ (wired into package.json `lint`, run by .husky/pre-commit + CI), howCaught: it flags
106
+ `.get('messaging.<child>.*', false)` — the exact read shape of the #1379 master gate — so the
107
+ un-enablable gate can never re-land }`.