mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[GH-ISSUE #361] [Design Proposal] Snapshot Catalog data model and API #88
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#88
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @guozy18 on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/361
Affected component
Snapshot Catalog schema, shared layers, and database transaction interfaces.
Related: Overview #360 · Usage holds #362 · Migration and field mapping #363 · Online GC #364 · Upgrades #365.
Problem statement
A full snapshot-to-layer mapping repeats inherited relationships on every publication. Store each sealed layer's existing UUID and fixed predecessor once; let snapshots retain only their chain heads. The tables below make that proposal concrete without introducing a second layer identity.
1. Before and after
manifest_refin SQL; immutable complete manifest in storageS is a snapshot identity, H is a usage-hold identity, and A/B/C are the existing file UUIDs after their contract is unified. They are not new database-only layer IDs.
2. Candidate table definitions
These four tables describe the snapshot-side layer-retention core, not a claim that four tables already cover the entire snapshot/volume system. Types below use PostgreSQL notation. SQLite uses canonical UUID text, validated JSON text, and equivalent transactional constraints.
snapshots: public identity and content entrysnapshot_idUUID PRIMARY KEY; immutablealiasTEXT NULL; unique when present, nonblank, case-sensitivesnapshot_typeTEXT NOT NULL;none,local, ordistributedmanifest_refTEXT NULL; exact immutable manifest locatormetadataJSONB NULL; object when present, validated by the business codecretiredBOOLEAN NOT NULL DEFAULT FALSE; cannot revertcreated_atBIGINT NOT NULL; immutable UTC epoch millisecondssnapshot_type=none, no runnablemanifest_ref, no published heads; requested resources/build state remain in metadataPromotion may change S's
manifest_ref, but it keeps old readers' fixed inputs and the union of old/new heads for as long as their protection requires. An immutable manifest is never overwritten in place.layers: one row per sealed UUIDuuidUUID PRIMARY KEY; non-nil, immutableparent_uuidUUID NULL; immutable reference to the predecessor; no cascading deletiondigestTEXT NOT NULL; nonempty, immutable for the canonical published representationstateTEXT NOT NULL;available,deleting,deletedlifecycle(digest,parent_uuid)rule that merges different embedded UUIDsThe existing OCI, memory, compaction, and publication paths must first satisfy this UUID/predecessor contract. The exact OCI UUID-generation algorithm remains open. Producers/transforms should return the final digest/size descriptor to avoid redundant digest scans; known-digest P2P reads must validate that exact content.
snapshot_heads: retain chain tops, not every layersnapshot_idUUID NOT NULL; referencessnapshots.snapshot_idlayer_uuidUUID NOT NULL; referenceslayers.uuidPrimary key:
(snapshot_id, layer_uuid). Do not cascade-delete these rows on public Delete; retirement must wait for active use. Role, order, and duplicate uses belong in the manifest. If rootfs and an attached drive both use[A,B], one(S,B)head is sufficient for retention.snapshot_pins: shared usage responsibilitypin_idUUID PRIMARY KEY; immutable Hsnapshot_idUUID NOT NULL; immutable H-to-S bindingreleasedBOOLEAN NOT NULL DEFAULT FALSE; false to true onlyDo not require a parent foreign key from this table to
snapshots: Release may arrive before S or before Pin. First active Pin validates S transactionally; a release-first Closed H cannot later become active. Multiple prepared inputs/VMs/children can share H through local ownership; this is not one row per VM.Required access paths
snapshotssnapshot_id; unique non-NULLaliassnapshots(created_at DESC, snapshot_id ASC)snapshotslayersuuid; index onparent_uuidlayersdigest; deletion-state scansnapshot_heads(snapshot_id,layer_uuid); reverse(layer_uuid,snapshot_id)snapshot_pinssnapshot_id WHERE NOT releasedNo persisted
ref_countis required: indexed head/valid-child existence checks are sufficient for logical layer retention. No separateheads_releasedflag is needed just to make head deletion idempotent. Zero heads does not prove that non-layer artifacts or external deletion work have finished. Any separate all-obligations-complete fact must cover those responsibilities before payload cleanup.3. Field placement: SQL versus immutable manifest
snapshotscolumnssnapshots.metadatasnapshots.metadataThe detailed old-to-new field mapping remains in #363. Build logs retain their existing ownership.
4. Concrete records: shared prefix, fork, and in-use deletion
All IDs and digests below are shortened examples.
mf0/mf1/mf2are exact references to different immutable manifests, not mutable aliases.Publish S0
snapshotslayerssnapshot_headssnapshot_pins: empty. Publishing S0 does not itself create a usage pin.Subsequent operations: exact row changes
Suppose S1 adds C to the same rootfs prefix, and a fork from S0 later publishes S2 with D:
snapshotslayerssnapshot_headssnapshot_pins(H0,S0,false)before fetching mf0 or artifacts(C,B,dC,available)only(S1,C)and(S1,M)(D,B,dD,available)only(S2,D)and(S2,M)S0.retired=true(S0,B)and(S0,M)H0.released=true(S0,B)and(S0,M)(S1,C)and(S1,M)After releasing S0, the surviving references are:
C can enter deletion only after current checks and durable authorization succeed; deleting its OSS bytes is a separate step. This example does not choose permanent layer tombstones or permission to reuse deleted UUIDs/keys; see #364.
Relationship-write comparison
layers; each snapshot retains its own headsThe intended database relationship-write bound is O(delta + h) for known prefixes. Full manifest serialization, uploads, digest work, and initial imports are measured separately; this is not a claim that the entire publish operation is O(delta).
5. Transaction and API boundaries
An unknown publication commit retains the original source responsibility and artifacts for reconciliation. It does not authorize compensating physical deletion. Async startup attachment commits its new description reference and protection together; old consumers keep their fixed inputs. Detailed hold/cache rules are in #362, reclamation in #364, and rollout gates in #365.
6. Coverage boundaries to finish before enabling GC
layersand the same retention protocolThis table is a coverage boundary, not permission to omit these dependencies or to call the four-table sketch a complete universal resource schema. Existing consumers and legacy files remain protected during the migration described in #363; schema readiness alone does not enable GC.
Acceptance criteria
Contribution
I can provide more design details. Implementation and validation ownership remain to be discussed.
Pre-submission checklist