@meetless/mla 0.1.4
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/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Plain-text ReviewPacket renderer per §5.3 + §6.
|
|
3
|
+
// Used by `mla review [--plain]`. ANSI colors stripped when --plain.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.renderPacket = renderPacket;
|
|
6
|
+
exports.renderCaseRow = renderCaseRow;
|
|
7
|
+
exports.fmtCaseShow = fmtCaseShow;
|
|
8
|
+
exports.renderKbAddReceipt = renderKbAddReceipt;
|
|
9
|
+
exports.renderKbShow = renderKbShow;
|
|
10
|
+
exports.renderKbForgetReceipt = renderKbForgetReceipt;
|
|
11
|
+
exports.renderKbReingestReceipt = renderKbReingestReceipt;
|
|
12
|
+
exports.renderKbPurgeReceipt = renderKbPurgeReceipt;
|
|
13
|
+
const temporal_1 = require("./temporal");
|
|
14
|
+
function fmtEvidenceRef(ev) {
|
|
15
|
+
if (typeof ev !== "string")
|
|
16
|
+
return null;
|
|
17
|
+
const s = ev.trim();
|
|
18
|
+
return s.length ? s : null;
|
|
19
|
+
}
|
|
20
|
+
function fmtList(items) {
|
|
21
|
+
if (!items || items.length === 0)
|
|
22
|
+
return " (none)";
|
|
23
|
+
return items.map((i) => ` - ${i}`).join("\n");
|
|
24
|
+
}
|
|
25
|
+
function fmtFacts(facts) {
|
|
26
|
+
if (!facts)
|
|
27
|
+
return " (deterministic facts not yet populated)";
|
|
28
|
+
const lines = [];
|
|
29
|
+
if (facts.branch)
|
|
30
|
+
lines.push(` branch: ${facts.branch}`);
|
|
31
|
+
if (facts.lastCommit)
|
|
32
|
+
lines.push(` last commit: ${facts.lastCommit}`);
|
|
33
|
+
const ds = facts.diffStat;
|
|
34
|
+
if (ds) {
|
|
35
|
+
lines.push(` diffStat: files=${ds.filesChanged ?? 0} +${ds.insertions ?? 0} -${ds.deletions ?? 0}`);
|
|
36
|
+
}
|
|
37
|
+
const changed = facts.changedFiles;
|
|
38
|
+
if (changed && changed.length > 0) {
|
|
39
|
+
lines.push(` changed files (${changed.length}):`);
|
|
40
|
+
for (const f of changed.slice(0, 50))
|
|
41
|
+
lines.push(` - ${f}`);
|
|
42
|
+
if (changed.length > 50)
|
|
43
|
+
lines.push(` ... +${changed.length - 50} more`);
|
|
44
|
+
}
|
|
45
|
+
const cls = facts.classifications;
|
|
46
|
+
if (cls) {
|
|
47
|
+
const counts = {};
|
|
48
|
+
for (const c of Object.values(cls))
|
|
49
|
+
counts[c] = (counts[c] ?? 0) + 1;
|
|
50
|
+
const summary = Object.entries(counts)
|
|
51
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
52
|
+
.join(", ");
|
|
53
|
+
if (summary)
|
|
54
|
+
lines.push(` classifications: ${summary}`);
|
|
55
|
+
}
|
|
56
|
+
return lines.length ? lines.join("\n") : " (empty)";
|
|
57
|
+
}
|
|
58
|
+
function fmtBashEvents(events) {
|
|
59
|
+
if (!events || events.length === 0)
|
|
60
|
+
return " (none observed)";
|
|
61
|
+
const lines = [];
|
|
62
|
+
for (const e of events.slice(0, 30)) {
|
|
63
|
+
const ev = e;
|
|
64
|
+
const cmd = (ev.command || "").replace(/\s+/g, " ").slice(0, 120);
|
|
65
|
+
lines.push(` [${ev.category || "unknown"}] exit=${ev.exitCode ?? "?"} ${cmd}`);
|
|
66
|
+
}
|
|
67
|
+
if (events.length > 30)
|
|
68
|
+
lines.push(` ... +${events.length - 30} more`);
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
// Capture-count rollup. This is the §10 Phase 1 "print capture counts" block:
|
|
72
|
+
// the deterministic shape of what the run captured, NOT a verdict on it. Phase 1
|
|
73
|
+
// has no claim/decision/contradiction extraction (that lands with the per-turn
|
|
74
|
+
// extraction substrate, RCA Phase 3), so we count only what is captured
|
|
75
|
+
// deterministically: changed files, observed bash events, missing-evidence
|
|
76
|
+
// flags, and whether the agent left a verbatim self-report.
|
|
77
|
+
function fmtCaptureSummary(packet) {
|
|
78
|
+
const ds = (packet.facts?.diffStat ?? {});
|
|
79
|
+
const changedFiles = packet.facts?.changedFiles?.length ?? ds.filesChanged ?? 0;
|
|
80
|
+
const bashCount = packet.bashEvents?.length ?? 0;
|
|
81
|
+
const missingCount = packet.missingEvidence?.length ?? 0;
|
|
82
|
+
const selfReport = !!(packet.agentClaimsRaw && packet.agentClaimsRaw.trim().length > 0);
|
|
83
|
+
const lines = [
|
|
84
|
+
` changed files: ${changedFiles}`,
|
|
85
|
+
` observed bash events: ${bashCount}`,
|
|
86
|
+
` missing-evidence flags: ${missingCount}`,
|
|
87
|
+
` agent self-report: ${selfReport ? "captured (unverified)" : "none"}`,
|
|
88
|
+
];
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
// INV-T3 / §5.3 / §5.4: this surface renders NO verdict, recommendation, or
|
|
92
|
+
// approve/reject for an agent-session capture. A captured run is a deterministic
|
|
93
|
+
// ledger of what the agent did and self-reported, never an independent review.
|
|
94
|
+
// The renderer DELIBERATELY ignores any recommendation / verification / risks /
|
|
95
|
+
// summary / LLM-trace fields the wire may still carry (e.g. a packet written by
|
|
96
|
+
// an older control build), so the false-authority line cannot reappear by data
|
|
97
|
+
// alone. The reviewable artifacts (relationship candidates, claims) live in the
|
|
98
|
+
// Console queues; this block is a convenience snapshot of capture, not a sign-off.
|
|
99
|
+
function renderPacket(packet) {
|
|
100
|
+
const out = [];
|
|
101
|
+
const isStale = typeof packet.staleEventCount === "number" && packet.staleEventCount > 0;
|
|
102
|
+
out.push(`╭─ Meetless Capture Ledger ────────────────────────────────────────────╮`);
|
|
103
|
+
out.push(` packet id: ${packet.id}`);
|
|
104
|
+
out.push(` run id: ${packet.runId}`);
|
|
105
|
+
out.push(` status: ${packet.status}`);
|
|
106
|
+
out.push(` note: this is a capture ledger, not a sign-off. No verdict is rendered here.`);
|
|
107
|
+
out.push(`────────────────────────────────────────────────────────────────────────`);
|
|
108
|
+
if (isStale) {
|
|
109
|
+
const n = packet.staleEventCount;
|
|
110
|
+
const since = packet.staleSince ? ` (${packet.staleSince})` : "";
|
|
111
|
+
const latest = packet.latestEventAt ? `; latest at ${packet.latestEventAt}` : "";
|
|
112
|
+
out.push(``);
|
|
113
|
+
out.push(`⚠ STALE LEDGER`);
|
|
114
|
+
out.push(` ${n} event(s) were recorded on this run AFTER this ledger was built${since}${latest}.`);
|
|
115
|
+
out.push(` This run is still live (one long-lived / continued session). The facts`);
|
|
116
|
+
out.push(` below reflect the session at build time and may be incomplete for the work since.`);
|
|
117
|
+
out.push(` Regenerate: end the session (SessionEnd) to finalize a fresh ledger, then re-run mla review.`);
|
|
118
|
+
out.push(` Inspect raw turns now: mla session show`);
|
|
119
|
+
}
|
|
120
|
+
if (packet.warnings && packet.warnings.length > 0) {
|
|
121
|
+
out.push(`\nWarnings`);
|
|
122
|
+
out.push(fmtList(packet.warnings));
|
|
123
|
+
}
|
|
124
|
+
out.push(`\nCapture summary`);
|
|
125
|
+
out.push(fmtCaptureSummary(packet));
|
|
126
|
+
out.push(`\nFacts (deterministic)`);
|
|
127
|
+
out.push(fmtFacts(packet.facts));
|
|
128
|
+
out.push(`\nObserved Bash events`);
|
|
129
|
+
out.push(fmtBashEvents(packet.bashEvents));
|
|
130
|
+
out.push(`\nMissing evidence flags`);
|
|
131
|
+
out.push(fmtList(packet.missingEvidence));
|
|
132
|
+
if (packet.agentClaimsRaw) {
|
|
133
|
+
out.push(`\nAgent self-report (verbatim final message; NOT verified)`);
|
|
134
|
+
out.push(` ${packet.agentClaimsRaw.slice(0, 1200).replace(/\n/g, "\n ")}`);
|
|
135
|
+
}
|
|
136
|
+
// No "Recommended next prompt" block (Mission deletion, PR1 Correction 5): the
|
|
137
|
+
// steering prompt was the lone surviving review-steering output and it embedded
|
|
138
|
+
// "Continue mission <X>". The producer side stopped emitting it; the renderer
|
|
139
|
+
// deliberately never surfaces `recommendedNextPrompt` even if a legacy/unmigrated
|
|
140
|
+
// packet still carries one on the wire. The surviving failure signal lives in the
|
|
141
|
+
// Missing-evidence flags above (e.g. `test_command_failed`).
|
|
142
|
+
// E2 run-end review directive (interim form). Per-session pending counts need
|
|
143
|
+
// the session->candidate linkage that lands with extraction (RCA Phase 3); until
|
|
144
|
+
// then this points at the session-scoped `mla review` and the Console queues the
|
|
145
|
+
// command already leads with. No verdict, no count fabrication.
|
|
146
|
+
out.push(`\nReview pending items`);
|
|
147
|
+
out.push(` This ledger carries no verdict. Captured candidates and claims are reviewed`);
|
|
148
|
+
out.push(` in the Console queues above. Re-run for this session: mla review`);
|
|
149
|
+
out.push(`\n╰──────────────────────────────────────────────────────────────────────╯`);
|
|
150
|
+
return out.join("\n");
|
|
151
|
+
}
|
|
152
|
+
function renderCaseRow(c) {
|
|
153
|
+
return ` ${c.id} [${c.status}] ${c.severity.toUpperCase()} ${c.riskCategory} ${c.title}`;
|
|
154
|
+
}
|
|
155
|
+
// Matches the risk on a ReviewPacket back to the case row. Cases were created
|
|
156
|
+
// via createFromAgentReview using risk.title + risk.category, so a (title,
|
|
157
|
+
// category) tuple is the load-bearing key the bridge already commits to. The
|
|
158
|
+
// fingerprint sha1 is not exposed on the packet's risks payload, so we cannot
|
|
159
|
+
// match by fingerprint client-side.
|
|
160
|
+
function pickRiskForCase(packet, rowMeta) {
|
|
161
|
+
if (!packet || !packet.risks || packet.risks.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
const t = (rowMeta.title || "").trim();
|
|
164
|
+
const cat = (rowMeta.riskCategory || "").trim();
|
|
165
|
+
if (!t && !cat)
|
|
166
|
+
return null;
|
|
167
|
+
const exact = packet.risks.find((r) => (r.title || "").trim() === t && (r.category || "").trim() === cat);
|
|
168
|
+
if (exact)
|
|
169
|
+
return exact;
|
|
170
|
+
const byCat = packet.risks.find((r) => (r.category || "").trim() === cat);
|
|
171
|
+
return byCat ?? null;
|
|
172
|
+
}
|
|
173
|
+
function fmtCaseShow(row, packet) {
|
|
174
|
+
const meta = (row?.metadata ?? {});
|
|
175
|
+
const out = [];
|
|
176
|
+
out.push(`╭─ Meetless CoordinationCase ───────────────────────────────────────────╮`);
|
|
177
|
+
out.push(` id: ${row?.id ?? "?"}`);
|
|
178
|
+
out.push(` kind: ${row?.kindId ?? "?"}`);
|
|
179
|
+
out.push(` status: ${row?.statusId ?? row?.status ?? "?"}`);
|
|
180
|
+
if (row?.canonicalFingerprint) {
|
|
181
|
+
out.push(` fingerprint: ${row.canonicalFingerprint}`);
|
|
182
|
+
}
|
|
183
|
+
if (row?.createdAt)
|
|
184
|
+
out.push(` created: ${row.createdAt}`);
|
|
185
|
+
if (row?.closedAt) {
|
|
186
|
+
out.push(` closed: ${row.closedAt} (${row.closedReason ?? "no reason"})`);
|
|
187
|
+
}
|
|
188
|
+
out.push(`────────────────────────────────────────────────────────────────────────`);
|
|
189
|
+
const sev = (meta.severity || "?").toString().toUpperCase();
|
|
190
|
+
const cat = meta.riskCategory || row?.kindId || "?";
|
|
191
|
+
const title = meta.title || "(no title)";
|
|
192
|
+
out.push(`\n ${sev} ${cat} ${title}`);
|
|
193
|
+
if (meta.description) {
|
|
194
|
+
out.push(`\n${(" " + String(meta.description)).replace(/\n/g, "\n ")}`);
|
|
195
|
+
}
|
|
196
|
+
// Evidence: prefer the packet's risk row (carries the wire refs). Fall back
|
|
197
|
+
// to evidenceCount on the case row so we at least surface that signal.
|
|
198
|
+
const matched = pickRiskForCase(packet, {
|
|
199
|
+
title: meta.title,
|
|
200
|
+
riskCategory: meta.riskCategory,
|
|
201
|
+
});
|
|
202
|
+
if (matched && matched.evidence && matched.evidence.length > 0) {
|
|
203
|
+
const rendered = matched.evidence.map(fmtEvidenceRef).filter((s) => !!s);
|
|
204
|
+
if (rendered.length > 0) {
|
|
205
|
+
out.push(`\n evidence:`);
|
|
206
|
+
for (const r of rendered)
|
|
207
|
+
out.push(` - ${r}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (typeof meta.evidenceCount === "number" && meta.evidenceCount > 0) {
|
|
211
|
+
out.push(`\n evidence: (${meta.evidenceCount} refs on packet; run \`mla review by-run <runId>\` to inspect)`);
|
|
212
|
+
}
|
|
213
|
+
if (meta.runId) {
|
|
214
|
+
out.push(`\n run id: ${meta.runId}`);
|
|
215
|
+
if (meta.packetId)
|
|
216
|
+
out.push(` packet id: ${meta.packetId}`);
|
|
217
|
+
out.push(`\n next: mla review (run inside the Claude Code session that produced this packet)`);
|
|
218
|
+
}
|
|
219
|
+
out.push(`\n╰──────────────────────────────────────────────────────────────────────╯`);
|
|
220
|
+
return out.join("\n");
|
|
221
|
+
}
|
|
222
|
+
// =============================================================================
|
|
223
|
+
// KB curation receipts (notes/20260530-mla-kb-curation-cli-proposal-v2 §4)
|
|
224
|
+
// =============================================================================
|
|
225
|
+
//
|
|
226
|
+
// The renderers below feed `mla kb add | show | forget | reingest | purge |
|
|
227
|
+
// move`. Each command's HTTP layer returns a typed JSON
|
|
228
|
+
// envelope; the commands hand the parsed envelope to a renderer here and a
|
|
229
|
+
// final console.log of the returned string is the only stdout the operator
|
|
230
|
+
// sees. Receipts always close with a one-line `next:` hint so the operator
|
|
231
|
+
// knows what verifies the action.
|
|
232
|
+
//
|
|
233
|
+
// Display formatting choices:
|
|
234
|
+
// - Box-drawing characters match the existing review/case renderers so
|
|
235
|
+
// `mla` output looks consistent across surfaces.
|
|
236
|
+
// - Hashes render as 12-char prefixes (`a3f9e2c10b48...`). The full value
|
|
237
|
+
// lives in the audit log; the prefix is enough to spot a divergence.
|
|
238
|
+
// - Counterpart tags ("[tombstoned: 2026-05-30T...]") inline next to the
|
|
239
|
+
// edge counterpart per §4.2 correction #8.
|
|
240
|
+
// - Timestamps render as the raw ISO string from intel. No timezone
|
|
241
|
+
// conversion in the CLI; the operator's terminal already knows.
|
|
242
|
+
function fmtKv(key, value) {
|
|
243
|
+
const v = value === undefined || value === null ? "(unset)" : String(value);
|
|
244
|
+
return ` ${key.padEnd(18, " ")} ${v}`;
|
|
245
|
+
}
|
|
246
|
+
function fmtHashPrefix(hash) {
|
|
247
|
+
if (!hash)
|
|
248
|
+
return "(none)";
|
|
249
|
+
return hash.length <= 12 ? hash : `${hash.slice(0, 12)}...`;
|
|
250
|
+
}
|
|
251
|
+
function fmtCounterpartTag(state, ts) {
|
|
252
|
+
if (!state || state === "ACTIVE")
|
|
253
|
+
return "";
|
|
254
|
+
const tsPart = ts ? `: ${ts}` : "";
|
|
255
|
+
return ` [${state.toLowerCase()}${tsPart}]`;
|
|
256
|
+
}
|
|
257
|
+
// B1: turn a GRAPH_EXTRACT extraction state into a human + agent signal. The
|
|
258
|
+
// async-queued case is the current default (the worker owns the LLM detector);
|
|
259
|
+
// the concrete states arrive once B3 polls the job to completion.
|
|
260
|
+
function fmtExtractionValue(ex) {
|
|
261
|
+
switch (ex.state) {
|
|
262
|
+
case "queued":
|
|
263
|
+
return "extraction queued (async; check `mla kb show` once it completes)";
|
|
264
|
+
case "running":
|
|
265
|
+
return "extracting now (async; check `mla kb show` once it completes)";
|
|
266
|
+
case "completed": {
|
|
267
|
+
const n = ex.candidateCount ?? 0;
|
|
268
|
+
const c = ex.conflictCount ?? 0;
|
|
269
|
+
const noun = `${n} candidate${n === 1 ? "" : "s"}`;
|
|
270
|
+
const conflictPart = c > 0 ? ` (${c} conflict${c === 1 ? "" : "s"}: CONTRADICTS/SUPERSEDES)` : "";
|
|
271
|
+
const review = n > 0 ? "; review with `mla kb pending`" : "; none to review";
|
|
272
|
+
return `${noun}${conflictPart}${review}`;
|
|
273
|
+
}
|
|
274
|
+
case "failed":
|
|
275
|
+
return "extraction FAILED; retry with `mla kb reingest`";
|
|
276
|
+
case "skipped":
|
|
277
|
+
return "not re-extracted (no body change)";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function renderKbAddReceipt(r) {
|
|
281
|
+
const out = [];
|
|
282
|
+
const verb = r.mode === "corpus" ? "corpus" : "file";
|
|
283
|
+
out.push(`╭─ mla kb add (${verb}) ${"─".repeat(Math.max(0, 49 - verb.length))}╮`);
|
|
284
|
+
out.push(fmtKv("workspace:", r.workspaceId));
|
|
285
|
+
out.push(fmtKv("outcome:", r.outcome));
|
|
286
|
+
out.push(fmtKv("documentId:", r.documentId));
|
|
287
|
+
out.push(fmtKv("canonicalPath:", r.canonicalPath));
|
|
288
|
+
out.push(fmtKv("parentUuid:", r.parentUuid));
|
|
289
|
+
out.push(fmtKv("provenance:", r.provenance));
|
|
290
|
+
if (r.outcome === "failed" && r.failure) {
|
|
291
|
+
out.push("");
|
|
292
|
+
out.push(fmtKv("FAILED code:", r.failure.code));
|
|
293
|
+
out.push(fmtKv("FAILED reason:", r.failure.reason));
|
|
294
|
+
out.push(fmtKv("FAILED at:", r.failure.failedAt));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
out.push("");
|
|
298
|
+
out.push(fmtKv("revisionId:", r.revisionId || "(unset)"));
|
|
299
|
+
out.push(fmtKv("revision status:", r.revisionStatus || "(unset)"));
|
|
300
|
+
out.push(fmtKv("chunk count:", r.chunkCount ?? 0));
|
|
301
|
+
out.push(fmtKv("normalizedBody:", fmtHashPrefix(r.normalizedBodyHash)));
|
|
302
|
+
out.push(fmtKv("fullDocument:", fmtHashPrefix(r.fullDocumentHash)));
|
|
303
|
+
out.push(fmtKv("outbox event:", r.outboxEventType || "(none)"));
|
|
304
|
+
// B1: never let `outbox event` be the only post-ingest signal. A minted
|
|
305
|
+
// revision enqueues GRAPH_EXTRACT; say so honestly. A noop_unchanged delivery
|
|
306
|
+
// mints nothing, so it enqueues no extraction.
|
|
307
|
+
const enqueuesExtraction = r.outcome === "ingested";
|
|
308
|
+
if (r.extraction) {
|
|
309
|
+
out.push(fmtKv("relationships:", fmtExtractionValue(r.extraction)));
|
|
310
|
+
}
|
|
311
|
+
else if (enqueuesExtraction) {
|
|
312
|
+
out.push(fmtKv("relationships:", fmtExtractionValue({ state: "queued" })));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (r.corpus) {
|
|
316
|
+
out.push("");
|
|
317
|
+
out.push(` corpus: ${r.corpus.corpusName}`);
|
|
318
|
+
out.push(` root: ${r.corpus.rootPath}`);
|
|
319
|
+
out.push(` totals: ingested=${r.corpus.ingested} restored=${r.corpus.restored} ` +
|
|
320
|
+
`no_change=${r.corpus.noChange} failed=${r.corpus.failed}`);
|
|
321
|
+
if (r.corpus.perDoc.length > 0) {
|
|
322
|
+
out.push("");
|
|
323
|
+
out.push(" per doc:");
|
|
324
|
+
for (const d of r.corpus.perDoc) {
|
|
325
|
+
const chunkPart = typeof d.chunkCount === "number" ? ` chunks=${d.chunkCount}` : "";
|
|
326
|
+
const failPart = d.failureCode ? ` failure=${d.failureCode}` : "";
|
|
327
|
+
out.push(` - ${d.canonicalPath} [${d.outcome}]${chunkPart}${failPart}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// B4a: always surface the Console review URL when the command layer supplies
|
|
332
|
+
// it. The human reviews candidate edges in the Console; the CLI prints the
|
|
333
|
+
// clickable link rather than auto-opening a browser.
|
|
334
|
+
if (r.consoleUrl) {
|
|
335
|
+
out.push("");
|
|
336
|
+
out.push(fmtKv("review (console):", r.consoleUrl));
|
|
337
|
+
}
|
|
338
|
+
out.push("");
|
|
339
|
+
out.push(` next: mla kb show ${r.canonicalPath}`);
|
|
340
|
+
out.push(`╰──────────────────────────────────────────────────────────────────────╯`);
|
|
341
|
+
return out.join("\n");
|
|
342
|
+
}
|
|
343
|
+
// B2: render the candidate-section line when there are zero candidate edges.
|
|
344
|
+
// Each branch is an honest, distinct signal; never the bare `(none)` that
|
|
345
|
+
// conflated "still running", "found nothing", and "crashed".
|
|
346
|
+
function fmtKbExtractionStatusLine(ext) {
|
|
347
|
+
if (!ext) {
|
|
348
|
+
// No GRAPH_EXTRACT run on record for this doc (e.g. ingested before the
|
|
349
|
+
// async detection lane, or a frontmatter-only reingest that never
|
|
350
|
+
// re-extracted). Honest, and distinct from a completed-with-nothing run.
|
|
351
|
+
return [` (no extraction on record for this revision)`];
|
|
352
|
+
}
|
|
353
|
+
switch (ext.state) {
|
|
354
|
+
case "queued":
|
|
355
|
+
case "running": {
|
|
356
|
+
const when = ext.state === "queued"
|
|
357
|
+
? ext.enqueuedAt
|
|
358
|
+
? `queued ${ext.enqueuedAt}`
|
|
359
|
+
: "queued"
|
|
360
|
+
: "running";
|
|
361
|
+
return [` extracting (${when}; job ${ext.jobId})`];
|
|
362
|
+
}
|
|
363
|
+
case "completed":
|
|
364
|
+
return [` none found (extraction completed; job ${ext.jobId})`];
|
|
365
|
+
case "failed":
|
|
366
|
+
return [
|
|
367
|
+
` extraction failed (job ${ext.jobId})${ext.error ? `: ${ext.error}` : ""}`,
|
|
368
|
+
` retry: mla kb add <path> --reingest`,
|
|
369
|
+
];
|
|
370
|
+
default:
|
|
371
|
+
return [` (no extraction on record for this revision)`];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function fmtRevisionLine(r) {
|
|
375
|
+
const parts = [`${r.id} [${r.status}]`];
|
|
376
|
+
if (r.status === "FAILED" && r.failureCode) {
|
|
377
|
+
parts.push(`failure=${r.failureCode}`);
|
|
378
|
+
}
|
|
379
|
+
if (r.provenanceOverride && r.status !== "FAILED") {
|
|
380
|
+
parts.push(`override=${r.provenanceOverride}`);
|
|
381
|
+
}
|
|
382
|
+
if (typeof r.chunkCount === "number")
|
|
383
|
+
parts.push(`chunks=${r.chunkCount}`);
|
|
384
|
+
if (r.createdAt)
|
|
385
|
+
parts.push(r.createdAt);
|
|
386
|
+
return parts.join(" ");
|
|
387
|
+
}
|
|
388
|
+
function fmtEdgeLine(e) {
|
|
389
|
+
const arrow = e.direction === "outgoing" ? "->" : "<-";
|
|
390
|
+
const tag = fmtCounterpartTag(e.counterpart.tombstoneState, e.counterpart.tombstonedAt);
|
|
391
|
+
const cp = e.counterpart.canonicalPath || `kbdoc:${e.counterpart.documentId}`;
|
|
392
|
+
const head = ` ${arrow} ${e.predicate} ${cp}${tag} (${e.status}/${e.posture}) ${e.id}`;
|
|
393
|
+
// Task 5.2: promoted edges carry a VALID-time window + clock trust. Print it
|
|
394
|
+
// on its own indented line so the operator sees WHEN the relation was true and
|
|
395
|
+
// HOW trustworthy that clock is. Candidates have no window (validity absent),
|
|
396
|
+
// so they keep the single-line form.
|
|
397
|
+
if (!e.validity)
|
|
398
|
+
return head;
|
|
399
|
+
const window = (0, temporal_1.renderValidWindow)(e.validity);
|
|
400
|
+
const trust = (0, temporal_1.renderTrustLabel)(e.validity.validTimeSource);
|
|
401
|
+
return `${head}\n ${window}, ${trust}`;
|
|
402
|
+
}
|
|
403
|
+
function renderKbShow(view) {
|
|
404
|
+
const out = [];
|
|
405
|
+
const d = view.document;
|
|
406
|
+
out.push(`╭─ mla kb show ────────────────────────────────────────────────────────╮`);
|
|
407
|
+
out.push(` workspace: ${view.workspaceId}`);
|
|
408
|
+
out.push("");
|
|
409
|
+
out.push(` IDENTITY + LIFECYCLE`);
|
|
410
|
+
out.push(fmtKv("id:", d.id));
|
|
411
|
+
out.push(fmtKv("canonicalPath:", d.canonicalPath || "(redacted; hard deleted)"));
|
|
412
|
+
if (d.pathAliases.length > 0) {
|
|
413
|
+
out.push(fmtKv("pathAliases:", d.pathAliases.join(", ")));
|
|
414
|
+
}
|
|
415
|
+
out.push(fmtKv("parentUuid:", d.parentUuid || "(cleared)"));
|
|
416
|
+
out.push(fmtKv("provenance:", d.provenance));
|
|
417
|
+
out.push(fmtKv("currentPosture:", d.currentPosture || "(null)"));
|
|
418
|
+
out.push(fmtKv("effectivePosture:", d.effectivePosture || "(null)"));
|
|
419
|
+
out.push(fmtKv("tombstoneState:", d.tombstoneState));
|
|
420
|
+
if (d.tombstonedAt)
|
|
421
|
+
out.push(fmtKv("tombstonedAt:", d.tombstonedAt));
|
|
422
|
+
if (d.tombstoneActorId)
|
|
423
|
+
out.push(fmtKv("tombstoneActor:", d.tombstoneActorId));
|
|
424
|
+
if (d.tombstoneReason)
|
|
425
|
+
out.push(fmtKv("tombstoneReason:", d.tombstoneReason));
|
|
426
|
+
if (d.deletedAt)
|
|
427
|
+
out.push(fmtKv("deletedAt:", d.deletedAt));
|
|
428
|
+
if (d.deletedBy)
|
|
429
|
+
out.push(fmtKv("deletedBy:", d.deletedBy));
|
|
430
|
+
if (d.redactedPathHash)
|
|
431
|
+
out.push(fmtKv("redactedPathHash:", fmtHashPrefix(d.redactedPathHash)));
|
|
432
|
+
out.push(fmtKv("createdAt:", d.createdAt));
|
|
433
|
+
out.push(fmtKv("updatedAt:", d.updatedAt));
|
|
434
|
+
out.push("");
|
|
435
|
+
out.push(` CURRENT REVISION`);
|
|
436
|
+
if (!view.currentRevision) {
|
|
437
|
+
out.push(` (no current revision; doc state = ${d.tombstoneState})`);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const cr = view.currentRevision;
|
|
441
|
+
out.push(fmtKv("revisionId:", cr.id));
|
|
442
|
+
out.push(fmtKv("status:", cr.status));
|
|
443
|
+
out.push(fmtKv("ingestRunId:", cr.ingestRunId || "(unset)"));
|
|
444
|
+
out.push(fmtKv("postureAtIngest:", cr.postureAtIngest || "(unset)"));
|
|
445
|
+
if (cr.provenanceOverride) {
|
|
446
|
+
out.push(fmtKv("provenanceOverride:", cr.provenanceOverride));
|
|
447
|
+
if (cr.overrideReason)
|
|
448
|
+
out.push(fmtKv("overrideReason:", cr.overrideReason));
|
|
449
|
+
}
|
|
450
|
+
out.push(fmtKv("normalizedBody:", fmtHashPrefix(cr.normalizedBodyHash)));
|
|
451
|
+
out.push(fmtKv("frontmatter:", fmtHashPrefix(cr.frontmatterHash)));
|
|
452
|
+
out.push(fmtKv("fullDocument:", fmtHashPrefix(cr.fullDocumentHash)));
|
|
453
|
+
out.push(fmtKv("chunkCount:", cr.chunkCount ?? 0));
|
|
454
|
+
out.push(fmtKv("createdAt:", cr.createdAt || "(unset)"));
|
|
455
|
+
}
|
|
456
|
+
out.push("");
|
|
457
|
+
out.push(` REVISION HISTORY${view.revisionHistoryTruncated ? " (truncated; pass --all)" : ""}`);
|
|
458
|
+
if (view.revisionHistory.length === 0) {
|
|
459
|
+
out.push(` (no revisions on file)`);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
for (const r of view.revisionHistory) {
|
|
463
|
+
out.push(` ${fmtRevisionLine(r)}`);
|
|
464
|
+
if (r.status === "FAILED" && r.failureReason) {
|
|
465
|
+
out.push(` failure: ${r.failureReason}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
out.push("");
|
|
470
|
+
out.push(` CHUNKS`);
|
|
471
|
+
out.push(fmtKv("total count:", view.chunks.totalCount));
|
|
472
|
+
out.push(fmtKv("total bytes:", view.chunks.totalBytes));
|
|
473
|
+
if (view.chunks.preview.length > 0) {
|
|
474
|
+
out.push(" active preview:");
|
|
475
|
+
for (const c of view.chunks.preview) {
|
|
476
|
+
out.push(` [${c.ordinal}] ${c.preview.slice(0, 80)}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (view.chunks.pendingReviewPreview.length > 0) {
|
|
480
|
+
out.push(" pending review preview:");
|
|
481
|
+
for (const c of view.chunks.pendingReviewPreview) {
|
|
482
|
+
out.push(` [${c.ordinal}] ${c.preview.slice(0, 80)}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
out.push("");
|
|
486
|
+
out.push(` CANDIDATES TOUCHING THIS DOC (${view.candidates.length})`);
|
|
487
|
+
if (view.candidates.length === 0) {
|
|
488
|
+
for (const line of fmtKbExtractionStatusLine(view.extraction))
|
|
489
|
+
out.push(line);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
for (const e of view.candidates)
|
|
493
|
+
out.push(fmtEdgeLine(e));
|
|
494
|
+
}
|
|
495
|
+
out.push("");
|
|
496
|
+
out.push(` PROMOTED EDGES (${view.promoted.length})`);
|
|
497
|
+
if (view.promoted.length === 0) {
|
|
498
|
+
out.push(` (none)`);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
for (const e of view.promoted)
|
|
502
|
+
out.push(fmtEdgeLine(e));
|
|
503
|
+
}
|
|
504
|
+
out.push("");
|
|
505
|
+
out.push(` AUDIT TRAIL${view.auditTruncated ? " (truncated; pass --audit-all)" : ""}`);
|
|
506
|
+
if (view.audit.length === 0) {
|
|
507
|
+
out.push(` (no audit rows)`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
for (const a of view.audit) {
|
|
511
|
+
const actor = a.actorId ? ` by ${a.actorId}` : "";
|
|
512
|
+
out.push(` ${a.occurredAt} ${a.eventType}${actor} ${a.id}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// B4a: surface the Console review URL (the human surface for candidate
|
|
516
|
+
// review). Printed always when present; the CLI never auto-opens a browser
|
|
517
|
+
// from an inspection command run repeatedly / over SSH / in CI.
|
|
518
|
+
if (view.consoleUrl) {
|
|
519
|
+
out.push("");
|
|
520
|
+
out.push(` CONSOLE`);
|
|
521
|
+
out.push(` review queue: ${view.consoleUrl}`);
|
|
522
|
+
}
|
|
523
|
+
out.push("");
|
|
524
|
+
out.push(`╰──────────────────────────────────────────────────────────────────────╯`);
|
|
525
|
+
return out.join("\n");
|
|
526
|
+
}
|
|
527
|
+
// The governed tombstone flips one column (tombstoneState ACTIVE -> TOMBSTONED);
|
|
528
|
+
// the serve gate drops the doc at read time via the liveness predicate, so there
|
|
529
|
+
// is no chunk sweep, no outbox event, and no tombstone-timestamp column to echo.
|
|
530
|
+
function renderKbForgetReceipt(r) {
|
|
531
|
+
const out = [];
|
|
532
|
+
out.push(`╭─ mla kb forget ──────────────────────────────────────────────────────╮`);
|
|
533
|
+
out.push(fmtKv("workspace:", r.workspaceId));
|
|
534
|
+
out.push(fmtKv("outcome:", r.outcome));
|
|
535
|
+
out.push(fmtKv("documentId:", r.documentId));
|
|
536
|
+
out.push(fmtKv("canonicalPath:", r.canonicalPath));
|
|
537
|
+
out.push(fmtKv("priorRevisionId:", r.priorRevisionId || "(none)"));
|
|
538
|
+
if (r.reason)
|
|
539
|
+
out.push(fmtKv("reason:", r.reason));
|
|
540
|
+
out.push("");
|
|
541
|
+
out.push(` next: mla kb show kbdoc:${r.documentId} (verify tombstoneState)`);
|
|
542
|
+
out.push(`╰──────────────────────────────────────────────────────────────────────╯`);
|
|
543
|
+
return out.join("\n");
|
|
544
|
+
}
|
|
545
|
+
function renderKbReingestReceipt(r) {
|
|
546
|
+
const out = [];
|
|
547
|
+
out.push(`╭─ mla kb reingest ────────────────────────────────────────────────────╮`);
|
|
548
|
+
out.push(fmtKv("workspace:", r.workspaceId));
|
|
549
|
+
out.push(fmtKv("outcome:", r.outcome));
|
|
550
|
+
out.push(fmtKv("documentId:", r.documentId));
|
|
551
|
+
out.push(fmtKv("canonicalPath:", r.canonicalPath));
|
|
552
|
+
out.push(fmtKv("parentUuid:", r.parentUuid));
|
|
553
|
+
out.push(fmtKv("priorRevisionId:", r.priorRevisionId || "(none)"));
|
|
554
|
+
if (r.outcome === "failed" && r.failure) {
|
|
555
|
+
out.push(fmtKv("newRevisionId:", r.newRevisionId || "(none)"));
|
|
556
|
+
out.push(fmtKv("FAILED code:", r.failure.code));
|
|
557
|
+
out.push(fmtKv("FAILED reason:", r.failure.reason));
|
|
558
|
+
out.push(fmtKv("FAILED at:", r.failure.failedAt));
|
|
559
|
+
}
|
|
560
|
+
else if (r.outcome === "noop_unchanged") {
|
|
561
|
+
// Content-identical re-delivery: nothing minted, prior head unchanged.
|
|
562
|
+
out.push(fmtKv("note:", "content unchanged; no new revision minted"));
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
out.push(fmtKv("newRevisionId:", r.newRevisionId || "(unset)"));
|
|
566
|
+
out.push(fmtKv("revision status:", r.newRevisionStatus || "(unset)"));
|
|
567
|
+
out.push(fmtKv("provenance:", r.provenance || "(unset)"));
|
|
568
|
+
out.push(fmtKv("chunk count:", r.chunkCount ?? 0));
|
|
569
|
+
out.push(fmtKv("normalizedBody:", fmtHashPrefix(r.normalizedBodyHash)));
|
|
570
|
+
out.push(fmtKv("fullDocument:", fmtHashPrefix(r.fullDocumentHash)));
|
|
571
|
+
}
|
|
572
|
+
if (r.reason)
|
|
573
|
+
out.push(fmtKv("reason:", r.reason));
|
|
574
|
+
out.push("");
|
|
575
|
+
out.push(` next: mla kb show kbdoc:${r.documentId}`);
|
|
576
|
+
out.push(`╰──────────────────────────────────────────────────────────────────────╯`);
|
|
577
|
+
return out.join("\n");
|
|
578
|
+
}
|
|
579
|
+
function renderKbPurgeReceipt(r) {
|
|
580
|
+
const out = [];
|
|
581
|
+
out.push(`╭─ mla kb purge ───────────────────────────────────────────────────────╮`);
|
|
582
|
+
out.push(fmtKv("workspace:", r.workspaceId));
|
|
583
|
+
out.push(fmtKv("outcome:", r.outcome));
|
|
584
|
+
out.push(fmtKv("documentId:", r.documentId));
|
|
585
|
+
out.push(fmtKv("canonicalPath:", r.canonicalPath));
|
|
586
|
+
out.push(fmtKv("priorRevisionId:", r.priorRevisionId || "(none)"));
|
|
587
|
+
out.push(fmtKv("revisions total:", r.revisionsTotal));
|
|
588
|
+
out.push(fmtKv("revisions redacted:", r.revisionsRedacted));
|
|
589
|
+
out.push(fmtKv("already redacted:", r.revisionsAlreadyRedacted));
|
|
590
|
+
out.push(fmtKv("tombstoneState:", r.tombstoneState));
|
|
591
|
+
out.push(fmtKv("reason:", r.reason));
|
|
592
|
+
out.push("");
|
|
593
|
+
if (r.outcome === "already_purged") {
|
|
594
|
+
out.push(` note: every revision was already redacted and the document was`);
|
|
595
|
+
out.push(` already tombstoned; nothing to do.`);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
out.push(` note: all revisions redacted (content gone, audit metadata kept).`);
|
|
599
|
+
out.push(` slice A has no physical-purge primitive, so the document is`);
|
|
600
|
+
out.push(` TOMBSTONED rather than PURGED.`);
|
|
601
|
+
}
|
|
602
|
+
out.push(` next: mla kb show kbdoc:${r.documentId} (verify tombstoneState)`);
|
|
603
|
+
out.push(`╰──────────────────────────────────────────────────────────────────────╯`);
|
|
604
|
+
return out.join("\n");
|
|
605
|
+
}
|
|
606
|
+
// `mla kb move` is a BLOCKED capability in slice A (governed identity is the
|
|
607
|
+
// source tuple; re-pathing yields a different document and there is no redirect
|
|
608
|
+
// primitive yet), so it emits no receipt. The legacy KbMoveReceipt /
|
|
609
|
+
// renderKbMoveReceipt were removed: they modeled now-dead concepts (parentUuid,
|
|
610
|
+
// path_aliases, the KB_MOVED outbox event). When move is unblocked under a real
|
|
611
|
+
// redirect primitive, a fresh receipt should be defined against that model.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseApplicability = parseApplicability;
|
|
4
|
+
// R0 applicability parser. Turns a raw, untrusted descriptor into an explicit
|
|
5
|
+
// OK / DISABLED / INVALID result. The cardinal rule: NEVER infer a mode from
|
|
6
|
+
// absence. A missing, malformed, or unknown mode is a diagnostic, not a silent
|
|
7
|
+
// fall-through to "ambient".
|
|
8
|
+
function invalid(diagnostic) {
|
|
9
|
+
return { status: "INVALID", diagnostic };
|
|
10
|
+
}
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function parseMatcher(raw) {
|
|
15
|
+
if (!isPlainObject(raw)) {
|
|
16
|
+
return { error: "action applicability requires a matcher object" };
|
|
17
|
+
}
|
|
18
|
+
if (typeof raw.field !== "string" || raw.field.length === 0) {
|
|
19
|
+
return { error: "action matcher requires a non-empty string field" };
|
|
20
|
+
}
|
|
21
|
+
const matcher = { field: raw.field };
|
|
22
|
+
if (raw.glob !== undefined) {
|
|
23
|
+
if (typeof raw.glob !== "string" || raw.glob.length === 0) {
|
|
24
|
+
return { error: "action matcher glob must be a non-empty string when present" };
|
|
25
|
+
}
|
|
26
|
+
matcher.glob = raw.glob;
|
|
27
|
+
}
|
|
28
|
+
return matcher;
|
|
29
|
+
}
|
|
30
|
+
function parseApplicability(raw) {
|
|
31
|
+
if (!isPlainObject(raw)) {
|
|
32
|
+
return invalid("applicability must be an object with an explicit mode");
|
|
33
|
+
}
|
|
34
|
+
if (typeof raw.mode !== "string") {
|
|
35
|
+
return invalid("applicability.mode is missing; ambient is never inferred from absence");
|
|
36
|
+
}
|
|
37
|
+
switch (raw.mode) {
|
|
38
|
+
case "ambient":
|
|
39
|
+
return { status: "OK", applicability: { mode: "ambient" } };
|
|
40
|
+
case "action": {
|
|
41
|
+
if (!Array.isArray(raw.tools) || raw.tools.length === 0) {
|
|
42
|
+
return invalid("action applicability requires a non-empty tools array");
|
|
43
|
+
}
|
|
44
|
+
if (!raw.tools.every((t) => typeof t === "string" && t.length > 0)) {
|
|
45
|
+
return invalid("action applicability tools must all be non-empty strings");
|
|
46
|
+
}
|
|
47
|
+
const matcher = parseMatcher(raw.matcher);
|
|
48
|
+
if ("error" in matcher) {
|
|
49
|
+
return invalid(matcher.error);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
status: "OK",
|
|
53
|
+
applicability: { mode: "action", tools: raw.tools, matcher },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
case "turn":
|
|
57
|
+
return {
|
|
58
|
+
status: "DISABLED",
|
|
59
|
+
diagnostic: "turn applicability is reserved and inert in R0/R1; rule not evaluated",
|
|
60
|
+
};
|
|
61
|
+
default:
|
|
62
|
+
return invalid(`unknown applicability mode: ${raw.mode}`);
|
|
63
|
+
}
|
|
64
|
+
}
|