instar 1.3.1142 → 1.3.1143

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.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
5
- "packageVersion": "1.3.1142",
5
+ "packageVersion": "1.3.1143",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "588004e7496311e759c00e2ee9419b8385e05ca9cedff117ec3e44c9f78d73e2",
2
+ "sha256": "c72f4bfcc850a6eac1406c3592cf3eb5d8bfbdad13c090be45803c4b2277ed15",
3
3
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
4
- "packageVersion": "1.3.1142"
4
+ "packageVersion": "1.3.1143"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
3
3
  "articleCount": 88,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1142"
5
+ "packageVersion": "1.3.1143"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.1142",
3
+ "version": "1.3.1143",
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",
@@ -23,8 +23,10 @@
23
23
  * WHAT IT DOES NOT PROVE, stated because the previous version's claims outran its analysis:
24
24
  * - It resolves a URL through a LOCAL declaration or a local helper only. A Bot API URL assembled
25
25
  * in another module and imported would not be recognised.
26
- * - It does not follow a `fetch` reference stored in a variable and called indirectly.
27
- * Both are narrower than the gaps they replace, and both are named here rather than discovered.
26
+ * - It follows a `fetch` stored in a LOCAL variable declaration (added 2026-08-14), but NOT one
27
+ * re-assigned after declaration, arriving as a function parameter, or imported as a wrapper from
28
+ * another module.
29
+ * All are narrower than the gaps they replace, and all are named here rather than discovered.
28
30
  *
29
31
  * WHERE THIS ENDS AND THE TESTS BEGIN — established by sabotage, not by assumption. Breaking the
30
32
  * door's OWN url-to-method recogniser (so it silently skips the check on every send) leaves this lint
@@ -109,18 +111,25 @@ function denotesBotApiUrl(node, sf) {
109
111
  * invisible to a lint whose headline claim is "exactly one file". A property access whose final name
110
112
  * is `fetch` counts too.
111
113
  *
112
- * Still NOT covered, said plainly rather than left for the next reading to find: a fetch bound to a
113
- * DIFFERENT name (`const send = fetch; send(url)`) or reached through a computed member. Catching
114
- * those needs alias resolution this does not do. The claim below is written to match this scope.
114
+ * Alias resolution ADDED 2026-08-14 (a peer-agent audit ranked this check #2 of 25 defeatable by
115
+ * renaming, and this header had honestly named its own gap): a fetch bound to a DIFFERENT name
116
+ * (`const send = fetch; send(url)`) is now caught, resolved on the AST via collectFetchAliases and
117
+ * followed to a fixpoint so `const a = fetch; const b = a;` closes too. A computed member on a
118
+ * string literal (`x['fetch']`) was already covered.
119
+ *
120
+ * Still NOT covered, said plainly rather than left for the next reading to find: re-assignment after
121
+ * declaration, a fetch arriving as a function PARAMETER, and a wrapper imported from another module.
122
+ * Those need flow and cross-module analysis this does not do. The claim below is written to match
123
+ * this scope.
115
124
  */
116
- function isFetchCall(n) {
125
+ function isFetchCall(n, aliases = EMPTY_ALIASES) {
117
126
  if (!ts.isCallExpression(n)) return false;
118
127
  const e = n.expression;
119
- if (ts.isIdentifier(e)) return e.text === 'fetch';
128
+ if (ts.isIdentifier(e)) return e.text === 'fetch' || aliases.has(e.text);
120
129
  if (ts.isPropertyAccessExpression(e)) {
121
130
  // `fetch.call(...)` / `fetch.apply(...)` are direct invocations of fetch, and `x['fetch']` is a
122
131
  // property access spelled differently (pass 40 F3).
123
- if (e.name.text === 'call' || e.name.text === 'apply') return isFetchTarget(e.expression);
132
+ if (e.name.text === 'call' || e.name.text === 'apply') return isFetchTarget(e.expression, aliases);
124
133
  return e.name.text === 'fetch';
125
134
  }
126
135
  if (ts.isElementAccessExpression(e)) {
@@ -131,12 +140,57 @@ function isFetchCall(n) {
131
140
  }
132
141
 
133
142
  /** Is this expression the `fetch` function itself (for `.call`/`.apply` forms)? */
134
- function isFetchTarget(e) {
135
- if (ts.isIdentifier(e)) return e.text === 'fetch';
143
+ function isFetchTarget(e, aliases = EMPTY_ALIASES) {
144
+ if (ts.isIdentifier(e)) return e.text === 'fetch' || aliases.has(e.text);
136
145
  if (ts.isPropertyAccessExpression(e)) return e.name.text === 'fetch';
137
146
  return false;
138
147
  }
139
148
 
149
+ const EMPTY_ALIASES = new Set();
150
+
151
+ /**
152
+ * Local names bound to `fetch` in this file, resolved to a fixpoint so a chain
153
+ * (`const a = fetch; const b = a;`) closes too.
154
+ *
155
+ * This closes the gap the header above named plainly and left open: a fetch
156
+ * bound to a DIFFERENT name. Done on the AST rather than by text, because the
157
+ * file is already parsed — a variable declaration whose initialiser IS the
158
+ * fetch function is unambiguous, where a regex over `= fetch` would also match
159
+ * a property called fetch on an unrelated object.
160
+ *
161
+ * Deliberately NOT resolved: re-assignment after declaration, parameters, and
162
+ * imports of a wrapper from another module. Those need flow/cross-module
163
+ * analysis; the claim stays scoped to what is actually checked.
164
+ */
165
+ export function collectFetchAliases(sf) {
166
+ const names = new Set();
167
+ const decls = [];
168
+ const walkNode = (n) => {
169
+ if (ts.isVariableDeclaration(n) && n.initializer && ts.isIdentifier(n.name)) {
170
+ decls.push([n.name.text, n.initializer]);
171
+ }
172
+ ts.forEachChild(n, walkNode);
173
+ };
174
+ walkNode(sf);
175
+ for (let pass = 0; pass < 10; pass++) {
176
+ const before = names.size;
177
+ for (const [name, init] of decls) {
178
+ if (isFetchTarget(init, names)) names.add(name);
179
+ }
180
+ if (names.size === before) break;
181
+ }
182
+ return names;
183
+ }
184
+
185
+ // ── CLI body ─────────────────────────────────────────────────────────────
186
+ // Guarded so collectFetchAliases can be imported by tests WITHOUT running the
187
+ // scan: this module has four process.exit(1) paths, so an unguarded import
188
+ // would kill any test run the moment the repo had a violation. Same pattern as
189
+ // scripts/eli16-pr-description-check.mjs.
190
+ const invokedDirectly =
191
+ process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href;
192
+
193
+ if (invokedDirectly) {
140
194
  const violations = [];
141
195
  let doorSeen = false;
142
196
 
@@ -147,9 +201,10 @@ for (const file of walk(SRC)) {
147
201
  if (!text.toLowerCase().includes('api.telegram.org')) continue;
148
202
  const sf = parse(file, text);
149
203
  const isDoor = path.resolve(file) === DOOR;
204
+ const aliases = collectFetchAliases(sf);
150
205
 
151
206
  const visit = (n) => {
152
- if (isFetchCall(n) && ts.isCallExpression(n)) {
207
+ if (isFetchCall(n, aliases) && ts.isCallExpression(n)) {
153
208
  if (denotesBotApiUrl(n.arguments[0], sf)) {
154
209
  if (isDoor) doorSeen = true;
155
210
  else {
@@ -254,3 +309,5 @@ console.log(
254
309
  'lint-telegram-egress-boundary: clean — Telegram Bot API egress is confined to '
255
310
  + 'src/messaging/telegram-egress.ts, which checks the serialised body before sending.',
256
311
  );
312
+
313
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-08-14T20:03:30.012Z",
5
- "instarVersion": "1.3.1142",
4
+ "generatedAt": "2026-08-14T20:31:20.547Z",
5
+ "instarVersion": "1.3.1143",
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.1142",
5
+ "packageVersion": "1.3.1143",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "588004e7496311e759c00e2ee9419b8385e05ca9cedff117ec3e44c9f78d73e2",
2
+ "sha256": "c72f4bfcc850a6eac1406c3592cf3eb5d8bfbdad13c090be45803c4b2277ed15",
3
3
  "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
4
- "packageVersion": "1.3.1142"
4
+ "packageVersion": "1.3.1143"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
3
3
  "articleCount": 88,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1142"
5
+ "packageVersion": "1.3.1143"
6
6
  }
@@ -0,0 +1,28 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The check that proves only one file can talk to Telegram had already admitted, in its own header, that renaming the network call would defeat it. That gap is now closed.
9
+
10
+ - **Assigning the network call to a variable and calling the variable** was not recognised. The check's own documentation said so plainly and scoped its stated claim to match — honest, and still a hole in a door that carries the credential for the whole messaging channel.
11
+ - **It is now recognised**, followed through chains of assignment to a fixed point.
12
+ - **Done on the parsed structure of the file, not by matching text.** The file is already parsed, and a declaration whose value *is* the network call is unambiguous — searching for the text would also match a property with that name on an unrelated object.
13
+ - **The header's statement of limits is corrected.** It named this gap as open; three narrower ones are named in its place. Leaving the old wording would have left a false statement in the one place a reader looks to learn what the check is worth.
14
+ - **Nothing new is forbidden**, and the codebase passes cleanly before and after.
15
+
16
+ ## What to Tell Your User
17
+
18
+ Everything sent to Telegram has to go through one file, which looks at the message before it goes out. A check proves nothing else can reach the network on a Telegram address.
19
+
20
+ It recognised that call written several ways, but not the simplest disguise: put it in a variable first. The check said so about itself, which is better than pretending otherwise, but it was still a way around a door that holds the messaging credential. It is closed now, and the three narrower ways still open are written down where the next reader will find them.
21
+
22
+ ## Summary of New Capabilities
23
+
24
+ None. This widens what an existing check can see and corrects its written description of its own limits. No new command, route, setting, or rule.
25
+
26
+ ## Evidence
27
+
28
+ The gap was not discovered by anyone — the check declared it, in two separate places, and both are corrected here rather than left stating something now false. Proven in both directions: with the resolution removed, three tests fail and six pass, and those six are exactly the four deliberate opposite-direction controls plus two tests that pin the remaining gaps as still open. Those controls matter more than usual because this check blocks work: an unrelated assignment is not absorbed, a property whose name merely differs is not absorbed, a file with no reference yields nothing, and the word itself as a piece of text is not a binding. The real codebase passes cleanly before and after, source restored byte-identical after the check, and the module is now safe to import — it previously had four exit paths and no guard, so importing it in a test would have stopped the test run the moment the codebase had a violation.