@smartmemory/compose 0.3.6-beta → 0.3.7
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/.claude/skills/compose/SKILL.md +42 -88
- package/bin/compose.js +288 -0
- package/bin/git-hooks/pre-push.template +29 -0
- package/bin/judgment-import.js +7 -0
- package/contracts/feature-json.schema.json +5 -0
- package/contracts/judgment-record.schema.json +425 -4
- package/dist/assets/App-PkZzHeMj.js +894 -0
- package/dist/assets/_baseUniq-Bo837sRJ.js +1 -0
- package/dist/assets/arc-BafGpyqE.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-BOBfUsqL.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-Dwodev1a.js +132 -0
- package/dist/assets/{browser-BSM23If2.js → browser-1ntj1-x_.js} +6 -6
- package/dist/assets/{c4Diagram-LMCZKHZV-DZf45Fbz.js → c4Diagram-AHTNJAMY-CU_bhYag.js} +1 -1
- package/dist/assets/channel-qVK_qn4E.js +1 -0
- package/dist/assets/{chunk-JWPE2WC7-_7ujgd_Q.js → chunk-4BX2VUAB-p8WsDwnO.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-B8h7-eR0.js +206 -0
- package/dist/assets/{chunk-XXDRQBXY-DfdVhbmA.js → chunk-55IACEB6-DxeEr98s.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-Dt9NZ67m.js → chunk-EDXVE4YY-BYt8F151.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BY4_PV5H.js → chunk-FMBD7UC4-DGSOVeie.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-B-QdgYR2.js +231 -0
- package/dist/assets/{chunk-2Q5K7J3B-Dn1spZYu.js → chunk-QZHKN3VN-Du5UAZLs.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-pURGrJDk.js → chunk-YZCP3GAM-C8JbNBSk.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B8UcfC1q.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-B8UcfC1q.js +1 -0
- package/dist/assets/clone-Pu3RyLUh.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BieYif4o.js → cose-bilkent-S5V4N54A-O1ESaqge.js} +1 -1
- package/dist/assets/dagre-KV5264BT-CPTmFPHw.js +4 -0
- package/dist/assets/diagram-5BDNPKRD-B3PNrWs5.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-Cscfr6vc.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-CSfqZ-TM.js +43 -0
- package/dist/assets/diagram-TYMM5635-Cg4aYS7W.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-_ZqwG5pl.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-C83boxFT.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-CWnIjuEi.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-DrMdxZfH.js +106 -0
- package/dist/assets/graph-Bi99_6Yf.js +331 -0
- package/dist/assets/graph-RE4I7Ty7.js +1 -0
- package/dist/assets/index-Rm2RE-c0.js +123 -0
- package/dist/assets/infoDiagram-42DDH7IO-BLmP4Epr.js +2 -0
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CzEB9fQS.js → ishikawaDiagram-UXIWVN3A-yuWWshKN.js} +5 -5
- package/dist/assets/{journeyDiagram-5HDEW3XC-Bz8TCdz2.js → journeyDiagram-VCZTEJTY-BOfhaJov.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tozrMoV_.js → kanban-definition-6JOO6SKY-Bbolde15.js} +7 -7
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-BSf33zm8.js +1 -0
- package/dist/assets/{linear-Ck7gpa5N.js → linear-AvSTWMqx.js} +1 -1
- package/dist/assets/min-QBM8H4xN.js +1 -0
- package/dist/assets/{mindmap-definition-LN4V7U3C-DTcHO0DJ.js → mindmap-definition-QFDTVHPH-BuvgtqIc.js} +7 -7
- package/dist/assets/{mobile-CaoXUwAr.js → mobile-BnXEOE3U.js} +2 -2
- package/dist/assets/pieDiagram-DEJITSTG-DIzF16vh.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-D-mbUIjS.js +7 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-bnI2zJeT.js → requirementDiagram-MS252O5E-CEs4kCLd.js} +3 -3
- package/dist/assets/sankeyDiagram-XADWPNL6-DFsnCr9n.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-BEJYdTjQ.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-BBXs57uY.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BqKuX4rj.js +1 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-D267GQFF.js → timeline-definition-GMOUNBTQ-BGvLoVAY.js} +3 -3
- package/dist/assets/vennDiagram-DHZGUBPP-9LaBTMe0.js +34 -0
- package/dist/assets/wardley-RL74JXVD-P4MEqMTP.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-o-tmxnlC.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-Dpn7V6qk.js +7 -0
- package/dist/index.html +2 -2
- package/lib/bug-escalation.js +30 -4
- package/lib/build.js +777 -52
- package/lib/canon-guard.js +223 -0
- package/lib/canon-registry.js +187 -0
- package/lib/codex-preflight.js +26 -4
- package/lib/dispatch-ledger.js +301 -0
- package/lib/dispatch-metrics.js +236 -0
- package/lib/experiment-judge.js +6 -1
- package/lib/feature-writer.js +9 -0
- package/lib/gsd.js +11 -2
- package/lib/hooks-status.js +32 -3
- package/lib/judgment/store/index.js +158 -0
- package/lib/judgment/store/records.js +183 -24
- package/lib/judgment-attest.js +259 -0
- package/lib/judgment-gen.js +370 -21
- package/lib/judgment-verify.js +153 -0
- package/lib/judgment-writer.js +2803 -277
- package/lib/lane-gate.js +2 -0
- package/lib/local-claude-connector.js +199 -54
- package/lib/mcp-enforcement.js +21 -35
- package/lib/result-normalizer.js +93 -15
- package/lib/review-normalize.js +4 -0
- package/lib/stratum-mcp-client.js +131 -6
- package/package.json +2 -2
- package/server/compose-mcp-tools.js +15 -1
- package/server/compose-mcp.js +96 -1
- package/server/mcp-tool-policy.js +1 -1
- package/dist/assets/App-BG3ngu8H.js +0 -896
- package/dist/assets/abnfDiagram-VRR7QNED-CjB_sD3D.js +0 -1
- package/dist/assets/arc-_v4hR_uD.js +0 -1
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DreJmzXQ.js +0 -36
- package/dist/assets/blockDiagram-677ZJIJ3-BG9-c0O1.js +0 -132
- package/dist/assets/channel-B3U5wFAT.js +0 -1
- package/dist/assets/chunk-EX3LRPZG-DdELs1qP.js +0 -231
- package/dist/assets/chunk-MOJQB5TN-D-ky35G-.js +0 -88
- package/dist/assets/chunk-RYQCIY6F-Dag_kVlO.js +0 -1
- package/dist/assets/chunk-V7JOEXUC-BtewURat.js +0 -206
- package/dist/assets/classDiagram-OUVF2IWQ-B6fCN-ht.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B6fCN-ht.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CT2BA6KE.js +0 -178
- package/dist/assets/cynefinDiagram-TSTJHNR4-Bh6exbyg.js +0 -62
- package/dist/assets/dagre-VKFMJZFB-aXMLSmQL.js +0 -4
- package/dist/assets/diagram-FQU43EPY-Dr7JAOuQ.js +0 -3
- package/dist/assets/diagram-G47NLZAW-DUvA3FQK.js +0 -24
- package/dist/assets/diagram-NH7WQ7WH-BQUARqcu.js +0 -24
- package/dist/assets/diagram-OA4YK3LP-dDUc1zHi.js +0 -30
- package/dist/assets/diagram-WEI45ONY-B2h5Qlb1.js +0 -41
- package/dist/assets/ebnfDiagram-CCIWWBDH-DThRGupB.js +0 -1
- package/dist/assets/erDiagram-Q63AITRT-BUCsprO2.js +0 -85
- package/dist/assets/flowDiagram-23GEKE2U-DXtNNi6r.js +0 -156
- package/dist/assets/ganttDiagram-NO4QXBWP-D4zbBHh_.js +0 -292
- package/dist/assets/gitGraphDiagram-IHSO6WYX-DpoQws0W.js +0 -106
- package/dist/assets/graph-BXPQrYYB.js +0 -331
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-3ZH5eMcZ.js +0 -119
- package/dist/assets/infoDiagram-FWYZ7A6U-Bbas2GAo.js +0 -2
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DEXfKzaS.js +0 -1
- package/dist/assets/map-Czzmt4hB.js +0 -1
- package/dist/assets/pegDiagram-2B236MQR-CHiINrNy.js +0 -1
- package/dist/assets/pieDiagram-ENE6RG2P-CfS4YFlR.js +0 -39
- package/dist/assets/quadrantDiagram-ABIIQ3AL-CadesS9w.js +0 -7
- package/dist/assets/railroadDiagram-RFXS5EU6-CgWEspBN.js +0 -1
- package/dist/assets/sankeyDiagram-HTMAVEWB-YWKFgOGw.js +0 -40
- package/dist/assets/sequenceDiagram-DBY2YBRQ-BvkNOyF9.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-DlFPA2yO.js +0 -1
- package/dist/assets/stateDiagram-2N3HPSRC-h8NIx0kQ.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DjPgZtJ9.js +0 -1
- package/dist/assets/swimlanes-5IMT3BWC-CT5n22kG.js +0 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dn318Bhq.js +0 -8
- package/dist/assets/vennDiagram-L72KCM5P-Dj-wWLYG.js +0 -34
- package/dist/assets/wardleyDiagram-EHGQE667-BxCeYxkG.js +0 -78
- package/dist/assets/xychartDiagram-FW5EYKEG-DMFqWn7z.js +0 -7
package/lib/judgment-gen.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/judgment-gen.js — judgment projections (S04).
|
|
3
3
|
*
|
|
4
|
-
* records → REGISTER.md, LEDGER.md, OBJECTIVE.md,
|
|
5
|
-
* the OKF bundle root index.md.
|
|
4
|
+
* records → REGISTER.md, LEDGER.md, OBJECTIVE.md, SITUATION.md,
|
|
5
|
+
* people/<slug>.md, positions/<slug>.md, and the OKF bundle root index.md.
|
|
6
|
+
* Projections are PURE OUTPUT: generation
|
|
6
7
|
* reads only records — never an existing projection — so any hand-edit is
|
|
7
8
|
* overwritten by the next regeneration and there are no preserved sections
|
|
8
9
|
* (curated prose is `note` records rendered at their anchors).
|
|
@@ -22,7 +23,11 @@ import {
|
|
|
22
23
|
readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, existsSync, readdirSync,
|
|
23
24
|
} from 'node:fs';
|
|
24
25
|
import { join } from 'node:path';
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
createJudgmentStore,
|
|
28
|
+
effectiveStore,
|
|
29
|
+
goalCutoverComplete,
|
|
30
|
+
} from './judgment/store/index.js';
|
|
26
31
|
|
|
27
32
|
function atomicWrite(path, content) {
|
|
28
33
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
@@ -50,8 +55,9 @@ function smartmemoryTeamId(cwd) {
|
|
|
50
55
|
* Load everything projection-relevant from the store into a plain snapshot,
|
|
51
56
|
* so the generator core stays pure.
|
|
52
57
|
*/
|
|
53
|
-
function loadSnapshot(cwd) {
|
|
54
|
-
const
|
|
58
|
+
export function loadSnapshot(cwd) {
|
|
59
|
+
const rawStore = createJudgmentStore(cwd);
|
|
60
|
+
const store = effectiveStore(rawStore);
|
|
55
61
|
const positions = store.listPositionSlugs().map((slug) => ({
|
|
56
62
|
slug,
|
|
57
63
|
chain: store.readPositionChain(slug),
|
|
@@ -61,6 +67,11 @@ function loadSnapshot(cwd) {
|
|
|
61
67
|
positions,
|
|
62
68
|
joints: store.listJoints(),
|
|
63
69
|
ledger: store.readLedgerEvents(),
|
|
70
|
+
people: store.listPeople(),
|
|
71
|
+
situationEntities: store.listSituationEntities(),
|
|
72
|
+
goalChain: store.readGoalChain(),
|
|
73
|
+
goalState: store.readGoalState(),
|
|
74
|
+
goalCutoverComplete: goalCutoverComplete(store),
|
|
64
75
|
teamId: smartmemoryTeamId(cwd),
|
|
65
76
|
};
|
|
66
77
|
}
|
|
@@ -75,11 +86,22 @@ function renderNotes(notes) {
|
|
|
75
86
|
return notes.map((n) => `> **${n.title}** — ${n.body ?? ''}`.trimEnd()).join('\n\n');
|
|
76
87
|
}
|
|
77
88
|
|
|
89
|
+
// Plain YAML scalars that need no quoting: no leading/trailing space, none
|
|
90
|
+
// of the indicator/structure characters that would change the parse. Risky
|
|
91
|
+
// titles (e.g. "Jane: CEO") are emitted JSON-quoted, which is valid YAML
|
|
92
|
+
// double-quote style.
|
|
93
|
+
const YAML_PLAIN_SAFE_RE = /^[A-Za-z0-9](?:[A-Za-z0-9 _./()-]*[A-Za-z0-9_./()-])?$/;
|
|
94
|
+
|
|
95
|
+
function yamlScalar(value) {
|
|
96
|
+
const s = String(value);
|
|
97
|
+
return YAML_PLAIN_SAFE_RE.test(s) ? s : JSON.stringify(s);
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
function okfFrontmatter({ type, title, timestamp, resource }) {
|
|
79
101
|
const lines = ['---'];
|
|
80
102
|
if (type) lines.push(`type: ${type}`);
|
|
81
103
|
else lines.push('okf_version: "0.1"');
|
|
82
|
-
lines.push(`title: ${title}`);
|
|
104
|
+
lines.push(`title: ${yamlScalar(title)}`);
|
|
83
105
|
if (timestamp) lines.push(`timestamp: "${timestamp}"`);
|
|
84
106
|
if (resource) lines.push(`resource: "${resource}"`);
|
|
85
107
|
lines.push('smartmemory:');
|
|
@@ -94,6 +116,194 @@ function buildResource(teamId, providerId) {
|
|
|
94
116
|
return `smartmemory://${encodeURIComponent(teamId)}/${encodeURIComponent(providerId)}`;
|
|
95
117
|
}
|
|
96
118
|
|
|
119
|
+
function auditValue(value) {
|
|
120
|
+
const encoded = JSON.stringify(value);
|
|
121
|
+
return encoded === undefined ? String(value) : encoded;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderTraceLines(trace = [], { prefix = ' - ' } = {}) {
|
|
125
|
+
return trace.map((entry) => {
|
|
126
|
+
const prior = Object.entries(entry.prior ?? {})
|
|
127
|
+
.map(([key, value]) => `${key}=${auditValue(value)}`)
|
|
128
|
+
.join(', ');
|
|
129
|
+
return `${prefix}corrected from ${prior} at ${entry.corrected_at ?? 'unknown'}`;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderRecordedAndRemoval(entry, { prefix = ' - ' } = {}) {
|
|
134
|
+
const lines = [];
|
|
135
|
+
lines.push(`${prefix}recorded at ${entry.provenance?.written_at ?? 'unknown'}`);
|
|
136
|
+
if (entry.removed) {
|
|
137
|
+
lines.push(`${prefix}removed at ${entry.removed.at ?? 'unknown'} — ${entry.removed.reason}`);
|
|
138
|
+
}
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderAuditedFact(fact) {
|
|
143
|
+
const lines = [`- **${fact.id}** ${fact.text}`];
|
|
144
|
+
lines.push(` - channel: \`${fact.channel}\``);
|
|
145
|
+
if (fact.via !== undefined) lines.push(` - via: ${fact.via}`);
|
|
146
|
+
lines.push(` - at: ${fact.at}`);
|
|
147
|
+
lines.push(...renderTraceLines(fact.trace));
|
|
148
|
+
return lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const PERSON_SECTIONS = ['role', 'life', 'stated', 'revealed'];
|
|
152
|
+
|
|
153
|
+
function sectionTitle(section) {
|
|
154
|
+
return section[0].toUpperCase() + section.slice(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function orderedPairLabel(left, right) {
|
|
158
|
+
const suffix = (id) => Number.parseInt(String(id).replace(/^[^0-9]+/, ''), 10);
|
|
159
|
+
const ids = [left, right].sort((a, b) => suffix(a) - suffix(b) || a.localeCompare(b));
|
|
160
|
+
return `${ids[0]} ↔ ${ids[1]}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderPerson(person) {
|
|
164
|
+
const lifecycle = person.facts.some((fact) => fact.channel === 'said') ? 'spoken' : 'stub';
|
|
165
|
+
const parts = [okfFrontmatter({
|
|
166
|
+
type: 'person',
|
|
167
|
+
title: person.display_name,
|
|
168
|
+
timestamp: person.provenance?.written_at,
|
|
169
|
+
})];
|
|
170
|
+
parts.push('');
|
|
171
|
+
parts.push(`# ${person.display_name}`);
|
|
172
|
+
parts.push('');
|
|
173
|
+
parts.push(`**Lifecycle:** ${lifecycle}`);
|
|
174
|
+
parts.push(`**Record:** \`${person.slug}\``);
|
|
175
|
+
|
|
176
|
+
const factsById = new Map(person.facts.map((fact) => [fact.id, fact]));
|
|
177
|
+
const rendered = new Set();
|
|
178
|
+
for (const section of PERSON_SECTIONS) {
|
|
179
|
+
parts.push('');
|
|
180
|
+
parts.push(`## ${sectionTitle(section)}`);
|
|
181
|
+
parts.push('');
|
|
182
|
+
const sectionLines = [];
|
|
183
|
+
for (const fact of person.facts.filter((entry) => entry.section === section)) {
|
|
184
|
+
if (rendered.has(fact.id)) continue;
|
|
185
|
+
const paired = fact.diverges_with ? factsById.get(fact.diverges_with) : null;
|
|
186
|
+
const isStatedRevealedPair = (
|
|
187
|
+
section === 'stated'
|
|
188
|
+
&& paired?.section === 'revealed'
|
|
189
|
+
&& paired.diverges_with === fact.id
|
|
190
|
+
);
|
|
191
|
+
sectionLines.push(renderAuditedFact(fact));
|
|
192
|
+
rendered.add(fact.id);
|
|
193
|
+
if (isStatedRevealedPair) {
|
|
194
|
+
sectionLines.push(renderAuditedFact(paired));
|
|
195
|
+
sectionLines.push(` - divergence pair: ${orderedPairLabel(fact.id, paired.id)}`);
|
|
196
|
+
rendered.add(paired.id);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
parts.push(sectionLines.length > 0 ? sectionLines.join('\n') : 'No facts recorded.');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
parts.push('');
|
|
203
|
+
parts.push('## Edges');
|
|
204
|
+
parts.push('');
|
|
205
|
+
if (person.edges.length === 0) {
|
|
206
|
+
parts.push('None.');
|
|
207
|
+
} else {
|
|
208
|
+
for (const edge of person.edges) {
|
|
209
|
+
parts.push(`- **${edge.id}** \`${edge.removed ? 'removed' : 'active'}\` — ${edge.kind} → [${edge.to}](${edge.to}.md)`);
|
|
210
|
+
parts.push(...renderRecordedAndRemoval(edge));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
parts.push('');
|
|
215
|
+
parts.push('## Open fields');
|
|
216
|
+
parts.push('');
|
|
217
|
+
if (person.open_fields.length === 0) {
|
|
218
|
+
parts.push('None.');
|
|
219
|
+
} else {
|
|
220
|
+
for (const field of person.open_fields) {
|
|
221
|
+
parts.push(`- **${field.id}** \`${field.status}\` — ${field.name}`);
|
|
222
|
+
parts.push(` - filled_by: ${field.filled_by ?? '—'}`);
|
|
223
|
+
parts.push(` - recorded at ${field.provenance?.written_at ?? 'unknown'}`);
|
|
224
|
+
parts.push(...renderTraceLines(field.trace));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
parts.push('');
|
|
229
|
+
parts.push('## Load links');
|
|
230
|
+
parts.push('');
|
|
231
|
+
if (person.load_links.length === 0) {
|
|
232
|
+
parts.push('None.');
|
|
233
|
+
} else {
|
|
234
|
+
for (const link of person.load_links) {
|
|
235
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — fact ${link.fact} carries ${link.carries}`);
|
|
236
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return parts.join('\n') + '\n';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function latestWrittenAt(records) {
|
|
243
|
+
return records
|
|
244
|
+
.map((record) => record.provenance?.written_at)
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.sort()
|
|
247
|
+
.at(-1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function renderSituation(snapshot) {
|
|
251
|
+
const entities = [...(snapshot.situationEntities ?? [])].sort((a, b) => (
|
|
252
|
+
a.display_name.localeCompare(b.display_name) || a.slug.localeCompare(b.slug)
|
|
253
|
+
));
|
|
254
|
+
const parts = [okfFrontmatter({
|
|
255
|
+
type: 'situation',
|
|
256
|
+
title: 'Situation',
|
|
257
|
+
timestamp: latestWrittenAt(entities),
|
|
258
|
+
})];
|
|
259
|
+
parts.push('');
|
|
260
|
+
parts.push('# Situation');
|
|
261
|
+
if (entities.length === 0) {
|
|
262
|
+
parts.push('');
|
|
263
|
+
parts.push('No situation entities recorded.');
|
|
264
|
+
}
|
|
265
|
+
for (const entity of entities) {
|
|
266
|
+
parts.push('');
|
|
267
|
+
parts.push(`## ${entity.display_name} (\`${entity.slug}\`)`);
|
|
268
|
+
parts.push('');
|
|
269
|
+
parts.push('### Facts');
|
|
270
|
+
parts.push('');
|
|
271
|
+
if (entity.facts.length === 0) {
|
|
272
|
+
parts.push('None.');
|
|
273
|
+
} else {
|
|
274
|
+
for (const fact of entity.facts) parts.push(renderAuditedFact(fact));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
parts.push('');
|
|
278
|
+
parts.push('### Owed');
|
|
279
|
+
parts.push('');
|
|
280
|
+
if (entity.owed.length === 0) {
|
|
281
|
+
parts.push('None.');
|
|
282
|
+
} else {
|
|
283
|
+
for (const owed of entity.owed) {
|
|
284
|
+
parts.push(`> **OWED ${owed.id} · ${owed.status}** ${owed.name}`);
|
|
285
|
+
parts.push(`> - why load-bearing: ${owed.why_load_bearing}`);
|
|
286
|
+
parts.push(`> - filled_by: ${owed.filled_by ?? '—'}`);
|
|
287
|
+
parts.push(`> - recorded at ${owed.provenance?.written_at ?? 'unknown'}`);
|
|
288
|
+
parts.push(...renderTraceLines(owed.trace, { prefix: '> - ' }));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
parts.push('');
|
|
293
|
+
parts.push('### Load links');
|
|
294
|
+
parts.push('');
|
|
295
|
+
if (entity.load_links.length === 0) {
|
|
296
|
+
parts.push('None.');
|
|
297
|
+
} else {
|
|
298
|
+
for (const link of entity.load_links) {
|
|
299
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — fact ${link.fact} carries ${link.carries}`);
|
|
300
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return parts.join('\n') + '\n';
|
|
305
|
+
}
|
|
306
|
+
|
|
97
307
|
function renderClaim(claim) {
|
|
98
308
|
const lines = [`- **${claim.id}** \`[${claim.grounding}]\`${claim.owner_locked ? ' `[owner-locked]`' : ''} ${claim.text}`];
|
|
99
309
|
if (Array.isArray(claim.supports) && claim.supports.length > 0) {
|
|
@@ -218,6 +428,7 @@ const EVENT_DETAIL_KEYS = [
|
|
|
218
428
|
'refs', 'rejected', 'conviction', 'trigger', 'open_joints', 'prediction',
|
|
219
429
|
'disposition', 'recall_verdict', 'attribution', 'prediction_ref',
|
|
220
430
|
'prediction_grade', 'reason', 'rank_change', 'elicitation',
|
|
431
|
+
'intent_id', 'tool', 'op', 'rests_on',
|
|
221
432
|
];
|
|
222
433
|
|
|
223
434
|
function renderLedger(snapshot) {
|
|
@@ -248,8 +459,118 @@ function renderLedger(snapshot) {
|
|
|
248
459
|
return parts.join('\n') + '\n';
|
|
249
460
|
}
|
|
250
461
|
|
|
462
|
+
function renderGoalClause(clause) {
|
|
463
|
+
const lines = [`- **${clause.id}** ${clause.text}`];
|
|
464
|
+
lines.push(` - channel: \`${clause.channel}\``);
|
|
465
|
+
if (clause.via !== undefined) lines.push(` - via: ${clause.via}`);
|
|
466
|
+
const citation = clause.elicitation;
|
|
467
|
+
lines.push(
|
|
468
|
+
` - elicitation: asked "${citation.asked}" (answered ${citation.answered_at}, ref ${citation.answer_ref})`,
|
|
469
|
+
);
|
|
470
|
+
lines.push(...renderTraceLines(clause.trace));
|
|
471
|
+
return lines.join('\n');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function tableCell(value) {
|
|
475
|
+
return String(value).replaceAll('|', '\\|').replaceAll('\n', ' ');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function renderGoalObjective(snapshot) {
|
|
479
|
+
const chain = snapshot.goalChain ?? [];
|
|
480
|
+
const current = chain.at(-1);
|
|
481
|
+
if (!current) {
|
|
482
|
+
return [
|
|
483
|
+
okfFrontmatter({ type: 'goal', title: 'Objective' }),
|
|
484
|
+
'',
|
|
485
|
+
'# Objective',
|
|
486
|
+
'',
|
|
487
|
+
'No goal recorded.',
|
|
488
|
+
].join('\n') + '\n';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const state = snapshot.goalState ?? { joints: [], load_links: [] };
|
|
492
|
+
const parts = [okfFrontmatter({
|
|
493
|
+
type: 'goal',
|
|
494
|
+
title: 'Objective',
|
|
495
|
+
timestamp: current.provenance?.written_at,
|
|
496
|
+
})];
|
|
497
|
+
parts.push('');
|
|
498
|
+
parts.push('# Objective');
|
|
499
|
+
parts.push('');
|
|
500
|
+
parts.push(`**Current version:** v${current.version}`);
|
|
501
|
+
const draft = (
|
|
502
|
+
['import', 'migration'].includes(current.provenance?.via)
|
|
503
|
+
&& !current.ratification
|
|
504
|
+
);
|
|
505
|
+
if (draft) {
|
|
506
|
+
parts.push('');
|
|
507
|
+
parts.push('**DRAFT HEALTH WARNING:** This imported/migrated goal is not owner-ratified.');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
parts.push('');
|
|
511
|
+
parts.push(`## Current clauses (v${current.version})`);
|
|
512
|
+
parts.push('');
|
|
513
|
+
for (const clause of current.clauses) parts.push(renderGoalClause(clause));
|
|
514
|
+
|
|
515
|
+
parts.push('');
|
|
516
|
+
parts.push('## Ratification citation');
|
|
517
|
+
parts.push('');
|
|
518
|
+
if (current.ratification) {
|
|
519
|
+
parts.push(`- asked "${current.ratification.asked}"`);
|
|
520
|
+
parts.push(`- quote: "${current.ratification.quote}"`);
|
|
521
|
+
parts.push(
|
|
522
|
+
`- answered ${current.ratification.answered_at}, ref ${current.ratification.answer_ref}`,
|
|
523
|
+
);
|
|
524
|
+
} else {
|
|
525
|
+
parts.push('None — imported/migrated draft.');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
parts.push('');
|
|
529
|
+
parts.push('## Goal joints');
|
|
530
|
+
parts.push('');
|
|
531
|
+
if ((state.joints ?? []).length === 0) {
|
|
532
|
+
parts.push('None.');
|
|
533
|
+
} else {
|
|
534
|
+
for (const link of state.joints) {
|
|
535
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — [${link.joint}](REGISTER.md#${link.joint})`);
|
|
536
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
parts.push('');
|
|
541
|
+
parts.push('## Load-link bill');
|
|
542
|
+
parts.push('');
|
|
543
|
+
if ((state.load_links ?? []).length === 0) {
|
|
544
|
+
parts.push('None.');
|
|
545
|
+
} else {
|
|
546
|
+
for (const link of state.load_links) {
|
|
547
|
+
const version = Number(/^v([1-9][0-9]*)#/.exec(link.clause)?.[1]);
|
|
548
|
+
const versionMark = version === current.version ? 'current version' : 'superseded version';
|
|
549
|
+
parts.push(`- **${link.id}** \`${link.removed ? 'removed' : 'active'}\` — ${link.clause} \`${versionMark}\` carries ${link.carries}`);
|
|
550
|
+
parts.push(...renderRecordedAndRemoval(link));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
parts.push('');
|
|
555
|
+
parts.push('## Trajectory');
|
|
556
|
+
parts.push('');
|
|
557
|
+
parts.push('| Version | Date | Provocation | Diff note | Wording fix |');
|
|
558
|
+
parts.push('|---|---|---|---|---|');
|
|
559
|
+
for (const version of chain) {
|
|
560
|
+
const provocation = version.provocation?.quote ?? 'unknown (migrated)';
|
|
561
|
+
const wordingFix = version.clauses.some((clause) => (clause.trace?.length ?? 0) > 0)
|
|
562
|
+
? 'yes'
|
|
563
|
+
: 'no';
|
|
564
|
+
parts.push(
|
|
565
|
+
`| v${version.version} | ${tableCell(version.provenance?.written_at ?? 'unknown')} | ${tableCell(provocation)} | ${tableCell(version.diff_note)} | ${wordingFix} |`,
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
return parts.join('\n') + '\n';
|
|
569
|
+
}
|
|
570
|
+
|
|
251
571
|
function renderObjective(snapshot) {
|
|
252
|
-
|
|
572
|
+
if (snapshot.goalCutoverComplete) return renderGoalObjective(snapshot);
|
|
573
|
+
const objective = (snapshot.positions ?? []).find((p) => p.slug === 'objective');
|
|
253
574
|
if (objective) return renderPosition(objective, snapshot);
|
|
254
575
|
return [
|
|
255
576
|
okfFrontmatter({ type: 'position', title: 'objective' }),
|
|
@@ -261,6 +582,9 @@ function renderObjective(snapshot) {
|
|
|
261
582
|
}
|
|
262
583
|
|
|
263
584
|
function renderIndex(snapshot) {
|
|
585
|
+
const positions = (snapshot.positions ?? []).filter(
|
|
586
|
+
(position) => !(snapshot.goalCutoverComplete && position.slug === 'objective'),
|
|
587
|
+
);
|
|
264
588
|
const parts = [okfFrontmatter({ type: null, title: 'Judgment canon' })];
|
|
265
589
|
parts.push('');
|
|
266
590
|
parts.push('# Judgment canon (generated)');
|
|
@@ -270,11 +594,11 @@ function renderIndex(snapshot) {
|
|
|
270
594
|
parts.push('- [Register](REGISTER.md)');
|
|
271
595
|
parts.push('- [Ledger](LEDGER.md)');
|
|
272
596
|
parts.push('- [Objective](OBJECTIVE.md)');
|
|
273
|
-
if (
|
|
597
|
+
if (positions.length) {
|
|
274
598
|
parts.push('');
|
|
275
599
|
parts.push('## Positions');
|
|
276
600
|
parts.push('');
|
|
277
|
-
for (const p of
|
|
601
|
+
for (const p of positions) {
|
|
278
602
|
parts.push(`- [${p.slug}](positions/${p.slug}.md) — ${p.status}`);
|
|
279
603
|
}
|
|
280
604
|
}
|
|
@@ -293,14 +617,40 @@ export function generateFromRecords(snapshot, { now } = {}) { // eslint-disable-
|
|
|
293
617
|
'docs/judgment/REGISTER.md': renderRegister(snapshot),
|
|
294
618
|
'docs/judgment/LEDGER.md': renderLedger(snapshot),
|
|
295
619
|
'docs/judgment/OBJECTIVE.md': renderObjective(snapshot),
|
|
620
|
+
'docs/judgment/SITUATION.md': renderSituation(snapshot),
|
|
296
621
|
'docs/judgment/index.md': renderIndex(snapshot),
|
|
297
622
|
};
|
|
298
|
-
for (const
|
|
623
|
+
for (const person of [...(snapshot.people ?? [])].sort((a, b) => a.slug.localeCompare(b.slug))) {
|
|
624
|
+
files[`docs/judgment/people/${person.slug}.md`] = renderPerson(person);
|
|
625
|
+
}
|
|
626
|
+
for (const position of (snapshot.positions ?? [])) {
|
|
627
|
+
if (snapshot.goalCutoverComplete && position.slug === 'objective') continue;
|
|
299
628
|
files[`docs/judgment/positions/${position.slug}.md`] = renderPosition(position, snapshot);
|
|
300
629
|
}
|
|
301
630
|
return files;
|
|
302
631
|
}
|
|
303
632
|
|
|
633
|
+
const MANAGED_MARKDOWN_DIRS = [
|
|
634
|
+
'docs/judgment/people',
|
|
635
|
+
'docs/judgment/positions',
|
|
636
|
+
];
|
|
637
|
+
|
|
638
|
+
function managedStaleProjections(cwd, files) {
|
|
639
|
+
const stale = [];
|
|
640
|
+
for (const relDir of MANAGED_MARKDOWN_DIRS) {
|
|
641
|
+
const dir = join(cwd, relDir);
|
|
642
|
+
if (!existsSync(dir)) continue;
|
|
643
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
644
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
645
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
646
|
+
for (const entry of entries) {
|
|
647
|
+
const rel = `${relDir}/${entry.name}`;
|
|
648
|
+
if (!(rel in files)) stale.push(rel);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return stale;
|
|
652
|
+
}
|
|
653
|
+
|
|
304
654
|
/**
|
|
305
655
|
* Regenerate every projection from records on disk. Atomic per file.
|
|
306
656
|
* @returns {{ files: string[] }} relative paths written
|
|
@@ -312,6 +662,9 @@ export function regenerateProjections(cwd) {
|
|
|
312
662
|
mkdirSync(join(path, '..'), { recursive: true });
|
|
313
663
|
atomicWrite(path, content);
|
|
314
664
|
}
|
|
665
|
+
for (const rel of managedStaleProjections(cwd, files)) {
|
|
666
|
+
unlinkSync(join(cwd, rel));
|
|
667
|
+
}
|
|
315
668
|
return { files: Object.keys(files) };
|
|
316
669
|
}
|
|
317
670
|
|
|
@@ -325,18 +678,14 @@ export function checkProjectionRoundtrip(cwd) {
|
|
|
325
678
|
const diffs = [];
|
|
326
679
|
for (const [rel, content] of Object.entries(files)) {
|
|
327
680
|
const path = join(cwd, rel);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// Orphan detection: a projection file the generator no longer emits (stale
|
|
332
|
-
// position projection, leftover from an older generator) is drift too.
|
|
333
|
-
const positionsDir = join(cwd, 'docs', 'judgment', 'positions');
|
|
334
|
-
if (existsSync(positionsDir)) {
|
|
335
|
-
for (const f of readdirSync(positionsDir)) {
|
|
336
|
-
if (!f.endsWith('.md')) continue;
|
|
337
|
-
const rel = `docs/judgment/positions/${f}`;
|
|
338
|
-
if (!(rel in files)) diffs.push(`${rel} (orphan — not emitted by the generator)`);
|
|
681
|
+
if (!existsSync(path)) {
|
|
682
|
+
diffs.push(`${rel} (missing)`);
|
|
683
|
+
continue;
|
|
339
684
|
}
|
|
685
|
+
if (readFileSync(path, 'utf8') !== content) diffs.push(`${rel} (drift)`);
|
|
686
|
+
}
|
|
687
|
+
for (const rel of managedStaleProjections(cwd, files)) {
|
|
688
|
+
diffs.push(`${rel} (orphan — not emitted by the generator)`);
|
|
340
689
|
}
|
|
341
690
|
return { fixedPoint: diffs.length === 0, diffs };
|
|
342
691
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combined drift verification for the judgment canon.
|
|
3
|
+
*
|
|
4
|
+
* The inventory, projection roundtrip, and record attestation are evaluated
|
|
5
|
+
* under the writer's advisory lock so a legitimate write cannot expose a
|
|
6
|
+
* partially-published canon to the verifier.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
lstatSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { join, posix } from 'node:path';
|
|
14
|
+
|
|
15
|
+
import { readManifest, recordFileSet, verifyRecords } from './judgment-attest.js';
|
|
16
|
+
import {
|
|
17
|
+
checkProjectionRoundtrip,
|
|
18
|
+
generateFromRecords,
|
|
19
|
+
loadSnapshot,
|
|
20
|
+
} from './judgment-gen.js';
|
|
21
|
+
import { withJudgmentLock } from './judgment-writer.js';
|
|
22
|
+
|
|
23
|
+
const JUDGMENT_REL = 'docs/judgment';
|
|
24
|
+
const MANIFEST_REL = `${JUDGMENT_REL}/.attest.json`;
|
|
25
|
+
const RECORDS_REL = `${JUDGMENT_REL}/records`;
|
|
26
|
+
const STRUCTURAL_DIRS = [
|
|
27
|
+
RECORDS_REL,
|
|
28
|
+
`${JUDGMENT_REL}/people`,
|
|
29
|
+
`${JUDGMENT_REL}/positions`,
|
|
30
|
+
];
|
|
31
|
+
// The legitimate depth-1 dirs under records/. Anything else there (e.g. a
|
|
32
|
+
// Bash-created `records/garbage/`) is drift. Deeper legitimate dirs — such as
|
|
33
|
+
// `records/positions/<slug>/` — are admitted by expectedTree's parent-walk over
|
|
34
|
+
// the real record files, so this list stays exact rather than blanket-exempting
|
|
35
|
+
// every nested path.
|
|
36
|
+
const RECORD_STRUCTURAL_DIRS = [
|
|
37
|
+
'goal', 'intents', 'joints', 'people', 'positions', 'predictions', 'situation',
|
|
38
|
+
].map((name) => `${RECORDS_REL}/${name}`);
|
|
39
|
+
|
|
40
|
+
function expectedTree(cwd) {
|
|
41
|
+
// Derive the expected projection set from the SAME snapshot builder that
|
|
42
|
+
// checkProjectionRoundtrip uses (loadSnapshot, exported from judgment-gen).
|
|
43
|
+
// Reconstructing it here would be a mirror that silently diverges into false
|
|
44
|
+
// tree drift the moment loadSnapshot changes.
|
|
45
|
+
const projections = Object.keys(generateFromRecords(loadSnapshot(cwd)));
|
|
46
|
+
const records = recordFileSet(cwd);
|
|
47
|
+
// The manifest is NOT in this set: it lives at .compose/judgment-attest.json,
|
|
48
|
+
// outside the tree being inventoried.
|
|
49
|
+
const files = new Set([...projections, ...records]);
|
|
50
|
+
const directories = new Set([JUDGMENT_REL, ...STRUCTURAL_DIRS, ...RECORD_STRUCTURAL_DIRS]);
|
|
51
|
+
|
|
52
|
+
for (const relPath of files) {
|
|
53
|
+
let parent = posix.dirname(relPath);
|
|
54
|
+
while (parent.startsWith(`${JUDGMENT_REL}/`)) {
|
|
55
|
+
directories.add(parent);
|
|
56
|
+
parent = posix.dirname(parent);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { files, directories };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function inventoryTree(cwd, expected) {
|
|
64
|
+
const root = join(cwd, ...JUDGMENT_REL.split('/'));
|
|
65
|
+
if (!existsSync(root)) return [];
|
|
66
|
+
|
|
67
|
+
const rootStat = lstatSync(root);
|
|
68
|
+
if (rootStat.isSymbolicLink()) {
|
|
69
|
+
return [{ path: JUDGMENT_REL, kind: 'symlink' }];
|
|
70
|
+
}
|
|
71
|
+
if (!rootStat.isDirectory()) {
|
|
72
|
+
return [{ path: JUDGMENT_REL, kind: 'unexpected' }];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const drift = [];
|
|
76
|
+
|
|
77
|
+
function walk(absDir, relDir) {
|
|
78
|
+
const entries = readdirSync(absDir)
|
|
79
|
+
.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
80
|
+
|
|
81
|
+
for (const name of entries) {
|
|
82
|
+
const absPath = join(absDir, name);
|
|
83
|
+
const relPath = `${relDir}/${name}`;
|
|
84
|
+
const stat = lstatSync(absPath);
|
|
85
|
+
|
|
86
|
+
if (stat.isSymbolicLink()) {
|
|
87
|
+
drift.push({ path: relPath, kind: 'symlink' });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (stat.isDirectory()) {
|
|
92
|
+
// An EMPTY unexpected dir is not reported: it holds no canon, and
|
|
93
|
+
// UndoLog.restore unlinks files without removing their directories, so a
|
|
94
|
+
// rolled-back op legitimately leaves e.g. an empty positions/<slug>/.
|
|
95
|
+
// Flagging those would false-RED on correct behaviour. A non-empty stray
|
|
96
|
+
// dir is still caught — every file inside it fails the file rule below.
|
|
97
|
+
if (!expected.directories.has(relPath) && readdirSync(absPath).length > 0) {
|
|
98
|
+
drift.push({ path: relPath, kind: 'unexpected' });
|
|
99
|
+
}
|
|
100
|
+
walk(absPath, relPath);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!stat.isFile() || !expected.files.has(relPath)) {
|
|
105
|
+
drift.push({ path: relPath, kind: 'unexpected' });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
walk(root, JUDGMENT_REL);
|
|
111
|
+
drift.sort((a, b) => {
|
|
112
|
+
if (a.path !== b.path) return a.path < b.path ? -1 : 1;
|
|
113
|
+
return a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0;
|
|
114
|
+
});
|
|
115
|
+
return drift;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* True when this project has no judgment canon at all — no `docs/judgment/`
|
|
120
|
+
* tree and no attestation baseline. There is nothing to verify, so verification
|
|
121
|
+
* must pass: most Compose projects never adopt the judgment layer, and a
|
|
122
|
+
* gate that fails them would break every pre-push and ship in those repos.
|
|
123
|
+
*
|
|
124
|
+
* This is NOT the same as a deleted canon. The baseline lives outside
|
|
125
|
+
* `docs/judgment/**` (at `.compose/judgment-attest.json`), so wiping the canon
|
|
126
|
+
* leaves the manifest behind — `hasNoCanon` is false and every record it
|
|
127
|
+
* remembers surfaces as `removed` drift.
|
|
128
|
+
*/
|
|
129
|
+
function hasNoCanon(cwd) {
|
|
130
|
+
return !existsSync(join(cwd, ...JUDGMENT_REL.split('/')))
|
|
131
|
+
&& readManifest(cwd) === null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function verifyJudgmentCanon(cwd) {
|
|
135
|
+
return withJudgmentLock(cwd, async () => {
|
|
136
|
+
if (hasNoCanon(cwd)) {
|
|
137
|
+
return { ok: true, treeDrift: [], projectionDrift: [], recordDrift: [] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const treeDrift = inventoryTree(cwd, expectedTree(cwd));
|
|
141
|
+
const projection = checkProjectionRoundtrip(cwd);
|
|
142
|
+
const records = verifyRecords(cwd);
|
|
143
|
+
const projectionDrift = projection.diffs;
|
|
144
|
+
const recordDrift = records.drift;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
ok: treeDrift.length === 0 && projection.fixedPoint && records.ok,
|
|
148
|
+
treeDrift,
|
|
149
|
+
projectionDrift,
|
|
150
|
+
recordDrift,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
}
|