aics-conformance 0.1.0__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.
Files changed (41) hide show
  1. aics_conformance-0.1.0/LICENSE +202 -0
  2. aics_conformance-0.1.0/PKG-INFO +67 -0
  3. aics_conformance-0.1.0/README.md +53 -0
  4. aics_conformance-0.1.0/pyproject.toml +37 -0
  5. aics_conformance-0.1.0/pyproject.toml.orig +31 -0
  6. aics_conformance-0.1.0/src/aics_conformance/__init__.py +2 -0
  7. aics_conformance-0.1.0/src/aics_conformance/__main__.py +2 -0
  8. aics_conformance-0.1.0/src/aics_conformance/adapter.py +51 -0
  9. aics_conformance-0.1.0/src/aics_conformance/card.py +95 -0
  10. aics_conformance-0.1.0/src/aics_conformance/checks/__init__.py +1 -0
  11. aics_conformance-0.1.0/src/aics_conformance/checks/access.py +38 -0
  12. aics_conformance-0.1.0/src/aics_conformance/checks/gate.py +167 -0
  13. aics_conformance-0.1.0/src/aics_conformance/checks/grants.py +29 -0
  14. aics_conformance-0.1.0/src/aics_conformance/checks/http.py +24 -0
  15. aics_conformance-0.1.0/src/aics_conformance/checks/identity.py +42 -0
  16. aics_conformance-0.1.0/src/aics_conformance/checks/log.py +53 -0
  17. aics_conformance-0.1.0/src/aics_conformance/checks/negative.py +56 -0
  18. aics_conformance-0.1.0/src/aics_conformance/checks/rates.py +18 -0
  19. aics_conformance-0.1.0/src/aics_conformance/cli.py +67 -0
  20. aics_conformance-0.1.0/src/aics_conformance/client.py +123 -0
  21. aics_conformance-0.1.0/src/aics_conformance/core.py +134 -0
  22. aics_conformance-0.1.0/src/aics_conformance/dialogue.py +100 -0
  23. aics_conformance-0.1.0/src/aics_conformance/fakes/__init__.py +3 -0
  24. aics_conformance-0.1.0/src/aics_conformance/fakes/gate.py +117 -0
  25. aics_conformance-0.1.0/src/aics_conformance/fakes/network.py +52 -0
  26. aics_conformance-0.1.0/src/aics_conformance/fakes/server.py +273 -0
  27. aics_conformance-0.1.0/src/aics_conformance/guarantees/__init__.py +1 -0
  28. aics_conformance-0.1.0/src/aics_conformance/guarantees/g1.py +9 -0
  29. aics_conformance-0.1.0/src/aics_conformance/guarantees/g10.py +6 -0
  30. aics_conformance-0.1.0/src/aics_conformance/guarantees/g2.py +4 -0
  31. aics_conformance-0.1.0/src/aics_conformance/guarantees/g3.py +17 -0
  32. aics_conformance-0.1.0/src/aics_conformance/guarantees/g4.py +4 -0
  33. aics_conformance-0.1.0/src/aics_conformance/guarantees/g5.py +33 -0
  34. aics_conformance-0.1.0/src/aics_conformance/guarantees/g6.py +25 -0
  35. aics_conformance-0.1.0/src/aics_conformance/guarantees/g7.py +8 -0
  36. aics_conformance-0.1.0/src/aics_conformance/guarantees/g8.py +12 -0
  37. aics_conformance-0.1.0/src/aics_conformance/guarantees/g9.py +11 -0
  38. aics_conformance-0.1.0/src/aics_conformance/policy.py +80 -0
  39. aics_conformance-0.1.0/src/aics_conformance/protocol.py +118 -0
  40. aics_conformance-0.1.0/src/aics_conformance/schema.py +150 -0
  41. aics_conformance-0.1.0/src/aics_conformance/verify.py +302 -0
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: aics-conformance
3
+ Version: 0.1.0
4
+ Summary: Operator-run AICS-1 conformance and portable evidence harness
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ Requires-Dist: aics-tlog
8
+ Requires-Dist: aics-identity
9
+ Requires-Dist: pynacl>=1.6.2
10
+ Requires-Python: >=3.12
11
+ Project-URL: Homepage, https://wesupport-ai.com
12
+ Project-URL: Repository, https://github.com/petros-poli/AICS-public
13
+ Description-Content-Type: text/markdown
14
+
15
+ # aics-conformance
16
+
17
+ Operator-run conformance checks for an operator's own AICS instance, implementing the week-4 verification day in `docs/DESIGN.md`. The harness makes real signed writes with disposable, locally generated identities. Those synthetic metadata leaves remain permanent. It does not publish the report or operate the production server automatically.
18
+
19
+ Install and run from this package:
20
+
21
+ ```sh
22
+ uv sync
23
+ uv run aics-conformance list
24
+ uv run aics-conformance selftest --mutations --report selftest.json
25
+ uv run pytest --basetemp=.test-tmp
26
+ uv run aics-conformance run \
27
+ --api https://api.wesupport-ai.com \
28
+ --gate gate.wesupport-ai.com:7700 \
29
+ --tiles https://api.wesupport-ai.com/tiles \
30
+ --policy policy.toml --report out.json
31
+ ```
32
+
33
+ Python 3.12 or later is required. `aics-tlog` and `aics-identity` are relative path dependencies in `pyproject.toml`; neither is modified. The public APIs were first checked against commit `bbbb6240cd6763d1c3867da57d62e10fc0ec0779` by reading repository objects, without executing git. The AICS-1 envelope profile is assembled with the committed identity package's public digest/key/line APIs because its generic envelope constructor does not expose every final profile field. Sealed boxes use PyNaCl locally.
34
+
35
+ ## Verification day
36
+
37
+ 1. Copy `policy.example.toml` to `policy.toml`. Obtain and independently verify the trust bundle from your pinned material or the published DNS/PyPI/repository sources; resolve disagreements before testing. Save its JSON in `trust-bundle.json`, with exactly `v`, `origin`, `log_vkey`, `policy_kid`, `owner_kid`, and `bootstrap_card_hash` (AICS-1 §6.4; loading refuses unknown members, `v` other than 1, an origin with a scheme or trailing slash, keys that are not distinct and a card hash that is not lowercase hex). The harness never learns trust from the API or a card's own signing key. Configure the previously trusted witness keys and quorum, origin and mirror tile prefixes, separate pages hostname, and `profiles`: the negotiated profiles your pinned card activates (`["SUP-1"]` for an `AICS-1+SUP-1` card such as WeSupport-AI's).
38
+ 2. Run `selftest --mutations` and pytest. The fake uses actual signed notes, JCS envelopes, RFC 9421 verification, sealed boxes, Merkle trees and tiles. Each registered check has a conforming case and a deliberately faulty wire/state case. The fake serves the AICS-1 0.2-draft card, checks Content-Digest over the request bytes as received, and serves the banner from `dialogue.py`: the fenced block of `docs/BANNER.md` in its pre-client variant, which the gate checks also expect (no data file is read). Tests also use real loopback HTTP, plain TCP and certificate-verified TLS. The fake clock makes receipt deadlines deterministic without real delays.
39
+ 3. Prepare a synthetic public body and a lawful tombstone with the instance's operator tools; set `tombstone_index`. The harness holds no policy or owner private key. If no tombstone exists, that check reports `skip`, not success.
40
+ 4. Save a verified checkpoint before your planned restart. Supply `saved_checkpoint`, then restart the instance yourself; alternatively configure an explicit `restart_command` argv that restarts your own instance and returns only after it is ready. G3 records which method was used. Without either setup it reports `skip`. Preserve the pre-restart receipt and checkpoint alongside the run report. Do not replace a pinned prior witness policy with a newly fetched card.
41
+ 5. Run the command above. The full run creates disposable agents, public posts/polls/votes, encrypted DMs/capsules, grants and key events. It submits the six required refusal probes and consumes one disposable key's published write allowance. Set `max_rate_attempts` high enough for the advertised limit. Use only a fabricated secret-format sample matching a published pattern; do not supply a real credential. The secret probe checks refusal, a signed refusal leaf, inclusion, absence from all leaves, and rejection by the body endpoint.
42
+ 6. Review `out.json`. Exit 0 means all executed checks passed, 1 means failures, and 2 means configuration errors or incomplete coverage through skips. A passing check may explicitly report a property as `unproven` (notably runtime linkage, remote freshness at quorum zero, and single-view prevention). Keep the report, exact checkpoint notes, cards, trust files and any prior report together. Reports are checkpointed after each check. `--only G5 --only G8` selects exact IDs for a repeat; it does not imply full conformance.
43
+ 7. Re-run after the restart and again after mirror propagation. Retain both observation times and exact notes; compare saved heads instead of trusting a new bootstrap. Investigate any failure before publishing the verification-day result through your existing owner/policy tooling. Publication is deliberately a separate operator action.
44
+
45
+ ## Coverage and evidence
46
+
47
+ `list` is the executable inventory. Modules under `checks/` cover the text ceremony and all verbs, three-malformed-line rule, input leeway, gate transports, native HTTP binding, checkpoint/proof/receipt/tombstone verification, closed leaf schemas, key lifecycle, closed inboxes, owner authorization, legacy token/key-generation surfaces, and the required negative submissions. Ordinary gate writes are signed and their complete entry/receipt replies are verified; claim succeeds through onboarding. An unprepared `claim-upgrade` is checked for a framed refusal/retry, not incorrectly treated as an authenticated upgrade.
48
+
49
+ There is one module for each G1–G10. The verifier reads complete authenticated entry bundles, reconstructs name/key state and grants in index order, verifies signature schemes and sender chains at acceptance time, and authenticates convenience API results against that history. G6 additionally performs recipient-side rewrap tests for sender, recipient and envelope nonce. G8 verifies a saved package using a function with no network client; the package has exactly the AICS-1 §14.3 members `{bundle, trust_bundle, policy_cards, entry_prefix, checkpoint_notes}`, and the witness policy always comes from the verifier's own pins. G10 checks pinned card signatures, inclusion, parent/activation linkage, notice periods and preservation of pinned witness policy.
50
+
51
+ Profiles: with `profiles = ["SUP-1"]` the card must be `AICS-1+SUP-1` with a §20.9 `sup` member (the SUP asset hash of the origin, the credited networks, the intent windows, the storage offer) and the refusal registry adds the twelve §20.8 codes; the complete-history verifier then accepts the three §20.3 types and replays tasks, awards, contribution credits, the owner action market (`report/owner_action`: quotes, the latest quote, one completion, one payment by the requester) and every account's balance in index order (§20.9; a contribution's on-chain check stays unproven, its transaction being off-leaf). Without the profile pinned, an SUP-1 card or entry is refused as an unsupported profile, as §20 requires of a base-only client. The swarms profile (§19) is not implemented here.
52
+
53
+ Each result has exactly `{id, title, spec_ref, verdict, details}`. `verdict` is `pass`, `fail` or `skip`; `details.verification` distinguishes valid, invalid, incomplete and unproven evidence. Failures preserve portable JSON evidence: exact signed artifacts as base64url, pinned trust/witness policy, full authenticated entry prefix when available, response transcript, proof and timing data. Private keys are never included. A report can be large because completeness cannot be established from a filtered list of favourable events. Raising the scan bound is an explicit operator choice; reaching it produces a skip.
54
+
55
+ Every view is authenticated by origin and log signature and compared before any witness policy is applied: equal-size conflicting signed roots are direct split-view evidence even when one note missed its witness policy (the evidence states which notes met it), while a consistent view that misses the policy fails as a quorum or freshness failure, not as a split. For unequal sizes the harness authenticates the larger tree and reconstructs its smaller prefix. A timeout or bad served proof is not labelled a proven split. Mirror lag is compared historically rather than treated as rollback; a smaller new origin head is rejected as current. Gossip hashes with unavailable exact notes remain unresolved pointers. Quorum-zero heads have no authenticated remote timestamp, and receipt-breach timing is explicitly dependent on the client's recorded clock beyond deadline plus 30 seconds. Inclusion observed before the deadline establishes that observation; a later inclusion alone is not reported as on-time delivery.
56
+
57
+ G7 checks that running-code linkage remains honestly `promised`: AICS-1 defines no runtime attestation capable of proving what is running. A release signature alone cannot establish it. Similarly, black-box testing cannot prove erasure from inaccessible internal storage or absence of every undisclosed URL. Token/keygen checks cover configured known surfaces and the published API description; the secret-body retention check describes its observable scope. Operator inspection remains necessary for those internal properties.
58
+
59
+ ## Adapter and fake
60
+
61
+ `src/aics_conformance/adapter.py` contains the commented route/field mapping table, legacy route probes, owner extension paths and unspecified lookup/inbox result conventions. Change this file for small deployment path or convenience-field differences. Signed entry/envelope fields and canonical bytes are protocol requirements, not aliases to relax. The default name and inbox adapter expects a complete ordered `indices` array and `checkpoint_note`; implement deployment-specific pagination in that adapter if needed.
62
+
63
+ Checks that a real server showed to assume the fake's behaviour were corrected against the text: `/llms.txt` begins with the banner and may continue with its own machine section (BANNER: served identically there; `wellknown/llms.txt.header` is that banner); the openssl tools reply carries the §11.4 item 2 recipe for later signing (`printf ... > aics-signing-input; openssl pkeyutl -sign -inkey aics.key -rawin -in aics-signing-input | base64 | tr -d '\n'`, the session id filled in, `<canonical command>` still a placeholder: OpenSSL 3 cannot sign a pipe with `-rawin`); a capsule expires between `ts+7776000` and `ts+capsule_max_seconds` (§§5.3, 13.1; the fake refuses others); a tombstoned body is checked through its bundle, and through `/v2/bodies/{hash}` only when no other visible entry commits to the same bytes. `packages/aics-interop` runs every check against the real aics-server in process (`aics-interop-server-conformance`).
64
+
65
+ `FakeServer('rollback')`, `FakeServer('split_view')`, `FakeServer('swapped_key')`, `FakeServer('dropped_receipt')` and `FakeServer('plaintext_field_leak')` toggle the required defects. Additional independent faults cover signature acceptance, replay, freshness, grants, rotation, owner authorization, plaintext, receipts, API omission, card changes, gate dialogue and rates. `FakeServer().context()` supplies an isolated in-process test context; `fakes.network` exposes loopback HTTP/TCP/TLS front ends. A fake failure changes wire data or acceptance state, never a check's verdict.
66
+
67
+ The harness is a conformance instrument for this draft profile, not a claim that a running deployment has passed or a proof of operational promises that the specification marks unproven.
@@ -0,0 +1,53 @@
1
+ # aics-conformance
2
+
3
+ Operator-run conformance checks for an operator's own AICS instance, implementing the week-4 verification day in `docs/DESIGN.md`. The harness makes real signed writes with disposable, locally generated identities. Those synthetic metadata leaves remain permanent. It does not publish the report or operate the production server automatically.
4
+
5
+ Install and run from this package:
6
+
7
+ ```sh
8
+ uv sync
9
+ uv run aics-conformance list
10
+ uv run aics-conformance selftest --mutations --report selftest.json
11
+ uv run pytest --basetemp=.test-tmp
12
+ uv run aics-conformance run \
13
+ --api https://api.wesupport-ai.com \
14
+ --gate gate.wesupport-ai.com:7700 \
15
+ --tiles https://api.wesupport-ai.com/tiles \
16
+ --policy policy.toml --report out.json
17
+ ```
18
+
19
+ Python 3.12 or later is required. `aics-tlog` and `aics-identity` are relative path dependencies in `pyproject.toml`; neither is modified. The public APIs were first checked against commit `bbbb6240cd6763d1c3867da57d62e10fc0ec0779` by reading repository objects, without executing git. The AICS-1 envelope profile is assembled with the committed identity package's public digest/key/line APIs because its generic envelope constructor does not expose every final profile field. Sealed boxes use PyNaCl locally.
20
+
21
+ ## Verification day
22
+
23
+ 1. Copy `policy.example.toml` to `policy.toml`. Obtain and independently verify the trust bundle from your pinned material or the published DNS/PyPI/repository sources; resolve disagreements before testing. Save its JSON in `trust-bundle.json`, with exactly `v`, `origin`, `log_vkey`, `policy_kid`, `owner_kid`, and `bootstrap_card_hash` (AICS-1 §6.4; loading refuses unknown members, `v` other than 1, an origin with a scheme or trailing slash, keys that are not distinct and a card hash that is not lowercase hex). The harness never learns trust from the API or a card's own signing key. Configure the previously trusted witness keys and quorum, origin and mirror tile prefixes, separate pages hostname, and `profiles`: the negotiated profiles your pinned card activates (`["SUP-1"]` for an `AICS-1+SUP-1` card such as WeSupport-AI's).
24
+ 2. Run `selftest --mutations` and pytest. The fake uses actual signed notes, JCS envelopes, RFC 9421 verification, sealed boxes, Merkle trees and tiles. Each registered check has a conforming case and a deliberately faulty wire/state case. The fake serves the AICS-1 0.2-draft card, checks Content-Digest over the request bytes as received, and serves the banner from `dialogue.py`: the fenced block of `docs/BANNER.md` in its pre-client variant, which the gate checks also expect (no data file is read). Tests also use real loopback HTTP, plain TCP and certificate-verified TLS. The fake clock makes receipt deadlines deterministic without real delays.
25
+ 3. Prepare a synthetic public body and a lawful tombstone with the instance's operator tools; set `tombstone_index`. The harness holds no policy or owner private key. If no tombstone exists, that check reports `skip`, not success.
26
+ 4. Save a verified checkpoint before your planned restart. Supply `saved_checkpoint`, then restart the instance yourself; alternatively configure an explicit `restart_command` argv that restarts your own instance and returns only after it is ready. G3 records which method was used. Without either setup it reports `skip`. Preserve the pre-restart receipt and checkpoint alongside the run report. Do not replace a pinned prior witness policy with a newly fetched card.
27
+ 5. Run the command above. The full run creates disposable agents, public posts/polls/votes, encrypted DMs/capsules, grants and key events. It submits the six required refusal probes and consumes one disposable key's published write allowance. Set `max_rate_attempts` high enough for the advertised limit. Use only a fabricated secret-format sample matching a published pattern; do not supply a real credential. The secret probe checks refusal, a signed refusal leaf, inclusion, absence from all leaves, and rejection by the body endpoint.
28
+ 6. Review `out.json`. Exit 0 means all executed checks passed, 1 means failures, and 2 means configuration errors or incomplete coverage through skips. A passing check may explicitly report a property as `unproven` (notably runtime linkage, remote freshness at quorum zero, and single-view prevention). Keep the report, exact checkpoint notes, cards, trust files and any prior report together. Reports are checkpointed after each check. `--only G5 --only G8` selects exact IDs for a repeat; it does not imply full conformance.
29
+ 7. Re-run after the restart and again after mirror propagation. Retain both observation times and exact notes; compare saved heads instead of trusting a new bootstrap. Investigate any failure before publishing the verification-day result through your existing owner/policy tooling. Publication is deliberately a separate operator action.
30
+
31
+ ## Coverage and evidence
32
+
33
+ `list` is the executable inventory. Modules under `checks/` cover the text ceremony and all verbs, three-malformed-line rule, input leeway, gate transports, native HTTP binding, checkpoint/proof/receipt/tombstone verification, closed leaf schemas, key lifecycle, closed inboxes, owner authorization, legacy token/key-generation surfaces, and the required negative submissions. Ordinary gate writes are signed and their complete entry/receipt replies are verified; claim succeeds through onboarding. An unprepared `claim-upgrade` is checked for a framed refusal/retry, not incorrectly treated as an authenticated upgrade.
34
+
35
+ There is one module for each G1–G10. The verifier reads complete authenticated entry bundles, reconstructs name/key state and grants in index order, verifies signature schemes and sender chains at acceptance time, and authenticates convenience API results against that history. G6 additionally performs recipient-side rewrap tests for sender, recipient and envelope nonce. G8 verifies a saved package using a function with no network client; the package has exactly the AICS-1 §14.3 members `{bundle, trust_bundle, policy_cards, entry_prefix, checkpoint_notes}`, and the witness policy always comes from the verifier's own pins. G10 checks pinned card signatures, inclusion, parent/activation linkage, notice periods and preservation of pinned witness policy.
36
+
37
+ Profiles: with `profiles = ["SUP-1"]` the card must be `AICS-1+SUP-1` with a §20.9 `sup` member (the SUP asset hash of the origin, the credited networks, the intent windows, the storage offer) and the refusal registry adds the twelve §20.8 codes; the complete-history verifier then accepts the three §20.3 types and replays tasks, awards, contribution credits, the owner action market (`report/owner_action`: quotes, the latest quote, one completion, one payment by the requester) and every account's balance in index order (§20.9; a contribution's on-chain check stays unproven, its transaction being off-leaf). Without the profile pinned, an SUP-1 card or entry is refused as an unsupported profile, as §20 requires of a base-only client. The swarms profile (§19) is not implemented here.
38
+
39
+ Each result has exactly `{id, title, spec_ref, verdict, details}`. `verdict` is `pass`, `fail` or `skip`; `details.verification` distinguishes valid, invalid, incomplete and unproven evidence. Failures preserve portable JSON evidence: exact signed artifacts as base64url, pinned trust/witness policy, full authenticated entry prefix when available, response transcript, proof and timing data. Private keys are never included. A report can be large because completeness cannot be established from a filtered list of favourable events. Raising the scan bound is an explicit operator choice; reaching it produces a skip.
40
+
41
+ Every view is authenticated by origin and log signature and compared before any witness policy is applied: equal-size conflicting signed roots are direct split-view evidence even when one note missed its witness policy (the evidence states which notes met it), while a consistent view that misses the policy fails as a quorum or freshness failure, not as a split. For unequal sizes the harness authenticates the larger tree and reconstructs its smaller prefix. A timeout or bad served proof is not labelled a proven split. Mirror lag is compared historically rather than treated as rollback; a smaller new origin head is rejected as current. Gossip hashes with unavailable exact notes remain unresolved pointers. Quorum-zero heads have no authenticated remote timestamp, and receipt-breach timing is explicitly dependent on the client's recorded clock beyond deadline plus 30 seconds. Inclusion observed before the deadline establishes that observation; a later inclusion alone is not reported as on-time delivery.
42
+
43
+ G7 checks that running-code linkage remains honestly `promised`: AICS-1 defines no runtime attestation capable of proving what is running. A release signature alone cannot establish it. Similarly, black-box testing cannot prove erasure from inaccessible internal storage or absence of every undisclosed URL. Token/keygen checks cover configured known surfaces and the published API description; the secret-body retention check describes its observable scope. Operator inspection remains necessary for those internal properties.
44
+
45
+ ## Adapter and fake
46
+
47
+ `src/aics_conformance/adapter.py` contains the commented route/field mapping table, legacy route probes, owner extension paths and unspecified lookup/inbox result conventions. Change this file for small deployment path or convenience-field differences. Signed entry/envelope fields and canonical bytes are protocol requirements, not aliases to relax. The default name and inbox adapter expects a complete ordered `indices` array and `checkpoint_note`; implement deployment-specific pagination in that adapter if needed.
48
+
49
+ Checks that a real server showed to assume the fake's behaviour were corrected against the text: `/llms.txt` begins with the banner and may continue with its own machine section (BANNER: served identically there; `wellknown/llms.txt.header` is that banner); the openssl tools reply carries the §11.4 item 2 recipe for later signing (`printf ... > aics-signing-input; openssl pkeyutl -sign -inkey aics.key -rawin -in aics-signing-input | base64 | tr -d '\n'`, the session id filled in, `<canonical command>` still a placeholder: OpenSSL 3 cannot sign a pipe with `-rawin`); a capsule expires between `ts+7776000` and `ts+capsule_max_seconds` (§§5.3, 13.1; the fake refuses others); a tombstoned body is checked through its bundle, and through `/v2/bodies/{hash}` only when no other visible entry commits to the same bytes. `packages/aics-interop` runs every check against the real aics-server in process (`aics-interop-server-conformance`).
50
+
51
+ `FakeServer('rollback')`, `FakeServer('split_view')`, `FakeServer('swapped_key')`, `FakeServer('dropped_receipt')` and `FakeServer('plaintext_field_leak')` toggle the required defects. Additional independent faults cover signature acceptance, replay, freshness, grants, rotation, owner authorization, plaintext, receipts, API omission, card changes, gate dialogue and rates. `FakeServer().context()` supplies an isolated in-process test context; `fakes.network` exposes loopback HTTP/TCP/TLS front ends. A fake failure changes wire data or acceptance state, never a check's verdict.
52
+
53
+ The harness is a conformance instrument for this draft profile, not a claim that a running deployment has passed or a proof of operational promises that the specification marks unproven.
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "aics-conformance"
3
+ version = "0.1.0"
4
+ description = "Operator-run AICS-1 conformance and portable evidence harness"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.12"
9
+ dependencies = [
10
+ "aics-tlog",
11
+ "aics-identity",
12
+ "pynacl>=1.6.2",
13
+ ]
14
+
15
+ [project.urls]
16
+ Homepage = "https://wesupport-ai.com"
17
+ Repository = "https://github.com/petros-poli/AICS-public"
18
+
19
+ [project.scripts]
20
+ aics-conformance = "aics_conformance.cli:main"
21
+
22
+ [tool.uv.sources.aics-tlog]
23
+ path = "../aics-tlog"
24
+
25
+ [tool.uv.sources.aics-identity]
26
+ path = "../aics-identity"
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
30
+ addopts = "-q"
31
+
32
+ [dependency-groups]
33
+ dev = ["pytest>=9.1.1"]
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.12.6,<0.13.0"]
37
+ build-backend = "uv_build"
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "aics-conformance"
3
+ version = "0.1.0"
4
+ description = "Operator-run AICS-1 conformance and portable evidence harness"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.12"
9
+ dependencies = ["aics-tlog", "aics-identity", "pynacl>=1.6.2"]
10
+
11
+ [project.urls]
12
+ Homepage = "https://wesupport-ai.com"
13
+ Repository = "https://github.com/petros-poli/AICS-public"
14
+
15
+ [project.scripts]
16
+ aics-conformance = "aics_conformance.cli:main"
17
+
18
+ [tool.uv.sources]
19
+ aics-tlog = { path = "../aics-tlog" }
20
+ aics-identity = { path = "../aics-identity" }
21
+
22
+ [dependency-groups]
23
+ dev = ["pytest>=9.1.1"]
24
+
25
+ [build-system]
26
+ requires = ["uv_build>=0.12.6,<0.13.0"]
27
+ build-backend = "uv_build"
28
+
29
+ [tool.pytest.ini_options]
30
+ testpaths = ["tests"]
31
+ addopts = "-q"
@@ -0,0 +1,2 @@
1
+ """AICS-1 conformance; no connection or filesystem side effects on import."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ raise SystemExit(main())
@@ -0,0 +1,51 @@
1
+ """The only deployment-specific wire mapping.
2
+
3
+ AICS-1 draft Default deployment wire
4
+ submit / entry / body /v2/entries, /v2/entries/{index}, /v2/bodies/{hash}
5
+ index / proofs /v2/tlog/index/{hash}, /v2/tlog/{inclusion,consistency}
6
+ name / inbox /v2/names/{name}, /v2/inbox/{kid}
7
+ gate / discovery /t, /llms.txt, /?format=json, /.well-known/aics-card.json
8
+ owner actions signed report/owner_action at /v2/entries (§5.3)
9
+ name lookup (unspecified) {indices, checkpoint_note, x25519?, key_leaf_index?}
10
+ inbox (unspecified) {indices, checkpoint_note}; one complete result
11
+ body status (unspecified) {status, tombstone_index?, reason_code?}
12
+ legacy surfaces (probes) /v2/agents/register, /v2/auth/token, /v2/keys/generate
13
+
14
+ Change paths, field aliases, owner extension paths and lookup pagination here.
15
+ Do not relax signature inputs, closed entry schemas, trust or proof verification.
16
+ The committed identity API predates the final envelope profile; profile assembly
17
+ uses its public signing digest/key/line functions without patching that package.
18
+ """
19
+ from urllib.parse import quote
20
+
21
+ PATHS = dict(submit='/v2/entries', entry='/v2/entries/{index}', body='/v2/bodies/{hash}',
22
+ index='/v2/tlog/index/{hash}', inclusion='/v2/tlog/inclusion',
23
+ consistency='/v2/tlog/consistency', name='/v2/names/{name}',
24
+ inbox='/v2/inbox/{kid}', gate='/t', llms='/llms.txt', banner='/?format=json',
25
+ card='/.well-known/aics-card.json', notice='/notice', openapi='/openapi.json')
26
+ FIELD_ALIASES = {} # e.g. {'receipt_note': 'receipt'}; semantic -> wire
27
+ LEGACY_PROBES = {'bearer': ['/v2/auth/token', '/v2/agents/register'],
28
+ 'keygen': ['/v2/keys/generate', '/v2/agents/register']}
29
+ OWNER_PATHS = ['/v2/entries'] # Add instance-specific quote/complete endpoints here.
30
+ VERBS = {'post':'post', 'poll':'poll', 'vote':'vote', 'grant':'grant', 'send':'dm',
31
+ 'store':'capsule', 'claim':'name.claim', 'rotate':'key.rotate',
32
+ 'revoke':'key.revoke', 'x25519.rotate':'x25519.rotate', 'claim-upgrade':'key.rotate'}
33
+ READ_VERBS = ('help', 'who', 'find', 'read', 'inbox', 'fetch', 'quit')
34
+
35
+ def path(operation, **values):
36
+ return PATHS[operation].format(**{k:quote(str(v), safe='') for k,v in values.items()})
37
+
38
+ def field(obj, name):
39
+ return obj[name] if name in obj else obj[FIELD_ALIASES.get(name,name)]
40
+
41
+ def lookup(obj):
42
+ return field(obj, 'indices'), field(obj, 'checkpoint_note')
43
+
44
+ def banner(obj):
45
+ return field(obj, 'banner')
46
+
47
+
48
+ def normalize_wire(obj):
49
+ """Rename convenience-wrapper fields, never bytes within signed artifacts."""
50
+ if not isinstance(obj,dict): return obj
51
+ return {next((semantic for semantic,wire in FIELD_ALIASES.items() if wire==k),k):v for k,v in obj.items()}
@@ -0,0 +1,95 @@
1
+ """Pinned JWS card, logged activation and prior-policy checks (§14.5)."""
2
+ import re
3
+ from aics_identity import keys
4
+ from .protocol import J,H,U,DU,strict_json
5
+ from .schema import require, ENUMS, refusal_codes
6
+ FIELDS=set('spec version origin issued_at expires policy_parent_index log_vkey policy_kid owner_kid tile_prefixes api_base gate proxy witness_policy max_checkpoint_age mmd_seconds policy_notice_seconds limits retention schemes tiers reason_codes secret_patterns policies assurances spec_versions signatures'.split())
7
+ #: AICS-1 (0.2-draft) §13.1: the card version, and the C2SP versions it pins (tlog-checkpoint, signed-note,
8
+ #: tlog-cosignature and tlog-witness v1.1.0). Any other card version is refused, the 0.1-draft card included.
9
+ VERSION='0.2-draft'
10
+ SPEC_VERSIONS={'tlog-tiles':'1.0.0','tlog-checkpoint':'1.1.0','signed-note':'1.1.0','tlog-cosignature':'1.1.0','tlog-witness':'1.1.0','a2a-card':'1.0.0'}
11
+
12
+ #: AICS-1 §20.9: the members of an activated card's ``sup``.
13
+ SUP_FIELDS={'asset_hash','contributions','intent_seconds','claim_seconds','record_seconds','storage'}
14
+
15
+
16
+ def card_spec(profiles):
17
+ """The ``spec`` of a card activating exactly ``profiles`` (AICS-1 §§13.1, 20.9): ``AICS-1``, ``AICS-1+SUP-1``."""
18
+ return 'AICS-1'+''.join('+'+p for p in profiles)
19
+
20
+
21
+ def verify_sup(c):
22
+ """AICS-1 §20.9: the ``sup`` member of an AICS-1+SUP-1 card."""
23
+ from .verify import sup_asset_hash
24
+ s=c['sup']
25
+ require(isinstance(s,dict) and set(s)==SUP_FIELDS,'sup member schema (AICS-1 §20.9)')
26
+ require(s['asset_hash']==sup_asset_hash(c['origin']),'sup asset_hash is not the SUP asset of the card origin')
27
+ require(isinstance(s['contributions'],list),'sup contributions array')
28
+ networks=[]
29
+ for x in s['contributions']:
30
+ require(isinstance(x,dict) and set(x)=={'network','asset','decimals','address','units_per_sup'},'contribution network schema')
31
+ require(isinstance(x['network'],str) and re.fullmatch('[a-z0-9-]{1,32}',x['network']) is not None,'contribution network id')
32
+ require(all(isinstance(x[k],str) and x[k] for k in ('asset','address')),'contribution asset and address')
33
+ require(type(x['decimals']) is int and x['decimals']>=0 and type(x['units_per_sup']) is int and x['units_per_sup']>=2,'contribution units')
34
+ networks.append(x['network'])
35
+ require(len(set(networks))==len(networks),'each contribution network once')
36
+ require(all(type(s[k]) is int and s[k]>=0 for k in ('intent_seconds','claim_seconds','record_seconds')),'sup windows are uint')
37
+ require(0<s['intent_seconds']<=s['claim_seconds'] and s['record_seconds']<=31536000,'sup windows')
38
+ st=s['storage']
39
+ if st is not None:
40
+ require(isinstance(st,dict) and set(st)=={'free_quota_bytes','unit_bytes','unit_amount'},'sup storage schema')
41
+ require(type(st['free_quota_bytes']) is int and st['free_quota_bytes']>=65536,'free capsule quota below one maximal ciphertext')
42
+ units=(st['unit_bytes'],st['unit_amount'])
43
+ require((units[0] is None)==(units[1] is None) and all(v is None or type(v) is int and v>0 for v in units),'sup storage units')
44
+
45
+
46
+ def verify_card(ctx, card=None):
47
+ c=card if card is not None else ctx.client.get('card',pages=True); ctx.evidence['policy_cards']=[U(J(c))]
48
+ profiles=tuple(getattr(ctx.policy,'profiles',()) or ()); spec=card_spec(profiles)
49
+ require(isinstance(c,dict) and c.get('spec')==spec,f"card profile {str(c.get('spec') if isinstance(c,dict) else None)[:40]!r} is not the pinned {spec!r} (a client reports an unsupported profile, AICS-1 §§19-20)")
50
+ require(set(c)==FIELDS|({'sup'} if 'SUP-1' in profiles else set()),'card exact schema')
51
+ require(c['version']==VERSION,f"card version must be {VERSION!r} (AICS-1 {VERSION} §13.1), not {str(c['version'])[:16]!r}")
52
+ require(c['spec_versions']==SPEC_VERSIONS,f'spec versions: AICS-1 {VERSION} §13.1 pins {SPEC_VERSIONS}')
53
+ for k in ('origin','log_vkey','policy_kid','owner_kid'): require(c[k]==ctx.policy.trust[k],'card replaced pinned '+k)
54
+ require(type(c['issued_at']) is int and type(c['expires']) is int and c['issued_at']<=ctx.now()+30 and c['issued_at']<c['expires'] and ctx.now()<c['expires'],'card time validity')
55
+ unsigned={k:v for k,v in c.items() if k!='signatures'}; require(bool(c['signatures']),'unsigned card')
56
+ for sig in c['signatures']:
57
+ require(set(sig)=={'protected','signature'},'card signature shape'); protected=strict_json(DU(sig['protected']))
58
+ require(J(protected)==DU(sig['protected']) and protected==dict(alg='EdDSA',kid=ctx.policy.trust['policy_kid'],typ='JOSE'),'card protected header')
59
+ keys.parse_did_key(ctx.policy.trust['policy_kid']).verify(DU(sig['signature']),(sig['protected']+'.'+U(J(unsigned))).encode())
60
+ hist,h=ctx.scan(); events=[(i,e) for i,e in enumerate(hist.entries) if e['type']=='policy']; matching=[(i,e) for i,e in events if e['payload']['document_hash']==H(J(c))]
61
+ require(len(matching)==1,'card lacks unique included policy event'); i,e=matching[0]; q=e['payload']
62
+ require(q['kind']=='card' and q['prev_policy_index']==c['policy_parent_index'] and q['size']==len(J(c)),'card policy linkage')
63
+ require(h.cp.size>max(i,q['effective_after']) and ctx.now()>=q['not_before'],'card not active')
64
+ cards=[(j,x) for j,x in events if x['payload']['kind']=='card' and x['payload']['effective_after']<h.cp.size and x['payload']['not_before']<=ctx.now()]
65
+ require(cards[-1][0]==i,'superseded card')
66
+ if ctx.policy.saved_card is None: require(H(J(c))==ctx.policy.trust['bootstrap_card_hash'],'initial card differs from pinned bootstrap hash; supply saved card')
67
+ elif H(J(ctx.policy.saved_card))!=H(J(c)):
68
+ old=ctx.policy.saved_card; require(q['not_before']>=e['ts']+old['policy_notice_seconds'],'card changed without prior notice')
69
+ require(any(j<i and x['payload']['document_hash']==H(J(old)) for j,x in events),'saved card missing from authenticated history')
70
+ require(c['mmd_seconds']==60 and c['max_checkpoint_age']>=60,'card timing profile')
71
+ wp=c['witness_policy']; require(set(wp)=={'quorum','witnesses'},'witness policy shape')
72
+ from aics_tlog import parse_vkey
73
+ wkeys=[parse_vkey(w['vkey']) for w in wp['witnesses']]
74
+ require(ctx.policy.quorum<=wp['quorum']<=len(wkeys) and len({w.public_key for w in wkeys})==len(wkeys),'witness uniqueness/quorum')
75
+ require(set(ctx.policy.witness_vkeys).issubset({w['vkey'] for w in wp['witnesses']}),'candidate removes pinned witnesses')
76
+ limits=c['limits']; expected=dict(public_body_bytes=32768,ciphertext_bytes=65536,envelope_window_seconds=300,future_skew_seconds=30,session_idle_seconds=900)
77
+ require(set(limits)==set(expected)|{'writes','registrations','preidentity','flood_suspension_seconds'},'limit schema')
78
+ for k,v in expected.items(): require(limits[k]==v,'incorrect '+k)
79
+ for k in ('writes','registrations','preidentity'): require(set(limits[k])=={'count','interval_seconds'} and all(type(x) is int and x>0 for x in limits[k].values()),'unbounded rate limits')
80
+ require(set(c['retention'])=={'dm_seconds','capsule_max_seconds','ciphertext_budget_bytes','eviction'} and c['retention']['eviction']=='oldest-ts-index','retention schema')
81
+ require(all(type(c['retention'][k]) is int and c['retention'][k]>0 for k in ('dm_seconds','capsule_max_seconds','ciphertext_budget_bytes')),'retention bounds')
82
+ patterns=c['secret_patterns']
83
+ require(isinstance(patterns,list) and all(isinstance(x,dict) and set(x)=={'id','pattern_hash'} and isinstance(x['id'],str) and re.fullmatch('[a-z0-9_]{1,32}',x['id']) and isinstance(x['pattern_hash'],str) and re.fullmatch('[0-9a-f]{64}',x['pattern_hash']) for x in patterns) and len({x['id'] for x in patterns})==len(patterns),'secret pattern schema')
84
+ require(set(c['reason_codes'])=={'refusal','tombstone','suspension','freeze','revocation','outcome'},'reason registries')
85
+ # §13.1: the §17.2 registries plus exactly the card's own secret_format:<id> instances (a closed expansion, §17.2)
86
+ active={'secret_format:'+x['id'] for x in patterns}
87
+ for k,vals in c['reason_codes'].items(): require(isinstance(vals,list) and set(vals)==(refusal_codes(profiles)|active if k=='refusal' else ENUMS[k]) and len(vals)==len(set(vals)),'reason registry differs')
88
+ if 'SUP-1' in profiles: verify_sup(c)
89
+ require(set(c['tiers'])=={'verified','third-party-generated','unverified'} and 'x25519-xsalsa20-poly1305' in c['schemes'],'required tier/scheme')
90
+ require(set(a['id'] for a in c['assurances'])=={f'G{i}' for i in range(1,11)}|{f'N{i}' for i in range(1,6)} and len(c['assurances'])==15,'assurance registry')
91
+ for a in c['assurances']:
92
+ require(set(a)=={'id','status','claim','assumptions','evidence'} and a['status'] in ('proven','promised','not-offered'),'assurance shape/status')
93
+ if a['id'].startswith('N'): require(a['status']=='not-offered','non-guarantee overstated')
94
+ require(bool(c['tile_prefixes']) and all(x.startswith('https://') for x in c['tile_prefixes']),'tile transport')
95
+ return c,hist,h
@@ -0,0 +1 @@
1
+ from . import gate, log, identity, negative, access, rates, grants, http
@@ -0,0 +1,38 @@
1
+ from ..core import check
2
+ from ..protocol import J,H
3
+ from ..schema import require
4
+ from .. import adapter
5
+ from .negative import post_attempt
6
+
7
+ @check('access.owner','Owner quote/complete endpoints reject non-owner signatures','AICS-1 §§3.1,5.3,12.1','owner')
8
+ def owner(ctx):
9
+ a=ctx.new_agent()
10
+ for action in ('quote','complete'):
11
+ q=dict(kind='owner_action',action=action,request_hash=H(b'request'),result_hash=None if action=='quote' else H(b'result'),asset_hash=H(b'asset'),amount=1,quote_index=None if action=='quote' else 0)
12
+ for route in adapter.OWNER_PATHS:
13
+ e=ctx.prepare(a,'report',q); r=ctx.client.submit(e,a.key,route=route)
14
+ require(400<=r.status<500,'non-owner action accepted')
15
+ # Profile's native endpoint must supply refusal evidence; extensions too.
16
+ obj=r.json(); reason=obj['entry']['payload']['reason_code']; ctx.refused(r,e,reason)
17
+
18
+ @check('access.bearer','No bearer authentication or token issuance surface','DECISIONS 1; AICS-1 §§3,12.1','bearer')
19
+ def bearer(ctx):
20
+ a=ctx.new_agent(); e,b=post_attempt(ctx,a)
21
+ r=ctx.client.submit(e,a.key,b,headers={'Authorization':'Bearer aics_live_conformance','Content-Type':'application/json'})
22
+ require(400<=r.status<500,'bearer-only write accepted')
23
+ for path in adapter.LEGACY_PROBES['bearer']:
24
+ r=ctx.client.request('POST',path,J({'name':'conformance','public_key':a.ed}),{'Content-Type':'application/json'})
25
+ require(r.status in (400,401,403,404,405,410,422),'legacy token route active')
26
+ require(not any(k in r.text().lower() for k in ('access_token','aics_live_','bearer_token','refresh_token')),'token returned')
27
+ schema=ctx.client.request('GET',adapter.path('openapi'))
28
+ if schema.status==200:
29
+ require('"bearer"' not in schema.text().lower(),'OpenAPI advertises bearer authentication')
30
+ return {'scope':'Configured native and legacy routes plus published OpenAPI; arbitrary undisclosed paths cannot be disproved by black-box probing.'}
31
+
32
+ @check('access.keygen','No server-generated key path or private key response','DECISIONS 1; AICS-1 §3.1','keygen')
33
+ def keygen(ctx):
34
+ for path in adapter.LEGACY_PROBES['keygen']:
35
+ r=ctx.client.request('POST',path,J({'name':'conformance-keygen-probe'}),{'Content-Type':'application/json'})
36
+ require(r.status in (400,401,403,404,405,410,422),'registration/keygen without public key accepted')
37
+ require(not any(k in r.text().lower() for k in ('private_seed','private_key','secret_key','seed_hex')),'server returned private key material')
38
+ return {'scope':'Configured key-generation and registration routes; no black-box claim about unadvertised paths.'}