feat(kc): checkout packing + ETag caching (Phase 2) #150

Merged
forbes merged 1 commits from feat/kc-checkout-packing into main 2026-02-18 23:06:18 +00:00
Owner

Implements issue #142 — .kc checkout pipeline that repacks silo/ entries with current DB state before serving downloads.

Depends on: #149 (Phase 1 — commit extraction + metadata API)

Checkout pipeline

When a client downloads a .kc file via GET /api/items/{pn}/file/{rev}, the server now:

  1. Reads the stored file into memory
  2. Checks for silo/ directory (plain .fcstd files bypass packing entirely)
  3. Queries item_metadata + revisions for current server state
  4. Repacks silo/ entries in the ZIP with fresh data
  5. Streams the repacked file to the client

Non-silo entries (Document.xml, thumbnails, .brp files) are copied verbatim with original compression methods and timestamps preserved.

Packed entries (Phase 2)

Entry Source
silo/manifest.json item_metadata + items table
silo/metadata.json item_metadata.fields + tags + lifecycle
silo/history.json Last 20 revisions
silo/dependencies.json Empty [] (populated in Phase 3)

Approvals, macros, and jobs are omitted — they'll be added in Phase 3-5.

ETag caching

  • ETag: SHA-256 of revision_number:metadata.updated_at, truncated to 16 hex chars
  • If-None-Match: Returns 304 before reading from storage if ETag matches
  • Cache-Control: private, must-revalidate

Lazy packing optimization

Skips the expensive ZIP rewrite when the stored blob is already current:

  • revision_hash matches current head
  • metadata.updated_at is older than the blob's upload time

New files

  • internal/kc/pack.goPack() and HasSiloDir() functions (pure library, 7 unit tests)
  • internal/api/pack_handlers.gopackKCFile, computeETag, canSkipRepack helpers

Modified files

  • internal/kc/kc.go — Added HistoryEntry and PackInput types
  • internal/api/handlers.go — Restructured HandleDownloadFile with packing + ETag logic

Closes #142

Implements issue #142 — .kc checkout pipeline that repacks `silo/` entries with current DB state before serving downloads. **Depends on:** #149 (Phase 1 — commit extraction + metadata API) ## Checkout pipeline When a client downloads a `.kc` file via `GET /api/items/{pn}/file/{rev}`, the server now: 1. Reads the stored file into memory 2. Checks for `silo/` directory (plain `.fcstd` files bypass packing entirely) 3. Queries `item_metadata` + revisions for current server state 4. Repacks `silo/` entries in the ZIP with fresh data 5. Streams the repacked file to the client Non-silo entries (Document.xml, thumbnails, .brp files) are copied verbatim with original compression methods and timestamps preserved. ## Packed entries (Phase 2) | Entry | Source | |-------|--------| | `silo/manifest.json` | `item_metadata` + `items` table | | `silo/metadata.json` | `item_metadata.fields` + tags + lifecycle | | `silo/history.json` | Last 20 revisions | | `silo/dependencies.json` | Empty `[]` (populated in Phase 3) | Approvals, macros, and jobs are omitted — they'll be added in Phase 3-5. ## ETag caching - **ETag**: SHA-256 of `revision_number:metadata.updated_at`, truncated to 16 hex chars - **If-None-Match**: Returns 304 before reading from storage if ETag matches - **Cache-Control**: `private, must-revalidate` ## Lazy packing optimization Skips the expensive ZIP rewrite when the stored blob is already current: - `revision_hash` matches current head - `metadata.updated_at` is older than the blob's upload time ## New files - `internal/kc/pack.go` — `Pack()` and `HasSiloDir()` functions (pure library, 7 unit tests) - `internal/api/pack_handlers.go` — `packKCFile`, `computeETag`, `canSkipRepack` helpers ## Modified files - `internal/kc/kc.go` — Added `HistoryEntry` and `PackInput` types - `internal/api/handlers.go` — Restructured `HandleDownloadFile` with packing + ETag logic Closes #142
forbes added 1 commit 2026-02-18 23:01:46 +00:00
Implements issue #142 — .kc checkout pipeline that repacks silo/ entries
with current DB state before serving downloads.

When a client downloads a .kc file via GET /api/items/{pn}/file/{rev},
the server now:
1. Reads the file from storage into memory
2. Checks for silo/ directory (plain .fcstd files bypass packing)
3. Repacks silo/ entries with current item_metadata + revision history
4. Streams the repacked ZIP to the client

New files:
- internal/kc/pack.go: Pack() replaces silo/ entries in ZIP, preserving
  all non-silo entries (FreeCAD files, thumbnails) with original
  compression and timestamps. HasSiloDir() for lightweight detection.
- internal/api/pack_handlers.go: packKCFile server helper, computeETag,
  canSkipRepack lazy optimization.

ETag caching:
- ETag computed from revision_number + metadata.updated_at
- If-None-Match support returns 304 Not Modified before reading storage
- Cache-Control: private, must-revalidate

Lazy packing optimization:
- Skips repack if revision_hash matches and metadata unchanged since upload

Phase 2 packs: manifest.json, metadata.json, history.json,
dependencies.json (empty []). Approvals, macros, jobs deferred to
Phase 3-5.

Closes #142
forbes merged commit 1a34455ad5 into main 2026-02-18 23:06:18 +00:00
forbes deleted branch feat/kc-checkout-packing 2026-02-18 23:06:18 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kindred/silo#150