Conversation
… encoding Root cause: packages/app/src/utils/id.ts still encoded timestamp * 4096 + counter into the 48-bit id prefix — the scheme that wrapped at epoch 1786706395136 (2026-08-14 19:19:55.136 +08) and was replaced everywhere else by the shared fixed generator (PR #265). The app generates message and part ids client-side, so every app-created id after the wrap carried a restarted prefix: it no longer decoded back to wall-clock time and sorted against core-generated ids as if it came from a different era. Migration: generateID now delegates to the shared fixed generator in @opencode-ai/schema/identifier (raw 48-bit ms behind a monotonic latch), so app ids share one encoding with core ids. The Identifier namespace API is unchanged — ascending/descending with the app prefix map and given-id passthrough/validation. The legacy module had no timestamp() decode API with live callers in app/desktop, so nothing needed re-implementing there. Coverage: id.test.ts pins the app boundary — raw-ms decode of live ids, prefix/shape for all six prefixes, app-vs-core same-wall-clock sort consistency, burst and descending ordering, given-id passthrough and validation; two probes were red against the legacy encoder (decoded prefix 189982101517 vs 1786752772485 wall-clock; app-vs-core drift ~1.6e12 ms) and are green after. id-wrap-boundary.test.ts mirrors packages/schema/test/identifier.test.ts through the app's generator seam. Declared @opencode-ai/schema as an app workspace dependency. Evidence: app unit 453/453, desktop 49/49, schema 16/16, typecheck green in packages/app and packages/desktop, rg 0x1000 packages/app clean, root lint 4849 warnings (budget untouched), spot-check app vs core msg_ prefix drift 1 ms. Refs #271
…onMoved (#269) Root cause (#269): the execution-location key (WorkflowTable.directory) was stamped at dag.create and never moved afterwards, while a SessionEvent.Moved only rewrote SessionTable.directory. ownsSession conjuncts the directory over ALL of a session's workflow rows, so after a move the rows disagree (the pre-move workflows keep the old stamp) and the fail-closed ownership policy leaves EVERY directory with no owner — the session's wakes wedge until restart. Probe C4 pinned the wedge rather than fixing it (v1.0.13 decision). Fix (design-gate Model a + b integration, atomic adoption): - C1: the session projector's Moved projection now re-stamps every workflow row of the moved session to the payload-sourced destination directory, inside the SAME durable publish transaction as the SessionTable update — the stamp moves WITH the session in one transaction, so there is never a mixed-stamp window (no SessionTable read, no fs I/O in the projector). - C5: DagLoop subscribes to SessionEvent.Moved. The instance whose directory no longer owns the moved session's workflows evicts its stale runtime entries (fail-closed), and the NEW owner re-forks the serialized wake drain so a wake that was wedged behind the old stamps delivers immediately (bounded time) rather than at the next idle event or restart. - C6: no-ALTER idempotent convergence migration converges pre-fix divergent stamps to the session's current directory, preserving fail-closed NULL rows. Probes (red-first; each was RED before the corresponding seam landed): - C4 flips from pinning the wedge to pinning the resolution: exactly one live owner = the session's new durable directory, no cross-directory double adoption. RED evidence: wf1 stayed "/wtA" pre-C1. - C8 pins the bounded-time resolution: a moved session's wedged wake reaches the new owner's admission seam immediately, delivered exactly once. RED evidence: the wake stayed wedged (poll timeout) pre-C5. - C9 pins the migration convergence (fail-closed NULL preserved). RED evidence: a no-op migration leaves the stale stamp divergent. Invariants preserved: fail-closed NULL, session-sourced create stamp, H1 latch, R7 negative half. Only the write-once rule is narrowed to a two-writer whitelist (create INSERT + Moved re-stamp) — encoded in the follow-up R7-ext(a) amendment.
Root cause (#270, shared territory with #269): admission was check-then-act — an instance re-read the durable row (ownsWorkflow) and then published its in-memory entry / materialized a child session. A deletion (Session.remove → FK cascade) that commits between the passed ownership check and the action slipped past it: the in-flight spawn could create a child for a workflow the cascade had already removed. SpawnReady eviction was the mitigation, not a fence. Fix (Model (b) conditional-claim fence of the accepted atomic-adoption design — status+directory conditionality, no lease column): - C2: DagStore.tryClaimAdoption — a single conditional UPDATE that matches the row only while it STILL EXISTS and is non-terminal, returning whether the admission claimed it. It converts the admission from check-then-act into act-with-condition; a committed deletion/terminal matches zero rows and the admission aborts atomically. Directory ownership is deliberately NOT re-done here: ownsWorkflow canonicalizes both sides and a SQL directory comparison would diverge (create stamps are realpathed, Moved re-stamps are not). - C3: recoverWorkflow and the WorkflowStarted handler gate runtimes.set on the claim (after the ownsWorkflow directory revalidation), so a cascade in the final window aborts the adoption before it ever publishes an entry. - C4: spawnNode re-admits atomically right before sessions.create — the fence that closes the spawn window nodeQueued's guard alone leaves open between its read and its publish. A committed deletion matches zero rows and the spawn aborts before any child session exists. Probe C7 (red-first, park-gate style): parks the window-2 claim, commits a deletion, releases, and asserts no post-deletion child survives. RED evidence (fence conditionality reverted → always-claim): a child session is created-then-cancelled for the deleted workflow, so `expect(cancelsA).toEqual([])` fails (Received +3). Restoring the conditional claim makes the claim return false on the deleted row and abort before sessions.create (GREEN). All existing DAG-LOC-01 guards stay green (24 probes).
…ext(a) (#269) Root cause (#269): the atomic-adoption redesign narrows the DAG-LOC-01 write-once directory invariant into a two-writer whitelist. R7-ext(a) previously asserted "no UPDATE writes the directory column anywhere in the dag trees," which held but left the new whitelisted writer (the Moved re-stamp) unpinned. This is an invariant UPDATE, not a weakening — mandated by #269's own acceptance ("the directory stamp must move WITH the session in one transaction"): - The ONLY sanctioned directory writers are the dag projector's WorkflowCreated INSERT (create-time stamp, onConflictDoNothing) and the session projector's SessionEvent.Moved re-stamp (payload-sourced, no SessionTable read). - R7-ext(a) keeps its negative half (no .set({directory}) in the dag trees) and gains a positive whitelist half asserting the Moved re-stamp exists in the session projector (outside the dag trees, so R7/R7-ext stay scoped correctly), pinning the #269 resolution at the static-contract level. - WorkflowTable.directory column comment documents the two-writer whitelist. RED evidence (mutation reversion — Moved re-stamp removed from the session projector): the whitelist expect fails, Expected true / Received false. No user-visible semantic change; no runtime behavior altered in this commit.
fix(app): migrate app id helper off the legacy 0x1000-shifted 48-bit encoding (#271)
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promote to main for the next official release. Two trains merged on dev since v1.0.13 (#268):
packages/appnow uses the shared schema identifier generator (raw 48-bit ms + monotonic latch);0x1000arithmetic eliminated from the app package; wrap-boundary/clock-regression suite mirrors schema; app-vs-core same-wall-clock decode pinned (drift 0-5ms).Issues #269, #270, #271 close with these PRs — the entire v1.0.13 known-limitations ledger is burned down.
Dev validation
Known residual (documented, non-blocking)
tryClaimAdoptionbumpstime_updatedon the wall clock) — no probe pins the window yet; carried for follow-up.Release plan
On merge: dispatch release-fork from main (official). Release notes carry the three fixed items (#269/#270/#271).