feat(8.0): thread commit generation through the graph-write provider contract
Graph time-travel needs an edge's existence recorded per generation so db.asOf(g) hops resolve historically correct endpoints. The metadata layer already threads brainy's commit generation per write; the graph write path did not, leaving a versioned verb-endpoint store unable to answer "which edges existed at generation g". - GraphIndexProvider.addVerb/removeVerb gain a `generation: bigint` parameter (the same watermark the storage layer stamps onto the record). A provider with a per-generation edge chain stamps the edge at that generation; the JS baseline has no such chain and accepts-and-ignores it — graph time-travel is a native-provider capability, and the open-core path serves edges as-of-now (the one documented graph time-travel limitation). - The two graph transaction operations resolve the generation via a thunk at EXECUTE time: the generation store assigns the batch generation only once the commit begins executing, after the operations are planned. The same generation is reused for an operation's rollback half. - All graph-write call sites pass the in-flight generation. Adds a spy-provider test proving the threading, execute-time resolution, and forward/rollback generation reuse. The JS index ignores the value, so behaviour is unchanged: unit 1402/1402, db-mvcc 25/25, bigint-contract relate/unrelate 10/10.
This commit is contained in:
parent
b26d3d42b3
commit
0951fa1da0
6 changed files with 195 additions and 26 deletions
|
|
@ -169,6 +169,12 @@ export class RemoveFromMetadataIndexOperation implements Operation {
|
|||
* shared idMapper (`getOrAssign`) and passes them alongside the verb; the
|
||||
* provider returns the interned verb int, which is surfaced through the
|
||||
* optional `onVerbInt` callback so the coordinator can feed its warm cache.
|
||||
*
|
||||
* Generation: `generationFn` is resolved at execute time (not construction) so
|
||||
* the edge is stamped at the transaction's in-flight commit generation — which
|
||||
* the generation store only assigns once the batch begins executing. The same
|
||||
* generation is reused for the rollback removal, so an add and its undo
|
||||
* reference one watermark in a provider's per-generation edge chain.
|
||||
*/
|
||||
export class AddToGraphIndexOperation implements Operation {
|
||||
readonly name = 'AddToGraphIndex'
|
||||
|
|
@ -178,6 +184,8 @@ export class AddToGraphIndexOperation implements Operation {
|
|||
* @param verb - The verb to index (`sourceInt`/`targetInt` mirrored on it).
|
||||
* @param sourceInt - The source entity's interned int.
|
||||
* @param targetInt - The target entity's interned int.
|
||||
* @param generationFn - Resolves the commit generation to stamp this edge at,
|
||||
* evaluated when the operation executes (see class note).
|
||||
* @param onVerbInt - Optional hook invoked with the interned verb int
|
||||
* returned by the provider (feeds the coordinator's verb-int warm cache).
|
||||
*/
|
||||
|
|
@ -186,18 +194,21 @@ export class AddToGraphIndexOperation implements Operation {
|
|||
private readonly verb: GraphVerb,
|
||||
private readonly sourceInt: bigint,
|
||||
private readonly targetInt: bigint,
|
||||
private readonly generationFn: () => bigint,
|
||||
private readonly onVerbInt?: (verbInt: bigint) => void
|
||||
) {}
|
||||
|
||||
async execute(): Promise<RollbackAction> {
|
||||
// Add verb to graph index
|
||||
const verbInt = await this.index.addVerb(this.verb, this.sourceInt, this.targetInt)
|
||||
// Stamp this edge at the in-flight commit generation; reuse it for the
|
||||
// rollback so add + undo reference the same watermark.
|
||||
const generation = this.generationFn()
|
||||
const verbInt = await this.index.addVerb(this.verb, this.sourceInt, this.targetInt, generation)
|
||||
this.onVerbInt?.(verbInt)
|
||||
|
||||
// Return rollback action
|
||||
return async () => {
|
||||
// Remove verb from graph index
|
||||
await this.index.removeVerb(this.verb.id)
|
||||
await this.index.removeVerb(this.verb.id, generation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -209,8 +220,10 @@ export class AddToGraphIndexOperation implements Operation {
|
|||
* - Re-add verb to graph index
|
||||
*
|
||||
* 8.0 u64 contract: rollback re-adds through `addVerb(verb, sourceInt,
|
||||
* targetInt)`, so the coordinator resolves the endpoint ints up front
|
||||
* (while the entity → int mappings are guaranteed to still exist).
|
||||
* targetInt, generation)`, so the coordinator resolves the endpoint ints up
|
||||
* front (while the entity → int mappings are guaranteed to still exist). The
|
||||
* removal generation is resolved at execute time and reused for the rollback
|
||||
* re-add, so the round trip references one watermark.
|
||||
*/
|
||||
export class RemoveFromGraphIndexOperation implements Operation {
|
||||
readonly name = 'RemoveFromGraphIndex'
|
||||
|
|
@ -220,22 +233,26 @@ export class RemoveFromGraphIndexOperation implements Operation {
|
|||
* @param verb - The verb being removed (required for rollback re-add).
|
||||
* @param sourceInt - The source entity's interned int (rollback re-add).
|
||||
* @param targetInt - The target entity's interned int (rollback re-add).
|
||||
* @param generationFn - Resolves the commit generation for this removal,
|
||||
* evaluated when the operation executes.
|
||||
*/
|
||||
constructor(
|
||||
private readonly index: GraphIndexProvider,
|
||||
private readonly verb: GraphVerb, // Required for rollback
|
||||
private readonly sourceInt: bigint,
|
||||
private readonly targetInt: bigint
|
||||
private readonly targetInt: bigint,
|
||||
private readonly generationFn: () => bigint
|
||||
) {}
|
||||
|
||||
async execute(): Promise<RollbackAction> {
|
||||
// Remove verb from graph index
|
||||
await this.index.removeVerb(this.verb.id)
|
||||
// Resolve the removal generation once; reuse it for the rollback re-add.
|
||||
const generation = this.generationFn()
|
||||
await this.index.removeVerb(this.verb.id, generation)
|
||||
|
||||
// Return rollback action
|
||||
return async () => {
|
||||
// Re-add verb with original data
|
||||
await this.index.addVerb(this.verb, this.sourceInt, this.targetInt)
|
||||
await this.index.addVerb(this.verb, this.sourceInt, this.targetInt, generation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue