mirror of
https://github.com/dohooo/helmor.git
synced 2026-09-22 16:40:02 +08:00
ecc41ef7c0
Cold start previously minted EVERY member's ForgeIdentity DO and wrote the whole team's gh/glab tokens into the shared container — one induced 'cat' away from a full-team credential leak. Per the user product ruling the creator-identity model stays (everyone's pushes reuse the creator's forge identity); only the storage narrows: - teams.forge_identity_member_id: the forge 'creator', bound FIRST-authorizer-wins on PUT /team/forge-identity (mirrors cloud_identity_member_id's single-row upsert, but COALESCE keeps an existing binding; a non-creator racing to authorize first is the same accepted TOFU edge as cloud identity). Added via the P1-4c idempotent D1 ALTER runner (0002) + schema.sql for fresh DBs. - collectMemberForgeCreds injects tokens ONLY for the creator entry (flagged creator:true); every other member is login-only (kept for per-member commit authorship). Local-docker launcher mirrors the same narrowing. - member_creds.rs (CONTAINER RUST — needs the next team-image bump): token lookups fall back to the creator's tokens when the acting member has none, and when no acting member is bound at all (the detached auto-push thread). Old injected files without the creator flag still parse (no fallback). Authorship lookups (member_for) do NOT fall back. NOTE: P1-8a (scrubbing the token out of .git/config) depends on this creator fallback for push auth — rolling back this commit requires rolling back the P1-8a commit with it.
50 lines
2.4 KiB
TypeScript
50 lines
2.4 KiB
TypeScript
// Versionless, duplicate-tolerant D1 migrations for PRE-EXISTING team
|
|
// databases (round6 P1-4c). schema.sql's `CREATE TABLE IF NOT EXISTS` only
|
|
// shapes FRESH databases — it never backfills a column onto an
|
|
// already-bootstrapped table — and D1/SQLite has no `ALTER TABLE … ADD COLUMN
|
|
// IF NOT EXISTS`. So additive columns land here, and provision-team.ts
|
|
// executes every entry against the live D1 on each provision / re-provision:
|
|
//
|
|
// fresh DB (column already in the CREATE TABLE) → "duplicate column name"
|
|
// → classified already-applied, skipped
|
|
// pre-existing DB missing the column → the ALTER applies it
|
|
// anything else → fatal; the provision fails
|
|
//
|
|
// Adding a migration: append an entry with a unique id, keep it additive and
|
|
// duplicate-classifiable, and add the column to schema.sql's CREATE TABLE too
|
|
// (fresh DBs read only that file). If migrations ever stop fitting this shape
|
|
// (backfills, type changes, data moves), upgrade to a versioned
|
|
// schema_migrations table instead of stretching this list past its design.
|
|
|
|
export type D1Migration = { id: string; sql: string };
|
|
|
|
export const D1_MIGRATIONS: readonly D1Migration[] = [
|
|
{
|
|
// Phase 1 cloud-run identity anchor. Teams whose D1 predates the column
|
|
// (long-lived dev DBs; re-provision onto an already-set-up account, where
|
|
// `d1 create` and the schema apply both no-op) hit "no such column" on
|
|
// team.ts's INSERT/SELECT without it.
|
|
id: "0001-teams-cloud-identity-member-id",
|
|
sql: "ALTER TABLE teams ADD COLUMN cloud_identity_member_id TEXT",
|
|
},
|
|
{
|
|
// Round6 P1-2a: the team's forge "creator" binding
|
|
// (first-forge-authorizer-wins; see schema.sql). Pre-existing DBs hit
|
|
// "no such column" on putForgeIdentity / collectMemberForgeCreds
|
|
// without it.
|
|
id: "0002-teams-forge-identity-member-id",
|
|
sql: "ALTER TABLE teams ADD COLUMN forge_identity_member_id TEXT",
|
|
},
|
|
];
|
|
|
|
/** Classify a failed `wrangler d1 execute --command` output. SQLite's
|
|
* "duplicate column name" means the column is already there (a fresh DB, or a
|
|
* re-run) — that's the desired end state, not an error. Anything else is
|
|
* fatal: the provision must fail loudly rather than continue against a DB in
|
|
* an unknown shape. */
|
|
export function classifyMigrationError(
|
|
output: string,
|
|
): "already-applied" | "fatal" {
|
|
return /duplicate column name/i.test(output) ? "already-applied" : "fatal";
|
|
}
|