sept-agent 0.1.3 → 0.1.4
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/CONTRIBUTING.md +8 -4
- package/README.md +33 -8
- package/dist/src/cli/commands.js +4 -0
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/hands-docker.d.ts +7 -0
- package/dist/src/cli/hands-docker.js +182 -0
- package/dist/src/cli/hands-docker.js.map +1 -0
- package/dist/src/cli/hands-finish.d.ts +2 -0
- package/dist/src/cli/hands-finish.js +164 -0
- package/dist/src/cli/hands-finish.js.map +1 -0
- package/dist/src/cli/hands-owner.d.ts +10 -0
- package/dist/src/cli/hands-owner.js +83 -0
- package/dist/src/cli/hands-owner.js.map +1 -0
- package/dist/src/cli/hands-record.d.ts +33 -0
- package/dist/src/cli/hands-record.js +164 -0
- package/dist/src/cli/hands-record.js.map +1 -0
- package/dist/src/cli/hands.d.ts +3 -0
- package/dist/src/cli/hands.js +169 -0
- package/dist/src/cli/hands.js.map +1 -0
- package/dist/src/cli/options.d.ts +1 -0
- package/dist/src/cli/options.js +2 -1
- package/dist/src/cli/options.js.map +1 -1
- package/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/execution/index.d.ts +4 -0
- package/dist/src/execution/index.js +4 -0
- package/dist/src/execution/index.js.map +1 -1
- package/examples/incident-investigator/README.md +4 -4
- package/examples/incident-investigator/package.json +1 -1
- package/examples/node-app/README.md +4 -4
- package/examples/node-app/package.json +1 -1
- package/package.json +1 -8
- package/src/cli/commands.ts +4 -0
- package/src/cli/hands-docker.ts +198 -0
- package/src/cli/hands-finish.ts +204 -0
- package/src/cli/hands-owner.ts +118 -0
- package/src/cli/hands-record.ts +213 -0
- package/src/cli/hands.ts +207 -0
- package/src/cli/options.ts +3 -1
- package/src/cli.ts +1 -1
- package/src/execution/index.ts +10 -0
- package/docs/architecture.md +0 -168
- package/docs/index.md +0 -55
- package/docs/models.md +0 -78
- package/docs/observability.md +0 -63
- package/docs/pi-contract.md +0 -482
- package/docs/sdk.md +0 -180
- package/docs/worker-api.md +0 -138
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/** Private ownership receipts for the optional local hands setup commands. */
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
chmodSync,
|
|
8
|
+
openSync,
|
|
9
|
+
fsyncSync,
|
|
10
|
+
closeSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { dirname, isAbsolute, join, normalize } from "node:path";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { atomicWrite, SeptError } from "../durable/index.js";
|
|
15
|
+
|
|
16
|
+
/** Fixed paths returned directly as existing createWorker options; no credential is generated. */
|
|
17
|
+
export interface HandsPaths {
|
|
18
|
+
stateRoot: string;
|
|
19
|
+
agentDir: string;
|
|
20
|
+
workspaceRoot: string;
|
|
21
|
+
handsConfigurationPath: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Durable exclusive setup identity survives partial creation and never grants task authority. */
|
|
25
|
+
export interface HandsReceipt {
|
|
26
|
+
version: 1;
|
|
27
|
+
owner: string;
|
|
28
|
+
engineSocket: string;
|
|
29
|
+
socketIdentity: string;
|
|
30
|
+
daemonId: string;
|
|
31
|
+
image: string;
|
|
32
|
+
containerId?: string;
|
|
33
|
+
generation?: string;
|
|
34
|
+
phase: "prepared" | "creating" | "running" | "ready" | "failed" | "finished";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Derive the sole supported fresh-instance layout. */
|
|
38
|
+
export function handsPaths(root: string): HandsPaths {
|
|
39
|
+
return {
|
|
40
|
+
stateRoot: join(root, "state"),
|
|
41
|
+
agentDir: join(root, "agent"),
|
|
42
|
+
workspaceRoot: join(root, "workspace"),
|
|
43
|
+
handsConfigurationPath: join(root, "hands.json"),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Refuse lexical aliases and mutable ancestors before creating or reading ownership. */
|
|
48
|
+
export function trustedHandsRoot(value: string | undefined, fresh = false): string {
|
|
49
|
+
if (
|
|
50
|
+
value === undefined ||
|
|
51
|
+
!isAbsolute(value) ||
|
|
52
|
+
value.length > 4096 ||
|
|
53
|
+
/[\x00-\x1f,]/.test(value)
|
|
54
|
+
) {
|
|
55
|
+
throw new SeptError(
|
|
56
|
+
"hands requires a canonical absolute --root without control characters or commas",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const existing = fresh ? dirname(value) : value;
|
|
60
|
+
if (realpathSync(existing) !== existing || normalize(value) !== value) {
|
|
61
|
+
throw new SeptError("Hands root must be canonical");
|
|
62
|
+
}
|
|
63
|
+
validateAncestors(existing);
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validateAncestors(existing: string): void {
|
|
68
|
+
for (let path = existing; ; path = dirname(path)) {
|
|
69
|
+
const stat = lstatSync(path);
|
|
70
|
+
const owner = stat.uid === 0 || stat.uid === process.getuid?.();
|
|
71
|
+
const sticky = stat.uid === 0 && (stat.mode & 0o1000) !== 0;
|
|
72
|
+
if (!stat.isDirectory() || !owner || ((stat.mode & 0o022) !== 0 && !sticky)) {
|
|
73
|
+
throw new SeptError("Hands root ancestry is not trusted");
|
|
74
|
+
}
|
|
75
|
+
if (path === dirname(path)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Create only a new root: a failed or foreign instance is never overwritten or adopted by init. */
|
|
82
|
+
export function createHandsRoot(root: string): HandsPaths {
|
|
83
|
+
mkdirSync(root, { mode: 0o755 });
|
|
84
|
+
chmodSync(root, 0o755);
|
|
85
|
+
const paths = handsPaths(root);
|
|
86
|
+
for (const directory of [
|
|
87
|
+
paths.stateRoot,
|
|
88
|
+
paths.agentDir,
|
|
89
|
+
paths.workspaceRoot,
|
|
90
|
+
join(root, "evidence"),
|
|
91
|
+
join(root, "docker-cli"),
|
|
92
|
+
]) {
|
|
93
|
+
mkdirSync(directory, { mode: 0o700 });
|
|
94
|
+
}
|
|
95
|
+
mkdirSync(join(root, "resources"), { mode: 0o755 });
|
|
96
|
+
chmodSync(join(root, "resources"), 0o755);
|
|
97
|
+
syncSetupDirectory(root);
|
|
98
|
+
syncSetupDirectory(dirname(root));
|
|
99
|
+
return paths;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function syncSetupDirectory(path: string): void {
|
|
103
|
+
const descriptor = openSync(path, "r");
|
|
104
|
+
try {
|
|
105
|
+
fsyncSync(descriptor);
|
|
106
|
+
} finally {
|
|
107
|
+
closeSync(descriptor);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Persist recovery ownership before creating a container and after every identity transition. */
|
|
112
|
+
export function writeHandsReceipt(root: string, receipt: HandsReceipt): void {
|
|
113
|
+
atomicWrite(join(root, "evidence", "setup.json"), receipt);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Read bounded private ownership, refusing edited schema, symlinks, hardlinks or broad permissions. */
|
|
117
|
+
export function readHandsReceipt(root: string): HandsReceipt {
|
|
118
|
+
const path = join(root, "evidence", "setup.json");
|
|
119
|
+
const raw = readHandsMetadata(path, 16 * 1024);
|
|
120
|
+
if (!isRecord(raw)) {
|
|
121
|
+
throw new SeptError("Hands setup ownership is malformed");
|
|
122
|
+
}
|
|
123
|
+
return decodeReceipt(raw);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Read private bounded setup metadata without presenting hostile JSON or filesystem diagnostics. */
|
|
127
|
+
export function readHandsMetadata(path: string, maximum: number): unknown {
|
|
128
|
+
try {
|
|
129
|
+
const file = lstatSync(path);
|
|
130
|
+
if (
|
|
131
|
+
!file.isFile() ||
|
|
132
|
+
file.nlink !== 1 ||
|
|
133
|
+
file.uid !== process.getuid?.() ||
|
|
134
|
+
(file.mode & 0o077) !== 0 ||
|
|
135
|
+
file.size > maximum
|
|
136
|
+
) {
|
|
137
|
+
throw new SeptError("Hands metadata must be a bounded private owned regular file");
|
|
138
|
+
}
|
|
139
|
+
const raw: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
140
|
+
return raw;
|
|
141
|
+
} catch (error: unknown) {
|
|
142
|
+
throw new SeptError("Hands setup metadata is unavailable or malformed", { cause: error });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
147
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function decodeReceipt(raw: Record<string, unknown>): HandsReceipt {
|
|
151
|
+
const keys = [
|
|
152
|
+
"version",
|
|
153
|
+
"owner",
|
|
154
|
+
"engineSocket",
|
|
155
|
+
"socketIdentity",
|
|
156
|
+
"daemonId",
|
|
157
|
+
"image",
|
|
158
|
+
"containerId",
|
|
159
|
+
"generation",
|
|
160
|
+
"phase",
|
|
161
|
+
];
|
|
162
|
+
if (Object.keys(raw).some((key) => !keys.includes(key)) || raw.version !== 1) {
|
|
163
|
+
throw new SeptError("Hands setup ownership is malformed");
|
|
164
|
+
}
|
|
165
|
+
const containerId =
|
|
166
|
+
raw.containerId === undefined ? undefined : text(raw.containerId, /^[a-f0-9]{64}$/);
|
|
167
|
+
const generation = raw.generation === undefined ? undefined : text(raw.generation);
|
|
168
|
+
return {
|
|
169
|
+
version: 1,
|
|
170
|
+
owner: text(raw.owner, /^[a-f0-9-]{36}$/),
|
|
171
|
+
engineSocket: text(raw.engineSocket),
|
|
172
|
+
socketIdentity: text(raw.socketIdentity),
|
|
173
|
+
daemonId: text(raw.daemonId),
|
|
174
|
+
image: text(raw.image, /^sha256:[a-f0-9]{64}$/),
|
|
175
|
+
phase: receiptPhase(raw.phase),
|
|
176
|
+
...(containerId === undefined ? {} : { containerId }),
|
|
177
|
+
...(generation === undefined ? {} : { generation }),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function text(value: unknown, pattern?: RegExp): string {
|
|
182
|
+
if (
|
|
183
|
+
typeof value !== "string" ||
|
|
184
|
+
value.length === 0 ||
|
|
185
|
+
value.length > 4096 ||
|
|
186
|
+
/[\x00-\x1f]/.test(value)
|
|
187
|
+
) {
|
|
188
|
+
throw new SeptError("Hands setup ownership text is malformed");
|
|
189
|
+
}
|
|
190
|
+
if (pattern !== undefined && !pattern.test(value)) {
|
|
191
|
+
throw new SeptError("Hands setup ownership identity is malformed");
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function receiptPhase(value: unknown): HandsReceipt["phase"] {
|
|
197
|
+
switch (value) {
|
|
198
|
+
case "prepared":
|
|
199
|
+
case "creating":
|
|
200
|
+
case "running":
|
|
201
|
+
case "ready":
|
|
202
|
+
case "failed":
|
|
203
|
+
case "finished":
|
|
204
|
+
return value;
|
|
205
|
+
default:
|
|
206
|
+
throw new SeptError("Hands setup phase is malformed");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Generate the unguessable container ownership label before any Engine effect. */
|
|
211
|
+
export function newHandsOwner(): string {
|
|
212
|
+
return randomUUID();
|
|
213
|
+
}
|
package/src/cli/hands.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** Optional local setup composition; the existing worker and policy authority remain unchanged. */
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { acquireStateRootLease, atomicWrite, SeptError } from "../durable/index.js";
|
|
5
|
+
import { DockerEngineControl, loadHandsConfiguration } from "../execution/index.js";
|
|
6
|
+
import { finishHands } from "./hands-finish.js";
|
|
7
|
+
import { runPermissions } from "./permissions.js";
|
|
8
|
+
import type { CliInvocation } from "./options.js";
|
|
9
|
+
import {
|
|
10
|
+
createHandsRoot,
|
|
11
|
+
handsPaths,
|
|
12
|
+
newHandsOwner,
|
|
13
|
+
trustedHandsRoot,
|
|
14
|
+
writeHandsReceipt,
|
|
15
|
+
type HandsReceipt,
|
|
16
|
+
} from "./hands-record.js";
|
|
17
|
+
import { createHandsArguments, handsDocker } from "./hands-docker.js";
|
|
18
|
+
import { captureHandsOwner, inspectHandsOwner, stopHandsOwner } from "./hands-owner.js";
|
|
19
|
+
|
|
20
|
+
/** Execute the two installed operator commands; signals join cleanup before the process exits. */
|
|
21
|
+
export async function runHands(invocation: CliInvocation): Promise<unknown> {
|
|
22
|
+
if (invocation.action !== "init" && invocation.action !== "finish") {
|
|
23
|
+
throw new SeptError("Usage: sept hands init|finish --root <absolute-new-instance>");
|
|
24
|
+
}
|
|
25
|
+
const abort = new AbortController();
|
|
26
|
+
const cancel = (): void => {
|
|
27
|
+
abort.abort();
|
|
28
|
+
};
|
|
29
|
+
process.on("SIGINT", cancel);
|
|
30
|
+
process.on("SIGTERM", cancel);
|
|
31
|
+
try {
|
|
32
|
+
return invocation.action === "init"
|
|
33
|
+
? await initialize(invocation.options, abort.signal)
|
|
34
|
+
: await finishHands(invocation.options);
|
|
35
|
+
} finally {
|
|
36
|
+
process.off("SIGINT", cancel);
|
|
37
|
+
process.off("SIGTERM", cancel);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function assertNotCancelled(signal: AbortSignal): void {
|
|
42
|
+
if (signal.aborted) {
|
|
43
|
+
throw new SeptError("Hands setup cancelled; ownership and recovery receipts retained");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function initialize(options: Record<string, string>, signal: AbortSignal): Promise<unknown> {
|
|
48
|
+
const root = trustedHandsRoot(options.root, true);
|
|
49
|
+
if (options.access !== "files" && options.access !== "none") {
|
|
50
|
+
throw new SeptError("hands init requires explicit --access files|none");
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
options.image === undefined ||
|
|
54
|
+
options.image.length > 512 ||
|
|
55
|
+
options.image.startsWith("-") ||
|
|
56
|
+
options["engine-socket"] === undefined
|
|
57
|
+
) {
|
|
58
|
+
throw new SeptError("hands init requires --image and explicit local --engine-socket");
|
|
59
|
+
}
|
|
60
|
+
const engine = await DockerEngineControl.open(options["engine-socket"]);
|
|
61
|
+
try {
|
|
62
|
+
assertNotCancelled(signal);
|
|
63
|
+
createHandsRoot(root);
|
|
64
|
+
return await initializeOwned(root, options, engine, signal);
|
|
65
|
+
} finally {
|
|
66
|
+
await engine.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function initializeOwned(
|
|
71
|
+
root: string,
|
|
72
|
+
options: Record<string, string>,
|
|
73
|
+
engine: DockerEngineControl,
|
|
74
|
+
signal: AbortSignal,
|
|
75
|
+
): Promise<unknown> {
|
|
76
|
+
const release = acquireStateRootLease(join(root, "evidence"));
|
|
77
|
+
const receipt = initialReceipt(options, engine);
|
|
78
|
+
try {
|
|
79
|
+
writeHandsReceipt(root, receipt);
|
|
80
|
+
resolveImage(root, receipt, options.image ?? "");
|
|
81
|
+
await createContainer(root, receipt, engine, signal);
|
|
82
|
+
await activatePermissions(root, options.access === "files");
|
|
83
|
+
assertNotCancelled(signal);
|
|
84
|
+
receipt.phase = "ready";
|
|
85
|
+
writeHandsReceipt(root, receipt);
|
|
86
|
+
return {
|
|
87
|
+
...handsPaths(root),
|
|
88
|
+
containerId: receipt.containerId,
|
|
89
|
+
generation: receipt.generation,
|
|
90
|
+
access: options.access,
|
|
91
|
+
};
|
|
92
|
+
} catch (error: unknown) {
|
|
93
|
+
await initializationFailure(root, receipt, engine);
|
|
94
|
+
throw new SeptError("Hands initialization failed; recovery evidence retained under --root", {
|
|
95
|
+
cause: error,
|
|
96
|
+
});
|
|
97
|
+
} finally {
|
|
98
|
+
release();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveImage(root: string, receipt: HandsReceipt, imageName: string): void {
|
|
103
|
+
const image = handsDocker(root, receipt, ["image", "inspect", "--format", "{{.Id}}", imageName]);
|
|
104
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(image)) {
|
|
105
|
+
throw new SeptError("Hands image must already be locally available with immutable identity");
|
|
106
|
+
}
|
|
107
|
+
receipt.image = image;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function initialReceipt(
|
|
111
|
+
options: Record<string, string>,
|
|
112
|
+
engine: DockerEngineControl,
|
|
113
|
+
): HandsReceipt {
|
|
114
|
+
return {
|
|
115
|
+
version: 1,
|
|
116
|
+
owner: newHandsOwner(),
|
|
117
|
+
engineSocket: realpathSync(options["engine-socket"] ?? ""),
|
|
118
|
+
socketIdentity: engine.socketIdentity,
|
|
119
|
+
daemonId: engine.daemonId,
|
|
120
|
+
image: `sha256:${"0".repeat(64)}`,
|
|
121
|
+
phase: "prepared",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function createContainer(
|
|
126
|
+
root: string,
|
|
127
|
+
receipt: HandsReceipt,
|
|
128
|
+
engine: DockerEngineControl,
|
|
129
|
+
signal: AbortSignal,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
receipt.phase = "creating";
|
|
132
|
+
writeHandsReceipt(root, receipt);
|
|
133
|
+
receipt.containerId = handsDocker(root, receipt, createHandsArguments(root, receipt));
|
|
134
|
+
await inspectHandsOwner(root, receipt, engine);
|
|
135
|
+
assertNotCancelled(signal);
|
|
136
|
+
handsDocker(root, receipt, ["start", receipt.containerId]);
|
|
137
|
+
const state = await inspectHandsOwner(root, receipt, engine);
|
|
138
|
+
if (typeof state.StartedAt !== "string" || state.Running !== true) {
|
|
139
|
+
throw new SeptError("Hands start generation was not established");
|
|
140
|
+
}
|
|
141
|
+
receipt.generation = state.StartedAt;
|
|
142
|
+
writeHandsReceipt(root, receipt);
|
|
143
|
+
const paths = handsPaths(root);
|
|
144
|
+
atomicWrite(paths.handsConfigurationPath, {
|
|
145
|
+
profile: "docker-linux-warm-v1",
|
|
146
|
+
engineSocket: receipt.engineSocket,
|
|
147
|
+
containerId: receipt.containerId,
|
|
148
|
+
generation: receipt.generation,
|
|
149
|
+
workspace: paths.workspaceRoot,
|
|
150
|
+
resources: join(root, "resources"),
|
|
151
|
+
dataDomain: "private",
|
|
152
|
+
workspaceLimits: { bytes: 134217728, inodes: 32768 },
|
|
153
|
+
shell: false,
|
|
154
|
+
readOnlyMounts: [],
|
|
155
|
+
});
|
|
156
|
+
await captureHandsOwner(
|
|
157
|
+
root,
|
|
158
|
+
receipt,
|
|
159
|
+
loadHandsConfiguration(paths.handsConfigurationPath),
|
|
160
|
+
engine,
|
|
161
|
+
);
|
|
162
|
+
assertNotCancelled(signal);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function activatePermissions(root: string, files: boolean): Promise<void> {
|
|
166
|
+
const paths = handsPaths(root);
|
|
167
|
+
const policy = join(root, "evidence", "initial-policy.json");
|
|
168
|
+
atomicWrite(policy, {
|
|
169
|
+
mode: "scoped",
|
|
170
|
+
workspace: { read: files, write: files },
|
|
171
|
+
selected: [],
|
|
172
|
+
execute: false,
|
|
173
|
+
network: false,
|
|
174
|
+
});
|
|
175
|
+
await runPermissions(
|
|
176
|
+
{
|
|
177
|
+
command: "permissions",
|
|
178
|
+
action: "set",
|
|
179
|
+
options: { hands: paths.handsConfigurationPath, policy, revision: "0" },
|
|
180
|
+
},
|
|
181
|
+
paths.stateRoot,
|
|
182
|
+
paths.agentDir,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function initializationFailure(
|
|
187
|
+
root: string,
|
|
188
|
+
receipt: HandsReceipt,
|
|
189
|
+
engine: DockerEngineControl,
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
let stopped = receipt.phase === "prepared";
|
|
192
|
+
try {
|
|
193
|
+
if (!stopped) {
|
|
194
|
+
await stopHandsOwner(root, receipt, engine);
|
|
195
|
+
stopped = true;
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
// Unknown ownership never authorizes a broader stop; retain evidence for explicit reconciliation.
|
|
199
|
+
} finally {
|
|
200
|
+
receipt.phase = "failed";
|
|
201
|
+
writeHandsReceipt(root, receipt);
|
|
202
|
+
atomicWrite(join(root, "evidence", "initialization-failure.json"), {
|
|
203
|
+
stopped,
|
|
204
|
+
reconciliationRequired: !stopped,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
package/src/cli/options.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { CliError } from "./errors.js";
|
|
7
7
|
|
|
8
8
|
const OPTION_KEYS = {
|
|
9
|
+
hands: ["root", "engine-socket", "image", "access"],
|
|
9
10
|
worker: ["hands", "agent-dir", "host", "port", "state-dir", "workspace-root"],
|
|
10
11
|
run: [
|
|
11
12
|
"hands",
|
|
@@ -83,7 +84,8 @@ export function parseCliInvocation(argv: string[]): CliInvocation | undefined {
|
|
|
83
84
|
if (!isCommand(command)) {
|
|
84
85
|
return undefined;
|
|
85
86
|
}
|
|
86
|
-
const hasAction =
|
|
87
|
+
const hasAction =
|
|
88
|
+
command === "models" || command === "auth" || command === "permissions" || command === "hands";
|
|
87
89
|
const action = hasAction ? argv[3] : undefined;
|
|
88
90
|
const options = parseOptions(argv.slice(hasAction ? 4 : 3), OPTION_KEYS[command]);
|
|
89
91
|
return action === undefined ? { command, options } : { command, action, options };
|
package/src/cli.ts
CHANGED
|
@@ -15,7 +15,7 @@ async function main(): Promise<void> {
|
|
|
15
15
|
const invocation = parseCliInvocation(process.argv);
|
|
16
16
|
if (invocation === undefined) {
|
|
17
17
|
process.stderr.write(
|
|
18
|
-
"Usage: sept worker|run|doctor|health|models|auth|permissions [action] [options]\n",
|
|
18
|
+
"Usage: sept worker|run|doctor|health|models|auth|permissions|hands [action] [options]\n",
|
|
19
19
|
);
|
|
20
20
|
process.exitCode = 2;
|
|
21
21
|
return;
|
package/src/execution/index.ts
CHANGED
|
@@ -34,3 +34,13 @@ export {
|
|
|
34
34
|
|
|
35
35
|
/** Exact-family filter generation for trusted consumer-owned network setup. */
|
|
36
36
|
export { networkFirewall } from "./network-profile.js";
|
|
37
|
+
|
|
38
|
+
/** Local setup uses the same authenticated Engine and pinned namespace cleanup as the worker. */
|
|
39
|
+
export { DockerEngineControl } from "./docker-engine.js";
|
|
40
|
+
export {
|
|
41
|
+
captureNamespaceLifetime,
|
|
42
|
+
stopCapturedNamespace,
|
|
43
|
+
parseNamespaceLifetime,
|
|
44
|
+
} from "./hands-processes.js";
|
|
45
|
+
|
|
46
|
+
export { guardedCommand } from "./hands-guarded-command.js";
|
package/docs/architecture.md
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# SEPT module map
|
|
2
|
-
|
|
3
|
-
SEPT exposes three npm entries over one runtime: `sept-agent` composes a complete worker, `sept-agent/client` is bounded HTTP transport, and `sept-agent/hands` exposes existing trusted operator configuration/filter helpers. Only those entries are consumer APIs.
|
|
4
|
-
|
|
5
|
-
SEPT is organized by the mechanisms it owns. Each mechanism has one public `index.ts`; production callers outside that folder use that surface rather than implementation files. Focused tests may import an implementation directly when doing so avoids widening a runtime facade.
|
|
6
|
-
|
|
7
|
-
```text
|
|
8
|
-
src/
|
|
9
|
-
index.ts export-only root npm entry
|
|
10
|
-
hands.ts export-only operator npm entry
|
|
11
|
-
worker-api.ts shared public worker contracts/identical function binding
|
|
12
|
-
hands-api.ts shared public hands contracts/identical helper bindings
|
|
13
|
-
package-metadata.ts import-safe installed package/Pi metadata
|
|
14
|
-
worker/
|
|
15
|
-
index.ts internal complete-composition boundary
|
|
16
|
-
worker.ts createWorker, bound readiness and shared idempotent stop
|
|
17
|
-
capabilities.ts shared CLI/factory/direct policy and Pi/backend wiring
|
|
18
|
-
client/
|
|
19
|
-
index.ts export-only public client entry and type-only DTOs
|
|
20
|
-
client.ts five existing HTTP operations, stable caller IDs
|
|
21
|
-
request-codecs.ts origin/header/identifier/page/request bounds
|
|
22
|
-
response-codecs.ts unknown JSON, durable DTO/status/identity/cursor checks
|
|
23
|
-
transport.ts fetch, first abort/timeout, streamed byte bounds
|
|
24
|
-
errors.ts fixed safe TaskClientError classifications
|
|
25
|
-
cli.ts installed dispatch, argv/env/defaults/signals/telemetry
|
|
26
|
-
cli/
|
|
27
|
-
auth-interaction.ts terminal adapter for Pi AuthInteraction
|
|
28
|
-
model-access/
|
|
29
|
-
index.ts intentional public surface
|
|
30
|
-
contracts.ts credential-blind provider/model/auth/selection DTOs
|
|
31
|
-
input.ts hostile-object narrowing at configuration boundaries
|
|
32
|
-
jsonc.ts bounded JSONC parsing for Pi models.json
|
|
33
|
-
custom-configuration.ts endpoint, credential, and rollover-metadata safety
|
|
34
|
-
model-metadata.ts identifiers, model limits, and built-in fallback pair
|
|
35
|
-
pod-selection.ts atomic credential-free Pod default persistence
|
|
36
|
-
normalization.ts credential-blind Pi metadata and error projection
|
|
37
|
-
runtime-factory.ts validated installed Pi ModelRuntime construction
|
|
38
|
-
service.ts catalogue, authentication, selection, and preflight facade
|
|
39
|
-
durable/
|
|
40
|
-
index.ts durable state public surface
|
|
41
|
-
contracts.ts run, task, handover, conversation, lifecycle contracts
|
|
42
|
-
errors.ts classified durability and capacity failures
|
|
43
|
-
limits.ts queue, record, conversation, and control bounds
|
|
44
|
-
filesystem.ts atomic files, fsync ordering, bounded JSONL scanning
|
|
45
|
-
validation-primitives.ts shared hostile-input narrowing primitives
|
|
46
|
-
state-codecs.ts manifest and lifecycle-snapshot codecs
|
|
47
|
-
evidence-codecs.ts event, message, conversation, and readiness codecs
|
|
48
|
-
control-codecs.ts assistant control and handover validation
|
|
49
|
-
lease.ts stable process identity and exclusive durable leases
|
|
50
|
-
run-evidence.ts sequenced internal events and bounded lifecycle logs
|
|
51
|
-
conversation-store.ts bounded client conversation retention and recovery
|
|
52
|
-
operator-queue.ts exact input admission, proof, and compaction
|
|
53
|
-
context-transfer-store.ts checkpoint and handover durability ordering
|
|
54
|
-
run-store.ts cohesive facade over one run's durable mechanisms
|
|
55
|
-
run-discovery.ts fail-closed restart discovery
|
|
56
|
-
worker-lifecycle-store.ts durable drain/readiness authority
|
|
57
|
-
ingress/
|
|
58
|
-
index.ts HTTP public surface
|
|
59
|
-
errors.ts request errors and HTTP status mapping
|
|
60
|
-
service.ts authenticated HTTP, model projection, replay, SSE
|
|
61
|
-
validation.ts task/workspace validation and model preflight composition
|
|
62
|
-
webhook.ts normalized event validation, hashes, receipts
|
|
63
|
-
webhook-router.ts idempotent event-to-worker routing
|
|
64
|
-
observability/
|
|
65
|
-
index.ts injected facade and construction surface
|
|
66
|
-
contract.ts business-facing observability contract
|
|
67
|
-
configuration.ts identity, resource, exporter selection
|
|
68
|
-
logger.ts bounded redacted JSON stderr logging
|
|
69
|
-
safe-fields.ts field allowlist and value bounds
|
|
70
|
-
exporters.ts contained exporter wrappers
|
|
71
|
-
metrics.ts runtime instruments and bounded state
|
|
72
|
-
setup.ts SDK/exporter selection and signal-owner construction
|
|
73
|
-
telemetry.ts facade implementation and signal lifecycle
|
|
74
|
-
tracing.ts spans, propagation, limits, provider
|
|
75
|
-
pi-integration/
|
|
76
|
-
index.ts Pi session-policy public surface
|
|
77
|
-
diagnostics.ts non-secret installed diagnostics
|
|
78
|
-
prompts.ts control, continuation, handover prompts
|
|
79
|
-
rollover.ts adaptive arithmetic and durable evidence
|
|
80
|
-
durable-host.ts native Harness/conversation lifetime and sanitized events
|
|
81
|
-
durable-runtime.ts authenticated Models, registry and SQLite FULL construction
|
|
82
|
-
durable-invocation.ts native submission/recovery and finalized-usage steering
|
|
83
|
-
durable-admission.ts bounded captured operator IDs for native admission
|
|
84
|
-
durable-session-files.ts strict native locators and unique successor discovery
|
|
85
|
-
durable-resources.ts operator prompts/skills and untrusted repository instructions
|
|
86
|
-
durable-extensions.ts bounded trusted native module loading
|
|
87
|
-
durable-tools.ts existing Pi tools registered as native durable tasks
|
|
88
|
-
message-text.ts text-only native response projection
|
|
89
|
-
execution/
|
|
90
|
-
index.ts supplied backend public boundary
|
|
91
|
-
contracts.ts one bounded effect/resource port and profile identity
|
|
92
|
-
profile.ts actual Linux isolation and mount provenance validation
|
|
93
|
-
protocol.ts bounded hostile request/result codecs
|
|
94
|
-
docker-backend.ts exclusive namespace lease, live authority and joined cancellation
|
|
95
|
-
docker-engine.ts explicit local Unix-socket Engine HTTP/exec control
|
|
96
|
-
warm-channel.ts authenticated resident RPC and serial parent operations
|
|
97
|
-
hands-entry.ts retained installed Pi body dispatch inside hands
|
|
98
|
-
hands-resident.ts resident frames and operation-local effect acknowledgements
|
|
99
|
-
hands-processes.ts guarded attachments and exact namespace lifetime
|
|
100
|
-
hands-process-owner.ts fixed command UID and bounded descendant joins
|
|
101
|
-
hands-native-child.ts minimal native search child streams/events over that owner
|
|
102
|
-
hands-selected-inputs.ts opening image/sealed-host checks and exact image aliases
|
|
103
|
-
hands-resources.ts protected authorized resource epoch view
|
|
104
|
-
native-env.ts minimum native environment with typed unsupported operations
|
|
105
|
-
runtime/
|
|
106
|
-
index.ts runtime public surface
|
|
107
|
-
controller.ts durable cycling and Pi-native cancellation state machine
|
|
108
|
-
worker.ts single resident slot, drain, controller ownership
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
The dependency direction is:
|
|
112
|
-
|
|
113
|
-
```text
|
|
114
|
-
public root / CLI worker -> worker -> ingress -> runtime -> pi-integration -> model-access
|
|
115
|
-
CLI direct -----------------> worker capability helpers -> native host/backend
|
|
116
|
-
public client -> HTTP -> ingress (no root/CLI/worker import)
|
|
117
|
-
public hands -> existing configuration/firewall functions
|
|
118
|
-
|
|
119
|
-
composition -> ingress -> runtime -> pi-integration -> model-access
|
|
120
|
-
| | | |
|
|
121
|
-
+--------+-------------------------+---------------+-> durable
|
|
122
|
-
+-------------------------+-----------------> observability contract
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
`model-access` is normalization and control around the installed Pi `ModelRuntime`, not another provider layer. Pi owns provider/model discovery, auth, credential storage/refresh, availability, compatible endpoint execution, and model requests. SEPT exposes only approved metadata, reads the atomic credential-free Pod default for every admission, and preflights the final pair before `RunStore.create()`. Direct and webhook tasks use the same admission function and service. Login remains UI-neutral in the service; only the installed CLI supplies an interaction adapter. Plain HTTP exposes authenticated read/selection routes but no secret mutation.
|
|
126
|
-
|
|
127
|
-
`durable` has no dependency on higher mechanisms. Existing format-2 manifests with fixed rollover options remain readable and normalize to format 3; all new manifests contain the resolved provider/model and no rollover knobs or credentials. The state-root `worker-state.json` remains authoritative for accepting/draining/drained readiness. Each run's bounded `conversation.jsonl` remains the sole client-facing event contract, separate from internal lifecycle events, logs, Pi sessions, rollover evidence, and checkpoint/handover controls.
|
|
128
|
-
|
|
129
|
-
`pi-integration` receives the selected Pi model from the manifest, verifies context/output metadata again, and opens the run's native Durable Harness with SQLite FULL and compaction disabled. Native conversation IDs are numeric; SEPT exposes their string form and bounded versioned locators. `rollover.json` remains scoped to provider, model, conversation identity and context window. Native finalized-response hooks supply usage; a stable steering submission is reused after restart or resumed with a distinct interrupted-handover recovery input. Handover JSON, Markdown, event and state commit before a fresh native conversation and provider UUID are created. Exact captured operator IDs accompany admission, so later queue arrivals cannot be marked in an earlier prompt.
|
|
130
|
-
|
|
131
|
-
Trusted agent-directory modules follow Node process/module-URL caching: host replacement and same-process worker stop/reopen do not guarantee edited-code reload; use a fresh Node process. They register native declarative extensions, including supported tools, sections, hooks, wrappers and tasks. Project trust remains false; repository instructions do not execute plugins. The existing Pi tools are adapted into native Durable tasks with explicit replay safety and cancellation. Both their complete existing bodies and the minimum native environment operations use the consumer-supplied `execution` backend. The backend enforces effects separately from brain; Pi remains the scheduler, replay and provider owner. New runs have one engine, with no coding-agent production host or historical transcript converter.
|
|
132
|
-
|
|
133
|
-
`runtime` retains execution, cancellation, wait/wake, shutdown, limits, restart adoption, and slot ownership without knowing HTTP. `ingress` translates the authenticated API and webhook contract onto runtime and model-access services. `observability` imports no business mechanisms and receives only bounded classifications/numbers; model identifiers, credentials, auth interactions, prompts, and provider errors are not telemetry fields.
|
|
134
|
-
|
|
135
|
-
The architecture suite enforces index-only cross-mechanism imports. `npm run build` cleans `dist`, so deleted or moved modules cannot survive as stale executable output. SEPT does not add ACP, a second agent loop, provider client, tokenizer, compactor, dashboard, cloud service, or model server.
|
|
136
|
-
|
|
137
|
-
`worker/worker.ts:createWorker` composes the authenticated service, capability admission and actual post-bind endpoint closure. The installed CLI worker calls that same factory; direct run reuses the extracted capability helpers. Library options are explicit, imports are inert, and the caller owns signals/telemetry shutdown. Ready means listener/router plus synchronous adoption, while native Pi work continues asynchronously and publishes task state. Stop reuses the durable boundary; waiting reopen preserves session and budgets.
|
|
138
|
-
|
|
139
|
-
This shared composition binds the single consumer-supplied Docker Linux hands container into the installed callers. Brain retains credentials, Models/Harness and SEPT control state; hands gets namespace aliases, a clean environment and exact bounded read-only resource projections. Missing/unsupported enforcement refuses without a local fallback. The `docker-linux-warm-v1` profile uses the same finite private `/workspace` tmpfs with default file rights and explicitly granted commands. Consumers seed/export it and preserve bytes before stopping. Selected data/code may be baked read-only image trees, with genuinely `FS_IMMUTABLE`-sealed Linux host inputs retained as optional compatibility. Ordinary RO binds and private-domain RW host binds refuse. The file-only `mutable-file-bind` alternative omits command/discovery traversal and image aliases. Completed commands join namespace descendants; normal close retains the consumer-owned idle namespace. Cancellation/loss stops and verifies it before settlement, and unverified cleanup durably blocks instead of publishing cancellation. Projection refresh relies on private bounded ownership receipts; consumer markers grant no deletion authority. See [the profile contract](pi-contract.md#supplied-hands-reference-profile) and [consumer image recipe](pi-contract.md#selected-image-data-and-code).
|
|
140
|
-
|
|
141
|
-
The bounded channel carries version/generation/configuration/session/sequence-correlated operation, effect check/ack and command frames. Authentication grants no rights: the live parent rechecks policy, roots, task identity, pins, expiry and cancellation at admission, immediately before effects/command start and at result acceptance. Opening qualifies immutable source/image/kernel facts; live checks retain bounded generation/mount/topology and anchored inode ownership without scanning the workspace on each call. Limits remain 512 KiB request, 4 MiB result and FIFO eight. Ordinary body errors preserve a healthy resident; broken framing/identity/cleanup seals admission and stops the exact generation before truthful settlement.
|
|
142
|
-
|
|
143
|
-
## Capability policy ownership
|
|
144
|
-
|
|
145
|
-
`durable/capability-policy.ts` owns the bounded private record, exact codec, fsync and revision CAS; it imports no lifecycle or execution behavior. `cli/permissions.ts` is the local operator writer; `worker/capabilities.ts` owns the shared worker/direct capability composition. `execution/capability-admission.ts` maps validated rights onto the supplied namespace and effective registrations. `DockerExecutionBackend` owns live revision/generation checks, queued/active ownership, policy-change shutdown and run binding reconciliation. `docker-control.ts` owns kernel qualification, bound lifetime receipts and exact-generation stop verification through the explicit `DockerEngineControl` Unix-socket HTTP client; `profile-configuration.ts` decodes trusted configuration while `profile.ts` validates actual kernel containment. `hands-files.ts` supplies complete supported Pi file operation seams within hands, with retained directory descriptors, independent rights and bounded regular single-link inode access.
|
|
146
|
-
|
|
147
|
-
`hands-selected-inputs.ts` parses optional `imageSelected` exact `{ alias }` objects, normalizes absence to `[]` and runs opening selected-tree/image qualification from `docker-control.qualifyKernelProfile`. Image aliases use `[A-Za-z0-9_-]{1,64}`; existing host aliases retain `[A-Za-z0-9_-]+`, including longer aliases. All image/RO/RW aliases share the existing eight-unique-alias bound and policy grants. Image roots have no host source/stat/seed path. The C checker retains one descriptor-anchored bounded type/link/device/depth/count/path walk, requiring per-inode immutable flags only for the sealed-host mode. Exact image/read-only-rootfs and live image/generation/mount receipts protect baked roots; equal, ancestor and subtree mounts refuse. Configuration/data/code changes require fresh image where applicable, fresh generation and authority after quiescence. Literal `uid-dac-fixed-helper-v1` in the kernel receipt prevents old authority being silently reused; incompatible history remains intact.
|
|
148
|
-
|
|
149
|
-
Policy authority never enters HTTP task/webhook/model input. Nonempty old state without policy remains dormant; malformed present state fails closed. Native Harness still owns scheduling, safe/unsafe replay and task identity; the worker still owns its single slot, drain, exact queues and budgets. Live denial wins before any queued/replayed effect, while already accepted durable inputs remain recoverable. Supported uniform-read policy retains whole native search/resource bodies. Partial-read namespaces omit search/list and workspace resource discovery; they keep independent anchored file tools and trusted global sections. There is no alternate scheduler, policy editor, provisioner or new operation journal.
|
|
150
|
-
|
|
151
|
-
`hands-guard.c` is the fixed Linux constructor for trusted entry process protection. The build stage alone compiles its shared library; the runtime image contains the fixed library under its read-only root. `hands-guarded-command.ts` composes the cleared offline environment, fixed preload and disabled SIGUSR1 inspection; a fixed Node prelude verifies version-2 constructor acknowledgement before the resident starts. The resident UID10002 retains imports, private resource epoch and a correlated non-TTY RPC attachment. Commands run UID10003:GID10002 without the brain group; `/app` is UID10002:GID10001 mode 0750 and resident HOME/epoch/MIME captures are private 0700 with files 0600. Root-owned0555 command/rg/fd executables live in `/usr/local/libexec/sept`; private HOME contains matching supplied discovery aliases. The command entry establishes a separate session for the SIGCONT boundary. Fixed command/join helpers retain sole execution/descendant ownership. DAC/nondumpability/guards protect resident bytes, memory, signals, imports and control descriptors. Some public proc metadata and resident command-line correlation remain visible; correlation and command stdout cannot acknowledge or dispatch authority.
|
|
152
|
-
|
|
153
|
-
Pi still owns the full grep/find bodies. The image patch adds their optional native spawn callback, bound only during the current admitted operation. `hands-command-authority.ts` reconstructs exact native argv for the matching grep→rg/find→fd parent, requires current READ authority/uniform readability, rejects private roots and supplies network:false without a new helper timer. These children consume no Bash execute grant. Fixed `/tmp` capture-name exclusions remain. `hands-native-child.ts` adapts separate bounded stdout/stderr, kill/error/close and drain behavior to the existing command owner; it is not another scheduler or process owner. Per-stream UTF-8/order survives the transport; Bash alone combines chunks in arrival order. Custom results use only stdout (4096-byte bound), with stderr drained within the shared 4 MiB output budget. Helper loss/overflow/cancellation cannot yield partial normal success; close/settlement waits for exact joins and native limit/abort conventions remain Pi-owned.
|
|
154
|
-
|
|
155
|
-
Image delivery uses exactly four assets in `image/pi-native-search`: overlay, patch, provenance and README. Revision `sept-native-search-v1` overlays only `dist/core/tools/{find,grep}.{js,d.ts}` after unchanged locked `npm ci`; host Pi 1.1.0 remains stock, with the exact locked package graph. Docker verifies upstream/overlay and effective bytes before compile, after prune and after final COPY; opening qualification rechecks pinned private provenance and effective installed hashes. The pinned registry archive omits LICENSE; the existing README retains the exact upstream MIT notice and is privately copied with provenance. See [reproduction/provenance](../image/pi-native-search/README.md).
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`durable/capability-request-codec.ts` validates exact bounded operation/content and decision facts; `capability-requests.ts` persists at most 32 immutable original denials and one matching retry claim through the existing policy lease/atomic helpers. `execution/capability-requests.ts` connects those facts to live run/policy/generation checks; the Docker backend retains effect ownership, expiry timers, cancellation and namespace joins. `cli/permission-decisions.ts` owns local inspection/decision/direct retry only. Native Pi remains the sole task journal/scheduler/provider loop. Pending wait is typed by `permissionRequestId`, published after closure and cleared only by the shared explicit accepted-message wake. Recovery of an interrupted request closes the captured namespace before checkpointing input without dispatching native work. One optional operator-registered hand uses anchored hash-verified code in `execution/custom-hand.ts`, outside the protected entry and brain API.
|
|
159
|
-
|
|
160
|
-
The single custom tool pins its operator path/hash/replay in native task memo evidence before execution intent. Recovery requires that original pin to be present and unchanged before effects; replacement code needs a new native task. This memo grants no permission and never resumes a denied original call.
|
|
161
|
-
|
|
162
|
-
Custom code must be below an admitted RO image or genuinely sealed host alias and satisfy the existing bounded arbitrary-execution profile. Anchored code is read/hash-checked once (64 KiB maximum), then the exact bytes/input are checked again by the parent and passed to the clean Node child. Native input has text `maxLength: 256` plus a 1024-byte transport bound, optional boolean network intent, stdout-only 4096-byte/64-line native output limits and the existing 5-second timeout. SAFE replay rechecks current pin/authority; UNSAFE interrupted effects retain uncertainty without replay. No native API object or credentials cross into the child.
|
|
163
|
-
|
|
164
|
-
Hands networking has two enforced layers: the consumer owns an exact IPv4/IPv6 kernel filter excluding special/metadata and all reachable public/private brain endpoints, and the resident remains socket denied while each actual command installs inherited seccomp socket denial unless explicit policy/exact approval and call intent permit network. Explicit grants include legitimate private/CGNAT/ULA peers and private DNS. Shared protected/gateway resolvers permit only UDP/TCP destination53 with complete nonconflicting protected endpoint facts. SEPT validates current brain interfaces, runtime-derived topology, DNS and actual filter tables before effects through its trusted control client; configuration fingerprints bind the complete portable consumer inventory. It does not provision a namespace or run a network broker. Native Bash/custom schemas preserve bounded network intent in operator request/retry identities; network expansion uses the existing quiescent fresh-generation rule. Credentials and Models remain brain-owned.
|
|
165
|
-
|
|
166
|
-
Support is bound to the exact qualified source/image/host combination. Native Windows worker operation remains unsupported because Unix control/POSIX leases remain; Intel Mac and Windows Docker Linux/WSL recipes remain unqualified. [The repository-only historical record at d361123](https://github.com/DB-Conneely/sept/blob/d361123d7c242db5af3ae2d1cb4daea4a2acdeff/docs/as-built/capability-confinement.md) retains source/image evidence and publicIPv6/UFW/provider-outage/OAuth and unknown original stream-loss qualifications.
|
|
167
|
-
|
|
168
|
-
The package ships matching source maps/source, canonical docs, notices and all four image assets. Its allowlist excludes tests/audits/private evidence/state/credentials. `prepack` reuses the existing clean/build; it adds no compiler, bootstrap, provider or provisioner. The tarball is not a complete Docker build context. [SDK ownership](sdk.md) and [the documentation index](index.md) are the consumer entry points.
|