OXYGENHUB

Organisation repositories require one sealed approval by default 462f88d161ce

A repository created under an organisation requires one sealed approval
(oxy seal --proposal URL) to land a proposal; existing organisation
repositories still at none move to one; personal repositories keep none.
Admins change it in Settings.

closes #64, closes #47
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-30 14:50 UTC
Signature
✔ signed by Andy McCall
Hash
462f88d161ce51c4ae1c952705b35fff46170e9be8fcdab9fa5ec59ed9045437
Depends on
19bb31ff1f5c 8fb35df7d66e 93be9ce4653e d23464c0ac60
Depended on by
3b1bc27fe805 5d90e0e045e2 ff6569530173
In bookmarks
api-unknown-fields cancel-landed-runs clear-waiting close-proposal-140 diffusion-pin-bookmark draft-ready image-check job-waits main oh-comment-body-file proposal-list-fast run-one-job wiki-says-so
In tags
v0.1.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
8 atoms, 1 tree operation, 4 files

Browse the tree at this change

added crates/hub/migrations/0025_org_approvals.sql +4 −0
1 +
-- Organisation repositories require one sealed approval by default
2 +
-- (oxygenhub#47, #64); personal repositories keep none. Admins can lower it.
3 +
UPDATE repositories SET required_approvals = 1
4 +
    WHERE required_approvals = 0 AND owner IN (SELECT name FROM organisations);
modified crates/hub/src/db/repos.rs +5 −1
…
96 unchanged lines
97 97
    .await
98 98
}
99 99
100 +
/// Create a repository row. One under an organisation requires one sealed
101 +
/// approval to land a proposal (oxygenhub#64); a personal one requires none.
100 102
pub async fn create(pool: &Pool, owner: &str, name: &str, description: &str, visibility: Visibility) -> sqlx::Result<RepoRow> {
101 103
    let now = now_ms();
102 104
    sqlx::query_as(
103 -
        "INSERT INTO repositories (owner, name, description, visibility, created_ms, updated_ms) VALUES (?, ?, ?, ?, ?, ?) RETURNING *",
105 +
        "INSERT INTO repositories (owner, name, description, visibility, created_ms, updated_ms, required_approvals) \
106 +
         VALUES (?, ?, ?, ?, ?, ?, (SELECT COUNT(*) > 0 FROM organisations WHERE name = ?)) RETURNING *",
104 107
    )
105 108
    .bind(owner)
106 109
    .bind(name)
…
1 unchanged lines
108 111
    .bind(visibility.as_str())
109 112
    .bind(now)
110 113
    .bind(now)
114 +
    .bind(owner)
111 115
    .fetch_one(pool)
112 116
    .await
113 117
}
…
151 unchanged lines
modified crates/hub/tests/accounts.rs +7 −0
…
399 unchanged lines
400 400
    let me = db::accounts::account_by_name(&app.db, "andy").await.unwrap().unwrap();
401 401
    let org = db::accounts::create_org(&app.db, "acme", "Acme", me.id).await.unwrap();
402 402
403 +
    // An organisation's repository requires one sealed approval to land a
404 +
    // proposal; a personal one requires none (oxygenhub#64).
405 +
    let tools = db::repos::create(&app.db, "acme", "tools", "", hub_api::Visibility::Public).await.unwrap();
406 +
    let solo = db::repos::create(&app.db, "andy", "solo", "", hub_api::Visibility::Public).await.unwrap();
407 +
    assert_eq!(db::proposals::requirements(&app.db, tools.id).await.unwrap().required_approvals, 1);
408 +
    assert_eq!(db::proposals::requirements(&app.db, solo.id).await.unwrap().required_approvals, 0);
409 +
403 410
    // Organisation webhooks: add, ping, deliveries; only for this organisation.
404 411
    let page = get(app, "/orgs/acme/settings", Some(&andy)).await;
405 412
    assert!(page.body.contains("id=\"webhooks\"") && page.body.contains("href=\"#webhooks\""));
…
64 unchanged lines
modified docs/spec.md +1 −1
…
344 unchanged lines
345 345
346 346
A review is `comment`, `request changes`, or `approve`. An approval MUST be backed by a **seal attestation** (spec §14.4, `kind = seal`) over the proposal's change hashes, signed with a key linked to the reviewer's account. Private keys never enter the browser, so the reviewer signs from a client: `oxy seal --proposal <url>` or `oxy seal <hashes>` (§18, a new command; the seal object already exists in the model) signs and pushes the seal; the hub matches the seal to the proposal and records the approval. The page shows the command ready to copy. An approval not yet backed by a seal is shown as "approval pending signature" and does not count towards requirements.
347 347
348 -
Landing requirements per repository (settings): N approvals, approvals from code owners (`.oxyowners` in the target state, GitHub `CODEOWNERS` syntax), required checks green on the projected state, no conflicts (or conflicts allowed, matching the policy rule's `allow_conflicts`), and no outstanding "request changes".
348 +
Landing requirements per repository (settings): N approvals, approvals from code owners (`.oxyowners` in the target state, GitHub `CODEOWNERS` syntax), required checks green on the projected state, no conflicts (or conflicts allowed, matching the policy rule's `allow_conflicts`), and no outstanding "request changes". A repository under an organisation starts with one approval required, a personal one with none; an admin changes either in Settings. When none is required and nobody has approved, the proposal page says so beside Land, and Land asks for confirmation.
349 349
350 350
### 7.5 Stacked proposals
351 351
…
216 unchanged lines