@zanii/custody-verdicts 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,159 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or Derivative
95
+ Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work; and
103
+
104
+ (d) If the Work includes a "NOTICE" text file as part of its
105
+ distribution, then any Derivative Works that You distribute must
106
+ include a readable copy of the attribution notices contained
107
+ within such NOTICE file.
108
+
109
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
110
+ any Contribution intentionally submitted for inclusion in the Work
111
+ by You to the Licensor shall be under the terms and conditions of
112
+ this License, without any additional terms or conditions.
113
+
114
+ 6. Trademarks. This License does not grant permission to use the trade
115
+ names, trademarks, service marks, or product names of the Licensor,
116
+ except as required for reasonable and customary use in describing the
117
+ origin of the Work and reproducing the content of the NOTICE file.
118
+
119
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
120
+ to in writing, Licensor provides the Work (and each Contributor
121
+ provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES
122
+ OR CONDITIONS OF ANY KIND, either express or implied, including,
123
+ without limitation, any warranties or conditions of TITLE,
124
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
125
+
126
+ 8. Limitation of Liability. In no event and under no legal theory,
127
+ whether in tort (including negligence), contract, or otherwise,
128
+ unless required by applicable law (such as deliberate and grossly
129
+ negligent acts) or agreed to in writing, shall any Contributor be
130
+ liable to You for damages, including any direct, indirect, special,
131
+ incidental, or consequential damages of any character arising as a
132
+ result of this License or out of the use or inability to use the
133
+ Work.
134
+
135
+ 9. Accepting Warranty or Additional Liability. While redistributing
136
+ the Work or Derivative Works thereof, You may choose to offer,
137
+ and charge a fee for, acceptance of support, warranty, indemnity,
138
+ or other liability obligations and/or rights consistent with this
139
+ License. However, in accepting such obligations, You may act only
140
+ on Your own behalf and on Your sole responsibility, not on behalf
141
+ of any other Contributor, and only if You agree to indemnify,
142
+ defend, and hold each Contributor harmless for any liability
143
+ incurred by, or claims asserted against, such Contributor.
144
+
145
+ END OF TERMS AND CONDITIONS
146
+
147
+ Copyright 2026 Zanii
148
+
149
+ Licensed under the Apache License, Version 2.0 (the "License");
150
+ you may not use this file except in compliance with the License.
151
+ You may obtain a copy of the License at
152
+
153
+ http://www.apache.org/licenses/LICENSE-2.0
154
+
155
+ Unless required by applicable law or agreed to in writing, software
156
+ distributed under the License is distributed on an "AS IS" BASIS,
157
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
158
+ See the License for the specific language governing permissions and
159
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # @zanii/custody-verdicts
2
+
3
+ **The one verifier.** Zanii Custody's hard rule is *one verifier, two surfaces*: the
4
+ subject page (zanii-id) and `@zanii/cli` import this same package and call the same
5
+ function. Never two implementations of the verdict logic: two drift, and the drift
6
+ always favours the vendor.
7
+
8
+ Verdicts are **computed, never stored**. `computeVerdicts` derives eight verdict rows
9
+ at view time, in the reader's browser or the auditor's terminal, from the evidence
10
+ itself: a `SubjectHistory` from `@zanii/subject` whose receipts were already verified
11
+ offline (signature + delegation + scope), plus an optional evidence bag.
12
+
13
+ ```ts
14
+ import { computeVerdicts, computeAnchorLag, TRAINING_MANIFEST_TAG } from '@zanii/custody-verdicts';
15
+
16
+ const rows = await computeVerdicts(history); // history = await fetchSubjectHistory(tag)
17
+ const lag = computeAnchorLag(history.sth, new Date()); // seconds; null when unknown
18
+ ```
19
+
20
+ `TRAINING_MANIFEST_TAG` (`"zanii:training-manifests"`) is the well-known ledger tag
21
+ under which declared training manifests are published. The page and the CLI fetch that
22
+ slice to build the row-4 training evidence bag (`ExtraEvidence.training`).
23
+
24
+ `computeVerdicts` is async for one reason: rows 3 and 6 check claims through injected
25
+ verifiers (`@zanii/attest`'s quote verifier, `@zanii/residency`'s region verifier)
26
+ that may be async. Still pure: no network, no clock.
27
+
28
+ ## The eight rows
29
+
30
+ Always in this order; each `{ id, label, state, detail }`, with `detail` the
31
+ plain-language working the page renders verbatim:
32
+
33
+ | id | label | `proven` when | `failed` when | otherwise |
34
+ |---|---|---|---|---|
35
+ | `purpose` | Used only for its declared purpose | history verified AND every embedded payload is a well-formed custody touch (`verifyTouch`), retention shred (`verifyKeyDestruction`), or one of the rows 6-8 evidence payloads (structurally checked) | any offline check failed or any payload malformed | empty slice → `cannot determine` |
36
+ | `self_sovereign` | You can check this yourself | every receipt verified offline AND the `sth` verifies | any offline check fails; bad sth | empty slice / no sth → `cannot determine` |
37
+ | `human_read` | Nobody read it | a SIGNED coverage attestation (sig + trusted signer when pinned + `attestationHash` binding + enclave quote via injected verifier) proves all egress paths receipt-gated for the whole window | any receipt shows a human actor; the attestation is unsigned, badly signed, from an unrecognized signer, unbound, malformed, or its quote is disproven | `not covered`: "No coverage attestation for this window"; no quote verifier injected → `cannot determine` |
38
+ | `trained_on` | Never used to train a model | `@zanii/train`'s `verifyAbsence` passes for this subject's tag against a declared manifest anchored BEFORE the run attestation | the proof does not verify, names another ref, or the manifest is malformed | no declared run → `not covered`: "No declared training runs to check against"; wrong anchor order / missing times → `cannot determine` |
39
+ | `copy_kept` | No copy was kept | every object touched in the slice has a shred receipt bound to its ref (payload `ref` = the touch's ref) that passes its own offline check AND `verifyKeyDestruction`, and any image evidence matches | a presented shred receipt fails either check; image evidence mismatches or is incomplete | none bound to a touched object's ref → `not covered`: "No key-destruction record on file."; some objects covered, some not → `cannot determine` |
40
+ | `residency` | Data stayed in its declared region | EVERY custody touch has a verified in-set claim (a claim with a `ref` binds every touch of that object; a ref-less claim covers the touches at its own timestamp), every region signal authenticates (injected `verifyRegion`), and `detectCrossings` finds no exit | a verified claim's region is outside the allow-set (a border crossing), or a claim is malformed, invalid, or rides on an unverified receipt | no claims → `not covered`: "No residency attestations on record" (self-declared claims are not attestations and say so); empty slice, any touch without a claim, no `jurisdiction` config, or no injected `verifyRegion` → `cannot determine` |
41
+ | `subprocessor` | Only declared third parties touched it | every custody-touch junction and recorded transfer resolves to a declared party under a named agreement (`verifyTransfers` against the reader-pinned list, plus a junction/transfer divergence check) | any `undeclared` receiver, any transfer without an agreement id, any malformed record, or a junction naming a destination with no matching declared transfer | none recorded → `not covered`: "No onward transfers declared or recorded"; destinations on record but no reader-provided `declaredSubprocessors` → `cannot determine` |
42
+ | `minimization` | Only what was needed was taken | every recorded access proof verifies (`verifyMinimization`) against its committed minimum field set, and every commitment record recomputes to its recorded commitment (`verifyFieldSet`) | any `excess` access (a field outside the declared minimum), a widened-after-commitment set, or a malformed record | no commitments → `not covered`: "No minimum-field commitments on record"; empty slice, commitments without access proofs, access proofs without commitments, or an access proof against an unknown commitment → `cannot determine` |
43
+
44
+ Four states: `proven`, `not covered`, `failed`, `cannot determine`. The last three are
45
+ as first-class as the first: a missing proof is not a clean result, and nothing here
46
+ ever renders "never" out of a database column.
47
+
48
+ On an **empty slice** (zero receipts) rows 1-2 answer `cannot determine` and rows 3-5
49
+ stay grey regardless of evidence: there is nothing to cover. Row 3 with a coverage
50
+ attestation over an empty slice answers `cannot determine` (coverage over nothing
51
+ proves nothing about this subject's data); row 4 answers `not covered`; row 5's
52
+ evidence lives in the slice, so an empty slice has none. Rows 6 and 8 answer
53
+ `cannot determine` on an empty slice (there is no window to cover); row 7's evidence
54
+ lives in the slice, so an empty slice answers `not covered`.
55
+
56
+ ## Evidence shapes
57
+
58
+ `ExtraEvidence` (`{ coverage?, training?, destructionImage?, jurisdiction?,
59
+ declaredSubprocessors? }`) carries the evidence and reader-side trust roots that do
60
+ not live in the slice; everything is checked offline, here, in this one place,
61
+ against the real producers' shapes:
62
+
63
+ - **`training`** (row 4) is `{ manifest, anchoredAt, runAttestedAt, absenceProof }`:
64
+ `@zanii/train`'s `TrainingManifest` and `AbsenceProof` (sorted-Merkle over
65
+ covering-gap pair leaves, reusing core's RFC 6962 inclusion proofs). The manifest
66
+ anchor must precede the run attestation, or the ordering cannot be trusted.
67
+ - **`destructionImage`** (row 5, optional) is `{ attestation, publishedImageHash }`: a
68
+ claimed destruction-image attestation plus the image hash the operator published.
69
+ What is checked here is **bag-internal consistency only**, via `@zanii/attest`'s
70
+ `attestationHash` convention (the published image hash is `attestationHash` of the
71
+ published attestation record): a mismatch is `failed`, a match appends the
72
+ attested-image line to the proven detail. A match does NOT by itself prove the
73
+ destruction ran under that image: nothing yet binds the shred receipt to the
74
+ attestation, and the attestation's quote is not verified on this path. The full
75
+ binding ships with the enclave phase. The destruction record itself needs no bag
76
+ entry: it is the `@zanii/retention` shred receipt carried in the subject's slice
77
+ (signature-covered embedded payload, target `data.retention.shred`), bound to the
78
+ subject by slice membership and to the OBJECT by ref: a shred counts only for an
79
+ object whose custody touches carry the same payload `ref`, and `proven` requires a
80
+ verified bound shred for every object the slice touches (partial coverage answers
81
+ `cannot determine`; a shred bound only to unrelated refs answers `not covered`).
82
+ Its salted `subject_commitment` can later be proven to name the subject by
83
+ revealing the salt.
84
+ - **`coverage`** (row 3) is a signed coverage attestation
85
+ (`{ v: 1, type: 'coverage.attestation', allPathsGated, windowStart, windowEnd,
86
+ imageHash, attestation, signer, sig, trustedSigners? }`): every egress path
87
+ receipt-gated (the `@zanii/broker` invariant) for a window, bound to an attested
88
+ image hash, signed by the platform key. Verified fail-closed: signature and (when
89
+ pinned) trusted signer, then the `attestationHash` binding, then
90
+ `verifyAttestation` with the caller's injected quote verifier
91
+ (pass it as `computeVerdicts(history, evidence, { verifyQuote })`). Without an
92
+ injected verifier the quote stays unknown and the row can only reach
93
+ `cannot determine`, never `proven`.
94
+ - **`jurisdiction`** (row 6) is `string[]`: the declared jurisdiction as an allow-set
95
+ of region codes (`@zanii/residency`'s `allowedRegions`). Reader-side configuration,
96
+ never producer-supplied (same trust rule as `coverage.trustedSigners`).
97
+ - **`declaredSubprocessors`** (row 7) is `string[]`: the declared subprocessor list
98
+ that onward transfers and custody junctions are checked against. Reader-side
99
+ configuration, never producer-supplied.
100
+
101
+ ## Row 6: "Data stayed in its declared region"
102
+
103
+ Mechanism: residency claim payloads in the slice (`@zanii/residency`'s
104
+ `buildResidencyClaim` shape, embedded signature-covered exactly like the row-5 shred
105
+ payloads) are checked with `verifyResidency` against the reader-pinned
106
+ `jurisdiction` allow-set and the caller's injected `verifyRegion` (pass it as
107
+ `computeVerdicts(history, evidence, { verifyRegion })`), which authenticates the
108
+ region signal (a cloud provider identity document, a TEE geo signal) and returns the
109
+ trusted region. `detectCrossings` then runs over the verified touch sequence: any
110
+ exit from the allow-set is a border crossing and a red, never silent.
111
+
112
+ `proven` requires per-touch coverage, never a window span: EVERY custody touch in the
113
+ slice must be covered by a verified in-set claim. A claim carrying a `ref` binds to
114
+ every touch of that object; a ref-less claim is time-scoped and covers the touches
115
+ made at its own timestamp. Every region signal must authenticate inside the
116
+ allow-set, no claim may be malformed or unverified, and no crossings may be
117
+ detected. A claim whose region source is self-declared (the operator asserted its
118
+ own region) is not an attestation: it is not counted as evidence, and when every
119
+ claim is self-declared the row answers `not covered` and says so.
120
+
121
+ The limit: the proof is only as strong as the injected geo trust root. A proven row
122
+ means "signals your verifier trusts placed every touch inside your declared
123
+ jurisdiction"; it cannot prove residency your geo source cannot. A touch with no
124
+ claim answers `cannot determine`, never `proven`.
125
+
126
+ ## Row 7: "Only declared third parties touched it"
127
+
128
+ Mechanism: transfer records in the slice (`@zanii/subprocessor`'s `buildTransfer`
129
+ shape) are checked with `verifyTransfers` against the reader-pinned
130
+ `declaredSubprocessors` list, and every custody-touch junction
131
+ (`@zanii/data-custody`'s `junction` field) must name a destination with a matching
132
+ declared transfer: a divergence between the custody trail and the transfer record is
133
+ a red, never silent.
134
+
135
+ `proven` requires: every recorded transfer's receiver is declared, every transfer
136
+ carries an agreement id (stricter than the producer, which requires an id only for
137
+ dpa/scc), no record is malformed, and every junction resolves to a declared party
138
+ under a named agreement.
139
+
140
+ The limit: this proves the transfers that were recorded. An off-record leak (a
141
+ processor that received plaintext and emitted no transfer receipt) cannot be caught
142
+ here; that residual is the enclave problem, unchanged. And an agreement id is a
143
+ reference, not a guarantee the counterparty honored it. Senders are not checked:
144
+ `verifyTransfers` runs without a controller, so an undeclared `from` is
145
+ indistinguishable from the origin and goes unchecked.
146
+
147
+ ## Row 8: "Only what was needed was taken"
148
+
149
+ Mechanism: field-set commitment records in the slice (`@zanii/minimization`'s
150
+ `commitFieldSet` output carried as a `minimization.commit` payload) are recomputed
151
+ with `verifyFieldSet` (a mismatch means the declared minimum was widened after the
152
+ commitment was recorded), and every access proof (`buildAccessProof` shape) is
153
+ checked with `verifyMinimization` against its committed field set: any accessed
154
+ field outside the declared minimum is a flagged excess, a red.
155
+
156
+ `proven` requires: every commitment record recomputes to its recorded commitment and
157
+ every recorded access stayed inside its committed minimum. Commitments without
158
+ access proofs, access proofs without commitments, or an access proof whose
159
+ commitment is not on record answer `cannot determine`: an unchecked access is
160
+ unknown, not clean.
161
+
162
+ The limit: this proves declared-vs-accessed: that the agent stayed inside the
163
+ minimum that was committed. It does not prove the minimum was well-chosen (a
164
+ judgement, not a cryptographic fact), and it proves the recorded accesses: an
165
+ off-record read is the enclave problem, unchanged.
166
+
167
+ ## Trust roots: who may attest coverage
168
+
169
+ The same reader-side rule now guards three trust roots: `coverage.trustedSigners`
170
+ (row 3), `jurisdiction` (row 6), and `declaredSubprocessors` (row 7). All three are
171
+ reader-side configuration, merged into the evidence by the subject page or the CLI
172
+ from its OWN deployment config, never from producer-supplied data: an attacker who
173
+ could name their own signer, their own jurisdiction, or their own subprocessor list
174
+ would attest anything.
175
+
176
+ A coverage attestation is only as good as the key that signs it. The producer side
177
+ (enclave phase) signs with the platform's coverage-signing key; the reader side, the
178
+ subject page and the CLI, pins the platform's coverage-signing DID(s) and merges them
179
+ into the evidence as `coverage.trustedSigners` before calling `computeVerdicts`. Two
180
+ rules make this a real trust root rather than a formality:
181
+
182
+ - **The list is reader-side configuration.** It must never come from
183
+ producer-supplied data: an attacker who could name their own key trusted would
184
+ attest anything. For the same reason `trustedSigners` is NOT part of the signed
185
+ record; it is excluded from the signature preimage and merged in after signing.
186
+ - **An unknown signer is never `proven`.** When `trustedSigners` is present, a
187
+ coverage attestation signed by any other DID is `failed` ("coverage attestation
188
+ from an unrecognized signer"); an empty list trusts no one. When it is absent (day
189
+ one), any well-formed did:key signer authenticates, and the remaining checks
190
+ decide. Deployments should always pin once the enclave phase ships.
191
+
192
+ **Row 3's producer side ships with the enclave phase.** `@zanii/attest` +
193
+ `@zanii/broker` produce signed coverage attestations there. Until then the evidence
194
+ does not exist, the row answers `not covered`, and `proven` is unreachable: that is
195
+ the design, not a gap. A "nobody read it" claim without hardware attestation would be
196
+ our word, not evidence.
197
+
198
+ ## Guarantees
199
+
200
+ - Pure: no network, no clock (`computeAnchorLag` takes `now` from the caller). Async
201
+ only for the injected verifiers (enclave quote, region signal).
202
+ - Never throws: malformed input yields honest `cannot determine` / `not covered` /
203
+ `failed` rows with reasons (verification paths return results; creation paths throw).
204
+ - No verdict is ever stored or served; the ledger serves raw evidence only.
@@ -0,0 +1,160 @@
1
+ import { type SignedTreeHead } from '@zanii/core';
2
+ import { type AbsenceProof, type TrainingManifest } from '@zanii/train';
3
+ import { type Attestation, type QuoteVerifier } from '@zanii/attest';
4
+ import { type RegionVerifier } from '@zanii/residency';
5
+ import type { SubjectHistory } from '@zanii/subject';
6
+ export type { AbsenceProof, TrainingManifest } from '@zanii/train';
7
+ export type { Attestation, QuoteVerifier } from '@zanii/attest';
8
+ export type { RegionVerifier, ResidencyClaim } from '@zanii/residency';
9
+ export type { Agreement, Transfer } from '@zanii/subprocessor';
10
+ export type { AccessProof } from '@zanii/minimization';
11
+ /**
12
+ * The well-known ledger tag under which declared training manifests are published.
13
+ * The subject page and the CLI fetch that slice to build the row-4 training evidence
14
+ * bag (`ExtraEvidence.training`).
15
+ */
16
+ export declare const TRAINING_MANIFEST_TAG = "zanii:training-manifests";
17
+ /** The four verdict states. The last three are as loud as the first. */
18
+ export type VerdictState = 'proven' | 'not covered' | 'failed' | 'cannot determine';
19
+ export type VerdictId = 'purpose' | 'self_sovereign' | 'human_read' | 'trained_on' | 'copy_kept' | 'residency' | 'subprocessor' | 'minimization';
20
+ export interface VerdictRow {
21
+ id: VerdictId;
22
+ label: string;
23
+ state: VerdictState;
24
+ /** Plain-language working: which receipts were computed over, why this state. Rendered verbatim. */
25
+ detail: string;
26
+ }
27
+ /**
28
+ * Coverage attestation for the "nobody read it" line: a SIGNED record asserting that
29
+ * every egress path to the data was receipt-gated (the `@zanii/broker` invariant) for
30
+ * the whole window, bound to the attested image whose code enforced it. The signature
31
+ * covers every field but `sig`; `imageHash` must equal `attestationHash(attestation)`
32
+ * (`@zanii/attest`'s convention), and the embedded attestation is verified through
33
+ * `verifyAttestation` with the caller's injected quote verifier. Anything unsigned or
34
+ * malformed is never `proven`. The producer side ships with the enclave phase
35
+ * (`@zanii/attest` + `@zanii/broker`); until then this evidence does not exist and the
36
+ * row answers `not covered`, by design.
37
+ */
38
+ export interface CoverageEvidence {
39
+ v: 1;
40
+ type: 'coverage.attestation';
41
+ /** Every egress path was receipt-gated; partial coverage is no coverage. */
42
+ allPathsGated: boolean;
43
+ /** ISO timestamp: start of the gated window. */
44
+ windowStart: string;
45
+ /** ISO timestamp: end of the gated window. */
46
+ windowEnd: string;
47
+ /** `attestationHash` of the embedded attestation: the image the claim is bound to. */
48
+ imageHash: string;
49
+ /** The full attestation record (`@zanii/attest`'s `Attestation`). */
50
+ attestation: Attestation;
51
+ /** did:key of the platform key that signed this attestation. */
52
+ signer: string;
53
+ /** `ed25519:<hex>` over the canonical record minus `sig`. */
54
+ sig: string;
55
+ /**
56
+ * Optional trust root: the deployment's pinned coverage-signing DIDs. The reader
57
+ * (subject page / CLI) sets this from its OWN configuration, never from
58
+ * producer-supplied data; otherwise an attacker could attest with their own key and
59
+ * name it trusted. Not part of the signed record (excluded from the signature
60
+ * preimage). When present, a signer not in the list is `failed`; an empty list
61
+ * trusts no one. When absent, any well-formed did:key signer is accepted.
62
+ */
63
+ trustedSigners?: string[];
64
+ }
65
+ /**
66
+ * A declared, anchored training run plus `@zanii/train`'s absence proof. Ordering is
67
+ * the whole claim: the manifest anchor must PRECEDE the run attestation. A manifest
68
+ * anchored afterwards could have been written to exclude the subject.
69
+ */
70
+ export interface TrainingEvidence {
71
+ /** The run's sorted-Merkle training manifest (`@zanii/train`'s `TrainingManifest`). */
72
+ manifest: TrainingManifest;
73
+ /** ISO timestamp when the manifest root was anchored. */
74
+ anchoredAt: string;
75
+ /** ISO timestamp of the run attestation. */
76
+ runAttestedAt: string;
77
+ /** `@zanii/train`'s `AbsenceProof` for this subject's tag, verified against the manifest. */
78
+ absenceProof: AbsenceProof;
79
+ }
80
+ /**
81
+ * Optional image evidence for the "no copy was kept" line: a CLAIMED destruction-image
82
+ * attestation plus the image hash the operator published for it. What is checked here
83
+ * is bag-internal consistency only, via `@zanii/attest`'s `attestationHash` convention
84
+ * (the published image hash is `attestationHash` of the published attestation record):
85
+ * a mismatch is `failed`. A match does NOT by itself prove the destruction ran under
86
+ * that image: nothing yet binds the shred receipt to the attestation, and the
87
+ * attestation's quote is not verified on this path. Full binding ships with the
88
+ * enclave phase.
89
+ */
90
+ export interface DestructionImageEvidence {
91
+ /** The attestation (`@zanii/attest`'s `Attestation`) claimed for the destruction image. */
92
+ attestation: Attestation;
93
+ /** The published image hash: `attestationHash` of the published attestation record. */
94
+ publishedImageHash: string;
95
+ }
96
+ /**
97
+ * Optional evidence bag for rows 3–5. Row 5 needs no bag entry for the destruction
98
+ * record itself: the shred receipts are read from the subject's slice, each already
99
+ * signature-covered and offline-verified. `destructionImage` only checks a claimed
100
+ * destruction-image attestation against the published image hash.
101
+ */
102
+ export interface ExtraEvidence {
103
+ coverage?: CoverageEvidence;
104
+ training?: TrainingEvidence;
105
+ destructionImage?: DestructionImageEvidence;
106
+ /**
107
+ * Row 6 trust root: the declared jurisdiction as an allow-set of region codes
108
+ * (`@zanii/residency`'s `allowedRegions`, e.g. `['AE', 'me-central-1']`). READER
109
+ * configuration (deployment-side), never producer-supplied: the same trust rule as
110
+ * `coverage.trustedSigners`, or a producer could declare the whole world in bounds.
111
+ * When absent, residency claims can reach `cannot determine`, never `proven`. An
112
+ * empty array allows no region: every verified claim is a crossing.
113
+ */
114
+ jurisdiction?: string[];
115
+ /**
116
+ * Row 7 trust root: the declared subprocessor list (the parties onward transfers
117
+ * may name). READER configuration, never producer-supplied, same trust rule as
118
+ * `coverage.trustedSigners`. When absent, transfers or junctions on record reach
119
+ * `cannot determine`, never `proven`. An empty array declares no one.
120
+ */
121
+ declaredSubprocessors?: string[];
122
+ }
123
+ /** Caller-injected verifiers: the enclave quote verifier for row 3, the region
124
+ * signal verifier for row 6. */
125
+ export interface VerdictOptions {
126
+ /**
127
+ * Platform-specific enclave quote verifier (`@zanii/attest`'s `QuoteVerifier`).
128
+ * Without it a signed coverage attestation can reach `cannot determine`, never
129
+ * `proven`: the quote stays unknown, and unknown is not verified.
130
+ */
131
+ verifyQuote?: QuoteVerifier;
132
+ /**
133
+ * Region signal verifier (`@zanii/residency`'s `RegionVerifier`): authenticates a
134
+ * claim's region attestation (a cloud provider identity document, a TEE geo
135
+ * signal) and returns the trusted region. Without it residency claims can reach
136
+ * `cannot determine`, never `proven`.
137
+ */
138
+ verifyRegion?: RegionVerifier;
139
+ }
140
+ /**
141
+ * Compute the eight verdict rows from an offline-verified subject history. Pure: no
142
+ * network, no clock. Async only because row 3's coverage attestation is checked
143
+ * through `@zanii/attest`'s `verifyAttestation` and row 6's residency claims through
144
+ * `@zanii/residency`'s `verifyResidency`, whose injected verifiers may be async;
145
+ * pass them via `options.verifyQuote` / `options.verifyRegion`. Never throws:
146
+ * malformed input yields honest `cannot determine` / `not covered` / `failed` rows
147
+ * with reasons. Rows are always returned in this fixed order: purpose,
148
+ * self_sovereign, human_read, trained_on, copy_kept, residency, subprocessor,
149
+ * minimization.
150
+ */
151
+ export declare function computeVerdicts(history: SubjectHistory, evidence?: ExtraEvidence, options?: VerdictOptions): Promise<VerdictRow[]>;
152
+ /**
153
+ * Seconds between the signed tree head's timestamp and `now` (the caller supplies
154
+ * `now`; this package has no clock). The page shows anchor lag: four minutes vs
155
+ * four hours is a property the reader is entitled to see. Null when unknown (no sth,
156
+ * no timestamp, unparseable). Negative means the sth is newer than the caller's
157
+ * clock: possible skew, shown as-is.
158
+ */
159
+ export declare function computeAnchorLag(sth: Record<string, unknown> | SignedTreeHead | undefined | null, now: Date | string | number): number | null;
160
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}