Skip to content

feat(agent): add isolated signed update delivery - #1214

Merged
AnthonyRonning merged 5 commits into
masterfrom
codex-upd-delivery-maple
Oct 22, 2026
Merged

AnthonyRonning merged 5 commits into
masterfrom
codex-upd-delivery-maple

Conversation

@AnthonyRonning

@AnthonyRonning AnthonyRonning commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Add independent Agent update delivery: an Agent-only Worker serves signed metadata, enumerated immutable artifacts, download redirects and byte ranges from its own R2 binding. The protected manual publisher verifies exact trusted-master Prod package producers, final downloaded-package consumers, workflow/source attestations, package identity and hashes before reservation, signing and publication stages. Credentialed Agent jobs reuse the existing updates-production environment and existing BWS access credential, following the pinned PCR action pattern with set_env: false; only the consuming step maps fetched values. No new environments, machine accounts or tokens are required. Underlying BWS grants and the shared environment secret context remain trusted.

Research's Worker, publisher, feed, clients, signing environment/policy and downloads remain unchanged. Agent publication creates no GitHub Releases or tags. Linux is omitted by default and requires its own packaged launch/update gates; this PR remains independent of #1212.

The publisher verifies current-pointer signatures with real minisign for either raw or commented public-key files. Identical retries compare immutable object bytes before writing locked keys; missing objects still use create-only writes and races require exact-byte equality. Different bytes, permission failures and storage errors fail closed. Pointer promotion remains conditional and monotonic. Private audit sidecars and exact-byte reservations make partial and already-promoted attempts retryable.

Validation at c40d3364 (bucket-binding follow-up to 8b5181d4):

  • 94 publisher tests, including real minisign existing-pointer/rotation/retry regressions and 14 deterministic R2 transport/signing tests with independent signature vectors, bounded streaming, conditional headers, errors and redirect refusal.
  • 7 workflow contracts, 10 Worker/workerd tests, formatting, TypeScript and pinned Wrangler dry run; 12 hook-selector tests.
  • gh and jq run inside pinned Nix during preparation. Profile/release-info changes select delivery validation.
  • At 8b5181d4, full local root nix flake check --no-update-lock-file passed for aarch64-darwin and the normal hook passed actionlint plus all 499 repository script tests. Signing-boundary, Agent-workflow, Nix-workflow and selector tests passed; standalone ShellCheck passed all 12 publisher run blocks.
  • The final bucket-binding change passed the full Agent delivery gate again: 7 workflow contracts, 94 publisher and 10 Worker/workerd tests, formatting/types and fixture-only Wrangler dry run. Repeated full-flake/normal-hook checks were bounded and stopped on an unchanged host-specific iOS setup fixture; the final signed commit skipped that duplicate hook. No Research fixture was changed.
  • Independent reviews found no actionable issue in either the credential-wiring or final bucket-binding change. Previous implementation-head hosted Agent Update Delivery Tests and Repository CI passed; the final head starts fresh hosted validation. No package/update signing was performed.
  • An owner-authorized private-bucket probe used the real publisher R2Store and immutable_put with the bucket-scoped S3 pair: identical locked-object retries, missing-object creation, differing-byte refusal, streaming hash/HEAD/range, successful pointer CAS and stale-ETag 412 preservation all passed. Direct overwrite/delete of locked objects returned 409 ObjectLockedByBucketPolicy with bytes preserved; an unrelated bucket denied access with 403. The real latest pointer was untouched. This proves bounded transport/lock/CAS interoperability, not full manifest publication or an installed update.

Delivery remains inactive. The owner provisioned the private maple-agent-updates R2 bucket, disabled public bucket endpoints, configured the immutable-release lock and stored the bucket-scoped publisher credentials in existing BWS infrastructure with Agent-specific inputs in the existing updates-production environment. The checked-in binding now names that bucket; local checks still substitute a separate fixture. Routes stay owner-managed outside routine deploy configuration, and empty source pins still block publication. No Worker/route deployment, release, publication or PR merge was performed; Research configuration is unchanged. Full locked-prefix manifest/publication retry rehearsal, narrowly scoped Worker deployment with its R2 binding, routing independence in both directions, and the signed installed 0.0.1 → 0.0.2 rehearsal remain activation gates before invitations.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Maple development preview: https://038b3c34.maple-ca8.pages.dev

Commit: c40d33644c145eeee1a74d74995021a4768c06dc

Uses development API, billing, flags and PCR configuration. Cloudflare Access applies.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Maple Auth development preview: https://e675ea15.maple-auth-dev.pages.dev

Commit: c40d33644c145eeee1a74d74995021a4768c06dc

Uses development services. Cloudflare Access applies. Provider OAuth is not configured for PR URLs; use the stable Auth Dev host for OAuth rehearsal.

Accept raw and commented public keys with the real minisign verifier. Reuse byte-identical immutable objects before attempting a conditional write, preserving create-only race handling and fail-closed storage errors. Add real-CLI pointer retry/repair regressions and offline R2 signing and transport coverage.

Pin the publisher GitHub CLI, run preparation in its pinned shell, and follow profile/package-contract changes in delivery checks. Configured-bucket lock and pointer-race behavior remains an activation rehearsal gate.

Validation: isolated delivery checks passed (94 publisher tests, 6 workflow tests, 10 Worker/workerd tests); selector tests and independent changed-workflow actionlint/ShellCheck passed. Full root flake check and normal hook were attempted but stalled in this host's actionlint/ShellCheck integration and unrelated iOS fixture. Commit hook skipped only after equivalent focused checks; hosted repository checks remain required.
@AnthonyRonning
AnthonyRonning merged commit 69cdac8 into master Oct 22, 2026
54 checks passed
AnthonyRonning added a commit that referenced this pull request Oct 27, 2026
…package build

- opensecret_change_detection.py lists services/agent-updates/ as an
  independent root. Since #1214 every agent-updates change selected every
  OpenSecret check, including both EIF builds. Unknown roots still select
  every check.
- os-flake-check runs only the component `nix flake check`. Nothing consumed
  the x86_64 `#default` package: the shipped EIF is aarch64, and os-rust,
  os-sdk-e2e and the EIF lanes compile and test the same code.
- deps-opensecret follows its own Cargo.toml, Cargo.lock and deny.toml
  instead of the selector's `audit` output, which CI files also trigger. CI
  edits no longer select any deps lane, so new RustSec advisories surface in
  the nightly run instead of failing unrelated PRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AnthonyRonning added a commit that referenced this pull request Oct 27, 2026
…package build

- opensecret_change_detection.py lists services/agent-updates/ as an
  independent root. Since #1214 every agent-updates change selected every
  OpenSecret check, including both EIF builds. Unknown roots still select
  every check.
- os-flake-check runs only the component `nix flake check`. Nothing consumed
  the x86_64 `#default` package: the shipped EIF is aarch64, and os-rust,
  os-sdk-e2e and the EIF lanes compile and test the same code.
- deps-opensecret follows its own Cargo.toml, Cargo.lock and deny.toml
  instead of the selector's `audit` output, which CI files also trigger. CI
  edits no longer select any deps lane, so new RustSec advisories surface in
  the nightly run instead of failing unrelated PRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AnthonyRonning added a commit that referenced this pull request Oct 27, 2026
…package build

- opensecret_change_detection.py lists services/agent-updates/ as an
  independent root. Since #1214 every agent-updates change selected every
  OpenSecret check, including both EIF builds. Unknown roots still select
  every check.
- os-flake-check runs only the component `nix flake check`. Nothing consumed
  the x86_64 `#default` package: the shipped EIF is aarch64, and os-rust,
  os-sdk-e2e and the EIF lanes compile and test the same code.
- deps-opensecret follows its own Cargo.toml, Cargo.lock and deny.toml
  instead of the selector's `audit` output, which CI files also trigger. CI
  edits no longer select any deps lane, so new RustSec advisories surface in
  the nightly run instead of failing unrelated PRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AnthonyRonning added a commit that referenced this pull request Oct 27, 2026
* ci: add ci.yml, the lane planner and the Agent PR gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: replace workflow shape tests with policy and contract tests; activate the repo lane

The manual Windows signature probe's `actions: read` moves from the
workflow to its one job, as the read-only workflow-permissions policy
requires; the job's token is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run Agent verification, packaging rehearsal and nightly checks from ci.yml

Activate the remaining Agent lanes (full Linux/macOS/Windows suites,
supply chain, provider, store bench, PR packaging rehearsals), add
nightly.yml with one ci-nightly issue, make agent-desktop-build.yml
master-only with exact-key release caches and a Blacksmith Windows
builder, and delete agent-ci.yml, the SQLCipher and store workflows
and the dependency archive, cache-scope, measurement and selector
scripts.

test_windows_updater_workflow.py reads the Windows Rust pin from
setup-agent-windows, which windows-build now uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: move SDK, proxy, Research and service checks into ci.yml lanes

Activate the sdk-ts, sdk-rust, proxy, deps-{research,sdk,proxy},
proxy-container(-arm64), research-web, research-rust, research-ios,
research-android, research-desktop-{macos,linux,windows}, research-dev-macos,
auth, updates and agent-updates lanes. PR lanes restore only caches that
master writes and never save one. frontend-tests, web-build and rust-tests
become push-only evidence twins (rust-tests on Blacksmith, writing
ci-v1-research-rust); proxy-rust keeps only the release rehearsal. Delete the
twelve workflows these lanes replace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run OpenSecret checks and the PR EIF candidate from ci.yml lanes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: finish the rebuild: signed-lane hygiene, hook on the planner, CI docs

Signed Research workflows get job timeouts and job-level OIDC and
attestation scopes; Maple Dev TestFlight builds only for Research web or
iOS inputs (dispatch always builds); CodeQL runs on every master push,
per SHA, never cancelled. The temporary policy and contract allowlists
are gone.

The pre-commit hook selects components with `plan.py hook --staged`
from the lane table, and the Agent hook runs the agent-lint and
agent-test recipes. docs/ci.md is new, docs/repository-workflows.md is
rewritten, AGENTS.md gains CI rules for agents, and the unused flake
apps.ci-* outputs are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: address review findings

- os-sdk-e2e and os-helpers select on their own entry scripts; the
  OpenSecret selector routes inert paths too, so its routing is verbatim
- widen the Agent packaging selector to the scripts packaging calls, the
  Agent flake and (macOS) the Xcode pin; agent-windows runs the native
  Windows tests; acp-only and proxy-only clippy move into ci-lint
- os-eif: PRs to master only, plus a ci:eif label; os-flake-check builds
  the x86_64 package again; opensecret-eif.yml runs on every PR to master
- ci-ok calls `plan.py verdict`; lint checks special jobs' runners and
  timeouts; setup-bun saves no PR cache; agent-test on 32 vCPU
- ci.yml's nightly run covers deps and supply chain; the reporter needs
  today's scheduled run
- drop PR-only scripts from the signed-build selector; Auth push paths
  rehearse publisher edits again; docs fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: fix validation

Keep services/opensecret/ byte-identical to master, as the OpenSecret port
requires: restore its README, nitro-deploy.md and hook comment. The C7 docs
contract skips that subtree; its docs move with OpenSecret PRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: route agent-updates away from OpenSecret and drop the unused x86 package build

- opensecret_change_detection.py lists services/agent-updates/ as an
  independent root. Since #1214 every agent-updates change selected every
  OpenSecret check, including both EIF builds. Unknown roots still select
  every check.
- os-flake-check runs only the component `nix flake check`. Nothing consumed
  the x86_64 `#default` package: the shipped EIF is aarch64, and os-rust,
  os-sdk-e2e and the EIF lanes compile and test the same code.
- deps-opensecret follows its own Cargo.toml, Cargo.lock and deny.toml
  instead of the selector's `audit` output, which CI files also trigger. CI
  edits no longer select any deps lane, so new RustSec advisories surface in
  the nightly run instead of failing unrelated PRs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(opensecret): report EIF approval mismatches on master as pending, not failed

check_opensecret_eif.sh exits 3 for a PCR mismatch and prints the built
pcr.json; usage, build and comparison failures keep their non-3 statuses
(a Nix build failure is now always 1, never 3).

opensecret-eif.yml's compare step, identical in eif and eif-trusted:
- master push or dispatch: a mismatch is a notice, with the built PCRs in
  the step summary, and the job passes;
- pull_request: a mode must match only if the PR's own diff (merge base to
  head, SHAs through env) edits that mode's pcr<Env>.json or
  pcr<Env>History.json; otherwise its mismatch is a notice;
- dispatch outside master: a mismatch fails, as before;
- any other non-zero status fails.
Job conditions, runners, permissions, cache steps and the matrix are
unchanged; opensecret-eif-release.yml does not call the script.

services/opensecret/README.md and docs/nitro-deploy.md now name ci.yml
instead of the deleted workflows and describe the new verdicts, so C7 no
longer skips services/opensecret/. The eif-dev and eif-prod derivations are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: address follow-up review findings

- opensecret-eif.yml: the EIF compare step accepts only full 40-hex PR
  base and head SHAs before it diffs the approval files. Anything else
  fails the job, so an option-like or abbreviated value cannot make an
  edited mode's mismatch look neutral. Both approval jobs keep the same
  step; test_contracts covers option-like and abbreviated SHAs.
- docs/ci.md: the advisory rule is scoped to the deps-* lanes, and names
  the two gate lanes that still audit advisories (agent-supply-chain,
  sdk-ts).
- services/opensecret/.githooks/pre-commit: the comment names ci.yml's
  os-rust and os-flake-check lanes and drops the removed package build.
  Comment lines only; the eif-dev, eif-prod and eif-preview drvPaths are
  unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(agent): allow two intentional Windows-only Clippy lints

The new Windows Clippy lane is the first CI job to lint the Agent's
cfg(windows) code. Both findings are intentional, so they get a scoped
allow with the reason; behaviour is unchanged:

- filesystem_kind joins "\" to the path prefix on purpose: joining a rooted
  path keeps the prefix, so C: becomes C:\ for GetDriveTypeW.
- The Windows-only hard-link cleanup test clears the read-only attribute,
  not Unix mode bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: size Agent lane timeouts for cold runs and lint on 32 vCPU

The first ci.yml run timed out agent-lint and agent-supply-chain at 15
minutes on a cold cache, and a lane that times out never saves its cache.
Timeouts now cover a cold run (lint and test 40, supply chain 30). The
lint lane joins agent-test on the 32 vCPU runner, since it is on the Agent
PR path whenever a dependency bump misses the cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: faster PR packaging, cached Research git deps, no store benchmark

- Agent PR packaging rehearsals build with thin LTO and 16 codegen units. They
  prove the packaging scripts, not the optimizer; master's signed builds keep
  fat LTO. A profile change rebuilds every dependency, so the rehearsals keep
  their own cache, which ci.yml's nightly master run saves.
- research-android restores the Research Rust cache rust-tests.yml writes.
  Cargo fetches every git dependency in the lockfile for any target, so the
  Android build was spending most of its time fetching Goose and PDFOxide.
- The store benchmark leaves CI: no ci:perf lane and no nightly job. The
  crate and `just store-bench` remain for local runs.
- docs/ci.md, AGENTS.md and the Agent skill say when a label is worth adding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: align with #1316

#1316 (signed Windows Agent updates) landed while this branch moved the
Windows jobs. Carry its contracts over:

- agent-package-windows replaces the deleted PR-only windows-unsigned job,
  so it now exports MAPLE_PACKAGE_BUILD_NUMBER, which windows-release.py
  requires: the Actions run number, or 1 outside Actions (as
  package-release.sh does), so `just lane agent-package-windows` still runs
  locally.
- test_contracts C2 asks the publisher to trust a run with Windows too, and
  pins MAPLE_PACKAGE_BUILD_NUMBER to github.run_number on every job that
  packages or verifies Windows (the assertion #1316 added to the deleted
  test_agent_workflows.py).
- The manual signature probe checks that each Windows Prod job ran on
  windows-2025. The unsigned build now runs on Blacksmith, so the build job
  may also report blacksmith-8vcpu-windows-2025 (signing and verification
  still must be GitHub's). A new C2 contract runs the probe's producer check
  against this workflow's jobs and runners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: fold the Windows updater tests into ci.yml lanes

agent-windows-updater-tests.yml (from #1316) becomes the
agent-windows-updater lane, reported by ci-ok:

- Selected by the workflow's own paths on pull requests, the merge queue
  and master pushes alike, by ci:windows and ci:full, by CI edits, and
  nightly. Only the deleted workflow's own path is dropped.
- It runs scripts/ci/windows-app-typecheck.ps1, then
  scripts/ci/windows-updater-tests.ps1, unchanged, with pwsh from the
  repository root and the workflow's environment (RUSTFLAGS -D warnings,
  CARGO_TERM_COLOR never, no incremental build, no debug info). Both
  always run, as the two parallel jobs did, and either failure fails the
  lane.
- A special ci.yml job keeps both receipts and Cargo logs as one artifact
  even on failure. It uses setup-agent-windows, the pinned Rust, MSVC,
  native Perl and linker the workflow set up by hand, and restores no
  cache. The fxc selection is not carried over: GPUI compiles shaders only
  in builds without debug assertions, so a dev-profile cargo check never
  runs fxc.
- It runs on the Windows lane runner (Blacksmith, CI_WINDOWS_RUNNER to
  fail over) instead of GitHub's windows-2025. The native reparse fixtures
  need the symlink privilege, so this PR's own run is the first proof on
  Blacksmith.

test_windows_updater_workflow.py now tests the lane, the job and the
scripts instead of the workflow's YAML: no authority, the pinned toolchain
and no cache, selection through the planner, both scripts running (with a
stub pwsh) and the retained evidence. The script assertions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: address #1316 integration review

- agent-package-windows also runs on its own Windows inputs: the Windows
  packaging scripts, build-release.sh (its Windows branch), release-info.py,
  the release profile files and the Windows toolchain. #1316 put Windows on
  the Agent feed, and on master every Agent PR built, installed and
  smoke-tested an unsigned Windows package; label-only selection assumed
  Windows was not published. ci:package and ci:full still add it.
- test_contracts C2 evaluates each agent-desktop-build.yml job's `if:` and
  `needs` for a master push and a dispatch, so a condition that skips a
  Prod job the publisher requires fails the contract. Bare true, false and
  null evaluate as GitHub's literals.
- A new C2 contract runs every lane that calls windows-release.py package
  or verify with stand-ins and checks they receive MAPLE_PACKAGE_BUILD_NUMBER:
  the Actions run number, or 1 outside Actions.
- C7 also checks quoted or bold workflow display names in docs; updates.md
  named the deleted Agent Windows Updater Tests workflow and now names the
  agent-windows-updater lane.
- magic-nix-cache-action counts as a cache writer unless use-gha-cache is
  disabled. opensecret-eif.yml's eif job (fork PRs that edit approvals,
  manual runs outside master) is the one exempt pull-request writer: no
  FlakeHub, no id-token, branch-scoped cache. The workflow is unchanged;
  the cache docs now name the exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run the Agent's native Windows test lanes on GitHub's windows-2025

The first Blacksmith run of agent-windows-updater failed one native test:
the updater's storage validation rejects the test image because Blacksmith's
workspace inherits a foreign write ACE, which GitHub's workspace does not.
The validation is right, and #1316 proved these tests on windows-2025, so
agent-windows-updater and agent-full-windows (which runs the same tests) use
GitHub's image again. The Windows Clippy lane and the unsigned builds stay on
Blacksmith.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(agent): satisfy Windows Clippy in the updater's Windows bundle code

The Windows Clippy lane is the first CI job to lint #1316's Windows-only
updater code. Five mechanical fixes, no behaviour change: three collapsible
ifs (two as let chains), a range check written with RangeInclusive::contains,
and PROCESSENTRY32W built with its size in the initializer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: run the Windows packaging rehearsal on GitHub's windows-2025

Run 5 built, packaged and verified the unsigned Dev installer on Blacksmith,
then the installer smoke test (setup.exe /S into a temporary directory)
failed there. Master's install smoke for the same installer runs on GitHub's
windows-2025 (agent-desktop-build.yml verify-windows), which also passes the
updater's native tests, so the PR rehearsal runs there too. It no longer
restores master's Blacksmith-written release cache, which a GitHub runner
cannot read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: cold-run timeouts for Research Rust and Android; fast Windows rehearsal

Run 6 cancelled research-rust at its 20-minute timeout: 14.7 minutes went to
Cargo fetching Goose's git history before master's Research cache exists.
research-rust and research-android get 45 minutes. The Windows packaging
rehearsal uses the same thin-LTO profile as the Linux and macOS ones; master's
signed Windows build keeps fat LTO.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
auth-pages-pr-1214 — c40d3364 Deployed Oct 22, 2026 by github-actions[bot]
pages-pr-1214 — c40d3364 Deployed Oct 22, 2026 by github-actions[bot]
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