@agnt-sdk/simon-server 0.0.0-stage → 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 +201 -0
- package/NOTICE +12 -0
- package/README.md +317 -2
- package/dist/account.d.ts +59 -0
- package/dist/account.js +244 -0
- package/dist/auth.d.ts +42 -0
- package/dist/auth.js +96 -0
- package/dist/config.d.ts +219 -0
- package/dist/config.js +300 -0
- package/dist/context.d.ts +57 -0
- package/dist/context.js +2 -0
- package/dist/continuity.d.ts +35 -0
- package/dist/continuity.js +74 -0
- package/dist/deliver.d.ts +22 -0
- package/dist/deliver.js +208 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +11 -0
- package/dist/extensions.d.ts +216 -0
- package/dist/extensions.js +75 -0
- package/dist/fanout.d.ts +32 -0
- package/dist/fanout.js +79 -0
- package/dist/http.d.ts +69 -0
- package/dist/http.js +112 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +12 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +41 -0
- package/dist/net/dns.d.ts +31 -0
- package/dist/net/dns.js +100 -0
- package/dist/net/fetch.d.ts +48 -0
- package/dist/net/fetch.js +173 -0
- package/dist/net/ip.d.ts +38 -0
- package/dist/net/ip.js +159 -0
- package/dist/net/psl.d.ts +22 -0
- package/dist/net/psl.js +91 -0
- package/dist/net/public-suffixes.d.ts +10 -0
- package/dist/net/public-suffixes.js +6885 -0
- package/dist/notice.d.ts +5 -0
- package/dist/notice.js +61 -0
- package/dist/outbox.d.ts +14 -0
- package/dist/outbox.js +356 -0
- package/dist/parts.d.ts +29 -0
- package/dist/parts.js +443 -0
- package/dist/public.d.ts +43 -0
- package/dist/public.js +134 -0
- package/dist/published.d.ts +3 -0
- package/dist/published.js +140 -0
- package/dist/rate.d.ts +28 -0
- package/dist/rate.js +77 -0
- package/dist/readers.d.ts +31 -0
- package/dist/readers.js +48 -0
- package/dist/register.d.ts +4 -0
- package/dist/register.js +401 -0
- package/dist/resolver.d.ts +140 -0
- package/dist/resolver.js +386 -0
- package/dist/send.d.ts +30 -0
- package/dist/send.js +216 -0
- package/dist/server.d.ts +52 -0
- package/dist/server.js +234 -0
- package/dist/signal.d.ts +5 -0
- package/dist/signal.js +137 -0
- package/dist/store/contract.d.ts +4 -0
- package/dist/store/contract.js +1177 -0
- package/dist/store/sqlite-migration.d.ts +82 -0
- package/dist/store/sqlite-migration.js +1019 -0
- package/dist/store/sqlite.d.ts +108 -0
- package/dist/store/sqlite.js +992 -0
- package/dist/store/store.d.ts +400 -0
- package/dist/store/store.js +23 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.js +199 -0
- package/dist/testing.d.ts +38 -0
- package/dist/testing.js +87 -0
- package/dist/webhooks.d.ts +10 -0
- package/dist/webhooks.js +162 -0
- package/dist/worker.d.ts +15 -0
- package/dist/worker.js +33 -0
- package/package.json +57 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
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
|
|
95
|
+
Derivative 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,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
SIMON Protocol
|
|
2
|
+
Copyright 2026 3Gen Internet Corporation
|
|
3
|
+
|
|
4
|
+
This product is licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
5
|
+
|
|
6
|
+
"AGNT" is a trademark of 3Gen Internet Corporation. The license does not grant permission to use it;
|
|
7
|
+
see TRADEMARK.md.
|
|
8
|
+
|
|
9
|
+
This product includes a snapshot of the ICANN section of the Public Suffix List
|
|
10
|
+
(server/src/net/public-suffixes.ts), from https://publicsuffix.org/, which is subject to the terms of
|
|
11
|
+
the Mozilla Public License, v. 2.0. A copy of the MPL is at https://mozilla.org/MPL/2.0/. The snapshot is
|
|
12
|
+
written by server/scripts/write-public-suffixes.ts from the list of 2024-01-07.
|
package/README.md
CHANGED
|
@@ -1,3 +1,318 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agnt-sdk/simon-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The basic SIMON server: everything in [`spec/PROTOCOL.md`](../spec/PROTOCOL.md) and [`spec/CLIENT.md`](../spec/CLIENT.md), and nothing else. No people, directory, graph, blocklists, webhooks or bridges; those are extensions. Runtime dependencies: `@agnt-sdk/simon-protocol` and Node's standard library (`node:http`, `node:sqlite`, `node:crypto`, `node:dns`). Node 26 runs it from this repository's TypeScript sources; the npm package is compiled and runs on Node 24.6.0 or later.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @agnt-sdk/simon-server # the library, and the simon-server binary
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
| Import | What it gives |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `@agnt-sdk/simon-server` | `createServer`, `defineConfig`, `loadConfig`, `SqliteStore`, the `Store` interface and its record types, the extension types, `json`, the DNS and HTTP seams |
|
|
12
|
+
| `@agnt-sdk/simon-server/contract` | `storeContract` and `retractionCapacityContract`: the Store contract as `node:test` suites |
|
|
13
|
+
| `@agnt-sdk/simon-server/testing` | `Clock`, `FakeDns`, `FakeNet`, `toRequest`, `replyBody`: the outside world as test doubles |
|
|
14
|
+
|
|
15
|
+
`node:sqlite` is loaded only when a `SqliteStore` is made, so an application on another Store (such as [`@agnt-sdk/simon-store-mongo`](../store-mongo/README.md)) never loads it. `simon-server help` lists the main settings.
|
|
16
|
+
|
|
17
|
+
## Run
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
SIMON_DEV=1 SIMON_ORIGIN=http://localhost:8080 SIMON_HOSTED_DOMAINS=example.test node server/src/main.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
In production: an https origin (TLS in front of the server, or `SIMON_TLS_CERT_FILE` and `SIMON_TLS_KEY_FILE`), a server key, and the domains you serve pointed at it (`_simon.<domain> TXT "v=simon1; server=<origin>"`).
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Every variable, read once at startup; an invalid value throws. Source: `src/config.ts`.
|
|
28
|
+
|
|
29
|
+
| Variable | Meaning | Default |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `SIMON_ORIGIN` | This server's origin, `https://host[:port]` | required |
|
|
32
|
+
| `SIMON_DEV` | `1`: development. http and local hosts allowed, register codes logged, a throwaway key when none is set | `0` |
|
|
33
|
+
| `SIMON_HOSTED_DOMAINS` | Domains whose names agents take at once, comma separated | none |
|
|
34
|
+
| `SIMON_LEASE_DOMAIN` | Domain for leased addresses (`register` with a key alone) | none |
|
|
35
|
+
| `SIMON_SERVER_KEY` / `_FILE` | Signing key: `<alg>:<base64url 32-byte seed>` (`scripts/server-keys.sh` makes one), or a PKCS #8 PEM file | required outside dev |
|
|
36
|
+
| `SIMON_NEXT_SERVER_KEY` / `_FILE` | Announced next key: listed, not used. List it 10 minutes before swapping it in | none |
|
|
37
|
+
| `SIMON_DB` | sqlite file; `:memory:` for none | `simon.sqlite` |
|
|
38
|
+
| `SIMON_PORT` | Port | `8080` |
|
|
39
|
+
| `SIMON_LISTEN_HOST` | The address to listen on; `127.0.0.1` behind a tunnel | every interface |
|
|
40
|
+
| `SIMON_CLIENT_IP_HEADER` | A header proxies set to the client's address: `x-forwarded-for` behind a load balancer, `cf-connecting-ip` behind Cloudflare. Believed only from this machine and from `SIMON_TRUSTED_PROXIES` | none: the connection's address |
|
|
41
|
+
| `SIMON_TRUSTED_PROXIES` | Networks a load balancer connects from, in CIDR notation, comma separated, each `/8` or narrower. Needs `SIMON_CLIENT_IP_HEADER` | none: this machine only |
|
|
42
|
+
| `SIMON_KEEP_ALIVE_TIMEOUT` | Seconds an idle connection stays open. Behind a load balancer, longer than its idle timeout | `5` |
|
|
43
|
+
| `SIMON_TRUSTED_CLAIM_SERVERS` | Origins whose claims agents here are shown, beyond the authoritative ones (below) | none |
|
|
44
|
+
| `SIMON_DOH_RESOLVERS` | Two or more DNS-over-HTTPS JSON endpoints; every TXT answer must agree with them | none |
|
|
45
|
+
| `SIMON_DNS_SERVERS` | DNS servers to ask instead of the system's, IP addresses (a tailnet's `100.100.100.100`, for one) | the system's |
|
|
46
|
+
| `SIMON_RATES` | Overrides, `name=count/seconds,...`; names in `DEFAULT_RATES` | see `config.ts` |
|
|
47
|
+
| `SIMON_PUBLIC_SUFFIX_LIST` | A newer copy of the Public Suffix List (`public_suffix_list.dat`) to judge registrable domains by; its ICANN section is read, and a file without its section markers or with fewer than 5000 rules there stops the server from starting | the snapshot of 2024-01-07 that ships in `src/net/public-suffixes.ts` |
|
|
48
|
+
| `SIMON_RETIRED_AGENT_DOCUMENTS` | `private`: a retired address answers its agent-document lookup with the same 404 as an unknown address. `public`: it answers its signed retired document, keys and unpairing included (see Storage). Any other value stops the server from starting | `private` |
|
|
49
|
+
| `SIMON_RATE_EXEMPT_NETWORKS` | Networks whose clients the per-client `request`, `register`, `codeClient` and `agentMiss` rates do not count, in CIDR notation, comma separated, each `/8` or narrower: a hosting service that registers and speaks for many agents from a few addresses. Its agents' own rates (`send`, `destinations`, `upload`, `code`) still apply | none |
|
|
50
|
+
| `SIMON_KEY_CHANGE_HOLD` | Seconds to hold a domain whose server signs with a key it was not seen to vouch for (spec 11.1) | `3600` |
|
|
51
|
+
| `SIMON_RECOVERY_WAIT` | Seconds a recovery by code waits while the agent still holds a valid key; the agent is told, and the address is sent a code that cancels it | `259200` (72 h) |
|
|
52
|
+
| `SIMON_MAX_CONNECTIONS` | Connections served at once | `1024` |
|
|
53
|
+
| `SIMON_RETENTION_DAYS` | Days messages are kept (at least 7) | `30` |
|
|
54
|
+
| `SIMON_DEV_RESOLVE` | Development only: `domain=origin,...` answered without DNS | none |
|
|
55
|
+
| `SIMON_TRUSTED_NETWORKS` | Private networks outbound calls may reach, in CIDR notation, comma separated. Each is `/8` or narrower; `0.0.0.0/0`, `::/0` and anything broader than `/8` stop the server at startup. Calls stay https only | none: public addresses only |
|
|
56
|
+
| `SIMON_TLS_CERT_FILE`, `SIMON_TLS_KEY_FILE` | Serve https directly with this PEM certificate chain and private key. Both or neither; the origin must then be https | plain http (put TLS in front) |
|
|
57
|
+
|
|
58
|
+
**Outbound calls** (deliveries, documents, well-known answers, part fetches) go through an SSRF guard:
|
|
59
|
+
https only, TLS 1.3 or later, no redirects, and only to public addresses, so a domain cannot point this
|
|
60
|
+
server at a private one. Lookups made before a delivery's signature verifies are counted against the
|
|
61
|
+
client's network (`lookup` in `DEFAULT_RATES`) and against the registrable domain of the name looked up
|
|
62
|
+
(`lookupDomain`, by the Public Suffix List's ICANN section, counted in the store so every process shares
|
|
63
|
+
it), only when made, and at most 24 run at once. Those a
|
|
64
|
+
delivery asks for once its carrier is known, its recipients' domains and its claims, are counted against
|
|
65
|
+
the carrier (`carrierLookup`) and run in a pool of their own, at most 8 for one carrier. An agent's send and
|
|
66
|
+
the outbox look up in a third pool, which nothing from outside can fill. `SIMON_TRUSTED_NETWORKS` is the one exception, for servers that federate over a private
|
|
67
|
+
network. A host with any address outside the public internet and outside every trusted network is
|
|
68
|
+
refused.
|
|
69
|
+
|
|
70
|
+
**Probing for addresses.** Anyone may read an agent document, which says an agent exists: verification
|
|
71
|
+
needs that. To make testing many addresses expensive, the server counts each client's requests for
|
|
72
|
+
addresses it has no agent for (`agentMiss` in `DEFAULT_RATES`, 100 an hour per client IP). Past that,
|
|
73
|
+
the client is answered `429` for every agent document, an agent's or not, until the hour ends, so the
|
|
74
|
+
refusal tells nothing about any address. Verifying a message asks for an agent that wrote it, so other
|
|
75
|
+
servers' verification seldom counts. A signed request from another server gets no larger budget:
|
|
76
|
+
anyone with a domain can sign as a server.
|
|
77
|
+
|
|
78
|
+
**Claims from other servers.** A carrier's claims about a message's author are its word, not proof.
|
|
79
|
+
Agents here are always shown a carrier's authoritative claims: an `acts-for` or an `identifier` for an
|
|
80
|
+
email address on the carrier's own domain. They are shown its other claims, `operator` and claims about
|
|
81
|
+
addresses elsewhere among them, only when the carrier's origin is in `SIMON_TRUSTED_CLAIM_SERVERS` (spec
|
|
82
|
+
section 6.2). So with the default settings, agents here see no `operator` claim from another server, and
|
|
83
|
+
no claim about an address that server does not speak for. To show a carrier's, list it:
|
|
84
|
+
`SIMON_TRUSTED_CLAIM_SERVERS=https://simon.partner.example`.
|
|
85
|
+
|
|
86
|
+
**The well-known answer.** When this server's origin host is itself a hosted domain or the lease domain,
|
|
87
|
+
the server answers `GET /.well-known/simon` with `{"server": <origin>}`. That domain then resolves to it
|
|
88
|
+
by the well-known method of spec section 3, with no TXT record.
|
|
89
|
+
|
|
90
|
+
## Behind a load balancer
|
|
91
|
+
|
|
92
|
+
The server can run as one container behind a load balancer that ends TLS, such as an AWS Application
|
|
93
|
+
Load Balancer in front of an ECS service. The image is the Dockerfile's default stage (`docker build -t
|
|
94
|
+
simon-server .`): it listens on 8080 with plain http and keeps its sqlite file in `/data`.
|
|
95
|
+
`scripts/image-check.sh` builds it and runs it this way.
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
SIMON_ORIGIN=https://simon.example.com # the public origin, https at the balancer
|
|
99
|
+
SIMON_SERVER_KEY_FILE=/run/secrets/simon-server-key # or SIMON_SERVER_KEY, from a secret store
|
|
100
|
+
SIMON_HOSTED_DOMAINS=example.com
|
|
101
|
+
SIMON_CLIENT_IP_HEADER=x-forwarded-for
|
|
102
|
+
SIMON_TRUSTED_PROXIES=10.0.0.0/24,10.0.1.0/24 # the balancer's own subnets, not the whole VPC
|
|
103
|
+
SIMON_KEEP_ALIVE_TIMEOUT=65 # longer than the balancer's idle timeout (60 s on ALB)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **The client's address.** Rate limits count clients by address. The balancer appends the address it
|
|
107
|
+
heard from to `X-Forwarded-For`, and the server reads the list from the right, past every address in
|
|
108
|
+
`SIMON_TRUSTED_PROXIES`: the first one outside is the client. Name only the balancer's own subnets; a
|
|
109
|
+
network that holds anything else, a whole VPC for one, lets what runs there choose the address it is
|
|
110
|
+
counted under. The balancer appends to the header without client ports (on ALB, the default `append`
|
|
111
|
+
mode with `routing.http.xff_client_port.enabled` off): an entry with a port is no address, so every
|
|
112
|
+
client would be counted as the balancer, and a balancer that does not append passes on what clients
|
|
113
|
+
wrote.
|
|
114
|
+
- **A service of your own in front.** A service that registers and speaks for many agents reaches the
|
|
115
|
+
server from a few addresses, which the per-client `request` and `register` rates would soon refuse.
|
|
116
|
+
`SIMON_RATE_EXEMPT_NETWORKS` names its networks; they are judged by the client's address as read above,
|
|
117
|
+
so nobody outside the trusted proxies' networks can claim one, and nobody inside them should be able
|
|
118
|
+
to. Its agents' own rates (`send`, `destinations`, `upload`, `code`) still
|
|
119
|
+
apply, and so do `lookup` and `badSignature`, which bound deliveries from other servers.
|
|
120
|
+
- **Health checks.** `GET /health` answers `200 {"status":"ok"}` while the server and its store answer,
|
|
121
|
+
and `503` when the store does not. It says nothing else.
|
|
122
|
+
- **Keep-alive.** A balancer reuses idle connections to its targets. If the server closes one first, a
|
|
123
|
+
request on it fails with `502`, so the server's timeout must be the longer. Raise it only when nothing
|
|
124
|
+
but the balancer can reach the server: one client holding long-lived idle connections fills
|
|
125
|
+
`SIMON_MAX_CONNECTIONS` within its request rate.
|
|
126
|
+
- **One task.** sqlite has one writer: run exactly one server on the data, and never start a replacement
|
|
127
|
+
beside it (on ECS, a maximum of 100 percent and a minimum healthy of 0). `SIGTERM` stops it cleanly.
|
|
128
|
+
- **The volume.** `/data` is a block volume, not a network file system, whose sqlite locking cannot be
|
|
129
|
+
relied on. It must be writable by the image's user, `node` (uid 1000). Snapshot it for backups.
|
|
130
|
+
- **DNS.** Each hosted domain points at the origin: `_simon.<domain> TXT "v=simon1; server=<origin>"`.
|
|
131
|
+
|
|
132
|
+
## Two laptops over Tailscale
|
|
133
|
+
|
|
134
|
+
Two people, each running a basic server on their own laptop, can federate over a
|
|
135
|
+
[Tailscale](https://tailscale.com) tailnet with no public DNS and no open ports. Each server hosts
|
|
136
|
+
addresses on its laptop's MagicDNS name, for example `ada-laptop.tail1234.ts.net`, and serves https on
|
|
137
|
+
port 443 with the certificate `tailscale cert` issues for that name. Each domain resolves by the
|
|
138
|
+
well-known answer its own server gives, because no TXT record can be added under `ts.net`.
|
|
139
|
+
|
|
140
|
+
In the tailnet's admin console, turn on MagicDNS and HTTPS certificates. Then, on each laptop, from
|
|
141
|
+
this repository:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
NAME=$(tailscale status --json | jq -r '.Self.DNSName | rtrimstr(".")') # ada-laptop.tail1234.ts.net
|
|
145
|
+
tailscale cert "$NAME" # writes $NAME.crt and $NAME.key here
|
|
146
|
+
[ -f simon-keys.env ] || (umask 077; scripts/server-keys.sh > simon-keys.env) # once: the server key
|
|
147
|
+
. ./simon-keys.env
|
|
148
|
+
SIMON_ORIGIN="https://$NAME" SIMON_PORT=443 SIMON_HOSTED_DOMAINS="$NAME" \
|
|
149
|
+
SIMON_TLS_CERT_FILE="$NAME.crt" SIMON_TLS_KEY_FILE="$NAME.key" \
|
|
150
|
+
SIMON_TRUSTED_NETWORKS=100.64.0.0/10,fd7a:115c:a1e0::/48 SIMON_DB=simon.sqlite \
|
|
151
|
+
node server/src/main.ts
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- **The port.** Resolution fetches `https://<domain>/.well-known/simon`, so the server listens on 443. On
|
|
155
|
+
Linux a process needs permission for that, for example `sudo sysctl net.ipv4.ip_unprivileged_port_start=443`.
|
|
156
|
+
- **The networks.** `100.64.0.0/10` and `fd7a:115c:a1e0::/48` are the IPv4 and IPv6 ranges Tailscale gives
|
|
157
|
+
its devices. A MagicDNS name has addresses in both, and the guard refuses a host if any of its
|
|
158
|
+
addresses is outside the trusted networks.
|
|
159
|
+
- **Name lookups.** The server asks the system's DNS servers through c-ares, not the operating system's
|
|
160
|
+
resolver library, so the other laptop's name must resolve that way. This prints its tailnet addresses
|
|
161
|
+
when it does: `node -e 'new (require("node:dns").promises.Resolver)().resolve4(process.argv[1]).then(console.log)' other-laptop.tail1234.ts.net`.
|
|
162
|
+
- **If the name does not resolve that way,** add `SIMON_DNS_SERVERS=100.100.100.100`, Tailscale's own resolver.
|
|
163
|
+
- **The key.** Keep `simon-keys.env` (mode 600) and reuse it: a server whose key changes wholesale is held
|
|
164
|
+
by its peers for `SIMON_KEY_CHANGE_HOLD` seconds (spec section 11.1).
|
|
165
|
+
|
|
166
|
+
Agents then register on the laptop's own name, for example
|
|
167
|
+
`simon register --server "https://$NAME" --address "ada@$NAME"`, and write to agents on the other
|
|
168
|
+
laptop's name.
|
|
169
|
+
|
|
170
|
+
Two laptops with no tailnet in common can federate over public Cloudflare quick tunnels instead, with the
|
|
171
|
+
server behind the tunnel (`SIMON_LISTEN_HOST=127.0.0.1`, `SIMON_CLIENT_IP_HEADER=cf-connecting-ip`):
|
|
172
|
+
[examples/two-laptops](../examples/two-laptops/README.md) does it in one script.
|
|
173
|
+
|
|
174
|
+
## Layout
|
|
175
|
+
|
|
176
|
+
| File | Responsibility |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| `server.ts` | `createServer`: assembly, routing, the request pipeline |
|
|
179
|
+
| `http.ts` | Request/Reply, router, the `node:http` adapter with body caps |
|
|
180
|
+
| `deliver.ts` | `POST /simon/deliver`, spec 6.3 in order |
|
|
181
|
+
| `outbox.ts` | Delivery to other servers: claimed rows, caps on POSTs, backoff to the deadline, dead and throttled destinations |
|
|
182
|
+
| `fanout.ts` | Arrivals offered to recipients here through the admit chain, each on its own; inbox events told |
|
|
183
|
+
| `parts.ts` | Uploads, the per-source reading rules, and part fetches from carriers (prefetched, bounded) |
|
|
184
|
+
| `register.ts`, `account.ts` | The agent's address and keys: register and recovery; the account, keys, cancelling a recovery, unpairing |
|
|
185
|
+
| `send.ts`, `sync.ts`, `public.ts` | Sending, retained sync and public reads |
|
|
186
|
+
| `signal.ts`, `webhooks.ts` | Client signals and verified webhook wakeups |
|
|
187
|
+
| `notice.ts` | Messages from the server itself to an agent here, as `postmaster@<its domain>` |
|
|
188
|
+
| `resolver.ts`, `continuity.ts` | Resolution and documents, bounded and cached; key continuity by possession |
|
|
189
|
+
| `auth.ts` | Signed requests, nonces recorded after verification |
|
|
190
|
+
| `net/` | c-ares DNS with DoH confirmation, the SSRF-guarded client, IP rules |
|
|
191
|
+
| `store/` | The `Store` interface, sqlite, and the contract suite |
|
|
192
|
+
| `extensions.ts`, `config.ts`, `rate.ts`, `worker.ts`, `main.ts` | The seam, settings, limits, the loop, the process |
|
|
193
|
+
| `context.ts`, `errors.ts` | What the parts share; the protocol's problem-detail errors |
|
|
194
|
+
| `testing.ts` | A fake clock, DNS and network, for tests here and in applications |
|
|
195
|
+
|
|
196
|
+
## Extensions
|
|
197
|
+
|
|
198
|
+
An application adds behaviour by passing extensions; it never edits the core. Every hook is the list of all extensions' hooks, in order.
|
|
199
|
+
|
|
200
|
+
| Hook | Called | Returns |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `routes` | Requests to its paths (core paths cannot be replaced) | a `Reply` |
|
|
203
|
+
| `admit(arrival, api)` | Per recipient here, after the 202 | `'accept'` or `'refuse'` or `'later'` ends the chain; `undefined` passes. Default: accept |
|
|
204
|
+
| `claimsFor(message, agent, api)` | On `send` | claims for the envelope |
|
|
205
|
+
| `allowAddress(request, api)` | `register` for a new hosted name or lease, before any pairing | `'allow'` or `'refuse'` (`409 address_taken`) ends the chain; `undefined` passes. Default: allow |
|
|
206
|
+
| `verifyPrincipal(request, api)` | `register` with a `mailto:`/`tel:` `for` | `{status:'verified'}`, `{status:'pending', body}`, or `undefined` |
|
|
207
|
+
| `upload(upload, api)` | `PUT /simon/parts/<sha256>`, before the bytes are kept | nothing; a thrown WireError refuses the upload |
|
|
208
|
+
| `on.stored / inbox / delivered / deliveryFailed / recovered` | After the event (`inbox` at least once) | nothing; failures are logged |
|
|
209
|
+
| `on.unroutable` | Recipients whose domain names no SIMON server | `true` when it took them: they count as delivered, and later listeners are not told |
|
|
210
|
+
|
|
211
|
+
`api` (`ServerApi`) offers `config`, `store`, `now()`, `log`, `authenticate(req)`, `admit(address, id)` (release a `'later'`), `offer(address, id)` (a stored message to an agent here, through the admit chain), `send(message, hints?)` (as an agent here, checked as `send` checks it) and `retire(address)`.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { createServer, json, loadConfig, SqliteStore, type Extension } from '@agnt-sdk/simon-server';
|
|
215
|
+
|
|
216
|
+
const held = new Map<string, string>(); // message id -> recipient
|
|
217
|
+
const quiet: Extension = {
|
|
218
|
+
name: 'quiet-hours',
|
|
219
|
+
admit: ({ id, recipient }) => {
|
|
220
|
+
const hour = new Date().getUTCHours();
|
|
221
|
+
if (hour >= 6) return undefined; // no opinion: the next link decides
|
|
222
|
+
held.set(id, recipient);
|
|
223
|
+
return 'later';
|
|
224
|
+
},
|
|
225
|
+
routes: [
|
|
226
|
+
{
|
|
227
|
+
method: 'POST',
|
|
228
|
+
path: '/app/release',
|
|
229
|
+
handle: async (req, api) => {
|
|
230
|
+
const agent = await api.authenticate(req);
|
|
231
|
+
for (const [id, to] of held) if (to === agent.address && (await api.admit(to, id))) held.delete(id);
|
|
232
|
+
return json({ released: true });
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
const config = loadConfig();
|
|
238
|
+
createServer({ config, store: new SqliteStore(config.database), extensions: [quiet] }).listen();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Storage
|
|
242
|
+
|
|
243
|
+
The ordinary SQLite Store refuses legacy standalone schema2. The separate
|
|
244
|
+
[offline candidate conversion tool](SQLITE-V2-MIGRATION.md) preserves a WAL-aware core/app-outbox
|
|
245
|
+
cohort and validates a new current-schema copy under explicit maintenance prerequisites. Its output stays
|
|
246
|
+
blocked for activation, with no operational promotion/reset override; passing source, CLI and synthetic
|
|
247
|
+
tests says nothing about readiness of real data, hosts or runtimes.
|
|
248
|
+
|
|
249
|
+
A retired agent-document lookup answers the same 404 as an unknown address by default (`'private'`).
|
|
250
|
+
Publishing signed retirement evidence is an explicit opt-in: `SIMON_RETIRED_AGENT_DOCUMENTS=public` in
|
|
251
|
+
the environment (or `retiredAgentDocuments: 'public'` in `defineConfig`), or `retiredAgentDocuments: 'public'`
|
|
252
|
+
passed to `createServer`, which overrides the configuration. The setting is validated and captured at
|
|
253
|
+
construction, and applies to both `handle` and the HTTP listener. Invalid values fail before workers
|
|
254
|
+
start. It does not remove internal retired records or change retired-key authentication, unpairing
|
|
255
|
+
retries, or previously published signed messages.
|
|
256
|
+
|
|
257
|
+
`store/store.ts` is everything the core keeps, with its uniqueness and retention rules written on the interface. Another store proves itself with the contract suite:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { storeContract } from '@agnt-sdk/simon-server/contract';
|
|
261
|
+
storeContract('mongo', async () => new MongoStore(await freshDb()).init());
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
[`@agnt-sdk/simon-store-mongo`](../store-mongo/README.md) is the Store on MongoDB and Amazon DocumentDB. Before running several processes on one store, read "Running several copies" in [docs/REFERENCE-SERVER.md](../docs/REFERENCE-SERVER.md): elect one of them to run the worker loop.
|
|
265
|
+
|
|
266
|
+
## Testing
|
|
267
|
+
|
|
268
|
+
`@agnt-sdk/simon-server/testing` is the fake outside world the tests use: a movable `Clock`, `FakeDns`, and `FakeNet`, whose origins answer in-process. An application runs whole federations of its own servers on it.
|
|
269
|
+
|
|
270
|
+
```sh
|
|
271
|
+
node --test server/test # spec rules, federation on a fake network, hardening, the store contract
|
|
272
|
+
node server/scripts/smoke.ts # two real server processes, a message each way over localhost
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
To run the conformance suite against it, start the server with its peer simulator's domain resolved
|
|
276
|
+
without DNS (`SIMON_DEV_RESOLVE` is honoured only with `SIMON_DEV=1`, and refused at startup otherwise) and
|
|
277
|
+
rate limits loose enough for one run:
|
|
278
|
+
|
|
279
|
+
```sh
|
|
280
|
+
SIMON_DEV=1 SIMON_ORIGIN=http://127.0.0.1:8080 SIMON_PORT=8080 SIMON_HOSTED_DOMAINS=sut.test \
|
|
281
|
+
SIMON_LEASE_DOMAIN=lease.sut.test SIMON_DB=:memory: \
|
|
282
|
+
SIMON_RATES=register=1000/3600,badSignature=100000/3600,code=100/3600,codeClient=1000/3600,deliver=100000/3600 \
|
|
283
|
+
SIMON_DEV_RESOLVE=sut.test=http://127.0.0.1:8080,lease.sut.test=http://127.0.0.1:8080,peer.test=http://127.0.0.1:47100 \
|
|
284
|
+
node server/src/main.ts
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
and, in another terminal, `node conformance/src/cli.ts --server http://127.0.0.1:8080 --hosted-domain sut.test --format text`.
|
|
288
|
+
[conformance/README.md](../conformance/README.md) explains every option, including the code-flow checks,
|
|
289
|
+
which read the register codes this server logs in development. How the server runs recovery and keys, and
|
|
290
|
+
its other choices, are in [docs/REFERENCE-SERVER.md](../docs/REFERENCE-SERVER.md).
|
|
291
|
+
|
|
292
|
+
### Client lifecycle and wakeups
|
|
293
|
+
|
|
294
|
+
Registration creates or pairs an address at `POST /simon/register`. Recovery uses `POST /simon/recovery`;
|
|
295
|
+
`GET /simon/recovery` reports pending state to an agent key. The old registration call does not recover a
|
|
296
|
+
held address. A pending mailbox proof can complete for seven days after its `completesAt`, including
|
|
297
|
+
legacy proofs that lack a nonce; it never grants old keys new cancellation authority.
|
|
298
|
+
|
|
299
|
+
`GET /simon/sync` includes received messages, newly accepted sends and transient signals. Its encrypted
|
|
300
|
+
account-bound tokens survive worker restarts through the shared Store secret. Unknown, tampered,
|
|
301
|
+
cross-account and expired tokens fail with `410 token_expired`. Schema 8 preserves historical received
|
|
302
|
+
positions; old stores have no trustworthy discriminator for historic local sends, so each affected device receives an explicit `410 token_expired` with `details.reason=history_migration`
|
|
303
|
+
and an account-bound `details.resumeToken`. Passing that token explicitly reads preserved received
|
|
304
|
+
history and newly tracked sends; starting without a token continues to report the old sent-history gap.
|
|
305
|
+
This never invents old sent history or acknowledges a gap for other devices.
|
|
306
|
+
|
|
307
|
+
`POST /simon/send-signal` carries registry signal types. Webhook creation, verification and deletion use
|
|
308
|
+
`/simon/webhooks`: at most five HTTPS URLs per account, verification before wakeups, signed posts,
|
|
309
|
+
one-second quiet time and lease-fenced retries. An unverified URL whose first verification post fails
|
|
310
|
+
must be deleted and recreated by its owner; it sends no changed notifications. Three days of failures
|
|
311
|
+
switch off a verified webhook.
|
|
312
|
+
|
|
313
|
+
Retraction admission is bounded by host policy: 4096 accepted IDs globally, 256 per author, and 4 MiB
|
|
314
|
+
of canonical receipt/journal state by default. Configure `retractions` on the SQLite/Mongo Store
|
|
315
|
+
constructor with `maxRecords`, `maxPerAuthor`, and `maxBytes` (at most 8 MiB). An exact repeat uses
|
|
316
|
+
its original receipt. Accepted suppression is never evicted to admit another entry, so a full store
|
|
317
|
+
returns a generic `503 unavailable` before acceptance. This conservative permanent allocation can
|
|
318
|
+
exhaust; it is an overload hold rather than permission to relist retracted content.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type Json } from '@agnt-sdk/simon-protocol';
|
|
2
|
+
import { type Context } from './context.ts';
|
|
3
|
+
import { type Reply, type Request } from './http.ts';
|
|
4
|
+
import type { Agent } from './store/store.ts';
|
|
5
|
+
/** The value scope of an address's pending recovery. */
|
|
6
|
+
export declare const RECOVERY = "recovery";
|
|
7
|
+
/**
|
|
8
|
+
* The value scope of a cancel code, by the address and the code's hash: the nonce of the recovery it
|
|
9
|
+
* cancels. A recovery may have several, one for each time the address was told of it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CANCEL_CODE = "recovery-cancel-code";
|
|
12
|
+
/** A recovery by the address's mailbox, waiting until `completesAt` (register.ts). */
|
|
13
|
+
export interface Recovery {
|
|
14
|
+
/** The key that proved the code, which repeats the call to complete it, and that code's hash and nonce. */
|
|
15
|
+
key: string;
|
|
16
|
+
hash: string;
|
|
17
|
+
nonce?: string;
|
|
18
|
+
startedAt?: string;
|
|
19
|
+
completesAt: string;
|
|
20
|
+
/** Whether a key may cancel it: not when it started within 30 days after a recovery a key cancelled. */
|
|
21
|
+
byKey?: boolean;
|
|
22
|
+
cancelHash?: string;
|
|
23
|
+
keyCancelBlocked?: true;
|
|
24
|
+
/** Private preparation gate: explicit false cannot replace keys even if rollback is unavailable. */
|
|
25
|
+
defended?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export type Body<Member extends string> = Partial<Record<Member, string>> & Record<string, Json | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* A body of these calls: a JSON object whose members named in `members` are strings when present. Other
|
|
30
|
+
* members are ignored, unless an extension defines them.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readBody<Member extends string>(bytes: Buffer, members: readonly Member[]): Body<Member>;
|
|
33
|
+
export declare const sha256: (s: string) => string;
|
|
34
|
+
/** Whether `code` is the one whose SHA-256 is `hash`, compared in constant time. */
|
|
35
|
+
export declare const matches: (code: string, hash: string) => boolean;
|
|
36
|
+
/** Whether a key may cancel a recovery starting now: not within 30 days after a key cancelled one. */
|
|
37
|
+
export declare const keysMayCancel: (ctx: Context, address: string) => Promise<boolean>;
|
|
38
|
+
/** Evaluate the exact final signed projection inside the Store's atomic key admission. */
|
|
39
|
+
export declare function canPublishAgent(ctx: Context, agent: Agent): boolean;
|
|
40
|
+
/** The account as it stands, for the key that asked: `cancellable` says whether it may cancel the recovery. */
|
|
41
|
+
export declare function account(ctx: Context, agent: Agent, signer: string): Promise<Reply>;
|
|
42
|
+
export declare function getAccount(ctx: Context, req: Request): Promise<Reply>;
|
|
43
|
+
/** A device's key added with its own proof, so no agent lists a key it does not hold. */
|
|
44
|
+
export declare function addKey(ctx: Context, req: Request): Promise<Reply>;
|
|
45
|
+
/**
|
|
46
|
+
* A key revoked at once. Keys rank by age: a key may revoke itself and any key added after it, never one
|
|
47
|
+
* added before it, so a thief who copies a newer device's key cannot evict the older ones.
|
|
48
|
+
*/
|
|
49
|
+
export declare function revokeKey(ctx: Context, req: Request): Promise<Reply>;
|
|
50
|
+
export declare function endGrant(ctx: Context, req: Request): Promise<Reply>;
|
|
51
|
+
/** A pending recovery cancelled by a key of the agent (`{}`), or by its cancel code (`{"address","cancelCode"}`). */
|
|
52
|
+
export declare function cancelRecovery(ctx: Context, req: Request): Promise<Reply>;
|
|
53
|
+
/**
|
|
54
|
+
* A key principal ends its pairing: the address retires at once, its keys revoked, whatever they say. The
|
|
55
|
+
* unpairing is the authority, so any key may sign the request; a repeat answers as the first call did.
|
|
56
|
+
*/
|
|
57
|
+
export declare function unpair(ctx: Context, req: Request): Promise<Reply>;
|
|
58
|
+
/** Recovery status is reserved to a currently authorized agent key. */
|
|
59
|
+
export declare function getRecovery(ctx: Context, req: Request): Promise<Reply>;
|