@passioncode-ai/fabric-agent-adapter 0.74.0 → 0.76.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.
- package/CHANGELOG.md +115 -0
- package/README.md +9 -4
- package/SECURITY.md +5 -1
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +3 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +10 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +6 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/channel-agent.example.json +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +5 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +11 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +42 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +31 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +103 -10
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +46 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +192 -8
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +6 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +6 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/communication-agent-template.md +380 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +8 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +3 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/practices.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""peer_owner: which OS account owns each end of a loopback TCP connection.
|
|
2
2
|
|
|
3
|
-
peer_owner 1.1.0+kit.
|
|
3
|
+
peer_owner 1.1.0+kit.2: Observatory's peer_owner 1.1.0, vendored into the Fabric service kit as `fabric_peer_owner` (layer L0;
|
|
4
4
|
DEC-0154 HA-2, FAA-226) from Project Observatory's `observatory/engine/peer_owner.py` at
|
|
5
5
|
commit f86f0622 (passioncode-ai/project-observatory-dashboard, the same licence). It differs
|
|
6
6
|
from that file in this paragraph, in `Owner` and `Row` (NamedTuples here where it has
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
row with inode 0 (a closed client) as unknown, and several rows of one owner are that owner;
|
|
16
16
|
a row printed twice by a `/proc` read under churn counts once, and a `/proc` question that
|
|
17
17
|
finds no row or several reads the table once more; ctypes is imported where it is used, so a
|
|
18
|
-
Python without it reads netstat on macOS and imports the module everywhere.
|
|
18
|
+
Python without it reads netstat on macOS and imports the module everywhere. Since kit.2 (FAA-267), on
|
|
19
|
+
macOS: the sysctl calibration also requires a child process's listener, an empty netstat reads as
|
|
20
|
+
`TABLE_RESTRICTED`, and a caller may vouch for a listener with a pid it started (`pid_listens`,
|
|
21
|
+
`parse_lsof_listeners`, `LSOF`, `CheckedHTTPConnection(vouch_pid=...)`).
|
|
19
22
|
A limit /proc sets: it carries a socket's creator and no pid, so a listening socket that root
|
|
20
23
|
created and handed to another account (systemd socket activation with User=) reads as root's. Standard library only, no import of another kit module.
|
|
21
24
|
The kit's tests are `test/test_fabric_peer_owner.py`, over `test/fixtures/peer-owner/`;
|
|
@@ -64,6 +67,12 @@ addresses are `unknown` (`several-rows`), never a guess.
|
|
|
64
67
|
accept) and the listener and both ends of the pair must read back with their exact addresses,
|
|
65
68
|
this process's uid and its pid. A layout that fails calibration is not used again in that
|
|
66
69
|
process.
|
|
70
|
+
- macOS 27.2 restricts both tables for an ad-hoc-signed interpreter (a Homebrew Python, measured on 2026-11-07 on a
|
|
71
|
+
host agent's Mac): sysctl lists only this process's own sockets and netstat prints nothing. The calibration therefore
|
|
72
|
+
also requires a child process's listener, and an empty netstat is `unknown` with reason `table-restricted`, never
|
|
73
|
+
another account's (FAA-267). A caller that started the server itself (an installer and its launchd job) may vouch
|
|
74
|
+
with that pid: `lsof -p PID` asks the process, which the restriction leaves readable, and `CheckedHTTPConnection`
|
|
75
|
+
accepts its listener on the port as this account's (`vouch_pid`). Apple's own `/usr/bin/python3` sees the tables.
|
|
67
76
|
- macOS, fallback (`netstat`): `/usr/sbin/netstat -anv -p tcp`, its `process:pid` column, then
|
|
68
77
|
`ps -o uid= -p PID` for the owner of the one row a question needs. Measured: about 20 ms over
|
|
69
78
|
846 sockets, and it names the pids of root's listeners, which `lsof` run by an ordinary account
|
|
@@ -100,7 +109,7 @@ import time
|
|
|
100
109
|
from typing import NamedTuple, Optional, Tuple
|
|
101
110
|
|
|
102
111
|
# This file's own version, for projects that copy it; not the engine's (that is configuration.VERSION).
|
|
103
|
-
PEER_OWNER_VERSION = "1.1.0+kit.
|
|
112
|
+
PEER_OWNER_VERSION = "1.1.0+kit.2" # Observatory's 1.1.0 with the kit's differences above
|
|
104
113
|
|
|
105
114
|
THIS_ACCOUNT = "this-account"
|
|
106
115
|
ANOTHER_ACCOUNT = "another-account"
|
|
@@ -120,6 +129,11 @@ LISTENER_TTL = 1.0
|
|
|
120
129
|
|
|
121
130
|
NETSTAT = "/usr/sbin/netstat"
|
|
122
131
|
PS = "/bin/ps"
|
|
132
|
+
LSOF = "/usr/sbin/lsof"
|
|
133
|
+
|
|
134
|
+
#: The reason a socket table hides other processes' sockets from this one (FAA-267: macOS 27.2 shows an ad-hoc-signed
|
|
135
|
+
#: interpreter, such as a Homebrew Python, only its own sockets through sysctl and nothing through netstat).
|
|
136
|
+
TABLE_RESTRICTED = "table-restricted"
|
|
123
137
|
|
|
124
138
|
#: Client codes of `own_connection` (ListenerRefused.code).
|
|
125
139
|
SERVER_NOT_OWN = "server-not-own"
|
|
@@ -221,6 +235,8 @@ def parse_netstat(text: str) -> list[Row]:
|
|
|
221
235
|
`process:pid` column is what is left between the leading and the trailing columns, both
|
|
222
236
|
counted from the header. A header without `process:pid` (an older macOS) is unreadable."""
|
|
223
237
|
lines = text.splitlines()
|
|
238
|
+
if not any(line.strip() for line in lines):
|
|
239
|
+
raise TableUnreadable(f"{TABLE_RESTRICTED}: netstat printed nothing to this process")
|
|
224
240
|
header = next((i for i, line in enumerate(lines) if "process:pid" in line and "Proto" in line), None)
|
|
225
241
|
if header is None:
|
|
226
242
|
raise TableUnreadable("netstat-format: no process:pid column")
|
|
@@ -259,6 +275,18 @@ def _netstat_address(text: str) -> Optional[tuple[str, int]]:
|
|
|
259
275
|
return None
|
|
260
276
|
|
|
261
277
|
|
|
278
|
+
def parse_lsof_listeners(text: str) -> list[tuple[str, int]]:
|
|
279
|
+
"""`lsof -nP -a -p PID -i4TCP -sTCP:LISTEN -Fn`: the `n` lines, `127.0.0.1:47480`, `*:47480` or `[::1]:47480`."""
|
|
280
|
+
found: list[tuple[str, int]] = []
|
|
281
|
+
for line in text.splitlines():
|
|
282
|
+
if not line.startswith("n") or ":" not in line:
|
|
283
|
+
continue
|
|
284
|
+
host, port = line[1:].rsplit(":", 1)
|
|
285
|
+
if port.isdigit():
|
|
286
|
+
found.append((norm_host(host.strip("[]")), int(port)))
|
|
287
|
+
return found
|
|
288
|
+
|
|
289
|
+
|
|
262
290
|
def parse_ps_uid(text: str) -> Optional[int]:
|
|
263
291
|
"""`ps -o uid= -p PID`: one number, or nothing when the process is gone."""
|
|
264
292
|
text = text.strip()
|
|
@@ -468,10 +496,47 @@ def calibration_holds(rows: list[Row], server: tuple[str, int], client: tuple[st
|
|
|
468
496
|
return (one(server, ("*", 0), True) and one(server, client, False) and one(client, server, False))
|
|
469
497
|
|
|
470
498
|
|
|
499
|
+
_CHILD_LISTENER = ("import socket,sys\ns=socket.socket();s.bind(('127.0.0.1',0));s.listen(1)\n"
|
|
500
|
+
"print(s.getsockname()[1],flush=True);sys.stdin.read()")
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _child_listener_seen(rows_of) -> Optional[bool]:
|
|
504
|
+
"""Whether the table shows a listener of a child process of this one: False when it does not (the table shows
|
|
505
|
+
this process alone, FAA-267), None when no child could be started or did not answer within TIMEOUT_SECONDS (a
|
|
506
|
+
frozen or embedded host has no interpreter to start). The calibration trusts only True."""
|
|
507
|
+
if not sys.executable or getattr(sys, "frozen", False):
|
|
508
|
+
return None
|
|
509
|
+
try:
|
|
510
|
+
child = subprocess.Popen([sys.executable, "-I", "-c", _CHILD_LISTENER], stdin=subprocess.PIPE,
|
|
511
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
|
512
|
+
except OSError:
|
|
513
|
+
return None
|
|
514
|
+
try:
|
|
515
|
+
import select # POSIX: this reader runs on macOS alone
|
|
516
|
+
if not select.select([child.stdout], [], [], TIMEOUT_SECONDS)[0]:
|
|
517
|
+
return None
|
|
518
|
+
line = child.stdout.readline().strip()
|
|
519
|
+
if not line.isdigit():
|
|
520
|
+
return None
|
|
521
|
+
port = int(line)
|
|
522
|
+
return any(r.listening and r.local == ("127.0.0.1", port) and r.pid == child.pid for r in rows_of())
|
|
523
|
+
except (TableUnreadable, OSError, struct.error, ValueError):
|
|
524
|
+
return False
|
|
525
|
+
finally:
|
|
526
|
+
try:
|
|
527
|
+
child.stdin.close()
|
|
528
|
+
child.wait(timeout=TIMEOUT_SECONDS)
|
|
529
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
530
|
+
child.kill()
|
|
531
|
+
child.wait()
|
|
532
|
+
child.stdout.close()
|
|
533
|
+
|
|
534
|
+
|
|
471
535
|
def _calibrate_sysctl() -> bool:
|
|
472
536
|
"""Open a connected pair of this process's own (listen, connect, accept) and read it back
|
|
473
|
-
(`calibration_holds`)
|
|
474
|
-
|
|
537
|
+
(`calibration_holds`), then require a child process's listener in the table too: a table that shows this process
|
|
538
|
+
alone (FAA-267) would pass the first check and find nobody else. A layout that does not hold is never trusted in
|
|
539
|
+
this process, and `netstat` answers instead."""
|
|
475
540
|
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
476
541
|
client = accepted = None
|
|
477
542
|
try:
|
|
@@ -480,8 +545,10 @@ def _calibrate_sysctl() -> bool:
|
|
|
480
545
|
client = socket.create_connection(probe.getsockname(), timeout=TIMEOUT_SECONDS)
|
|
481
546
|
accepted, _ = probe.accept()
|
|
482
547
|
rows = parse_pcblist_n(_sysctl_blob())
|
|
483
|
-
|
|
484
|
-
uid=_posix_uid(), pid=os.getpid())
|
|
548
|
+
if not calibration_holds(rows, _address(probe.getsockname()), _address(client.getsockname()),
|
|
549
|
+
uid=_posix_uid(), pid=os.getpid()):
|
|
550
|
+
return False
|
|
551
|
+
return _child_listener_seen(lambda: parse_pcblist_n(_sysctl_blob())) is True
|
|
485
552
|
except (TableUnreadable, OSError, struct.error, ValueError):
|
|
486
553
|
return False
|
|
487
554
|
finally:
|
|
@@ -805,6 +872,23 @@ def refusal(owner: Owner, port: int, expect_pid: Optional[int] = None) -> Option
|
|
|
805
872
|
return None
|
|
806
873
|
|
|
807
874
|
|
|
875
|
+
def pid_listens(pid: int, port: int) -> bool:
|
|
876
|
+
"""macOS, when the socket table is restricted (FAA-267): whether process `pid`, this account's, listens on
|
|
877
|
+
127.0.0.1:<port> or the IPv4 wildcard (IPv4 alone: a v6-only wildcard never vouches for a 127.0.0.1 connection).
|
|
878
|
+
`lsof -p` asks the process itself (libproc), which the restriction does not hide. Only a caller that started `pid`
|
|
879
|
+
itself (a launchd job it installed) may vouch with it."""
|
|
880
|
+
if sys.platform != "darwin" or not isinstance(pid, int) or isinstance(pid, bool) or pid <= 1:
|
|
881
|
+
return False
|
|
882
|
+
try:
|
|
883
|
+
uid = parse_ps_uid(_run([PS, "-o", "uid=", "-p", str(pid)]))
|
|
884
|
+
if uid is None or uid != _posix_uid():
|
|
885
|
+
return False
|
|
886
|
+
out = _run([LSOF, "-nP", "-a", "-p", str(pid), "-i4TCP", "-sTCP:LISTEN", "-Fn"])
|
|
887
|
+
except TableUnreadable:
|
|
888
|
+
return False
|
|
889
|
+
return any(host in ("127.0.0.1", "*") and p == port for host, p in parse_lsof_listeners(out))
|
|
890
|
+
|
|
891
|
+
|
|
808
892
|
class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
809
893
|
"""An `http.client.HTTPConnection` whose every `connect()` checks the server end before
|
|
810
894
|
returning. `http.client` connects again by itself (`auto_open`) when the server answered
|
|
@@ -812,9 +896,11 @@ class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
|
812
896
|
different listener; so the check is in `connect()`, not once in `own_connection`. A refused
|
|
813
897
|
connect closes the socket and raises ListenerRefused before any byte of a request is sent."""
|
|
814
898
|
|
|
815
|
-
def __init__(self, host: str, port: int, *, expect_pid: Optional[int] = None, timeout: float = 10.0
|
|
899
|
+
def __init__(self, host: str, port: int, *, expect_pid: Optional[int] = None, timeout: float = 10.0,
|
|
900
|
+
vouch_pid: Optional[int] = None) -> None:
|
|
816
901
|
super().__init__(host, port, timeout=timeout)
|
|
817
902
|
self.expect_pid = expect_pid
|
|
903
|
+
self.vouch_pid = vouch_pid
|
|
818
904
|
|
|
819
905
|
def set_tunnel(self, *args, **kwargs): # noqa: D401 - a tunnel would put a proxy's socket under the check
|
|
820
906
|
raise ListenerRefused(SERVER_UNVERIFIED, "a tunnel would check the proxy, not the server; nothing was sent")
|
|
@@ -822,6 +908,10 @@ class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
|
822
908
|
def connect(self) -> None:
|
|
823
909
|
super().connect()
|
|
824
910
|
owner = server_end(self.sock)
|
|
911
|
+
if (owner.kind == UNKNOWN and (owner.reason or "").startswith(TABLE_RESTRICTED) and getattr(self, "vouch_pid", None)
|
|
912
|
+
and pid_listens(self.vouch_pid, self.port)):
|
|
913
|
+
# The table hides other processes (FAA-267); the process this caller started holds the listener.
|
|
914
|
+
owner = Owner(THIS_ACCOUNT, _posix_uid(), self.vouch_pid, "lsof-pid")
|
|
825
915
|
refused = refusal(owner, self.port, self.expect_pid)
|
|
826
916
|
if refused is not None:
|
|
827
917
|
self.close()
|
|
@@ -829,7 +919,7 @@ class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
|
829
919
|
|
|
830
920
|
|
|
831
921
|
def own_connection(port: int, *, expect_pid: Optional[int] = None, timeout: float = 10.0,
|
|
832
|
-
host: str = "127.0.0.1") -> CheckedHTTPConnection:
|
|
922
|
+
host: str = "127.0.0.1", vouch_pid: Optional[int] = None) -> CheckedHTTPConnection:
|
|
833
923
|
"""A `CheckedHTTPConnection` to `host:port`, connected, whose server end is this account's
|
|
834
924
|
(and, with `expect_pid`, that process's). The check runs on the very connection the caller
|
|
835
925
|
then sends on, so no listener can change between the check and the request, and it runs
|
|
@@ -839,7 +929,10 @@ def own_connection(port: int, *, expect_pid: Optional[int] = None, timeout: floa
|
|
|
839
929
|
server-not-own another account's or the system's process accepted it
|
|
840
930
|
server-unverified its owner could not be told (nothing is sent: fail closed)
|
|
841
931
|
server-not-this-workspace this account's process, but not the pid expected
|
|
932
|
+
|
|
933
|
+
`vouch_pid`, as on `CheckedHTTPConnection` and the Node twin's `checkedSocket`: a pid the caller started
|
|
934
|
+
(FAA-267).
|
|
842
935
|
"""
|
|
843
|
-
conn = CheckedHTTPConnection(host, port, expect_pid=expect_pid, timeout=timeout)
|
|
936
|
+
conn = CheckedHTTPConnection(host, port, expect_pid=expect_pid, timeout=timeout, vouch_pid=vouch_pid)
|
|
844
937
|
conn.connect()
|
|
845
938
|
return conn
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py
CHANGED
|
@@ -53,6 +53,20 @@ def _no_marker_argument(argv: Sequence[str]) -> None:
|
|
|
53
53
|
raise ServiceError("%s is written by the supervisor file's writer; leave it out of the program arguments" % SUPERVISOR_ARG)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
_PATH_ANCHOR = re.compile(r"^(?:[/~.]|[A-Za-z]:[\\/])")
|
|
57
|
+
_PATH_WORD = re.compile(r"[a-z][a-z0-9_-]{2,}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _secret_env_value(value: str) -> bool:
|
|
61
|
+
"""An environment value: read path-aware (`secret_argument`) only when it reads as a path (it starts as one, has at
|
|
62
|
+
least two separators and a lower-case word between them), so a release folder under Application Support is no key
|
|
63
|
+
(FAA-266) while a base64 key whose `/` splits it stays one; any other value keeps the strict `secret_value`."""
|
|
64
|
+
import fabric_secrets # noqa: E402 - beside this file; the contract's detector (DEC-0044)
|
|
65
|
+
pieces = re.split(r"[\\/]", value)
|
|
66
|
+
path = bool(_PATH_ANCHOR.match(value)) and len(pieces) >= 3 and any(_PATH_WORD.fullmatch(x) for x in pieces)
|
|
67
|
+
return fabric_secrets.secret_argument(value) if path else fabric_secrets.secret_value(value)
|
|
68
|
+
|
|
69
|
+
|
|
56
70
|
def _no_secret_in_supervisor_file(argv: Sequence[str], environment: Optional[Dict[str, str]]) -> None:
|
|
57
71
|
"""No supervisor file carries a secret (service.md): not in an argument, an environment name or its value."""
|
|
58
72
|
import fabric_secrets # noqa: E402 - beside this file; the contract's detector (DEC-0044)
|
|
@@ -60,7 +74,7 @@ def _no_secret_in_supervisor_file(argv: Sequence[str], environment: Optional[Dic
|
|
|
60
74
|
if fabric_secrets.secret_argument(arg):
|
|
61
75
|
raise ServiceError("an argument carries a credential; a supervisor file never does (read it from the token file or a secret reference)")
|
|
62
76
|
for name, value in (environment or {}).items():
|
|
63
|
-
if fabric_secrets.credential_name(name) or
|
|
77
|
+
if fabric_secrets.credential_name(name) or _secret_env_value(str(value)):
|
|
64
78
|
raise ServiceError("environment %s looks like a credential; a supervisor file never carries one" % name)
|
|
65
79
|
|
|
66
80
|
|
|
@@ -263,7 +277,7 @@ def launchd_plist(label: str, program_arguments: Sequence[str], *, working_direc
|
|
|
263
277
|
for key, value in env.items():
|
|
264
278
|
if re.search(r"(TOKEN|SECRET|PASSWORD|KEY)\Z", key) and not key.endswith("_FILE"):
|
|
265
279
|
raise ServiceError("Environment variable %s looks like a secret; pass a *_FILE path instead." % key)
|
|
266
|
-
if
|
|
280
|
+
if _secret_env_value(str(value)): # path-aware only for a path: a release folder is no key (FAA-266)
|
|
267
281
|
raise ServiceError("Environment %s holds a value shaped like a credential; a plist never carries one." % key)
|
|
268
282
|
for arg in argv:
|
|
269
283
|
if fabric_secrets.secret_argument(arg):
|
|
@@ -307,19 +321,37 @@ def launchd_loaded(label: str) -> bool:
|
|
|
307
321
|
return _launchctl("print", "%s/%s" % (_domain(), label)).returncode == 0
|
|
308
322
|
|
|
309
323
|
|
|
310
|
-
|
|
324
|
+
_PID_ROW = re.compile(r"^\s*pid\s*=\s*(\d+)\s*\Z")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def launchd_pid(label: str) -> Optional[int]:
|
|
328
|
+
"""The running pid of this account's launchd job ``label``, or None (not running, or not readable)."""
|
|
329
|
+
result = _launchctl("print", "%s/%s" % (_domain(), label))
|
|
330
|
+
if result.returncode != 0:
|
|
331
|
+
return None
|
|
332
|
+
for line in (result.stdout or "").splitlines():
|
|
333
|
+
hit = _PID_ROW.match(line)
|
|
334
|
+
if hit:
|
|
335
|
+
return int(hit.group(1))
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _read_well_known(origin: str, timeout: float = 2.0,
|
|
340
|
+
vouch_pid: Optional[int] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
|
|
311
341
|
"""(the well-known answer or None, why the listener was refused or None). A loopback origin is read only on a
|
|
312
342
|
connection whose server end is this OS account's process (DEC-0154 HA-2): another account's, root's, or one whose
|
|
313
|
-
owner cannot be told is `foreign-user`, never this service's answer.
|
|
343
|
+
owner cannot be told is `foreign-user`, never this service's answer. `vouch_pid`, the pid of a launchd job this
|
|
344
|
+
caller started, proves the listener when the socket table hides other processes (FAA-267)."""
|
|
314
345
|
parts = urllib.parse.urlsplit(origin)
|
|
315
346
|
if parts.scheme == "http" and parts.hostname == "127.0.0.1" and parts.port:
|
|
316
|
-
conn = _po.CheckedHTTPConnection("127.0.0.1", parts.port, timeout=timeout)
|
|
347
|
+
conn = _po.CheckedHTTPConnection("127.0.0.1", parts.port, timeout=timeout, vouch_pid=vouch_pid)
|
|
317
348
|
try:
|
|
318
349
|
conn.request("GET", "/.well-known/fabric-service", headers={"Host": "127.0.0.1:%d" % parts.port})
|
|
319
350
|
response = conn.getresponse()
|
|
320
351
|
return (json.loads(response.read().decode()) if response.status == 200 else None), None
|
|
321
352
|
except _po.ListenerRefused as exc:
|
|
322
|
-
|
|
353
|
+
restricted = exc.owner is not None and (exc.owner.reason or "").startswith(_po.TABLE_RESTRICTED)
|
|
354
|
+
return None, "%s: %s" % (_po.TABLE_RESTRICTED if restricted else "foreign-user", exc)
|
|
323
355
|
except (OSError, ValueError, http.client.HTTPException):
|
|
324
356
|
return None, None
|
|
325
357
|
finally:
|
|
@@ -392,14 +424,20 @@ def launchd_install(label: str, plist_path: Path, plist_bytes: bytes, *, origin:
|
|
|
392
424
|
else:
|
|
393
425
|
raise ServiceError("launchctl bootstrap failed: %s" % last)
|
|
394
426
|
deadline = time.time() + timeout
|
|
395
|
-
foreign = None
|
|
427
|
+
foreign = vouch = None
|
|
396
428
|
while time.time() < deadline:
|
|
397
|
-
doc, foreign = _read_well_known(origin)
|
|
429
|
+
doc, foreign = _read_well_known(origin, vouch_pid=vouch)
|
|
430
|
+
if foreign and foreign.startswith(_po.TABLE_RESTRICTED):
|
|
431
|
+
vouch = launchd_pid(label) # only a restricted table needs the job's pid (FAA-267)
|
|
398
432
|
if doc and doc.get("service", {}).get("id") == service_id and doc.get("service", {}).get("instance") == instance:
|
|
399
433
|
return doc
|
|
400
434
|
time.sleep(0.5)
|
|
401
435
|
if foreign:
|
|
402
436
|
# DEC-0154 HA-2 and service.md's Install row: a listener of another account is never a successful install.
|
|
437
|
+
if foreign.startswith(_po.TABLE_RESTRICTED):
|
|
438
|
+
raise ServiceError("%s could not be proved this service's: %s (DEC-0154 HA-2); this interpreter sees no other "
|
|
439
|
+
"process's sockets and the job's pid did not show the listener: run the installer from "
|
|
440
|
+
"Apple's /usr/bin/python3, or read the job's log." % (origin, foreign))
|
|
403
441
|
raise ServiceError("%s is not this service's: %s (DEC-0154 HA-2); the port is another OS account's or its owner "
|
|
404
442
|
"cannot be told." % (origin, foreign))
|
|
405
443
|
raise ServiceError("%s did not answer as %s.%s within %.0f s; read its log." % (origin, service_id, instance, timeout))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabric-service-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.0",
|
|
4
4
|
"about": "The service kit's modules in dependency order: a module imports only modules listed before it. Written by fabric_kit.py build; edit the order and layers by hand, never the digests.",
|
|
5
5
|
"python": [
|
|
6
6
|
{
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"file": "fabric_peer_owner.py",
|
|
39
39
|
"layer": "L0",
|
|
40
40
|
"imports": [],
|
|
41
|
-
"sha256": "
|
|
41
|
+
"sha256": "628c8e56eb067ecb7c343a606f8c75661257d7f349c47d9628449850d41d1f78"
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"module": "fabric_crypto",
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
"fabric_peer_owner",
|
|
339
339
|
"fabric_secrets"
|
|
340
340
|
],
|
|
341
|
-
"sha256": "
|
|
341
|
+
"sha256": "1ebf842de248ff0f154939ae6c9d5d0229b3358aecaefea07a80ed1b8755ac70"
|
|
342
342
|
},
|
|
343
343
|
{
|
|
344
344
|
"module": "fabric_service",
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
"fabric_time",
|
|
363
363
|
"fabric_usage"
|
|
364
364
|
],
|
|
365
|
-
"sha256": "
|
|
365
|
+
"sha256": "73082f69844739708732f6331742e00f17d27385d3c3efa3331a1fd61b523595"
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"module": "fabric_profiles",
|
|
@@ -494,7 +494,7 @@
|
|
|
494
494
|
"fabric_jsvalue",
|
|
495
495
|
"fabric_peer_owner"
|
|
496
496
|
],
|
|
497
|
-
"sha256": "
|
|
497
|
+
"sha256": "b11b9930c3f0ab911a9cd325086a4c07a5ed4c246799d290f6085245669f4994"
|
|
498
498
|
},
|
|
499
499
|
{
|
|
500
500
|
"module": "fabric_openrouter",
|
|
@@ -542,7 +542,7 @@
|
|
|
542
542
|
"file": "fabric-peer-owner.mjs",
|
|
543
543
|
"layer": "L0",
|
|
544
544
|
"imports": [],
|
|
545
|
-
"sha256": "
|
|
545
|
+
"sha256": "0d5a52385127546dea797e2dec36daeec6e05b04fdef04c1d8fd1ecf8db1a4e8"
|
|
546
546
|
},
|
|
547
547
|
{
|
|
548
548
|
"module": "fabric-crypto",
|
|
@@ -841,7 +841,7 @@
|
|
|
841
841
|
"fabric-peer-owner",
|
|
842
842
|
"fabric-secrets"
|
|
843
843
|
],
|
|
844
|
-
"sha256": "
|
|
844
|
+
"sha256": "de6468aa2876be437a374499dd084281a10d62c3fd23bd7c65285da3ee282ce2"
|
|
845
845
|
},
|
|
846
846
|
{
|
|
847
847
|
"module": "fabric-service",
|
|
@@ -863,7 +863,7 @@
|
|
|
863
863
|
"fabric-supervisors",
|
|
864
864
|
"fabric-usage"
|
|
865
865
|
],
|
|
866
|
-
"sha256": "
|
|
866
|
+
"sha256": "2fec45eb56d4a9ddc479f05d99abda4d2bb324746e94c156d96157462b824de0"
|
|
867
867
|
},
|
|
868
868
|
{
|
|
869
869
|
"module": "fabric-profiles",
|
|
@@ -5969,6 +5969,190 @@
|
|
|
5969
5969
|
"fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
|
|
5970
5970
|
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
5971
5971
|
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
5972
|
+
},
|
|
5973
|
+
"0.75.0": {
|
|
5974
|
+
"fabric_jsvalue.py": "052f1b40bab18600b3731dbdca27cbff17588c21d9458fa6241efd1340d0e09a",
|
|
5975
|
+
"fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
|
|
5976
|
+
"fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
|
|
5977
|
+
"fabric_secrets.py": "53c5df8d6f2c87433c288853bfaad9a43f544789bdf1e39bda8d3ff14485c89d",
|
|
5978
|
+
"fabric_peer_owner.py": "3d66b1084c0b8b11ccfc4c66c1b32751ea237df03d864bfc0661d1a2e4665338",
|
|
5979
|
+
"fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
|
|
5980
|
+
"fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
|
|
5981
|
+
"fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
|
|
5982
|
+
"fabric_paths.py": "8a1ee742f80f436e9f28c61b4f049a9545de2a9e0d66bfc61faa6ee5d1ba3734",
|
|
5983
|
+
"fabric_estate_rules.py": "8b36a7f4108d9c2b1fb38c8bff344778c807bed631a99b4a563ecb0a49c10b1c",
|
|
5984
|
+
"fabric_estate.py": "0737ae1d1bb12586bae49de1f15ef1348bc207b5007cced9dad4c55bb5870874",
|
|
5985
|
+
"fabric_mandate.py": "fb19ce5359606bd6e3352ab02b2a12f49605ec6e5c49b55cce1f79c831325044",
|
|
5986
|
+
"fabric_delegation.py": "716f0ff7ac01095f54b24970beac1962128e8ff33026b243fc0467876142ab03",
|
|
5987
|
+
"fabric_share.py": "da36a466426fc29dd7dd40013167f3959a18375f8fedd228bf64f298cec1040c",
|
|
5988
|
+
"fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
|
|
5989
|
+
"fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
|
|
5990
|
+
"fabric_package.py": "1f2b9e73b2b32a7e1e887d4b69f87c6e21b4d1227ad924a235334223e6dd51ca",
|
|
5991
|
+
"fabric_protocols.py": "077c6f03f3b887a2e22d9ce6b5bd3750199d71a29d4c82b1d5af8a615b509d93",
|
|
5992
|
+
"fabric_actions.py": "ae21d1199614c4f74d1dcd1e0a87107a9d52434cd9e2d64aa24a7b1e196fb723",
|
|
5993
|
+
"fabric_connections.py": "7de55cb7d808f756d650effabfaa12027d40a5bd5d2f6d448c32969d47529ecb",
|
|
5994
|
+
"fabric_a2a.py": "8b01502131215bb4de1c01866187c56e99b791543923ed8577535573db33b919",
|
|
5995
|
+
"fabric_page_links.py": "1b57ea573c550e2f90d1ec3edcc655917ceaa0db9dd1635fccf422ec3024d25e",
|
|
5996
|
+
"fabric_lock.py": "dfaf92a7086cbb097d832a305869b7964e2d2a9da0e3365c8ec1685a7bb4fef9",
|
|
5997
|
+
"fabric_usage.py": "5cc2e03ccc5b06e81987ed7bc3eead7a7e6cbf1a7cb8e05c6c64315eed9d91b6",
|
|
5998
|
+
"fabric_backups.py": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420",
|
|
5999
|
+
"fabric_descriptor.py": "c08c91ca0960c694ed7677e7e9247d6307c7b63f37a7e614db022c080cd92ccc",
|
|
6000
|
+
"fabric_context.py": "6ae311ddc7217a4aff915e7ce2646486931acd128462b7bebe4a52bbb5fbb6c7",
|
|
6001
|
+
"fabric_auth.py": "74ccc10239b65a3567cec5213a7b344cbd88d6eee1fd1c5f7be1d6daf985993c",
|
|
6002
|
+
"fabric_events.py": "cd94b186e2a8f8cb4ad2b6bd44d83ec5021933075e28c5d655f387f16dff42b8",
|
|
6003
|
+
"fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
|
|
6004
|
+
"fabric_supervisors.py": "bf5aac4034468f966cefae28dcbdca7f04f42fe73aefd1bac1b377cf4bda2b74",
|
|
6005
|
+
"fabric_service.py": "4b13f90aa26881fbfe0d9f3077fbb3552f3ce0a3c59279d6baa92df872867d8d",
|
|
6006
|
+
"fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
|
|
6007
|
+
"fabric_interop_rules.py": "9bdb7989e57b9d0b0d15eb9982d396499823a5e2c528230b71c67866c736e0f5",
|
|
6008
|
+
"fabric_interop.py": "d27e31834611a37cf2a117746f2f60fc187ccc48718eb546ff923c5d0fce7dea",
|
|
6009
|
+
"fabric_hub.py": "c261a81616c52f603eb28cc12ec4034dc479f0517328a65f0dc48d436cf0708b",
|
|
6010
|
+
"fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
|
|
6011
|
+
"fabric_board_link.py": "052bc3c474ece835bb428c37ca864810d04e394d991ddb9c93989a7a825073a5",
|
|
6012
|
+
"fabric_board_slots.py": "92b065e3df935547cf46653245f1129e4a1f8b59b0a41e8fa9ffdc41596ee592",
|
|
6013
|
+
"fabric_board_session.py": "1fd3c3884f3044b56a40f78b33ff88dcac9272284f6681ea359c662879826f7e",
|
|
6014
|
+
"fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
|
|
6015
|
+
"fabric_board.py": "f86c40b8291f8c818f6924910f4022b013d5fd107abaf8825dd0aa6c5e418638",
|
|
6016
|
+
"fabric_http.py": "e02b049b40eaabd994d93c7b084ee34ef3a78b5cbb61303b8e519b906af3c27a",
|
|
6017
|
+
"fabric_openrouter.py": "d06a40f83f2fb8bd441ab3485ae882f1fbdd9cd7a9c1ec175eb47cb8ae2cce81",
|
|
6018
|
+
"fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
|
|
6019
|
+
"fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
|
|
6020
|
+
"fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
|
|
6021
|
+
"fabric-secrets.mjs": "5822a7e25ecf2e87b5bab4f99c1074af7379d8b49e2784283b5d7d8c25022cde",
|
|
6022
|
+
"fabric-peer-owner.mjs": "0e65d59bc1048a5635c8ac04e73fe4fd1f34094902ced1580859017e5ca5a991",
|
|
6023
|
+
"fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
|
|
6024
|
+
"fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
|
|
6025
|
+
"fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
|
|
6026
|
+
"fabric-paths.mjs": "aa630d611e748a899e7043480f4a4e46b444f2e15ff945f30c2fbef21c1ceea5",
|
|
6027
|
+
"fabric-estate-rules.mjs": "7476be71174aa5b1026134f2b335abf647feaa55584e2f9de8618e3054aa8e1e",
|
|
6028
|
+
"fabric-estate.mjs": "e8a2e62d2313121e6cb502f6e6abad94d1d546a44b4d9b3ce2be29e7808e8cf3",
|
|
6029
|
+
"fabric-mandate.mjs": "b70f3de03a659e60bba7b5a0580b3582825cd7b8a8e30a28c79621919ec5b7bb",
|
|
6030
|
+
"fabric-delegation.mjs": "8f63a63aab9a8746b7f664720b87e9398137d01b8d50880974d5070038e414a0",
|
|
6031
|
+
"fabric-share.mjs": "bfb26581c21ad984c2a337da02429f9f3a165cc582f8ba48de4d3dfb7d163a59",
|
|
6032
|
+
"fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
|
|
6033
|
+
"fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
|
|
6034
|
+
"fabric-package.mjs": "8cfb32eebbd4e1bcf28a6e4437344ae2dd54479b0e7c820f002e9f94ab9ecaad",
|
|
6035
|
+
"fabric-protocols.mjs": "a560b7d6eb0d26204c5536711c3dda9daae90cc91e07cdc75412e75c86272767",
|
|
6036
|
+
"fabric-actions.mjs": "06e42f5e24c96311d5417d0d1effa93479ef7128a7ab139903b2bcf59253f6be",
|
|
6037
|
+
"fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
|
|
6038
|
+
"fabric-a2a.mjs": "51b72fe1d80649d659fcad3a8f96370db3476c4a7b30cbabd8522c138be69346",
|
|
6039
|
+
"fabric-page-links.mjs": "a8834926e9d4bbf7c4857f551451d2ebf6ac8873bbccaaf89c5e30cf45b17ee6",
|
|
6040
|
+
"fabric-lock.mjs": "02cf833c97a07a8b3c788f9c93f70c4b9c9066efa25318dccf0038e0fc79717f",
|
|
6041
|
+
"fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
|
|
6042
|
+
"fabric-backups.mjs": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06",
|
|
6043
|
+
"fabric-descriptor.mjs": "7c0ef7d245c037eb9e859ab481f6e66fb3e5ef2cf6d1e0a8d32b6d303097ca43",
|
|
6044
|
+
"fabric-context.mjs": "1112d695e872f9825cbc94f08484974bff3880087bc29b8be59f885f03e8f7be",
|
|
6045
|
+
"fabric-auth.mjs": "10ff37a39f9e3adb4d0f05db66c45f49b10eef582e9220c213907964109d7e52",
|
|
6046
|
+
"fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
|
|
6047
|
+
"fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
|
|
6048
|
+
"fabric-supervisors.mjs": "e33b2b8916aeed617eee6233e05bd637bb4ecd87064248fd7ea08fdfcd45d375",
|
|
6049
|
+
"fabric-service.mjs": "297fef11ac76dc831d12d24c5581b7a93587c300c20664db703b3f924c0f9f1a",
|
|
6050
|
+
"fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
|
|
6051
|
+
"fabric-interop-rules.mjs": "b4e386c359a48e49ffff8a842215760c18a7c98a0af4754a808a35fb2820d458",
|
|
6052
|
+
"fabric-interop.mjs": "989040aed33b2712a258244192bdf70e811a51076aa1b62f2adc2a6db051dbed",
|
|
6053
|
+
"fabric-hub.mjs": "73ab07f72320a50042f39b52893b563157be2ac94615614416df45a796eac988",
|
|
6054
|
+
"fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
|
|
6055
|
+
"fabric-board-link.mjs": "6e7d51f4fd9730d93455f50dd30e97907b8b9e1ca65a1d69f036f2e8ec05cc0d",
|
|
6056
|
+
"fabric-board-slots.mjs": "fc77a3c6e62aa18575568bef4f703cce2edaba8cef648813ebcdbb21bdc27a11",
|
|
6057
|
+
"fabric-board-session.mjs": "18555962e5f6d18080a3e7971c4c6640452542f6042d07917e7ebc073721c415",
|
|
6058
|
+
"fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
|
|
6059
|
+
"fabric-board.mjs": "15fbb4441fcf7d2f285375b75de64ad6cffea129c515084c110245376b07775f",
|
|
6060
|
+
"fabric-http.mjs": "b83b4c0c208763d56c3eebda5fe9e4a46d595cd91227699bf6876e65a3c7f9b9",
|
|
6061
|
+
"fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
|
|
6062
|
+
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
6063
|
+
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
6064
|
+
},
|
|
6065
|
+
"0.76.0": {
|
|
6066
|
+
"fabric_jsvalue.py": "052f1b40bab18600b3731dbdca27cbff17588c21d9458fa6241efd1340d0e09a",
|
|
6067
|
+
"fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
|
|
6068
|
+
"fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
|
|
6069
|
+
"fabric_secrets.py": "53c5df8d6f2c87433c288853bfaad9a43f544789bdf1e39bda8d3ff14485c89d",
|
|
6070
|
+
"fabric_peer_owner.py": "628c8e56eb067ecb7c343a606f8c75661257d7f349c47d9628449850d41d1f78",
|
|
6071
|
+
"fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
|
|
6072
|
+
"fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
|
|
6073
|
+
"fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
|
|
6074
|
+
"fabric_paths.py": "8a1ee742f80f436e9f28c61b4f049a9545de2a9e0d66bfc61faa6ee5d1ba3734",
|
|
6075
|
+
"fabric_estate_rules.py": "8b36a7f4108d9c2b1fb38c8bff344778c807bed631a99b4a563ecb0a49c10b1c",
|
|
6076
|
+
"fabric_estate.py": "0737ae1d1bb12586bae49de1f15ef1348bc207b5007cced9dad4c55bb5870874",
|
|
6077
|
+
"fabric_mandate.py": "fb19ce5359606bd6e3352ab02b2a12f49605ec6e5c49b55cce1f79c831325044",
|
|
6078
|
+
"fabric_delegation.py": "716f0ff7ac01095f54b24970beac1962128e8ff33026b243fc0467876142ab03",
|
|
6079
|
+
"fabric_share.py": "da36a466426fc29dd7dd40013167f3959a18375f8fedd228bf64f298cec1040c",
|
|
6080
|
+
"fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
|
|
6081
|
+
"fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
|
|
6082
|
+
"fabric_package.py": "1f2b9e73b2b32a7e1e887d4b69f87c6e21b4d1227ad924a235334223e6dd51ca",
|
|
6083
|
+
"fabric_protocols.py": "077c6f03f3b887a2e22d9ce6b5bd3750199d71a29d4c82b1d5af8a615b509d93",
|
|
6084
|
+
"fabric_actions.py": "ae21d1199614c4f74d1dcd1e0a87107a9d52434cd9e2d64aa24a7b1e196fb723",
|
|
6085
|
+
"fabric_connections.py": "7de55cb7d808f756d650effabfaa12027d40a5bd5d2f6d448c32969d47529ecb",
|
|
6086
|
+
"fabric_a2a.py": "8b01502131215bb4de1c01866187c56e99b791543923ed8577535573db33b919",
|
|
6087
|
+
"fabric_page_links.py": "1b57ea573c550e2f90d1ec3edcc655917ceaa0db9dd1635fccf422ec3024d25e",
|
|
6088
|
+
"fabric_lock.py": "dfaf92a7086cbb097d832a305869b7964e2d2a9da0e3365c8ec1685a7bb4fef9",
|
|
6089
|
+
"fabric_usage.py": "5cc2e03ccc5b06e81987ed7bc3eead7a7e6cbf1a7cb8e05c6c64315eed9d91b6",
|
|
6090
|
+
"fabric_backups.py": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420",
|
|
6091
|
+
"fabric_descriptor.py": "c08c91ca0960c694ed7677e7e9247d6307c7b63f37a7e614db022c080cd92ccc",
|
|
6092
|
+
"fabric_context.py": "6ae311ddc7217a4aff915e7ce2646486931acd128462b7bebe4a52bbb5fbb6c7",
|
|
6093
|
+
"fabric_auth.py": "74ccc10239b65a3567cec5213a7b344cbd88d6eee1fd1c5f7be1d6daf985993c",
|
|
6094
|
+
"fabric_events.py": "cd94b186e2a8f8cb4ad2b6bd44d83ec5021933075e28c5d655f387f16dff42b8",
|
|
6095
|
+
"fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
|
|
6096
|
+
"fabric_supervisors.py": "1ebf842de248ff0f154939ae6c9d5d0229b3358aecaefea07a80ed1b8755ac70",
|
|
6097
|
+
"fabric_service.py": "73082f69844739708732f6331742e00f17d27385d3c3efa3331a1fd61b523595",
|
|
6098
|
+
"fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
|
|
6099
|
+
"fabric_interop_rules.py": "9bdb7989e57b9d0b0d15eb9982d396499823a5e2c528230b71c67866c736e0f5",
|
|
6100
|
+
"fabric_interop.py": "d27e31834611a37cf2a117746f2f60fc187ccc48718eb546ff923c5d0fce7dea",
|
|
6101
|
+
"fabric_hub.py": "c261a81616c52f603eb28cc12ec4034dc479f0517328a65f0dc48d436cf0708b",
|
|
6102
|
+
"fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
|
|
6103
|
+
"fabric_board_link.py": "052bc3c474ece835bb428c37ca864810d04e394d991ddb9c93989a7a825073a5",
|
|
6104
|
+
"fabric_board_slots.py": "92b065e3df935547cf46653245f1129e4a1f8b59b0a41e8fa9ffdc41596ee592",
|
|
6105
|
+
"fabric_board_session.py": "1fd3c3884f3044b56a40f78b33ff88dcac9272284f6681ea359c662879826f7e",
|
|
6106
|
+
"fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
|
|
6107
|
+
"fabric_board.py": "f86c40b8291f8c818f6924910f4022b013d5fd107abaf8825dd0aa6c5e418638",
|
|
6108
|
+
"fabric_http.py": "b11b9930c3f0ab911a9cd325086a4c07a5ed4c246799d290f6085245669f4994",
|
|
6109
|
+
"fabric_openrouter.py": "d06a40f83f2fb8bd441ab3485ae882f1fbdd9cd7a9c1ec175eb47cb8ae2cce81",
|
|
6110
|
+
"fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
|
|
6111
|
+
"fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
|
|
6112
|
+
"fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
|
|
6113
|
+
"fabric-secrets.mjs": "5822a7e25ecf2e87b5bab4f99c1074af7379d8b49e2784283b5d7d8c25022cde",
|
|
6114
|
+
"fabric-peer-owner.mjs": "0d5a52385127546dea797e2dec36daeec6e05b04fdef04c1d8fd1ecf8db1a4e8",
|
|
6115
|
+
"fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
|
|
6116
|
+
"fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
|
|
6117
|
+
"fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
|
|
6118
|
+
"fabric-paths.mjs": "aa630d611e748a899e7043480f4a4e46b444f2e15ff945f30c2fbef21c1ceea5",
|
|
6119
|
+
"fabric-estate-rules.mjs": "7476be71174aa5b1026134f2b335abf647feaa55584e2f9de8618e3054aa8e1e",
|
|
6120
|
+
"fabric-estate.mjs": "e8a2e62d2313121e6cb502f6e6abad94d1d546a44b4d9b3ce2be29e7808e8cf3",
|
|
6121
|
+
"fabric-mandate.mjs": "b70f3de03a659e60bba7b5a0580b3582825cd7b8a8e30a28c79621919ec5b7bb",
|
|
6122
|
+
"fabric-delegation.mjs": "8f63a63aab9a8746b7f664720b87e9398137d01b8d50880974d5070038e414a0",
|
|
6123
|
+
"fabric-share.mjs": "bfb26581c21ad984c2a337da02429f9f3a165cc582f8ba48de4d3dfb7d163a59",
|
|
6124
|
+
"fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
|
|
6125
|
+
"fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
|
|
6126
|
+
"fabric-package.mjs": "8cfb32eebbd4e1bcf28a6e4437344ae2dd54479b0e7c820f002e9f94ab9ecaad",
|
|
6127
|
+
"fabric-protocols.mjs": "a560b7d6eb0d26204c5536711c3dda9daae90cc91e07cdc75412e75c86272767",
|
|
6128
|
+
"fabric-actions.mjs": "06e42f5e24c96311d5417d0d1effa93479ef7128a7ab139903b2bcf59253f6be",
|
|
6129
|
+
"fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
|
|
6130
|
+
"fabric-a2a.mjs": "51b72fe1d80649d659fcad3a8f96370db3476c4a7b30cbabd8522c138be69346",
|
|
6131
|
+
"fabric-page-links.mjs": "a8834926e9d4bbf7c4857f551451d2ebf6ac8873bbccaaf89c5e30cf45b17ee6",
|
|
6132
|
+
"fabric-lock.mjs": "02cf833c97a07a8b3c788f9c93f70c4b9c9066efa25318dccf0038e0fc79717f",
|
|
6133
|
+
"fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
|
|
6134
|
+
"fabric-backups.mjs": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06",
|
|
6135
|
+
"fabric-descriptor.mjs": "7c0ef7d245c037eb9e859ab481f6e66fb3e5ef2cf6d1e0a8d32b6d303097ca43",
|
|
6136
|
+
"fabric-context.mjs": "1112d695e872f9825cbc94f08484974bff3880087bc29b8be59f885f03e8f7be",
|
|
6137
|
+
"fabric-auth.mjs": "10ff37a39f9e3adb4d0f05db66c45f49b10eef582e9220c213907964109d7e52",
|
|
6138
|
+
"fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
|
|
6139
|
+
"fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
|
|
6140
|
+
"fabric-supervisors.mjs": "de6468aa2876be437a374499dd084281a10d62c3fd23bd7c65285da3ee282ce2",
|
|
6141
|
+
"fabric-service.mjs": "2fec45eb56d4a9ddc479f05d99abda4d2bb324746e94c156d96157462b824de0",
|
|
6142
|
+
"fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
|
|
6143
|
+
"fabric-interop-rules.mjs": "b4e386c359a48e49ffff8a842215760c18a7c98a0af4754a808a35fb2820d458",
|
|
6144
|
+
"fabric-interop.mjs": "989040aed33b2712a258244192bdf70e811a51076aa1b62f2adc2a6db051dbed",
|
|
6145
|
+
"fabric-hub.mjs": "73ab07f72320a50042f39b52893b563157be2ac94615614416df45a796eac988",
|
|
6146
|
+
"fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
|
|
6147
|
+
"fabric-board-link.mjs": "6e7d51f4fd9730d93455f50dd30e97907b8b9e1ca65a1d69f036f2e8ec05cc0d",
|
|
6148
|
+
"fabric-board-slots.mjs": "fc77a3c6e62aa18575568bef4f703cce2edaba8cef648813ebcdbb21bdc27a11",
|
|
6149
|
+
"fabric-board-session.mjs": "18555962e5f6d18080a3e7971c4c6640452542f6042d07917e7ebc073721c415",
|
|
6150
|
+
"fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
|
|
6151
|
+
"fabric-board.mjs": "15fbb4441fcf7d2f285375b75de64ad6cffea129c515084c110245376b07775f",
|
|
6152
|
+
"fabric-http.mjs": "b83b4c0c208763d56c3eebda5fe9e4a46d595cd91227699bf6876e65a3c7f9b9",
|
|
6153
|
+
"fabric-openrouter.mjs": "6041f3ab30f516658e1dbab4897a651cc8509b2136cfa8e769802c6b40ec2f35",
|
|
6154
|
+
"fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
|
|
6155
|
+
"share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
|
|
5972
6156
|
}
|
|
5973
6157
|
},
|
|
5974
6158
|
"data": {
|
|
@@ -5,7 +5,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.76.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "18f8d48762edc5bfb1433657cbfe1c16196df1a6"
|
|
11
11
|
---
|
|
@@ -61,10 +61,10 @@ No file is created until every row has an answer. An answer of "later" fails the
|
|
|
61
61
|
| **Whose data does it keep?** | the kind of its data, named in the README: **U** when it keeps data of people outside the owner's team (messages, support requests, reviews, customers' answers), which never enters the source, a copy of it or its tests: the source holds the schema and synthetic fixtures, and a copy starts with an empty store of its own; **O** when it keeps none (a tool or service for other agents, the owner's reports, runs and media). In doubt, U. Either way the data lives in `<agent>-data` (a kit service: the kit's folders; on a cloud host: a durable store), apart from the source, from the first commit (references/source-and-instance.md) |
|
|
62
62
|
| **Who holds the copyright?** | the holder the agent's `LICENSE` names: a new private agent (built for its owner or for a client) gets a `LICENSE` naming that holder in its first commit, all rights reserved unless the owner gives other terms; a public product keeps its own licence (a PassionCode.ai repository: the organization's, step 2) |
|
|
63
63
|
| **Which models?** | per lane (planning, replies, volume work, media): a primary, a fallback of another vendor, an open-vendor model where it reads what users send, the input kinds each reads, declared as classes in the profile (references/models-and-providers.md) |
|
|
64
|
-
| **Who pays?** | one key per agent and context with its own ceiling, from the estate's key manager, read from the environment; checked before every call; a personal key never pays for an organization |
|
|
64
|
+
| **Who pays?** | one key per agent and context with its own ceiling (USD 100 a month by default, more only where the operator names it), from the estate's key manager, read from the environment; checked before every call, the agent's own stop under the key's limit; a personal key never pays for an organization |
|
|
65
65
|
| **How does it learn?** | decisions recorded with reasons, one trace per turn, every failure a planted fixture first, history replayed, learnings proposed and never self-approved (references/orchestration-and-learning.md) |
|
|
66
66
|
| **Text for people?** | if it writes anything a person reads (a reply, a message, a post, a report, a UI string), its prompt forbids the em dash (U+2014) and any substitute for it, and a test fails on U+2014 in new output, from the first commit (the resilience reference, *Text for people*) |
|
|
67
|
-
| **Does it answer people?** | if it answers people in a chat, by mail, in store reviews or in a group, it carries from its first release the turn record and the daily `support.quality_daily` report, the lane table with vendor exclusion and fallback recoveries counted, a writer change only after an offline replay, false-state gates and a checker that sees the material first, numbers and account state from tools, the live tail re-read before sending, repair then research then a hold that comes back, hand-offs by cause and never counted or judged as answers, a commitments ledger, a knowledge client that reads the service's `answer` flag, a gap queue and a feed re-read on a new event kind (references/agents-that-answer-people.md) |
|
|
67
|
+
| **Does it answer people?** | if it answers people in a chat, by mail, in store reviews or in a group, it starts from references/communication-agent-template.md and carries from its first release the turn record and the daily `support.quality_daily` report, the lane table with vendor exclusion and fallback recoveries counted, a writer change only after an offline replay, false-state gates and a checker that sees the material first, numbers and account state from tools, the live tail re-read before sending, repair then research then a hold that comes back, hand-offs by cause and never counted or judged as answers, a commitments ledger, a knowledge client that reads the service's `answer` flag, a gap queue and a feed re-read on a new event kind (references/agents-that-answer-people.md) |
|
|
68
68
|
|
|
69
69
|
## Step 1: the knowledge intake
|
|
70
70
|
|
|
@@ -101,6 +101,9 @@ prior art of its own:
|
|
|
101
101
|
calling other agents, stops for a person, learning loops, the loop guard.
|
|
102
102
|
- [agents-that-answer-people.md](references/agents-that-answer-people.md): what an agent that
|
|
103
103
|
answers people carries from its first release, each part with the check that proves it.
|
|
104
|
+
- [communication-agent-template.md](references/communication-agent-template.md): the template prompt
|
|
105
|
+
for an agent that talks with people on a channel (support, sales and leads, community, inbox, reviews): the measured
|
|
106
|
+
inbound pipeline, business patterns, knowledge, the learning loop and acceptance.
|
|
104
107
|
|
|
105
108
|
## Step 2: the project skeleton
|
|
106
109
|
|