af360 0.1.4 → 0.1.11

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.
Files changed (121) hide show
  1. package/dist/.next/BUILD_ID +1 -1
  2. package/dist/.next/app-build-manifest.json +83 -83
  3. package/dist/.next/app-path-routes-manifest.json +19 -19
  4. package/dist/.next/build-manifest.json +2 -2
  5. package/dist/.next/prerender-manifest.json +36 -36
  6. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  7. package/dist/.next/server/app/_not-found.html +1 -1
  8. package/dist/.next/server/app/_not-found.rsc +3 -3
  9. package/dist/.next/server/app/ahead/page.js +1 -1
  10. package/dist/.next/server/app/ahead/page_client-reference-manifest.js +1 -1
  11. package/dist/.next/server/app/ahead.html +1 -1
  12. package/dist/.next/server/app/ahead.rsc +4 -4
  13. package/dist/.next/server/app/api/browse/route_client-reference-manifest.js +1 -1
  14. package/dist/.next/server/app/api/card/route.js +1 -1
  15. package/dist/.next/server/app/api/card/route.js.nft.json +1 -1
  16. package/dist/.next/server/app/api/card/route_client-reference-manifest.js +1 -1
  17. package/dist/.next/server/app/api/check/route_client-reference-manifest.js +1 -1
  18. package/dist/.next/server/app/api/config/route_client-reference-manifest.js +1 -1
  19. package/dist/.next/server/app/api/connections/route_client-reference-manifest.js +1 -1
  20. package/dist/.next/server/app/api/connectors/github/route_client-reference-manifest.js +1 -1
  21. package/dist/.next/server/app/api/consent/route_client-reference-manifest.js +1 -1
  22. package/dist/.next/server/app/api/context/route_client-reference-manifest.js +1 -1
  23. package/dist/.next/server/app/api/discover/route_client-reference-manifest.js +1 -1
  24. package/dist/.next/server/app/api/expiry/route_client-reference-manifest.js +1 -1
  25. package/dist/.next/server/app/api/first-light/route_client-reference-manifest.js +1 -1
  26. package/dist/.next/server/app/api/handoff/route_client-reference-manifest.js +1 -1
  27. package/dist/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  28. package/dist/.next/server/app/api/incidents/route_client-reference-manifest.js +1 -1
  29. package/dist/.next/server/app/api/insights/route.js +1 -1
  30. package/dist/.next/server/app/api/insights/route.js.nft.json +1 -1
  31. package/dist/.next/server/app/api/insights/route_client-reference-manifest.js +1 -1
  32. package/dist/.next/server/app/api/log/route.js +1 -1
  33. package/dist/.next/server/app/api/log/route_client-reference-manifest.js +1 -1
  34. package/dist/.next/server/app/api/pulse/route_client-reference-manifest.js +1 -1
  35. package/dist/.next/server/app/api/radar/route_client-reference-manifest.js +1 -1
  36. package/dist/.next/server/app/api/report/route.js +1 -1
  37. package/dist/.next/server/app/api/report/route.js.nft.json +1 -1
  38. package/dist/.next/server/app/api/report/route_client-reference-manifest.js +1 -1
  39. package/dist/.next/server/app/api/services/route_client-reference-manifest.js +1 -1
  40. package/dist/.next/server/app/api/status/route_client-reference-manifest.js +1 -1
  41. package/dist/.next/server/app/connections/page.js +1 -1
  42. package/dist/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  43. package/dist/.next/server/app/connections.html +1 -1
  44. package/dist/.next/server/app/connections.rsc +4 -4
  45. package/dist/.next/server/app/dashboard/page_client-reference-manifest.js +1 -1
  46. package/dist/.next/server/app/dashboard.html +1 -1
  47. package/dist/.next/server/app/dashboard.rsc +3 -3
  48. package/dist/.next/server/app/discoveries/page.js +1 -1
  49. package/dist/.next/server/app/discoveries/page_client-reference-manifest.js +1 -1
  50. package/dist/.next/server/app/discoveries.html +1 -1
  51. package/dist/.next/server/app/discoveries.rsc +4 -4
  52. package/dist/.next/server/app/expiry/page_client-reference-manifest.js +1 -1
  53. package/dist/.next/server/app/expiry.html +1 -1
  54. package/dist/.next/server/app/expiry.rsc +3 -3
  55. package/dist/.next/server/app/fleet/page_client-reference-manifest.js +1 -1
  56. package/dist/.next/server/app/fleet.html +1 -1
  57. package/dist/.next/server/app/fleet.rsc +3 -3
  58. package/dist/.next/server/app/home/page_client-reference-manifest.js +1 -1
  59. package/dist/.next/server/app/home.html +1 -1
  60. package/dist/.next/server/app/home.rsc +3 -3
  61. package/dist/.next/server/app/legal/privacy/page_client-reference-manifest.js +1 -1
  62. package/dist/.next/server/app/legal/terms/page_client-reference-manifest.js +1 -1
  63. package/dist/.next/server/app/log/page_client-reference-manifest.js +1 -1
  64. package/dist/.next/server/app/log.html +1 -1
  65. package/dist/.next/server/app/log.rsc +4 -4
  66. package/dist/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/dist/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  68. package/dist/.next/server/app/projects.html +1 -1
  69. package/dist/.next/server/app/projects.rsc +4 -4
  70. package/dist/.next/server/app/radar/page.js +1 -1
  71. package/dist/.next/server/app/radar/page_client-reference-manifest.js +1 -1
  72. package/dist/.next/server/app/radar.html +1 -1
  73. package/dist/.next/server/app/radar.rsc +4 -4
  74. package/dist/.next/server/app/report/page.js +1 -1
  75. package/dist/.next/server/app/report/page_client-reference-manifest.js +1 -1
  76. package/dist/.next/server/app/report.html +1 -1
  77. package/dist/.next/server/app/report.rsc +4 -4
  78. package/dist/.next/server/app/settings/page.js +1 -1
  79. package/dist/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  80. package/dist/.next/server/app/settings.html +1 -1
  81. package/dist/.next/server/app/settings.rsc +4 -4
  82. package/dist/.next/server/app/sites/page_client-reference-manifest.js +1 -1
  83. package/dist/.next/server/app/sites.html +1 -1
  84. package/dist/.next/server/app/sites.rsc +4 -4
  85. package/dist/.next/server/app/welcome/page_client-reference-manifest.js +1 -1
  86. package/dist/.next/server/app/welcome.html +1 -1
  87. package/dist/.next/server/app/welcome.rsc +4 -4
  88. package/dist/.next/server/app-paths-manifest.json +19 -19
  89. package/dist/.next/server/chunks/1155.js +1 -0
  90. package/dist/.next/server/chunks/2346.js +1 -1
  91. package/dist/.next/server/chunks/2465.js +1 -1
  92. package/dist/.next/server/chunks/9170.js +1 -1
  93. package/dist/.next/server/middleware-manifest.json +1 -1
  94. package/dist/.next/server/pages/404.html +1 -1
  95. package/dist/.next/server/pages/500.html +1 -1
  96. package/dist/.next/server/pages-manifest.json +1 -1
  97. package/dist/.next/static/chunks/3192-f33976256a7713fd.js +1 -0
  98. package/dist/.next/static/chunks/{5449-42f9c47cf519229a.js → 5449-1f71ab05b6de2699.js} +1 -1
  99. package/dist/.next/static/chunks/app/ahead/page-ec9c355d92be3655.js +1 -0
  100. package/dist/.next/static/chunks/app/connections/page-78928db455ee443e.js +1 -0
  101. package/dist/.next/static/chunks/app/discoveries/page-09ae376238d3c1da.js +1 -0
  102. package/dist/.next/static/chunks/app/radar/page-ca0fa3338fe9ddb4.js +1 -0
  103. package/dist/.next/static/chunks/app/report/page-b6eef7480ac39e24.js +1 -0
  104. package/dist/.next/static/chunks/app/settings/{page-3ca6b23c69637aab.js → page-dd56fc2e097505c7.js} +1 -1
  105. package/dist/.next/static/css/9db3c04b1822381e.css +1 -0
  106. package/dist/build-id.json +114 -114
  107. package/dist/package.json +8 -9
  108. package/dist/shell/bin/af360-bridge.exe +0 -0
  109. package/lib/mcpActivity.mjs +318 -23
  110. package/package.json +8 -9
  111. package/shell/bin/af360-bridge.exe +0 -0
  112. package/dist/.next/server/chunks/9601.js +0 -1
  113. package/dist/.next/static/chunks/3192-3df9f9fedb9e38b1.js +0 -1
  114. package/dist/.next/static/chunks/app/ahead/page-2d37c636630d12e7.js +0 -1
  115. package/dist/.next/static/chunks/app/connections/page-7e0dbe13ce939fc9.js +0 -1
  116. package/dist/.next/static/chunks/app/discoveries/page-0ac8bd1bf0178837.js +0 -1
  117. package/dist/.next/static/chunks/app/radar/page-24540a7c154a41b8.js +0 -1
  118. package/dist/.next/static/chunks/app/report/page-f1ede34a209ac047.js +0 -1
  119. package/dist/.next/static/css/f0c4f79b3d7295b1.css +0 -1
  120. /package/dist/.next/static/{235399e935b20d0efd52d → 24e7279870f90176b29aa}/_buildManifest.js +0 -0
  121. /package/dist/.next/static/{235399e935b20d0efd52d → 24e7279870f90176b29aa}/_ssgManifest.js +0 -0
package/dist/package.json CHANGED
@@ -1,23 +1,22 @@
1
1
  {
2
2
  "name": "af360",
3
- "version": "0.1.4",
3
+ "version": "0.1.11",
4
4
  "description": "Watches the things you have built - folders, repos and sites - and tells you what needs you. Runs entirely on your own machine.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Afeleos LLC",
7
7
  "homepage": "https://afeleos.com",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/Afeleos-LLC/AF360.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/Afeleos-LLC/AF360/issues"
14
- },
8
+ "mcpName": "com.afeleos/af360",
15
9
  "keywords": [
16
10
  "local-first",
17
11
  "projects",
18
12
  "git",
19
13
  "monitoring",
20
- "privacy"
14
+ "privacy",
15
+ "mcp",
16
+ "model-context-protocol"
17
+ ],
18
+ "os": [
19
+ "win32"
21
20
  ],
22
21
  "bin": {
23
22
  "af360": "bin/af360.mjs"
Binary file
@@ -104,6 +104,67 @@
104
104
  * the rail says af360 could not identify what asked, rather than guessing.
105
105
  *
106
106
  * ============================================================
107
+ * **AND ON EVERY INSTALLED COPY THE PARENT IS AF360 ITSELF** (R340, owner
108
+ * ruling 2026-09-05, measured on 0.1.6).
109
+ * ============================================================
110
+ *
111
+ * THE PARAGRAPH ABOVE IS TRUE AND WAS NOT ENOUGH, AND THE SENTENCE THAT GAVE
112
+ * IT AWAY IS FOUR LINES UP: *"the process that spawned this one, which for an
113
+ * MCP server is the client itself."* **THAT STOPPED BEING TRUE WHEN R306
114
+ * SHIPPED.** A packaged install writes the alias entry `af360.exe mcp`; the
115
+ * alias resolves to af360's own native host; and that host starts node on
116
+ * bin/af360.mjs AS A CHILD. The client is this process's GRANDparent.
117
+ *
118
+ * So the owner read `estate_status asked by af360` three times over a Log room
119
+ * whose whole purpose is naming who asked, and af360 was naming itself - the
120
+ * exact defect guard 4 exists to prevent, arriving through the one name the
121
+ * guard did not know was its own.
122
+ *
123
+ * **THE HONEST ANSWER SHIPS AND THE REAL ONE IS OWED.** `observeCaller` now
124
+ * refuses OWN_APP as flatly as it refuses the runtime, so the record says af360
125
+ * could not identify what asked - which is TRUE, and which a reader can act on.
126
+ * Naming the client for real means walking up one more hop, `caller-name`
127
+ * answers with a name rather than a pid, and a parent-of-pid verb is a NEW DOOR
128
+ * in af360-bridge.exe. **THAT IS THE OWNER'S TO RULE (RULINGS 001) AND IT IS
129
+ * RECORDED AS OWED IN 0b.4, NOT TAKEN QUIETLY HERE.**
130
+ *
131
+ * ============================================================
132
+ * **AND HE RULED IT. THE DEBT ABOVE IS PAID** (R349/R350, owner ruling
133
+ * 2026-09-05, measured on the installed 0.1.7 before a line was written).
134
+ * ============================================================
135
+ *
136
+ * **THE PARAGRAPH ABOVE IS KEPT WHOLE AND NOT REWRITTEN** (RULINGS 65): a debt
137
+ * that was named, carried for a day and then paid is a different fact from one
138
+ * nobody ever wrote down, and only one of those is true here.
139
+ *
140
+ * `caller-chain` is the door, the owner opened it, and `callerFromChain` is the
141
+ * rule that reads it - **which is NOT the rule R350 was drafted with.** Driven
142
+ * against this machine's live client before it was built, the drafted rule
143
+ * answered `cmd`, because the client spawns af360 through a shell. Driven
144
+ * against a node-based client it answered `powershell`, the client's own
145
+ * launcher. Two false sentences, both found by measuring rather than by
146
+ * reasoning, which is R349's whole reason for ordering the measurement first.
147
+ *
148
+ * **WHAT DID NOT CHANGE, AND IT IS EVERY GUARD THIS FILE CLAIMS.** The four
149
+ * guards below hold exactly as written: `observeCaller` still takes no
150
+ * parameters, the binary is still found without the environment, the mask still
151
+ * runs - now on EVERY hop rather than on one - and the shape gate still has a
152
+ * constant fallback. **AND ONE CLAIM GOT STRONGER RATHER THAN WEAKER**: the old
153
+ * verb opened a process HANDLE to read a name; the new one reads a snapshot
154
+ * struct and opens no handle on any walked process at all, so "af360 cannot
155
+ * start, stop or signal anything through this door" became a property of the
156
+ * code instead of a promise about it.
157
+ *
158
+ * THE OTHER HALF OF WHAT WAS MEASURED, BECAUSE A HALF-ANSWERED QUESTION READS
159
+ * AS A WHOLE ONE: every `tools/call` IS logged - `noteToolCall` sits before the
160
+ * branch in scripts/mcp-server.mjs and cannot be skipped by an unknown tool, a
161
+ * refusal or a config fault - and REPEATS COLLAPSE FOR DISPLAY ONLY, per tool
162
+ * and per caller, inside `COLLAPSE_MS`, rendered `x3` (lib/mcpRail.mjs
163
+ * `collapse`). Nothing is dropped from the file. **AN ASK THAT NEVER REACHED
164
+ * THIS SERVER LEAVES NO TRACE HERE AND CANNOT**: a client that answers from its
165
+ * own context did not ask af360, and af360 must not invent a line for it.
166
+ *
167
+ * ============================================================
107
168
  * ONE FILE, IN AF360'S OWN HOME, APPEND-ONLY, CAPPED (RULINGS 56a, 56c)
108
169
  * ============================================================
109
170
  *
@@ -203,6 +264,58 @@ export const UNIDENTIFIED = "unidentified";
203
264
  */
204
265
  export const CALLER_SHAPE = /^[a-z0-9][a-z0-9._+-]{0,31}$/;
205
266
 
267
+ /**
268
+ * AF360'S OWN IMAGE NAME, AS WINDOWS REPORTS IT (R340, owner ruling
269
+ * 2026-09-05).
270
+ *
271
+ * THE BRIDGE RETURNS `ProcessName`, WHICH CARRIES NO EXTENSION - "af360", not
272
+ * "af360.exe" - so this is the alias name with its `.exe` already off. It is
273
+ * the one literal in this file that has a twin elsewhere (`ALIAS_NAME` in
274
+ * lib/connections.mjs) and it is NOT imported from there: that module reaches
275
+ * a config parser and a filesystem walk, and this one is loaded by the cockpit
276
+ * for its path constants. A four-character duplicate is cheaper than that
277
+ * graph, and scripts/mcp-fixture.mjs asserts the two agree so the duplicate
278
+ * cannot drift.
279
+ *
280
+ * IT IS COMPARED, NEVER WRITTEN. Like every other value here it can only ever
281
+ * cause UNIDENTIFIED to be recorded; no branch puts it in a line.
282
+ */
283
+ export const OWN_APP = "af360";
284
+
285
+ /**
286
+ * THE SHELLS af360 WALKS STRAIGHT PAST (R349/R350, owner ruling 2026-09-05).
287
+ *
288
+ * **THE MEASUREMENT THAT PUT THIS LIST HERE, AND IT WAS TAKEN OFF THE OWNER'S
289
+ * OWN LIVE CLIENT.** R350 as drafted said the asker is *"the first ancestor
290
+ * that is neither af360's own runtime nor its executable"*. Run against the
291
+ * real chain on 2026-09-05, that rule answered `cmd`:
292
+ *
293
+ * node -> af360 -> cmd -> claude -> sh
294
+ * ^^^ the rule as drafted stopped here
295
+ *
296
+ * `.claude.json` registers af360 as `"command": "af360.exe"`, and the client
297
+ * spawns that through `cmd /d /s /c "af360.exe ^"mcp^""`. **SO THERE IS A
298
+ * SHELL BETWEEN AF360'S HOST AND THE CLIENT, AND `estate_status asked by cmd`
299
+ * IS R340'S DEFECT WEARING A DIFFERENT NAME** - a false sentence, which is the
300
+ * worst thing this repo can ship (CLAUDE.md 0).
301
+ *
302
+ * **THE OWNER RULED THE LIST RATHER THAN A SESSION GUESSING IT** (2026-09-05,
303
+ * on the measurement above): *"shells are plumbing, not askers."* A shell does
304
+ * not ask af360 anything; it is only ever interposed by the client's own spawn,
305
+ * so walking past one lands on the client rather than away from it.
306
+ *
307
+ * **IT IS A SKIP LIST AND `node` IS NOT ON IT, AND THAT DISTINCTION IS THE
308
+ * WHOLE RULING.** See `callerFromChain`: a conduit is walked THROUGH, a runtime
309
+ * is walked INTO A STOP. Adding `node` here would name a node client's
310
+ * launcher, which is the second false sentence the measurement found.
311
+ */
312
+ export const CONDUIT = Object.freeze(["cmd", "powershell", "pwsh", "sh", "bash"]);
313
+
314
+ /** THE WALK'S CEILING ON THIS SIDE, and it is the bridge's own `ChainMax`.
315
+ * A second bound in a second language is a bound that drifts, so
316
+ * scripts/bridge-doors-fixture.mjs asserts the two numbers agree. */
317
+ export const CHAIN_MAX = 16;
318
+
206
319
  /** THE OBSERVED NAME, and it starts as the honest answer rather than as null:
207
320
  * until af360 has looked, af360 has not identified anything. */
208
321
  let CALLER = UNIDENTIFIED;
@@ -223,17 +336,34 @@ export function callerName() {
223
336
  *
224
337
  * EVERY FAILURE IS THE SAME FAILURE: the constant. No bridge (an npm install,
225
338
  * a Mac, a tree nobody has built the shell in), a dead parent, a name the shape
226
- * gate refuses, af360's own runtime looking back - all of them leave CALLER at
227
- * UNIDENTIFIED, and the rail then says af360 could not identify what asked.
339
+ * gate refuses, af360's own runtime looking back, AF360'S OWN APP LOOKING BACK
340
+ * (R340) - all of them leave CALLER at UNIDENTIFIED, and the rail then says
341
+ * af360 could not identify what asked.
228
342
  * NOTHING HERE MAY EVER FAIL AN ANSWER, so it cannot throw.
343
+ *
344
+ * **WHAT IT DOES SINCE R340 IS READ THE KERNEL AND ASK ~~`callerFrom`~~
345
+ * `callerFromChain` (R349/R350).** Every rule that decides moved there so a
346
+ * fixture can drive it with a name this machine's process tree does not happen
347
+ * to contain; this function keeps the two things only it can do - the ppid and
348
+ * the bridge - and keeps guard 1.
349
+ *
350
+ * **AND THE VERB IT ASKS CHANGED ON 2026-09-05, WHICH IS THE DEBT R340 WROTE
351
+ * DOWN BEING PAID.** `caller-name` answered ONE hop, and on every packaged
352
+ * install that hop is af360's own host, so af360 could only ever say it could
353
+ * not tell. `caller-chain` answers the ancestors by name, af360 walks them, and
354
+ * the client is named where the walk can name it.
229
355
  */
230
356
  export async function observeCaller() {
231
357
  if (looked) return CALLER;
232
358
  looked = true;
233
359
  try {
234
360
  /* THE KERNEL'S OWN NUMBER. Node did not choose it and a caller cannot set
235
- it: it is the process that spawned this one, which for an MCP server is
236
- the client itself. */
361
+ it: it is the process that spawned this one.
362
+ ~~"which for an MCP server is the client itself"~~ **AND THAT CLAUSE WAS
363
+ THE DEFECT** (R340). It is the client on a `node <entry> mcp` entry and
364
+ it is AF360'S OWN HOST on the alias entry every packaged install writes.
365
+ The number is still unforgeable; what it points AT depends on how af360
366
+ was started, and this file may not assume one shape. */
237
367
  const ppid = process.ppid;
238
368
  if (!Number.isInteger(ppid) || ppid <= 0) return CALLER;
239
369
 
@@ -244,31 +374,196 @@ export async function observeCaller() {
244
374
 
245
375
  /* `fixedPath` refuses AF360_BRIDGE - guard 2 in the header, and the whole
246
376
  reason lib/bridge.mjs grew a second resolver. Two seconds is generous
247
- against a measured 149ms round trip and it runs once. */
248
- const r = await bridge("caller-name", String(ppid), { fixedPath: true, timeout: 2_000 });
377
+ against a measured 149ms round trip - the chain walk was measured at
378
+ ~77ms on 2026-09-05, snapshot included - and it runs once. */
379
+ /* **IT IS THE ppid AND NOT `process.pid`, AND THAT IS LOAD-BEARING RATHER
380
+ THAN INCIDENTAL.** The chain comes back nearest-first, so sending this
381
+ process's own id would put af360's OWN node at the head of it - and
382
+ `callerFromChain` STOPS at a runtime, so every answer on every machine
383
+ would silently become the constant. This exact confusion produced a wrong
384
+ answer while this was being driven on 2026-09-05, against real data, and
385
+ it is written here because the failure is INVISIBLE: nothing throws,
386
+ nothing logs, af360 just quietly stops naming anybody.
387
+ scripts/mcp-rail-fixture.mjs asserts the call site passes `ppid`. */
388
+ const r = await bridge("caller-chain", String(ppid), { fixedPath: true, timeout: 2_000 });
249
389
  if (!r.ok) return CALLER;
250
390
 
251
- /* THE MASK FIRST (RULINGS 57b, guard 3), then the gate. Order matters: a
252
- name the mask ate comes back as "[secret masked]", which has brackets and
253
- a space in it, so the gate refuses it and af360 says it could not
254
- identify the caller - rather than writing af360's own redaction marker
255
- into a row as though it were somebody's name. */
256
- const seen = mask(String(r.out ?? "")).trim().toLowerCase();
257
-
258
- /* AF360'S OWN RUNTIME IS NOT AN ANSWER (RULINGS 42a). A wrapper script
259
- between the client and af360 leaves this process's parent as node, and
260
- "node asked" would be af360 naming itself as the agent. The comparison is
261
- against THIS process's own executable rather than the literal alone, so a
262
- renamed runtime is caught too. */
263
- const ownRuntime = path.basename(process.execPath, ".exe").toLowerCase();
264
- if (seen === "node" || seen === ownRuntime) return CALLER;
265
-
266
- if (!CALLER_SHAPE.test(seen)) return CALLER;
267
- CALLER = seen;
391
+ CALLER = callerFromChain(r.out);
268
392
  } catch { /* a courtesy to the rail must never become a failed answer */ }
269
393
  return CALLER;
270
394
  }
271
395
 
396
+ /**
397
+ * THE WHOLE DECISION, AND NOTHING BUT THE DECISION (R340, 2026-09-05).
398
+ *
399
+ * ============================================================
400
+ * WHY IT IS A FUNCTION, AND WHY IT TAKES THE ARGUMENT observeCaller REFUSES
401
+ * ============================================================
402
+ *
403
+ * R340's fixture has to DRIVE TWO DISTINCT CLIENTS and watch each get named.
404
+ * `observeCaller` reads the kernel, so on this machine it can only ever
405
+ * observe one parent, and a fixture that asserts anything about a second would
406
+ * have to REBUILD these rules beside them. **A TWIN OF A REDACTION-SHAPED RULE
407
+ * IS A RULE THAT DRIFTS**, and this repo has already ruled that twice (RULINGS
408
+ * 44a; lib/shareExport.mjs's own header). So the rules move to where a fixture
409
+ * can reach the SHIPPING copy, and `observeCaller` is what remains: a kernel
410
+ * read, and a call to this.
411
+ *
412
+ * **THE NO-PARAMETER GUARD IS NOT WEAKENED, BECAUSE THE GUARD WAS NEVER ABOUT
413
+ * THIS FUNCTION.** Guard 1 says nothing UPSTREAM can hand this module a name -
414
+ * and nothing can: `observeCaller` still takes no arguments, it is still the
415
+ * only caller of this in the shipping path, and it still feeds it one value it
416
+ * got from the bridge. This function RETURNS and never assigns: `CALLER` has
417
+ * exactly one writer, four lines above, and grep proves it. A fixture calling
418
+ * this gets a verdict back and changes nothing about what the writer stamps.
419
+ *
420
+ * EVERY REFUSAL RETURNS THE SAME CONSTANT, and there are five:
421
+ *
422
+ * 1. THE MASK ATE IT (RULINGS 57b, guard 3). Run FIRST, on purpose: a name
423
+ * the mask redacted comes back as "[secret masked]" - brackets and a
424
+ * space - so the shape gate refuses it and af360 says it could not tell,
425
+ * rather than writing its own redaction marker as somebody's name.
426
+ * 2. "node" - a bare runtime is not an answer to "who asked".
427
+ * 3. THIS PROCESS'S OWN EXECUTABLE, compared rather than assumed, so a
428
+ * renamed runtime is caught too.
429
+ * 4. AF360'S OWN APP (R340). See OWN_APP.
430
+ * 5. THE SHAPE GATE. Not a filter - one narrow shape or nothing.
431
+ *
432
+ * @param {unknown} raw whatever `caller-name` printed. Compared and classified;
433
+ * it reaches a log line only by being RETURNED and only if it survives all
434
+ * five.
435
+ * @returns {string} the observed name, or UNIDENTIFIED.
436
+ */
437
+ export function callerFrom(raw) {
438
+ const seen = mask(String(raw ?? "")).trim().toLowerCase();
439
+
440
+ /* AF360'S OWN RUNTIME IS NOT AN ANSWER (RULINGS 42a). A wrapper script
441
+ between the client and af360 leaves this process's parent as node, and
442
+ "node asked" would be af360 naming itself as the agent. The comparison is
443
+ against THIS process's own executable rather than the literal alone, so a
444
+ renamed runtime is caught too. */
445
+ const ownRuntime = path.basename(process.execPath, ".exe").toLowerCase();
446
+ if (seen === "node" || seen === ownRuntime) return UNIDENTIFIED;
447
+
448
+ /* AND NEITHER IS AF360'S OWN APP (R340, owner ruling 2026-09-05, measured
449
+ on the installed 0.1.6). THE LINE ABOVE HELD A RULE THIS ONE COMPLETES:
450
+ af360 never names itself, and for eight days it only knew how to spot
451
+ itself wearing the RUNTIME's name.
452
+ WHAT THE OWNER READ, AND IT WAS af360 ANSWERING WITH ITS OWN NAME:
453
+ estate_status asked by af360 - answered from af360's last look.
454
+ Claude Code asked. Every time.
455
+ THE CAUSE IS R306'S OWN REPAIR, AND IT IS NOT A REGRESSION IN IT. The
456
+ alias entry a packaged install writes is `af360.exe mcp`, the alias
457
+ resolves to `shell/bin/af360.exe`, and that executable STARTS NODE ON
458
+ bin/af360.mjs AS A CHILD (shell/Af360Shell.cs, McpVerb.Run). So the
459
+ parent of this process is not the client at all - IT IS AF360. The
460
+ kernel's number is still the kernel's number; what moved is what sits
461
+ on the other end of it.
462
+ BOTH SYMPTOMS ARE ONE CAUSE. Before the alias the entry was `node
463
+ <entry> mcp`, the parent WAS the client, and the client was Claude Code -
464
+ which is itself node, so the line above refused it and the row read
465
+ "af360 could not identify what asked". That is the single older entry in
466
+ the owner's log, and it was the guard working correctly.
467
+ **THIS IS A WRONG ANSWER BECOMING A TRUE ONE, NOT A MISSING ONE BEING
468
+ FILLED IN** (RULINGS 59). Naming the real client means walking UP past
469
+ af360's own host to its parent, and `caller-name` returns a name and not a
470
+ pid - so it needs a NEW BRIDGE VERB, which is a door, which is the owner's
471
+ (RULINGS 001) and is OWED IN 0b.4 rather than taken here. Until that ruling
472
+ af360 says the thing it can prove: it could not tell.
473
+ **THE OWNER RULED THE DOOR ON 2026-09-05 AND THE DEBT IS PAID (R349).**
474
+ `caller-chain` walks the ancestors and `callerFromChain` below decides over
475
+ them. THIS LINE DOES NOT MOVE AND MUST NOT: reaching this function with
476
+ `af360` still means af360 is looking at itself, and the answer to that is
477
+ still the honest sentence. What changed is that the CHAIN no longer STOPS
478
+ at af360 - it walks past it and asks this question of the next name up. */
479
+ if (seen === OWN_APP) return UNIDENTIFIED;
480
+
481
+ if (!CALLER_SHAPE.test(seen)) return UNIDENTIFIED;
482
+ return seen;
483
+ }
484
+
485
+ /**
486
+ * THE ASKER, PICKED OUT OF THE ANCESTORS (R349/R350, owner ruling 2026-09-05).
487
+ *
488
+ * ============================================================
489
+ * TWO LISTS, AND THEY DO OPPOSITE THINGS. THAT IS THE RULING.
490
+ * ============================================================
491
+ *
492
+ * A reader who takes this for one list with two spellings has the rule
493
+ * backwards, so it is said plainly:
494
+ *
495
+ * A CONDUIT IS WALKED THROUGH. af360's own executable, and a shell. Neither
496
+ * ever ASKS af360 anything - af360 wrote the
497
+ * first one, and the second was interposed by
498
+ * the client's own spawn - so the client is
499
+ * further up and walking past reaches it.
500
+ * A RUNTIME IS WALKED INTO A `node`, and this process's own executable.
501
+ * STOP. af360 cannot tell a node CLIENT from a node
502
+ * WRAPPER around one, and the two want
503
+ * opposite answers. So it stops and says it
504
+ * could not tell.
505
+ *
506
+ * **BOTH HALVES WERE MEASURED BEFORE EITHER WAS BUILT (R349's own order), AND
507
+ * EACH ONE CAUGHT A FALSE SENTENCE:**
508
+ *
509
+ * the owner's live client node -> af360 -> cmd -> claude -> sh
510
+ * skip only af360 names `cmd` <- the client's SHELL
511
+ * this rule names `claude` <- the client
512
+ *
513
+ * a node-based client node -> af360 -> node -> powershell -> claude
514
+ * treat node as a conduit names `powershell` <- the CLIENT'S LAUNCHER
515
+ * this rule stops at node, says af360 could not tell
516
+ *
517
+ * **THE SECOND ROW IS WHY THE STOP EXISTS AND WHY IT IS NOT A DEFECT.** A
518
+ * Node-based CLI client is UNNAMEABLE BY THIS WALK, deliberately and for ever:
519
+ * the only thing that would separate it from a wrapper is its COMMAND LINE, and
520
+ * this door returns names and refuses command lines by construction
521
+ * (shell/Af360Bridge.cs). A limit that could only be closed by widening a
522
+ * security bound is a limit af360 keeps. docs/SECURITY-MODEL.md records it as
523
+ * one (RULINGS 63: an unproven residual is a prediction, not a disclaimer).
524
+ *
525
+ * **AND THE ANSWER AT A STOP IS THE CONSTANT, NEVER THE RUNTIME'S NAME** (the
526
+ * owner's own words on ruling this: *"the honest sentence, never a runtime's
527
+ * name in the asker slot"*). `node` never reaches the caller field. That is
528
+ * `callerFrom`'s existing first refusal and this function reuses it rather than
529
+ * restating it - a twin of a redaction-shaped rule is a rule that drifts
530
+ * (RULINGS 44a), which is the same argument that lifted `callerFrom` out of
531
+ * `observeCaller` in the first place.
532
+ *
533
+ * **THE MASK RUNS ON EVERY HOP AND NOT ONLY ON THE ONE THAT WINS** (RULINGS
534
+ * 57b, guard 3). A redacted name comes back as `[secret masked]`, which is not
535
+ * a conduit and not a runtime, so it falls to the shape gate and refuses.
536
+ *
537
+ * @param {unknown} raw whatever `caller-chain` printed: names, one per line,
538
+ * nearest ancestor first. Only ever COMPARED against constants in this
539
+ * module, exactly as the tool enum is; a value reaches a log line by being
540
+ * RETURNED and only after surviving `callerFrom`.
541
+ * @returns {string} the asker's name, or UNIDENTIFIED.
542
+ */
543
+ export function callerFromChain(raw) {
544
+ let hops = 0;
545
+ for (const line of String(raw ?? "").split("\n")) {
546
+ const seen = mask(line).trim().toLowerCase();
547
+ if (seen === "") continue;
548
+
549
+ /* THE BRIDGE ALREADY CAPS THE WALK AT `ChainMax`. THIS SIDE CAPS IT AGAIN
550
+ ANYWAY, and not out of tidiness: `r.out` is a string this module did not
551
+ produce, and a reader of this loop should not have to go and read a C#
552
+ file to know it terminates. */
553
+ if (++hops > CHAIN_MAX) break;
554
+
555
+ if (seen === OWN_APP || CONDUIT.includes(seen)) continue;
556
+
557
+ /* THE FIRST NAME THAT IS NOT PLUMBING IS THE ANSWER, WHATEVER IT IS - and
558
+ `callerFrom` is what decides whether it is an answer af360 may say. A
559
+ runtime, a masked string and a name the shape gate refuses all come back
560
+ as the constant from there, so there is exactly one refusal path in this
561
+ module and this function does not own a second copy of it. */
562
+ return callerFrom(seen);
563
+ }
564
+ return UNIDENTIFIED;
565
+ }
566
+
272
567
  /**
273
568
  * The shape of every line this file can ever contain, as a regex, so the walk
274
569
  * can assert the whole file against it in one move rather than hunting for
package/package.json CHANGED
@@ -1,23 +1,22 @@
1
1
  {
2
2
  "name": "af360",
3
- "version": "0.1.4",
3
+ "version": "0.1.11",
4
4
  "description": "Watches the things you have built - folders, repos and sites - and tells you what needs you. Runs entirely on your own machine.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Afeleos LLC",
7
7
  "homepage": "https://afeleos.com",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/Afeleos-LLC/AF360.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/Afeleos-LLC/AF360/issues"
14
- },
8
+ "mcpName": "com.afeleos/af360",
15
9
  "keywords": [
16
10
  "local-first",
17
11
  "projects",
18
12
  "git",
19
13
  "monitoring",
20
- "privacy"
14
+ "privacy",
15
+ "mcp",
16
+ "model-context-protocol"
17
+ ],
18
+ "os": [
19
+ "win32"
21
20
  ],
22
21
  "bin": {
23
22
  "af360": "bin/af360.mjs"
Binary file