@@ -91,9 +91,11 @@ and the portable Agent Spec envelope in
9191
9292## Transactional catalog authoring
9393
94+ ` st2 agent digest (--spec FILE | --bundle DIR) ` captures a source through
95+ retained no-follow file descriptors and returns its authoritative digest.
9496`st2 agent publish --catalog ROOT (--spec FILE | --bundle DIR)
95- (--expect-absent | --expect-sha256 HEX) --json` is the sole supported
96- declaration writer . It accepts exactly one canonical KDL ` agent ` node with an
97+ --input-sha256 HEX (--expect-absent | --expect-sha256 HEX) --json` binds
98+ publication to that exact capture . It accepts exactly one canonical KDL ` agent ` node with an
9799explicit, path-safe host and identity. st2 no longer exposes an intent compiler:
98100external renderers own the transformation from human intent to exact Agent Spec
99101bytes or a create-only publication bundle.
@@ -104,18 +106,45 @@ read/write transaction domain:
104106``` text
105107publisher (EX) : snapshot input -> CAS -> full-catalog admission -> atomic publish + fsync
106108reader (SH) : discover -> materialize/observe -> plan -> execute
107- state plane : message | context | Resource | status (unlocked)
109+ bulk apply (EX) : root CAS -> durable stage+marker -> converge -> verify+clear
110+ state plane : message | context | Resource | status (unlocked)
108111```
109112
110113The publisher derives the destination from the captured declaration, replaces
111114only ` agent.kdl ` for a hash-authorized update, and preserves all sibling runtime
112115state. A bundle is create-only and is renamed from a hidden same-filesystem
113116stage; retry reports ` unchanged ` only when every projected bundle file already
114117matches. ` --expect-absent ` is idempotent for identical input.
115- ` --expect-sha256 ` rejects a stale writer. Full-catalog admission rejects any
118+ ` --input-sha256 ` rejects a caller/source swap and ` --expect-sha256 ` rejects a
119+ stale declaration writer. Full-catalog admission rejects any
116120structural validation error before publication. The typed result is
117121` published ` or ` unchanged ` .
118122
123+ ` st2 catalog snapshot --catalog ROOT --output DIR --json ` holds SH while it
124+ captures the canonical declaration projection: ` catalog.kdl ` , exact
125+ ` agents/<host>/<identity>/agent.kdl ` files, static files inside those bounded
126+ agent bundles, and catalog-owned files statically referenced by `render {
127+ copy ... }` . Runtime state, ` .git` , ` .st2` , the native ` pty` registry, and
128+ arbitrary catalog-contained workspaces are excluded. The output is a
129+ create-only durable directory; an identical retry is ` unchanged ` . Its
130+ domain-separated, path-sorted root SHA-256 covers normalized relative paths,
131+ file bytes, and executable bits.
132+
133+ ` st2 catalog apply --catalog ROOT --prepared DIR --expect-sha256 HEX --json `
134+ rejects any prepared state/control path, symlink, special node, unprojected
135+ file/directory, malformed declaration, or effective PTY-root change. It
136+ captures and validates exact prepared bytes, takes EX, rechecks the canonical
137+ live root, and either reports ` unchanged ` or creates a durable content-addressed
138+ stage before publishing the marker. It changes declaration leaves only;
139+ canonical state directories and arbitrary workspaces are never traversed,
140+ deleted, or hashed. New identity bundles use an exclusive directory rename, so
141+ no empty identity becomes state-addressable. Applied leaves and their parents
142+ are fsynced, the live root is re-hashed and fully admitted, then the marker is
143+ unlinked and ` .st2 ` is fsynced.
144+ The catalog parent is fsynced when ` .st2 ` is first created, including the
145+ concurrent create/observe race. Retained source capture rejects a staging
146+ destination contained by its source before enumerating that source.
147+
119148The lock file is never removed: replacing its inode would split the transaction
120149domain for processes that already hold it open. Reconciliation holds SH from
121150discovery through execution. Validation, doctor, roster, listing,
@@ -128,18 +157,28 @@ transaction fence. Any presence is authoritative, including malformed content.
128157The reserved canonical record is:
129158
130159``` json
131- {"schema" :" st2.catalog-apply-incomplete.v1" ,"desiredStorePath " :" /nix/store/ ..." }
160+ {"schema" :" st2.catalog-apply-incomplete.v1" ,"stageName " :" catalog-apply-stage-<prepared-root-sha256> " , "expectedRootSha256" : " <previous-root-sha256> " , "preparedRootSha256" : " <prepared-root-sha256> " , "originalPaths" :[ " <sorted-owned-declaration-leaf> " , " ..." ] }
132161```
133162
134163After taking its authoring lock, st2 refuses publication, validation,
135164materialization, teardown, roster, doctor, and catalog listing while the marker
136165exists. One-shot and selected reconcile fail explicitly. A resident supervisor
137166instead remains alive, reports a skipped/incomplete pass, and performs no
138167runtime observation or lifecycle action, avoiding a service restart storm.
139- Message, context, Resource, and status operations remain available. The future
140- whole-catalog apply command owns marker creation, admission, durable apply,
141- verification, and clearing inside one transaction; external lock execution and
142- bypass flags are not part of the contract.
168+ Message, context, Resource, and status operations remain available. While the
169+ marker exists they resolve canonical state independently of live declaration
170+ discovery; an existing state-only orphan remains addressable, an incomplete new
171+ identity does not fall back to a flat bus. A dotted bare identity is tried as
172+ the complete local identity alongside every possible qualified bus-address
173+ split; exactly one distinct canonical address must exist. Only real state
174+ directories and a real regular status file can establish marker-time addressability. Only
175+ ` catalog apply ` may open an existing marker. A retry must name the same prepared
176+ and expected roots; marker
177+ authority proves the original CAS already passed, so recovery converges the
178+ partial live tree from the durable desired stage and original owned-leaf list
179+ without re-enforcing that stale precondition. Malformed or
180+ mismatched records remain fenced. External lock execution and bypass flags are
181+ not part of the contract.
143182
144183## Host-local scheduling and supervision
145184
0 commit comments