feat(vector): the vectored-noun scalar joins the count ledger; the open gate closes the vector leg
All checks were successful
CI / Node 24 (push) Successful in 12m22s
CI / Node 22 (push) Successful in 12m32s
CI / Integration + conformance (Node 22) (push) Successful in 19m42s
CI / Bun (latest) (push) Successful in 12m15s

The coverage denominator the health-by-accounting ratification named for
the vector family — never built until now, and its absence was measured as
the exact outage class it existed to prevent: a migrated store with
canonical vectors and no derived index opened with the vector leg EMPTY,
served [] from vector search with no error, and the report-driven read gate
had nothing to refuse on (the provider's coverage invariant was honestly
unledgered — the denominator was ours to supply).

- getCanonicalCounts() gains vectors: { all } — the count of canonical
  nouns holding a REAL vector. Incremented where a vector lands (the
  isNew-gated metadata seam for explicit vectors — the same discipline that
  keeps HNSW neighbor-link re-saves from inflating counts; a narrow
  noteVectorLanded hook for the deferred-embed landing, gated on the
  worker's own pre-embed read). Decremented on a proven delete of a
  vectored noun; a vector-uncertain delete marks the ledger suspect rather
  than guessing (no new reads on the delete path). Recounted by the
  sanctioned recount; legacy counts.json derives it once (a deferred noun's
  vector file exists with an empty vector, so presence requires one
  content read at derivation — never on the hot path).
- The open gate's vector leg: when a health-reporting provider claims
  serving while the index holds zero nodes and the ledger proves vectored
  canonical rows exist, open BUILDS (narrated) — routed through the
  provider's idempotent fillFromCanonical() when exposed (the joint door;
  a partial shortfall stays repair()'s operator business), the JS rebuild
  otherwise — or fails typed pre-serve. Scoped exactly: bare isReady()
  providers, migrating providers, and white-box size stubs open as before.

Pinned end-to-end from the partner gate's probe shape (store with vectored
canonical rows, no derived index, reopen → search serves N, never []),
red-proved against the pre-fix path; the inverse (zero vectored rows) opens
without building and serves [] honestly.
This commit is contained in:
David Snelling 2026-08-25 15:31:19 -07:00
parent bce2593e24
commit 9730835bdf
10 changed files with 851 additions and 32 deletions

View file

@ -52,7 +52,15 @@ export class SaveNounMetadataOperation implements Operation {
private readonly storage: StorageAdapter,
private readonly id: string,
private readonly metadata: NounMetadata,
private readonly isNew: boolean = false
private readonly isNew: boolean = false,
/**
* OPTIONAL vectored-noun ledger hint: `true` when this write's paired
* vector (the SAME insert's `vector` local) is real/non-empty see
* {@link StorageAdapter.saveNounMetadata}'s JSDoc for the isNew-gated,
* double-count-proof seam this rides. Default `false`: a deferred-embed
* insert (or any caller that doesn't know) never counts here.
*/
private readonly hasVector: boolean = false
) {}
async execute(): Promise<RollbackAction> {
@ -62,7 +70,7 @@ export class SaveNounMetadataOperation implements Operation {
: await tornHealsToNull(this.storage.getNounMetadata(this.id), 'noun metadata')
// Save new metadata
await this.storage.saveNounMetadata(this.id, this.metadata)
await this.storage.saveNounMetadata(this.id, this.metadata, this.hasVector)
// Return rollback action
return async () => {
@ -70,8 +78,10 @@ export class SaveNounMetadataOperation implements Operation {
// Restore previous metadata
await this.storage.saveNounMetadata(this.id, previousMetadata)
} else {
// Delete newly created metadata
await this.storage.deleteNounMetadata(this.id)
// Delete newly created metadata. `this.hasVector` is the SAME fact
// this operation's own execute() used to (maybe) count the vectored
// ledger — reversing with it on rollback needs no new read.
await this.storage.deleteNounMetadata(this.id, undefined, this.hasVector)
}
}
}
@ -140,7 +150,9 @@ export class SaveNounOperation implements Operation {
// Note: Not all adapters implement deleteNoun
// This is acceptable - metadata deletion makes entity invisible
if ('deleteNoun' in this.storage && typeof this.storage.deleteNoun === 'function') {
await this.storage.deleteNoun(this.noun.id)
// `this.noun.vector` is the SAME record just written — the
// vectored-noun ledger fact is free (no added read) and exact.
await this.storage.deleteNoun(this.noun.id, undefined, this.noun.vector.length > 0)
}
}
}
@ -198,14 +210,21 @@ export class DeleteNounMetadataOperation implements Operation {
return async () => {}
}
// Vectored-noun ledger fact: `previousNoun` is already read above for the
// before-image capture — no added read. `undefined` (noun genuinely
// absent, metadata-only ghost) is passed through honestly; the storage
// layer marks the ledger suspect rather than guessing.
const hadVector = previousNoun ? previousNoun.vector.length > 0 : undefined
// Full removal: both canonical legs + the entity container + count decrement
// (the prior record keeps the decrement honest on a null canonical read).
await this.storage.deleteNoun(this.id, previousMetadata)
await this.storage.deleteNoun(this.id, previousMetadata, hadVector)
// Return rollback action
return async () => {
// Restore the vector leg, then the metadata leg through the count-aware
// save so deleteNoun()'s decrement is reversed.
// save so deleteNoun()'s decrement is reversed (hadVector's mirror:
// re-increments the vectored ledger iff the restored vector is real).
if (previousNoun) {
await this.storage.saveNoun({
id: previousNoun.id,
@ -215,7 +234,7 @@ export class DeleteNounMetadataOperation implements Operation {
})
}
if (previousMetadata) {
await this.storage.saveNounMetadata(this.id, previousMetadata)
await this.storage.saveNounMetadata(this.id, previousMetadata, hadVector === true)
}
}
}