@passioncode-ai/fabric-agent-adapter 0.49.1 → 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,17 @@
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
+
3
15
  ## 0.49.1 (2026-10-24)
4
16
 
5
17
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.49.1",
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.1",
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.1"
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.1"
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."""
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fabric-service-kit",
3
- "version": "0.49.1",
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
  {
@@ -3027,6 +3027,82 @@
3027
3027
  "fabric-openrouter.mjs": "ab6c4c7251d563980d895061ee19976bde2f364c21b6354efdd9a9027b1ea139",
3028
3028
  "fabric-package.schema.json": "2ba799f576fad0f608029223c4ccccd122152f1c5dbfa8ce8b31cd63ac92ad44",
3029
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"
3030
3106
  }
3031
3107
  },
3032
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.1"
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.1"
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.1"
60
+ VERSION = "0.49.2"
61
61
 
62
62
 
63
63
  HOSTS = {"claude": "Claude Code", "codex": "Codex", "kimi": "Kimi Code",