Skip to content

feat(release): consume independent config source - #445

Open
AprilNEA wants to merge 2 commits into
xuan/code-561from
xuan/code-561-cross-repo-client
Open

feat(release): consume independent config source#445
AprilNEA wants to merge 2 commits into
xuan/code-561from
xuan/code-561-cross-repo-client

Conversation

@AprilNEA

Copy link
Copy Markdown
Member

Summary

  • render from fixed arcboxlabs/linkcodehq@publisherGitSha and independent arcboxlabs/linkcode-config@sourceGitSha
  • mint separate short-lived Contents read tokens before any selected client checkout, passing no GitHub App private key to client-controlled actions
  • fail closed on non-commit refs, repository redirects, source-path changes, symlinks, and schema mirror drift
  • inspect release-environment protection with the built-in GITHUB_TOKEN plus actions: read
  • remove the CONFIG_PUBLISHER_REPO, CONFIG_PUBLISHER_TOKEN, and RELEASE_ENVIRONMENT_ADMIN_TOKEN contracts
  • pin the nonproduction pilot evidence to HQ 986d9f21403df53bc932f511eb1b5f0bb634d48d and config a1ed4d666721c3aed0d563aaea42fce8b5f945b5
  • add a CI job that proves both private repositories can be checked out and validates the example with the exact pinned parser

This is a non-draft follow-up stacked on #436. Acme/Zenith remain nonproduction examples under examples/acme-zenith; no production data or destinations are added.

Validation

  • pnpm check:ci (format, lint with existing warnings, typecheck)
  • pnpm test (2,813 passed, 5 skipped)
  • focused release workflow tests (17 passed)
  • parsed all changed Actions YAML
  • exact pinned config validation (2 brands, 6 layers, schema mirror and golden fixture exact)

@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

CODE-561

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29b8f73911

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml

config-integration:
name: Cross-repository config contract
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip the secret-backed job on Dependabot PRs

Dependabot pull-request workflows do not receive Actions secrets, but Dependabot branches still belong to this repository, so this condition evaluates true. The job then reaches the credential check with empty BOT_APP_ID/BOT_APP_PRIVATE_KEY, fails, and causes the required all-green job to fail for every npm, Cargo, and Actions update configured in .github/dependabot.yml; exclude dependabot[bot] here just as external forks are excluded.

Useful? React with 👍 / 👎.

@pullfrog pullfrog Bot 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.

Important

The security rework itself is sound — I traced every new fail-closed gate in the render action and none of them fail open, and the deployments: readactions: read swap is the documented-correct scope. Two things need a human answer before merge: the production render path is now fail-closed by design (which the desktop release train runs through), and the new blocking CI job makes ordinary CI depend on organization App credentials.

Reviewed changes — full initial review of both commits on xuan/code-561-cross-repo-client; CI/release plumbing only, no product code.

  • Fixed repository identitiesrender-release-config drops the publisher-repo input and hardcodes arcboxlabs/linkcodehq plus arcboxlabs/linkcode-config, so release data controls only the two SHAs and a source-root chosen from a two-entry allow-list.
  • Two independent checkouts — the single fetch + git worktree add is replaced by two shallow fetches, each with its own token, http.followRedirects=false, and a post-checkout rev-parse HEAD + remote get-url origin identity assertion.
  • New fail-closed gates — 40-hex lowercase SHA regex, structural-root existence, symlink rejection under the source root, and a byte-exact cmp of the config repo's schema mirror against the publisher's canonical schema.
  • Short-lived scoped tokens — every render job pre-flights BOT_APP_ID/BOT_APP_PRIVATE_KEY and mints two permission-contents: read installation tokens before checking out the selected client ref, and adds persist-credentials: false. CONFIG_PUBLISHER_REPO, CONFIG_PUBLISHER_TOKEN, and RELEASE_ENVIRONMENT_ADMIN_TOKEN are removed from workflows, release-inputs.cjs, and the docs.
  • Environment preflight — reads protection metadata with ${{ github.token }} under workflow-level actions: read.
  • New config-integration CI job — checks out both private repos at hardcoded SHAs and validates examples/acme-zenith with the publisher's pinned parser; gated into all-green.
  • Pilot repin + testscode-561-pilot.json moves to publisher 986d9f21… / now-divergent source a1ed4d66…; brand-matrix.test.mjs gains a mint-before-checkout assertion and a publisher≠source assertion.

Two things I checked and am not flagging, so they don't get re-litigated: actions: read is the scope GitHub documents for GET /repos/{owner}/{repo}/environments/{name} (the removed deployments: read was the wrong one, and protection_rules.required_reviewers.reviewers is not redacted for non-admin readers), and all-green's success || skipped escape hatch is safe here because config-integration has no needs: and so can only be skipped by its own fork guard.

⚠️ The production render path is fail-closed after this merges, and the desktop release train runs through it

build-desktop.yml and build-mobile.yml pass no source-root, so both default to . — the root that the action's own error text and the new docs/RELEASE.md paragraph describe as "intentionally unavailable until reviewed production data exists". release-desktop.yml invokes build-desktop.yml with sign: true on a v* tag, and render-config already runs on master under if: inputs.sign, so this is a behaviour change on the shipping path rather than new scaffolding. Only release-brand-matrix.yml passes the reachable examples/acme-zenith root.

Technical details
# Production render path defaults to a root the action declares unavailable

## Affected sites
- `.github/actions/render-release-config/action.yml:21``source-root` defaults to `"."`.
- `.github/actions/render-release-config/action.yml:168-172``.` resolves to the `linkcode-config`
  repository root; the step aborts unless that root holds `brands.manifest.yaml` and
  `schema/config.schema.json`.
- `.github/workflows/build-desktop.yml:146-154``render-config` (runs when `inputs.sign`) omits
  `source-root`.
- `.github/workflows/build-mobile.yml:131-140``render-config` (runs whenever
  `rendered_artifact == ''`) omits `source-root`.
- `.github/workflows/release-desktop.yml:34-41` — tag-triggered release calls `build-desktop.yml`
  with `sign: true`.

## Required outcome
- A reader (and the next person to cut a release) can tell whether the desktop/mobile release path is
  expected to succeed after this merges.

## Open questions for the human
- Does the `arcboxlabs/linkcode-config` root already carry `brands.manifest.yaml` and
  `schema/config.schema.json`? If yes, nothing to do here.
- If not: was production rendering already unreachable for an independent reason (e.g.
  `CONFIG_RELEASE_MANIFEST_DESKTOP` unset in the `release` environment)? If it was reachable, this
  needs to land with the production data, or the release train needs an explicit hold and a note in
  `docs/RELEASE.md` saying signed releases are blocked until then.

ℹ️ Nitpicks

  • .github/workflows/release-brand-matrix.yml:371 re-hardcodes examples/acme-zenith in the cp path 30 lines after passing it as source-root:. It fails loudly if the two drift, but deriving the path from one value would be tidier.
  • .github/workflows/ci.yml:354-355 asserts the Node and pnpm versions that the two steps immediately above configure — the assertion can only fail if setup-node/action-setup silently ignored their inputs.
  • .github/actions/render-release-config/action.yml:184 still runs pnpm install --frozen-lockfile without --ignore-scripts on a publisher checkout whose SHA comes from release data, while the new CI job at ci.yml:357 does pass it. The install line is untouched by this PR, so it's pre-existing — but the asymmetry is now visible in the same diff.
  • docs/ENVIRONMENT.md:132 keeps BOT_APP_ID/BOT_APP_PRIVATE_KEY under the Release-only secrets heading, but ci.yml now requires them on every push and internal PR. The cell text says "release and config-render workflows", which doesn't cover CI.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread .github/workflows/ci.yml
if [ -z "$BOT_APP_ID" ] || [ -z "$BOT_APP_PRIVATE_KEY" ]; then
echo "::error::BOT_APP_ID and BOT_APP_PRIVATE_KEY must be available so CI can read arcboxlabs/linkcodehq and arcboxlabs/linkcode-config"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This job has no environment:, so BOT_APP_ID/BOT_APP_PRIVATE_KEY have to be repository- or organization-level secrets — but docs/ENVIRONMENT.md files them under "Release-only secrets … Set as GitHub repository/environment secrets". If they are currently scoped to the release environment, this step fails on every push to master and every internal PR, and all-green goes red repo-wide.

Worth confirming the actual scoping before merge, and deciding whether an outage in linkcodehq/linkcode-config (or a revoked installation) should be able to block all CI rather than just release CI.

Technical details
# `config-integration` gives ordinary CI a hard dependency on org App credentials

## Affected sites
- `.github/workflows/ci.yml:283-297` — job has no `environment:` key; the guard hard-fails when the
  two secrets resolve empty.
- `.github/workflows/ci.yml:363-384``all-green` treats any non-`success`/non-`skipped` result as a
  failure, so this propagates to the required check.
- `docs/ENVIRONMENT.md:132` — still lists the pair under "Release-only secrets".

## Required outcome
- CI stays green for changes unrelated to the config contract when the credentials or the private
  repositories are unavailable, **or** the blocking behaviour is a deliberate, documented choice.

## Open questions for the human
- Are `BOT_APP_ID`/`BOT_APP_PRIVATE_KEY` repo/org secrets today, or environment secrets on `release`?
- Is this job meant to block `all-green`, or should it be advisory (fail-soft) until the config
  repositories are stable?

Comment thread .github/workflows/ci.yml
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: arcboxlabs/linkcodehq
ref: 986d9f21403df53bc932f511eb1b5f0bb634d48d

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These two SHAs are hardcoded here (again at lines 332, 352, 353), in .github/release/brand-matrices/code-561-pilot.json, and in docs/RELEASE.md. brand-matrix.test.mjs pins the matrix values to the same literals, but nothing ties ci.yml to the matrix — so when the pilot is repinned, this job keeps validating the old commit pair and the "cross-repository contract" silently stops covering what the release actually renders.

Technical details
# The CI contract job's pins can drift from the matrix it is meant to validate

## Affected sites
- `.github/workflows/ci.yml:323` / `:332``ref:` literals for the two checkouts.
- `.github/workflows/ci.yml:352-353``rev-parse` assertions repeating the same literals.
- `.github/release/brand-matrices/code-561-pilot.json``publisherGitSha` / `sourceGitSha` for all
  six manifests.
- `.github/scripts/brand-matrix.test.mjs:104-107` — asserts the matrix against the literals but never
  reads `ci.yml`.

## Required outcome
- Repinning the pilot matrix either updates this job automatically, or fails a test until it is
  updated.

## Suggested approach (optional)
- Read `publisherGitSha` / `sourceGitSha` out of `code-561-pilot.json` with `jq` in the job and use
  them for both checkouts, or extend the existing `brand-matrix.test.mjs` case to assert that
  `ci.yml` contains the same two SHAs the matrix pins.

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.

1 participant