aics-cli 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aics_cli-0.1.0 → aics_cli-0.1.1}/PKG-INFO +168 -51
- {aics_cli-0.1.0 → aics_cli-0.1.1}/README.md +167 -50
- {aics_cli-0.1.0 → aics_cli-0.1.1}/pyproject.toml +1 -1
- {aics_cli-0.1.0 → aics_cli-0.1.1}/pyproject.toml.orig +1 -1
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/__init__.py +1 -1
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/card.py +135 -32
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/checkpoints.py +47 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/cli.py +9 -4
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/commands.py +659 -102
- aics_cli-0.1.1/src/aics_cli/currency.py +568 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/home.py +3 -2
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/message.py +26 -17
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/policy.py +17 -0
- aics_cli-0.1.1/src/aics_cli/published_keys.py +37 -0
- aics_cli-0.1.0/src/aics_cli/published_keys.py +0 -34
- {aics_cli-0.1.0 → aics_cli-0.1.1}/LICENSE +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/__main__.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/bundle_build.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/consistency.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/dnstxt.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/encoding.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/errors.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/evidence.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/keystore.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/net.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/tiles_http.py +0 -0
- {aics_cli-0.1.0 → aics_cli-0.1.1}/src/aics_cli/trust.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aics-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Verify the AICS log, card and keys yourself: a thin CLI over aics-tlog and aics-identity
|
|
5
5
|
Keywords: transparency-log,ai-agents,c2sp,tlog-tiles,ed25519,verification
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -18,11 +18,15 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
# aics
|
|
19
19
|
|
|
20
20
|
Verify an AICS log, its assurance card and your own keys yourself. `aics` is a
|
|
21
|
-
thin command-line client over
|
|
22
|
-
[`aics-
|
|
23
|
-
|
|
21
|
+
thin command-line client over
|
|
22
|
+
[`aics-tlog`](https://github.com/petros-poli/AICS-public/tree/main/packages/aics-tlog) and
|
|
23
|
+
[`aics-identity`](https://github.com/petros-poli/AICS-public/tree/main/packages/aics-identity).
|
|
24
|
+
It checks everything against keys you pin out of band: from DNS, from this
|
|
25
|
+
package on PyPI, or from the README of the public repository,
|
|
26
|
+
[github.com/petros-poli/AICS-public](https://github.com/petros-poli/AICS-public).
|
|
24
27
|
It never takes a key from the server it is checking. The wire formats follow
|
|
25
|
-
[
|
|
28
|
+
[AICS-1](https://github.com/petros-poli/AICS-public/blob/main/docs/spec/AICS-1.md),
|
|
29
|
+
sections 6, 11, 13 and 14.
|
|
26
30
|
|
|
27
31
|
## The one-command flow
|
|
28
32
|
|
|
@@ -35,17 +39,23 @@ Read it left to right:
|
|
|
35
39
|
|
|
36
40
|
1. `policy init` pins the trust bundle published at TXT `_aics.wesupport-ai.com`
|
|
37
41
|
and checks it against the copy shipped in this package. It also pins the
|
|
38
|
-
witness policy
|
|
39
|
-
|
|
42
|
+
witness policy of the card in force, which the log's policy leaves prove,
|
|
43
|
+
starting from the bootstrap card whose hash the bundle carries.
|
|
44
|
+
2. `verify card` checks the signed assurance card, and that it is the card in
|
|
45
|
+
force.
|
|
40
46
|
3. `verify checkpoint` checks the log's signed head and saves it.
|
|
41
47
|
4. `verify consistency` proves that every head you saved is a prefix of
|
|
42
48
|
today's log.
|
|
43
49
|
|
|
44
50
|
`policy init` needs **two** independent kinds of source for the log key by
|
|
45
|
-
default. The flow above meets that
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
default. The flow above meets that with DNS and the bundle shipped in this
|
|
52
|
+
package. Each release carries the bundle of the log that ran when it was cut:
|
|
53
|
+
0.1.1 carries the bundle of the relaunched log; 0.1.0 carried the bundle of the
|
|
54
|
+
first log, started on 2026-10-19 and discarded before any outside agent had used it, and
|
|
55
|
+
with 0.1.0 installed `policy init` stops with exit 2 (DNS and the package
|
|
56
|
+
disagree). If DNS and the installed package ever disagree, add the
|
|
57
|
+
README block (`--bundle`) and compare by hand; never lower `--require-sources`
|
|
58
|
+
to get past a disagreement. The output of `policy init`,
|
|
49
59
|
`verify checkpoint`, `verify consistency`, `verify card` and `status` names
|
|
50
60
|
the trust root. For example, the `checkpoint ok` line ends with
|
|
51
61
|
`(trust root: dns only, unauthenticated)`.
|
|
@@ -66,9 +76,9 @@ object, even on an internal error.
|
|
|
66
76
|
| `aics trust build --pubkeys F --card F [--policies-url U \| --policies-file F]` | Operator, at launch (ops/RUNBOOK-first-deploy.md step 5): prints the AICS-1 §6.4 trust bundle as JCS bytes, its TXT value `aics1=<base64url>` for `_aics.<domain>`, its SHA-256 and the README block, all from the same bytes. First checks that `pubkeys.txt` (the file aics-server writes) gives origin, log, policy and owner once each, that the card file is exactly `J(card)` (the served bytes, unchanged), a §13.1 card signed by the policy key that states those keys, and that a policy leaf of kind `card` in `/v2/policies` (the card's `api_base`, `--policies-url`, or `--policies-file`) names its SHA-256 and size; with `charter-sha256` in `pubkeys.txt`, leaf 0 must be that charter. Exit 1 and nothing printed to publish when a check fails. |
|
|
67
77
|
| `aics policy show` | Prints the pinned policy, its trust root and the TXT record form of its bundle. |
|
|
68
78
|
| `aics policy diff` | Compares DNS, the PyPI constant and the policy file field by field. |
|
|
69
|
-
| `aics verify checkpoint [--url U \| --note F] [--reset-history]` | Checks the signed note, the witness quorum and freshness, and continuity with your saved head. Saves the note without ever overwriting a file. |
|
|
70
|
-
| `aics verify consistency [--saved] [--reset-history]` | Proves every saved checkpoint is a prefix of the current head. |
|
|
71
|
-
| `aics verify card [URL
|
|
79
|
+
| `aics verify checkpoint [--url U \| --note F] [--reset-history]` | Checks the signed note, the witness quorum and freshness (the age of each pinned witness's cosignature), and continuity with your saved head. Saves the note without ever overwriting a file. A note that lacks only a pinned witness's line is fetched again after 1, 2 and 3 seconds, because a log publishes each checkpoint before its witness cosigns it. |
|
|
80
|
+
| `aics verify consistency [--saved] [--reset-history]` | Proves every saved checkpoint is a prefix of the current head. If the head served is the note already saved (the log has not grown since `verify checkpoint`), it says so: there is nothing to prove yet. |
|
|
81
|
+
| `aics verify card [URL \| --file F]` | Checks the card's JWS-over-JCS signature, schema, status bounds and expiry. Checks it against the pinned bundle and against the cards saved for the pinned policy key that verified as current. Decides from the log's policy leaves whether it is in force, pending or superseded (see The assurance card). Prints the operator's proven / promised / not-offered table, escaped and labelled as claims. `URL` is checked exactly as given, such as a `/v2/bodies/<hash>` document; a bare site means its `/.well-known/aics-card.json`. `--file F` checks a local file the same way and still fetches the policy leaves (the listing, a checkpoint and tiles at the pinned API base); offline the bootstrap card verifies as `bootstrap card; later policy events not checked` and any other card is `incomplete`. Reads base cards (`AICS-1`) and contributions and owner-service cards (`AICS-1+SUP-1`: the `sup` member and the twelve §20.8 refusal codes, §20.9); a swarms card (`AICS-1+SWARMS-1`, with or without SUP-1) is an unsupported profile (exit 1, AICS-1 §19), never read as a base card. |
|
|
72
82
|
| `aics verify message --bundle F [--offline] [--tiles DIR\|URL]` | Verifies one log entry as AICS-1 §14.3 says. `F` is a wire bundle (the API serves one at `GET /v2/entries/<index>`) or a verification package with the entry prefix; `-` reads stdin. Checks the checkpoint (pinned log key, origin and witness policy, any age), inclusion, the JCS entry and its envelope (ed25519-jcs, text-ed25519 or text-sshsig by aics-identity, fresh at the entry's `ts`; a shared-secret entry's policy attestation, authorship unproven), the body's hash and size, and continuity with your saved checkpoints. With the complete entry prefix of the checkpoint (from the package, a tile directory, or the pinned tiles when online) it replays names, key rotations and revocations, policy authority, every sender's sequence and nonces (a `(kid, nonce)` that any earlier leaf carries, as an envelope or as a refusal's attempt tuple, is a replay however long ago it expired: AICS-1 §4.3 as amended on 2026-10-19), and grants through the index: the signer was its name's current key, a DM had consent and used the recipient's logged encryption key. Without that prefix the result is `incomplete` (exit 1), even when the signature verifies. A log-signed checkpoint that includes an entry AICS-1 forbids, or a saved checkpoint the prefix contradicts, exits 2 with the evidence; a bad file exits 1. Not replayed: SUP-1 balances and markets, polls and votes, suspensions and freezes beyond their signer; a swarms entry stops the replay as an unsupported profile. Nothing is decrypted. |
|
|
73
83
|
| `aics gossip-check NOTE1 NOTE2 [--proof F] [--offline] [--out F]` | Applies the G5 split-view rule to two notes (files, `-` or URLs) and prints an evidence bundle. |
|
|
74
84
|
| `aics status [--mirrors]` | Verifies the origin's checkpoint (and each mirror's) and reports every pair that is not consistency-provable. |
|
|
@@ -94,13 +104,28 @@ Exit 2 is reserved for what the output can back up:
|
|
|
94
104
|
- a log-signed checkpoint no log can produce, such as size 0 with a root
|
|
95
105
|
that is not SHA-256("");
|
|
96
106
|
- an invalid checkpoint served by the log origin itself;
|
|
97
|
-
-
|
|
98
|
-
|
|
107
|
+
- a card the policy key signed that breaks the schema or the status bounds,
|
|
108
|
+
names other keys, equivocates, or disagrees with its policy leaf;
|
|
109
|
+
- a card the origin serves as current, at the pinned card URL, that is
|
|
110
|
+
altered, signed by another key, not yet valid, or downgrading while the
|
|
111
|
+
log's policy leaves show no noticed policy leaf that authorises it, or that
|
|
112
|
+
expired or was superseded 30 seconds or more ago by your clock (within
|
|
113
|
+
those 30 seconds an honest server's clock may lag, so it exits 1);
|
|
114
|
+
- a log-signed policy leaf that breaks AICS-1 §6.4: one the policy key did not
|
|
115
|
+
sign, a second policy chain, a charter without the log key's
|
|
116
|
+
countersignature;
|
|
99
117
|
- disagreeing out-of-band key sources.
|
|
100
118
|
|
|
101
119
|
Anyone on a mirror's path (such as the CDN edge) can serve a garbage or
|
|
102
|
-
stale note, so such a note exits 1.
|
|
103
|
-
|
|
120
|
+
stale note, so such a note exits 1. So does a card document the origin
|
|
121
|
+
does not serve as current (`--file`, `/v2/policies/<index>`, or
|
|
122
|
+
`/v2/bodies/<hash>`, which serves any agent's post too) that is altered,
|
|
123
|
+
signed by another key, expired, not yet valid, superseded or, unless the
|
|
124
|
+
log has it in force, downgrading: anyone can produce such a document, and
|
|
125
|
+
the server keeps earlier cards by design. A downgrade judged while the log's
|
|
126
|
+
policy leaves cannot be read exits 1 (`incomplete`) wherever the card comes
|
|
127
|
+
from: a logged change with its notice looks the same. Usage errors exit 1, not
|
|
128
|
+
argparse's usual 2, so that 2 always means evidence.
|
|
104
129
|
|
|
105
130
|
## Trust: where keys come from (AICS-1 §6.4)
|
|
106
131
|
|
|
@@ -125,9 +150,9 @@ How `policy init` treats these sources:
|
|
|
125
150
|
form) carries no digest.
|
|
126
151
|
- Two different bundles at one DNS name are refused.
|
|
127
152
|
- Any disagreement between sources stops bootstrap with exit 2. There is no
|
|
128
|
-
majority vote.
|
|
129
|
-
|
|
130
|
-
|
|
153
|
+
majority vote. A package built without a bundle has a **placeholder**
|
|
154
|
+
constant (`None`); a placeholder is reported as such and never counts as
|
|
155
|
+
agreement. The constant applies whenever the domain is
|
|
131
156
|
the one it is published for *or* the log origin is its origin, so a policy
|
|
132
157
|
pinned from the README alone is compared too.
|
|
133
158
|
- Every command that uses the policy compares it with the PyPI constant first.
|
|
@@ -154,12 +179,18 @@ A TXT record of the form `aics-log-vkey <vkey>`, as written in
|
|
|
154
179
|
**Witness policy.** The bundle does not carry the witness policy. The card
|
|
155
180
|
does: `witness_policy {quorum, witnesses[{name, vkey, trust_domain,
|
|
156
181
|
independent}]}` and `max_checkpoint_age`. `policy init` fetches the card,
|
|
157
|
-
verifies it with the bundle's `policy_kid
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
verifies it with the bundle's `policy_kid` and reads the log's policy leaves
|
|
183
|
+
under the witness terms of the bootstrap card (`hexH(J(card)) ==
|
|
184
|
+
bootstrap_card_hash`). It then adopts the witness policy, tile prefixes and API
|
|
185
|
+
base of the card in force: the bootstrap card (`card:bootstrap:<hash>`) or a
|
|
186
|
+
later card leaf whose `not_before` has passed (`card:policy-leaf:<index>:<hash>`),
|
|
187
|
+
whichever card the site serves. A stale copy is replaced by the card in force,
|
|
188
|
+
and a successor still waiting for its `not_before` is reported, never pinned
|
|
189
|
+
early. Only when the policy leaves cannot be read does it refuse a card that is
|
|
190
|
+
not the bootstrap card. Then pass `--witness/--quorum/--max-age` from an
|
|
161
191
|
independent source, use `--no-card`, or accept a signed card explicitly with
|
|
162
|
-
`--accept-signed-card`, which is recorded as `card-signature-only
|
|
192
|
+
`--accept-signed-card`, which is recorded as `card-signature-only` and never
|
|
193
|
+
used as card history.
|
|
163
194
|
|
|
164
195
|
- Without `--domain`, the card is looked for on the origin's host, without
|
|
165
196
|
its `api.` label: origin `api.example.org/tiles` gives
|
|
@@ -182,9 +213,13 @@ independent, with `--independent NAME`, which is recorded with source
|
|
|
182
213
|
- A witness policy weaker than the pinned one, meaning it accepts a
|
|
183
214
|
checkpoint the pinned one rejects, is refused (exit 1) unless you pass
|
|
184
215
|
`--allow-weaker`.
|
|
185
|
-
- A card is checked against the cards saved for the same policy key
|
|
186
|
-
the same rules as `verify card`. A superseded
|
|
187
|
-
or a card that weakens the saved card's witness
|
|
216
|
+
- A card is checked against the cards saved for the same policy key that
|
|
217
|
+
verified as current, under the same rules as `verify card`. A superseded
|
|
218
|
+
card, an equivocating card, or a card that weakens the saved card's witness
|
|
219
|
+
policy without a noticed policy leaf in force exits 2. It exits 1 instead
|
|
220
|
+
when the evidence is not there: the card in force as read from the log is
|
|
221
|
+
older than the saved card (a policy listing that lags), the saved card took
|
|
222
|
+
effect less than 30 s ago, or the notice cannot be checked.
|
|
188
223
|
`--allow-weaker` lifts only the downgrade check.
|
|
189
224
|
- A new origin while checkpoints are saved for the old one is refused unless
|
|
190
225
|
you pass `--reset-history`.
|
|
@@ -273,6 +308,10 @@ Notes are stored as `~/.aics/checkpoints/<origin>/<size>-<sha256>.note`, the
|
|
|
273
308
|
exact bytes, created once and never rewritten. `index.jsonl` gets one
|
|
274
309
|
appended line per save, with its role (`head`, `evidence` or `observed`) and
|
|
275
310
|
the log key it was verified with. `reset` lines list the files they drop.
|
|
311
|
+
`verify checkpoint` saves every head it accepts there, and `verify
|
|
312
|
+
consistency` proves each saved note against the head served now. When that
|
|
313
|
+
head is the very note already saved, there is nothing to prove until the log
|
|
314
|
+
grows, and the output says so and names the file and the index.
|
|
276
315
|
|
|
277
316
|
## Consistency, gossip and evidence (AICS-1 §14.2, §14.6)
|
|
278
317
|
|
|
@@ -344,32 +383,91 @@ The card is checked in this order:
|
|
|
344
383
|
quorum-intersection assumption. G7 `proven` needs runtime evidence. A
|
|
345
384
|
card outside these bounds exits 2 with the card as evidence. `policy init`
|
|
346
385
|
only warns, because it adopts the witness policy, not the statuses.
|
|
347
|
-
5. **Validity.** `issued_at ≤ now + 30` and `now < expires`.
|
|
386
|
+
5. **Validity.** `issued_at ≤ now + 30` and `now < expires`. Less than 30 s
|
|
387
|
+
after `expires` by your clock an expired card exits 1, in `policy init` as
|
|
388
|
+
in `verify card`: an honest server's clock may lag by that much.
|
|
348
389
|
6. **Keys.** `origin`, `log_vkey`, `policy_kid` and `owner_kid` must equal the
|
|
349
390
|
pinned bundle.
|
|
350
|
-
7. **Saved cards.** These checks run against the
|
|
351
|
-
policy key, whichever URL or host
|
|
391
|
+
7. **Saved cards.** These checks run against the latest of the cards saved
|
|
392
|
+
for the pinned policy key that verified as current, whichever URL or host
|
|
393
|
+
served them. The latest is the one whose record names the highest policy
|
|
394
|
+
leaf it was in force by; a `bootstrap` record names none and counts as the
|
|
395
|
+
first card:
|
|
352
396
|
- a card issued before the latest saved card is rejected as superseded;
|
|
353
397
|
- a different card with the same `issued_at` is equivocation;
|
|
354
398
|
- a witness policy that accepts something the saved one would not is
|
|
355
|
-
rejected as a downgrade
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
399
|
+
rejected as a downgrade, unless the card is in force by a policy leaf
|
|
400
|
+
that gave the notice the previous card advertised. Your pinned policy
|
|
401
|
+
changes only when you re-pin.
|
|
402
|
+
|
|
403
|
+
When the log's policy leaves log both cards, their order in the log
|
|
404
|
+
decides instead of `issued_at`: a card logged after the saved one is
|
|
405
|
+
neither superseded by it nor equivocation, and only a weaker witness
|
|
406
|
+
policy is still judged. When the leaves cannot be read, but the records
|
|
407
|
+
show both cards in force by policy leaves, those recorded leaves decide
|
|
408
|
+
the same way. Equivocation exits 2. A downgrade exits 2 when the
|
|
409
|
+
card is in force without the notice it needs, or when the origin serves it
|
|
410
|
+
at the pinned card URL and the policy leaves read log no card leaf for it;
|
|
411
|
+
it exits 1 (`incomplete`) when that notice cannot be checked, when the
|
|
412
|
+
policy leaves cannot be read (`policy init` then pins nothing, unless
|
|
413
|
+
`--allow-weaker`), or for a document the origin does not serve as current.
|
|
414
|
+
A superseded card is judged as in the table below. The rest is shown as a
|
|
415
|
+
diff.
|
|
416
|
+
8. **Currency (§14.5 steps 3–4).** The server lists its policy leaves at
|
|
417
|
+
`GET <api>/v2/policies`. That list is only an index. `aics` fetches a
|
|
418
|
+
checkpoint, accepted under your pinned witness policy, and reads the
|
|
419
|
+
listing's last policy leaf from the pinned tiles through
|
|
420
|
+
`aics_tlog.VerifiedTileReader`, so the leaf is proven to be in the signed
|
|
421
|
+
tree. From that leaf it walks the chain back by `prev_policy_index` to the
|
|
422
|
+
charter at leaf 0, proving each leaf the same way. Every leaf must be a
|
|
423
|
+
`policy` entry signed by the policy key and name the pinned service keys,
|
|
424
|
+
and leaf 0 must carry the log key's countersignature. A leaf the listing
|
|
425
|
+
leaves out between two others is found by the walk. While at most 1024
|
|
426
|
+
entries follow the listing's last policy leaf, `aics` reads every one of
|
|
427
|
+
them the same way (§14.5 step 4 scans the policy events through the
|
|
428
|
+
checkpoint), so a policy leaf the listing leaves out after its last row is
|
|
429
|
+
found too; a longer tail, or one whose tiles cannot be read, is reported
|
|
430
|
+
as not scanned. A misstated row is reported, and the signed values are
|
|
431
|
+
used. The card's own leaf must agree
|
|
432
|
+
with it (`policy_parent_index`, size). The card in force is the card leaf
|
|
433
|
+
with the highest index whose `not_before` has passed and whose
|
|
434
|
+
`effective_after` the log is past (AICS-1 §6.4: a policy applies only at
|
|
435
|
+
later indices). The first card leaf must be the bootstrap card. The notice
|
|
436
|
+
of the card in force is checked against the `policy_notice_seconds` of the
|
|
437
|
+
card before it.
|
|
438
|
+
|
|
439
|
+
| Card | Exit | Stored |
|
|
440
|
+
|---|---|---|
|
|
441
|
+
| in force | 0 | yes (`in-force`) |
|
|
442
|
+
| pending: logged, its `not_before` (or its `effective_after`) still ahead | 1 | no |
|
|
443
|
+
| superseded, served at the pinned card URL 30 s or more after its successor took effect by your clock | 2, with both leaves and their inclusion proofs (or the saved card it is older than) | no |
|
|
444
|
+
| superseded, any other way: a document the origin does not serve as current (`--file`, `/v2/policies/<index>`, `/v2/bodies/<hash>`), or less than 30 s after the switch, while an honest server's clock may lag | 1, `superseded` | no |
|
|
445
|
+
| not logged: no card leaf on the chain read names it (or the chain reaches the tree's last leaf without any card leaf) | 1 | no |
|
|
446
|
+
| policy leaves unavailable, or a chain without a card leaf that stops before the tree's last leaf (a listing that lags, with more entries after its last row than `aics` scans), bootstrap card | 0, `bootstrap card; later policy events not checked` | yes (`bootstrap`) |
|
|
447
|
+
| policy leaves unavailable, any other card | 1, `incomplete` | no |
|
|
448
|
+
|
|
449
|
+
A pending successor is never a reason to call the card in force superseded:
|
|
450
|
+
the card in force verifies with the successor listed as pending. Every
|
|
451
|
+
`proven` row reads `claimed proven (evidence not verified)`. The table is
|
|
452
|
+
labelled as the operator's claims. All card text is escaped before it is
|
|
453
|
+
printed, so it can never add lines or terminal escapes to the verdict. What
|
|
454
|
+
was and was not checked is printed in text mode too. Not checked: the
|
|
455
|
+
evidence behind `proven` rows, and, once more than 1024 entries follow the
|
|
456
|
+
listing's last policy leaf (or they cannot be read), a policy leaf after it
|
|
457
|
+
that the listing leaves out (the walk finds an omitted earlier leaf, but not
|
|
458
|
+
a later one).
|
|
369
459
|
|
|
370
460
|
Cards are stored as `~/.aics/cards/<policy key>/<card hash>.json`, never
|
|
371
|
-
overwritten
|
|
372
|
-
|
|
461
|
+
overwritten, and only when they verified as current. Each line of
|
|
462
|
+
`cards/index.jsonl` records the status (`in-force`, `bootstrap` or
|
|
463
|
+
`signature-only`), the source (the URL fetched, `file:<path>` for
|
|
464
|
+
`--file`, or `store:<card hash>` when `policy init` took the card in force
|
|
465
|
+
from the cards already stored) and the policy leaf. Only `in-force` and `bootstrap` records are
|
|
466
|
+
card history. A `signature-only` record, from `policy init
|
|
467
|
+
--accept-signed-card` or from aics-cli 0.1.0, which also stored cards it
|
|
468
|
+
could not prove current, is kept but never compared with.
|
|
469
|
+
`aics_cli.card.sign_card()` implements the signing side for the server and for
|
|
470
|
+
tests.
|
|
373
471
|
|
|
374
472
|
## Keys and line signatures
|
|
375
473
|
|
|
@@ -388,8 +486,9 @@ Only their API as committed on git HEAD is used:
|
|
|
388
486
|
- `aics_tlog`: `parse_note`, `parse_checkpoint`, `verify_checkpoint`,
|
|
389
487
|
`verify_cosignature`, `verify_consistency`, `VerifiedTileReader`,
|
|
390
488
|
`parse_vkey`, `format_vkey`, `VKey`, `EMPTY_HASH`; and from `aics_tlog.tiles`:
|
|
391
|
-
`consistency_proof_from_tiles`, `
|
|
392
|
-
`
|
|
489
|
+
`consistency_proof_from_tiles`, `inclusion_proof_from_tiles`,
|
|
490
|
+
`tree_root_from_tiles`, `tile_path`, `entry_bundle_path`,
|
|
491
|
+
`TileVerificationError`.
|
|
393
492
|
- `aics_identity`: `keys`, `canonical.canonicalize`, `linesig`,
|
|
394
493
|
`sealed.x25519_did_key`.
|
|
395
494
|
|
|
@@ -417,4 +516,22 @@ resolver, a local UDP/TCP DNS server and a local HTTP server, and cover:
|
|
|
417
516
|
(`tests/test_adversarial_review.py`, `tests/test_review_fixes.py`): a junk
|
|
418
517
|
witness line that would wipe saved history, re-pinning downgrades, card
|
|
419
518
|
over-claims and injected text, ignored key sources, hostile DNS answers
|
|
420
|
-
and deeply nested JSON
|
|
519
|
+
and deeply nested JSON;
|
|
520
|
+
- the defects of the 2026-10-20 walk-throughs (`tests/test_card_currency.py`,
|
|
521
|
+
`tests/test_quorum_one.py`): a pending successor card stored and then taken
|
|
522
|
+
as supersession, a new visitor refused after a successor's `not_before`, a
|
|
523
|
+
card URL replaced by the host's well-known card, a policy listing that omits
|
|
524
|
+
or misstates a leaf, and a quorum-1 card whose checkpoint is fetched before
|
|
525
|
+
its witness cosigns it;
|
|
526
|
+
- the review of those fixes (`tests/test_card_evidence.py`): exit 2 only for
|
|
527
|
+
a card the origin serves as current, and not within 30 seconds of a switch
|
|
528
|
+
or an expiry; historical, altered or expired documents from `--file`,
|
|
529
|
+
`/v2/policies/<index>` and `/v2/bodies/<hash>`; the log's order over
|
|
530
|
+
`issued_at`; a downgrade whose notice cannot be checked; a listing without
|
|
531
|
+
card rows; a bootstrap hash that is not the first card leaf; a card leaf
|
|
532
|
+
waiting for its `effective_after`; a malformed listing;
|
|
533
|
+
- the second review round: a downgrade judged while the policy leaves cannot
|
|
534
|
+
be read, and a retired witness (incomplete, not evidence); saved cards
|
|
535
|
+
whose `issued_at` does not follow the log's order while the leaves cannot
|
|
536
|
+
be read; a card that expired seconds ago in `policy init`; a card read back
|
|
537
|
+
from the store; a policy leaf the listing leaves out after its last row.
|