Skip to content

Three Apps registered and installed, and their real grants compared with the data #26

Closed

Description

@don-petry

Part of the epic "Epic: slice 0, foundations. The repo holds the line, its context is tested, and the platform facts are recorded".

Why. Slice 0 ends when the three Apps exist. Two files typed by hand agreeing with each other proves nothing about what GitHub actually granted.

Done when

  • The Maintainer registers the three with the prefix combsmith-sf-, from the permission data, and installs them on this repo and the canary only.
  • Each App's identifier is recorded in the repo. No key is. Each private key is an environment secret restricted to the default branch, referred to by name.
  • A trial in the canary reads what each installation was actually granted, and the fixture agrees with the data. A difference fails the trial.

Needs. The story "The Apps' permissions as data", and trial P-3's verdict before any key is stored. The registrations are the Maintainer's.

Design. §5.1; §9.1, slice 0; §4.9.

Activity

  1. don-petry commented on Oct 14, 2026

    @don-petry
    ContributorAuthor

    Amendment (Solution Architect working ruling on #20, 2026-10-14): a fourth criterion. The canary trial reads a queued pull request's membership through the Controller's installation, and records the least permission that read needs. read:facts.queue in config/apps.json waits on it.

  2. don-petry commented on Oct 18, 2026

    @don-petry
    ContributorAuthor

    Working rulings (overturnable in review)

    Solution Architect, 2026-10-17. All three Apps are installed on selected repos, the reader too (5364151), so nothing waits on it. A read-only look today found each one's permissions, registered and installed, equal to config/apps.json, with no events. Which repos each reaches is unread.

    1. A trial number. The comparison is P-39, a new row in §6, batch 1: "Does what GitHub granted each Factory App, its permissions, its events and the repos it reaches, agree with config/apps.json?" Pass: "They agree exactly, and a difference fails." Why: a fixture is named for a §6 trial, and slice 0's exit rests on this one.

    2. How the grants are read. Three reads, in order, as exchanges of trials/P-39.json:

    • GET /apps/{slug} for each App: the registration. A session, through the Maintainer's login.
    • GET /orgs/combsmith/installations: what each installation accepted. A session, through his login. Kept whole, third-party Apps included.
    • GET /installation/repositories for each, at the Maintainer's terminal, with a token minted from the local key, used once and revoked. Only this response is captured, outside the repo, and a session redacts it.

    Why: the first two mint nothing. Reach needs an installation token, so it runs where the keys are.

    3. The pass, field by field. For each App: app_id is the data's; the permissions and their levels are exactly the data's, no more and no fewer, and the same registered and installed; events is empty; the selection is selected; and the repos are exactly installed_on.

    4. What a mismatch does. The verdict is fail, and no key for that App is stored. The Maintainer corrects the registration and the trial runs again; only the passing fixture is kept. If GitHub cannot grant what the data asks, the data changes by pull request, with an issue against the design. Why: the data says what each need takes, and only a checked grant gets a key.

    5. The data is held to the fixture. config/apps.json gives each App an app_id and a key of {environment, secret}. tools/check_apps.py compares each App's permissions and levels with what P-39 found installed for that app_id. It also refuses two Apps in one environment. Why: otherwise the data drifts from GitHub and two hand-typed files agree again. The trial-author App is not Factory data; its ID stays in fixtures.

    6. The environments and secrets, on combsmith/software-factory. One per job that holds a key: factory-poller holds CONTROLLER_APP_KEY, factory-apply holds PR_REVIEW_APP_KEY and factory-run holds READER_APP_KEY. Each has a deployment branch policy of main alone, admin bypass off, and no required reviewer. Why: a job that names an environment reads all its secrets, so one environment would hand run, which reads untrusted text, a key that can write (decision 0004, §4.9). No reviewer: the Factory runs unattended, and P-3 showed the branch policy holds.

    7. The fourth criterion moves to a new story, with P-2. That story's change repoints read:facts.queue's waits_on. Why: it needs a merge queue and a queued pull request in the canary, which is P-2's batch-2 set-up; slice 0's exit should not wait on it.

    8. Order. P-39 first. Then the environments, then the keys for the Apps that passed. Then one pull request: the fixture, the data, the check, §6's row, and decision 0034. It may run beside #24's trials: it writes nothing to the canary. It closes slice 0's "the three Apps exist", and unblocks P-4, P-5, P-23, P-25 and P-35 of batch 2, and P-12.

    9. Decision record 0034: "An App's grants are read from GitHub and the data is held to them; each key lives in the environment of the one job that holds it." Not 0035.

    Needs the Maintainer?

    Yes. Each write waits for his yes:

    1. Run P-39's reach script at his terminal. It mints three installation tokens and revokes each one.
    2. Create factory-poller, factory-apply and factory-run on combsmith/software-factory: branch policy main, admin bypass off. Session, through his login.
    3. Store each passing App's key with gh secret set <NAME> --env <environment> -R combsmith/software-factory. Maintainer alone.
    4. Approve the protected edits in the pull request: §6's P-39, decision 0034 and config/apps.json.

    After write 3, a session reads only names, dates and branch policies, for the pull request's body.

    Approved by the Maintainer, 2026-10-18: write 2, the three environments, made by a session through the Maintainer's gh login. Writes 1 and 3 (the reach script and every key) stay the Maintainer's. Write 4 is the pull request's review.

  3. don-petry commented on Oct 18, 2026

    @don-petry
    ContributorAuthor

    Decision record renumbered to 0033: #158 needs none, so the numbers close up (#42 0031, #160 0032, #26 0033).

  4. don-petry commented on Oct 18, 2026

    @don-petry
    ContributorAuthor

    Decision number: P-13 (#15) has run and taken decision record 0033, as planned on #15 for the case where #26 was still waiting. Numbering has no gaps and merges in order, so a number held by a waiting story blocks every later one. This story therefore takes the next free number when it is built. The branch trials/p-39-apps still says 0033 and gets renumbered then. The record's content and the rulings above are unchanged.

  5. added a commit that references this issue on Oct 18, 2026
  6. don-petry commented on Oct 19, 2026

    @don-petry
    ContributorAuthor

    Status, 2026-10-19 ~03:10Z (read-only, through the Maintainer's login)

    Write 2 is made and matches ruling 6. Each environment has a deployment branch policy of main alone, admin bypass off and no required reviewer. None holds a secret yet.

    Environment Secret it will hold (ruling 6) Secrets now
    factory-poller CONTROLLER_APP_KEY 0
    factory-apply PR_REVIEW_APP_KEY 0
    factory-run READER_APP_KEY 0

    P-39 so far. The grants reads passed: trials/P-39.grants.json is on trials/p-39-apps, at d19bdfd. All four installations are selected and not suspended (controller 172571513, pr-review 172571064, reader 172572220, trial-author 172549542). Which repos each one reaches can't be read with a user login (GET /user/installations/{id}/repositories answers 403), so the reach read stays with the Maintainer, as ruling 2 says.

    Next, in order:

    1. Maintainer: run tools/trial_p39_reach.sh from a checkout of trials/p-39-apps, with a new OUT directory outside the repo. A session read the script: each token is narrowed to metadata: read, is never printed or written, and is revoked after one read.
    2. Oversight session: check OUT against ruling 3. Each installation must reach exactly its installed_on repos, and each revocation must return 204. The verdict gets posted here.
    3. Delivery session: build trials/P-39.reach.json from OUT alone. The decision record takes the next free number when built; 0034 is now Kernel: an issue goes stale as a pull request does #165's.
    4. Maintainer, and only for the Apps that pass: gh secret set <NAME> --env <environment> -R combsmith/software-factory, once for each row above.
  7. don-petry commented on Oct 19, 2026

    @don-petry
    ContributorAuthor

    P-39 reach: pass (2026-10-19 03:24:52Z)

    The Maintainer ran tools/trial_p39_reach.sh (branch trials/p-39-apps, d19bdfd) at his terminal. A session checked OUT against ruling 3. OUT holds no credential: it was scanned for token, JWT and private-key patterns, and none were found.

    App app_id Installation GET /installation/repositories Reaches installed_on in config/apps.json Revoked Verdict
    combsmith-sf-controller 5364130 172571513 200, total_count 2, selected software-factory, software-factory-canary the same two 204 pass
    combsmith-sf-pr-review 5364144 172571064 200, total_count 2, selected software-factory, software-factory-canary the same two 204 pass
    combsmith-sf-reader 5364151 172572220 200, total_count 2, selected software-factory, software-factory-canary the same two 204 pass

    Each list fits in one page (no link header). Each token was narrowed to metadata: read. With the grants reads already passed, every field ruling 3 names now agrees with the data for all three Apps.

    Next:

    1. Maintainer: store the three keys, one for each App that passed (ruling 6):
      • CONTROLLER_APP_KEY in factory-poller
      • PR_REVIEW_APP_KEY in factory-apply
      • READER_APP_KEY in factory-run
    2. Delivery session: build trials/P-39.reach.json from OUT alone, then open the one pull request of ruling 8. Its decision record takes the next free number.
    3. Oversight session: after step 1, read only each secret's name, date and branch policy, for the pull request's body.
  8. don-petry commented on Oct 19, 2026

    @don-petry
    ContributorAuthor

    Decision number, updated: this story's record is 0036 (it was the next free number). #26's P-39 reach trial passed and is ready to build now, so it takes 0036, ahead of the stories still being ruled or built. Order: 0034 #165 (PR #176), 0035 #161, 0036 #26, 0037 #19, 0038 #28, 0039 #174 PR B.

  9. don-petry commented on Oct 19, 2026

    @don-petry
    ContributorAuthor

    Write 3 made: the three keys are stored (2026-10-19)

    The Maintainer stored each passing App's key himself with gh secret set … --env …. A session then read only the names, dates and branch policies, as the rulings allow, for the pull request's body.

    Environment Secret Created Deployment branches Admin bypass Required reviewer
    factory-poller CONTROLLER_APP_KEY 2026-10-19T03:39:48Z main (branch) off none
    factory-apply PR_REVIEW_APP_KEY 2026-10-19T03:39:58Z main (branch) off none
    factory-run READER_APP_KEY 2026-10-19T03:40:06Z main (branch) off none

    Each environment holds one secret, and no repository-level secret exists. This matches ruling 6.

    Left for #26: write 4, the one pull request of ruling 8 (the fixture trials/P-39.reach.json built from OUT, the data, the check, §6's row and decision record 0036). The delivery session builds it.

  10. don-petry commented on Oct 19, 2026

    @don-petry
    ContributorAuthor

    Decision numbers change rule (Maintainer, 2026-10-19). A decision record takes its number when its pull request is clean and green and about to be enqueued, as the next free number. It is no longer fixed when the rulings are posted. The number posted on this issue is provisional. The builder renames the record at enqueue if an earlier PR took that number. CI's gapless decision check still holds the order. Boundary rows in other records name tests, never numbers, so nothing else moves. This ends the renumbering, and a story with a record no longer waits behind an unrelated story that holds a lower number.

  11. don-petry commented on Oct 19, 2026

    @don-petry
    ContributorAuthor

    Decision numbers are taken at enqueue (the Maintainer, 2026-10-19, on the plan review's recommendation). A record takes the next free number when its PR is clean and green and about to be enqueued, not when its ruling is posted. Rulings say "the next free number at enqueue". Boundary rows in other records name tests, never numbers, so nothing else moves. The order posted on 2026-10-19 (0035 #161, 0036 #26, 0037 #19, 0038 #28, 0039 #174 PR B) is the expected order, not a reservation: #161 keeps 0035 as the next to enqueue, and each of the others takes the next free number in the order it is actually enqueued. A rows-only PR still merges any time.

  12. added 3 commits that reference this issue on Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions