Skip to content

feat: inventory and ratchet first-party private API access - #1078

Merged
lbliii merged 2 commits into
mainfrom
feat/first-party-private-api-ratchet
Sep 5, 2026
Merged

lbliii merged 2 commits into
mainfrom
feat/first-party-private-api-ratchet

Conversation

@lbliii

@lbliii lbliii commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Production applications currently depend on private Chirp APIs without a consistent inventory or gate for new dependencies. This adds a pinned five-application ledger covering 51 production accesses across 45 identities, with owners, rationale, and follow-ups, plus a static ratchet that rejects new unclassified production access.

The scanner follows import provenance, annotations, aliases, and reviewed receiver hints. Offline fixtures and ledger validation run in ordinary CI; a separate path-scoped workflow parses all five pinned downstream repositories without installing or executing them. Dynamic names and control-flow-dependent rebinding remain explicit manual-review boundaries.

Validation: 31 focused tests pass with 89% scanner coverage; all-repository Ruff and formatting pass; all five pinned source audits pass, including a clean Pidge checkout.

Closes #1053
Advances-Epic #1052
Acceptance #1053: tests/test_private_api_ratchet.py uses @pytest.mark.issue(1053); committed source pins and owner/follow-up mappings document the audited production boundary.

Copilot AI lite review requested due to automatic review settings September 5, 2026 13:41
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Hypermedia surface change (examples.chirpui.forum_shell.app:app vs origin/main)

No contract issue changes.

Automated by chirp diff. New contract ERRORs block merge; new warnings are advisory.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The scanner currently constructs invalid AST nodes (missing ast.Name.ctx) and ledger validation can crash on malformed entries, which would break the ratchet’s reliability in CI.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a maintained inventory and CI-enforced ratchet for first-party production usage of Chirp private APIs, backed by a pinned downstream audit ledger and an offline static scanner (no downstream code execution).

Changes:

  • Introduce an AST-based scanner + ratchet script (scripts/private_api_ratchet.py) and a pinned five-repository ledger (scripts/private_api_ledger.json).
  • Add focused tests proving provenance tracking, boundaries, and failure modes (tests/test_private_api_ratchet.py).
  • Wire validation into standard CI and add a path-scoped workflow that checks out and parses the pinned downstream repos (.github/workflows/private-api-audit.yml).
File summaries
File Description
tests/test_private_api_ratchet.py Adds offline fixtures covering scanner provenance, dynamic attribute detection, receiver hint behavior, and ratchet failures.
scripts/private_api_ratchet.py Implements the static scanner, ledger validation, and per-repo ratchet checks with a CLI surface for CI/workflows.
scripts/private_api_ledger.json Adds the pinned 5-app ledger with classifications, ownership, rationale, follow-ups, counts, and source links.
docs/design/first-party-private-api-inventory.md Documents the inventory/ratchet intent, workflow model, and scanner boundaries/review process.
changelog.d/1053.added.md Changelog entry for the new ledger + ratchet + audit workflow.
.github/workflows/private-api-audit.yml Adds a path-scoped workflow that checks out pinned downstream revisions and runs the ratchet.
.github/workflows/ci.yml Adds a CI step to validate the private API ledger via the ratchet script.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +191 to +192
for arg in [*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs]:
self._bind(ast.Name(id=arg.arg), self.origin(arg.annotation))
Comment on lines +290 to +300
prefix = f"https://github.com/{repository['repository']}/blob/{revision}/"
if not finding.get("source", "").startswith(prefix):
errors.append(f"{name}: {key}: source must link the pinned revision.")
if finding.get("classification") == "test-only" and not _test_path(key.split("|")[0]):
errors.append(
f"{name}: production access cannot use test-only classification: {key}."
)
if type(finding.get("count")) is not int or finding["count"] < 1:
errors.append(f"{name}: {key}: count must be positive.")
prefix = f"https://github.com/{repository['repository']}/blob/{revision}/"
errors.extend(
Comment on lines +319 to +322
def check_repository(root: Path, repository: dict[str, Any]) -> list[str]:
findings = scan_repository(root, repository)
expected = {row["key"]: row for row in repository["findings"]}
counts = Counter(row.key for row in findings if not _test_path(row.path))
Copilot AI review requested due to automatic review settings September 5, 2026 13:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new ratchet script has a few robustness/runtime hazards (notably AST node construction and validation paths that can raise KeyError) that should be fixed before relying on it in CI.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (5)

scripts/private_api_ratchet.py:192

  • ast.Name nodes require a ctx field in the stdlib AST API; constructing ast.Name(id=...) can raise a TypeError at runtime on many Python versions (and is unnecessary here). Pass an explicit ast.Load() context so argument-binding provenance scanning can’t crash.
        for arg in [*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs]:
            self._bind(ast.Name(id=arg.arg), self.origin(arg.annotation))

scripts/private_api_ratchet.py:292

  • validate_ledger() indexes repository['repository'] while still in the validation phase; a malformed ledger will raise KeyError instead of returning actionable validation errors. Use .get(...) here so validation stays non-throwing.
            prefix = f"https://github.com/{repository['repository']}/blob/{revision}/"
            if not finding.get("source", "").startswith(prefix):
                errors.append(f"{name}: {key}: source must link the pinned revision.")

scripts/private_api_ratchet.py:299

  • Same KeyError risk as above: validate_ledger() builds a prefix via repository['repository'] while validating. Using .get(...) keeps validation robust for partially-edited ledgers.
        prefix = f"https://github.com/{repository['repository']}/blob/{revision}/"

scripts/private_api_ratchet.py:322

  • check_repository() assumes repository['findings'] exists; if the ledger is missing that key (or is mid-edit), this will crash instead of producing a clear validation error. Prefer repository.get('findings', []) and let validate_ledger() be the authoritative gate.
def check_repository(root: Path, repository: dict[str, Any]) -> list[str]:
    findings = scan_repository(root, repository)
    expected = {row["key"]: row for row in repository["findings"]}
    counts = Counter(row.key for row in findings if not _test_path(row.path))

scripts/private_api_ratchet.py:379

  • --matrix output still iterates ledger['repositories'] directly; if the ledger is missing that key, this will crash. Use the already-derived repositories mapping (or ledger.get(...)) so --matrix stays non-throwing and can show ledger validation errors cleanly.
    if args.matrix:
        print(
            json.dumps(
                {
                    "include": [
                        {
                            "name": name,
                            "repository": repo["repository"],
                            "revision": repo["revision"],
                        }
                        for name, repo in ledger["repositories"].items()
                    ]
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +346 to +353
ledger = json.loads(args.ledger.read_text())
errors = validate_ledger(ledger)
for spec in args.repo:
name, separator, directory = spec.partition("=")
if not separator or name not in ledger["repositories"]:
errors.append(f"Unknown repository mapping {spec!r}; use NAME=PATH from the ledger.")
continue
repository = ledger["repositories"][name]
@lbliii
lbliii merged commit 0d38dad into main Sep 5, 2026
51 checks passed
@lbliii
lbliii deleted the feat/first-party-private-api-ratchet branch September 5, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1] Inventory first-party private API use and add a production-path ratchet

2 participants