diff --git a/.agents/review/decisions.md b/.agents/review/decisions.md deleted file mode 100644 index fe70d9a..0000000 --- a/.agents/review/decisions.md +++ /dev/null @@ -1,7 +0,0 @@ -# Review decisions registry - -Durable, already-litigated review decisions. How reviewers must treat entries and when orchestrators append them is defined in the `review` and `review-fix` skills. - -Entry format: heading `### D-NNN (date, status) — title`, where status is `decided` or `open`, followed by the decision and its rationale in prose. Entries are never edited silently; superseding an entry means a new entry that references the old id. - -## Entries diff --git a/.agents/skills/ci-pipelines/SKILL.md b/.agents/skills/ci-pipelines/SKILL.md index 87fbc40..e04e038 100644 --- a/.agents/skills/ci-pipelines/SKILL.md +++ b/.agents/skills/ci-pipelines/SKILL.md @@ -5,6 +5,8 @@ description: Use when changing continuous integration, GitHub Actions workflows, # CI pipelines +Canonical workflows use maintained major-version tags for external actions. + ## Billing shapes the job graph - Jobs bill per minute, rounded up, minimum one. Fold sub-minute checks into an existing job on the same trust level instead of giving them their own. diff --git a/.agents/skills/declarative-infra/SKILL.md b/.agents/skills/declarative-infra/SKILL.md index de34694..9a83e91 100644 --- a/.agents/skills/declarative-infra/SKILL.md +++ b/.agents/skills/declarative-infra/SKILL.md @@ -26,3 +26,5 @@ Before pushing, run `nix flake check`, build the host toplevel, run `tofu fmt -c - For SOPS, development environments, CI secrets, or provider credentials, read [Secrets bootstrap](references/secrets.md). - For preview environments, read [Pull request previews](references/pr-previews.md). They are the default for web-host adoption; omitting them requires a recorded decision in the host repository. - For image promotion into a dedicated infrastructure repository, read [Image promotion](references/image-promotion.md). + +- For third-party container updates whose publisher is outside the infrastructure owner's control, read [Vendor image promotion](references/vendor-image-promotion.md). diff --git a/.agents/skills/declarative-infra/references/bootstrap.md b/.agents/skills/declarative-infra/references/bootstrap.md index 7032147..2d733f1 100644 --- a/.agents/skills/declarative-infra/references/bootstrap.md +++ b/.agents/skills/declarative-infra/references/bootstrap.md @@ -146,6 +146,26 @@ in { email = acmeEmail; # required: ACME registration contact }; + systemd.services.podman-image-prune = { + description = "Remove unused container images older than seven days"; + serviceConfig = { + Type = "oneshot"; + Nice = 19; + IOSchedulingClass = "idle"; + }; + script = '' + ${pkgs.podman}/bin/podman image prune --all --force --filter until=168h + ''; + }; + systemd.timers.podman-image-prune = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + RandomizedDelaySec = "30m"; + }; + }; + virtualisation.podman = { enable = true; defaultNetwork.settings.dns_enabled = true; # containers resolve each other by name @@ -162,6 +182,9 @@ Beyond the module: - Only Caddy publishes services; nothing else opens 80/443, and every additional firewall port is a documented decision in the host repo. - Pin container images by digest. +- Every Podman host needs image retention for production as well as previews. Daily image-only pruning of unused images created more than seven days ago is the default. Podman protects images referenced by any container, including stopped containers. Do not use `podman system prune`, volume pruning, or external-container removal as a substitute. Nix garbage collection and journal retention do not reclaim OCI images. +- The age filter uses the image creation timestamp, not its pull or last-use time. Retired images may need to be pulled again for rollback; keep registry access available. Where a deployment or preview controller protects staged or rollback images beyond container references, coordinate cleanup with that controller or exclude its labels and retain its own collector. A preview-only collector does not replace production retention. +- Verify the timer and a successful cleanup on each host after deployment. During a disk-full incident, check PostgreSQL recovery and every service sharing the filesystem after reclaiming unused images; never remove database files or volumes to make room. - A host running PostgreSQL also runs a local dump timer with retention (hourly `pg_dump --format=custom` into a `postgres`-owned directory is the norm); shape the unit however reads best. - A host running GitHub Actions jobs uses `services.github-runners.` with a SOPS-provided token, `programs.nix-ld.enable = true` plus `NIX_LD`/`NIX_LD_LIBRARY_PATH` in the runner environment so downloaded tooling executes, and systemd resource caps (`CPUQuota`, `MemoryHigh`/`MemoryMax`) so jobs cannot starve the host's services. @@ -267,7 +290,7 @@ A repo whose trusted CI builds and deploys NixOS system closures uses a private, A deploy that changes an app's image restarts `podman-.service` in place: the old container stops before the new one starts, and Caddy has no upstream for that app until the new container is up. That brief interruption is the accepted default for this profile. The health readback in `image-promotion.md` verifies that the deploy completed; it does not keep the old container serving through the switch. -Keep the window to container startup, not registry pull: after the deploy job's identity and registry-access checks and immediately before `nix run .#deploy-rs`, the workflow pre-pulls every gated image reference on the target over the deploy SSH connection. Set `REGISTRY_AUTH_FILE` and pass `--authfile` on every Podman registry command. Public entries use the root-owned empty `/run/containers/auth/anonymous.json`; private entries use only `/run/containers/auth/ghcr-private.json`, atomically created by the host's SOPS-backed `podman-ghcr-login` unit. Give every container unit the matching `REGISTRY_AUTH_FILE` too, so implicit pulls cannot fall back to ambient Podman or Docker auth. A private pre-pull always contacts the registry even when the digest is cached, so missing or expired credentials fail before activation; a public cached digest may skip its network pull after the drift detector has independently proved anonymous access. Pulling by digest is idempotent and additive, and any failed pre-pull fails the deploy before activation touches the running system. +Keep the window to container startup, not registry pull: after the deploy job's identity and registry-access checks and immediately before `nix run .#deploy-rs`, the workflow pre-pulls every gated image reference on the target over the deploy SSH connection. Set `REGISTRY_AUTH_FILE` and pass `--authfile` on every Podman registry command. Public entries use the root-owned empty `/run/containers/auth/anonymous.json`; private entries use only `/run/containers/auth/ghcr-private.json`, atomically created by the host's SOPS-backed `podman-ghcr-login` unit. Give every container unit the matching `REGISTRY_AUTH_FILE` too, so implicit pulls cannot fall back to ambient Podman or Docker auth. A private pre-pull always contacts the registry even when the digest is cached, so missing or expired credentials fail before activation; a public cached digest may skip its network pull after the same deployment has independently proved anonymous access to that exact digest. Pulling by digest is idempotent and additive, and any failed pre-pull fails the deploy before activation touches the running system. Private-image migration and container units require and order after `podman-ghcr-login.service`. Public-image units must not depend on that service. The SOPS secret restarts the oneshot when its decrypted value changes. The unit reads `registry.github_token` through stdin, writes a same-directory `0600` temporary auth file, and atomically renames it to the private path only after `podman login` succeeds. The complete credential, two-stage adoption, auth-file, and rotation contract is in `image-promotion.md#private-ghcr-host-access`. diff --git a/.agents/skills/declarative-infra/references/image-promotion-contracts.md b/.agents/skills/declarative-infra/references/image-promotion-contracts.md index 95ca4e2..a1a20e1 100644 --- a/.agents/skills/declarative-infra/references/image-promotion-contracts.md +++ b/.agents/skills/declarative-infra/references/image-promotion-contracts.md @@ -65,7 +65,7 @@ lifecycle: [announced, branch, open, merged, deploy-failed, completed, supersede ```yaml imagesPath: infra/images.json -metadataFields: [sourceRepository, sourceRef, sourceWorkflow, imageRepository, registryAccess, trackedTag, promotionLatencyMinutes] +metadataFields: [sourceRepository, sourceRef, sourceWorkflow, imageRepository, registryAccess] pinFields: [promotionEnabled, digest, promotedSourceSha] disabledPin: { promotionEnabled: false, digest: null, promotedSourceSha: null } operations: @@ -80,14 +80,14 @@ operations: ```yaml public: - detectorCredential: none - detectorProof: anonymously-readable + workflowCredential: none + workflowProof: anonymously-readable hostCredential: none hostAuthFile: /run/containers/auth/anonymous.json private: - detectorCredential: github-actions-token - detectorPermissions: { contents: read, packages: read } - detectorProof: exact-private-visibility-then-anonymous-denied-then-authenticated-readable + workflowCredential: github-actions-token + workflowPermissions: { contents: read, packages: read } + workflowProof: exact-private-visibility-then-anonymous-denied-then-authenticated-readable hostCredential: sops-classic-pat hostCredentialScopes: [read:packages] hostCredentialAuthority: all-packages-readable-by-token-owner @@ -99,25 +99,6 @@ private: forbiddenDesiredStateFields: [credential, secretPath, username, authFile] ``` - -```sh -set -euo pipefail -case "$REGISTRY_ACCESS" in - public) - resolve-anonymous-tag - ;; - private) - require-exact-private-visibility - reject-anonymous-readable - resolve-authenticated-tag - ;; - *) - printf 'unsupported registry access mode: %s\n' "$REGISTRY_ACCESS" >&2 - exit 1 - ;; -esac -``` - ```sh set -euo pipefail @@ -231,8 +212,13 @@ digest_hex=${DIGEST#sha256:} branch="image-bump/${APP}/${SOURCE_SHA:0:12}-${digest_hex:0:12}" prs=$(gh pr list --repo example/infra --state all --search "\"$marker\" in:body" --json number,body,state) pr=$(jq -er --arg marker "$marker" '[.[] | select(.state == "MERGED" and (.body | contains($marker)))] | if length == 1 then .[0].number else error("expected one merged promotion PR") end' <<<"$prs") -view=$(gh pr view "$pr" --repo example/infra --json state,mergeCommit,author,headRefName,headRepository,files,statusCheckRollup) -merge_sha=$(jq -er --arg branch "$branch" 'if .state == "MERGED" and .author.login == "promotion-bot[bot]" and .headRefName == $branch and .headRepository.nameWithOwner == "example/infra" and [.files[].path] == ["infra/images.json"] and ([.statusCheckRollup[] | select(.name == "trusted-promotion-provenance" and .conclusion == "SUCCESS")] | length) == 1 then .mergeCommit.oid else error("merged promotion PR is not trusted") end' <<<"$view") +view=$(gh pr view "$pr" --repo example/infra --json state,mergeCommit,author,headRefName,headRepository,headRefOid,files) +merge_sha=$(jq -er --arg branch "$branch" 'if .state == "MERGED" and .author.login == "promotion-bot[bot]" and .headRefName == $branch and .headRepository.nameWithOwner == "example/infra" and [.files[].path] == ["infra/images.json"] then .mergeCommit.oid else error("merged promotion PR is not trusted") end' <<<"$view") +head=$(jq -er '.headRefOid | select(test("^[0-9a-f]{40}$"))' <<<"$view") +checks=$(gh api "repos/example/infra/commits/$head/check-runs?check_name=trusted-promotion-provenance&filter=latest&per_page=100" --paginate --slurp) +jq -e --arg head "$head" '[.[].check_runs[]] | length == 1 and all(.[]; + .name == "trusted-promotion-provenance" and .head_sha == $head and + .app.slug == "github-actions" and .status == "completed" and .conclusion == "success")' <<<"$checks" >/dev/null encoded=$(gh api "repos/example/infra/contents/infra/images.json?ref=$merge_sha") images=$(jq -er '.content' <<<"$encoded" | base64 --decode) jq -er --arg app "$APP" --arg digest "$DIGEST" --arg sha "$SOURCE_SHA" 'select(.[$app].promotionEnabled == true and .[$app].digest == $digest and .[$app].promotedSourceSha == $sha) | true' <<<"$images" >/dev/null @@ -242,19 +228,3 @@ gh run watch "$run_id" --repo example/infra --exit-status result=$(gh run view "$run_id" --repo example/infra --json headSha,conclusion,jobs) jq -er --arg sha "$merge_sha" 'if .headSha == $sha and .conclusion == "success" and ([.jobs[] | select(.name == "deploy" and .conclusion == "success")] | length) == 1 and ([.jobs[] | select(.name == "deploy")] | length) == 1 then true else error("exact deploy did not complete successfully") end' <<<"$result" >/dev/null ``` - - -```sh -set -euo pipefail -window=0 -while :; do - initial_desired=$(read-desired-digest "$window" initial) - initial_observed=$(resolve-tracked-tag "$window" initial) - test "$initial_desired" != "$initial_observed" || exit 0 - wait-promotion-window "$window" - current_desired=$(read-desired-digest "$window" current) - current_observed=$(resolve-tracked-tag "$window" current) - if test "$current_desired" != "$initial_desired" || test "$current_observed" != "$initial_observed"; then window=$((window + 1)); continue; fi - exit 1 -done -``` diff --git a/.agents/skills/declarative-infra/references/image-promotion-groups.md b/.agents/skills/declarative-infra/references/image-promotion-groups.md new file mode 100644 index 0000000..06bbf4f --- /dev/null +++ b/.agents/skills/declarative-infra/references/image-promotion-groups.md @@ -0,0 +1,27 @@ +# Coordinated image releases + +Use a release group when images from one source repository share a release boundary, such as a UI and worker that share a database and API. Grouped promotion is required once the infrastructure owner declares that boundary. It is not required for unrelated applications. + +## Declaration and proof + +Keep group membership in a reviewed infrastructure-owned manifest alongside `images.json`. A group has one primary app and a nonempty set of unique members that includes that primary. Every member exists in `images.json`, belongs to at most one group, and has a distinct image repository. All members share the source repository, ref, authorized workflow, enablement state, and promoted source SHA. Validate the whole document before using any member. Membership and source metadata changes use the disabled metadata transition for the whole group. + +For example, `{"mail-ui":["mail-ui","mail-worker"]}` declares `mail-ui` as the announcement entrypoint. A companion cannot independently request promotion. The trusted writer reads membership from its trusted base, never from an announcement or candidate branch. + +One successful source push run builds all members from the same source commit. Its unique successful build job emits one immutable `IMAGE_PROMOTION_RECORD` per member. Announce only after the entire source workflow has completed successfully. The infrastructure verifier authenticates the configured workflow and run, normalizes its build log, and requires exactly the declared set of records. Every record must bind the same repository, ref, source SHA, and run ID to its configured image and a valid digest. Missing, duplicate, additional, or conflicting records reject the whole candidate. Prove registry access independently for every member. + +The primary announcement supplies the existing scalar payload. Companion digests come exclusively from the verified records. A primary identity is bound to the complete proven member-to-digest map. Repeated announcements may add successful run evidence only when that map is identical. A different companion digest is a conflicting release, even when the primary digest is unchanged; reject it and publish a new source commit. + +## Desired state and lifecycle + +The trusted writer updates every member's digest and source SHA in one infrastructure PR. An unchanged member digest is allowed, but its source SHA must still record the new coordinated release. The provenance gate compares the whole group against the exact proof and rejects partial transitions, unrelated app changes, or metadata edits. The PR changes only `images.json`. + +Apply duplicate detection, branch reuse, terminal supersession, current-main ancestry, and rollback to the whole group. Comparing only the primary pin cannot prove a duplicate. Reusing an existing branch requires its complete group map to match. A superseded group operation cannot be reopened through a companion announcement. A rollback selects and verifies the whole previous release as a new approved operation. + +## Deployment boundary + +A single PR prevents inconsistent desired pins; it does not make container replacement atomic. Choose and document a rollout that respects the application's compatibility boundary. If old and new processes cannot safely share the database or API, stop every group member before migration, run migrations from the selected release, and restart the complete group. Otherwise explicitly establish compatibility across the transition. Do not claim that grouped pins alone prevent temporarily mixed running versions. + +Pre-pull and verify all group images before stopping services. Migration failure stops the rollout and leaves completion failed. Do not restart an old member against a changed database without a separately approved recovery decision. + +Completion requires the exact infrastructure merge SHA and deploy attempt, matching image readback and health for every member, and successful shared migrations and infrastructure postconditions. A healthy UI with a failed or old worker is incomplete. Record that result on the coordinated PR with a link to the deployment evidence. diff --git a/.agents/skills/declarative-infra/references/image-promotion.md b/.agents/skills/declarative-infra/references/image-promotion.md index cffee1d..936e85c 100644 --- a/.agents/skills/declarative-infra/references/image-promotion.md +++ b/.agents/skills/declarative-infra/references/image-promotion.md @@ -2,13 +2,13 @@ When an app's infrastructure home is a dedicated infra repo, deployment freshness is automation-owned: the source repo announces every successful image build, and the home repo's trusted writer proposes the desired-state change. Never edit a live pin by hand or treat either PR merge as deployment completion. -**Completion invariant:** a source change is done only when the exact infra merge SHA has passed its fail-closed gate and every required target has returned a healthy readback of the expected digest. A failed or partial activation is incomplete; report it instead of attempting automatic cross-system rollback. +**Completion invariant:** an approved deployment is done only when the exact infra merge SHA has passed its fail-closed gate and every required target has returned a healthy readback of the expected digest. A failed or partial activation is incomplete; report it instead of attempting automatic cross-system rollback. -The machine-readable writer, provenance, deploy, completion, and detector examples in [Image promotion contracts](image-promotion-contracts.md) are part of this contract and must be copied with the policy below. +The machine-readable writer, provenance, deploy, and completion examples in [Image promotion contracts](image-promotion-contracts.md) are part of this contract and must be copied with the policy below. ## One desired-state owner -The home repo owns one `images.json` (`infra/images.json`, or root `images.json` in a dedicated infra repo). Announcement validation, the trusted writer, deployment, readback, and drift detection all read its per-app objects: +The home repo owns one `images.json` (`infra/images.json`, or root `images.json` in a dedicated infra repo). Announcement validation, the trusted writer, deployment, and readback all read its per-app objects: ```json @@ -22,8 +22,6 @@ The home repo owns one `images.json` (`infra/images.json`, or root `images.json` }, "imageRepository": "ghcr.io/example/app/web", "registryAccess": "private", - "trackedTag": "main", - "promotionLatencyMinutes": 30, "promotionEnabled": true, "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", "promotedSourceSha": "1111111111111111111111111111111111111111" @@ -33,9 +31,13 @@ The home repo owns one `images.json` (`infra/images.json`, or root `images.json` `sourceWorkflow.path` and `sourceWorkflow.id` bind the immutable authorized Actions workflow; a different successful workflow with a job named `build` is not evidence. `registryAccess` is required metadata with exactly two values: `public` requires anonymous manifest access, while `private` requires exact provider visibility `private`, anonymous denial, and authenticated workflow and host access. Every reader first requires a plain object document root, then validates each complete object at runtime: the exact metadata and pin key sets, a GHCR repository, the access enum, and valid paired pins. Arrays, primitives, prototype-bearing objects, unknown fields, and authentication material fail closed. Derive production references only as `imageRepository@digest`. `images.json` is the single declarative state owner being converged, not a third credential ledger of the kind rejected by `CREDS-CLOUDFLARE-001`; it never contains a credential, secret path, username, or authentication-file path. +## Coordinated releases + +When multiple images must be released together, use the [coordinated release extension](image-promotion-groups.md). Its group proof replaces the single-record proof below for those apps. Independent apps keep the single-image contract. + ## Source side: bind and announce the build -The trusted build job publishes `imageRepository:trackedTag`, obtains the registry digest, and emits exactly one single-line JSON record to its immutable job log. The marker is assembled from fragments so the full marker cannot appear in the runner's echoed shell source. A separate announcement job runs only after build success. Its fallback token is read-only; its one-infra-repository App token has only Contents write. +The trusted build job publishes an image under its source-owned tag, obtains the registry digest, and emits exactly one single-line JSON record to its immutable job log. The marker is assembled from fragments so the full marker cannot appear in the runner's echoed shell source. A separate announcement job runs only after build success. Its fallback token is read-only; its one-infra-repository App token has only Contents write. The App credentials live at `ci.broker_app.app_id` and `ci.broker_app.private_key` in `secrets/ci.yaml`. Resolve both with the canonical action, which transports nested multiline values through `GITHUB_ENV`, never outputs. @@ -120,13 +122,19 @@ The `registryAccess` hard cutover has one document-wide migration operation for Private host adoption has a separate two-stage boundary that runs before private metadata or promotion can require a pull. First deploy the SOPS secret, login unit, explicit auth files, and container-unit environment while a new app remains disabled or the existing app remains public. Read back the decrypted secret presence, successful login unit, and root-only private auth file. Only a later reviewed metadata change and trusted promotion may select `private` and require private pre-pull. The same sequence applies to a new private app and a public-to-private migration; an old host never has to pull a private image to install the credential plumbing needed for that pull. -## Deploy, completion, and drift +## Deploy and completion The deploy workflow serializes production without cancellation. Its deploy job depends on a successful gate for exact `github.sha`. Immediately before its first mutation it requires checkout, gated, event, and current remote-main SHAs to be identical, then reruns the shared exact repository/digest registry-access proof from the gated `images.json`. A queued run therefore performs zero mutations when main moved, visibility changed, anonymous access changed, or the job token lost its package grant. It derives full references from the gated `images.json`; every activation must pass exact registry-digest and health readback, followed by all OpenTofu postconditions. Completion filters merged PRs before uniqueness, then authenticates the App bot, canonical same-repository branch, `images.json`-only file set, successful trusted provenance check, and exact resulting pin at the merge SHA. Open and closed marker copies are ignored; forged or multiple merged candidates fail closed. The exact merge-SHA deploy and its one successful deploy job are required. -The scheduled detector has Contents read and Packages read through its per-job `GITHUB_TOKEN`. It records initial desired and observed tag digests. A public entry resolves anonymously and fails when anonymous access stops working. A private entry queries the GitHub package API for the exact package path and fails unless visibility is exactly `private`; inability to read visibility also fails. It then proves anonymous denial and resolves the same digest with the workflow token; grant the infrastructure repository read access in that package's Actions access settings. `internal` is not private: broader organization or enterprise access fails the declared mode even though anonymous access is denied. Missing package access, a package that became public, an invalid access mode, or any registry error fails closed. Only an unchanged mismatch after a complete latency window fails; movement of either value starts a new window. The detector never writes and never decrypts a durable registry credential. +Read provenance through the Checks API for the exact PR head SHA, filter by `trusted-promotion-provenance` and `latest`, paginate, and require exactly one completed, successful GitHub Actions check. A missing, duplicate, failed, pending, wrong-head, or foreign-App check fails closed. The completion reader needs Actions read, Contents read, Pull requests read and Checks read; a reporter that publishes the completion check needs Checks write. Do not request `statusCheckRollup`: it also fetches commit statuses and can fail without `statuses: read`, even when the needed check is readable. Completion does not need access to those unrelated statuses. + +Announcing or opening a promotion proposes a release; it does not authorize deployment. An open or deliberately deferred PR is informational and must not fail a freshness check because a newer image exists. Record the pending proposal and its evidence without treating it as completed deployment. + +Promotion is push-based. Verify the exact approved images and service health during deployment and report completion on the promotion PR. Do not add a scheduled image drift detector, periodic running-image comparisons, publication-age failures, or a promotion latency field. There is no periodic image recheck after successful completion. + +Registry-access checks remain mandatory and independent of release deferral. Use Contents read and Packages read through the per-job `GITHUB_TOKEN` where needed. Public entries must remain anonymously readable. Private entries require exact package visibility `private`, anonymous denial, and digest resolution with the workflow token; grant the infrastructure repository read access in the package's Actions access settings. Missing access, changed visibility, invalid access modes, or registry errors fail closed. Never decrypt a durable registry credential for this check. ## Private GHCR host access @@ -140,4 +148,4 @@ Rotation is replace, refresh, verify, revoke: create a replacement classic PAT w ## Adoption boundary -This contract supports public and private GHCR images. Choose `registryAccess` explicitly during disabled adoption and prove that access mode before the first trusted promotion. Source-repository API access remains a separate plane: a private source repository uses the existing broker App's short-lived Actions-read token for provenance and drift timing, never the registry PAT. Registries other than GHCR and private-image credentials other than the host `read:packages` PAT are out of scope and require a new reviewed contract. +This contract supports public and private GHCR images. Choose `registryAccess` explicitly during disabled adoption and prove that access mode before the first trusted promotion. Source-repository API access remains a separate plane: a private source repository uses the existing broker App's short-lived Actions-read token for build provenance, never the registry PAT. Registries other than GHCR and private-image credentials other than the host `read:packages` PAT are out of scope and require a new reviewed contract. diff --git a/.agents/skills/declarative-infra/references/vendor-image-promotion.md b/.agents/skills/declarative-infra/references/vendor-image-promotion.md new file mode 100644 index 0000000..da30514 --- /dev/null +++ b/.agents/skills/declarative-infra/references/vendor-image-promotion.md @@ -0,0 +1,33 @@ +# Vendor image promotion + +Use this contract for third-party containers whose publisher is outside the infrastructure owner's control. Such images require reviewed, immutable desired-state updates and verified deployment completion. They do not require our source announcement workflow or access to the vendor's private Actions logs. + +## Publisher and desired state + +The infrastructure repository owns a strict manifest with each release family's approved image repositories, allowed tag or version policy, release-notes URL, selected version, and per-image digest. Reject unknown fields, malformed references, duplicate ownership, and credentials. Derive each deployed reference exclusively from this manifest as `repository@sha256:digest`. + +Prefer the official upstream publisher. A personal fork needs a documented server customization or another concrete reason, plus responsibility for keeping it current. Repository ownership alone does not turn third-party software into an internally maintained application. Changing publishers is a reviewed metadata change, with runtime, migration, and data-format compatibility checked before deployment. + +Existing immutable production pins may be adopted unchanged into the manifest without republishing a vendor build or disabling the application. Show that adoption preserves the evaluated container references. Do not duplicate a pin in both source and vendor manifests. + +## Discovery and review + +A scheduled or manually invoked trusted workflow discovers versions only within the approved repository and tag policy. Prefer stable release tags when provided. If upstream only publishes a moving tag, record that choice and resolve it to a digest for each proposal. Tag age does not impose a deployment deadline. + +This contract covers public GHCR and Docker Hub images. Normalize Docker Hub names for registry requests without changing the declared publisher. Use anonymous pull-scoped registry tokens, restrict pagination to the same registry and repository, bound requests, and validate returned digests. Resolve all release-family members before writing anything. Registry errors, missing members, invalid versions, or ambiguous selection fail discovery; they must not produce a partial update or a successful no-op. + +The updater opens a manifest-only PR containing the previous and proposed immutable references, selected version, publisher, and upstream release notes. Its canonical candidate identity includes the complete repository-to-digest map. Reruns reuse the same open candidate; a closed candidate remains closed. A newer candidate can be proposed while an older one is deliberately deferred. Do not execute vendor source or image contents in a workflow holding a write token. + +Human review authorizes the publisher and release. Registry availability proves pullability, not authenticity or application compatibility. Verify upstream signatures or attestations when the declared publisher policy requires them. Review migration prerequisites and backups for data-bearing upgrades. Do not enable automatic merge as part of adoption; any later automation requires a separate explicit release policy. + +## Release families and deployment + +Images published as one vendor release, such as Immich server and machine learning, form a release family. Select the same upstream version for every member and pin them in one PR. A separately versioned database or cache remains a separate family unless upstream declares a joint version contract. Vendor images need not share a source SHA or Actions run. + +One PR coordinates desired state, but container rollout remains sequential. Establish mixed-version compatibility or stop the family before shared migrations. Pre-pull every selected digest first and fail the whole deployment when a required member or migration fails. + +Use the infrastructure home's exact-commit quality gate, current-main guard, serialized deployment, and pre-mutation registry verification. Completion belongs to the exact merged PR, infrastructure merge SHA, and successful deploy attempt. Require every affected image's runtime digest and health plus all required infrastructure postconditions. A merge, successful pull, or another commit's deployment does not prove completion. Attach a deployment result and evidence link to the original PR. + +A rollback is a new reviewed manifest change with verified old digests and an explicit database recovery decision where needed. Never move a tag or reactivate a closed candidate to bypass review. + +An available version is informational until approved through merge. Report failed discovery separately from failed deployment. Verify approved images and health during deployment. Do not add a scheduled image drift detector or deployment deadline. diff --git a/.agents/skills/review-fix/SKILL.md b/.agents/skills/review-fix/SKILL.md index 8ca3e79..308d19c 100644 --- a/.agents/skills/review-fix/SKILL.md +++ b/.agents/skills/review-fix/SKILL.md @@ -13,7 +13,7 @@ An explicit user choice wins. Otherwise use Claude Opus 5 at high effort in Clau ## Scope -Read `.agents/review/decisions.md` when present. Post one scope comment with the intent, threat model, out-of-scope work, and selected lenses; its timestamp starts the cycle. Ask about splitting only when the PR contains independent product outcomes. +Post one scope comment with the intent, threat model, out-of-scope work, and selected lenses; its timestamp starts the cycle. Ask about splitting only when the PR contains independent product outcomes. Choose distinct lenses: @@ -37,14 +37,14 @@ Reuse a successful equivalent exact-head gate. Otherwise run the repository gate For every required review or verification lens, spawn a separate read-only subagent using the [review skill](../review/SKILL.md). `review-pass` is an optional workflow helper. If delegation is unavailable, report incomplete coverage and stop. -Review the PR base → initial head with the scope, gate result, decisions registry, lenses, and any model override. Retry a skipped lens once, then stop if coverage is still incomplete. Merge duplicate findings while preserving every reporting lens, and assign one decision: +Review the PR base → initial head with the scope, gate result, lenses, and any model override. Retry a skipped lens once, then stop if coverage is still incomplete. Merge duplicate findings while preserving every reporting lens, and assign one decision: - `block`: demonstrated, in scope, material under the threat model, and worth stopping the merge; - `defer`: real but outside this PR or below the merge bar; - `discard`: refuted, speculative, already accepted, or not worth scheduling; - `ask`: a costly, durable product or architecture choice remains unresolved. -Do not ask about inferable implementation details, naming, local refactors, test shape, or other reversible choices. Choose the smallest sound option and record durable assumptions. Collect every unavoidable `ask` into one decision brief with the options, consequences, and a recommendation. +Do not ask about inferable implementation details, naming, local refactors, test shape, or other reversible choices. Choose the smallest sound option. Collect every unavoidable `ask` into one decision brief with the options, consequences, and a recommendation. ## Fix diff --git a/.agents/skills/review/SKILL.md b/.agents/skills/review/SKILL.md index 2d7db3f..9ff4709 100644 --- a/.agents/skills/review/SKILL.md +++ b/.agents/skills/review/SKILL.md @@ -13,7 +13,6 @@ Review the requested change without editing it. The goal is a trustworthy merge - Ground findings in inspected code, repository contracts, tests, command output, or documented framework behavior. - Show a reachable failure scenario. Suspicious patterns or theoretical possibilities alone are not findings. - Judge materiality against the supplied intent and threat model. Repository-rule drift is evidence, not automatically a blocker. -- Read `.agents/review/decisions.md` when present and do not reopen a still-valid decision without new evidence. - If an exact-head gate result was supplied, do not rerun the full gate. Use focused probes only. Instrumented probes belong in a disposable worktree; never modify the shared checkout. Enumerate the surfaces owned by the lens rather than sampling them. Read other files when they prove an in-lens finding, but do not duplicate another lens’s charter. @@ -24,7 +23,7 @@ Return exactly one decision per finding; do not add a separate severity: - **block** — demonstrated, in intent, material under the threat model, and serious enough to stop this merge. - **defer** — real and actionable, but outside the PR or below the merge bar. -- **discard** — refuted, speculative, already accepted, or too low-value to schedule. Report only durable discards worth recording. +- **discard** — refuted, speculative, already accepted, or too low-value to schedule. - **ask** — the repository cannot choose between materially different durable product or architecture outcomes, and choosing wrongly would be expensive to reverse. Do not ask about inferable implementation details, reversible choices, local refactors, naming, or test shape. Prefer the smallest in-scope correction and defer optional machinery. diff --git a/.agents/skills/standards-sync/references/github.md b/.agents/skills/standards-sync/references/github.md index 770df50..829433a 100644 --- a/.agents/skills/standards-sync/references/github.md +++ b/.agents/skills/standards-sync/references/github.md @@ -20,3 +20,5 @@ bun standards creds add github --dest ci:ci.broker_app ``` The workflow mints two short-lived tokens for the current repository: a branch writer for contents and workflows, and a pull-request opener. Neither token enters the sync process, and there is no fallback credential. A repository with `autoSync: false` does not need these permissions until automatic sync is re-enabled. + +Canonical sync branches contain trusted upstream code and may run consumer CI before the generated PR is reviewed. diff --git a/.agents/skills/unslop/LICENSE.txt b/.agents/skills/unslop/LICENSE.txt deleted file mode 100644 index 6b54002..0000000 --- a/.agents/skills/unslop/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Lauren Tan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/.agents/skills/unslop/SKILL.md b/.agents/skills/unslop/SKILL.md deleted file mode 100644 index 2a93c06..0000000 --- a/.agents/skills/unslop/SKILL.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -name: unslop -description: Cut AI tells from any writing. Must always apply. ---- - -# Unslop - -Edit text to remove AI patterns and add human voice. - -## Process - -1. Scan for the patterns below. -2. Rewrite. Preserve meaning, match intended tone. -3. Add soul (see next section). -4. Self-audit: "What makes this obviously AI generated?" Fix remaining tells. - -## Adding soul - -Removing patterns is half the job. Sterile, voiceless writing is just as obvious. - -- **Have opinions.** React to facts instead of neutrally listing pros and cons. -- **Vary rhythm.** Short sentences. Then longer ones that take their time. Mix it up. -- **Acknowledge complexity.** "Impressive but also kind of unsettling" beats "impressive." -- **Use "I" when it fits.** First person isn't unprofessional. -- **Let some mess in.** Perfect structure looks machine-made. -- **Be specific.** Not "this is concerning" but "there's something unsettling about agents churning away at 3am." - -## Patterns to detect and fix - -### Content - -1. **Puffery.** "pivotal moment", "testament to", "evolving landscape", "setting the stage for", "indelible mark", "deeply rooted". Cut puffery, state what happened. -2. **Name-dropping.** Listing media outlets without context. Pick one, say what was said. -3. **Superficial -ing phrases.** "highlighting...", "ensuring...", "reflecting...", "showcasing...", "fostering...". Delete or expand with real sources. -4. **Promotional language.** "nestled", "vibrant", "breathtaking", "groundbreaking", "renowned", "stunning", "must-visit". Use neutral descriptions. -5. **Vague attributions.** "Experts believe", "Industry reports suggest", "Some critics argue". Name the source or delete. -6. **Formulaic challenges.** "Despite challenges... continues to thrive." Replace with specific facts. - -### Language - -7. **AI vocabulary.** Additionally, crucial, delve, enduring, enhance, fostering, garner, interplay, intricate, landscape (abstract), pivotal, showcase, tapestry (abstract), testament, underscore, vibrant. Replace with plain words. -8. **Fancy ways to say "is".** "serves as", "stands as", "boasts", "features". Just say "is" or "has". -9. **"Not just X, but Y."** State the point directly instead. -10. **Rule of three.** Forcing ideas into groups of three. Use the natural number. -11. **Synonym cycling.** Protagonist, main character, central figure, hero all in one paragraph. Pick one, repeat it. -12. **False ranges.** "from X to Y" where X and Y aren't on a meaningful scale. List topics directly. - -### Style - -13. **Em dash overuse.** Avoid em dashes entirely. Use periods or commas only (no parentheses, no en dashes, no hyphen-as-dash substitutes). Em dashes are an AI tell, and reaching for parentheses instead just trades one tell for another. If a thought needs separation, end the sentence or use a comma. -14. **Colon overuse.** Colons are fine before a list or example. Not as mid-sentence connectors. "If you're coming from traditional automation: instead of registering event handlers, you describe conditions" adds nothing with the colon. Rewrite to let the point stand on its own without comparison framing. "Describing when the scheduler should fire works best as plain English." Same meaning, no crutch punctuation. -15. **Boldface overuse.** Don't bold every proper noun or acronym. -16. **Inline-header lists.** The tell is a bold label and colon that restates the line: "**Performance:** Performance improved...". Convert those to prose. A bold lead-in that ends in a period, names the item, and is followed by genuinely new detail ("**Schema in TypeScript.** Tables live in one file.") is fine, not a tell. -17. **Title case headings.** Use sentence case. -18. **Decorative emojis.** Remove from headings and bullets. -19. **Curly quotes.** Replace with straight quotes. - -### Communication artifacts - -20. **Chatbot phrases.** "I hope this helps!", "Let me know if...", "Of course!", "Certainly!", "Found the smoking gun!" Remove. -21. **Cutoff disclaimers.** "While specific details are limited..." Find sources or remove. -22. **Sycophantic tone.** "Great question! You're absolutely right!" Respond directly. - -### Filler - -23. **Filler phrases.** "In order to" becomes "To". "Due to the fact that" becomes "Because". "It is important to note that" gets deleted. -24. **Excessive hedging.** "could potentially possibly be argued that it might" becomes "may". -25. **Generic conclusions.** "The future looks bright." State specific plans or facts. - -### Jargon - -26. **Abstract metaphor nouns.** Substrate, wedge, vector, locus, vantage, nexus, primitive (as noun), harness (as metaphor), surface (as in "API surface"), bedrock, scaffolding (as metaphor), modality, paradigm, gold-plating, ratchet (as metaphor), evacuate (for moving code), endgame, north star, flywheel. These read as technical but usually have a plainer concrete word. "Substrate" becomes "base". "Wedge in" becomes "add". "Vector" becomes "way" or "method". "Gold-plating" becomes "more than the job needs". "Ratchet" becomes the mechanism's real name or "a limit that only tightens". "Evacuate" becomes "move out". "Endgame" becomes "the last phase". Pick the concrete word. - -### Plain speech - -27. **Say what it does, not how it feels.** "the database stays close at hand", "SQL you can read", "types that follow your schema" name a feeling. The fix names the mechanism or a number: "`.toSQL()` returns the exact string sent to the database", "a column rename fails the build". Ask what the sentence tells the reader to do or know, then write that. If you can't restate it as a concrete instruction, fact, or number, cut it. One more check: if the sentence could appear unchanged in another project's docs, it says nothing about this one. Cut it. -28. **Shorten or split dense sentences.** If the reader has to backtrack to parse a sentence, break it in two or drop clauses. One idea per sentence. -29. **Active voice.** Prefer it. Catch "is/are/was/were + past participle" and name the actor: "queries are validated" becomes "the compiler validates queries", "the file is parsed by the loader" becomes "the loader parses the file". Passive is fine only when the actor is unknown or genuinely doesn't matter. -30. **Cut adverbs, or use a stronger verb.** "runs quickly" becomes "is fast" or the number. "significantly improves" becomes the measured delta. An adverb propping up a weak verb means the verb is wrong. -31. **Prefer the plain word.** "utilize" becomes "use", "leverage" becomes "use", "facilitate" becomes "help", "numerous" becomes "many", "in the event that" becomes "if". The fancier synonym is rarely clearer. diff --git a/.agents/skills/unslop/agents/openai.yaml b/.agents/skills/unslop/agents/openai.yaml deleted file mode 100644 index 8dc1480..0000000 --- a/.agents/skills/unslop/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Unslop" - short_description: "Remove AI tells and add a natural human voice" - default_prompt: "Use $unslop to rewrite this text without AI tells while preserving its meaning and tone." diff --git a/.github/workflows/standards-sync.yml b/.github/workflows/standards-sync.yml index 615343c..ea24495 100644 --- a/.github/workflows/standards-sync.yml +++ b/.github/workflows/standards-sync.yml @@ -97,7 +97,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.4.0 + bun-version: 1.4.2 - name: Install dependencies run: bun install --frozen-lockfile diff --git a/.github/workflows/standards.yml b/.github/workflows/standards.yml index cc18caa..3e7bd53 100644 --- a/.github/workflows/standards.yml +++ b/.github/workflows/standards.yml @@ -269,6 +269,9 @@ jobs: echo "::error::Found a browser a11y suite but no installed playwright binary. Declare @playwright/test in the workspace that owns the suite." exit 1 fi + # Playwright downloads its own Chromium. The runner's unused Chrome APT + # repository can break dependency installation with inconsistent metadata. + sudo rm -f /etc/apt/sources.list.d/google-chrome.list /etc/apt/sources.list.d/google-chrome.sources "$playwright" install --with-deps chromium - name: Check @@ -403,10 +406,10 @@ jobs: - name: Install pinned settings checker run: | set -euo pipefail - bun_version=1.4.0 + bun_version=1.4.2 case "$(uname -m)" in - x86_64) bun_arch=x64; bun_sha=2d03fb5fb83ac8b567aca0a281b2ce1a1a19d488f56c2968d88c3f25e92fe452 ;; - aarch64) bun_arch=aarch64; bun_sha=4b1a332ee861983eb93bcfe6f770fff94e3e31b2c388bdaea3c8ed35e58eed0e ;; + x86_64) bun_arch=x64; bun_sha=36368faef7527875d5ffa52e53cd48021741f2a83eb6208a8dd64068d422a913 ;; + aarch64) bun_arch=aarch64; bun_sha=54328bbc2d9c8e0c9f892c544d66c57a83b84139e34909e5ee81758f1ac8fda7 ;; *) echo "::error::No pinned Bun binary for runner architecture $(uname -m)" exit 1 diff --git a/AGENTS.md b/AGENTS.md index 4c049b5..02bec9b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,23 +1,18 @@ # AGENTS.md -This file is the root operating contract for agents in this repository. Keep root instructions for non-negotiable constraints; put specialized workflows in `.agents/skills/*/SKILL.md`. - ## Quality gates -- Never weaken a quality gate (lint, types, tests, a11y) to make a change pass. Fix findings in the code instead of downgrading or disabling rules. -- Every inline suppression needs a reason. Use a per-file override only when a rule genuinely cannot apply, narrowed to that path and rule. +Do not weaken quality gates to make a change pass. Explain inline suppressions. Use configuration exceptions only where a rule cannot apply, scoped to the affected path and rule. ## Change policy - Do not build backwards compatibility by default. Migrate every call site and delete the old shape in the same change. Do not add deprecated aliases, versioned copies, or compatibility-only optional parameters. - Ask before choosing product intent or another costly, durable direction. Assume no background knowledge or familiarity with the code; explain what is at stake, where each option leads, and recommend one before presenting technical evidence. -- Propose before changing CI workflows, quality gates, or canonical synced files, even to unblock a failure. The file class is the trigger. ## Package management - Use Bun only, at the exact version declared by the root `packageManager`. -- Add dependencies with `bun add`; do not manually edit dependency versions into `package.json`. -- Workspaces that rely on Bun runtime or `bun:test` types must declare `@types/bun`, not custom ambient declaration shims. +- Workspaces using Bun runtime or `bun:test` types must declare `@types/bun`. ## Architecture @@ -35,28 +30,22 @@ This file is the root operating contract for agents in this repository. Keep roo ## Effect standards -- Decode untrusted input with Schema before using it. -- Required for async work, concurrency, retries, timeouts, resource acquisition, cancellation, and injected dependencies; at service boundaries the error and requirement channels are the contract. -- Not required for total synchronous logic or UI components, which stay plain and consume Effect at the boundary. -- Never `throw` for expected failures; return typed Effect errors. Recoverable errors are `Data.TaggedError` classes with stable `_tag` values and actionable `message` fields. -- A workspace may opt out wholesale only for a stated architectural reason recorded in `AGENTS.local.md`; do not mix idioms inside one workspace. +- Use Effect extensively where it makes code more robust. Keep simple synchronous logic and UI components plain, integrating Effect at boundaries. +- Service contracts expose typed errors and requirements. Represent expected failures with `Data.TaggedError`, a stable `_tag`, and an actionable `message` instead of throwing. +- Workspace-wide exceptions require an architectural reason in `AGENTS.local.md`; keep each workspace consistent. ## Writing style +- Write plainly and directly. Avoid mannered prose, decorative metaphors, and stock phrases. Prefer literal wording and sentences that are easy to follow. - Use sentence case for reader-facing text — UI copy, labels, command-style actions, Markdown headings — preserving proper nouns, acronyms, filenames, package names, and domain terms. -- Comment only non-obvious intent. - Do not hard-wrap Markdown prose; keep each paragraph or list item on one logical line. -## Definition of done - -1. Test changed behavior and regression-prone states. Do not add tests that only pin trivial copy, static literals, or type-impossible states. -2. Search for stale references to changed concepts, names, paths, configuration, secrets, commands, public APIs, error types, or architectural patterns. Update docs and SOPS secret examples when needed. -3. Run `bun run check:fix` from the repo root for code changes. If it fails, read the full error, fix the root cause, and run it again. +## Documentation -For documentation-only changes, run a narrower verification when the full check would not add useful signal. +Write documentation when it helps someone use, operate, or change the project. Keep it concise and current; do not narrate the implementation or repeat what the code makes clear. Put local rationale near the code and change history in PRs. ## Project-specific rules -This file is canonical and synced from the standards template — do not edit it locally. Project-specific rules that extend this contract live in `AGENTS.local.md`; add local guidance there instead. +This is a canonical file from the standards repository. Project-specific rules belong in `AGENTS.local.md`. @AGENTS.local.md diff --git a/nix/standards-bun.nix b/nix/standards-bun.nix index 480f667..141de24 100644 --- a/nix/standards-bun.nix +++ b/nix/standards-bun.nix @@ -7,7 +7,9 @@ }: let bunVersion = - if builtins.match "^bun@(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" packageManager == null then + if + builtins.match "^bun@(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$" packageManager == null + then throw "packageManager must pin an exact bun@x.y.z version" else lib.removePrefix "bun@" packageManager; @@ -22,6 +24,16 @@ let hash = "sha256-SxozLuhhmD65O8/m93D/+U4+MbLDiL2uo8jtNeWO7Q4="; }; }; + "1.4.2" = { + x86_64-linux = { + asset = "bun-linux-x64-baseline.zip"; + hash = "sha256-xngEDxT+BEDrg503y9DOTAUaMtpygGrJfeamqra/co8="; + }; + aarch64-linux = { + asset = "bun-linux-aarch64.zip"; + hash = "sha256-VDKLvC2cjgyfiSxUTWbFeoO4QTnjSQnl7oF1jxrI/ac="; + }; + }; }; versionSources = sources.${bunVersion} @@ -30,10 +42,12 @@ let versionSources.${stdenv.hostPlatform.system} or (throw "Bun ${bunVersion} is not available for ${stdenv.hostPlatform.system}"); in -bun.overrideAttrs (_final: _previous: { - version = bunVersion; - src = fetchurl { - url = "https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${source.asset}"; - inherit (source) hash; - }; -}) +bun.overrideAttrs ( + _final: _previous: { + version = bunVersion; + src = fetchurl { + url = "https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${source.asset}"; + inherit (source) hash; + }; + } +) diff --git a/sync-standards.json b/sync-standards.json index 1659a29..86a6df3 100644 --- a/sync-standards.json +++ b/sync-standards.json @@ -17,7 +17,6 @@ ".agents/skills/secrets-and-config", ".agents/skills/screenshots-in-prs", ".agents/skills/standards-sync", - ".agents/skills/unslop", ".agents/skills/ux-ui", ".claude/skills", ".claude/agents/reviewer.md", diff --git a/sync-standards.lock b/sync-standards.lock index e912d6c..7e9a8f1 100644 --- a/sync-standards.lock +++ b/sync-standards.lock @@ -1,33 +1,32 @@ { "upstream": "github:davidvornholt/standards", - "sha": "c3a6b3224dc11dffe32f47e350923dfed9c8b5ac", + "sha": "343a9d3b795c204bda384e16d6c731051f9851c2", "files": { - ".agents/skills/ci-pipelines/SKILL.md": "87728fdc98f9ac95df243ef4c1d44d616cc0440e2ae384da28dda83d627e00a0", + ".agents/skills/ci-pipelines/SKILL.md": "2b9acdddb838273af146f1aaaecaa0edbb9ca460da8bb32535fe06a74e7e2cba", ".agents/skills/database/agents/openai.yaml": "4c9f47df24219d5b195c88eef374431f64b453ed1ba057f50d4c11e4549343c1", ".agents/skills/database/SKILL.md": "cc89d5118e76537321ec3f7da1ef46fcbc7606ab6b44a630ef80373e6f28f988", ".agents/skills/declarative-infra/agents/openai.yaml": "d8631747495397fa341d956c20d7b3155b68f8f5a86b9d8015e654cb61e3d649", - ".agents/skills/declarative-infra/references/bootstrap.md": "c58fbf33207d471364ab1ffe4aae870d0a6a9285f0a1160137cccf3ee5dd6f47", - ".agents/skills/declarative-infra/references/image-promotion-contracts.md": "127b73ede1e3210e59cf8ac90c3a14b2401a486e32d0ee70f02ad08cc305b121", - ".agents/skills/declarative-infra/references/image-promotion.md": "7b363f5f4b360cab3ea5f8366581e8b4bafdd33ef16284b9ef8ddea38517429f", + ".agents/skills/declarative-infra/references/bootstrap.md": "a7e6c96de118870f43059ab745b72fcd9c387bb5cc2e25c65393f95442303f01", + ".agents/skills/declarative-infra/references/image-promotion-contracts.md": "2cb57660e0ee45ec9b2bb48add089b960d986e174a0763ee74a5223e9ca0b33c", + ".agents/skills/declarative-infra/references/image-promotion-groups.md": "dc6e8f2ad41a47c11f5a2d438c45f4ff3def7a2dca6584a85c958873e91c97f5", + ".agents/skills/declarative-infra/references/image-promotion.md": "d5796d16e668c8e335b8b824a99adde3131c138cb32236b33bb5f055ef59a16b", ".agents/skills/declarative-infra/references/pr-previews.md": "6553e36e29976f736570661b757b23c246f45ee01ccafba6ad8ef35c2bd47a89", ".agents/skills/declarative-infra/references/secrets.md": "0c7f0a6c2a4243fc58a4568d426071720a58ae07f29cada1e63ac49b5026ef3f", - ".agents/skills/declarative-infra/SKILL.md": "870e68c440404ae4bf45bbe6da472158e9047c61b73c45369810941a4c0202ee", + ".agents/skills/declarative-infra/references/vendor-image-promotion.md": "8ab7ab748cd428c30ab79c1d46489748c96768bb32ab7d88f3bd1ba8ff9ad683", + ".agents/skills/declarative-infra/SKILL.md": "58452e1ddc6f4f972a16d3b697e50696f2021a1f5ab319ef993774ab418669cb", ".agents/skills/frontend-design/LICENSE.txt": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594", ".agents/skills/frontend-design/SKILL.md": "92e164e0553d9551688107161189fb8b9d936ebaf3c432f217d131fcb144ca3e", ".agents/skills/issues/SKILL.md": "c0e3304564a7b5d532bc69995ae86f0e45d3fdaa1bdc1b383fcf4dded7ac62d9", ".agents/skills/nextjs/SKILL.md": "f9cd2a37e05246f3a371efed3a71380c3b83f9c0ee32371d57ac0f4f260cd005", ".agents/skills/prs/SKILL.md": "03eadfac76d5b1e92a576e41e18d0eb64c3c98da6003f79dcda6fe0d069acddc", - ".agents/skills/review-fix/SKILL.md": "0ed723a8d85ec0d8d320f1ccc58bee3afd25d7ceff0fc56fed482336034218c7", - ".agents/skills/review/SKILL.md": "4ca22b900fda25a9d303b017b0059bcd4a29743f5f50f1c7d5d02f24efc9508c", + ".agents/skills/review-fix/SKILL.md": "2cb0cd4a50b477f13d2a6cf431a8ec7c6c2b5203feb109a8751a9578b53234c6", + ".agents/skills/review/SKILL.md": "5e0f3b899ca68e1b124a0d60fc9b9db0e8262bce9f3c73963494bc556edf835e", ".agents/skills/screenshots-in-prs/agents/openai.yaml": "c412b92301b5d1ab50f62def7b845adf27b7a10bfd9491e270f544a7500661af", ".agents/skills/screenshots-in-prs/SKILL.md": "1b0992cf3d5a7c6a42ddfa66f0f3e7d233e1e94553e12f2e76d1915d7a358f21", ".agents/skills/secrets-and-config/SKILL.md": "5d8d0360ed533f507b441db5d703a091262cee9a3c40b22c6586a00f2e0b3081", ".agents/skills/standards-sync/agents/openai.yaml": "438b20601fde0e393a7d431f9795b6b9ac9bed256386d93ab414de917130c478", - ".agents/skills/standards-sync/references/github.md": "5c764f71d0d691d218f23b33fa5abf4425dd70c6f637837e5d15b05e9c1c04cd", + ".agents/skills/standards-sync/references/github.md": "6ffcffab7014f1b169ae570be1aeed7d588240e65fb85eeed6a658fb649325b3", ".agents/skills/standards-sync/SKILL.md": "b04b516cf2bc333a4636f3d734022b115983b300df2300e962b9f95326711e62", - ".agents/skills/unslop/agents/openai.yaml": "572b933f5f24d650386308d288ec3465a3e26ddcc95ec83e64b13fc46b8ab0ee", - ".agents/skills/unslop/LICENSE.txt": "bc957ca6bee02792566a1a028d105e02e247c6e77cf057061674273da77b200e", - ".agents/skills/unslop/SKILL.md": "181883e539caec8258ec9129e3ba5f133409144a2cbf2aa361158ab94cfc3441", ".agents/skills/ux-ui/SKILL.md": "b6edf5c2ccba99517542a147d38d5b8468fe2faf322ab04683a61fee5030be15", ".claude/agents/reviewer.md": "693e55cbae5f28954cefda6c475e97ddf62f4a58216c3cdf792852104b9e8d76", ".claude/skills": "46e7c0c6e8f3bd5ac2cb8978db058041d60b9034ba2a60c7268777a1438ff117", @@ -37,14 +36,14 @@ ".github/dependabot.base.yml": "1ef94dc3ecc9e3e39f34042bb37cfbf128f8dcde56b14243d6315f1e1b783db3", ".github/settings.json": "a06b6aa69b27bf1c3b8ebf25d90131a9653f54bc1ec08384a92581627527911c", ".github/workflows/notify-pause.yml": "ea228580fb33b34dd5c41a690f09158807d4486cf0647e557f830486a6d7a0f3", - ".github/workflows/standards-sync.yml": "5d77b45d539945967a9d61d6e4bb3b57b3d48ef6dad09017a3455a801657b7f2", - ".github/workflows/standards.yml": "ce9af99341a549cb91b2ae430a6a774d20bdb6c0396fc2b7d9020c9815fcb26a", + ".github/workflows/standards-sync.yml": "0565e5d9c1dc7f8ac08fe433c8866ea36853a502b7f96c2988361f0e9e6f3ae8", + ".github/workflows/standards.yml": "6aa762b627462fe74999c9a2ef8e89f560c7523cd2a899b9ce6bdaa9eb4c52bc", ".mise/config.toml": "650a919aa3f06f5dfe20a047250c84f7afbfcc8886862c4421c61e815d2fd3f6", - "AGENTS.md": "3feb223fc28578673c7b8fd58a682250dba166a89487dfb389ac58c61387851a", + "AGENTS.md": "8109abf34f1ce0a6175bffb08bf6993a937a4bbdb368812a1d2ac89bf1d2ba3d", "biome.base.jsonc": "abf829f063a411a369d6ab8241b0c1b37149ee7d444cb962e5050abdc1d10c4a", "CLAUDE.md": "336cc4fbf19beaada7ccf9986414fa91851a8d7a07dfb3ccbe800a69eed0ab49", "justfile": "2b52e2a98fbdbe43aef6134e08f1937642f7fb3aa46f195e94fb01793465a45c", - "nix/standards-bun.nix": "5b1776c85e29fc3936f0bd5c9b3ef2832ecadf4508ef33c984cf8993e7a7012a", + "nix/standards-bun.nix": "395e7524721e2dc6b3a558ddadfbeef4e371da900401bf590ba687304eb5160a", "packages/a11y-testing/package.json": "930542057f329fff22aab3c101e64e09fb2202221a671db75cd61b2025651470", "packages/a11y-testing/README.md": "73be1fcbf6e63df2e3e28cb3355fe03ee00452d82fa06497a01fd963dc4488be", "packages/a11y-testing/src/axe.ts": "8dc8fee67bc124ebef4b8eefa3aa8cc3deb22522fa134d455a8156d5b20a7949", @@ -60,6 +59,6 @@ "packages/typescript-config/tanstack-start.json": "8a9102e47af6a72daab2b9f0baaa53dea98da19651fb01002dfe380ab9056cc5", "packages/typescript-config/tsconfig.json": "a7ad2417a335f31b5ce66273618ce64bf365a10a58cb06c598487a68ead7391c", "secrets.just": "c1d4d6557df5d96702179717839cee435b63df4f36fe38581a32a6359c6fd548", - "sync-standards.json": "81de04114ab939898798ee19916e075ce36a8821446dbdd55637de31b6289305" + "sync-standards.json": "8f59d1a02d09b4178882a21a0799c2fdc6d7c0bf2820ef5d09908492971cb1a7" } }