@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_delegation.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
|
"""The callee's side of fabric-delegation/0.1 (Fabric Agent Contract DEC-0039, Fabric ADR-0144): check a
|
|
3
4
|
delegation token or an approval offline against the estate's published keys, and answer `ok` or one refusal
|
|
4
5
|
code. A port of the contract's `src/delegation.ts` at the pinned revision; the shared vectors in
|
|
@@ -46,6 +47,8 @@ from fabric_jsvalue import ( # noqa: E402 - the core (FAA-65)
|
|
|
46
47
|
)
|
|
47
48
|
from fabric_paths import atomic_write as _atomic_write # noqa: E402 - the kit's private writer (FAA-85)
|
|
48
49
|
import fabric_peer_owner as _po # noqa: E402 - a loopback key set is read from this account's listener only (FAA-255)
|
|
50
|
+
import fabric_secrets # noqa: E402 - the WHATWG host reading (FAA-98, FAA-309)
|
|
51
|
+
import fabric_socket_proof # noqa: E402 - the one loopback host rule (FAA-187 (15))
|
|
49
52
|
from fabric_mandate import effect_closure # noqa: E402,F401 - defined once beside the mandate that reads it too (DEC-0125)
|
|
50
53
|
|
|
51
54
|
REFUSAL_CODES = (
|
|
@@ -621,16 +624,10 @@ def action_digest(action: Any) -> str:
|
|
|
621
624
|
|
|
622
625
|
# --- the estate's keys ----------------------------------------------------------------------------------------
|
|
623
626
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
address = ipaddress.ip_address(host)
|
|
629
|
-
except ValueError:
|
|
630
|
-
return False
|
|
631
|
-
# A zone (`::1%eth0`) is no loopback: urllib decodes `%3A80` in it into another address (0.56.0 seam r13).
|
|
632
|
-
# Nor an IPv4-mapped address, which Python 3.13+ counts as loopback and 3.9 and Node do not (0.56.0 seam r14).
|
|
633
|
-
return address.is_loopback and not getattr(address, "scope_id", None) and not getattr(address, "ipv4_mapped", None)
|
|
627
|
+
#: A key set source's loopback host, the kit's one rule (fabric_socket_proof.loopback_host), shared with fabric_http's
|
|
628
|
+
#: check_url, so a source starts a service on both kits alike (FAA-187 (15); `127.1`, `2130706433`, a name under
|
|
629
|
+
#: `.localhost`). A zone and an IPv4-mapped address stay refused, as before (0.56.0 seam r13, r14).
|
|
630
|
+
_loopback = fabric_socket_proof.loopback_host
|
|
634
631
|
|
|
635
632
|
|
|
636
633
|
# fabric_http._URL_ENDS (this module may not import fabric_http; test_fabric_delegation pins them equal).
|
|
@@ -663,7 +660,16 @@ def _source_authority_ok(parts: urllib.parse.SplitResult) -> bool:
|
|
|
663
660
|
parts.port # noqa: B018 - raises on a port above 65535
|
|
664
661
|
except ValueError:
|
|
665
662
|
return False
|
|
666
|
-
|
|
663
|
+
# A host `new URL` refuses for its structure (`a127.0.0.1`, `09`), or one Python's IDNA codec cannot write for
|
|
664
|
+
# http.client (U+180E), is refused here as check_url refuses it, never read and failed on every call (FAA-187 (14),
|
|
665
|
+
# (16)).
|
|
666
|
+
host = parts.hostname or ""
|
|
667
|
+
try:
|
|
668
|
+
if not host.isascii():
|
|
669
|
+
host.encode("idna")
|
|
670
|
+
except UnicodeError:
|
|
671
|
+
return False
|
|
672
|
+
return bool(host) and not fabric_secrets.url_refused(parts.geturl())
|
|
667
673
|
|
|
668
674
|
|
|
669
675
|
class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
@@ -734,7 +740,7 @@ def _fetch(source: str) -> Any:
|
|
|
734
740
|
body += chunk
|
|
735
741
|
if deadline["fired"] or time.monotonic() > deadline["end"]:
|
|
736
742
|
raise TimeoutError("the key set did not arrive within %s s" % JWKS_FETCH_SECONDS)
|
|
737
|
-
return json.loads(body[:65536])
|
|
743
|
+
return json.loads(body[:65536], parse_constant=_no_constant) # NaN is no JSON, as JSON.parse reads it (FAA-194)
|
|
738
744
|
except urllib.error.HTTPError as exc:
|
|
739
745
|
exc.close()
|
|
740
746
|
raise
|
|
@@ -745,7 +751,7 @@ def _fetch(source: str) -> Any:
|
|
|
745
751
|
finally:
|
|
746
752
|
if deadline["timer"] is not None:
|
|
747
753
|
deadline["timer"].cancel()
|
|
748
|
-
return json.loads(Path(source).expanduser().read_text())
|
|
754
|
+
return json.loads(Path(source).expanduser().read_text(), parse_constant=_no_constant)
|
|
749
755
|
|
|
750
756
|
|
|
751
757
|
class JwksCache:
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""How a service is found (fabric-service/0.1): its descriptor, peers and app descriptors (DEC-0061, DEC-0069),
|
|
2
3
|
build identity and the well-known document, the loopback HTTP server. Part of the service kit (FAA-65, layer L3)."""
|
|
3
4
|
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
@@ -55,7 +56,7 @@ from fabric_usage import (
|
|
|
55
56
|
DASHBOARD_EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/dashboard/0.1"
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.
|
|
59
|
+
DASHBOARD_KIT = "fabric-dashboard-kit@1.1.9"
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
REFERENCE_THEMES = ("graphite", "fabric", "paper", "forest", "ocean", "ember", "contrast")
|
|
@@ -197,9 +198,11 @@ def read_descriptors(directory: Optional[Path] = None) -> List[Tuple[Path, Dict[
|
|
|
197
198
|
return found
|
|
198
199
|
for path in sorted(root.glob("*.json")):
|
|
199
200
|
try:
|
|
200
|
-
|
|
201
|
-
except (OSError, ValueError):
|
|
201
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
202
|
+
except (OSError, ValueError, RecursionError):
|
|
202
203
|
continue
|
|
204
|
+
if isinstance(value, dict): # a file that holds no object is no descriptor (FAA-200 (50))
|
|
205
|
+
found.append((path, value))
|
|
203
206
|
return found
|
|
204
207
|
|
|
205
208
|
|
|
@@ -222,10 +225,11 @@ def validate_descriptor(descriptor: Dict[str, Any]) -> List[str]:
|
|
|
222
225
|
return problems
|
|
223
226
|
if descriptor["protocol"] != PROTOCOL:
|
|
224
227
|
problems.append("protocol must be %s" % PROTOCOL)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
for key, pattern in (("id", _ID), ("instance", _INSTANCE)): # a string before its pattern (FAA-200 (9), (36))
|
|
229
|
+
if not isinstance(descriptor[key], str):
|
|
230
|
+
problems.append("%s must be a string" % key)
|
|
231
|
+
elif not pattern.match(descriptor[key]):
|
|
232
|
+
problems.append("%s must match %s" % (key, pattern.pattern))
|
|
229
233
|
if remote:
|
|
230
234
|
problems.extend(remote_origin_problems(descriptor["origin"]))
|
|
231
235
|
elif "origin" in descriptor and not _ORIGIN.match(str(descriptor["origin"])):
|
|
@@ -234,11 +238,11 @@ def validate_descriptor(descriptor: Dict[str, Any]) -> List[str]:
|
|
|
234
238
|
if "tokenFile" not in auth:
|
|
235
239
|
problems.append("auth.tokenFile is required")
|
|
236
240
|
elif not _LOCAL_PATH.match(str(auth["tokenFile"])):
|
|
237
|
-
problems.append("auth.tokenFile must be
|
|
241
|
+
problems.append("auth.tokenFile must be an absolute local path (/, ~/ or a drive letter), never a relative path, never a network share")
|
|
238
242
|
paths = descriptor.get("paths") or {}
|
|
239
243
|
for value in ([paths["data"]] if "data" in paths else []) + list(paths.get("logs") or []):
|
|
240
244
|
if not _LOCAL_PATH.match(str(value)):
|
|
241
|
-
problems.append("paths must be local paths, never a network share: %r" % value)
|
|
245
|
+
problems.append("paths must be absolute local paths (/, ~/ or a drive letter), never a relative path, never a network share: %r" % value)
|
|
242
246
|
header = auth.get("header", "Authorization")
|
|
243
247
|
scheme = auth.get("scheme", "Bearer")
|
|
244
248
|
if not (isinstance(header, str) and _AUTH_HEADER.fullmatch(header)):
|
|
@@ -405,7 +409,7 @@ def descriptor_member_problems(descriptor: Dict[str, Any]) -> List[str]:
|
|
|
405
409
|
elif "@" in repository and not _HIERARCHICAL.match(repository): # 0.62.0 readings 7: never an scp form's user
|
|
406
410
|
problems.append("source.repository with a user is a URL with an authority (https://user@host/path), never an scp form")
|
|
407
411
|
elif fabric_secrets.repository_credential(repository): # its name is no key (0.62.0 seam reading 6)
|
|
408
|
-
problems.append("FAC-SEM-086: /source/repository holds a value shaped like a credential; it names a repository")
|
|
412
|
+
problems.append("FAC-SEM-086: /source/repository holds a value shaped like a credential; it names a repository: write the repository's name in lower case if it is one")
|
|
409
413
|
if "peer" in descriptor:
|
|
410
414
|
peer = descriptor.get("peer")
|
|
411
415
|
if not isinstance(peer, dict):
|
|
@@ -589,8 +593,15 @@ def port_listener_refusal(port: int, reader: Optional[Any] = None) -> Optional[s
|
|
|
589
593
|
"FAA-225)." % (port, uid))
|
|
590
594
|
|
|
591
595
|
|
|
596
|
+
def warn_no_source(descriptor: Dict[str, Any], stacklevel: int = 2) -> None:
|
|
597
|
+
"""The rewrite warning of a descriptor that names no source.repository (FAA-198)."""
|
|
598
|
+
me = "%s.%s" % (descriptor["id"], descriptor["instance"])
|
|
599
|
+
warnings.warn("Descriptor %s %s; a new install is refused without it." % (me, NO_SOURCE_REPOSITORY),
|
|
600
|
+
NoSourceRepositoryWarning, stacklevel=stacklevel + 1)
|
|
601
|
+
|
|
602
|
+
|
|
592
603
|
def write_descriptor(descriptor: Dict[str, Any], directory: Optional[Path] = None, *, _stacklevel: int = 2,
|
|
593
|
-
port_owner: Optional[Any] = None) -> Path:
|
|
604
|
+
port_owner: Optional[Any] = None, _warned: bool = False) -> Path:
|
|
594
605
|
"""Installer-only. Refuses a port or id.instance another descriptor claims (a port is a claim), and a port another
|
|
595
606
|
OS account listens on (FAA-225; ``port_owner(port) -> (kind, uid) | None`` reads it, the kit's own by default).
|
|
596
607
|
|
|
@@ -604,9 +615,8 @@ def write_descriptor(descriptor: Dict[str, Any], directory: Optional[Path] = Non
|
|
|
604
615
|
refusal = source_refusal(descriptor, root)
|
|
605
616
|
if refusal:
|
|
606
617
|
raise ServiceError(refusal)
|
|
607
|
-
if not names_repository(descriptor):
|
|
608
|
-
|
|
609
|
-
NoSourceRepositoryWarning, stacklevel=_stacklevel)
|
|
618
|
+
if not names_repository(descriptor) and not _warned: # register_remote warns itself, before its token (FAA-200 (1))
|
|
619
|
+
warn_no_source(descriptor, _stacklevel)
|
|
610
620
|
# DEC-0019: only a local placement claims a port on this computer; DEC-0069: an app's descriptor names none.
|
|
611
621
|
port = None if placement_of(descriptor) == "remote" or "origin" not in descriptor else port_of(descriptor["origin"])
|
|
612
622
|
for path, other in read_descriptors(root):
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.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
|
"""`estate-record/1` (Fabric Agent Contract DEC-0055): verify an estate revision and check its rules.
|
|
3
4
|
|
|
4
5
|
One revision of one person's estate: the passkeys that sign it, the machines with their device keys and hub-key
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate_rules.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
|
"""`estate-record/1` (Fabric Agent Contract DEC-0055): the record's own rules, without a verifier.
|
|
3
4
|
|
|
4
5
|
The contract's `src/estate-record-rules.ts` at the pin, ported answer for answer and held to the contract's own vectors
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""A service's events (DEC-0029): making them, paging them and the JSONL event log. Part of the service kit
|
|
2
3
|
(FAA-65, layer L3)."""
|
|
3
4
|
from __future__ import annotations
|
|
@@ -51,7 +52,9 @@ def make_event(event_id: Any, at: str, kind: str, level: str, text: str, *, subj
|
|
|
51
52
|
link: Optional[str] = None, notify: bool = False, trace_id: Optional[str] = None,
|
|
52
53
|
span_id: Optional[str] = None, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
53
54
|
"""One activity event: one sentence a person reads, never a machine id. `level` is one of `LEVELS`; `warn`, the
|
|
54
|
-
dashboard
|
|
55
|
+
dashboard kit's chip for "a person is needed" (`.fd-s-warn`), is stored as `warning`, the feed's name for the same
|
|
56
|
+
level, so a reader that draws chips shows a stored `warning` as the `warn` chip (FAA-276, FAA-277 (6)). `data`
|
|
57
|
+
(DEC-0070) adds machine-readable
|
|
55
58
|
fields beside the sentence, `{schema, value}`, never a secret: it is refused here, so no feed carries one.
|
|
56
59
|
|
|
57
60
|
An event about traced work carries its trace as a pair, `trace_id` and `span_id`
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""Work a service holds by its own limits, said to its caller and its operator (FAA-298). Part of the service kit
|
|
2
3
|
(layer L3, after fabric_interop).
|
|
3
4
|
|
|
@@ -21,7 +22,8 @@ says so three ways:
|
|
|
21
22
|
|
|
22
23
|
`HoldBook` keeps one JSON file (0600, written atomically); a ThreadingHTTPServer's handlers share it under the store
|
|
23
24
|
lock. It holds caller names, work labels, reasons and the operator's words, never a credential: `hold` refuses a text
|
|
24
|
-
the contract's detector reads as one.
|
|
25
|
+
the contract's detector reads as one. A record written since 0.83.0 carries `squash: "ecmascript"`; one without it
|
|
26
|
+
(0.81.0, 0.82.0) is matched under both squashes, since 0.81.0's Python twin squashed names with str.split() (FAA-320).
|
|
25
27
|
"""
|
|
26
28
|
from __future__ import annotations
|
|
27
29
|
|
|
@@ -65,6 +67,27 @@ def _squash(text: str) -> str:
|
|
|
65
67
|
return _SPACES.sub(" ", text).strip(" ")
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
#: What a record written since 0.83.0 carries: its names were squashed with ECMAScript's whitespace (FAA-320).
|
|
71
|
+
SQUASH = "ecmascript"
|
|
72
|
+
#: Python's str.isspace() class, the one 0.81.0's Python twin squashed a name with (`" ".join(text.split())`).
|
|
73
|
+
_PY_SPACES = re.compile("[\t\n\x0b\x0c\r\x1c-\x1f \x85\xa0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _legacy_squash(text: str) -> str:
|
|
77
|
+
"""A name as 0.81.0's Python twin wrote it (str.split()), written out so both twins read it alike."""
|
|
78
|
+
return _PY_SPACES.sub(" ", text).strip(" ")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _names(caller: Any, record: Dict[str, Any]) -> tuple:
|
|
82
|
+
"""The stored forms `caller` matches in `record`: the ECMAScript squash, and for a record with no `squash` mark
|
|
83
|
+
(written before 0.83.0) also the squash 0.81.0's Python twin wrote (FAA-320). None matches only None."""
|
|
84
|
+
if not isinstance(caller, str):
|
|
85
|
+
return (caller,)
|
|
86
|
+
if record.get("squash") == SQUASH:
|
|
87
|
+
return (_squash(caller),)
|
|
88
|
+
return (_squash(caller), _legacy_squash(caller))
|
|
89
|
+
|
|
90
|
+
|
|
68
91
|
def _instant(value: Any, name: str) -> str:
|
|
69
92
|
if utc_time(value) is None:
|
|
70
93
|
raise ServiceError("%s is a UTC instant (Z, at most 3 fraction digits)." % name)
|
|
@@ -197,6 +220,7 @@ class HoldBook:
|
|
|
197
220
|
job_id: Optional[str] = None, now: Optional[str] = None) -> Dict[str, Any]:
|
|
198
221
|
"""Hold `work` for `caller` by `reason`. Returns {hold, statusMessage, push}: `push` is the notify event when this
|
|
199
222
|
call opened the hold, None when the work joined an open one (one push per hold)."""
|
|
223
|
+
raw_caller = caller
|
|
200
224
|
caller, work = _text(caller, "caller"), _text(work, "work")
|
|
201
225
|
if reason not in HOLD_REASONS:
|
|
202
226
|
raise ServiceError("A hold's reason is one of %s." % ", ".join(HOLD_REASONS))
|
|
@@ -207,11 +231,11 @@ class HoldBook:
|
|
|
207
231
|
data = self._load()
|
|
208
232
|
self._expire(data, utc_time(at))
|
|
209
233
|
hold = next((h for h in data["holds"] if h.get("closed") is None and h["reason"] == reason
|
|
210
|
-
and h["caller"]
|
|
234
|
+
and h["caller"] in _names(raw_caller, h)), None)
|
|
211
235
|
opened = hold is None
|
|
212
236
|
if opened:
|
|
213
237
|
hold = {"id": uuid.uuid4().hex, "reason": reason, "caller": caller, "work": work, "since": at,
|
|
214
|
-
"until": until, "jobs": [], "closed": None}
|
|
238
|
+
"until": until, "jobs": [], "closed": None, "squash": SQUASH}
|
|
215
239
|
data["holds"].append(hold)
|
|
216
240
|
if job_id is not None and job_id not in hold["jobs"]:
|
|
217
241
|
hold["jobs"].append(str(job_id))
|
|
@@ -229,7 +253,7 @@ class HoldBook:
|
|
|
229
253
|
with self._lock:
|
|
230
254
|
data = self._load()
|
|
231
255
|
hold = next((h for h in data["holds"] if h.get("closed") is None and h["reason"] == reason
|
|
232
|
-
and h["caller"]
|
|
256
|
+
and h["caller"] in _names(caller, h)), None)
|
|
233
257
|
if hold is None:
|
|
234
258
|
return None
|
|
235
259
|
hold["closed"], hold["closedAt"] = "released", at
|
|
@@ -269,14 +293,15 @@ class HoldBook:
|
|
|
269
293
|
raise ServiceError("A raised limit needs its window's end (until): it falls back when the window ends.")
|
|
270
294
|
record: Dict[str, Any] = {"id": uuid.uuid4().hex, "reason": reason, "caller": None if caller is None else _text(caller, "caller"),
|
|
271
295
|
"job": None if job_id is None else str(job_id), "from": at, "until": until,
|
|
272
|
-
given[0]: size, "decision": dict(decision), "at": at}
|
|
296
|
+
given[0]: size, "decision": dict(decision), "at": at, "squash": SQUASH}
|
|
273
297
|
if given[0] != "limit":
|
|
274
298
|
record["left"] = size
|
|
275
299
|
with self._lock:
|
|
276
300
|
data = self._load()
|
|
277
301
|
self._expire(data, utc_time(at))
|
|
278
302
|
for hold in data["holds"]:
|
|
279
|
-
if hold.get("closed") is None and hold["reason"] == reason and
|
|
303
|
+
if hold.get("closed") is None and hold["reason"] == reason and (
|
|
304
|
+
caller is None or hold["caller"] in _names(caller, hold)) and (
|
|
280
305
|
job_id is None or str(job_id) in hold["jobs"]):
|
|
281
306
|
hold["closed"], hold["closedAt"], hold["lift"] = "lifted", at, record["id"]
|
|
282
307
|
data["lifts"].append(record)
|
|
@@ -291,7 +316,7 @@ class HoldBook:
|
|
|
291
316
|
def _matching(self, data: Dict[str, Any], reason: str, caller: Optional[str], job_id: Optional[str], at: int,
|
|
292
317
|
kind: str) -> List[Dict[str, Any]]:
|
|
293
318
|
return [l for l in data["lifts"] if l["reason"] == reason and kind in l and self._live(l, at)
|
|
294
|
-
and l.get("caller") in (
|
|
319
|
+
and (l.get("caller") is None or l["caller"] in _names(caller, l)) and l.get("job") in (None, job_id)]
|
|
295
320
|
|
|
296
321
|
def admit(self, *, reason: str, caller: str, job_id: Optional[str] = None, cost_usd: Optional[float] = None,
|
|
297
322
|
now: Optional[str] = None) -> Dict[str, Any]:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""An outbound HTTP client for a service: one deadline across every attempt, bounded retries with jittered backoff,
|
|
2
3
|
`Retry-After` honoured up to a cap, a response size cap, and no secret in an error.
|
|
3
4
|
|
|
@@ -50,6 +51,8 @@ from pathlib import Path as _Path # noqa: E402
|
|
|
50
51
|
if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from any working directory (FAA-65)
|
|
51
52
|
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
|
|
52
53
|
from fabric_jsvalue import json_stringify # noqa: E402 - beside this file
|
|
54
|
+
import fabric_secrets # noqa: E402 - beside this file: the WHATWG host reading (FAA-98, FAA-309)
|
|
55
|
+
import fabric_socket_proof # noqa: E402 - beside this file: the one loopback host rule (FAA-187 (15))
|
|
53
56
|
import fabric_peer_owner as _po # noqa: E402 - beside this file: the listener's owner (DEC-0154 HA-2)
|
|
54
57
|
|
|
55
58
|
RETRY_STATUSES = (408, 425, 429, 500, 502, 503, 504)
|
|
@@ -211,28 +214,28 @@ def redact_url(url: str) -> str:
|
|
|
211
214
|
return urllib.parse.urlunsplit((parts.scheme.lower(), netloc, path, "", ""))
|
|
212
215
|
|
|
213
216
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
#: Loopback as a WHATWG URL parser leaves the host: the kit's one rule, shared with fabric_delegation (FAA-187 (15)).
|
|
218
|
+
_is_loopback = fabric_socket_proof.loopback_host
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _idna_sendable(host: Optional[str]) -> bool:
|
|
222
|
+
"""Whether http.client can write the host: a non-ASCII one goes out in IDNA through Python's own codec, which refuses
|
|
223
|
+
some hosts `new URL` maps (U+180E, a fullwidth dot beside a dot), so such a URL would fail on every call (FAA-187
|
|
224
|
+
(16)); it is refused at the check instead."""
|
|
225
|
+
if not host or host.isascii():
|
|
218
226
|
return True
|
|
219
|
-
if not host or any(c.isspace() for c in host):
|
|
220
|
-
return False
|
|
221
227
|
try:
|
|
222
|
-
|
|
223
|
-
return
|
|
224
|
-
except
|
|
225
|
-
pass
|
|
226
|
-
host = host[:-1] if host.endswith(".") else host
|
|
227
|
-
try: # the short IPv4 forms a WHATWG URL also reads (127.1, 2130706433, 0x7f.1)
|
|
228
|
-
return socket.inet_aton(host)[0] == 127
|
|
229
|
-
except (OSError, ValueError):
|
|
228
|
+
host.encode("idna")
|
|
229
|
+
return True
|
|
230
|
+
except UnicodeError:
|
|
230
231
|
return False
|
|
231
232
|
|
|
232
233
|
|
|
233
234
|
def check_url(url: str, *, insecure: bool = False) -> None:
|
|
234
235
|
"""Refuse what this client never sends: a scheme other than http or https, no host, credentials in the URL, or
|
|
235
|
-
plain http to a host that is not loopback (unless `insecure`).
|
|
236
|
+
plain http to a host that is not loopback (unless `insecure`). A host `new URL` refuses for its structure (a last
|
|
237
|
+
label that is a number but no IPv4 address, `a127.0.0.1`, `09`; a host UTS46 refuses, a joiner beside ASCII alone) is
|
|
238
|
+
refused as the Node twin refuses it, read by fabric_secrets.url_refused (FAA-187 (14))."""
|
|
236
239
|
try:
|
|
237
240
|
# The ends are trimmed as urllib trims what it sends (and `new URL` too); a URL ending in a line break, which
|
|
238
241
|
# 0.55.0 sent, still passes (0.56.0 product r17).
|
|
@@ -242,7 +245,7 @@ def check_url(url: str, *, insecure: bool = False) -> None:
|
|
|
242
245
|
parts = urllib.parse.urlsplit(sent)
|
|
243
246
|
host = parts.hostname
|
|
244
247
|
parts.port # noqa: B018 - raises on a bad port
|
|
245
|
-
if not _authority_ok(parts.netloc):
|
|
248
|
+
if not _authority_ok(parts.netloc) or fabric_secrets.url_refused(sent) or not _idna_sendable(host):
|
|
246
249
|
raise ValueError("authority")
|
|
247
250
|
except (ValueError, TypeError):
|
|
248
251
|
raise HttpError("the URL does not parse", url=redact_url(url), kind="refused")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""The Fabric hub's door, as a service agent reaches it (fabric-hub/0.1; Fabric ADR-0115 §1, ADR-0149 §4; FAA-12b).
|
|
2
3
|
|
|
3
4
|
A service that answers a Project's requests on the board (`fabric-project-comms/0.1`) needs three things from Fabric's
|
|
@@ -96,20 +97,24 @@ def fabric_root(platform: str = sys.platform, env: Optional[Dict[str, str]] = No
|
|
|
96
97
|
|
|
97
98
|
|
|
98
99
|
def _windows_pid_alive(pid: int, kernel: Any = None, last_error: Any = None) -> Optional[bool]:
|
|
99
|
-
"""pid_alive on Windows, where os.kill(pid, 0) would send CTRL_C_EVENT:
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
"""pid_alive on Windows, where os.kill(pid, 0) would send CTRL_C_EVENT: the check Node's process.kill(pid, 0) makes
|
|
101
|
+
(libuv's uv_kill), asking OpenProcess for the same access (terminate, query, synchronize; never the limited query an
|
|
102
|
+
elevated process of the same user grants, FAA-323). Refused with ERROR_ACCESS_DENIED (5), another account's or an
|
|
103
|
+
elevated process: None, as Node answers EPERM there (FAA-304); refused otherwise, no process; opened, running
|
|
104
|
+
while its exit code is STILL_ACTIVE and a zero wait times out. `kernel` and `last_error` stand in for kernel32 and
|
|
102
105
|
ctypes.get_last_error in a test."""
|
|
103
106
|
import ctypes
|
|
104
107
|
if kernel is None:
|
|
105
108
|
kernel = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore[attr-defined]
|
|
106
109
|
last_error = ctypes.get_last_error # type: ignore[attr-defined]
|
|
107
|
-
handle = kernel.OpenProcess(
|
|
110
|
+
handle = kernel.OpenProcess(0x0001 | 0x0400 | 0x00100000, False, pid) # TERMINATE | QUERY_INFORMATION | SYNCHRONIZE
|
|
108
111
|
if not handle:
|
|
109
112
|
return None if last_error() == 5 else False # ERROR_ACCESS_DENIED: not verified, never called running
|
|
110
113
|
try:
|
|
111
114
|
code = ctypes.c_ulong()
|
|
112
|
-
|
|
115
|
+
if not kernel.GetExitCodeProcess(handle, ctypes.byref(code)) or code.value != 259: # STILL_ACTIVE
|
|
116
|
+
return False
|
|
117
|
+
return kernel.WaitForSingleObject(handle, 0) != 0 # WAIT_OBJECT_0: ended, with exit code 259
|
|
113
118
|
finally:
|
|
114
119
|
kernel.CloseHandle(handle)
|
|
115
120
|
|
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_identifiers.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
1
2
|
"""Identifier equality (contract DEC-0076): two identifiers are the same when their normal forms are equal.
|
|
2
3
|
|
|
3
4
|
A port of the contract's `src/identifiers.ts`, held to its `fixtures/vectors/identifiers.json`. The normal form of a URI
|