feat(server-core): [YW-125] draft delta-table schema + compacted command log#151
Conversation
Adds 6 `<table>__draft` shadow tables (one per draftable artifact) and the `draft_command_log` table, wiring DashFrame's artifact storage to the @wystack/db withDraft primitive (YW-120/YW-121). Shadow tables match the exact write-path contract (confirmed against wystack 86fe8856): `draft_id TEXT NOT NULL`, all canonical columns nullable (sparse — NULL = no override), `__tombstone BOOLEAN NOT NULL DEFAULT false`, composite PK `(draft_id, id)`. Canonical tables are pristine; a no-draft read never touches the shadow tables (zero-overhead property holds). Security invariant enforced: `secret_mappings` and `project_meta` have NO shadow. A `secret_mappings__draft` would be a credential-in-draft leak surface — the draft system operates on ARTIFACTS only. The command log stores ordered `DraftCommand[]` per draft for replay at publish (NOT a row-delta copy — intent grouping is only in the log). Compaction collapses add-tweak-delete chains per `compactionKey` to net effect so the log stays bounded. Tests: 62 new assertions covering all 6 shadow shapes, the composite-PK DB constraint, the security boundary (asserting absence of forbidden tables), the command-log schema, and the compactLog algorithm against all documented cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a full draft-overlay layer to the Drizzle schema: six ChangesDraft Overlay Schema
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ket-refs gate Remove YW-nnn references from schema.ts, index.ts, and schema.test.ts. Ticket ids belong in commit messages and Linear, not in source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e09761326
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .notNull() | ||
| .defaultNow(), | ||
| }, | ||
| (t) => [index("draft_command_log_draft_id_seq_idx").on(t.draftId, t.seq)], |
There was a problem hiding this comment.
Enforce unique sequence numbers per draft
These rows are supposed to be replayed in seq order, but this schema only creates a non-unique index on (draft_id, seq). If two appends for the same draft compute/insert the same sequence (e.g. concurrent saves or a retry after compaction), the database accepts both and ORDER BY seq has no deterministic tie-breaker, so publish can replay commands in the wrong order. Make (draft_id, seq) unique (or part of a key) so the append path fails/retries instead of storing an ambiguous log.
Useful? React with 👍 / 👎.
…+ unused-disable Remove the unused @typescript-eslint/no-non-null-assertion disable comments (the rule is not active in this eslint config so the directives are reported as unused). Remove the void expression (sonarjs/void-use) by dropping the already-unused cols variable from the PK test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/server-core/src/schema.ts`:
- Around line 401-418: The index `draft_command_log_draft_id_seq_idx` on columns
`draftId` and `seq` is currently a non-unique index, but since `seq` is
documented as the monotonically increasing position within a draft, it should
enforce uniqueness to prevent duplicate `(draft_id, seq)` combinations. Change
the index definition from a regular `index()` call to a `uniqueIndex()` call to
enforce the unique constraint on the combination of `draftId` and `seq`, which
will prevent ambiguous replay order and preserve deterministic compaction
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a767ec32-71ab-417b-9399-85fef44d34d9
📒 Files selected for processing (3)
packages/server-core/src/index.tspackages/server-core/src/schema.test.tspackages/server-core/src/schema.ts
The seq column is the monotonic replay position within a draft; a duplicate (draft_id, seq) would create ambiguous replay order at publish time. Upgrade the index from a plain index to a uniqueIndex so duplicate seq values within the same draft are rejected at the DB level. Add a test that asserts: same-draft duplicate-seq is rejected, while the same seq for a different draft is accepted. Addresses CodeRabbit review finding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addressing reviewer findingsCodeRabbit — unique
|
Summary
<table>__draftshadow tables (one per draftable artifact:data_sources,data_tables,data_frames,insights,visualizations,dashboards) matching the exact @wystack/dbwithDraftwrite-path contract:draft_id TEXT NOT NULL, canonical columns all nullable (sparse — NULL = no override),__tombstone BOOLEAN NOT NULL DEFAULT false, composite PK(draft_id, id).draft_command_logtable for orderedDraftCommand[]storage per draft, supporting compacted replay at publish time (command-log replay is the publish mechanism, NOT a row-delta copy).secret_mappingsandproject_metahave NO shadow tables. Credentials route through the SecretVault, never the draft overlay.Contract verification
Wystack
withDraftwrite-path (86fe8856) confirmed: the shadow table DDL in this PR matches the write-path's exact(draft_id, <pk>)ON CONFLICT key and__tombstonecolumn contract frompackages/db/src/__tests__/with-draft-write.test.tsandwriteShadowRow.Command-log compaction
compactLogcollapses add-tweak-delete chains percompactionKeyto net effect:compactionKey→ always preservedTests
62 new assertions in
schema.test.ts:draft_id+__tombstone, and enforces(draft_id, id)uniqueness at the DB level.secret_mappings__draftandproject_meta__draftdo NOT exist.compactLog: all documented compaction cases.Local review gate
turbo typecheck: 46/46 tasks successfulsecret_mappings/project_metaTracked internally as YW-125.
🤖 Generated with Claude Code
Greptile Summary
Adds the draft delta-table schema for DashFrame's
withDraftoverlay layer: 6__draftshadow tables (one per draftable artifact) plusdraft_command_logfor compacted command-log replay at publish time, with thesecret_mappings/project_metasecurity boundary enforced at the schema level.data_sources__draftthroughdashboards__draft) each mirror their canonical columns as nullable sparse overrides, carrydraft_id+__tombstone, and enforce a composite PK(draft_id, id)matching thewithDraftwrite-path upsert contract exactly.draft_command_logstores orderedDraftCommand[]per draft with auniqueIndexon(draft_id, seq)to prevent replay-order ambiguity; publish is command-log replay, not a row-delta copy.compactLognet-effect collapse — but one edge case in thecompactLogverification implementation is missing both a test and a fix (canonical delete → create same key → delete new row silently drops the canonical delete).Confidence Score: 4/5
Safe to merge with the compactLog fix applied — the schema DDL and DB-level constraints are correct; only the compaction algorithm specification has a logic gap.
The
compactLogfunction inschema.test.tsserves as the DashFrame-side specification for command-log compaction. Itscreateand cancel-create branches both unconditionally callsurvivingDelete.delete(key), which erases a prior canonical delete when the sequence is "canonical delete → create (same key) → delete of new row". No test covers this path, so the gap is invisible today, but if DashFrame implements compactLog from this specification, a canonical row that should be deleted at publish time would silently survive.packages/server-core/src/schema.test.ts — the compactLog implementation and its test coverage for the canonical-delete interaction.
Important Files Changed
__draftshadow tables anddraft_command_log; canonical columns correctly mirrored as nullable, composite PKs on(draft_id, id)match thewithDraftwrite-path contract, security boundary (no shadow forsecret_mappings/project_meta) is enforced, anduniqueIndexon(draft_id, seq)guards replay order.compactLogimplementation has a logic bug where a canonical delete is silently dropped under the sequence "canonical delete → create (same key) → delete of new row", and no test covers this scenario.Entity Relationship Diagram
%%{init: {'theme': 'neutral'}}%% erDiagram data_sources { uuid id PK text name text kind text storage jsonb config } data_sources__draft { text draft_id PK uuid id PK text name boolean __tombstone } data_tables { uuid id PK uuid data_source_id FK } data_tables__draft { text draft_id PK uuid id PK boolean __tombstone } insights { uuid id PK text name jsonb definition } insights__draft { text draft_id PK uuid id PK boolean __tombstone } dashboards { uuid id PK text name jsonb layout } dashboards__draft { text draft_id PK uuid id PK boolean __tombstone } draft_command_log { uuid id PK text draft_id integer seq text path jsonb args text compaction_key text kind } secret_mappings { text ref PK text backend text locator } data_sources ||--o{ data_sources__draft : "draft overlay" data_tables ||--o{ data_tables__draft : "draft overlay" insights ||--o{ insights__draft : "draft overlay" dashboards ||--o{ dashboards__draft : "draft overlay" data_sources ||--o{ data_tables : "has" draft_command_log }o--|| data_sources__draft : "published via replay"%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% erDiagram data_sources { uuid id PK text name text kind text storage jsonb config } data_sources__draft { text draft_id PK uuid id PK text name boolean __tombstone } data_tables { uuid id PK uuid data_source_id FK } data_tables__draft { text draft_id PK uuid id PK boolean __tombstone } insights { uuid id PK text name jsonb definition } insights__draft { text draft_id PK uuid id PK boolean __tombstone } dashboards { uuid id PK text name jsonb layout } dashboards__draft { text draft_id PK uuid id PK boolean __tombstone } draft_command_log { uuid id PK text draft_id integer seq text path jsonb args text compaction_key text kind } secret_mappings { text ref PK text backend text locator } data_sources ||--o{ data_sources__draft : "draft overlay" data_tables ||--o{ data_tables__draft : "draft overlay" insights ||--o{ insights__draft : "draft overlay" dashboards ||--o{ dashboards__draft : "draft overlay" data_sources ||--o{ data_tables : "has" draft_command_log }o--|| data_sources__draft : "published via replay"Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "fix(server-core): enforce unique (draft_..." | Re-trigger Greptile