@passioncode-ai/fabric-agent-adapter 0.39.1 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/README.md +9 -3
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +15 -4
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +64 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/config.example.json +15 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/knowledge.example.json +13 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +264 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +93 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +128 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/usage.md +2 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +10 -183
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +204 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-auth.mjs +286 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-backups.mjs +327 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-base.mjs +47 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-fixture.mjs +79 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +840 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-shapes.mjs +340 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +42 -1237
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-ceremonies.mjs +1 -16
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-crypto.mjs +41 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +13 -45
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +387 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +8 -67
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-events.mjs +52 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +231 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +2 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-jsvalue.mjs +67 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-lock.mjs +202 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-logs.mjs +136 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +335 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +2 -12
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-paths.mjs +69 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-platform.mjs +164 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-profiles.mjs +1 -32
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +122 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +177 -2541
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-share.mjs +1 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +400 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-time.mjs +46 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-usage.mjs +458 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_auth.py +402 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_backups.py +399 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_base.py +78 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +64 -1398
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_fixture.py +116 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +902 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_shapes.py +457 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_ceremonies.py +36 -38
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_crypto.py +289 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +15 -180
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +586 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +39 -400
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +137 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +402 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +1 -6
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_jsvalue.py +310 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_kit.py +243 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_lock.py +308 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_logs.py +202 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +431 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +21 -43
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_paths.py +102 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_platform.py +338 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_profiles.py +31 -38
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +173 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +298 -3450
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_share.py +8 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +412 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_time.py +120 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +697 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +1526 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +3 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +116 -1392
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +270 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +508 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_hosts.py +265 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mail.py +155 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +335 -0
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +1 -0
package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py
CHANGED
|
@@ -31,6 +31,21 @@ from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple
|
|
|
31
31
|
import urllib.error
|
|
32
32
|
import urllib.parse
|
|
33
33
|
import urllib.request
|
|
34
|
+
import sys as _sys # noqa: E402
|
|
35
|
+
from pathlib import Path as _Path # noqa: E402
|
|
36
|
+
if str(_Path(__file__).resolve().parent) not in _sys.path: # the kit loads from any working directory (FAA-65)
|
|
37
|
+
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
|
|
38
|
+
from fabric_crypto import ( # noqa: E402 - the core (FAA-65)
|
|
39
|
+
SMALL_ORDER_ENCODINGS,
|
|
40
|
+
_ED_P as _P,
|
|
41
|
+
_ed_decode_point as _decode_point,
|
|
42
|
+
b64url_decode as _b64decode,
|
|
43
|
+
ed25519_verify,
|
|
44
|
+
is_small_order_key,
|
|
45
|
+
)
|
|
46
|
+
from fabric_jsvalue import ( # noqa: E402 - the core (FAA-65)
|
|
47
|
+
jcs,
|
|
48
|
+
)
|
|
34
49
|
|
|
35
50
|
REFUSAL_CODES = (
|
|
36
51
|
"unauthenticated", "token-expired", "wrong-audience", "scope-not-granted", "effect-above-ceiling", "no-grant",
|
|
@@ -85,24 +100,9 @@ _URI = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:\S+$")
|
|
|
85
100
|
_JTI = re.compile(r"^[A-Za-z0-9._~-]{16,128}$")
|
|
86
101
|
_SCOPE = re.compile(r"^[a-z][a-z0-9._-]{1,127}( [a-z][a-z0-9._-]{1,127})*$")
|
|
87
102
|
_SPEND_WINDOWS = ("total", "task", "project", "day", "week", "month")
|
|
88
|
-
# The small-order points of edwards25519 and their non-canonical encodings, sign bit cleared (the contract's
|
|
89
|
-
# src/ed25519.ts; libsodium's has_small_order list).
|
|
90
|
-
SMALL_ORDER_ENCODINGS = (
|
|
91
|
-
"0000000000000000000000000000000000000000000000000000000000000000", "0100000000000000000000000000000000000000000000000000000000000000",
|
|
92
|
-
"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
|
|
93
|
-
"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
|
94
|
-
"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
|
95
|
-
)
|
|
96
103
|
_MISSING = object()
|
|
97
104
|
|
|
98
105
|
|
|
99
|
-
def is_small_order_key(raw: bytes) -> bool:
|
|
100
|
-
"""Whether a 32-byte Ed25519 public key is a point of small order, in any encoding (CT-29)."""
|
|
101
|
-
if len(raw) != 32:
|
|
102
|
-
return True
|
|
103
|
-
return (raw[:31] + bytes([raw[31] & 0x7F])).hex() in SMALL_ORDER_ENCODINGS
|
|
104
|
-
|
|
105
|
-
|
|
106
106
|
def _full(pattern: "re.Pattern[str]", value: Any) -> bool:
|
|
107
107
|
return isinstance(value, str) and pattern.fullmatch(value) is not None
|
|
108
108
|
|
|
@@ -128,90 +128,9 @@ def _is_time(value: Any) -> bool:
|
|
|
128
128
|
|
|
129
129
|
# --- Ed25519 (RFC 8032 §5.1) ----------------------------------------------------------------------------
|
|
130
130
|
|
|
131
|
-
_P = 2 ** 255 - 19
|
|
132
|
-
_L = 2 ** 252 + 27742317777372353535851937790883648493
|
|
133
|
-
_D = (-121665 * pow(121666, _P - 2, _P)) % _P
|
|
134
|
-
_SQRT_M1 = pow(2, (_P - 1) // 4, _P)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def _add(a: Tuple[int, int, int, int], b: Tuple[int, int, int, int]) -> Tuple[int, int, int, int]:
|
|
138
|
-
x1, y1, z1, t1 = a
|
|
139
|
-
x2, y2, z2, t2 = b
|
|
140
|
-
aa = (y1 - x1) * (y2 - x2) % _P
|
|
141
|
-
bb = (y1 + x1) * (y2 + x2) % _P
|
|
142
|
-
cc = 2 * t1 * t2 * _D % _P
|
|
143
|
-
dd = 2 * z1 * z2 % _P
|
|
144
|
-
e, f, g, h = bb - aa, dd - cc, dd + cc, bb + aa
|
|
145
|
-
return (e * f % _P, g * h % _P, f * g % _P, e * h % _P)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def _mul(scalar: int, point: Tuple[int, int, int, int]) -> Tuple[int, int, int, int]:
|
|
149
|
-
result = (0, 1, 1, 0)
|
|
150
|
-
while scalar:
|
|
151
|
-
if scalar & 1:
|
|
152
|
-
result = _add(result, point)
|
|
153
|
-
point = _add(point, point)
|
|
154
|
-
scalar >>= 1
|
|
155
|
-
return result
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _decode_point(data: bytes) -> Optional[Tuple[int, int, int, int]]:
|
|
159
|
-
if len(data) != 32:
|
|
160
|
-
return None
|
|
161
|
-
y = int.from_bytes(data, "little")
|
|
162
|
-
sign = y >> 255
|
|
163
|
-
y &= (1 << 255) - 1
|
|
164
|
-
if y >= _P:
|
|
165
|
-
return None
|
|
166
|
-
x2 = (y * y - 1) * pow(_D * y * y + 1, _P - 2, _P) % _P
|
|
167
|
-
if x2 == 0:
|
|
168
|
-
if sign:
|
|
169
|
-
return None
|
|
170
|
-
return (0, y, 1, 0)
|
|
171
|
-
x = pow(x2, (_P + 3) // 8, _P)
|
|
172
|
-
if (x * x - x2) % _P:
|
|
173
|
-
x = x * _SQRT_M1 % _P
|
|
174
|
-
if (x * x - x2) % _P:
|
|
175
|
-
return None
|
|
176
|
-
if x & 1 != sign:
|
|
177
|
-
x = _P - x
|
|
178
|
-
return (x, y, 1, x * y % _P)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _encode_point(point: Tuple[int, int, int, int]) -> bytes:
|
|
182
|
-
x, y, z, _ = point
|
|
183
|
-
zi = pow(z, _P - 2, _P)
|
|
184
|
-
x, y = x * zi % _P, y * zi % _P
|
|
185
|
-
return (y | ((x & 1) << 255)).to_bytes(32, "little")
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
_G = _decode_point(bytes.fromhex("5866666666666666666666666666666666666666666666666666666666666666"))
|
|
189
|
-
_IDENTITY = (1).to_bytes(32, "little")
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def ed25519_verify(public_key: bytes, message: bytes, signature: bytes) -> bool:
|
|
193
|
-
"""RFC 8032 §5.1.7, cofactorless: [S]B == R + [k]A, compared by R's encoding. False, never an exception."""
|
|
194
|
-
if len(public_key) != 32 or len(signature) != 64:
|
|
195
|
-
return False
|
|
196
|
-
s = int.from_bytes(signature[32:], "little")
|
|
197
|
-
if s >= _L:
|
|
198
|
-
return False
|
|
199
|
-
a = _decode_point(public_key)
|
|
200
|
-
if a is None or _encode_point(_mul(8, a)) == _IDENTITY:
|
|
201
|
-
return False # a small-order key would accept R = [S]B for a quarter or more of all messages; OpenSSL refuses it
|
|
202
|
-
k = int.from_bytes(hashlib.sha512(signature[:32] + public_key + message).digest(), "little") % _L
|
|
203
|
-
neg_a = ((-a[0]) % _P, a[1], a[2], (-a[3]) % _P)
|
|
204
|
-
return secrets.compare_digest(_encode_point(_add(_mul(s, _G), _mul(k, neg_a))), signature[:32])
|
|
205
|
-
|
|
206
131
|
|
|
207
132
|
# --- the compact document ------------------------------------------------------------------------------
|
|
208
133
|
|
|
209
|
-
def _b64decode(segment: str) -> bytes:
|
|
210
|
-
"""Base64url without padding, as Node reads it: a dangling sixth bit group is ignored."""
|
|
211
|
-
if len(segment) % 4 == 1:
|
|
212
|
-
segment = segment[:-1]
|
|
213
|
-
return base64.urlsafe_b64decode(segment + "=" * (-len(segment) % 4))
|
|
214
|
-
|
|
215
134
|
|
|
216
135
|
class _Duplicate(ValueError):
|
|
217
136
|
pass
|
|
@@ -681,90 +600,6 @@ def read_delegation_token(headers: Any) -> Optional[Dict[str, str]]:
|
|
|
681
600
|
|
|
682
601
|
# --- RFC 8785 canonical JSON --------------------------------------------------------------------------------
|
|
683
602
|
|
|
684
|
-
def _number(value: float) -> str:
|
|
685
|
-
"""ECMAScript Number::toString, which RFC 8785 §3.2.2.3 prescribes."""
|
|
686
|
-
if value == 0:
|
|
687
|
-
return "0"
|
|
688
|
-
if value < 0:
|
|
689
|
-
return "-" + _number(-value)
|
|
690
|
-
digits, exponent = _shortest(value)
|
|
691
|
-
k, n = len(digits), len(digits) + exponent
|
|
692
|
-
if k <= n <= 21:
|
|
693
|
-
return digits + "0" * (n - k)
|
|
694
|
-
if 0 < n <= 21:
|
|
695
|
-
return digits[:n] + "." + digits[n:]
|
|
696
|
-
if -6 < n <= 0:
|
|
697
|
-
return "0." + "0" * (-n) + digits
|
|
698
|
-
e = n - 1
|
|
699
|
-
mantissa = digits if k == 1 else digits[0] + "." + digits[1:]
|
|
700
|
-
return "%se%s%d" % (mantissa, "+" if e >= 0 else "-", abs(e))
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
def _shortest(value: float) -> Tuple[str, int]:
|
|
704
|
-
"""The shortest round-trip digits of a positive finite double (Python's repr is shortest round-trip)."""
|
|
705
|
-
text = repr(value)
|
|
706
|
-
mantissa, _, exp = text.partition("e")
|
|
707
|
-
whole, _, frac = mantissa.partition(".")
|
|
708
|
-
digits = (whole + frac).lstrip("0")
|
|
709
|
-
exponent = (int(exp) if exp else 0) - len(frac)
|
|
710
|
-
if not digits:
|
|
711
|
-
return "0", 0
|
|
712
|
-
stripped = digits.rstrip("0")
|
|
713
|
-
exponent += len(digits) - len(stripped)
|
|
714
|
-
return stripped, exponent
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
_ESCAPES = {'"': '\\"', "\\": "\\\\", "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t"}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
def _string(value: str) -> str:
|
|
721
|
-
out = ['"']
|
|
722
|
-
for ch in value:
|
|
723
|
-
code = ord(ch)
|
|
724
|
-
if 0xD800 <= code <= 0xDFFF:
|
|
725
|
-
raise TypeError("a lone surrogate has no canonical form")
|
|
726
|
-
if ch in _ESCAPES:
|
|
727
|
-
out.append(_ESCAPES[ch])
|
|
728
|
-
elif code < 0x20:
|
|
729
|
-
out.append("\\u%04x" % code)
|
|
730
|
-
else:
|
|
731
|
-
out.append(ch)
|
|
732
|
-
out.append('"')
|
|
733
|
-
return "".join(out)
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
def jcs(value: Any) -> str:
|
|
737
|
-
"""RFC 8785: members sorted by UTF-16 code units, numbers in ECMAScript form, strings as JSON.stringify
|
|
738
|
-
escapes them. Refuses what has no canonical form: a non-finite number, a lone surrogate, a non-JSON value.
|
|
739
|
-
A JSON number is a double: an integer beyond 2**53 is written as JavaScript would write it."""
|
|
740
|
-
if value is None:
|
|
741
|
-
return "null"
|
|
742
|
-
if value is True:
|
|
743
|
-
return "true"
|
|
744
|
-
if value is False:
|
|
745
|
-
return "false"
|
|
746
|
-
if isinstance(value, int):
|
|
747
|
-
if abs(value) < 2 ** 53:
|
|
748
|
-
return str(value)
|
|
749
|
-
try:
|
|
750
|
-
return _number(float(value))
|
|
751
|
-
except OverflowError:
|
|
752
|
-
raise TypeError("a number beyond a double has no canonical form") from None
|
|
753
|
-
if isinstance(value, float):
|
|
754
|
-
if not math.isfinite(value):
|
|
755
|
-
raise TypeError("a non-finite number has no canonical form")
|
|
756
|
-
return _number(value)
|
|
757
|
-
if isinstance(value, str):
|
|
758
|
-
return _string(value)
|
|
759
|
-
if isinstance(value, list):
|
|
760
|
-
return "[" + ",".join(jcs(item) for item in value) + "]"
|
|
761
|
-
if isinstance(value, dict):
|
|
762
|
-
if not all(isinstance(key, str) for key in value):
|
|
763
|
-
raise TypeError("only string member names have a canonical form")
|
|
764
|
-
keys = sorted(value, key=lambda key: key.encode("utf-16-be", "surrogatepass"))
|
|
765
|
-
return "{" + ",".join(_string(key) + ":" + jcs(value[key]) for key in keys) + "}"
|
|
766
|
-
raise TypeError("%s has no canonical form" % type(value).__name__)
|
|
767
|
-
|
|
768
603
|
|
|
769
604
|
def action_digest(action: Any) -> str:
|
|
770
605
|
"""The digest an approval binds: sha256: and the hex SHA-256 of the action's RFC 8785 form."""
|