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 +2 -2
- package/scripts/lint-no-unreachable-messaging-gate.js +100 -0
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.766.md +37 -0
- package/upgrades/side-effects/lint-unreachable-messaging-gate.eli16.md +42 -0
- package/upgrades/side-effects/lint-unreachable-messaging-gate.md +107 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instar",
|
|
3
|
-
"version": "1.3.
|
|
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:
|
|
5
|
-
"instarVersion": "1.3.
|
|
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 }`.
|