instar 1.3.1148 → 1.3.1149
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/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/package.json +1 -1
- package/scripts/lint-dev-agent-dark-gate.js +45 -1
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/upgrades/1.3.1149.md +48 -0
- package/upgrades/side-effects/dev-agent-gate-alias.md +104 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1149",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/audits/phase-b/f10-triage.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "1ed8f5bd6959a002ef362c9be5f88a382b0956d4992b16879350851ce3711ac9",
|
|
3
3
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1149"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -72,6 +72,49 @@ const FUNNEL_ALLOWLIST = new Set([
|
|
|
72
72
|
// in the spec's Layer-1 "misses" row.)
|
|
73
73
|
const HANDROLLED_GATE =
|
|
74
74
|
/\?\?\s*(?:!{1,2}\s*|Boolean\s*\(\s*)?[A-Za-z_$][\w$.?]*(?:\.developmentAgent\b|\[\s*['"]developmentAgent['"]\s*\])/;
|
|
75
|
+
|
|
76
|
+
// ── 2026-08-14: the gate value does not have to be spelled at the `??`. ──
|
|
77
|
+
// The matcher above requires `.developmentAgent` LITERALLY after the `??`, so
|
|
78
|
+
// lifting it into a local const first walks past it while resolving the gate by
|
|
79
|
+
// hand exactly as before:
|
|
80
|
+
// const da = config.developmentAgent;
|
|
81
|
+
// return enabled ?? !!da; // exit 0 against the old matcher
|
|
82
|
+
// The header's declared limit ("cannot catch arbitrary aliases/wrapper helpers")
|
|
83
|
+
// was honest; this closes the LOCAL-CONST half of it, which is the shape a
|
|
84
|
+
// rename-defeat audit actually reproduced.
|
|
85
|
+
//
|
|
86
|
+
// Deliberately NOT closed, so it stays stated rather than implied: wrapper
|
|
87
|
+
// helpers (`isDevAgent(config)`), cross-module aliases, and any value that needs
|
|
88
|
+
// dataflow to resolve. Guessing at those would over-match, and this lint fails
|
|
89
|
+
// builds — flagging correct code is the more expensive error here.
|
|
90
|
+
//
|
|
91
|
+
// Scope-bounded on purpose: an alias binds only within its own file, so the map
|
|
92
|
+
// is rebuilt per file and never leaks between them.
|
|
93
|
+
const ALIAS_DECL =
|
|
94
|
+
/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::\s*[^=]+?)?=\s*[A-Za-z_$][\w$.?]*(?:\.developmentAgent\b|\[\s*['"]developmentAgent['"]\s*\])/;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Local identifiers bound to `<expr>.developmentAgent` in THIS file.
|
|
98
|
+
* `config.developmentAgentName` does not qualify — the `\b` keeps the boundary
|
|
99
|
+
* through the alias exactly as it holds at the direct callsite.
|
|
100
|
+
*/
|
|
101
|
+
function collectDevAgentAliases(lines) {
|
|
102
|
+
const names = new Set();
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
const code = codeOnly(line);
|
|
105
|
+
if (code === null) continue;
|
|
106
|
+
const m = ALIAS_DECL.exec(code);
|
|
107
|
+
if (m) names.add(m[1]);
|
|
108
|
+
}
|
|
109
|
+
return names;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** `?? [!! | Boolean(] <alias>` — the aliased spelling of the same hand-rolled gate. */
|
|
113
|
+
function aliasGateMatcher(names) {
|
|
114
|
+
if (names.size === 0) return null;
|
|
115
|
+
const alts = [...names].join('|');
|
|
116
|
+
return new RegExp(`\\?\\?\\s*(?:!{1,2}\\s*|Boolean\\s*\\(\\s*)?(?:${alts})\\b`);
|
|
117
|
+
}
|
|
75
118
|
// A comment referencing the gate convention (for assertion B).
|
|
76
119
|
const GATE_MARKER = /developmentAgent/i;
|
|
77
120
|
const GATE_MARKER_QUALIFIER = /\b(dark|gate)\b/i;
|
|
@@ -171,10 +214,11 @@ for (const file of resolveTargets()) {
|
|
|
171
214
|
|
|
172
215
|
// ── Assertion A: funnel ──
|
|
173
216
|
if (!FUNNEL_ALLOWLIST.has(rel)) {
|
|
217
|
+
const aliasGate = aliasGateMatcher(collectDevAgentAliases(lines));
|
|
174
218
|
lines.forEach((line, i) => {
|
|
175
219
|
const code = codeOnly(line);
|
|
176
220
|
if (code === null) return;
|
|
177
|
-
if (HANDROLLED_GATE.test(code)) {
|
|
221
|
+
if (HANDROLLED_GATE.test(code) || (aliasGate !== null && aliasGate.test(code))) {
|
|
178
222
|
violations.push({
|
|
179
223
|
file: rel, line: i + 1, kind: 'A: hand-rolled gate',
|
|
180
224
|
text: line.trim(),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-08-15T00:08:31.593Z",
|
|
5
|
+
"instarVersion": "1.3.1149",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1149",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/audits/phase-b/f10-triage.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "1ed8f5bd6959a002ef362c9be5f88a382b0956d4992b16879350851ce3711ac9",
|
|
3
3
|
"registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1149"
|
|
5
5
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
`scripts/lint-dev-agent-dark-gate.js` assertion A bans hand-rolled dev-agent gate resolution outside
|
|
9
|
+
`resolveDevAgentGate`. The matcher required `.developmentAgent` **literally after the `??`**, so lifting the
|
|
10
|
+
value into a local const first walked past it while resolving the gate by hand exactly as before:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const da = config.developmentAgent;
|
|
14
|
+
return enabled ?? !!da; // exit 0 against the shipped lint
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
instar-codey reproduced this while auditing rename-defeatable checks and pre-scoped the remedy ("low FP for
|
|
18
|
+
simple local alias folding around `developmentAgent` feeding `??`"). That is the scope implemented: local
|
|
19
|
+
`const`/`let`/`var` aliases, collected per file, matched at `?? [!!|Boolean(] <alias>`.
|
|
20
|
+
|
|
21
|
+
The lint's header already declared this limit ("cannot catch arbitrary aliases/wrapper helpers"). It is
|
|
22
|
+
closed for the shape that actually occurs; the declaration was honest, not wrong.
|
|
23
|
+
|
|
24
|
+
Four controls keep it from failing correct builds, each with a test: only a `developmentAgent` binding
|
|
25
|
+
counts; matching is whole-word so `developmentAgentName` is untouched; an alias never used at a `??` is
|
|
26
|
+
legal (reading the flag is fine — hand-rolling the fallback is what is banned); and a commented-out
|
|
27
|
+
declaration binds nothing.
|
|
28
|
+
|
|
29
|
+
Still not caught, stated in the source: wrapper helpers, cross-module aliases, and anything needing
|
|
30
|
+
dataflow. Guessing at those would over-match a build-failing lint.
|
|
31
|
+
|
|
32
|
+
## What to Tell Your User
|
|
33
|
+
|
|
34
|
+
None — internal change (no user-facing surface).
|
|
35
|
+
|
|
36
|
+
## Summary of New Capabilities
|
|
37
|
+
|
|
38
|
+
None — internal change (no user-facing surface).
|
|
39
|
+
|
|
40
|
+
## Evidence
|
|
41
|
+
|
|
42
|
+
- `tests/unit/lint-dev-agent-dark-gate.test.ts` — **31/31 green** (24 existing + 7 added).
|
|
43
|
+
- Negative control: tests written BEFORE the fix, run against the shipped lint — **3 of 31 fail** (const
|
|
44
|
+
alias, bracket-access alias, `Boolean(alias)`). The other 28 pass both ways.
|
|
45
|
+
- Reproduced by hand first with a positive control: direct form exits 1, aliased form exits 0.
|
|
46
|
+
- Real-tree verdict: `node scripts/lint-dev-agent-dark-gate.js` → exit 0, no new flags.
|
|
47
|
+
- Full `npm run lint` chain green.
|
|
48
|
+
- Side-effects: `upgrades/side-effects/dev-agent-gate-alias.md` · ELI16: `docs/specs/dev-agent-gate-alias.eli16.md`.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Side-Effects Review — dev-agent gate check follows a local alias
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `dev-agent-gate-alias`
|
|
4
|
+
**Date:** `2026-08-14`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required — Tier 1 (CI-only lint script; no runtime path). The rule is unchanged; the check now recognises one more spelling of the thing it already forbids.`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`scripts/lint-dev-agent-dark-gate.js` assertion A bans hand-rolled dev-agent gate resolution — anything
|
|
11
|
+
resolving `enabled ?? !!<x>.developmentAgent` outside `resolveDevAgentGate`. The matcher required
|
|
12
|
+
`.developmentAgent` (or `['developmentAgent']`) **literally after the `??`**, so lifting the value into a
|
|
13
|
+
local const first walked past it while resolving the gate by hand exactly as before:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const da = config.developmentAgent;
|
|
17
|
+
return enabled ?? !!da; // exit 0 against the shipped lint
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
instar-codey reproduced this while auditing rename-defeatable checks and scoped the remedy: *"low for
|
|
21
|
+
simple local alias folding around `developmentAgent` feeding `??`."* That is the scope implemented — local
|
|
22
|
+
`const`/`let`/`var` aliases only, rebuilt per file.
|
|
23
|
+
|
|
24
|
+
The lint's header **already declared this limit** ("cannot catch arbitrary aliases/wrapper helpers"). Like
|
|
25
|
+
the journal-actuation ban earlier today, it is closed because the declared gap is cheap to close for the
|
|
26
|
+
shape that actually occurs, not because the declaration was dishonest.
|
|
27
|
+
|
|
28
|
+
## Decision-point inventory
|
|
29
|
+
|
|
30
|
+
- `collectDevAgentAliases(lines)` — ADD — per-file identifiers bound to `<expr>.developmentAgent`.
|
|
31
|
+
- `aliasGateMatcher(names)` — ADD — `?? [!!|Boolean(] <alias>`; returns null when there are no aliases, so
|
|
32
|
+
a file without one is byte-identical to before.
|
|
33
|
+
- Assertion A predicate — WIDEN — `HANDROLLED_GATE || aliasGate`.
|
|
34
|
+
- Assertions B and C, the funnel allowlist, the comment-stripping (`codeOnly`), and the marker logic are
|
|
35
|
+
untouched.
|
|
36
|
+
- No runtime block/allow decision added or modified. CI-time only.
|
|
37
|
+
|
|
38
|
+
## 1. Over-block
|
|
39
|
+
|
|
40
|
+
The failure that matters: this lint fails builds, so flagging correct code costs more than missing a case.
|
|
41
|
+
Four controls, each with a test:
|
|
42
|
+
|
|
43
|
+
- **An alias of something else** (`config.somethingElse`) is not flagged — only `developmentAgent` binds.
|
|
44
|
+
- **A look-alike name** (`config.developmentAgentName`) is not flagged: the `\b` holds through the alias
|
|
45
|
+
exactly as it holds at the direct callsite.
|
|
46
|
+
- **An alias never used at a `??`** is not flagged. *Reading* the flag is legal; resolving the GATE by hand
|
|
47
|
+
is what is banned, and that distinction is preserved.
|
|
48
|
+
- **A comment describing the aliased pattern** is not flagged — `codeOnly` already strips comments and the
|
|
49
|
+
alias collector runs on the same stripped lines, so a commented-out declaration binds nothing.
|
|
50
|
+
|
|
51
|
+
Scope is per-file by construction: aliases cannot leak between files.
|
|
52
|
+
|
|
53
|
+
Verified against the real tree: exit 0 — the widened check introduces **no new flags on existing code**.
|
|
54
|
+
|
|
55
|
+
## 2. Under-block
|
|
56
|
+
|
|
57
|
+
Stated in the source rather than implied:
|
|
58
|
+
|
|
59
|
+
- **Wrapper helpers** (`isDevAgent(config)`) — still invisible.
|
|
60
|
+
- **Cross-module aliases** — an alias exported from another file is not followed.
|
|
61
|
+
- **Anything needing dataflow** to resolve.
|
|
62
|
+
|
|
63
|
+
Guessing at those would over-match. The header's original claim is narrowed, not erased: it now cannot
|
|
64
|
+
catch *arbitrary* aliases, having gained the local-const case.
|
|
65
|
+
|
|
66
|
+
## 3. Level-of-abstraction fit
|
|
67
|
+
|
|
68
|
+
Same layer as the existing check — line-oriented regex over comment-stripped source, no AST, no type
|
|
69
|
+
information, no new dependency. The alias map is the minimum needed to answer "what value is at this
|
|
70
|
+
`??`?" without climbing to a parser.
|
|
71
|
+
|
|
72
|
+
## 4. Signal vs authority compliance
|
|
73
|
+
|
|
74
|
+
Unchanged. A CI guard, not a runtime authority. It pushes callers toward `resolveDevAgentGate`; the funnel
|
|
75
|
+
allowlist still exempts the funnel itself.
|
|
76
|
+
|
|
77
|
+
## 5. Interactions
|
|
78
|
+
|
|
79
|
+
- `npm run lint` chain — position unchanged; full chain green.
|
|
80
|
+
- Assertions B/C unaffected; their env-fixture tests pass untouched.
|
|
81
|
+
- No source module, route, config key, or state file touched.
|
|
82
|
+
|
|
83
|
+
## 6. External surfaces
|
|
84
|
+
|
|
85
|
+
None. Developer tooling, not an agent capability; the Agent Awareness Standard does not apply.
|
|
86
|
+
|
|
87
|
+
## 7. Rollback cost
|
|
88
|
+
|
|
89
|
+
`git revert` of one script plus the appended tests. No migration, no state, no deployed artifact.
|
|
90
|
+
|
|
91
|
+
## Conclusion
|
|
92
|
+
|
|
93
|
+
Ship. One evasion closed at the scope a peer's audit recommended, four anti-over-block controls added, real
|
|
94
|
+
tree verified clean.
|
|
95
|
+
|
|
96
|
+
## Evidence pointers
|
|
97
|
+
|
|
98
|
+
- `tests/unit/lint-dev-agent-dark-gate.test.ts` — **31/31 green** (24 existing + 7 added).
|
|
99
|
+
- Negative control: tests written BEFORE the fix and run against the shipped lint — **3 of 31 fail** (const
|
|
100
|
+
alias, bracket-access alias, `Boolean(alias)`); the four new controls and all 24 existing tests pass both
|
|
101
|
+
ways, which is what makes them controls.
|
|
102
|
+
- Reproduced by hand first, with a positive control: the direct form exits 1, the aliased form exits 0.
|
|
103
|
+
- Real-tree verdict: `node scripts/lint-dev-agent-dark-gate.js` → exit 0.
|
|
104
|
+
- Full `npm run lint` chain green.
|