From 0424f924a7a667e07321338e5e36077cf5e76f4c Mon Sep 17 00:00:00 2001 From: Zoey Rose Date: Sat, 17 Oct 2026 18:10:17 -0500 Subject: [PATCH 1/6] chore: drop editor and renderer after transfer to Seraphina Studios The editor and renderer repositories were transferred on 2026-10-17 to seraphinastudios/tilewright-editor and seraphinastudios/tilewright-renderer. Remove them from every desired-state inventory, validator identity map and test fixture, and record the transfer in the README. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 16 +++-- CONTRIBUTING.md | 8 +-- README.md | 25 +++++++- bin/publish | 4 +- bin/validate | 16 ++--- bin/verify-manual-settings | 4 +- community-health/profile/README.md | 1 - config/immutable-releases.json | 4 -- config/manual-settings.json | 83 +------------------------ config/repositories.json | 12 ---- config/repository-properties.json | 20 ------ config/security-merge-protection.json | 4 -- tests/audit.sh | 6 +- tests/publish-maintenance-branch.sh | 13 ++-- tests/validate-immutable-releases.sh | 3 +- tests/validate-manual-package-access.sh | 2 +- tests/verify-manual-settings.sh | 12 ++-- 17 files changed, 59 insertions(+), 174 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 854008a..5150f6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -117,7 +117,7 @@ the source association, verify anonymous digest access, and remove obsolete repository grants without altering foreign entries. The private linux-build package keeps its exact package/source identities; - record client/editor-only read grants separately from effective workflow pulls. + record client-only read grants separately from effective workflow pulls. Metadata verifies identity/source/visibility, not grants or effective pulls. Preserve all foreign grants and prove each owning authenticated workflow pulls its pinned manifest before claiming effective access; never widen visibility. @@ -126,8 +126,8 @@ branch policies and reviewer sets, and variable and secret names only. Never record credential values. Provision them only after the owning workflow is merged and reviewed, and do not imply that `bin/publish` applies them. -- Crates.io Trusted Publishing is a value-free desired contract for protocol, - renderer, and content-toolkit. Bind exact stable repository IDs, +- Crates.io Trusted Publishing is a value-free desired contract for protocol + and content-toolkit. Bind exact stable repository IDs, `publish-crate.yml`, protected `refs/heads/main`, `crates-io-release`, and the official auth action with a full immutable workflow SHA; allow the reviewed `rust-lang/crates-io-auth-action@*` pattern for Dependabot updates without a @@ -151,8 +151,8 @@ in the producer helper. Before registry PUT, atomically reserve the global crate/version Git-reference coordinate; existing/ambiguous reservations block absent-version retries and must never be automatically deleted or reset. - First owner-local API-token publication is limited to the fourteen declared - renderer/toolkit libraries; exclude CLIs, revoke bootstrap credentials, and + First owner-local API-token publication is limited to the seven declared + toolkit libraries; exclude CLIs, revoke bootstrap credentials, and use OIDC only afterward. Protocol 0.2 retains its approved manual path. Before any broad publisher apply mutation, qualify newly added live immutable producer selections through fresh scoped source/environment verification; @@ -160,7 +160,7 @@ qualification is separate from later immutable-release/runtime proof, and does not require a pre-existing immutable release. - Keep the crate dependency App separate from unrelated Apps. Select only - client/editor, exactly contents/PR write and metadata read, no events, + client, exactly contents/PR write and metadata read, no events, bypass or auto-merge; inventory names only for repository secret `CRATE_DEPENDENCY_UPDATE_APP_PRIVATE_KEY` and variable `CRATE_DEPENDENCY_UPDATE_APP_ID`. Record observed installation and name-only @@ -228,6 +228,10 @@ `release_policy: semantic-release` are released by semantic-release; active services without a release contract remain pull-request governed with `release_policy: none` and are omitted from `release_tags`. +- Remove a repository transferred out of the organization from every + desired-state inventory, validator identity map and test fixture in one + change, and record its transfer in `README.md#transferred-repositories`. + Historical evidence sections stay as written. - Update this `AGENTS.md` in the same change when major rework alters governance ownership, policy scope, publisher behavior, required checks, or validation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9dc802..1776b5a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -222,8 +222,8 @@ Dependabot updates without a central version lock. Automatic publication follows source/version merges with zero environment reviewers/wait and no registry secrets. Keep pending onboarding distinct from observed source/environment and owner-observed external registration; verify live protected main and exact -refs. First owner-local API-token bootstrap is restricted to the fourteen -listed renderer/toolkit libraries and ends with revocation; subsequent releases +refs. First owner-local API-token bootstrap is restricted to the seven +listed toolkit libraries and ends with revocation; subsequent releases use OIDC only. Protocol 0.2 retains its approved manual path. Crate publication must qualify both exact-main workflows, isolate inert source signing from build/semantic-release OIDC, and keep automatic verify permissions @@ -241,7 +241,7 @@ newly added live producer selections before its first mutation, with observed inventory and fresh scoped source/environment checks. Initial independently qualified draft-first code is sufficient for setup; actual immutable public release and registry proof remain later runtime obligations. Dependency-update -App setup is client/editor only, exact contents/PR write and metadata read, +App setup is client only, exact contents/PR write and metadata read, repository credential names only, registry readiness before owned PR creation, and ordinary review/checks without auto-merge. Distinguish observed installation and credential-name provisioning from pending reviewed workflow activation and @@ -252,7 +252,7 @@ creation; that internal deadline does not assert upstream expiry. Verify a replacement key in the owning writing workflow before revoking the old key. Keep App-authenticated selected-set readback or owner UI evidence, and do not infer the webhook switch from an empty event set. -Private linux-build consumption needs separate client/editor-only read grants +Private linux-build consumption needs separate client-only read grants through owner UI. Keep exact private package/source identities and pending grant state until observed; metadata and owner pulls do not prove an effective owning workflow pull. Preserve foreign grants and visibility. Changes require negative schema diff --git a/README.md b/README.md index aead68f..9ca3d9b 100644 --- a/README.md +++ b/README.md @@ -1565,7 +1565,7 @@ and unrelated App credentials. ## Automatic crate Trusted Publishing `config/manual-settings.json` records the desired, value-free contracts below. -All three source contracts are `observed-reviewed-workflow`, and their +The remaining source contracts are `observed-reviewed-workflow`, and their environments are `observed`: supported live readback qualified both workflow files at the same protected-main revision, exact repository identities, and main-only environments with no reviewers, wait, administrator bypass, secrets, @@ -1584,7 +1584,6 @@ perform external registration or publication. | Repository | Stable repository ID | Published libraries | | --- | --- | --- | | `atrinik/protocol` | `1327106950` | `atrinik-protocol` | -| `atrinik/renderer` | `1327106998` | `atrinik-render-api`, `atrinik-render-resources`, `atrinik-render-sdl3`, `atrinik-render-testkit`, `atrinik-render-ui`, `atrinik-render-wgpu`, `atrinik-scene` | | `atrinik/content-toolkit` | `1327107043` | `atrinik-artifact`, `atrinik-catalog`, `atrinik-diagnostics`, `atrinik-schema`, `atrinik-source`, `atrinik-testkit`, `atrinik-transaction` | Every Trusted Publisher binds the exact repository, `publish-crate.yml`, and @@ -1779,7 +1778,10 @@ observed successful-owned-PR record refers to editor acceptance; the client no-op does not prove that its writer job ran. The live App is `atrinik-crate-dependency-update`, App ID `5276152`, installation ID `170332148`, with **selected repositories** exactly `atrinik/client` (ID -`1327106844`) and `atrinik/editor` (ID `1327106902`). App-authenticated supported +`1327106844`). The editor repository left the organization on 2026-10-17 (see +[Transferred repositories](#transferred-repositories)); the successful-owned-PR +observation above was made there before the transfer, and the editor's copy of +the App credential and variable were deleted from the transferred repository. App-authenticated supported API readback confirmed that complete selected set, exact contents write/pull requests write/metadata read permissions, no subscribed events, and the external homepage `https://www.atrinik.org`. Preserve every other App and installation, @@ -1833,3 +1835,20 @@ App permissions with a disposable/owned PR and current ordinary checks, without exposing credentials. On retirement, stop these workflows and revoke the App/key and only the two named repository credential slots; preserve unrelated installations and secrets. + +## Transferred repositories + +On 2026-10-17 two repositories left the organization by GitHub transfer: + +| Former repository | Stable repository ID | New location | +| --- | --- | --- | +| `atrinik/editor` | `1327106902` | `seraphinastudios/tilewright-editor` | +| `atrinik/renderer` | `1327106998` | `seraphinastudios/tilewright-renderer` | + +They are no longer governed here: they are removed from the repository, +required-CI, release-tag, immutable-release, custom-property, CodeQL, Codecov, +Trusted Publishing, package-access and crate dependency App inventories. The +organization rulesets that named them stop matching anything; an authorized +`bin/publish --apply` removes the obsolete `03 - Required CI` rulesets. Atrinik +consumes both projects as external MIT dependencies. Historical evidence in the +sections above that names them is preserved unchanged. diff --git a/bin/publish b/bin/publish index cdc307f..36ae768 100755 --- a/bin/publish +++ b/bin/publish @@ -1805,7 +1805,7 @@ preflight_new_crate_immutable_selection() { # the scoped verifier rather than silently skipping qualification. "${root}/bin/verify-manual-settings" --crate-publication-setup "${organization}/${repository}" done < <(jq -r --argjson additions "${additions}" ' - .repositories[] | select(.name | IN("protocol", "renderer", "content-toolkit")) | + .repositories[] | select(.name | IN("protocol", "content-toolkit")) | select(.id as $id | $additions | index($id) != null) | .name ' "${immutable_releases_config}") if [[ $(current_immutable_release_payload) != "${snapshot}" ]]; then @@ -1973,5 +1973,5 @@ while IFS= read -r package_record; do echo "MANUAL ${repository} read-only package Actions access to ${package_name}: ${grant_state}; publisher never provisions grants" done < <(jq -c '.github_packages_actions_access[]' "${root}/config/manual-settings.json") app_state=$(jq -r '.crate_dependency_update_app | "identity=\(.status), credential_names=\(.credential_state), workflow=\(.workflow_activation.state)"' "${root}/config/manual-settings.json") -echo "MANUAL atrinik-crate-dependency-update ${app_state} selected client/editor App and credential slots; publisher does not apply" +echo "MANUAL atrinik-crate-dependency-update ${app_state} selected client App and credential slots; publisher does not apply" echo "Manual confirmation required for config/manual-settings.json." diff --git a/bin/validate b/bin/validate index faeb445..64be716 100755 --- a/bin/validate +++ b/bin/validate @@ -336,7 +336,7 @@ jq -e \ else true end) and (.id | type == "number" and floor == . and . > 0) and (.name | type == "string" and length > 0) and - ({classic: 1327289971, protocol: 1327106950, renderer: 1327106998, + ({classic: 1327289971, protocol: 1327106950, "content-toolkit": 1327107043, "metaserver-worker": 1324297032}[.name] as $id | $id == null or .id == $id) and (.name as $repository | @@ -891,8 +891,7 @@ jq -e --arg today "${validation_today}" ' .package_name == "linux-build" and .package_id == 14205615 and .preserve_visibility == "private" and .source_repository == "atrinik/devcontainer" and .source_repository_id == 1322185908 and - ((.actions_repository == "atrinik/client" and .actions_repository_id == 1327106844) or - (.actions_repository == "atrinik/editor" and .actions_repository_id == 1327106902)) and + (.actions_repository == "atrinik/client" and .actions_repository_id == 1327106844) and (if .grant_state == "pending-owner-ui-read-grant" then .observed_on == null and .evidence_location == null elif .grant_state == "owner-observed-read-grant" then @@ -924,16 +923,13 @@ jq -e \ else false end); { protocol: {id: 1327106950, crates: ["atrinik-protocol"], bootstrap: []}, - renderer: {id: 1327106998, crates: ["atrinik-render-api", "atrinik-render-resources", - "atrinik-render-sdl3", "atrinik-render-testkit", "atrinik-render-ui", - "atrinik-render-wgpu", "atrinik-scene"], bootstrap: true}, "content-toolkit": {id: 1327107043, crates: ["atrinik-artifact", "atrinik-catalog", "atrinik-diagnostics", "atrinik-schema", "atrinik-source", "atrinik-testkit", "atrinik-transaction"], bootstrap: true} } as $inventory | .github_actions_environments as $environments | .crates_io_trusted_publishing as $publishers | - ($publishers | type == "array" and length <= 3) and + ($publishers | type == "array" and length <= 2) and ([$publishers[].repository] | length == (unique | length)) and all($publishers[]; (.repository | split("/")[1]) as $name | @@ -998,13 +994,11 @@ jq -e --arg today "${validation_today}" ' $app.permissions == {contents: "write", metadata: "read", pull_requests: "write"} and $app.events == [] and $app.owner == "Atrinik organization owners" and $app.rotation_owner == "Atrinik organization owners" and $app.rotation_cadence_days == 90 and - $app.repositories == [{repository: "atrinik/client", repository_id: 1327106844}, - {repository: "atrinik/editor", repository_id: 1327106902}] and + $app.repositories == [{repository: "atrinik/client", repository_id: 1327106844}] and $app.secret_name == "CRATE_DEPENDENCY_UPDATE_APP_PRIVATE_KEY" and $app.secret_scope == "repository" and $app.variable_name == "CRATE_DEPENDENCY_UPDATE_APP_ID" and $app.variable_scope == "repository" and $app.workflow_filename == "released-dependencies.yml" and - $app.consumers == ["atrinik/client/.github/workflows/released-dependencies.yml", - "atrinik/editor/.github/workflows/released-dependencies.yml"] and + $app.consumers == ["atrinik/client/.github/workflows/released-dependencies.yml"] and $app.workflow_triggers == ["schedule-hourly", "workflow_dispatch"] and $app.readiness == "exact-released-crates-and-checksums-before-pr" and $app.pull_request_policy == "owned-branch-and-pr-only-no-auto-merge" and diff --git a/bin/verify-manual-settings b/bin/verify-manual-settings index 5739335..4143129 100755 --- a/bin/verify-manual-settings +++ b/bin/verify-manual-settings @@ -932,9 +932,9 @@ while IFS= read -r selected_repository; do fi done < <(jq -c '.repositories[]' <<<"${app_record}") if [[ $(jq -r '.status' <<<"${app_record}") == planned ]]; then - echo "PENDING ${app_slug} selected client/editor App installation and repository credential slots; no provisioning applied" + echo "PENDING ${app_slug} selected client App installation and repository credential slots; no provisioning applied" else - echo "MANUAL ${app_slug} selected repositories require current owner UI or App-authenticated API proof: atrinik/client and atrinik/editor only" + echo "MANUAL ${app_slug} selected repositories require current owner UI or App-authenticated API proof: atrinik/client only" activation_status=PENDING if [[ $(jq -r '.workflow_activation.state' <<<"${app_record}") == observed-successful-owned-pr ]]; then activation_status=OWNER-OBSERVED diff --git a/community-health/profile/README.md b/community-health/profile/README.md index 75694c4..4ae16bf 100644 --- a/community-health/profile/README.md +++ b/community-health/profile/README.md @@ -44,7 +44,6 @@ repositories or by itself approve a GPL dependency, link, or bundle. | [client](https://github.com/atrinik/client) | Rust and SDL3 foundation for the next-generation MIT client | | [server](https://github.com/atrinik/server) | Go foundation for the next-generation MIT authoritative server | | [protocol](https://github.com/atrinik/protocol) | Game Protocol 1 schemas, QUIC specifications, and Go/Rust bindings | -| [renderer](https://github.com/atrinik/renderer) | Shared Rust GPU renderer for clients, editors, and tools | Atrinik's creators shape the game world with whatever tools serve the work, including AI tools; every asset keeps its exact credit and license. diff --git a/config/immutable-releases.json b/config/immutable-releases.json index 21e6163..094e958 100644 --- a/config/immutable-releases.json +++ b/config/immutable-releases.json @@ -20,10 +20,6 @@ { "id": 1327106950, "name": "protocol" - }, - { - "id": 1327106998, - "name": "renderer" } ] } diff --git a/config/manual-settings.json b/config/manual-settings.json index 55b045a..abdf6c3 100644 --- a/config/manual-settings.json +++ b/config/manual-settings.json @@ -39,9 +39,7 @@ "classic", "client", "content-toolkit", - "editor", "protocol", - "renderer", "server" ], "crates_io_trusted_publishing": [ @@ -90,65 +88,6 @@ "workflow_trigger": "workflow_dispatch", "release_approval": "reviewed-source-and-version-merge" }, - { - "authentication_action": "rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18", - "bootstrap_crates": [ - "atrinik-render-api", - "atrinik-render-resources", - "atrinik-render-sdl3", - "atrinik-render-testkit", - "atrinik-render-ui", - "atrinik-render-wgpu", - "atrinik-scene" - ], - "bootstrap_policy": "owner-local-api-token-once-then-oidc-only", - "crates": [ - "atrinik-render-api", - "atrinik-render-resources", - "atrinik-render-sdl3", - "atrinik-render-testkit", - "atrinik-render-ui", - "atrinik-render-wgpu", - "atrinik-scene" - ], - "environment": { - "can_admins_bypass": false, - "deployment_branch_policy": { - "custom_branch_policies": true, - "patterns": [ - { - "name": "main", - "type": "branch" - } - ], - "protected_branches": false - }, - "name": "crates-io-release", - "prevent_self_review": false, - "required_reviewers": [], - "secret_names": [], - "variable_names": [], - "wait_timer_minutes": 0 - }, - "environment_state": "observed", - "publisher_registration": { - "state": "owner-observed-registration", - "observed_on": "2026-10-12", - "evidence_location": "README.md#initial-library-onboarding-evidence" - }, - "provider": "github", - "repository": "atrinik/renderer", - "repository_id": 1327106998, - "runbook": "README.md#automatic-crate-trusted-publishing", - "source_evidence_policy": "github-attested-protected-main-manifest", - "source_state": "observed-reviewed-workflow", - "source_workflow_filename": "release.yml", - "upload_intent_policy": "create-only-global-git-ref-before-upload", - "workflow_filename": "publish-crate.yml", - "workflow_ref": "refs/heads/main", - "workflow_trigger": "workflow_dispatch", - "release_approval": "reviewed-source-and-version-merge" - }, { "authentication_action": "rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18", "bootstrap_crates": [ @@ -623,21 +562,6 @@ "role": "read", "source_repository": "atrinik/devcontainer", "source_repository_id": 1322185908 - }, - { - "actions_repository": "atrinik/editor", - "actions_repository_id": 1327106902, - "evidence_location": "README.md#private-linux-updater-build-image", - "grant_state": "owner-observed-read-grant", - "observed_on": "2026-10-11", - "package_id": 14205615, - "package_name": "linux-build", - "package_owner": "atrinik", - "package_type": "container", - "preserve_visibility": "private", - "role": "read", - "source_repository": "atrinik/devcontainer", - "source_repository_id": 1322185908 } ], "require_two_factor_authentication": true, @@ -647,8 +571,7 @@ "app_id": 5276152, "app_slug": "atrinik-crate-dependency-update", "consumers": [ - "atrinik/client/.github/workflows/released-dependencies.yml", - "atrinik/editor/.github/workflows/released-dependencies.yml" + "atrinik/client/.github/workflows/released-dependencies.yml" ], "key_created_on": "2026-10-11", "last_verified_on": "2026-10-11", @@ -670,10 +593,6 @@ { "repository": "atrinik/client", "repository_id": 1327106844 - }, - { - "repository": "atrinik/editor", - "repository_id": 1327106902 } ], "secret_name": "CRATE_DEPENDENCY_UPDATE_APP_PRIVATE_KEY", diff --git a/config/repositories.json b/config/repositories.json index fc006e3..bdf33f8 100644 --- a/config/repositories.json +++ b/config/repositories.json @@ -8,14 +8,12 @@ "content-toolkit", "deploy-control", "devcontainer", - "editor", "github-settings", "metaserver-worker", "observatory", "pathfinder", "playtester", "protocol", - "renderer", "resources", "server", "service-updater", @@ -42,18 +40,10 @@ "Content toolkit validation", "Conventional PR title" ], - "editor": [ - "Editor validation", - "Conventional PR title" - ], "protocol": [ "Protocol validation", "Conventional PR title" ], - "renderer": [ - "Renderer validation", - "Conventional PR title" - ], "server": [ "Server validation", "Conventional PR title" @@ -123,12 +113,10 @@ "content", "content-toolkit", "devcontainer", - "editor", "github-settings", "metaserver-worker", "playtester", "protocol", - "renderer", "resources", "server", "service-updater", diff --git a/config/repository-properties.json b/config/repository-properties.json index 89f48c3..19cd692 100644 --- a/config/repository-properties.json +++ b/config/repository-properties.json @@ -166,16 +166,6 @@ "lifecycle": "active", "release_policy": "semantic-release" }, - "editor": { - "component_role": [ - "editor" - ], - "provider_set": [ - "replacement" - ], - "lifecycle": "active", - "release_policy": "semantic-release" - }, "github-settings": { "component_role": [ "governance" @@ -302,16 +292,6 @@ "lifecycle": "active", "release_policy": "semantic-release" }, - "renderer": { - "component_role": [ - "renderer" - ], - "provider_set": [ - "replacement" - ], - "lifecycle": "active", - "release_policy": "semantic-release" - }, "resources": { "component_role": [ "assets" diff --git a/config/security-merge-protection.json b/config/security-merge-protection.json index dd2e34e..1ddf4c0 100644 --- a/config/security-merge-protection.json +++ b/config/security-merge-protection.json @@ -9,14 +9,12 @@ "content-toolkit", "deploy-control", "devcontainer", - "editor", "github-settings", "metaserver-worker", "observatory", "pathfinder", "playtester", "protocol", - "renderer", "resources", "server", "service-updater", @@ -39,14 +37,12 @@ "content-toolkit": "pending", "deploy-control": "pending", "devcontainer": "pending", - "editor": "pending", "github-settings": "pending", "metaserver-worker": "pending", "observatory": "pending", "pathfinder": "pending", "playtester": "pending", "protocol": "pending", - "renderer": "pending", "resources": "pending", "server": "pending", "service-updater": "pending", diff --git a/tests/audit.sh b/tests/audit.sh index a4c1278..c17b225 100755 --- a/tests/audit.sh +++ b/tests/audit.sh @@ -120,15 +120,15 @@ run_audit() { } output=$(run_audit production-advisory full) -grep -Fxq 'Governance audit passed: 24 active repositories checked.' <<<"${output}" +grep -Fxq 'Governance audit passed: 22 active repositories checked.' <<<"${output}" if grep -Fq 'Dependency review merge protection' <<<"${output}"; then echo 'advisory production must not require dependency merge protection' >&2 exit 1 fi output=$(run_audit pass full) -grep -Fxq 'Governance audit passed: 24 active repositories checked.' <<<"${output}" +grep -Fxq 'Governance audit passed: 22 active repositories checked.' <<<"${output}" output=$(run_audit temporary full) -grep -Fxq 'Governance audit passed: 24 active repositories checked.' <<<"${output}" +grep -Fxq 'Governance audit passed: 22 active repositories checked.' <<<"${output}" output=$(run_audit legacy-security) grep -Fq 'Governance audit passed:' <<<"${output}" output=$(run_audit repository-rulesets) diff --git a/tests/publish-maintenance-branch.sh b/tests/publish-maintenance-branch.sh index a9755a6..9979150 100755 --- a/tests/publish-maintenance-branch.sh +++ b/tests/publish-maintenance-branch.sh @@ -339,7 +339,6 @@ repos/atrinik/*/actions/permissions/workflow\|*) if [[ ${GH_CRATE_IMMUTABLE_INVENTORY:-false} == true ]]; then printf 'metaserver-worker\t1324297032\tfalse\tpublic\tfalse\tfalse\ttrue\ttrue\ttrue\n' printf 'protocol\t%s\tfalse\tpublic\tfalse\tfalse\ttrue\ttrue\ttrue\n' "${GH_PROTOCOL_REPOSITORY_ID:-1327106950}" - printf 'renderer\t1327106998\tfalse\tpublic\tfalse\tfalse\ttrue\ttrue\ttrue\n' printf 'content-toolkit\t1327107043\tfalse\tpublic\tfalse\tfalse\ttrue\ttrue\ttrue\n' fi printf 'classic-ghsa-8533-3vg8-r287\t999\ttrue\tprivate\tfalse\tfalse\tfalse\tfalse\tfalse\n' @@ -1330,9 +1329,7 @@ while IFS=$'\t' read -r repository validation_context; do done <<'EOF' client Client validation content-toolkit Content toolkit validation -editor Editor validation protocol Protocol validation -renderer Renderer validation server Server validation website Website validation EOF @@ -1481,9 +1478,7 @@ while IFS=$'\t' read -r repository validation_context; do done <<'EOF' client Client validation content-toolkit Content toolkit validation -editor Editor validation protocol Protocol validation -renderer Renderer validation server Server validation website Website validation EOF @@ -1712,20 +1707,20 @@ GH_API_LOG=${crate_log} GH_IMMUTABLE_STATE=${crate_state} \ GH_CRATE_IMMUTABLE_INVENTORY=true GH_SECURITY_SCENARIO=converged \ GH_CRATE_FIXTURE_ROOT=${source_root} PATH="${temporary}/bin:${PATH}" ATRINIK_POLICY_SCOPE=organization \ "${source_root}/bin/publish" --apply >"${temporary}/crate-immutable-apply.out" -jq -e '. == {enforced_repositories:"selected",selected_repository_ids:[1324297032,1327106950,1327106998,1327107043,1327289971]}' "${crate_state}" >/dev/null +jq -e '. == {enforced_repositories:"selected",selected_repository_ids:[1324297032,1327106950,1327107043,1327289971]}' "${crate_state}" >/dev/null jq -s -e ' . as $calls | ([ $calls | to_entries[] | select(.value.method != "GET") | .key ] | first) as $first_mutation | all( - ["protocol", "renderer", "content-toolkit"][] as $repository | + ["protocol", "content-toolkit"][] as $repository | ([ $calls | to_entries[] | select(.value.endpoint == ("repos/atrinik/" + $repository + "/git/ref/heads/main")) | .key ] | last) < $first_mutation ) and all(.[]; (.endpoint | test("/(releases|attestations|actions/runs)(/|$)") | not)) ' "${crate_log}" >/dev/null -for repository in classic protocol renderer content-toolkit; do +for repository in classic protocol content-toolkit; do jq -se --arg endpoint "repos/atrinik/${repository}/immutable-releases" \ 'any(.[]; .method == "GET" and .endpoint == $endpoint)' "${crate_log}" >/dev/null done @@ -1840,7 +1835,7 @@ assert_setup_failed_before_mutation "${snapshot_drift_log}" for mode in selected all; do mode_log=${temporary}/crate-${mode}-already-selected.jsonl if [[ ${mode} == selected ]]; then - printf '%s\n' '{"enforced_repositories":"selected","selected_repository_ids":[1324297032,1327106950,1327106998,1327107043,1327289971]}' >"${crate_state}" + printf '%s\n' '{"enforced_repositories":"selected","selected_repository_ids":[1324297032,1327106950,1327107043,1327289971]}' >"${crate_state}" else printf '%s\n' '{"enforced_repositories":"all"}' >"${crate_state}" fi diff --git a/tests/validate-immutable-releases.sh b/tests/validate-immutable-releases.sh index 7df9a57..b705e10 100755 --- a/tests/validate-immutable-releases.sh +++ b/tests/validate-immutable-releases.sh @@ -37,14 +37,13 @@ expect_invalid() { jq -e '.enforced_repositories == "selected" and .repositories == [ {id:1327289971,name:"classic"},{id:1327107043,name:"content-toolkit"}, {id:1324297032,name:"metaserver-worker",activation_variable:{name:"ATRINIK_RELEASE_IMMUTABILITY_ACTIVE",scope:"repository"}}, - {id:1327106950,name:"protocol"},{id:1327106998,name:"renderer"} + {id:1327106950,name:"protocol"} ]' "${root}/config/immutable-releases.json" >/dev/null expect_invalid metaserver-identity '.repositories |= map(if .name == "metaserver-worker" then .id = 1 else . end)' expect_invalid metaserver-activation-scope '.repositories |= map(if .name == "metaserver-worker" then .activation_variable.scope = "environment" else . end)' expect_invalid metaserver-activation-name '.repositories |= map(if .name == "metaserver-worker" then .activation_variable.name = "OTHER_GATE" else . end)' expect_invalid foreign-activation-variable '.repositories[0].activation_variable = {name:"ATRINIK_RELEASE_IMMUTABILITY_ACTIVE",scope:"repository"}' expect_invalid protocol-identity '.repositories |= map(if .name == "protocol" then .id = 1 else . end)' -expect_invalid renderer-identity '.repositories |= map(if .name == "renderer" then .id = 1 else . end)' expect_invalid toolkit-identity '.repositories |= map(if .name == "content-toolkit" then .id = 1 else . end)' expect_invalid mode-all '.enforced_repositories = "all"' diff --git a/tests/validate-manual-package-access.sh b/tests/validate-manual-package-access.sh index 56c7940..fe55a22 100755 --- a/tests/validate-manual-package-access.sh +++ b/tests/validate-manual-package-access.sh @@ -33,7 +33,7 @@ rewrite_manual_settings() { jq -e ' [.github_packages_actions_access[] | select(.package_name != "linux-build")] == [] and - [.github_packages_actions_access[].actions_repository] == ["atrinik/client", "atrinik/editor"] and + [.github_packages_actions_access[].actions_repository] == ["atrinik/client"] and all(.github_packages_actions_access[]; .package_id == 14205615 and .preserve_visibility == "private" and .source_repository == "atrinik/devcontainer" and .source_repository_id == 1322185908 and .role == "read" and .grant_state == "owner-observed-read-grant" and diff --git a/tests/verify-manual-settings.sh b/tests/verify-manual-settings.sh index 5e736cb..61000f3 100755 --- a/tests/verify-manual-settings.sh +++ b/tests/verify-manual-settings.sh @@ -85,13 +85,11 @@ repos/atrinik/protocol) jq -n --argjson id "${repository_id}" \ '{id: $id, full_name: "atrinik/protocol", archived: false, default_branch: "main"}' ;; -repos/atrinik/renderer | repos/atrinik/content-toolkit | repos/atrinik/client | repos/atrinik/editor) +repos/atrinik/content-toolkit | repos/atrinik/client) name=${endpoint##*/} case ${name} in - renderer) id=1327106998 ;; content-toolkit) id=1327107043 ;; client) id=1327106844 ;; - editor) id=1327106902 ;; esac jq -n --arg name "${name}" --argjson id "${id}" \ '{id:$id, full_name:("atrinik/"+$name), archived:false, default_branch:"main"}' @@ -176,14 +174,14 @@ repos/atrinik/protocol/environments/crates-io-release) "repos/atrinik/protocol/environments/crates-io-release/variables?per_page=100&page=1") jq -n '{total_count:0,variables:[]}' ;; -"repos/atrinik/client/actions/secrets?per_page=100&page=1" | "repos/atrinik/editor/actions/secrets?per_page=100&page=1") +"repos/atrinik/client/actions/secrets?per_page=100&page=1") if [[ ${FAKE_GH_SCENARIO} == updater-missing-secret ]]; then jq -n '{total_count:0,secrets:[]}' else jq -n '{total_count:1,secrets:[{name:"CRATE_DEPENDENCY_UPDATE_APP_PRIVATE_KEY",updated_at:"2026-08-31T00:00:00Z"}]}' fi ;; -"repos/atrinik/client/actions/variables?per_page=100&page=1" | "repos/atrinik/editor/actions/variables?per_page=100&page=1") +"repos/atrinik/client/actions/variables?per_page=100&page=1") if [[ ${FAKE_GH_SCENARIO} == updater-missing-variable ]]; then jq -n '{total_count:0,variables:[]}' else @@ -837,7 +835,6 @@ if grep -Fq 'metaserver-worker/environments/development/deployment-branch-polici exit 1 fi grep -Fq 'OWNER-OBSERVED atrinik/client read-only Actions access to private atrinik/linux-build' <<<"${output}" -grep -Fq 'OWNER-OBSERVED atrinik/editor read-only Actions access to private atrinik/linux-build' <<<"${output}" grep -Fq 'PENDING atrinik/protocol crates.io atrinik-protocol via publish-crate.yml / crates-io-release' <<<"${output}" if grep -Eq 'protocol/environments|crates.io|/trusted-publishers' "${temporary}/gh.log"; then echo "error: planned crate contract inspected or mutated unprovisioned state" >&2 @@ -905,8 +902,7 @@ jq '.crate_dependency_update_app |= (.status="owner-observed" | .app_id=5276152 "${fixture_root}/config/manual-settings.json" >"${updater_root}/config/manual-settings.json" updater_output=$(run_verify updater-present "${updater_root}") grep -Fq 'KEEP atrinik/client crate dependency App credential name metadata' <<<"${updater_output}" -grep -Fq 'KEEP atrinik/editor crate dependency App credential name metadata' <<<"${updater_output}" -grep -Fq 'selected repositories require current owner UI or App-authenticated API proof: atrinik/client and atrinik/editor only' <<<"${updater_output}" +grep -Fq 'selected repositories require current owner UI or App-authenticated API proof: atrinik/client only' <<<"${updater_output}" for scenario in updater-missing updater-permissions updater-selection updater-missing-secret updater-missing-variable; do if run_verify "${scenario}" "${updater_root}" >"${temporary}/updater-${scenario}.out" \ 2>"${temporary}/updater-${scenario}.err"; then From c75c5cdf030679d526386177f50bda53abb511c3 Mon Sep 17 00:00:00 2001 From: Zoey Rose Date: Sat, 17 Oct 2026 20:01:40 -0500 Subject: [PATCH 2/6] fix: reset App activation for the client and drop renderer fixtures The crate dependency update App's only remaining consumer is the client, whose recorded run skipped the PR-writing job. The owned-PR proof came from the transferred editor, so workflow_activation goes back to pending-reviewed-workflows until a client-owned PR is observed. verify-manual-settings now reports the activation as PENDING. The README keeps the editor observation as history, outside the client's evidence. The maintenance-branch publication test no longer serves renderer identities, workflows or API routes, so a query against the transferred repository fails as an unexpected endpoint. Co-Authored-By: Claude Opus 5.5 --- README.md | 36 +++++++++++++---------------- config/manual-settings.json | 6 ++--- tests/publish-maintenance-branch.sh | 19 +++++---------- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 9ca3d9b..f90c29d 100644 --- a/README.md +++ b/README.md @@ -1755,33 +1755,29 @@ future reviewed source/version merges use the automatic path after observed onboarding. Protocol registration is owner-observed with the successful manual 0.2 run; its automatic source/environment setup is observed. Renderer/toolkit initial publication and owner registration are complete, with the known-coordinate OIDC -proofs above. The dependency App has observed successful owned-PR activation -through the editor workflow and its signed bot PR with ordinary checks. Client's -no-op preparation remains a separate observation; it did not exercise its writer. +proofs above. The dependency App's workflow activation is pending: its only +remaining consumer, the client, has not yet run its PR-writing job. ## Crate dependency update App -`crate_dependency_update_app` records owner-observed App setup on 2026-10-11 -and `workflow_activation: observed-successful-owned-pr` on 2026-10-12. Both -reviewed updater workflows have merged. -[Editor run 38193476856](https://github.com/atrinik/editor/actions/runs/38193476856) -passed credential-free preparation, independent lock/consumer/aggregate -verification, and candidate/metadata sealing before its isolated App job created -[PR 36](https://github.com/atrinik/editor/pull/36). The App bot authored the -verified signed commit; all twelve ordinary CI checks passed, independent -review accepted the candidate, and the maintainer merged the PR through the -ordinary process. The App did not approve or merge its own PR. - +`crate_dependency_update_app` records owner-observed App setup on 2026-10-11. +Its `workflow_activation` is `pending-reviewed-workflows`: the client's reviewed +updater workflow has merged, but no client-owned PR has been observed yet. Client [run 38192029425](https://github.com/atrinik/client/actions/runs/38192029425) -completed preparation as a no-op and skipped the isolated PR-writing job. The -observed successful-owned-PR record refers to editor acceptance; the client -no-op does not prove that its writer job ran. +completed preparation as a no-op and skipped the isolated PR-writing job, so it +does not prove that the writer works. Record `observed-successful-owned-pr` only +after a client run creates its own PR that passes ordinary checks and review. + +Before the editor left the organization, its run 38193476856 created the App's +first owned PR (editor PR 36) on 2026-10-12. The App bot authored the verified +signed commit, all ordinary checks passed, independent review accepted it, and +the maintainer merged it through the ordinary process. That observation belongs +to the transferred repository and does not count as client activation. The live App is `atrinik-crate-dependency-update`, App ID `5276152`, installation ID `170332148`, with **selected repositories** exactly `atrinik/client` (ID `1327106844`). The editor repository left the organization on 2026-10-17 (see -[Transferred repositories](#transferred-repositories)); the successful-owned-PR -observation above was made there before the transfer, and the editor's copy of -the App credential and variable were deleted from the transferred repository. App-authenticated supported +[Transferred repositories](#transferred-repositories)); the editor's copy of the +App credential and variable were deleted from the transferred repository. App-authenticated supported API readback confirmed that complete selected set, exact contents write/pull requests write/metadata read permissions, no subscribed events, and the external homepage `https://www.atrinik.org`. Preserve every other App and installation, diff --git a/config/manual-settings.json b/config/manual-settings.json index abdf6c3..cb560d9 100644 --- a/config/manual-settings.json +++ b/config/manual-settings.json @@ -613,9 +613,9 @@ "webhook_active": false, "credential_state": "observed-name-metadata", "workflow_activation": { - "state": "observed-successful-owned-pr", - "observed_on": "2026-10-12", - "evidence_location": "README.md#crate-dependency-update-app" + "state": "pending-reviewed-workflows", + "observed_on": null, + "evidence_location": null } } } diff --git a/tests/publish-maintenance-branch.sh b/tests/publish-maintenance-branch.sh index 9979150..4465348 100755 --- a/tests/publish-maintenance-branch.sh +++ b/tests/publish-maintenance-branch.sh @@ -85,7 +85,6 @@ security_scenario=${GH_SECURITY_SCENARIO:-converged} crate_repository_id() { case $1 in protocol) printf '%s\n' 1327106950 ;; - renderer) printf '%s\n' 1327106998 ;; content-toolkit) printf '%s\n' 1327107043 ;; *) return 1 ;; esac @@ -106,11 +105,6 @@ crate_workflow_content() { if [[ ${workflow} == publish-crate.yml ]]; then sed -e "s|atrinik/protocol|atrinik/${repository}|g" \ -e "s/1327106950/${identifier}/g" "${fixture}/crate-publication.yml" - elif [[ ${repository} == renderer ]]; then - sed -e 's/package/source-package/g' \ - -e "s/needs.source-package.outputs.version != ''/needs.source-package.outputs.release == 'true'/g" \ - -e 's/source-release-${{ github.sha }}/prepared-renderer-release-${{ github.sha }}/g' \ - "${fixture}/crate-source-release.yml" else cat "${fixture}/crate-source-release.yml" fi @@ -354,11 +348,10 @@ repos/atrinik/*/actions/permissions/workflow\|*) } ' "${GH_IMMUTABLE_STATE}" | jq -r "${jq_filter}" ;; -"repos/atrinik/metaserver-worker/immutable-releases|"* | "repos/atrinik/protocol/immutable-releases|"* | "repos/atrinik/renderer/immutable-releases|"* | "repos/atrinik/content-toolkit/immutable-releases|"*) +"repos/atrinik/metaserver-worker/immutable-releases|"* | "repos/atrinik/protocol/immutable-releases|"* | "repos/atrinik/content-toolkit/immutable-releases|"*) case ${endpoint} in */metaserver-worker/*) repository_id=1324297032 ;; */protocol/*) repository_id=1327106950 ;; - */renderer/*) repository_id=1327106998 ;; */content-toolkit/*) repository_id=1327107043 ;; esac if [[ ${GH_CRATE_IMMUTABLE_VERIFY_FAILURE:-false} == true && ${repository_id} == 1327107043 ]]; then @@ -369,13 +362,13 @@ repos/atrinik/*/actions/permissions/workflow\|*) printf '{"enabled":false,"enforced_by_owner":false}\n' fi ;; -"repos/atrinik/metaserver-worker/code-security-configuration|"* | "repos/atrinik/protocol/code-security-configuration|"* | "repos/atrinik/renderer/code-security-configuration|"* | "repos/atrinik/content-toolkit/code-security-configuration|"*) +"repos/atrinik/metaserver-worker/code-security-configuration|"* | "repos/atrinik/protocol/code-security-configuration|"* | "repos/atrinik/content-toolkit/code-security-configuration|"*) printf '{"status":"enforced","configuration":{"id":265376}}\n' ;; "repos/atrinik/metaserver-worker/code-scanning/default-setup|"*) printf '{"state":"configured","query_suite":"default","languages":["javascript-typescript","python","actions"]}\n' ;; -"repos/atrinik/protocol/code-scanning/default-setup|"* | "repos/atrinik/renderer/code-scanning/default-setup|"* | "repos/atrinik/content-toolkit/code-scanning/default-setup|"*) +"repos/atrinik/protocol/code-scanning/default-setup|"* | "repos/atrinik/content-toolkit/code-scanning/default-setup|"*) printf '{"state":"configured","query_suite":"default","languages":["rust"]}\n' ;; "repos/atrinik/classic/immutable-releases|"*) @@ -393,12 +386,12 @@ repos/atrinik/*/actions/permissions/workflow\|*) printf '{"enabled":false,"enforced_by_owner":false}\n' fi ;; -repos/atrinik/protocol\| | repos/atrinik/renderer\| | repos/atrinik/content-toolkit\|) +repos/atrinik/protocol\| | repos/atrinik/content-toolkit\|) repository=${endpoint#repos/atrinik/} jq -cn --arg repository "${repository}" --argjson id "$(crate_repository_id "${repository}")" \ '{id: $id, full_name: ("atrinik/" + $repository), archived: false, default_branch: "main"}' ;; -repos/atrinik/protocol/branches/main\| | repos/atrinik/renderer/branches/main\| | repos/atrinik/content-toolkit/branches/main\|) +repos/atrinik/protocol/branches/main\| | repos/atrinik/content-toolkit/branches/main\|) printf '{"name":"main","protected":true,"commit":{"sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}\n' ;; repos/atrinik/metaserver-worker/git/ref/heads/main\|) @@ -429,7 +422,7 @@ repos/atrinik/metaserver-worker/actions/workflows/check.yml/runs*\|) head_repository:{full_name:"atrinik/metaserver-worker"},status:"completed",conclusion:$conclusion }]}' ;; -repos/atrinik/protocol/git/ref/heads/main\| | repos/atrinik/renderer/git/ref/heads/main\| | repos/atrinik/content-toolkit/git/ref/heads/main\|) +repos/atrinik/protocol/git/ref/heads/main\| | repos/atrinik/content-toolkit/git/ref/heads/main\|) printf '{"ref":"refs/heads/main","object":{"type":"commit","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}\n' ;; repos/atrinik/*/contents/.github/workflows/publish-crate.yml*\|) From 73d2805e078194ffe731277935a3a34de6172a73 Mon Sep 17 00:00:00 2001 From: Zoey Rose Date: Sat, 17 Oct 2026 20:19:31 -0500 Subject: [PATCH 3/6] refactor: drop the transferred renderer's crate workflow variant verify_crate_workflow.py kept a renderer-only package job name, release guard and artifact name. With the renderer transferred, every remaining crate repository uses the standard package job, so the verifier accepts only that shape. Co-Authored-By: Claude Opus 5.5 --- lib/verify_crate_workflow.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/verify_crate_workflow.py b/lib/verify_crate_workflow.py index 6801b92..73be7e4 100644 --- a/lib/verify_crate_workflow.py +++ b/lib/verify_crate_workflow.py @@ -311,15 +311,13 @@ def verify_source_release(source, repository): require(signer.get("runs-on") == "ubuntu-24.04" and "container" not in signer and "services" not in signer, "signer must use the reviewed hosted runner without execution overrides") package_name = signer.get("needs") - expected_package = "source-package" if repository == "atrinik/renderer" else "package" - require(package_name == expected_package and package_name in jobs, + require(package_name == "package" and package_name in jobs, "signer must depend on one credential-free package job") package = jobs[package_name] require(package.get("permissions", workflow["permissions"]) == {"contents": "read"}, "source package preparation must be credential-free") require(package.get("continue-on-error", "false") == "false", "package preparation must fail on errors") - guard = (f"needs.{package_name}.outputs.release == 'true'" if repository == "atrinik/renderer" - else f"needs.{package_name}.outputs.version != ''") + guard = f"needs.{package_name}.outputs.version != ''" require(expression(signer.get("if")) == guard, "signer must use the package version readiness guard") release = jobs.get("release", {}) require(set(release.get("needs", [])) == {package_name, signer_name} @@ -335,8 +333,7 @@ def verify_source_release(source, repository): critical_step(step, "source signing step") revision = "${{ needs." + package_name + ".outputs.revision }}" version = "${{ needs." + package_name + ".outputs.version }}" - artifact = ("prepared-renderer-release-${{ github.sha }}" if repository == "atrinik/renderer" - else "source-release-${{ github.sha }}") + artifact = "source-release-${{ github.sha }}" require(re.fullmatch(r"actions/checkout@[0-9a-f]{40}", str(checkout.get("uses"))) is not None and checkout.get("with") == {"ref": revision, "persist-credentials": "false"}, "signer checkout must use the exact prepared revision without persisted credentials") From 574b3776f17b5d703bdc520dfe42d5071ffa5f48 Mon Sep 17 00:00:00 2001 From: Zoey Rose Date: Sat, 17 Oct 2026 21:24:19 -0500 Subject: [PATCH 4/6] docs(readme): make the crate dependency App runbook client-only The rotation, verification and retirement instructions still described two consumer workflows and two secret slots after the editor's transfer. Point them at the client's single workflow and credential slots, and keep the editor run as historical evidence that does not prove the client writer. Co-Authored-By: Claude Opus 5.5 --- README.md | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f90c29d..c4ce6d3 100644 --- a/README.md +++ b/README.md @@ -1796,41 +1796,44 @@ cannot prove that a key is valid or effective. These observations do not grant workflow activation, bypass, self-approval, or self-merge authority. The value-free key lifecycle records creation and verification on 2026-10-11, -consumer paths for both `released-dependencies.yml` workflows, and rotation -owner `Atrinik organization owners`. Its 90-day internal rotation deadline is -2027-01-09; this is not an upstream key expiry. Validation rejects missing, -future, overdue, or over-cadence lifecycle dates. Rotate by issuing a replacement -App key, verifying the App identity with it, securely updating only the two -existing repository secret slots, and proving the owning PR-writing workflow. -Revoke the old key after successful replacement proof; retain name-only evidence -and update creation/verification/deadline metadata through reviewed source. If -replacement proof fails, stop the writing workflows and preserve the old working -key until the owner restores service or explicitly revokes it. +the consumer path of the client's `released-dependencies.yml` workflow, and +rotation owner `Atrinik organization owners`. Its 90-day internal rotation +deadline is 2027-01-09; this is not an upstream key expiry. Validation rejects +missing, future, overdue, or over-cadence lifecycle dates. Rotate by issuing a +replacement App key, verifying the App identity with it, securely updating only +the client's existing repository secret slot, and proving the client's +PR-writing workflow. Revoke the old key after successful replacement proof; +retain name-only evidence and update creation/verification/deadline metadata +through reviewed source. If replacement proof fails, stop the client's writing +workflow and preserve the old working key until the owner restores service or +explicitly revokes it. The read-only verifier checks live installation identity, exact permissions, empty events, active state, selected repository identities, and credential **names**. An administrative-token API cannot enumerate the complete selected repository set; retain current supported App-authenticated readback or owner UI proof. It reports `credential_state: observed-name-metadata` separately from -workflow activation. Both reviewed `released-dependencies.yml` workflows have -merged; the dated editor run/PR above proves effective App use and ordinary -required checks for that writer. A no-op run or setup/name metadata alone cannot +workflow activation. The client's reviewed `released-dependencies.yml` workflow +has merged. The dated editor run/PR above is historical evidence of effective +App use by the transferred repository's writer and does not prove the client's +writer. A no-op run or setup/name metadata alone cannot prove a successful PR-writing job. Keep new or replacement credential acceptance bound to the actual owning workflow and its required checks. -The updaters run hourly (`17 * * * *`) and through `workflow_dispatch`. Their -credential-free preparation verifies exact released versions and registry -checksums, resolves dependencies, updates Cargo locks, and runs aggregate -validation before requesting an App token in the isolated PR-writing job. They -maintain only their owned `automation/released-crate-dependencies` branch and -pull request; ordinary required checks and the pull-request process still govern -merging, with zero required human approvals. -Missing registry readiness produces no migration PR. They never upload crates, -approve a pull request, merge it, or loosen branch protection. Prove initial +The client's updater runs hourly (`17 * * * *`) and through +`workflow_dispatch`. Its credential-free preparation verifies exact released +versions and registry checksums, resolves dependencies, updates Cargo locks, and +runs aggregate validation before requesting an App token in the isolated +PR-writing job. It maintains only its owned +`automation/released-crate-dependencies` branch and pull request; ordinary +required checks and the pull-request process still govern merging, with zero +required human approvals. +Missing registry readiness produces no migration PR. It never uploads crates, +approves a pull request, merges it, or loosens branch protection. Prove initial App permissions with a disposable/owned PR and current ordinary checks, without -exposing credentials. On retirement, stop these workflows and revoke the -App/key and only the two named repository credential slots; preserve unrelated -installations and secrets. +exposing credentials. On retirement, stop the client's workflow and revoke the +App/key and only the client's named secret and variable slots; preserve +unrelated installations and secrets. ## Transferred repositories From b75f4035a51fdbf319e7d528b505f0262a97d3d3 Mon Sep 17 00:00:00 2001 From: Zoey Rose Date: Sat, 17 Oct 2026 22:02:12 -0500 Subject: [PATCH 5/6] docs(readme): make the linux-build package runbook client-only The package Actions-access text still described client and editor grants as the current inventory and rolled back "these two" grants. Describe only the client's grant and rollback, and keep the editor's 2026-10-11 grant observation as history tied to its transfer. Co-Authored-By: Claude Opus 5.5 --- README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c4ce6d3..05fd545 100644 --- a/README.md +++ b/README.md @@ -1530,17 +1530,21 @@ could not resolve that manifest. An owner's successful pull cannot prove a repository workflow's package access. `github_packages_actions_access` records read-only Actions-access setup for -`atrinik/client` (`1327106844`) and `atrinik/editor` (`1327106902`). The owner -confirmed those two **Manage Actions access** read grants on 2026-10-11; their -`grant_state` is `owner-observed-read-grant`, with dated sanitized owner evidence. +`atrinik/client` (`1327106844`) only. The owner confirmed its **Manage Actions +access** read grant on 2026-10-11; its `grant_state` is +`owner-observed-read-grant`, with dated sanitized owner evidence. The same +2026-10-11 owner observation also covered a read grant for `atrinik/editor` +(`1327106902`); that entry is historical, because the editor left the +organization on 2026-10-17 (see +[Transferred repositories](#transferred-repositories)) and is no longer in the +inventory. Client's exact-main post-grant run [38164640476](https://github.com/atrinik/client/actions/runs/38164640476) subsequently proved a repository-token pull of the pinned image and complete read-only preparation/application validation. It correctly produced no migration PR before registry readiness; App-writing jobs were skipped, so no effective -App writer was proved. Editor owning workflow pull/activation proof remains -pending. The grant observation comes from owner UI, not API enumeration of the -grant list. Retain permission inheritance, private +App writer was proved. The grant observation comes from owner UI, not API +enumeration of the grant list. Retain permission inheritance, private visibility, linked source and every foreign entry. Do not broaden to organization/all repositories or change a foreign grant. The publisher never writes these manual grants. @@ -1557,8 +1561,8 @@ be removed before credential-free Cargo/build preparation; the updater App remains isolated to its PR-writing job. No package grant supplies a registry-publication token or proves the dependency App key effective. -Rollback stops the owning updater workflows first and removes only these two -read grants through supported owner UI after reviewed inventory removal. +Rollback stops the client's owning updater workflow first and removes only the +client's read grant through supported owner UI after reviewed inventory removal. Preserve the package, published versions, source, visibility, foreign grants, and unrelated App credentials. From c8b0d5fb73c2418a94a1e989457a5cc2df1b8c98 Mon Sep 17 00:00:00 2001 From: Zoey Rose Date: Sat, 17 Oct 2026 23:23:40 -0500 Subject: [PATCH 6/6] docs(readme): drop renderer from the operational immutable-release runbook The metaserver activation text still preserved renderer, the source-release inventory still added it, and the readback loop still queried atrinik/renderer. Remove renderer from those operational parts and keep the completed activation's rollout and payload as a historical record, with a note to derive later payloads from config/immutable-releases.json. Co-Authored-By: Claude Opus 5.5 --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 05fd545..74a20ec 100644 --- a/README.md +++ b/README.md @@ -1048,8 +1048,8 @@ repair coverage. ## Metaserver immutable releases and production environment The desired selected inventory adds only `atrinik/metaserver-worker`, stable -repository ID `1324297032`, while preserving Classic, content-toolkit, protocol -and renderer. This is pending activation until the reviewed metaserver producer +repository ID `1324297032`, while preserving Classic, content-toolkit and +protocol. This is pending activation until the reviewed metaserver producer creates a draft, finishes and verifies every release asset, and only then publishes that draft. Before any broad publisher mutation introducing this selection, the read-only preflight checks the exact authenticated current-main @@ -1172,8 +1172,11 @@ only after this inventory is reviewed and accepted. ## Enabling immutable source releases The desired organization-level `selected` inventory preserves Classic -(`1327289971`) and adds protocol (`1327106950`), renderer (`1327106998`), and -content-toolkit (`1327107043`). These additions are pending activation until +(`1327289971`) and adds protocol (`1327106950`) and content-toolkit +(`1327107043`). Renderer (`1327106998`) was part of the same addition until it +left the organization on 2026-10-17 (see +[Transferred repositories](#transferred-repositories)); the desired inventory +no longer selects it. These additions are pending activation until reviewed source workflows are merged and qualified. Their release producers must assemble and verify the complete crate/archive/manifest payload on a draft before publishing it. A published immutable release must be complete because @@ -1214,6 +1217,14 @@ and the complete asset payload. A configured flag alone is not release proof. Qualification includes build, packaging, draft assembly, retry/recovery, actual immutable-release upload gating, and publication paths. +The rollout order below is the record of the activation that added protocol, +renderer and content-toolkit, and it stays as written. Its three-producer +payload and four-repository checks describe the selection at that time, before +renderer left the organization. Derive the payload for any later change, +including the pending metaserver addition, a rollback or an intentional removal, +from the current `config/immutable-releases.json`, and never select or query +`atrinik/renderer`. + Use this narrow rollout order for this release activation: 1. Qualify each reviewed draft-first producer candidate and retain its exact @@ -1261,7 +1272,7 @@ gh api -H 'X-GitHub-Api-Version: 2026-03-10' \ gh api --paginate -H 'X-GitHub-Api-Version: 2026-03-10' \ 'orgs/atrinik/settings/immutable-releases/repositories?per_page=100' \ --jq '.repositories[] | [.name, .id]' -for repository in classic protocol renderer content-toolkit; do +for repository in classic protocol content-toolkit; do gh api -H 'X-GitHub-Api-Version: 2026-03-10' \ "repos/atrinik/${repository}/immutable-releases" done