- Shell 75%
- Dockerfile 22.1%
- JavaScript 1.9%
- Makefile 0.7%
- Python 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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 |
||
| imports | ||
| recipes | ||
| scripts | ||
| tests | ||
| AGENTS.md | ||
| bundle.json | ||
| CLAUDE.md | ||
| Makefile | ||
| object-store.json | ||
| README.md | ||
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.