@passioncode-ai/fabric-agent-adapter 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +3 -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 +3 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +7 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +14 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +90 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/profiles.md +23 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +26 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +443 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +140 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +8 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +32 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +826 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +217 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +7 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py
CHANGED
|
@@ -432,11 +432,30 @@ def _approval_shape(payload: Dict[str, Any]) -> bool:
|
|
|
432
432
|
return payload.get("method") in ("prompt", "operator-channel") # DEC-0045: `device` is removed
|
|
433
433
|
|
|
434
434
|
|
|
435
|
+
def _estate_problem(estate: Any, jwks: Dict[str, Any], kid: str, now: float) -> Optional[Tuple[str, str]]:
|
|
436
|
+
"""DL-14 with DEC-0055: a verifier that reads the estate record trusts a kid only while a current revision certifies
|
|
437
|
+
it for a machine and does not revoke it. `estate` is a revision the caller verified (fabric_estate.
|
|
438
|
+
verify_estate_record) or a callable returning the current one; None skips the rule. (refusal kind, detail) or None."""
|
|
439
|
+
if estate is None:
|
|
440
|
+
return None
|
|
441
|
+
try:
|
|
442
|
+
record = estate() if callable(estate) else estate
|
|
443
|
+
except Exception: # an unreadable record certifies nothing; it is never a pass
|
|
444
|
+
return ("issuer", "estate-unreadable")
|
|
445
|
+
import fabric_estate # beside this file; imported here, since fabric_estate imports this module
|
|
446
|
+
jwk = next((k for k in jwks["keys"] if isinstance(k, dict) and k.get("kid") == kid), {})
|
|
447
|
+
if not fabric_estate.hub_key_certified(record, kid, jwk.get("x"), now):
|
|
448
|
+
return ("token", "kid-not-certified")
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
|
|
435
452
|
def verify_delegation(token: Any, *, audience: str, jwks: Any, now: float, capability: str, effect: str,
|
|
436
|
-
issuer: Optional[str], read_only: bool = False) -> Dict[str, Any]:
|
|
453
|
+
issuer: Optional[str], read_only: bool = False, estate: Any = None) -> Dict[str, Any]:
|
|
437
454
|
"""DL-13, in its fixed order: nothing about a token is believed before its signature is.
|
|
438
455
|
`now` is seconds since the epoch. Returns {ok: True, claims, principal, caller, chain, kid} or a refusal
|
|
439
|
-
{ok: False, code, detail[, data]} whose detail is a short machine word, never the token. `issuer` is required.
|
|
456
|
+
{ok: False, code, detail[, data]} whose detail is a short machine word, never the token. `issuer` is required.
|
|
457
|
+
With `estate` (a verified estate revision, or a callable returning the current one), the token's kid must be one
|
|
458
|
+
the revision certifies for a machine (DL-14, DEC-0055)."""
|
|
440
459
|
if not _is_string(issuer):
|
|
441
460
|
return _refuse("issuer-unavailable", "no-trusted-issuer")
|
|
442
461
|
if not _has_keys(jwks):
|
|
@@ -444,6 +463,9 @@ def verify_delegation(token: Any, *, audience: str, jwks: Any, now: float, capab
|
|
|
444
463
|
checked = _authentic(token, jwks, DELEGATION_TYP)
|
|
445
464
|
if "problem" in checked:
|
|
446
465
|
return _refuse("issuer-unavailable" if checked.get("issuer") else "unauthenticated", checked["problem"])
|
|
466
|
+
uncertified = _estate_problem(estate, jwks, checked["kid"], now)
|
|
467
|
+
if uncertified:
|
|
468
|
+
return _refuse("issuer-unavailable" if uncertified[0] == "issuer" else "unauthenticated", uncertified[1])
|
|
447
469
|
claims = checked["compact"]["payload"]
|
|
448
470
|
problem, depth = _delegation_shape(claims)
|
|
449
471
|
if problem:
|
|
@@ -543,7 +565,7 @@ UNSAFE_NO_JTI_STORE = _NoReplayProtection()
|
|
|
543
565
|
|
|
544
566
|
|
|
545
567
|
def verify_approval(approval: Optional[str], *, audience: str, jwks: Any, now: float, action_digest: str,
|
|
546
|
-
jti_store: Any, issuer: Optional[str]) -> Dict[str, Any]:
|
|
568
|
+
jti_store: Any, issuer: Optional[str], estate: Any = None) -> Dict[str, Any]:
|
|
547
569
|
"""DL-18: a missing approval is approval-required carrying the action digest; any fault of the document is
|
|
548
570
|
approval-invalid; a second use is approval-reused. The jti is consumed last."""
|
|
549
571
|
if approval is None or approval == "":
|
|
@@ -557,6 +579,9 @@ def verify_approval(approval: Optional[str], *, audience: str, jwks: Any, now: f
|
|
|
557
579
|
checked = _authentic(approval, jwks, APPROVAL_TYP)
|
|
558
580
|
if "problem" in checked:
|
|
559
581
|
return _refuse("issuer-unavailable" if checked.get("issuer") else "approval-invalid", checked["problem"])
|
|
582
|
+
uncertified = _estate_problem(estate, jwks, checked["kid"], now)
|
|
583
|
+
if uncertified:
|
|
584
|
+
return _refuse("issuer-unavailable" if uncertified[0] == "issuer" else "approval-invalid", uncertified[1])
|
|
560
585
|
claims = checked["compact"]["payload"]
|
|
561
586
|
if not _approval_shape(claims):
|
|
562
587
|
return _refuse("approval-invalid", "malformed-claims")
|
|
@@ -795,7 +820,7 @@ TIERS = ("T0", "T1", "T2")
|
|
|
795
820
|
|
|
796
821
|
|
|
797
822
|
def delegation_gate(*, audience: str, jwks: Any, effects: Mapping[str, str], issuer: str,
|
|
798
|
-
read_only: bool = False, clock: Callable[[], float] = time.time) -> Callable[..., Dict[str, Any]]:
|
|
823
|
+
read_only: bool = False, clock: Callable[[], float] = time.time, estate: Any = None) -> Callable[..., Dict[str, Any]]:
|
|
799
824
|
"""A check for one tools/call: `gate(tool, headers, tier="T2")` answers the accepted delegation or a DL-19
|
|
800
825
|
refusal. `effects` maps each tool to its declared effect class (its manifest entry); a tool with none is
|
|
801
826
|
refused rather than guessed. `jwks` is a JwksCache or a key set; `issuer` (auth.delegation.issuer) is required.
|
|
@@ -804,7 +829,8 @@ def delegation_gate(*, audience: str, jwks: Any, effects: Mapping[str, str], iss
|
|
|
804
829
|
own local floor has authenticated; T1 an enrolled machine of the same owner; T2 everything else, and any call
|
|
805
830
|
relayed from off the machine. A T0 or T1 caller is never required to present a token: with none, the call
|
|
806
831
|
passes with no delegation ({ok: True, tier, principal: None, ...}). A present token is verified at every tier
|
|
807
|
-
and refused when invalid. Unknown tiers count as T2.
|
|
832
|
+
and refused when invalid. Unknown tiers count as T2. `estate` (a callable returning the current verified estate
|
|
833
|
+
revision) makes the gate trust only kids that revision certifies (DL-14, DEC-0055)."""
|
|
808
834
|
if not _is_string(issuer):
|
|
809
835
|
raise ValueError("the gate needs the issuer it trusts (auth.delegation.issuer)")
|
|
810
836
|
|
|
@@ -819,7 +845,7 @@ def delegation_gate(*, audience: str, jwks: Any, effects: Mapping[str, str], iss
|
|
|
819
845
|
return _refuse("unauthenticated", "no-token")
|
|
820
846
|
keys = jwks.get(_kid_of(carried["token"])) if isinstance(jwks, JwksCache) else jwks
|
|
821
847
|
verdict = verify_delegation(carried["token"], audience=audience, jwks=keys, now=clock(), capability=tool,
|
|
822
|
-
effect=effect, issuer=issuer, read_only=read_only)
|
|
848
|
+
effect=effect, issuer=issuer, read_only=read_only, estate=estate)
|
|
823
849
|
if verdict["ok"]:
|
|
824
850
|
verdict["tier"] = tier if tier in TIERS else "T2"
|
|
825
851
|
return verdict
|