@passioncode-ai/fabric-agent-adapter 0.49.0 → 0.49.2

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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.49.2 (2026-10-24)
4
+
5
+ ### Fixed
6
+
7
+ - **The probe judges a remote service's session cookie by its name** (FAA-115, a finding from a service on
8
+ DigitalOcean App Platform). `check_service.py` kept one `Set-Cookie` per answer, so when a CDN edge added its own
9
+ cookie to the login redirect (`__cf_bm` from Cloudflare) `login.cookie-host-bound` and `login.single-use` judged the
10
+ edge's cookie and failed a service that does everything right. The probe now keeps every `Set-Cookie`
11
+ (`merge_headers`) and picks the session cookie by name, `__Host-fabric_session` or `fabric_session`; with neither,
12
+ the first cookie a known edge does not set (`session_cookie` in `check_service_rules.py`). A session cookie that is
13
+ not host-bound still fails.
14
+
15
+ ## 0.49.1 (2026-10-24)
16
+
17
+ ### Fixed
18
+
19
+ - **The Python estate kit matches every membership as the contract does** (FAA-113, from the 0.49.0 seam reading).
20
+ The contract keeps machine ids, passkeys, publisher keys, grants and revocations in a `Set`, or tests them with
21
+ `includes`; both match NaN with NaN. Fifteen lookups in `fabric_estate.py` used `===`-style matching, so a record a
22
+ Python caller builds with a NaN id was read differently from the Node twin: a NaN id listed twice passed, and a
23
+ secret from a NaN machine was flagged as coming from outside the estate. They now use `set_has`. No JSON the kit
24
+ loads carries NaN (`parse_constant` refuses it), so a service reading records from JSON saw no difference.
25
+
26
+ ### Tests
27
+
28
+ - FAC-SEM-137 on the verify path (FAA-112): six `verify` vectors vary the delta anchor on the contract's EdDSA
29
+ revision 2, re-signed with the vectors' test seed and answered by the contract's `verifyEstateRecord`.
30
+ - The board restart cases wait on their condition, not a fixed 20 ms (FAA-114).
31
+
3
32
  ## 0.49.0 (2026-10-24)
4
33
 
5
34
  The operator's sweep (FAA-111): blind seam and product readings of 0.47.0 to 0.48.3, their findings fixed, documented
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.49.0",
3
+ "version": "0.49.2",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
6
- "version": "0.49.0",
6
+ "version": "0.49.2",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,7 +5,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.49.0"
8
+ version: "0.49.2"
9
9
  contract-version: "0.1.0"
10
10
  contract-commit: "2f020a696453ae299b123517f14957f6a1354379"
11
11
  ---
@@ -14,7 +14,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
14
14
  compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
15
15
  metadata:
16
16
  author: PassionCode.ai
17
- version: "0.49.0"
17
+ version: "0.49.2"
18
18
  contract-version: "0.1.0"
19
19
  extension: "fabric-service/0.1"
20
20
  extension-commit: "2f020a696453ae299b123517f14957f6a1354379"
@@ -3,6 +3,15 @@
3
3
  Normative source: `fabric-agent-contract` `docs/specification/service.md` → *Remote placement*,
4
4
  DEC-0019, at the commit this skill pins. This page is the how-to.
5
5
 
6
+ ## Contents
7
+
8
+ - [Is it a remote service?](#is-it-a-remote-service)
9
+ - [The descriptor](#the-descriptor)
10
+ - [On the platform](#on-the-platform)
11
+ - [Checking it](#checking-it)
12
+ - [What a host does and will not do](#what-a-host-does-and-will-not-do)
13
+ - [Rotation](#rotation)
14
+
6
15
  ## Is it a remote service?
7
16
 
8
17
  It is, when it runs outside the operator's computer and the operator wants it in Fabric beside
@@ -77,6 +86,10 @@ FAILs remain possible, and the evidence names the value it found: a proxy page t
77
86
  `id.instance` (an application the proxy titles after the service), and, when the document was read, a page that happens
78
87
  to contain the service's pid as a whole number (an inline script's constant, say).
79
88
 
89
+ A CDN edge may add its own cookie to the login redirect (DigitalOcean App Platform's Cloudflare edge sets `__cf_bm`).
90
+ The login rules keep every `Set-Cookie` of the answer and judge the session cookie by its name, `__Host-fabric_session`
91
+ (or `fabric_session`); with neither, the first cookie that is not a known edge cookie (FAA-115).
92
+
80
93
  ## What a host does and will not do
81
94
 
82
95
  | Situation | Host |
@@ -68,6 +68,7 @@ from check_service_rules import session_lifetime_problem # noqa: E402,F401 - mo
68
68
  from check_service_rules import _semver # noqa: E402,F401 - moved (FAA-65)
69
69
  from check_service_rules import usage_problems # noqa: E402,F401 - moved (FAA-65)
70
70
  from check_service_rules import same_repository # noqa: E402,F401 - moved (FAA-65)
71
+ from check_service_rules import merge_headers, session_cookie # noqa: E402,F401 - FAA-115
71
72
 
72
73
 
73
74
  class _PinnedHTTPS(http.client.HTTPSConnection):
@@ -131,7 +132,7 @@ class Probe:
131
132
  try:
132
133
  conn.request(method, path, body=body, headers=base)
133
134
  resp = conn.getresponse()
134
- return resp.status, {k.lower(): v for k, v in resp.getheaders()}, resp.read()
135
+ return resp.status, merge_headers(resp.getheaders()), resp.read()
135
136
  finally:
136
137
  conn.close()
137
138
 
@@ -444,7 +445,7 @@ class Probe:
444
445
  except (OSError, ValueError, KeyError) as exc:
445
446
  self.add("login.single-use", "FAIL", "login-code flow broke: %s" % exc)
446
447
  return
447
- cookie = headers1.get("set-cookie", "")
448
+ cookie = session_cookie(headers1.get("set-cookie", ""), self.remote)
448
449
  ok = status1 in (302, 303) and "HttpOnly" in cookie and "SameSite=Strict" in cookie and status2 not in (302, 303)
449
450
  if self.remote:
450
451
  host_bound = cookie.startswith("__Host-") and "Secure" in cookie and "Path=/" in cookie and "domain=" not in cookie.lower()
@@ -13,7 +13,7 @@ import subprocess
13
13
  import sys
14
14
  import time
15
15
  from pathlib import Path
16
- from typing import Any, Dict, List, Optional, Tuple
16
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
17
17
 
18
18
  sys.path.insert(0, str(Path(__file__).resolve().parent))
19
19
  import fabric_service as fs # noqa: E402 - beside this file
@@ -185,6 +185,36 @@ def board_responder_verdict(wk: Any, kit: Optional[str] = None) -> Tuple[str, st
185
185
  ", ".join(declared), " at %s" % kit if kit else " beside this probe")
186
186
 
187
187
 
188
+ def merge_headers(pairs: Iterable[Tuple[str, str]]) -> Dict[str, str]:
189
+ """Response headers by lower-case name. Every Set-Cookie is kept, one per line (a header value never holds a
190
+ newline): an edge in front of a service adds its own (FAA-115), so the last or the first is not the service's."""
191
+ merged: Dict[str, str] = {}
192
+ for name, value in pairs:
193
+ key = name.lower()
194
+ merged[key] = merged[key] + "\n" + value if key == "set-cookie" and key in merged else value
195
+ return merged
196
+
197
+
198
+ def session_cookie(set_cookie: str, remote: bool) -> str:
199
+ """The service's session cookie among the Set-Cookie lines merge_headers kept: the one named as the kit names it
200
+ (`__Host-fabric_session` remote, `fabric_session` local, either accepted), else the first a CDN edge does not
201
+ set, so a service that names its cookie otherwise is still judged on its own cookie. "" when none was set."""
202
+ lines = [line.strip() for line in set_cookie.split("\n") if line.strip()]
203
+ names = (fs.REMOTE_SESSION_COOKIE, fs.SESSION_COOKIE) if remote else (fs.SESSION_COOKIE, fs.REMOTE_SESSION_COOKIE)
204
+ for wanted in names:
205
+ for line in lines:
206
+ if line.split("=", 1)[0].strip() == wanted:
207
+ return line
208
+ for line in lines:
209
+ if line.split("=", 1)[0].strip() not in EDGE_COOKIES:
210
+ return line
211
+ return lines[0] if lines else ""
212
+
213
+
214
+ # Cookies CDN edges add on their own (Cloudflare, AWS load balancers, Akamai); never a service's session.
215
+ EDGE_COOKIES = frozenset({"__cf_bm", "_cfuvid", "cf_clearance", "__cflb", "AWSALB", "AWSALBCORS", "ak_bmsc", "bm_sv"})
216
+
217
+
188
218
  def session_lifetime_problem(set_cookie: str, now: Optional[float] = None) -> Optional[str]:
189
219
  """DEC-0039: a session cookie set at sign-in lives at most 7 days. Max-Age wins over Expires (RFC 6265
190
220
  §5.3); a cookie with neither ends with the browser session. None when it is within the bound."""
@@ -49,7 +49,6 @@ from fabric_crypto import ( # noqa: E402 - the core (FAA-65)
49
49
  )
50
50
  from fabric_jsvalue import ( # noqa: E402 - the core (FAA-65)
51
51
  MISSING as _MISSING,
52
- contains as _contains,
53
52
  get as _get,
54
53
  get_string as _s,
55
54
  indexed_objects as _indexed,
@@ -59,7 +58,7 @@ from fabric_jsvalue import ( # noqa: E402 - the core (FAA-65)
59
58
  objects as _list,
60
59
  safe_integer as _safe_integer,
61
60
  same as _same,
62
- set_has as _set_has,
61
+ set_has as _set_has, # every membership the contract keeps in a Set or tests with includes (FAA-113)
63
62
  show as _show,
64
63
  )
65
64
  from fabric_time import ( # noqa: E402 - the core (FAA-65)
@@ -331,7 +330,7 @@ def verify_estate_record(record: Any, *, at: Any, parent: Any = _MISSING, author
331
330
  if not matches:
332
331
  return {"ok": False, "problem": "unknown-authority"}
333
332
  authority = matches[0]
334
- if _contains(revoked, _get(authority, "credentialId")):
333
+ if _set_has(revoked, _get(authority, "credentialId")):
335
334
  return {"ok": False, "problem": "revoked-authority"}
336
335
  origins = _get(authority, "origins")
337
336
  credential = {"credentialId": _s(authority, "credentialId"), "alg": _s(authority, "alg"),
@@ -385,7 +384,7 @@ def _scope_admits(record: Any, kid: Any, context: Any) -> bool:
385
384
  """DEC-0090: a key no `publisherScopes[]` entry names is trusted in any context; a partner's key, only in a context
386
385
  one of its scopes lists."""
387
386
  scopes = [s for s in _list(_get(record, "publisherScopes")) if _same(_get(s, "kid"), kid)]
388
- return not scopes or any(isinstance(_get(s, "contexts"), list) and _contains(s["contexts"], context) for s in scopes)
387
+ return not scopes or any(isinstance(_get(s, "contexts"), list) and _set_has(s["contexts"], context) for s in scopes)
389
388
 
390
389
 
391
390
  #: v4 §7, Freshness: an estate revision's window is at most 30 days (DEC-0055 item 2).
@@ -545,7 +544,7 @@ def _keys(value: Dict[str, Any], flag) -> None:
545
544
  hub_keys: List[Any] = []
546
545
  for machine, m in _indexed(_get(value, "machines")):
547
546
  at = "/machines/%d" % m
548
- if _contains(ids, _get(machine, "id")):
547
+ if _set_has(ids, _get(machine, "id")):
549
548
  flag("%s/id" % at, "machine %s is listed twice" % _show(_get(machine, "id")))
550
549
  ids.append(_get(machine, "id"))
551
550
  device_keys = _list(_get(machine, "deviceKeys"))
@@ -562,15 +561,15 @@ def _keys(value: Dict[str, Any], flag) -> None:
562
561
  flag("%s/deviceKeys/%d/publicKey" % (at, k), "a device key's octets are written in canonical padded base64, the one form a key reference can name (DEC-0084)")
563
562
  elif not _valid_device_key(key):
564
563
  flag("%s/deviceKeys/%d/publicKey" % (at, k), "an Ed25519 key is never of small order (DEC-0045) and a P-256 key is a valid point (SEC 1 §3.2.2)")
565
- if _contains(seen, _get(key, "publicKey")):
564
+ if _set_has(seen, _get(key, "publicKey")):
566
565
  flag("%s/deviceKeys/%d/publicKey" % (at, k), "one key is never used for the other role")
567
566
  seen.append(_get(key, "publicKey"))
568
567
  hub = _get(machine, "hubKey")
569
568
  if isinstance(hub, dict):
570
- if _contains(hub_kids, _get(hub, "kid")):
569
+ if _set_has(hub_kids, _get(hub, "kid")):
571
570
  flag("%s/hubKey/kid" % at, "hub key %s is certified for two machines: one kid per machine" % _show(_get(hub, "kid")))
572
571
  hub_kids.append(_get(hub, "kid"))
573
- if _contains(hub_keys, _get(hub, "x")):
572
+ if _set_has(hub_keys, _get(hub, "x")):
574
573
  flag("%s/hubKey/x" % at, "one hub public key is certified for two machines under two kids: one key per machine")
575
574
  hub_keys.append(_get(hub, "x"))
576
575
  x = _get(hub, "x")
@@ -579,7 +578,7 @@ def _keys(value: Dict[str, Any], flag) -> None:
579
578
  flag("%s/hubKey/x" % at, "a hub key of small order verifies forged tokens and is never certified (DEC-0045)")
580
579
  credentials: List[Any] = []
581
580
  for authority, index in _indexed(_get(value, "authorities")):
582
- if _contains(credentials, _get(authority, "credentialId")):
581
+ if _set_has(credentials, _get(authority, "credentialId")):
583
582
  flag("/authorities/%d/credentialId" % index, "a passkey is listed once")
584
583
  credentials.append(_get(authority, "credentialId"))
585
584
  if "problem" in credential_key(_get(authority, "publicKey"), _show(_get(authority, "alg"))):
@@ -589,7 +588,7 @@ def _keys(value: Dict[str, Any], flag) -> None:
589
588
  def _placements(value: Dict[str, Any], flag) -> None:
590
589
  kids: List[Any] = []
591
590
  for key, index in _indexed(_get(value, "publisherKeys")):
592
- if _contains(kids, _get(key, "kid")):
591
+ if _set_has(kids, _get(key, "kid")):
593
592
  flag("/publisherKeys/%d/kid" % index, "publisher key %s is pinned twice" % _show(_get(key, "kid")))
594
593
  kids.append(_get(key, "kid"))
595
594
  for machine, m in _indexed(_get(value, "machines")):
@@ -601,7 +600,7 @@ def _placements(value: Dict[str, Any], flag) -> None:
601
600
  flag("%s/agentRef" % at, "%s is placed twice on one machine in the context %s"
602
601
  % (_show(_get(placement, "agentRef")), _show(_context_of(placement))))
603
602
  agents.append(key)
604
- if not _contains(kids, _get(placement, "publisherKid")):
603
+ if not _set_has(kids, _get(placement, "publisherKid")):
605
604
  flag("%s/publisherKid" % at, "publisher key %s is not in publisherKeys, so the machine pins no key to verify the package under" % _show(_get(placement, "publisherKid")))
606
605
 
607
606
 
@@ -611,10 +610,10 @@ def _partner_scopes(value: Dict[str, Any], flag) -> None:
611
610
  pinned = [_get(k, "kid") for k in _list(_get(value, "publisherKeys"))]
612
611
  scoped: List[Any] = []
613
612
  for scope, index in _indexed(_get(value, "publisherScopes")):
614
- if _contains(scoped, _get(scope, "kid")):
613
+ if _set_has(scoped, _get(scope, "kid")):
615
614
  flag("/publisherScopes/%d/kid" % index, "publisher key %s is scoped twice" % _show(_get(scope, "kid")))
616
615
  scoped.append(_get(scope, "kid"))
617
- if not _contains(pinned, _get(scope, "kid")):
616
+ if not _set_has(pinned, _get(scope, "kid")):
618
617
  flag("/publisherScopes/%d/kid" % index, "publisher key %s is not in publisherKeys, so the scope limits no key" % _show(_get(scope, "kid")))
619
618
  for machine, m in _indexed(_get(value, "machines")):
620
619
  for placement, p in _indexed(_get(machine, "packages")):
@@ -638,7 +637,7 @@ def _secret_references(value: Dict[str, Any], flag) -> None:
638
637
  flag("%s/%s" % (at, field), "a secret placement names a slot, never a value: this looks like a credential")
639
638
  if _key_of(_get(secret, "agentRef"), _context_of(secret)) not in placed:
640
639
  flag("%s/agentRef" % at, "a secret goes only where a placed package declares it (v4 Path 2): this agent is not placed on this machine in the secret's context")
641
- if not _contains(machine_ids, _get(secret, "from")):
640
+ if not _set_has(machine_ids, _get(secret, "from")):
642
641
  flag("%s/from" % at, "the slot is filled from a machine of this estate")
643
642
  elif not _same(_context_of(secret), PERSONAL_CONTEXT) and not _same(_get(secret, "from"), _get(machine, "id")):
644
643
  # An envelope names no context (DEC-0082): an organization's instance fills its slot on its own machine.
@@ -693,7 +692,7 @@ def _machine_profiles(value: Dict[str, Any], flag) -> None:
693
692
  flag("/machines/%d/secrets" % m, "a view machine holds none of the estate's secrets")
694
693
  for machine, m in _indexed(_get(value, "machines")):
695
694
  for secret, s in _indexed(_get(machine, "secrets")):
696
- if _set_has(views, _get(secret, "from")): # the contract's Set: NaN matches NaN
695
+ if _set_has(views, _get(secret, "from")):
697
696
  flag("/machines/%d/secrets/%d/from" % (m, s), "machine %s is a view machine: it is the source of no secret" % _show(_get(secret, "from")))
698
697
 
699
698
 
@@ -709,7 +708,7 @@ def _grants_and_revocations(value: Dict[str, Any], flag) -> None:
709
708
  if _get(grant, "principalKind") != "external":
710
709
  flag("/shareGrants/%d/principalKind" % index, "a share grant is to a principal outside the estate: principalKind external")
711
710
  ident = _get(_get(grant, "meta"), "id") if isinstance(_get(grant, "meta"), dict) else _MISSING
712
- if _contains(grant_ids, _grant_key(ident)):
711
+ if _set_has(grant_ids, _grant_key(ident)):
713
712
  flag("/shareGrants/%d/meta/id" % index, "grant %s is listed twice" % _show(ident))
714
713
  grant_ids.append(_grant_key(ident))
715
714
  machines = _list(_get(value, "machines"))
@@ -735,7 +734,7 @@ def _grants_and_revocations(value: Dict[str, Any], flag) -> None:
735
734
  if _get(revocation, "kind") == "device-key" and isinstance(raw_ref, str) and "@" in raw_ref and not KEY_REFERENCE.match(raw_ref):
736
735
  flag("/revocations/%d/ref" % index, "a device-key revocation names a kid or a key reference <kid>@sha256:<64 lower-case hex digits> (DEC-0084)")
737
736
  seen.append(key)
738
- if _contains(listed.get(kind, []), ref):
737
+ if _set_has(listed.get(kind, []), ref):
739
738
  flag("/revocations/%d/ref" % index, "the record still lists the %s it revokes: revocation outranks addition" % kind)
740
739
 
741
740
 
@@ -906,7 +905,7 @@ def _installation(value: Dict[str, Any]) -> List[Dict[str, str]]:
906
905
  declared = [_get(slot, "name") for slot in _list(_get(pkg, "secrets"))]
907
906
  flag075 = _finding(findings, "FAC-SEM-075")
908
907
  for secret, index in _indexed(_get(machine, "secrets")):
909
- if _same(_get(secret, "agentRef"), _get(pkg, "agentRef")) and _same(_context_of(secret), context) and not _contains(declared, _get(secret, "slot")):
908
+ if _same(_get(secret, "agentRef"), _get(pkg, "agentRef")) and _same(_context_of(secret), context) and not _set_has(declared, _get(secret, "slot")):
910
909
  flag075("/record/machines/%d/secrets/%d/slot" % (machine_index, index),
911
910
  "the package declares no slot %s: a secret goes only where a placed package declares it" % _show(_get(secret, "slot")))
912
911
  if placement is not None and not _scope_admits(record, _get(placement, "publisherKid"), context):
@@ -158,7 +158,7 @@ def double(value: Any) -> float:
158
158
 
159
159
  def same(a: Any, b: Any) -> bool:
160
160
  """JavaScript's === on parsed JSON: an object equals only itself. A Set's SameValueZero differs only for NaN, which
161
- no JSON value is: use ``set_has`` where the contract keeps a Set (FAA-107)."""
161
+ no JSON value is: use ``set_has`` where the contract keeps a Set or tests with ``includes`` (FAA-107, FAA-113)."""
162
162
  if a is MISSING or b is MISSING:
163
163
  return a is b
164
164
  if isinstance(a, (dict, list)) or isinstance(b, (dict, list)):
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.49.0",
3
+ "version": "0.49.2",
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
  {
@@ -8,7 +8,7 @@
8
8
  "file": "fabric_jsvalue.py",
9
9
  "layer": "L0",
10
10
  "imports": [],
11
- "sha256": "ff1bbfae999fb5683e59e31189143f1c85fa9d7780040b5a6952e060a143fc18"
11
+ "sha256": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28"
12
12
  },
13
13
  {
14
14
  "module": "fabric_time",
@@ -77,7 +77,7 @@
77
77
  "fabric_secrets",
78
78
  "fabric_time"
79
79
  ],
80
- "sha256": "f493cd424dae6380d1caad91ffdca2764e51a49a6b1051a77731e3d8755277cd"
80
+ "sha256": "a0ddc49d748c1efb1830de176998d6c8eac30f77d387c71eae12549c52aaab27"
81
81
  },
82
82
  {
83
83
  "module": "fabric_delegation",
@@ -2951,6 +2951,158 @@
2951
2951
  "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
2952
2952
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
2953
2953
  "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2954
+ },
2955
+ "0.49.1": {
2956
+ "fabric_jsvalue.py": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28",
2957
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
2958
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
2959
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
2960
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
2961
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
2962
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
2963
+ "fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
2964
+ "fabric_estate.py": "a0ddc49d748c1efb1830de176998d6c8eac30f77d387c71eae12549c52aaab27",
2965
+ "fabric_delegation.py": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36",
2966
+ "fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
2967
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
2968
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
2969
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
2970
+ "fabric_protocols.py": "28e693282cddff8e4396ae39ce6fa935e875e8ddac7375bea2c189dd6fb04970",
2971
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
2972
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
2973
+ "fabric_usage.py": "b1f335cab4625171a616cf68cf3fd892b18bdadcf4baec3a3a0fdcf12785c170",
2974
+ "fabric_backups.py": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb",
2975
+ "fabric_descriptor.py": "daf16f95b1810283b1386488e006e940a0cdfa5c3c8d09386ac33d29d58df78d",
2976
+ "fabric_auth.py": "55794587750f6c9f40935b74295d83d7096cb4b26a0b36ac292334cd87d8f853",
2977
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
2978
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
2979
+ "fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
2980
+ "fabric_service.py": "103ab4026f96e127c01cd91e4ad44f6b0ce2424ff33f078ed106b39e773a10e3",
2981
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
2982
+ "fabric_interop_rules.py": "b5f0ecb1791b4c50c5ac151f987f692ed7001bc74841a8a161125ec775145e09",
2983
+ "fabric_interop.py": "f92fa111328fe3811438f733d8f0eb5295e6941c774123a78e26318bdcb35fc3",
2984
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
2985
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
2986
+ "fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
2987
+ "fabric_board_session.py": "2845d7d758b06ac4588bdf825a471c201a46ce535a631805697b1e35495fead6",
2988
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
2989
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
2990
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
2991
+ "fabric_openrouter.py": "309f588f9c976b1061323f3161e35d86248afd5b59b16391afbfc4e1099ecd65",
2992
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
2993
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
2994
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
2995
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
2996
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
2997
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
2998
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
2999
+ "fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
3000
+ "fabric-estate.mjs": "b6610aaad9d00d8a7f42955627fb81725950841455bcedfc050abb0e079f0f93",
3001
+ "fabric-delegation.mjs": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7",
3002
+ "fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
3003
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
3004
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
3005
+ "fabric-package.mjs": "13913ea6d12056c3a31b8222b6c4aab8395941fe519913692e1a06d8672e1c88",
3006
+ "fabric-protocols.mjs": "dc039ec3612d0090c9ba6a3a949b0b7df54712bead300dcb6cc8b9bc305cd183",
3007
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
3008
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
3009
+ "fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
3010
+ "fabric-backups.mjs": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005",
3011
+ "fabric-descriptor.mjs": "95c0b7ff36c29d7c748f1fa6fb1767d6fe8c97fca7f1ee404559930abbd3cc84",
3012
+ "fabric-auth.mjs": "3ea6796470fcd829bc72a772fb7e4c37532a09d1e37ff41592dcf1a1a3074ca2",
3013
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
3014
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
3015
+ "fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
3016
+ "fabric-service.mjs": "1b4639a10c530c1241d317c5e9cf3a10cd96e5d9af8b29fc1b1844de5c63bfec",
3017
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
3018
+ "fabric-interop-rules.mjs": "57a8081307956e8c381119b361fa5232a7bf791815be8e3541eb5492820e2ea5",
3019
+ "fabric-interop.mjs": "581983ec3e197e5c2340ad25e8468320c69a2459c90097c7fcf2699766b4fb26",
3020
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
3021
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
3022
+ "fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
3023
+ "fabric-board-session.mjs": "26c9b0b2818a4cedc5248b08ea6732e2ee4ee81e31f6fee2b572a019db813c93",
3024
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
3025
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
3026
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
3027
+ "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
3028
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
3029
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
3030
+ },
3031
+ "0.49.2": {
3032
+ "fabric_jsvalue.py": "12888a2c934a885d35999ac9782eaedc8d04946d9775213344a235a8814d3d28",
3033
+ "fabric_time.py": "588f383da4ac6a243ea95b6ffc3a0aa99ca94245732dc4937734f190b7af4ae9",
3034
+ "fabric_identifiers.py": "0fd814d521ac3aa39907458265870753046a73f48af3d8581ab864b93b211607",
3035
+ "fabric_secrets.py": "68a0b5e9e90c468ec9ac67b2f0fd1848e08813d6ec1ec14a2a47337eb1a98911",
3036
+ "fabric_crypto.py": "9c34aee20dbe4d6742e4fdbaab68ceb934bbe7dbd3a904c0010824ace16cdeaa",
3037
+ "fabric_base.py": "f62bcc2ed9912660fa875d27a09f60867260cc4925f79873501fd9620be59611",
3038
+ "fabric_platform.py": "a9350dff55ef0fb3a23cdb4d0320a15868b15a8deb10d2eb74d72c58740c284b",
3039
+ "fabric_paths.py": "6c92af34292baddf7e10d3134d0a5b0bb23387741812a9f79c409cf1628bad23",
3040
+ "fabric_estate.py": "a0ddc49d748c1efb1830de176998d6c8eac30f77d387c71eae12549c52aaab27",
3041
+ "fabric_delegation.py": "6638a6039befe3b43f59018bab292e050931af21b544df1044cd5059572ded36",
3042
+ "fabric_share.py": "78506fe1a60829c525dbf6266a0aa0749a2164290ed94da8249a20b4e9be5605",
3043
+ "fabric_ceremonies.py": "a59e83cf4adb2669a9b98008e3d3a0cba991a35cbff97bc604fc82fe81cfd49f",
3044
+ "fabric_package_schema.py": "e623f0408d38e3343ed66222fb812907c6c52e1a286eb1c1176ab2e64640c9ad",
3045
+ "fabric_package.py": "ff533f1b3f92b4ea0f532fcec50b68c610d47549930eef8ad5247692a64dfb3c",
3046
+ "fabric_protocols.py": "28e693282cddff8e4396ae39ce6fa935e875e8ddac7375bea2c189dd6fb04970",
3047
+ "fabric_connections.py": "77d3ccf863c70e9209d21ef45c46a6db9c06237019bc20d54f8d3946873f3f11",
3048
+ "fabric_lock.py": "ba2399d2137c7a7791b7133b9db9cca36693f4eb7e30955b25a75ade814e95ba",
3049
+ "fabric_usage.py": "b1f335cab4625171a616cf68cf3fd892b18bdadcf4baec3a3a0fdcf12785c170",
3050
+ "fabric_backups.py": "02dc5eb66ec956723d3ddba07b6dd15f6a9726d901f4d60edfd02eee57880ceb",
3051
+ "fabric_descriptor.py": "daf16f95b1810283b1386488e006e940a0cdfa5c3c8d09386ac33d29d58df78d",
3052
+ "fabric_auth.py": "55794587750f6c9f40935b74295d83d7096cb4b26a0b36ac292334cd87d8f853",
3053
+ "fabric_events.py": "0086886c2c29dbd5c9d152be9d375c5bdf4a29f89b8095baff8490c2bcd64e47",
3054
+ "fabric_logs.py": "86dc55bfafa2a57264722e2403510a249b95de0e4a722b2e4bc3948e3bd93da9",
3055
+ "fabric_supervisors.py": "a2dbf15aa1c4eb9de42996b413103fa4814afd2e4e66d1ab7315f30e06ab6841",
3056
+ "fabric_service.py": "103ab4026f96e127c01cd91e4ad44f6b0ce2424ff33f078ed106b39e773a10e3",
3057
+ "fabric_profiles.py": "26e21ec29c016000f8ca8053b7b489184854a73be1b7fdc0a18d12990203db9b",
3058
+ "fabric_interop_rules.py": "b5f0ecb1791b4c50c5ac151f987f692ed7001bc74841a8a161125ec775145e09",
3059
+ "fabric_interop.py": "f92fa111328fe3811438f733d8f0eb5295e6941c774123a78e26318bdcb35fc3",
3060
+ "fabric_hub.py": "b9ac852e601b1d54cf61351677a628afc69faeed7d1fc74ebe541c1cf38e0c2a",
3061
+ "fabric_board_shapes.py": "0a0e8d3d40e33dfd8c457aedb85330b2ff25f8c49a33eb6cbd5a5cc8a5007f7e",
3062
+ "fabric_board_link.py": "b5bff994cb615c6a4bb0d88d020e3a59504166180e1fa1aea3be93224f2aeccb",
3063
+ "fabric_board_session.py": "2845d7d758b06ac4588bdf825a471c201a46ce535a631805697b1e35495fead6",
3064
+ "fabric_board_fixture.py": "69761ce205ec640762ec9b591c6903e5a87f844e8d423b041681924027e39d1e",
3065
+ "fabric_board.py": "0f075894b3e352194fcaf3fc7d08dee4e9f1c215e859769ed85fa5010e4a1599",
3066
+ "fabric_http.py": "5d7f72af3a73fdbe8c1f487ecf60cef23a7c46289eba542b03aff4706b8218d4",
3067
+ "fabric_openrouter.py": "309f588f9c976b1061323f3161e35d86248afd5b59b16391afbfc4e1099ecd65",
3068
+ "fabric-jsvalue.mjs": "dfbdb8c41584972c9389750a41719374a1cfc5652ed438dd1623025190b0106e",
3069
+ "fabric-time.mjs": "3ff932069038a720df1c68c6492976906737b2e67e5c12d9c81c418597f661de",
3070
+ "fabric-identifiers.mjs": "a18c2a501a4aa170bfd3d99e571ad6b7662649b4d3b9d2a540c002ea42811bbf",
3071
+ "fabric-secrets.mjs": "6ad8ab18ac899ecc2384e8ec6a3fb689b92be3b9a5ce3a59b92a37d52da5d766",
3072
+ "fabric-crypto.mjs": "038e03689701e3c9721a1e7f8e0a4d02a52d6a5d1661b0dc84035edc3ebbfcf6",
3073
+ "fabric-base.mjs": "7804b2df7715dbbf9702a9b61b6b8cef2110f9a52b04cabd8fb0a66458d87512",
3074
+ "fabric-platform.mjs": "a8119d95ebba4d589a9e401f5d6fc8dda62d59d44e4c1ff811d97fac3771ac0c",
3075
+ "fabric-paths.mjs": "2201b4ed7126f260931b63e8386377b4317d8dcad419d109bf22c683e44b7f29",
3076
+ "fabric-estate.mjs": "b6610aaad9d00d8a7f42955627fb81725950841455bcedfc050abb0e079f0f93",
3077
+ "fabric-delegation.mjs": "dafde9ea327c88d92dc73d1400282b43dc6f84dd345a2d2b9b154304f65fc9d7",
3078
+ "fabric-share.mjs": "d40a7405d7bd4c3213ed691330b7549afe8e023db0753b582e249714e672010d",
3079
+ "fabric-ceremonies.mjs": "117d4374c9e95c930a38de7adbd4323774798f4fe6ec6f24afcd6f2c7298ca44",
3080
+ "fabric-package-schema.mjs": "cd28092375db0fc21ffab1f0bb7794f7f8979ee08c64d3b9faf4b3845357caa6",
3081
+ "fabric-package.mjs": "13913ea6d12056c3a31b8222b6c4aab8395941fe519913692e1a06d8672e1c88",
3082
+ "fabric-protocols.mjs": "dc039ec3612d0090c9ba6a3a949b0b7df54712bead300dcb6cc8b9bc305cd183",
3083
+ "fabric-connections.mjs": "303432e547ce563e90ca9ffcd827f5612f51d3582929834e1add0fdaf6485dcc",
3084
+ "fabric-lock.mjs": "ac219ab0a326d5c43c7c4c255e0c3843d4794de795a60e3af661e7d5350ea052",
3085
+ "fabric-usage.mjs": "1c4d18004639409624ac3516a02f2b60bbf55abdb612e9a96c37e83666eb87a7",
3086
+ "fabric-backups.mjs": "849acdc6eba0fff648c8158e61f6b68cccd7bedf0880f7d5ec03a6a20f1e7005",
3087
+ "fabric-descriptor.mjs": "95c0b7ff36c29d7c748f1fa6fb1767d6fe8c97fca7f1ee404559930abbd3cc84",
3088
+ "fabric-auth.mjs": "3ea6796470fcd829bc72a772fb7e4c37532a09d1e37ff41592dcf1a1a3074ca2",
3089
+ "fabric-events.mjs": "c463d6ffcae24229a5c608d8fcea4268d2ccc5024b19fa10e5a452284068fa71",
3090
+ "fabric-logs.mjs": "58467e5719c16e099d05bbedec47a95712506ab03c33f3cd4be1252e8498a946",
3091
+ "fabric-supervisors.mjs": "9d61c1a17538a5e2453e33e5ae347337336748041bc054478a96dd51f3d4fc51",
3092
+ "fabric-service.mjs": "1b4639a10c530c1241d317c5e9cf3a10cd96e5d9af8b29fc1b1844de5c63bfec",
3093
+ "fabric-profiles.mjs": "314376cf2deb87f235fb18b11d3b532e211f585b9af0a9302b5f4cc39971b532",
3094
+ "fabric-interop-rules.mjs": "57a8081307956e8c381119b361fa5232a7bf791815be8e3541eb5492820e2ea5",
3095
+ "fabric-interop.mjs": "581983ec3e197e5c2340ad25e8468320c69a2459c90097c7fcf2699766b4fb26",
3096
+ "fabric-hub.mjs": "c005115a864cfd67daa4862097bb14acfe5a60bf653897e5bf8a570dd80f1070",
3097
+ "fabric-board-shapes.mjs": "c1db7b7bd0d58d82bb6d8dfbc3cd1547a256a333052d367e82ff1b3edf2804d7",
3098
+ "fabric-board-link.mjs": "4cd6d4e21713a38c8e56f7d59db3148638b8c27c08e774d96a0f6283c6049e0a",
3099
+ "fabric-board-session.mjs": "26c9b0b2818a4cedc5248b08ea6732e2ee4ee81e31f6fee2b572a019db813c93",
3100
+ "fabric-board-fixture.mjs": "83c4b7e33ee47b68385faf2569d38d5c8a0c5cb0f8656090f41e1b0eed2f436e",
3101
+ "fabric-board.mjs": "9e7bfe4d412521982224ef12bebfc2ff255eeeaf8d96cf40234ac18de68e7a59",
3102
+ "fabric-http.mjs": "f886bcb49c4353d039387ac9568738dda26697a94dd07fc6a6204500f0bc27bf",
3103
+ "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
3104
+ "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
3105
+ "share-registration.schema.json": "34b3997eeaf936ebc861342a23045634a7c18c19f01d7ed4198310d81989f5b0"
2954
3106
  }
2955
3107
  },
2956
3108
  "data": {
@@ -5,7 +5,7 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.49.0"
8
+ version: "0.49.2"
9
9
  contract-version: "0.1.0"
10
10
  contract-commit: "2f020a696453ae299b123517f14957f6a1354379"
11
11
  ---
@@ -19,7 +19,7 @@ compatibility: >-
19
19
  this plugin; Codex/Kimi use command hooks; Hermes/OpenCode/OpenClaw/Pi use native context plugins after opt-in.
20
20
  metadata:
21
21
  author: PassionCode.ai
22
- version: "0.49.0"
22
+ version: "0.49.2"
23
23
  ---
24
24
 
25
25
  # Working with Fabric
@@ -57,7 +57,7 @@ SESSION_ID = re.compile(r"[A-Za-z0-9._:-]{1,128}")
57
57
  LOOPBACK_ORIGIN = re.compile(r"http://127\.0\.0\.1:(\d{1,5})")
58
58
 
59
59
 
60
- VERSION = "0.49.0"
60
+ VERSION = "0.49.2"
61
61
 
62
62
 
63
63
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",