[GH-ISSUE #365] [Design Proposal] SQL schema migrations and mixed-version operation #94

Open
opened 2026-10-29 14:19:31 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @guozy18 on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/365

Affected component

Cross-cutting / other

Problem statement

Part of the parent proposal. Related: data model and interfaces, sandbox lifecycle and resource holds, and online GC and history retention.

Once the database is authoritative, an upgrade involves more than replacing Node binaries. Old and new Nodes, GC workers, tools, and existing connections may access the same database concurrently. Existing sandboxes continue to fork, resume, publish, and eventually release their holds.

A DDL change can permit new values without making old code understand them. Unified layer UUID/predecessor semantics, immutable manifests referenced by manifest_ref, head-only dependency registration, P2P artifact selection and layer GC affect more than SQL row shapes. A program may still connect successfully while misunderstanding identity or protection. Schema changes, compatible code deployment, new writes and GC activation need separate steps.

Use case

  • A single-node or clustered deployment already uses the SQL Catalog, with ongoing sandbox creation, snapshot publication, and online GC.
  • V1 and V2 coexist during a rollout. Existing sandboxes and long-lived paused state remain with their original Nodes; upgrades must not force consumers to stop.
  • Adding columns or indexes, backfilling data, changing codecs, or clearing terminal payloads requires an explicit deployment and rollback boundary.

The initial transition from an OSS/POSIX file catalog to SQL is covered by data mapping and migration.

Current behavior and workarounds

Running Nodes, workers, tools and P2P publishers/readers may use different artifact and metadata contracts during a rollout. The example below covers the transition from an older representation to unified layer identities, immutable manifests referenced from SQL, and dependency heads. It is a compatibility example, not a requirement to implement the older full-mapping SQL proposal first. If SQL has not been deployed, initialize the agreed target model directly and follow #363 for the file-catalog transition.

A schema check at Node startup does not cover later changes: existing pools need not reconnect, and old workers, tools, or delayed callbacks may still write. Replica counts or missing heartbeats do not prove that all old accessors and external side effects have ended.

Neither “upgrade the database first” nor “upgrade the code first” is sufficient. The order must account for what old code can read, what the schema permits, and which data formats are currently allowed to be produced.

Desired behavior

  • While V1 and V2 share a database, all visible data remains readable by every version still allowed to access it.
  • Expand the schema compatibly before independently enabling new data semantics. Ordinary Nodes do not run DDL.
  • Existing sandbox requests retain their Node bindings; new creations can use upgraded capacity. Replacement does not depend on VM live migration or forced pauses.
  • An interrupted migrator can inspect and resume its work. Repeated backfills do not reset terminal states, holds, or alias bindings.
  • Before new semantics are enabled, compatible old code can be restored. After activation, rollback requires a version that understands existing data; lowering a version number does not restore compatibility.

Proposed approach

Example: enabling unified layer identity, manifest references and heads

The target Catalog records each validated layer UUID with its fixed predecessor and published descriptor, records the chain heads retained by S, and refers to a complete immutable backend manifest through manifest_ref. It does not register every layer again for each snapshot. The data-model issue defines the final schema and field names; these examples do not prescribe a table count or a common volume schema.

A legacy accessor may expect full descriptions in SQL, explicit snapshot-to-object mappings, or old UUID/P2P behavior. A compatible release must understand both the existing deployment contract and the approved new one before new-only data appears. Legacy layers with missing/conflicting UUIDs or predecessors require the compatibility path in #363; a schema change does not make them valid.

Phase Database and artifact structures Permitted behavior Running code and compatibility condition
1. Existing deployment Existing structures and formats Existing writes and protection rules Existing consumers keep their fixed inputs
2. Compatible expansion Add the structures/read paths needed for manifest references, layer relations and heads Continue the existing write contract; do not activate new GC Prove old accessors remain compatible with the expansion
3. Deploy compatible code Expanded structures; bounded backfill where needed Read both forms, still produce only the permitted compatible form Include producers, consumers, P2P, workers, tools and final Release paths
4. Enable new writes Approved target structures and identity contract Permit unified UUID/descriptor results, immutable manifest references and head registration All affected accessors are compatible or their obligations have been handed off and old access has ended
5. Enable graph GC separately Authoritative, verified layer relations and protected roots Enable eligible reclamation only after old data, writers and consumers have been accounted for #363 import/adoption and #364 deletion/reuse conditions are satisfied; identity pruning/F advancement have their own prerequisites
6. Contract transitional structures Remove obsolete structures after the rollback window Preserve the activated contract No live query, writer, rollback version or unfinished obligation requires the old representation

Phase 2 requires proof that old code can use the expanded schema. If an old binary accepts only one exact schema version, first deploy a bridge release that improves compatibility checks while retaining old writes. Unknown structures must not be assumed compatible.

The exact OCI UUID-generation algorithm, canonical publication representation, layer terminal retention and safe physical-key reuse must be resolved by their owning proposals before enabling operations that depend on them. This issue defines the activation discipline, not those choices. Snapshot/hold first-admission and pruning rules do not automatically apply to layer UUIDs.

How requests continue during coexistence

Migrator: expand structures; permitted writes remain in the compatible legacy form
Node A / old version: keep serving sandbox A and its existing binding
Node B / compatible version: read both forms; write only the currently permitted form

Resume/fork existing A -> Gateway follows its binding -> Node A reuses local holds
Create sandbox B      -> Gateway schedules compatible capacity -> Node B
A's publish/Release   -> Original responsible manager -> Authoritative Catalog contract

All affected accessors compatible or retired; handoff and validation complete
-> Permit the new UUID/manifest/head write contract
-> Separately enable graph GC after data, writer and consumer prerequisites pass

The existing no_schedule mechanism can exclude old Nodes from new allocations while retaining their sandbox bindings, API, and heartbeats. Fork can still create children on an old Node, and resume follows the original binding. Its workload therefore need not decrease monotonically.

Keep the old Pod serving until its business obligations are handed off or complete. Its shutdown path stops the reporter, orchestrator, and devices, while Gateway routes existing requests by sandbox binding. Preserving recoverable state alone does not keep requests uninterrupted.

If an old Node or long-lived paused state still requires the old contract, retain that Node, its required metadata and fixed artifact inputs, and delay incompatible new semantics. Proceed only when the relevant read, write, and final-release paths are compatible or their obligations have ended. This does not promise replacement of every Node within a fixed time.

Separate schema compatibility from permitted data formats

Control information Purpose
schema_version / migration steps Completed table, column, index, and CHECK changes
required_read_formats Formats every accessor must understand; raised before enabling new writes and not lowered when cleanup is disabled
allowed_write_formats Formats currently permitted to be produced; can remain in the old form after schema expansion
Binary compatibility declaration Supported schema range, readable/writable manifest and artifact formats, UUID/predecessor semantics, P2P selection, and hold/GC contract
GC/history activation Separately permits graph reclamation, F advancement and history pruning after their prerequisites; schema expansion alone grants none of them

Database policy is durable; its concrete layout belongs to the Catalog migration infrastructure, not a new per-Node or per-VM table. Admission of a binary and permission to produce a new format are separate decisions. An expanded schema must not automatically enable new identity semantics, graph GC or historical pruning.

Enabling new-format writes raises the required read contract. Disabling those writes later does not remove that requirement: new manifests, heads, UUID bindings and terminal states may already exist. Lowering the requirement would need an independently verified reverse migration; this proposal provides no automatic one.

The following pseudocode illustrates format admission, not a mandatory schema layout:

struct DbPolicy {
    schema_version: u32,
    required_read_formats: Set<DataFormat>,
    allowed_write_formats: Set<DataFormat>,
}

fn check_open(binary: &Capabilities, db: &DbPolicy) -> Result<()> {
    ensure!(binary.supports_schema(db.schema_version));
    // Existing data may require formats whose future writes were disabled.
    ensure!(binary.read_formats.is_superset(&db.required_read_formats));
    ensure!(binary.has_compatible_writer(&db.allowed_write_formats));
    ensure!(binary.supports_required_identity_and_protection_contracts());
    Ok(()) // No DDL and no automatic activation of new writes or GC.
}

Format activation and Catalog publication/cleanup checks use a common synchronization boundary. Prepare bytes and immutable manifests outside SQL; checking the currently permitted contract and committing its rows/heads must be ordered against activation. External I/O, device waits and uploads stay outside that policy lock. Normal hot VM operations do not add SQL requests for version checks.

Constraints cannot prevent every semantically incompatible write that bypasses Catalog. Only reviewed write/cleanup paths may connect. Policy checks, connection permissions and deployment isolation must constrain actual accessors; a version number alone is not isolation evidence. P2P compatibility also requires controlling producers, aliases and readers, not just database permissions. An existing digest requirement must not be silently bypassed by UUID lookup during coexistence.

Migrator and accessor responsibilities

  • Each migration step has a monotonic number, immutable checksum, state, and completion evidence. Only one migrator may execute a phase against a target at a time.
  • Check the target, prerequisite version, and actual structure before proceeding. Mark a step complete only after its DDL/backfill completes. After interruption or an unknown response, inspect actual database state rather than blindly repeating work or advancing the version.
  • Handle PostgreSQL transactions and locks per statement. Concurrent index creation cannot simply be wrapped in the same DDL transaction; failures require checking index validity. Validate SQLite's write locking and table-rebuild behavior separately.
  • Nodes, workers, and tools check compatibility on startup, reconnection, and Catalog reinitialization, before destructive local cleanup or device recovery. Each process must not independently run migrations.
  • Before enabling a format, account for old Nodes, workers, tools, pools, caches, P2P producers/readers, in-flight transactions, delayed callbacks, and final Release paths. Use separate application database identities for incompatible generations. After their business obligations are handed off or complete, prevent reconnection and confirm their existing sessions and transactions have ended. Revoking access so responsible requests fail is not a substitute for handoff.
  • Silent heartbeats, zero Pod replicas, or an upgraded HTTP entry point do not prove that old accessors or external side effects have ended. When evidence is insufficient, keep serving the old format and delay activation.

Apply the same sequence to other schema changes

Change Expansion and coexistence Backfill / validation Contraction condition
Add a column Add a compatible column/default; old writes remain valid New code accepts missing values; conditional batches must not overwrite concurrent updates Add stricter constraints only after required values exist and old writers have exited
Rename a field Retain the old column and add the new one; compatible code defines synchronization Verify both representations; a direct rename must not break old queries Remove the old column after all old readers/writers exit and the rollback window closes
Add a JSON/enum form Deploy readers for old and new forms while still writing the old form Preserve defaults and unknown fields; business updates must not erase new facts Activate the new form independently before producing data old readers cannot understand
Add an index Use an engine-specific online creation procedure with verified lock behavior Check validity, query plans, space, and concurrent impact Remove the old index only after its dependencies are gone

Backfills and structural changes must not require pausing user operations. Use bounded lock waits/retries and measure the actual latency impact; this is not a claim that arbitrary DDL is nonblocking.

During a representation transition, keep one explicit authority while old writers remain active. Where compatible dual maintenance is required, update database representations in the same transaction; prepare immutable external content before publishing its reference. Do not infer a complete chain from an unordered old object mapping: validate the complete ordered manifest. After old writers exit, finish and verify the backfill before switching authority. One completed backfill pass is not sufficient.

Acceptance criteria

  • Run the applicable phases with real old/new binaries sharing one database and artifact namespace. Expansion preserves old operation; new identity/manifest/head writes and graph GC remain disabled until their separate activation conditions pass.
  • Specify compatibility declarations and the storage/synchronization of format policy. Cover ordinary connections, existing pools, workers, tools, cached manifests, P2P, and final Release.
  • Keep existing sandbox resume/fork/publish requests on the old Node while new creations can use V2. Long-lived old consumers must not be stopped to enable the new format.
  • Inject migration interruption, checksum mismatch, partial backfill, invalid indexes, concurrent writes, and delayed callbacks. Actual structure and completion records must agree.
  • Exercise rollback before and after the first new-format write and GC activation. Changing a version/configuration value alone must not admit readers or writers that misunderstand existing UUID bindings, manifests, heads or deletion states.
  • Validate nil/duplicate UUIDs, conflicting predecessors, late legacy callbacks, manifest publication failures and old P2P aliases. Preserve existing consumers and safe identity admission/pruning throughout the transition.
  • Measure DDL/backfill lock waits, business tail latency, and recovery per engine. Do not advance a step that fails the continuous-service requirements.

Compatibility and operational impact

  • API/config changes: No new sandbox API. Add explicit binary compatibility declarations, database format policy, and migration execution configuration.
  • Snapshot or storage format changes: Compatibility covers immutable manifest references, layer identity/predecessor semantics, head registration and exact artifact selection. Existing readers keep their fixed inputs; legacy bytes are not rewritten in place. First new-format writes change the rollback boundary. Generating a canonical artifact or using an existing descriptor must not confuse UUID identity with a byte digest.
  • New host/runtime requirements: Ordinary processes do not perform migrations. The migrator has explicit authorization and mutual exclusion. Reuse existing Nodes, hold journals, and devices; no live-migration capability is introduced.
  • Upgrade and rollback considerations: After expansion but before new data is written, rollback is possible if compatibility is verified. Once new data or irreversible cleanup exists, rollback requires a version that understands the resulting contract. Disabling new writes or GC does not restore prior metadata or bytes, and lowering schema_version does not make old code safe.

The rollback window and schema contraction are separate. Stopping new-format writes does not remove previously written data. Before dropping old structures, confirm that no query, writer, compatible rollback version, or unfinished task still needs them.

Alternatives considered

  • Migrate automatically on every Node startup: this creates DDL races and may cross compatibility boundaries while old processes still run. Use a dedicated controlled migrator.
  • Check only schema_version: this cannot distinguish a structure permitted by SQL from an identity, manifest or protection contract understood by every accessor. Track required read contracts and permitted writes separately.
  • Stop the service or replace old Pods directly: simpler operationally, but it violates continuous service. Retaining old Nodes may delay new semantics instead.

Non-goals

A general migration platform and VM lifecycle redesign are outside this issue. Preserve Local/promotion protection and existing volume ownership. This issue does not select a shared volume schema, an OCI UUID algorithm, or a terminal layer-retention policy. The upgrade contract does not require lock-free DDL, VM live migration, or forced retirement of long-lived consumers within a deadline.

Contribution

I can provide more design details

Pre-submission checklist

  • I searched existing issues and discussions and did not find a duplicate.
  • I described a concrete problem and use case, not only a proposed technology.
  • I understand that acceptance of the problem does not imply approval of a particular implementation.
Originally created by @guozy18 on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/365 ### Affected component Cross-cutting / other ### Problem statement Part of the [parent proposal](https://github.com/kvcache-ai/AgentENV/issues/360). Related: [data model and interfaces](https://github.com/kvcache-ai/AgentENV/issues/361), [sandbox lifecycle and resource holds](https://github.com/kvcache-ai/AgentENV/issues/362), and [online GC and history retention](https://github.com/kvcache-ai/AgentENV/issues/364). Once the database is authoritative, an upgrade involves more than replacing Node binaries. Old and new Nodes, GC workers, tools, and existing connections may access the same database concurrently. Existing sandboxes continue to fork, resume, publish, and eventually release their holds. A DDL change can permit new values without making old code understand them. Unified layer UUID/predecessor semantics, immutable manifests referenced by manifest_ref, head-only dependency registration, P2P artifact selection and layer GC affect more than SQL row shapes. A program may still connect successfully while misunderstanding identity or protection. Schema changes, compatible code deployment, new writes and GC activation need separate steps. ### Use case - A single-node or clustered deployment already uses the SQL Catalog, with ongoing sandbox creation, snapshot publication, and online GC. - V1 and V2 coexist during a rollout. Existing sandboxes and long-lived paused state remain with their original Nodes; upgrades must not force consumers to stop. - Adding columns or indexes, backfilling data, changing codecs, or clearing terminal payloads requires an explicit deployment and rollback boundary. The initial transition from an OSS/POSIX file catalog to SQL is covered by [data mapping and migration](https://github.com/kvcache-ai/AgentENV/issues/363). ### Current behavior and workarounds Running Nodes, workers, tools and P2P publishers/readers may use different artifact and metadata contracts during a rollout. The example below covers the transition from an older representation to unified layer identities, immutable manifests referenced from SQL, and dependency heads. It is a compatibility example, not a requirement to implement the older full-mapping SQL proposal first. If SQL has not been deployed, initialize the agreed target model directly and follow #363 for the file-catalog transition. A schema check at Node startup does not cover later changes: existing pools need not reconnect, and old workers, tools, or delayed callbacks may still write. Replica counts or missing heartbeats do not prove that all old accessors and external side effects have ended. Neither “upgrade the database first” nor “upgrade the code first” is sufficient. The order must account for what old code can read, what the schema permits, and which data formats are currently allowed to be produced. ### Desired behavior - While V1 and V2 share a database, all visible data remains readable by every version still allowed to access it. - Expand the schema compatibly before independently enabling new data semantics. Ordinary Nodes do not run DDL. - Existing sandbox requests retain their Node bindings; new creations can use upgraded capacity. Replacement does not depend on VM live migration or forced pauses. - An interrupted migrator can inspect and resume its work. Repeated backfills do not reset terminal states, holds, or alias bindings. - Before new semantics are enabled, compatible old code can be restored. After activation, rollback requires a version that understands existing data; lowering a version number does not restore compatibility. ### Proposed approach ### Example: enabling unified layer identity, manifest references and heads The target Catalog records each validated layer UUID with its fixed predecessor and published descriptor, records the chain heads retained by S, and refers to a complete immutable backend manifest through `manifest_ref`. It does not register every layer again for each snapshot. The data-model issue defines the final schema and field names; these examples do not prescribe a table count or a common volume schema. A legacy accessor may expect full descriptions in SQL, explicit snapshot-to-object mappings, or old UUID/P2P behavior. A compatible release must understand both the existing deployment contract and the approved new one before new-only data appears. Legacy layers with missing/conflicting UUIDs or predecessors require the compatibility path in #363; a schema change does not make them valid. | Phase | Database and artifact structures | Permitted behavior | Running code and compatibility condition | |---|---|---|---| | 1. Existing deployment | Existing structures and formats | Existing writes and protection rules | Existing consumers keep their fixed inputs | | 2. Compatible expansion | Add the structures/read paths needed for manifest references, layer relations and heads | Continue the existing write contract; do not activate new GC | Prove old accessors remain compatible with the expansion | | 3. Deploy compatible code | Expanded structures; bounded backfill where needed | Read both forms, still produce only the permitted compatible form | Include producers, consumers, P2P, workers, tools and final Release paths | | 4. Enable new writes | Approved target structures and identity contract | Permit unified UUID/descriptor results, immutable manifest references and head registration | All affected accessors are compatible or their obligations have been handed off and old access has ended | | 5. Enable graph GC separately | Authoritative, verified layer relations and protected roots | Enable eligible reclamation only after old data, writers and consumers have been accounted for | #363 import/adoption and #364 deletion/reuse conditions are satisfied; identity pruning/F advancement have their own prerequisites | | 6. Contract transitional structures | Remove obsolete structures after the rollback window | Preserve the activated contract | No live query, writer, rollback version or unfinished obligation requires the old representation | Phase 2 requires proof that old code can use the expanded schema. If an old binary accepts only one exact schema version, first deploy a bridge release that improves compatibility checks while retaining old writes. Unknown structures must not be assumed compatible. The exact OCI UUID-generation algorithm, canonical publication representation, layer terminal retention and safe physical-key reuse must be resolved by their owning proposals before enabling operations that depend on them. This issue defines the activation discipline, not those choices. Snapshot/hold first-admission and pruning rules do not automatically apply to layer UUIDs. ### How requests continue during coexistence ```text Migrator: expand structures; permitted writes remain in the compatible legacy form Node A / old version: keep serving sandbox A and its existing binding Node B / compatible version: read both forms; write only the currently permitted form Resume/fork existing A -> Gateway follows its binding -> Node A reuses local holds Create sandbox B -> Gateway schedules compatible capacity -> Node B A's publish/Release -> Original responsible manager -> Authoritative Catalog contract All affected accessors compatible or retired; handoff and validation complete -> Permit the new UUID/manifest/head write contract -> Separately enable graph GC after data, writer and consumer prerequisites pass ``` The existing `no_schedule` mechanism can exclude old Nodes from new allocations while retaining their sandbox bindings, API, and heartbeats. Fork can still create children on an old Node, and resume follows the original binding. Its workload therefore need not decrease monotonically. Keep the old Pod serving until its business obligations are handed off or complete. Its shutdown path stops the reporter, orchestrator, and devices, while Gateway routes existing requests by sandbox binding. Preserving recoverable state alone does not keep requests uninterrupted. If an old Node or long-lived paused state still requires the old contract, retain that Node, its required metadata and fixed artifact inputs, and delay incompatible new semantics. Proceed only when the relevant read, write, and final-release paths are compatible or their obligations have ended. This does not promise replacement of every Node within a fixed time. ### Separate schema compatibility from permitted data formats | Control information | Purpose | |---|---| | `schema_version` / migration steps | Completed table, column, index, and CHECK changes | | `required_read_formats` | Formats every accessor must understand; raised before enabling new writes and not lowered when cleanup is disabled | | `allowed_write_formats` | Formats currently permitted to be produced; can remain in the old form after schema expansion | | Binary compatibility declaration | Supported schema range, readable/writable manifest and artifact formats, UUID/predecessor semantics, P2P selection, and hold/GC contract | | GC/history activation | Separately permits graph reclamation, F advancement and history pruning after their prerequisites; schema expansion alone grants none of them | Database policy is durable; its concrete layout belongs to the Catalog migration infrastructure, not a new per-Node or per-VM table. Admission of a binary and permission to produce a new format are separate decisions. An expanded schema must not automatically enable new identity semantics, graph GC or historical pruning. Enabling new-format writes raises the required read contract. Disabling those writes later does not remove that requirement: new manifests, heads, UUID bindings and terminal states may already exist. Lowering the requirement would need an independently verified reverse migration; this proposal provides no automatic one. The following pseudocode illustrates format admission, not a mandatory schema layout: ```rust struct DbPolicy { schema_version: u32, required_read_formats: Set<DataFormat>, allowed_write_formats: Set<DataFormat>, } fn check_open(binary: &Capabilities, db: &DbPolicy) -> Result<()> { ensure!(binary.supports_schema(db.schema_version)); // Existing data may require formats whose future writes were disabled. ensure!(binary.read_formats.is_superset(&db.required_read_formats)); ensure!(binary.has_compatible_writer(&db.allowed_write_formats)); ensure!(binary.supports_required_identity_and_protection_contracts()); Ok(()) // No DDL and no automatic activation of new writes or GC. } ``` Format activation and Catalog publication/cleanup checks use a common synchronization boundary. Prepare bytes and immutable manifests outside SQL; checking the currently permitted contract and committing its rows/heads must be ordered against activation. External I/O, device waits and uploads stay outside that policy lock. Normal hot VM operations do not add SQL requests for version checks. Constraints cannot prevent every semantically incompatible write that bypasses Catalog. Only reviewed write/cleanup paths may connect. Policy checks, connection permissions and deployment isolation must constrain actual accessors; a version number alone is not isolation evidence. P2P compatibility also requires controlling producers, aliases and readers, not just database permissions. An existing digest requirement must not be silently bypassed by UUID lookup during coexistence. ### Migrator and accessor responsibilities - Each migration step has a monotonic number, immutable checksum, state, and completion evidence. Only one migrator may execute a phase against a target at a time. - Check the target, prerequisite version, and actual structure before proceeding. Mark a step complete only after its DDL/backfill completes. After interruption or an unknown response, inspect actual database state rather than blindly repeating work or advancing the version. - Handle PostgreSQL transactions and locks per statement. Concurrent index creation cannot simply be wrapped in the same DDL transaction; failures require checking index validity. Validate SQLite's write locking and table-rebuild behavior separately. - Nodes, workers, and tools check compatibility on startup, reconnection, and Catalog reinitialization, before destructive local cleanup or device recovery. Each process must not independently run migrations. - Before enabling a format, account for old Nodes, workers, tools, pools, caches, P2P producers/readers, in-flight transactions, delayed callbacks, and final Release paths. Use separate application database identities for incompatible generations. After their business obligations are handed off or complete, prevent reconnection and confirm their existing sessions and transactions have ended. Revoking access so responsible requests fail is not a substitute for handoff. - Silent heartbeats, zero Pod replicas, or an upgraded HTTP entry point do not prove that old accessors or external side effects have ended. When evidence is insufficient, keep serving the old format and delay activation. ### Apply the same sequence to other schema changes | Change | Expansion and coexistence | Backfill / validation | Contraction condition | |---|---|---|---| | Add a column | Add a compatible column/default; old writes remain valid | New code accepts missing values; conditional batches must not overwrite concurrent updates | Add stricter constraints only after required values exist and old writers have exited | | Rename a field | Retain the old column and add the new one; compatible code defines synchronization | Verify both representations; a direct rename must not break old queries | Remove the old column after all old readers/writers exit and the rollback window closes | | Add a JSON/enum form | Deploy readers for old and new forms while still writing the old form | Preserve defaults and unknown fields; business updates must not erase new facts | Activate the new form independently before producing data old readers cannot understand | | Add an index | Use an engine-specific online creation procedure with verified lock behavior | Check validity, query plans, space, and concurrent impact | Remove the old index only after its dependencies are gone | Backfills and structural changes must not require pausing user operations. Use bounded lock waits/retries and measure the actual latency impact; this is not a claim that arbitrary DDL is nonblocking. During a representation transition, keep one explicit authority while old writers remain active. Where compatible dual maintenance is required, update database representations in the same transaction; prepare immutable external content before publishing its reference. Do not infer a complete chain from an unordered old object mapping: validate the complete ordered manifest. After old writers exit, finish and verify the backfill before switching authority. One completed backfill pass is not sufficient. ### Acceptance criteria - [ ] Run the applicable phases with real old/new binaries sharing one database and artifact namespace. Expansion preserves old operation; new identity/manifest/head writes and graph GC remain disabled until their separate activation conditions pass. - [ ] Specify compatibility declarations and the storage/synchronization of format policy. Cover ordinary connections, existing pools, workers, tools, cached manifests, P2P, and final Release. - [ ] Keep existing sandbox resume/fork/publish requests on the old Node while new creations can use V2. Long-lived old consumers must not be stopped to enable the new format. - [ ] Inject migration interruption, checksum mismatch, partial backfill, invalid indexes, concurrent writes, and delayed callbacks. Actual structure and completion records must agree. - [ ] Exercise rollback before and after the first new-format write and GC activation. Changing a version/configuration value alone must not admit readers or writers that misunderstand existing UUID bindings, manifests, heads or deletion states. - [ ] Validate nil/duplicate UUIDs, conflicting predecessors, late legacy callbacks, manifest publication failures and old P2P aliases. Preserve existing consumers and safe identity admission/pruning throughout the transition. - [ ] Measure DDL/backfill lock waits, business tail latency, and recovery per engine. Do not advance a step that fails the continuous-service requirements. ### Compatibility and operational impact - **API/config changes:** No new sandbox API. Add explicit binary compatibility declarations, database format policy, and migration execution configuration. - **Snapshot or storage format changes:** Compatibility covers immutable manifest references, layer identity/predecessor semantics, head registration and exact artifact selection. Existing readers keep their fixed inputs; legacy bytes are not rewritten in place. First new-format writes change the rollback boundary. Generating a canonical artifact or using an existing descriptor must not confuse UUID identity with a byte digest. - **New host/runtime requirements:** Ordinary processes do not perform migrations. The migrator has explicit authorization and mutual exclusion. Reuse existing Nodes, hold journals, and devices; no live-migration capability is introduced. - **Upgrade and rollback considerations:** After expansion but before new data is written, rollback is possible if compatibility is verified. Once new data or irreversible cleanup exists, rollback requires a version that understands the resulting contract. Disabling new writes or GC does not restore prior metadata or bytes, and lowering `schema_version` does not make old code safe. The rollback window and schema contraction are separate. Stopping new-format writes does not remove previously written data. Before dropping old structures, confirm that no query, writer, compatible rollback version, or unfinished task still needs them. ### Alternatives considered - Migrate automatically on every Node startup: this creates DDL races and may cross compatibility boundaries while old processes still run. Use a dedicated controlled migrator. - Check only `schema_version`: this cannot distinguish a structure permitted by SQL from an identity, manifest or protection contract understood by every accessor. Track required read contracts and permitted writes separately. - Stop the service or replace old Pods directly: simpler operationally, but it violates continuous service. Retaining old Nodes may delay new semantics instead. ### Non-goals A general migration platform and VM lifecycle redesign are outside this issue. Preserve Local/promotion protection and existing volume ownership. This issue does not select a shared volume schema, an OCI UUID algorithm, or a terminal layer-retention policy. The upgrade contract does not require lock-free DDL, VM live migration, or forced retirement of long-lived consumers within a deadline. ### Contribution I can provide more design details ### Pre-submission checklist - [x] I searched existing issues and discussions and did not find a duplicate. - [x] I described a concrete problem and use case, not only a proposed technology. - [x] I understand that acceptance of the problem does not imply approval of a particular implementation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/AgentENV#94
No description provided.