Store extraction evidence#

Publication preparation at 0.1.0-next.0 adds an owner-dispatched workspace publish workflow. The packed TypeScript consumer now pins @types/node@24.19.2 alongside typescript@5.9.3, with install scripts disabled. It optionally exports the exact tested Store and Conformance tarballs plus their SHA-256 hashes for the separate OIDC publish job. Conformance includes the root Apache-2.0 license. This preparation does not claim npm publication or change the behavioral evidence below.

This package-only change implements adjusted phase 3 steps 1–2. It does not publish, merge, migrate consumers or apply the deferred owner decisions. Station source: 2479f38b1ab062e2d9cd6df9f8fa2cdbeee1d136 (origin/main). Corpus base: 8e82ec4 (origin/main).

Module mapping#

Paths at left are under Station src-server/knowledge-store/.

Station source Corpus destination
adapters/default-store.ts packages/store/src/default-store.ts
adapters/obsidian-store.ts packages/store/src/obsidian-store.ts
adapters/shared/category.ts packages/store/src/shared/category.ts
adapters/shared/freshness.ts packages/store/src/shared/freshness.ts
adapters/shared/frontmatter.ts packages/store/src/shared/frontmatter.ts
adapters/shared/graph-index.ts packages/store/src/shared/graph-index.ts
adapters/shared/identity.ts packages/store/src/shared/identity.ts
adapters/shared/record-schema.ts packages/store/src/shared/record-schema.ts
adapters/shared/wikilinks.ts packages/store/src/shared/wikilinks.ts
adapters/shared/file-transactions.ts packages/store/src/shared/file-transactions.ts
adapters/shared/observation-file.ts packages/store/src/shared/observation-file.ts
errors.ts packages/store/src/errors.ts
adapters/__tests__/contract-suite.ts packages/store/test/contract-suite.ts
adapters/__tests__/default-store.contract.test.ts packages/store/test/default-store.contract.test.ts
adapters/__tests__/obsidian-store.contract.test.ts packages/store/test/obsidian-store.contract.test.ts
adapters/__tests__/file-transactions.test.ts packages/store/test/file-transactions.test.ts
adapters/__tests__/persistence-corruption.test.ts packages/store/test/persistence-corruption.test.ts
adapters/__tests__/wikilinks.test.ts packages/store/test/wikilinks.test.ts
adapters/__tests__/fixtures/knowledge-file-transaction-child.ts packages/store/test/fixtures/knowledge-file-transaction-child.ts

Additional seams: Station packages/contracts/src/knowledge-store.ts record, evidence, error and adapter interfaces → packages/store/src/contracts.ts; src-server/knowledge-index/path-safety.ts predicate → src/path-safety.ts; src-server/utils/is-record.ts predicate → src/is-record.ts. Root registry, provider and descriptors stay host-owned. services.ts, standalone-lock.ts, index.ts and observation.ts compose/export the extracted owners.

The Station-only source scan for host document services and vault onboarding validateRoot tests were not ported: those host owners are outside the package. The 123 ported behavioral tests were executed, not merely copied; round 1 also restores a derived package-wide raw-fs guard. Logger mocks become injected diagnostics; transaction fixtures receive an isolated coordination directory. The Station lock itself is neither copied nor moved.

Conformance interpretation#

The runner executes every public store vector for both profiles; host vectors are a host obligation (see Host vectors). It preserves raw negative results and adjudicates them against the explicit expected-fail list. Expected failures await versioned working-set/history, expiry, compiled-source or any-type proposal implementation; they do not mean full semantic conformance. The list fails on an unexpected pass or wrong failure pattern.

Two existing vector defects were corrected: wikilink body $b was a literal, not an interpolated reference; the fixture now supplies an explicit target ID. Expiry clearing removes the field, so its assertion now uses absent: true instead of requiring a stored null. The runner tests absence rejection.

Storage probes exercise real subprocess concurrency, crash points (prepared, first/final publication, commit marker), traversal (including safe opaque-ID acceptance), physical-ID collisions without indexes/after archival/mismatched frontmatter, symlink refusal, lost/corrupt derived indexes and disk graph integrity. The vault path index is authoritative, so derived-index loss probes preserve it.

Initial extraction evidence (before round 1)#

EXECUTED on macOS, Node 22.22.2 and 24.19.0:

  • Package-local TypeScript build succeeds, producing ESM and declarations.
  • Store suite: 134/134 tests (123 ported behavior tests, 9 codec regressions, 2 injected-service/observation tests), seven files, on both Node versions.
  • Root docs/conformance-runner suite: 138/138 tests on Node 24.
  • Built and installed-tarball conformance: all 124 vectors per profile execute; 104 pass and 20 are the explicitly listed deferred decisions, for both default and vault. No unexpected failures or unexpected passes.
  • Isolated JS consumer imports all public subpaths, creates/reads both profiles, and proves source and storage deep imports are rejected.
  • Isolated TS consumer compiles declarations with strict NodeNext and executes the emitted JS against the installed package with a real create/read.
  • Installed packed docs CLI checks this repository without building docs or importing store sources. The installed root has no prepare or build script.
  • Packed store SHA-256 on both Node versions: e492f8678cfa88bab42789bb76ad28f0d5c54af8028579bc1d8a5d6bf9ae49e6. Store source is identical to extraction commit d71564d361e21dd7a96d02c6d0c29b293296534b; executable TS pack fixture is recorded in f964bf1f0eee628f6a1cc3fbca235e2c1a3e7421.
  • Docs dogfood and committed-range whitespace checks pass.

EXECUTED fault proofs at clean committed baseline d71564d361e21dd7a96d02c6d0c29b293296534b, Node 24.19.0:

Injection Baseline Injected Restored
Skip injected lock acquisition in the transaction owner exit 0 exit 1: subprocess create/update conflicts, failed storage invariants exit 0
Restore typed YAML loading and remove legacy-dash repair exit 0 exit 1: 1.10, null, true, - fixtures and unknown-text metadata fail exit 0
Add passing create-roundtrip to expected-fail list exit 0 exit 1: expected-fail vector passed, remove it from the list exit 0

Each mutation was restored byte for byte, rebuilt, and rerun. The fault driver asserts a clean tree before and after, writes separate negative logs, and can be reproduced from a committed worktree with:

npm run proof:faults --workspace=@kontourai/corpus-store -- <output-directory>

The fault driver writes diagnostic receipts to the caller-selected output directory. CI repeats build, contracts and packed qualification on Linux Node 22/24. Local results are distinct from hosted CI. No Windows or mixed-host qualification is inferred from these executions.

Round 1 corrections#

The independent review exposed numeric supersede evidence that the original empty-log byte fixtures missed. The field inventory is: record strings (id/type/title/category/status/timestamps/expiry/sentinel), tags/aliases, provenance strings and source IDs, link target/kind/label, log op/time/agent/note and supersession rationale/new ID; update evidence fields (string array), link added evidence (link array), propose/apply/reject/retire/superseded-by evidence strings; numeric TTL and supersede evidence count. No Station-written boolean record field exists. Unknown extension scalars follow the orchestrator's lossless core-schema rule, with source slices retained for unchanged values.

Round 1 also requires explicit lock/coordination options; adds injected host-home identity rechecks; binds 6 of the 20 expected failures to ref/kind/expected/actual assertions and 14 to specific error patterns, and adjudicates selected vectors; restores held-owner PID/liveness and raw-fs publication guards; removes machine-private paths; and hides the existing internal transaction field from enumeration. Origin main was merged, including #13's dependency and instruction changes. Package/root CI is rerun after this merge. Each correction has a targeted negative control recorded below after execution.

Round 1 executed qualification#

EXECUTED at source revision 9ec0bed on macOS:

  • Node 22.22.2 and 24.19.0: package build and 166/166 store tests.
  • Node 24.19.0: 231/231 root tests, including main #13's builtOn gate tests and the new public-document path guard.
  • Packed JS and strict TS consumers execute successfully on both Node versions. TS also rejects missing lock/directory configurations at compile time.
  • All 124 vectors per default/vault profile execute against installed tarballs: 104 pass, 20 expected failures, with no unexpected result. Six are assertion-bound and fourteen are specific-error-pattern-bound. Selected vectors receive the same adjudication.
  • Packed docs CLI executes without a build, including the merged builtOn gate.
  • Both Node versions produce store tarball SHA-256 1db0ff939d4caf08adda171d920d17573724e0f26a60df3455e4f57806ce335f.

EXECUTED negative controls at clean baseline 862b17c6c5fd53f83790f503dc19e3d1eaec4dd6, Node 24.19.0:

Control Injected regression Baseline Injected Restored
B1 Leave numeric supersede count as text 0 1 0
B2-read Disable lossless unknown-scalar conversion 0 1 0
B2-source Drop retained unknown-field source ranges 0 1 0
B3 Restore silent lock/directory fallback 0 1 0
M1 Skip injected writer/observation home-identity rechecks 0 1 0
M2-assertion Excuse a different assertion in a listed vector 0 1 0
M2-selected Bypass expected-fail adjudication for selected vectors 0 1 0
M3-publication Import raw file publication into an adapter 0 1 0
M3-owner Publish parent PID instead of actual lock holder PID 0 1 0
L1 Restore a machine-private path to the public document 0 1 0
L2 Make the transaction field enumerable again 0 1 0
L3 Restore unconditional quoted numeric-field coercion 0 1 0
B3-types Make required coordination options optional in declarations 0 1 0
B4-packed-dependency Drop the merged npm-package-arg dependency from the packed docs CLI 0 1 0

The derived publication guard was separately proved with the same 0 → 1 → 0 sequence at 9ec0bed. Each completed control restored the exact original bytes and confirmed a clean worktree. The first quoted-TTL control was corrected because removing only the plain-style condition still left core-schema typing intact; the completed control actually restores the original unconditional coercion. That diagnostic did not count as successful fault proof.

npm run proof:round1 --workspace=@kontourai/corpus-store -- <output-directory>

These are local execution receipts. Hosted CI status is reported on the PR for its exact pushed head; local results do not stand in for hosted checks.

Plan disagreements and limits#

REASONED: the brief's extraction-only scope wins over design suggestions for public storage, capabilities, moving the Station lock, dual vault formats and immediate semantic convergence. Search vectors remained an expected host-level failure because retrieval is deliberately outside this library; they were later reclassified as host vectors. No ranked search, hosted CI, mixed Station/standalone lock or deployment qualification is inferred from the package tests.

Round 2 policy and data preservation#

The current rule replaces round 1's broad source-slice preservation. Only an unchanged top-level, single-line plain scalar whose core conversion is lossy may retain its original token, and that token must be valid YAML that re-parses as the same failsafe string. Collections, block scalars, nested or multiline data use the normal serializer. Invalid bare-dash text is always quoted. This avoids copying missing line separators or obsolete indentation into new YAML.

The package preserves parsed values and following keys across updates for Station-written | and |-, externally authored > accepted by Station, Kit-style unindented log sequences, and flow collections. Station's serializer uses lineWidth: -1 and does not emit folded >; the folded fixture therefore starts with a Station-written envelope and deliberately models an external edit. coordinationIdentity is now mandatory for stores and exact observation; its factory/recheck shape is validated before directory preparation. Fixed-directory standalone hosts must also opt in to an explicit identity.

Round 2 bound 6 of its 20 expected failures to ref/kind/expected/actual, and 14 to specific error patterns. This is a diagnostic extraction with deferred owner decisions, not a claim of complete semantic conformance. The live counts are in Owner decisions after extraction.

EXECUTED on macOS at 1ff4b262163edf3edf4c5481302300ce41f0733f:

  • Node 22.22.2 and 24.19.0: 181/181 store tests, including both-profile block, sequence, following-key, standard-YAML and required-identity regressions.
  • Node 24.19.0: 233/233 root tests.
  • Strict TS/executable JS tarball consumers and build-free packed docs CLI pass on both Node versions. All 124 vectors per profile execute with 104 passes, 6 assertion-bound expected failures and 14 error-pattern-bound expected failures.
  • Both Node versions produce store tarball SHA-256 5fd72a61f7dc1940e2632dc29f1c9b6f0540660d56b8eb6fa40456df36b966af.

EXECUTED Station byte comparison: the driver reads immutable Station revision 2479f38b1ab062e2d9cd6df9f8fa2cdbeee1d136 into an isolated snapshot and runs its actual adapters and lock, using the installed dependency set from the supplied Station checkout. Separate Corpus stores use the same fixed mutation clock, explicit IDs and inputs. No Station checkout or user store is modified. The stores contain |, |-, externally authored >, Kit-style log sequences, flow collections and counterparts without lossy numeric text. Updates append following expiry/tags keys and move vault titles; both owners must re-read their own prior extension values identically.

The complete comparison receipt records per-file hashes and results. Of 18 files, 14 are byte-identical. Only these four differ:

Profile File Reason
Default records/kit-sequence.md Corpus preserves nested 1.10 as a string; Station reads/writes numeric 1.1.
Default records/flow.md The same decided unknown-text divergence within a flow collection.
Vault engineering/kit-sequence-updated.md The same nested-sequence text divergence.
Vault engineering/flow-updated.md The same flow-collection text divergence.

Block-scalar records, lossless sequence/flow counterparts and all emitted indexes match byte for byte. Folded input normalizes through each owner's serializer identically. No swallowed keys, malformed records or unexpected file differences remain in this comparison.

EXECUTED reverted-fix controls at clean baseline 627217c3629c5021ef9b6c3743b5f7a46ab2cda5, Node 24.19.0:

Control Injected regression Baseline Injected Restored
N1 Restore c0c44c7 codec: EOF block absorbs newly appended tags 0 1 0
N2 Restore c0c44c7 codec: Kit-indented items become sibling log entries 0 1 0
N3 Restore c0c44c7 codec: bare dash is published as invalid YAML 0 1 0
N4 Restore silent missing-identity fallback 0 1 0
N4 types Make coordination identity optional in declarations 0 1 0
N5 Misstate all 20 failures as assertion-bound 0 1 0

Each source mutation was restored exactly and the worktree checked clean. The first N1 probe already had a following key and did not exercise the EOF-to-new-key transition; the retained regression adds keys during update. The first N2 fixture produced a related indentation parse error; the retained fixture reproduces the reported sibling-entry/schema failure. Neither incomplete diagnostic counted as completed fault proof.

npm run proof:round2 --workspace=@kontourai/corpus-store -- <output-directory>
npm run compare:station --workspace=@kontourai/corpus-store -- <station-repository> <report.json>

Hosted CI is tracked against the exact pushed head on the PR. These local receipts do not claim hosted CI success.

Owner decisions after extraction#

Each later change that implements an owner decision removes its vectors from the expected-fail list in the same commit; the runner fails a listed vector that starts passing. The list is empty when every decision has shipped.

The expected-fail list is empty.

Host vectors#

A vector with "scope": "host" is a host obligation: it needs behavior that this library deliberately does not implement. search-working-set is the only one. It retires a record and expects search to omit it unless includeHistory: true is passed, and search belongs to the host that mounts the store. The package therefore cannot pass it, and listing it as an expected failure could never empty the list. Corpus Conformance exports storeVectors and hostVectors; this package's runner executes only storeVectors (123 per profile), refuses a selected host vector, and fails if the expected-fail list names one. A host such as Station runs hostVectors through its own search route and must pass them.

EXECUTED on macOS, Node 24.19.0: 123 store vectors per profile, 104 passing and 19 expected failures, with no unexpected result, for both default and vault.

Proposals may target any record type#

propose, apply and reject no longer require a concept target. Only the type guard was removed; the evidence, existence and proposes-link checks remain. The 12 propose-target-*, apply-target-* and reject-target-* vectors now pass on both profiles and leave the list. The contract suite's three wrong-type rejection tests are replaced by an every-type acceptance test and a test that the kept checks still reject.

EXECUTED on macOS, Node 24.19.0: 123 store vectors per profile, 116 passing and 7 expected failures, with no unexpected result, for both default and vault.

Lists return the working set#

One predicate (shared/lifecycle.ts) decides history for both profiles: retired status, a superseded-by mutation-log entry, or an archived vault note. Lists exclude historical records unless includeHistory: true; the vault's history walks archived notes. includeRetired is translated to includeHistory. Before this change the default profile listed superseded records and the vault excluded archived notes even from includeRetired, so the two profiles disagreed; the contract suite's header exclusion for supersede listing is removed and the shared suite now asserts the working set and history for both. retire-working-set, supersede-listing and read-retired leave the list. The adjudication tests used live list entries as examples, so they now adjudicate a fixture list instead (the selected-vector CLI test passes it through CORPUS_EXPECTED_FAILS), keeping their force once the real list is empty.

EXECUTED on macOS, Node 24.19.0: 123 store vectors per profile, 119 passing and 4 expected failures, with no unexpected result, for both default and vault.

One expiry rule and an expiry-only stale filter#

shared/freshness.ts computes one expiry for both profiles: explicit expires_at, otherwise created_at + ttl_seconds, otherwise none. List options {stale, now} filter on it, with now defaulting to the injected clock. test/expiry.test.ts covers both profiles with an injected clock: the TTL survives a later update, expiry is inclusive, expires_at wins and clearing it restores the TTL, records without expiry never go stale, the filter composes with the working set, and malformed options are refused. The public ttl-creation-anchor vector creates without updating, so it cannot detect a TTL that resets on update; the update case lives in the expiry test. read-stale-filter, ttl-creation-anchor and expiry-precedence leave the list.

EXECUTED on macOS, Node 24.19.0: 123 store vectors per profile, 122 passing and 1 expected failure, with no unexpected result, for both default and vault.

create of a compiled record requires a source link to an existing raw record (shared/compiled-source.ts), checked before any write in both profiles. Provenance IDs, related links and wikilinks do not satisfy it. Fixtures that created source-less compiled records now create a raw source first, including the conformance harness's physical-id-archived probe. A Station-written source-less compiled record is kept readable by writing its exact legacy bytes (fixtures/station/contract-record.md) into a default store rather than creating it. compiled-without-source leaves the list, which is now {}.

EXECUTED on macOS, Node 24.19.0: 123 store vectors per profile, all 123 passing, with no expected failure and no unexpected result, for both default and vault.

Canonical vault index and offline migration#

The vault adapter selects its path index on every operation: canonical path-index.json, or the Knowledge Kit's .graph-index.json (kit-v1), which opens read-only. Both at once is refused, and a root that holds store data without either index is refused instead of being read as empty. The corpus-store migrate bin converts a kit-v1 vault through one journaled transaction of the store's transaction engine, which gains readBytes and writeBytes so the saved index keeps its exact bytes. It writes a hashed manifest and the original index, verifies every other file's sha256 and reads every record through the vault adapter, and reverses only while the bytes still match.

Two committed fixtures were written by the Knowledge Kit's own adapters (see packages/store/test/fixtures/README.md): a vault with every record type, archive, retirement, alias and expiry, and a hybrid root that a Kit default store and a Kit vault share. test/vault-index.test.ts copies them and covers read-only kit-v1 access, refusal of an occupied unindexed vault, plain-note and empty roots, the dual-index conflict, conversion of both fixtures with a byte-hash check before and after and identical reads through the package, reversal and its refusal after an edit or a move, refusal of inexact mappings, and CLI usage errors. Pack verification runs the installed bin on a copy of the Kit vault.

The phase plan asked for the conformance vectors on "both vault formats". kit-v1 is read-only by design, and the vectors write, so they run against the canonical format only; kit-v1 is covered by the read and conversion tests above.

Named vault dimensions#

The optional vault constructor dimensions setting names category segments after the domain. For example, ['territory', 'customer', 'initiative'] projects sales.east.acme.renewal into those three frontmatter fields. Every write recomputes the configured fields; shortening the category removes fields whose segments are absent. Configured dimension fields stay out of returned record envelopes. Omitting the setting preserves unknown frontmatter without projecting named dimensions.

Names must be unique identifiers and cannot replace identity, evidence or bookkeeping fields. Invalid settings refuse before creating the store directory. The vault contract tests read actual on-disk frontmatter after create and category changes, check obsolete-field removal and the returned record, and exercise reserved and invalid names. Existing files change only when an operation writes them; setting this option alone does not rewrite the vault.

Source: docs/store-extraction.md