@awebai/oats 0.25.8 → 0.26.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/README.md +8 -6
- package/bin/oats.mjs +576 -1714
- package/capabilities/oats-authoring/oats-package.json +2 -2
- package/capabilities/oats-authoring/oats.json +2 -2
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +334 -72
- package/capabilities/oats-aweb/injects/aweb.md +7 -2
- package/capabilities/oats-aweb/lib/binding-wire.mjs +107 -5
- package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
- package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
- package/capabilities/oats-aweb/oats.json +14 -4
- package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
- package/capabilities/oats-jira/oats.json +2 -2
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
- package/capabilities/oats-linear/oats.json +2 -2
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +1 -1
- package/capabilities/oats-okf/lib/binding-wire.mjs +1 -1
- package/capabilities/oats-okf/lib/migration.mjs +2 -2
- package/capabilities/oats-okf/lib/sources.mjs +5 -4
- package/capabilities/oats-okf/lib/stores.mjs +40 -9
- package/capabilities/oats-okf/lib/worker.mjs +3 -3
- package/capabilities/oats-okf/oats.json +1 -1
- package/capabilities/oats-review/oats.json +3 -2
- package/docs/capabilities.md +218 -47
- package/docs/capability-manifest.schema.json +13 -4
- package/docs/configuration.md +17 -5
- package/docs/conventions.md +16 -26
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
- package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
- package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
- package/docs/design/2026-09-20-redesign-program-board.md +2 -2
- package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +54 -10
- package/docs/design/2026-09-24-phase-d-plan.md +77 -0
- package/docs/design/2026-09-25-teams-contract.md +226 -0
- package/docs/design/README.md +3 -3
- package/docs/design/launch-configurations.md +20 -16
- package/docs/design/operations-contract.md +27 -10
- package/docs/desktop-cli-api.md +546 -264
- package/docs/desktop-instance-start.md +1 -1
- package/docs/desktop.md +7 -13
- package/docs/execution-targets.md +16 -18
- package/docs/first-team.md +14 -17
- package/docs/implementation.md +28 -59
- package/docs/integrations.md +88 -32
- package/docs/knowledge-capability-authoring.md +1 -1
- package/docs/knowledge-reference/package-craft.md +10 -8
- package/docs/knowledge-theory.md +1 -1
- package/docs/knowledge.md +10 -11
- package/docs/layers.md +16 -17
- package/docs/oats-local.schema.json +29 -1
- package/docs/oats-membership.schema.json +5 -3
- package/docs/oats-package.schema.json +2 -2
- package/docs/oats-workspace.schema.json +1 -1
- package/docs/{official-marketplace.md → official-catalog.md} +15 -16
- package/docs/packages.md +75 -52
- package/docs/release-notes/v0.22.0.md +1 -1
- package/docs/release-notes/v0.23.1.md +1 -1
- package/docs/release-notes/v0.25.9.md +23 -0
- package/docs/release-notes/v0.26.0.md +670 -0
- package/docs/schedules.md +48 -126
- package/docs/soul.schema.json +11 -4
- package/docs/souls-and-instances.md +56 -43
- package/docs/workspaces.md +80 -58
- package/injects/instance-boundary.md +1 -1
- package/injects/work-attached.md +1 -1
- package/injects/work-workspace.md +2 -2
- package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
- package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
- package/lib/capability-contract.mjs +110 -0
- package/lib/config-data.mjs +2 -2
- package/lib/core.mjs +700 -4824
- package/lib/digest.mjs +12 -0
- package/lib/instance-inspect.mjs +396 -0
- package/lib/instance-lifecycle.mjs +3 -4
- package/lib/instance-resolution.mjs +212 -26
- package/lib/instruction-composition.mjs +0 -20
- package/lib/materialize.mjs +6 -4
- package/lib/operator-dispatch.mjs +33 -13
- package/lib/packages.mjs +25 -190
- package/lib/provider-binding.mjs +4 -2
- package/lib/provider-reasons.mjs +3 -68
- package/lib/resolve.mjs +204 -68
- package/lib/schedule.mjs +97 -272
- package/lib/servers.mjs +13 -13
- package/lib/{portable-shape.mjs → shape.mjs} +4 -3
- package/lib/tree-copy.mjs +44 -0
- package/lib/workspace.mjs +125 -20
- package/package-catalog.json +7 -7
- package/package.json +1 -1
- package/skills/integration-authoring/SKILL.md +48 -40
- package/skills/oats-getting-started/SKILL.md +105 -110
- package/skills/oats-support/SKILL.md +2 -2
- package/skills/soul-craft/SKILL.md +13 -6
- package/bin/oats-pi-sdk-host.mjs +0 -17
- package/docs/2026-09-03-architecture-proposal.md +0 -642
- package/docs/artifact-approvals.schema.json +0 -7
- package/docs/captured-invocation-context.schema.json +0 -7
- package/docs/captured-resolution.schema.json +0 -7
- package/docs/design/package-engine-contract.md +0 -813
- package/docs/design/package-runtime-api.md +0 -588
- package/docs/desktop-succession.md +0 -57
- package/docs/execution-capsule.schema.json +0 -108
- package/docs/first-team-demo.md +0 -92
- package/docs/knowledge-migration.md +0 -147
- package/docs/migration-from-oas.md +0 -103
- package/docs/oats-config.schema.json +0 -172
- package/docs/oats-lock-v3.schema.json +0 -7
- package/docs/oats-lock.schema.json +0 -175
- package/docs/operating-team-migration.md +0 -470
- package/docs/portable.schema.json +0 -2512
- package/docs/provider-check-input.schema.json +0 -7
- package/docs/rebuild-to-v2.md +0 -511
- package/docs/workspace-adoption.md +0 -74
- package/injects/framework-workspace.md +0 -7
- package/injects/local-soul.md +0 -19
- package/injects/oats-portable.md +0 -20
- package/injects/oats.md +0 -11
- package/injects/portable-instance-boundary.md +0 -39
- package/injects/portable-work-directory.md +0 -29
- package/lib/artifact-approvals.mjs +0 -120
- package/lib/artifact-tree.mjs +0 -141
- package/lib/capability-artifacts.mjs +0 -179
- package/lib/capability-execution.mjs +0 -15
- package/lib/capability-inputs.mjs +0 -39
- package/lib/capability-provenance.mjs +0 -231
- package/lib/captured-action-shape.mjs +0 -21
- package/lib/captured-admission-shape.mjs +0 -20
- package/lib/captured-binding-file.mjs +0 -36
- package/lib/captured-dispatch.mjs +0 -66
- package/lib/captured-instance-index.mjs +0 -277
- package/lib/captured-invocation-context.mjs +0 -130
- package/lib/captured-launch-request.mjs +0 -66
- package/lib/captured-operation-process.mjs +0 -15
- package/lib/captured-pi-custody.mjs +0 -29
- package/lib/captured-pi-host.mjs +0 -167
- package/lib/captured-pi-outcome.mjs +0 -172
- package/lib/captured-resolutions.mjs +0 -275
- package/lib/captured-scaffold.mjs +0 -87
- package/lib/captured-selector.mjs +0 -28
- package/lib/captured-session-backend.mjs +0 -52
- package/lib/captured-source-receipt-file.mjs +0 -72
- package/lib/helper-injection-policy.mjs +0 -104
- package/lib/legacy-lock-codec.mjs +0 -106
- package/lib/manifest-settings.mjs +0 -84
- package/lib/package-closure.mjs +0 -48
- package/lib/package-materialization.mjs +0 -83
- package/lib/pi-sdk-host.mjs +0 -229
- package/lib/portable-artifacts.mjs +0 -115
- package/lib/portable-choices.mjs +0 -82
- package/lib/portable-composition.mjs +0 -136
- package/lib/portable-digest.mjs +0 -105
- package/lib/portable-identity.mjs +0 -40
- package/lib/portable-lock.mjs +0 -117
- package/lib/portable-onboarding-request.mjs +0 -49
- package/lib/portable-onboarding.mjs +0 -256
- package/lib/portable-package-preparation.mjs +0 -188
- package/lib/portable-policy.mjs +0 -44
- package/lib/portable-soul.mjs +0 -42
- package/lib/portable-state.mjs +0 -80
- package/lib/prepare-composition.mjs +0 -170
- package/lib/prepared-bindings.mjs +0 -92
- package/lib/prepared-resources.mjs +0 -127
- package/lib/provider-binding-broker.mjs +0 -65
- package/lib/provider-binding-wire.mjs +0 -116
- package/lib/readiness.mjs +0 -225
- package/lib/repository-observation.mjs +0 -226
- package/lib/resolution-shape.mjs +0 -393
- package/lib/schedule-capsule.mjs +0 -206
- package/lib/soul-constraints.mjs +0 -40
- package/lib/source-projection.mjs +0 -84
- package/lib/source-spec.mjs +0 -189
- package/lib/workspace-definition.mjs +0 -126
- package/lib/workspace-discovery.mjs +0 -146
- package/skills/oats/SKILL.md +0 -162
- package/skills/oats-config/SKILL.md +0 -164
- package/skills/oats-packages/SKILL.md +0 -184
- package/skills/oats-portable/SKILL.md +0 -115
- package/skills/oats-portable-artifacts/SKILL.md +0 -63
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/** Read-only fresh setup and source discovery facade.
|
|
2
|
-
*
|
|
3
|
-
* Existing project content is allowed. Relevant OATS-managed state produces a
|
|
4
|
-
* separate-path hold; nothing is deleted, rewritten, installed, enrolled or
|
|
5
|
-
* prepared here. Repository reads are delegated to the existing transaction and
|
|
6
|
-
* workspace discovery/parser stack. */
|
|
7
|
-
import { lstatSync, opendirSync, realpathSync } from "node:fs";
|
|
8
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
|
-
import { createWorkspaceDiscovery } from "./workspace-discovery.mjs";
|
|
10
|
-
import { parseMemberExports } from "./workspace-definition.mjs";
|
|
11
|
-
import { canonicalJson, compareUtf8, freezeJson } from "./portable-values.mjs";
|
|
12
|
-
import { validateOrigin } from "./resolution-shape.mjs";
|
|
13
|
-
import { oatsError } from "./errors.mjs";
|
|
14
|
-
|
|
15
|
-
export const PORTABLE_ONBOARDING_VERSION = 1;
|
|
16
|
-
const PREPARATION_ONLY_FIELDS = Object.freeze(["operator", "launch", "helperLaunches", "mode", "allowLocalPaths"]);
|
|
17
|
-
const issued = new WeakMap();
|
|
18
|
-
const preflightWitnesses = new WeakMap();
|
|
19
|
-
const MANAGED_PATHS = Object.freeze([
|
|
20
|
-
["deployment-config", "oats-config.yaml"],
|
|
21
|
-
["selection-lock", "oats-lock.json"],
|
|
22
|
-
["schedules", "oats-schedules.json"],
|
|
23
|
-
["portable-state", ".agents/portable"],
|
|
24
|
-
["captured-resolutions", ".agents/resolutions"],
|
|
25
|
-
["migration-evidence", ".agents/resolution-evidence"],
|
|
26
|
-
["installed-capabilities", ".agents/capabilities/installed"],
|
|
27
|
-
["retained-capabilities", ".agents/capabilities/artifacts"],
|
|
28
|
-
["retained-souls", ".agents/soul-artifacts"],
|
|
29
|
-
["retained-resources", ".agents/resource-artifacts"],
|
|
30
|
-
["schedule-state", ".agents/schedules"],
|
|
31
|
-
["installed-packages", ".agents/packages/installed"],
|
|
32
|
-
["native-history", ".oats-native-record"],
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
const present = (path) => { try { return lstatSync(path); } catch (error) { if (error.code === "ENOENT") return null; throw error; } };
|
|
36
|
-
function exact(value, allowed, required, where) {
|
|
37
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw oatsError("invalid-declaration", `${where} must be an object`);
|
|
38
|
-
for (const key of Object.keys(value)) if (!allowed.includes(key)) throw oatsError("invalid-declaration", `${where} has unsupported field ${key}`);
|
|
39
|
-
for (const key of required) if (!Object.hasOwn(value, key)) throw oatsError("invalid-declaration", `${where} is missing ${key}`);
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
function canonicalExistingDirectory(path, label) {
|
|
43
|
-
if (typeof path !== "string" || !isAbsolute(path) || resolve(path) !== path) throw oatsError("invalid-declaration", `${label} must be a normalized absolute path`);
|
|
44
|
-
let actual;
|
|
45
|
-
try { actual = realpathSync(path); } catch (error) { if (error.code === "ENOENT") throw oatsError("source-unavailable", `${label} is absent`); throw error; }
|
|
46
|
-
if (actual !== path || !lstatSync(actual).isDirectory()) throw oatsError("invalid-declaration", `${label} must be a real non-symlinked directory`);
|
|
47
|
-
return actual;
|
|
48
|
-
}
|
|
49
|
-
function directoryIdentity(path) {
|
|
50
|
-
const stat = lstatSync(path);
|
|
51
|
-
if (!stat.isDirectory()) throw oatsError("selection-changed", "fresh setup directory is no longer a real directory");
|
|
52
|
-
return { dev: stat.dev, ino: stat.ino };
|
|
53
|
-
}
|
|
54
|
-
function freshDeploymentPath(path) {
|
|
55
|
-
if (typeof path !== "string" || !isAbsolute(path) || resolve(path) !== path) throw oatsError("invalid-declaration", "fresh deployment must be a normalized absolute path");
|
|
56
|
-
const stat = present(path);
|
|
57
|
-
if (stat) {
|
|
58
|
-
if (!stat.isDirectory() || realpathSync(path) !== path) throw oatsError("invalid-declaration", "fresh deployment path must be a real non-symlinked directory");
|
|
59
|
-
return { path, state: "existing", identity: { dev: stat.dev, ino: stat.ino } };
|
|
60
|
-
}
|
|
61
|
-
let parent;
|
|
62
|
-
try { parent = realpathSync(dirname(path)); }
|
|
63
|
-
catch (error) { if (error.code === "ENOENT") throw oatsError("source-unavailable", "fresh deployment parent is absent"); throw error; }
|
|
64
|
-
if (parent !== dirname(path) || !lstatSync(parent).isDirectory()) throw oatsError("invalid-declaration", "fresh deployment parent must be a real non-symlinked directory");
|
|
65
|
-
return { path, state: "absent", parent: directoryIdentity(parent) };
|
|
66
|
-
}
|
|
67
|
-
function relativePath(root, path) { return relative(root, path).split(sep).join("/"); }
|
|
68
|
-
function scanInstanceState(root, base, budget) {
|
|
69
|
-
const found = [], path = join(root, base), stat = present(path);
|
|
70
|
-
if (!stat) return found;
|
|
71
|
-
if (!stat.isDirectory() || realpathSync(path) !== path) return [{ kind: "managed-agents-root", path: base }];
|
|
72
|
-
const directory = opendirSync(path);
|
|
73
|
-
try {
|
|
74
|
-
for (;;) {
|
|
75
|
-
const entry = directory.readSync();
|
|
76
|
-
if (entry === null) break;
|
|
77
|
-
if (++budget.visited > budget.maxEntries) throw oatsError("resource-limit", "fresh deployment instance scan entry limit exceeded");
|
|
78
|
-
if (!entry.isDirectory()) continue;
|
|
79
|
-
for (const [kind, child] of [["instance-homes", "instances"], ["retirement-state", ".oats-retirement"]]) {
|
|
80
|
-
const target = join(path, entry.name, child);
|
|
81
|
-
if (present(target)) found.push({ kind, path: relativePath(root, target) });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} finally { directory.closeSync(); }
|
|
85
|
-
return found;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Detect only known deployment-owned state. Ordinary files, Git metadata,
|
|
89
|
-
* authored souls/capabilities and source repositories do not make a path dirty. */
|
|
90
|
-
export function preflightFreshDeployment({ deployment, maxEntries = 4096 }) {
|
|
91
|
-
canonicalJson({ deployment, maxEntries });
|
|
92
|
-
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1 || maxEntries > 100_000) throw oatsError("resource-limit", "invalid fresh deployment scan budget");
|
|
93
|
-
const selected = freshDeploymentPath(deployment), managedState = [];
|
|
94
|
-
if (selected.state === "existing") {
|
|
95
|
-
for (const [kind, relative] of MANAGED_PATHS) if (present(join(selected.path, relative))) managedState.push({ kind, path: relative });
|
|
96
|
-
const budget = { visited: 0, maxEntries };
|
|
97
|
-
managedState.push(...scanInstanceState(selected.path, "agents", budget), ...scanInstanceState(selected.path, "local-agents", budget));
|
|
98
|
-
}
|
|
99
|
-
managedState.sort((a, b) => compareUtf8(`${a.path}:${a.kind}`, `${b.path}:${b.kind}`));
|
|
100
|
-
if (canonicalJson(freshDeploymentPath(deployment)) !== canonicalJson(selected)) {
|
|
101
|
-
throw oatsError("selection-changed", "fresh deployment changed during preflight; inspect it again");
|
|
102
|
-
}
|
|
103
|
-
const status = managedState.length ? "separate-deployment-required" : "ready";
|
|
104
|
-
const result = freezeJson({ schemaVersion: PORTABLE_ONBOARDING_VERSION, status,
|
|
105
|
-
deployment: { path: selected.path, state: selected.state }, managedState,
|
|
106
|
-
advice: managedState.length ? ["Preserve this deployment and choose a separate fresh deployment path; no existing state was changed."] : [],
|
|
107
|
-
effects: { writes: false, deletes: false, installs: false, enrollment: false } });
|
|
108
|
-
preflightWitnesses.set(result, selected);
|
|
109
|
-
return result;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function inspectWorkTarget(path) {
|
|
113
|
-
const canonical = canonicalExistingDirectory(path, "work target"), marker = present(join(canonical, ".git"));
|
|
114
|
-
return { path: canonical, state: "existing", git: marker ? { present: true, kind: marker.isDirectory() ? "directory" : marker.isFile() ? "file" : "other" } : { present: false, kind: null } };
|
|
115
|
-
}
|
|
116
|
-
function sourceOrigin(workspace, alias) {
|
|
117
|
-
const index = workspace?.parsed.imports.findIndex((entry) => entry.alias === alias) ?? -1;
|
|
118
|
-
if (index < 0) throw oatsError("export-not-found", "requested alias is not advertised by the explicit workspace");
|
|
119
|
-
return { reference: workspace.parsed.imports[index], origin: { ...workspace.parsed.origins[`/imports/${index}`], kind: "import-adoption" } };
|
|
120
|
-
}
|
|
121
|
-
function explicitContext(input) {
|
|
122
|
-
const hasWorkspace = Object.hasOwn(input, "workspace");
|
|
123
|
-
const hasStandalone = Object.hasOwn(input, "standaloneContextKey");
|
|
124
|
-
if (hasWorkspace && hasStandalone) throw oatsError("invalid-declaration", "workspace and standaloneContextKey are mutually exclusive contexts");
|
|
125
|
-
if (hasWorkspace) return null;
|
|
126
|
-
if (!hasStandalone) throw oatsError("needs-configuration", "standalone onboarding requires an explicit standaloneContextKey (null explicitly means no private messaging context)");
|
|
127
|
-
const key = input.standaloneContextKey;
|
|
128
|
-
if (key !== null && (typeof key !== "string" || !key || Buffer.byteLength(key, "utf8") > 256 || key.trim() !== key || /[\x00-\x1f\x7f]/.test(key))) {
|
|
129
|
-
throw oatsError("invalid-declaration", "standaloneContextKey must be explicit null or non-empty text up to 256 UTF-8 bytes");
|
|
130
|
-
}
|
|
131
|
-
return { kind: "standalone", key };
|
|
132
|
-
}
|
|
133
|
-
function catalogSelection(value, count) {
|
|
134
|
-
if (value === undefined) return [];
|
|
135
|
-
if (!Array.isArray(value) || value.length > count || value.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= count)
|
|
136
|
-
|| new Set(value).size !== value.length) throw oatsError("invalid-declaration", "catalogIndexes must be unique declared workspace catalog indexes");
|
|
137
|
-
return [...value].sort((a, b) => a - b);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Inspect one explicit source and optional explicit workspace/member/catalogs.
|
|
141
|
-
* The caller owns the repository transaction lifetime. */
|
|
142
|
-
export function inspectPortableOnboarding(input, { repositories } = {}) {
|
|
143
|
-
canonicalJson(input);
|
|
144
|
-
exact(input, ["deployment", "workTarget", "source", "origin", "workspace", "member", "catalogIndexes", "standaloneContextKey", ...PREPARATION_ONLY_FIELDS],
|
|
145
|
-
["deployment", "workTarget", "source", "origin"], "portable onboarding inspection");
|
|
146
|
-
if (!repositories || typeof repositories !== "object") throw oatsError("invalid-source", "portable onboarding needs an explicit repository transaction");
|
|
147
|
-
validateOrigin(input.origin);
|
|
148
|
-
const standalone = explicitContext(input);
|
|
149
|
-
const deployment = preflightFreshDeployment({ deployment: input.deployment });
|
|
150
|
-
const target = inspectWorkTarget(input.workTarget), discovery = createWorkspaceDiscovery(repositories);
|
|
151
|
-
const ignored = PREPARATION_ONLY_FIELDS.filter(field => Object.hasOwn(input, field));
|
|
152
|
-
// Retain authored data privately for explicit request export ONLY. Inspection
|
|
153
|
-
// neither validates provider/launch semantics nor exposes these values.
|
|
154
|
-
const witness = { deployment: preflightWitnesses.get(deployment), work: directoryIdentity(target.path),
|
|
155
|
-
preparationFields: Object.fromEntries(ignored.map(field => [field, structuredClone(input[field])])) };
|
|
156
|
-
const workspace = Object.hasOwn(input, "workspace") ? discovery.readWorkspace(input.workspace) : null;
|
|
157
|
-
const selected = typeof input.source === "string" ? sourceOrigin(workspace, input.source) : { reference: input.source, origin: input.origin };
|
|
158
|
-
const imported = discovery.importSoul(selected.reference, { origin: selected.origin });
|
|
159
|
-
const indexDocument = repositories.readFile(imported.observation, "oats.yaml");
|
|
160
|
-
const exports = parseMemberExports(indexDocument.bytes, { origin: indexDocument.origin });
|
|
161
|
-
const membership = input.member ? (() => {
|
|
162
|
-
if (!workspace) throw oatsError("needs-configuration", "member inspection needs an explicit workspace");
|
|
163
|
-
return discovery.checkMember(workspace, input.member);
|
|
164
|
-
})() : { schemaVersion: 1, status: "not-requested", evidence: [], problems: [] };
|
|
165
|
-
const catalogs = [];
|
|
166
|
-
const selectedCatalogs = catalogSelection(input.catalogIndexes, workspace?.parsed.catalogs.length ?? 0);
|
|
167
|
-
if (selectedCatalogs.length && !workspace) throw oatsError("needs-configuration", "catalog inspection needs an explicit workspace");
|
|
168
|
-
for (const index of selectedCatalogs) {
|
|
169
|
-
const declared = workspace.parsed.catalogs[index], base = workspace.parsed.origins[`/catalogs/${index}`];
|
|
170
|
-
if (!declared.path) throw oatsError("needs-configuration", "catalog inspection needs an explicit descriptor path");
|
|
171
|
-
const origin = { ...base, kind: "workspace-default" }; validateOrigin(origin);
|
|
172
|
-
const observation = repositories.observe(declared.source, { ...(declared.revision ? { revision: declared.revision } : {}), origin });
|
|
173
|
-
const document = repositories.readFile(observation, declared.path);
|
|
174
|
-
catalogs.push({ index, declaration: declared, source: observation.source, document: document.origin });
|
|
175
|
-
}
|
|
176
|
-
const declaredTeams = workspace?.parsed.declaration.teams ?? {};
|
|
177
|
-
const status = deployment.status !== "ready" ? deployment.status
|
|
178
|
-
: input.member && membership.status !== "eligible" ? "needs-configuration" : "ready-for-preparation";
|
|
179
|
-
const result = freezeJson({ schemaVersion: PORTABLE_ONBOARDING_VERSION, status, deployment, workTarget: target, ignored,
|
|
180
|
-
source: { location: imported.reference.source, reference: imported.reference, identity: imported.identity,
|
|
181
|
-
revision: imported.observation.source, alias: imported.reference.alias, exportPath: imported.reference.soul,
|
|
182
|
-
definition: imported.definition, roots: imported.roots, exports: exports.exports, provenance: imported.provenance },
|
|
183
|
-
workspace: workspace ? { request: structuredClone(input.workspace), identity: workspace.identity, source: workspace.source,
|
|
184
|
-
name: workspace.parsed.declaration.name, members: workspace.parsed.members,
|
|
185
|
-
imports: workspace.parsed.imports, catalogs: workspace.parsed.catalogs } : null,
|
|
186
|
-
context: workspace ? { kind: "workspace", identity: workspace.identity } : standalone,
|
|
187
|
-
repositoryMembership: { request: input.member ? structuredClone(input.member) : null, result: membership },
|
|
188
|
-
teams: { declared: declaredTeams, enrollment: "not-performed", privateTeamQualification: "not-evaluated" },
|
|
189
|
-
catalogs, problems: membership.problems ?? [],
|
|
190
|
-
effects: { deploymentWrites: false, repositoryReads: true, repositoryScratch: "caller-owned",
|
|
191
|
-
installs: false, activation: false, credentials: false, teams: false, jobs: false } });
|
|
192
|
-
issued.set(result, witness);
|
|
193
|
-
if (status === "ready-for-preparation") recheckFreshOnboarding(result);
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Metadata-only public view, NOT a serialized fresh-inspection witness or a
|
|
198
|
-
* preparation request. Provider payloads/adoption values have not gone through
|
|
199
|
-
* their owner's nonsecret classification and must not leak through inspection. */
|
|
200
|
-
export function describePortableOnboarding(inspection) {
|
|
201
|
-
if (!issued.has(inspection)) throw oatsError("invalid-declaration", "inspection summary requires an issued onboarding view");
|
|
202
|
-
const reference = value => ({ source: value.source, soul: value.soul, revision: value.revision, alias: value.alias,
|
|
203
|
-
adoptionPresent: Object.hasOwn(value, "adoption") });
|
|
204
|
-
const { reference: selected, exports, ...source } = inspection.source;
|
|
205
|
-
return freezeJson({ ...inspection,
|
|
206
|
-
source: { ...source, adoptionPresent: Object.hasOwn(selected, "adoption"), exports: {
|
|
207
|
-
souls: (exports.souls ?? []).map(({ path, definition, description }) => ({ path, definition, ...(description === undefined ? {} : { description }) })),
|
|
208
|
-
packages: (exports.packages ?? []).map(({ path, description }) => ({ path, ...(description === undefined ? {} : { description }) })),
|
|
209
|
-
knowledge: (exports.knowledge ?? []).map(({ contract, version }) => ({ contract, version, payloadOmitted: true })),
|
|
210
|
-
} },
|
|
211
|
-
workspace: inspection.workspace ? { ...inspection.workspace, imports: inspection.workspace.imports.map(reference) } : null,
|
|
212
|
-
omitted: { providerPayloads: true, adoptionValues: true },
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** Ephemeral local root custody, not a new persistent authority/schema. Ordinary
|
|
217
|
-
* project content changes are allowed; replacing the inspected directories or
|
|
218
|
-
* provisioning a previously absent deployment requires a fresh inspection. */
|
|
219
|
-
export function recheckFreshOnboarding(inspection) {
|
|
220
|
-
const witness = issued.get(inspection);
|
|
221
|
-
if (!witness) throw oatsError("invalid-declaration", "fresh target recheck requires an issued inspection");
|
|
222
|
-
const current = preflightFreshDeployment({ deployment: inspection.deployment.deployment.path });
|
|
223
|
-
if (current.status !== "ready") throw oatsError("fresh-deployment-required", "fresh deployment acquired managed state after inspection; preserve it and choose another path");
|
|
224
|
-
if (canonicalJson(preflightWitnesses.get(current)) !== canonicalJson(witness.deployment)) {
|
|
225
|
-
throw oatsError("selection-changed", "inspected deployment or its absent-path parent changed; inspect it again");
|
|
226
|
-
}
|
|
227
|
-
let work;
|
|
228
|
-
try { work = directoryIdentity(canonicalExistingDirectory(inspection.workTarget.path, "work target")); }
|
|
229
|
-
catch { throw oatsError("selection-changed", "inspected work target is no longer available at its owned path"); }
|
|
230
|
-
if (canonicalJson(work) !== canonicalJson(witness.work)) throw oatsError("selection-changed", "inspected work target directory changed; inspect it again");
|
|
231
|
-
return current;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Build data for the existing preparation API. This function never invokes it.
|
|
235
|
-
* Work target remains adjacent and distinct from deployment/source inputs. */
|
|
236
|
-
export function buildFreshPreparationRequest(inspection, options = {}) {
|
|
237
|
-
canonicalJson(options);
|
|
238
|
-
exact(options, ["operator", "mode", "allowLocalPaths"], [], "fresh preparation options");
|
|
239
|
-
if (!issued.has(inspection)) throw oatsError("invalid-declaration", "fresh preparation requires an inspection issued by this module");
|
|
240
|
-
if (inspection.status !== "ready-for-preparation") throw oatsError("fresh-deployment-required", "fresh preparation is held by deployment or membership preflight");
|
|
241
|
-
const { operator, mode, allowLocalPaths = false } = options;
|
|
242
|
-
if (typeof allowLocalPaths !== "boolean") throw oatsError("invalid-declaration", "allowLocalPaths must be boolean");
|
|
243
|
-
if (mode !== undefined && (typeof mode !== "string" || !mode.trim())) throw oatsError("invalid-declaration", "preparation mode must be non-empty text");
|
|
244
|
-
const input = { deployment: inspection.deployment.deployment.path, workTarget: inspection.workTarget.path,
|
|
245
|
-
source: inspection.source.reference,
|
|
246
|
-
origin: inspection.source.revision.provenance[0], allowLocalPaths,
|
|
247
|
-
...(inspection.workspace ? { workspace: inspection.workspace.request } : { standaloneContextKey: inspection.context.key }),
|
|
248
|
-
...(inspection.repositoryMembership.request ? { member: inspection.repositoryMembership.request } : {}),
|
|
249
|
-
...issued.get(inspection).preparationFields,
|
|
250
|
-
...(operator === undefined ? {} : { operator }), ...(mode === undefined ? {} : { mode }),
|
|
251
|
-
...(Object.hasOwn(options, "allowLocalPaths") ? { allowLocalPaths } : {}) };
|
|
252
|
-
canonicalJson(input);
|
|
253
|
-
return freezeJson({ schemaVersion: PORTABLE_ONBOARDING_VERSION, operation: "prepare", persisted: false,
|
|
254
|
-
preparation: input, workTarget: inspection.workTarget,
|
|
255
|
-
effects: { writes: false, installs: false, activation: false, enrollment: false } });
|
|
256
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/** Package-side portable preparation over frozen observations. No current lock,
|
|
2
|
-
* config cascade, approval or activation lookup; caller owns final composition/CAS. */
|
|
3
|
-
import { chmodSync, lstatSync, mkdtempSync, readdirSync, readlinkSync, realpathSync } from "node:fs";
|
|
4
|
-
import { isAbsolute, join, posix, relative } from "node:path";
|
|
5
|
-
import { canonicalJson, compareUtf8, decodeUtf8, parseStrictJson } from "./portable-values.mjs";
|
|
6
|
-
import { PACKAGE_FORMAT, bytesIntegrity, treeIntegrity } from "./portable-digest.mjs";
|
|
7
|
-
import { readPortableBytes } from "./portable-files.mjs";
|
|
8
|
-
import { removeOwnedStaging } from "./artifact-tree.mjs";
|
|
9
|
-
import { createSourceProjection, validateProjectionPath } from "./source-projection.mjs";
|
|
10
|
-
import { resolvePackageClosure } from "./package-closure.mjs";
|
|
11
|
-
import { parseLockedSource3, parsePackageDependency3, parseRepositorySource, portablePath, validateRepoAnchor, validateSelectionSource } from "./source-spec.mjs";
|
|
12
|
-
import { isMaterializedCapabilityId } from "./capability-provenance.mjs";
|
|
13
|
-
import { objectAt } from "./portable-shape.mjs";
|
|
14
|
-
import { validateArtifactSet, validateOrigin } from "./resolution-shape.mjs";
|
|
15
|
-
import { retainPortableArtifact } from "./portable-artifacts.mjs";
|
|
16
|
-
import { oatsError } from "./errors.mjs";
|
|
17
|
-
|
|
18
|
-
const outside = (root, target) => { const path = relative(root, target); return path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute(path); };
|
|
19
|
-
function copyLocalPayload(source, destination) {
|
|
20
|
-
const root = realpathSync(source), parent = realpathSync(join(destination, "..")), rootStat = lstatSync(root);
|
|
21
|
-
if (!rootStat.isDirectory() || !outside(root, parent)) throw oatsError("invalid-source", "package input must be a directory outside its staging tree");
|
|
22
|
-
const entries = []; let count = 0, bytes = 0;
|
|
23
|
-
const inspect = (directory, parts) => {
|
|
24
|
-
if (parts.length > 64) throw oatsError("resource-limit", "local package source depth exceeded");
|
|
25
|
-
for (const raw of readdirSync(directory, { encoding: "buffer" })) {
|
|
26
|
-
if (++count > 10_000) throw oatsError("resource-limit", "local package source entry budget exceeded");
|
|
27
|
-
const name = decodeUtf8(raw), lowered = name.toLowerCase();
|
|
28
|
-
if (lowered === ".git") continue; // Never read/copy Git administrative state, at any depth.
|
|
29
|
-
if ([".aw", ".agents", ".oats-state"].includes(lowered) || (lowered === "instances" && parts.at(-2)?.toLowerCase() === "agents")) {
|
|
30
|
-
throw oatsError("source-incomplete", "local package subtree contains private deployment/auth/instance state");
|
|
31
|
-
}
|
|
32
|
-
const path = [...parts, name].join("/"), file = join(directory, name), stat = lstatSync(file);
|
|
33
|
-
validateProjectionPath(path);
|
|
34
|
-
if (!stat.isDirectory() && !stat.isFile() && !stat.isSymbolicLink()) throw oatsError("invalid-source", "local package source contains a special file");
|
|
35
|
-
if (stat.isFile() && (bytes += stat.size) > 256 * 1024 * 1024) throw oatsError("resource-limit", "local package source byte budget exceeded");
|
|
36
|
-
entries.push({ path, file, stat });
|
|
37
|
-
if (stat.isDirectory()) inspect(file, [...parts, name]);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
inspect(root, []); // Complete no-follow privacy/size preflight before any copy.
|
|
41
|
-
const projection = createSourceProjection(destination);
|
|
42
|
-
const unchanged = (entry) => {
|
|
43
|
-
const actual = lstatSync(entry.file), before = entry.stat;
|
|
44
|
-
if (actual.dev !== before.dev || actual.ino !== before.ino || actual.mode !== before.mode || actual.size !== before.size
|
|
45
|
-
|| actual.mtimeMs !== before.mtimeMs || actual.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "local package source changed during snapshot");
|
|
46
|
-
};
|
|
47
|
-
for (const entry of entries) {
|
|
48
|
-
unchanged(entry);
|
|
49
|
-
if (entry.stat.isDirectory()) projection.directory(entry.path, entry.stat.mode & 0o7777);
|
|
50
|
-
else if (entry.stat.isSymbolicLink()) projection.link(entry.path, readlinkSync(entry.file));
|
|
51
|
-
else projection.copyFile(entry.path, entry.file, entry.stat.mode & 0o7777);
|
|
52
|
-
unchanged(entry);
|
|
53
|
-
}
|
|
54
|
-
projection.finish(); chmodSync(destination, rootStat.mode & 0o7777);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function preparePackageArtifacts({ requests, deployment, directory, repositories, kernel, observations = [], catalog, allowLocalPaths = false }) {
|
|
58
|
-
canonicalJson(requests);
|
|
59
|
-
if (!Array.isArray(requests) || !requests.length || typeof directory !== "string" || !isAbsolute(directory)
|
|
60
|
-
|| typeof deployment !== "string" || !isAbsolute(deployment) || typeof allowLocalPaths !== "boolean") throw oatsError("invalid-source", "package preparation needs explicit requests, scope and scratch directory");
|
|
61
|
-
for (const name of ["loadPackageManifestAt", "capabilityCompatibility", "assertPlatformInvariantLocks", "materializeCapability"]) {
|
|
62
|
-
if (typeof kernel?.[name] !== "function") throw new TypeError(`package preparation requires kernel.${name}`);
|
|
63
|
-
}
|
|
64
|
-
for (const request of requests) {
|
|
65
|
-
objectAt(request, ["source", "origin", "capability"], ["source", "origin"]); validateOrigin(request.origin);
|
|
66
|
-
if (request.capability !== undefined && !isMaterializedCapabilityId(request.capability)) throw oatsError("invalid-source", "invalid requested capability identity");
|
|
67
|
-
}
|
|
68
|
-
const staging = mkdtempSync(join(realpathSync(directory), ".package-preparation-")), owned = lstatSync(staging);
|
|
69
|
-
const cached = new Map(), sourceObservations = [...observations]; let counter = 0, closed = false;
|
|
70
|
-
const cleanup = () => {
|
|
71
|
-
if (closed) return;
|
|
72
|
-
const current = lstatSync(staging);
|
|
73
|
-
if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError("source-incomplete", "package staging cleanup lost ownership");
|
|
74
|
-
removeOwnedStaging(staging); closed = true;
|
|
75
|
-
};
|
|
76
|
-
const observe = (source, selector, origin) => {
|
|
77
|
-
const existing = sourceObservations.find((value) => value.source.remote === source && (value.source.commit === selector || value.source.selector === selector));
|
|
78
|
-
if (existing) return existing;
|
|
79
|
-
const observation = repositories.observe(source, { ...(selector === undefined ? {} : { revision: selector }), origin });
|
|
80
|
-
sourceObservations.push(observation);
|
|
81
|
-
return observation;
|
|
82
|
-
};
|
|
83
|
-
const readPackage = (request) => {
|
|
84
|
-
validateOrigin(request.origin);
|
|
85
|
-
let selected = request.source;
|
|
86
|
-
if (selected?.kind === "catalog") {
|
|
87
|
-
const normalized = parsePackageDependency3(selected.source);
|
|
88
|
-
if (canonicalJson(normalized) !== canonicalJson(selected)) throw oatsError("invalid-source", "catalog request fields disagree");
|
|
89
|
-
} else validateSelectionSource(selected);
|
|
90
|
-
const key = canonicalJson(selected);
|
|
91
|
-
if (cached.has(key)) return cached.get(key);
|
|
92
|
-
let originalSource, packagePath, commit, payload, sourceDocument, repoAnchor = null, localBase;
|
|
93
|
-
const destination = join(staging, `package-${counter++}`);
|
|
94
|
-
let repository, selector;
|
|
95
|
-
if (selected.kind === "repo") {
|
|
96
|
-
repoAnchor = selected.anchor;
|
|
97
|
-
if (repoAnchor.source.startsWith("path:")) {
|
|
98
|
-
const root = parseLockedSource3(repoAnchor.source, ".").localPath;
|
|
99
|
-
const localPath = join(root, selected.path);
|
|
100
|
-
selected = { kind: "path", source: `path:${localPath}`, path: ".", localPath, portable: false };
|
|
101
|
-
} else {
|
|
102
|
-
repository = repoAnchor.source; selector = repoAnchor.revision; packagePath = selected.path;
|
|
103
|
-
originalSource = `${repository}@${selector}`;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (selected.kind === "catalog") {
|
|
107
|
-
if (typeof catalog !== "function") throw oatsError("needs-configuration", "package dependency requires an explicit catalog resolver");
|
|
108
|
-
const entry = catalog(selected.id, selected.selector);
|
|
109
|
-
if (!entry || typeof entry.url !== "string") throw oatsError("invalid-source", "catalog did not resolve a repository");
|
|
110
|
-
repository = parseRepositorySource(`git:${entry.url}`).normalized;
|
|
111
|
-
selector = entry.ref; packagePath = entry.path ?? "oats-package"; originalSource = selected.source;
|
|
112
|
-
} else if (selected.kind === "git") {
|
|
113
|
-
repository = parseRepositorySource(`git:${selected.url}`).normalized;
|
|
114
|
-
selector = selected.selector; packagePath = selected.path; originalSource = selected.source;
|
|
115
|
-
}
|
|
116
|
-
if (selected.kind === "path") {
|
|
117
|
-
if (!allowLocalPaths) throw oatsError("needs-configuration", "local package input requires explicit authorization");
|
|
118
|
-
localBase = selected.localPath; originalSource = selected.source; packagePath = "."; commit = "local";
|
|
119
|
-
copyLocalPayload(localBase, destination); payload = destination;
|
|
120
|
-
sourceDocument = { kind: "source", source: originalSource, revision: "local", path: "oats-package.json" };
|
|
121
|
-
} else {
|
|
122
|
-
portablePath(packagePath, { allowRoot: true });
|
|
123
|
-
const observation = observe(repository, selector, request.origin);
|
|
124
|
-
repositories.materialize(observation, [packagePath], destination);
|
|
125
|
-
payload = join(destination, packagePath); commit = observation.source.commit;
|
|
126
|
-
repoAnchor = { source: observation.source.remote, revision: commit };
|
|
127
|
-
sourceDocument = { kind: "source", source: observation.source.remote, revision: commit, path: posix.join(packagePath, "oats-package.json") };
|
|
128
|
-
}
|
|
129
|
-
// Strict JSON ingress precedes the sole existing full package/manifest codec.
|
|
130
|
-
const bytes = readPortableBytes(join(payload, "oats-package.json")); parseStrictJson(bytes);
|
|
131
|
-
sourceDocument.integrity = bytesIntegrity(bytes);
|
|
132
|
-
const manifest = kernel.loadPackageManifestAt(payload, { strict: true });
|
|
133
|
-
const dependencyRequests = (manifest.dependencies ?? []).map((spec, index) => {
|
|
134
|
-
// Local siblings are authoring input. A remote manifest never gains access
|
|
135
|
-
// to adopter files merely because another root request was local.
|
|
136
|
-
let source = parsePackageDependency3(spec, { localBase, allowLocalPaths: allowLocalPaths && localBase !== undefined });
|
|
137
|
-
if (source.kind === "repo") {
|
|
138
|
-
if (!repoAnchor) throw oatsError("needs-configuration", "repo dependency has no explicit repository snapshot anchor");
|
|
139
|
-
source = { ...source, anchor: validateRepoAnchor(repoAnchor) };
|
|
140
|
-
}
|
|
141
|
-
return { source, origin: { kind: "package-dependency", document: sourceDocument, pointer: `/dependencies/${index}` } };
|
|
142
|
-
});
|
|
143
|
-
const record = { package: manifest.package, version: manifest.version, source: originalSource, path: packagePath, commit,
|
|
144
|
-
sourceKey: canonicalJson({ source: originalSource, path: packagePath, commit }), dir: payload, manifest,
|
|
145
|
-
capabilities: manifest._capabilities, dependencyRequests, origins: [request.origin] };
|
|
146
|
-
cached.set(key, record); return record;
|
|
147
|
-
};
|
|
148
|
-
try {
|
|
149
|
-
const graph = resolvePackageClosure({ requests, readPackage,
|
|
150
|
-
validatePackage(record) {
|
|
151
|
-
const compatibility = kernel.capabilityCompatibility(record.manifest);
|
|
152
|
-
if (!compatibility.compatible) throw oatsError("incompatible-oats", `package ${record.package} requires OATS ${compatibility.range}`);
|
|
153
|
-
},
|
|
154
|
-
finalizePackage: (record, deps) => ({ ...record, deps, integrity: treeIntegrity(record.dir, { format: PACKAGE_FORMAT }) }),
|
|
155
|
-
discardPackage: () => {}, // All duplicate payloads belong to outer owned staging.
|
|
156
|
-
});
|
|
157
|
-
const owners = new Map();
|
|
158
|
-
for (const record of graph.packages.values()) for (const cap of record.capabilities) {
|
|
159
|
-
if (owners.has(cap.id) && owners.get(cap.id) !== record.package) throw oatsError("duplicate-capability-id", "selected package closures export conflicting capability identities");
|
|
160
|
-
owners.set(cap.id, record.package);
|
|
161
|
-
}
|
|
162
|
-
requests.forEach((request, index) => {
|
|
163
|
-
if (request.capability && !graph.packages.get(graph.roots[index]).capabilities.some((cap) => cap.id === request.capability)) throw oatsError("capability-list-mismatch", "selected source does not export the required capability");
|
|
164
|
-
});
|
|
165
|
-
kernel.assertPlatformInvariantLocks([...graph.packages.values()].flatMap((record) => record.capabilities.map((cap) => cap.dir)));
|
|
166
|
-
const artifactSet = { schemaVersion: 1, packages: Object.create(null), capabilities: Object.create(null) };
|
|
167
|
-
const artifacts = new Map();
|
|
168
|
-
for (const record of graph.packages.values()) {
|
|
169
|
-
artifactSet.packages[record.package] = { source: record.source, path: record.path, version: record.version, commit: record.commit,
|
|
170
|
-
integrity: record.integrity, dependencies: [...new Set(record.deps)].sort(compareUtf8) };
|
|
171
|
-
for (const cap of record.capabilities) {
|
|
172
|
-
const projected = kernel.materializeCapability({ cap, pkg: record, artifactsDir: join(staging, "artifacts") });
|
|
173
|
-
const artifact = { kind: "capability", capability: cap.id, integrity: treeIntegrity(projected.dir) };
|
|
174
|
-
artifactSet.capabilities[cap.id] = { version: projected.version, artifact, origin: { kind: "package", package: record.package, path: cap.rel, projectionVersion: 1 } };
|
|
175
|
-
artifacts.set(cap.id, projected.dir);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
validateArtifactSet(artifactSet);
|
|
179
|
-
for (const [id, source] of artifacts) retainPortableArtifact(deployment, source, artifactSet.capabilities[id].artifact);
|
|
180
|
-
return { artifactSet, roots: graph.roots, cleanup };
|
|
181
|
-
} catch (error) {
|
|
182
|
-
try { cleanup(); } catch (failure) {
|
|
183
|
-
const combined = new AggregateError([error, failure], "package preparation and cleanup failed", { cause: error });
|
|
184
|
-
combined.code = error.code; combined.stagingPath = staging; throw combined;
|
|
185
|
-
}
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
}
|
package/lib/portable-policy.mjs
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/** Shared authored selection shapes for the two policy authorities. Validation
|
|
2
|
-
* only: no precedence, provider selection, acquisition, enrollment or execution. */
|
|
3
|
-
import { isMaterializedCapabilityId } from "./capability-provenance.mjs";
|
|
4
|
-
import { parsePortableSource } from "./source-spec.mjs";
|
|
5
|
-
import { invalidShape, objectAt, pointerKey, stringAt } from "./portable-shape.mjs";
|
|
6
|
-
|
|
7
|
-
export const FUNDAMENTAL_SLOTS = Object.freeze(["knowledge", "messaging", "tasks"]);
|
|
8
|
-
export function validatePolicyChoices(value, { pointer, required = false, origins = {}, origin = null, localBase, allowLocalPaths = false }) {
|
|
9
|
-
const rules = objectAt(value, ["capabilities", ...FUNDAMENTAL_SLOTS], [], pointer);
|
|
10
|
-
const sources = Object.create(null);
|
|
11
|
-
const capabilityAt = (id, at) => { if (!isMaterializedCapabilityId(id)) invalidShape(at, "invalid capability identity"); };
|
|
12
|
-
const selection = (item, at, capability) => {
|
|
13
|
-
objectAt(item, capability === undefined ? ["capability", "source", "settings"] : ["source", "settings"],
|
|
14
|
-
capability === undefined ? ["capability", "source"] : ["source"], at);
|
|
15
|
-
capabilityAt(capability ?? item.capability, capability === undefined ? `${at}/capability` : at);
|
|
16
|
-
stringAt(item.source, `${at}/source`);
|
|
17
|
-
try { sources[`${at}/source`] = parsePortableSource(item.source, { localBase, allowLocalPaths }); }
|
|
18
|
-
catch (error) { error.provenance = [origins[`${at}/source`] ?? { document: origin, pointer: `${at}/source` }]; throw error; }
|
|
19
|
-
if (Object.hasOwn(item, "settings")) objectAt(item.settings, null, [], `${at}/settings`);
|
|
20
|
-
};
|
|
21
|
-
if (Object.hasOwn(rules, "capabilities")) {
|
|
22
|
-
objectAt(rules.capabilities, null, [], `${pointer}/capabilities`);
|
|
23
|
-
for (const [id, item] of Object.entries(rules.capabilities)) {
|
|
24
|
-
const at = `${pointer}/capabilities/${pointerKey(id)}`;
|
|
25
|
-
capabilityAt(id, at);
|
|
26
|
-
if (!required && item === false) continue;
|
|
27
|
-
selection(item, at, id);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
for (const slot of FUNDAMENTAL_SLOTS) {
|
|
31
|
-
if (!Object.hasOwn(rules, slot)) continue;
|
|
32
|
-
const item = rules[slot], at = `${pointer}/${slot}`;
|
|
33
|
-
if ((required && item === "any") || (!required && item === "none")) continue;
|
|
34
|
-
selection(item, at);
|
|
35
|
-
}
|
|
36
|
-
return sources;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function validateProviderDeclaration(value, pointer) {
|
|
40
|
-
const envelope = objectAt(value, ["contract", "version", "payload"], ["contract", "version", "payload"], pointer);
|
|
41
|
-
stringAt(envelope.contract, `${pointer}/contract`, { pattern: /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/ });
|
|
42
|
-
if (!Number.isSafeInteger(envelope.version) || envelope.version < 1) invalidShape(`${pointer}/version`, "invalid provider contract version");
|
|
43
|
-
return envelope;
|
|
44
|
-
}
|
package/lib/portable-soul.mjs
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/** Versioned soul declarations: parsing and origins, not composition or execution.
|
|
2
|
-
* Requirements constrain the later single resolver; defaults never erase them. */
|
|
3
|
-
import { parseConfigData } from "./config-data.mjs";
|
|
4
|
-
import { portablePath } from "./source-spec.mjs";
|
|
5
|
-
import { invalidShape, objectAt, stringAt, stringSetAt, versionAt } from "./portable-shape.mjs";
|
|
6
|
-
import { validatePolicyChoices, validateProviderDeclaration } from "./portable-policy.mjs";
|
|
7
|
-
|
|
8
|
-
export { FUNDAMENTAL_SLOTS } from "./portable-policy.mjs";
|
|
9
|
-
const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
10
|
-
const ALIAS = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
11
|
-
const LAUNCH_CONFIG_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
12
|
-
const ROOT_FIELDS = ["schemaVersion", "name", "description", "requires", "defaults", "knowledge", "teams", "resources", "work", "runtime", "model", "yolo", "launch-config", "backend", "children"];
|
|
13
|
-
|
|
14
|
-
export function parsePortableSoul(input, { origin = null, localBase, allowLocalPaths = false, limits } = {}) {
|
|
15
|
-
const parsed = parseConfigData(input, { origin, limits });
|
|
16
|
-
const value = objectAt(parsed.value, ROOT_FIELDS, ["schemaVersion", "name"]);
|
|
17
|
-
versionAt(value.schemaVersion);
|
|
18
|
-
stringAt(value.name, "/name", { pattern: NAME });
|
|
19
|
-
if (Object.hasOwn(value, "description")) stringAt(value.description, "/description", { empty: true });
|
|
20
|
-
const sources = Object.create(null);
|
|
21
|
-
for (const section of ["requires", "defaults"]) {
|
|
22
|
-
if (!Object.hasOwn(value, section)) continue;
|
|
23
|
-
Object.assign(sources, validatePolicyChoices(value[section], {
|
|
24
|
-
pointer: `/${section}`, required: section === "requires", origins: parsed.origins,
|
|
25
|
-
origin, localBase, allowLocalPaths,
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
if (Object.hasOwn(value, "knowledge")) validateProviderDeclaration(value.knowledge, "/knowledge");
|
|
29
|
-
if (Object.hasOwn(value, "teams")) stringSetAt(value.teams, "/teams", (item, pointer) => stringAt(item, pointer, { pattern: ALIAS }));
|
|
30
|
-
if (Object.hasOwn(value, "resources")) stringSetAt(value.resources, "/resources", (item) => portablePath(item, { allowRoot: true }));
|
|
31
|
-
if (Object.hasOwn(value, "work") && !["worktree", "checkout", "attached", "workspace", "directory"].includes(value.work)) invalidShape("/work", "unsupported work mode");
|
|
32
|
-
if (Object.hasOwn(value, "runtime") && !["pi", "claude", "codex"].includes(value.runtime)) invalidShape("/runtime", "unsupported runtime");
|
|
33
|
-
if (Object.hasOwn(value, "model")) stringAt(value.model, "/model");
|
|
34
|
-
if (Object.hasOwn(value, "launch-config")) stringAt(value["launch-config"], "/launch-config", { pattern: LAUNCH_CONFIG_NAME });
|
|
35
|
-
if (Object.hasOwn(value, "backend") && !["tmux", "herdr"].includes(value.backend)) invalidShape("/backend", "unsupported session backend");
|
|
36
|
-
if (Object.hasOwn(value, "yolo") && typeof value.yolo !== "boolean") invalidShape("/yolo", "expected boolean");
|
|
37
|
-
if (Object.hasOwn(value, "children")) {
|
|
38
|
-
const children = objectAt(value.children, ["spawn"], [], "/children");
|
|
39
|
-
if (Object.hasOwn(children, "spawn") && typeof children.spawn !== "boolean") invalidShape("/children/spawn", "expected boolean");
|
|
40
|
-
}
|
|
41
|
-
return { declaration: value, sources, origins: parsed.origins, integrity: parsed.integrity };
|
|
42
|
-
}
|
package/lib/portable-state.mjs
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/** Cooperative serialization for new portable-state writers, not legacy cutover
|
|
2
|
-
* or hostile-host locking. A crashed guard is held for explicit recovery. */
|
|
3
|
-
import { linkSync, lstatSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
5
|
-
import { canonicalJson } from "./portable-values.mjs";
|
|
6
|
-
import { ensureStoreIgnore } from "./capability-artifacts.mjs";
|
|
7
|
-
import { oatsError } from "./errors.mjs";
|
|
8
|
-
|
|
9
|
-
export function portableScope(scope) {
|
|
10
|
-
if (typeof scope !== "string" || !isAbsolute(scope)) throw oatsError("invalid-artifact-store", "portable deployment must be explicit and absolute");
|
|
11
|
-
const actual = realpathSync(scope);
|
|
12
|
-
if (!lstatSync(actual).isDirectory()) throw oatsError("invalid-artifact-store", "portable deployment must be a directory");
|
|
13
|
-
return actual;
|
|
14
|
-
}
|
|
15
|
-
export function portableStateDirectory(scope, create = false) {
|
|
16
|
-
let root = portableScope(scope);
|
|
17
|
-
for (const name of [".agents", "portable"]) {
|
|
18
|
-
root = join(root, name);
|
|
19
|
-
if (create) { try { mkdirSync(root, { mode: 0o700 }); } catch (error) { if (error.code !== "EEXIST") throw error; } }
|
|
20
|
-
let stat;
|
|
21
|
-
try { stat = lstatSync(root); } catch (error) { if (!create && error.code === "ENOENT") return null; throw error; }
|
|
22
|
-
if (!stat.isDirectory()) throw oatsError("invalid-artifact-store", "managed portable-state component is not a real directory");
|
|
23
|
-
}
|
|
24
|
-
if (create) ensureStoreIgnore(root);
|
|
25
|
-
return root;
|
|
26
|
-
}
|
|
27
|
-
const activeWrites = new WeakSet();
|
|
28
|
-
|
|
29
|
-
/** Only an active synchronous write can publish a mutable metadata document. */
|
|
30
|
-
export function writeGuardedPortableDocument(context, target, value, { absent = false } = {}) {
|
|
31
|
-
if (!activeWrites.has(context)) throw oatsError("selection-changed", "portable write context is not active");
|
|
32
|
-
const { root, deployment } = context;
|
|
33
|
-
if (dirname(target) !== root && target !== join(deployment, "oats-lock.json")) throw oatsError("invalid-artifact-store", "portable write target is outside its owned metadata namespace");
|
|
34
|
-
const bytes = canonicalJson(value), staging = mkdtempSync(join(root, ".document-")), candidate = join(staging, "document.json");
|
|
35
|
-
let failed = false, primary;
|
|
36
|
-
try {
|
|
37
|
-
writeFileSync(candidate, bytes, { flag: "wx", mode: 0o600 });
|
|
38
|
-
if (absent) {
|
|
39
|
-
try { linkSync(candidate, target); }
|
|
40
|
-
catch (error) { if (error.code === "EEXIST") throw oatsError("selection-changed", "portable metadata appeared during first publication"); throw error; }
|
|
41
|
-
} else renameSync(candidate, target);
|
|
42
|
-
} catch (error) { failed = true; primary = error; throw error; }
|
|
43
|
-
finally {
|
|
44
|
-
try { rmSync(staging, { recursive: true, force: true }); }
|
|
45
|
-
catch (cleanup) {
|
|
46
|
-
const error = failed ? new AggregateError([primary, cleanup], "portable metadata write and staging cleanup failed", { cause: primary })
|
|
47
|
-
: new Error("portable metadata staging cleanup failed", { cause: cleanup });
|
|
48
|
-
error.code = failed ? primary.code : cleanup.code; error.stagingPath = staging; throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function withPortableStateWrite(scope, write) {
|
|
54
|
-
const deployment = portableScope(scope), root = portableStateDirectory(deployment, true), guard = join(root, ".write-guard");
|
|
55
|
-
try { mkdirSync(guard, { mode: 0o700 }); }
|
|
56
|
-
catch (error) {
|
|
57
|
-
if (error.code === "EEXIST") throw oatsError("selection-changed", "portable state has another writer or a held recovery guard");
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
const owned = lstatSync(guard), context = Object.freeze({ deployment, root });
|
|
61
|
-
activeWrites.add(context);
|
|
62
|
-
let failed = false, primary;
|
|
63
|
-
try { return write(context); }
|
|
64
|
-
catch (error) { failed = true; primary = error; throw error; }
|
|
65
|
-
finally {
|
|
66
|
-
activeWrites.delete(context);
|
|
67
|
-
try {
|
|
68
|
-
const current = lstatSync(guard);
|
|
69
|
-
if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError("selection-changed", "portable write guard ownership changed");
|
|
70
|
-
// Empty owned guard only; never force-remove a foreign/stale guard tree.
|
|
71
|
-
rmdirSync(guard);
|
|
72
|
-
} catch (cleanup) {
|
|
73
|
-
const error = failed ? new AggregateError([primary, cleanup], "portable state write and guard cleanup failed", { cause: primary })
|
|
74
|
-
: new Error("portable write guard cleanup failed", { cause: cleanup });
|
|
75
|
-
error.code = failed ? primary.code : cleanup.code;
|
|
76
|
-
error.guardPath = guard;
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|