Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/evidence/g01-broker.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The historical unversioned broker approval schema included `discover-actions-hos

Runner-group and private repository inspection require an installation/user authority; App JWT alone cannot perform those checks. The agreed executable sequence is therefore:

0. Validate private inputs and the supported native controller build; reserve, write and sync the exact non-secret controller snapshot when applicable. Acquire the fixed account ledger, pin resources/controller state, consume this phase slot durably, and check compatible existing controller inventory. For controller mode, run the credential-free canonical local-state preparation and validate its exact receipt before any authenticated request. These local checks precede every API call.
0. Validate private inputs and the supported native controller build; open and durably prepare the exact per-attempt journal. For controller/paired modes, before claiming a phase slot, hold the fixed account-ledger lock, append the non-secret workflow-provenance GET intent to the attempt journal, and sync `workflow_provenance_get_started` to the account ledger. Perform the authenticated workflow-run GET under that lock; append the validated observation to the attempt journal and sync `workflow_provenance_get_confirmed` before releasing the lock. Only after that confirmation does the broker write/sync the controller snapshot, consume the phase slot durably and run credential-free local preparation. These local checks precede every later API call. On ledger replay, each historical reusable-callee claim is also checked against its confirmed GET event: slot, attempt and journal identities, controller-approval digest, provenance source, workflow run ID, ref, SHA and path must all match before recovery can reuse the claim.
1. Verify App, owner and organization installation with the App JWT.
2. Flush a non-secret token issuance intent.
3. Flush the non-secret read-token intent, then immediately before dispatching the installation-token POST, sync `read_token_lifecycle_started` to the fixed account admission ledger. If that sync cannot be established, do not send the request. When a v3 attempt exits before this boundary, sync `read_token_request_not_dispatched`; this preserves later recovery without claiming a request happened. Request a token restricted to `repository_ids: [exact_canary_id]` and exactly `organization_self_hosted_runners: read` plus `metadata: read`. The organization-only scope requests only `organization_self_hosted_runners: read` and no repository IDs. If the response exposes a syntactically valid token but fails typed decoding, expiry or exact-scope validation, do not use it for provider reads; make one self-revocation attempt under the already-durable lifecycle intent. If the returned read token equals the caller workflow-verification token, record the collision and stop before provider-state preflight or DELETE, leaving the lifecycle unresolved.
Expand All @@ -35,7 +35,7 @@ Runner-group and private repository inspection require an installation/user auth

Repository restriction does **not** cryptographically restrict the organization-level self-hosted-runners permission to one runner group. The broker and reviewed controller enforce the selected canary policy, under the trusted-controller assumption. This boundary assumes a trusted controller UID and host administrators; file modes and a stdin pipe do not isolate either. The read-only preflight token is issued by the existing App; no separate user or organization credential is requested. The broker never adds Actions permission to the G02 App or mints the separate workflow authority.

A failed/ambiguous token request, failed policy check, expired credential, uncertain handoff or error leaves the intent and stops. This account-wide start is written before the POST can be dispatched, so a lost success response remains an unresolved lifecycle even when no token candidate is available to revoke. The v3 reusable-callee route makes one self-revocation attempt for each usable read-token candidate, including a token rejected for expiry or scope, and does not run provider preflight for rejected issuance. The per-attempt journal records `read_only_token_revocation_started` before DELETE. A caller-token collision records `read_only_token_revocation_skipped` and stops without a provider request, leaving the account intent pending. After a successful DELETE and a durable per-attempt `read_only_token_revoked` record, the broker syncs `read_token_lifecycle_confirmed` to the account ledger. If the per-attempt success record or the account confirmation cannot be synced, a best-effort `read_only_token_revocation_uncertain` record is attempted and the unmatched lifecycle intent blocks every later phase, including `inspect` and `cleanup`, before another authenticated request, management-token issuance or child handoff. An incomplete legacy reusable-callee claim without lifecycle or no-dispatch evidence also fails closed because its earlier POST outcome cannot be reconstructed. Correctly paired historical `read_token_revocation_started` / `read_token_revocation_confirmed` records remain accepted as completed history. The next open re-syncs the ledger before interpreting a visible confirmation. There is no automatic retry or token refresh/replacement. An uncertain revocation may leave the read token valid until expiry. A management token issued before a later failure may also remain valid until expiry; it is not automatically revoked. A future operator approval must include both potential issuances and the read-token revocation effect, not just the eventual read/phase. The maximum remaining operation lifetime is ten minutes, approval expiry, or one minute before the management installation-token expiry, whichever occurs first.
A failed/ambiguous token request, failed policy check, expired credential, uncertain handoff or error leaves the intent and stops. The initial authenticated workflow-run GET has the same account-wide uncertainty rule: its per-attempt journal intent and account-ledger start precede dispatch, and the fixed account lock remains held through observation and confirmation. A timeout, transport failure, lost response, crash-visible start or failed confirmation is ambiguous; it leaves `workflow_provenance_get_started` pending and blocks every later phase, including `inspect` and `cleanup`, before another workflow GET, installation-token issuance or child handoff. Only a failure proven before HTTP dispatch may append matching per-attempt and account `workflow_provenance_get_not_dispatched`; that explicit outcome permits a later different recovery slot, but the consumed attempt and exact same phase are not automatically retried. The v3 reusable-callee route makes one self-revocation attempt for each usable read-token candidate, including a token rejected for expiry or scope, and does not run provider preflight for rejected issuance. The per-attempt journal records `read_only_token_revocation_started` before DELETE. A caller-token collision records `read_only_token_revocation_skipped` and stops without a provider request, leaving the account intent pending. After a successful DELETE and a durable per-attempt `read_only_token_revoked` record, the broker syncs `read_token_lifecycle_confirmed` to the account ledger. If the per-attempt success record or the account confirmation cannot be synced, a best-effort `read_only_token_revocation_uncertain` record is attempted and the unmatched lifecycle intent blocks every later phase, including `inspect` and `cleanup`, before another authenticated request, management-token issuance or child handoff. An incomplete legacy reusable-callee claim without workflow/read lifecycle or no-dispatch evidence also fails closed because its earlier outcomes cannot be reconstructed. Correctly paired historical `read_token_revocation_started` / `read_token_revocation_confirmed` records remain accepted as completed history. The next open re-syncs the ledger before interpreting a visible confirmation. There is no automatic retry or token refresh/replacement. An uncertain workflow observation or revocation may leave the corresponding authority valid until expiry. A management token issued before a later failure may also remain valid until expiry; it is not automatically revoked. A future operator approval must include both potential issuances and the workflow/read-token effects, not just the eventual read/phase. The maximum remaining operation lifetime is ten minutes, approval expiry, or one minute before the management installation-token expiry, whichever occurs first.

The self-revocation request uses the [GitHub App installation-token revocation endpoint](https://docs.github.com/en/rest/apps/installations#revoke-an-installation-access-token), which accepts an installation access token and requires no additional permissions. This API contract is official documentation; fixture evidence does not prove live GitHub behavior for the selected installation.

Expand Down
97 changes: 97 additions & 0 deletions docs/evidence/g01-live-canary.md
Original file line number Diff line number Diff line change
Expand Up @@ -829,3 +829,100 @@ roots, broker-only execution, management-token lifecycle, conditional Scale Set
deletion, trusted runtime/isolation/resource qualification, the authorized
Linux ARM64/JIT canary and sanitized live exit evidence remain open. Issue #1
stays OPEN and Project #2 stays In progress.

### Workflow-run provenance GET accounting fence — 2026-11-04

Issue #160 starts from the exact local base `a4d6f03b81cbc77f51bf9eb0d37fdf47d67c02f3`.
The initial authenticated workflow-run GET now has a two-record lifecycle in
the fixed account ledger, bound to the already-open per-attempt journal,
attempt/journal device+inode, controller-approval digest, source and exact run
identity. The per-attempt intent is durable first; the account start is synced
before dispatch; the account confirmation follows the validated GET and durable
per-attempt observation while the account lock remains held. Timeout, transport
failure, lost response, crash-visible start and confirmation failure remain
pending and block later replay before any installation-token mint. Only an
explicitly durable pre-dispatch outcome permits a later different recovery
slot; the consumed attempt and same phase are not automatically retried.

The causal fixture records the workflow GET itself in its ordered event stream,
including the synthetic lost-response and timeout cases, while its API fixture
rejects any installation-token mint before the workflow fence. A journal-boundary
failure records `workflow_provenance_get_not_dispatched` and permits a later
inspect; ambiguous GET outcomes leave `workflow_provenance_get_started` without
confirmation and block the inspect before any API call.

Historical local offline verification at exact candidate head
`6950a80639931e497b55e665049af4d4956eacdf` used the cached Go 1.27.3 toolchain
with external GOCACHE/GOMODCACHE/GOTMPDIR and downloads disabled:

The RED fixture files were applied to a clean archive of exact base
`a4d6f03b81cbc77f51bf9eb0d37fdf47d67c02f3`; no implementation files were
changed in that archive. The identical selector was rerun on a fresh archive
of the same base. Both runs showed no account workflow intent, then `inspect`
completed and minted twice.

| Check | Result |
|---|---|
| `go test -race -count=1 -timeout=120s -run '^TestBrokerExperimentV3LostWorkflowProvenanceResponseBlocksLaterInspect$' .` on exact base | FAIL as expected; `inspect` completed (`controller_completed`) and minted twice |
| Same RED command on a fresh archive of exact base | Same failure and behavior |
| Focused lost-response, timeout, no-dispatch and replay tests on candidate | PASS (4.573s) |
| G02 `go test -race -p=1 -count=1 -timeout=300s ./...` | PASS (all packages; library 138.676s) |
| G02 `go vet -p=1 ./...` | PASS |
| Provider/credential/workflow/runner/Scale Set/Docker/host/live-canary actions | Not run |

These are synthetic offline fixture results only. No provider credential or
installation token was minted, no workflow or runner was invoked, and no live
canary operation was attempted. At this historical `6950a80` candidate,
independent exact-head review and hosted quick checks were still pending; the
later PR #161 status is recorded below. All live authorization gates remain
unresolved.

### Historical workflow-provenance claim replay binding — 2026-11-04

Independent Luna Max review of PR #161 at exact head
`b9ca7c96c7881391a1cc9ed6f06b6a7848e9f1b4` found a P2 replay-binding gap:
historical reusable-callee claims required a confirmed workflow GET record to
exist, but did not require that record to match the claim's authority and
workflow identity. A test-only overlay on a clean archive of that exact source
reproduced the issue: after changing the historical event's attempt/journal,
approval digest and workflow identity while retaining the claim, `inspect`
completed and minted twice.

Commit `eefe8dfde5134b014b54ca45277640c6401c0395` fixes replay by matching the
claim to the confirmed record across slot, attempt and journal identities,
controller-approval digest, provenance source, workflow run ID, ref, SHA and
path. The exact-base RED was reproduced twice; the corrected focused race test
passed in 4.498s, the full G02 race suite passed all packages (root package
143.977s), and `go vet -p=1 ./...` passed. These checks were offline and used
the cached Go 1.27.3 toolchain with caches and temporary files configured on an
external volume.

The corrected code and initial regression test were pushed as PR head
`73c1299319441da6499dca55b79c237a3df5e4e2`; hosted Go checks passed and GitHub
Codex review completed on that exact SHA with no major findings. The subsequent
independent Luna Max review returned HOLD/P2: the regression changed several
binding fields together, lacked isolated slot/source cases and did not name a
matching-history positive control. The reviewer found the source comparisons
correct and reported no P0/P1 source defect. A follow-up test-only change now
mutates slot, attempt, journal, approval digest, source, run ID, ref, SHA and
path independently, checks each rejection before API calls or minting, and
includes a named matching-history replay control. On a clean archive of exact
prior head `b9ca7c96c7881391a1cc9ed6f06b6a7848e9f1b4` with only this test file
overlaid, the focused race test reproduced unsafe replay for each mismatched
field except slot (which already failed closed); the matching-history control
passed. The same focused race selector passes on the corrected local source,
covering all nine mismatch cases and the positive control in 7.673s.

The test refinement was committed and pushed in PR head
`f0b4e97d323a0dc637dfe6cff9c74fa62ca1d922`; hosted Go checks passed. At the
time of the independent Luna Max re-review, the exact-head Codex review was
still running. Luna Max confirmed the P2 binding-test gap was resolved and no
source P0/P1/P2 finding remained, but held overall because this section still
described the pushed candidate as local and pending. The historical `6950a80`
pending-review statements above are now explicitly labeled as such. At the time
this review result arrived, the documentation correction was committed locally
but not pushed; the follow-up PR head still required a hosted check
and fresh exact-head Codex/Luna Max reviews. No live GitHub provider,
credential, runner, Scale Set, Docker or host operation was performed. Issue
#160 and parent issue #1 remain OPEN / Project #2 In progress; all live canary
and production qualification gates remain unresolved.
25 changes: 21 additions & 4 deletions experiments/g02-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,27 @@ Controller and paired-terminal entrypoints require a one-shot signed
`BrokerProvenanceReceipt` before reading credential input. Its Ed25519 payload
binds the controller-approval SHA-256, workflow run ID, workflow ref and
commit, selected outer phase, owner nonce, receipt nonce, and named source.
The admission ledger retains the receipt digest, receipt nonce, and source so a
completed receipt cannot be replayed for another claim; direct regular-file,
FIFO, or tagged-entry paths without a receipt stop before credential input or
GitHub API effects.
The authenticated workflow-run GET that supplies the receipt is itself
accounted: the per-attempt journal records its non-secret intent, then the
fixed account admission ledger records `workflow_provenance_get_started`
before dispatch and `workflow_provenance_get_confirmed` only after the
validated observation and journal record are durable. The ledger retains the
receipt digest, receipt nonce, and source so a completed receipt cannot be
replayed for another claim; direct regular-file, FIFO, or tagged-entry paths
without a receipt stop before credential input or GitHub API effects.
During account-ledger replay, every historical reusable-callee claim must
match its confirmed GET event by slot, attempt and journal identities,
controller-approval digest, source, workflow run ID, ref, SHA and path. A
present confirmation with mismatched claim identity is rejected before API
calls or token minting.

Timeout, transport failure, lost response, crash-visible start, or failed
confirmation leaves the workflow GET intent pending and blocks later replay
before installation-token minting. Only a failure proven before HTTP dispatch
may record the matching `workflow_provenance_get_not_dispatched` outcome;
that permits a later different recovery slot, while the consumed attempt and
same phase are never automatically retried. No provider credential or
workflow action is used by these fixture tests.

The current GitHub REST response does not attest which workflow input selected
the controller phase or whether an ambiguous `head_branch` names a branch or a
Expand Down
7 changes: 4 additions & 3 deletions experiments/g02-auth/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ const organizationOnlyManagementScope = "organization-only"
func brokerSlotAllowed(slot string) bool { return brokerPhases[slot] || brokerSpecialSlots[slot] }

// One header, one claim and one completion for every finite schema slot, up to
// one read-token revocation start/confirmation pair per slot, and the final
// empty split element from the required trailing newline.
func brokerLedgerMaxLines() int { return 1 + 4*(len(brokerPhases)+len(brokerSpecialSlots)) + 1 }
// one workflow-provenance GET start/confirmation pair and one read-token
// revocation start/confirmation pair per slot, plus the final empty split
// element from the required trailing newline.
func brokerLedgerMaxLines() int { return 1 + 6*(len(brokerPhases)+len(brokerSpecialSlots)) + 1 }

const (
// Terminal collection has seven five-second cadence gaps in production.
Expand Down
Loading