@kontourai/flow-agents 0.2.0 → 0.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/release-please.yml +13 -1
- package/.github/workflows/runtime-compat.yml +1 -1
- package/AGENTS.md +8 -1
- package/CHANGELOG.md +41 -0
- package/README.md +38 -19
- package/build/src/cli/flow-kit.js +9 -4
- package/build/src/cli/runtime-adapter.js +9 -5
- package/build/src/cli/telemetry-doctor.js +4 -1
- package/build/src/runtime-adapters.js +34 -0
- package/build/src/tools/build-universal-bundles.js +18 -1
- package/console.telemetry.json +115 -20
- package/docs/_layouts/default.html +2 -0
- package/docs/index.md +8 -0
- package/docs/integrations/index.md +4 -0
- package/docs/integrations/knowledge-kit-live.md +211 -0
- package/docs/kit-authoring-guide.md +169 -0
- package/docs/spec/runtime-hook-surface.md +56 -3
- package/evals/acceptance/run.sh +10 -1
- package/evals/acceptance/test_knowledge_kit_live.sh +221 -0
- package/evals/acceptance/test_pi_harness.sh +15 -0
- package/evals/integration/test_runtime_adapter_activation.sh +113 -1
- package/evals/static/test_universal_bundles.sh +10 -0
- package/integrations/strands/examples/knowledge_kit_live.py +461 -0
- package/integrations/strands/flow_agents_strands/steering.py +54 -1
- package/integrations/strands/tests/test_hooks.py +88 -0
- package/integrations/strands-ts/src/hooks.ts +104 -0
- package/integrations/strands-ts/test/test-steering.ts +159 -0
- package/kits/catalog.json +6 -0
- package/kits/knowledge/adapters/default-store/index.js +902 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1469 -0
- package/kits/knowledge/adapters/flow-runner/telemetry.js +174 -0
- package/kits/knowledge/adapters/similarity-vector/index.js +284 -0
- package/kits/knowledge/docs/README.md +328 -0
- package/kits/knowledge/docs/store-contract.md +650 -0
- package/kits/knowledge/evals/consolidation/suite.test.js +1234 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +675 -0
- package/kits/knowledge/evals/ingest-compile/suite.test.js +574 -0
- package/kits/knowledge/evals/retirement/suite.test.js +1173 -0
- package/kits/knowledge/evals/similarity-vector/suite.test.js +685 -0
- package/kits/knowledge/evals/synthesis/suite.test.js +916 -0
- package/kits/knowledge/flows/compile.flow.json +60 -0
- package/kits/knowledge/flows/consolidate.flow.json +77 -0
- package/kits/knowledge/flows/ingest.flow.json +60 -0
- package/kits/knowledge/flows/retire.flow.json +77 -0
- package/kits/knowledge/flows/store-contract.flow.json +48 -0
- package/kits/knowledge/flows/synthesize.flow.json +77 -0
- package/kits/knowledge/kit.json +98 -0
- package/package.json +1 -1
- package/src/cli/flow-kit.ts +10 -4
- package/src/cli/runtime-adapter.ts +10 -5
- package/src/cli/telemetry-doctor.ts +4 -1
- package/src/runtime-adapters.ts +35 -0
- package/src/tools/build-universal-bundles.ts +18 -1
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Knowledge Kit Store Contract
|
|
3
|
+
version: "1.0"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Knowledge Kit Store Contract
|
|
7
|
+
|
|
8
|
+
This document defines the storage contract for the Knowledge Kit. Any store adapter that
|
|
9
|
+
implements this contract can be substituted for the default adapter without changing kit flows.
|
|
10
|
+
The contract is self-contained: a second adapter author should be able to read this document
|
|
11
|
+
alone and produce a conforming implementation.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Record Types
|
|
16
|
+
|
|
17
|
+
The store holds three record types. Every record, regardless of type, shares a common envelope.
|
|
18
|
+
|
|
19
|
+
### 1.1 Common Record Envelope
|
|
20
|
+
|
|
21
|
+
| Field | Type | Required | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `id` | string | yes | Stable, opaque identifier. Must be unique within the store. Adapter may generate (e.g. UUID or slug). |
|
|
24
|
+
| `type` | `"raw"` \| `"compiled"` \| `"concept"` | yes | Discriminates the record type. |
|
|
25
|
+
| `title` | string | yes | Human-readable title. |
|
|
26
|
+
| `body` | string | yes | Primary content. Format is type-specific (see below). |
|
|
27
|
+
| `category` | string | yes | Dot-separated hierarchical category string, e.g. `"engineering.api"`. Must be non-empty. |
|
|
28
|
+
| `tags` | string[] | no | Flat list of tag strings for secondary classification. |
|
|
29
|
+
| `links` | Link[] | no | Outbound links from this record (see §2). |
|
|
30
|
+
| `provenance` | Provenance | yes | Immutable creation provenance (see §4). |
|
|
31
|
+
| `created_at` | ISO-8601 string | yes | Creation timestamp in UTC. |
|
|
32
|
+
| `updated_at` | ISO-8601 string | yes | Last mutation timestamp in UTC. |
|
|
33
|
+
|
|
34
|
+
### 1.2 `raw` Record
|
|
35
|
+
|
|
36
|
+
A raw record holds unprocessed source material exactly as received — a document excerpt,
|
|
37
|
+
a transcript snippet, a URL with notes, or any other unrefined input.
|
|
38
|
+
|
|
39
|
+
- `body`: free-form string, preserved verbatim.
|
|
40
|
+
- No semantic constraints on content.
|
|
41
|
+
- Intended as the input stage before compilation.
|
|
42
|
+
|
|
43
|
+
### 1.3 `compiled` Record
|
|
44
|
+
|
|
45
|
+
A compiled record is a normalized, editor-reviewed distillation of one or more raw records.
|
|
46
|
+
|
|
47
|
+
- `body`: structured markdown, expected to be human-readable reference prose.
|
|
48
|
+
- Must link (via `links`) to at least one `raw` record as its source, using link kind `"source"`.
|
|
49
|
+
- The `source_ids` provenance field (see §4) records which raw record(s) were compiled.
|
|
50
|
+
|
|
51
|
+
### 1.4 `concept` Record
|
|
52
|
+
|
|
53
|
+
A concept record defines a named idea, term, or principle that other records can reference.
|
|
54
|
+
|
|
55
|
+
- `body`: definition or explanation string.
|
|
56
|
+
- May link to `compiled` records via link kind `"example"` or `"related"`.
|
|
57
|
+
- Concept ids are used as the `target_id` in wikilink-style link syntax.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. Links
|
|
62
|
+
|
|
63
|
+
Links express directed relationships between records. A link is stored as part of the source
|
|
64
|
+
record's `links` array. The graph index (see §5) mirrors all links for efficient traversal.
|
|
65
|
+
|
|
66
|
+
### 2.1 Link Object
|
|
67
|
+
|
|
68
|
+
| Field | Type | Required | Description |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `target_id` | string | yes | `id` of the target record in this store. |
|
|
71
|
+
| `kind` | string | yes | Relationship kind (see §2.2). |
|
|
72
|
+
| `label` | string | no | Human display label for the link. |
|
|
73
|
+
|
|
74
|
+
### 2.2 Link Kinds
|
|
75
|
+
|
|
76
|
+
| Kind | Direction | Meaning |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `"source"` | compiled → raw | Compiled record was derived from this raw record. |
|
|
79
|
+
| `"example"` | concept → compiled | This compiled record exemplifies the concept. |
|
|
80
|
+
| `"related"` | any → any | General semantic relation. |
|
|
81
|
+
| `"refines"` | compiled → compiled | Later record refines or supersedes an earlier one. |
|
|
82
|
+
| `"proposes"` | any → concept | Record proposes a change to the concept (used with `propose` mutation). |
|
|
83
|
+
|
|
84
|
+
Adapters MUST store and return link objects with at least `target_id` and `kind`. Unknown
|
|
85
|
+
kinds MUST NOT be rejected — forward compatibility requires tolerating new kinds.
|
|
86
|
+
|
|
87
|
+
### 2.3 Wikilink Syntax
|
|
88
|
+
|
|
89
|
+
In `body` text, outbound links may also appear as `[[target_id]]` or `[[target_id|label]]`.
|
|
90
|
+
The default adapter indexes these inline wikilinks into the `links` array on write and renders
|
|
91
|
+
them back on read. The contract requires that after a round-trip, links declared in the `links`
|
|
92
|
+
array are queryable via `getLinks(id)`. Inline wikilink parsing is an adapter implementation
|
|
93
|
+
detail; the contract does not mandate a body format.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 3. Categories
|
|
98
|
+
|
|
99
|
+
Categories provide hierarchical classification. Rules:
|
|
100
|
+
|
|
101
|
+
- A category is a dot-separated string of one or more non-empty segments, e.g. `"engineering"`,
|
|
102
|
+
`"engineering.api"`, `"engineering.api.rest"`.
|
|
103
|
+
- Segments must match `[a-z0-9_-]+` (lowercase alphanumeric, hyphens, underscores).
|
|
104
|
+
- The store MUST support querying records by exact category or by prefix (all descendants).
|
|
105
|
+
- Empty string is not a valid category. The adapter MUST reject records with an empty category.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 4. Provenance
|
|
110
|
+
|
|
111
|
+
Every record carries immutable creation provenance. Mutation operations supply evidence that
|
|
112
|
+
is appended to a mutable `mutation_log`. Adapters MUST enforce required provenance fields and
|
|
113
|
+
MUST reject mutations missing them (see §6).
|
|
114
|
+
|
|
115
|
+
### 4.1 Creation Provenance Object (`provenance`)
|
|
116
|
+
|
|
117
|
+
| Field | Type | Required | Description |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `source_ids` | string[] | no | IDs of records this record was derived from (for compiled records). |
|
|
120
|
+
| `agent` | string | yes | Identifier of the agent or process that created this record. |
|
|
121
|
+
| `session_id` | string | no | Identifier of the session in which the record was created. |
|
|
122
|
+
| `note` | string | no | Free-form provenance note. |
|
|
123
|
+
|
|
124
|
+
### 4.2 Mutation Log Entry
|
|
125
|
+
|
|
126
|
+
Each mutation appends one log entry. Log entries are append-only; adapters MUST NOT
|
|
127
|
+
overwrite or delete log entries.
|
|
128
|
+
|
|
129
|
+
| Field | Type | Required | Description |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| `op` | string | yes | Operation name (one of the mutation ops in §6). |
|
|
132
|
+
| `at` | ISO-8601 string | yes | Timestamp of the mutation. |
|
|
133
|
+
| `agent` | string | yes | Agent that performed the mutation. |
|
|
134
|
+
| `note` | string | no | Reason or human note for the mutation. |
|
|
135
|
+
| `evidence` | object | no | Op-specific evidence (see §6 per-op tables). |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 5. Graph Index
|
|
140
|
+
|
|
141
|
+
The store maintains a JSON graph index file (or equivalent in-memory structure) that mirrors
|
|
142
|
+
all link relationships for O(1) lookup by source or target. The index enables:
|
|
143
|
+
|
|
144
|
+
- Forward lookup: given a record id, return all outbound links.
|
|
145
|
+
- Reverse lookup: given a record id, return all inbound links (backlinks).
|
|
146
|
+
|
|
147
|
+
The index MUST be updated atomically with every mutation that changes links. After any
|
|
148
|
+
mutation, a `getLinks(id)` call MUST reflect the current link state.
|
|
149
|
+
|
|
150
|
+
The default adapter stores the index as `graph-index.json` at the store root.
|
|
151
|
+
|
|
152
|
+
### 5.1 Graph Index Schema
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"schema_version": "1.0",
|
|
157
|
+
"forward": {
|
|
158
|
+
"<source_id>": [
|
|
159
|
+
{ "target_id": "<id>", "kind": "<kind>", "label": "<optional>" }
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
"reverse": {
|
|
163
|
+
"<target_id>": [
|
|
164
|
+
{ "source_id": "<id>", "kind": "<kind>" }
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 6. Mutation Operations
|
|
173
|
+
|
|
174
|
+
The store exposes six mutation operations. Each operation specifies:
|
|
175
|
+
|
|
176
|
+
- **Required provenance/evidence fields** that the caller MUST supply.
|
|
177
|
+
- The adapter MUST reject the call with an error if any required field is missing.
|
|
178
|
+
- Optional fields that the adapter records when present.
|
|
179
|
+
|
|
180
|
+
### 6.1 `create`
|
|
181
|
+
|
|
182
|
+
Create a new record. The adapter assigns `id`, `created_at`, and `updated_at`.
|
|
183
|
+
|
|
184
|
+
**Required fields:**
|
|
185
|
+
|
|
186
|
+
| Field | Location | Description |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `type` | top-level | Record type: `"raw"`, `"compiled"`, or `"concept"`. |
|
|
189
|
+
| `title` | top-level | Non-empty title string. |
|
|
190
|
+
| `body` | top-level | Non-empty body string. |
|
|
191
|
+
| `category` | top-level | Non-empty dot-separated category. |
|
|
192
|
+
| `provenance.agent` | provenance | Agent identifier. |
|
|
193
|
+
|
|
194
|
+
**Optional fields:** `tags`, `links`, `provenance.source_ids`, `provenance.session_id`, `provenance.note`.
|
|
195
|
+
|
|
196
|
+
**Rejection conditions:**
|
|
197
|
+
- Missing or empty `type`, `title`, `body`, `category`.
|
|
198
|
+
- `type` not one of `"raw"`, `"compiled"`, `"concept"`.
|
|
199
|
+
- Empty `category` or category with invalid segments.
|
|
200
|
+
- Missing `provenance.agent`.
|
|
201
|
+
|
|
202
|
+
**Post-conditions:** Record is retrievable by `get(id)`. Links in `links` array are indexed.
|
|
203
|
+
|
|
204
|
+
### 6.2 `update`
|
|
205
|
+
|
|
206
|
+
Update mutable fields of an existing record. Immutable fields (`id`, `type`, `created_at`,
|
|
207
|
+
`provenance`) MUST NOT change.
|
|
208
|
+
|
|
209
|
+
**Required fields:**
|
|
210
|
+
|
|
211
|
+
| Field | Location | Description |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `id` | argument | ID of the record to update. |
|
|
214
|
+
| `agent` | evidence | Agent performing the update. |
|
|
215
|
+
|
|
216
|
+
**Optional update fields (at least one must be supplied):** `title`, `body`, `category`, `tags`, `links`.
|
|
217
|
+
|
|
218
|
+
**Rejection conditions:**
|
|
219
|
+
- Record with `id` does not exist.
|
|
220
|
+
- No mutable fields supplied (no-op updates are rejected).
|
|
221
|
+
- Missing `agent` in evidence.
|
|
222
|
+
|
|
223
|
+
**Post-conditions:** `updated_at` is refreshed. Mutation log entry appended. If `links`
|
|
224
|
+
changed, graph index is updated.
|
|
225
|
+
|
|
226
|
+
### 6.3 `link`
|
|
227
|
+
|
|
228
|
+
Add one or more directed links from a source record to target records. Idempotent: adding
|
|
229
|
+
an already-existing (source, target, kind) triple is not an error; the link is not duplicated.
|
|
230
|
+
|
|
231
|
+
**Required fields:**
|
|
232
|
+
|
|
233
|
+
| Field | Location | Description |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `source_id` | argument | ID of the source record. |
|
|
236
|
+
| `links` | argument | Non-empty array of Link objects with `target_id` and `kind`. |
|
|
237
|
+
| `agent` | evidence | Agent performing the link operation. |
|
|
238
|
+
|
|
239
|
+
**Rejection conditions:**
|
|
240
|
+
- `source_id` does not exist.
|
|
241
|
+
- Any `target_id` in `links` does not exist.
|
|
242
|
+
- `links` array is empty.
|
|
243
|
+
- Missing `agent` in evidence.
|
|
244
|
+
|
|
245
|
+
**Post-conditions:** Each new link is present in `getLinks(source_id)`. Graph index updated.
|
|
246
|
+
Mutation log entry appended to source record.
|
|
247
|
+
|
|
248
|
+
### 6.4 `propose`
|
|
249
|
+
|
|
250
|
+
Record a proposed change to a concept record. The proposal does not modify the concept;
|
|
251
|
+
it creates a link of kind `"proposes"` from the proposing record to the concept, and
|
|
252
|
+
appends a mutation log entry with the proposal text.
|
|
253
|
+
|
|
254
|
+
**Required fields:**
|
|
255
|
+
|
|
256
|
+
| Field | Location | Description |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| `concept_id` | argument | ID of the concept record to propose a change to. |
|
|
259
|
+
| `proposer_id` | argument | ID of the record making the proposal. |
|
|
260
|
+
| `proposal` | evidence | Non-empty string describing the proposed change. |
|
|
261
|
+
| `agent` | evidence | Agent submitting the proposal. |
|
|
262
|
+
|
|
263
|
+
**Rejection conditions:**
|
|
264
|
+
- `concept_id` does not exist or is not of type `"concept"`.
|
|
265
|
+
- `proposer_id` does not exist.
|
|
266
|
+
- `proposal` string is empty or missing.
|
|
267
|
+
- Missing `agent` in evidence.
|
|
268
|
+
|
|
269
|
+
**Post-conditions:** A link of kind `"proposes"` from `proposer_id` to `concept_id` is recorded
|
|
270
|
+
in both the record and the graph index. Mutation log entry appended to concept record.
|
|
271
|
+
|
|
272
|
+
### 6.5 `apply`
|
|
273
|
+
|
|
274
|
+
Apply a pending proposal: update the concept body with the proposed change and mark the
|
|
275
|
+
proposal as applied.
|
|
276
|
+
|
|
277
|
+
**Required fields:**
|
|
278
|
+
|
|
279
|
+
| Field | Location | Description |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `concept_id` | argument | ID of the concept being updated. |
|
|
282
|
+
| `proposer_id` | argument | ID of the record whose proposal is being applied. |
|
|
283
|
+
| `new_body` | evidence | The replacement body text for the concept. |
|
|
284
|
+
| `agent` | evidence | Agent applying the proposal. |
|
|
285
|
+
| `rationale` | evidence | Non-empty string explaining why the proposal is accepted. |
|
|
286
|
+
|
|
287
|
+
**Rejection conditions:**
|
|
288
|
+
- `concept_id` does not exist or is not of type `"concept"`.
|
|
289
|
+
- `proposer_id` does not exist.
|
|
290
|
+
- No `"proposes"` link from `proposer_id` to `concept_id` exists.
|
|
291
|
+
- `new_body` is missing or empty.
|
|
292
|
+
- `rationale` is missing or empty.
|
|
293
|
+
- Missing `agent` in evidence.
|
|
294
|
+
|
|
295
|
+
**Post-conditions:** Concept `body` is replaced with `new_body`. `updated_at` refreshed.
|
|
296
|
+
Mutation log entry (op=`"apply"`) appended. The `"proposes"` link MAY remain in the
|
|
297
|
+
graph (it is a historical record); adapters SHOULD NOT silently delete it.
|
|
298
|
+
|
|
299
|
+
### 6.6 `reject`
|
|
300
|
+
|
|
301
|
+
Reject a pending proposal: record that the proposal was reviewed and declined.
|
|
302
|
+
The concept is not modified.
|
|
303
|
+
|
|
304
|
+
**Required fields:**
|
|
305
|
+
|
|
306
|
+
| Field | Location | Description |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| `concept_id` | argument | ID of the concept. |
|
|
309
|
+
| `proposer_id` | argument | ID of the proposing record. |
|
|
310
|
+
| `agent` | evidence | Agent rejecting the proposal. |
|
|
311
|
+
| `reason` | evidence | Non-empty string explaining the rejection. |
|
|
312
|
+
|
|
313
|
+
**Rejection conditions:**
|
|
314
|
+
- `concept_id` does not exist or is not of type `"concept"`.
|
|
315
|
+
- `proposer_id` does not exist.
|
|
316
|
+
- No `"proposes"` link from `proposer_id` to `concept_id` exists.
|
|
317
|
+
- `reason` is missing or empty.
|
|
318
|
+
- Missing `agent` in evidence.
|
|
319
|
+
|
|
320
|
+
**Post-conditions:** Concept `body` is unchanged. `updated_at` is NOT changed (concept
|
|
321
|
+
itself was not mutated). Mutation log entry (op=`"reject"`) appended to concept record.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## 7. Query Interface
|
|
326
|
+
|
|
327
|
+
The adapter MUST implement:
|
|
328
|
+
|
|
329
|
+
| Method | Signature | Description |
|
|
330
|
+
|---|---|---|
|
|
331
|
+
| `get` | `(id: string) => Record \| null` | Retrieve record by id. Returns null if not found. |
|
|
332
|
+
| `getLinks` | `(id: string) => { forward: Link[], reverse: Link[] }` | Return all links for a record from the graph index. |
|
|
333
|
+
| `listByCategory` | `(category: string, options?: { prefix?: boolean }) => Record[]` | List records by exact category match. If `prefix` is true, match all records whose category starts with the given string. |
|
|
334
|
+
| `listByType` | `(type: RecordType) => Record[]` | List all records of a given type. |
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 8. Adapter Contract Summary
|
|
339
|
+
|
|
340
|
+
An adapter is a JavaScript module (ESM) exporting a default class or factory function. The
|
|
341
|
+
constructor / factory accepts `{ storeRoot: string }`. The instance exposes:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
interface KnowledgeStoreAdapter {
|
|
345
|
+
create(record: CreateInput): Promise<string>; // returns new id
|
|
346
|
+
update(id: string, fields: UpdateFields, evidence: UpdateEvidence): Promise<void>;
|
|
347
|
+
link(sourceId: string, links: Link[], evidence: LinkEvidence): Promise<void>;
|
|
348
|
+
propose(conceptId: string, proposerId: string, evidence: ProposeEvidence): Promise<void>;
|
|
349
|
+
apply(conceptId: string, proposerId: string, evidence: ApplyEvidence): Promise<void>;
|
|
350
|
+
reject(conceptId: string, proposerId: string, evidence: RejectEvidence): Promise<void>;
|
|
351
|
+
get(id: string): Promise<Record | null>;
|
|
352
|
+
getLinks(id: string): Promise<{ forward: Link[]; reverse: Link[] }>;
|
|
353
|
+
listByCategory(category: string, options?: { prefix?: boolean }): Promise<Record[]>;
|
|
354
|
+
listByType(type: "raw" | "compiled" | "concept"): Promise<Record[]>;
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
All mutation methods MUST throw (or return a rejected Promise) when required evidence is
|
|
359
|
+
missing. The thrown error MUST have a `code` property set to `"MISSING_EVIDENCE"` and a
|
|
360
|
+
human-readable `message`. This enables the contract suite to distinguish enforcement failures
|
|
361
|
+
from unexpected errors.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 9. YAML Frontmatter Convention (Default Adapter)
|
|
366
|
+
|
|
367
|
+
The default adapter serializes each record as a markdown file with YAML frontmatter:
|
|
368
|
+
|
|
369
|
+
```markdown
|
|
370
|
+
---
|
|
371
|
+
id: <id>
|
|
372
|
+
type: <raw|compiled|concept>
|
|
373
|
+
title: <title>
|
|
374
|
+
category: <category>
|
|
375
|
+
tags: [<tag>, ...]
|
|
376
|
+
created_at: <ISO-8601>
|
|
377
|
+
updated_at: <ISO-8601>
|
|
378
|
+
provenance:
|
|
379
|
+
agent: <agent>
|
|
380
|
+
session_id: <optional>
|
|
381
|
+
source_ids: [<id>, ...]
|
|
382
|
+
note: <optional>
|
|
383
|
+
links:
|
|
384
|
+
- target_id: <id>
|
|
385
|
+
kind: <kind>
|
|
386
|
+
label: <optional>
|
|
387
|
+
mutation_log:
|
|
388
|
+
- op: <op>
|
|
389
|
+
at: <ISO-8601>
|
|
390
|
+
agent: <agent>
|
|
391
|
+
note: <optional>
|
|
392
|
+
evidence: {}
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
<body text, may include [[wikilinks]]>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Files are stored as `<store_root>/records/<id>.md`. The graph index lives at
|
|
399
|
+
`<store_root>/graph-index.json`.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Addendum A — Snapshot Record Semantics (S6)
|
|
404
|
+
|
|
405
|
+
### A.1 Snapshot Type Decision
|
|
406
|
+
|
|
407
|
+
A `snapshot` is a **distinct record type** (not a concept subtype). Rationale: snapshots have
|
|
408
|
+
unique semantics that differ from concept records in three ways:
|
|
409
|
+
|
|
410
|
+
1. **Topic binding** — a snapshot is always bound to a topic (category or explicit topic string),
|
|
411
|
+
whereas a concept is an independent named idea.
|
|
412
|
+
2. **Supersedes relationship** — snapshots participate in a directed supersedes chain; concepts do
|
|
413
|
+
not. A snapshot "supersedes" its predecessors, preserving them for provenance while establishing
|
|
414
|
+
the current view.
|
|
415
|
+
3. **Consolidation lifecycle** — snapshots are produced by the `knowledge.consolidate` flow and
|
|
416
|
+
carry evidence of which compiled records contributed to the latest decisions. Concepts carry
|
|
417
|
+
definitions; snapshots carry bounded decision summaries.
|
|
418
|
+
|
|
419
|
+
Adding `snapshot` as a fourth top-level type is the smallest, clearest extension: it extends the
|
|
420
|
+
`type` discriminant, adds one link kind, and adds one mutation op — all self-contained.
|
|
421
|
+
|
|
422
|
+
### A.2 Extended Type Discriminant
|
|
423
|
+
|
|
424
|
+
The `type` field on the common envelope (§1.1) now accepts four values:
|
|
425
|
+
|
|
426
|
+
| Type | Description |
|
|
427
|
+
|---|---|
|
|
428
|
+
| `"raw"` | Unprocessed source material (§1.2). |
|
|
429
|
+
| `"compiled"` | Normalized, editor-reviewed distillation (§1.3). |
|
|
430
|
+
| `"concept"` | Named idea, term, or principle (§1.4). |
|
|
431
|
+
| `"snapshot"` | Bounded decision summary for a topic (§A.3). |
|
|
432
|
+
|
|
433
|
+
### A.3 `snapshot` Record
|
|
434
|
+
|
|
435
|
+
A snapshot record holds the current consolidated decisions for a topic. It is produced and
|
|
436
|
+
updated only through the `knowledge.consolidate` flow.
|
|
437
|
+
|
|
438
|
+
- `body`: structured markdown summarising the latest known decisions, open items, and context for
|
|
439
|
+
the topic.
|
|
440
|
+
- `topic` (in `provenance.note` or a dedicated field — default adapter stores it in `tags[0]`
|
|
441
|
+
prefixed `topic:`) — the topic string this snapshot covers. Must be non-empty.
|
|
442
|
+
- Must link (via `links`) to the compiled records that contributed to the latest consolidation
|
|
443
|
+
using link kind `"source"`.
|
|
444
|
+
- May link to superseded predecessor snapshots using link kind `"supersedes"`.
|
|
445
|
+
- Carries `provenance.source_ids` referencing every compiled record that contributed to the
|
|
446
|
+
current body.
|
|
447
|
+
|
|
448
|
+
### A.4 Extended Link Kinds
|
|
449
|
+
|
|
450
|
+
| Kind | Direction | Meaning |
|
|
451
|
+
|---|---|---|
|
|
452
|
+
| `"source"` | compiled → raw | (existing) |
|
|
453
|
+
| `"example"` | concept → compiled | (existing) |
|
|
454
|
+
| `"related"` | any → any | (existing) |
|
|
455
|
+
| `"refines"` | compiled → compiled | (existing) |
|
|
456
|
+
| `"proposes"` | any → concept | (existing) |
|
|
457
|
+
| `"supersedes"` | snapshot → snapshot | New snapshot supersedes an older snapshot for the same topic. |
|
|
458
|
+
|
|
459
|
+
### A.5 `supersede` Mutation Operation
|
|
460
|
+
|
|
461
|
+
The `supersede` op marks one or more existing records as superseded by a newer record.
|
|
462
|
+
It NEVER deletes records. Superseded records remain fully queryable with their provenance intact.
|
|
463
|
+
|
|
464
|
+
**Required fields:**
|
|
465
|
+
|
|
466
|
+
| Field | Location | Description |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
| `new_id` | argument | ID of the record that supersedes. |
|
|
469
|
+
| `superseded_ids` | argument | Non-empty array of IDs that are being superseded. |
|
|
470
|
+
| `agent` | evidence | Agent performing the supersede operation. |
|
|
471
|
+
| `rationale` | evidence | Non-empty string explaining why the new record supersedes the old ones. |
|
|
472
|
+
|
|
473
|
+
**Rejection conditions:**
|
|
474
|
+
- `new_id` does not exist.
|
|
475
|
+
- `superseded_ids` is empty.
|
|
476
|
+
- Any id in `superseded_ids` does not exist.
|
|
477
|
+
- Missing `agent` in evidence.
|
|
478
|
+
- Missing or empty `rationale` in evidence.
|
|
479
|
+
|
|
480
|
+
**Post-conditions:**
|
|
481
|
+
- For each id in `superseded_ids`, a link of kind `"supersedes"` from `new_id` to that id is added
|
|
482
|
+
to the graph index.
|
|
483
|
+
- A mutation log entry (op=`"supersede"`) is appended to the record at `new_id`.
|
|
484
|
+
- A mutation log entry (op=`"superseded-by"`) is appended to each record in `superseded_ids`,
|
|
485
|
+
recording which record supersedes them (`new_id`).
|
|
486
|
+
- The superseded records are NOT deleted. Their `body`, `links`, and provenance remain intact.
|
|
487
|
+
- After `supersede`, a `get(superseded_id)` MUST still return the full record.
|
|
488
|
+
- Superseded records can be discovered via `getLinks(superseded_id).reverse` — they will have a
|
|
489
|
+
reverse link of kind `"supersedes"` pointing from `new_id`.
|
|
490
|
+
|
|
491
|
+
**Supersede-not-delete invariant:**
|
|
492
|
+
> Calling `supersede` MUST NOT remove any record. No mutation op in this contract deletes records.
|
|
493
|
+
> This is a hard invariant; adapters MUST enforce it. A retention policy hook for physical archival
|
|
494
|
+
> is a future concern and is explicitly out of scope for this version.
|
|
495
|
+
|
|
496
|
+
### A.6 Adapter Contract Extension
|
|
497
|
+
|
|
498
|
+
The adapter interface (§8) is extended with one method:
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
interface KnowledgeStoreAdapter {
|
|
502
|
+
// ... existing methods ...
|
|
503
|
+
supersede(newId: string, supersededIds: string[], evidence: SupersedeEvidence): Promise<void>;
|
|
504
|
+
listByType(type: "raw" | "compiled" | "concept" | "snapshot"): Promise<Record[]>;
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
`SupersedeEvidence`:
|
|
509
|
+
```ts
|
|
510
|
+
interface SupersedeEvidence {
|
|
511
|
+
agent: string;
|
|
512
|
+
rationale: string;
|
|
513
|
+
note?: string;
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### A.7 Snapshot Queryability Guarantee
|
|
518
|
+
|
|
519
|
+
Superseded snapshots MUST remain queryable:
|
|
520
|
+
- `get(id)` returns the full record (body, links, provenance, mutation_log).
|
|
521
|
+
- `listByType("snapshot")` returns ALL snapshots, including superseded ones.
|
|
522
|
+
- The caller can determine supersession status by inspecting `getLinks(id).reverse` for entries
|
|
523
|
+
with `kind === "supersedes"`.
|
|
524
|
+
|
|
525
|
+
There is no `"archived"` or `"deleted"` status field; the supersession chain is expressed entirely
|
|
526
|
+
through links and mutation log entries.
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Addendum B — Record Status Lifecycle (S7)
|
|
531
|
+
|
|
532
|
+
### B.1 Status Field
|
|
533
|
+
|
|
534
|
+
Every record envelope (§1.1) gains an optional `status` field:
|
|
535
|
+
|
|
536
|
+
| Field | Type | Required | Default | Description |
|
|
537
|
+
|---|---|---|---|---|
|
|
538
|
+
| `status` | `"active"` \| `"implemented"` \| `"retired"` | no | `"active"` | Lifecycle status of the record. Records without a status field are treated as `"active"`. |
|
|
539
|
+
|
|
540
|
+
`status` is a mutable field but MUST only change via the `retire` mutation op (§B.4).
|
|
541
|
+
Direct field updates via the `update` op MUST NOT change `status`; `update` MUST ignore `status` if
|
|
542
|
+
supplied in the fields argument.
|
|
543
|
+
|
|
544
|
+
### B.2 Allowed Status Transitions
|
|
545
|
+
|
|
546
|
+
| From | To | Op | Required Evidence |
|
|
547
|
+
|---|---|---|---|
|
|
548
|
+
| `"active"` | `"implemented"` | `retire` | `implementedByRef` (non-empty, references the implementing artifact/commit/PR) |
|
|
549
|
+
| `"active"` | `"retired"` | `retire` | `rationale` (non-empty, explains obsolescence) |
|
|
550
|
+
| `"implemented"` | `"retired"` | `retire` | `rationale` (non-empty) |
|
|
551
|
+
|
|
552
|
+
No other transitions are permitted. Attempting an invalid transition MUST throw with
|
|
553
|
+
`error.code === "MISSING_EVIDENCE"` and a human-readable `message`.
|
|
554
|
+
|
|
555
|
+
Records in `"retired"` status have no further transitions — they are terminal.
|
|
556
|
+
|
|
557
|
+
### B.3 Working-Set Exclusion
|
|
558
|
+
|
|
559
|
+
Records with `status === "retired"` are EXCLUDED from the default working set:
|
|
560
|
+
|
|
561
|
+
- `listByType(type)` returns only non-retired records by default.
|
|
562
|
+
- `listByCategory(category, options?)` returns only non-retired records by default.
|
|
563
|
+
- `defaultSimilarityDetector` considers only non-retired compiled records as candidates.
|
|
564
|
+
- The vector similarity detector (`createVectorSimilarityDetector`) considers only non-retired
|
|
565
|
+
compiled records as candidates.
|
|
566
|
+
|
|
567
|
+
All four filtering surfaces accept an `includeRetired: true` option (or equivalent flag on the
|
|
568
|
+
similarity detector) to restore retired records to the result set.
|
|
569
|
+
|
|
570
|
+
Retired records remain **fully queryable with provenance**:
|
|
571
|
+
- `get(id)` always returns the full record regardless of status.
|
|
572
|
+
- `listByType(type, { includeRetired: true })` returns all records of that type.
|
|
573
|
+
- `listByCategory(category, { includeRetired: true })` returns all matching records.
|
|
574
|
+
- The record's `mutation_log` carries the full retirement evidence.
|
|
575
|
+
|
|
576
|
+
### B.4 `retire` Mutation Operation
|
|
577
|
+
|
|
578
|
+
The `retire` op transitions a record from `"active"` or `"implemented"` to the target status.
|
|
579
|
+
It NEVER deletes the record. The record body, links, and provenance remain intact.
|
|
580
|
+
|
|
581
|
+
**Required fields:**
|
|
582
|
+
|
|
583
|
+
| Field | Location | Description |
|
|
584
|
+
|---|---|---|
|
|
585
|
+
| `id` | argument | ID of the record to retire. |
|
|
586
|
+
| `targetStatus` | argument | Target status: `"implemented"` or `"retired"`. |
|
|
587
|
+
| `agent` | evidence | Agent performing the retirement. |
|
|
588
|
+
| `rationale` | evidence | Non-empty string explaining why the record is being retired. Required for all target statuses. |
|
|
589
|
+
|
|
590
|
+
**Conditional evidence fields:**
|
|
591
|
+
|
|
592
|
+
| Field | Location | Condition | Description |
|
|
593
|
+
|---|---|---|---|
|
|
594
|
+
| `implementedByRef` | evidence | `targetStatus === "implemented"` | Non-empty reference to the implementing artifact (commit SHA, PR URL, issue number, etc.). |
|
|
595
|
+
| `supersededByRef` | evidence | optional for `targetStatus === "retired"` | Reference to a superseding record or artifact. |
|
|
596
|
+
|
|
597
|
+
**Rejection conditions:**
|
|
598
|
+
- Record with `id` does not exist.
|
|
599
|
+
- `targetStatus` is not `"implemented"` or `"retired"`.
|
|
600
|
+
- Current status transition is invalid (see §B.2).
|
|
601
|
+
- `rationale` is missing or empty.
|
|
602
|
+
- `targetStatus === "implemented"` and `implementedByRef` is missing or empty.
|
|
603
|
+
- Missing `agent` in evidence.
|
|
604
|
+
|
|
605
|
+
**Post-conditions:**
|
|
606
|
+
- Record `status` is updated to `targetStatus`.
|
|
607
|
+
- Record `updated_at` is refreshed.
|
|
608
|
+
- A mutation log entry (op=`"retire"`) is appended, carrying `targetStatus`, `rationale`,
|
|
609
|
+
and any supplied `implementedByRef` / `supersededByRef`.
|
|
610
|
+
- The record body, `links`, and creation `provenance` are NOT changed.
|
|
611
|
+
- `get(id)` returns the full record with the updated status.
|
|
612
|
+
- `listByType(type)` (without `includeRetired`) no longer returns this record if
|
|
613
|
+
`targetStatus === "retired"`.
|
|
614
|
+
|
|
615
|
+
### B.5 Adapter Contract Extension
|
|
616
|
+
|
|
617
|
+
The adapter interface (§8) is extended:
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
interface KnowledgeStoreAdapter {
|
|
621
|
+
// ... existing methods ...
|
|
622
|
+
retire(id: string, targetStatus: "implemented" | "retired", evidence: RetireEvidence): Promise<void>;
|
|
623
|
+
listByType(type: RecordType, options?: { includeRetired?: boolean }): Promise<Record[]>;
|
|
624
|
+
listByCategory(category: string, options?: { prefix?: boolean; includeRetired?: boolean }): Promise<Record[]>;
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
`RetireEvidence`:
|
|
629
|
+
```ts
|
|
630
|
+
interface RetireEvidence {
|
|
631
|
+
agent: string;
|
|
632
|
+
rationale: string;
|
|
633
|
+
implementedByRef?: string; // required when targetStatus === "implemented"
|
|
634
|
+
supersededByRef?: string; // optional
|
|
635
|
+
note?: string;
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### B.6 Provenance and History Guarantee
|
|
640
|
+
|
|
641
|
+
Retired records MUST remain reachable from:
|
|
642
|
+
- `get(id)` — always returns the full record.
|
|
643
|
+
- `listByType(type, { includeRetired: true })` and `listByCategory(category, { includeRetired: true })`.
|
|
644
|
+
- Snapshot `provenance.source_ids` — any snapshot that included the record in its cluster before
|
|
645
|
+
retirement retains the reference intact. The retired record can be retrieved via `get(sourceId)`.
|
|
646
|
+
- The retirement `mutation_log` entry carries the full evidence of why and when the record was
|
|
647
|
+
retired and by whom.
|
|
648
|
+
|
|
649
|
+
There is no deletion of records. Physical purge (if ever needed) is a separate, future policy
|
|
650
|
+
hook not defined in this version.
|