@tealbrick/avm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tealbrick contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PROVENANCE.md ADDED
@@ -0,0 +1,38 @@
1
+ # Provenance and source contract
2
+
3
+ Implementation authored independently for Tealbrick, MIT. No Kybernesis package dependency or copied runtime source. Upstream was inspected; this is not a source-unexposed clean-room claim.
4
+
5
+ Source authority inspected 2026-09-08: `/Users/puma/work/active/kybernesis-avm`, HEAD `860fb32ef70813760c99631231424fbe0ddde3b1`; `agentvm/bin/avm.py` reports source AVM version 0.12.9. Working checkout may contain additional uncommitted changes; the cited source file, not installed npm CLI metadata, defines this host contract.
6
+
7
+ - `agentvm/bin/avm.py`: `parser()` and `add_identity_actions()` define host `--json`, `worker ls/status/create/start/stop/promote/destroy/exec`, runs/events, monitor, inventory, bundle and capability manifest. Client uses native worker commands rather than npm `agent spawn` aliases.
8
+ - Same file `main()` maps worker create to kind=worker, profile/mode/catalog/capabilities; exec passes normalized remainder argv. Success envelope is `{api_version:'agentvm/v1',result:...}`; error envelope contains `error.message`; exec returns guest returncode as CLI exit code.
9
+ - `agentvm/lib/agentvmd.py`: source of daemon policy/execution and run metadata. No unsupported daemon API has been invented and no root socket is exposed.
10
+ - Eve 0.49.0 installed docs `docs/extensions.md` plus `docs/guides/dynamic-capabilities.md` and public ToolContext declaration informed extension layout, builder-managed exports and abortSignal propagation. `eve extension init tealbrick-avm-scaffold-20260908` was run in disposable `/tmp` as the scaffold contract reference; package implementation is separately authored.
11
+
12
+ Environment boundary: no Puma-local AVM invocation, no SSH/live AVM invocation, no remote mutation, no credentials copied. Mock results do not establish deployed host compatibility. Upstream CLI may evolve without changing the envelope version, so consumers should pin and verify their host release independently.
13
+
14
+ ## AVMM update — 2026-09-15
15
+
16
+ Current compatibility authority supersedes the historical checkout above:
17
+ [Martin-Atrin/avmm, codex/avmm-final](https://github.com/Martin-Atrin/avmm/tree/75078743b9bfc2ba268f1fdeea74f298983dfac4),
18
+ release `0.13.0-alpha.2`. Inspected in `/Users/puma/work/upstreams/avmm` at that exact
19
+ revision. `main` still reports 0.12.8 and is not the selected target.
20
+
21
+ - `agentvm/bin/avm.py`: current flags for resource pool, local workspaces/users,
22
+ ownership/shares, resident/wake/cool, provider-neutral ingress, event filters,
23
+ worker resize and all worker creation selections.
24
+ - `agentvm/bin/agentvm-remote-shell.py`: restricted paired SSH command grammar,
25
+ attestation requirement and `/usr/local/bin/avm` execution boundary.
26
+ - `agentvm/lib/agentvmd.py`: authoritative verified actor, share action allowlist,
27
+ auth selection restrictions, queue policy and resource field semantics.
28
+ - `packages/agentvm-cli/src/commands.mjs`, `remote.mjs`, `config.mjs`: operator
29
+ aliases, quoting contract and current single-connection configuration. Named
30
+ Teal Brick connection profiles are newly authored convenience configuration;
31
+ they are not attributed to an upstream multi-host profile API.
32
+ - `docs/releases/0.13.0-alpha.2.md`: upstream ingress acceptance history, distinct
33
+ from this package's tests and the blocked Neuu preflight.
34
+
35
+ No upstream runtime source was copied into this package. Tests construct mock
36
+ responses and execute no AVM process on Puma. The attempted SSH Neuu version probe
37
+ failed strict host-key verification before any remote command ran. No key was
38
+ replaced, runtime changed, credential copied or tenant operation executed.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # @tealbrick/avm
2
+
3
+ Typed host-owned AVMM transport and opt-in Eve capabilities. Node 24+, Eve 0.55.0.
4
+ Tested source contract: **AVMM 0.13.0-alpha.2**, `codex/avmm-final` at
5
+ `75078743b9bfc2ba268f1fdeea74f298983dfac4`. The host executable remains
6
+ `/usr/local/bin/avm`; the JSON envelope remains `agentvm/v1`.
7
+
8
+ ## Connection and identity profiles
9
+
10
+ ```ts
11
+ import avm from '@tealbrick/avm/eve';
12
+ export default avm({
13
+ selectedProfile: 'alice',
14
+ profiles: {
15
+ alice: {
16
+ mode: 'paired', target: 'agentvm-client@host.example',
17
+ identityFile: '/run/keys/alice',
18
+ },
19
+ bob: {
20
+ mode: 'paired', target: 'agentvm-client@host.example',
21
+ identityFile: '/run/keys/bob',
22
+ },
23
+ },
24
+ });
25
+ ```
26
+
27
+ The operator selects one profile. Model inputs cannot switch host, key, user,
28
+ workspace or policy. Named **connection profiles are a Teal Brick convenience**;
29
+ AVMM's **resource profiles** are `light`, `small`, `standard`, `browser`.
30
+ Neither is itself a tenant security boundary. AVMM verifies the paired SSH key's
31
+ host-managed attestation, local user/workspace binding, ownership and shares.
32
+ A workspace name in an administration request identifies the object to manage;
33
+ it does not authenticate the caller.
34
+
35
+ - `paired`: speaks AVMM's restricted SSH ForceCommand protocol. Requires `agentvm-client@host` and a bound
36
+ `identityFile`; rejects sudo and identity-token overrides. Uses only that key,
37
+ disables SSH agent fallback, skips SSH config (`-F /dev/null`), and therefore
38
+ requires an actual resolvable hostname/IP, not an SSH-only alias. Host keys must
39
+ already be trusted. No arbitrary remote Python or shell is required.
40
+ - `operator` (legacy default): constant remote Python subprocess, with argv sent
41
+ as JSON stdin; optional `sudo: true`. Uses the configured SSH account's authority.
42
+ This can be host administrator authority and is not a tenant sandbox.
43
+ Optional `identityTokenFile` references an existing root-managed **remote** file;
44
+ the package never reads or copies the token. `identityFile` is an SSH key path,
45
+ never the key contents.
46
+
47
+ Both execute AVM only on the remote host, preserve strict host-key checking,
48
+ disable forwarding, exclude ambient provider environment, and never load a local
49
+ AVM config or run local AVM. No pairing, credential creation or host installation
50
+ occurs. Backward-compatible single connection: `{target:'nucbox', sudo:true}`.
51
+
52
+ Harness-neutral consumers use `@tealbrick/avm/client`:
53
+
54
+ ```ts
55
+ import {AvmClient, SshTransport, avmConfigSchema} from '@tealbrick/avm/client';
56
+ const config = avmConfigSchema.parse({
57
+ target: 'agentvm-client@host.example', mode: 'paired', identityFile: '/run/keys/alice',
58
+ });
59
+ const client = new AvmClient(new SshTransport(config), config);
60
+ const pool = await client.request({operation: 'poolStatus'});
61
+ ```
62
+
63
+ ## Reads
64
+
65
+ `doctor`, `inventory`, `workers`, `workerStatus`, `bundles`, `monitor`,
66
+ `capabilities`, `runs`, `runStatus`, `events`, `poolStatus`, `poolPressure`,
67
+ `poolQueue`, `poolPolicy`, `workspaces`, `workspace`, `users`, `user`, `owner`,
68
+ `shares`, `resident`, `ingress`. Events accept `worker`, `event`, `runId` and
69
+ bounded `limit`; ingress accepts an optional worker. The host can deny reads
70
+ according to the authenticated caller. Successful results are generic JSON,
71
+ not a promise of stable upstream result schemas or a replacement for consumer
72
+ authorization/output handling.
73
+
74
+ ## Explicit action grants
75
+
76
+ No action tool exists without configured grants. Existing `lifecycle` and fixed
77
+ `commands` grants remain supported. Spawn selections now cover all current worker
78
+ create flags:
79
+
80
+ ```ts
81
+ const policy = {
82
+ spawns: [{
83
+ worker: 'reviewer', profile: 'light', mode: 'durable',
84
+ harnesses: ['eve', 'codex'], bundles: ['base'],
85
+ mcps: ['approved-mcp'], plugins: ['approved-plugin'],
86
+ auth: ['codex=shared'], capabilities: ['browser:public'],
87
+ }],
88
+ administration: [{
89
+ name: 'wake-reviewer',
90
+ request: {operation: 'wake', worker: 'reviewer', idempotencyKey: 'task-123'},
91
+ }],
92
+ };
93
+ // Attach policy to the connection/profile. Caller chooses only the exact grant:
94
+ // client.request({operation:'configured', action:'wake-reviewer'});
95
+ ```
96
+
97
+ Catalog selections are names, not package installation or credentials.
98
+ Auth choices are `provider=none`, `provider=local`, and `codex=shared`.
99
+ AVMM currently rejects `scoped` and shared auth for other providers; this adapter
100
+ rejects those too. Provider secrets remain with AVMM's configured credential owner.
101
+
102
+ Named `administration` grants accept strictly validated requests:
103
+
104
+ | Operation | Configured fields / behavior |
105
+ |---|---|
106
+ | `resize` | worker, memoryTargetMiB |
107
+ | `workspacePut` / `workspaceDisable` | workspace; put: displayName, weight, concurrentHeavyWork, queuePolicy |
108
+ | `tenantSync` | workspace, displayName, principalId, issuer, subject, role, same queue policy fields |
109
+ | `userPut` | user, workspace, role |
110
+ | `ownerSet` | worker, workspace, optional user |
111
+ | `shareGrant` / `shareRevoke` | worker; grant: grantee `{kind:'workspace'|'user',id}`, allow; revoke: shareId |
112
+ | `poolPolicySet` | cpuPercent, memoryMaxBytes, memoryHighBytes, hostMemoryReserveBytes, launchReserveBytes |
113
+ | `residentSet` / `cool` | worker |
114
+ | `wake` | worker, required idempotencyKey, optional source/reason/externalEventId |
115
+ | `ingressPut` | provider, externalIdentity, worker, optional channelScope/metadataFile |
116
+ | `ingressRevoke` | identityId |
117
+ | `ingressWake` | provider, externalIdentity, eventId, optional channelScope/payloadFile/correlationId |
118
+ | `ingressComplete` | wakeId, optional error; record delivery only after actual delivery |
119
+
120
+ File fields reference trusted remote files; contents are not put into argv.
121
+ These grants are operator configuration, not browser/model input. They cannot
122
+ bypass daemon permissions. Share actions use AVMM's allowlist and exclude destroy
123
+ and ownership transfer. Owner changes, workspace disable, pool changes, cooling,
124
+ and disk destruction have real side effects; grant only the intended operation.
125
+ A fixed ingress/wake grant represents a specific event. A provider adapter needs
126
+ fresh trusted grants for subsequent events and remains responsible for delivery.
127
+ No retries are automatic. Cancellation stops SSH; remote work may continue.
128
+ Reconcile before retrying; wake event keys do not make all CLI commands idempotent.
129
+
130
+ ## Eve and kit
131
+
132
+ Importing dependencies contributes nothing. An explicit mount contributes inspect;
133
+ a dynamic action is exposed only with grants. It rechecks current configuration
134
+ on each execution, including after profile switching or revocation.
135
+
136
+ The kit accepts the same configuration under `avm`. `tealbrick setup` can select
137
+ an existing named profile or configure paired/operator SSH. `tealbrick apply
138
+ config.json` supports full profiles and policies. Provider transport is unrelated
139
+ to AVMM's tenant authorization. No channels, hooks, miniapps or credential tools
140
+ are incidentally mounted.
141
+
142
+ ## Evidence and remaining scope
143
+
144
+ The 2026-09-15 suite passes 95 package tests, including 22 AVM tests, plus isolated
145
+ kit tarball install, strict consumer type checking and Eve discovery/build.
146
+ Coverage includes denial, immutable grants, profile switching/revocation,
147
+ forced-command quoting, transport identity isolation, timeouts/output bounds,
148
+ legacy kit receipts, activation/deactivation and exact command construction.
149
+ All action/transport tests are disposable and mocked; no local AVM is executed.
150
+
151
+ Live Neuu verification was blocked by a changed SSH host key before connection.
152
+ No tenant isolation, VM creation, cold wake or message delivery was exercised live
153
+ by this package update. Earlier 0.12.9 doctor receipts are historical only.
154
+
155
+ This remains a bounded adapter, not the entire operator CLI. Host installation,
156
+ pairing, client-key enrollment, trusted VM management, interactive SSH, provider
157
+ login/leases, browser/LCM, role deployment and schedules remain operator-owned.
158
+ The package does not implement a Buzz/Telegram adapter or automatic Portal agent
159
+ registration. Packages are unpublished; neither npm release nor deployment is
160
+ implied by tests.
161
+
162
+ Deployment authority correction (2026-09-15): live AVMM/Eve/Teal Brick packages
163
+ belong exclusively on Nucbox, the Fedora N150. Neuu attempts above are historical
164
+ and were aimed at the wrong host. Nucbox is reachable and reports AVMM
165
+ 0.13.0-alpha.2; its resident Aura still runs Eve 0.49.0. No live upgrade is implied.
@@ -0,0 +1,232 @@
1
+ import { fileURLToPath as __eveFileURLToPath } from "node:url";
2
+ import { dirname as __eveDirname } from "node:path";
3
+ import { createRequire as __eveCreateRequire } from "node:module";
4
+ const __filename = __eveFileURLToPath(import.meta.url);
5
+ __eveDirname(__filename);
6
+ __eveCreateRequire(import.meta.url);
7
+ import { defineExtension } from "eve/extension";
8
+ import { z } from "zod";
9
+ const id = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/);
10
+ const workspace = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
11
+ const user = z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/);
12
+ const nonnegative = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
13
+ const publicValue = z.string().min(1).max(512).regex(/^[^\0\r\n]+$/);
14
+ const provider = z.string().regex(/^[a-z][a-z0-9._-]{0,63}$/);
15
+ const positive = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
16
+ const path = z.string().regex(/^\/[A-Za-z0-9_./ -]+$/).max(4096);
17
+ const queue = {
18
+ weight: positive.optional(),
19
+ concurrentHeavyWork: nonnegative.optional(),
20
+ queuePolicy: z.enum([
21
+ "fair",
22
+ "fifo",
23
+ "reject"
24
+ ]).optional()
25
+ };
26
+ const administrationSchema = z.discriminatedUnion("operation", [
27
+ z.object({
28
+ operation: z.literal("tenantSync"),
29
+ workspace,
30
+ displayName: publicValue.optional(),
31
+ principalId: user.optional(),
32
+ issuer: z.url().optional(),
33
+ subject: publicValue.optional(),
34
+ role: z.enum([
35
+ "member",
36
+ "admin",
37
+ "operator"
38
+ ]).default("member"),
39
+ ...queue
40
+ }).strict(),
41
+ z.object({
42
+ operation: z.literal("ingressPut"),
43
+ provider,
44
+ externalIdentity: publicValue,
45
+ worker: id,
46
+ channelScope: publicValue.optional(),
47
+ metadataFile: path.optional()
48
+ }).strict(),
49
+ z.object({
50
+ operation: z.literal("ingressRevoke"),
51
+ identityId: id
52
+ }).strict(),
53
+ z.object({
54
+ operation: z.literal("ingressWake"),
55
+ provider,
56
+ externalIdentity: publicValue,
57
+ eventId: publicValue,
58
+ channelScope: publicValue.optional(),
59
+ payloadFile: path.optional(),
60
+ correlationId: publicValue.optional()
61
+ }).strict(),
62
+ z.object({
63
+ operation: z.literal("ingressComplete"),
64
+ wakeId: id,
65
+ error: publicValue.optional()
66
+ }).strict(),
67
+ z.object({
68
+ operation: z.literal("resize"),
69
+ worker: id,
70
+ memoryTargetMiB: positive
71
+ }).strict(),
72
+ z.object({
73
+ operation: z.literal("workspacePut"),
74
+ workspace,
75
+ displayName: z.string().min(1).max(256).regex(/^[^\0\r\n]+$/).optional(),
76
+ ...queue
77
+ }).strict(),
78
+ z.object({
79
+ operation: z.literal("workspaceDisable"),
80
+ workspace
81
+ }).strict(),
82
+ z.object({
83
+ operation: z.literal("userPut"),
84
+ user,
85
+ workspace,
86
+ role: z.enum([
87
+ "member",
88
+ "admin",
89
+ "operator"
90
+ ])
91
+ }).strict(),
92
+ z.object({
93
+ operation: z.literal("ownerSet"),
94
+ worker: id,
95
+ workspace,
96
+ user: user.optional()
97
+ }).strict(),
98
+ z.object({
99
+ operation: z.literal("shareGrant"),
100
+ worker: id,
101
+ grantee: z.discriminatedUnion("kind", [z.object({
102
+ kind: z.literal("workspace"),
103
+ id: workspace
104
+ }).strict(), z.object({
105
+ kind: z.literal("user"),
106
+ id: user
107
+ }).strict()]),
108
+ allow: z.array(z.enum([
109
+ "status",
110
+ "exec",
111
+ "start",
112
+ "stop",
113
+ "ssh",
114
+ "logs",
115
+ "events",
116
+ "runs",
117
+ "assignments",
118
+ "schedule",
119
+ "capabilities",
120
+ "auth",
121
+ "leases",
122
+ "resident",
123
+ "wake",
124
+ "cool",
125
+ "list",
126
+ "inventory",
127
+ "monitor",
128
+ "roles",
129
+ "ingress"
130
+ ])).min(1)
131
+ }).strict(),
132
+ z.object({
133
+ operation: z.literal("shareRevoke"),
134
+ worker: id,
135
+ shareId: id
136
+ }).strict(),
137
+ z.object({
138
+ operation: z.literal("residentSet"),
139
+ worker: id
140
+ }).strict(),
141
+ z.object({
142
+ operation: z.literal("wake"),
143
+ worker: id,
144
+ reason: z.string().min(1).max(256).optional(),
145
+ source: z.enum([
146
+ "cli",
147
+ "schedule",
148
+ "control-plane",
149
+ "delegation"
150
+ ]).default("cli"),
151
+ externalEventId: id.optional(),
152
+ idempotencyKey: id
153
+ }).strict(),
154
+ z.object({
155
+ operation: z.literal("cool"),
156
+ worker: id
157
+ }).strict(),
158
+ z.object({
159
+ operation: z.literal("poolPolicySet"),
160
+ cpuPercent: positive.optional(),
161
+ memoryMaxBytes: positive.optional(),
162
+ memoryHighBytes: positive.optional(),
163
+ hostMemoryReserveBytes: nonnegative.optional(),
164
+ launchReserveBytes: nonnegative.optional()
165
+ }).strict()
166
+ ]);
167
+ const policySchema = z.object({
168
+ lifecycle: z.array(z.object({
169
+ worker: id,
170
+ operations: z.array(z.enum([
171
+ "start",
172
+ "stop",
173
+ "promote",
174
+ "destroy"
175
+ ]))
176
+ }).strict()).default([]),
177
+ spawns: z.array(z.object({
178
+ worker: id,
179
+ profile: z.enum([
180
+ "light",
181
+ "small",
182
+ "standard",
183
+ "browser"
184
+ ]),
185
+ mode: z.enum(["ephemeral", "durable"]),
186
+ harnesses: z.array(z.string()).optional(),
187
+ bundles: z.array(z.string()).optional(),
188
+ capabilities: z.array(z.string()).optional(),
189
+ mcps: z.array(z.string()).optional(),
190
+ plugins: z.array(z.string()).optional(),
191
+ auth: z.array(z.string().regex(/^[a-z][a-z0-9_-]*=(none|local)$|^codex=shared$/)).optional()
192
+ }).strict()).default([]),
193
+ commands: z.array(z.object({
194
+ worker: id,
195
+ command: id,
196
+ argv: z.array(z.string().max(8192).refine((v) => !v.includes("\0"))).min(1).max(100)
197
+ }).strict()).default([]),
198
+ administration: z.array(z.object({
199
+ name: id,
200
+ request: administrationSchema
201
+ }).strict()).default([])
202
+ }).strict();
203
+ const connectionSchema = z.object({
204
+ target: z.string().regex(/^(?:[A-Za-z0-9_][A-Za-z0-9_.-]*@)?[A-Za-z0-9][A-Za-z0-9.-]*$/),
205
+ mode: z.enum(["operator", "paired"]).default("operator"),
206
+ sudo: z.boolean().default(false),
207
+ identityFile: path.optional(),
208
+ identityTokenFile: path.optional(),
209
+ timeoutMs: z.number().int().min(1).max(3e5).default(3e4),
210
+ policy: policySchema.default({
211
+ lifecycle: [],
212
+ spawns: [],
213
+ commands: [],
214
+ administration: []
215
+ })
216
+ }).strict().refine((c) => c.mode !== "paired" || c.target.startsWith("agentvm-client@") && !c.sudo && !!c.identityFile && !c.identityTokenFile, { message: "Paired mode requires agentvm-client@host and its bound SSH identity file and disallows sudo or identity token overrides" });
217
+ const avmConfigSchema = z.union([connectionSchema, z.object({
218
+ selectedProfile: id,
219
+ profiles: z.record(id, connectionSchema)
220
+ }).strict().transform((c, ctx) => {
221
+ const selected = Object.hasOwn(c.profiles, c.selectedProfile) ? c.profiles[c.selectedProfile] : void 0;
222
+ if (!selected) {
223
+ ctx.addIssue({
224
+ code: "custom",
225
+ message: "Unknown AVM connection profile"
226
+ });
227
+ return z.NEVER;
228
+ }
229
+ return selected;
230
+ })]);
231
+ var extension_default = defineExtension({ config: avmConfigSchema });
232
+ export { administrationSchema as n, extension_default as t };