@okfit/engine 0.9.4 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/errors.js +56 -6
- package/index.d.ts +409 -18
- package/index.js +6 -3
- package/package.json +18 -18
- package/query/ConceptQuery.js +102 -0
- package/render/query.js +104 -0
- package/render/verify.js +12 -1
- package/verify/run.js +127 -56
- package/verify/select.js +86 -0
- package/verify/splice.js +25 -1
- package/version.js +1 -1
package/verify/select.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { VerifySelectionError } from "../errors.js";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/verify/select.ts
|
|
5
|
+
/**
|
|
6
|
+
* Issue #138/#143: which concepts of `types` the actor may attest in one
|
|
7
|
+
* batch. A draft is unsettled and a deprecated concept is retired (#143), so
|
|
8
|
+
* both are skipped before the already-verified check.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
const selectAttestable = (bundle, types, actor) => {
|
|
13
|
+
const candidates = [];
|
|
14
|
+
const skipped = [];
|
|
15
|
+
for (const [id, concept] of bundle.concepts) {
|
|
16
|
+
if (!types.has(concept.frontmatter.type)) continue;
|
|
17
|
+
const status = concept.frontmatter.status;
|
|
18
|
+
if (status === "draft" || status === "deprecated") {
|
|
19
|
+
skipped.push({
|
|
20
|
+
id,
|
|
21
|
+
reason: status
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if ((concept.frontmatter.verified ?? []).some((entry) => entry.by === actor)) {
|
|
26
|
+
skipped.push({
|
|
27
|
+
id,
|
|
28
|
+
reason: "already-verified"
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
candidates.push(concept);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
candidates,
|
|
36
|
+
skipped
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const requireVerifiedTypes = (config) => new Set(Object.entries(config.types ?? {}).flatMap(([name, spec]) => spec.require_verified === true ? [name] : []));
|
|
40
|
+
/**
|
|
41
|
+
* Every concept whose type sets `require_verified` that `actor` has not
|
|
42
|
+
* attested: drafts included, deprecated concepts and the actor's own
|
|
43
|
+
* attestations excluded. Sorted by type, then id; grouping is the caller's.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
const selectPickerCandidates = (bundle, config, actor) => {
|
|
48
|
+
const types = requireVerifiedTypes(config);
|
|
49
|
+
const rows = [];
|
|
50
|
+
for (const [id, concept] of bundle.concepts) {
|
|
51
|
+
const { type, status, title, description } = concept.frontmatter;
|
|
52
|
+
if (!types.has(type) || status === "deprecated") continue;
|
|
53
|
+
const verified = concept.frontmatter.verified ?? [];
|
|
54
|
+
if (verified.some((entry) => entry.by === actor)) continue;
|
|
55
|
+
rows.push({
|
|
56
|
+
id,
|
|
57
|
+
type,
|
|
58
|
+
status: status === "draft" ? "draft" : "stable",
|
|
59
|
+
title: title ?? null,
|
|
60
|
+
description: description ?? null,
|
|
61
|
+
otherAttestations: verified.length
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return rows.toSorted((a, b) => a.type < b.type ? -1 : a.type > b.type ? 1 : a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The batch type-resolution rule shared by `runVerifyBatch` and any future
|
|
68
|
+
* interactive picker: explicit `types` must each be declared in
|
|
69
|
+
* `config.types` (else `VerifySelectionError` with reason `unknown-type`);
|
|
70
|
+
* an empty `types` selects every declared type whose `require_verified` is
|
|
71
|
+
* `true`.
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
const resolveBatchTypes = (config, types) => {
|
|
76
|
+
const declared = config.types ?? {};
|
|
77
|
+
if (types.length === 0) return Effect.succeed(requireVerifiedTypes(config));
|
|
78
|
+
for (const type of types) if (!Object.hasOwn(declared, type)) return Effect.fail(new VerifySelectionError({
|
|
79
|
+
reason: "unknown-type",
|
|
80
|
+
detail: type
|
|
81
|
+
}));
|
|
82
|
+
return Effect.succeed(new Set(types));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { resolveBatchTypes, selectAttestable, selectPickerCandidates };
|
package/verify/splice.js
CHANGED
|
@@ -128,6 +128,30 @@ const spliceGeneratedBlock = (target, fields, newline) => MarkdownEdit.make({
|
|
|
128
128
|
length: 0,
|
|
129
129
|
content: `generated:${newline} by: ${fields.by}${newline} at: ${fields.at}${newline} body_sha256: ${fields.bodySha256}${newline}`
|
|
130
130
|
});
|
|
131
|
+
/**
|
|
132
|
+
* Issue #185: `okfit verify --stable` splices `status` and `verified` in one
|
|
133
|
+
* write. When `status` is absent it is inserted after `title:` (else `type:`),
|
|
134
|
+
* and when `verified` is absent it is appended as the last key, so both land
|
|
135
|
+
* on the same offset when that anchor is the last key. `MarkdownEdit.applyAll`
|
|
136
|
+
* rejects two zero-length inserts at one offset, the same case
|
|
137
|
+
* `spliceGeneratedFields` merges, so this folds them into one edit, input
|
|
138
|
+
* order preserved.
|
|
139
|
+
*
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
const mergeSameOffset = (edits) => {
|
|
143
|
+
const out = [];
|
|
144
|
+
for (const edit of edits) {
|
|
145
|
+
const previous = out.at(-1);
|
|
146
|
+
if (previous !== void 0 && previous.length === 0 && edit.length === 0 && previous.offset === edit.offset) out[out.length - 1] = MarkdownEdit.make({
|
|
147
|
+
offset: edit.offset,
|
|
148
|
+
length: 0,
|
|
149
|
+
content: previous.content + edit.content
|
|
150
|
+
});
|
|
151
|
+
else out.push(edit);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
};
|
|
131
155
|
|
|
132
156
|
//#endregion
|
|
133
|
-
export { splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields, spliceTopLevelScalar };
|
|
157
|
+
export { mergeSameOffset, splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields, spliceTopLevelScalar };
|