No description
  • Shell 75%
  • Dockerfile 22.1%
  • JavaScript 1.9%
  • Makefile 0.7%
  • Python 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Sean OMeara a91a501e38
fix(rpg-munchers-tng): no schema payload and no arcade-clock reel for the twin
The twin's source stage is the plain `source-base`: it has neither the Go
schema installer nor the qualification client that the rpg-munchers stages
build from that repository's pipeline tooling. The twin records the SQL
schema as generated DDL text and installs only what its recovered content
and retained objects give, so its recipe declares no `database_schema`,
and its reels are the seven scenarios that need only zelnet.

Claude-Session: https://claude.ai/code/session_01HPdKaFBKQVs8hzfgXMrnwV
2026-09-14 16:02:50 +02:00
imports test(rpg): verify native and imported reels in cluster browser 2026-09-06 04:01:56 +02:00
recipes fix(rpg-munchers-tng): no schema payload and no arcade-clock reel for the twin 2026-09-14 16:02:50 +02:00
scripts fix(ibmi-rpg): stage the loss profile and apply the routine loss from the recipe 2026-09-14 15:21:36 +02:00
tests test(ibmi-rpg): pass the platform refutation pattern with -e 2026-09-14 15:40:55 +02:00
AGENTS.md refactor(tests): the last Python harness in this repository is gone 2026-09-10 20:23:26 +02:00
bundle.json fix(ibmi-rpg): render reels on the worker's native platform with cast-render v9 2026-09-14 15:40:14 +02:00
CLAUDE.md docs(agents): never use Python; tests and guards use bash and jq 2026-09-10 15:07:43 +02:00
Makefile fix(ibmi-rpg): stage the loss profile and apply the routine loss from the recipe 2026-09-14 15:21:36 +02:00
object-store.json feat(catalog): register the rpg-lemonade-tng and rpg-munchers-tng twins 2026-09-14 14:34:08 +02:00
README.md feat(catalog): register the rpg-lemonade-tng and rpg-munchers-tng twins 2026-09-14 14:34:08 +02:00

binjovi-plans

This repository is the protected Binjovi project catalog.

hetzner-node-image uses a fenced native task to control one isolated Kubernetes Job. It builds a file-backed ARM boot disk and checks an actual QEMU boot before it exports the disk package. Kernel and ZFS compilation stay in separate native cross builds. The image project has no deployment target and cannot change a worker. Its schema 27 requires the matching Binjovi node-image controller support before activation.

bundle.json is the activation unit. Binjovi integrates a Release into the protected trunk branch, tests that exact revision, and then promotes it to main. The main push sends a signed Forgejo webhook to Binjovi. Binjovi reads the current main commit and then reads this file by that exact commit ID. It validates every project before it makes the revision active.

Promotion to main is fast-forward-only. Before a Release, trunk must contain the current main revision. This rule keeps the protected revision history linear and lets Binjovi prove the exact active plan revision.

The Build path is a fixed plan_bundle operation in Binjovi. It reads the exact bundle.json bytes and validates them in the control-plane process. It does not start Docker, BuildKit, an Argo Workflow, or a shell. The Release is a revision tag and Forgejo Release. It does not publish an OCI image. Text-only changes use this same in-process path.

Binjovi schema v12 uses separate long-lived native agents for pull-request Builds and combined-trunk Release image Builds. Each lane has an isolated BuildKit daemon, cache, identity, and worker label. Binjovi does not activate this bundle until an exact-revision Release agent is healthy.

Seanfarm uses the schema v14 native GitOps route. An ordered changed-path policy selects only the static checks, Kustomize roots, Flux Kustomizations, and availability checks that a change can affect. Build runs the selected static checks. Release validates the exact combined trunk revision. Deploy publishes that revision to Codeberg, waits only for the selected Flux objects, and tests only the selected service endpoints. Unknown paths fail closed.

The six RPG projects use the schema v20 native route. Their shared Dockerfile, artifact helper, and runner are in recipes/ibmi-rpg. Each project has a recipes/<project>/recipe.json file. Tasks read these files from the exact active catalog revision. Recipe changes do not require a Binjovi server release.

Linux uses the native kernel route. Build reads the exact Linux source and this catalog revision inside the cluster. The pinned amd64 toolchain compiles the full amd64 kernel and cross-compiles the full ARM64 kernel with aarch64-linux-gnu-; it does not use QEMU or binfmt. Both builds check the built configuration and BTF section. Each build also makes one kernel RPM, derives the in-tree ZFS version, and checks the RPM payload against the compiled kernel image. The thin kernel-artifact and kernel-artifact-arm64 targets contain those RPMs, recoverable configs, and versioned hash metadata. Negative tests reject changed bytes, wrong source/recipe identities, unsafe paths, extra RPMs, symlinks, missing BTF and loadable modules. The node-bootc target replaces the kernel in the digest-pinned current node OS. It builds the initramfs with no network, then checks the actual installed kernel bytes, one kernel tree, ZFS version, ostree root support and masked automatic updates. All non-kernel RPM versions and critical node files must remain equal to the pinned base. The image retains config and hash metadata under /usr/lib/binjovi/kernel. Its boot evidence explicitly says no real boot has been verified. The check target includes the amd64 boot image assembly and verified ARM64 package evidence. ARM64 node-image assembly and boot qualification remain separate gates before an ARM64 node can join the rollout. Linux source must not pass through a macOS checkout. Admission publishes no image and starts no node operation. Binjovi refuses Linux Release before merge until native boot-image publication and node deployment are ready. No Linux action falls back to Pipelines or Argo.

The RPG worker image must include jq, diff, node, zelnet, and rpglint. The reel image must encode real H.264 video; an encoder name in FFmpeg is not sufficient. Run make test-rpg-artifacts in the pinned publisher image and make test-rpg-reels in the pinned renderer image in the cluster. The reel test uses the actual Dockerfile command and checks H.264 video, JPEG previews, and failure when recordings are missing.

Release records the compiled program and display-file metadata in compiled-objects.txt. Deploy checks that record against staging before it changes production, then against production after the copy. Missing or different evidence fails the Deploy. Existing physical and logical file data stays intact. Run bash recipes/ibmi-rpg/test-object-evidence in the pinned IBM i worker image in the cluster. make test-rpg-objects is the equivalent Make target. See RPG object verification for the proof boundary and the read-only live check.

Run:

make check

The local and in-process checks verify the envelope, sorted project order, unique project names, unique repository names, required bootstrap projects, and canonical JSON format. Binjovi applies the full versioned project schema before activation.

Keep each project on the same phase boundary:

  • Build compiles code and runs static checks and unit tests.
  • Release runs integration tests for the exact combined revision and creates immutable artifacts.
  • Deploy runs functional checks against the deployed artifact.

The native Go operator recipes use Alma-based build and envtest images. Each architecture build runs on the native CPU and sets the Go target architecture. Athens and BuildKit cache mounts retain modules and compiler output. The final image contains the static manager and CA certificates, runs as a non-root user, and does not contain the build toolchain.

Every image a project names must run on the fleet. The fleet is ARM64 and has no emulation since 2026-09-12, so an AMD64 image fails with an exec format error before it does anything. zelnet pins the ARM64 half of its install test matrix for that reason: its AMD64 packages are still cross-compiled and published, and they are no longer installed and executed by the pipeline. The matrix is a ceiling, so a project may name fewer testers than the maximum.

The platform Flux plan includes APIs, instances, post-instances, and Crossplane RBAC. Activate it only after the platform agent has the matching runtime and can obtain its project-scoped OAuth token.

object-store.json

object-store.json declares the RustFS tenant: the buckets with their lifecycle rules, and the identities with, for each one, its exact policy, the buckets it may reach, the buckets it must be DENIED, and the namespace, Secret name and key names its credential is delivered under.

RustFS has an independent reconciler with durable PostgreSQL state. Ordinary catalog releases do not create a Deploy. Catalog activation wakes the reconciler, which reads this file from the exact active revision. A changed document, mounted credential fingerprint, executor image or agent process identity selects new work. The catalog revision alone does not. A scan each minute repairs missed wakeups and startup work; a completed generation schedules a drift check fifteen minutes later.

Reconciliation creates the declared buckets, lifecycle rules, policies and users, attaches policies, and proves the complete permitted and denied access set. Before adding a user again, it authenticates the desired credential against a private grant. An unchanged authenticated key avoids the expensive password derivation. User existence and public reads do not count as authentication. The full policy and access proofs still run, and missing proof fails the generation.

A catalog release NEVER carries storage work. The embedded catalog project declares no deployment target, and ObjectStoreReconciler.automatic_target/3 -- the last code that could give it one -- was removed on 2026-09-13. The compatibility and recovery route described here until then is gone: a failed generation is repaired by the reconciler or by the reconcile request below, never by releasing this catalog again. Binjovi still substitutes its embedded catalog-project definition over this bundle, so the catalog cannot redefine its own authority. Independent native tasks retain their own generation, input, log and result evidence.

This file declares the tenant's SHAPE and nothing else. Credential generation, freezing and delivery belong to External Secrets in namespace rustfs. Binjovi never writes a Secret into a consuming namespace: it is arbitrary code execution as a service, and that grant would make a CI compromise a cluster compromise. The secret block on each identity records where the credential is delivered so the two halves can be checked against each other.

tests/object-store-test refuses a bad document before it is committed, and Binjovi.Domain.ObjectStorePlan refuses one that reaches the control plane by any other path. The two must agree. Two rules in both carry lessons already paid for: the wildcard is refused in the BUCKET position only -- s3:* as an action over an identity's own bucket stays legal -- and the endpoint is the ClusterIP Service with an explicit :9000, never the headless name, which hands out individual server addresses and loses an upload during a node roll.

The thirteen native package identities -- the zdns, zelnet and zerotea build/read/promote keys, the three IBM i RPG keys, and the zelnet SDK reader -- are declared with their MinIO policies verbatim and under their MinIO user names. That is deliberate: every package agent mounts its binjovi-native-*-s3 Secret and reads accesskey and secretkey, so keeping the names means the value in accesskey never changes and the cutover is a store change in one ExternalSecret. Their policies are prefix-scoped inside the shared workflows and pkg buckets; the authority maps each resource to its bucket by the first path segment for the buckets and denied lists.

THE PROOF FOLLOWS THE POLICY, NOT THE BUCKET LIST. The first converge of these identities (2026-09-10) ran mc ls workflows for zdnsread and failed, because a key that may list only workflows/zdns/ refuses the bucket root BY DESIGN -- the policy was right and the proof was wrong. Each grant is now proved at its own target, one proof per verb: a s3:ListBucket under a StringLike s3:prefix condition is proved by listing each prefix; an object grant bucket/prefix* by writing and/or reading under that prefix; an exact key by reading that key, which must already exist, because a probe there would overwrite it. The ROOT of a bucket reached only by prefix is proved as a denial in each verb not granted there, alongside the declared denials. Two rules are refused before any mc call: s3:PutObject on an exact key, and any condition other than a ListBucket prefix, because the converge could attach either and prove neither. Binjovi.Domain.ObjectStorePlan.proofs/1 derives the same list from this file and the evidence must equal it exactly.

pkg is readable by EVERY principal once it carries anonymous download -- measured on RustFS: a key with no grant on pkg listed it, exactly as MinIO evaluates a bucket policy for authenticated callers too. A read denial on a public bucket is therefore unprovable and is not asked for; the write denial is. Declare a public bucket knowing that every identity in this file can read it.

binjovi-candidates-read is the control plane's own read key for workflows/<project>/candidates/ on every package_set project. The dashboard serves a Build's candidate files to a signed-in user through GET /api/v1/projects/:project/builds/:build_id/candidates/:name, so the bucket stays private and the browser never holds a store credential. The key cannot write. The workflows bucket expires each candidates/ prefix after 30 days, one rule per project, so a candidate link lasts 30 days. A candidate older than the current base cannot be released anyway: the release train refuses it with candidate_catalogs_diverged and needs a rebuild. The binjovi route reports the same 30 days.

migration-writer, the whole-bucket writer the workflows and pkg mirrors used, is gone: both mirrors reached zero, their directory left seanfarm, and an identity nothing declares a consumer for must not keep a key on the store.

pkg carries "anonymous": "download". It is the public RPM repository, and pkg-host, the edge that serves it, is a plain HTTP proxy with no S3 SDK and no request signing, so the bucket must answer unauthenticated GETs. The bucket key set is closed -- a misspelled key is refused rather than ignored -- and download is the only value accepted: mc would also take upload and public, and an anonymous write grant on the store this cluster installs its packages from is not something a typo may arrange. The converge sets the policy on EVERY bucket, none where undeclared, and proves it over plain HTTP with no credential: pkg must answer, the canary must refuse.

When a converge fails

Use authenticated GET /api/v1/object-store/rustfs-tenant to inspect the generation, pending task, last verified task and error. The task retains the failed proof and logs. Credential reports contain fingerprints, never key values; the status response omits the credential fingerprint.

After fixing the cause, changed inputs trigger another generation. Otherwise, a failed attempt schedules reconciliation after fifteen minutes. To request an earlier retry, a release operator can send authenticated POST /api/v1/object-store/rustfs-tenant/reconcile with JSON {}, content type application/json, and a new Idempotency-Key header. Repeating the same key has no additional effect. A request during active work is retained for one generation after it finishes. The request cannot supply a document, image, credential or proof. A new catalog release is not required just to retry.

A converge that fails leaves the tenant part-converged, in declaration order, and that is deliberate: an identity whose scoping could not be proved is left failing loudly rather than rolled back into a state nothing declares.

Recovery project

rpg-demo uses offline Python checks and Git revision releases. It has no IBM i deployment target while source recovery is under test. The plan-owned runner fails if recovery tests are absent, empty, or unsuccessful. The test step has no network access or IBM i credentials. The other five RPG projects keep their IBM i build and deployment profiles. Old rpg-demo release evidence is retained.

The Hetzner node-image plan uses node_image_job.v1 on the native admission lane. It controls the isolated file-backed bake Job and has no node deployment target. Activate this plan only after the runtime and gateway support it.

Its build.source_profile remains native.v1. Its explicit build.tool_image selects the smaller runtime with the same native recipe and source format:

registry.sean.farm/build/node-image-tools:v3@sha256:0c23a97c3a56bd6f238af7ee8229d3596b4d21ad7c09a2bf14cccbb0ec4da66c.

Release this selection only after the isolated v3 bake has passed complete Job/Pod ownership, disk and boot proof, and an independent candidate digest read, and after the matching reader is deployed. Check that the next ordinary Build freezes this exact pin. This selection has no worker deployment target. The old omitted selection remains readable and uses the original v2 pin: registry.sean.farm/build/node-image-tools:v2@sha256:04ba97d4fcb243c5cbbdfee0cc233cf2a924ff2911a385994ab039140d334bf6. The reader refuses unlisted image pins and commands. The source must declare the exact profile in NODE_IMAGE_PROFILE; retained legacy recipe and package files are ignored by the native reader.

The explicit build.package_cache: "none" removes registry cache import and export from disk packaging. The disk bake and boot test still run. Activate this field only after the compatible controller reader is independently verified. Keep the old cache tag for frozen tasks. Check that the next ordinary Build freezes this policy and publishes the verified disk package. Export durations overlap; compare the complete package phase before reporting savings. To restore package caching for new work, remove only build.package_cache.

To roll back new work to v2, remove build.tool_image and build.package_cache and keep the native source profile. Old tasks keep their frozen v2 or sysadmin image and source revision. Removing the source profile requires a source revision with the legacy recipe and package files; it is not the v3 runtime rollback path.