You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Three Apps registered and installed, and their real grants compared with the data #26
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.
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.
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:
Run P-39's reach script at his terminal. It mints three installation tokens and revokes each one.
Create factory-poller, factory-apply and factory-run on combsmith/software-factory: branch policy main, admin bypass off. Session, through his login.
Store each passing App's key with gh secret set <NAME> --env <environment> -R combsmith/software-factory. Maintainer alone.
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.
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.
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:
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.
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.
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:
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
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.
Oversight session: after step 1, read only each secret's name, date and branch policy, for the pull request's body.
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.
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.
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.
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.
Activity
don-petry commentedon Oct 14, 2026
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.queueinconfig/apps.jsonwaits on it.don-petry commentedon Oct 18, 2026
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/repositoriesfor 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_idis the data's; the permissions and their levels are exactly the data's, no more and no fewer, and the same registered and installed;eventsis empty; the selection isselected; and the repos are exactlyinstalled_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.jsongives each App anapp_idand akeyof{environment, secret}.tools/check_apps.pycompares each App's permissions and levels with what P-39 found installed for thatapp_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-pollerholdsCONTROLLER_APP_KEY,factory-applyholdsPR_REVIEW_APP_KEYandfactory-runholdsREADER_APP_KEY. Each has a deployment branch policy ofmainalone, admin bypass off, and no required reviewer. Why: a job that names an environment reads all its secrets, so one environment would handrun, 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'swaits_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:
factory-poller,factory-applyandfactory-runon combsmith/software-factory: branch policymain, admin bypass off. Session, through his login.gh secret set <NAME> --env <environment> -R combsmith/software-factory. Maintainer alone.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
ghlogin. Writes 1 and 3 (the reach script and every key) stay the Maintainer's. Write 4 is the pull request's review.don-petry commentedon Oct 18, 2026
Decision record renumbered to 0033: #158 needs none, so the numbers close up (#42 0031, #160 0032, #26 0033).
don-petry commentedon Oct 18, 2026
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-appsstill says 0033 and gets renumbered then. The record's content and the rulings above are unchanged.Decision 0034 becomes 0033: P-13 takes 0033, and #26 takes 0034 when …
don-petry commentedon Oct 19, 2026
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
mainalone, admin bypass off and no required reviewer. None holds a secret yet.factory-pollerCONTROLLER_APP_KEYfactory-applyPR_REVIEW_APP_KEYfactory-runREADER_APP_KEYP-39 so far. The grants reads passed:
trials/P-39.grants.jsonis ontrials/p-39-apps, at d19bdfd. All four installations areselectedand 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}/repositoriesanswers 403), so the reach read stays with the Maintainer, as ruling 2 says.Next, in order:
tools/trial_p39_reach.shfrom a checkout oftrials/p-39-apps, with a new OUT directory outside the repo. A session read the script: each token is narrowed tometadata: read, is never printed or written, and is revoked after one read.installed_onrepos, and each revocation must return 204. The verdict gets posted here.trials/P-39.reach.jsonfrom 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.gh secret set <NAME> --env <environment> -R combsmith/software-factory, once for each row above.don-petry commentedon Oct 19, 2026
P-39 reach: pass (2026-10-19 03:24:52Z)
The Maintainer ran
tools/trial_p39_reach.sh(branchtrials/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.GET /installation/repositoriesinstalled_oninconfig/apps.jsontotal_count2,selectedtotal_count2,selectedtotal_count2,selectedEach list fits in one page (no
linkheader). Each token was narrowed tometadata: read. With the grants reads already passed, every field ruling 3 names now agrees with the data for all three Apps.Next:
CONTROLLER_APP_KEYinfactory-pollerPR_REVIEW_APP_KEYinfactory-applyREADER_APP_KEYinfactory-runtrials/P-39.reach.jsonfrom OUT alone, then open the one pull request of ruling 8. Its decision record takes the next free number.don-petry commentedon Oct 19, 2026
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.
don-petry commentedon Oct 19, 2026
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.factory-pollerCONTROLLER_APP_KEYmain(branch)factory-applyPR_REVIEW_APP_KEYmain(branch)factory-runREADER_APP_KEYmain(branch)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.jsonbuilt from OUT, the data, the check, §6's row and decision record 0036). The delivery session builds it.don-petry commentedon Oct 19, 2026
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.
don-petry commentedon Oct 19, 2026
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.
#26 review: each App's key is held to its job's environment, and the …
#161: the record is 0036, since #26 (PR #199) takes 0035
#26 review: the data is held to every condition of P-39's pass, from …
Kernel: a Run listed before its name is unidentified and defers every…