@aarmos/bridge 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 +190 -0
- package/README.md +280 -0
- package/dist/bridge.js +129 -0
- package/dist/bridge.js.map +1 -0
- package/dist/commands/attest.js +62 -0
- package/dist/commands/attest.js.map +1 -0
- package/dist/commands/demo.js +169 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/dev.js +56 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/pack.js +94 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/playbook.js +55 -0
- package/dist/commands/playbook.js.map +1 -0
- package/dist/commands/run.js +279 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/simulate.js +181 -0
- package/dist/commands/simulate.js.map +1 -0
- package/dist/commands/verify.js +81 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.js +231 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/attest.js +160 -0
- package/dist/lib/attest.js.map +1 -0
- package/dist/lib/http-broker.js +82 -0
- package/dist/lib/http-broker.js.map +1 -0
- package/dist/lib/mcp-broker.js +80 -0
- package/dist/lib/mcp-broker.js.map +1 -0
- package/dist/lib/pack.js +80 -0
- package/dist/lib/pack.js.map +1 -0
- package/dist/lib/playbook.js +199 -0
- package/dist/lib/playbook.js.map +1 -0
- package/dist/lib/scope-templates.js +129 -0
- package/dist/lib/scope-templates.js.map +1 -0
- package/dist/rpc/router.js +321 -0
- package/dist/rpc/router.js.map +1 -0
- package/dist/session.js +100 -0
- package/dist/session.js.map +1 -0
- package/dist/watchers/files.js +31 -0
- package/dist/watchers/files.js.map +1 -0
- package/dist/watchers/git.js +39 -0
- package/dist/watchers/git.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
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 the 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
|
+
Copyright 2024 Aarmatix LLC
|
|
179
|
+
|
|
180
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
|
+
you may not use this file except in compliance with the License.
|
|
182
|
+
You may obtain a copy of the License at
|
|
183
|
+
|
|
184
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
185
|
+
|
|
186
|
+
Unless required by applicable law or agreed to in writing, software
|
|
187
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
188
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
189
|
+
See the License for the specific language governing permissions and
|
|
190
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# aarmos (CLI bridge)
|
|
2
|
+
|
|
3
|
+
Prosumer CLI that acts as a native host for the Aarmos PWA. The PWA stays
|
|
4
|
+
the brain (agent runtime, governance, policies). The CLI is the arms on the
|
|
5
|
+
developer's machine (files, shell, git).
|
|
6
|
+
|
|
7
|
+
**Status:** `0.1.0` — published to npm under the `next` dist-tag while we
|
|
8
|
+
watch for field issues. The bridge is intentionally thin: pairing, RPC,
|
|
9
|
+
and a deny-by-default capability surface. The PWA at
|
|
10
|
+
[aarmos.io](https://www.aarmos.io) is the brain.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i -g aarmos@next
|
|
16
|
+
aarmos dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or run without installing:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx aarmos@next dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start (local dev of this repo)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd packages/cli
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
npm link # makes `aarmos` available globally
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then try it against the shipped sample workspace — safest first run:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd ../../examples/basic-agent
|
|
38
|
+
aarmos dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The PWA opens paired, in dry-run mode, against a throwaway repo. See
|
|
42
|
+
`examples/basic-agent/README.md` for the 30-second demo.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### `aarmos dev`
|
|
48
|
+
|
|
49
|
+
Boots the local bridge and pairs with the PWA.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Options:
|
|
53
|
+
-p, --port <port> WebSocket port on 127.0.0.1 (default: 7423)
|
|
54
|
+
--pwa <url> PWA base URL to open for pairing
|
|
55
|
+
(default: https://www.aarmos.io)
|
|
56
|
+
--no-open Do not auto-open the PWA in the browser
|
|
57
|
+
--allow-shell <bins> Comma-separated extra shell binaries to allow
|
|
58
|
+
--allow-sql <alias=dsn> Register a read-only Postgres target.
|
|
59
|
+
Repeatable. Wrapped in BEGIN READ ONLY at
|
|
60
|
+
the bridge boundary. DSN never leaves the
|
|
61
|
+
CLI. Example:
|
|
62
|
+
--allow-sql prod=postgres://ro@host/db
|
|
63
|
+
--allow-read <path> Jail filesystem reads to this root.
|
|
64
|
+
Repeatable. Symlink escapes are rejected.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Deny-by-default:** `db.query` and `fs.scoped.read` are not advertised in
|
|
68
|
+
`capabilities` unless the matching `--allow-*` flag is present. Without the
|
|
69
|
+
flag, the PWA does not even see the tool — there is no way to silently fall
|
|
70
|
+
back to a serverless path.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
What it does:
|
|
74
|
+
1. Boots a localhost-only WebSocket bridge on `127.0.0.1:<port>`.
|
|
75
|
+
2. Generates a fresh per-session pairing token.
|
|
76
|
+
3. Opens the PWA at `?bridge=1&port=<port>&token=<token>` so the browser
|
|
77
|
+
can pair.
|
|
78
|
+
4. Streams file-change and git-branch events to the PWA so the agent stays
|
|
79
|
+
in sync while the developer keeps editing in VS Code.
|
|
80
|
+
5. Stays up across accidents — closed tabs, killed browsers, or garbage
|
|
81
|
+
payloads log `◇ Client disconnected. Standing by…` and keep the port
|
|
82
|
+
listening for the same pairing token to reconnect. `Ctrl+C` shuts it
|
|
83
|
+
down cleanly.
|
|
84
|
+
|
|
85
|
+
### `aarmos run`
|
|
86
|
+
|
|
87
|
+
Headless, unattended execution loop for CI runners, jump hosts, VMs, or
|
|
88
|
+
anywhere a human is not watching. **The PWA is still the brain** — this
|
|
89
|
+
loop can only auto-execute steps that were pre-blessed in a playbook
|
|
90
|
+
(`autoApply: true`). Everything else dry-runs and is logged for the PWA
|
|
91
|
+
to approve later.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Options:
|
|
95
|
+
--playbook <path> JSON playbook (see examples/)
|
|
96
|
+
--once Run one pass and exit (default: loop)
|
|
97
|
+
--dry Simulate only — no side effects. Prints per-step
|
|
98
|
+
verdicts and exits (equivalent to `aarmos simulate`).
|
|
99
|
+
--enforce-signatures Refuse to run unless the playbook is Ed25519-signed.
|
|
100
|
+
--trusted-pubkey <b64u> Only accept playbooks signed by this pubkey.
|
|
101
|
+
Repeatable. Requires --enforce-signatures.
|
|
102
|
+
--kill-switch <file> Halt gracefully when this file exists
|
|
103
|
+
--receipts <file> Append hash-chained receipts here (JSONL)
|
|
104
|
+
(default: .aarmos/receipts.jsonl)
|
|
105
|
+
--max-per-hour <n> Ceiling on applied actions per rolling hour
|
|
106
|
+
(default: 60)
|
|
107
|
+
--heartbeat-url <url> POST receipts + heartbeats to this base URL
|
|
108
|
+
--heartbeat-token <token> Bearer token attached to heartbeat POSTs
|
|
109
|
+
--workspace <path> Workspace to run against (default: cwd)
|
|
110
|
+
--allow-shell <bins> Comma-separated extra shell binaries to allow
|
|
111
|
+
--allow-http <host> Allow http.call steps to reach this host. Repeatable.
|
|
112
|
+
--allow-mcp <url> Allow mcp.tool steps against this MCP endpoint. Repeatable.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Guarantees:
|
|
116
|
+
- Every action flows through the same dry-run → `apply.batch` pipeline
|
|
117
|
+
as the interactive bridge — there is no shortcut path.
|
|
118
|
+
- Steps without `autoApply: true` are dry-run only and logged as
|
|
119
|
+
`needs-approval`; nothing is written until a human reviews them.
|
|
120
|
+
- **Destructive gate:** verbs classified as destructive (`kubectl delete`,
|
|
121
|
+
`kubectl drain`, `git push --force`, `Jira DELETE /issue/{id}`, etc.)
|
|
122
|
+
force approval at runtime unless the step sets
|
|
123
|
+
`acknowledgeDestructive: true` **and** the playbook is signed. Same rule
|
|
124
|
+
applies whether or not `--enforce-signatures` is set.
|
|
125
|
+
- The kill-switch file is checked before every tick; presence halts.
|
|
126
|
+
- The `--max-per-hour` ceiling is a rolling window over applied runs.
|
|
127
|
+
- Every action produces a SHA-256-hashed receipt on disk; if
|
|
128
|
+
`--heartbeat-url` is set, receipts and 30-second heartbeats are
|
|
129
|
+
forwarded (best-effort — a dead sink never crashes the loop).
|
|
130
|
+
|
|
131
|
+
### `aarmos simulate`
|
|
132
|
+
|
|
133
|
+
Compile-time governance. Loads a playbook, runs it through the same
|
|
134
|
+
dry-run pipeline as `run`, and prints a per-step verdict — `would-apply`,
|
|
135
|
+
`needs-approval`, or `would-fail` — with the risk classification and
|
|
136
|
+
approval reason. **No side effects.** Use it in CI before scheduling a
|
|
137
|
+
loop for real.
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
Options:
|
|
141
|
+
--playbook <path> JSON playbook (required)
|
|
142
|
+
--workspace <path> Workspace to simulate against (default: cwd)
|
|
143
|
+
--json Emit machine-readable JSON (for CI)
|
|
144
|
+
--strict Exit non-zero if any step would fail
|
|
145
|
+
--allow-shell <bins> Same as `run`
|
|
146
|
+
--allow-http <host> Same as `run`
|
|
147
|
+
--allow-mcp <url> Same as `run`
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `aarmos playbook`
|
|
151
|
+
|
|
152
|
+
Sign, verify, and manage Ed25519 keys for playbooks. Signing is optional
|
|
153
|
+
in dev; only `aarmos run --enforce-signatures` refuses unsigned or
|
|
154
|
+
untrusted playbooks.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
aarmos playbook keygen --out .aarmos/keys --name playbook
|
|
158
|
+
aarmos playbook sign --playbook path.json --key .aarmos/keys/playbook.key.pem
|
|
159
|
+
aarmos playbook verify --playbook path.json [--trusted-pubkey <b64u>]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Signatures cover a canonical JSON of the playbook body with the
|
|
163
|
+
`signature` field stripped — same primitive the on-device receipt
|
|
164
|
+
signer uses, so one verifier reads both.
|
|
165
|
+
|
|
166
|
+
### `aarmos pack`
|
|
167
|
+
|
|
168
|
+
Certified playbook packs — signed JSON bundles of playbooks + scope
|
|
169
|
+
allowlists (`shell`, `http`, `mcp`). Installing a pack widens allowlists
|
|
170
|
+
for that workspace only.
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
aarmos pack install --source ./pack.json [--workspace .]
|
|
174
|
+
aarmos pack list [--workspace .]
|
|
175
|
+
aarmos pack verify --source ./pack.json [--trusted-pubkey <b64u>]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Installed packs land under `.aarmos/packs/<safe-name>-<version>/pack.json`
|
|
179
|
+
so they're inspectable and diff-able in git.
|
|
180
|
+
|
|
181
|
+
### `aarmos attest` / `aarmos verify`
|
|
182
|
+
|
|
183
|
+
Export a JSON evidence bundle: hash-chained receipts within a time
|
|
184
|
+
window, the playbook hash, the policy config hash, installed pack
|
|
185
|
+
hashes, and a sequential `rootHash`. Optionally Ed25519-signed.
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
aarmos attest --since 24h --out audit.attest.json \
|
|
189
|
+
[--workspace .] [--receipts .aarmos/receipts.jsonl] \
|
|
190
|
+
[--playbook path.json] [--policy aarmos.json] \
|
|
191
|
+
[--sign-key .aarmos/keys/playbook.key.pem]
|
|
192
|
+
|
|
193
|
+
aarmos verify audit.attest.json [--trusted-pubkey <b64u>]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`aarmos verify` re-computes `rootHash` from the embedded receipts and
|
|
197
|
+
checks the signature against the embedded bytes — pure function, no
|
|
198
|
+
network, runnable air-gapped. It attests the file wasn't edited after
|
|
199
|
+
issuance; whether it satisfies a specific audit or regulation is up to
|
|
200
|
+
your auditor.
|
|
201
|
+
|
|
202
|
+
### `aarmos demo`
|
|
203
|
+
|
|
204
|
+
Scripted end-to-end walkthrough of the dry-run pipeline. **No PWA, no
|
|
205
|
+
pairing, no devtools required** — great for CI, screencasts, and sanity
|
|
206
|
+
checks on a fresh machine.
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Options:
|
|
210
|
+
--apply Commit the proposed batch after previewing
|
|
211
|
+
(default: preview only)
|
|
212
|
+
--workspace <path> Workspace to run against (default: cwd)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`aarmos demo` proposes a file edit, a `git status`, and a `node src/hello.js`
|
|
216
|
+
against the current workspace, prints the same diffs + command previews the
|
|
217
|
+
PWA drawer renders, and — only with `--apply` — commits them through the
|
|
218
|
+
same `apply.batch` RPC.
|
|
219
|
+
|
|
220
|
+
## The dry-run safety net
|
|
221
|
+
|
|
222
|
+
Every mutating RPC (`fs.write`, `shell.exec`) is intercepted at the bridge
|
|
223
|
+
boundary. By default, the CLI returns a **preview** payload instead of
|
|
224
|
+
executing:
|
|
225
|
+
|
|
226
|
+
- `fs.write` → before/after content, byte counts, and existed-or-new flag.
|
|
227
|
+
- `shell.exec` → the exact command line, absolute cwd, binary + args.
|
|
228
|
+
|
|
229
|
+
The PWA queues previews into a batch. When the developer approves the
|
|
230
|
+
batch, the PWA calls `apply.batch` with the approved subset, which executes
|
|
231
|
+
atomically (stop-on-first-error) — this is the only path that touches disk.
|
|
232
|
+
|
|
233
|
+
Toggle dry-run per session with the PWA drawer or:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
await bridge.call("session.setDryRun", { on: false }); // live mode
|
|
237
|
+
await bridge.call("session.state"); // { dryRun, workspace }
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Bridge RPC surface
|
|
241
|
+
|
|
242
|
+
| Method | Kind | Notes |
|
|
243
|
+
| -------------------- | --------- | ------------------------------------------------- |
|
|
244
|
+
| `fs.read` | read | utf8 or base64 |
|
|
245
|
+
| `fs.list` | read | |
|
|
246
|
+
| `git.status` | read | |
|
|
247
|
+
| `git.branch` | read | |
|
|
248
|
+
| `fs.write` | mutation | returns `preview` under dry-run |
|
|
249
|
+
| `shell.exec` | mutation | returns `preview` under dry-run; allow-listed bin |
|
|
250
|
+
| `http.call` | mutation | governed; requires `--allow-http <host>` |
|
|
251
|
+
| `mcp.tool` | mutation | governed; requires `--allow-mcp <url>` |
|
|
252
|
+
| `session.setDryRun` | control | flips the interceptor |
|
|
253
|
+
| `session.state` | control | reports posture + workspace |
|
|
254
|
+
| `apply.batch` | control | commits an approved plan atomically |
|
|
255
|
+
|
|
256
|
+
Streamed events (server-push):
|
|
257
|
+
|
|
258
|
+
- `event:fs` — workspace file created / changed / deleted
|
|
259
|
+
- `event:git` — HEAD or branch changed
|
|
260
|
+
|
|
261
|
+
## Security posture (v1 — prosumer)
|
|
262
|
+
|
|
263
|
+
- Localhost-only bind (`127.0.0.1`), never `0.0.0.0`.
|
|
264
|
+
- Origin allow-list on every WS upgrade (PWA production + preview + local
|
|
265
|
+
dev).
|
|
266
|
+
- Per-session pairing token that must be presented on connect. It stays
|
|
267
|
+
valid for the life of the `aarmos dev` process so the PWA can reconnect
|
|
268
|
+
after a dropped socket, but concurrent duplicate connections are refused
|
|
269
|
+
(WS close code 4409).
|
|
270
|
+
- Workspace jail: all `fs.*` paths are resolved and asserted to live under
|
|
271
|
+
the CWD `aarmos dev` was invoked in. Symlink escapes are rejected.
|
|
272
|
+
- Shell exec uses an allow-list (`git`, `npm`, `pnpm`, `bun`, `node`, `ls`,
|
|
273
|
+
`cat`, `rg`, `grep`). Extras via `--allow-shell`.
|
|
274
|
+
- Dry-run is ON by default. Every write and shell command surfaces in the
|
|
275
|
+
PWA drawer with a full diff or command preview before touching disk.
|
|
276
|
+
- Process guards: uncaught exceptions and unhandled rejections are logged
|
|
277
|
+
and swallowed — the bridge stays up so the developer can recover.
|
|
278
|
+
|
|
279
|
+
The enterprise trust model (org policy overlay, signed CLI binary,
|
|
280
|
+
device-code pairing, admin audit) is intentionally out of scope for v1.
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
2
|
+
import { handleRpc } from "./rpc/router.js";
|
|
3
|
+
import { startFileWatcher } from "./watchers/files.js";
|
|
4
|
+
import { startGitWatcher } from "./watchers/git.js";
|
|
5
|
+
import { createSession } from "./session.js";
|
|
6
|
+
export async function startBridge(opts) {
|
|
7
|
+
// Process-level safety net: an unhandled socket/JSON error must never
|
|
8
|
+
// take the whole bridge down. The dev is likely mid-flow in another tab.
|
|
9
|
+
installProcessGuards();
|
|
10
|
+
const wss = new WebSocketServer({
|
|
11
|
+
host: "127.0.0.1",
|
|
12
|
+
port: opts.port,
|
|
13
|
+
// Origin check — reject anything outside the allow list.
|
|
14
|
+
verifyClient: (info, cb) => {
|
|
15
|
+
const origin = info.origin ?? "";
|
|
16
|
+
if (opts.allowedOrigins.includes(origin))
|
|
17
|
+
return cb(true);
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.warn(`[aarmos] rejected origin: ${origin}`);
|
|
20
|
+
cb(false, 403, "origin not allowed");
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
wss.on("error", (err) => {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.warn(`[aarmos] server error (continuing): ${err.message}`);
|
|
26
|
+
});
|
|
27
|
+
// Track live connection so we can reject concurrent duplicates while still
|
|
28
|
+
// allowing the developer to close a tab and re-open with the same pairing
|
|
29
|
+
// (e.g. browser crash, laptop lid). Token remains the shared secret; the
|
|
30
|
+
// localhost bind + origin allow-list are the real trust boundary.
|
|
31
|
+
let activeClient = null;
|
|
32
|
+
wss.on("connection", (ws, req) => {
|
|
33
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
34
|
+
const token = url.searchParams.get("token");
|
|
35
|
+
if (!token || token !== opts.pairingToken) {
|
|
36
|
+
ws.close(4401, "invalid pairing token");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (activeClient && activeClient.readyState === WebSocket.OPEN) {
|
|
40
|
+
ws.close(4409, "another client is already paired");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
activeClient = ws;
|
|
44
|
+
const session = createSession({
|
|
45
|
+
workspace: opts.workspace,
|
|
46
|
+
extraShellAllow: opts.extraShellAllow,
|
|
47
|
+
sqlAllow: opts.sqlAllow,
|
|
48
|
+
readAllow: opts.readAllow,
|
|
49
|
+
});
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log("◆ PWA paired");
|
|
52
|
+
const send = (msg) => {
|
|
53
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
54
|
+
try {
|
|
55
|
+
ws.send(JSON.stringify(msg));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.warn(`[aarmos] send failed (client likely gone): ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const stopFiles = startFileWatcher(session, (evt) => send({ type: "event", channel: "fs", event: evt }));
|
|
64
|
+
const stopGit = startGitWatcher(session, (evt) => send({ type: "event", channel: "git", event: evt }));
|
|
65
|
+
ws.on("message", async (raw) => {
|
|
66
|
+
let msg;
|
|
67
|
+
try {
|
|
68
|
+
msg = JSON.parse(String(raw));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return send({ type: "error", error: "invalid json" });
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const res = await handleRpc(session, msg);
|
|
75
|
+
send(res);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// Never let an RPC handler bubble up and kill the process.
|
|
79
|
+
send({ type: "error", error: err.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// Per-socket error handler — closed lids, killed tabs, and network
|
|
83
|
+
// resets all surface here. Log softly, let 'close' run cleanup.
|
|
84
|
+
ws.on("error", (err) => {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.warn(`[aarmos] socket error: ${err.message}`);
|
|
87
|
+
});
|
|
88
|
+
ws.on("close", (code, reason) => {
|
|
89
|
+
stopFiles();
|
|
90
|
+
stopGit();
|
|
91
|
+
if (activeClient === ws)
|
|
92
|
+
activeClient = null;
|
|
93
|
+
const why = reason?.toString() || `code ${code}`;
|
|
94
|
+
// eslint-disable-next-line no-console
|
|
95
|
+
console.log(`◇ Client disconnected (${why}). Standing by on ${opts.port} — rerun pairing from the PWA to reconnect.`);
|
|
96
|
+
});
|
|
97
|
+
send({
|
|
98
|
+
type: "hello",
|
|
99
|
+
workspace: opts.workspace,
|
|
100
|
+
capabilities: session.capabilities,
|
|
101
|
+
dryRun: session.dryRun,
|
|
102
|
+
protocol: "aarmos-bridge/1",
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
await new Promise((resolve) => wss.once("listening", () => resolve()));
|
|
106
|
+
const address = wss.address();
|
|
107
|
+
const boundPort = typeof address === "object" && address ? address.port : opts.port;
|
|
108
|
+
return { port: boundPort, url: `ws://127.0.0.1:${boundPort}` };
|
|
109
|
+
}
|
|
110
|
+
let guardsInstalled = false;
|
|
111
|
+
function installProcessGuards() {
|
|
112
|
+
if (guardsInstalled)
|
|
113
|
+
return;
|
|
114
|
+
guardsInstalled = true;
|
|
115
|
+
process.on("uncaughtException", (err) => {
|
|
116
|
+
// eslint-disable-next-line no-console
|
|
117
|
+
console.warn(`[aarmos] uncaughtException (kept alive): ${err.message}`);
|
|
118
|
+
});
|
|
119
|
+
process.on("unhandledRejection", (reason) => {
|
|
120
|
+
// eslint-disable-next-line no-console
|
|
121
|
+
console.warn(`[aarmos] unhandledRejection (kept alive): ${String(reason)}`);
|
|
122
|
+
});
|
|
123
|
+
process.on("SIGINT", () => {
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.log("\n◇ Shutting down aarmos bridge. bye.");
|
|
126
|
+
process.exit(0);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAiB7C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAmB;IACnD,sEAAsE;IACtE,yEAAyE;IACzE,oBAAoB,EAAE,CAAC;IAEvB,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC;QAC9B,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,yDAAyD;QACzD,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE;YACzB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;YACjC,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;YAC1D,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAC;YACpD,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAC;QACvC,CAAC;KACF,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACtB,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,uCAAuC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,0EAA0E;IAC1E,yEAAyE;IACzE,kEAAkE;IAClE,IAAI,YAAY,GAAqB,IAAI,CAAC;IAE1C,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;QAC/B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1C,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QACD,IAAI,YAAY,IAAI,YAAY,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC/D,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,kCAAkC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,YAAY,GAAG,EAAE,CAAC;QAElB,MAAM,OAAO,GAAY,aAAa,CAAC;YACrC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;QAEH,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAE5B,MAAM,IAAI,GAAG,CAAC,GAAY,EAAE,EAAE;YAC5B,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACrC,IAAI,CAAC;oBACH,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC/B,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sCAAsC;oBACtC,OAAO,CAAC,IAAI,CAAC,8CAA+C,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;gBACvF,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAClD,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CACnD,CAAC;QACF,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAC/C,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CACpD,CAAC;QAEF,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7B,IAAI,GAAY,CAAC;YACjB,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YACxD,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBAC1C,IAAI,CAAC,GAAG,CAAC,CAAC;YACZ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,2DAA2D;gBAC3D,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,mEAAmE;QACnE,gEAAgE;QAChE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAC5B,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,0BAA0B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;YAC9C,SAAS,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;YACV,IAAI,YAAY,KAAK,EAAE;gBAAE,YAAY,GAAG,IAAI,CAAC;YAC7C,MAAM,GAAG,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,QAAQ,IAAI,EAAE,CAAC;YACjD,sCAAsC;YACtC,OAAO,CAAC,GAAG,CAAC,0BAA0B,GAAG,qBAAqB,IAAI,CAAC,IAAI,6CAA6C,CAAC,CAAC;QACxH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,iBAAiB;SAC5B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAE7E,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAEpF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,kBAAkB,SAAS,EAAE,EAAE,CAAC;AACjE,CAAC;AAED,IAAI,eAAe,GAAG,KAAK,CAAC;AAC5B,SAAS,oBAAoB;IAC3B,IAAI,eAAe;QAAE,OAAO;IAC5B,eAAe,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE;QACtC,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,4CAA4C,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;QAC1C,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,6CAA6C,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// `aarmos attest` and `aarmos attest verify` — audit-ready export.
|
|
2
|
+
//
|
|
3
|
+
// The attestation bundle is the artifact enterprises actually pay for:
|
|
4
|
+
// a single self-contained JSON file with signed receipts, the policy
|
|
5
|
+
// hash, playbook hash, installed pack hashes, and a merkle-style root
|
|
6
|
+
// hash — verifiable air-gapped by anyone.
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { buildAttestation, parseSince, readAttestation, signAttestation, verifyAttestation, writeAttestation, } from "../lib/attest.js";
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
const log = (s) => console.log(s);
|
|
12
|
+
export async function runAttest(opts) {
|
|
13
|
+
const workspace = path.resolve(opts.workspace ?? process.cwd());
|
|
14
|
+
const since = opts.since ? parseSince(opts.since) : new Date(0);
|
|
15
|
+
const bundle = await buildAttestation({
|
|
16
|
+
workspace,
|
|
17
|
+
receiptsFile: path.resolve(opts.receipts),
|
|
18
|
+
since,
|
|
19
|
+
until: new Date(),
|
|
20
|
+
playbookFile: opts.playbook,
|
|
21
|
+
policyFile: opts.policy ?? path.join(workspace, "aarmos.json"),
|
|
22
|
+
packsDir: path.join(workspace, ".aarmos/packs"),
|
|
23
|
+
});
|
|
24
|
+
const finalBundle = opts.signKey
|
|
25
|
+
? await signAttestation(bundle, await fs.readFile(path.resolve(opts.signKey), "utf8"))
|
|
26
|
+
: bundle;
|
|
27
|
+
await writeAttestation(opts.out, finalBundle);
|
|
28
|
+
log("");
|
|
29
|
+
log(" aarmos attest");
|
|
30
|
+
log(` window : ${finalBundle.since} → ${finalBundle.until}`);
|
|
31
|
+
log(` receipts : ${finalBundle.receipts.length}`);
|
|
32
|
+
log(` playbook : ${finalBundle.playbook ? finalBundle.playbook.name + " (" + finalBundle.playbook.hash.slice(0, 16) + "…)" : "(none)"}`);
|
|
33
|
+
log(` policy : ${finalBundle.policyHash ? finalBundle.policyHash.slice(0, 16) + "…" : "(none)"}`);
|
|
34
|
+
log(` packs : ${finalBundle.packs.length}`);
|
|
35
|
+
log(` rootHash : ${finalBundle.rootHash}`);
|
|
36
|
+
log(` signature : ${finalBundle.signature ? finalBundle.signature.pubKey : "(unsigned)"}`);
|
|
37
|
+
log(` written to : ${path.resolve(opts.out)}`);
|
|
38
|
+
log("");
|
|
39
|
+
}
|
|
40
|
+
export async function runAttestVerify(opts) {
|
|
41
|
+
const bundle = await readAttestation(opts.file);
|
|
42
|
+
const res = verifyAttestation(bundle);
|
|
43
|
+
log("");
|
|
44
|
+
log(` aarmos attest verify — ${opts.file}`);
|
|
45
|
+
log(` receipts : ${bundle.receipts.length}`);
|
|
46
|
+
log(` rootHash : ${bundle.rootHash}`);
|
|
47
|
+
log(` result : ${res.ok ? "OK" : "INVALID"}${res.reason ? " " + res.reason : ""}`);
|
|
48
|
+
if (bundle.signature) {
|
|
49
|
+
log(` signer : ${bundle.signature.pubKey}`);
|
|
50
|
+
if (opts.trustedPubkey && bundle.signature.pubKey !== opts.trustedPubkey) {
|
|
51
|
+
log(" trust : REJECTED — signer not in trusted set");
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
log(" signer : (unsigned)");
|
|
57
|
+
}
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
log("");
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=attest.js.map
|