magicite 1.0.0__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.
- magicite/__init__.py +0 -0
- magicite/__main__.py +760 -0
- magicite/config.py +531 -0
- magicite/core/__init__.py +0 -0
- magicite/core/activation.py +310 -0
- magicite/core/approvals.py +685 -0
- magicite/core/audit.py +161 -0
- magicite/core/backup.py +1763 -0
- magicite/core/bundles.py +507 -0
- magicite/core/calibration.py +661 -0
- magicite/core/calibration_admission.py +227 -0
- magicite/core/candidates.py +610 -0
- magicite/core/communities.py +156 -0
- magicite/core/comparison_budget.py +63 -0
- magicite/core/composition.py +1270 -0
- magicite/core/context.py +168 -0
- magicite/core/custody_admin.py +310 -0
- magicite/core/decay.py +365 -0
- magicite/core/decay_math.py +31 -0
- magicite/core/distill.py +308 -0
- magicite/core/dream.py +1186 -0
- magicite/core/edge_weight.py +98 -0
- magicite/core/eligibility.py +858 -0
- magicite/core/evaluation_context.py +76 -0
- magicite/core/evidence.py +3167 -0
- magicite/core/fingerprint_key.py +145 -0
- magicite/core/fitness.py +159 -0
- magicite/core/index_generation.py +856 -0
- magicite/core/lifecycle.py +521 -0
- magicite/core/migration.py +1340 -0
- magicite/core/plasticity.py +164 -0
- magicite/core/policy_store.py +805 -0
- magicite/core/recovery_gate.py +319 -0
- magicite/core/registry.py +2011 -0
- magicite/core/router.py +2553 -0
- magicite/core/routing_policy.py +221 -0
- magicite/core/session.py +191 -0
- magicite/core/signals.py +338 -0
- magicite/core/trust.py +921 -0
- magicite/core/trust_artifacts.py +428 -0
- magicite/core/trust_custodian.py +822 -0
- magicite/core/trust_custodian_transport.py +548 -0
- magicite/core/trust_journal.py +593 -0
- magicite/core/trust_legacy.py +646 -0
- magicite/core/trust_reconciliation.py +166 -0
- magicite/core/trust_rotation.py +101 -0
- magicite/core/trust_rotation_client.py +101 -0
- magicite/core/trust_rotation_service.py +121 -0
- magicite/core/writer_guard.py +171 -0
- magicite/embeddings/__init__.py +61 -0
- magicite/embeddings/base.py +37 -0
- magicite/embeddings/cache.py +76 -0
- magicite/embeddings/fastembed_provider.py +206 -0
- magicite/embeddings/hashing_provider.py +75 -0
- magicite/embeddings/ollama_provider.py +98 -0
- magicite/embeddings/reranker.py +68 -0
- magicite/engram/__init__.py +106 -0
- magicite/engram/assets.py +210 -0
- magicite/engram/digests.py +93 -0
- magicite/engram/ids.py +79 -0
- magicite/engram/lint.py +220 -0
- magicite/engram/model.py +252 -0
- magicite/engram/model_v1.py +250 -0
- magicite/engram/parser.py +330 -0
- magicite/engram/schema/engram-0.2.schema.json +176 -0
- magicite/engram/schema/engram-1.0.schema.json +325 -0
- magicite/engram/schema_validate.py +252 -0
- magicite/engram/skillmd.py +383 -0
- magicite/engram/transform.py +257 -0
- magicite/engram/version_constraints.py +80 -0
- magicite/engram/writer.py +599 -0
- magicite/errors.py +115 -0
- magicite/eval/__init__.py +73 -0
- magicite/eval/__main__.py +619 -0
- magicite/eval/ablations.py +206 -0
- magicite/eval/bench.py +519 -0
- magicite/eval/calibration_consumer.py +745 -0
- magicite/eval/composition_eval.py +166 -0
- magicite/eval/data_readiness.py +644 -0
- magicite/eval/digests.py +48 -0
- magicite/eval/envelopes.py +421 -0
- magicite/eval/external.py +166 -0
- magicite/eval/gold.py +175 -0
- magicite/eval/grouped_evaluation.py +891 -0
- magicite/eval/host_tasks.py +161 -0
- magicite/eval/manifests.py +395 -0
- magicite/eval/metrics.py +462 -0
- magicite/eval/operator_cli.py +565 -0
- magicite/eval/production.py +353 -0
- magicite/eval/profiles.py +356 -0
- magicite/eval/protected_benchmark.py +870 -0
- magicite/eval/quality.py +249 -0
- magicite/eval/query_context.py +24 -0
- magicite/eval/runner.py +130 -0
- magicite/eval/scale.py +53 -0
- magicite/eval/skillret.py +444 -0
- magicite/eval/unevaluated.py +197 -0
- magicite/eval/validate.py +582 -0
- magicite/eval/verdicts.py +432 -0
- magicite/mcp/__init__.py +0 -0
- magicite/mcp/app.py +579 -0
- magicite/mcp/bind_dream.py +95 -0
- magicite/mcp/bind_inspect.py +117 -0
- magicite/mcp/bind_lifecycle.py +343 -0
- magicite/mcp/bind_ops.py +616 -0
- magicite/mcp/bind_registry.py +114 -0
- magicite/mcp/bind_retrieval.py +646 -0
- magicite/mcp/bind_signals.py +102 -0
- magicite/mcp/redact.py +80 -0
- magicite/mcp/registry.py +146 -0
- magicite/mcp/schemas.py +572 -0
- magicite/obs/__init__.py +0 -0
- magicite/obs/doctor.py +720 -0
- magicite/obs/events.py +112 -0
- magicite/obs/kpi.py +203 -0
- magicite/obs/logging.py +54 -0
- magicite/project_init.py +499 -0
- magicite/storage/__init__.py +0 -0
- magicite/storage/authorizer.py +141 -0
- magicite/storage/db.py +154 -0
- magicite/storage/durable.py +542 -0
- magicite/storage/ephemeral.py +544 -0
- magicite/storage/lease.py +593 -0
- magicite/storage/migration_ops.py +345 -0
- magicite/storage/migrations/001_init.sql +206 -0
- magicite/storage/migrations/002_integrity_recovery.sql +9 -0
- magicite/storage/migrations/003_migration_authority.sql +53 -0
- magicite/storage/migrations/004_fulltext_index.sql +37 -0
- magicite/storage/migrations/005_bundle_trust.sql +37 -0
- magicite/storage/migrations/006_evidence_ledger.sql +48 -0
- magicite/storage/migrations/registry.py +109 -0
- magicite/storage/queries.py +378 -0
- magicite-1.0.0.dist-info/METADATA +423 -0
- magicite-1.0.0.dist-info/RECORD +137 -0
- magicite-1.0.0.dist-info/WHEEL +4 -0
- magicite-1.0.0.dist-info/entry_points.txt +3 -0
- magicite-1.0.0.dist-info/licenses/LICENSE +202 -0
magicite/__main__.py
ADDED
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
"""``magicite`` click CLI: serve|sync|dream|export|tools|doctor|fetch-model
|
|
2
|
+
plus S11 operator surfaces: trust|policy|evidence|backup.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from typing import NoReturn, TextIO
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from magicite.config import Config
|
|
15
|
+
from magicite.errors import MagiciteError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _echo_json(payload: object) -> None:
|
|
19
|
+
click.echo(json.dumps(payload, indent=2, default=str))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _die_magicite(exc: MagiciteError) -> NoReturn:
|
|
23
|
+
from magicite.mcp.redact import redact_error_payload
|
|
24
|
+
|
|
25
|
+
payload = redact_error_payload(exc.to_dict(), strict=True)
|
|
26
|
+
payload["code"] = exc.code.value
|
|
27
|
+
payload["message"] = f"command failed ({exc.code.value})"
|
|
28
|
+
payload["hint"] = "inspect the error code and command help; correct input or reconcile state"
|
|
29
|
+
_echo_json(payload)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SafeGroup(click.Group):
|
|
34
|
+
def invoke(self, ctx: click.Context) -> object:
|
|
35
|
+
try:
|
|
36
|
+
return super().invoke(ctx)
|
|
37
|
+
except MagiciteError as exc:
|
|
38
|
+
if ctx.invoked_subcommand == "serve":
|
|
39
|
+
click.echo(f"server startup failed ({exc.code.value})", err=True)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
_die_magicite(exc)
|
|
42
|
+
except (click.ClickException, click.Abort, click.exceptions.Exit):
|
|
43
|
+
# click.exceptions.Exit (e.g. --help) subclasses RuntimeError; it is
|
|
44
|
+
# normal control flow, not an internal error.
|
|
45
|
+
raise
|
|
46
|
+
except Exception as exc:
|
|
47
|
+
if ctx.invoked_subcommand == "serve":
|
|
48
|
+
category = "PermissionError" if isinstance(exc, PermissionError) else "internal"
|
|
49
|
+
click.echo(f"server startup failed ({category})", err=True)
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
_echo_json(
|
|
52
|
+
{
|
|
53
|
+
"code": "internal",
|
|
54
|
+
"message": "internal command error",
|
|
55
|
+
"hint": "inspect local state with doctor; no exception content is disclosed",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.group(cls=SafeGroup)
|
|
62
|
+
@click.version_option(package_name="magicite")
|
|
63
|
+
def cli() -> None:
|
|
64
|
+
"""Magicite -- a local-first, plasticity-inspired skill router speaking MCP over stdio."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@cli.command()
|
|
68
|
+
@click.option("--project-root", default=".", show_default=True, help="Registry project root.")
|
|
69
|
+
def serve(project_root: str) -> None:
|
|
70
|
+
"""Boot the stdio MCP server (AC-001/AC-002)."""
|
|
71
|
+
from magicite.mcp.app import run_stdio
|
|
72
|
+
|
|
73
|
+
cfg = Config.load(project_root)
|
|
74
|
+
asyncio.run(run_stdio(cfg))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@cli.command(name="init")
|
|
78
|
+
@click.option("--host", required=True, type=click.Choice(["claude"]), help="Project MCP host.")
|
|
79
|
+
@click.option("--project-root", default=".", show_default=True, help="Existing enrolled project directory.")
|
|
80
|
+
@click.option("--skills", "skills_path", default=None, help="Optional project-contained skills directory.")
|
|
81
|
+
@click.option("--fetch-model", is_flag=True, help="Explicit model download consent; never skill approval.")
|
|
82
|
+
def init_cmd(host: str, project_root: str, skills_path: str | None, fetch_model: bool) -> None:
|
|
83
|
+
"""Connect Claude Code after protected custody Setup; optional explicit skill review."""
|
|
84
|
+
from magicite.project_init import run_init
|
|
85
|
+
|
|
86
|
+
run_init(project_root, fetch=fetch_model, skills_path=skills_path)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@cli.command(name="tools")
|
|
90
|
+
def tools_cmd() -> None:
|
|
91
|
+
"""Print the authoritative 16-tool manifest as JSON (AC-003/AC-004)."""
|
|
92
|
+
# Importing mcp.app registers every bind_*.py tool onto TOOL_REGISTRY.
|
|
93
|
+
from magicite.mcp import app as _app # noqa: F401
|
|
94
|
+
from magicite.mcp.registry import manifest
|
|
95
|
+
|
|
96
|
+
click.echo(json.dumps({"tools": manifest()}, indent=2))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@cli.command(name="sync")
|
|
100
|
+
@click.option("--project-root", default=".", show_default=True, help="Registry project root.")
|
|
101
|
+
def sync_cmd(project_root: str) -> None:
|
|
102
|
+
"""Rebuild the durable index from the .egr.md registry (spec §2.6)."""
|
|
103
|
+
from dataclasses import asdict
|
|
104
|
+
|
|
105
|
+
from magicite.core import registry as registry_mod
|
|
106
|
+
from magicite.embeddings import get_embedder
|
|
107
|
+
from magicite.storage import db as db_mod
|
|
108
|
+
|
|
109
|
+
cfg = Config.load(project_root)
|
|
110
|
+
from magicite.core.writer_guard import preflight_custody
|
|
111
|
+
|
|
112
|
+
preflight_custody(cfg)
|
|
113
|
+
cfg.ensure_dirs()
|
|
114
|
+
conn = db_mod.connect(cfg.db_path)
|
|
115
|
+
embedder = get_embedder(cfg)
|
|
116
|
+
outcome = registry_mod.sync(cfg, conn, embedder)
|
|
117
|
+
click.echo(json.dumps(asdict(outcome), indent=2, default=str))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@cli.command(name="dream")
|
|
121
|
+
@click.option("--once", is_flag=True, default=False)
|
|
122
|
+
@click.option("--autonomous", is_flag=True, default=False)
|
|
123
|
+
@click.option("--project-root", default=".", show_default=True, help="Registry project root.")
|
|
124
|
+
def dream_cmd(once: bool, autonomous: bool, project_root: str) -> None:
|
|
125
|
+
"""Run the Dream consolidation worker inline (spec §4.1)."""
|
|
126
|
+
import json as _json
|
|
127
|
+
|
|
128
|
+
from magicite.core import dream as dream_mod
|
|
129
|
+
from magicite.storage import authorizer as authorizer_mod
|
|
130
|
+
|
|
131
|
+
if not once:
|
|
132
|
+
raise click.ClickException("magicite dream currently only supports --once (spec §4.1 v1 scope)")
|
|
133
|
+
|
|
134
|
+
cfg = Config.load(project_root)
|
|
135
|
+
if autonomous:
|
|
136
|
+
cfg.autonomous = True
|
|
137
|
+
from magicite.core.writer_guard import preflight_custody
|
|
138
|
+
|
|
139
|
+
preflight_custody(cfg)
|
|
140
|
+
cfg.ensure_dirs()
|
|
141
|
+
conn = authorizer_mod.writer_connection(cfg.db_path)
|
|
142
|
+
result = dream_mod.run(cfg, conn, trigger="cli")
|
|
143
|
+
click.echo(
|
|
144
|
+
_json.dumps(
|
|
145
|
+
{
|
|
146
|
+
"run_id": result.run_id,
|
|
147
|
+
"state": result.state,
|
|
148
|
+
"checkpoint_write_ratio": result.checkpoint_write_ratio,
|
|
149
|
+
"modified_engrams": result.modified_engrams,
|
|
150
|
+
"archived_engrams": result.archived_engrams,
|
|
151
|
+
"stats": result.stats,
|
|
152
|
+
},
|
|
153
|
+
indent=2,
|
|
154
|
+
default=str,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@cli.command(name="export")
|
|
160
|
+
@click.option("--out-dir", required=True)
|
|
161
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
162
|
+
@click.option(
|
|
163
|
+
"--min-status",
|
|
164
|
+
default="consolidated",
|
|
165
|
+
type=click.Choice(["consolidated", "promoted"]),
|
|
166
|
+
show_default=True,
|
|
167
|
+
)
|
|
168
|
+
def export_cmd(out_dir: str, project_root: str, min_status: str) -> None:
|
|
169
|
+
"""Render SKILL.md shims from consolidated+ engrams (spec §5.4)."""
|
|
170
|
+
from dataclasses import asdict
|
|
171
|
+
|
|
172
|
+
from magicite.core import registry as registry_mod
|
|
173
|
+
from magicite.storage import db as db_mod
|
|
174
|
+
|
|
175
|
+
cfg = Config.load(project_root)
|
|
176
|
+
from magicite.core.writer_guard import preflight_custody
|
|
177
|
+
|
|
178
|
+
preflight_custody(cfg)
|
|
179
|
+
cfg.ensure_dirs()
|
|
180
|
+
conn = db_mod.connect(cfg.db_path)
|
|
181
|
+
outcome = registry_mod.export(cfg, conn, out_dir=out_dir, min_status=min_status)
|
|
182
|
+
click.echo(json.dumps(asdict(outcome), indent=2, default=str))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@cli.command(name="doctor")
|
|
186
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
187
|
+
def doctor_cmd(project_root: str) -> None:
|
|
188
|
+
"""Diagnose the registry/filesystem/embedding-provider setup (doctor/1)."""
|
|
189
|
+
from magicite.mcp import bind_ops
|
|
190
|
+
|
|
191
|
+
report = bind_ops.doctor_report(project_root)
|
|
192
|
+
click.echo(json.dumps(report, indent=2, default=str))
|
|
193
|
+
if not report["healthy"]:
|
|
194
|
+
for warning in report["warnings"]:
|
|
195
|
+
click.echo(f"WARNING: {warning}", err=True)
|
|
196
|
+
raise SystemExit(1)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@cli.command(name="fetch-model")
|
|
200
|
+
@click.option("--model-name", default=None, help="Override the default fastembed model name.")
|
|
201
|
+
def fetch_model_cmd(model_name: str | None) -> None:
|
|
202
|
+
"""Pre-download the default ONNX embedding model for offline use."""
|
|
203
|
+
from magicite.embeddings.fastembed_provider import DEFAULT_MODEL_NAME, fetch_model
|
|
204
|
+
|
|
205
|
+
resolved = model_name or DEFAULT_MODEL_NAME
|
|
206
|
+
click.echo(f"fetching {resolved!r} ...")
|
|
207
|
+
fetch_model(model_name=resolved)
|
|
208
|
+
click.echo(json.dumps({"fetched": resolved}, indent=2))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ── S11: trust ──────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@cli.group(name="trust")
|
|
215
|
+
def trust_group() -> None:
|
|
216
|
+
"""Bundle trust and local review (C10). Requires explicit operator actor."""
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@trust_group.command(name="list")
|
|
220
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
221
|
+
def trust_list_cmd(project_root: str) -> None:
|
|
222
|
+
from magicite.mcp import bind_ops
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
_echo_json({"decisions": bind_ops.trust_list(project_root)})
|
|
226
|
+
except MagiciteError as exc:
|
|
227
|
+
_die_magicite(exc)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@trust_group.command(name="review")
|
|
231
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
232
|
+
@click.option("--engram-id", required=True)
|
|
233
|
+
def trust_review_cmd(project_root: str, engram_id: str) -> None:
|
|
234
|
+
from magicite.mcp import bind_ops
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
_echo_json(bind_ops.trust_review(project_root, engram_id=engram_id))
|
|
238
|
+
except MagiciteError as exc:
|
|
239
|
+
_die_magicite(exc)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@trust_group.command(name="approve")
|
|
243
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
244
|
+
@click.option("--engram-id", required=True)
|
|
245
|
+
@click.option("--expected-digest", required=True)
|
|
246
|
+
@click.option("--actor", required=True, help="Explicit operator identity (required).")
|
|
247
|
+
@click.option("--reason", default=None)
|
|
248
|
+
@click.option("--event-id", default=None)
|
|
249
|
+
def trust_approve_cmd(
|
|
250
|
+
project_root: str,
|
|
251
|
+
engram_id: str,
|
|
252
|
+
expected_digest: str,
|
|
253
|
+
actor: str,
|
|
254
|
+
reason: str | None,
|
|
255
|
+
event_id: str | None,
|
|
256
|
+
) -> None:
|
|
257
|
+
from magicite.mcp import bind_ops
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
_echo_json(
|
|
261
|
+
bind_ops.trust_approve(
|
|
262
|
+
project_root,
|
|
263
|
+
engram_id=engram_id,
|
|
264
|
+
expected_digest=expected_digest,
|
|
265
|
+
actor=actor,
|
|
266
|
+
reason=reason,
|
|
267
|
+
event_id=event_id,
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
except MagiciteError as exc:
|
|
271
|
+
_die_magicite(exc)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@trust_group.command(name="reject")
|
|
275
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
276
|
+
@click.option("--engram-id", required=True)
|
|
277
|
+
@click.option("--expected-digest", required=True)
|
|
278
|
+
@click.option("--actor", required=True)
|
|
279
|
+
@click.option("--reason", default=None)
|
|
280
|
+
@click.option("--event-id", default=None)
|
|
281
|
+
def trust_reject_cmd(
|
|
282
|
+
project_root: str,
|
|
283
|
+
engram_id: str,
|
|
284
|
+
expected_digest: str,
|
|
285
|
+
actor: str,
|
|
286
|
+
reason: str | None,
|
|
287
|
+
event_id: str | None,
|
|
288
|
+
) -> None:
|
|
289
|
+
from magicite.mcp import bind_ops
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
_echo_json(
|
|
293
|
+
bind_ops.trust_reject(
|
|
294
|
+
project_root,
|
|
295
|
+
engram_id=engram_id,
|
|
296
|
+
expected_digest=expected_digest,
|
|
297
|
+
actor=actor,
|
|
298
|
+
reason=reason,
|
|
299
|
+
event_id=event_id,
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
except MagiciteError as exc:
|
|
303
|
+
_die_magicite(exc)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@trust_group.command(name="revoke")
|
|
307
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
308
|
+
@click.option("--engram-id", required=True)
|
|
309
|
+
@click.option("--actor", required=True)
|
|
310
|
+
@click.option("--expected-digest", default=None)
|
|
311
|
+
@click.option("--reason", default=None)
|
|
312
|
+
@click.option("--event-id", default=None)
|
|
313
|
+
def trust_revoke_cmd(
|
|
314
|
+
project_root: str,
|
|
315
|
+
engram_id: str,
|
|
316
|
+
actor: str,
|
|
317
|
+
expected_digest: str | None,
|
|
318
|
+
reason: str | None,
|
|
319
|
+
event_id: str | None,
|
|
320
|
+
) -> None:
|
|
321
|
+
from magicite.mcp import bind_ops
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
_echo_json(
|
|
325
|
+
bind_ops.trust_revoke(
|
|
326
|
+
project_root,
|
|
327
|
+
engram_id=engram_id,
|
|
328
|
+
actor=actor,
|
|
329
|
+
expected_digest=expected_digest,
|
|
330
|
+
reason=reason,
|
|
331
|
+
event_id=event_id,
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
except MagiciteError as exc:
|
|
335
|
+
_die_magicite(exc)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@trust_group.command(name="import-bundle")
|
|
339
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
340
|
+
@click.option("--archive", "archive_path", required=True, type=click.Path(exists=True))
|
|
341
|
+
@click.option("--actor", required=True)
|
|
342
|
+
def trust_import_bundle_cmd(project_root: str, archive_path: str, actor: str) -> None:
|
|
343
|
+
from magicite.mcp import bind_ops
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
_echo_json(bind_ops.trust_import_bundle(project_root, archive_path=archive_path, actor=actor))
|
|
347
|
+
except MagiciteError as exc:
|
|
348
|
+
_die_magicite(exc)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# ── S11: policy ─────────────────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@cli.group(name="policy")
|
|
355
|
+
def policy_group() -> None:
|
|
356
|
+
"""Reviewed policy activation/rollback (compare-and-swap)."""
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@policy_group.command(name="status")
|
|
360
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
361
|
+
def policy_status_cmd(project_root: str) -> None:
|
|
362
|
+
from magicite.mcp import bind_ops
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
_echo_json(bind_ops.policy_status(project_root))
|
|
366
|
+
except MagiciteError as exc:
|
|
367
|
+
_die_magicite(exc)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@policy_group.command(name="reconcile")
|
|
371
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
372
|
+
def policy_reconcile_cmd(project_root: str) -> None:
|
|
373
|
+
"""Finish an authenticated interrupted policy commit under the writer lease."""
|
|
374
|
+
from magicite.mcp import bind_ops
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
_echo_json(bind_ops.policy_reconcile(project_root))
|
|
378
|
+
except MagiciteError as exc:
|
|
379
|
+
_die_magicite(exc)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@policy_group.command(name="activate")
|
|
383
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
384
|
+
@click.option("--candidate-digest", required=True)
|
|
385
|
+
@click.option("--approval-id", required=True)
|
|
386
|
+
@click.option(
|
|
387
|
+
"--expected-current",
|
|
388
|
+
default=None,
|
|
389
|
+
help="Compare-and-swap guard; omit/empty when no incumbent.",
|
|
390
|
+
)
|
|
391
|
+
def policy_activate_cmd(
|
|
392
|
+
project_root: str,
|
|
393
|
+
candidate_digest: str,
|
|
394
|
+
approval_id: str,
|
|
395
|
+
expected_current: str | None,
|
|
396
|
+
) -> None:
|
|
397
|
+
from magicite.mcp import bind_ops
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
_echo_json(
|
|
401
|
+
bind_ops.policy_activate(
|
|
402
|
+
project_root,
|
|
403
|
+
candidate_digest=candidate_digest,
|
|
404
|
+
approval_id=approval_id,
|
|
405
|
+
expected_current=expected_current or None,
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
except MagiciteError as exc:
|
|
409
|
+
_die_magicite(exc)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@policy_group.command(name="rollback")
|
|
413
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
414
|
+
@click.option("--prior-digest", required=True)
|
|
415
|
+
@click.option("--expected-current", default=None)
|
|
416
|
+
def policy_rollback_cmd(project_root: str, prior_digest: str, expected_current: str | None) -> None:
|
|
417
|
+
from magicite.mcp import bind_ops
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
_echo_json(
|
|
421
|
+
bind_ops.policy_rollback(
|
|
422
|
+
project_root,
|
|
423
|
+
prior_digest=prior_digest,
|
|
424
|
+
expected_current=expected_current or None,
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
except MagiciteError as exc:
|
|
428
|
+
_die_magicite(exc)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@policy_group.command(name="register-evaluated")
|
|
432
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
433
|
+
@click.option("--manifest", "manifest_path", required=True, type=click.Path(exists=True))
|
|
434
|
+
@click.option(
|
|
435
|
+
"--evaluation-status", required=True, type=click.Choice(["pass", "fail", "inconclusive", "unevaluated"])
|
|
436
|
+
)
|
|
437
|
+
@click.option("--evidence", required=True)
|
|
438
|
+
def policy_register_evaluated_cmd(
|
|
439
|
+
project_root: str, manifest_path: str, evaluation_status: str, evidence: str
|
|
440
|
+
) -> None:
|
|
441
|
+
from magicite.mcp import bind_ops
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
_echo_json(
|
|
445
|
+
bind_ops.policy_register_evaluated(
|
|
446
|
+
project_root,
|
|
447
|
+
manifest_path=manifest_path,
|
|
448
|
+
evaluation_status=evaluation_status,
|
|
449
|
+
evidence=evidence,
|
|
450
|
+
)
|
|
451
|
+
)
|
|
452
|
+
except MagiciteError as exc:
|
|
453
|
+
_die_magicite(exc)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@policy_group.command(name="admit-calibration")
|
|
457
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
458
|
+
@click.option("--artifact", "artifact_path", required=True, type=click.Path(exists=True))
|
|
459
|
+
@click.option("--evidence", "evidence_path", required=True, type=click.Path(exists=True))
|
|
460
|
+
@click.option("--actor", required=True)
|
|
461
|
+
@click.option(
|
|
462
|
+
"--reviewed-sha256", default=None, help="Exact complete bundle digest; omitted gives read-only review."
|
|
463
|
+
)
|
|
464
|
+
def policy_admit_calibration_cmd(
|
|
465
|
+
project_root: str, artifact_path: str, evidence_path: str, actor: str, reviewed_sha256: str | None
|
|
466
|
+
) -> None:
|
|
467
|
+
"""Review or admit protected calibration; approve and activate separately."""
|
|
468
|
+
from magicite.mcp import bind_ops
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
_echo_json(
|
|
472
|
+
bind_ops.policy_admit_calibration(
|
|
473
|
+
project_root,
|
|
474
|
+
artifact_path=artifact_path,
|
|
475
|
+
evidence_path=evidence_path,
|
|
476
|
+
actor=actor,
|
|
477
|
+
reviewed_sha256=reviewed_sha256,
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
except MagiciteError as exc:
|
|
481
|
+
_die_magicite(exc)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@policy_group.command(name="approve")
|
|
485
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
486
|
+
@click.option("--policy-digest", required=True)
|
|
487
|
+
@click.option("--actor", required=True)
|
|
488
|
+
def policy_approve_cmd(project_root: str, policy_digest: str, actor: str) -> None:
|
|
489
|
+
from magicite.mcp import bind_ops
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
_echo_json(bind_ops.policy_approve(project_root, policy_digest=policy_digest, actor=actor))
|
|
493
|
+
except MagiciteError as exc:
|
|
494
|
+
_die_magicite(exc)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
@cli.group(name="migration")
|
|
498
|
+
def migration_group() -> None:
|
|
499
|
+
"""Preview, apply, inspect, resume or restore a fenced format migration."""
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@migration_group.command(name="preview")
|
|
503
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
504
|
+
def migration_preview_cmd(project_root: str) -> None:
|
|
505
|
+
from magicite.mcp import bind_ops
|
|
506
|
+
|
|
507
|
+
try:
|
|
508
|
+
_echo_json(bind_ops.migration_preview(project_root))
|
|
509
|
+
except MagiciteError as exc:
|
|
510
|
+
_die_magicite(exc)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
@migration_group.command(name="apply")
|
|
514
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
515
|
+
@click.option("--operation-id", default=None)
|
|
516
|
+
@click.option("--reviewed-sha256", default=None)
|
|
517
|
+
@click.option("--backup-path", default=None, type=click.Path(exists=True))
|
|
518
|
+
def migration_apply_cmd(
|
|
519
|
+
project_root: str, operation_id: str | None, reviewed_sha256: str | None, backup_path: str | None
|
|
520
|
+
) -> None:
|
|
521
|
+
from magicite.mcp import bind_ops
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
_echo_json(
|
|
525
|
+
bind_ops.migration_apply(
|
|
526
|
+
project_root,
|
|
527
|
+
operation_id=operation_id,
|
|
528
|
+
reviewed_sha256=reviewed_sha256,
|
|
529
|
+
backup_path=backup_path,
|
|
530
|
+
)
|
|
531
|
+
)
|
|
532
|
+
except MagiciteError as exc:
|
|
533
|
+
_die_magicite(exc)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@migration_group.command(name="status")
|
|
537
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
538
|
+
@click.option("--operation-id", required=True)
|
|
539
|
+
def migration_status_cmd(project_root: str, operation_id: str) -> None:
|
|
540
|
+
from magicite.mcp import bind_ops
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
_echo_json(bind_ops.migration_status(project_root, operation_id=operation_id))
|
|
544
|
+
except MagiciteError as exc:
|
|
545
|
+
_die_magicite(exc)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
@migration_group.command(name="resume")
|
|
549
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
550
|
+
@click.option("--operation-id", required=True)
|
|
551
|
+
@click.option("--reviewed-sha256", default=None)
|
|
552
|
+
@click.option("--backup-path", default=None, type=click.Path(exists=True))
|
|
553
|
+
def migration_resume_cmd(
|
|
554
|
+
project_root: str, operation_id: str, reviewed_sha256: str | None, backup_path: str | None
|
|
555
|
+
) -> None:
|
|
556
|
+
from magicite.mcp import bind_ops
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
_echo_json(
|
|
560
|
+
bind_ops.migration_resume(
|
|
561
|
+
project_root,
|
|
562
|
+
operation_id=operation_id,
|
|
563
|
+
reviewed_sha256=reviewed_sha256,
|
|
564
|
+
backup_path=backup_path,
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
except MagiciteError as exc:
|
|
568
|
+
_die_magicite(exc)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@migration_group.command(name="restore")
|
|
572
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
573
|
+
@click.option("--backup-path", required=True, type=click.Path(exists=True))
|
|
574
|
+
@click.option("--staging-path", default=None, type=click.Path())
|
|
575
|
+
@click.option("--reviewed-sha256", default=None)
|
|
576
|
+
def migration_restore_cmd(
|
|
577
|
+
project_root: str, backup_path: str, staging_path: str | None, reviewed_sha256: str | None
|
|
578
|
+
) -> None:
|
|
579
|
+
from magicite.mcp import bind_ops
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
_echo_json(
|
|
583
|
+
bind_ops.migration_restore(
|
|
584
|
+
project_root,
|
|
585
|
+
backup_path=backup_path,
|
|
586
|
+
staging_path=staging_path,
|
|
587
|
+
reviewed_sha256=reviewed_sha256,
|
|
588
|
+
)
|
|
589
|
+
)
|
|
590
|
+
except MagiciteError as exc:
|
|
591
|
+
_die_magicite(exc)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
# ── S11: evidence ───────────────────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@cli.group(name="evidence")
|
|
598
|
+
def evidence_group() -> None:
|
|
599
|
+
"""Evidence export, erasure, and retention (C6)."""
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
@evidence_group.command(name="checkpoint")
|
|
603
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
604
|
+
@click.option("--decision-event-id", default=None)
|
|
605
|
+
@click.option(
|
|
606
|
+
"--route-request",
|
|
607
|
+
type=click.File("r"),
|
|
608
|
+
default=None,
|
|
609
|
+
help="Explicit new route and checkpoint JSON file; use - for stdin.",
|
|
610
|
+
)
|
|
611
|
+
@click.option("--event-id", required=True, help="Stable idempotency identity for retries.")
|
|
612
|
+
@click.option("--outcome", default=None, type=click.Choice(["success", "failure", "unknown"]))
|
|
613
|
+
def evidence_checkpoint_cmd(
|
|
614
|
+
project_root: str,
|
|
615
|
+
decision_event_id: str | None,
|
|
616
|
+
route_request: TextIO | None,
|
|
617
|
+
event_id: str,
|
|
618
|
+
outcome: str | None,
|
|
619
|
+
) -> None:
|
|
620
|
+
"""Explicit new decision checkpoint, or linked operator outcome self-report."""
|
|
621
|
+
from magicite.mcp import bind_ops
|
|
622
|
+
|
|
623
|
+
if route_request is not None:
|
|
624
|
+
if decision_event_id is not None or outcome is not None:
|
|
625
|
+
raise click.UsageError("route-request cannot be combined with outcome/decision-event-id")
|
|
626
|
+
_echo_json(
|
|
627
|
+
bind_ops.evidence_route_checkpoint(
|
|
628
|
+
project_root, request=json.load(route_request), event_id=event_id
|
|
629
|
+
)
|
|
630
|
+
)
|
|
631
|
+
else:
|
|
632
|
+
if decision_event_id is None or outcome is None:
|
|
633
|
+
raise click.UsageError("provide route-request, or both decision-event-id and outcome")
|
|
634
|
+
_echo_json(
|
|
635
|
+
bind_ops.evidence_checkpoint(
|
|
636
|
+
project_root, decision_event_id=decision_event_id, event_id=event_id, outcome=outcome
|
|
637
|
+
)
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
@evidence_group.command(name="export")
|
|
642
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
643
|
+
@click.option("--export-dir", default=None, type=click.Path())
|
|
644
|
+
@click.option("--event-id", "event_ids", multiple=True)
|
|
645
|
+
def evidence_export_cmd(project_root: str, export_dir: str | None, event_ids: tuple[str, ...]) -> None:
|
|
646
|
+
from magicite.mcp import bind_ops
|
|
647
|
+
|
|
648
|
+
try:
|
|
649
|
+
_echo_json(
|
|
650
|
+
bind_ops.evidence_export(
|
|
651
|
+
project_root,
|
|
652
|
+
export_dir=export_dir,
|
|
653
|
+
event_ids=list(event_ids) or None,
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
except MagiciteError as exc:
|
|
657
|
+
_die_magicite(exc)
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
@evidence_group.command(name="delete")
|
|
661
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
662
|
+
@click.option("--event-id", required=True)
|
|
663
|
+
@click.option("--actor", required=True)
|
|
664
|
+
@click.option("--reason", default=None)
|
|
665
|
+
def evidence_delete_cmd(project_root: str, event_id: str, actor: str, reason: str | None) -> None:
|
|
666
|
+
from magicite.mcp import bind_ops
|
|
667
|
+
|
|
668
|
+
try:
|
|
669
|
+
_echo_json(bind_ops.evidence_delete(project_root, event_id=event_id, actor=actor, reason=reason))
|
|
670
|
+
except MagiciteError as exc:
|
|
671
|
+
_die_magicite(exc)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@evidence_group.command(name="retention-status")
|
|
675
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
676
|
+
def evidence_retention_status_cmd(project_root: str) -> None:
|
|
677
|
+
from magicite.mcp import bind_ops
|
|
678
|
+
|
|
679
|
+
try:
|
|
680
|
+
_echo_json(bind_ops.evidence_retention_status(project_root))
|
|
681
|
+
except MagiciteError as exc:
|
|
682
|
+
_die_magicite(exc)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
@evidence_group.command(name="apply-retention")
|
|
686
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
687
|
+
def evidence_apply_retention_cmd(project_root: str) -> None:
|
|
688
|
+
from magicite.mcp import bind_ops
|
|
689
|
+
|
|
690
|
+
try:
|
|
691
|
+
_echo_json(bind_ops.evidence_apply_retention(project_root))
|
|
692
|
+
except MagiciteError as exc:
|
|
693
|
+
_die_magicite(exc)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
# ── S11: backup ─────────────────────────────────────────────────────────
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
@cli.group(name="backup")
|
|
700
|
+
def backup_group() -> None:
|
|
701
|
+
"""Backup create/restore/status (C8)."""
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
@backup_group.command(name="create")
|
|
705
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
706
|
+
@click.option("--dest", required=True, type=click.Path())
|
|
707
|
+
def backup_create_cmd(project_root: str, dest: str) -> None:
|
|
708
|
+
from magicite.mcp import bind_ops
|
|
709
|
+
|
|
710
|
+
try:
|
|
711
|
+
_echo_json(bind_ops.backup_create(project_root, dest=dest))
|
|
712
|
+
except MagiciteError as exc:
|
|
713
|
+
_die_magicite(exc)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@backup_group.command(name="restore")
|
|
717
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
718
|
+
@click.option("--backup-path", required=True, type=click.Path(exists=True))
|
|
719
|
+
@click.option("--overlay", "overlay_path", default=None, type=click.Path(exists=True))
|
|
720
|
+
@click.option("--anchor", "anchor_path", default=None, type=click.Path(exists=True))
|
|
721
|
+
def backup_restore_cmd(
|
|
722
|
+
project_root: str,
|
|
723
|
+
backup_path: str,
|
|
724
|
+
overlay_path: str | None,
|
|
725
|
+
anchor_path: str | None,
|
|
726
|
+
) -> None:
|
|
727
|
+
from magicite.mcp import bind_ops
|
|
728
|
+
|
|
729
|
+
try:
|
|
730
|
+
_echo_json(
|
|
731
|
+
bind_ops.backup_restore(
|
|
732
|
+
project_root,
|
|
733
|
+
backup_path=backup_path,
|
|
734
|
+
overlay_path=overlay_path,
|
|
735
|
+
anchor_path=anchor_path,
|
|
736
|
+
)
|
|
737
|
+
)
|
|
738
|
+
except MagiciteError as exc:
|
|
739
|
+
_die_magicite(exc)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
@backup_group.command(name="status")
|
|
743
|
+
@click.option("--project-root", default=".", show_default=True)
|
|
744
|
+
@click.option("--backup-path", default=None, type=click.Path())
|
|
745
|
+
def backup_status_cmd(project_root: str, backup_path: str | None) -> None:
|
|
746
|
+
from magicite.mcp import bind_ops
|
|
747
|
+
|
|
748
|
+
try:
|
|
749
|
+
_echo_json(bind_ops.backup_status(project_root, backup_path=backup_path))
|
|
750
|
+
except MagiciteError as exc:
|
|
751
|
+
_die_magicite(exc)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
from magicite.core.custody_admin import custody_cli # noqa: E402
|
|
755
|
+
|
|
756
|
+
cli.add_command(custody_cli)
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
if __name__ == "__main__":
|
|
760
|
+
cli()
|