@awebai/oats 0.24.13 → 0.25.1
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/bin/oats.mjs +994 -2837
- package/docs/capabilities.md +136 -323
- package/docs/configuration.md +68 -533
- package/docs/conventions.md +51 -24
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
- package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
- package/docs/design/2026-09-16-portable-onboarding.md +4 -2
- package/docs/design/2026-09-20-redesign-program-board.md +1 -1
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
- package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
- package/docs/design/2026-09-23-workspace-module-contracts.md +460 -0
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
- package/docs/design/README.md +20 -8
- package/docs/design/operations-contract.md +1 -0
- package/docs/design/package-engine-contract.md +1 -1
- package/docs/design/package-runtime-api.md +1 -1
- package/docs/desktop-cli-api.md +356 -5
- package/docs/desktop-succession.md +12 -6
- package/docs/desktop.md +9 -4
- package/docs/execution-targets.md +16 -4
- package/docs/first-team.md +107 -224
- package/docs/implementation.md +41 -11
- package/docs/integrations.md +50 -47
- package/docs/knowledge-capability-authoring.md +10 -4
- package/docs/knowledge-migration.md +21 -12
- package/docs/knowledge-reference/package-craft.md +11 -3
- package/docs/knowledge.md +60 -18
- package/docs/layers.md +3 -3
- package/docs/migration-from-oas.md +20 -9
- package/docs/oats-local.schema.json +50 -0
- package/docs/oats-membership.schema.json +23 -0
- package/docs/oats-workspace.schema.json +133 -48
- package/docs/official-marketplace.md +9 -6
- package/docs/packages.md +229 -440
- package/docs/rebuild-to-v2.md +347 -0
- package/docs/release-notes/v0.25.0.md +99 -0
- package/docs/release-notes/v0.25.1.md +94 -0
- package/docs/schedules.md +12 -6
- package/docs/soul.schema.json +41 -68
- package/docs/souls-and-instances.md +175 -108
- package/docs/workspace-adoption.md +70 -345
- package/docs/workspaces.md +436 -119
- package/lib/core.mjs +462 -61
- package/lib/instance-resolution.mjs +387 -0
- package/lib/materialize.mjs +580 -0
- package/lib/operator-dispatch.mjs +117 -0
- package/lib/packages.mjs +558 -1269
- package/lib/remote.mjs +718 -0
- package/lib/resolve.mjs +638 -0
- package/lib/schedule.mjs +90 -16
- package/lib/workspace.mjs +654 -0
- package/package.json +1 -1
- package/lib/portable-migration-artifacts.mjs +0 -135
- package/lib/portable-migration-evidence.mjs +0 -305
- package/lib/portable-migration-store.mjs +0 -199
- package/lib/portable-migration.mjs +0 -104
- package/lib/portable-onboarding-acceptance.mjs +0 -66
- package/lib/setup-expert-source.mjs +0 -100
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** Operator-level capability commands — `oats <ns> <cmd>` run from a DEPLOYMENT
|
|
2
|
+
* directory (one holding `oats-local.yaml`), not from an instance home.
|
|
3
|
+
*
|
|
4
|
+
* Contract (docs/design/2026-09-23-workspace-module-contracts.md, "Post-0.25.0
|
|
5
|
+
* clarifications"): resolve exactly as `oats spawn --soul <name>` would —
|
|
6
|
+
* `prepareInstance(dir, soul)` → the soul's Resolution — find the module whose
|
|
7
|
+
* manifest `command` is the namespace, fetch that capability into the
|
|
8
|
+
* deployment's per-commit module store `<deployment>/.oats/modules/<cap>@<commit12>/`
|
|
9
|
+
* (the same store capability-defined agents use; see
|
|
10
|
+
* lib/instance-resolution.mjs#resolvePackageCapabilityAgent) and dispatch to that
|
|
11
|
+
* copy with the soul's merged payload as `OATS_SETTINGS`.
|
|
12
|
+
*
|
|
13
|
+
* Never "the newest instance's copy" (an instance is not an authority for the
|
|
14
|
+
* deployment) and never an unlocked cache read: trust is exactly spawn's —
|
|
15
|
+
* members by membership, packages by the lock's approval (`prepareInstance`
|
|
16
|
+
* already refuses E_PACKAGE_UNAPPROVED). A module in the Resolution IS active.
|
|
17
|
+
*
|
|
18
|
+
* `--soul` is required: without a soul there is no Resolution to answer from
|
|
19
|
+
* (E_BAD_ARGS naming --soul). Nothing here reads oats-config.yaml. */
|
|
20
|
+
import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
21
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
22
|
+
import { randomBytes } from "node:crypto";
|
|
23
|
+
import { oatsError } from "./errors.mjs";
|
|
24
|
+
import { loadLocal } from "./workspace.mjs";
|
|
25
|
+
import { packageRef, refForKey } from "./resolve.mjs";
|
|
26
|
+
import { MODULES_DIR } from "./materialize.mjs";
|
|
27
|
+
import * as defaultRemote from "./remote.mjs";
|
|
28
|
+
import { prepareInstance } from "./instance-resolution.mjs";
|
|
29
|
+
|
|
30
|
+
function err(code, message, details) { const e = oatsError(code, message, details); e.details = details; return e; }
|
|
31
|
+
|
|
32
|
+
/** Is `dir` (or an ancestor) a v2 deployment? → the loadLocal result or null when
|
|
33
|
+
* no oats-local.yaml is in reach (the caller then falls back to the classic chain).
|
|
34
|
+
* Any OTHER failure (a malformed oats-local.yaml → E_WORKSPACE_SCHEMA) propagates. */
|
|
35
|
+
export function deploymentOf(dir) {
|
|
36
|
+
try { return loadLocal(dir); }
|
|
37
|
+
catch (e) { if (e?.code === "E_LOCAL_MISSING") return null; throw e; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The repo ref a module's tree is fetched from, spelled as lib/remote.mjs accepts it:
|
|
41
|
+
* member → the member repoKey (`local/<abs>` → the abs path, else `git:<key>`);
|
|
42
|
+
* package → packageRef over the lock entry (catalog url or git source). */
|
|
43
|
+
export function moduleRef(module, lock, { catalog = null, remote = defaultRemote } = {}) {
|
|
44
|
+
const from = module?.from;
|
|
45
|
+
if (!from || typeof from !== "object") throw err("E_CAPABILITY_BROKEN", `module ${module?.name ?? "?"}: resolution carries no provenance`, { module: module?.name ?? null });
|
|
46
|
+
if (from.kind === "member") return refForKey(from.repoKey);
|
|
47
|
+
if (from.kind === "package") {
|
|
48
|
+
const entry = lock?.packages?.[from.package];
|
|
49
|
+
if (!entry || typeof entry !== "object") throw err("E_PACKAGE_MISSING", `module ${module.name}: package ${from.package} is not in the deployment's lock`, { module: module.name, id: from.package });
|
|
50
|
+
return packageRef(from.package, entry, catalog, remote);
|
|
51
|
+
}
|
|
52
|
+
throw err("E_CAPABILITY_BROKEN", `module ${module.name}: unknown provenance kind ${JSON.stringify(from.kind)}`, { module: module.name, kind: from.kind ?? null });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `<deployment>/.oats/modules/<cap>@<commit12>` — the per-commit store path of a module. */
|
|
56
|
+
export function moduleStoreDir(deployment, module) {
|
|
57
|
+
return join(deployment, MODULES_DIR, `${module.name}@${String(module.from.commit).slice(0, 12)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Ensure the module's capability tree is in the deployment store; returns its dir.
|
|
61
|
+
* A tree already there (oats.json present) is reused — the commit is in the name,
|
|
62
|
+
* so it cannot be stale. The fetch lands in a sibling staging dir and is renamed
|
|
63
|
+
* into place, so a failed or interrupted fetch never leaves a half tree that a
|
|
64
|
+
* later call would trust. */
|
|
65
|
+
export async function ensureModuleTree(deployment, module, lock, { catalog = null, remote = defaultRemote, remoteOptions, fetch = defaultRemote.fetchRemoteTree } = {}) {
|
|
66
|
+
const store = join(deployment, MODULES_DIR);
|
|
67
|
+
const dir = moduleStoreDir(deployment, module);
|
|
68
|
+
if (existsSync(join(dir, "oats.json"))) return dir;
|
|
69
|
+
const ref = moduleRef(module, lock, { catalog, remote });
|
|
70
|
+
if (typeof module.dir !== "string" || !module.dir) throw err("E_CAPABILITY_BROKEN", `module ${module.name}: resolution records no capability directory`, { module: module.name });
|
|
71
|
+
mkdirSync(store, { recursive: true });
|
|
72
|
+
const staging = join(store, `.staging-${module.name}-${process.pid}-${randomBytes(4).toString("hex")}`);
|
|
73
|
+
try {
|
|
74
|
+
await fetch(ref, module.from.commit, module.dir, staging, { ...(remoteOptions || {}), allowSymlinks: defaultRemote.OATS_ALIAS_SYMLINK });
|
|
75
|
+
if (!existsSync(join(staging, "oats.json"))) throw err("E_CAPABILITY_BROKEN", `module ${module.name}: ${module.dir} at ${String(module.from.commit).slice(0, 12)} has no oats.json`, { module: module.name, dir: module.dir, commit: module.from.commit });
|
|
76
|
+
// A concurrent call may have won; its tree is the same commit — keep it.
|
|
77
|
+
if (existsSync(join(dir, "oats.json"))) { rmSync(staging, { recursive: true, force: true }); return dir; }
|
|
78
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }); // a half tree from an interrupted direct write
|
|
79
|
+
renameSync(staging, dir);
|
|
80
|
+
return dir;
|
|
81
|
+
} catch (x) { try { rmSync(staging, { recursive: true, force: true }); } catch { /* nothing */ } throw x; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve an operator-level command namespace from a deployment directory.
|
|
86
|
+
*
|
|
87
|
+
* contextDir — where the operator stands (oats-local.yaml is found walking up)
|
|
88
|
+
* namespace — the `<ns>` the operator typed (`okf`)
|
|
89
|
+
* soulName — the value of --soul; REQUIRED (undefined/true/"" → E_BAD_ARGS)
|
|
90
|
+
*
|
|
91
|
+
* → { deployment, soul: { name, repoKey, commit, team }, module, manifest, commands,
|
|
92
|
+
* settings: <resolution.payloads[cap] or {}>, resolution, prepared,
|
|
93
|
+
* ensureTree(): Promise<dir> } — or null when no module of the soul's
|
|
94
|
+
* resolution claims the namespace (the caller answers E_UNKNOWN_COMMAND).
|
|
95
|
+
* Two modules claiming one namespace → E_DUPLICATE_NAMESPACE. Everything
|
|
96
|
+
* prepareInstance can throw (E_SOUL_UNKNOWN, E_PACKAGE_UNAPPROVED,
|
|
97
|
+
* E_REMOTE_UNREADABLE, …) propagates untouched.
|
|
98
|
+
*/
|
|
99
|
+
export async function resolveOperatorDispatch(contextDir, namespace, soulName, { remoteOptions, remote, catalog = null, fetch, prepare = prepareInstance } = {}) {
|
|
100
|
+
if (typeof namespace !== "string" || !namespace) throw err("E_BAD_ARGS", "a command namespace is required");
|
|
101
|
+
if (typeof soulName !== "string" || !soulName.trim()) {
|
|
102
|
+
throw err("E_BAD_ARGS", `oats ${namespace}: outside an instance home a capability command resolves as a spawn would — pass --soul <name> (the soul whose resolution provides the "${namespace}" namespace)`, { namespace, flag: "--soul" });
|
|
103
|
+
}
|
|
104
|
+
const prepared = await prepare(resolvePath(contextDir), soulName, { remoteOptions, remote });
|
|
105
|
+
const { resolution, lock } = prepared;
|
|
106
|
+
const deployment = prepared.deployment ?? dirname(loadLocal(contextDir).path);
|
|
107
|
+
const claimants = (resolution.modules || []).filter((m) => m?.manifest && m.manifest.command === namespace && m.manifest.commands && typeof m.manifest.commands === "object");
|
|
108
|
+
if (!claimants.length) return null;
|
|
109
|
+
if (claimants.length > 1) throw err("E_DUPLICATE_NAMESPACE", `duplicate operational command namespace "${namespace}": ${claimants.map((m) => m.name).join(", ")}`, { namespace, modules: claimants.map((m) => m.name) });
|
|
110
|
+
const module = claimants[0];
|
|
111
|
+
const payload = resolution.payloads?.[module.name];
|
|
112
|
+
const settings = payload && typeof payload === "object" ? { ...payload } : {};
|
|
113
|
+
return {
|
|
114
|
+
deployment, soul: resolution.soul, module, manifest: module.manifest, commands: module.manifest.commands, settings, resolution, prepared,
|
|
115
|
+
ensureTree: () => ensureModuleTree(deployment, module, lock, { catalog, remote, remoteOptions: remoteOptions ?? prepared.remoteOptions, fetch }),
|
|
116
|
+
};
|
|
117
|
+
}
|