@passioncode-ai/fabric-agent-adapter 0.83.1 → 0.85.0

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 (129) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +2 -1
  3. package/package.json +1 -1
  4. package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  5. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
  6. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +3 -0
  7. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_identifiers.py +1 -0
  8. package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +1 -0
  9. package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +2 -2
  10. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +3 -2
  11. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +2 -1
  12. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +3 -2
  13. package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  14. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +8 -3
  15. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
  16. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +9 -1
  17. package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +3 -2
  18. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +3 -0
  19. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +1 -0
  20. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +1 -0
  21. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +1 -0
  22. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +1 -0
  23. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +1 -0
  24. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +1 -0
  25. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +1 -0
  26. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +1 -0
  27. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +2 -0
  28. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +1 -0
  29. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +9 -1
  30. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -0
  31. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +1 -0
  32. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +1 -0
  33. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +1 -0
  34. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +1 -0
  35. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +1 -0
  36. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +2 -1
  37. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate-rules.mjs +1 -0
  38. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +1 -0
  39. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +1 -0
  40. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +1 -0
  41. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +1 -0
  42. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +1 -0
  43. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-identifiers.mjs +1 -0
  44. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +1 -0
  45. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +6 -2
  46. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +1 -0
  47. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +1 -0
  48. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +1 -0
  49. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +1 -0
  50. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +4 -2
  51. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +1 -0
  52. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +1 -0
  53. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package-schema.mjs +1 -0
  54. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +1 -0
  55. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-page-links.mjs +1 -0
  56. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +1 -0
  57. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +2 -1
  58. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +1 -0
  59. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -0
  60. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -0
  61. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +1 -0
  62. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +1 -0
  63. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -0
  64. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +1 -0
  65. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +15 -3
  66. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +1 -0
  67. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet.mjs +1 -0
  68. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +1 -0
  69. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +1 -0
  70. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +1 -0
  71. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +1 -0
  72. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +1 -0
  73. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +1 -0
  74. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +1 -0
  75. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +1 -0
  76. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -0
  77. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +1 -0
  78. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +1 -0
  79. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +2 -0
  80. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +1 -0
  81. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +10 -1
  82. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +1 -0
  83. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +1 -0
  84. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +1 -0
  85. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +1 -0
  86. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +1 -0
  87. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +2 -1
  88. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +1 -0
  89. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.py +1 -0
  90. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +1 -0
  91. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +1 -0
  92. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +1 -0
  93. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +1 -0
  94. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_identifiers.py +1 -0
  95. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +2 -0
  96. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +1 -0
  97. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +3 -0
  98. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +1 -0
  99. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +1 -0
  100. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +1 -0
  101. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +20 -3
  102. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +1 -0
  103. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +1 -0
  104. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +1 -0
  105. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package_schema.py +1 -0
  106. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_page_links.py +1 -0
  107. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +1 -0
  108. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +47 -47
  109. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +1 -0
  110. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +1 -0
  111. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -0
  112. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +1 -0
  113. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +1 -0
  114. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +1 -0
  115. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +1 -0
  116. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +16 -8
  117. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet.py +1 -0
  118. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +1 -0
  119. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +1 -0
  120. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +1 -0
  121. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +428 -105
  122. package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +2 -1
  123. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  124. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +2 -2
  125. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +33 -13
  126. package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +3 -0
  127. package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
  128. package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
  129. package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
@@ -1,26 +1,25 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """peer_owner: which OS account owns each end of a loopback TCP connection.
2
3
 
3
- peer_owner 1.3.1+kit.7: Observatory's peer_owner (passioncode-ai/project-observatory-dashboard,
4
+ peer_owner 1.3.1+kit.8: Observatory's peer_owner (passioncode-ai/project-observatory-dashboard,
4
5
  `observatory/engine/peer_owner.py`, vendored at f86f0622, read against 2180235c for 1.2.0 and a69c6fee for 1.3.1),
5
- the kit's `fabric_peer_owner` (layer L0; DEC-0154 HA-2, FAA-226, FAA-258). 1.3.1's proof from this account's own
6
- sockets (DEC-0155 RT-2) is the kit's `fabric_socket_proof`, asked when it sits beside this file (FAA-292); without
7
- it this file stands alone, as before. The kit's differences: NamedTuples (a service may load a kit file by path);
8
- the Python 3.9 floor; `classify` / `classify_sid` ask for this process's uid or SID first (a client running as root
9
- owns root's listener); on Linux `server_end` reads an accepted connection's own row and, for one not accept()ed yet
10
- (uid 0, inode 0), the listener on its local address (CT-254), several rows of one owner are that owner, a row
11
- printed twice counts once, and a `/proc` question that finds no row or several reads once more. On macOS (FAA-267,
12
- FAA-273): the sysctl calibration also requires a child's listener, read under one deadline with `selectors`; a
13
- number is ASCII digits only; a caller may vouch for a listener with a pid it started (`pid_listens`,
14
- `CheckedHTTPConnection(vouch_pid=...)`, RT-3, the fallback after RT-2). DEC-0155 (FAA-312): a netstat that fails or
15
- is empty, a table with no row of another uid (sysctl) or process (netstat) and a missing connection row read as
16
- `table-restricted` (RT-1); a restricted table's listener is this account's only on exactly 127.0.0.1 (RT-4); no
17
- proof is taken from a set-uid reader. `main` is the CLI, `listener <port>` (FAA-315). /proc carries a socket's
18
- creator, so a listener root created and handed to another account (socket activation) reads as root's. Tests:
19
- `test/test_fabric_peer_owner.py`; the Node twin is `fabric-peer-owner.mjs`.
20
-
21
- WHY. Loopback is one per machine, not one per OS account. On a host with several accounts, every account's processes
22
- reach every service any account listens with on 127.0.0.1, and a loopback socket proves nothing about who is on the
23
- other end. This module answers that question from the host's socket table, read without privileges:
6
+ kit layer L0 (DEC-0154 HA-2, FAA-226, FAA-258). 1.3.1's proof from this account's own sockets (DEC-0155 RT-2) is the
7
+ kit's `fabric_socket_proof`, asked when it sits beside this file (FAA-292, FAA-327); else this file
8
+ stands alone. Kit differences: NamedTuples (a service may load a kit file by path); the Python 3.9 floor; `classify`
9
+ / `classify_sid` ask for this process's uid or SID first (a client running as root owns root's listener); on Linux
10
+ `server_end` reads an accepted connection's own row and, for one not accept()ed yet (uid 0, inode 0), the listener
11
+ on its local address (CT-254), several rows of one owner are that owner, a row printed twice counts once, and a
12
+ `/proc` question that finds no row or several reads once more. On macOS (FAA-267, FAA-273): the sysctl calibration
13
+ also requires a child's listener, read under one deadline with `selectors`; a number is ASCII digits only; a caller
14
+ may vouch for a listener with a pid it started (`pid_listens`, `CheckedHTTPConnection(vouch_pid=...)`, RT-3, after
15
+ RT-2). DEC-0155 (FAA-312): a netstat that fails or is empty, a table with no row of another uid (sysctl) or process
16
+ (netstat) and a missing connection row read as `table-restricted` (RT-1); a restricted table's listener is this
17
+ account's only on exactly 127.0.0.1 (RT-4); no proof is taken from a set-uid reader. `main` is the CLI, `listener
18
+ <port>` (FAA-315). /proc carries a socket's creator, so a listener root created and handed to another account
19
+ (socket activation) reads as root's. Tests: `test/test_fabric_peer_owner.py`; Node twin: `fabric-peer-owner.mjs`.
20
+
21
+ WHY. Loopback is one per machine, not per OS account: every account's processes reach every 127.0.0.1 service, and a
22
+ loopback socket proves nothing about the other end. This module reads the host's socket table, without privileges:
24
23
 
25
24
  listener(port) -> Owner who listens on 127.0.0.1:<port> (also 0.0.0.0, ::1, ::)
26
25
  server_end(sock) -> Owner for a client's connected socket: who accepted it
@@ -65,13 +64,13 @@ answer; two live rows with the same addresses are `unknown` (`several-rows`), ne
65
64
  cannot be opened (another account's, usually) is `unknown`, never `another-account`. `Owner.uid` is None there.
66
65
  - Anything else: `unknown`, reason `unsupported-platform`.
67
66
 
68
- `LOCAL_PEERCRED`, `getpeereid` and `SO_PEERCRED` answer this for Unix-domain sockets only, hence a socket table. It
69
- never reads a byte from the socket it is asked about, never resolves a name, never sends anything, never needs a
70
- privilege, and reports the process names `netstat` prints only as far as the pid behind them.
67
+ `LOCAL_PEERCRED`, `getpeereid` and `SO_PEERCRED` cover Unix-domain sockets only, hence a socket table. It never reads
68
+ from the asked socket, resolves a name, sends anything or needs a privilege; of `netstat`'s names, only the pid.
71
69
  """
72
70
  from __future__ import annotations
73
71
 
74
72
  import http.client
73
+ import importlib.util
75
74
  import ipaddress
76
75
  import json
77
76
  import os
@@ -83,8 +82,7 @@ import threading
83
82
  import time
84
83
  from typing import NamedTuple, Optional, Tuple
85
84
 
86
- # This file's own version, for projects that copy it; not the engine's (that is configuration.VERSION).
87
- PEER_OWNER_VERSION = "1.3.1+kit.7" # Observatory's 1.3.1 line with the kit's differences above
85
+ PEER_OWNER_VERSION = "1.3.1+kit.8" # this file's own, for projects that copy it: 1.3.1 with the kit's differences above
88
86
 
89
87
  THIS_ACCOUNT = "this-account"
90
88
  ANOTHER_ACCOUNT = "another-account"
@@ -93,8 +91,7 @@ UNKNOWN = "unknown"
93
91
  NONE = "none"
94
92
  KINDS = (THIS_ACCOUNT, ANOTHER_ACCOUNT, SYSTEM, UNKNOWN, NONE)
95
93
 
96
- #: The most table bytes one read accepts: 1 MB held about 1,200 sockets at `pcblist_n`'s 860 bytes each, which a busy
97
- #: developer machine passes; 4 MiB holds about 6,800 at 616 bytes measured on macOS 26 (FAA-260).
94
+ #: The most table bytes one read accepts: about 6,800 sockets at 616 bytes each, measured on macOS 26 (FAA-260).
98
95
  MAX_TABLE_BYTES = 4 * 1024 * 1024
99
96
  #: Every subprocess this module starts is stopped after this many seconds.
100
97
  TIMEOUT_SECONDS = 2.0
@@ -105,8 +102,7 @@ NETSTAT = "/usr/sbin/netstat"
105
102
  PS = "/bin/ps"
106
103
  LSOF = "/usr/sbin/lsof"
107
104
 
108
- #: The reason a socket table hides other processes' sockets from this one (FAA-267: macOS 27.2 shows an ad-hoc-signed
109
- #: interpreter, such as a Homebrew Python, only its own sockets through sysctl and nothing through netstat).
105
+ #: Why a table hides other processes' sockets (FAA-267: macOS 27.2 shows an ad-hoc-signed Python only its own).
110
106
  TABLE_RESTRICTED = "table-restricted"
111
107
  #: A macOS table that lists sockets but not the reader's own connection (DEC-0155 RT-1, FAA-312).
112
108
  NO_ROW_RESTRICTED = TABLE_RESTRICTED + ": no row for this connection"
@@ -117,8 +113,7 @@ SERVER_UNVERIFIED = "server-unverified"
117
113
  SERVER_NOT_THIS_WORKSPACE = "server-not-this-workspace"
118
114
 
119
115
 
120
- # The fields carry real types, not postponed strings: a NamedTuple's generated __new__ resolves its annotations in a
121
- # namespace without builtins, where `int | None` as a string is unresolvable (and a TypeError on Python 3.9).
116
+ # Real types, not postponed strings: a NamedTuple's __new__ cannot resolve `int | None` as a string (3.9: TypeError).
122
117
  _OwnerFields = NamedTuple("_OwnerFields", [("kind", str), ("uid", Optional[int]), ("pid", Optional[int]),
123
118
  ("method", str), ("reason", Optional[str])])
124
119
  _OwnerFields.__new__.__defaults__ = (None, None, "none", None)
@@ -229,8 +224,7 @@ def parse_netstat(text: str) -> list[Row]:
229
224
  leading = at - sum(1 for w in names[:at] if w == "Address")
230
225
  trailing = len(names) - at - 1
231
226
  if not any(line.split()[:1] and line.split()[0].startswith("tcp") for line in lines[header + 1:]):
232
- # The caller's own connection is in any table it can see: a header and no tcp row is the restricted table
233
- # (FAA-273 item 3), not `no-row`.
227
+ # A header and no tcp row is the restricted table, not `no-row`: the caller's own row is in any table (FAA-273)
234
228
  raise TableUnreadable(f"{TABLE_RESTRICTED}: netstat printed only its header to this process")
235
229
  rows: list[Row] = []
236
230
  for line in lines[header + 1:]:
@@ -532,8 +526,7 @@ def _child_listener_seen(rows_of) -> Optional[bool]:
532
526
  except (TableUnreadable, OSError, struct.error, ValueError):
533
527
  return False
534
528
  finally:
535
- # One deadline for the whole check: a child that gave no port is killed at once, and one that did gets what is
536
- # left of the deadline to exit on its closed stdin before it is killed.
529
+ # One deadline: a child that gave no port is killed at once; one that did has the rest to exit on a closed stdin
537
530
  try:
538
531
  child.stdin.close()
539
532
  if port is None:
@@ -790,9 +783,8 @@ def _owner_of(local: tuple[str, int], remote: tuple[str, int], server: bool = Fa
790
783
  if len(found) > 1:
791
784
  return Owner(UNKNOWN, None, None, how, "several-rows")
792
785
  if how == "proc" and server and found[0].uid is None:
793
- # Linux: a connection not accept()ed yet (uid 0, inode 0) belongs to the listener on its local address (exact, else
794
- # the wildcard) and dies with it; an accepted one's own row carries the accepting process's uid (sock_graft), so a
795
- # listener swapped after another account accepted cannot pass (CT-254). An orphaned server row reads so too.
786
+ # Linux: an unaccepted connection (uid 0, inode 0) or an orphaned server row is the listener's on its local address
787
+ # (exact, else wildcard); an accepted row holds the acceptor's uid (sock_graft): no listener swap passes (CT-254).
796
788
  return _one_owner(_listeners_of(rows, found[0].local), how, "not-accepted")
797
789
  if how == "proc" and found[0].uid is None:
798
790
  # A client's socket with inode 0 has already closed (0.74.0 unit reading 4): never another socket's owner.
@@ -830,10 +822,18 @@ def _proved(ask: str, *args, fallback: Owner, **kwargs) -> Owner:
830
822
  if fallback.kind != UNKNOWN or not (fallback.reason or "").startswith(TABLE_RESTRICTED) or sys.platform != "darwin":
831
823
  return fallback
832
824
  try:
833
- if _PROOF[0] is None:
834
- sys.path.append(os.path.dirname(os.path.abspath(__file__))) # beside this file, as the kit's modules load
835
- import fabric_socket_proof
836
- _PROOF[0] = fabric_socket_proof
825
+ if _PROOF[0] is None: # the file beside this one; sys.path stays the host's (FAA-327); alone, the table stands
826
+ here = os.path.realpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "fabric_socket_proof.py"))
827
+ if not os.path.isfile(here):
828
+ raise ImportError(here)
829
+ try: # the host's imported copy if it is that file (one calibration); the statement keeps it in the closure
830
+ import fabric_socket_proof as module
831
+ except ImportError:
832
+ module = None
833
+ if os.path.realpath(getattr(module, "__file__", None) or "-") != here: # else that file, by its path
834
+ spec = importlib.util.spec_from_file_location("fabric_socket_proof", here)
835
+ spec.loader.exec_module(module := importlib.util.module_from_spec(spec))
836
+ _PROOF[0] = module
837
837
  proof = getattr(_PROOF[0], ask)(*args, **kwargs) if _PROOF[0] else None
838
838
  except ImportError:
839
839
  _PROOF[0], proof = False, None
@@ -973,11 +973,11 @@ class CheckedHTTPConnection(http.client.HTTPConnection):
973
973
 
974
974
  def own_connection(port: int, *, expect_pid: Optional[int] = None, timeout: float = 10.0,
975
975
  host: str = "127.0.0.1", vouch_pid: Optional[int] = None) -> CheckedHTTPConnection:
976
- """A `CheckedHTTPConnection` to `host:port`, connected, whose server end is this account's (and, with `expect_pid`,
977
- that process's), checked on the very connection the caller sends on and on every reconnect; no proxy is consulted.
978
- ListenerRefused, the connection closed: `server-not-own` (another account's or the system's process accepted it),
979
- `server-unverified` (its owner could not be told: fail closed), `server-not-this-workspace` (not the pid expected).
980
- `vouch_pid`, as on `CheckedHTTPConnection` and the Node twin's `checkedSocket`: a pid the caller started (FAA-267)."""
976
+ """A `CheckedHTTPConnection` to `host:port`, connected, whose server end is this account's (with `expect_pid`, that
977
+ process's), checked on the connection the caller sends on and on every reconnect; no proxy is consulted.
978
+ ListenerRefused, closed: `server-not-own` (another account's or the system's process), `server-unverified` (owner
979
+ not told: fail closed), `server-not-this-workspace` (not the pid expected). `vouch_pid`: a pid the caller started
980
+ (FAA-267), as on `CheckedHTTPConnection` and the Node twin's `checkedSocket`."""
981
981
  conn = CheckedHTTPConnection(host, port, expect_pid=expect_pid, timeout=timeout, vouch_pid=vouch_pid)
982
982
  conn.connect()
983
983
  return conn
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """Windows, where the kit needs it (DEC-0032): named pipes that hold one copy, protecting a file to its owner, and
2
3
  reading a file's ACL to refuse a token file others can read. On other systems the same names exist and do nothing
3
4
  or answer "absent". Part of the service kit (FAA-65, layer L1)."""
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
2
3
  """Runner and model profiles in the kit (Fabric Agent Contract DEC-0046, Fabric ADR-0147). A port of the contract's
3
4
  `src/profile-rules.ts` at the pin — `resolve_model_profile` and the FAC-SEM-058…060 checks — held to the contract's own
4
5
  answers by test/fixtures/contract/profile-vectors.json; plus what a service does with them: the doctor's runner and
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """Protocol revisions (DEC-0067) and an event's machine-readable data (DEC-0070, the feed-data protocols): parse,
2
3
  accept, negotiate and check them. Part of the service kit (FAA-65, layer L3)."""
3
4
  from __future__ import annotations
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
2
3
  """The contract's one credential detector (Fabric Agent Contract DEC-0044, `src/secret-shape.ts` at the pin): a name
3
4
  that can only carry a credential, a value shaped like one, a launch argument that carries one, and a URL that does
4
5
  (LC-21). A port held to the contract's own answers by test/fixtures/contract/profile-vectors.json. Standard library
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """Reference kit for the fabric-service/0.1 local service extension: the facade.
2
3
 
3
4
  Since 0.40.0 (FAA-65) the kit is a set of modules, and this file re-exports every name it defined at 0.39.1, so
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
2
3
  """Share grants into an organization and their registrations (contract DEC-0086), the Python kit; the Node twin is
3
4
  fabric-share.mjs. Standard library only.
4
5
 
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """fabric_socket_proof: this OS account's own TCP sockets, read from its own processes (layer L0; DEC-0155 RT-2,
2
3
  FAA-292; FAA-272).
3
4
 
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """Supervisors (DEC-0032, LC-14): launchd plists, systemd user units and Task Scheduler tasks, installed and
2
3
  removed without touching what the operator turned off; release pruning (LC-15). Part of the service kit
3
4
  (FAA-65, layer L3)."""
@@ -25,6 +26,7 @@ from pathlib import Path as _Path # noqa: E402
25
26
  if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from any working directory (FAA-65)
26
27
  _sys.path.insert(0, str(_Path(__file__).resolve().parent))
27
28
  import fabric_peer_owner as _po # noqa: E402 - the listener's owner (DEC-0154 HA-2)
29
+ import fabric_socket_proof as _sp # noqa: E402 - the one loopback host rule (FAA-326)
28
30
  from fabric_base import (
29
31
  DRAIN_SECONDS,
30
32
  SUPERVISOR_ENV,
@@ -462,16 +464,20 @@ def launchd_pid(label: str, domain: Optional[str] = None) -> Optional[int]:
462
464
  return None
463
465
 
464
466
 
465
- def _loopback_host(host: Optional[str]) -> bool:
466
- """Whether `host` (urlsplit's hostname: lower-case, no brackets) names this machine: localhost, 127/8 or ::1."""
467
- if not host:
468
- return False
469
- if host == "localhost":
470
- return True
467
+ #: Whether urlsplit's hostname names this machine: the kit's one loopback rule (FAA-326), so 127.1, 2130706433 and a
468
+ #: name under .localhost, which the Node twin's `new URL` reads as loopback, take the checked path here too.
469
+ _loopback_host = _sp.loopback_host
470
+
471
+
472
+ def _loopback_otherwise(host: Optional[str]) -> bool:
473
+ """A loopback address the kit's rule does not take: one with a zone (`::1%lo0`) or IPv4-mapped (`::ffff:127.0.0.1`).
474
+ urlopen reaches loopback through either, so it is refused unread, as Node refuses it (0.84.0 seam reading)."""
471
475
  try:
472
- return ipaddress.ip_address(host).is_loopback
476
+ address = ipaddress.ip_address((host or "").split("%", 1)[0])
473
477
  except ValueError:
474
478
  return False
479
+ mapped = getattr(address, "ipv4_mapped", None)
480
+ return bool(address.is_loopback or (mapped and mapped.is_loopback))
475
481
 
476
482
 
477
483
  class ForeignUserAnswer:
@@ -498,7 +504,7 @@ def _read_well_known(origin: str, timeout: float = 2.0,
498
504
  except ValueError: # a port out of range
499
505
  port = None
500
506
  if parts.scheme == "http" and port and _loopback_host(parts.hostname):
501
- # every loopback spelling (127.0.0.1, another 127/8 address, localhost, [::1]) goes on a checked connection,
507
+ # every loopback spelling (127/8 in any form, localhost or a name under it, [::1]) goes on a checked connection,
502
508
  # never the unchecked way (FAA-257)
503
509
  conn = _po.CheckedHTTPConnection(parts.hostname, port, timeout=timeout, vouch_pid=vouch_pid)
504
510
  try:
@@ -512,6 +518,8 @@ def _read_well_known(origin: str, timeout: float = 2.0,
512
518
  return None, None
513
519
  finally:
514
520
  conn.close()
521
+ if _loopback_otherwise(parts.hostname):
522
+ return None, None
515
523
  try:
516
524
  with urllib.request.urlopen(origin + "/.well-known/fabric-service", timeout=timeout) as response:
517
525
  return json.loads(response.read().decode()), None
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
2
3
  """Who is calling an agent that sits behind `tailscale serve`, and what that person may open (FAA-297). Standard
3
4
  library only; the Node twin is fabric-tailnet.mjs, and both answer test/fixtures/tailnet/vectors.json alike.
4
5
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
2
3
  """A person's identity at every door of a service published through `tailscale serve` (FAA-324). Standard library
3
4
  only; the Node twin is fabric-tailnet-identity.mjs, and both answer test/fixtures/tailnet-identity/vectors.json alike.
4
5
 
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """Dates and times the way the contract reads them, once for the whole kit (FAA-65, layer L0).
2
3
 
3
4
  - ``parse_rfc3339_ms(text)``: strict RFC 3339 (a ``T``, an offset, a day that exists; no space, no leap second) as
@@ -1,3 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
1
2
  """What a service spends on models (DEC-0021, DEC-0065, DEC-0091): usage receipts, the bounded ledger, the usage
2
3
  report and its rules (FAC-SEM-025), OpenRouter attribution (FAC-SEM-098) and every spending limit (DEC-0027).
3
4
  Part of the service kit (FAA-65, layer L3)."""