Repository navigation
Releases: action-state-group/capsule-cli
Release list
capsulectl v0.1.0-rc15
Prebuilt capsulectl binaries (linux/amd64, linux/arm64, darwin/arm64). Verify with: sha256sum -c SHA256SUMS. Check the build attestation with no GitHub account: gh attestation verify FILE --bundle capsulectl-v0.1.0-rc15.sigstore.json --repo action-state-group/capsule-cli --signer-workflow action-state-group/capsule-cli/.github/workflows/release.yml
capsulectl v0.1.0-rc14
capsulectl v0.1.0-rc14, from commit 49651e2.
Disclosure first. A shared copy no longer names the per-profile payee companion record: it is still listed as a withheld step, so the copy stays complete, but under a neutral kind. The user's floor and spending limit are now recognised in any written money form (symbols, codes, locale separators, k, a bare number after a money word), compared by value. A shared copy that states the floor, or a counterparty copy that states the limit, is refused. A message the agent writes, and when the user sells an agent's claim, is refused before sealing if it states either. The user's own words are recorded as said.
Fails closed. When a seller's floor is in force but its opening can't be built, the deal check is refused before anything is sealed, instead of running without it.
Wire changes and release pairing. New deal records seal action taxonomy version "6", and every deal check now sends party_role at the top level of the rules checker's input. A rules checker compares the taxonomy version exactly, and a checker built before this release refuses an input carrying party_role, so this release needs a rules checker that holds taxonomy 6 and accepts party_role. Records sealed before re-derive unchanged.
Changes
A shared copy lists the counterparty_profile step under a neutral kind
- Changed: a shared copy (counterparty or adjudicator) lists every step of the log, withheld
ones included, so a verifier can tell withheld from missing. The check'scounterparty_profile
companion was listed by that kind, which tells the reader the user keeps a fingerprint of
merchants across deals. It is now listed as kindprivate, still withheld, with the line "A
record kept for the user only (withheld)". - Unchanged: the record itself, and the user's own copy, which names it as it is.
The user's floor and limit are recognised in any written form
-
Fixed: the check that keeps a seller's floor (
min_total_minor) and a spending limit
(max_total_minor) out of a shared copy only matched the form capsulectl writes ("$1700.00").
It now reads money as prose writes it, and compares by value:- currency symbols before or after the amount (
$,€,£,¥and others), ISO codes
(USD 1,700,1700 usd, also written against the number:USD1,700,1700usd) and
currency words (dollars,euros); - thousands separators as locales use them (comma, dot, space, apostrophe, no-break or thin
space), with or without cents (1.700,00 €,1'700.00 CHF), andk($1.7k); - a bare number a few words after a money word ("the lowest I'll take is 1700").
Dates, times, ids and digests are not read as money. An amount the other party is shown anyway
(the asked price, an offer, a payment) is still not protected. - currency symbols before or after the amount (
-
Added: a message the agent writes (
from: agent), and, where the user sells, an agent's
claim (which the buyer's copy opens), is refused before it is sealed when it states the floor or
the limit in any of these forms. The refusal names the field, never the value. The user's own
words are recorded as said.
A floor in force goes to the rules checker opened, or the check is refused
- Fixed (fail closed). A rules checker reads a floor only from its verified
commercial_bounds_opening, and given none it finds the floor rule not applicable. When a floor
was in force but its opening could not be built, the checker was run without it. Now:deal checkrefuses before sealing anything, naming the cause and never the floor. The causes
are: the deal's limits cannot be read, no opening on this device recomputes to the commitment,
or the opening is not a commercial-bounds document.- A floor is in force when the limits in force set one, whatever can be recomputed from its
opening. - With no floor in force, nothing changes.
- Fixed: typed deals sent the wrong opening. In a typed deal, the opening sent was the open
step's, but a checker verifies it against the task authority it is given, which seals the floor
under its own nonce. The two never matched, so a checker verifying the opening failed every such
check. The task authority's own opening is now sent.
New deal records seal action taxonomy version 6
- Changed (wire; records sealed before re-derive unchanged). A deal record's
taxonomy_versionis now"6": capsule-engine'scapsule_engine/guards/action_taxonomy.json
at7ae6b95b37a3a1c4955af90f56ae4c1c0c404aa9(file sha256
7563fcaeb6c192a27877fae3131bca1021526046c8976475c46eebf4fb374272, JCS sha256
fc12eb90bc2b3c85bb49c4d3d7be8f384c6e0b8e189bc3b68a753a4f1af5cd94). - Every
action_classis unchanged. Against version 5 (e5ec2c1), version 6 changes only
approver_role(toaccount_holder) onagreement.accept,marketplace.offerand
marketplace.sale. No class is renamed, removed or added, and none changes trigger class.- A seller's steps carry: an offer
external_commitment.other, a commit (accepting a buyer)
agreement.accept, and telling the buyer where the item isdisclosure.personal. A rental's
or service's commit or sign is alsoagreement.accept, whose meaning is unchanged. - Deal records never carry
marketplace.offerormarketplace.sale.
- A seller's steps carry: an offer
- Each step keeps its own version. A record sealed under
"2","3","4"or"5"re-derives
with it. - Release pairing. A rules checker compares
taxonomy_versionexactly, so a record sealing
"6"needs a checker that holds version 6. The next release frommainships with such a
checker; none is cut frommainbefore one is available.
A rules checker is told which side of the deal the user is on
- Changed (checker input). Every deal check now sends
party_role(buyerorseller) at the
top level ofexternal-check-input/v0. It is the side the deal's opening intent sealed, or
buyerwhen it names none, and it is always explicit, so a checker selects its rules without
inferring the side. - Release pairing. The input's top level refuses members it does not list, so a checker built
before this change refuses every check that carriesparty_role. A release that sends it ships
with a checker that accepts it. - The deal id has one source per record: an x-deal-v0 record's
deal_id, or a typed record's
chain_id, which is exactly that value. No record carries both, and a test now holds it.
A typed deal's history acts carry the profile payee key
- Fixed (checker input only; nothing sealed changes). A history act in a deal recorded with typed
records now carriescounterparty_profile, resolved from the check its authorization rests on,
whether that act needed an approval or not. Before, it was resolved only through an approval, so a
repeated payment in a typed deal was not seen as a repeat by a rules checker. Untyped deals are
unchanged.
Verify
Prebuilt binaries: linux/amd64, linux/arm64, darwin/arm64.
V=v0.1.0-rc14; R=action-state-group/capsule-cli
gh release download "$V" --repo "$R"
sha256sum -c SHA256SUMS
for f in capsulectl-"$V"-*-*; do
gh attestation verify "$f" --bundle "capsulectl-$V.sigstore.json" --repo "$R" \
--signer-workflow "$R/.github/workflows/release.yml"
doneThe deal skill is installed from this tag's source archive; its skills/deal/SKILL.md equals the tagged commit's.
capsulectl v0.1.0-rc13
capsulectl v0.1.0-rc13, from commit 7eb05ac.
Disclosure first. Deals where the user sells are new in this release. A seller's floor is never sealed in the clear and no shared copy carries it as a sealed field; the buyer's copy shows the offer, the task authority, the acceptance and the seller's agent's representations, and per-thread salted commitments keep two buyers' copies of one sale from being linked by any digest or commitment; both still show the item's name and the offer, the seller's own listing. A buyer's deal shares the same records with the same audiences as before. A new per-profile payee fingerprint is sealed only for the user's own copy, on a companion record that every shared copy withholds. The check that keeps the floor out of written text still misses some formats; a fix follows.
Wire changes (all additive; records sealed before re-derive unchanged). New deal records seal action taxonomy version "5". A refund's check carries reverses_ref. A check naming a payee is followed by a counterparty_profile companion record, and the rules checker's input gains optional counterparty_profile, item_ref and commercial_bounds_opening members.
Changes
Where the user sells: a seller profile, an exact acceptance, and one item for several buyers
- Added (wire, additive; every deal sealed before re-derives unchanged).
party_role
(buyer|seller) on the intent, set when a deal opens and fixed; absent means buyer.
deliver_byandperform_byobligations with adue_bydate. Anot_selectedclose. - A seller's floor stays private.
min_total_minoris never sealed. Records that state it
sealbounds_commitment, a salted commitment (sha256-jcs-nonce256) to a
commercial-bounds/v0document. Only the user's own copy carries its openings, and only the
user's own rules checker is given the opening in force (commercial_bounds_opening). No
shared copy, for any audience, carries the floor (see "Not yet"). A check below the floor asks
(under_floor). - An exact acceptance.
offeris a check, allowed only where the user sells; a later offer
supersedes the earlier one (supersedes).deal note --kind acceptanceseals the buyer's
acceptance of the exact offer by its digest. A seller'scommitneeds an acceptance of the
latest offer with no change since, must equal that offer's amount, currency, terms and
recourse, and cites the acceptance. A deal where the user sells must use typed records. - A sale is money in. A seller's commit and offer count 0 toward any spend total.
- What the buyer's copy shows (seller deals only). The offer, the task authority in full,
the acceptance, and the seller's agent's own representations to the buyer (claims, with an
optionalclass:condition,warranty,refund_terms,delivery_promise,other) whose
words hold none of the user's private details. A buyer's deal is shared exactly as before. - One item, several buyers.
deal sale newseals a sale's own log;deal open --sale ID
opens a buyer's thread under it. Each thread seals the item reference and the sale's task
authority only as per-thread salted commitments, so no digest or commitment links two buyers'
copies; both still show the item's name and the offer, which are the seller's own listing.
The user's own rules checker is given the plainitem_ref. - Not yet: the check that keeps the floor out of written text misses some formats
("$1,700.00", bare amounts); a fix follows.
The payee keyed per profile, for the user's own history
- Added (wire, additive; records sealed before re-derive unchanged). A check that names a
payee is now followed, before any rules checker runs, by acounterparty_profilerecord.- What it holds: only
{"fp_alg": "hmac-sha256-profile-key", "ids": {"payee": …}}, the
payee fingerprinted with the same construction as the per-deal fingerprints. Its key is
derived from the profile store's own secret, so one merchant has one value across the
profile's deals and another in any other profile. - Where it points: it names its check with an
aboutref. Each deal record's per-deal
counterpartyfingerprints are unchanged.
- What it holds: only
- Never shared: no shared copy, for the counterparty or an adjudicator, carries it; the check
itself stays shareable. - The rules checker gets it:
external-check-input/v0gives the checker
record.counterparty_profilefor the check being decided, andhistory[i].counterparty_profile
for each earlier act (from its approval's check). It is supplied beside the sealed capsule and
never part of it. Acts checked before this release carry none.
New deal records seal action taxonomy version 5
- Changed (wire; records sealed before re-derive unchanged). A deal record's
taxonomy_versionis now"5": capsule-engine'scapsule_engine/guards/action_taxonomy.json
ate5ec2c1ba84f9d9269cbb164eb6a92400e4d5d49(file sha256
7d31e892cdea7352d0485c89430dba028bf02ce91fb0e932f4752c1f4066022f, JCS sha256
af3a054cae880e13ce5d8d8bdaa64383e6f3af14fa2bf2ddb88aab226865b13a). - Every
action_classis unchanged. Against version 4 (5300e0b), version 5 changes only
approver_role(toaccount_holder) oncommunication.send, a class no deal record carries;
no class is renamed, removed or added, and none changes trigger class. - Each step keeps its own version. A record sealed under
"2","3"or"4"re-derives with
it.
A refund's check names the payment it returns
- Added (wire, additive; records sealed before re-derive unchanged). A deal
checkof a
cancelthat returns a sealed payment (a refund) now carriesreverses_ref: that payment's
action, by its record digest, as a typed reference ({"type": "record", "digest_alg": "SHA-256", "digest": …}). It names the same payment, by the same digest, as the refund
action'sreversesref. A typed deal'sproposed-action/v0carries it too. - Amounts unchanged: a refund's
checkandactionboth keepamount_minor. A partial
cancel's keepcancelled_amount_minorand name no payment. Noreturned_minoris sealed.
deal checkpoint status: a deal log's latest checkpoint, read-only
-
Added:
capsulectl deal checkpoint status --deal IDreturns, as JSON:- the deal log's latest checkpoint: its
id(the SHA-256 of the signed statement),at, and
the log head it covers (mmr_size,entries,root); - the log's
log_entries; - the checkpoint's witness state as stored.
A deal with no checkpoint yet has
checkpointandwitnessnull, and exits 0. - the deal log's latest checkpoint: its
-
Read-only: it never seals, never cuts a checkpoint and never contacts the witness, and every
file under the store is byte-for-byte the same after it. It works on aread_onlyprofile. -
Fixed: opening a log that does not exist could panic while cleaning up the failed open. It
now returns the log's error.
Verify
Prebuilt binaries: linux/amd64, linux/arm64, darwin/arm64.
V=v0.1.0-rc13; R=action-state-group/capsule-cli
gh release download "$V" --repo "$R"
sha256sum -c SHA256SUMS
for f in capsulectl-"$V"-*-*; do
gh attestation verify "$f" --bundle "capsulectl-$V.sigstore.json" --repo "$R" \
--signer-workflow "$R/.github/workflows/release.yml"
doneThe deal skill is installed from this tag's source archive; its skills/deal/SKILL.md equals the tagged commit's.
capsulectl v0.1.0-rc12
capsulectl v0.1.0-rc12, from commit ba436db.
Disclosure first. A deal claim's words are now sealed as a commitment, and only whose claim it is (source_kind) shows; a counterparty's copy no longer carries a claim's text, and an adjudicator's copy opens only claims that hold none of the user's private details. Records sealed before this release are unchanged and still verify.
Wire change. New deal records seal action taxonomy version "4". Records sealed under "2" or "3" keep their stored value and re-derive byte for byte. A rules checker that compares the taxonomy version must know version 4.
Changes
A deal claim's words are sealed as a commitment; only whose it is shows
-
Changed (wire, additive; records sealed before re-derive unchanged). A claim, on a
claim
step and in the baseline'sclaims[], is now sealed as:text_commitment: the claim's words, salted, like every other text a deal record carries;source_kind: whose it is (merchant,agent,platform,userorexternal), in the
clear;- optional
source_ref_commitment: the free-form note of where it was read, committed.
A verdict names the claims it found unverified by reference (
unverified_claims), not by their
words. The typedaction-evaluation/v0does the same. -
source_kindis stated by the caller. A source that can only mean the other party
(counterparty,seller_message,merchant_email) is taken asmerchant. Any other source (a
page, a photo, a snapshot) must state it, and a claim that leaves it open is refused, naming the
field. The deal skill says how to choose. -
Openings:
- the user's own report carries
claim_openings; - an adjudicator's copy carries those of claims holding none of the user's private details;
- a counterparty's copy shows each disclosed claim record (its
source_kindand salted
commitments) and no opening.
- the user's own report carries
-
The profile checker:
check_profile.py --openings=FILErecomputes each opening against the
sealed record. A shared vector (claim words and source note) is checked by both the Go and the
Python implementation.
doctor --check-witness is ok only when the witness answers with success
- Fixed: the witness probe sent a HEAD to the endpoint and called any answer ok. A witness that
serves no HEAD routes answered 405 and was reportedreachable: true, status_code: 405, ok: true,
while the first delivery could still fail. - Now: the probe is a read-only GET of
<endpoint>/health, the witness's health endpoint. It is
ok only on a 2xx answer. Any 4xx or 5xx isok: false, with an issue naming the status, the
method and the URL. - Changed: a delivery the witness answered with an error keeps the witness's HTTP status (never
its response body), and its pending text says it: "pending: the witness answered with an error
(HTTP 409); it is retried at every tick."
New deal records seal action taxonomy version 4
- Changed (wire; records sealed before re-derive unchanged). A deal record's
taxonomy_versionis now"4": capsule-engine'scapsule_engine/guards/action_taxonomy.json
at5300e0b.
A reader that holds a later version found no class-keyed result for a record that named"2". - Every
action_classis unchanged. Versions 2, 3 and 4 name the same classes. Versions 3
and 4 addapprover_roleaccount_holder:- version 3 to
money.purchase,money.subscription,booking.createandbooking.modify; - version 4 to
booking.cancel,data.delete,communication.publishand
disclosure.personal.
- version 3 to
- Each step keeps its own version. A record sealed under
"2"or"3"re-derives with it.
A check carries the inputs a rules checker reads
-
Added (wire, additive): a check sealed from this version carries scalars a rules checker
reads. They go on thex-deal-v0check and the typedproposed-action/v0, and each is absent
when not known:recipient_role(fulfilling_merchantorthird_party, on a share);channelandfirst_contact_channel(channel kinds);upfront_amount_minor(the stated deposit, never inferred);material_fields_changedandoffer_fields_changed(counts over two fixed field lists: against
what was agreed, and against the keys the user's own words state, absent when they state none),
withmaterial_fields_basisandoffer_fields_basis(each list's digest);- on typed records,
task_authority_ref.
None carries a name, a contact detail or free text. Records sealed before re-derive unchanged.
-
Added: a typed deal's
task-authority/v0also carries its plan:outcome_id(a fixed id per
deal type,capsulectl.deal.<purchase|rental|booking|service>/1.0.0), its allowed actions as
allowed_actions, andpreconditions: []. The external-check input gains an optionaltask_authority_record
member: that whole sealed record, whose SHA-256 over its JCS bytes is the check's
task_authority_refdigest; its plan is atbody. -
The deal skill now says to always state
recourse(the rail and whether it is refundable) on
a pay, and to put a deposit the checkout asks for interms.
Rules a checker declares but does not measure are said quietly, not as a warning
- Fixed: a rules checker reports the rules its ruleset declares but it does not measure as
not_evaluablewith the reasondeclared, not measured. On every escalated purchase, the deal
card listed each one as a reason to ask ("Your rules ask for approval: …: declared, not
measured"); under an overallnot_evaluableit would read "not fully checked". - Now: such a finding is never a difference.
- The approval text names the rules quietly beside the verdict: "3 rules are declared by the
ruleset but not measured by this checker (r14, r24, r25)." - So does the user's own receipt, on the check's line. A shared copy does not name them.
- The verdict is unchanged: an allowed purchase stays allowed.
- Any other
not_evaluableis still a difference, said as before.
- The approval text names the rules quietly beside the verdict: "3 rules are declared by the
- Presentation goldens: a new fixture,
7-receipt-declared-rules.
Verify
Prebuilt binaries: linux/amd64, linux/arm64, darwin/arm64.
V=v0.1.0-rc12; R=action-state-group/capsule-cli
gh release download "$V" --repo "$R"
sha256sum -c SHA256SUMS
for f in capsulectl-"$V"-*-*; do
gh attestation verify "$f" --bundle "capsulectl-$V.sigstore.json" --repo "$R" \
--signer-workflow "$R/.github/workflows/release.yml"
doneThe deal skill is installed from this tag's source archive; its skills/deal/SKILL.md equals the tagged commit's.
capsulectl v0.1.0-rc11
Prebuilt capsulectl binaries (linux/amd64, linux/arm64, darwin/arm64). Verify with: sha256sum -c SHA256SUMS. Check the build attestation with no GitHub account: gh attestation verify FILE --bundle capsulectl-v0.1.0-rc11.sigstore.json --repo action-state-group/capsule-cli --signer-workflow action-state-group/capsule-cli/.github/workflows/release.yml
capsulectl v0.1.0-rc10
capsulectl v0.1.0-rc10
Behaviour and wire changes
A deal receipt's text is sealed with it
Before: a deal receipt's readable text (the summary lines, each step's line, the amounts, what was told and the anomalies)
was carried in the bundle's x-deal-v0 extension, which no record seals. Editing that text left verify --bundle VALID, and the
receipt page showed the edited text.
Now: writing a receipt seals that copy's own text as a record. That covers deal report --html, --bundle and --email,
bundle --deal and disclose --deal.
Wire change: one sealed report record per receipt written.
- The record: an
fyicapsule on the deal's own log (action_id: "capsulectl-deal-report"), not chained to the steps. - Its input:
{"type": "deal_report", "profile": "x-deal-v0", "deal_id", "audience", "nonce", "report"}. The nonce is a fresh
random 256-bit value, so the record's digest confirms no guess at the text. - The bundle: its
x-deal-v0extension is now{"sealed_report": "<capsule id>"}. The text is that record's disclosed input,
checked like every other disclosure. - One record per receipt: each copy seals its own text. A shared copy seals the text already rewritten for its audience, never
the user's own. - Later copies: they carry every earlier report's record with its input withheld. A copy discloses its own text and no other
copy's. - No file, no record: a
deal reportthat writes no file seals nothing.
What verify --bundle reports for x-deal-v0:
| The bundle | Result |
|---|---|
| Names its sealed report, whose disclosed text matches | pass |
| Text edited, or the extension names another record | INVALID, sealed_report_unverified |
| Holds a sealed report record but the extension names none: removed, or replaced with text, even with the record's input dropped | INVALID, sealed_report_not_named |
| A receipt written before this release (no report record; the text in the extension) | VALID as before: uninterpreted, with the finding extension_unbound and a note that its text is not verified |
The page:
- A sealed copy says it checked that the text has not changed since it was sealed, and that the text is capsulectl's summary of
the steps, not the steps themselves. - An edited or downgraded copy shows no deal text.
- An older receipt rendered with this release says its text is not checked.
The deal's log now holds report records after its steps:
- Reading a deal sets them aside.
- Witness coverage is counted in steps: a tick whose checkpoint holds every step, with only report records after it, still reads as
witnessed, or as pending for its stated reason.
Records declare their commitments' construction (commit_alg)
Wire change (additive). Every deal record sealed from this release carries commit_alg: "sha256-jcs-nonce256": in the
x-deal-v0 block, or in a typed record's header. It names the construction of the record's *_commitment values:
- SHA-256 over the RFC 8785 (JCS) bytes of
{"nonce", "text"}; - a fresh 256-bit random nonce for each commitment, and no key;
- recomputable only by a holder of the opening (the nonce and the text).
Older records and shared copies: records sealed before carry none and re-derive unchanged. commit_alg is on the share
allow-list, so shared copies still disclose the records that carry it. PROFILE.md section 4 states the construction, its nonce and
key scope, and where openings travel (only the user's own copy carries any).
disclose seals each disclosure as a capsule
Wire change: what disclose puts on the log. It used to append the bare digest of a disclosure record that was kept nowhere
else. Once a checkpoint covered such a digest, every later bundle and disclose on that profile failed ("operation failed …
sensitive details suppressed").
- Now: it seals the disclosure record as an ordinary
fyicapsule (action_id: "capsulectl-disclosure") with the profile's
signing key, stores it, and appends its capsule id. - The record's input: ids, digests and labels only, never disclosed payload bytes, plus a random 256-bit nonce.
- A later bundle proves the earlier disclosure's place in the log but withholds its input (
payloads_mode: selected), so a copy
made for one party never shows what was disclosed to another.
A page is written only when it can stand behind what it shows
bundle --html, disclose --html and deal report --html write a page only for a bundle that verify --bundle calls VALID.
Before, the page's viewer said "Bundle verification passed" for a bundle the CLI calls INCOMPLETE.
| The bundle | What happens |
|---|---|
| INCOMPLETE or INVALID | No page. The command exits as verify --bundle would (3 or 1) and names the claims that did not pass. |
A report/v1 root whose rows cite records the bundle does not carry |
No page, exit 3, naming how many rows it would show ("would show 1 of 2 rows"). |
| From a jsonl profile | No page, exit 2. Its bundle carries its evidence book's records about what was sealed, not the records themselves, so a page cannot show them. Publish on a SQLite profile for a page. |
bundlestill writes the bundle (--out, or stdout) when its page is refused.disclosewrites nothing and puts no disclosure on record.- Unchanged: every page path measured on rc9 was VALID: deal receipts, own and shared, and the README's self-checking report.
A spend limit binds the most a payment may take
The gap: a pay check carried only the expected charge, so an authorization buffer evaded every limit. For example, a $4.54
order passed a $5 limit while the card approval let the merchant take up to $9.54.
The new field: a pay check takes authorized_max_minor, the most the counterparty may take under the payment's authorization
(a card hold, or a pre-authorization with a buffer). It must be at least amount_minor, which stays the expected charge. The intent
limit evaluates it, and a difference names it.
Required for card, wallet and PayPal pays (fail safe): a pay by card, wallet, PayPal, or any rail not known to be holdless is
refused (exit 2) unless it states authorized_max_minor. Give the same value as amount_minor when no larger maximum is shown. A
pay on a holdless rail (bank transfer, Zelle, wire, cash and the like) defaults it to the amount.
Sealed (additive): the check carries authorized_max_minor, and spend_authorized_minor beside spend_minor. A typed
proposed-action/v0 carries it too, so an approval never extends to a larger maximum.
- The capture: an action that takes more than the authorized maximum differs from its check.
- Older records: records already sealed re-derive unchanged.
deal check runs the user's pinned rules checker, and seals what it said
A deal profile may now pin an external rules checker. Every deal check runs it and folds its answer into the check's one verdict
and one prompt. capsulectl knows no ruleset of its own: a ruleset id appears in a check only because the pinned checker reported
it.
Pinning (the user's policy, never the agent's): profile update --rules-checker FILE, where FILE is
{"command": ["/absolute/path", "arg", …], "timeout": "10s", "definition_digest": "<64 hex>"}.
- Where the executable lives: it must sit under a trusted plugin root, and is pinned by its SHA-256.
- Re-checked every time: every check re-reads the executable once, re-hashes it, and runs a private copy of exactly those
bytes. A file swapped after the hash is not what runs. - Optional:
definition_digestpins the ruleset the checker must report. - Timeout: 10s by default, at most 60s.
The contract (neutral, versioned v0; checkers vendor these files byte for byte):
- Input: stdin is one
external-check-input/v0object (skills/deal/profile/external-check-input-v0.schema.json).record: the sealed capsule of the step being checked, with its disclosed record.history: the profile's sealed acts with an amount, from every deal on the profile's own store, sealed in the last 31 days,
newest first, at most 1,000, in the same shape. An act taken without a check carries its fields underbody.unchecked.history_scope:{days, max_records, complete}.
- Output: exit 0 and one
external-check-result/v0object (skills/deal/profile/external-check-result-v0.schema.json).ruleset_id,definition_digest;verdict:allow,deny,escalateornot_evaluable;- an optional
tier, as in Result v0:recomputedorjudged. An absent tier reads as judged, never recomputed; - an optional
grade, by reference to Result v0's Grade:self-attested,witnessedorcountersigned; findings[]:{id, check, verdict, reason, limit, value}, passes included. Each limit and value is a number, or a string of
at most 64 characters with no line break.- Anything outside the schema is refused.
- Refusal: any other exit refuses the input, with the cause on stderr.
- Rolling windows: a rolling window is evaluated over the history supplied. A checker given too little reports the rule
not_evaluable.
One verdict:
| The checker | deal check |
|---|---|
allow |
adds nothing |
escalate or not_evaluable |
pauses, naming each failing rule with its limit and value ("Your rules ask for approval: …", "Your rules were not fully checked: …") |
deny |
the new verdict deny: the card names the rule, its limit and the value, and offers only "Hold"; an approval to proceed is refused |
| configured but changed since pinned, refused the input, timed out, printed anything else, or reported another ruleset digest | pauses: "Your rules were not checked: " |
| none configured | does not pause; the text says "Your rules were not checked: no rules checker configured." |
Wire changes (additive)....
capsulectl v0.1.0-rc9
capsulectl v0.1.0-rc9
Security
verify --bundle now fails a bundle whose disclosed payload was changed.
The flaw. In v0.1.0-rc3 through v0.1.0-rc8, capsulectl verify --bundle checked each disclosed payload against what its
record sealed, but did not let that result change the verdict. A bundle in which a disclosed agent_input or agent_output had
been edited after sealing was reported as:
"verdict": "VALID", with exit status 0;"status": "disclosure_mismatch"for that member, listed underdisclosuresin the same output.
The same releases also reported VALID for a bundle whose disclosures named a record the bundle does not hold.
What was not affected:
- The checks on the records themselves: record identity, the record signatures, the checkpoint signature, log membership and
interval coverage. An edited record, or an edited checkpoint, was reported INVALID as before. verify --capsule.- The self-checking HTML page. Its own verifier refused such a bundle ("Bundle verification failed"), and showed none of its
rows. That holds:- for
bundle --htmlanddisclose --htmlfrom rc5 (in rc3 and rc4 those commands'--htmlwas not implemented, and they
wrote no page); - for
deal report --htmlfrom rc3.
- for
What changes in this release. verify --bundle reports INVALID, exit status 1, when any disclosed member is anything other
than a match or withheld:
- a mismatch;
- a member that cannot be disclosed;
- a member whose record seals no digest for it;
- a disclosure for a record the bundle does not hold.
What to do. If you relied on verify --bundle from rc3 to rc8 to accept a bundle that discloses payloads, check that bundle
again with this release. Reading the disclosures list in the earlier output shows an edited payload (any status other than
disclosure_match or withheld), but may not show a disclosure for a record the bundle does not hold; only re-checking with this
release catches that case. Bundles that disclose nothing are not affected.
Behaviour changes for existing deal profiles
The checkpoint cadence ticks every 5 minutes
A deal profile's witness cadence, its checkpoint cadence, now defaults to a tick every 5m, give or take 1m. It was every 1h,
give or take 10m.
- No change to the construction: one cadence-log entry per tick whether or not anything happened, and nothing publishes on
deal activity. - Witness load: about 288 submissions per profile per day, up from 24.
| Your profile | What happens |
|---|---|
| Sets no cadence | Moves to the new default at upgrade. |
Sets both cadence.interval and cadence.jitter |
Keeps them. |
Sets only cadence.jitter |
May now be refused: the jitter must be under half the 5m default, so under 2m30s. The error names both values. Set cadence.interval (for example 1h), or lower the jitter. |
The poll. Schedule capsulectl --profile <name> cll checkpoint cadence --wait-up-to 5m every 5 minutes.
deal tickis the same command and stays callable.- A run with no tick due publishes nothing. It contacts the witness only to retry a cadence delivery still pending, once that
delivery's backoff has passed. - Keep
--wait-up-to: it publishes each tick at its jittered time, not on the scheduler's grid.
Receipts say when a pending checkpoint will reach the witness, from the profile's own cadence. For example: "within about 6m
(every 5m, give or take 1m)".
The action class on deal records, and cancels are never spend
Each check, action and disclosure sealed from this release carries action_class and taxonomy_version: "2" (new optional
x-deal-v0 members, also sealed inside the typed proposed-action/v0 and action-record/v0), so a limit keyed on an action's
class (money.purchase, booking.create, …) can select deal records. The classes are those of the published action taxonomy v2.
- Committing to a purchase has the same class as paying for it.
- A pair the table does not name is
external_commitment.other. - A cancel is never spend. Every cancel seals
spend_minor: 0. A cancel with an amount sealsdirection: "in". A cancel
that returns a payment it names ismoney.refund. An unmatched cancel's amount moves tocancelled_amount_minorand it carries
noamount_minor. A cancellation fee is recorded asfee_minor, not as spend. - Shared copies: a cancel's check that carries an amount is now withheld from copies shared with a counterparty.
- Records already sealed are unchanged and verify as before.
Materiality: sealed at open, a re-pin mid-deal is flagged
These build on the pinned materiality predicate described under "Carried from rc8" below.
- Sealed at open.
deal openseals the predicate the profile pins into the deal's baseline (materiality, additive). It
comes from the profile, never from the input. - A re-pin mid-deal is flagged. A later check under a different predicate carries a
materiality_changeddifference: it is
shown on the card and pauses the check. - A deleted predicate file refuses with its own message: "materiality predicate file missing; re-pin it with profile update".
- What is sealed. Sealed records carry the predicate's digest and a commitment to its name and version (
label_commitment):- the user's own copy opens the commitment (
materiality_openings); - a copy shared with a counterparty discloses the digest only;
- shared copies say a predicate change in fixed words, without the predicates' names.
- the user's own copy opens the commitment (
- Fixed: shared copies withheld every check step once the verdict began recording
materialityin rc8. Verdicts that rc8
sealed with the name and version in the clear stay withheld from shared copies.
Carried from rc8 (not described in its release notes)
Which agent picks pause a deal check is a pinned materiality predicate. Which attributes the agent picked on its own pause a
check is no longer compiled into capsulectl. It is a materiality predicate (materiality-predicate/v0).
- A profile with no predicate pauses on every attribute the agent picked. To keep the earlier behaviour, pin the example
predicate, which reproduces it:
capsulectl --profile NAME profile update --materiality skills/deal/profile/materiality-predicate/neutral.json.
capsulectl doctor --profile NAMEsays which applies. - Pinned by digest in the profile (
materiality.digest). A check refuses, sealing nothing, when the pinned file has changed;
re-pin it withprofile update --materiality FILE. - The user's policy. Setting the predicate is the user's to do: a check takes no predicate of its own.
- Recorded. Every check says which predicate decided (name, version and digest, or digest
none) in its output, and seals it
in its verdict.
New
Typed action records (opt in: deal open --records typed)
A deal can seal its authority steps as typed action records, beside its x-deal-v0 evidence records in one chain:
task-authority/v0, proposed-action/v0, action-evaluation/v0, action-approval/v0, action-record/v0 and
action-outcome/v0. action-report/v0 is defined, and will be sealed by a later release. Deals opened without the flag are
unchanged.
The check contract. The evaluation carries proposed_action_digest, task_authority_ref, ruleset_digest,
materiality_digest, valid_until and authority_basis.
- A step after
valid_untilis refused. - An approval never extends to a changed proposed action.
- An ASK is answered only by the user's own approval, on the card the check showed.
Platform approval observations. deal note --kind platform_approval records that another platform asked for approval, and
what it showed and returned. It authorizes nothing.
The receipt's Authority block. For each action in a typed deal, the receipt lists every authority layer it relied on, in the
order of its sealed authority basis, each with its own time:
- your request;
- your rules (DO or ASK);
- your approval on the card;
- any platform's own approval, marked as observed;
- the action.
The block is in the JSON report, the receipt page and the emailed receipt. It is in the user's own copy only.
The Capsule follows the authority basis. A typed action's Capsule reads its disposition from its sealed authority_basis:
| Authority basis | Capsule |
|---|---|
| Within the user's rules (a DO) | decide with approver: policy |
| The user's own approval | decide with approver: human |
| No covered basis | fyi |
A platform's approval never makes it human.
A self-checking report of records you sealed
The README has a worked example, run in CI exactly as written, that turns sealed records into a bundle plus a page that checks
itself offline. It uses:
- a SQLite profile with a log;
- a
report/v1root citing the records; - one checkpoint;
disclose --out bundle.json --html report.html;verify --bundle.
The README states what VALID rests on: keys the bundle carries, which a reader compares with the producer's key obtained
another way. It also states what the page does not check: the checkpoint's signature and the records' signatures.
Fixes
verify --bundleaccepts the log id from the completeness certificate. Bundles built on a JSONL profile failed with
checkpoint_field_missing:log_id, though capsulectl wrote them. The signed log id must now equal every log id the bundle
states, in the certificate, the checkpoint or both.- The deal receipt page renders deal bundles. The vendored evidence-graph viewer threw "root aggregate payload is not
disclosed" on a bundle with no evaluation summary, and showed a blank page. It now renders the bundle and says it has no
aggregate view. - Shared copies of deals keep their check steps. See "Materiality" above. (One new withholding: a cancel's check with an amount; see "The action class" above.)
Dependencies
- `githu...
capsulectl v0.1.0-rc8
Prebuilt capsulectl binaries (linux/amd64, linux/arm64, darwin/arm64). Verify with: sha256sum -c SHA256SUMS. Check the build attestation with no GitHub account: gh attestation verify FILE --bundle capsulectl-v0.1.0-rc8.sigstore.json --repo action-state-group/capsule-cli --signer-workflow action-state-group/capsule-cli/.github/workflows/release.yml
capsulectl v0.1.0-rc7
Prebuilt capsulectl binaries (linux/amd64, linux/arm64, darwin/arm64). Verify with: sha256sum -c SHA256SUMS. Check the build attestation with no GitHub account: gh attestation verify FILE --bundle capsulectl-v0.1.0-rc7.sigstore.json --repo action-state-group/capsule-cli --signer-workflow action-state-group/capsule-cli/.github/workflows/release.yml
capsulectl v0.1.0-rc6
Prebuilt capsulectl binaries (linux/amd64, linux/arm64, darwin/arm64). Verify with: sha256sum -c SHA256SUMS. Check the build attestation with no GitHub account: gh attestation verify FILE --bundle capsulectl-v0.1.0-rc6.sigstore.json --repo action-state-group/capsule-cli --signer-workflow action-state-group/capsule-cli/.github/workflows/release.yml