( state: PiM0M1State, db: ContextDatabase, piMessages: PiAgentMessage[], entryIds?: readonly (string | undefined)[], recomputeM1ThisPass = false, )
| 2070 | } |
| 2071 | |
| 2072 | export function injectM0M1Pi( |
| 2073 | state: PiM0M1State, |
| 2074 | db: ContextDatabase, |
| 2075 | piMessages: PiAgentMessage[], |
| 2076 | entryIds?: readonly (string | undefined)[], |
| 2077 | recomputeM1ThisPass = false, |
| 2078 | ): PiM0M1InjectionResult { |
| 2079 | // One compartment snapshot for the WHOLE decision: the materialize decision |
| 2080 | // and every cached-marker reload below normalize against this same set, so a |
| 2081 | // concurrent count change can't flip markers to null mid-decision and escape |
| 2082 | // the guarded fallback (TOCTOU). |
| 2083 | const currentCompartments = getCompartments(db, state.sessionId); |
| 2084 | let decision = mustMaterializePi(state, db, currentCompartments); |
| 2085 | let m0 = ""; |
| 2086 | let m1 = PI_M1_PLACEHOLDER; |
| 2087 | let markers: PiM0SnapshotMarkers | null = null; |
| 2088 | let materialized = false; |
| 2089 | let contentionExhausted = false; |
| 2090 | let memoryUpdateCount = 0; |
| 2091 | let m1Recomputed = false; |
| 2092 | let freshFallbackRenderedMemoryIds: number[] | null = null; |
| 2093 | |
| 2094 | if (decision.value) { |
| 2095 | // On contention exhaustion, reuse the cached m[0]/m[1] pair rather than |
| 2096 | // throwing (matches OpenCode injectM0M1). A sibling process mutated state |
| 2097 | // mid-materialization; serving the slightly-stale cached pair this pass is |
| 2098 | // correct and the next pass retries — dropping injection entirely would lose |
| 2099 | // the whole history block. |
| 2100 | try { |
| 2101 | const result = materializeM0PiWithRetry(state, db); |
| 2102 | m0 = result.m0; |
| 2103 | m1 = result.m1; |
| 2104 | markers = result.snapshotMarkers; |
| 2105 | materialized = true; |
| 2106 | m1Recomputed = true; |
| 2107 | } catch (error) { |
| 2108 | if (!(error instanceof PiMaterializeContentionError)) throw error; |
| 2109 | try { |
| 2110 | const cached = replayCachedM1Pi(db, state, currentCompartments); |
| 2111 | contentionExhausted = true; |
| 2112 | m0 = cached.m0; |
| 2113 | m1 = cached.m1; |
| 2114 | markers = cached.markers; |
| 2115 | logSession( |
| 2116 | state.sessionId, |
| 2117 | "pi m[0] materialization contention exhausted; reusing cached m[0]/m[1]", |
| 2118 | ); |
| 2119 | } catch { |
| 2120 | // No cached baseline to fall back to — this happens when the cache was |
| 2121 | // deliberately cleared THIS pass (cache-bust) and then hit contention. |
| 2122 | // Dropping injection would lose the entire history block, so render a |
| 2123 | // fresh (non-persisted) m[0]/m[1] pair as a last resort. It is not cached |
| 2124 | // because we couldn't win the materialize lock; the next pass |
| 2125 | // re-materializes and persists. |
| 2126 | const fresh = renderFreshM0PiNonPersisted(state, db); |
| 2127 | m0 = fresh.m0; |
| 2128 | markers = fresh.snapshotMarkers; |
| 2129 | freshFallbackRenderedMemoryIds = fresh.renderedMemoryIds; |
no test coverage detected