feat(engine): the wiring wave — stamps ride every flush, provider generations, waitForIndexed, adopt-backfill, match-all serves
- Watermark stamping fans out at flush: all three projections stamped
with the committed generation before their flushes persist.
- waitForIndexed(path?, {generation, timeoutMs}) — the one honest read
barrier for write-then-recall consumers; typed timeout error carries
the pending count and names the gauge; getIndexStatus() gains
per-projection gauges. awaitPendingEmbeds() unchanged underneath.
- adoptLogAuthority() self-backfills curable divergences (pre-log
records, witness drift) by identity re-commit before flipping — a
fresh brain flips clean; log-ahead divergences still refuse loudly.
- The verification oracle gains VERB legs (all four divergence classes;
unwired = honest verbsChecked: 0, never a scope claim).
- find({where: {}}) match-all serves (was silent-empty, warm AND cold;
same fix in count/streaming/subgraph seeding); removeMany({where:{}})
refuses typed — a match-all bulk delete must be explicit.
- Aggregation native envelope stamped via noteSourceGeneration before
serializeState; the native-blob restore gates through the same
adoption verdict as caller-side state (the unconditional adopt dies).
- LC8 pinned: a wholesale directory move opens and serves identically
across all three intelligences, with history traveling.
Gates: unit 2031/2031 (156 files) · integration 812 (91 files) ·
conformance 27/27.
This commit is contained in:
parent
b35d87a7ab
commit
b53e6e8987
10 changed files with 1234 additions and 30 deletions
|
|
@ -1614,6 +1614,15 @@ export interface AggregationProvider {
|
|||
|
||||
/** Serialize internal state for persistence (called during flush) */
|
||||
serializeState?(): string
|
||||
|
||||
/**
|
||||
* Bake the committed generation into the provider's own state envelope
|
||||
* before {@link serializeState} (called during flush, immediately prior).
|
||||
* Lets a native-side reopen verify the envelope's honesty independently of
|
||||
* the host's wrapper stamp. Optional — providers without it rely on the
|
||||
* host wrapper's `sourceGeneration` alone.
|
||||
*/
|
||||
noteSourceGeneration?(generation: number): void
|
||||
}
|
||||
|
||||
// ============= Configuration =============
|
||||
|
|
@ -2244,6 +2253,79 @@ export interface Highlight {
|
|||
contentCategory?: ContentCategory
|
||||
}
|
||||
|
||||
// ============= Read barrier (waitForIndexed) =============
|
||||
|
||||
/**
|
||||
* One projection leg of the read barrier (`brain.waitForIndexed(path)`) — a
|
||||
* derived view of the committed data that queries are served from:
|
||||
*
|
||||
* - `'semantic'` — the vector index (deferred embeds land here asynchronously)
|
||||
* - `'metadata'` — the field/filter index behind `find({ where })`
|
||||
* - `'graph'` — the relationship adjacency index
|
||||
* - `'aggregation'` — the incremental aggregate states
|
||||
*/
|
||||
export type IndexedProjectionPath = 'semantic' | 'metadata' | 'graph' | 'aggregation'
|
||||
|
||||
/**
|
||||
* Options for `brain.waitForIndexed()`.
|
||||
*/
|
||||
export interface WaitForIndexedOptions {
|
||||
/**
|
||||
* Resolve as soon as the projection has caught up to this committed
|
||||
* generation (rather than the current head). Today the pending-embed set
|
||||
* carries no generation stamps, so the refinement is conservative: an
|
||||
* empty backlog resolves immediately (the watermark is at the head, hence
|
||||
* ≥ any committed generation); a non-empty backlog waits for the full
|
||||
* drain — a SUPERSET of the requested wait, never a partial one.
|
||||
*/
|
||||
generation?: number
|
||||
|
||||
/**
|
||||
* Upper bound on the wait in milliseconds. On expiry the promise REJECTS
|
||||
* with {@link WaitForIndexedTimeoutError} (typed: the leg + the
|
||||
* still-pending count) — never a silent partial wait.
|
||||
*/
|
||||
timeoutMs?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* The typed rejection of `brain.waitForIndexed(path, { timeoutMs })` on
|
||||
* expiry. Carries the projection leg (`path`; `'all'` for the no-argument
|
||||
* barrier) and the deferred-embed backlog size at the moment the timer fired
|
||||
* (`pendingEmbeds` — the same number as
|
||||
* `getIndexStatus().projections.semantic.pendingEmbeds`), so a caller can
|
||||
* log an honest gauge and retry instead of guessing. A timeout means the
|
||||
* projection has NOT caught up — nothing was skipped, nothing partially
|
||||
* waited.
|
||||
*/
|
||||
export class WaitForIndexedTimeoutError extends Error {
|
||||
/** The projection leg that had not caught up (`'all'` = the no-arg barrier). */
|
||||
public readonly path: IndexedProjectionPath | 'all'
|
||||
|
||||
/** The expired timeout, in milliseconds. */
|
||||
public readonly timeoutMs: number
|
||||
|
||||
/** Deferred embeds still pending when the timer fired — the live value of
|
||||
* `getIndexStatus().projections.semantic.pendingEmbeds`. */
|
||||
public readonly pendingEmbeds: number
|
||||
|
||||
constructor(path: IndexedProjectionPath | 'all', timeoutMs: number, pendingEmbeds: number) {
|
||||
super(
|
||||
`waitForIndexed(${path === 'all' ? '' : `'${path}'`}) timed out after ${timeoutMs}ms — ` +
|
||||
`${pendingEmbeds} deferred embed${pendingEmbeds === 1 ? '' : 's'} still pending; the projection has ` +
|
||||
`NOT caught up. Check getIndexStatus().projections.semantic.pendingEmbeds, then retry with a ` +
|
||||
`larger timeoutMs or use awaitPendingEmbeds() for an unbounded drain.`
|
||||
)
|
||||
this.name = 'WaitForIndexedTimeoutError'
|
||||
this.path = path
|
||||
this.timeoutMs = timeoutMs
|
||||
this.pendingEmbeds = pendingEmbeds
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, WaitForIndexedTimeoutError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============= Export all types =============
|
||||
|
||||
export * from './graphTypes.js' // Re-export NounType, VerbType, etc.
|
||||
Loading…
Add table
Add a link
Reference in a new issue