Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e7c776a
test: define fail-closed workflow registry audit contract
seonghobae Aug 14, 2026
578b101
feat: add fail-closed workflow registry audit
seonghobae Aug 14, 2026
59b6a94
fix: keep workflow audit token on fixed GitHub API
seonghobae Aug 14, 2026
be28004
test: require protected-ref movement detection
seonghobae Aug 14, 2026
f84b5c6
fix: fail closed when protected ref moves
seonghobae Aug 14, 2026
6f6eaa0
test: lock authenticated audit reads to GitHub API
seonghobae Aug 14, 2026
a4f9dc7
test(workflows): expose same-count pagination drift
seonghobae Aug 14, 2026
8d0c6ed
fix(workflows): verify multi-page registry stability
seonghobae Aug 14, 2026
d1b8449
test(workflows): verify stable multi-page registry pass
seonghobae Aug 14, 2026
c33759e
test(workflows): require fixed HTTPS transport boundary
seonghobae Aug 14, 2026
580b757
fix(workflows): pin audit transport to HTTPS host
seonghobae Aug 14, 2026
93ba3ba
test(workflows): require fixed aiohttp origin boundary
seonghobae Aug 14, 2026
ab948ba
fix(workflows): use fixed-origin aiohttp transport
seonghobae Aug 14, 2026
3c6ae8a
test(workflows): expose commit-tree identity mismatch
seonghobae Aug 14, 2026
df2d4de
fix(workflows): resolve commit tree before registry audit
seonghobae Aug 14, 2026
fabaad7
test(workflows): separate protected commit and tree fixtures
seonghobae Aug 14, 2026
4dd0979
test(workflows): resolve protected commit tree in ref movement
seonghobae Aug 14, 2026
1514b16
test(workflows): resolve commit tree in stability fixtures
seonghobae Aug 14, 2026
1197fd4
test(workflows): expose rate-limit diagnostic ambiguity
seonghobae Aug 14, 2026
9d54970
fix(workflows): classify GitHub rate-limit failures
seonghobae Aug 14, 2026
a2920a5
test(workflows): prove slash ref path regression
seonghobae Aug 14, 2026
c2ca8b5
fix(workflows): preserve slash refs in GitHub paths
seonghobae Aug 14, 2026
8a1215c
test(workflows): reproduce dynamic registry identity gap
seonghobae Aug 14, 2026
7aa1ab9
fix(workflows): classify dynamic registry identities safely
seonghobae Aug 14, 2026
3cd6896
test(workflows): reproduce protected-ref namespace confusion
seonghobae Aug 14, 2026
5faf4be
fix(workflows): reject protected-ref namespace prefixes
seonghobae Aug 14, 2026
7618fc4
test(workflows): reject nonfinite audit timeouts
seonghobae Aug 15, 2026
2f6788c
fix(workflows): require finite audit timeout
seonghobae Aug 15, 2026
842610c
test(security): reject repository dot segments before transport
seonghobae Aug 15, 2026
bdb0fb3
fix(security): reject repository dot segments
seonghobae Aug 15, 2026
a50d803
test(audit): cover negative infinite timeout
seonghobae Aug 15, 2026
5c7c3e2
test(workflows): prove GitHub audit response is unbounded
seonghobae Aug 15, 2026
8770ad8
fix(workflows): bound GitHub audit response memory
seonghobae Aug 15, 2026
c54978c
test(workflows): exercise bounded aiohttp response stream
seonghobae Aug 15, 2026
09fc0de
test(workflows): prove recursive JSON fails closed
seonghobae Aug 15, 2026
5defb01
fix(workflows): normalize recursive JSON failure
seonghobae Aug 15, 2026
156f134
test(workflows): bound registry pagination cardinality
seonghobae Aug 15, 2026
77732cd
fix(workflows): bound registry audit cardinality
seonghobae Aug 15, 2026
150b71d
test(workflows): reject unknown workflow registry state
seonghobae Aug 15, 2026
6a48171
fix(workflows): reject unknown workflow registry states
seonghobae Aug 15, 2026
ca65227
test(workflows): require exact registry primitive types
seonghobae Aug 16, 2026
6140e45
test(workflows): reject hostile registry mappings
seonghobae Aug 16, 2026
124e5b3
fix(workflows): bound malformed registry primitive types
seonghobae Aug 16, 2026
7a71400
test(workflows): reject hostile audit boundary subclasses
seonghobae Aug 16, 2026
b336272
revert(workflows): restore pre-run audit branch after blocked fix
seonghobae Aug 16, 2026
4e87d9e
test(workflows): reject hostile audit boundary subclasses
seonghobae Aug 16, 2026
1127c75
fix(workflows): reject hostile audit boundary subclasses
seonghobae Aug 16, 2026
cc4362a
Merge branch 'main' into fix/workflow-registry-audit-d0a4b30
opencode-agent[bot] Aug 16, 2026
4b1ca74
feat(workflows): package fail-closed registry audit
cursoragent Aug 16, 2026
5957c3b
test(workflows): bound audit receipt timestamps
seonghobae Aug 16, 2026
0e3fa51
fix(workflows): validate canonical audit timestamps
seonghobae Aug 16, 2026
0714188
test(workflows): require collision-free registry-audit ADR
cursoragent Aug 16, 2026
84866b5
fix(workflows): allocate ADR 0021 after recovery inventory
cursoragent Aug 16, 2026
a3ccb8c
Merge branch 'main' into cursor/bc-df73ecc5-7f76-47cd-9c06-483e539fc6…
seonghobae Aug 16, 2026
eef4641
fix(workflow-audit): reconcile detector onto current protected main
seonghobae Sep 12, 2026
46a34ec
fix(workflow-audit): reject RFC3339 hour 24 across Python versions
seonghobae Sep 12, 2026
e3ea237
chore(pr222): reconcile workflow auditor through recovery root
seonghobae Sep 12, 2026
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
106 changes: 106 additions & 0 deletions docs/adr/0021-workflow-registry-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ADR 0021: Read-only exact-SHA workflow registry audit

- **Status:** Proposed
- **Date:** 2026-08-16
- **Reconciled:** 2026-09-12

## Context

GitHub Actions can retain active workflow identities after their repository
YAML is removed, or can register platform-managed `dynamic/` identities that
never had a protected-tree blob. Operators need a fail-closed way to list those
identities against one immutable protected commit. Automatic disable, rerun, or
YAML rewrite would change production control-plane state from a detector.

GitHub's Git database and Actions registry are separate evidence sources. A
tree response `sha` is a tree object identity, not a commit SHA. A multi-page
registry read can splice two registry generations even when `total_count` is
unchanged. JSON subclasses can lie about equality if identity members are
compared before an exact-type check.

The original proposal reserved ADR 0021 while recovery decisions 0016-0020
were still on contributor branches. Protected `main` now contains those
recovery decisions and ADR 0022 explicitly records 0021 as the workflow-audit
decision reserved by #222. There is still no protected `0021-*` file. This
current-base reconciliation therefore keeps 0021 instead of renumbering the
decision or creating a duplicate prefix. The status remains Proposed until the
bounded detector is normally integrated through protected governance.

NIST SP 800-218 (SSDF) and SLSA v1.0 treat build-system integrity as a
provenance control: verify the source that is supposed to produce automation
before changing that automation. in-toto's farm-to-table argument is the same
shape: bind an observation to an exact artifact identity rather than to a
moving name. Receipt timestamps follow the RFC 3339 UTC profile so an operator
or library caller cannot inject an unbounded or non-calendar value into
otherwise successful evidence.

## Decision

`pg_llm_batch.workflow_registry_audit` is proposed as a packaged, installable,
read-only auditor.

1. Transport is GET-only to `https://api.github.com`, path-only, no redirects,
finite timeout, default TLS verification, no automatic retry.
2. The CLI always verifies the live protected ref before and after the SHA
audit. Library callers may classify one SHA without live-ref checks.
3. Commit objects are resolved first; tree reads use `commit.tree.sha`.
4. Multi-page registries require a second identical pass.
5. Active repository-backed paths absent from the exact tree are candidates
only. `dynamic/` identities are receipted and never orphaned.
6. Exact decoder types are required for containers and for identity members
before any `==` / `!=`.
7. Optional caller `captured_at` values must be exact built-in strings in the
finite canonical UTC RFC 3339 shape `YYYY-MM-DDTHH:MM:SSZ` with a real
calendar instant. Invalid, non-UTC, non-canonical, or oversized values fail
before any GitHub read. Omitted values are generated by the auditor clock.
8. Exit codes are `0` (clean), `1` (failed closed), and `2` (candidates).
9. The tool never disables, enables, edits, or reruns workflows.

## Consequences

After normal protected integration and package publication, operators can run
`pg-llm-batch-workflow-audit` without a special `PYTHONPATH`. Until then, the
command and this ADR are contributor-branch evidence only and must not be
presented as released product behavior.

The detector does not replace branch protection, required reviews, or a human
decision to disable a workflow. False confidence from a stale PR body or from
comparing a tree SHA to a commit SHA is rejected.

Canonical root README, architecture and changelog text are owned by their live
documentation lanes. This source lane carries only the bounded ADR/doctoring
contract needed to review the detector; public-document convergence follows
through those owners after source ancestry is stable.

## Rollback

Rollback is code-only: remove the package module, console-script entry,
checkout shim, ADR, doctoring record, and focused regressions. No database
migration or Actions mutation is required. Adding mutation to this tool
requires a separate reviewed control-plane contract.

## References

GitHub. (n.d.). *REST API endpoints for GitHub Actions workflows*. GitHub Docs.
Retrieved August 16, 2026, from
https://docs.github.com/en/rest/actions/workflows

GitHub. (n.d.). *REST API endpoints for Git trees*. GitHub Docs. Retrieved
August 16, 2026, from
https://docs.github.com/en/rest/git/trees

Klyne, G., & Newman, C. (2002). *Date and time on the internet: Timestamps*
(RFC 3339). https://doi.org/10.17487/RFC3339

National Institute of Standards and Technology. (2022). *Secure software
development framework (SSDF) version 1.1: Recommendations for mitigating the
risk of software vulnerabilities* (NIST SP 800-218).
https://doi.org/10.6028/NIST.SP.800-218

The Linux Foundation. (2023). *Supply-chain Levels for Software Artifacts
(SLSA) v1.0*. https://slsa.dev/spec/v1.0/

Torres-Arias, S., Afzali, H., Kuppusamy, T. K., Curtmola, R., & Cappos, J.
(2019). in-toto: Providing farm-to-table guarantees for bits and bytes. In
*Proceedings of the 28th USENIX Security Symposium* (pp. 1393–1410). USENIX
Association. https://www.usenix.org/conference/usenixsecurity19/presentation/torres-arias
152 changes: 152 additions & 0 deletions docs/doctoring/workflow-registry-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Workflow registry audit doctoring

## Purpose

This record describes the proposed read-only detector for GitHub Actions
identities that are active in a repository registry but absent from an exact
protected source tree, without disabling, editing, rerunning, or recreating any
workflow.

The detector is contributor-branch capability until it reaches protected main
through normal governance and is included in an accepted package/release.
After that integration, operators can use the installed command instead of a
checkout-root shim. Do not describe the command as released merely because the
branch tests or package build succeed.

```bash
export GITHUB_TOKEN="$(gh auth token)" # contents:read + actions:read is enough
pg-llm-batch-workflow-audit \
--repository ContextualWisdomLab/pg-llm-batch \
--protected-ref main \
--protected-sha "$(git rev-parse origin/main)"
```

Equivalent module form:

```bash
python -m pg_llm_batch.workflow_registry_audit \
--repository ContextualWisdomLab/pg-llm-batch \
--protected-ref main \
--protected-sha "$(git rev-parse origin/main)"
```

## What to do with the result

- Exit `0`: the live protected ref still matches the supplied SHA, the registry
was stable across a complete pagination pass, and no active
repository-backed identity is missing from that exact tree. Keep operating.
- Exit `2`: stdout is a JSON receipt whose `active_absent_workflows` lists
candidates only. Open a separate operator review. Do not disable those
workflows from this tool.
- Exit `1`: the audit failed closed. Read the single stderr line, then correct
the repository selector, SHA, ref, token scope, rate-limit budget, or
truncated-tree condition and run the same command again.

GitHub-managed `dynamic/` identities appear in `workflow_records` with
`source_kind=platform_dynamic` and `source_present=null`. They are never
orphans. Do not delete platform-managed identities because they lack a
`.github/workflows/` blob.

The library function `audit_repository_workflows` can classify one exact SHA
without live-ref pre/post checks. The CLI always uses
`audit_live_protected_ref_workflows` so a moving protected branch cannot
certify a stale head.

If a library caller supplies `captured_at`, pass one exact built-in string in
the canonical UTC RFC 3339 shape `YYYY-MM-DDTHH:MM:SSZ` with a real calendar
instant. Empty strings, offsets such as `+00:00`, fractional seconds, lowercase
`z`, invalid dates, and oversized values fail before any GitHub read. Omit the
argument to let the auditor write the clock instant. See ADR 0021.

## Trust and security boundary

The client sends path-only GET requests to the fixed origin
`https://api.github.com`, disables redirects, and uses the default TLS
verifier. It never retries automatically. Non-success bodies are not read.
Rate-limit evidence is limited to HTTP 429 or HTTP 403 plus
`X-RateLimit-Remaining: 0` or `Retry-After`.

JSON objects, arrays, strings, integers, and booleans are accepted only as
exact decoder built-in types. Identity members (`ref`, commit `sha`, tree
`sha`) are type-checked before equality. A hostile subclass cannot certify the
caller SHA while resolving a different tree.

`GITHUB_TOKEN` is used only as an `Authorization` header. It is never copied
into stdout, stderr, receipts, or exception cause chains.

This tool does not select tenant scope, touch PostgreSQL, or mutate Actions
state. It is a read-only control-plane detector for this repository and for
hosts that embed `pg-llm-batch` as a module.

## Documentation ownership

ADR 0021 and this doctoring record are the bounded source-lane contract.
Canonical root README, architecture, changelog, PRD/TRD and product-gap text
have separate active writers and are deliberately not rewritten by the current
reconciliation. Once the source lane is stable, those owners must converge any
buyer-facing documentation before protected integration or release claims.

## Recovery and rollback

No schema, secret, or workflow mutation is performed. Recovery is to rerun the
same read after correcting the failed input or GitHub condition. Rollback is
removing the console-script entry, package module, checkout shim, ADR, this
record, and focused regressions. Reintroducing workflow disable/enable from
this tool is not a routine rollback step.

## Verification

Permanent regression coverage requires that:

- hostile `str` subclasses cannot satisfy commit, tree, or ref identity by
lying about equality;
- raising identity subclasses become `WorkflowRegistryAuditError` without
leaking custom exception text;
- `dynamic/` identities are receipted and never orphaned;
- unknown workflow states fail closed;
- multi-page registries are accepted only after a second identical pass;
- truncated trees, moving protected refs, and oversize responses fail closed;
- the console script `pg-llm-batch-workflow-audit` is declared in package
metadata;
- ADR 0021 and this doctoring record tell the operator to review candidates
instead of disabling workflows; and
- caller-supplied receipt timestamps are rejected unless they are finite
canonical UTC RFC 3339 values.

## References

GitHub. (n.d.). *REST API endpoints for GitHub Actions workflows*. GitHub Docs.
Retrieved August 16, 2026, from
https://docs.github.com/en/rest/actions/workflows

GitHub. (n.d.). *REST API endpoints for Git commits*. GitHub Docs. Retrieved
August 16, 2026, from
https://docs.github.com/en/rest/git/commits

GitHub. (n.d.). *REST API endpoints for Git trees*. GitHub Docs. Retrieved
August 16, 2026, from
https://docs.github.com/en/rest/git/trees

GitHub. (n.d.). *REST API endpoints for Git references*. GitHub Docs. Retrieved
August 16, 2026, from
https://docs.github.com/en/rest/git/refs

GitHub. (n.d.). *Rate limits for the REST API*. GitHub Docs. Retrieved August
16, 2026, from
https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api

Klyne, G., & Newman, C. (2002). *Date and time on the internet: Timestamps*
(RFC 3339). https://doi.org/10.17487/RFC3339

National Institute of Standards and Technology. (2022). *Secure software
development framework (SSDF) version 1.1: Recommendations for mitigating the
risk of software vulnerabilities* (NIST SP 800-218).
https://doi.org/10.6028/NIST.SP.800-218

The Linux Foundation. (2023). *Supply-chain Levels for Software Artifacts
(SLSA) v1.0*. https://slsa.dev/spec/v1.0/

Torres-Arias, S., Afzali, H., Kuppusamy, T. K., Curtmola, R., & Cappos, J.
(2019). in-toto: Providing farm-to-table guarantees for bits and bytes. In
*Proceedings of the 28th USENIX Security Symposium* (pp. 1393–1410). USENIX
Association. https://www.usenix.org/conference/usenixsecurity19/presentation/torres-arias
Loading
Loading