domain-knowledge-kit 0.2.15 → 0.2.19
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/README.md +4 -0
- package/dist/cli.js +24 -1
- package/dist/cli.js.map +1 -1
- package/dist/features/agent/commands/init.d.ts +90 -1
- package/dist/features/agent/commands/init.d.ts.map +1 -1
- package/dist/features/agent/commands/init.js +328 -32
- package/dist/features/agent/commands/init.js.map +1 -1
- package/dist/features/agent/commands/prime.d.ts +11 -0
- package/dist/features/agent/commands/prime.d.ts.map +1 -1
- package/dist/features/agent/commands/prime.js +105 -8
- package/dist/features/agent/commands/prime.js.map +1 -1
- package/dist/features/agent/commands/update.d.ts +27 -0
- package/dist/features/agent/commands/update.d.ts.map +1 -0
- package/dist/features/agent/commands/update.js +316 -0
- package/dist/features/agent/commands/update.js.map +1 -0
- package/dist/features/agent/dkk-artifacts.d.ts +76 -0
- package/dist/features/agent/dkk-artifacts.d.ts.map +1 -0
- package/dist/features/agent/dkk-artifacts.js +328 -0
- package/dist/features/agent/dkk-artifacts.js.map +1 -0
- package/dist/features/agent/install-mode.d.ts +34 -0
- package/dist/features/agent/install-mode.d.ts.map +1 -0
- package/dist/features/agent/install-mode.js +78 -0
- package/dist/features/agent/install-mode.js.map +1 -0
- package/dist/features/agent/mcp-register.d.ts +20 -0
- package/dist/features/agent/mcp-register.d.ts.map +1 -0
- package/dist/features/agent/mcp-register.js +116 -0
- package/dist/features/agent/mcp-register.js.map +1 -0
- package/dist/features/agent/settings-prune.d.ts +29 -0
- package/dist/features/agent/settings-prune.d.ts.map +1 -0
- package/dist/features/agent/settings-prune.js +70 -0
- package/dist/features/agent/settings-prune.js.map +1 -0
- package/dist/features/agent/tests/settings-prune.test.d.ts +2 -0
- package/dist/features/agent/tests/settings-prune.test.d.ts.map +1 -0
- package/dist/features/agent/tests/settings-prune.test.js +118 -0
- package/dist/features/agent/tests/settings-prune.test.js.map +1 -0
- package/dist/features/federation/commands/consumers.d.ts +40 -0
- package/dist/features/federation/commands/consumers.d.ts.map +1 -0
- package/dist/features/federation/commands/consumers.js +126 -0
- package/dist/features/federation/commands/consumers.js.map +1 -0
- package/dist/features/federation/commands/peers-add.d.ts +14 -0
- package/dist/features/federation/commands/peers-add.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-add.js +79 -0
- package/dist/features/federation/commands/peers-add.js.map +1 -0
- package/dist/features/federation/commands/peers-list.d.ts +8 -0
- package/dist/features/federation/commands/peers-list.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-list.js +51 -0
- package/dist/features/federation/commands/peers-list.js.map +1 -0
- package/dist/features/federation/commands/peers-status.d.ts +8 -0
- package/dist/features/federation/commands/peers-status.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-status.js +78 -0
- package/dist/features/federation/commands/peers-status.js.map +1 -0
- package/dist/features/federation/commands/pull.d.ts +18 -0
- package/dist/features/federation/commands/pull.d.ts.map +1 -0
- package/dist/features/federation/commands/pull.js +153 -0
- package/dist/features/federation/commands/pull.js.map +1 -0
- package/dist/features/federation/git-fetcher.d.ts +45 -0
- package/dist/features/federation/git-fetcher.d.ts.map +1 -0
- package/dist/features/federation/git-fetcher.js +70 -0
- package/dist/features/federation/git-fetcher.js.map +1 -0
- package/dist/features/federation/loader.d.ts +60 -0
- package/dist/features/federation/loader.d.ts.map +1 -0
- package/dist/features/federation/loader.js +193 -0
- package/dist/features/federation/loader.js.map +1 -0
- package/dist/features/federation/lock.d.ts +12 -0
- package/dist/features/federation/lock.d.ts.map +1 -0
- package/dist/features/federation/lock.js +48 -0
- package/dist/features/federation/lock.js.map +1 -0
- package/dist/features/federation/tests/git-fetcher.test.d.ts +2 -0
- package/dist/features/federation/tests/git-fetcher.test.d.ts.map +1 -0
- package/dist/features/federation/tests/git-fetcher.test.js +167 -0
- package/dist/features/federation/tests/git-fetcher.test.js.map +1 -0
- package/dist/features/federation/tests/loader.test.d.ts +2 -0
- package/dist/features/federation/tests/loader.test.d.ts.map +1 -0
- package/dist/features/federation/tests/loader.test.js +144 -0
- package/dist/features/federation/tests/loader.test.js.map +1 -0
- package/dist/features/federation/tests/phase5.test.d.ts +2 -0
- package/dist/features/federation/tests/phase5.test.d.ts.map +1 -0
- package/dist/features/federation/tests/phase5.test.js +137 -0
- package/dist/features/federation/tests/phase5.test.js.map +1 -0
- package/dist/features/federation/tests/schema-load.test.d.ts +2 -0
- package/dist/features/federation/tests/schema-load.test.d.ts.map +1 -0
- package/dist/features/federation/tests/schema-load.test.js +97 -0
- package/dist/features/federation/tests/schema-load.test.js.map +1 -0
- package/dist/features/federation/tests/validator.test.d.ts +2 -0
- package/dist/features/federation/tests/validator.test.d.ts.map +1 -0
- package/dist/features/federation/tests/validator.test.js +319 -0
- package/dist/features/federation/tests/validator.test.js.map +1 -0
- package/dist/features/mcp/commands/serve.d.ts +10 -0
- package/dist/features/mcp/commands/serve.d.ts.map +1 -0
- package/dist/features/mcp/commands/serve.js +12 -0
- package/dist/features/mcp/commands/serve.js.map +1 -0
- package/dist/features/mcp/server.d.ts +15 -0
- package/dist/features/mcp/server.d.ts.map +1 -0
- package/dist/features/mcp/server.js +438 -0
- package/dist/features/mcp/server.js.map +1 -0
- package/dist/features/pipeline/commands/validate.d.ts.map +1 -1
- package/dist/features/pipeline/commands/validate.js +7 -0
- package/dist/features/pipeline/commands/validate.js.map +1 -1
- package/dist/features/pipeline/indexer.d.ts +28 -2
- package/dist/features/pipeline/indexer.d.ts.map +1 -1
- package/dist/features/pipeline/indexer.js +82 -27
- package/dist/features/pipeline/indexer.js.map +1 -1
- package/dist/features/pipeline/validator.d.ts +10 -0
- package/dist/features/pipeline/validator.d.ts.map +1 -1
- package/dist/features/pipeline/validator.js +274 -27
- package/dist/features/pipeline/validator.js.map +1 -1
- package/dist/features/query/commands/list.d.ts +10 -0
- package/dist/features/query/commands/list.d.ts.map +1 -1
- package/dist/features/query/commands/list.js +1 -1
- package/dist/features/query/commands/list.js.map +1 -1
- package/dist/features/query/commands/locate.d.ts +1 -0
- package/dist/features/query/commands/locate.d.ts.map +1 -1
- package/dist/features/query/commands/locate.js +1 -1
- package/dist/features/query/commands/locate.js.map +1 -1
- package/dist/features/query/commands/search.d.ts.map +1 -1
- package/dist/features/query/commands/search.js +2 -0
- package/dist/features/query/commands/search.js.map +1 -1
- package/dist/features/query/commands/show.d.ts +15 -0
- package/dist/features/query/commands/show.d.ts.map +1 -1
- package/dist/features/query/commands/show.js +116 -58
- package/dist/features/query/commands/show.js.map +1 -1
- package/dist/features/query/commands/story.d.ts +70 -0
- package/dist/features/query/commands/story.d.ts.map +1 -1
- package/dist/features/query/commands/story.js +2 -2
- package/dist/features/query/commands/story.js.map +1 -1
- package/dist/features/query/commands/summary.d.ts +3 -0
- package/dist/features/query/commands/summary.d.ts.map +1 -1
- package/dist/features/query/commands/summary.js +1 -1
- package/dist/features/query/commands/summary.js.map +1 -1
- package/dist/features/query/searcher.d.ts +18 -1
- package/dist/features/query/searcher.d.ts.map +1 -1
- package/dist/features/query/searcher.js +11 -2
- package/dist/features/query/searcher.js.map +1 -1
- package/dist/features/scaffold/commands/new-domain.d.ts +22 -0
- package/dist/features/scaffold/commands/new-domain.d.ts.map +1 -1
- package/dist/features/scaffold/commands/new-domain.js +44 -28
- package/dist/features/scaffold/commands/new-domain.js.map +1 -1
- package/dist/features/scaffold/commands/service-init.d.ts +12 -0
- package/dist/features/scaffold/commands/service-init.d.ts.map +1 -0
- package/dist/features/scaffold/commands/service-init.js +69 -0
- package/dist/features/scaffold/commands/service-init.js.map +1 -0
- package/dist/shared/graph.d.ts +8 -0
- package/dist/shared/graph.d.ts.map +1 -1
- package/dist/shared/graph.js +180 -112
- package/dist/shared/graph.js.map +1 -1
- package/dist/shared/index.d.ts +4 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +6 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/loader.d.ts +22 -0
- package/dist/shared/loader.d.ts.map +1 -1
- package/dist/shared/loader.js +31 -1
- package/dist/shared/loader.js.map +1 -1
- package/dist/shared/paths.d.ts +59 -7
- package/dist/shared/paths.d.ts.map +1 -1
- package/dist/shared/paths.js +93 -11
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/refs.d.ts +96 -0
- package/dist/shared/refs.d.ts.map +1 -0
- package/dist/shared/refs.js +182 -0
- package/dist/shared/refs.js.map +1 -0
- package/dist/shared/service-id.d.ts +11 -0
- package/dist/shared/service-id.d.ts.map +1 -0
- package/dist/shared/service-id.js +64 -0
- package/dist/shared/service-id.js.map +1 -0
- package/dist/shared/tests/paths.test.d.ts +2 -0
- package/dist/shared/tests/paths.test.d.ts.map +1 -0
- package/dist/shared/tests/paths.test.js +111 -0
- package/dist/shared/tests/paths.test.js.map +1 -0
- package/dist/shared/tests/refs.test.d.ts +2 -0
- package/dist/shared/tests/refs.test.d.ts.map +1 -0
- package/dist/shared/tests/refs.test.js +104 -0
- package/dist/shared/tests/refs.test.js.map +1 -0
- package/dist/shared/types/domain.d.ts +14 -0
- package/dist/shared/types/domain.d.ts.map +1 -1
- package/dist/shared/types/federation.d.ts +60 -0
- package/dist/shared/types/federation.d.ts.map +1 -0
- package/dist/shared/types/federation.js +12 -0
- package/dist/shared/types/federation.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +8 -5
- package/tools/dkk/claude/agents/dkk-domain-reviewer.md +69 -0
- package/tools/dkk/claude/commands/dkk-adr.md +11 -0
- package/tools/dkk/claude/commands/dkk-impact.md +34 -0
- package/tools/dkk/claude/commands/dkk-implement.md +12 -0
- package/tools/dkk/claude/commands/dkk-prime.md +6 -0
- package/tools/dkk/claude/commands/dkk-review.md +12 -0
- package/tools/dkk/claude/commands/dkk-story.md +12 -0
- package/tools/dkk/claude/hooks/post-edit-validate.mjs +68 -0
- package/tools/dkk/claude/hooks/pre-edit-block-generated.mjs +39 -0
- package/tools/dkk/claude/hooks/session-start-prime.mjs +20 -0
- package/tools/dkk/claude/hooks/stop-validate.mjs +67 -0
- package/tools/dkk/claude/settings.json +62 -0
- package/tools/dkk/claude/skills/dkk-adr-author/SKILL.md +54 -0
- package/tools/dkk/claude/skills/dkk-flow-implementer/SKILL.md +51 -0
- package/tools/dkk/claude/skills/dkk-story-analyst/SKILL.md +108 -0
- package/tools/dkk/schema/actors.schema.json +1 -1
- package/tools/dkk/schema/adr-frontmatter.schema.json +4 -4
- package/tools/dkk/schema/aggregate.schema.json +1 -1
- package/tools/dkk/schema/command.schema.json +1 -1
- package/tools/dkk/schema/event.schema.json +1 -1
- package/tools/dkk/schema/federation.schema.json +71 -0
- package/tools/dkk/schema/glossary.schema.json +1 -1
- package/tools/dkk/schema/index.schema.json +2 -2
- package/tools/dkk/schema/policy.schema.json +1 -1
- package/tools/dkk/schema/read-model.schema.json +1 -1
- package/tools/dkk/schema/service.schema.json +30 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 integration tests — federated indexer, searcher, graph, and
|
|
3
|
+
* `dkk consumers`.
|
|
4
|
+
*
|
|
5
|
+
* Builds a two-repo fixture, then verifies:
|
|
6
|
+
* - Indexer namespaces peer rows with `<service>:` prefix.
|
|
7
|
+
* - Search returns peer hits with the `service` field populated.
|
|
8
|
+
* - Search `service` filter narrows correctly.
|
|
9
|
+
* - Graph contains both local and peer nodes, with peer node ids
|
|
10
|
+
* prefixed.
|
|
11
|
+
* - Local policy with `when.events: ['ordering:ordering.OrderPlaced']`
|
|
12
|
+
* produces a cross-service edge that BFS can traverse.
|
|
13
|
+
* - `findConsumers` walks peers correctly.
|
|
14
|
+
*/
|
|
15
|
+
import { mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { loadDomainModel } from "../../../shared/loader.js";
|
|
19
|
+
import { DomainGraph } from "../../../shared/graph.js";
|
|
20
|
+
import { buildIndex } from "../../pipeline/indexer.js";
|
|
21
|
+
import { search } from "../../query/searcher.js";
|
|
22
|
+
// Importing from consumers.js transitively registers the federation
|
|
23
|
+
// peer-hydration hook (via its side-effect import of ../loader.js).
|
|
24
|
+
import { findConsumers } from "../commands/consumers.js";
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
function assert(label, condition, detail) {
|
|
28
|
+
if (condition) {
|
|
29
|
+
console.log(` OK: ${label}`);
|
|
30
|
+
passed++;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.error(`FAIL: ${label}${detail ? ` — ${detail}` : ""}`);
|
|
34
|
+
failed++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const RAW_TMP = join(tmpdir(), `dkk-p5-${Date.now()}`);
|
|
38
|
+
mkdirSync(RAW_TMP, { recursive: true });
|
|
39
|
+
const TMP = realpathSync(RAW_TMP);
|
|
40
|
+
const ORDER = join(TMP, "order-svc");
|
|
41
|
+
const BILLING = join(TMP, "billing-svc");
|
|
42
|
+
// ── Fixture: order-svc (peer) ───────────────────────────────────────────
|
|
43
|
+
mkdirSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "events"), { recursive: true });
|
|
44
|
+
mkdirSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "aggregates"), { recursive: true });
|
|
45
|
+
mkdirSync(join(ORDER, ".dkk", "adr"), { recursive: true });
|
|
46
|
+
writeFileSync(join(ORDER, ".dkk", "service.yml"), "name: ordering\nexports:\n - ordering\n");
|
|
47
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "index.yml"), "contexts:\n - name: ordering\n description: Orders\nflows: []\n");
|
|
48
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
49
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "context.yml"), "name: ordering\ndescription: Ordering context\n");
|
|
50
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "events", "OrderPlaced.yml"), "name: OrderPlaced\ndescription: Order is placed for processing.\nraised_by: Order\n");
|
|
51
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "aggregates", "Order.yml"), "name: Order\ndescription: Order aggregate.\nemits:\n events:\n - OrderPlaced\n");
|
|
52
|
+
// ── Fixture: billing-svc (local) with a policy that subscribes to peer event ───
|
|
53
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "policies"), { recursive: true });
|
|
54
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "commands"), { recursive: true });
|
|
55
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "aggregates"), { recursive: true });
|
|
56
|
+
mkdirSync(join(BILLING, ".dkk", "adr"), { recursive: true });
|
|
57
|
+
writeFileSync(join(BILLING, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
58
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "index.yml"), "contexts:\n - name: billing\n description: Billing\nflows: []\n");
|
|
59
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
60
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "context.yml"), "name: billing\ndescription: Billing context\n");
|
|
61
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "commands", "IssueInvoice.yml"), "name: IssueInvoice\ndescription: Issue an invoice.\nhandled_by: Invoice\n");
|
|
62
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "aggregates", "Invoice.yml"), "name: Invoice\ndescription: Invoice aggregate.\nhandles:\n commands:\n - IssueInvoice\n");
|
|
63
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "policies", "InitiateInvoice.yml"), [
|
|
64
|
+
"name: InitiateInvoice",
|
|
65
|
+
"description: Issue an invoice when an order is placed.",
|
|
66
|
+
"when:",
|
|
67
|
+
" events:",
|
|
68
|
+
" - ordering:ordering.OrderPlaced",
|
|
69
|
+
"then:",
|
|
70
|
+
" commands:",
|
|
71
|
+
" - IssueInvoice",
|
|
72
|
+
].join("\n"));
|
|
73
|
+
writeFileSync(join(BILLING, ".dkk", "federation.yml"), [
|
|
74
|
+
"peers:",
|
|
75
|
+
" - name: ordering",
|
|
76
|
+
" source:",
|
|
77
|
+
" type: local",
|
|
78
|
+
" path: ../order-svc",
|
|
79
|
+
].join("\n") + "\n");
|
|
80
|
+
try {
|
|
81
|
+
const model = loadDomainModel({ root: BILLING });
|
|
82
|
+
// ── Indexer namespaces peer rows ─────────────────────────────────────
|
|
83
|
+
console.log("\n=== indexer namespaces peer rows ===");
|
|
84
|
+
const dbPath = buildIndex(model, { root: BILLING });
|
|
85
|
+
assert("index.db built", typeof dbPath === "string" && dbPath.length > 0);
|
|
86
|
+
const local = search("OrderPlaced", {}, { root: BILLING });
|
|
87
|
+
assert("search finds OrderPlaced", local.length > 0);
|
|
88
|
+
const peerHit = local.find((r) => r.id === "ordering:ordering.OrderPlaced");
|
|
89
|
+
assert("peer row has service-prefixed id", peerHit !== undefined);
|
|
90
|
+
assert("peer row carries service field", peerHit?.service === "ordering");
|
|
91
|
+
assert("peer row has ordering context", peerHit?.context === "ordering");
|
|
92
|
+
const localHit = local.find((r) => r.id === "billing.InitiateInvoice");
|
|
93
|
+
// (May or may not match depending on FTS tokenization of "OrderPlaced".)
|
|
94
|
+
// What matters is that the peer-prefixed and local rows don't collide.
|
|
95
|
+
assert("local + peer ids are disjoint", !local.some((r, i) => local.findIndex((other) => other.id === r.id && local.indexOf(r) !== i) >= 0));
|
|
96
|
+
void localHit;
|
|
97
|
+
// ── service filter narrows correctly ─────────────────────────────────
|
|
98
|
+
console.log("\n=== search service filter ===");
|
|
99
|
+
const onlyOrdering = search("OrderPlaced", { service: "ordering" }, { root: BILLING });
|
|
100
|
+
assert("service: ordering filter narrows to ordering rows", onlyOrdering.every((r) => r.service === "ordering"));
|
|
101
|
+
assert("service filter returns at least one row", onlyOrdering.length > 0);
|
|
102
|
+
const onlyBilling = search("Invoice", { service: "billing" }, { root: BILLING });
|
|
103
|
+
assert("service: billing filter narrows to billing rows", onlyBilling.every((r) => r.service === "billing"));
|
|
104
|
+
// ── graph contains both namespaces with cross-service edge ───────────
|
|
105
|
+
console.log("\n=== graph cross-service edge ===");
|
|
106
|
+
const graph = DomainGraph.from(model);
|
|
107
|
+
const localPolicyId = "billing.InitiateInvoice";
|
|
108
|
+
const peerEventId = "ordering:ordering.OrderPlaced";
|
|
109
|
+
assert("local policy node exists", graph.nodes.has(localPolicyId));
|
|
110
|
+
assert("peer event node exists with prefix", graph.nodes.has(peerEventId));
|
|
111
|
+
const fromPolicy = graph.getRelated(localPolicyId, 1);
|
|
112
|
+
assert("BFS from local policy reaches peer event in one hop", fromPolicy.has(peerEventId));
|
|
113
|
+
// ── findConsumers — reverse lookup from order-svc's perspective ──────
|
|
114
|
+
console.log("\n=== consumers reverse lookup ===");
|
|
115
|
+
// Build a federation manifest in order-svc that points back at
|
|
116
|
+
// billing-svc, so order-svc can ask "who consumes my OrderPlaced?".
|
|
117
|
+
writeFileSync(join(ORDER, ".dkk", "federation.yml"), [
|
|
118
|
+
"peers:",
|
|
119
|
+
" - name: billing",
|
|
120
|
+
" source:",
|
|
121
|
+
" type: local",
|
|
122
|
+
" path: ../billing-svc",
|
|
123
|
+
].join("\n") + "\n");
|
|
124
|
+
const orderModel = loadDomainModel({ root: ORDER });
|
|
125
|
+
const consumers = findConsumers(orderModel, "ordering.OrderPlaced", orderModel.service?.name);
|
|
126
|
+
assert("at least one consumer found", consumers.length > 0);
|
|
127
|
+
assert("consumer is billing.InitiateInvoice via when.events", consumers.some((c) => c.service === "billing" &&
|
|
128
|
+
c.relation === "when.events" &&
|
|
129
|
+
c.source === "billing:billing.InitiateInvoice"));
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
rmSync(RAW_TMP, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
135
|
+
if (failed > 0)
|
|
136
|
+
process.exit(1);
|
|
137
|
+
//# sourceMappingURL=phase5.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase5.test.js","sourceRoot":"","sources":["../../../../src/features/federation/tests/phase5.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-load.test.d.ts","sourceRoot":"","sources":["../../../../src/features/federation/tests/schema-load.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-at-load tests — `.dkk/service.yml` and `.dkk/federation.yml`
|
|
3
|
+
* are now validated against their JSON schemas when read, so malformed
|
|
4
|
+
* manifests produce a clean error up front rather than crashing later
|
|
5
|
+
* in the pipeline.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { loadServiceId } from "../../../shared/service-id.js";
|
|
11
|
+
import { loadFederation } from "../loader.js";
|
|
12
|
+
let passed = 0;
|
|
13
|
+
let failed = 0;
|
|
14
|
+
function assert(label, condition, detail) {
|
|
15
|
+
if (condition) {
|
|
16
|
+
console.log(` OK: ${label}`);
|
|
17
|
+
passed++;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.error(`FAIL: ${label}${detail ? ` — ${detail}` : ""}`);
|
|
21
|
+
failed++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function assertThrows(label, fn, match) {
|
|
25
|
+
try {
|
|
26
|
+
fn();
|
|
27
|
+
console.error(`FAIL: ${label} — expected throw, got success`);
|
|
28
|
+
failed++;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32
|
+
if (match.test(msg)) {
|
|
33
|
+
console.log(` OK: ${label}`);
|
|
34
|
+
passed++;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.error(`FAIL: ${label} — error did not match: ${msg}`);
|
|
38
|
+
failed++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const RAW_TMP = join(tmpdir(), `dkk-schema-load-${Date.now()}`);
|
|
43
|
+
mkdirSync(RAW_TMP, { recursive: true });
|
|
44
|
+
const TMP = realpathSync(RAW_TMP);
|
|
45
|
+
const ROOT = join(TMP, "repo");
|
|
46
|
+
mkdirSync(join(ROOT, ".dkk"), { recursive: true });
|
|
47
|
+
try {
|
|
48
|
+
// ── service.yml: valid manifests load ────────────────────────────────
|
|
49
|
+
console.log("\n=== service.yml: valid ===");
|
|
50
|
+
writeFileSync(join(ROOT, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
51
|
+
const ok = loadServiceId(ROOT);
|
|
52
|
+
assert("valid manifest loads", ok?.name === "billing");
|
|
53
|
+
// ── service.yml: PascalCase name rejected ────────────────────────────
|
|
54
|
+
console.log("\n=== service.yml: invalid name ===");
|
|
55
|
+
writeFileSync(join(ROOT, ".dkk", "service.yml"), "name: Billing\nexports:\n - billing\n");
|
|
56
|
+
assertThrows("PascalCase service name rejected", () => loadServiceId(ROOT), /Invalid.*service\.yml/);
|
|
57
|
+
// ── service.yml: missing required field ──────────────────────────────
|
|
58
|
+
console.log("\n=== service.yml: missing exports ===");
|
|
59
|
+
writeFileSync(join(ROOT, ".dkk", "service.yml"), "name: billing\n");
|
|
60
|
+
assertThrows("missing exports rejected", () => loadServiceId(ROOT), /exports/);
|
|
61
|
+
// ── service.yml: unknown property rejected ───────────────────────────
|
|
62
|
+
console.log("\n=== service.yml: unknown property ===");
|
|
63
|
+
writeFileSync(join(ROOT, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\nrogue: true\n");
|
|
64
|
+
assertThrows("unknown property rejected", () => loadServiceId(ROOT), /additional/i);
|
|
65
|
+
// Restore a valid service.yml for federation tests.
|
|
66
|
+
writeFileSync(join(ROOT, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
67
|
+
// ── federation.yml: valid local peer ─────────────────────────────────
|
|
68
|
+
console.log("\n=== federation.yml: valid local ===");
|
|
69
|
+
writeFileSync(join(ROOT, ".dkk", "federation.yml"), "peers:\n - name: ordering\n source:\n type: local\n path: ../order-svc\n");
|
|
70
|
+
const fed = loadFederation(ROOT);
|
|
71
|
+
assert("valid local peer loads", fed?.peers[0].name === "ordering");
|
|
72
|
+
// ── federation.yml: missing source ───────────────────────────────────
|
|
73
|
+
console.log("\n=== federation.yml: missing source ===");
|
|
74
|
+
writeFileSync(join(ROOT, ".dkk", "federation.yml"), "peers:\n - name: ordering\n");
|
|
75
|
+
assertThrows("missing source rejected", () => loadFederation(ROOT), /Invalid.*federation\.yml/);
|
|
76
|
+
// ── federation.yml: invalid source type ──────────────────────────────
|
|
77
|
+
console.log("\n=== federation.yml: bogus source type ===");
|
|
78
|
+
writeFileSync(join(ROOT, ".dkk", "federation.yml"), "peers:\n - name: ordering\n source:\n type: ftp\n path: x\n");
|
|
79
|
+
assertThrows("invalid source type rejected", () => loadFederation(ROOT), /Invalid.*federation\.yml/);
|
|
80
|
+
// ── federation.yml: git source missing branch ────────────────────────
|
|
81
|
+
console.log("\n=== federation.yml: git missing branch ===");
|
|
82
|
+
writeFileSync(join(ROOT, ".dkk", "federation.yml"), "peers:\n - name: ordering\n source:\n type: git\n url: https://example.com/repo.git\n");
|
|
83
|
+
assertThrows("git without branch rejected", () => loadFederation(ROOT), /branch/);
|
|
84
|
+
// ── No manifest = null (no error) ────────────────────────────────────
|
|
85
|
+
console.log("\n=== absent manifest returns null ===");
|
|
86
|
+
rmSync(join(ROOT, ".dkk", "service.yml"));
|
|
87
|
+
rmSync(join(ROOT, ".dkk", "federation.yml"));
|
|
88
|
+
assert("loadServiceId returns null when absent", loadServiceId(ROOT) === null);
|
|
89
|
+
assert("loadFederation returns null when absent", loadFederation(ROOT) === null);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
rmSync(RAW_TMP, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
95
|
+
if (failed > 0)
|
|
96
|
+
process.exit(1);
|
|
97
|
+
//# sourceMappingURL=schema-load.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-load.test.js","sourceRoot":"","sources":["../../../../src/features/federation/tests/schema-load.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,IAAI,MAAM,GAAG,CAAC,CAAC;AACf,IAAI,MAAM,GAAG,CAAC,CAAC;AAEf,SAAS,MAAM,CAAC,KAAa,EAAE,SAAkB,EAAE,MAAe;IAChE,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;QAC9B,MAAM,EAAE,CAAC;IACX,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,SAAS,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,EAAE,CAAC;IACX,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,EAAiB,EAAE,KAAa;IACnE,IAAI,CAAC;QACH,EAAE,EAAE,CAAC;QACL,OAAO,CAAC,KAAK,CAAC,SAAS,KAAK,gCAAgC,CAAC,CAAC;QAC9D,MAAM,EAAE,CAAC;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;YAC9B,MAAM,EAAE,CAAC;QACX,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,SAAS,KAAK,2BAA2B,GAAG,EAAE,CAAC,CAAC;YAC9D,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAChE,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACxC,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;AAClC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAC/B,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAEnD,IAAI,CAAC;IACH,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC5C,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,wCAAwC,CAAC,CAAC;IAC3F,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,sBAAsB,EAAE,EAAE,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC;IAEvD,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;IACnD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,wCAAwC,CAAC,CAAC;IAC3F,YAAY,CAAC,kCAAkC,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,uBAAuB,CAAC,CAAC;IAErG,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACtD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,iBAAiB,CAAC,CAAC;IACpE,YAAY,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAE/E,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACvD,aAAa,CACX,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EACjC,qDAAqD,CACtD,CAAC;IACF,YAAY,CAAC,2BAA2B,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,CAAC;IAEpF,oDAAoD;IACpD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,wCAAwC,CAAC,CAAC;IAE3F,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACrD,aAAa,CACX,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,EACpC,wFAAwF,CACzF,CAAC;IACF,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,CAAC,wBAAwB,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAEpE,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;IACxD,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,8BAA8B,CAAC,CAAC;IACpF,YAAY,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,0BAA0B,CAAC,CAAC;IAEhG,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,aAAa,CACX,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,EACpC,2EAA2E,CAC5E,CAAC;IACF,YAAY,CAAC,8BAA8B,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,0BAA0B,CAAC,CAAC;IAErG,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC5D,aAAa,CACX,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,EACpC,qGAAqG,CACtG,CAAC;IACF,YAAY,CAAC,6BAA6B,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;IAElF,wEAAwE;IACxE,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACtD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,wCAAwC,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;IAC/E,MAAM,CAAC,yCAAyC,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;AACnF,CAAC;QAAS,CAAC;IACT,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,YAAY,MAAM,SAAS,CAAC,CAAC;AACpD,IAAI,MAAM,GAAG,CAAC;IAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.test.d.ts","sourceRoot":"","sources":["../../../../src/features/federation/tests/validator.test.ts"],"names":[],"mappings":"AAsBA,OAAO,cAAc,CAAC"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-service ref validation tests (Phase 4).
|
|
3
|
+
*
|
|
4
|
+
* Covers the federation-aware paths in the validator:
|
|
5
|
+
* - Bare refs to peer items DO NOT resolve (no fall-through).
|
|
6
|
+
* - Service-prefixed refs to a loaded peer's items resolve.
|
|
7
|
+
* - Service-prefixed refs to missing peer items error.
|
|
8
|
+
* - Service-prefixed refs to an unreachable peer warn (lenient) or
|
|
9
|
+
* error (strict).
|
|
10
|
+
* - ADR domain_refs accept service-prefixed forms.
|
|
11
|
+
* - Flow step refs accept service-prefixed forms.
|
|
12
|
+
* - Self-prefix (`<localService>:foo.Bar`) is treated as local.
|
|
13
|
+
* - Refs to peer items in a non-exported context warn.
|
|
14
|
+
* - Malformed federated refs (colon present but unparseable) error.
|
|
15
|
+
*/
|
|
16
|
+
import { mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { loadDomainModel } from "../../../shared/loader.js";
|
|
20
|
+
import { validateDomainModel } from "../../pipeline/validator.js";
|
|
21
|
+
// Import the federation slice for its side-effect: registering the
|
|
22
|
+
// peer-hydration hook with the shared loader.
|
|
23
|
+
import "../loader.js";
|
|
24
|
+
let passed = 0;
|
|
25
|
+
let failed = 0;
|
|
26
|
+
function assert(label, condition, detail) {
|
|
27
|
+
if (condition) {
|
|
28
|
+
console.log(` OK: ${label}`);
|
|
29
|
+
passed++;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.error(`FAIL: ${label}${detail ? ` — ${detail}` : ""}`);
|
|
33
|
+
failed++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const RAW_TMP = join(tmpdir(), `dkk-fed-val-${Date.now()}`);
|
|
37
|
+
mkdirSync(RAW_TMP, { recursive: true });
|
|
38
|
+
const TMP = realpathSync(RAW_TMP);
|
|
39
|
+
const ORDER = join(TMP, "order-svc");
|
|
40
|
+
const BILLING = join(TMP, "billing-svc");
|
|
41
|
+
// ── Fixture: order-svc ─────────────────────────────────────────────────
|
|
42
|
+
mkdirSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "events"), { recursive: true });
|
|
43
|
+
mkdirSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "aggregates"), { recursive: true });
|
|
44
|
+
mkdirSync(join(ORDER, ".dkk", "adr"), { recursive: true });
|
|
45
|
+
writeFileSync(join(ORDER, ".dkk", "service.yml"), "name: ordering\nexports:\n - ordering\n");
|
|
46
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "index.yml"), "contexts:\n - name: ordering\n description: Customer orders\nflows: []\n");
|
|
47
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
48
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "context.yml"), "name: ordering\ndescription: Customer orders\n");
|
|
49
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "events", "OrderPlaced.yml"), "name: OrderPlaced\ndescription: Raised on placement.\nraised_by: Order\n");
|
|
50
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "events", "OrderCancelled.yml"), "name: OrderCancelled\ndescription: Raised on cancellation.\nraised_by: Order\n");
|
|
51
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "contexts", "ordering", "aggregates", "Order.yml"), "name: Order\ndescription: Order aggregate.\nemits:\n events:\n - OrderPlaced\n - OrderCancelled\n");
|
|
52
|
+
writeFileSync(join(ORDER, ".dkk", "adr", "adr-0042.md"), ["---", "id: adr-0042", "title: Order ADR", "status: accepted", "date: 2026-01-01", "---", "# body"].join("\n"));
|
|
53
|
+
// ── Fixture builder: billing-svc with configurable policy + ADR ──────
|
|
54
|
+
function writeBilling(opts) {
|
|
55
|
+
// Clean and recreate.
|
|
56
|
+
rmSync(BILLING, { recursive: true, force: true });
|
|
57
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "policies"), { recursive: true });
|
|
58
|
+
mkdirSync(join(BILLING, ".dkk", "adr"), { recursive: true });
|
|
59
|
+
writeFileSync(join(BILLING, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
60
|
+
const indexYml = [
|
|
61
|
+
"contexts:",
|
|
62
|
+
" - name: billing",
|
|
63
|
+
" description: Billing",
|
|
64
|
+
];
|
|
65
|
+
if (opts.flowStepRef) {
|
|
66
|
+
indexYml.push("flows:", " - name: BillFlow", " description: bill", " steps:", ` - ref: ${opts.flowStepRef}`, " type: event");
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
indexYml.push("flows: []");
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "index.yml"), indexYml.join("\n") + "\n");
|
|
72
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
73
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "context.yml"), "name: billing\ndescription: Billing context\n");
|
|
74
|
+
const policyEvents = opts.policyWhen.map((e) => ` - ${e}`).join("\n");
|
|
75
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "policies", "InitiateRefund.yml"), [
|
|
76
|
+
"name: InitiateRefund",
|
|
77
|
+
"description: Issue a refund.",
|
|
78
|
+
"when:",
|
|
79
|
+
" events:",
|
|
80
|
+
policyEvents,
|
|
81
|
+
].join("\n"));
|
|
82
|
+
// ADR referring to a (possibly peer) item.
|
|
83
|
+
const refs = (opts.adrDomainRefs ?? []).map((r) => ` - ${r}`).join("\n");
|
|
84
|
+
writeFileSync(join(BILLING, ".dkk", "adr", "adr-0001.md"), [
|
|
85
|
+
"---",
|
|
86
|
+
"id: adr-0001",
|
|
87
|
+
"title: Billing ADR",
|
|
88
|
+
"status: accepted",
|
|
89
|
+
"date: 2026-01-01",
|
|
90
|
+
...(opts.adrDomainRefs?.length ? ["domain_refs:", refs] : []),
|
|
91
|
+
"---",
|
|
92
|
+
"# body",
|
|
93
|
+
].join("\n"));
|
|
94
|
+
if (opts.federationLocal !== undefined) {
|
|
95
|
+
writeFileSync(join(BILLING, ".dkk", "federation.yml"), [
|
|
96
|
+
"peers:",
|
|
97
|
+
" - name: ordering",
|
|
98
|
+
" source:",
|
|
99
|
+
" type: local",
|
|
100
|
+
` path: ${opts.federationLocal}`,
|
|
101
|
+
].join("\n") + "\n");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
// ── Service-prefixed ref to a loaded peer item resolves ──────────────
|
|
106
|
+
console.log("\n=== peer ref resolves ===");
|
|
107
|
+
writeBilling({
|
|
108
|
+
policyWhen: ["ordering:ordering.OrderCancelled"],
|
|
109
|
+
federationLocal: "../order-svc",
|
|
110
|
+
});
|
|
111
|
+
let model = loadDomainModel({ root: BILLING });
|
|
112
|
+
let result = validateDomainModel(model);
|
|
113
|
+
assert("policy with ordering:ordering.OrderCancelled is valid", result.valid, JSON.stringify(result.errors));
|
|
114
|
+
assert("no peer-related warnings", result.warnings.filter((w) => w.message.includes("peer")).length === 0);
|
|
115
|
+
// ── Service-prefixed ref to a missing peer item errors ───────────────
|
|
116
|
+
console.log("\n=== peer ref to missing item errors ===");
|
|
117
|
+
writeBilling({
|
|
118
|
+
policyWhen: ["ordering:ordering.OrderNonExistent"],
|
|
119
|
+
federationLocal: "../order-svc",
|
|
120
|
+
});
|
|
121
|
+
model = loadDomainModel({ root: BILLING });
|
|
122
|
+
result = validateDomainModel(model);
|
|
123
|
+
assert("validation fails on missing peer item", !result.valid);
|
|
124
|
+
assert("error mentions peer ref", result.errors.some((e) => e.message.includes("ordering:ordering.OrderNonExistent")));
|
|
125
|
+
// ── Bare ref to peer-only item is NOT silently accepted ──────────────
|
|
126
|
+
console.log("\n=== bare ref does not fall through to peer ===");
|
|
127
|
+
writeBilling({
|
|
128
|
+
policyWhen: ["OrderCancelled"], // exists ONLY in peer; bare name in billing → must fail
|
|
129
|
+
federationLocal: "../order-svc",
|
|
130
|
+
});
|
|
131
|
+
model = loadDomainModel({ root: BILLING });
|
|
132
|
+
result = validateDomainModel(model);
|
|
133
|
+
assert("validation fails on bare ref pointing at peer-only item", !result.valid);
|
|
134
|
+
assert("error mentions bare name not the peer name", result.errors.some((e) => e.message.includes("OrderCancelled") && e.message.includes("billing")));
|
|
135
|
+
// ── Unreachable peer: lenient warns, strict errors ───────────────────
|
|
136
|
+
console.log("\n=== unreachable peer ===");
|
|
137
|
+
writeBilling({
|
|
138
|
+
policyWhen: ["ordering:ordering.OrderCancelled"],
|
|
139
|
+
federationLocal: "../does-not-exist",
|
|
140
|
+
});
|
|
141
|
+
model = loadDomainModel({ root: BILLING });
|
|
142
|
+
result = validateDomainModel(model);
|
|
143
|
+
assert("lenient: unreachable peer warns", result.valid &&
|
|
144
|
+
result.warnings.some((w) => w.message.includes("not loaded") && w.message.includes("ordering")));
|
|
145
|
+
result = validateDomainModel(model, { federation: "strict" });
|
|
146
|
+
assert("strict: unreachable peer errors", !result.valid &&
|
|
147
|
+
result.errors.some((e) => e.message.includes("not loaded") && e.message.includes("ordering")));
|
|
148
|
+
// ── ADR domain_refs with peer prefix ─────────────────────────────────
|
|
149
|
+
console.log("\n=== ADR domain_refs accept peer prefix ===");
|
|
150
|
+
writeBilling({
|
|
151
|
+
policyWhen: ["ordering:ordering.OrderPlaced"],
|
|
152
|
+
adrDomainRefs: ["ordering:ordering.OrderPlaced"],
|
|
153
|
+
federationLocal: "../order-svc",
|
|
154
|
+
});
|
|
155
|
+
model = loadDomainModel({ root: BILLING });
|
|
156
|
+
result = validateDomainModel(model);
|
|
157
|
+
assert("ADR domain_ref resolves into peer", result.valid, JSON.stringify(result.errors));
|
|
158
|
+
// ── ADR adr_refs accept service-qualified ADR ────────────────────────
|
|
159
|
+
// Skipped at fixture level — exercised indirectly via the peer-loaded
|
|
160
|
+
// adrs map. Add a focused test:
|
|
161
|
+
console.log("\n=== peer ADR ref via superseded_by ===");
|
|
162
|
+
rmSync(BILLING, { recursive: true, force: true });
|
|
163
|
+
mkdirSync(join(BILLING, ".dkk", "adr"), { recursive: true });
|
|
164
|
+
mkdirSync(join(BILLING, ".dkk", "domain"), { recursive: true });
|
|
165
|
+
writeFileSync(join(BILLING, ".dkk", "service.yml"), "name: billing\nexports: []\n");
|
|
166
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "index.yml"), "contexts: []\nflows: []\n");
|
|
167
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
168
|
+
writeFileSync(join(BILLING, ".dkk", "federation.yml"), [
|
|
169
|
+
"peers:",
|
|
170
|
+
" - name: ordering",
|
|
171
|
+
" source:",
|
|
172
|
+
" type: local",
|
|
173
|
+
" path: ../order-svc",
|
|
174
|
+
].join("\n") + "\n");
|
|
175
|
+
writeFileSync(join(BILLING, ".dkk", "adr", "adr-0001.md"), [
|
|
176
|
+
"---",
|
|
177
|
+
"id: adr-0001",
|
|
178
|
+
"title: Supersedes a peer ADR",
|
|
179
|
+
"status: superseded",
|
|
180
|
+
"date: 2026-01-01",
|
|
181
|
+
"superseded_by: ordering:adr-0042",
|
|
182
|
+
"---",
|
|
183
|
+
"# body",
|
|
184
|
+
].join("\n"));
|
|
185
|
+
model = loadDomainModel({ root: BILLING });
|
|
186
|
+
result = validateDomainModel(model);
|
|
187
|
+
assert("superseded_by with peer prefix resolves", result.valid, JSON.stringify(result.errors));
|
|
188
|
+
// missing peer ADR errors
|
|
189
|
+
writeFileSync(join(BILLING, ".dkk", "adr", "adr-0001.md"), [
|
|
190
|
+
"---",
|
|
191
|
+
"id: adr-0001",
|
|
192
|
+
"title: Bad",
|
|
193
|
+
"status: superseded",
|
|
194
|
+
"date: 2026-01-01",
|
|
195
|
+
"superseded_by: ordering:adr-9999",
|
|
196
|
+
"---",
|
|
197
|
+
"# body",
|
|
198
|
+
].join("\n"));
|
|
199
|
+
model = loadDomainModel({ root: BILLING });
|
|
200
|
+
result = validateDomainModel(model);
|
|
201
|
+
assert("superseded_by to missing peer ADR errors", !result.valid &&
|
|
202
|
+
result.errors.some((e) => e.message.includes("ordering:adr-9999")));
|
|
203
|
+
// ── Flow step ref with peer prefix ───────────────────────────────────
|
|
204
|
+
console.log("\n=== flow step refs accept peer prefix ===");
|
|
205
|
+
writeBilling({
|
|
206
|
+
policyWhen: ["ordering:ordering.OrderPlaced"],
|
|
207
|
+
flowStepRef: "ordering:ordering.OrderPlaced",
|
|
208
|
+
federationLocal: "../order-svc",
|
|
209
|
+
});
|
|
210
|
+
model = loadDomainModel({ root: BILLING });
|
|
211
|
+
result = validateDomainModel(model);
|
|
212
|
+
assert("flow step with peer prefix resolves", result.valid, JSON.stringify(result.errors));
|
|
213
|
+
// ── Malformed federated ref errors ───────────────────────────────────
|
|
214
|
+
console.log("\n=== malformed federated ref errors ===");
|
|
215
|
+
writeBilling({
|
|
216
|
+
policyWhen: ["ordering:NotARealForm"],
|
|
217
|
+
federationLocal: "../order-svc",
|
|
218
|
+
});
|
|
219
|
+
model = loadDomainModel({ root: BILLING });
|
|
220
|
+
result = validateDomainModel(model);
|
|
221
|
+
assert("ref with colon but no dot fails parse and errors", !result.valid &&
|
|
222
|
+
result.errors.some((e) => e.message.includes("looks federated")));
|
|
223
|
+
// ── Non-exported context warns ───────────────────────────────────────
|
|
224
|
+
console.log("\n=== non-exported context warns ===");
|
|
225
|
+
// Strip exports on the order-svc service.yml to test the warning.
|
|
226
|
+
writeFileSync(join(ORDER, ".dkk", "service.yml"), "name: ordering\nexports: []\n");
|
|
227
|
+
writeBilling({
|
|
228
|
+
policyWhen: ["ordering:ordering.OrderPlaced"],
|
|
229
|
+
federationLocal: "../order-svc",
|
|
230
|
+
});
|
|
231
|
+
model = loadDomainModel({ root: BILLING });
|
|
232
|
+
result = validateDomainModel(model);
|
|
233
|
+
// The item exists in peer; the warning should be about non-export.
|
|
234
|
+
// Validation should still pass (warning, not error).
|
|
235
|
+
// Note: when exports is empty array, our helper skips the warning
|
|
236
|
+
// (we only warn when exports has entries but doesn't include the
|
|
237
|
+
// referenced context). That's fine — an empty exports[] means "no
|
|
238
|
+
// strong declaration", not "this is private".
|
|
239
|
+
assert("empty exports does not warn or error", result.valid, JSON.stringify(result.errors));
|
|
240
|
+
// Restore the proper exports for any later runs.
|
|
241
|
+
writeFileSync(join(ORDER, ".dkk", "service.yml"), "name: ordering\nexports:\n - ordering\n");
|
|
242
|
+
// ── Self-prefix resolves locally (regression for the bug where a
|
|
243
|
+
// self-prefixed form `<localSvc>:<id>` failed lookup because the
|
|
244
|
+
// validator did set.has(rawString)) ─────────────────────────────────
|
|
245
|
+
console.log("\n=== self-prefix resolves locally ===");
|
|
246
|
+
// ADR self-prefix (`<localSvc>:adr-NNNN`)
|
|
247
|
+
rmSync(BILLING, { recursive: true, force: true });
|
|
248
|
+
mkdirSync(join(BILLING, ".dkk", "adr"), { recursive: true });
|
|
249
|
+
mkdirSync(join(BILLING, ".dkk", "domain"), { recursive: true });
|
|
250
|
+
writeFileSync(join(BILLING, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
251
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "index.yml"), "contexts: []\nflows: []\n");
|
|
252
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
253
|
+
writeFileSync(join(BILLING, ".dkk", "adr", "adr-0001.md"), ["---", "id: adr-0001", "title: First", "status: accepted", "date: 2026-01-01", "---", "# body"].join("\n"));
|
|
254
|
+
writeFileSync(join(BILLING, ".dkk", "adr", "adr-0002.md"), [
|
|
255
|
+
"---",
|
|
256
|
+
"id: adr-0002",
|
|
257
|
+
"title: Successor",
|
|
258
|
+
"status: superseded",
|
|
259
|
+
"date: 2026-02-01",
|
|
260
|
+
"superseded_by: billing:adr-0001",
|
|
261
|
+
"---",
|
|
262
|
+
"# body",
|
|
263
|
+
].join("\n"));
|
|
264
|
+
model = loadDomainModel({ root: BILLING });
|
|
265
|
+
result = validateDomainModel(model);
|
|
266
|
+
assert("self-prefix superseded_by (`billing:adr-0001`) resolves to local ADR", result.valid, JSON.stringify(result.errors));
|
|
267
|
+
// Self-prefix on a domain item: a local policy with
|
|
268
|
+
// `when.events: ['billing:billing.LocalEvent']` should resolve.
|
|
269
|
+
rmSync(BILLING, { recursive: true, force: true });
|
|
270
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "events"), { recursive: true });
|
|
271
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "policies"), { recursive: true });
|
|
272
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "aggregates"), { recursive: true });
|
|
273
|
+
mkdirSync(join(BILLING, ".dkk", "adr"), { recursive: true });
|
|
274
|
+
writeFileSync(join(BILLING, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
275
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "index.yml"), "contexts:\n - name: billing\n description: Billing\nflows: []\n");
|
|
276
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
277
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "context.yml"), "name: billing\ndescription: Billing\n");
|
|
278
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "events", "LocalEvent.yml"), "name: LocalEvent\ndescription: A local event.\nraised_by: billing:billing.LocalAgg\n");
|
|
279
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "aggregates", "LocalAgg.yml"), "name: LocalAgg\ndescription: Local aggregate.\nemits:\n events:\n - LocalEvent\n");
|
|
280
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "policies", "P.yml"), [
|
|
281
|
+
"name: P",
|
|
282
|
+
"description: Self-prefix policy",
|
|
283
|
+
"when:",
|
|
284
|
+
" events:",
|
|
285
|
+
" - billing:billing.LocalEvent",
|
|
286
|
+
].join("\n"));
|
|
287
|
+
model = loadDomainModel({ root: BILLING });
|
|
288
|
+
result = validateDomainModel(model);
|
|
289
|
+
assert("self-prefix when.events (`billing:billing.LocalEvent`) resolves locally", result.valid, JSON.stringify(result.errors));
|
|
290
|
+
assert("self-prefix raised_by (`billing:billing.LocalAgg`) resolves locally", !result.errors.some((e) => e.message.includes("raised_by")));
|
|
291
|
+
// ── Cross-service actor ref via command.actor ────────────────────────
|
|
292
|
+
console.log("\n=== cross-service actor refs ===");
|
|
293
|
+
// Add an external actor to order-svc.
|
|
294
|
+
writeFileSync(join(ORDER, ".dkk", "domain", "actors.yml"), "actors:\n - name: PaymentGateway\n type: external\n description: 3P payment processor.\n");
|
|
295
|
+
// billing has a command whose actor lives in ordering.
|
|
296
|
+
rmSync(BILLING, { recursive: true, force: true });
|
|
297
|
+
mkdirSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "commands"), { recursive: true });
|
|
298
|
+
writeFileSync(join(BILLING, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
299
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "index.yml"), "contexts:\n - name: billing\n description: Billing\nflows: []\n");
|
|
300
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
301
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "context.yml"), "name: billing\ndescription: Billing\n");
|
|
302
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "commands", "Charge.yml"), "name: Charge\ndescription: Charge a card.\nactor: ordering:actor.PaymentGateway\n");
|
|
303
|
+
writeFileSync(join(BILLING, ".dkk", "federation.yml"), "peers:\n - name: ordering\n source:\n type: local\n path: ../order-svc\n");
|
|
304
|
+
model = loadDomainModel({ root: BILLING });
|
|
305
|
+
result = validateDomainModel(model);
|
|
306
|
+
assert("cross-service actor ref resolves into peer actors", result.valid, JSON.stringify(result.errors));
|
|
307
|
+
// Missing peer actor errors.
|
|
308
|
+
writeFileSync(join(BILLING, ".dkk", "domain", "contexts", "billing", "commands", "Charge.yml"), "name: Charge\ndescription: Charge a card.\nactor: ordering:actor.NoSuchActor\n");
|
|
309
|
+
model = loadDomainModel({ root: BILLING });
|
|
310
|
+
result = validateDomainModel(model);
|
|
311
|
+
assert("missing peer actor errors", !result.valid && result.errors.some((e) => e.message.includes("NoSuchActor")));
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
rmSync(RAW_TMP, { recursive: true, force: true });
|
|
315
|
+
}
|
|
316
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
317
|
+
if (failed > 0)
|
|
318
|
+
process.exit(1);
|
|
319
|
+
//# sourceMappingURL=validator.test.js.map
|