@okfit/engine 0.6.0 → 0.7.1
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 +35 -1
- package/index.d.ts +262 -31
- package/index.js +7 -5
- package/init/scaffold.js +1 -19
- package/package.json +3 -4
- package/render/context.js +7 -0
- package/render/distribution.js +11 -0
- package/render/graph.js +6 -0
- package/render/json.js +42 -10
- package/render/stale.js +6 -0
- package/render/sync.js +6 -0
- package/render/verify.js +51 -1
- package/sync/generated.js +55 -28
- package/sync/log.js +27 -6
- package/sync/run.js +64 -16
- package/verify/locate.js +24 -1
- package/verify/run.js +103 -27
- package/verify/splice.js +13 -1
- package/version.js +17 -0
package/verify/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VerifyConceptNotFoundError, VerifyUnsupportedFrontmatterError } from "../errors.js";
|
|
1
|
+
import { VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "../errors.js";
|
|
2
2
|
import { documentNewline, locate, stripBom } from "./locate.js";
|
|
3
3
|
import { splice } from "./splice.js";
|
|
4
4
|
import { Bundle, ConceptId, Timestamp } from "@okfit/core";
|
|
@@ -23,6 +23,39 @@ const UNDECODABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
23
23
|
"frontmatter-missing",
|
|
24
24
|
"type-missing"
|
|
25
25
|
]);
|
|
26
|
+
/** Steps 9-14 of runVerify: read, locate (fail closed), splice; no write. */
|
|
27
|
+
const prepareVerify = Effect.fn("okfit/verify/prepareVerify")(function* (bundle, concept, actor, at) {
|
|
28
|
+
const fs = yield* FileSystem.FileSystem;
|
|
29
|
+
const absolutePath = (yield* Path.Path).join(bundle.root, concept.path);
|
|
30
|
+
const source = yield* fs.readFileString(absolutePath);
|
|
31
|
+
const { text, bom } = stripBom(source);
|
|
32
|
+
const located = yield* locate(text);
|
|
33
|
+
if (located._tag === "unsupported") return yield* new VerifyUnsupportedFrontmatterError({
|
|
34
|
+
id: concept.id,
|
|
35
|
+
shape: located.shape
|
|
36
|
+
});
|
|
37
|
+
const priorAt = (concept.frontmatter.verified ?? []).filter((entry) => entry.by === actor).map((entry) => Schema.encodeSync(Timestamp)(entry.at));
|
|
38
|
+
const edit = splice(located, {
|
|
39
|
+
by: actor,
|
|
40
|
+
at
|
|
41
|
+
}, documentNewline(text));
|
|
42
|
+
return {
|
|
43
|
+
absolutePath,
|
|
44
|
+
finalText: bom + MarkdownEdit.applyAll(text, [edit]),
|
|
45
|
+
fragment: edit.content,
|
|
46
|
+
priorAt
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
/** The I4/V-16 atomic write: real path, temp beside it, mode preserved, rename. */
|
|
50
|
+
const writeVerified = Effect.fn("okfit/verify/writeVerified")(function* (absolutePath, finalText) {
|
|
51
|
+
const fs = yield* FileSystem.FileSystem;
|
|
52
|
+
const target = yield* fs.realPath(absolutePath);
|
|
53
|
+
const tempPath = `${target}.okfit-verify.tmp`;
|
|
54
|
+
const original = yield* fs.stat(target);
|
|
55
|
+
yield* fs.writeFileString(tempPath, finalText);
|
|
56
|
+
yield* fs.chmod(tempPath, original.mode);
|
|
57
|
+
yield* fs.rename(tempPath, target).pipe(Effect.onError(() => fs.remove(tempPath, { force: true }).pipe(Effect.ignore)));
|
|
58
|
+
});
|
|
26
59
|
/**
|
|
27
60
|
* Contract §2.3 steps 6–15. Loads the bundle, resolves the id, resolves the
|
|
28
61
|
* human actor from git, splices exactly one entry into the concept's
|
|
@@ -34,8 +67,6 @@ const UNDECODABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
34
67
|
* @internal
|
|
35
68
|
*/
|
|
36
69
|
const runVerify = Effect.fn("okfit/verify/runVerify")(function* (options) {
|
|
37
|
-
const fs = yield* FileSystem.FileSystem;
|
|
38
|
-
const path = yield* Path.Path;
|
|
39
70
|
const bundle = yield* Bundle.load({ root: options.bundleRoot });
|
|
40
71
|
const normalized = ConceptId.normalize(options.id);
|
|
41
72
|
if (Option.isNone(normalized)) return yield* new VerifyConceptNotFoundError({
|
|
@@ -67,40 +98,85 @@ const runVerify = Effect.fn("okfit/verify/runVerify")(function* (options) {
|
|
|
67
98
|
cwd: options.projectRoot,
|
|
68
99
|
config: options.config
|
|
69
100
|
});
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (located._tag === "unsupported") return yield* new VerifyUnsupportedFrontmatterError({
|
|
101
|
+
const at = Schema.encodeSync(Timestamp)(options.at);
|
|
102
|
+
const prepared = yield* prepareVerify(bundle, concept, actor, at);
|
|
103
|
+
if (!options.dryRun) yield* writeVerified(prepared.absolutePath, prepared.finalText);
|
|
104
|
+
return {
|
|
75
105
|
id,
|
|
76
|
-
|
|
106
|
+
bundleRoot: bundle.root,
|
|
107
|
+
conceptPath: concept.path,
|
|
108
|
+
by: actor,
|
|
109
|
+
at,
|
|
110
|
+
priorAt: prepared.priorAt,
|
|
111
|
+
dryRun: options.dryRun,
|
|
112
|
+
fragment: prepared.fragment
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* Issue #138. Attests every concept selected by `options.types` (or, when
|
|
117
|
+
* empty, every type whose declaration sets `require_verified = true`) that
|
|
118
|
+
* is not a draft and does not already carry a `verified` entry by the
|
|
119
|
+
* resolved actor. Every candidate is located FIRST -- an unsupported
|
|
120
|
+
* `verified` shape on any one of them fails the whole batch with nothing
|
|
121
|
+
* written, mirroring `runVerify`'s own fail-closed rule at batch scale.
|
|
122
|
+
*
|
|
123
|
+
* @public
|
|
124
|
+
*/
|
|
125
|
+
const runVerifyBatch = Effect.fn("okfit/verify/runVerifyBatch")(function* (options) {
|
|
126
|
+
const bundle = yield* Bundle.load({ root: options.bundleRoot });
|
|
127
|
+
const declared = options.config.types ?? {};
|
|
128
|
+
let types;
|
|
129
|
+
if (options.types.length > 0) {
|
|
130
|
+
for (const type of options.types) if (!Object.hasOwn(declared, type)) return yield* new VerifySelectionError({
|
|
131
|
+
reason: "unknown-type",
|
|
132
|
+
detail: type
|
|
133
|
+
});
|
|
134
|
+
types = new Set(options.types);
|
|
135
|
+
} else types = new Set(Object.entries(declared).flatMap(([name, spec]) => spec.require_verified === true ? [name] : []));
|
|
136
|
+
const actor = yield* Derivation.generatedBy({
|
|
137
|
+
writer: "human",
|
|
138
|
+
cwd: options.projectRoot,
|
|
139
|
+
config: options.config
|
|
77
140
|
});
|
|
78
141
|
const at = Schema.encodeSync(Timestamp)(options.at);
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
142
|
+
const skipped = [];
|
|
143
|
+
const prepared = [];
|
|
144
|
+
for (const [id, concept] of bundle.concepts) {
|
|
145
|
+
if (!types.has(concept.frontmatter.type)) continue;
|
|
146
|
+
if (concept.frontmatter.status === "draft") {
|
|
147
|
+
skipped.push({
|
|
148
|
+
id,
|
|
149
|
+
reason: "draft"
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if ((concept.frontmatter.verified ?? []).some((entry) => entry.by === actor)) {
|
|
154
|
+
skipped.push({
|
|
155
|
+
id,
|
|
156
|
+
reason: "already-verified"
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
prepared.push({
|
|
161
|
+
id,
|
|
162
|
+
conceptPath: concept.path,
|
|
163
|
+
...yield* prepareVerify(bundle, concept, actor, at)
|
|
164
|
+
});
|
|
92
165
|
}
|
|
166
|
+
if (!options.dryRun) for (const entry of prepared) yield* writeVerified(entry.absolutePath, entry.finalText);
|
|
93
167
|
return {
|
|
94
|
-
id,
|
|
95
168
|
bundleRoot: bundle.root,
|
|
96
|
-
conceptPath: concept.path,
|
|
97
169
|
by: actor,
|
|
98
170
|
at,
|
|
99
|
-
priorAt,
|
|
100
171
|
dryRun: options.dryRun,
|
|
101
|
-
|
|
172
|
+
verified: prepared.map(({ id, conceptPath, fragment }) => ({
|
|
173
|
+
id,
|
|
174
|
+
conceptPath,
|
|
175
|
+
fragment
|
|
176
|
+
})),
|
|
177
|
+
skipped
|
|
102
178
|
};
|
|
103
179
|
});
|
|
104
180
|
|
|
105
181
|
//#endregion
|
|
106
|
-
export { runVerify };
|
|
182
|
+
export { runVerify, runVerifyBatch };
|
package/verify/splice.js
CHANGED
|
@@ -92,6 +92,18 @@ const spliceGeneratedFields = (atTarget, bodySha256Target, encodedAt, bodySha256
|
|
|
92
92
|
})];
|
|
93
93
|
return [spliceGenerated(atTarget, encodedAt, newline, "at"), spliceGenerated(bodySha256Target, bodySha256, newline, "body_sha256")];
|
|
94
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* A whole `generated:` block mapping, appended as the LAST top-level key
|
|
97
|
+
* (issue #73) — block style, matching how this repository writes every
|
|
98
|
+
* `generated:` and how `splice`'s own `absent` case writes `verified:`.
|
|
99
|
+
*
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
const spliceGeneratedBlock = (target, fields, newline) => MarkdownEdit.make({
|
|
103
|
+
offset: target.insertAt,
|
|
104
|
+
length: 0,
|
|
105
|
+
content: `generated:${newline} by: ${fields.by}${newline} at: ${fields.at}${newline} body_sha256: ${fields.bodySha256}${newline}`
|
|
106
|
+
});
|
|
95
107
|
|
|
96
108
|
//#endregion
|
|
97
|
-
export { splice, spliceGenerated, spliceGeneratedFields };
|
|
109
|
+
export { splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields };
|
package/version.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/version.ts
|
|
2
|
+
/**
|
|
3
|
+
* The version this engine reports as `engine_version` in every JSON
|
|
4
|
+
* envelope. `@savvy-web/bundler` replaces `process.env.__PACKAGE_VERSION__`
|
|
5
|
+
* with this package's own version at build time (K-32), so a release can
|
|
6
|
+
* never desync from the printed version. `"0.0.0"` is the unbuilt-source
|
|
7
|
+
* fallback and reads as dev mode. This is the ONE `process` read this
|
|
8
|
+
* package's own `boundaries.test.ts` allowlists — see that file and this
|
|
9
|
+
* package's `CLAUDE.md` for why it is safe here even though every other
|
|
10
|
+
* `process` read is forbidden.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
const ENGINE_VERSION = "0.7.1";
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ENGINE_VERSION };
|