The column becomes token_sealed (migration 0122 seals existing rows with a
frozen copy of the v1 key), every reader opens it through connection_token,
a row this instance cannot open is skipped with a warning, and Settings asks
for that token again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading all 28 models against each other: 54 `x.isoformat() if x else None` /
`x.isoformat()` expressions in 23 to_dict methods, in two guarded/unguarded
wordings, become iso() from models/base.py — uniform, and a row read before
flush serialises as null instead of raising. Rulebook / RulebookTopic / Rule
carried byte-identical copies of TimestampMixin's two columns;
InvitationToken / PasswordResetToken / NoteUsageEvent carried CreatedAtMixin's —
all six now use the mixin. AppLog and RetrievalLog keep their explicit
created_at, commented: their composite index orders on `created_at.desc()`,
which needs the column object in the class body. Schema-neutral (same column
definitions) — no migration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A forge token is a user's credential, not an instance's. The single
admin-settings config is replaced by per-user keyring rows (one per forge
host), and every server-side forge read runs on the PROJECT OWNER's keyring:
- forge_connections table + projects.forge_connection_id pin (migration 0078,
which also carries the existing admin config into the first admin's row and
deletes the old setting keys — no legacy dual-read)
- get_forge() replaced by get_forges(owner_id, project_id) -> ForgeSelector;
resolve(repo) picks the connection whose host serves the repo. A pinned
project uses ONLY its pinned connection; a stale pin (ownership moved) is
ignored, never honored across users
- env FORGE_* config survives as an implicit entry for admin owners only;
a stored row for the same host beats it
- consumers threaded: pull-time freshness (owner of the note), coverage
(owner of the project), coverage routes' configured flag
- routes: /api/settings/forge-connections CRUD + per-connection test
(own-rows only, tokens never returned); /api/admin/forge shrinks to
/api/admin/forge-webhook (secret only); PUT /api/projects/<id>/forge pins,
owner-or-admin asking, owner's connections only
- UI: Git Forges card moves to Settings -> Integrations as a connection
list; webhook secret stays in the admin Config tab; owner-only forge
select on the project coverage card
- backups exclude forge_connections (credentials, api_keys precedent) and
the pin, so restores fall back to keyring resolution
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>