@kontourai/flow-agents 1.4.0 → 2.0.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/CODEOWNERS +29 -0
- package/.github/actions/trust-verify/action.yml +145 -0
- package/.github/workflows/ci.yml +11 -4
- package/.github/workflows/kit-gates-demo.yml +2 -2
- package/.github/workflows/publish-npm.yml +10 -2
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/trust-reconcile.yml +113 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +4 -4
- package/README.md +1 -0
- package/agents/tool-planner.json +1 -1
- package/build/src/cli/init.js +242 -20
- package/build/src/cli/validate-workflow-artifacts.js +19 -2
- package/build/src/cli/verify.d.ts +1 -0
- package/build/src/cli/verify.js +90 -0
- package/build/src/cli/workflow-sidecar.d.ts +300 -8
- package/build/src/cli/workflow-sidecar.js +1934 -83
- package/build/src/cli.js +2 -3
- package/build/src/lib/flow-resolver.d.ts +82 -0
- package/build/src/lib/flow-resolver.js +237 -0
- package/build/src/tools/build-universal-bundles.js +34 -22
- package/build/src/tools/generate-context-map.js +3 -16
- package/build/src/tools/validate-source-tree.d.ts +1 -1
- package/build/src/tools/validate-source-tree.js +42 -162
- package/context/contracts/artifact-contract.md +10 -0
- package/context/contracts/delivery-contract.md +1 -0
- package/context/contracts/review-contract.md +1 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/gate-awareness.md +39 -0
- package/context/scripts/hooks/stop-goal-fit.js +632 -70
- package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
- package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0007-skill-audit.md +1 -1
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
- package/docs/adr/0011-mcp-posture.md +100 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
- package/docs/adr/0013-context-lifecycle.md +151 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
- package/docs/adr/0016-three-hard-boundary-model.md +71 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
- package/docs/agent-system-guidebook.md +5 -12
- package/docs/context-map.md +4 -10
- package/docs/index.md +3 -2
- package/docs/integrations/framework-adapter.md +19 -6
- package/docs/integrations/index.md +2 -2
- package/docs/north-star.md +4 -4
- package/docs/operating-layers.md +3 -3
- package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
- package/docs/repository-structure.md +2 -2
- package/docs/skills-map.md +1 -0
- package/docs/spec/runtime-hook-surface.md +62 -9
- package/docs/standards-register.md +3 -3
- package/docs/survey-utterance-check.md +1 -1
- package/docs/trust-anchor-adoption.md +197 -0
- package/docs/verifiable-trust.md +95 -0
- package/docs/veritas-integration.md +2 -2
- package/docs/workflow-usage-guide.md +69 -0
- package/evals/acceptance/DEMO-false-completion.md +144 -0
- package/evals/acceptance/demo-cast.sh +92 -0
- package/evals/acceptance/demo-false-completion.sh +72 -0
- package/evals/acceptance/demo-real-evidence.sh +104 -0
- package/evals/acceptance/demo.tape +29 -0
- package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
- package/evals/acceptance/prove-capture-teeth.sh +114 -0
- package/evals/acceptance/prove-teeth.sh +105 -0
- package/evals/ci/antigaming-suite.sh +54 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
- package/evals/integration/test_builder_step_producers.sh +379 -0
- package/evals/integration/test_bundle_install.sh +35 -71
- package/evals/integration/test_bundle_lifecycle.sh +39 -2
- package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
- package/evals/integration/test_checkpoint_signing.sh +489 -0
- package/evals/integration/test_claim_lookup.sh +352 -0
- package/evals/integration/test_command_log_integrity.sh +275 -0
- package/evals/integration/test_context_map.sh +0 -2
- package/evals/integration/test_dual_emit_flow_step.sh +278 -0
- package/evals/integration/test_enforcer_expects_driven.sh +281 -0
- package/evals/integration/test_evidence_capture_hook.sh +185 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +273 -0
- package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
- package/evals/integration/test_gate_bypass_chain.sh +448 -0
- package/evals/integration/test_gate_lockdown.sh +1137 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
- package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
- package/evals/integration/test_goal_fit_hook.sh +69 -4
- package/evals/integration/test_goal_fit_rederive.sh +263 -0
- package/evals/integration/test_install_merge.sh +1176 -0
- package/evals/integration/test_mint_attestation.sh +373 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
- package/evals/integration/test_publish_delivery.sh +269 -0
- package/evals/integration/test_reconcile_soundness.sh +528 -0
- package/evals/integration/test_resolvefirststep_security.sh +208 -0
- package/evals/integration/test_session_resume_roundtrip.sh +286 -0
- package/evals/integration/test_trust_checkpoint.sh +325 -0
- package/evals/integration/test_trust_reconcile.sh +293 -0
- package/evals/integration/test_verify_cli.sh +208 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
- package/evals/lib/node.sh +0 -6
- package/evals/run.sh +45 -0
- package/evals/static/test_workflow_skills.sh +6 -13
- package/install.sh +0 -7
- package/integrations/strands-ts/README.md +25 -15
- package/integrations/veritas/flow-agents.adapter.json +1 -2
- package/kits/builder/flows/build.flow.json +59 -12
- package/kits/builder/kit.json +85 -15
- package/kits/builder/skills/continue-work/SKILL.md +116 -0
- package/kits/builder/skills/deliver/SKILL.md +36 -6
- package/kits/builder/skills/design-probe/SKILL.md +28 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -1
- package/kits/builder/skills/gate-review/SKILL.md +234 -0
- package/kits/builder/skills/learning-review/SKILL.md +30 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
- package/kits/builder/skills/plan-work/SKILL.md +13 -1
- package/kits/builder/skills/pull-work/SKILL.md +19 -0
- package/kits/knowledge/adapters/default-store/index.js +38 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
- package/kits/knowledge/docs/store-contract.md +314 -0
- package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
- package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
- package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
- package/kits/knowledge/evals/entities/suite.test.js +40 -0
- package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
- package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
- package/kits/knowledge/evals/retirement/suite.test.js +145 -0
- package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
- package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
- package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
- package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
- package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
- package/kits/knowledge/kit.json +51 -1
- package/package.json +4 -4
- package/packaging/conformance/README.md +10 -2
- package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
- package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
- package/packaging/conformance/run-conformance.js +1 -1
- package/scripts/README.md +2 -1
- package/scripts/build-universal-bundles.js +0 -1
- package/scripts/ci/mint-attestation.js +221 -0
- package/scripts/ci/trust-reconcile.js +545 -0
- package/scripts/hooks/config-protection.js +423 -1
- package/scripts/hooks/evidence-capture.js +348 -0
- package/scripts/hooks/lib/liveness-read.js +113 -0
- package/scripts/hooks/run-hook.js +6 -1
- package/scripts/hooks/stop-goal-fit.js +1471 -79
- package/scripts/hooks/workflow-steering.js +135 -5
- package/scripts/install-codex-home.sh +39 -0
- package/scripts/install-merge.js +330 -0
- package/src/cli/init.ts +218 -20
- package/src/cli/validate-workflow-artifacts.ts +18 -2
- package/src/cli/verify.ts +100 -0
- package/src/cli/workflow-sidecar.ts +2064 -77
- package/src/cli.ts +2 -3
- package/src/lib/flow-resolver.ts +284 -0
- package/src/tools/build-universal-bundles.ts +34 -21
- package/src/tools/generate-context-map.ts +3 -17
- package/src/tools/validate-source-tree.ts +44 -104
- package/build/src/tools/filter-installed-packs.d.ts +0 -2
- package/build/src/tools/filter-installed-packs.js +0 -135
- package/packaging/packs.json +0 -49
- package/scripts/filter-installed-packs.js +0 -2
- package/src/tools/filter-installed-packs.ts +0 -132
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Kit — Glossary-Sync Eval Suite (#106 hygiene #3)
|
|
3
|
+
*
|
|
4
|
+
* knowledge.glossary-sync keeps the glossary (concept records) in sync with the
|
|
5
|
+
* canonical docs that DEFINE those terms. It surveys a CONFIGURABLE list of
|
|
6
|
+
* canonical source docs (the "glossary source list" — opt-in), extracts
|
|
7
|
+
* term→definition entries with a PLUGGABLE extractor, and classifies each entry
|
|
8
|
+
* against existing concepts:
|
|
9
|
+
* - "gap": no concept captures the term → propose a canonical definition
|
|
10
|
+
* - "outdated": a concept exists but its body has drifted → propose the update
|
|
11
|
+
* - "current": the concept matches the canonical definition → no-op
|
|
12
|
+
*
|
|
13
|
+
* Read-only by DEFAULT (returns the plan, mutates nothing). With apply=true it
|
|
14
|
+
* enacts the plan via the EXISTING concept-record ops (create → propose → apply
|
|
15
|
+
* for gaps; propose → apply for drift), with the canonical doc as the proposer —
|
|
16
|
+
* consume-never-fork.
|
|
17
|
+
*
|
|
18
|
+
* Covers:
|
|
19
|
+
* - default term extraction from canonical-doc glossary lines (bold + colon).
|
|
20
|
+
* - classification: gap / outdated / current, each citing its source doc.
|
|
21
|
+
* - drift detection is whitespace-insensitive (cosmetic reflow is NOT drift).
|
|
22
|
+
* - term matching is case/space-insensitive, scoped to the concept category.
|
|
23
|
+
* - configurable source list: record id + category selector; opt-in empty list;
|
|
24
|
+
* an unknown source id is rejected (the source list is evidence).
|
|
25
|
+
* - pluggable term extractor is honoured.
|
|
26
|
+
* - read-only invariant: default mode mutates no record.
|
|
27
|
+
* - apply mode: gaps create a concept via create→propose→apply; drift goes
|
|
28
|
+
* through propose→apply with the canonical doc as proposer; the "proposes"
|
|
29
|
+
* link + mutation_log entries prove the gated path was used.
|
|
30
|
+
* - gate telemetry (collect-gate, diff-gate, propose-gate) is emitted.
|
|
31
|
+
*
|
|
32
|
+
* Run:
|
|
33
|
+
* node --test kits/knowledge/evals/glossary-sync/suite.test.js
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { test, describe, before, after } from "node:test";
|
|
37
|
+
import assert from "node:assert/strict";
|
|
38
|
+
import * as fs from "node:fs";
|
|
39
|
+
import * as path from "node:path";
|
|
40
|
+
import * as os from "node:os";
|
|
41
|
+
import { fileURLToPath } from "node:url";
|
|
42
|
+
|
|
43
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const KIT_ROOT = path.resolve(__dirname, "../..");
|
|
45
|
+
|
|
46
|
+
const adapterPath = path.join(KIT_ROOT, "adapters/default-store/index.js");
|
|
47
|
+
const runnerPath = path.join(KIT_ROOT, "adapters/flow-runner/index.js");
|
|
48
|
+
|
|
49
|
+
const { DefaultKnowledgeStore } = await import(adapterPath);
|
|
50
|
+
const { KnowledgeFlowRunner, glossarySync } = await import(runnerPath);
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Helpers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
function makeTempDir() {
|
|
57
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "knowledge-glossary-sync-"));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeStore(dir) {
|
|
61
|
+
return new DefaultKnowledgeStore({ storeRoot: dir });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeRunner(store, dir) {
|
|
65
|
+
return new KnowledgeFlowRunner({
|
|
66
|
+
store,
|
|
67
|
+
workspace: dir,
|
|
68
|
+
agent: "glossary-sync-test-runner",
|
|
69
|
+
sessionId: "glossary-sync-session-001",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readTelemetryEvents(dir) {
|
|
74
|
+
const sinkPath = path.join(dir, ".telemetry", "full.jsonl");
|
|
75
|
+
if (!fs.existsSync(sinkPath)) return [];
|
|
76
|
+
return fs.readFileSync(sinkPath, "utf8")
|
|
77
|
+
.trim()
|
|
78
|
+
.split("\n")
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.map((line) => JSON.parse(line));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function recordBytes(dir, id) {
|
|
84
|
+
return fs.readFileSync(path.join(dir, "records", `${id}.md`), "utf8");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// A canonical doc whose body holds a small glossary in the two default shapes.
|
|
88
|
+
const GLOSSARY_BODY = [
|
|
89
|
+
"# Engineering Glossary",
|
|
90
|
+
"",
|
|
91
|
+
"**Idempotency** — An operation that can be applied multiple times without changing the result beyond the initial application.",
|
|
92
|
+
"- **API Gateway**: A single entry point that routes requests to backend services.",
|
|
93
|
+
"Eventual Consistency: A consistency model where replicas converge given no new writes.",
|
|
94
|
+
"",
|
|
95
|
+
"Some prose that is not a glossary entry and should be ignored.",
|
|
96
|
+
].join("\n");
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Suite
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
describe("Knowledge Kit Glossary-Sync Suite (#106)", () => {
|
|
103
|
+
let dir;
|
|
104
|
+
let store;
|
|
105
|
+
let runner;
|
|
106
|
+
let canonicalDocId;
|
|
107
|
+
|
|
108
|
+
before(async () => {
|
|
109
|
+
dir = makeTempDir();
|
|
110
|
+
store = makeStore(dir);
|
|
111
|
+
runner = makeRunner(store, dir);
|
|
112
|
+
|
|
113
|
+
// The canonical doc that DEFINES the engineering glossary.
|
|
114
|
+
canonicalDocId = await store.create({
|
|
115
|
+
type: "compiled",
|
|
116
|
+
title: "Engineering Glossary (canonical)",
|
|
117
|
+
body: GLOSSARY_BODY,
|
|
118
|
+
category: "engineering.glossary",
|
|
119
|
+
provenance: { agent: "fixture" },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// An existing concept that MATCHES its canonical definition → "current".
|
|
123
|
+
await store.create({
|
|
124
|
+
type: "concept",
|
|
125
|
+
title: "Idempotency",
|
|
126
|
+
body: "An operation that can be applied multiple times without changing the result beyond the initial application.",
|
|
127
|
+
category: "engineering.glossary",
|
|
128
|
+
provenance: { agent: "fixture" },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// An existing concept whose body has DRIFTED from the canonical → "outdated".
|
|
132
|
+
await store.create({
|
|
133
|
+
type: "concept",
|
|
134
|
+
title: "API Gateway",
|
|
135
|
+
body: "Old definition: a reverse proxy in front of microservices.",
|
|
136
|
+
category: "engineering.glossary",
|
|
137
|
+
provenance: { agent: "fixture" },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// (No concept for "Eventual Consistency" → it is a GAP.)
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
after(() => {
|
|
144
|
+
if (dir) fs.rmSync(dir, { recursive: true, force: true });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const baseConfig = () => ({ sources: [canonicalDocId] });
|
|
148
|
+
|
|
149
|
+
test("default extractor pulls term→definition entries from the canonical doc", async () => {
|
|
150
|
+
const result = await runner.glossarySync(baseConfig());
|
|
151
|
+
assert.equal(result.sourcesAudited, 1, "one canonical doc audited");
|
|
152
|
+
assert.equal(result.entries, 3, "three glossary entries extracted (prose ignored)");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("classification: gap / outdated / current — each cites its source doc", async () => {
|
|
156
|
+
const result = await runner.glossarySync(baseConfig());
|
|
157
|
+
|
|
158
|
+
const gapTerms = result.gaps.map((g) => g.term);
|
|
159
|
+
const outdatedTerms = result.outdated.map((o) => o.term);
|
|
160
|
+
const currentTerms = result.current.map((c) => c.term);
|
|
161
|
+
|
|
162
|
+
assert.deepEqual(gapTerms, ["Eventual Consistency"], "the undefined term is a gap");
|
|
163
|
+
assert.deepEqual(outdatedTerms, ["API Gateway"], "the drifted concept is outdated");
|
|
164
|
+
assert.deepEqual(currentTerms, ["Idempotency"], "the matching concept is current");
|
|
165
|
+
|
|
166
|
+
for (const entry of [...result.gaps, ...result.outdated, ...result.current]) {
|
|
167
|
+
assert.equal(entry.sourceDocId, canonicalDocId, "every entry cites its source doc id");
|
|
168
|
+
assert.ok(entry.sourceDocTitle, "every entry cites its source doc title");
|
|
169
|
+
assert.ok(entry.definition, "every entry carries the canonical definition");
|
|
170
|
+
assert.equal(entry.category, "engineering.glossary", "entry carries the concept category");
|
|
171
|
+
}
|
|
172
|
+
// Outdated entries additionally cite the drifted body (the evidence of drift).
|
|
173
|
+
const apiGw = result.outdated[0];
|
|
174
|
+
assert.equal(apiGw.classification, "outdated");
|
|
175
|
+
assert.equal(apiGw.conceptId !== undefined, true, "outdated entry references the existing concept");
|
|
176
|
+
assert.match(apiGw.currentBody, /reverse proxy/, "outdated entry cites the drifted body");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("drift detection is whitespace-insensitive (cosmetic reflow is not drift)", async () => {
|
|
180
|
+
const wdir = makeTempDir();
|
|
181
|
+
const wstore = makeStore(wdir);
|
|
182
|
+
const wrunner = makeRunner(wstore, wdir);
|
|
183
|
+
try {
|
|
184
|
+
const docId = await wstore.create({
|
|
185
|
+
type: "compiled",
|
|
186
|
+
title: "WS glossary",
|
|
187
|
+
body: "**Term One** — A definition with irregular spacing.",
|
|
188
|
+
category: "ws.glossary",
|
|
189
|
+
provenance: { agent: "fixture" },
|
|
190
|
+
});
|
|
191
|
+
// Concept body differs from canonical ONLY in whitespace.
|
|
192
|
+
await wstore.create({
|
|
193
|
+
type: "concept",
|
|
194
|
+
title: "Term One",
|
|
195
|
+
body: "A definition with irregular spacing.",
|
|
196
|
+
category: "ws.glossary",
|
|
197
|
+
provenance: { agent: "fixture" },
|
|
198
|
+
});
|
|
199
|
+
const result = await wrunner.glossarySync({ sources: [docId] });
|
|
200
|
+
assert.equal(result.outdated.length, 0, "whitespace-only difference is NOT flagged as drift");
|
|
201
|
+
assert.equal(result.current.length, 1, "it is classified current");
|
|
202
|
+
} finally {
|
|
203
|
+
fs.rmSync(wdir, { recursive: true, force: true });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("term matching is case/space-insensitive and scoped to the concept category", async () => {
|
|
208
|
+
const cdir = makeTempDir();
|
|
209
|
+
const cstore = makeStore(cdir);
|
|
210
|
+
const crunner = makeRunner(cstore, cdir);
|
|
211
|
+
try {
|
|
212
|
+
const docId = await cstore.create({
|
|
213
|
+
type: "compiled",
|
|
214
|
+
title: "Case glossary",
|
|
215
|
+
body: "**api GATEWAY** — Canonical gateway definition.",
|
|
216
|
+
category: "svc.glossary",
|
|
217
|
+
provenance: { agent: "fixture" },
|
|
218
|
+
});
|
|
219
|
+
// Concept titled with different case/spacing — must still match (current).
|
|
220
|
+
await cstore.create({
|
|
221
|
+
type: "concept",
|
|
222
|
+
title: "API Gateway",
|
|
223
|
+
body: "Canonical gateway definition.",
|
|
224
|
+
category: "svc.glossary",
|
|
225
|
+
provenance: { agent: "fixture" },
|
|
226
|
+
});
|
|
227
|
+
// A same-named concept in a DIFFERENT category must NOT match → still a gap.
|
|
228
|
+
await cstore.create({
|
|
229
|
+
type: "concept",
|
|
230
|
+
title: "API Gateway",
|
|
231
|
+
body: "Unrelated category definition.",
|
|
232
|
+
category: "other.glossary",
|
|
233
|
+
provenance: { agent: "fixture" },
|
|
234
|
+
});
|
|
235
|
+
const result = await crunner.glossarySync({ sources: [docId] });
|
|
236
|
+
assert.equal(result.gaps.length, 0, "case/space-insensitive match prevents a false gap");
|
|
237
|
+
assert.equal(result.current.length, 1, "matched in-category despite case/spacing differences");
|
|
238
|
+
} finally {
|
|
239
|
+
fs.rmSync(cdir, { recursive: true, force: true });
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("configurable source list: empty list is opt-in no-op; unknown source rejected", async () => {
|
|
244
|
+
const empty = await runner.glossarySync({ sources: [] });
|
|
245
|
+
assert.equal(empty.sourcesAudited, 0, "empty source list audits nothing (opt-in)");
|
|
246
|
+
assert.equal(empty.entries, 0);
|
|
247
|
+
assert.equal(empty.gaps.length + empty.outdated.length + empty.current.length, 0);
|
|
248
|
+
|
|
249
|
+
await assert.rejects(
|
|
250
|
+
() => runner.glossarySync({ sources: ["does-not-exist"] }),
|
|
251
|
+
/source doc not found/,
|
|
252
|
+
"an unknown source id is rejected — the source list is evidence"
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("category selector resolves multiple canonical docs", async () => {
|
|
257
|
+
const sdir = makeTempDir();
|
|
258
|
+
const sstore = makeStore(sdir);
|
|
259
|
+
const srunner = makeRunner(sstore, sdir);
|
|
260
|
+
try {
|
|
261
|
+
await sstore.create({
|
|
262
|
+
type: "compiled", title: "Doc A", category: "kb.glossary",
|
|
263
|
+
body: "**Alpha** — First term.", provenance: { agent: "fixture" },
|
|
264
|
+
});
|
|
265
|
+
await sstore.create({
|
|
266
|
+
type: "compiled", title: "Doc B", category: "kb.glossary",
|
|
267
|
+
body: "**Beta** — Second term.", provenance: { agent: "fixture" },
|
|
268
|
+
});
|
|
269
|
+
const result = await srunner.glossarySync({
|
|
270
|
+
sources: [{ category: "kb.glossary" }],
|
|
271
|
+
});
|
|
272
|
+
assert.equal(result.sourcesAudited, 2, "both docs in the category are collected");
|
|
273
|
+
assert.deepEqual(result.gaps.map((g) => g.term).sort(), ["Alpha", "Beta"]);
|
|
274
|
+
} finally {
|
|
275
|
+
fs.rmSync(sdir, { recursive: true, force: true });
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("pluggable term extractor is honoured", async () => {
|
|
280
|
+
const customExtractor = () => [
|
|
281
|
+
{ term: "Custom Term", definition: "A definition from a custom extractor." },
|
|
282
|
+
];
|
|
283
|
+
const result = await runner.glossarySync({
|
|
284
|
+
sources: [canonicalDocId],
|
|
285
|
+
termExtractor: customExtractor,
|
|
286
|
+
});
|
|
287
|
+
assert.equal(result.entries, 1, "only the custom extractor's entries are used");
|
|
288
|
+
assert.equal(result.gaps[0].term, "Custom Term");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("read-only invariant: default mode mutates no record", async () => {
|
|
292
|
+
const before = {};
|
|
293
|
+
const ids = fs.readdirSync(path.join(dir, "records")).map((f) => f.replace(/\.md$/, ""));
|
|
294
|
+
for (const id of ids) before[id] = recordBytes(dir, id);
|
|
295
|
+
|
|
296
|
+
await runner.glossarySync(baseConfig());
|
|
297
|
+
|
|
298
|
+
const idsAfter = fs.readdirSync(path.join(dir, "records")).map((f) => f.replace(/\.md$/, ""));
|
|
299
|
+
assert.deepEqual(idsAfter.sort(), ids.sort(), "no record is created by a read-only sync");
|
|
300
|
+
for (const id of ids) {
|
|
301
|
+
assert.equal(recordBytes(dir, id), before[id], `record ${id} is byte-identical after read-only sync`);
|
|
302
|
+
}
|
|
303
|
+
// And no proposal was applied.
|
|
304
|
+
const result = await runner.glossarySync(baseConfig());
|
|
305
|
+
assert.equal(result.applied.length, 0, "read-only mode applies nothing");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("apply mode: a gap is enacted via create→propose→apply with the doc as proposer", async () => {
|
|
309
|
+
const adir = makeTempDir();
|
|
310
|
+
const astore = makeStore(adir);
|
|
311
|
+
const arunner = makeRunner(astore, adir);
|
|
312
|
+
try {
|
|
313
|
+
const docId = await astore.create({
|
|
314
|
+
type: "compiled", title: "Apply glossary", category: "app.glossary",
|
|
315
|
+
body: "**Backpressure** — A mechanism to slow producers when consumers lag.",
|
|
316
|
+
provenance: { agent: "fixture" },
|
|
317
|
+
});
|
|
318
|
+
const result = await arunner.glossarySync({ sources: [docId], apply: true });
|
|
319
|
+
|
|
320
|
+
assert.equal(result.applied.length, 1, "one entry applied");
|
|
321
|
+
const { conceptId, action } = result.applied[0];
|
|
322
|
+
assert.equal(action, "create", "a gap is enacted as a concept creation");
|
|
323
|
+
|
|
324
|
+
// The concept now exists with the canonical definition.
|
|
325
|
+
const concept = await astore.get(conceptId);
|
|
326
|
+
assert.equal(concept.type, "concept");
|
|
327
|
+
assert.equal(concept.title, "Backpressure");
|
|
328
|
+
assert.match(concept.body, /slow producers/, "concept body is the canonical definition");
|
|
329
|
+
|
|
330
|
+
// The canonical doc is the proposer: it has a "proposes" link to the concept.
|
|
331
|
+
const { forward } = await astore.getLinks(docId);
|
|
332
|
+
assert.ok(
|
|
333
|
+
forward.some((l) => l.target_id === conceptId && l.kind === "proposes"),
|
|
334
|
+
"the canonical doc proposes the concept (consume-never-fork lineage)"
|
|
335
|
+
);
|
|
336
|
+
// Concept mutation log shows the gated propose→apply path.
|
|
337
|
+
const ops = (concept.mutation_log || []).map((e) => e.op);
|
|
338
|
+
assert.ok(ops.includes("propose"), "concept has a propose log entry");
|
|
339
|
+
assert.ok(ops.includes("apply"), "concept has an apply log entry");
|
|
340
|
+
} finally {
|
|
341
|
+
fs.rmSync(adir, { recursive: true, force: true });
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("apply mode: drift is enacted via propose→apply on the existing concept", async () => {
|
|
346
|
+
const ddir = makeTempDir();
|
|
347
|
+
const dstore = makeStore(ddir);
|
|
348
|
+
const drunner = makeRunner(dstore, ddir);
|
|
349
|
+
try {
|
|
350
|
+
const docId = await dstore.create({
|
|
351
|
+
type: "compiled", title: "Drift glossary", category: "drift.glossary",
|
|
352
|
+
body: "**Saga** — The canonical, up-to-date saga definition.",
|
|
353
|
+
provenance: { agent: "fixture" },
|
|
354
|
+
});
|
|
355
|
+
const conceptId = await dstore.create({
|
|
356
|
+
type: "concept", title: "Saga", category: "drift.glossary",
|
|
357
|
+
body: "Stale: an old saga definition.",
|
|
358
|
+
provenance: { agent: "fixture" },
|
|
359
|
+
});
|
|
360
|
+
const result = await drunner.glossarySync({ sources: [docId], apply: true });
|
|
361
|
+
|
|
362
|
+
assert.equal(result.applied.length, 1);
|
|
363
|
+
assert.equal(result.applied[0].action, "update", "drift is enacted as an update");
|
|
364
|
+
assert.equal(result.applied[0].conceptId, conceptId, "the existing concept is updated, not forked");
|
|
365
|
+
|
|
366
|
+
const concept = await dstore.get(conceptId);
|
|
367
|
+
assert.match(concept.body, /up-to-date saga/, "concept body refreshed to the canonical definition");
|
|
368
|
+
const ops = (concept.mutation_log || []).map((e) => e.op);
|
|
369
|
+
assert.ok(ops.includes("propose") && ops.includes("apply"), "gated propose→apply path used");
|
|
370
|
+
|
|
371
|
+
// A subsequent read-only sync now sees it as current (no remaining drift).
|
|
372
|
+
const after = await drunner.glossarySync({ sources: [docId] });
|
|
373
|
+
assert.equal(after.outdated.length, 0, "no drift remains after apply");
|
|
374
|
+
assert.equal(after.current.length, 1);
|
|
375
|
+
} finally {
|
|
376
|
+
fs.rmSync(ddir, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("gate telemetry: collect-gate, diff-gate, propose-gate events are emitted", async () => {
|
|
381
|
+
const tdir = makeTempDir();
|
|
382
|
+
const tstore = makeStore(tdir);
|
|
383
|
+
const trunner = makeRunner(tstore, tdir);
|
|
384
|
+
try {
|
|
385
|
+
const docId = await tstore.create({
|
|
386
|
+
type: "compiled", title: "Telemetry glossary", category: "tel.glossary",
|
|
387
|
+
body: "**Quorum** — The minimum number of nodes for a decision.",
|
|
388
|
+
provenance: { agent: "fixture" },
|
|
389
|
+
});
|
|
390
|
+
const result = await trunner.glossarySync({ sources: [docId] });
|
|
391
|
+
|
|
392
|
+
assert.ok(
|
|
393
|
+
result.telemetryEvents.length >= 6,
|
|
394
|
+
"collect + diff + propose gates produce at least 6 in/out events"
|
|
395
|
+
);
|
|
396
|
+
const persisted = readTelemetryEvents(tdir);
|
|
397
|
+
const syncEvents = persisted.filter((e) =>
|
|
398
|
+
JSON.stringify(e).includes("knowledge.glossary-sync")
|
|
399
|
+
);
|
|
400
|
+
assert.ok(syncEvents.length > 0, "glossary-sync flow telemetry is persisted to the sink");
|
|
401
|
+
} finally {
|
|
402
|
+
fs.rmSync(tdir, { recursive: true, force: true });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("module-level glossarySync export delegates to the runner", async () => {
|
|
407
|
+
const result = await glossarySync({
|
|
408
|
+
store,
|
|
409
|
+
workspace: dir,
|
|
410
|
+
agent: "glossary-sync-test-runner",
|
|
411
|
+
sources: [canonicalDocId],
|
|
412
|
+
});
|
|
413
|
+
assert.deepEqual(result.gaps.map((g) => g.term), ["Eventual Consistency"]);
|
|
414
|
+
assert.deepEqual(result.outdated.map((o) => o.term), ["API Gateway"]);
|
|
415
|
+
});
|
|
416
|
+
});
|