cs-image-system-system
The command-line host of cs-image-system. It installs one console script,
cs-image-system, that loads the plugins, reads a configuration tree and
drives the lifecycle runner, the state tools and the value tools of
cs-image-system-base. The package holds no builder logic of its own:
every command delegates to the base library or to a plugin.
Licensed under Apache-2.0. Requires Python 3.13 or later. Declared
dependencies (pyproject.toml): cs-image-system-base
(pinned to the same version), typer, rich and PyYAML. The entry point
is cs_image_system.system.cli:app (a Typer application); python -m cs_image_system.system runs the same application through a wrapper that
prints Error: <e> and exits 1 on any exception the application lets
through.
Source: src/cs_image_system/system/cli.py.
Contents
- Invocation and output
- Global options
- What the callback does before a command
- Commands
- Credentials
- Commands the generated runner scripts call
- Exit codes at a glance
- Tests
- Prerequisites and integration
- Configuration reference
- What it tests and verifies
- When it fails
- Related
Invocation and output
cs-image-system [GLOBAL OPTIONS] <command> [ARGS]
cs-image-system --help
cs-image-system <command> --help
-h and --help both print help. Logs go to standard error through a
rich handler; standard output is reserved for machine-readable output (the
run summary JSON, the gid shim's JSON, a decrypted value, a materialized
path). A few one-line verdicts also go to standard output: run follows
its JSON with Run <id> completed: ..., validate prints Validation successful., preflight prints its session: lines there. Inside the
workspace, just cli <args> runs the CLI against the live configuration
root (CSIS_CONFIG_ROOT, a Justfile variable, not one the CLI reads).
Global options
These are options of the top-level callback and come before the command name. Which commands honour which is in the configuration reference.
| Option | Default | Meaning |
|---|---|---|
--root-dir PATH |
the current directory | The configuration root: the directory holding cfg/, groups/, storages/, images/, instances/ and meta-state/. A command that loads the configuration changes into it (resolved) during the load and returns to the starting directory when the command ends; the value tools, mask, materialize and preflight only record it; gate-plan, apply-check and identity export-gids ignore it. |
--verbose / --no-verbose |
off | Log at DEBUG, for every command. With a configuration load, also writes the string-stage-rendered document to YAML_DUMP.yaml in the directory the command was started from (it is written before the change of directory). |
--dry-run / --no-dry-run |
--dry-run |
Dry run: generation is complete and runner scripts are written, but deferred finalization commands (packer builds, tofu init/plan/gate/apply, system CLI steps) are enumerated, not executed. --no-dry-run executes them. The same flag governs dispose image, lineage restamp and lineage relabel, which report a plan under a dry run, and run --migrate-state, which a dry run refuses. reencrypt has a --dry-run of its own and ignores the global one. |
--overlay FILE (repeatable) |
none | A transient declaration file merged over the tree for this invocation only. config: keys override; named instances:/storages: (and other item) entries update or add; an entry with undeclare: true removes the tree's entry. A file that does not exist, is not a mapping, has a top-level key other than config and the item collections, or lists an entry without a name is refused before anything loads. The tree on disk never changes. |
--undeclare KIND:NAME (repeatable) |
none | Treat a declared tree entry as absent for this invocation, for example instance:gce-test. KIND is an item collection key (instances, storages, ...); the singular is accepted; anything else is refused before the load. |
--locked |
off | Hold the tofu lock for the whole command (stage 64, replacing scripts/with-tofu-lock): a directory .lock under TF_PLUGIN_CACHE_DIR, taken before anything else runs and released when the command ends however it ends. The provider cache is not safe under concurrent init, so every recipe that may execute the roots passes it. A second holder is refused with --locked: another tofu-using command holds <lock> (pid N) -- one tofu process at a time ... and exit 75; an unset TF_PLUGIN_CACHE_DIR is exit 2. A dry run never needs it. |
--base-only |
off | Makes build-all and generate select the base-image lifecycle only. The configuration is still read in full. |
--version |
Prints cs-image-system <version>, the installed release (the cs-image-system distribution's version, the one a tree's .csis-version pins), and exits. Eager: it answers before the callback runs, so nothing loads and --root-dir is not looked at. A starter's just init asks it for its init: cs-image-system <version> ready against <root> line; on 0.1.3 and before the option did not exist and that line gave the bare name (hygiene XVIII item 1). |
|
--only-providers |
Retired (stage 67, 2026-09-26): it was recorded, split and warned about (Only providers ... specified but configuration will still be read in full), and read by nothing; --only-runtime <rt> is the runtime filter. Passing it is No such option. |
There is no global --force (stage 63). It was "Force execution even if
the system balks", stored on the context and read by nothing; it was
removed on 2026-09-25, and passing it is now refused by the CLI parser
with No such option: --force (exit 2). The overrides that do something
are named for what they override and stay: run --force-bake,
gate-plan --allow-destroy, and test-mods --force (a different,
command-level flag that re-tests mods that already passed).
What the callback does before a command
The callback runs first for every invocation: it configures logging
(--verbose), remembers the directory the command was started from,
arranges to change back to it when the command ends, and then decides,
per command, how much to load:
| Commands | Before the command runs |
|---|---|
test, cleanup |
Nothing. They are retired names and exit 2. |
preflight |
Records the root (--root-dir or the current directory) and the resolved overlays. The command reads only the raw cfg/*.yml files for runtime_builders: (every file directly under cfg/, as the loader does, since stage 63 item 11) and cfg/_config.yml (and overlays' config:). No plugin loads, no configuration loads, no change of directory. |
gate-plan, apply-check, identity export-gids |
No configuration tree loads and every global option except --verbose is ignored. identity export-gids loads plugins on demand; apply-check reads only cfg/_config.yml (and its own --overlay files) as text. |
encrypt, decrypt, reencrypt, public-safe, materialize, mask |
Records the configuration root (--root-dir or the current directory). No plugin loads, no configuration loads, no change of directory. encrypt and reencrypt read encryption.recipients from cfg/_config.yml as text; public-safe reads public_safe.allow the same way. |
| every other command | Loads plugins. For run and state ... it first prints the credential session lines read from the raw tree and exits 1 when a session has expired. Then it reads the whole configuration (read_config_and_transform): the string stage, decryption of every ENC[age:...] value, structuring, builder construction and item reading, with overlays and undeclarations applied. The load changes the process directory to the (resolved) configuration root and REPLACES the Typer context object; the callback then records the invocation directory again (invoked_cwd) and the --base-only flag on the new object, so a command that runs from a terraform root (state-migration, prune-attachments) still knows where it was started (found live 2026-09-23; see When it fails). A load that raises prints Error reading config file : <e> on standard error and re-raises: a traceback, exit 1. |
Loading the configuration constructs every runtime builder, and the cloud runtime plugins validate their networking against the cloud during that step. See Credentials.
Commands
run [LIFECYCLES...]
Run one or more lifecycles of the meta-workflow in declared order. The
lifecycle names are identity, storage, base-image, instance-image,
the registered release and retention, or all. Aliases: id,
storages, base-images, base, instance-images, instances. Names
resolve in declared order, never in argument order. The hook plugins that
register release and retention are loaded before the names resolve
(found live: a fresh process once resolved --all to the four built-ins
because the hooks loaded only inside the run).
| Option | Default | Meaning |
|---|---|---|
--all |
off | Every lifecycle, registered ones included. |
--apply / --no-apply |
--apply |
After generation, execute the runner scripts that exist. Under a dry run they are enumerated. |
--commit / --no-commit |
--no-commit |
Make the meta-state commit (read-models, pins, lineage, launch parameters, generated IaC) in the configuration repository after the run. A root outside a git repository, or a gitignored path, is skipped with a warning, never an error. |
--state-query / --no-state-query |
--state-query |
First ask reality (read-only) and write generated/state-report.json; validation refuses to run on hard drift. --no-state-query keeps the existing report. |
--only NAME (repeatable) |
none | Restrict the bake surface to the named image(s), as <image> or <image>@<runtime>. Sources, build blocks and bake steps exist for nothing else; terraform roots are untouched. Unknown names fail the run (exit 1, the summary's error names them and the known names). --only none bakes nothing. |
--only-runtime NAME |
none | Restrict the bake surface to every image baked on that runtime. Terraform roots of other runtimes are not generated or planned. |
--apply-runtime NAME |
none | Let the storage and instance roots of that runtime apply, as if apply_storage and apply_instances listed it. Implies --only-runtime NAME unless --only or --only-runtime is given. The generated apply-check steps carry it. |
--force-bake NAME (repeatable) |
none | Bake the named image(s) even when current; all forces every image in the run's surface. |
--allow-unscoped-bakes |
off | Let a --no-dry-run run bake on runtimes outside its apply scope. Without it such a run refuses before any bake. |
--migrate-state NAME (repeatable) |
none | MOVE the named terraform workspace's state to the backend it now resolves to. Needs --no-dry-run: a dry run exits 2 before anything loads further. The root's runner then carries state-migration begin, init -migrate-state -force-copy, plan -detailed-exitcode, the gate and state-migration finish (see the runner scripts). There is deliberately no flag that merely proceeds past the state-location guard. |
The run summary is printed to standard output as JSON, followed by one
Run <id> completed: <lifecycles> line. Exit 0 on success. Exit 1 when
the run failed (validation, generation, apply or commit): the summary
carries ok: false and error, and Run <id> FAILED: <error> goes to
standard error. Exit 2 for an unknown lifecycle name, no lifecycle
selected, an unknown --apply-runtime / --only-runtime runtime, or
--migrate-state under a dry run.
build-all
Alias: run every lifecycle with --apply and --no-commit; with
--base-only, the base-image lifecycle only. Exit codes as for run.
generate
Alias: run every lifecycle with --no-apply and --no-commit; with
--base-only, the base-image lifecycle only. Exit codes as for run.
validate
Run every validation rule (unique names, executables present and
version-compliant, lineage policies, every registered validator) and
generate nothing. Prints each error to standard error as - <error>,
then Validation failed with N error(s).. Exit 0 when clean (Validation successful. on standard output), exit 1 with errors.
upgrade KIND NAME [--to BUILD] [--runtime RT]
Move exactly one pin. KIND is instance or image; NAME is the
instance or the instance image. --to names the target build id (default:
the head of the relevant series in lineage). --runtime selects which
runtime's pin moves for an image baked on several runtimes. For an
instance the move is understood as a destroy-and-recreate: the next
instance-image run plans a whitelisted replacement. Prints <kind> <name>: pin moved <from> -> <to>. Exit 1 on any error (unknown kind, unknown name,
unknown build, ambiguous runtime, already pinned there, a build baked on
another runtime), with the upgrade: ... message on standard error.
preflight [--strict]
Report the credential sessions the configuration's runtimes need (AWS SSO
profiles or static keys, GCP application default credentials), read from
the local caches without loading the configuration and without printing a
credential value. One session: line per credential source goes to
standard output (red when absent or expired, yellow when it expires within
the window). Exit 0 when every session is present (preflight: every session present on standard error); exit 2 when no runtime is declared,
when a session is absent or expired, or when a credential-shaped
environment variable (AWS_*, GOOGLE_*, OKTA_*, TF_VAR_*, CSIS_*)
is set but EMPTY (reported by name, never by value); exit 1 with
--strict when a session expires within
config.preflight.expected_run_minutes (default 30). A session is expired
when its minutes left are known and zero or fewer; exactly zero minutes
left counts as expired since stage 63 (until 2026-09-25 the test read
0.0 as one minute left), and a session whose expiry cannot be read is
never called expired.
encrypt [VALUE] [--file FILE ...] [--field NAME ...]
Encrypt to the tree's encryption.recipients. With a VALUE (or - for
standard input) prints one ENC[age:...] marker. With --file and at
least one --field, encrypts in place every scalar under the named keys
and every element of a block list under them, preserving every other byte,
and reports counts on standard error. Needs no identity. Exit 2 when
--file is given without --field, or neither a value nor --file. A
root whose recipients cannot be read -- cfg/_config.yml is missing or
unreadable, or declares no encryption.recipients -- prints
encrypt: <reason> on standard error and exits 1, for example
encrypt: <root>/cfg/_config.yml: no encryption.recipients declared
(stage 63; until 2026-09-25 it ended in a traceback).
decrypt [MARKER] [--json] [--file FILE ...] [--field NAME ...]
Decrypt one marker (or - for standard input) with the identity in
CSIS_CONFIG_IDENTITY and print the plaintext without a trailing newline.
With --json it speaks the terraform external data source protocol:
reads a JSON object of strings on standard input and prints it with every
marker decrypted and every other value passed through. With --file and
at least one --field it is the inverse of encrypt --file: every marker
under the named keys is written back in clear, in place, every other byte
preserved, counts on standard error. Exit 1 (decrypt: <reason>) on a bad
marker, a missing identity, a value none of the identities can open, a
--json input that is not an object, or no marker at all; exit 2 when
--file is given without --field.
reencrypt [--dry-run]
Rotate every marker in every *.yml / *.yaml under the root (skipping
.git and generated) to the current encryption.recipients: decrypt
with CSIS_CONFIG_IDENTITY, re-encrypt, write in place. Nothing is
written unless every value opens. --dry-run (the command's own flag,
not the global one) reports what would change. Exit 1 (reencrypt: NOTHING written -- <file>: <reason>) when a value cannot be opened, and
exit 1 with reencrypt: NOTHING written -- <reason> when the recipients
or the identity cannot be read (no cfg/_config.yml or no
encryption.recipients; CSIS_CONFIG_IDENTITY unset or not a usable
identity). Both are checked before any file is read (stage 63; until
2026-09-25 those two ended in a traceback).
mask [--min N]
Print a ::add-mask:: line for every value the configuration carries
encrypted, so a GitHub Actions log never shows one. It opens every
*.yml / *.yaml under the root (skipping .git, generated and
_private) with CSIS_CONFIG_IDENTITY and prints every line of every
plaintext at least --min characters long (default 8; a short common
word would mask unrelated log text). A file that cannot be decrypted is
skipped silently (a refusal is validate's to report), so with no
identity the command prints nothing and exits 0. Exit 1 on an error
(mask: <reason>). The CI jobs run it (just cli mask) before the
first step that touches the live tree.
materialize SOURCE [DESTINATION]
Copy a generated root (or one file) into the private mirror
<root>/_private/<the same relative path> with every ENC[age:...]
marker replaced by its plaintext, and print where it landed (no trailing
newline, so a script can cd "$(...)"). SOURCE is relative to the
current directory and, without DESTINATION, must lie under the
configuration root, whose generated/ directory name the mirror replaces
at the same depth. The copy is incremental (what the tools wrote in the
mirror survives; files the emission no longer has are removed), the
provider lock file is synced back, and the root's .gitignore gains
_private/ when the root is a git repository. Needs CSIS_CONFIG_IDENTITY
when any marker is present; loads no configuration. Exit 1 (materialize: <reason>) on a missing source, a source outside the root, an unreadable
identity or a value that cannot be opened. This is the wrapper every
deferred command in a runner script runs through.
init-config DESTINATION [--from NAME] [--force] [--changes-since PREVIOUS_STARTERS]
Write a starter configuration repository from this release (stage 64).
The release carries five starter trees -- standard-aws (the default),
standard-gce, complete, standard-aws-posix (no Okta; the posix
identity plugin, stage 75) and standard-docker (a docker host, no cloud;
stage 103), the source being the system repository's
docs/examples/ -- and DESTINATION that does not exist or is empty takes
the whole one: the YAML, the Justfile, the workflow, the public-safe
hook, .gitignore, tfmodules/ (module_source_base: tfmodules) and
.csis-version pinned to the running release. A DESTINATION that
already holds a configuration takes only the parts the release owns (the
same list without the YAML and the README), so an existing repository
gains them, or refreshes them after an upgrade, with its own files
untouched; a release-owned file that exists and differs is REFUSED by
name, exit 1, until --force overwrites it, and an identical one is
kept. Prints what was written and kept. Loads no configuration and needs
no credential. Exit 2 for an unknown starter or a destination that is a
file.
With --changes-since PREVIOUS_STARTERS nothing is written (hygiene XXI):
the command lists which release-owned files this release changed in an
existing tree, given the previous release's starters/ directory (its
wheel's cs_image_system/system/starters; just take finds it with uvx --from cs-image-system==<previous> python -c ...). A file the release did
not change is not listed. A changed file the tree still holds as the
previous release wrote it (byte for byte, or a workflow with only the
team's values where that release had REPLACE-ME) is listed to refresh; one
the tree also edited is listed apart; one the tree lacks is listed as new.
Exit 1 when any is listed, 0 when none, 2 when the previous release has no
such starter or the destination holds no configuration.
config-drift
Is the committed emission current with the declarations? (stage 45; a
command since stage 64, where scripts/normalise-emission and the recipe
that used it became this.) A headless dry run --all in a process of its
own over a private copy of the configuration under --root-dir (its
.git, generated/, _private/ and provider cache left out -- the cache
as .tofu-plugin-cache at any depth and as TF_PLUGIN_CACHE_DIR names it
when that lies inside the tree, since the copy's run reads the original's
through that variable; a relative module_source_base that escapes the
tree is copied beside the copy at the same relative place), compared with
the generated/ tree committed at HEAD. On both sides tool residue (.terraform, .terraform.lock.hcl,
tfplan, temp_assets, release/release, retention/retention), the
run-local files (run-summary.json, state-report.json,
manifest.json), run ids (<RUN>) and the run's date stamp in image
names (<STAMP>) are normalised; nothing else legitimately differs, so an
absolute path in the emission IS drift. Exit 0 (config-drift: the committed emission is current with the configuration), 1 with the file
count and a unified diff when the committed emission is BEHIND the
configuration, 2 when nothing is committed under generated/ at HEAD or
the dry run itself fails (its log's tail is printed). It takes minutes,
mostly the tofu init of every root, so it says on stderr what it is
doing as it goes: the copy (its size and time), each lifecycle as it
starts and each root's init, with the time since the dry run started,
then the comparison and the total; the verdict alone is on stdout. This
process loads nothing; the copy's run needs what a load needs (the sessions,
CSIS_CONFIG_IDENTITY, the identity credentials) and a dry run's init
skips the backend, so no state access.
runtime-unchanged RUNTIME [--ref REF]
Has a runtime's emission changed since REF (default HEAD)? (stage 45;
a command since stage 64.) The runtime's builder directories
(runtime describe -> emission: <lifecycle>/<builder> under
generated/) in the working tree, normalised as config-drift
normalises, against the same paths at REF in the configuration
repository. Exit 0 (... is unchanged since REF (<dirs>)), 1 with the
diff when a declaration of that runtime changed, 2 for an unknown
runtime. Loads the configuration. CI's performing job asks this of the
runtime that stays out of CI by the cost decision, so a change there
fails the job loudly rather than bake. The vendor's newest image moving
under a base with no build yet is not a change, read as config-drift
reads it: exit 0, ... unchanged since REF (<dirs>), but the vendor's newest image moved under a base this tree has not baked, and the
moves (hygiene XVII item 1; on 0.1.2 and before it was a change and
failed the job, with nothing to apply). A diff too long to show whole
is never read as a vendor move, by either command.
public-safe [--staged] [--tree PATH] [--config FILE]
Scan a tree (tracked plus untracked non-ignored files; default --root-dir
or the current directory) or, with --staged, the git index, for material
that must not be public: keys, tokens, PEM bodies, an age identity, a
service-account file, plans and state by name; addresses and long tokens
outside prose and tests. --config names the _config.yml whose
public_safe.allow applies (default: the tree's, or the frozen fixture's
inside the system repository). Prints every finding. Exit 0 when clean;
exit 1 with findings (public-safe: REFUSED -- N finding(s) in M file(s) (<scope>); allow a value by decision in cfg/_config.yml public_safe.allow, never by silence); exit 2 when --staged is used outside a git
repository.
test-mods [--image NAME ...] [--force] [--strict]
Run every modification of the instance images against a throwaway local
container, twice (apply, then idempotence), and record the results in
meta-state/mod-tests.yaml. --image restricts to named images;
--force re-tests mods that already passed; --strict fails when tests
are skipped (no docker). Results are printed as JSON, one FAIL <image>/<mod>: rc=<a>/<b> idempotent=<bool> <detail> line per failure on
standard error. Exit 1 on a failed or non-idempotent mod, or on skips
with --strict.
release [IMAGE] [--build ID] [--model NAME] [--note TEXT] [--runtime RT] [--declared]
Mark a tested build as released for a model (default default). Without
--build the series head is used (--runtime picks which runtime's
head). Prints the release entry as JSON. With --declared, releases every
image's verified head per its declared release: {model}; this is the
step the release runner script invokes, and when nothing qualifies it
prints release --declared: nothing to release (no verified head without a current release) and exits 0. Exit 2 when neither an image nor
--declared is given; exit 1 when no build is recorded (release: no build of '<image>' recorded in lineage) or the release is refused
(release refused: <reason>: not in lineage, another series, no in-bake
verification, a failed or missing mod test under require_mod_tests, no
or failed post-bake tests under require_image_tests).
gate-plan [PLAN_JSON] [--planfile FILE] [--tofu BIN] [--allow-destroy ADDR ...] [--require-unmounted INSTANCE:STORAGE ...]
The apply gate. Reads a plan as JSON (the output of tofu show -json) or
converts a saved plan file locally with <tofu> show -json (default
binary tofu). Every planned destroy must match an --allow-destroy
address exactly or as a prefix followed by . or [. Every
--require-unmounted pair needs a successful unmount receipt under
./unmount-receipts (checked first). Exit 0 when the plan passes (Plan passes the apply gate.). Exit 3 on a destroy that is not whitelisted
(DESTROY NOT WHITELISTED: <address>, one per violation), a stale plan
file (STALE PLANFILE: <reason>: older than the newest .tf under its
root, or missing), or a missing unmount receipt (DETACH NOT UNMOUNTED: <pair> has no successful unmount receipt). Exit 2 when neither a plan nor
--planfile is given. The staleness check applies to --planfile only.
apply-check --lifecycle NAME [--config-root PATH] [--root NAME] [--root-alias NAME ...] [--overlay FILE ...] [--apply-runtime RT]
The execution-time apply guard. Re-reads config.apply_<lifecycle> from
cfg/_config.yml now (walking up from the working directory to the nearest
directory holding it unless --config-root is given), applies the
config: keys of every --overlay, and honours --apply-runtime for the
storage and instances lifecycles (the flag becomes [<rt>]). --root
and --root-alias let a list-valued flag be checked for one terraform
root: the root's builder name or any alias (its runtime) must be listed.
Exit 0 when the flag allows the apply (apply_<lifecycle> allows [root '<name>']; proceeding.); exit 3 when it does not (apply_<lifecycle> is <value> NOW in <path>/cfg/_config.yml [for root '<name>'], then "this
runner script was generated when it allowed the apply; not applying"),
when no cfg/_config.yml is found, or when an overlay file is gone.
identity export-gids
The gid shim: a terraform data "external" program. Reads the query (a
JSON object of strings: identity_type, groups as a comma-separated
list, and provider fields such as org, team, api_host) on standard
input, asks the identity plugin registered for that type, and prints
{group: gid} as JSON. An empty groups answers {}. Credentials come
from the environment, never from the query. Exit 1 (export-gids: <reason>) on any error, including a query without identity_type, a type
no plugin registers, missing credentials, and a group the provider cannot
answer for.
runtime describe NAME
Print configuration-driven facts about a runtime as JSON: project or zone,
ephemerality, retention, the images baked there, the declared storages with
their cloud names, the instances, its builders (image, storage and
instance builders bound to it) and its emission (the
<lifecycle>/<builder> directories under generated/ that hold its
emission and exist now -- what a run scoped to another runtime leaves
untouched, and what just runtime-unchanged compares across records).
Exit 1 for an unknown runtime (runtime describe: unknown runtime '<name>'; known: [...]).
empty --runtime NAME
Assert the runtime holds nothing beyond its declared storages (no
instances, no custom images other than released builds, no other disks or
buckets) and that a strict state query agrees (the report is rewritten).
Names every leftover (empty: <kind> still present on <rt>: <names>) and
every real drift line. Exit 2 when the runtime cannot answer (unknown
runtime, or a runtime whose plugin cannot list its inventory); exit 1 on
leftovers or real drift; exit 0 when empty.
lineage restamp --runtime RT [--series NAME ...] [--commit/--no-commit]
Record the current input fingerprint on series heads whose recorded one
predates the current fingerprint recipe, keeping the previous value, and
re-tag the cloud image to match. Prints the plan as JSON. Under the global
dry run it only reports (restamp (dry run): N head(s) would be restamped; pass --no-dry-run). --commit makes the meta-state commit. Exit 1 on
error (restamp: <reason>, for example an unknown runtime).
lineage relabel --runtime RT [--build ID ...]
Re-tag cloud images whose lineage tags disagree with their record, from
the record. No meta-state change. Prints the plan as JSON. Under the
global dry run it only reports. Exit 1 on error or when an image could not
be retagged (relabel: could not retag [<ids>]).
unmount storage --instance NAME --storage NAME --mount-point PATH [--workdir DIR] [--confirm] [--timeout SECONDS]
Unmount a storage on a launched instance through the runtime's session
mechanism and write the receipt that gate-plan --require-unmounted
checks, under <workdir>/unmount-receipts (default: the current
directory). --confirm runs nothing and records the operator's assertion
instead (the operator's name from USER or USERNAME). --timeout
defaults to 300. Prints the receipt as JSON. Exit 1 on error (unmount: <reason>: an undeclared instance, an unconfigured runtime, a failed
unmount).
verify instance NAME [--expect-build ID] [--timeout SECONDS] [--record-only]
Run the runtime's verification of a launched instance (serial console,
booted image, data disks, and the image's tests.post_bake) and record the
verdict in meta-state/verifications.yaml (and image-tests.yaml).
--expect-build defaults to the instance's pin or launch record;
--timeout defaults to 600. Prints the record as JSON. Exit 1 on failure,
which stops a runner script and leaves an ephemeral instance standing.
With --record-only a failed verdict is recorded, instance <name> FAILED verification (recorded; teardown proceeds) is printed, and the exit is 0,
so a teardown can proceed. Exit 1 (verify: <reason>) for an undeclared
instance or an unconfigured runtime.
verify assert NAME
Exit 1 when the instance's last recorded verification failed (or none is recorded), else 0.
verify login [NAMES...] [--runtime RT] [--timeout SECONDS] [--record-only]
Log into each standing instance (every launched, non-ephemeral instance,
or the named ones, or those on --runtime) over sft ssh -- as the OPA
workload when OPA_TOKEN is set (the client is then handed the group
builder's team and api_host as SFT_TEAM and OPA_ADDR, the
environment's own values winning), else as the enrolled client -- after
checking that exactly one server is registered under the hostname and
that the client resolves it, and record the verdicts in
meta-state/login-proofs.yaml. A stopped machine, or one whose group
names no workload connection and role, is skipped (login SKIPPED for <names> (nothing proved)), never started. --timeout (default 120) is
per client call. Prints the records as JSON. Exit 1 on a failed login or
an error (verify login: <reason>); with --record-only the verdicts are
recorded and the exit is 0.
workload describe [--env]
What CI's workload login needs from the configuration: for every group
builder that names the team's workload connection and role, the builder,
connection, role, team and api_host, as JSON. Nothing secret.
--env prints shell exports (OPA_WORKLOAD_CONNECTION,
OPA_WORKLOAD_ROLE, SFT_TEAM, OPA_ADDR) for the first such builder
(what just ci-login-proof evaluated until stage 64; workload token
reads the configuration itself now); exit 2 when none names one. This
command loads the configuration (it is deliberately not under identity,
whose group is exempt from loading; since stage 64 the workload group
loads lazily, and describe loads at once).
workload token
This GitHub Actions run's OIDC token, presented to the team's workload
connection; the OPA access token it yields is the ONLY thing on standard
output, without a trailing newline, so OPA_TOKEN="$(cs-image-system workload token)" captures it and nothing else (stage 56 step 2; a
command since stage 64, replacing scripts/opa-workload-token, whose
::add-mask:: lines went to standard output and into the captured
value). On standard error: a mask command for each token before anything
else, the OIDC token's public claims (claim iss: ..., repository,
sub, ...), the client's exit and its own output. The Actions runner
scans standard error for workflow commands as it scans standard output.
The four names -- OPA_WORKLOAD_CONNECTION, OPA_WORKLOAD_ROLE,
SFT_TEAM, OPA_ADDR -- come from the environment (the probe workflow
runs on a checkout with no configuration); any that is missing comes from
the configuration's first group builder that names a workload connection,
and only then is the configuration loaded. Needs a job holding id-token: write (ACTIONS_ID_TOKEN_REQUEST_URL / _TOKEN) and the sft client.
Exit 2 outside such a job or when a name cannot be found; 1 when the
runner's endpoint refused, the client exited non-zero, or it exited 0
with no token (a DRAFT connection validates and issues nothing usable).
forget instance NAME
Drop a destroyed instance's pin and launch parameters from meta-state,
recording the correction as an op: forget entry in pins.yaml. A
decommission through the gate does this itself; this is for records that
outlived their instance. Exit 2 while the instance is still declared
(forget: instance '<name>' is still declared -- undeclare it and let its destroy apply); exit 0 with forget: nothing recorded for instance '<name>' when there is nothing to drop.
dispose image [BUILD_ID ...] [--runtime RT] [--all] [--retention] [--commit/--no-commit]
Delete recorded images from their runtime and drop their lineage record and
any image pin at them in one operation. --runtime restricts to builds
baked there; with --all, every one of them. --retention applies the
declared retention instead: image retention.keep, runtime
retention_keep, everything on an ephemeral runtime. Refuses unrecorded
builds and builds an instance is pinned to or launched from. Prints the
plan as JSON. Under the global dry run nothing is deleted (dispose (dry run): N image(s) would be deleted; pass --no-dry-run). Exit 1 on error
(dispose: ...: --all without --runtime, no build and no selector, an
unknown runtime, a build baked elsewhere, a build that records an
unconfigured runtime).
state query [--strict] [--json]
Ask every plugin for its provider's view of what the system manages
(images by lineage tags, storages by name, groups by gid), diff it against
meta-state, and write generated/state-report.json (state report written to <path> on standard error). Prints the rendered report, or JSON with
--json. Touches nothing. Exit 1 on hard drift; with --strict, also on
any missing, foreign or changed drift (stale is reported only) and
when a credential session expires within the expected run length (the
sessions read from the loaded runtimes, the same reading the callback
printed).
state import [--images/--no-images] [--storages/--no-storages]
Adopt foreign artifacts (tagged as ours, unrecorded) into meta-state:
images into lineage, storages into storage-state. Writes meta-state only,
never the cloud, the identity provider or terraform state, then rewrites
the state report. Prints imported: <line> per adoption or nothing foreign to import. Exit 0.
state-migration ACTION --workspace NAME --run ID [--tofu BIN] [--backend-config FILE] [--location LOC]
One step of a state migration or a state backup, emitted into a root's
runner; never a by-hand command. It runs FROM the root the runner entered
(the private mirror), which is why the callback records the invocation
directory: the configuration load changes directory to the root and the
step must act where it was started. ACTION is:
begin: write the previous location from meta-state beside the root as<workspace>.tfbackend.previous.hcl, refuse a non-empty new location that holds another lineage, recognise a move that already happened, pull the old state to<workspace>.backup-<run>.tfstate(kept), leave the root initialised against the previous location and write<workspace>.state-migration.json.--backend-config(the root's.tfbackend.hcl, the NEW location) is required;--locationis that location as its type renders it. Exit 2 without--backend-config, when the file does not exist, or when meta-state records no location for the workspace; exit 1 when an init or state pull fails or the new location holds someone else's state.finish: record the move (from, to, when, serial, backup) in meta-state and move the workspace's location record. Exit 2 whenbegindid not run (no progress file).backup: pull the state at the root's location and keep it as<root>/_private/state-backups/<workspace>.backup-<run>.tfstate, before a pre-planstate rm. Exit 1 (and the runner stops) when the directory is not an initialised terraform root, the pull fails, or the location holds no state: astate rmis due, so there is no honest empty case.
Any other action exits 2. --run is the id of the run that emitted the
step; --tofu defaults to tofu.
prune-attachments --builder NAME --run ID [--tofu BIN]
One step of an identity root's runner, between its init and its plan,
in the initialised root: list terraform state, keep every membership
attachment the declaration still has, ask the identity provider about
each dropped one, take a state-migration backup, and state rm only the
attachments the provider no longer holds. A silent or unreachable provider
removes nothing (the plan decides). Exit 2 when no group builder has that
name; exit 1 when state list, the backup or a state rm fails (the
runner stops before its plan). Never a by-hand command.
identity-attributes [--probe] [--dry-run-apply] [--apply] [--json]
Show the identity attributes plan written by the identity lifecycle (or
compute it), probe the provider read-only with --probe, and preview an
apply with --dry-run-apply (one would set: line per change, or
attributes already match). Writing is disabled: --apply exits 4
whenever there is a change to write (writing identity attributes is disabled (DESIGN Q7 not confirmed); N change(s) were NOT applied), and no
code path writes. Exit 3 when the provider reports attribute conflicts.
--json prints the plan as JSON.
test, cleanup
Retired names. Each prints the commands that replace it (test-mods and
verify instance; the retention lifecycle and dispose image) and exits 2
without loading anything.
Credentials
| Needs | Commands |
|---|---|
| Nothing | preflight (reads local caches only), gate-plan, apply-check, public-safe, encrypt (recipients are read from cfg/_config.yml as text), init-config, config-drift (this process; the dry run it starts needs what a load needs), workload token with the four names in the environment (else it loads), test, cleanup. |
The age identity in CSIS_CONFIG_IDENTITY only |
decrypt, reencrypt, mask, materialize (when a marker is present). |
| Identity-provider credentials from the environment, and plugins installed | identity export-gids. |
| The age identity when the tree holds encrypted values, and live credentials for every declared cloud runtime | Every command that loads the configuration: run, build-all, generate, validate, upgrade, test-mods, release, runtime describe, empty, lineage restamp, lineage relabel, unmount storage, workload describe, verify instance, verify login, verify assert, forget instance, dispose image, state query, state import, state-migration, prune-attachments, identity-attributes, runtime-unchanged. Loading constructs every runtime builder, and the cloud plugins validate networking against the cloud in that step. run and state print the session lines first and exit 1 on an expired session. |
Beyond loading, test-mods needs a local docker; verify login needs
sft (and OPA_TOKEN to log in as the workload); verify instance,
unmount storage, dispose image, lineage relabel, empty,
prune-attachments, state query and state import call the cloud or
the identity provider; state-migration and prune-attachments run
tofu against the root's state backend; a real run executes packer and
tofu with their own credentials.
Commands the generated runner scripts call
A run writes generated/<lifecycle>/run-<lifecycle>.sh per lifecycle
with deferred work and generated/final_execution.sh, which runs each
script that exists. The scripts call back into this CLI by the bare name
cs-image-system (a committed script names no absolute path), so it must
be on the PATH of whoever runs them: a release installed with uv tool install, uv run, or a configuration repository's Justfile, which puts
a CSIS that names a path first on PATH. The steps the running process
spawns itself (materialize, gate-plan, prune-attachments, the
state-migration steps) are invoked as the running interpreter's own module
and need no PATH (hygiene VII item 2, 2026-09-29). Every deferred command that has a
working directory is wrapped as
( cd "<root under generated/>" && cd "$(cs-image-system materialize . --root-dir "$CSIS_ROOT")" && <command> )
so the command runs in the private mirror of its root, where the ciphertext the committed emission carries has been replaced by plaintext. The commands themselves:
| Command line | Emitted where |
|---|---|
cs-image-system gate-plan --planfile tfplan --tofu <tofu> [--allow-destroy ADDR ...] [--require-unmounted PAIR ...] |
After every terraform root's plan -out=tfplan, in the identity, storage and instance-image runners. |
cs-image-system apply-check --lifecycle <identity|storage|instances> [--root NAME --root-alias RT] [--overlay FILE ...] [--apply-runtime RT] |
Just before tofu apply tfplan, only when the root is allowed to apply. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] prune-attachments --builder <group builder> --tofu <tofu> --run <run id> |
Every identity group root, after its init and before its plan. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] state-migration backup --workspace <root> --tofu <tofu> --run <run id> |
An identity group root whose runner carries a pre-plan state rm (a group newly declared unmanaged: true), after init and before the rm. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] state-migration begin --workspace <root> --tofu <tofu> --backend-config <root>.tfbackend.hcl --location <type>://... --run <run id> and, after the gate, ... state-migration finish --workspace <root> --run <run id> |
A root named by run --migrate-state: begin before its init -input=false -migrate-state -force-copy, finish after gate-plan; the plan in between carries -detailed-exitcode. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] release --declared |
The release runner, when an image declares release:. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] dispose image --retention [--runtime RT] |
The retention runner, when retention or an ephemeral runtime is declared; --runtime when the run was scoped to one. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay <config-only overlays> ...] run storage --only none --no-state-query --no-commit [--apply-runtime RT] |
The retention runner, for transient overlay storages on an ephemeral runtime; the overlays that declared them are left out so the storages are undeclared there and destroyed through the gate. |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] verify instance NAME [--record-only] |
The instance-image runner, after an ephemeral instance's launch applied (--record-only under the teardown failure policy). |
cs-image-system --root-dir "$CSIS_ROOT" --no-dry-run [--overlay ...] unmount storage --instance NAME --storage NAME --mount-point PATH --workdir DIR |
The instance-image runner, before a plan that detaches a storage. |
cs-image-system identity-attributes --probe --dry-run-apply |
The identity runner, after the apply, when users or groups declare attributes. Emitted WITHOUT --root-dir; the command loads the configuration from the directory it runs in, which is the root's mirror (see When it fails). |
Two more are not script lines but programs terraform runs at plan time:
cs-image-system identity export-gids is the data "external" program in
an identity root that needs gids by query, and cs-image-system decrypt --json is the data "external" "sensitive" program in every root that
references an encrypted value. Both read their query on standard input and
answer on standard output, which is why logs go to standard error.
"$CSIS_ROOT" is defined at the top of each runner script as the
configuration root relative to the script, so a committed script names no
absolute path. The --run <run id> the state steps carry is the id of the
run that generated the script (the run-start timestamp with every
separator replaced by _), not of the process that executes the step.
Exit codes at a glance
| Code | Meaning |
|---|---|
| 0 | Success. |
| 1 | The operation failed or was refused: a failed run, validation errors, a refused release, a failed verification or login, hard drift, a value that cannot be decrypted or materialized, a public-safe finding, a release-owned file init-config refused, config-drift behind or runtime-unchanged changed, workload token issued nothing, an expired session before a load, a failed load (a traceback), a state-migration/prune-attachments step that failed (the runner stops). |
| 2 | Usage: unknown lifecycle or runtime, nothing selected, a missing argument, a retired command, --migrate-state under a dry run, public-safe --staged outside git, preflight with an absent or expired session, an empty credential variable or no runtimes, empty on a runtime that cannot answer, forget instance on a declared instance, workload describe --env with nothing named, init-config with an unknown starter, config-drift with nothing committed or a failed dry run, runtime-unchanged on an unknown runtime, workload token outside a job or with a name it cannot find, --locked without TF_PLUGIN_CACHE_DIR, a state-migration step without its inputs, prune-attachments for an unknown builder. |
| 3 | A gate refused: gate-plan (destroy not whitelisted, stale plan file, missing unmount receipt), apply-check (flag off, no _config.yml, overlay gone), identity-attributes provider conflicts. |
| 4 | identity-attributes --apply: writing attributes is disabled. |
| 75 | --locked: another command holds the tofu lock (EX_TEMPFAIL); wait for it. |
Tests
tests/test_cli_dry_run.py pins the dry-run
contract: on by default and toggled by --dry-run/--no-dry-run. The CLI is
exercised end to end by the workspace suite against the frozen fixture at
tests/fixtures/config, whose emission is
pinned under tests/fixtures/v2_golden:
test_v2_gate7_headless.py (the
whole workflow through run --all, the aliases, the exit codes of run,
validate and gate-plan, the gid shim's pure-JSON standard output),
test_v2_justfile_contract.py
(preflight), test_v2_hygiene_five.py
(state-migration backup from the directory the runner entered),
test_v2_encrypted_values.py
and test_v2_emit_by_reference.py
(encrypt, decrypt, decrypt --json),
test_v2_scoped_runs.py,
test_v2_gce_cycle.py and
test_v2_runtime_facts.py
(apply-check), test_v2_detach.py
(gate-plan --require-unmounted),
test_v2_decommission.py
(forget instance), test_v2_post_bake_tests.py
(release), test_v2_defects_dead.py
(stage 63: --force is No such option, encrypt without recipients is a message, a zero-minute session
is expired). tests/test_docs_contract.py
holds this README to the four-section contract below and checks every
relative link in it.
Prerequisites and integration
Everything this package needs exists outside it; it creates none of it.
Python 3.13 and the installed workspace. The console script is a
Python entry point; just init (uv sync) installs it into the
workspace's .venv. Whoever runs a generated runner script, and terraform
itself when it runs decrypt --json or identity export-gids, needs
cs-image-system on PATH: just and uv run provide that; a hand
tofu plan in a generated root needs the venv on the path. The base
library is pinned to the same version in
pyproject.toml; typer, rich and PyYAML are the
only other declared dependencies.
The plugins. The CLI has no builder of its own; every model and
builder comes from a plugin discovered through the
cs_image_system.plugins.<type> entry points, and the release and
retention lifecycles (plus every validator and recorder hook) from the
cs_image_system.plugins.hooks group. The workspace pins all sixteen. A
missing runtime plugin surfaces at load as No runtime providers configured. or <kind> builder '<type>' specified for '<name>' not configured; a missing hook plugin makes run release an unknown
lifecycle.
A configuration tree. Found through --root-dir (else the current
directory): cfg/_config.yml, cfg/runtime-builders.yml and the other
cfg/*.yml, the item collections and meta-state/, as
docs/CONFIGURATION.md describes them. Every
load writes under <root>/generated/ (or the tree's
generation_directory), <root>/meta-state/ and, at execution,
<root>/_private/. The live tree is the sibling
cs-image-system-testconfig checkout; the tests use the frozen fixture.
Credentials, all from the environment or the local caches. The CLI prints the names of what it needs and never a value.
CSIS_CONFIG_IDENTITY: the age identity (anAGE-SECRET-KEY-1...string, an identity file, or a directory of*.age-identityfiles) that opens everyENC[age:...]value. Read by every configuration load and bydecrypt,reencrypt,maskandmaterialize. Operators keep theirs under~/.config/cs-image-system/age/; CI's is a repository secret; the workspace's.envrcexports it (the tool shell has no direnv:source .envrcin a subshell or set it inline).- AWS: the profile each runtime names (
profileorcredentials.profile_nameincfg/runtime-builders.yml, elseAWS_PROFILE), whose SSO tokenpreflightand therun/statecallback read from~/.aws/sso/cache(CSIS_AWS_DIRoverrides the~/.awsdirectory); or staticAWS_ACCESS_KEY_ID(with no profile named), which has no readable expiry. An expired SSO session refusesrunandstatebefore the load; every other loading command dies inside the AWS networking validation instead. - GCP: Application Default Credentials at
GOOGLE_APPLICATION_CREDENTIALSor~/.config/gcloud/application_default_credentials.json; only their presence is read. - The identity provider:
TF_VAR_<team>_key/TF_VAR_<team>_secret(the team name with non-alphanumerics replaced by_), orOKTAPAM_KEY/OKTAPAM_SECRET, foridentity export-gids, the state query andprune-attachments;OKTA_API_*for the okta provider at plan time;OPA_TOKENforverify loginas the workload. These are the identity plugin's contract (okta-opa-plugin). - The whole environment is exposed to the configuration's templates as
ENVduring the load, so a{{ ENV['X'] }}in the tree reads any variable.
Tools. tofu (the binary the runner passes as --tofu, default
tofu) for gate-plan --planfile, state-migration and
prune-attachments; docker for test-mods; sft for verify login;
git for run --commit and public-safe --staged; packer, the cloud
CLIs and the ansible tools as cfg/executables.yml declares them, checked
by validate and by every run. The age implementation is a Python
dependency of the base library, not a binary.
Network. Every configuration load validates each cloud runtime's
networking against its cloud, so a load needs reachability to AWS and GCP
(a dry run and validate included). identity export-gids, the state
query and prune-attachments need the identity provider's API;
verify login needs the OPA gateway path sft uses.
Integration points. Two Justfiles wrap this CLI (stage 64). The
system's: just cli <args> runs it against CSIS_CONFIG_ROOT (default:
the reference configuration checked out beside), just preflight is
preflight, just test-mods is test-mods, just fixture-live runs
validate, run --all --no-state-query and test-mods --strict over
the frozen fixture. A configuration repository's, which the release ships
and init-config writes: the cloud-* recipes wrap runtime describe,
state query --strict, --locked --no-dry-run run ... --only-runtime/--apply-runtime, empty, verify instance, dispose image and lineage relabel; record is run --all --commit;
config-drift and runtime-unchanged are the commands of those names;
ci-login-proof captures workload token into OPA_TOKEN and runs
verify login. The pre-commit hook both repositories carry
(.githooks/pre-commit, enabled by just hooks, and by just init in a
configuration repository) runs cs-image-system public-safe --staged --tree <top>. The configuration repository's CI runs mask, just validate, just config-drift, just state-query --strict, just test-mods --strict, just record, just runtime-unchanged, just cloud-perform <rt> and just ci-login-proof; the system's runs just fixture-live, as docs/OPERATIONS.md section 3
lists. Terraform calls identity export-gids and decrypt --json from
the generated roots.
Configuration reference
This package owns no YAML. What it reads is the command line, the environment, and a handful of raw keys of the tree that it must read BEFORE (or without) the model load; every modelled field belongs to the base and the plugins (docs/CONFIGURATION.md).
Global options
| Option | Type | Default | Meaning | Honoured by |
|---|---|---|---|---|
--root-dir |
path | the current directory | the configuration root | every loading command (changes into it); preflight, encrypt, decrypt, reencrypt, public-safe, materialize, mask (recorded as the root); ignored by gate-plan, apply-check, identity export-gids, test, cleanup |
--verbose |
bool | false |
DEBUG logging; YAML_DUMP.yaml in the starting directory when a load happens |
every command (the log level); the dump only with a load |
--dry-run/--no-dry-run |
bool | true |
enumerate deferred work instead of executing it | run, build-all, generate, dispose image, lineage restamp, lineage relabel; the deferred CLI steps a run emits carry --no-dry-run themselves; reencrypt has its own flag; every other command ignores it |
--overlay |
path, repeatable | none | a transient declaration file | every loading command; preflight and the run/state session check (their config: keys, for preflight.expected_run_minutes); ignored by the value tools, gate-plan (which has none), apply-check (which has its own), identity export-gids |
--undeclare |
kind:name, repeatable |
none | treat a tree entry as absent | every loading command; ignored elsewhere |
--base-only |
bool | false |
select the base-image lifecycle alone | build-all, generate |
--only-providers |
retired (stage 67): No such option; --only-runtime filters runtimes |
Environment variables
| Variable | Read by | Meaning |
|---|---|---|
CSIS_CONFIG_IDENTITY |
every configuration load; decrypt, reencrypt, mask, materialize |
the age identity (a key string, an identity file, or a directory of *.age-identity files); required only when a marker is present |
AWS_PROFILE |
preflight; the run/state session check; every load through the AWS plugin |
the profile when a runtime names none |
AWS_ACCESS_KEY_ID |
preflight; the run/state session check |
static keys are reported as present with no readable expiry, when no profile is named |
CSIS_AWS_DIR |
preflight; the run/state session check; state query --strict |
the ~/.aws directory to read config and sso/cache from |
GOOGLE_APPLICATION_CREDENTIALS |
preflight; the run/state session check; state query --strict |
the ADC file; default ~/.config/gcloud/application_default_credentials.json |
AWS_*, GOOGLE_*, OKTA_*, TF_VAR_*, CSIS_* (any name with these prefixes) |
preflight |
a variable set to the empty string is a failure (exit 2), reported by name |
TF_VAR_<team>_key, TF_VAR_<team>_secret, OKTAPAM_KEY, OKTAPAM_SECRET |
identity export-gids; the state query; prune-attachments (through the identity plugin) |
the OPA API pair |
OPA_TOKEN |
verify login |
log in as the workload when set, as the enrolled client when not; the record says which |
USER, USERNAME |
unmount storage --confirm |
the operator's name in the receipt (unknown when neither is set) |
| the whole environment | every load | exposed to templates as ENV |
CSIS_CONFIG_ROOT |
the Justfile, never the CLI | the live root just cli passes as --root-dir |
Keys of the tree read raw, before or without the load
| Key | File | Type | Default | Read by |
|---|---|---|---|---|
runtime_builders[].name, .type, .profile, .credentials.profile_name |
cfg/runtime-builders.yml |
strings | none | preflight; the run/state session check. An entry without a name is skipped; a file that is missing or unreadable yields no sessions (preflight then exits 2 with no runtimes declared). |
config.preflight.expected_run_minutes |
cfg/_config.yml, then each --overlay |
int | 30 |
preflight, the run/state session check (raw); state query --strict (from the loaded context) |
config.apply_<lifecycle> |
cfg/_config.yml, then each --overlay |
bool or list of root/runtime names | false |
apply-check |
encryption.recipients |
cfg/_config.yml |
list of age recipients | none (an error) | encrypt, reencrypt |
public_safe.allow |
the --config file, else the tree's cfg/_config.yml, else the fixture's inside the system repository |
list | none | public-safe |
dateformat, working_directory, generation_directory |
the merged tree | strings | %Y%m%d_%H%M%S, ./workdir, generated |
the load (base). The callback always supplies a root (--root-dir or the current directory) as the working directory, so working_directory is never consulted through the CLI; generation_directory is resolved under that root |
Accepted, not read, anywhere in this package: apply-check --lifecycle release (the help lists it;
nothing emits it, and the release lifecycle's cloud marking runs under the
plugin's own commands); sleep_before_finalization in the tree (kept for
compatibility: the load reads it, and a real run with a non-zero value
logs sleep_before_finalization=<n> is ignored: finalization never waits on a terminal (DESIGN §3G); finalization never sleeps). The global
--force is gone (stage 63, see Global options).
Variations
- Dry run (the default) versus
--no-dry-run. A dry run generates everything, writes every runner script andfinal_execution.sh, runs the state query and validation, and then only enumerates the deferred commands (dry-runper lifecycle in the summary); the terraform roots initialise with-backend=false; pins move nothing;--commitstill commits the emission and records.--no-dry-runexecutes the scripts in-process with the builders' finalize hooks; the deferred CLI steps they carry are themselves--no-dry-run.dispose image,lineage restampandlineage relabelprint their plan and stop under a dry run and act under--no-dry-run.run --migrate-stateexits 2 under a dry run instead of moving anything. runversusbuild-allversusgenerate. The aliases arerun --all --apply --no-commitandrun --all --no-apply --no-commit, with--base-onlynarrowing both tobase-image; they take none ofrun's options.--applyversus--no-apply. With--no-applyevery lifecycle isnot-attemptedand nothing after generation runs. With--apply, a lifecycle generated this run whose script exists runs; one that deferred nothing isno-script; a script left by an earlier run of a lifecycle not requested now isstale-script-skipped, never executed.--state-queryversus--no-state-query. With the query, reality is read first,generated/state-report.jsonis rewritten, the session lines are logged (a warning for a blocking one, never a refusal in a run) and hard drift refuses the run at validation. Without it the existing report stands and the validator reads that.--commitversus--no-commit.--commitrecords the run and makes one commit ofmeta-state/and the generated tree in the configuration repository (the run-local files are never staged); a root outside a git repository or a gitignored path is skipped with a warning andmeta_state_commitstaysnull. Without it the summary and journal are still written.- Scope:
--only,--only-runtime,--apply-runtime,--allow-unscoped-bakes.--onlynames images (unknown names fail the run);--only nonebakes nothing.--only-runtimeexpands to<image>@<rt>for every image baked there (ornonewhen none is) and makes the other runtimes' storage and instance roots emit nothing.--apply-runtimesets both apply flags to[<rt>]for the run and implies--only-runtime <rt>unless--onlyor--only-runtimewas given (the implication is recorded asimplied_scope). When the apply scope is a proper subset of the runtimes and the bake plan would bake outside it, a real run refuses before any bake withrun scope: bake(s) outside the apply scope [...]unless--allow-unscoped-bakesor an explicit selection was given; a dry run logs the same as a warning and goes on. --overlayversus--undeclareversus the tree. An overlay'sconfig:keys overridecfg/_config.ymlfor the invocation and the generatedapply-checkre-reads the same file at execution (a vanished file refuses the apply); its item entries update or add declarations, and the retention runner's transient-storage teardown re-passes only the config-only overlays.--undeclare kind:nameremoves an entry without a file. Neither changes the tree.preflightversuspreflight --strictversusstate query --strictversus therun/statecallback.preflightreads the raw tree and refuses (2) on absent or expired sessions and on empty credential variables;--strictalso refuses (1) on a session expiring within the window. The callback beforerunandstateprints the same lines and refuses (1) only on EXPIRED.state query --strictrefuses (1) on a session expiring within the window, from the loaded runtimes. A plain run warns and continues.- Encrypted versus clear values. A tree with no
ENC[age:...]marker loads with no identity; one marker anywhere makes every load, andmaterialize, requireCSIS_CONFIG_IDENTITY.encryptnever needs an identity;decrypt,reencryptandmaskalways do.decrypttakes a marker, or--json(the terraform protocol, unmarked values pass through), or--file --field(in place, the inverse ofencrypt --file). release IMAGEversusrelease --declared. The explicit form releases one build (the series head, or--build) for--modeland prints the entry;--declaredreleases every image's verified head per itsrelease: {model}declaration, prints onereleased: ...line and exits 0 when nothing qualifies. Both apply the same refusals.verify instancewith and without--record-only;verify loginas workload or client. Without--record-onlya failed verdict exits 1 and stops the runner, leaving an ephemeral instance standing; with it (theteardownfailure policy) the verdict is recorded, the exit is 0 andverify assertfails the run afterwards.verify loginuses the workload path whenOPA_TOKENis set and the enrolled client otherwise, and recordsas: workload|client.gate-plan PLAN_JSONversus--planfile. A JSON file is read as is;--planfileis converted with<tofu> show -jsonand is first checked for staleness against the newest*.tfunder its root.apply-checkwith and without--config-root,--root,--apply-runtime. Without--config-rootthe nearest ancestor of the working directory holdingcfg/_config.ymlis the root. Without--roota list-valued flag counts as on for the lifecycle; with it the root's name or an alias must be listed.--apply-runtimereplaces thestorage/instancesflag with[<rt>]and leavesidentityalone.public-safeover a tree versus--staged. The tree form scans tracked plus untracked non-ignored files under--tree/--root-dir;--stagedscans the git index of the repository containing that path and exits 2 when there is none.empty --runtimeon a runtime whose plugin lists inventory versus one that cannot. GCE answers; a plugin without inventory makes the command exit 2 (empty: <builder> cannot list its inventory).materialize SOURCEwith and withoutDESTINATION. Without one the mirror path is derived (the root'sgenerated/name replaced by_private/at the same depth); with one the copy goes exactly there.- The console script versus
python -m cs_image_system.system. Both run the same application; the module wrapper additionally catches any exception the application lets through and exits 1 withError: <e>.
What it tests and verifies
The CLI verifies little of its own; it decides what runs, and reports where the base and the plugins put their verdicts.
Before the command (the callback). For run and state ...: the
credential sessions from the raw cfg/*.yml files (every file that
declares runtime_builders:), one session: line each on standard error, and preflight: a session has EXPIRED; renew it (aws sso login --profile <p>) before the configuration can even load with exit 1 on an expired one. For every loading command:
the overlay files (existence, shape, allowed top-level keys, named
entries) and the --undeclare specs are validated up front, then the load
itself applies the base's checks (naming rules, structuring, foreign keys,
every ENC[age:...] value opened, the cloud plugins' networking
validation); a failure is Error reading config file : <e> and a
traceback, exit 1. Nothing else is checked at load.
At validate. Delegated whole to the base's validate_only: unique
names, every declared executable present at its path and within its
version requirement, every foreign key resolving, every root's state
location sound, the lineage and release policies, every registered
validator. The verdict is the - <error> lines and Validation failed with N error(s). on standard error, exit 1; nothing is written.
At generation (run). In order: the state query (verdict:
generated/state-report.json and the state counts in the summary), the
same validation as validate (verdict: validation_errors in
generated/run-summary.json, validation: <error> log lines, exit 1
before anything is generated), the bake plan (bake plan: <key>: <decision> lines and bake_plan in the summary), the scope check
(run scope: ... refusal or warning), and the --only name check (the
summary's error). The run's verdict is ok, error and the
per-lifecycle apply states in the summary on standard output and in
generated/run-summary.json, the journal entry in meta-state/runs.yaml,
and the exit code.
At apply (the runner scripts). The steps this CLI provides and where
each verdict lands: gate-plan (standard error, exit 3, the runner
stops), apply-check (standard error, exit 3), state-migration begin
(<workspace>.state-migration.json and <workspace>.backup-<run>.tfstate
beside the root in the mirror; log lines; exit 1 or 2), state-migration backup (_private/state-backups/<workspace>.backup-<run>.tfstate; a log
line naming the serial and count; exit 1 stops the runner), state-migration finish (meta-state/state-locations.yaml and the migration record),
prune-attachments (log lines per attachment kept, left or removed),
unmount storage (the receipt under <workdir>/unmount-receipts),
verify instance (meta-state/verifications.yaml, image-tests.yaml,
the record on standard output), release --declared and dispose image --retention (lineage, releases and pins in meta-state). A failed step
fails the lifecycle (failed in the summary, exit 1 of the run).
After apply. verify assert (exit 1 from verifications.yaml),
verify login (meta-state/login-proofs.yaml, login proof <name>: <check>: ok|FAILED -- <detail> lines), empty --runtime (the leftovers on
standard error, the state report rewritten), identity-attributes (the
would set: lines; nothing written).
In the state query. state query writes generated/state-report.json
and prints the report; --strict turns any drift but stale, and a
session expiring within the window, into exit 1. state import rewrites
the report after adopting.
In this package's own tests.
tests/test_cli_dry_run.py asserts the
--dry-run default is True and the flag is --dry-run/--no-dry-run;
everything else is proven by the workspace suite listed under
Tests.
When it fails
The failures this command surface produces, those that have happened first. Dates are from docs/OPERATIONS.md, docs/history/LEDGER.md, the tests and the source comments.
Seen live
- Stage 1 (by 2026-09-02, finding 33):
gate-planjudged the previous sequence's plan. A failedplanleft the earliertfplanbehind and the gate passed it. Every gated sequence now begins withrm -f tfplanandgate-plan --planfilerefusesSTALE PLANFILE: <file> does not existor... is older than <newest .tf>with exit 3. If it recurs, the plan step did not run or did not succeed; read the runner's output above the gate line. - 2026-09-03 (finding 34):
apply-checkcrashed because it loaded the configuration. The guard failed closed, but the runner could not apply at all.apply-check(withgate-planandidentity export-gids) is now exempt from the load and readscfg/_config.ymlas text; the symptom today would beapply-check: no cfg/_config.yml found from the working directory up; refusing to apply(exit 3), which means the step ran from a directory with no configuration root above it: pass--config-root. - 2026-09-03 (finding 36): the apply step executed other lifecycles'
leftover runner scripts by bare bash and baked two unrecorded AMIs. A
script of a lifecycle not requested in this run is now
stale-script-skippedin the summary and never runs. Seeing that state is not an error; it says an older script is lying undergenerated/. - 2026-09-05 (ledger 52 to 53, side lesson):
run --commitfailed with git exit 128 after a peer session'sgit flow feature start/finishswitched the shared checkout's HEAD mid-run; the operator committed by hand. The CIperformjob names its committer for the same reason (a runner has no git identity). A--commitrun does all its work first and fails at the very end: the emission and meta-state are on disk; commit them by hand or re-run. - 2026-09-08 (ledger 65) and 2026-09-09: the AWS SSO session lapsed
mid-cycle, then an expired session killed the load before the preflight
could say so. The configuration load validates every runtime's
networking against its cloud, so it died inside the AWS call. The
callback now prints the sessions from the raw tree before
runandstateload and exits 1 onpreflight: a session has EXPIRED; renew it (aws sso login --profile <p>) before the configuration can even load. Every OTHER loading command (validate,upgrade,verify ...) still dies inside the load with an AWS error; runpreflightfirst. - 2026-09-09 (ledger 68):
run ... --apply-runtime gcloud-east1without--only-runtimebaked two AMIs on AWS.--apply-runtimescoped the applies, never the bakes. It now implies--only-runtimeunless--onlyor--only-runtimeis given, and a real run whose bake plan reaches outside its apply scope refuses before any bake:run scope: bake(s) outside the apply scope ['<rt>']: <series>@<rt> -- pass --allow-unscoped-bakes, or select images with --only / --only-runtime(exit 1, in the summary'serror). A dry run only warns with the same text. - 2026-09-09 (ledger 68):
verify assertas the runner's last step failed the script before the after-apply hook ran, leaving a launch record for an instance that no longer existed. Under theteardownpolicy the runner now carriesverify instance --record-onlyand the failed verdict fails the run from the hook, after the record is forgotten. - 2026-09-10 (ledger 70): a scoped AWS bake failed on the GCE instance
root's plan (its image-family lookup returned 404 after a cycle had
disposed every GCE image).
--only-runtime, explicit or implied, now makes the other runtimes' storage and instance roots emit nothing. - 2026-09-11: the V1 placeholders
test,verifyandcleanup. Theverifycommand GROUP shadowed the placeholder, which could never run;testandcleanupprinted a warning and succeeded, which an old script would take as success.verifywas deleted;testandcleanupnow print<name>: retired in V2. ...naming the replacement and exit 2. - 2026-09-17 (stage 43): three green CI jobs ran nothing because a
secret existed with an empty value.
preflightnow lists every credential-shaped variable that is set but empty (environment: <NAME> is set but EMPTY -- a credential that exists with no value is a failure, not an absence) and exits 2: give it its value or unset it. - 2026-09-19:
tofu initin the mirror could not read its module.materializehad nested the mirror one level deeper than the root it copied, and the relative module source andlocalstate path counted one directory too many. The mirror now replaces thegenerated/name at the same depth. If a materialized root cannot find../tfmodulesor its local state, compare the depth of_private/<...>withgenerated/<...>. - 2026-09-20 (stage 54): a pin outlived its instance.
coops-modelwas destroyed through the gate andpins.yamlkept binding it, so withrequire_released_buildson,validaterefused every run, including the one that would release the build.forget instance <name>drops the pin and launch parameters and records the correction; it refuses (exit 2) while the instance is still declared. - 2026-09-21: an
sso-sessionprofile was read as ABSENT for an hour while the CLI was refreshing its token underneath, andjust full-testskipped its live legs.preflightnow reports such a profile aspresent; refreshes itself from the refresh token in sso cache <id> while the portal session lives (no fixed expiry readable). A session that lapses mid-run is environmental:aws sso login --profile <p>, then re-run the legs. - 2026-09-22:
sft resolve --quietexited 126 with no output when the client wanted a browser.verify loginnames the cause in itsresolvescheck:the client has no session (as the enrolled client: run sft login; as the workload: OPA_TOKEN is missing or was refused). - 2026-09-23 10:15 (stage 61 item 3): a state backup pulled from the
configuration root instead of the mirror and kept nothing. The
callback's load changes directory to the root AND replaces the Typer
context object, so the
invoked_cwdrecorded before the load was gone whenstate-migration backupread it, and the step ran in the root, found no initialised terraform root, said "nothing to keep" and let thestate rmrun unbacked. The callback now records the invocation directory again after the load,backuprefuses (exit 1) a directory that is not an initialised root or a location with no state, andstate-migration begin/finishshare the fix. Proved live the same day (backup serial 36 kept). If a runner step reportsREFUSED: <dir> is not an initialised terraform root, the step was invoked from the wrong directory; the runner's( cd ... && cd "$(materialize ...)" && ... )wrapper is what puts it in the right one. - Undated, found live:
run --allin a fresh process resolved to the four built-ins. The hook plugins that registerreleaseandretentionloaded only inside the run, after the names were parsed.runnow loads them first.Unknown lifecycle 'release'today means the hook entry points are not installed in the environment running the command.
Raised by the code, not yet seen
A retired global option is a parser error, not a message of this
package's own: cs-image-system --force <command> prints the usage and
No such option: --force and exits 2 (stage 63; until 2026-09-25 the
option was accepted and ignored). Drop it; if something should be forced,
use the named override (run --force-bake, gate-plan --allow-destroy,
test-mods --force).
Usage (exit 2), all on standard error:
Unknown lifecycle '<name>'; expected one of [...] or 'all'
No lifecycles selected; pass names or --all (...)
--migrate-state moves state and needs --no-dry-run: a dry run never moves state
--apply-runtime: unknown runtime '<rt>'
--only-runtime: unknown runtime '<rt>'
encrypt: --file needs at least one --field
encrypt: give a value (or '-' for stdin), or --file with --field
decrypt: --file needs at least one --field
release: an image name is required (or --declared)
gate-plan: pass a plan JSON file or --planfile
public-safe: <path> is not inside a git repository
state-migration: the action is begin, finish or backup, not '<x>'
prune-attachments: no group builder named '<name>'
workload describe: no group builder names a workload connection and role
<name>: retired in V2. ...
The load (exit 1, Error reading config file : <e> then a traceback):
Failed to read configuration from <root>/cfg (no tree there);
--undeclare '<spec>': expected <kind>:<name> or unknown kind;
Overlay <path> does not exist / must be a mapping / unknown top-level key(s) / '<key>' must be a list of named entries; an encrypted value is present but CSIS_CONFIG_IDENTITY is not set -- export the age identity (...); CSIS_CONFIG_IDENTITY=<value>: not an AGE-SECRET-KEY-1 string, an identity file or a directory; no identity in CSIS_CONFIG_IDENTITY can decrypt this value (N tried; it was encrypted to other recipients); No runtime providers configured.; Working directory <path> does not exist.;
and every plugin's own load-time refusal (the AWS and GCE networking
checks, the Okta workspace's credential assertions). Look at the line
before the traceback; the message names the file or value.
The run (exit 1, Run <id> FAILED: <error> and the summary's error):
--only names unknown image(s): <names>; known: <names>; Validation failed with N error(s) (each also logged as validation: <error>);
run scope: bake(s) outside the apply scope ...; --migrate-state moves state and needs --no-dry-run (raised again inside the run, in case a
library caller set it); any generation or apply exception, named by class.
The summary and journal are still written.
The value tools (exit 1): decrypt: not an ENC[age:...] marker,
decrypt: expected a JSON object of string values on stdin, decrypt: a marker is required (or '-' for stdin, or --json), reencrypt: NOTHING written -- <file>: <reason>, materialize: nothing to materialize at <path>, materialize: '<path>' is not in the subpath of '<root>',
mask: <reason>, public-safe: REFUSED -- N finding(s) ... (with every
finding on standard output), encrypt: <reason> and reencrypt: NOTHING written -- <reason> when the recipients (or, for reencrypt, the
identity) cannot be read: for example encrypt: <root>/cfg/_config.yml: no encryption.recipients declared, or reencrypt: NOTHING written -- an encrypted value is present but CSIS_CONFIG_IDENTITY is not set -- export the age identity (the AGE-SECRET-KEY-1 string, an identity file, or a directory of *.age-identity files). Until 2026-09-25 (stage 63) those
two ended in a traceback rather than a message.
The gates (exit 3): DETACH NOT UNMOUNTED: <instance>:<storage> has no successful unmount receipt (unmount storage ..., or unmount storage --confirm); STALE PLANFILE: ...; DESTROY NOT WHITELISTED: <address>
(one line per address; whitelist it by operation, never by hand);
apply-check: no cfg/_config.yml found ...; apply-check: overlay <path> is gone; refusing to apply; apply_<lifecycle> is false NOW in <path> ... (turn the flag on, or pass --apply-runtime on the run that
generates the script).
Runner steps that stop the runner (exit 1): state-migration begin: the new location <loc> already holds state (lineage <id>) that is not this workspace's: a collision, refused. The old state is untouched.;
state-migration begin: init against <file> failed: / state pull failed:; state backup for workspace '<ws>' REFUSED: <dir> is not an initialised terraform root; nothing is removed from state; state backup for workspace '<ws>' REFUSED: the location holds no state, yet a state rm is due; Identity builder <name>: 'state list' failed in <dir>; nothing is removed from state; Identity builder <name>: 'state rm <addr>' failed; the runner stops here. Exit 2 from state-migration begin when
--backend-config is missing, names a file that does not exist, or
meta-state records no location for the workspace (there is nothing to migrate from), and from finish when begin did not run.
The identity runner's identity-attributes --probe --dry-run-apply step
is emitted without --root-dir, so it loads the configuration from the
directory it runs in, the root's mirror, which holds no cfg/. In a real
run of a tree that declares group or user attributes the step would fail
its load (Error reading config file : ..., exit 1) and with it the
identity lifecycle, after its apply. No tree has declared attributes in a
real run yet. The fix belongs to the identity
plugin's emission (system_cli_executable_with_config), not to this
package.
Other commands (exit 1 unless noted): upgrade: <reason> (kind, name,
build, runtime); release refused: <reason>; release: no build of '<image>' recorded in lineage; runtime describe: unknown runtime;
empty: <reason> (exit 2), empty: <kind> still present on <rt>: ...,
empty: state query drift: ...; restamp: unknown runtime; relabel: could not retag [...]; unmount: instance '<name>' is not declared,
unmount: ... names runtime '<rt>', which is not configured, unmount of <mount> on <instance> failed (exit N): <tail>; verify: instance '<name>' is not declared, and the verification record with ok: false; verify login: <reason>; dispose: ...; export-gids: <reason> (query must name an identity_type, No identity plugin registered for identity type '<t>', identity plugin '<t>' reported no gid for groups [...], OPA credentials for team '<t>' not found in the environment (...)); FAIL <image>/<mod>: ... from test-mods; identity attributes: the provider reports conflicts; refusing: ... (exit 3); writing identity attributes is disabled ... (exit 4). preflight (exit 2): preflight: no runtimes declared under <root> (cfg/runtime-builders.yml), preflight: a session is absent or has EXPIRED -- the configuration cannot load (aws sso login --profile <p> / gcloud auth application-default login), preflight: an environment credential is set but empty (see above); (exit 1 with
--strict) preflight: a session expires before the expected run length (see above); renew it or lower config.preflight.expected_run_minutes.
Silent behaviour to know about (mask is no longer among it: since
stage 63 item 2, 2026-09-24, a file it cannot read for masking -- the
identity missing or wrong, a file that does not parse, a malformed marker
-- ends it with mask: FAILED naming each file and exit 1, so a CI step
that cannot mask fails instead of printing nothing);
run --commit outside a git repository or over a gitignored generated/
logs a warning and reports meta_state_commit: null; a plain run logs
preflight session: ... warnings and state query: <unavailable>
warnings and goes on.
Related
- The base library: the runner, the commands every subcommand delegates to, meta-state, encryption and the public-safe gate.
- docs/OPERATIONS.md: the operator's manual, every command in use, the exit-code table, the credentials contract and CI; docs/CONFIGURATION.md: the tree, the overlays, the run scoping options and the environment variables; docs/DESIGN.md: the headless contract; docs/PLUGINS.md: the package index.
- The plugins whose steps the runner scripts call through this CLI:
okta-opa-plugin (
export-gids,prune-attachments,identity-attributes), tf-ebs-instance-plugin and tf-gcp-plugin (verify instance,unmount storage), hashicorp-utils (the gated sequence,state-migration). - The Justfile:
just cli,preflight,test-mods,fixture-live; the starter Justfile the release ships (docs/examples/standard-aws/Justfile): thecloud-*recipes,record,config-drift,runtime-unchanged,ci-login-proof.
Metadata
Release files for cs-image-system-system 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cs_image_system_system-0.1.6.tar.gz | 414.1 kB | Without provenance | Details for cs_image_system_system-0.1.6.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cs_image_system_system-0.1.6-py3-none-any.whl | Python 3 | none | any | Details for cs_image_system_system-0.1.6-py3-none-any.whl |
Total release size: 1.0 MB
Release files / cs_image_system_system-0.1.6.tar.gz
| Download URL | cs_image_system_system-0.1.6.tar.gz |
|---|---|
| Size | 414.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
353425ac360c405e3502427926114bcb65e4e92ef6009c9d906e60d543606718
|
|
BLAKE2b-256 checksum How to use checksums |
81e368497a8a8790ab316b392a850a44cb85a8ab2dfca3de9f6b1ffc926c4044
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / cs_image_system_system-0.1.6-py3-none-any.whl
| Download URL | cs_image_system_system-0.1.6-py3-none-any.whl |
|---|---|
| Size | 593.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
43fbb06dc604fd81bfcfd5a2d0df70178f4216e966c7a0a71eaf1489c2b62621
|
|
BLAKE2b-256 checksum How to use checksums |
4062d5fbc12a90fd96c5631f68b5bddefbfae4b1931503bcc7d9f39dee2e6195
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for cs-image-system-system 0.1.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.