@passioncode-ai/passioncode 0.2.38 → 0.2.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/family.json +1 -1
  3. package/package.json +1 -1
  4. package/payload/.claude-plugin/marketplace.json +2 -2
  5. package/payload/manifest.json +8 -8
  6. package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
  7. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +29 -11
  8. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +53 -3
  9. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/operator-channel-and-repository.md +11 -3
  10. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  11. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +47 -12
  12. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-button.js +5 -4
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.css +1 -1
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/fabric-dashboard.js +2 -2
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/assets/dashboard-kit/kit.json +9 -4
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-kit.md +3 -2
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +53 -3
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard.md +13 -4
  19. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +10 -1
  20. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +99 -1
  21. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +1 -0
  22. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +5 -2
  23. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/settings-backups.md +2 -1
  24. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/surfaces-and-auth.md +5 -3
  25. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +45 -2
  26. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +325 -2
  27. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +246 -0
  28. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +3 -1
  29. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +22 -0
  30. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +18 -2
  31. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +45 -3
  32. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +2 -0
  33. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -1
  34. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +1 -0
  35. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +302 -0
  36. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +2 -1
  37. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +22 -1
  38. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +22 -3
  39. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +154 -8
  40. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +45 -2
  41. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -1
  42. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +9 -0
  43. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +124 -16
  44. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +29 -11
  45. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +53 -3
  46. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/operator-channel-and-repository.md +11 -3
  47. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +41 -9
  48. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +2 -0
  49. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
  50. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
@@ -24,11 +24,14 @@ from pathlib import Path
24
24
  import re
25
25
  import secrets
26
26
  import sys
27
+ import time
27
28
  from typing import Any, Callable, Dict, List, Optional
28
29
 
29
30
  sys.path.insert(0, str(Path(__file__).resolve().parent))
30
31
  import fabric_service as _fs # noqa: E402
31
32
  from fabric_base import store_lock # noqa: E402 - one lock per store path (R3)
33
+ from fabric_actions import action_caller, readable_action # noqa: E402 - DEC-0121
34
+ from fabric_time import format_rfc3339 # noqa: E402 - the core's (FAA-65)
32
35
 
33
36
  from fabric_interop_rules import ( # noqa: E402,F401 - the rule layer (FAA-92)
34
37
  _days_from_civil,
@@ -77,6 +80,8 @@ from fabric_interop_rules import ( # noqa: E402,F401 - the rule layer (FAA-92)
77
80
  intent_idempotency_key,
78
81
  reads_null_usage,
79
82
  request_meta,
83
+ action_gate,
84
+ action_refusal_tool_result,
80
85
  usage_findings,
81
86
  usage_toward,
82
87
  _job_findings,
@@ -430,16 +435,26 @@ class JobStore:
430
435
  # --- a minimal MCP tool server (streamable HTTP, JSON responses) ------------------------
431
436
 
432
437
  class CallContext:
433
- """What a tool handler gets: the child span it runs in, and a way to start a job."""
438
+ """What a tool handler gets: the child span it runs in, and a way to start a job.
439
+
440
+ DEC-0121: `tier` is the caller's (DL-25), `caller` the identity the service authenticated (decision D9), `meta`
441
+ the request's `_meta`. For a tool registered with an action, `record` is action_decision's recordAs: "caller"
442
+ (the caller's own act) or {by, via, caller}, the act to put in the service's audit record, and `decision` the
443
+ operator's decision it took (its `instruction` stays in that record, never in events or a page another party
444
+ reads), else None. `input_records` maps each answered input request to its record when input_actions is on."""
434
445
 
435
446
  def __init__(self, server: "McpToolServer", tool: str, arguments: Any, traceparent: str,
436
- delegation: Optional[Dict[str, Any]] = None):
447
+ delegation: Optional[Dict[str, Any]] = None, *, tier: Optional[str] = None, caller: Optional[str] = None,
448
+ meta: Optional[Dict[str, Any]] = None, record: Any = None, decision: Optional[Dict[str, Any]] = None,
449
+ input_records: Optional[Dict[str, Any]] = None):
437
450
  self.server = server
438
451
  self.tool = tool
439
452
  self.arguments = arguments
440
453
  self.traceparent = traceparent
441
454
  # DEC-0039: the accepted delegation (principal, caller, chain, claims) when the server has a gate.
442
455
  self.delegation = delegation
456
+ self.tier, self.caller, self.meta = tier, caller, {} if meta is None else meta
457
+ self.record, self.decision, self.input_records = record, decision, input_records
443
458
 
444
459
  def start_job(self, poll_interval_ms: Optional[int] = None) -> Dict[str, Any]:
445
460
  if self.server.jobs is None:
@@ -547,11 +562,26 @@ class McpToolServer:
547
562
  def __init__(self, name: str, version: str, jobs: Optional[JobStore] = None,
548
563
  on_job_started: Optional[Callable[[str, CallContext], None]] = None,
549
564
  on_input: Optional[OnInput] = None,
550
- authorize: Optional[Callable[[str, Any], Dict[str, Any]]] = None):
565
+ authorize: Optional[Callable[[str, Any], Dict[str, Any]]] = None, *,
566
+ identify: Optional[Callable[[Any, str], Optional[str]]] = None,
567
+ approve: Optional[Callable[[str, Dict[str, Any], Any, str], Optional[Dict[str, Any]]]] = None,
568
+ elicited: Optional[Callable[[str, Dict[str, Any], Optional[str]], bool]] = None,
569
+ input_actions: bool = False, clock: Callable[[], float] = time.time,
570
+ on_fault: Optional[Callable[[str, str], None]] = None):
551
571
  # authorize(tool, headers, tier=) runs before every handler (fabric_delegation.delegation_gate, DEC-0039):
552
572
  # a refusal is answered in the DL-19 shape and the handler never runs. The built-in job tools are
553
573
  # gated by the job's own capability, and answered only for the principal and caller that started it.
574
+ # DEC-0121: a tool added with an action runs action_gate after authorize and before its handler.
575
+ # identify(headers, tier) names the caller the service authenticated when no delegation did (D9; else
576
+ # local:T0 / local:T1); approve(tool, arguments, headers, digest) answers verify_approval's outcome for an
577
+ # approval the call presented, or None; elicited(tool, arguments, caller) is True only on the service's own
578
+ # record of an accepted elicitation. input_actions=True gates each answered input request as an action (D8).
579
+ # on_fault(tool, fault) hears a service fault the caller only sees as unknown-state (e.g. caller-missing: an
580
+ # action-gated server needs a tier, identify or authorize that names who calls).
554
581
  self._authorize = authorize
582
+ self._identify, self._approve, self._elicited = identify, approve, elicited
583
+ self.input_actions, self._clock, self._on_fault = input_actions is True, clock, on_fault
584
+ self.actions: Dict[str, Dict[str, Any]] = {}
555
585
  self.info = {"name": name, "version": version}
556
586
  self.jobs = jobs
557
587
  self.tools: List[Dict[str, Any]] = []
@@ -559,14 +589,101 @@ class McpToolServer:
559
589
  self._on_job_started = on_job_started
560
590
  self._on_input = on_input
561
591
 
562
- def add_tool(self, tool: Dict[str, Any], handler: Handler) -> None:
592
+ def add_tool(self, tool: Dict[str, Any], handler: Handler, action: Optional[Dict[str, Any]] = None, *,
593
+ target: Any = None, human_step_url: Optional[str] = None) -> None:
594
+ """Serve a tool. `action` (DEC-0121 AP-1) is the declared action this tool performs, {id, title, tool, gate,
595
+ humanStep?}, its `tool` this tool's name: the server runs action_gate before the handler. `target` names the
596
+ object a call acts on: an argument's name, or a function of the arguments; `human_step_url` is the link of a
597
+ `human` action's step (an https URL or a path on the service's origin, AP-4)."""
563
598
  if tool["name"] in self.handlers:
564
599
  raise InteropError("Tool %s is served twice." % tool["name"])
565
600
  if "outputSchema" in tool:
566
601
  require_object_root(tool["name"], tool["outputSchema"])
602
+ if action is not None:
603
+ if not readable_action(action) or action["tool"] != tool["name"]:
604
+ raise InteropError("Tool %s: its action is not a declared action of this tool (DEC-0121)." % tool["name"])
605
+ if action["id"] in {a["action"]["id"] for a in self.actions.values()}:
606
+ raise InteropError("Action %s is declared twice." % action["id"])
607
+ if action["gate"] == "human" and not (isinstance(human_step_url, str) and re.match(r"https://|/", human_step_url)):
608
+ raise InteropError("Tool %s: a human step needs its link, an https URL or a path (DEC-0121 AP-4)." % tool["name"])
609
+ if not (target is None or isinstance(target, str) or callable(target)):
610
+ raise InteropError("Tool %s: target is an argument's name or a function of the arguments." % tool["name"])
611
+ self.actions[tool["name"]] = {"action": dict(action), "target": target, "url": human_step_url}
567
612
  self.tools.append(tool)
568
613
  self.handlers[tool["name"]] = handler
569
614
 
615
+ def action_table(self) -> List[Dict[str, Any]]:
616
+ """The declared actions in the order they were added: build_well_known(actions=) writes them (DEC-0121 AP-1)."""
617
+ return [dict(entry["action"]) for entry in self.actions.values()]
618
+
619
+ def _caller(self, headers: Any, tier: str, verdict: Optional[Dict[str, Any]]) -> Optional[str]:
620
+ """D9: the delegation's caller when a gate accepted a token, else identify(headers, tier), else the local floor."""
621
+ authenticated = (verdict or {}).get("caller")
622
+ if not authenticated and self._identify is not None:
623
+ authenticated = self._identify(headers, tier)
624
+ return action_caller(tier, authenticated)
625
+
626
+ def _gate_action(self, action: Dict[str, Any], name: str, arguments: Dict[str, Any], target: Any, url: Any,
627
+ headers: Any, tier: str, meta: Dict[str, Any], caller: Optional[str]) -> Dict[str, Any]:
628
+ """action_gate for one call: the approval the call presented, the digest of {tool, arguments}, the clock."""
629
+ digest, approval = None, None
630
+ if action["gate"] == "relayed":
631
+ import fabric_delegation as _fd # noqa: E402 - only an action-gated server needs it
632
+ try:
633
+ digest = _fd.action_digest({"tool": name, "arguments": arguments})
634
+ except (TypeError, ValueError, RecursionError): # no canonical form (a lone surrogate, a non-finite number,
635
+ # nesting past the recursion limit), as intent_digest reads it: no digest to bind
636
+ return {"ok": False, "invalid": "Invalid params: the arguments have no canonical form."}
637
+ outcome = self._approve(name, arguments, headers, digest) if self._approve is not None else None
638
+ if isinstance(outcome, dict) and outcome.get("ok") is True:
639
+ approval = outcome
640
+ elif isinstance(outcome, dict) and outcome.get("code") not in (None, "approval-required"):
641
+ # DL-18: the presented approval's own refusal; an outcome that is not verify_approval's is the service's.
642
+ readable = outcome.get("code") in _fd.REFUSAL_CODES and isinstance(outcome.get("detail"), str) and outcome["detail"]
643
+ if readable:
644
+ return {"ok": False, "refusal": outcome}
645
+ return {"ok": False, "refusal": {"ok": False, "code": "unknown-state", "detail": "service-fault"},
646
+ "fault": "approval-outcome-unreadable"}
647
+ elicited = action["gate"] == "relayed" and self._elicited is not None and self._elicited(name, arguments, caller) is True
648
+ return action_gate(action, tier=tier, caller=caller, meta=meta, target=target, elicited=elicited, approval=approval,
649
+ action_digest=digest, human_step_url=url, now=format_rfc3339(self._clock() * 1000))
650
+
651
+ def _gate_inputs(self, arguments: Dict[str, Any], headers: Any, tier: str, meta: Dict[str, Any],
652
+ caller: Optional[str]) -> Dict[str, Any]:
653
+ """D8: each input request an answer would take is an action (id its key, tool fabric.job.get, gate relayed),
654
+ one decision per call; the first refusal refuses the call and nothing is applied."""
655
+ job_id = str(arguments.get("id", ""))
656
+ job = self.jobs.get(job_id)
657
+ asked = job.get("inputRequests", {}) if job["status"] == "input_required" else {}
658
+ records: Dict[str, Any] = {}
659
+ for key, value in arguments["inputResponses"].items():
660
+ if key not in asked or not isinstance(value, dict) or value.get("action") not in ("accept", "decline", "cancel"):
661
+ continue
662
+ action = {"id": key, "title": "Answer %s" % key, "tool": "fabric.job.get", "gate": "relayed"}
663
+ gated = self._gate_action(action, "fabric.job.get", {"id": job_id, "inputResponses": {key: value}}, None, None,
664
+ headers, tier, meta, caller)
665
+ if not gated["ok"]:
666
+ return gated
667
+ records[key] = gated["record"]
668
+ return {"ok": True, "records": records}
669
+
670
+ def _fault(self, tool: str, gated: Dict[str, Any]) -> None:
671
+ """Hand a service fault to on_fault; the caller saw only unknown-state. A hook that raises is the service's."""
672
+ if gated.get("fault") and self._on_fault is not None:
673
+ try:
674
+ self._on_fault(tool, str(gated["fault"]))
675
+ except Exception: # noqa: BLE001 - logging must not change the answer
676
+ pass
677
+
678
+ @staticmethod
679
+ def _target(target: Any, arguments: Dict[str, Any]) -> Optional[str]:
680
+ """The object a call acts on: the named argument, or what the service's function answers; a string or None."""
681
+ try:
682
+ value = target(arguments) if callable(target) else arguments.get(target) if isinstance(target, str) else None
683
+ except Exception: # noqa: BLE001 - a function that cannot read this call names no object
684
+ return None
685
+ return value if isinstance(value, str) else None
686
+
570
687
  def job_started(self, job_id: str, ctx: CallContext) -> None:
571
688
  if self._on_job_started:
572
689
  self._on_job_started(job_id, ctx)
@@ -641,7 +758,21 @@ class McpToolServer:
641
758
  if not verdict.get("ok"):
642
759
  import fabric_delegation as _fd # noqa: E402 - only a gated server needs it
643
760
  return {"jsonrpc": "2.0", "id": rid, "result": _fd.refusal_tool_result(verdict, traceparent=span)}
644
- return {"jsonrpc": "2.0", "id": rid, "result": self._job_call(name, arguments, span, verdict)}
761
+ caller, inputs = self._caller(headers, tier, verdict), None
762
+ responses = arguments.get("inputResponses")
763
+ if name == "fabric.job.get" and self.input_actions and isinstance(responses, dict) and responses:
764
+ try:
765
+ inputs = self._gate_inputs(arguments, headers, tier, meta, caller)
766
+ except UnknownJob:
767
+ return {"jsonrpc": "2.0", "id": rid, "result": unknown_job_result(str(arguments.get("id", "")), traceparent=span)}
768
+ if not inputs["ok"] and "invalid" in inputs:
769
+ return self._error(rid, -32602, inputs["invalid"])
770
+ if not inputs["ok"]:
771
+ self._fault(name, inputs)
772
+ import fabric_delegation as _fd # noqa: E402 - DL-19
773
+ return {"jsonrpc": "2.0", "id": rid, "result": _fd.refusal_tool_result(inputs["refusal"], traceparent=span)}
774
+ context = {"tier": tier, "caller": caller, "meta": meta, "input_records": inputs["records"] if inputs else None}
775
+ return {"jsonrpc": "2.0", "id": rid, "result": self._job_call(name, arguments, span, verdict, context)}
645
776
  handler = self.handlers.get(name)
646
777
  if handler is None:
647
778
  return self._error(rid, -32602, "Unknown tool: %s." % name)
@@ -652,14 +783,26 @@ class McpToolServer:
652
783
  import fabric_delegation as _fd # noqa: E402 - only a gated server needs it
653
784
  return {"jsonrpc": "2.0", "id": rid, "result": _fd.refusal_tool_result(verdict, traceparent=span)}
654
785
  delegation = verdict
786
+ caller, gated, entry = self._caller(headers, tier, delegation), {"record": None, "decision": None}, self.actions.get(name)
787
+ if entry is not None: # DEC-0121: after the authorize gate, before the handler
788
+ gated = self._gate_action(entry["action"], name, arguments, self._target(entry["target"], arguments), entry["url"],
789
+ headers, tier, meta, caller)
790
+ if not gated["ok"] and "invalid" in gated:
791
+ return self._error(rid, -32602, gated["invalid"])
792
+ if not gated["ok"]:
793
+ self._fault(name, gated)
794
+ import fabric_delegation as _fd # noqa: E402 - DL-19
795
+ return {"jsonrpc": "2.0", "id": rid, "result": _fd.refusal_tool_result(gated["refusal"], traceparent=span)}
796
+ context = CallContext(self, name, arguments, span, delegation, tier=tier, caller=caller, meta=meta,
797
+ record=gated["record"], decision=gated["decision"])
655
798
  try:
656
- structured = handler(arguments, CallContext(self, name, arguments, span, delegation))
799
+ structured = handler(arguments, context)
657
800
  except InteropError as exc:
658
801
  return {"jsonrpc": "2.0", "id": rid, "result": tool_result({"error": {"code": "tool-error", "message": str(exc)}}, is_error=True, traceparent=span)}
659
802
  return {"jsonrpc": "2.0", "id": rid, "result": tool_result(structured, traceparent=span)}
660
803
 
661
804
  def _job_call(self, name: str, arguments: Dict[str, Any], span: str,
662
- delegation: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
805
+ delegation: Optional[Dict[str, Any]] = None, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
663
806
  job_id = str(arguments.get("id", ""))
664
807
  try:
665
808
  job_span = self.jobs.traceparent(job_id) or span
@@ -674,11 +817,14 @@ class McpToolServer:
674
817
  state = self.jobs.get(job_id)
675
818
  return tool_result({"job": state}, traceparent=job_span)
676
819
  responses = arguments.get("inputResponses")
820
+ context = context or {}
821
+ if responses and context.get("input_records") is not None: # D8: only the keys the action gate took
822
+ responses = {key: responses[key] for key in context["input_records"]}
677
823
  if responses:
678
824
  matched = self.jobs.answer(job_id, responses)
679
825
  if matched and self._on_input:
680
826
  private = self.jobs.private(job_id)
681
- self._on_input(job_id, matched, CallContext(self, private["capability"], private["input"], job_span, delegation))
827
+ self._on_input(job_id, matched, CallContext(self, private["capability"], private["input"], job_span, delegation, **context))
682
828
  return tool_result({"job": self.jobs.get(job_id)}, traceparent=job_span)
683
829
  except UnknownJob:
684
830
  return unknown_job_result(job_id, traceparent=span)
@@ -17,6 +17,7 @@ if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from
17
17
  _sys.path.insert(0, str(_Path(__file__).resolve().parent))
18
18
 
19
19
  from fabric_base import ServiceError # noqa: E402
20
+ from fabric_actions import action_decision, decision_shape # noqa: E402 - DEC-0121
20
21
 
21
22
  PROTOCOL = "fabric-interop/0.1"
22
23
  EXTENSION_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
@@ -288,14 +289,56 @@ def reads_null_usage(request_meta: Any) -> bool:
288
289
  return isinstance(protocols, list) and INTEROP_NULL_USAGE in protocols
289
290
 
290
291
 
291
- def request_meta(traceparent: Optional[str] = None) -> Dict[str, Any]:
292
- """A request's `_meta` from this kit as a caller: its trace, and the interop revisions it reads (DEC-0087)."""
292
+ def request_meta(traceparent: Optional[str] = None, decision: Any = None) -> Dict[str, Any]:
293
+ """A request's `_meta` from this kit as a caller: its trace, the interop revisions it reads (DEC-0087) and, for a
294
+ relayed action, the operator's decision (DEC-0121 AP-2; build one with fabric_actions.operator_decision). A
295
+ decision travels here only, never in a tool's arguments; one decision.schema.json refuses raises InteropError."""
293
296
  meta: Dict[str, Any] = {"protocols": list(PROTOCOLS_READ)}
294
297
  if parse_traceparent(traceparent):
295
298
  meta["traceparent"] = traceparent
299
+ if decision is not None:
300
+ if not decision_shape(decision):
301
+ raise InteropError("The decision is not one decision.schema.json reads; it is not sent.")
302
+ meta["decision"] = dict(decision)
296
303
  return meta
297
304
 
298
305
 
306
+ def action_gate(action: Any, *, tier: Any, caller: Any, meta: Any = None, target: Optional[str] = None,
307
+ elicited: bool = False, approval: Any = None, action_digest: Optional[str] = None,
308
+ human_step_url: Optional[str] = None, now: Any = None) -> Dict[str, Any]:
309
+ """One call of a declared action, before its handler runs (DEC-0121 AP-2, AP-4): fabric_actions.action_decision over
310
+ the call's `_meta.decision` (read from `meta` only, never from arguments, decision D11), the service's authenticated
311
+ `caller` (fabric_actions.action_caller), `target` (the object the call acts on), `elicited` (the service's own
312
+ record of an accepted elicitation), `approval` (verify_approval's accepted outcome), `action_digest`,
313
+ `human_step_url` and `now` (RFC 3339). Answers {ok: True, record, decision}, `decision` being the one taken as the
314
+ operator's (None otherwise), or {ok: False, refusal} in the DL-19 form (fabric_delegation.refusal_tool_result
315
+ sends it); a service's own fault is the refusal unknown-state with `fault` beside it for the service's log."""
316
+ import fabric_delegation as _fd # noqa: E402 - beside this file: DL-19
317
+ inputs: Dict[str, Any] = {"action": action, "tier": tier, "caller": caller, "elicited": elicited is True, "now": now}
318
+ if isinstance(meta, dict) and "decision" in meta:
319
+ inputs["decision"] = meta["decision"]
320
+ for name, value in (("target", target), ("approval", approval), ("actionDigest", action_digest), ("humanStepUrl", human_step_url)):
321
+ if value is not None:
322
+ inputs[name] = value
323
+ answer = action_decision(inputs)
324
+ if answer.get("ok") is True:
325
+ record = answer["recordAs"]
326
+ taken = isinstance(record, dict) and record.get("via") == "relayed-instruction"
327
+ return {"ok": True, "record": record, "decision": inputs["decision"] if taken else None}
328
+ out: Dict[str, Any] = {"ok": False, "refusal": _fd.refusal_from_action(answer)}
329
+ if "fault" in answer:
330
+ out["fault"] = answer["fault"]
331
+ return out
332
+
333
+
334
+ def action_refusal_tool_result(answer: Any, traceparent: Optional[str] = None) -> Optional[Dict[str, Any]]:
335
+ """The tools/call result a service's own MCP server answers for an action_decision answer: None when the action
336
+ takes the call, else DL-19's refusal as structuredContent with isError true (DEC-0121)."""
337
+ import fabric_delegation as _fd # noqa: E402 - beside this file: DL-19
338
+ refusal = _fd.refusal_from_action(answer)
339
+ return None if refusal is None else _fd.refusal_tool_result(refusal, traceparent=traceparent)
340
+
341
+
299
342
  def usage_findings(usage: Any, at: str) -> List[Dict[str, str]]:
300
343
  """FAC-SEM-119 on a usage: partial false asserts every number known, so it stands beside no null."""
301
344
  if not isinstance(usage, dict) or usage.get("partial", _ABSENT) is not False:
@@ -15,7 +15,7 @@ if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from
15
15
  PROTOCOL_REVISIONS: Dict[str, Tuple[str, ...]] = {
16
16
  "access-log": ("1",), "approval": ("1",), "activity-summary": ("1",), "agent-directory": ("1",), "call-record": ("1",), "coding-agents": ("1",), "credential-decision": ("1",), "credential-request": ("1",), "credential-revocation": ("1",), "enroll-token": ("1",), "entitlement": ("1",),
17
17
  "entitlement-key-set": ("1",), "estate-record": ("1",), "execution-packet": ("1",),
18
- "fabric-activity": ("0.1",), "fabric-agent-call": ("1",), "fabric-connections": ("0.1",), "fabric-credential-envelope": ("1",), "fabric-load": ("0.1",), "fabric-dashboard": ("0.1",), "fabric-delegation": ("0.1",), "fabric-device": ("0.1",),
18
+ "fabric-actions": ("0.1",), "fabric-activity": ("0.1",), "fabric-agent-call": ("1",), "fabric-connections": ("0.1",), "fabric-credential-envelope": ("1",), "fabric-load": ("0.1",), "fabric-dashboard": ("0.1",), "fabric-delegation": ("0.1",), "fabric-device": ("0.1",),
19
19
  "fabric-interop": ("0.1", "0.2"), "fabric-local-channel": ("0.1",), "fabric-local-runner": ("0.1",),
20
20
  "fabric-operator-channel": ("0.1",), "fabric-package": ("1",), "fabric-project-comms": ("0.1",), "fabric-proposal": ("1",),
21
21
  "fabric-provider": ("0.1",), "fabric-relay": ("0.1",), "fabric-secret-envelope": ("1",), "fabric-service": ("0.1",),
@@ -181,6 +181,15 @@ from fabric_backups import ( # noqa: F401
181
181
  newest_intact_backup,
182
182
  restore_plan,
183
183
  )
184
+ from fabric_actions import ( # noqa: F401
185
+ ACTIONS_EXTENSION_KEY,
186
+ action_decision,
187
+ action_rules,
188
+ actions_extension,
189
+ actions_extension_rules,
190
+ actions_view,
191
+ decision_shape,
192
+ )
184
193
  from fabric_connections import ( # noqa: F401
185
194
  CONNECTIONS_EXTENSION_KEY,
186
195
  connections_extension,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.49.3",
3
+ "version": "0.50.0",
4
4
  "about": "The service kit's modules in dependency order: a module imports only modules listed before it. Written by fabric_kit.py build; edit the order and layers by hand, never the digests.",
5
5
  "python": [
6
6
  {
@@ -89,7 +89,7 @@
89
89
  "fabric_jsvalue",
90
90
  "fabric_paths"
91
91
  ],
92
- "sha256": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36"
92
+ "sha256": "f6c135126d2b9f6ff0c93f2947be1aa03d99460cb759495f51fe86f3edf481b3"
93
93
  },
94
94
  {
95
95
  "module": "fabric_share",
@@ -150,7 +150,18 @@
150
150
  "imports": [
151
151
  "fabric_secrets"
152
152
  ],
153
- "sha256": "28e693282cddff8e4396ae39ce6fa935e875e8ddac7375bea2c189dd6fb04970"
153
+ "sha256": "4e78c4004d5d8eb26d1a6e40d6ab75f68af2a7f9165d386f9a62e79e69dcae19"
154
+ },
155
+ {
156
+ "module": "fabric_actions",
157
+ "file": "fabric_actions.py",
158
+ "layer": "L3",
159
+ "imports": [
160
+ "fabric_base",
161
+ "fabric_jsvalue",
162
+ "fabric_time"
163
+ ],
164
+ "sha256": "44feb5e70bd9894c5ed1fa23bf8df55989ec99acb934b24bf97ddd7295891251"
154
165
  },
155
166
  {
156
167
  "module": "fabric_connections",
@@ -195,13 +206,14 @@
195
206
  "fabric_paths",
196
207
  "fabric_usage"
197
208
  ],
198
- "sha256": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb"
209
+ "sha256": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420"
199
210
  },
200
211
  {
201
212
  "module": "fabric_descriptor",
202
213
  "file": "fabric_descriptor.py",
203
214
  "layer": "L3",
204
215
  "imports": [
216
+ "fabric_actions",
205
217
  "fabric_base",
206
218
  "fabric_connections",
207
219
  "fabric_paths",
@@ -209,7 +221,7 @@
209
221
  "fabric_secrets",
210
222
  "fabric_usage"
211
223
  ],
212
- "sha256": "ffb573c485f8b4d3c7b0a94ccc53472cd71d9800daf63b5f3f7ff53f2ac38026"
224
+ "sha256": "361610f7c5bc94642a26a6dd7e4b6a5849efa0bd2aa814a8e2034eaed30368b6"
213
225
  },
214
226
  {
215
227
  "module": "fabric_auth",
@@ -261,6 +273,7 @@
261
273
  "file": "fabric_service.py",
262
274
  "layer": "L3",
263
275
  "imports": [
276
+ "fabric_actions",
264
277
  "fabric_auth",
265
278
  "fabric_backups",
266
279
  "fabric_base",
@@ -277,7 +290,7 @@
277
290
  "fabric_time",
278
291
  "fabric_usage"
279
292
  ],
280
- "sha256": "103ab4026f96e127c01cd91e4ad44f6b0ce2424ff33f078ed106b39e773a10e3"
293
+ "sha256": "a2e813469a47d4d3eea996628635c8903007498bdb6a1ae032a7a5ce6f49fe2f"
281
294
  },
282
295
  {
283
296
  "module": "fabric_profiles",
@@ -297,23 +310,26 @@
297
310
  "file": "fabric_interop_rules.py",
298
311
  "layer": "L3",
299
312
  "imports": [
313
+ "fabric_actions",
300
314
  "fabric_base",
301
315
  "fabric_delegation",
302
316
  "fabric_time"
303
317
  ],
304
- "sha256": "b5f0ecb1791b4c50c5ac151f987f692ed7001bc74841a8a161125ec775145e09"
318
+ "sha256": "33dfb0841077df5379df425e288c1e01ae03c22344e6870024b5f4231ad89e75"
305
319
  },
306
320
  {
307
321
  "module": "fabric_interop",
308
322
  "file": "fabric_interop.py",
309
323
  "layer": "L3",
310
324
  "imports": [
325
+ "fabric_actions",
311
326
  "fabric_base",
312
327
  "fabric_delegation",
313
328
  "fabric_interop_rules",
314
- "fabric_service"
329
+ "fabric_service",
330
+ "fabric_time"
315
331
  ],
316
- "sha256": "f92fa111328fe3811438f733d8f0eb5295e6941c774123a78e26318bdcb35fc3"
332
+ "sha256": "0999ded29d908c8a55d13061f0b501887a5ea259dbf7d2251a90c04018761fbb"
317
333
  },
318
334
  {
319
335
  "module": "fabric_hub",
@@ -485,7 +501,7 @@
485
501
  "fabric-jsvalue",
486
502
  "fabric-paths"
487
503
  ],
488
- "sha256": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7"
504
+ "sha256": "fab1efdc1a9a0a9a00768905dded538fe185ca1b63c115dc4273717efc8e81f6"
489
505
  },
490
506
  {
491
507
  "module": "fabric-share",
@@ -544,7 +560,18 @@
544
560
  "imports": [
545
561
  "fabric-secrets"
546
562
  ],
547
- "sha256": "dc039ec3612d0090c9ba6a3a949b0b7df54712bead300dcb6cc8b9bc305cd183"
563
+ "sha256": "acd32d9d05cf1a0319e84ad77cede479ef9b6337011e2cf7726e0e8aa590a9e0"
564
+ },
565
+ {
566
+ "module": "fabric-actions",
567
+ "file": "fabric-actions.mjs",
568
+ "layer": "L3",
569
+ "imports": [
570
+ "fabric-base",
571
+ "fabric-jsvalue",
572
+ "fabric-time"
573
+ ],
574
+ "sha256": "66f9ca7d1da31c89eeedffe7b78ff2230361d375d44c3aa90d9c60b66dfe5cbe"
548
575
  },
549
576
  {
550
577
  "module": "fabric-connections",
@@ -590,13 +617,14 @@
590
617
  "fabric-protocols",
591
618
  "fabric-usage"
592
619
  ],
593
- "sha256": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005"
620
+ "sha256": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06"
594
621
  },
595
622
  {
596
623
  "module": "fabric-descriptor",
597
624
  "file": "fabric-descriptor.mjs",
598
625
  "layer": "L3",
599
626
  "imports": [
627
+ "fabric-actions",
600
628
  "fabric-base",
601
629
  "fabric-connections",
602
630
  "fabric-paths",
@@ -604,7 +632,7 @@
604
632
  "fabric-secrets",
605
633
  "fabric-usage"
606
634
  ],
607
- "sha256": "673bff656b403c3ef77a93878eaf4eda91c859df4b4275028fe303fcc153dd64"
635
+ "sha256": "c1ccaa9dff5edf90b82d6d77e2e5189a5d0f677fd48cc0aed3cccb83ad2a1f12"
608
636
  },
609
637
  {
610
638
  "module": "fabric-auth",
@@ -657,6 +685,7 @@
657
685
  "file": "fabric-service.mjs",
658
686
  "layer": "L3",
659
687
  "imports": [
688
+ "fabric-actions",
660
689
  "fabric-auth",
661
690
  "fabric-backups",
662
691
  "fabric-base",
@@ -671,7 +700,7 @@
671
700
  "fabric-supervisors",
672
701
  "fabric-usage"
673
702
  ],
674
- "sha256": "1b4639a10c530c1241d317c5e9cf3a10cd96e5d9af8b29fc1b1844de5c63bfec"
703
+ "sha256": "6aa673657427211b491a9b63f94b9847a7e6aa3ee1b7353e1af11f46469803a3"
675
704
  },
676
705
  {
677
706
  "module": "fabric-profiles",
@@ -690,11 +719,12 @@
690
719
  "file": "fabric-interop-rules.mjs",
691
720
  "layer": "L3",
692
721
  "imports": [
722
+ "fabric-actions",
693
723
  "fabric-base",
694
724
  "fabric-delegation",
695
725
  "fabric-jsvalue"
696
726
  ],
697
- "sha256": "57a8081307956e8c381119b361fa5232a7bf791815be8e3541eb5492820e2ea5"
727
+ "sha256": "fc8f3044afe209a1b23ddb3bc9b3993f4831913fa66bfd6e7855f1be80428e43"
698
728
  },
699
729
  {
700
730
  "module": "fabric-interop",
@@ -705,7 +735,7 @@
705
735
  "fabric-interop-rules",
706
736
  "fabric-service"
707
737
  ],
708
- "sha256": "581983ec3e197e5c2340ad25e8468320c69a2459c90097c7fcf2699766b4fb26"
738
+ "sha256": "8f1ca418112d7129402783376bb9cbc9d2607f2aceec2a6796e703591a88f535"
709
739
  },
710
740
  {
711
741
  "module": "fabric-hub",
@@ -3179,6 +3209,84 @@
3179
3209
  "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
3180
3210
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
3181
3211
  "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
3212
+ },
3213
+ "0.50.0": {
3214
+ "fabric_jsvalue.py": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28",
3215
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
3216
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
3217
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
3218
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
3219
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
3220
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
3221
+ "fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
3222
+ "fabric_estate.py": "a0ddc49d748c1efb1830de176998d6c8eac30f77d387c71eae12549c52aaab27",
3223
+ "fabric_delegation.py": "f6c135126d2b9f6ff0c93f2947be1aa03d99460cb759495f51fe86f3edf481b3",
3224
+ "fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
3225
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
3226
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
3227
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
3228
+ "fabric_protocols.py": "4e78c4004d5d8eb26d1a6e40d6ab75f68af2a7f9165d386f9a62e79e69dcae19",
3229
+ "fabric_actions.py": "44feb5e70bd9894c5ed1fa23bf8df55989ec99acb934b24bf97ddd7295891251",
3230
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
3231
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
3232
+ "fabric_usage.py": "b1f335cab4625171a616cf68cf3fd892b18bdadcf4baec3a3a0fdcf12785c170",
3233
+ "fabric_backups.py": "1f3f0d7ecf361b29e9101e07f1c53e5dc304d598bf26fd59ec89f16b9fed2420",
3234
+ "fabric_descriptor.py": "361610f7c5bc94642a26a6dd7e4b6a5849efa0bd2aa814a8e2034eaed30368b6",
3235
+ "fabric_auth.py": "71115d18c67b7b806cb57b64b77e01ad97fc0e63e6f49ee850f778123617d905",
3236
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
3237
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
3238
+ "fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
3239
+ "fabric_service.py": "a2e813469a47d4d3eea996628635c8903007498bdb6a1ae032a7a5ce6f49fe2f",
3240
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
3241
+ "fabric_interop_rules.py": "33dfb0841077df5379df425e288c1e01ae03c22344e6870024b5f4231ad89e75",
3242
+ "fabric_interop.py": "0999ded29d908c8a55d13061f0b501887a5ea259dbf7d2251a90c04018761fbb",
3243
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
3244
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
3245
+ "fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
3246
+ "fabric_board_session.py": "2845d7d758b06ac4588bdf825a471c201a46ce535a631805697b1e35495fead6",
3247
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
3248
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
3249
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
3250
+ "fabric_openrouter.py": "309f588f9c976b1061323f3161e35d86248afd5b59b16391afbfc4e1099ecd65",
3251
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
3252
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
3253
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
3254
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
3255
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
3256
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
3257
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
3258
+ "fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
3259
+ "fabric-estate.mjs": "b6610aaad9d00d8a7f42955627fb81725950841455bcedfc050abb0e079f0f93",
3260
+ "fabric-delegation.mjs": "fab1efdc1a9a0a9a00768905dded538fe185ca1b63c115dc4273717efc8e81f6",
3261
+ "fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
3262
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
3263
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
3264
+ "fabric-package.mjs": "13913ea6d12056c3a31b8222b6c4aab8395941fe519913692e1a06d8672e1c88",
3265
+ "fabric-protocols.mjs": "acd32d9d05cf1a0319e84ad77cede479ef9b6337011e2cf7726e0e8aa590a9e0",
3266
+ "fabric-actions.mjs": "66f9ca7d1da31c89eeedffe7b78ff2230361d375d44c3aa90d9c60b66dfe5cbe",
3267
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
3268
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
3269
+ "fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
3270
+ "fabric-backups.mjs": "cfd8c9085e3d597a783099c2bd2ffe2f3faa0daaadab46dedd30523894cfeb06",
3271
+ "fabric-descriptor.mjs": "c1ccaa9dff5edf90b82d6d77e2e5189a5d0f677fd48cc0aed3cccb83ad2a1f12",
3272
+ "fabric-auth.mjs": "782c2aa732f8320f604194660117640a7ea145e12b8155c04e88cef7c516a5b3",
3273
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
3274
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
3275
+ "fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
3276
+ "fabric-service.mjs": "6aa673657427211b491a9b63f94b9847a7e6aa3ee1b7353e1af11f46469803a3",
3277
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
3278
+ "fabric-interop-rules.mjs": "fc8f3044afe209a1b23ddb3bc9b3993f4831913fa66bfd6e7855f1be80428e43",
3279
+ "fabric-interop.mjs": "8f1ca418112d7129402783376bb9cbc9d2607f2aceec2a6796e703591a88f535",
3280
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
3281
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
3282
+ "fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
3283
+ "fabric-board-session.mjs": "26c9b0b2818a4cedc5248b08ea6732e2ee4ee81e31f6fee2b572a019db813c93",
3284
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
3285
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
3286
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
3287
+ "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
3288
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
3289
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
3182
3290
  }
3183
3291
  },
3184
3292
  "data": {