Repository navigation
Description
platform's GitHub configuration creates overnightworks/figure
Caller: overnightworks/hopin#629, card line 4, ruled A by the operator on 09.11.2026. Its outline puts "figure's repository" right after P0, and FG1a (bootstrap, container rules, figure check) waits on it. The operator, in chat to hopin's head: „also ich hab a entschieden! aber figure wäre der richtige name und nciht character?“
Blocked by #307. Today every plan of the root fails its listing preconditions, because agents is personas on GitHub. This slice is built after #307 has landed and been applied, and its fixtures name personas.
Ruled
-
hopin#629, ruled sentence 11: "The figure package lives in a new repository named figure, which every app takes (card line 4, A)."
-
hopin#629, card line 4, option A ("Eigenes Repo figure"), its contract word for word:
A new repository overnightworks/figure, created through platform's GitHub configuration, ships framework-free TypeScript ESM with three and @pixiv/three-vrm as its dependencies. It owns the fleet's VRM 1.0 profile, its check command
figure check(npmovernightworks-figure), and drawing and driving a figure in any browser. It is released as a tarball on an immutable release through quality's release gate and pinned by URL, like appkit. Its first callers are hopin's agent tile, presentator's co-presenter and the CI of personas (today's overnightworks/agents). The accept field isfigure: {url, sha256}, and every name the plan writes as character becomes figure. Source: hopin#629 comments 6741826135 and 6741910486; the name from personas' head after the operator's doubt about character, 09.11.2026. -
Names (hal
docs/naming.md, bygit show origin/mainonly): the repository is the stemfigure, and the Sonar project key isovernightworks_figure. -
Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10, line 3, ruled B on 06.11.2026: "Every managed repository carries lifecycle destroy = false and archive_on_destroy = true … a removed key is forgotten, never destroyed".
figurefalls under it from its first apply. -
docs/github-settings.md, Platform on k3s and Flux: from an empty PC to every app live, with hal archived #1 to Bump actions/checkout from 6.1.0 to 7.0.1 #6. The slice makes none of them false and changes nothing they require. The apply goes through Add the repository skeleton and the fleet's quality gate #3 to Bump astral-sh/setup-uv from 10.0.1 to 10.3.3 #5.
What it builds
Change 1, the create (in src/opentofu/github/repositories.tf):
- A
createdmap besidemanagedandunmanaged. It mapsfigureto its description, and nothing else is in it. A new localdeclaredholdsmanagedplus the keys ofcreated. - Both resources (
github_repository.managedandgithub_repository_vulnerability_alerts.managed) iterate overdeclared. Their addresses stay…managed["figure"]. - The
importblocks stay overlocal.managed, unchanged. A typo inmanagedstill fails the import and creates nothing, and every existing repository still recovers from a lost state by import. - Six attributes are set only for a key of
created. Every other key getsnull, andignore_changeskeeps them out of every later plan:description, from the map;visibility = "public";has_issues = true;auto_init = true;has_wiki = trueandhas_projects = true, which every fleet repository carries as GitHub's default; the provider sendsfalsefor an unset one, so leaving them out would make figure the one repository without them.
- One new precondition on
output.managed_repositories: a name increatedthat the listing holds (archived, fork or not) fails the plan, naming it. The message tells you to move it tomanaged. This keeps a name from being created twice. It also stops a plan whose name was created by hand before the apply, instead of failing halfway through the apply on GitHub's 422. undeclared_repositoriessubtractsdeclaredinstead ofmanaged.- The output's value becomes
sort(tolist(local.declared)). - The import comment "so no repository is created" now says that a name in
managedis never created and only a name increatedis.
Size: one production file, about 25 changed production lines. Tests: one new run in tests/repository_listing.tftest.hcl and a new tests/repository_creation.tftest.hcl with one run (below).
Change 2, the fold, is an item of its own (#313), cut together with this one and blocked by it, since a pull request lands one item and the fold can only follow the apply. It is dispatched the moment this item's apply has run, and no other apply of the root runs between this apply and the fold's landing, since every plan fails on the new precondition until then. What it does:
figuremoves fromcreatedintomanaged, and the listing fixtures gain it.created,declared, the four conditional attributes, the precondition and the creation test are deleted. The tree goes back to import-only, and the comment goes back to "so no repository is created".- The mechanism stays recoverable from git, and the next new repository brings it back from this item's first commit. An empty
createdwould be an extension point with no caller. - The plan after the fold must exit 0. The import blocks then name
figure, which is already in the state, so they are a no-op. The vulnerability alerts' import id is the name while the state holds the numeric id; the 15 existing repositories plan empty the same way since the apply of 08.11.2026.
Alternatives weighed:
- (a) A separate
createdset, which import and listing checks skip, folded intomanagedlater.- Chosen, in the form above.
- Costs: about 25 lines that live for a few hours, and a second small pull request.
- Leaves behind: nothing after the fold.
- Keeps every guarantee the brief names. Between the apply and the fold, every plan of the root fails on the new precondition, which forces the fold.
- (b) One set, with the import skipped for names the listing does not hold.
- Rejected. A typo in
managedwould silently create a repository, which is exactly the safeguard the import gives today. - It also needs
unmanageable_namesdropped, and animportfor_eachthat depends on a data source. Nobody has verified that OpenTofu supports that dependency, andtofu testcannot exercise import blocks.
- Rejected. A typo in
- (c) An agent runs
gh repo createby hand; the slice only addsfiguretomanaged.- The cheapest path: one pull request, no mechanism.
- It does not meet the ruled sentence "created through platform's GitHub configuration", so it needs the operator's new word.
- It also leaves a window in which the repository has the organisation's defaults for new repositories: secret scanning, push protection and Dependabot alerts are off for new repositories in this organisation, and merge commits are allowed. In that window, a hand step that no review saw sets visibility, issues and the first commit.
- (d) Keep an empty
createdafter the fold as a standing create path.- Rejected under the code policy: there is no current caller.
- The operator may rule a standing path if creations keep coming. platform-secrets (06.11) and figure (09.11) came in one week.
Attributes on create, and what provider 6.13.0 sends. ignore_changes does not apply to a create; the configured values are sent. Sources: github/resource_github_repository.go resourceGithubRepositoryObject (587–652), resourceGithubRepositoryCreate (654–786), resourceGithubRepositoryUpdate, calculateVisibility (523–537); go-github v88 createRepoRequest (github/repos.go 508–600), whose pointer fields send an explicit false.
| Attribute | Declared | What 6.13.0 sends | Why |
|---|---|---|---|
| visibility | "public" |
POST visibility: public; without it, calculateVisibility also falls back to public |
The ruling says "pinned by URL, like appkit"; appkit is public (gh api repos/overnightworks/appkit). A release asset of a private repository cannot be fetched by URL without a token, which hopin's build, presentator and personas' CI would then need. Secret scanning and push protection are free only on a public repository (#34). |
| has_issues | true |
POST has_issues; the schema has no default, so without it false is sent and issues are off |
FG1a and the rest of the outline are issues in figure; every fleet repository has issues on. |
| auto_init | true |
POST auto_init: true, so GitHub writes a first commit (README) on main |
FG1a needs a default branch to open a pull request against. Whether main: PR only would refuse a first direct push into an empty repository is not verified; the first commit avoids the question. |
| default_branch | not set | Create refuses anything but main; Update never sends it on a new resource |
GitHub names the first branch main. |
| description | from created |
POST description; without it, "" |
It is sent only at the create; afterwards it stays as set on GitHub (ignore_changes), so figure's head may change it. |
allow_squash_merge true, merge commit and rebase false, delete_branch_on_merge true |
as for every repository | POST, then the same in the PATCH that Create ends with | Same as the fleet, from the first moment. |
| security_and_analysis (secret scanning, push protection) | the existing dynamic block (figure is not in listed_private) |
Not in the POST; the PATCH after the create carries it, since HasChange("security_and_analysis") holds on a new resource |
The organisation does not enable it for new repositories (secret_scanning_enabled_for_new_repositories: false). |
| Dependabot alerts | github_repository_vulnerability_alerts.managed["figure"], enabled = true |
PUT /repos/…/vulnerability-alerts, and the state id is the repository's numeric id |
The organisation's default for new repositories is off. |
| has_wiki, has_projects, has_discussions, has_downloads | not set | false (no schema default) |
Off on figure, while older repositories carry GitHub's default on; nothing in the fleet uses them. The head may add them as true before the build if the fleet should look the same. |
archive_on_destroy, destroy = false |
as for every repository | Written into the state by the apply | #10 line 3. A created object carries destroy = false in its state from the first apply, unlike an import (GH-6). |
| topics | not set | none | hal docs/naming.md: topics are never set by hand, and are not set at all until edge#30. |
What reaches figure without this slice:
- Rulesets.
main: PR onlyandrelease tags are immutabletarget~ALL. - Immutable releases. The organisation enforces them for all repositories:
orgs/overnightworks/settings/immutable-releasesanswersenforced_repositories: all, and appkit, speech-runtime, quality and personas reportenabled: true, enforced_by_owner: true. quality_gate. It is required with the defaultfalse, soquality / verdict requireddoes not reach figure until quality's merge command setstrue.- Quality's onboarding stays outside platform: the property value,
role(library, as appkit), the Sonar projectovernightworks_figure, CodeQL through quality'scodeql.yml(which Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10's G4 rule will then require), and the release gate.
Proof
- CI.
mise run github:testin CI'sopentofujob is green (format, init against the lock, validate, the mocked tests, the delete-refusing test).- New listing run:
a_repository_to_create_that_the_listing_holds_fails. - New creation run:
a_repository_to_create_is_public_with_issues_and_a_first_commit. It targetsgithub_repository.managed["figure"]andgithub_repository_vulnerability_alerts.managed["figure"]with a full listing, and assertsvisibility == "public",has_issues,auto_init, the description equal tocreated's, both scanning statusesenabledand the alertsenabled.
- New listing run:
- Plan before the pull request (builder). The builder runs
scripts/apply_github_settings.shon the lane's commit. Like platform's GitHub configuration manages personas, the repository agents became #307, it is a plan with the operator's gh login and writes no state. Its summary goes into the journal: exactly 2 to add (github_repository.managed["figure"],github_repository_vulnerability_alerts.managed["figure"]), 0 to change, 0 to destroy, no import and no forget, and the output change. Anything else stops the lane. - Apply after the landing (agent). An agent runs
scripts/apply_github_settings.sh --applyfrom a cleanorigin/main; the plan and apply output go into the journal. Thenscripts/apply_github_settings.shmust exit 1 on exactly the new precondition, namingfigure. - Read-back with
gh api(GET only), recorded in the journal:repos/overnightworks/figure: visibility, has_issues, default_branch, the merge settings, the description,security_and_analysis;repos/overnightworks/figure/vulnerability-alerts: 204;repos/overnightworks/figure/commits?sha=main: one commit;repos/overnightworks/figure/rules/branches/main: deletion, non_fast_forward, pull_request;repos/overnightworks/figure/rulesets: both~ALLrulesets;repos/overnightworks/figure/immutable-releases;repos/overnightworks/figure/properties/values:quality_gatefalse.
- Fold, the fold item's proof. Its pull request is green in CI. Its pre-PR plan and the plan after its landing both exit 0.
- Pre-check (09.11.2026). A scratch copy of
origin/main, with platform's GitHub configuration manages personas, the repository agents became #307's rename and change 1 applied, passed all 15 mocked runs undertofu test, the two new runs included. So targeting the onefor_eachinstance that has no import block works beside the import blocks of the other instances.tofu fmtasked only for alignment.
Trace (main e375b80)
- The managed list is in
src/opentofu/github/repositories.tf(local.managed, the import comment at line 27) and in the 6 fixtures ofsrc/opentofu/github/tests/repository_listing.tftest.hcl.- Change 1 leaves the fixtures without
figure: it is not listed yet, and the new precondition requires that. - The fold adds
figureto every fixture. tests/organization_rulesets.tftest.hcl,organization.tf,organization/main.tfandscripts/apply_github_settings.shname no repository.src/legal/namesspeech-runtimeas a legal-section key, not as a repository list.
- Change 1 leaves the fixtures without
- The comment
repositories.tf:27, "so no repository is created", is false whilecreatedholds a name. Change 1 rewrites it and the fold restores it.organization.tf:1, "so nothing is created", stays true. - Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10's body, What is known → Safeguards, says "the code cannot create a repository". hopin#629 line 4 (A, 09.11.2026) overtakes it, and the head corrects that sentence in Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10's body when change 1 lands. It is item text, not a ruled expectation: line 1's contract does not forbid a create.
docs/github-settings.md:- Platform on k3s and Flux: from an empty PC to every app live, with hal archived #1 ("the security and merge settings of the managed fleet repositories") stays true.
- Add the repository skeleton and the fleet's quality gate #3 to Bump actions/checkout from 6.1.0 to 7.0.1 #6 hold, and a create is no
delete. - No sentence is added, since the create path lives only until the fold.
AGENTS.md: no sentence becomes false. Line 441, a GitHub setting changes only through the root, holds.
Risk
Material: an outward-facing, public creation that nothing deletes. The repository name is taken for good, and it is public from its first commit. The lane gets an Opus review at high effort and a cross-provider final gate. The first review drives this matrix:
| # | Sentence | Cell | Driven by |
|---|---|---|---|
| 1 | The plan creates exactly figure's two resources and changes nothing else | the builder's pre-PR plan summary: 2 add, 0 change, 0 destroy, no import or forget | builder, reviewer reads the journal |
| 2 | A typo in managed still cannot create a repository |
the diff leaves both import for_each on local.managed; the existing run a_managed_repository_missing_from_the_listing_fails still passes |
reviewer, CI |
| 3 | A name in created that the listing holds fails the plan |
the new mocked run; the live plan after the apply exits 1 naming figure |
CI, agent |
| 4 | Created public, issues on, first commit on main, the description, squash-only, delete-branch-on-merge |
the creation run; the gh api read-back |
CI, agent |
| 5 | Secret scanning, push protection and Dependabot alerts on from the create | the creation run; read-back of security_and_analysis and vulnerability-alerts 204 |
CI, agent |
| 6 | The organisation rulesets and immutable releases reach figure, and quality / verdict does not until quality_gate=true |
read-back of rules/branches/main, rulesets, immutable-releases, properties/values |
agent |
| 7 | Nothing deletes it: no delete is ever planned, destroy = false is in its state, and a removal is a forget |
the delete-refusing test (CI); the reviewer reads the state's figure entry after the apply for skip_destroy/destroy = false (names and flags only) |
CI, agent |
| 8 | The apply's own output preconditions hold (the listing is read at plan time, not again at apply) | --apply exits 0 |
agent |
| 9 | After the fold, a plan is empty and figure recovers by import like every other name | the plan after the fold exits 0; a lost-state import of figure is not driven, since it is the same import block that 15 names pass |
agent; row 9's second half not driven, reason named |
Rollback
A created repository is never deleted (#10 line 3). The login has no delete_repo scope, and the apply refuses every delete (GH-5).
- Before the apply: reverting change 1 changes nothing on GitHub.
- After the apply: a revert removes
figurefromcreated. The plan forgets both resources, because the apply wrotedestroy = false. The repository stays on GitHub, andundeclared_repositorieskeeps every plan red untilfigureis inmanagedor inunmanagedwith a reason. - Archiving or deleting the repository is the operator's own hand step, on his word.
- What stays in any case: the name, the first commit, and anything pushed since.
Done when
- platform's GitHub configuration manages personas, the repository agents became #307 has landed and its apply ran, with an empty plan after it.
- Change 1 is landed:
createdholdsfigurewith its description; the import blocks are unchanged; the new precondition and the two mocked runs exist;mise run github:testis green in CI. - The pre-PR plan showed only the two creates. An agent applied from a clean
origin/main, and the following plan failed only on the new precondition, namingfigure. Plan, apply and read-back are in the journal, and the read-back matches matrix rows 4 to 6. - The fold item, figure moves into managed, and platform's GitHub configuration is import-only again #313, is open, blocked by this one, and dispatched at the apply.
- Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10's Safeguards sentence "the code cannot create a repository" is corrected in Every fleet repository's GitHub settings and the organisation rulesets live as code in platform #10's body.
- hopin's head is told that
overnightworks/figureexists, for FG1a and quality's onboarding. - Predecessor: none. No older path creates repositories; the hand creation of platform-secrets on 06.11.2026 was not a declared path.
version = 1
now = "Drafted on 09.11.2026 from hopin#629 line 4 (ruled A): figure is created through platform's GitHub configuration. Blocked by #307, since every plan of the root fails until agents is personas in managed."
next = "Once #307 has landed and been applied, claim and build change 1 (created map, precondition, two mocked runs) with the builder's pre-PR plan; an agent applies after the landing; then the fold item is dispatched."
done_when = "Change 1 is landed with a pre-PR plan of exactly figure's two creates; an agent applied it from a clean origin/main and the gh api read-back matches; the fold item is dispatched; #10's Safeguards sentence is corrected."
scope = ["src/opentofu/github/repositories.tf", "src/opentofu/github/tests/repository_listing.tftest.hcl", "src/opentofu/github/tests/repository_creation.tftest.hcl"]
Activity
FlexOr2 commentedon Nov 9, 2026
Landed in #332 as 869856f. The head's plan from origin/main at 869856f named exactly
github_repository.managed["figure"]andgithub_repository_vulnerability_alerts.managed["figure"](2 to add, 0 to change, 0 to destroy);--applycompleted with 2 added; read-back:overnightworks/figure public squash=true merge=false rebase=false delete_branch=true archived=false, vulnerability alerts 204. The fold #313 is dispatched; #10's Safeguards sentence is corrected.