@kontourai/flow-agents 1.2.0 → 1.4.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/.github/workflows/ci.yml +6 -1
- package/.github/workflows/kit-gates-demo.yml +6 -2
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +30 -0
- package/agents/dev.json +1 -1
- package/agents/tool-planner.json +1 -1
- package/build/src/cli/console-learning-projection.d.ts +1 -0
- package/build/src/cli/effective-backlog-settings.d.ts +1 -0
- package/build/src/cli/fixture-retirement-audit.d.ts +2 -0
- package/build/src/cli/init.d.ts +17 -0
- package/build/src/cli/kit.d.ts +1 -0
- package/build/src/cli/promote-workflow-artifact.d.ts +1 -0
- package/build/src/cli/publish-change-helper.d.ts +1 -0
- package/build/src/cli/pull-work-provider.d.ts +1 -0
- package/build/src/cli/runtime-adapter.d.ts +1 -0
- package/build/src/cli/telemetry-doctor.d.ts +1 -0
- package/build/src/cli/usage-feedback.d.ts +1 -0
- package/build/src/cli/utterance-check.d.ts +1 -0
- package/build/src/cli/validate-hook-influence.d.ts +1 -0
- package/build/src/cli/validate-source-tree.d.ts +1 -0
- package/build/src/cli/validate-workflow-artifacts.d.ts +2 -0
- package/build/src/cli/veritas-governance.d.ts +1 -0
- package/build/src/cli/workflow-artifact-cleanup-audit.d.ts +1 -0
- package/build/src/cli/workflow-sidecar.d.ts +32 -0
- package/build/src/cli/workflow-sidecar.js +119 -22
- package/build/src/cli.d.ts +2 -0
- package/build/src/flow-kit/validate.d.ts +81 -0
- package/build/src/flow-kit/validate.js +32 -1
- package/build/src/index.d.ts +5 -0
- package/build/src/index.js +36 -0
- package/build/src/lib/args.d.ts +8 -0
- package/build/src/lib/fs.d.ts +7 -0
- package/build/src/lib/workflow-learning-projection.d.ts +132 -0
- package/build/src/runtime-adapters.d.ts +18 -0
- package/build/src/tools/build-universal-bundles.d.ts +2 -0
- package/build/src/tools/build-universal-bundles.js +14 -0
- package/build/src/tools/common.d.ts +9 -0
- package/build/src/tools/filter-installed-packs.d.ts +2 -0
- package/build/src/tools/generate-context-map.d.ts +2 -0
- package/build/src/tools/validate-package.d.ts +2 -0
- package/build/src/tools/validate-source-tree.d.ts +2 -0
- package/console.telemetry.json +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +7 -7
- package/docs/developer-architecture.md +14 -0
- package/docs/kit-authoring-guide.md +99 -6
- package/docs/operating-layers.md +2 -2
- package/docs/spec/runtime-hook-surface.md +16 -1
- package/docs/veritas-integration.md +4 -4
- package/docs/workflow-eval-strategy.md +2 -2
- package/docs/workflow-usage-guide.md +1 -1
- package/evals/acceptance/test_opencode_harness.sh +18 -10
- package/evals/acceptance/test_pi_harness.sh +10 -6
- package/evals/ci/run-baseline.sh +1 -1
- package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/flows/runtime.flow.json +4 -4
- package/evals/fixtures/flow-kit-repository/valid-local-kit/flows/review.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/k0-flows-only/flows/review.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/flows/build.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/flows/synthesize.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/third-party-extension/flows/review.flow.json +4 -4
- package/evals/fixtures/surface-trust/accepted-claim-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/artifact-absent.json +2 -2
- package/evals/fixtures/surface-trust/integrity-mismatch-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/missing-authority-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/provider-absent.json +2 -2
- package/evals/fixtures/surface-trust/rejected-claim-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/stale-claim-trust-snapshot.json +2 -2
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_goal_fit_hook.sh +144 -0
- package/evals/integration/test_hook_category_behaviors.sh +14 -0
- package/evals/integration/test_kit_conformance_levels.sh +55 -1
- package/evals/integration/test_workflow_sidecar_writer.sh +9 -9
- package/evals/run.sh +2 -0
- package/evals/static/test_library_exports.sh +85 -0
- package/evals/static/test_package.sh +3 -3
- package/evals/static/test_universal_bundles.sh +15 -0
- package/evals/static/test_workflow_skills.sh +4 -4
- package/kits/builder/flows/build.flow.json +48 -48
- package/kits/builder/flows/shape.flow.json +36 -36
- package/kits/knowledge/adapters/obsidian-store/index.js +137 -26
- package/kits/knowledge/evals/contract-suite/suite.test.js +90 -0
- package/kits/knowledge/flows/compile.flow.json +12 -12
- package/kits/knowledge/flows/consolidate.flow.json +16 -16
- package/kits/knowledge/flows/ingest.flow.json +12 -12
- package/kits/knowledge/flows/retire.flow.json +16 -16
- package/kits/knowledge/flows/store-contract.flow.json +12 -12
- package/kits/knowledge/flows/synthesize.flow.json +16 -16
- package/kits/release-evidence/flows/release-evidence.flow.json +3 -3
- package/package.json +14 -2
- package/schemas/workflow-evidence.schema.json +2 -1
- package/scripts/hooks/stop-goal-fit.js +66 -18
- package/src/cli/workflow-sidecar.ts +101 -21
- package/src/flow-kit/validate.ts +55 -1
- package/src/index.ts +53 -0
- package/src/tools/build-universal-bundles.ts +14 -0
- package/tsconfig.json +1 -0
|
@@ -11,7 +11,25 @@
|
|
|
11
11
|
* graph-index.json (link graph — required by suite §13)
|
|
12
12
|
* .graph-index.json (path index — id→{path,archived})
|
|
13
13
|
*
|
|
14
|
-
* Frontmatter carries
|
|
14
|
+
* Frontmatter carries all contract fields EXCEPT `body`.
|
|
15
|
+
* The rendered note body below the frontmatter fence IS the canonical body
|
|
16
|
+
* storage — body is parsed back from the rendered markdown on read.
|
|
17
|
+
*
|
|
18
|
+
* Body render/parse inverse:
|
|
19
|
+
* ALL types → an invisible sentinel `<!-- kit:body-end -->` is emitted on
|
|
20
|
+
* its own line immediately after the body text. Obsidian renders
|
|
21
|
+
* HTML comments as nothing so it is invisible to vault readers.
|
|
22
|
+
* On read, everything before the sentinel (trimmed) is the body.
|
|
23
|
+
* There is NO body-content constraint — bodies may freely contain
|
|
24
|
+
* any markdown, including `## Sources`, `## Related`, etc.
|
|
25
|
+
* raw → body is additionally wrapped in a callout block for readability:
|
|
26
|
+
* > [!note]- Raw Notes
|
|
27
|
+
* > {body lines}
|
|
28
|
+
* The sentinel follows the callout block.
|
|
29
|
+
* Parse: strip callout wrapper, then split on sentinel.
|
|
30
|
+
* others → body verbatim, then sentinel, then optional ## sections.
|
|
31
|
+
* Parse: everything before the sentinel, trimmed.
|
|
32
|
+
*
|
|
15
33
|
* Category dots map to directory segments: "eng.api" → "eng/api/".
|
|
16
34
|
* Filename: slugified title, collision-suffixed (-2, -3, …).
|
|
17
35
|
* Superseded records MOVE to archive/ (supersede-not-delete invariant).
|
|
@@ -41,6 +59,19 @@ import {
|
|
|
41
59
|
validateCategory,
|
|
42
60
|
} from "../shared/codec.js";
|
|
43
61
|
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Body render / parse constants
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
// Invisible sentinel emitted between the body and the generated structural
|
|
67
|
+
// sections in every rendered note. Obsidian renders HTML comments as nothing
|
|
68
|
+
// so vault readers never see it. On read, everything before this sentinel
|
|
69
|
+
// (trimmed) is the canonical body — no heading-text collision possible.
|
|
70
|
+
const BODY_END_SENTINEL = "<!-- kit:body-end -->";
|
|
71
|
+
|
|
72
|
+
// Callout header line emitted for raw records
|
|
73
|
+
const RAW_CALLOUT_HEADER = "> [!note]- Raw Notes";
|
|
74
|
+
|
|
44
75
|
// ---------------------------------------------------------------------------
|
|
45
76
|
// ObsidianKnowledgeStore
|
|
46
77
|
// ---------------------------------------------------------------------------
|
|
@@ -128,6 +159,67 @@ export class ObsidianKnowledgeStore {
|
|
|
128
159
|
fs.writeFileSync(this._pathIndexPath, JSON.stringify(index, null, 2) + "\n", "utf8");
|
|
129
160
|
}
|
|
130
161
|
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
// Body render / parse (render+parse must be an exact inverse for body)
|
|
164
|
+
// -------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Parse the record body back from the rendered Obsidian markdown section
|
|
168
|
+
* (the text after the frontmatter fence).
|
|
169
|
+
*
|
|
170
|
+
* All record types use the invisible sentinel BODY_END_SENTINEL
|
|
171
|
+
* (`<!-- kit:body-end -->`) as the exclusive delimiter between the body
|
|
172
|
+
* and any appended structural sections. The sentinel appears on its own
|
|
173
|
+
* line immediately after the body text; everything before it (trimmed) is
|
|
174
|
+
* the canonical body. This is collision-proof: body text may freely contain
|
|
175
|
+
* any markdown, including lines like `## Sources` or `## Related`.
|
|
176
|
+
*
|
|
177
|
+
* raw records additionally wrap the body in a callout block for human
|
|
178
|
+
* readability. The sentinel appears after the closing callout line.
|
|
179
|
+
* Parse: strip the callout (header line + `> ` prefix), then split on
|
|
180
|
+
* the sentinel to recover the exact original body.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} type - record type
|
|
183
|
+
* @param {string} renderedText - text after the frontmatter fence (raw markdown)
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
_parseBodyFromRendered(type, renderedText) {
|
|
187
|
+
// Split on the sentinel first — the body is always everything before it,
|
|
188
|
+
// regardless of type. For raw records we still need to strip the callout
|
|
189
|
+
// wrapper from within that portion.
|
|
190
|
+
const sentinelIdx = renderedText.indexOf(BODY_END_SENTINEL);
|
|
191
|
+
const bodySection = sentinelIdx === -1
|
|
192
|
+
? renderedText // sentinel missing (legacy note): fall back to full text
|
|
193
|
+
: renderedText.slice(0, sentinelIdx);
|
|
194
|
+
|
|
195
|
+
if (type === "raw") {
|
|
196
|
+
// bodySection is the callout block:
|
|
197
|
+
// > [!note]- Raw Notes
|
|
198
|
+
// > line1
|
|
199
|
+
// > line2
|
|
200
|
+
// Strip the header line and the `> ` prefix from each body line.
|
|
201
|
+
const lines = bodySection.split("\n");
|
|
202
|
+
// First line is the callout header — skip it
|
|
203
|
+
const bodyLines = [];
|
|
204
|
+
for (let i = 1; i < lines.length; i++) {
|
|
205
|
+
const line = lines[i];
|
|
206
|
+
if (line.startsWith("> ")) {
|
|
207
|
+
bodyLines.push(line.slice(2));
|
|
208
|
+
} else if (line === ">") {
|
|
209
|
+
bodyLines.push("");
|
|
210
|
+
} else {
|
|
211
|
+
// Safety: non-callout line stops the block (should not occur in
|
|
212
|
+
// well-formed notes written by this adapter).
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return bodyLines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// compiled / concept / snapshot / person: body is verbatim before sentinel
|
|
220
|
+
return bodySection.trimEnd();
|
|
221
|
+
}
|
|
222
|
+
|
|
131
223
|
// -------------------------------------------------------------------------
|
|
132
224
|
// Record I/O
|
|
133
225
|
// -------------------------------------------------------------------------
|
|
@@ -142,17 +234,19 @@ export class ObsidianKnowledgeStore {
|
|
|
142
234
|
}
|
|
143
235
|
|
|
144
236
|
/**
|
|
145
|
-
* Read a record by id. Returns the full record object
|
|
146
|
-
*
|
|
237
|
+
* Read a record by id. Returns the full record object with `body` parsed
|
|
238
|
+
* from the rendered note body (the canonical storage), or null if not found.
|
|
147
239
|
*/
|
|
148
240
|
_readRecord(id, pathIndex) {
|
|
149
241
|
const absPath = this._getAbsPath(id, pathIndex);
|
|
150
242
|
if (!absPath || !fs.existsSync(absPath)) return null;
|
|
151
243
|
const text = fs.readFileSync(absPath, "utf8");
|
|
152
|
-
const { meta } = parseMarkdown(text);
|
|
244
|
+
const { meta, body: renderedText } = parseMarkdown(text);
|
|
153
245
|
if (!meta.id) return null;
|
|
154
|
-
// body
|
|
155
|
-
|
|
246
|
+
// Reconstruct the record body from the rendered markdown section.
|
|
247
|
+
// `meta` no longer contains `body` — the rendered text is the source of truth.
|
|
248
|
+
const body = this._parseBodyFromRendered(meta.type, renderedText);
|
|
249
|
+
return { ...meta, body };
|
|
156
250
|
}
|
|
157
251
|
|
|
158
252
|
/**
|
|
@@ -160,8 +254,8 @@ export class ObsidianKnowledgeStore {
|
|
|
160
254
|
*
|
|
161
255
|
* - On first write: computes slug path, registers in path index.
|
|
162
256
|
* - On update with title change: renames file (old deleted, new path used).
|
|
163
|
-
* - All contract fields stored in YAML frontmatter.
|
|
164
|
-
* -
|
|
257
|
+
* - All contract fields EXCEPT `body` stored in YAML frontmatter.
|
|
258
|
+
* - `body` is encoded in the rendered Obsidian markdown section below the fence.
|
|
165
259
|
*/
|
|
166
260
|
_writeRecord(record, pathIndex) {
|
|
167
261
|
const ownedIndex = !pathIndex;
|
|
@@ -195,7 +289,7 @@ export class ObsidianKnowledgeStore {
|
|
|
195
289
|
targetRelPath = newRelPath;
|
|
196
290
|
}
|
|
197
291
|
|
|
198
|
-
//
|
|
292
|
+
// Frontmatter: all contract fields EXCEPT `body` (body is in the rendered section).
|
|
199
293
|
const { body, ...frontmatterFields } = record;
|
|
200
294
|
// Derived dimension fields (territory: east, customer: acme, ...) from
|
|
201
295
|
// category segments after the domain segment — presentation-only, never
|
|
@@ -205,8 +299,7 @@ export class ObsidianKnowledgeStore {
|
|
|
205
299
|
const segs = record.category.split(".").slice(1);
|
|
206
300
|
this._dimensions.forEach((name, i) => { if (segs[i]) derived[name] = segs[i]; });
|
|
207
301
|
}
|
|
208
|
-
|
|
209
|
-
const frontmatter = { ...frontmatterFields, ...derived, body };
|
|
302
|
+
const frontmatter = { ...frontmatterFields, ...derived };
|
|
210
303
|
const obsidianBody = this._renderObsidianBody(record, pathIndex);
|
|
211
304
|
const text = `---\n${serializeYaml(frontmatter)}\n---\n\n${obsidianBody}`;
|
|
212
305
|
|
|
@@ -262,7 +355,22 @@ export class ObsidianKnowledgeStore {
|
|
|
262
355
|
|
|
263
356
|
/**
|
|
264
357
|
* Render the human-readable Obsidian body below the frontmatter fence.
|
|
265
|
-
*
|
|
358
|
+
* The body content IS the canonical storage — parsing this rendered text
|
|
359
|
+
* back via _parseBodyFromRendered() must return the original body exactly.
|
|
360
|
+
*
|
|
361
|
+
* render/parse contract:
|
|
362
|
+
* ALL types → emit BODY_END_SENTINEL (<!-- kit:body-end -->) on its own
|
|
363
|
+
* line immediately after the body content and before any
|
|
364
|
+
* generated ## sections. The sentinel is invisible in Obsidian
|
|
365
|
+
* (HTML comments are not rendered) and cannot be confused with
|
|
366
|
+
* any user-supplied body text.
|
|
367
|
+
*
|
|
368
|
+
* raw → body is wrapped in a callout block for human readability:
|
|
369
|
+
* > [!note]- Raw Notes
|
|
370
|
+
* > {body lines}
|
|
371
|
+
* The sentinel immediately follows the callout block.
|
|
372
|
+
*
|
|
373
|
+
* others → body verbatim, then sentinel, then optional ## sections.
|
|
266
374
|
*/
|
|
267
375
|
_renderObsidianBody(record, pathIndex) {
|
|
268
376
|
const links = record.links || [];
|
|
@@ -282,40 +390,43 @@ export class ObsidianKnowledgeStore {
|
|
|
282
390
|
.filter(Boolean)
|
|
283
391
|
.join(", ");
|
|
284
392
|
|
|
285
|
-
|
|
286
|
-
|
|
393
|
+
// bodyPart: the portion of the rendered note containing the record body
|
|
394
|
+
// (possibly wrapped in a callout for raw records).
|
|
395
|
+
let bodyPart;
|
|
287
396
|
if (record.type === "raw") {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// then Appears In backlinks to sources, then Related.
|
|
292
|
-
sections.push(record.body);
|
|
397
|
+
// Callout block: header line + body lines each prefixed with `> `.
|
|
398
|
+
// _parseBodyFromRendered strips these back to recover the exact body.
|
|
399
|
+
bodyPart = `${RAW_CALLOUT_HEADER}\n> ${record.body.replace(/\n/g, "\n> ")}`;
|
|
293
400
|
} else {
|
|
294
|
-
// compiled / concept / snapshot
|
|
295
|
-
|
|
401
|
+
// compiled / concept / snapshot / person: body verbatim
|
|
402
|
+
bodyPart = record.body;
|
|
296
403
|
}
|
|
297
404
|
|
|
405
|
+
// Sentinel immediately follows the body part, on its own line.
|
|
406
|
+
// Everything between the frontmatter fence and this sentinel is the body.
|
|
407
|
+
const parts = [`${bodyPart}\n${BODY_END_SENTINEL}`];
|
|
408
|
+
|
|
298
409
|
if (sourceLinks.length > 0) {
|
|
299
|
-
|
|
410
|
+
parts.push(`## Sources\n\n${wikiLinks(sourceLinks)}`);
|
|
300
411
|
}
|
|
301
412
|
|
|
302
413
|
// Person cards: render appears-in links (backlinks to raw+compiled records)
|
|
303
414
|
const appearsInLinks = links.filter((l) => l.kind === "appears-in");
|
|
304
415
|
if (record.type === "person" && appearsInLinks.length > 0) {
|
|
305
|
-
|
|
416
|
+
parts.push(`## Appears In\n\n${wikiLinks(appearsInLinks)}`);
|
|
306
417
|
}
|
|
307
418
|
|
|
308
419
|
// Compiled/person records: render people links (links to person cards)
|
|
309
420
|
const peopleLinks = links.filter((l) => l.kind === "person");
|
|
310
421
|
if (peopleLinks.length > 0) {
|
|
311
|
-
|
|
422
|
+
parts.push(`## People\n\n${wikiLinks(peopleLinks)}`);
|
|
312
423
|
}
|
|
313
424
|
|
|
314
425
|
if (relatedLinks.length > 0) {
|
|
315
|
-
|
|
426
|
+
parts.push(`## Related\n\n${wikiLinks(relatedLinks)}`);
|
|
316
427
|
}
|
|
317
428
|
|
|
318
|
-
return
|
|
429
|
+
return parts.join("\n\n");
|
|
319
430
|
}
|
|
320
431
|
|
|
321
432
|
// -------------------------------------------------------------------------
|
|
@@ -672,4 +672,94 @@ describe("Knowledge Kit Store Contract Suite", () => {
|
|
|
672
672
|
assert.ok(rev, "reverse index has the backlink");
|
|
673
673
|
});
|
|
674
674
|
});
|
|
675
|
+
|
|
676
|
+
// -----------------------------------------------------------------------
|
|
677
|
+
// §14 body round-trip — collision-proof delimiter regression (AC: sentinel)
|
|
678
|
+
// -----------------------------------------------------------------------
|
|
679
|
+
describe("body round-trip: sentinel delimiter (regression for heading-collision bug)", () => {
|
|
680
|
+
let dir, store;
|
|
681
|
+
before(() => { dir = makeTempDir(); store = makeStore(dir); });
|
|
682
|
+
after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
683
|
+
|
|
684
|
+
test("body containing '## Sources' round-trips exactly (regression: was silently truncated)", async () => {
|
|
685
|
+
// Previously the adapter delimited body from structural sections by
|
|
686
|
+
// searching for '## Sources' / '## Related' etc. in the rendered text.
|
|
687
|
+
// A body that contained one of those exact headings was silently truncated
|
|
688
|
+
// on read. The fix: an invisible sentinel <!-- kit:body-end --> is
|
|
689
|
+
// emitted after the body so body content cannot collide with the delimiter.
|
|
690
|
+
const body = "Findings below.\n\n## Sources\nThis is body content, not a structural section.";
|
|
691
|
+
const id = await store.create({
|
|
692
|
+
type: "concept",
|
|
693
|
+
title: "Sources In Body Regression",
|
|
694
|
+
body,
|
|
695
|
+
category: "test",
|
|
696
|
+
provenance: { agent: "tester" },
|
|
697
|
+
});
|
|
698
|
+
const record = await store.get(id);
|
|
699
|
+
assert.equal(record.body, body,
|
|
700
|
+
"body containing '## Sources' must round-trip exactly (no silent truncation)");
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
test("body containing '## Related' and '## People' round-trips exactly", async () => {
|
|
704
|
+
const body = "## Related\nSome related context.\n\n## People\nAlice, Bob.";
|
|
705
|
+
const id = await store.create({
|
|
706
|
+
type: "snapshot",
|
|
707
|
+
title: "Related And People In Body",
|
|
708
|
+
body,
|
|
709
|
+
category: "test",
|
|
710
|
+
provenance: { agent: "tester" },
|
|
711
|
+
});
|
|
712
|
+
const record = await store.get(id);
|
|
713
|
+
assert.equal(record.body, body,
|
|
714
|
+
"body containing '## Related' and '## People' must round-trip exactly");
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test("raw body containing callout-like lines round-trips exactly", async () => {
|
|
718
|
+
const body = "> [!note] Looks like a callout\n> quoted line\nNormal line after.";
|
|
719
|
+
const id = await store.create({
|
|
720
|
+
type: "raw",
|
|
721
|
+
title: "Callout-like Raw Body",
|
|
722
|
+
body,
|
|
723
|
+
category: "test",
|
|
724
|
+
provenance: { agent: "tester" },
|
|
725
|
+
});
|
|
726
|
+
const record = await store.get(id);
|
|
727
|
+
assert.equal(record.body, body,
|
|
728
|
+
"raw body with callout-like lines must round-trip exactly");
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("multi-paragraph body with various ## headings round-trips exactly", async () => {
|
|
732
|
+
const body = "# Overview\n\nPara one.\n\n## Section A\n\nContent A.\n\n## Sources\n\nFake sources.\n\n## Related\n\nFake related.";
|
|
733
|
+
const id = await store.create({
|
|
734
|
+
type: "concept",
|
|
735
|
+
title: "Multi Heading Body",
|
|
736
|
+
body,
|
|
737
|
+
category: "test",
|
|
738
|
+
provenance: { agent: "tester" },
|
|
739
|
+
});
|
|
740
|
+
const record = await store.get(id);
|
|
741
|
+
assert.equal(record.body, body,
|
|
742
|
+
"body with multiple ## headings must round-trip exactly");
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("body field is NOT stored in frontmatter", async () => {
|
|
746
|
+
// Contract invariant: the body lives in the rendered note section,
|
|
747
|
+
// not as a YAML field. Verifiable via the default adapter since the
|
|
748
|
+
// default store stores everything in memory (never writes body to YAML
|
|
749
|
+
// frontmatter), but the key guarantee is get() returns the correct body.
|
|
750
|
+
const body = "Should not appear as body: in frontmatter";
|
|
751
|
+
const id = await store.create({
|
|
752
|
+
type: "compiled",
|
|
753
|
+
title: "No Body In Frontmatter",
|
|
754
|
+
body,
|
|
755
|
+
category: "test",
|
|
756
|
+
provenance: { agent: "tester" },
|
|
757
|
+
});
|
|
758
|
+
const record = await store.get(id);
|
|
759
|
+
assert.equal(record.body, body, "body is preserved through get()");
|
|
760
|
+
// The record object itself should not have body as a frontmatter-derived
|
|
761
|
+
// field separate from the canonical body — just that get() works.
|
|
762
|
+
assert.ok(typeof record.body === "string", "body is a string");
|
|
763
|
+
});
|
|
764
|
+
});
|
|
675
765
|
});
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"expects": [
|
|
14
14
|
{
|
|
15
15
|
"id": "raw-ids-selected",
|
|
16
|
-
"kind": "
|
|
16
|
+
"kind": "trust.bundle",
|
|
17
17
|
"required": true,
|
|
18
18
|
"description": "One or more raw record IDs have been selected for compilation. Each ID must resolve to an existing raw record in the store.",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
19
|
+
"bundle_claim": {
|
|
20
|
+
"claimType": "knowledge.compile.selection",
|
|
21
|
+
"subjectType": "artifact",
|
|
22
22
|
"accepted_statuses": ["trusted", "accepted"]
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"expects": [
|
|
30
30
|
{
|
|
31
31
|
"id": "compiled-record-provenance",
|
|
32
|
-
"kind": "
|
|
32
|
+
"kind": "trust.bundle",
|
|
33
33
|
"required": true,
|
|
34
34
|
"description": "The compiled record has been created with provenance.source_ids listing every consumed raw ID, and source links (kind='source') from the compiled record to each raw record — satisfying the store contract's compiled record requirements.",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
35
|
+
"bundle_claim": {
|
|
36
|
+
"claimType": "knowledge.compile.provenance",
|
|
37
|
+
"subjectType": "artifact",
|
|
38
38
|
"accepted_statuses": ["trusted", "accepted"]
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"expects": [
|
|
46
46
|
{
|
|
47
47
|
"id": "provenance-refs-resolve",
|
|
48
|
-
"kind": "
|
|
48
|
+
"kind": "trust.bundle",
|
|
49
49
|
"required": true,
|
|
50
50
|
"description": "Every provenance ref in the compiled record resolves to a retrievable raw record via the store's get() operation. The graph index reflects all source links.",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
51
|
+
"bundle_claim": {
|
|
52
|
+
"claimType": "knowledge.compile.link-integrity",
|
|
53
|
+
"subjectType": "artifact",
|
|
54
54
|
"accepted_statuses": ["trusted", "accepted"]
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"expects": [
|
|
15
15
|
{
|
|
16
16
|
"id": "related-compiled-records-found",
|
|
17
|
-
"kind": "
|
|
17
|
+
"kind": "trust.bundle",
|
|
18
18
|
"required": true,
|
|
19
19
|
"description": "One or more compiled records linked to the snapshot's topic have been identified. The trigger fires when new compiled records matching the snapshot topic (by category or explicit topic tag) are present. The result is a cluster of compiled record IDs that form the consolidation input.",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
20
|
+
"bundle_claim": {
|
|
21
|
+
"claimType": "knowledge.consolidate.trigger",
|
|
22
|
+
"subjectType": "artifact",
|
|
23
23
|
"accepted_statuses": ["trusted", "accepted"]
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"expects": [
|
|
31
31
|
{
|
|
32
32
|
"id": "consolidation-proposal-recorded",
|
|
33
|
-
"kind": "
|
|
33
|
+
"kind": "trust.bundle",
|
|
34
34
|
"required": true,
|
|
35
35
|
"description": "A consolidation proposal has been recorded. The proposal carries: the updated snapshot body (latest decisions), source_ids referencing every compiled record that contributed, and supersedes_ids referencing any prior snapshot(s) for the same topic. The snapshot record is NOT mutated at this step — only a consolidation proposal record and proposes link are created.",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
36
|
+
"bundle_claim": {
|
|
37
|
+
"claimType": "knowledge.consolidate.proposal",
|
|
38
|
+
"subjectType": "artifact",
|
|
39
39
|
"accepted_statuses": ["trusted", "accepted"]
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"expects": [
|
|
47
47
|
{
|
|
48
48
|
"id": "proposal-carries-source-refs",
|
|
49
|
-
"kind": "
|
|
49
|
+
"kind": "trust.bundle",
|
|
50
50
|
"required": true,
|
|
51
51
|
"description": "The consolidation proposal evidence includes source_ids referencing every compiled record that contributed to the proposed snapshot body. Gate rejects if source_ids is empty or any referenced record does not exist. Also verifies that the proposer record has a 'proposes' link to the snapshot target.",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
52
|
+
"bundle_claim": {
|
|
53
|
+
"claimType": "knowledge.consolidate.evidence",
|
|
54
|
+
"subjectType": "artifact",
|
|
55
55
|
"accepted_statuses": ["trusted", "accepted"]
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
"expects": [
|
|
63
63
|
{
|
|
64
64
|
"id": "consolidation-gate-decision",
|
|
65
|
-
"kind": "
|
|
65
|
+
"kind": "trust.bundle",
|
|
66
66
|
"required": true,
|
|
67
67
|
"description": "A gate decision (apply or reject) has been recorded. If applied: snapshot body is updated via the store update op with the proposed body; supersede op links the new snapshot to any prior snapshots for the same topic; all superseded snapshots remain queryable with provenance intact. If rejected: the snapshot body is byte-identical to its pre-proposal state.",
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
68
|
+
"bundle_claim": {
|
|
69
|
+
"claimType": "knowledge.consolidate.gate-decision",
|
|
70
|
+
"subjectType": "artifact",
|
|
71
71
|
"accepted_statuses": ["trusted", "accepted"]
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"expects": [
|
|
14
14
|
{
|
|
15
15
|
"id": "raw-text-received",
|
|
16
|
-
"kind": "
|
|
16
|
+
"kind": "trust.bundle",
|
|
17
17
|
"required": true,
|
|
18
18
|
"description": "Raw text and metadata have been received and are non-empty.",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
19
|
+
"bundle_claim": {
|
|
20
|
+
"claimType": "knowledge.ingest.capture",
|
|
21
|
+
"subjectType": "artifact",
|
|
22
22
|
"accepted_statuses": ["trusted", "accepted"]
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"expects": [
|
|
30
30
|
{
|
|
31
31
|
"id": "classification-recorded",
|
|
32
|
-
"kind": "
|
|
32
|
+
"kind": "trust.bundle",
|
|
33
33
|
"required": true,
|
|
34
34
|
"description": "The raw record has been classified and stored with a non-empty category and type='raw', satisfying the store contract's create op provenance requirements.",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
35
|
+
"bundle_claim": {
|
|
36
|
+
"claimType": "knowledge.ingest.classification",
|
|
37
|
+
"subjectType": "artifact",
|
|
38
38
|
"accepted_statuses": ["trusted", "accepted"]
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"expects": [
|
|
46
46
|
{
|
|
47
47
|
"id": "routing-decision",
|
|
48
|
-
"kind": "
|
|
48
|
+
"kind": "trust.bundle",
|
|
49
49
|
"required": true,
|
|
50
50
|
"description": "A routing decision has been recorded for the classified raw record (e.g. queue for compile, discard, or hold).",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
51
|
+
"bundle_claim": {
|
|
52
|
+
"claimType": "knowledge.ingest.routing",
|
|
53
|
+
"subjectType": "decision",
|
|
54
54
|
"accepted_statuses": ["trusted", "accepted"]
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"expects": [
|
|
15
15
|
{
|
|
16
16
|
"id": "target-record-found",
|
|
17
|
-
"kind": "
|
|
17
|
+
"kind": "trust.bundle",
|
|
18
18
|
"required": true,
|
|
19
19
|
"description": "The record to be retired has been identified and is in a retirable status (active or implemented). The record ID and current status are surfaced for the proposal step.",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
20
|
+
"bundle_claim": {
|
|
21
|
+
"claimType": "knowledge.retire.identify",
|
|
22
|
+
"subjectType": "artifact",
|
|
23
23
|
"accepted_statuses": ["trusted", "accepted"]
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"expects": [
|
|
31
31
|
{
|
|
32
32
|
"id": "retirement-proposal-recorded",
|
|
33
|
-
"kind": "
|
|
33
|
+
"kind": "trust.bundle",
|
|
34
34
|
"required": true,
|
|
35
35
|
"description": "A retirement proposal has been recorded. The proposal carries: the target status (implemented or retired), the retirement rationale, and — when targetStatus is 'implemented' — an implementedByRef pointing to the implementing artifact (commit SHA, PR URL, issue number, etc.). The record is NOT mutated at this step — only a retirement proposal record and proposes link are created.",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
36
|
+
"bundle_claim": {
|
|
37
|
+
"claimType": "knowledge.retire.proposal",
|
|
38
|
+
"subjectType": "artifact",
|
|
39
39
|
"accepted_statuses": ["trusted", "accepted"]
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"expects": [
|
|
47
47
|
{
|
|
48
48
|
"id": "retirement-evidence-valid",
|
|
49
|
-
"kind": "
|
|
49
|
+
"kind": "trust.bundle",
|
|
50
50
|
"required": true,
|
|
51
51
|
"description": "The retirement proposal evidence is valid: rationale is non-empty; implementedByRef is non-empty when targetStatus is 'implemented'; the target record exists and is in a state that allows the requested transition (active→implemented, active→retired, implemented→retired). Gate rejects if any required evidence field is missing or the transition is invalid.",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
52
|
+
"bundle_claim": {
|
|
53
|
+
"claimType": "knowledge.retire.evidence",
|
|
54
|
+
"subjectType": "artifact",
|
|
55
55
|
"accepted_statuses": ["trusted", "accepted"]
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
"expects": [
|
|
63
63
|
{
|
|
64
64
|
"id": "retirement-gate-decision",
|
|
65
|
-
"kind": "
|
|
65
|
+
"kind": "trust.bundle",
|
|
66
66
|
"required": true,
|
|
67
67
|
"description": "A gate decision (apply or reject) has been recorded. If applied: the record status is updated to the target status via the store retire op; the retirement evidence (rationale, implementedByRef, supersededByRef) is appended to the mutation log; the record body, links, and provenance remain intact; the record is excluded from default working-set queries (listByType, listByCategory, similarity detection). If rejected: the record status is byte-identical to its pre-proposal state.",
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
68
|
+
"bundle_claim": {
|
|
69
|
+
"claimType": "knowledge.retire.gate-decision",
|
|
70
|
+
"subjectType": "artifact",
|
|
71
71
|
"accepted_statuses": ["trusted", "accepted"]
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -11,34 +11,34 @@
|
|
|
11
11
|
"expects": [
|
|
12
12
|
{
|
|
13
13
|
"id": "contract-suite-passed",
|
|
14
|
-
"kind": "
|
|
14
|
+
"kind": "trust.bundle",
|
|
15
15
|
"required": true,
|
|
16
16
|
"description": "The contract test suite has passed 100% against the target adapter, verifying round-trip integrity, required-evidence enforcement, and graph-index consistency.",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
17
|
+
"bundle_claim": {
|
|
18
|
+
"claimType": "knowledge.store-contract.suite-result",
|
|
19
|
+
"subjectType": "artifact",
|
|
20
20
|
"accepted_statuses": ["trusted", "accepted"]
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"id": "mutation-provenance-enforced",
|
|
25
|
-
"kind": "
|
|
25
|
+
"kind": "trust.bundle",
|
|
26
26
|
"required": true,
|
|
27
27
|
"description": "Every mutation operation (create, update, link, propose, apply, reject) rejects requests missing required provenance fields, as verified by the contract suite.",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
28
|
+
"bundle_claim": {
|
|
29
|
+
"claimType": "knowledge.store-contract.provenance-enforcement",
|
|
30
|
+
"subjectType": "artifact",
|
|
31
31
|
"accepted_statuses": ["trusted", "accepted"]
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"id": "round-trip-integrity",
|
|
36
|
-
"kind": "
|
|
36
|
+
"kind": "trust.bundle",
|
|
37
37
|
"required": true,
|
|
38
38
|
"description": "Records round-trip raw to stored to queried with category and links intact, as verified by the contract suite.",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
39
|
+
"bundle_claim": {
|
|
40
|
+
"claimType": "knowledge.store-contract.round-trip",
|
|
41
|
+
"subjectType": "artifact",
|
|
42
42
|
"accepted_statuses": ["trusted", "accepted"]
|
|
43
43
|
}
|
|
44
44
|
}
|