warrant-graph 2.0.0a1__py3-none-any.whl
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.
- warrant/__init__.py +11 -0
- warrant/bindings.py +145 -0
- warrant/cli.py +401 -0
- warrant/compute.py +291 -0
- warrant/engine.py +990 -0
- warrant/jcs.py +140 -0
- warrant/keys.py +80 -0
- warrant/limits.py +39 -0
- warrant/pilot.py +436 -0
- warrant/records.py +318 -0
- warrant/shapes.py +63 -0
- warrant/store.py +469 -0
- warrant/triggers.py +138 -0
- warrant_agent/__init__.py +21 -0
- warrant_agent/_assess_worker.py +60 -0
- warrant_agent/api.py +486 -0
- warrant_agent/cli.py +58 -0
- warrant_agent/computeops.py +177 -0
- warrant_agent/config.py +159 -0
- warrant_agent/drafts.py +220 -0
- warrant_agent/errors.py +24 -0
- warrant_agent/glossary.py +102 -0
- warrant_agent/handoff.py +283 -0
- warrant_agent/init.py +312 -0
- warrant_agent/mcp_server.py +238 -0
- warrant_agent/objects.py +168 -0
- warrant_agent/views.py +427 -0
- warrant_graph-2.0.0a1.dist-info/METADATA +130 -0
- warrant_graph-2.0.0a1.dist-info/RECORD +33 -0
- warrant_graph-2.0.0a1.dist-info/WHEEL +5 -0
- warrant_graph-2.0.0a1.dist-info/entry_points.txt +4 -0
- warrant_graph-2.0.0a1.dist-info/licenses/LICENSE +21 -0
- warrant_graph-2.0.0a1.dist-info/top_level.txt +2 -0
warrant/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Warrant 2: transitions of work, exact closures and what remains open (SPEC.md)."""
|
|
2
|
+
from .jcs import canon, loads
|
|
3
|
+
from .engine import assess, capsule, frontier, profile, questions, reliance
|
|
4
|
+
from .keys import Key
|
|
5
|
+
from .records import accept, challenge, duty, grant, item, observation, record, transition
|
|
6
|
+
from .store import Snapshot, Store, Trust
|
|
7
|
+
|
|
8
|
+
__version__ = "2.0.0a1"
|
|
9
|
+
__all__ = ["Key", "Snapshot", "Store", "Trust", "accept", "assess", "canon", "capsule", "challenge", "duty",
|
|
10
|
+
"frontier", "grant", "item", "loads", "observation", "profile", "questions", "record", "reliance",
|
|
11
|
+
"transition"]
|
warrant/bindings.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Bindings and roles of a repository's trust (the open circle of witnesses, part A, and
|
|
2
|
+
AMEND 1 of #170): the one rule tools/claims.py and a warrant-agent workspace admit keys by.
|
|
3
|
+
|
|
4
|
+
A binding certifies itself: its key signs canon({actor, public, purpose}). `admit_bindings`
|
|
5
|
+
admits one mechanically, in file order, under the roles .warrant/roles.json pins; nobody judges
|
|
6
|
+
who may join. A binding grants only "this key signs as this actor" (SPEC §6). This module reads
|
|
7
|
+
documents; it writes none and runs nothing (the claims tool writes them)."""
|
|
8
|
+
from . import jcs as C
|
|
9
|
+
from . import keys as K
|
|
10
|
+
from . import records as R
|
|
11
|
+
from . import shapes as S
|
|
12
|
+
from .store import Trust
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Open circle of witnesses: a binding certifies itself (its key signs it); nobody admits anyone.
|
|
16
|
+
BINDINGS = "warrant-claims/bindings@1" # .warrant/trust.json: {"bindings": BINDINGS, "keys": [...]}
|
|
17
|
+
BINDING = "warrant-claims/binding@1" # the purpose a proof signs: canon({actor, public, purpose})
|
|
18
|
+
BINDING_FIELDS = ("actor", "public", "proof")
|
|
19
|
+
ROLES = "warrant-claims/roles@1" # .warrant/roles.json: the actors the configuration gives a role
|
|
20
|
+
COLLECTIVE = "collective" # a role no key signs as (readers): never bindable
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def binding_message(actor, public):
|
|
24
|
+
"""The bytes a binding's proof signs: canon({actor, public, purpose}). It starts with `{`,
|
|
25
|
+
so it is never the message of a record signature ("warrant-sig-v1:" || id)."""
|
|
26
|
+
return C.canon({"actor": actor, "public": public, "purpose": BINDING})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def binding_proof(key, actor):
|
|
30
|
+
return key.sign(binding_message(actor, key.public))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_roles(doc):
|
|
34
|
+
"""({actor: public hex | None | COLLECTIVE}, problems) of a roles document (.warrant/roles.json).
|
|
35
|
+
|
|
36
|
+
AMEND 1 of #170: a name the repository's own configuration gives a role (an acceptor, a
|
|
37
|
+
creditor, the claims' author) is not first-come. Its key is pinned here, as a receiver's
|
|
38
|
+
profile pins the keys it trusts: a hex64 public key (a binding of that actor is admitted
|
|
39
|
+
with exactly that key), null (reserved: not bindable until a pin names its key) or
|
|
40
|
+
"collective" (a role no key signs as, such as `readers`: never bindable). Every other
|
|
41
|
+
name stays first-come. Changing who a role is, is a diff of this reviewed file."""
|
|
42
|
+
if not (isinstance(doc, dict) and set(doc) == {"roles", "actors"} and doc["roles"] == ROLES
|
|
43
|
+
and isinstance(doc["actors"], dict)):
|
|
44
|
+
return {}, [("ROLES_SHAPE", f".warrant/roles.json is not {{\"roles\": {ROLES!r}, \"actors\": {{...}}}}")]
|
|
45
|
+
roles, problems = {}, []
|
|
46
|
+
for actor, v in doc["actors"].items():
|
|
47
|
+
try:
|
|
48
|
+
R.check_actor(actor)
|
|
49
|
+
except R.Invalid as e:
|
|
50
|
+
problems.append(("ROLES_SHAPE", f".warrant/roles.json: {actor[:60]!r} is not an actor id: {e}"))
|
|
51
|
+
continue
|
|
52
|
+
if not (v is None or v == COLLECTIVE or S.hex64(v)):
|
|
53
|
+
problems.append(("ROLES_SHAPE", f".warrant/roles.json: {actor}: a role is a hex64 public key, null or "
|
|
54
|
+
f"{COLLECTIVE!r}, not {str(v)[:70]!r}"))
|
|
55
|
+
continue
|
|
56
|
+
roles[actor] = v
|
|
57
|
+
return roles, problems
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _role_name(v):
|
|
61
|
+
"""A role value for a message: 'key <id>' for a key, else what it is (never raises)."""
|
|
62
|
+
if S.hex64(v):
|
|
63
|
+
return f"key {K.key_id(v)[:12]}"
|
|
64
|
+
if v == COLLECTIVE:
|
|
65
|
+
return "collective"
|
|
66
|
+
return "reserved" if v is None else repr(v)[:40]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def admit_bindings(doc, legacy=False, roles=None):
|
|
70
|
+
"""(admitted [(actor, public)], problems [(code, detail)]) of a bindings document.
|
|
71
|
+
|
|
72
|
+
A binding is admitted mechanically, in file order, iff its proof is the signature of its
|
|
73
|
+
own key over binding_message(actor, public), its actor id has SPEC-3-21's shape, its
|
|
74
|
+
actor's role (`roles`, read_roles) admits that key, its key is not bound already, and its
|
|
75
|
+
actor is not bound already to another key: the first binding of a name with no role
|
|
76
|
+
wins, and a second key for it would need a record signed by the first, which this tool
|
|
77
|
+
does not implement (rotation is out of scope), so it is refused. Nobody judges who may
|
|
78
|
+
join; what a binding grants is only "this key signs as this actor" (SPEC §6). `legacy`
|
|
79
|
+
reads a trust/2 document (actor, public, no proof) as admitted: only for a base revision
|
|
80
|
+
written before bindings carried proofs."""
|
|
81
|
+
if legacy and isinstance(doc, dict) and doc.get("warrant") == "trust/2":
|
|
82
|
+
try:
|
|
83
|
+
return [(a, p) for a, p in Trust(doc).keys.values()], []
|
|
84
|
+
except R.Invalid as e:
|
|
85
|
+
return [], [("BINDINGS_SHAPE", f".warrant/trust.json: {e}")]
|
|
86
|
+
if not (isinstance(doc, dict) and set(doc) == {"bindings", "keys"} and doc["bindings"] == BINDINGS
|
|
87
|
+
and isinstance(doc["keys"], list)):
|
|
88
|
+
return [], [("BINDINGS_SHAPE", f".warrant/trust.json is not {{\"bindings\": {BINDINGS!r}, \"keys\": [...]}} "
|
|
89
|
+
"(tools/claims.py bind writes it)")]
|
|
90
|
+
admitted, problems = [], []
|
|
91
|
+
publics = [k.get("public") for k in doc["keys"] if isinstance(k, dict)]
|
|
92
|
+
for i, k in enumerate(doc["keys"]):
|
|
93
|
+
code, why = _binding_problem(k, publics, admitted, roles or {})
|
|
94
|
+
if code:
|
|
95
|
+
problems.append((code, f".warrant/trust.json keys[{i}]: {why}"))
|
|
96
|
+
else:
|
|
97
|
+
admitted.append((k["actor"], k["public"]))
|
|
98
|
+
return admitted, problems
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _binding_problem(k, publics, admitted, roles):
|
|
102
|
+
"""(code, why) when entry `k` is not admitted after `admitted`, else (None, None)."""
|
|
103
|
+
if not (isinstance(k, dict) and set(k) <= set(BINDING_FIELDS) and {"actor", "public"} <= set(k)
|
|
104
|
+
and S.hex64(k["public"])):
|
|
105
|
+
return "BINDING_SHAPE", f"an entry is {{actor, public: hex64, proof: hex128}}, not {str(k)[:120]}"
|
|
106
|
+
try:
|
|
107
|
+
R.check_actor(k["actor"])
|
|
108
|
+
except R.Invalid as e:
|
|
109
|
+
return "BINDING_ACTOR_SHAPE", f"{str(k['actor'])[:60]!r} is not an actor id (SPEC-3-21): {e}"
|
|
110
|
+
if "proof" not in k:
|
|
111
|
+
return "BINDING_UNPROVEN", f"{k['actor']}: no proof; the key's own signature over the binding is needed"
|
|
112
|
+
msg = binding_message(k["actor"], k["public"])
|
|
113
|
+
if not K.verify(k["public"], k["proof"], msg):
|
|
114
|
+
other = [p for p in publics if p != k["public"] and isinstance(p, str) and K.verify(p, k["proof"], msg)]
|
|
115
|
+
if other:
|
|
116
|
+
return "BINDING_PROOF_OTHER_KEY", (f"{k['actor']}: the proof is signed by key {K.key_id(other[0])[:12]}, "
|
|
117
|
+
f"not by the key it binds ({K.key_id(k['public'])[:12]})")
|
|
118
|
+
return "BINDING_PROOF_FORGED", f"{k['actor']}: the proof is not the bound key's signature over this binding"
|
|
119
|
+
role = roles.get(k["actor"], k["public"])
|
|
120
|
+
if role != k["public"]: # one rule; the code only says which kind of role refused it
|
|
121
|
+
if role == COLLECTIVE:
|
|
122
|
+
return "BINDING_ROLE_COLLECTIVE", f"{k['actor']} is a collective role in .warrant/roles.json: no key signs as it"
|
|
123
|
+
if role is None:
|
|
124
|
+
return "BINDING_ROLE_RESERVED", (f"{k['actor']} is reserved in .warrant/roles.json: it is bindable once a "
|
|
125
|
+
"reviewed pin names its key")
|
|
126
|
+
return "BINDING_ROLE_KEY", (f"{k['actor']}: .warrant/roles.json pins {_role_name(role)}, this binding is of "
|
|
127
|
+
f"key {K.key_id(k['public'])[:12]}")
|
|
128
|
+
if any(p == k["public"] for _, p in admitted):
|
|
129
|
+
return "BINDING_KEY_TWICE", f"key {K.key_id(k['public'])[:12]} is bound already"
|
|
130
|
+
first = next((p for a, p in admitted if a == k["actor"]), None)
|
|
131
|
+
if first is not None:
|
|
132
|
+
return "BINDING_ACTOR_TAKEN", (f"{k['actor']} is bound already to key {K.key_id(first)[:12]}; the first "
|
|
133
|
+
"binding wins, and rotation (a record signed by the first key) is not "
|
|
134
|
+
"implemented, so a second key for an actor is refused")
|
|
135
|
+
return None, None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def trust_of(bindings):
|
|
139
|
+
"""A Trust of (actor, public) pairs, the first binding of each key kept."""
|
|
140
|
+
seen, pairs = set(), []
|
|
141
|
+
for a, p in bindings:
|
|
142
|
+
if p not in seen:
|
|
143
|
+
seen.add(p)
|
|
144
|
+
pairs.append((a, p))
|
|
145
|
+
return Trust(Trust.document(pairs))
|
warrant/cli.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""`warrant` — the one command-line interface of Warrant 2 (SPEC.md).
|
|
2
|
+
|
|
3
|
+
Writing: init, key, trust, blob, file, accept, observe, manifest, compute
|
|
4
|
+
Reading: assess, reliance, frontier, questions, capsule
|
|
5
|
+
Every reading command assesses one view: --store, --trust, --profile, --as-of,
|
|
6
|
+
and optionally --current and --workspace. Output is JSON."""
|
|
7
|
+
import argparse
|
|
8
|
+
import base64
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from . import jcs as C
|
|
17
|
+
from . import compute as P
|
|
18
|
+
from . import engine as E
|
|
19
|
+
from . import limits as L
|
|
20
|
+
from . import records as R
|
|
21
|
+
from . import triggers as TR
|
|
22
|
+
from .keys import Key
|
|
23
|
+
from .store import Store, Trust
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _out(doc):
|
|
27
|
+
sys.stdout.write(json.dumps(doc, indent=2, ensure_ascii=False, sort_keys=True) + "\n")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _read_json(arg):
|
|
31
|
+
if arg == "-":
|
|
32
|
+
return json.loads(sys.stdin.read())
|
|
33
|
+
if arg.lstrip().startswith(("{", "[")):
|
|
34
|
+
return json.loads(arg)
|
|
35
|
+
return json.loads(Path(arg).read_text())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _key(path):
|
|
39
|
+
return Key.from_seed_hex(Path(path).read_text())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _now(v):
|
|
43
|
+
return int(time.time()) if v is None else v
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------- writing
|
|
47
|
+
|
|
48
|
+
def cmd_init(a):
|
|
49
|
+
Store(a.store).init()
|
|
50
|
+
_out({"store": str(Path(a.store))})
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cmd_key(a):
|
|
54
|
+
p = Path(a.out)
|
|
55
|
+
if p.exists():
|
|
56
|
+
raise SystemExit(f"refusing to overwrite {p}")
|
|
57
|
+
k = Key.generate()
|
|
58
|
+
fd = os.open(p, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
59
|
+
with os.fdopen(fd, "w") as f:
|
|
60
|
+
f.write(k.seed_hex() + "\n")
|
|
61
|
+
_out({"key_file": str(p), "public": k.public, "key_id": k.id})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_trust(a):
|
|
65
|
+
p = Path(a.trust)
|
|
66
|
+
doc = json.loads(p.read_text()) if p.exists() else Trust.document([])
|
|
67
|
+
pub = _key(a.key).public if a.key else a.public
|
|
68
|
+
doc["keys"].append({"actor": a.actor, "public": pub})
|
|
69
|
+
Trust(doc) # refuse a malformed or doubly bound file
|
|
70
|
+
p.write_bytes(C.canon(doc))
|
|
71
|
+
_out({"trust": str(p), "actor": a.actor, "public": pub})
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cmd_blob(a):
|
|
75
|
+
s = Store(a.store).init()
|
|
76
|
+
if a.json is not None:
|
|
77
|
+
h = s.put_json(_read_json(a.json))
|
|
78
|
+
else:
|
|
79
|
+
h = s.put_blob(sys.stdin.buffer.read() if a.file == "-" else Path(a.file).read_bytes())
|
|
80
|
+
_out({"blob": h})
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Structural members `warrant file` fills when a body omits them. Only empty
|
|
84
|
+
# values: nothing here states a meaning. Semantic members (a duty's statement,
|
|
85
|
+
# holder, owed_to, accept_by, condition; a transition's basis, established,
|
|
86
|
+
# residual, closes) are never filled, so their absence is still refused.
|
|
87
|
+
STRUCTURAL = {
|
|
88
|
+
"duty": {"release_by": [], "schedule": None, "requires": [], "serves": [], "answers": None, "subject": None},
|
|
89
|
+
"transition": {"relied_on": [], "opened": [], "subject": None, "supersedes": [], "triggers": []},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def fill(rtype, body):
|
|
94
|
+
"""`body` with omitted structural members filled; given members are kept as they are.
|
|
95
|
+
A transition that omits `evidence` carries exactly the evidence its items cite."""
|
|
96
|
+
if rtype not in STRUCTURAL or not isinstance(body, dict):
|
|
97
|
+
return body
|
|
98
|
+
out = {k: v for k, v in STRUCTURAL[rtype].items() if k not in body}
|
|
99
|
+
if rtype == "transition" and "evidence" not in body and isinstance(body.get("closes"), list):
|
|
100
|
+
out["evidence"] = sorted({h for m in body["closes"] if isinstance(m, dict)
|
|
101
|
+
for h in (m.get("evidence") if isinstance(m.get("evidence"), list) else [])})
|
|
102
|
+
out.update(body)
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _store(a):
|
|
107
|
+
"""The store a signer writes, with its own verdicts directory if it names one (F44)."""
|
|
108
|
+
return Store(a.store, verdicts=a.verdicts).init()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _append(s, rtype, actor, at, body, key):
|
|
112
|
+
"""File a record extending `actor`'s chain in store `s` (SPEC §3: `prev` is its head)."""
|
|
113
|
+
try:
|
|
114
|
+
return s.append(rtype, actor, at, body, key)
|
|
115
|
+
except R.Invalid as e:
|
|
116
|
+
raise SystemExit(f"invalid {rtype}: {e}")
|
|
117
|
+
except ValueError as e:
|
|
118
|
+
raise SystemExit(str(e))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def cmd_file(a):
|
|
122
|
+
s = _store(a)
|
|
123
|
+
body = fill(a.type, _read_json(a.body))
|
|
124
|
+
rid = _append(s, a.type, a.actor, _now(a.at), body, _key(a.key))
|
|
125
|
+
if a.id:
|
|
126
|
+
sys.stdout.write(rid + "\n")
|
|
127
|
+
else:
|
|
128
|
+
_out({"record": rid, "type": a.type})
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def cmd_accept(a):
|
|
132
|
+
"""Accept one exact item (or a transition's results) as it is filed."""
|
|
133
|
+
s = _store(a)
|
|
134
|
+
snap = s.snapshot()
|
|
135
|
+
env = C.loads(snap.records[a.transition])
|
|
136
|
+
t = env["record"]["body"]
|
|
137
|
+
ev = t["evidence"] if a.item is None else t["closes"][a.item]["evidence"]
|
|
138
|
+
rid = _append(s, "accept", a.actor, _now(a.at), R.accept(a.transition, a.item, ev, a.support or []), _key(a.key))
|
|
139
|
+
_out({"record": rid, "type": "accept"})
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def cmd_observe(a):
|
|
143
|
+
"""Run a probe once and file what was observed, when, and how."""
|
|
144
|
+
s = _store(a)
|
|
145
|
+
probe = _read_json(a.probe)
|
|
146
|
+
if not (isinstance(probe, dict) and isinstance(probe.get("argv"), list) and probe["argv"]):
|
|
147
|
+
raise SystemExit("a probe is {\"argv\": [...], ...}: the command this observer runs")
|
|
148
|
+
probe_h = s.put_json(probe)
|
|
149
|
+
t0 = int(time.time())
|
|
150
|
+
try:
|
|
151
|
+
p = subprocess.run(probe["argv"], capture_output=True, timeout=probe.get("timeout", 30), check=False)
|
|
152
|
+
result = p.stdout.decode(errors="replace").strip()[:R.MAX_TEXT]
|
|
153
|
+
raw = [s.put_blob(p.stdout), s.put_json({"exit": p.returncode, "stderr": p.stderr.decode(errors="replace")[-2000:]})]
|
|
154
|
+
except (OSError, subprocess.TimeoutExpired) as e:
|
|
155
|
+
result, raw = f"PROBE_FAILED: {type(e).__name__}", []
|
|
156
|
+
t1 = max(t0, int(time.time()))
|
|
157
|
+
body = R.observation(a.target, probe_h, [t0, t1], result, raw)
|
|
158
|
+
rid = _append(s, "observation", a.actor, _now(a.at) if a.at is not None else t1, body, _key(a.key))
|
|
159
|
+
_out({"record": rid, "type": "observation", "result": result, "window": [t0, t1]})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cmd_manifest(a):
|
|
163
|
+
s = Store(a.store).init()
|
|
164
|
+
try:
|
|
165
|
+
doc = TR.manifest(a.repo, a.rev, sorted(a.paths))
|
|
166
|
+
except TR.Refusal as e:
|
|
167
|
+
raise SystemExit(f"manifest refused: {e}")
|
|
168
|
+
_out({"blob": s.put_json(doc), "commit": TR.resolve(a.repo, a.rev)})
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------- compute (producer side)
|
|
172
|
+
|
|
173
|
+
def _env_blobs(s, env_h):
|
|
174
|
+
snap = s.snapshot()
|
|
175
|
+
doc = snap.json_blob(env_h)
|
|
176
|
+
if doc is None or P.environment_problem(doc):
|
|
177
|
+
raise SystemExit("not a sigma environment manifest in this store")
|
|
178
|
+
hashes = P.environment_hashes(doc)
|
|
179
|
+
return hashes, {h: snap.blobs[h] for h in hashes if h in snap.blobs}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _call(op, args, blobs=None):
|
|
183
|
+
try:
|
|
184
|
+
out = P.Provider.capture().call(op, args, blobs)
|
|
185
|
+
except P.ProviderError as e:
|
|
186
|
+
raise SystemExit(f"provider: {e}")
|
|
187
|
+
if "refused" in out:
|
|
188
|
+
raise SystemExit(f"provider refused: {out['refused']}")
|
|
189
|
+
return out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cmd_compute(a):
|
|
193
|
+
s = Store(a.store).init()
|
|
194
|
+
runtime = {"book1": P.BOOK1, "rich": P.RICH}[a.runtime]
|
|
195
|
+
if a.op == "provider":
|
|
196
|
+
try:
|
|
197
|
+
p = P.Provider.capture()
|
|
198
|
+
except P.ProviderError as e:
|
|
199
|
+
raise SystemExit(f"provider: {e}")
|
|
200
|
+
return _out({"provider": p.id, "where": p.where, "files": p.identity["files"]})
|
|
201
|
+
if a.op == "term":
|
|
202
|
+
out = _call("term", {"expr": a.expr})
|
|
203
|
+
nodes = [s.put_blob(base64.b64decode(b)) for b in out["nodes"].values()]
|
|
204
|
+
env_h = s.put_json(P.environment_doc(nodes))
|
|
205
|
+
return _out({"input": out["input"], "environment": env_h, "runtime": runtime, "nodes": len(nodes)})
|
|
206
|
+
hashes, blobs = _env_blobs(s, a.environment)
|
|
207
|
+
base = {"runtime": runtime, "input": a.input, "hashes": hashes}
|
|
208
|
+
if a.op == "start":
|
|
209
|
+
out = _call("start", base, blobs)
|
|
210
|
+
elif a.op == "advance":
|
|
211
|
+
snap = s.snapshot()
|
|
212
|
+
out = _call("advance", dict(base, checkpoint=base64.b64encode(snap.blobs[a.checkpoint]).decode(),
|
|
213
|
+
state=base64.b64encode(snap.blobs[a.state]).decode(), credit=a.credit), blobs)
|
|
214
|
+
elif a.op == "cycle":
|
|
215
|
+
out = _call("find_cycle", dict(base, max_steps=a.max_steps), blobs)
|
|
216
|
+
if out.get("witness") is None:
|
|
217
|
+
return _out({"cycle": None, "outcome": out.get("outcome")})
|
|
218
|
+
w = s.put_blob(base64.b64decode(out["witness"]))
|
|
219
|
+
doc = {"cycle": "sigma-resume", "target": a.target, "input": a.input, "environment": a.environment,
|
|
220
|
+
"witness": w, "lost": []}
|
|
221
|
+
return _out({"cycle": s.put_json(doc), "witness": w})
|
|
222
|
+
else:
|
|
223
|
+
raise SystemExit(f"unknown compute op {a.op}")
|
|
224
|
+
ck = s.put_blob(base64.b64decode(out["checkpoint"]))
|
|
225
|
+
st = s.put_blob(base64.b64decode(out["state"]))
|
|
226
|
+
res = {"outcome": out.get("outcome", "STARTED"), "checkpoint": ck, "state": st, "result": out.get("result")}
|
|
227
|
+
if out.get("outcome") == "NORMAL_FORM" and a.target:
|
|
228
|
+
res["receipt"] = s.put_json({"receipt": "sigma-resume", "target": a.target, "input": a.input,
|
|
229
|
+
"environment": a.environment, "checkpoint": ck, "state": st,
|
|
230
|
+
"claimed": out["result"], "lost": []})
|
|
231
|
+
_out(res)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# ---------------------------------------------------------------- reading
|
|
235
|
+
|
|
236
|
+
def _limits(a):
|
|
237
|
+
out = {}
|
|
238
|
+
for item in a.limit:
|
|
239
|
+
name, _, value = item.partition("=")
|
|
240
|
+
if not value.isdigit():
|
|
241
|
+
raise SystemExit(f"--limit {item!r}: NAME=N")
|
|
242
|
+
out[name] = int(value)
|
|
243
|
+
try:
|
|
244
|
+
return L.resolve(out)
|
|
245
|
+
except ValueError as e:
|
|
246
|
+
raise SystemExit(str(e)) from None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _report(a):
|
|
250
|
+
s = Store(a.store)
|
|
251
|
+
lim = _limits(a)
|
|
252
|
+
snap = s.snapshot(limits=lim)
|
|
253
|
+
trust = Trust.load(a.trust)
|
|
254
|
+
prof = _read_json(a.profile) if a.profile else E.profile()
|
|
255
|
+
ws = None
|
|
256
|
+
if a.workspace:
|
|
257
|
+
repo, _, rev = a.workspace.rpartition("@")
|
|
258
|
+
ws = (repo, rev or "HEAD")
|
|
259
|
+
try:
|
|
260
|
+
provider = P.Provider.capture()
|
|
261
|
+
except P.ProviderError as e:
|
|
262
|
+
print(f"warning: provider unavailable, replays are UNVERIFIED: {e}", file=sys.stderr)
|
|
263
|
+
provider = None
|
|
264
|
+
return E.assess(snap, trust, _now(a.as_of), prof, current=a.current or [], workspace=ws, provider=provider,
|
|
265
|
+
replay_cache=a.replay_cache, limits=lim, verdicts=a.verdicts)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def cmd_assess(a):
|
|
269
|
+
_out(_report(a))
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def cmd_reliance(a):
|
|
273
|
+
_out(E.reliance(_report(a), a.target, a.use))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def cmd_frontier(a):
|
|
277
|
+
rep = _report(a)
|
|
278
|
+
_out({"assessment_id": rep["assessment_id"], "actor": a.actor, "frontier": E.frontier(rep, a.actor)})
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def cmd_questions(a):
|
|
282
|
+
rep = _report(a)
|
|
283
|
+
_out({"assessment_id": rep["assessment_id"], "questions": E.questions(rep, a.scope, owned=a.all)})
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def cmd_capsule(a):
|
|
287
|
+
_out(E.capsule(_report(a), a.target, a.use))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def build_parser():
|
|
291
|
+
ap = argparse.ArgumentParser(prog="warrant", description=__doc__, allow_abbrev=False,
|
|
292
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
293
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
294
|
+
|
|
295
|
+
def store(p):
|
|
296
|
+
p.add_argument("--store", required=True)
|
|
297
|
+
|
|
298
|
+
def signer(p):
|
|
299
|
+
store(p)
|
|
300
|
+
p.add_argument("--key", required=True, help="file holding the signer's Ed25519 seed (hex)")
|
|
301
|
+
p.add_argument("--actor", required=True)
|
|
302
|
+
p.add_argument("--at", type=int, help="record time (default: now)")
|
|
303
|
+
p.add_argument("--verdicts", help="directory of this signer's earlier authentication verdicts (cost only)")
|
|
304
|
+
|
|
305
|
+
def view(p):
|
|
306
|
+
store(p)
|
|
307
|
+
p.add_argument("--trust", required=True)
|
|
308
|
+
p.add_argument("--profile", help="the recipient's profile JSON (default: one use 'act', no acceptors)")
|
|
309
|
+
p.add_argument("--as-of", type=int, help="end of the view (default: now)")
|
|
310
|
+
p.add_argument("--current", action="append", help="a revision the recipient treats as current")
|
|
311
|
+
p.add_argument("--workspace", help="REPO@REV for subject_changed triggers")
|
|
312
|
+
p.add_argument("--replay-cache", help="directory of this recipient's earlier verified replays (cost only)")
|
|
313
|
+
p.add_argument("--verdicts", help="directory of this recipient's earlier authentication verdicts (cost only)")
|
|
314
|
+
p.add_argument("--limit", action="append", default=[], metavar="NAME=N",
|
|
315
|
+
help="override an admission limit (SPEC §9), e.g. duty_periods=50000")
|
|
316
|
+
|
|
317
|
+
p = sub.add_parser("init", help="create a store")
|
|
318
|
+
store(p)
|
|
319
|
+
p.set_defaults(fn=cmd_init)
|
|
320
|
+
p = sub.add_parser("key", help="generate an Ed25519 key file (0600, never overwritten)")
|
|
321
|
+
p.add_argument("--out", required=True)
|
|
322
|
+
p.set_defaults(fn=cmd_key)
|
|
323
|
+
p = sub.add_parser("trust", help="bind a key to an actor in a trust file")
|
|
324
|
+
p.add_argument("--trust", required=True)
|
|
325
|
+
p.add_argument("--actor", required=True)
|
|
326
|
+
g = p.add_mutually_exclusive_group(required=True)
|
|
327
|
+
g.add_argument("--key")
|
|
328
|
+
g.add_argument("--public")
|
|
329
|
+
p.set_defaults(fn=cmd_trust)
|
|
330
|
+
p = sub.add_parser("blob", help="add bytes (FILE or -) or a JSON document (--json) to the store")
|
|
331
|
+
store(p)
|
|
332
|
+
p.add_argument("file", nargs="?", default="-")
|
|
333
|
+
p.add_argument("--json")
|
|
334
|
+
p.set_defaults(fn=cmd_blob)
|
|
335
|
+
p = sub.add_parser("file", help="sign and file a record body (SPEC §3); a duty or transition body may omit "
|
|
336
|
+
"its structural members (empty lists, nulls; a transition's evidence is "
|
|
337
|
+
"then the evidence its items cite)")
|
|
338
|
+
signer(p)
|
|
339
|
+
p.add_argument("--type", required=True, choices=R.TYPES)
|
|
340
|
+
p.add_argument("--id", action="store_true", help="print only the new record id")
|
|
341
|
+
p.add_argument("body", help="JSON body, a file, or -")
|
|
342
|
+
p.set_defaults(fn=cmd_file)
|
|
343
|
+
p = sub.add_parser("accept", help="accept one exact item of a transition, or its results")
|
|
344
|
+
signer(p)
|
|
345
|
+
p.add_argument("transition")
|
|
346
|
+
p.add_argument("--item", type=int, help="item index; omit to accept the transition's results")
|
|
347
|
+
p.add_argument("--support", action="append")
|
|
348
|
+
p.set_defaults(fn=cmd_accept)
|
|
349
|
+
p = sub.add_parser("observe", help="run a probe and file the observation")
|
|
350
|
+
signer(p)
|
|
351
|
+
p.add_argument("--target", required=True)
|
|
352
|
+
p.add_argument("--probe", required=True, help='JSON {"argv": [...], "timeout": s}')
|
|
353
|
+
p.set_defaults(fn=cmd_observe)
|
|
354
|
+
p = sub.add_parser("manifest", help="store a subject_changed baseline for explicit paths at a revision")
|
|
355
|
+
store(p)
|
|
356
|
+
p.add_argument("--repo", required=True)
|
|
357
|
+
p.add_argument("--rev", required=True)
|
|
358
|
+
p.add_argument("paths", nargs="+")
|
|
359
|
+
p.set_defaults(fn=cmd_manifest)
|
|
360
|
+
p = sub.add_parser("compute", help="the installed Sigma provider: provider (its identity), term, start, advance, cycle")
|
|
361
|
+
store(p)
|
|
362
|
+
p.add_argument("op", choices=("provider", "term", "start", "advance", "cycle"))
|
|
363
|
+
p.add_argument("--runtime", choices=("book1", "rich"), default="book1")
|
|
364
|
+
p.add_argument("--expr")
|
|
365
|
+
p.add_argument("--input")
|
|
366
|
+
p.add_argument("--environment")
|
|
367
|
+
p.add_argument("--checkpoint")
|
|
368
|
+
p.add_argument("--state")
|
|
369
|
+
p.add_argument("--credit", type=int, default=0)
|
|
370
|
+
p.add_argument("--max-steps", type=int, default=10000)
|
|
371
|
+
p.add_argument("--target", help="crossing target named in the receipt or cycle")
|
|
372
|
+
p.set_defaults(fn=cmd_compute)
|
|
373
|
+
for name, fn, extra in (("assess", cmd_assess, None), ("reliance", cmd_reliance, "target"),
|
|
374
|
+
("frontier", cmd_frontier, "actor"), ("questions", cmd_questions, None),
|
|
375
|
+
("capsule", cmd_capsule, "target")):
|
|
376
|
+
p = sub.add_parser(name)
|
|
377
|
+
view(p)
|
|
378
|
+
if extra:
|
|
379
|
+
p.add_argument(extra)
|
|
380
|
+
if name in ("reliance", "capsule"):
|
|
381
|
+
p.add_argument("--use", default="act")
|
|
382
|
+
if name == "questions":
|
|
383
|
+
p.add_argument("--scope")
|
|
384
|
+
p.add_argument("--all", action="store_true", help="include questions a duty has taken")
|
|
385
|
+
p.set_defaults(fn=fn)
|
|
386
|
+
return ap
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def main(argv=None):
|
|
390
|
+
a = build_parser().parse_args(argv)
|
|
391
|
+
try:
|
|
392
|
+
a.fn(a)
|
|
393
|
+
except L.LimitExceeded as e:
|
|
394
|
+
# An operational refusal (SPEC §9), never a report: nothing was assessed.
|
|
395
|
+
print(f"warrant: RESOURCE_LIMIT {e}", file=sys.stderr)
|
|
396
|
+
return 3
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
if __name__ == "__main__":
|
|
401
|
+
sys.exit(main())
|