@passioncode-ai/fabric-agent-adapter 0.82.0 → 0.84.1
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 +169 -3
- package/README.md +7 -3
- package/SECURITY.md +4 -3
- 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 +7 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/provider-entry.md +4 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +16 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_identifiers.py +1 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +31 -15
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +38 -12
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +21 -148
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check_cli.py +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +15 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +4 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +14 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/README.md +76 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/config.example.json +28 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/data-agent/data_agent.py +366 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +32 -17
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +4 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support-agent.mjs +115 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +63 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/a2a.md +98 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +28 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +13 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +11 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/ops-digest.md +75 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +7 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +25 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/remote-placement.md +10 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +70 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_a2a.py +158 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a-server.mjs +275 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-a2a.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +5 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +15 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +55 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +47 -31
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +25 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-connections.mjs +27 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-context.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +4 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +19 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate-rules.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +7 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +22 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +4 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-hub.mjs +7 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-identifiers.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +21 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-load.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +16 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-mandate.mjs +6 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +25 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ops-digest.mjs +456 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package-schema.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +18 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-page-links.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +19 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +56 -19
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +16 -2
- 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-share.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-socket-proof.mjs +181 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +20 -15
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet-identity.mjs +469 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-tailnet.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a.py +3 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_a2a_server.py +405 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +7 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +17 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +98 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +37 -48
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +25 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +28 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_context.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +19 -13
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +24 -14
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +4 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +32 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +19 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_hub.py +10 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_identifiers.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +56 -96
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +99 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_load.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +16 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_mandate.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +44 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ops_digest.py +648 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +17 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package_schema.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_page_links.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +21 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +108 -108
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +38 -12
- 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_share.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_socket_proof.py +391 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +19 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_tailnet_identity.py +493 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +632 -104
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample-remote-service.mjs +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +17 -7
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/tailnet-groups.schema.json +69 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +6 -5
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +16 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +14 -9
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +4 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +18 -4
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/where-agents-run.md +4 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/admit.py +182 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.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/references/agents-database.md +10 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/on-demand-session.md +14 -9
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +7 -4
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +10 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +2 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +16 -5
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +79 -8
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +147 -45
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
2
3
|
"""``fabric-package/1`` (contract DEC-0050, DEC-0051, DEC-0071, DEC-0074; FAA-27a): pack an agent or a skill set as one
|
|
3
4
|
folder with ``fabric.package.json``, sign it, and verify it. The twin of ``fabric-package.mjs``.
|
|
4
5
|
|
|
@@ -390,12 +391,24 @@ def package_findings(pkg: Dict[str, Any]) -> List[Dict[str, str]]:
|
|
|
390
391
|
|
|
391
392
|
# --- the folder check and the verifier ------------------------------------------------------------------
|
|
392
393
|
|
|
394
|
+
def _json_int(text: str) -> Any:
|
|
395
|
+
"""An integer literal as JSON.parse reads it: one longer than a Python with `int_max_str_digits` reads is a float,
|
|
396
|
+
as a JavaScript number is (FAA-194 (36))."""
|
|
397
|
+
return int(text) if len(text) <= 4000 else float(text)
|
|
398
|
+
|
|
399
|
+
|
|
393
400
|
def _parsed(data: Optional[bytes]) -> Optional[Dict[str, Any]]:
|
|
401
|
+
"""A plugin or provider manifest as the contract's checkPackageFolder reads it: decoded with replacement characters
|
|
402
|
+
(`Buffer.toString`, FAA-194 (29)); NaN or Infinity, or nesting past MAX_NESTING (the limit both kits share, FAA-194
|
|
403
|
+
(27)), is no manifest."""
|
|
394
404
|
if data is None:
|
|
395
405
|
return None
|
|
406
|
+
text = data.decode("utf-8", "replace")
|
|
407
|
+
if _nesting(text) > MAX_NESTING:
|
|
408
|
+
return None
|
|
396
409
|
try:
|
|
397
|
-
value = json.loads(
|
|
398
|
-
except (ValueError, RecursionError):
|
|
410
|
+
value = json.loads(text, parse_constant=fd._no_constant, parse_int=_json_int)
|
|
411
|
+
except (ValueError, RecursionError):
|
|
399
412
|
return None
|
|
400
413
|
return value if isinstance(value, dict) else None
|
|
401
414
|
|
|
@@ -419,7 +432,7 @@ def check_package_folder(pkg: Dict[str, Any], files: Dict[str, bytes]) -> List[D
|
|
|
419
432
|
problems.append({"problem": "file-unlisted", "path": path})
|
|
420
433
|
if PLUGIN_MANIFEST in files:
|
|
421
434
|
plugin = _parsed(files.get(PLUGIN_MANIFEST))
|
|
422
|
-
if
|
|
435
|
+
if plugin is None or not _same_member(plugin, "name", pkg.get("id", _UNDEFINED)) or not _same_member(plugin, "version", pkg.get("version", _UNDEFINED)):
|
|
423
436
|
problems.append({"problem": "plugin-manifest", "path": PLUGIN_MANIFEST})
|
|
424
437
|
provider = pkg.get("provider")
|
|
425
438
|
if isinstance(provider, str) and provider in files:
|
|
@@ -615,7 +628,7 @@ def read_folder(folder: Any) -> Dict[str, bytes]:
|
|
|
615
628
|
if _BY_FOLDER:
|
|
616
629
|
os.close(top) # type: ignore[arg-type]
|
|
617
630
|
if refused:
|
|
618
|
-
raise PackageError("Not packed:
|
|
631
|
+
raise PackageError("Not packed: pack a clean copy (what Git would commit, references/package.md); delete only litter and credential files in place: " + "; ".join(sorted(refused)))
|
|
619
632
|
return files
|
|
620
633
|
|
|
621
634
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""fabric-package/1, the schema reading: the package schema bundled from the contract at the pin, read by a JSON Schema
|
|
2
3
|
2020-12 subset with JavaScript's patterns, lengths in code points and the formats the contract uses, as
|
|
3
4
|
fabric-package.mjs reads it. fabric_package checks, packs, signs and verifies on it and re-exports these names. Part of
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_page_links.py
CHANGED
|
@@ -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 service's pages as Fabric Dashboards' open_link (Fabric Agent Contract DEC-0123, PL-1 to PL-4, FAC-SEM-143;
|
|
3
4
|
`src/page-link-rules.ts` at the pin): build and read the open_link, tell a raw local address, and the findings on a
|
|
4
5
|
link a document hands a person and on a URL-mode input request. A port held to the contract's answers by
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""Where a service's things live (fabric-service/0.1): the services directory, a service's data, config, logs and run
|
|
2
3
|
directories per OS, private directories and atomic writes. Part of the service kit (FAA-65, layer L1)."""
|
|
3
4
|
from __future__ import annotations
|
|
@@ -38,23 +39,35 @@ def services_dir() -> Path:
|
|
|
38
39
|
return Path(base) / "passioncode-fabric/services"
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
def _durable(service_id: str, dirs: Dict[str, Path]) -> Dict[str, Path]:
|
|
43
|
+
"""FABRIC_DATA_HOME (FAA-192): an absolute folder on a durable volume holds the data folder, `<home>/<id>`, on a host
|
|
44
|
+
whose own disk does not persist; logs and cache stay where the OS puts them. A relative value is refused."""
|
|
45
|
+
value = os.environ.get("FABRIC_DATA_HOME")
|
|
46
|
+
if not value:
|
|
47
|
+
return dirs
|
|
48
|
+
if not os.path.isabs(value):
|
|
49
|
+
raise ServiceError("FABRIC_DATA_HOME is an absolute folder on a durable volume.")
|
|
50
|
+
return dict(dirs, data=Path(value) / service_id)
|
|
51
|
+
|
|
52
|
+
|
|
41
53
|
def service_dirs(service_id: str) -> Dict[str, Path]:
|
|
42
|
-
"""State lives outside code: data+config, logs and cache in per-user OS directories
|
|
54
|
+
"""State lives outside code: data+config, logs and cache in per-user OS directories (the data folder under
|
|
55
|
+
FABRIC_DATA_HOME when it is set)."""
|
|
43
56
|
_require_id(service_id)
|
|
44
57
|
home = Path.home()
|
|
45
58
|
if sys.platform == "darwin":
|
|
46
|
-
return {
|
|
59
|
+
return _durable(service_id, {
|
|
47
60
|
"data": home / "Library/Application Support" / service_id,
|
|
48
61
|
"logs": home / "Library/Logs" / service_id,
|
|
49
62
|
"cache": home / "Library/Caches" / service_id,
|
|
50
|
-
}
|
|
63
|
+
})
|
|
51
64
|
if sys.platform == "win32":
|
|
52
65
|
data = _local_app_data() / service_id
|
|
53
|
-
return {"data": data, "logs": data / "Logs", "cache": data / "Cache"}
|
|
66
|
+
return _durable(service_id, {"data": data, "logs": data / "Logs", "cache": data / "Cache"})
|
|
54
67
|
data = Path(os.environ.get("XDG_DATA_HOME") or home / ".local/share") / service_id
|
|
55
68
|
state = Path(os.environ.get("XDG_STATE_HOME") or home / ".local/state") / service_id
|
|
56
69
|
cache = Path(os.environ.get("XDG_CACHE_HOME") or home / ".cache") / service_id
|
|
57
|
-
return {"data": data, "logs": state / "logs", "cache": cache}
|
|
70
|
+
return _durable(service_id, {"data": data, "logs": state / "logs", "cache": cache})
|
|
58
71
|
|
|
59
72
|
|
|
60
73
|
def instance_dirs(service_id: str, instance: str = "default") -> Dict[str, Path]:
|
|
@@ -69,6 +82,9 @@ def instance_dirs(service_id: str, instance: str = "default") -> Dict[str, Path]
|
|
|
69
82
|
folder, personal = "%s.%s" % (service_id, instance), service_dirs(service_id)
|
|
70
83
|
if sys.platform == "win32":
|
|
71
84
|
data = personal["data"].parent / folder
|
|
85
|
+
if os.environ.get("FABRIC_DATA_HOME"): # logs and cache stay on the local disk, beside the default's
|
|
86
|
+
local = personal["logs"].parent.parent / folder
|
|
87
|
+
return {"data": data, "logs": local / "Logs", "cache": local / "Cache"}
|
|
72
88
|
return {"data": data, "logs": data / "Logs", "cache": data / "Cache"}
|
|
73
89
|
if sys.platform == "darwin":
|
|
74
90
|
return {key: path.parent / folder for key, path in personal.items()}
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py
CHANGED
|
@@ -1,45 +1,38 @@
|
|
|
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.
|
|
4
|
-
`observatory/engine/peer_owner.py`,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
FAA-
|
|
15
|
-
`TABLE_RESTRICTED`, a caller may vouch for a listener with a pid it started (`pid_listens`,
|
|
16
|
-
`CheckedHTTPConnection(vouch_pid=...)`), the child's port is read under one deadline with `selectors`, a number is
|
|
17
|
-
ASCII digits only. DEC-0155 (kit.6, FAA-312): a netstat that fails, a table with no row of another uid (sysctl) or process
|
|
4
|
+
peer_owner 1.3.1+kit.8: Observatory's peer_owner (passioncode-ai/project-observatory-dashboard,
|
|
5
|
+
`observatory/engine/peer_owner.py`, vendored at f86f0622, read against 2180235c for 1.2.0 and a69c6fee for 1.3.1),
|
|
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
|
|
18
16
|
(netstat) and a missing connection row read as `table-restricted` (RT-1); a restricted table's listener is this
|
|
19
|
-
account's only on exactly 127.0.0.1 (RT-4);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
kit module. Tests: `test/test_fabric_peer_owner.py`; the Node twin is `fabric-peer-owner.mjs` (no ctypes).
|
|
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`.
|
|
23
20
|
|
|
24
|
-
WHY. Loopback is one per machine, not
|
|
25
|
-
|
|
26
|
-
other end. This module answers that question from the host's socket table, read without privileges:
|
|
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:
|
|
27
23
|
|
|
28
24
|
listener(port) -> Owner who listens on 127.0.0.1:<port> (also 0.0.0.0, ::1, ::)
|
|
29
25
|
server_end(sock) -> Owner for a client's connected socket: who accepted it
|
|
30
26
|
peer(sock) -> Owner for a server's accepted socket: who connected
|
|
31
27
|
peer_at(server, client) the same as `peer`, from two (host, port) addresses
|
|
32
|
-
own_connection(port, ...) a CheckedHTTPConnection, connected: every connect
|
|
33
|
-
|
|
34
|
-
that the server end is this account's, or raises ListenerRefused
|
|
35
|
-
before a byte of the request is sent
|
|
28
|
+
own_connection(port, ...) a CheckedHTTPConnection, connected: every connect (the first, and any reconnect
|
|
29
|
+
http.client makes) checks the server end is this account's before a byte is sent
|
|
36
30
|
|
|
37
31
|
`Owner.kind` is one of:
|
|
38
32
|
|
|
39
33
|
this-account the socket belongs to the account running this process
|
|
40
34
|
another-account another OS account's
|
|
41
|
-
system uid 0, pid 1 (launchd
|
|
42
|
-
Windows, the system's own accounts and processes
|
|
35
|
+
system uid 0, pid 1 (launchd's socket-activated jobs) or, on Windows, the system's accounts and processes
|
|
43
36
|
unknown the owner could not be told; `reason` says why
|
|
44
37
|
none `listener` only: nothing listens on that port
|
|
45
38
|
|
|
@@ -52,40 +45,32 @@ answer; two live rows with the same addresses are `unknown` (`several-rows`), ne
|
|
|
52
45
|
|
|
53
46
|
- macOS, primary (`sysctl`): `sysctl net.inet.tcp.pcblist_n` through ctypes, the record list `netstat` itself reads.
|
|
54
47
|
Each connection is an `xinpcb_n` (addresses and ports) followed by an `xsocket_n` that carries `so_uid` and
|
|
55
|
-
`so_last_pid` (XNU `bsd/netinet/in_pcb.h` and `bsd/sys/socketvar.h`, both under `#pragma pack(4)`)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`table-restricted`, never another account's. A caller that started the server (an installer, its launchd job) may
|
|
65
|
-
vouch with that pid: `lsof -p PID` asks the process, which the restriction leaves readable (RT-3).
|
|
48
|
+
`so_last_pid` (XNU `bsd/netinet/in_pcb.h` and `bsd/sys/socketvar.h`, both under `#pragma pack(4)`), paired by the pcb
|
|
49
|
+
handle (`xi_inpp` equals `so_pcb`); the `xtcpcb_n` that follows gives the TCP state. Measured on macOS 26: about 4 ms
|
|
50
|
+
for 770 sockets. The layout is not an API, so the first read in a process calibrates it on a connected pair of its
|
|
51
|
+
own (the listener and both ends must read back with their exact addresses, this uid and this pid); a layout that
|
|
52
|
+
fails is not used again in that process.
|
|
53
|
+
- macOS 27.2 shows an ad-hoc-signed interpreter (a Homebrew Python) only its own sockets: the calibration also requires
|
|
54
|
+
a child's listener, and a restricted table (DEC-0155 RT-1) is `unknown` with a reason beginning `table-restricted`,
|
|
55
|
+
never another account's, unless `fabric_socket_proof` finds the socket in this account's own processes (RT-2) or a
|
|
56
|
+
caller that started the server vouches with its pid, which `lsof -p PID` asks the process itself about (RT-3).
|
|
66
57
|
- macOS, fallback (`netstat`): `/usr/sbin/netstat -anv -p tcp`, its `process:pid` column, then `ps -o uid= -p PID` for
|
|
67
|
-
the
|
|
68
|
-
|
|
69
|
-
way; it is kept as the documented fallback for a macOS whose record layout the calibration refuses.
|
|
58
|
+
the one row a question needs; about 20 ms over 846 sockets, and it names the pids of root's listeners, which `lsof`
|
|
59
|
+
run by an ordinary account cannot see. Kept for a macOS whose record layout the calibration refuses.
|
|
70
60
|
- Linux (`proc`): `/proc/net/tcp` and `/proc/net/tcp6`, whose `uid` column every account may read (also under
|
|
71
|
-
`hidepid=2
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`GetTokenInformation(TokenUser)` give its account SID, compared with this process's. A process that cannot be opened
|
|
76
|
-
(another account's, usually) is `unknown`, never `another-account`: the caller refuses either way, and the reason
|
|
77
|
-
says which it was. `Owner.uid` is None there.
|
|
61
|
+
`hidepid=2`). No pid: that is only readable for this account's own processes, and the decision needs the uid alone.
|
|
62
|
+
- Windows (`iphlpapi`): `GetExtendedTcpTable(TCP_TABLE_OWNER_PID_ALL)` gives each row's owning pid; `OpenProcess`,
|
|
63
|
+
`OpenProcessToken` and `GetTokenInformation(TokenUser)` its account SID, compared with this process's. A process that
|
|
64
|
+
cannot be opened (another account's, usually) is `unknown`, never `another-account`. `Owner.uid` is None there.
|
|
78
65
|
- Anything else: `unknown`, reason `unsupported-platform`.
|
|
79
66
|
|
|
80
|
-
`LOCAL_PEERCRED`, `getpeereid` and
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
WHAT IT NEVER DOES. It never reads a byte from the socket it is asked about, never resolves a name, never sends
|
|
84
|
-
anything, and never needs a privilege. It 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.
|
|
85
69
|
"""
|
|
86
70
|
from __future__ import annotations
|
|
87
71
|
|
|
88
72
|
import http.client
|
|
73
|
+
import importlib.util
|
|
89
74
|
import ipaddress
|
|
90
75
|
import json
|
|
91
76
|
import os
|
|
@@ -97,8 +82,7 @@ import threading
|
|
|
97
82
|
import time
|
|
98
83
|
from typing import NamedTuple, Optional, Tuple
|
|
99
84
|
|
|
100
|
-
#
|
|
101
|
-
PEER_OWNER_VERSION = "1.2.0+kit.6" # Observatory's 1.2.0 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
|
|
102
86
|
|
|
103
87
|
THIS_ACCOUNT = "this-account"
|
|
104
88
|
ANOTHER_ACCOUNT = "another-account"
|
|
@@ -107,8 +91,7 @@ UNKNOWN = "unknown"
|
|
|
107
91
|
NONE = "none"
|
|
108
92
|
KINDS = (THIS_ACCOUNT, ANOTHER_ACCOUNT, SYSTEM, UNKNOWN, NONE)
|
|
109
93
|
|
|
110
|
-
#: The most table bytes one read accepts:
|
|
111
|
-
#: 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).
|
|
112
95
|
MAX_TABLE_BYTES = 4 * 1024 * 1024
|
|
113
96
|
#: Every subprocess this module starts is stopped after this many seconds.
|
|
114
97
|
TIMEOUT_SECONDS = 2.0
|
|
@@ -119,8 +102,7 @@ NETSTAT = "/usr/sbin/netstat"
|
|
|
119
102
|
PS = "/bin/ps"
|
|
120
103
|
LSOF = "/usr/sbin/lsof"
|
|
121
104
|
|
|
122
|
-
#:
|
|
123
|
-
#: 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).
|
|
124
106
|
TABLE_RESTRICTED = "table-restricted"
|
|
125
107
|
#: A macOS table that lists sockets but not the reader's own connection (DEC-0155 RT-1, FAA-312).
|
|
126
108
|
NO_ROW_RESTRICTED = TABLE_RESTRICTED + ": no row for this connection"
|
|
@@ -131,8 +113,7 @@ SERVER_UNVERIFIED = "server-unverified"
|
|
|
131
113
|
SERVER_NOT_THIS_WORKSPACE = "server-not-this-workspace"
|
|
132
114
|
|
|
133
115
|
|
|
134
|
-
#
|
|
135
|
-
# 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).
|
|
136
117
|
_OwnerFields = NamedTuple("_OwnerFields", [("kind", str), ("uid", Optional[int]), ("pid", Optional[int]),
|
|
137
118
|
("method", str), ("reason", Optional[str])])
|
|
138
119
|
_OwnerFields.__new__.__defaults__ = (None, None, "none", None)
|
|
@@ -201,11 +182,6 @@ def _posix_uid() -> Optional[int]:
|
|
|
201
182
|
return os.getuid() if hasattr(os, "getuid") else None
|
|
202
183
|
|
|
203
184
|
|
|
204
|
-
def _own_pid() -> int:
|
|
205
|
-
"""This process's pid: a netstat that shows no other process's row is a restricted table (DEC-0155 RT-1)."""
|
|
206
|
-
return os.getpid()
|
|
207
|
-
|
|
208
|
-
|
|
209
185
|
def _set_uid() -> bool:
|
|
210
186
|
"""Real and effective uid differ: such a reader uses none of DEC-0155's proofs (RT-2 to RT-5), the vouch included."""
|
|
211
187
|
return hasattr(os, "geteuid") and os.geteuid() != _posix_uid()
|
|
@@ -248,8 +224,7 @@ def parse_netstat(text: str) -> list[Row]:
|
|
|
248
224
|
leading = at - sum(1 for w in names[:at] if w == "Address")
|
|
249
225
|
trailing = len(names) - at - 1
|
|
250
226
|
if not any(line.split()[:1] and line.split()[0].startswith("tcp") for line in lines[header + 1:]):
|
|
251
|
-
#
|
|
252
|
-
# (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)
|
|
253
228
|
raise TableUnreadable(f"{TABLE_RESTRICTED}: netstat printed only its header to this process")
|
|
254
229
|
rows: list[Row] = []
|
|
255
230
|
for line in lines[header + 1:]:
|
|
@@ -529,10 +504,9 @@ def _child_line(child: "subprocess.Popen[bytes]", deadline: float) -> Optional[b
|
|
|
529
504
|
|
|
530
505
|
|
|
531
506
|
def _child_listener_seen(rows_of) -> Optional[bool]:
|
|
532
|
-
"""Whether the table shows a listener of a child process of this one: False when it
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
pipe that cannot be read). The calibration trusts only True."""
|
|
507
|
+
"""Whether the table shows a listener of a child process of this one: False when not (it shows this process alone,
|
|
508
|
+
FAA-267), None when no child could be started (a frozen or embedded host) or it gave no port line within
|
|
509
|
+
TIMEOUT_SECONDS (silence, a partial line, an early exit, an unreadable pipe). The calibration trusts only True."""
|
|
536
510
|
if not sys.executable or getattr(sys, "frozen", False):
|
|
537
511
|
return None
|
|
538
512
|
try:
|
|
@@ -552,8 +526,7 @@ def _child_listener_seen(rows_of) -> Optional[bool]:
|
|
|
552
526
|
except (TableUnreadable, OSError, struct.error, ValueError):
|
|
553
527
|
return False
|
|
554
528
|
finally:
|
|
555
|
-
# One deadline
|
|
556
|
-
# 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
|
|
557
530
|
try:
|
|
558
531
|
child.stdin.close()
|
|
559
532
|
if port is None:
|
|
@@ -780,7 +753,7 @@ def restricted(rows: list[Row], how: str, *, own_uid: Optional[int] = None, own_
|
|
|
780
753
|
own = _posix_uid() if own_uid is None else own_uid
|
|
781
754
|
return not any(row.uid is not None and row.uid != own for row in rows)
|
|
782
755
|
if how == "netstat":
|
|
783
|
-
own =
|
|
756
|
+
own = os.getpid() if own_pid is None else own_pid
|
|
784
757
|
return not any(row.pid is not None and row.pid != own for row in rows)
|
|
785
758
|
return False
|
|
786
759
|
|
|
@@ -810,10 +783,8 @@ def _owner_of(local: tuple[str, int], remote: tuple[str, int], server: bool = Fa
|
|
|
810
783
|
if len(found) > 1:
|
|
811
784
|
return Owner(UNKNOWN, None, None, how, "several-rows")
|
|
812
785
|
if how == "proc" and server and found[0].uid is None:
|
|
813
|
-
# Linux:
|
|
814
|
-
# else
|
|
815
|
-
# process's uid (sock_graft), so a listener swapped after another account accepted cannot pass (Observatory's
|
|
816
|
-
# 1.2.0, board thread ced00f6e; CT-254). An orphaned server row reads so too: nothing sent to it reaches anyone.
|
|
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).
|
|
817
788
|
return _one_owner(_listeners_of(rows, found[0].local), how, "not-accepted")
|
|
818
789
|
if how == "proc" and found[0].uid is None:
|
|
819
790
|
# A client's socket with inode 0 has already closed (0.74.0 unit reading 4): never another socket's owner.
|
|
@@ -842,10 +813,40 @@ def _one_owner(found: list[Row], how: str, none_reason: Optional[str] = None) ->
|
|
|
842
813
|
return Owner(UNKNOWN, None, None, how, "several-rows")
|
|
843
814
|
|
|
844
815
|
|
|
816
|
+
_PROOF: list = [None] # fabric_socket_proof once looked for: None not yet, False not beside this file (FAA-292)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def _proved(ask: str, *args, fallback: Owner, **kwargs) -> Owner:
|
|
820
|
+
"""`fallback`, unless it is a restricted macOS table's `unknown` and `fabric_socket_proof` beside this file finds the
|
|
821
|
+
socket (`accepted`) or the 127.0.0.1 listener (`own_listener`, FAA-272) in this account's processes: else it stands."""
|
|
822
|
+
if fallback.kind != UNKNOWN or not (fallback.reason or "").startswith(TABLE_RESTRICTED) or sys.platform != "darwin":
|
|
823
|
+
return fallback
|
|
824
|
+
try:
|
|
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
|
+
proof = getattr(_PROOF[0], ask)(*args, **kwargs) if _PROOF[0] else None
|
|
838
|
+
except ImportError:
|
|
839
|
+
_PROOF[0], proof = False, None
|
|
840
|
+
except Exception: # noqa: BLE001 - a proof that fails proves nothing; the table's answer stands (fail closed)
|
|
841
|
+
proof = None
|
|
842
|
+
return Owner(THIS_ACCOUNT, _posix_uid(), proof.pid, proof.method, proof.reason) if proof and proof.proved else fallback
|
|
843
|
+
|
|
844
|
+
|
|
845
845
|
def peer_at(server: tuple, client: tuple) -> Owner:
|
|
846
846
|
"""The owner of the CLIENT socket of the connection between `server` and `client`, as a server sees them (its own
|
|
847
847
|
address, the peer's): the row whose local address is the client's and whose remote address is the server's."""
|
|
848
|
-
|
|
848
|
+
client, server = _address(client), _address(server)
|
|
849
|
+
return _proved("accepted", client, server, wait=False, fallback=_owner_of(client, server))
|
|
849
850
|
|
|
850
851
|
|
|
851
852
|
def peer(sock: socket.socket) -> Owner:
|
|
@@ -859,14 +860,14 @@ def peer(sock: socket.socket) -> Owner:
|
|
|
859
860
|
|
|
860
861
|
def server_end(sock: socket.socket) -> Owner:
|
|
861
862
|
"""For a client's connected socket: who accepted it. The row whose local address is the server's and whose remote
|
|
862
|
-
address is this socket's own,
|
|
863
|
-
|
|
864
|
-
its listener answers (the contract's CT-254, HA-2's Linux source)."""
|
|
863
|
+
address is this socket's own, the server process's accepted socket, so a listener swapped after `listen` cannot
|
|
864
|
+
pass; on Linux a connection not accept()ed yet answers through its listener (CT-254); RT-2 under a restricted table."""
|
|
865
865
|
try:
|
|
866
866
|
own, server = sock.getsockname(), sock.getpeername()
|
|
867
867
|
except OSError as exc:
|
|
868
868
|
return Owner(UNKNOWN, None, None, "none", f"socket: {type(exc).__name__}")
|
|
869
|
-
|
|
869
|
+
server, own = _address(server), _address(own)
|
|
870
|
+
return _proved("accepted", server, own, wait=True, fallback=_owner_of(server, own, server=True))
|
|
870
871
|
|
|
871
872
|
|
|
872
873
|
#: port -> (time.monotonic() of the read, the Owner)
|
|
@@ -899,6 +900,7 @@ def listener(port: int) -> Owner:
|
|
|
899
900
|
owner = Owner(UNKNOWN, None, None, method(), str(exc))
|
|
900
901
|
else:
|
|
901
902
|
owner = _restricted_listener(rows, port, how) if restricted(rows, how) else _one_owner(pick_listener(rows, port), how)
|
|
903
|
+
owner = _proved("own_listener", port, fallback=owner)
|
|
902
904
|
_LISTENERS[port] = (now, owner)
|
|
903
905
|
return owner
|
|
904
906
|
|
|
@@ -922,17 +924,21 @@ def refusal(owner: Owner, port: int, expect_pid: Optional[int] = None) -> Option
|
|
|
922
924
|
return None
|
|
923
925
|
|
|
924
926
|
|
|
925
|
-
def pid_listens(pid: int, port: int) -> bool:
|
|
926
|
-
"""macOS,
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if
|
|
927
|
+
def pid_listens(pid: int, port: int, owner: object = THIS_ACCOUNT) -> bool:
|
|
928
|
+
"""macOS, a restricted table (FAA-267, DEC-0155 RT-3): whether process `pid` listens on exactly 127.0.0.1:<port>
|
|
929
|
+
(never the IPv4 wildcard, which `lsof` prints as an IPv6-only one, nor ::1), asked of the process (`lsof -p`). Only
|
|
930
|
+
a caller that started `pid` (a launchd job it installed) vouches with it, never a set-uid one for this account. `owner`:
|
|
931
|
+
the uid `pid` runs as, this account's by default, or None when the caller checks it (a LaunchDaemon's UserName)."""
|
|
932
|
+
if owner is not None and owner != THIS_ACCOUNT and (not isinstance(owner, int) or isinstance(owner, bool) or owner < 0):
|
|
933
|
+
raise ValueError("owner is this-account, a uid or None")
|
|
934
|
+
if sys.platform != "darwin" or not isinstance(pid, int) or isinstance(pid, bool) or pid <= 1 or (
|
|
935
|
+
owner == THIS_ACCOUNT and _set_uid()):
|
|
931
936
|
return False
|
|
932
937
|
try:
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
938
|
+
if owner is not None:
|
|
939
|
+
uid = parse_ps_uid(_run([PS, "-o", "uid=", "-p", str(pid)]))
|
|
940
|
+
if uid is None or uid != (_posix_uid() if owner == THIS_ACCOUNT else owner):
|
|
941
|
+
return False
|
|
936
942
|
out = _run([LSOF, "-nP", "-a", "-p", str(pid), "-i4TCP", "-sTCP:LISTEN", "-Fn"])
|
|
937
943
|
except TableUnreadable:
|
|
938
944
|
return False
|
|
@@ -940,9 +946,8 @@ def pid_listens(pid: int, port: int) -> bool:
|
|
|
940
946
|
|
|
941
947
|
|
|
942
948
|
class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
943
|
-
"""An `http.client.HTTPConnection` whose every `connect()`
|
|
944
|
-
|
|
945
|
-
listener. A refused connect closes the socket and raises ListenerRefused before any byte of a request is sent."""
|
|
949
|
+
"""An `http.client.HTTPConnection` whose every `connect()` (the reconnect `auto_open` makes too, which may reach
|
|
950
|
+
another listener) checks the server end: refused, it closes the socket and raises ListenerRefused before a byte."""
|
|
946
951
|
|
|
947
952
|
def __init__(self, host: str, port: int, *, expect_pid: Optional[int] = None, timeout: float = 10.0,
|
|
948
953
|
vouch_pid: Optional[int] = None) -> None:
|
|
@@ -957,7 +962,7 @@ class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
|
957
962
|
super().connect()
|
|
958
963
|
owner = server_end(self.sock)
|
|
959
964
|
if (owner.kind == UNKNOWN and (owner.reason or "").startswith(TABLE_RESTRICTED) and getattr(self, "vouch_pid", None)
|
|
960
|
-
and pid_listens(self.vouch_pid, self.port)):
|
|
965
|
+
and _address(self.sock.getpeername())[0] == "127.0.0.1" and pid_listens(self.vouch_pid, self.port)):
|
|
961
966
|
# The table hides other processes (FAA-267); the process this caller started holds the listener.
|
|
962
967
|
owner = Owner(THIS_ACCOUNT, _posix_uid(), self.vouch_pid, "lsof-pid")
|
|
963
968
|
refused = refusal(owner, self.port, self.expect_pid)
|
|
@@ -968,16 +973,11 @@ class CheckedHTTPConnection(http.client.HTTPConnection):
|
|
|
968
973
|
|
|
969
974
|
def own_connection(port: int, *, expect_pid: Optional[int] = None, timeout: float = 10.0,
|
|
970
975
|
host: str = "127.0.0.1", vouch_pid: Optional[int] = None) -> CheckedHTTPConnection:
|
|
971
|
-
"""A `CheckedHTTPConnection` to `host:port`, connected, whose server end is this account's (
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
server-not-own another account's or the system's process accepted it
|
|
977
|
-
server-unverified its owner could not be told (nothing is sent: fail closed)
|
|
978
|
-
server-not-this-workspace this account's process, but not the pid expected
|
|
979
|
-
|
|
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
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py
CHANGED
|
@@ -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)."""
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py
CHANGED
|
@@ -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
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py
CHANGED
|
@@ -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
|