gabriel / musehub public
0077_add_committed_at_raw.py python
53 lines 2.0 KB
Raw
sha256:06cdc3de1e7f8091954906cf91f22c941373474c3322392a79300adc19b33197 fix(#195): narrow fix — committed_at_raw preserves commit i… Sonnet 5 minor ⚠ breaking 2 days ago
1 """Add committed_at_raw to musehub_commits
2
3 musehub#195 (narrow fix): committed_at is identity-bearing -- a commit's
4 content-addressed id is a hash whose preimage includes the exact ISO-8601
5 committed_at string the client used. musehub_commits.timestamp is a
6 Postgres timestamptz, which normalizes any non-UTC offset to UTC on
7 write. When the wire-serve path (_to_wire_commit) reconstructs a commit's
8 bytes from `timestamp.isoformat()` to send it back to a client, a commit
9 originally authored with a non-UTC offset gets served with a DIFFERENT
10 committed_at string than the one baked into its id -- the client correctly
11 rejects the download as an integrity failure.
12
13 This is an instance of the broader problem tracked in musehub#63 (commits
14 are DB-only, reconstructed from DB fields rather than served as stored
15 canonical bytes). This migration is the deliberately narrow, additive fix:
16 capture the exact original string alongside the normalized timestamptz, so
17 the serve path can use it verbatim instead of re-deriving a lossy one.
18 Still correct after #63 eventually lands (canonical-bytes storage would
19 make this column redundant, not wrong).
20
21 Additive and nullable -- existing rows get NULL. The serve path
22 (musehub/services/musehub_wire_shared.py::_to_wire_commit) falls back to
23 `timestamp.isoformat()` for NULL rows and verifies the reconstructed bytes
24 actually reproduce the stored commit_id, refusing to serve (fail closed)
25 rather than silently serving a mismatched reconstruction if they don't.
26
27 Revision ID: 0077
28 Revises: 0076
29 """
30 from __future__ import annotations
31
32 from typing import Sequence, Union
33
34 import sqlalchemy as sa
35 from alembic import op
36
37
38 # revision identifiers, used by Alembic.
39 revision: str = '0077'
40 down_revision: Union[str, None] = '0076'
41 branch_labels: Union[str, Sequence[str], None] = None
42 depends_on: Union[str, Sequence[str], None] = None
43
44
45 def upgrade() -> None:
46 op.add_column(
47 'musehub_commits',
48 sa.Column('committed_at_raw', sa.Text(), nullable=True),
49 )
50
51
52 def downgrade() -> None:
53 op.drop_column('musehub_commits', 'committed_at_raw')
File History 1 commit
sha256:06cdc3de1e7f8091954906cf91f22c941373474c3322392a79300adc19b33197 fix(#195): narrow fix — committed_at_raw preserves commit i… Sonnet 5 minor 2 days ago