@awebai/oats 0.24.8 → 0.24.10
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 +69 -13
- package/capabilities/oats-okf/bin/oats-okf.mjs +3 -3
- package/capabilities/oats-okf/lib/binding-wire.mjs +35 -8
- package/capabilities/oats-okf/lib/config.mjs +1 -0
- package/capabilities/oats-okf/lib/sources.mjs +27 -2
- package/capabilities/oats-okf/lib/worker.mjs +5 -2
- package/capabilities/oats-okf/oats.json +29 -6
- package/docs/design/2026-09-20-redesign-program-board.md +1 -1
- package/docs/desktop-cli-api.md +138 -0
- package/docs/release-notes/v0.24.10.md +55 -0
- package/docs/release-notes/v0.24.9.md +67 -0
- package/docs/workspace-adoption.md +4 -1
- package/docs/workspaces.md +1 -1
- package/lib/core.mjs +124 -11
- package/lib/process-group.mjs +12 -0
- package/lib/readiness.mjs +119 -35
- package/package-catalog.json +1 -1
- package/package.json +1 -1
package/lib/readiness.mjs
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* executable approval, activation, runtime-package requirements, soul
|
|
8
8
|
* declarations) — never a second opinion. Unknown is unknown; "Ready" is the
|
|
9
9
|
* consumer's word and only when every required check passes. */
|
|
10
|
-
import {
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { killGroup } from "./process-group.mjs";
|
|
11
12
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
12
13
|
import { tmpdir } from "node:os";
|
|
13
14
|
import { join } from "node:path";
|
|
@@ -29,26 +30,67 @@ const item = (subject, status, { required = true, reason = null, producer = "ker
|
|
|
29
30
|
/** Verified Git signature of a source commit, named signer or nothing.
|
|
30
31
|
* Requires network (fetch) — only when the caller asks (`verify: true`);
|
|
31
32
|
* otherwise `unknown` with the reason. Never a URL, owner or hash as signer. */
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
export const SIGNATURE_FAILURES = Object.freeze(["transport-not-allowed", "fetch-failed", "fetch-timeout", "budget-exhausted", "verifier-failed", "verifier-timeout", "cannot-check", "no-source"]);
|
|
34
|
+
const ALLOWED_TRANSPORTS = /^(https:\/\/|ssh:\/\/|git@[^/:]+:)/;
|
|
35
|
+
/** Verified Git signature of a source commit, named signer or nothing.
|
|
36
|
+
* Requires network (fetch) — only when the caller asks (`verify: true`);
|
|
37
|
+
* otherwise `unknown` with the reason. Never a URL, owner or hash as signer.
|
|
38
|
+
* Bounded custody: one total budget per call (`budgetMs`, default 60s) shared
|
|
39
|
+
* by fetch and verify; each Git child runs in its own process group and is
|
|
40
|
+
* killed with the group on timeout; the scratch repository is removed on every
|
|
41
|
+
* exit including signals; Git reads NO global/system config and cannot prompt;
|
|
42
|
+
* only https/ssh transports are fetched. Failures carry a closed `failure`
|
|
43
|
+
* code — never stderr. */
|
|
44
|
+
/** One verification budget for a whole readiness read: every signatureOf call
|
|
45
|
+
* in that read draws from it, so a target with N capabilities is bounded by the
|
|
46
|
+
* TOTAL (default 120 s), not N × per-call. */
|
|
47
|
+
export function verificationBudget(totalMs = 120000) {
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
return { totalMs, remaining: () => totalMs - (Date.now() - started) };
|
|
50
|
+
}
|
|
51
|
+
const scratchDirs = new Set();
|
|
52
|
+
let signalCleanupInstalled = false;
|
|
53
|
+
function installSignalCleanup() {
|
|
54
|
+
if (signalCleanupInstalled) return; signalCleanupInstalled = true;
|
|
55
|
+
const sweep = () => { for (const d of scratchDirs) { try { rmSync(d, { recursive: true, force: true }); } catch { /* best effort */ } } scratchDirs.clear(); };
|
|
56
|
+
process.once("exit", sweep);
|
|
57
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, () => { sweep(); process.exit(128 + (sig === "SIGINT" ? 2 : sig === "SIGTERM" ? 15 : 1)); });
|
|
58
|
+
}
|
|
59
|
+
export function signatureOf({ url, commit }, { verify = false, budgetMs = 60000, budget = null } = {}) {
|
|
60
|
+
if (!url || !commit || commit === "local") return { status: "not-applicable", signer: null, reason: commit === "local" ? "path-installed capability has no source commit" : "no source recorded", failure: null };
|
|
61
|
+
if (!verify) return { status: "unknown", signer: null, reason: "signature verification needs a network fetch; pass --verify-signatures", failure: null };
|
|
62
|
+
if (!ALLOWED_TRANSPORTS.test(url)) return { status: "unknown", signer: null, reason: "source transport is not https or ssh; not fetched", failure: { code: "transport-not-allowed" } };
|
|
63
|
+
const shared = budget ?? verificationBudget(budgetMs);
|
|
64
|
+
if (shared.remaining() <= 0) return { status: "unknown", signer: null, reason: "the verification budget was exhausted", failure: { code: "budget-exhausted" } };
|
|
65
|
+
installSignalCleanup();
|
|
66
|
+
const dir = mkdtempSync(join(tmpdir(), "oats-sig-")); scratchDirs.add(dir);
|
|
67
|
+
const cleanup = () => { scratchDirs.delete(dir); try { rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ } };
|
|
68
|
+
const env = { PATH: process.env.PATH ?? "", HOME: dir, GIT_TERMINAL_PROMPT: "0", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", GIT_ASKPASS: "/bin/false", SSH_ASKPASS: "/bin/false", GIT_SSH_COMMAND: "ssh -o BatchMode=yes", LC_ALL: "C" };
|
|
69
|
+
const git = (args, stage) => {
|
|
70
|
+
const left = shared.remaining();
|
|
71
|
+
if (left <= 0) throw Object.assign(new Error("budget"), { failure: "budget-exhausted" });
|
|
72
|
+
const child = spawnSync("git", ["-C", dir, "-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null", "-c", "protocol.allow=never", "-c", "protocol.https.allow=always", "-c", "protocol.ssh.allow=always", ...args],
|
|
73
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: left, killSignal: "SIGKILL", detached: true, env, shell: false });
|
|
74
|
+
if (child.error?.code === "ETIMEDOUT" || child.signal === "SIGKILL") { killGroup(child); throw Object.assign(new Error(stage), { failure: `${stage}-timeout` }); }
|
|
75
|
+
if (child.error || child.status !== 0) { killGroup(child); throw Object.assign(new Error(stage), { failure: `${stage}-failed` }); }
|
|
76
|
+
return child.stdout;
|
|
77
|
+
};
|
|
38
78
|
try {
|
|
39
|
-
git(["init", "-q"]);
|
|
40
|
-
|
|
41
|
-
catch (e) { return { status: "unknown", signer: null, reason: `fetch failed: ${String(e.stderr ?? e.message ?? "").trim().slice(0, 200) || "unknown"}` }; }
|
|
79
|
+
git(["init", "-q"], "verifier");
|
|
80
|
+
git(["fetch", "-q", "--depth", "1", url, commit], "fetch");
|
|
42
81
|
// %G? : G good, B bad, U good-untrusted, X expired, Y expired key, R revoked, E cannot check, N none
|
|
43
|
-
const out = git(["log", "-1", "--format=%G?%x00%GS%x00%GK%x00%GF", commit]).trim();
|
|
82
|
+
const out = git(["log", "-1", "--format=%G?%x00%GS%x00%GK%x00%GF", commit], "verifier").trim();
|
|
44
83
|
const [code, signerName, keyId, fingerprint] = out.split("\0");
|
|
45
|
-
if (code === "N") return { status: "unsigned", signer: null, reason: "commit carries no signature" };
|
|
46
|
-
if (code === "G") return { status: "verified", signer: { id: fingerprint || keyId || null, label: signerName || null }, reason: null };
|
|
47
|
-
if (code === "U") return { status: "verified", signer: { id: fingerprint || keyId || null, label: signerName || null }, reason: "good signature from a key not marked trusted in the local keyring", trust: "untrusted-key" };
|
|
48
|
-
if (code === "E") return { status: "unknown", signer: null, reason: "signature present but cannot be checked (missing public key)" };
|
|
49
|
-
return { status: "invalid", signer: null, reason: { B: "bad signature", X: "good signature that has expired", Y: "good signature made by an expired key", R: "good signature made by a revoked key" }[code] ||
|
|
50
|
-
} catch (e) {
|
|
51
|
-
|
|
84
|
+
if (code === "N") return { status: "unsigned", signer: null, reason: "commit carries no signature", failure: null };
|
|
85
|
+
if (code === "G") return { status: "verified", signer: { id: fingerprint || keyId || null, label: signerName || null }, reason: null, failure: null };
|
|
86
|
+
if (code === "U") return { status: "verified", signer: { id: fingerprint || keyId || null, label: signerName || null }, reason: "good signature from a key not marked trusted in the local keyring", trust: "untrusted-key", failure: null };
|
|
87
|
+
if (code === "E") return { status: "unknown", signer: null, reason: "signature present but cannot be checked (missing public key)", failure: { code: "cannot-check" } };
|
|
88
|
+
return { status: "invalid", signer: null, reason: { B: "bad signature", X: "good signature that has expired", Y: "good signature made by an expired key", R: "good signature made by a revoked key" }[code] || "verifier reported an unrecognised state", failure: null };
|
|
89
|
+
} catch (e) {
|
|
90
|
+
const code = SIGNATURE_FAILURES.includes(e.failure) ? e.failure : "verifier-failed";
|
|
91
|
+
const reasons = { "fetch-failed": "the source could not be fetched", "fetch-timeout": "the fetch exceeded the verification budget", "budget-exhausted": "the verification budget was exhausted", "verifier-failed": "the local verifier failed", "verifier-timeout": "the local verifier exceeded the verification budget" };
|
|
92
|
+
return { status: "unknown", signer: null, reason: reasons[code], failure: { code } };
|
|
93
|
+
} finally { cleanup(); }
|
|
52
94
|
}
|
|
53
95
|
|
|
54
96
|
function sourceOfCapability(cap, catalog) {
|
|
@@ -61,37 +103,58 @@ function sourceOfCapability(cap, catalog) {
|
|
|
61
103
|
}
|
|
62
104
|
|
|
63
105
|
/** The quartet for a scope or one soul, from an inspect result. */
|
|
64
|
-
export function readinessOf(inspect, { soul = null, verifySignatures = false, catalog = null, deploymentDir = null, memberDocument = null } = {}) {
|
|
106
|
+
export function readinessOf(inspect, { soul = null, verifySignatures = false, catalog = null, deploymentDir = null, memberDocument = null, selector = null, verificationBudgetMs = 120000 } = {}) {
|
|
65
107
|
const caps = inspect.capabilities || [];
|
|
108
|
+
const budget = verifySignatures ? verificationBudget(verificationBudgetMs) : null;
|
|
66
109
|
const soulEntry = soul ? (inspect.souls || []).find((s) => s.name === soul) : null;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
110
|
+
const declaredRequires = soulEntry?.declarations?.requires?.capabilities ? Object.keys(soulEntry.declarations.requires.capabilities) : null;
|
|
111
|
+
const required = new Set(declaredRequires ?? caps.filter((c) => c.activation?.enabled).map((c) => c.id));
|
|
112
|
+
const declaredForSoul = (c) => !!c.activation?.declaredAt?.some((d) => (d.targets || []).includes(`soul:${soul}`));
|
|
113
|
+
const relevant = soul ? caps.filter((c) => required.has(c.id) || declaredForSoul(c)) : caps;
|
|
114
|
+
// Typed linkage for consumers (frame-level per-capability rows): WHICH
|
|
115
|
+
// capability, at which config level/scope, and WHY it is in this quartet.
|
|
116
|
+
const capabilityOf = (c) => ({ id: c.id, level: c.activation?.level ?? null, scope: c.activation?.target ?? null });
|
|
117
|
+
const originOf = (c) => declaredRequires?.includes(c.id) ? { kind: "requires", target: `soul:${soul}` }
|
|
118
|
+
: soul && declaredForSoul(c) ? { kind: "declares", target: `soul:${soul}` }
|
|
119
|
+
: c.activation?.enabled ? { kind: "default", target: c.activation?.target ?? "global" } : { kind: "inventory", target: null };
|
|
120
|
+
const typed = (c) => ({ capability: capabilityOf(c), origin: originOf(c) });
|
|
69
121
|
|
|
70
122
|
// installed — the artifact's bytes are present and locked with matching integrity
|
|
71
123
|
const installed = relevant.map((c) => {
|
|
72
124
|
const ok = c.health?.installed === true && (c.health.integrity == null || c.health.installedIntegrity == null || c.health.integrity === c.health.installedIntegrity);
|
|
73
125
|
return item(c.id, ok ? "pass" : c.health?.installed === false ? "fail" : "unknown", { required: required.has(c.id), producer: "oats list",
|
|
74
126
|
reason: ok ? null : c.health?.installed === false ? "not acquired" : c.health?.code || "integrity drift", evidence: { version: c.version ?? null, integrity: c.health?.integrity ?? null, origin: c.origin ?? null },
|
|
75
|
-
remedy: ok ? null : `oats install ${c.package || c.id}
|
|
127
|
+
remedy: ok ? null : `oats install ${c.package || c.id}`, ...typed(c) });
|
|
76
128
|
});
|
|
77
|
-
for (const id of required) if (!caps.some((c) => c.id === id)) installed.push(item(id, "fail", { producer: "soul declaration", reason: "declared by the soul but not in the inventory", remedy: `oats install <package providing ${id}
|
|
129
|
+
for (const id of required) if (!caps.some((c) => c.id === id)) installed.push(item(id, "fail", { producer: "soul declaration", reason: "declared by the soul but not in the inventory", remedy: `oats install <package providing ${id}>`,
|
|
130
|
+
capability: { id, level: null, scope: null }, origin: { kind: "requires", target: `soul:${soul}` } }));
|
|
78
131
|
|
|
79
132
|
// trusted — executable approval of the exact artifact; signature separately
|
|
80
133
|
const trusted = relevant.map((c) => {
|
|
81
|
-
|
|
134
|
+
// The inspect row states whether the manifest has anything trust approves
|
|
135
|
+
// (commands/hooks/launch env). No surface → trust is not-applicable, however
|
|
136
|
+
// the lock records it. Older rows without the flag fall back to the old heuristic.
|
|
137
|
+
const executable = typeof c.health?.executableSurface === "boolean" ? c.health.executableSurface
|
|
138
|
+
: !!(c.operations?.length || c.health?.code === "untrusted-surface");
|
|
82
139
|
const approved = c.health?.trusted === true;
|
|
83
|
-
const sig = signatureOf(sourceOfCapability(c, catalog), { verify: verifySignatures });
|
|
140
|
+
const sig = signatureOf(sourceOfCapability(c, catalog), { verify: verifySignatures, budget });
|
|
84
141
|
return item(c.id, !executable ? "not-applicable" : approved ? "pass" : c.health?.trusted === false ? "fail" : "unknown", { required: required.has(c.id), producer: "artifact approval",
|
|
85
142
|
reason: !executable ? "no executable surface" : approved ? null : "executable surface not approved", evidence: { integrity: c.health?.integrity ?? null }, remedy: approved || !executable ? null : `oats trust ${c.id}`,
|
|
86
|
-
signature: sig });
|
|
143
|
+
signature: sig, ...typed(c) });
|
|
87
144
|
});
|
|
88
145
|
|
|
89
|
-
// configured — activation for the subject
|
|
146
|
+
// configured — EFFECTIVE activation for the subject (a declaration at the
|
|
147
|
+
// soul is not activation: `enabled` is the resolved verdict for this subject,
|
|
148
|
+
// and a declared-but-disabled binding is a fail that says so) + runtime
|
|
149
|
+
// package requirements + layer readiness problems
|
|
90
150
|
const configured = [];
|
|
91
151
|
for (const c of relevant) {
|
|
92
|
-
const active =
|
|
93
|
-
|
|
94
|
-
|
|
152
|
+
const active = c.activation?.enabled === true;
|
|
153
|
+
const declaredOnly = !active && soul && declaredForSoul(c);
|
|
154
|
+
if (required.has(c.id)) configured.push(item(`${c.id} activation`, active ? "pass" : "fail", { producer: "oats-config.yaml",
|
|
155
|
+
reason: active ? null : declaredOnly ? `declared for soul ${soul} but disabled${c.activation?.reason ? ` (${c.activation.reason})` : ""}` : `not active for ${soul ? `soul ${soul}` : "this scope"}`,
|
|
156
|
+
evidence: { target: c.activation?.target ?? null, level: c.activation?.level ?? null, source: c.activation?.source ?? null }, remedy: active ? null : `oats use ${c.id}${soul ? ` --soul ${soul}` : ""}`, ...typed(c) }));
|
|
157
|
+
for (const miss of c.missingRequires || []) configured.push(item(`${c.id} requires ${miss.command}`, "fail", { producer: "capability manifest", reason: miss.why || "required command not on PATH", remedy: miss.install || null, ...typed(c) }));
|
|
95
158
|
}
|
|
96
159
|
for (const p of inspect.problems || []) if (/runtime package|DISABLED|extension/i.test(p.message || "")) configured.push(item(p.capability || p.code, "fail", { producer: "runtime settings", reason: p.message, remedy: null }));
|
|
97
160
|
if (soulEntry && soulEntry.readiness?.status === "undeclared") configured.push(item(`${soul} declarations`, "not-applicable", { required: false, producer: "soul.yaml", reason: "no requirements declared" }));
|
|
@@ -101,16 +164,32 @@ export function readinessOf(inspect, { soul = null, verifySignatures = false, ca
|
|
|
101
164
|
const enrolled = [];
|
|
102
165
|
const member = memberDocument ?? readMemberDocument(deploymentDir);
|
|
103
166
|
if (!member) enrolled.push(item("workspace membership", "not-applicable", { required: false, producer: "oats.yaml", reason: "standalone deployment: no workspace declared in oats.yaml" }));
|
|
167
|
+
else if (member.unreadable) enrolled.push(item("workspace membership", "unknown", { producer: "oats.yaml", reason: "workspace member document is unreadable; membership cannot be stated", evidence: { file: member.file ?? null }, remedy: "repair oats.yaml (valid YAML) and re-run" }));
|
|
104
168
|
else if (!member.workspace?.source) enrolled.push(item("workspace membership", "not-applicable", { required: false, producer: "oats.yaml", reason: "oats.yaml declares exports but no workspace backlink" }));
|
|
105
169
|
else enrolled.push(item(`member of ${member.workspace.source}`, member.admitted === true ? "pass" : member.admitted === false ? "fail" : "unknown", { producer: "workspace discovery",
|
|
106
|
-
reason: member.admitted === true ? null : member.admitted === false ? "this repository is not admitted in the workspace's members" : "admission not
|
|
170
|
+
reason: member.admitted === true ? null : member.admitted === false ? "this repository is not admitted in the workspace's members" : "reciprocal admission not observed: this CLI reads the backlink but does not yet fetch the workspace's members (declared, not verified)",
|
|
107
171
|
evidence: { workspace: member.workspace.source, revision: member.workspace.revision ?? null }, remedy: member.admitted === true ? null : "ask the workspace maintainer to admit this repository (oats-workspace.yaml members) — enrolment is admission, not login" }));
|
|
108
172
|
|
|
109
173
|
const checks = { installed: { status: roll(installed), items: installed }, trusted: { status: roll(trusted), items: trusted }, configured: { status: roll(configured), items: configured }, enrolled: { status: roll(enrolled), items: enrolled } };
|
|
110
174
|
const requiredStatuses = Object.values(checks).flatMap((c) => c.items.filter((i) => i.required).map((i) => i.status));
|
|
111
|
-
|
|
175
|
+
// Per-capability grouping of the same items (no second observation): each
|
|
176
|
+
// capability's four verdicts, ready only if all its REQUIRED items pass.
|
|
177
|
+
const byCapability = [...new Set(Object.values(checks).flatMap((c) => c.items.map((i) => i.capability?.id).filter(Boolean)))].sort().map((id) => {
|
|
178
|
+
const of = (name) => checks[name].items.filter((i) => i.capability?.id === id);
|
|
179
|
+
const statuses = Object.keys(checks).flatMap((name) => of(name).filter((i) => i.required).map((i) => i.status));
|
|
180
|
+
const any = Object.keys(checks).flatMap((name) => of(name)).find(Boolean);
|
|
181
|
+
return { capability: any?.capability ?? { id, level: null, scope: null }, origin: any?.origin ?? null, required: statuses.length > 0,
|
|
182
|
+
checks: Object.fromEntries(Object.keys(checks).map((name) => [name, of(name).length ? roll(of(name)) : "not-applicable"])),
|
|
183
|
+
ownReady: statuses.length > 0 && statuses.every((s) => s === "pass" || s === "not-applicable") };
|
|
184
|
+
});
|
|
185
|
+
// Items that belong to no capability (workspace membership, soul declarations)
|
|
186
|
+
// are SUBJECT-level: a capability's own verdict never overrides them, and a
|
|
187
|
+
// row must not read "ready" while the subject is blocked by one of them.
|
|
188
|
+
const subjectBlockers = Object.entries(checks).flatMap(([name, c]) => c.items.filter((i) => i.required && !i.capability && i.status !== "pass" && i.status !== "not-applicable").map((i) => ({ check: name, subject: i.subject, status: i.status })));
|
|
189
|
+
for (const g of byCapability) g.ready = g.ownReady && subjectBlockers.length === 0;
|
|
190
|
+
return { readinessApi: READINESS_API, subject: { ...(soul ? { kind: "soul", name: soul } : { kind: "scope", context: inspect.scope?.context ?? null }), ...(selector ? { selector } : {}) }, at: new Date().toISOString(),
|
|
112
191
|
checks, summary: { ready: requiredStatuses.length > 0 && requiredStatuses.every((s) => s === "pass" || s === "not-applicable"), required: requiredStatuses.length,
|
|
113
|
-
pass: requiredStatuses.filter((s) => s === "pass").length, fail: requiredStatuses.filter((s) => s === "fail").length, unknown: requiredStatuses.filter((s) => s === "unknown").length },
|
|
192
|
+
pass: requiredStatuses.filter((s) => s === "pass").length, fail: requiredStatuses.filter((s) => s === "fail").length, unknown: requiredStatuses.filter((s) => s === "unknown").length, byCapability, subjectBlockers },
|
|
114
193
|
notes: [
|
|
115
194
|
...(verifySignatures ? [] : ["signatures are unknown until --verify-signatures (network fetch)"]),
|
|
116
195
|
"ready means every REQUIRED check passes; it is never inferred from an empty set",
|
|
@@ -122,8 +201,13 @@ function readMemberDocument(deploymentDir) {
|
|
|
122
201
|
if (!deploymentDir) return null;
|
|
123
202
|
const file = join(deploymentDir, "oats.yaml");
|
|
124
203
|
if (!existsSync(file)) return null;
|
|
125
|
-
try {
|
|
126
|
-
|
|
204
|
+
try {
|
|
205
|
+
const doc = parseYamlNested(readFileSync(file, "utf8"));
|
|
206
|
+
// The lenient parser never throws: a `workspace:` that is present but not a
|
|
207
|
+
// mapping is a document we cannot read a membership from — unreadable, not absent.
|
|
208
|
+
if (doc.workspace !== undefined && (doc.workspace === null || typeof doc.workspace !== "object")) return { workspace: null, admitted: null, unreadable: true, file };
|
|
209
|
+
return { workspace: doc.workspace && typeof doc.workspace === "object" ? doc.workspace : null, admitted: null, exports: doc.exports ?? null };
|
|
210
|
+
} catch { return { workspace: null, admitted: null, unreadable: true, file }; }
|
|
127
211
|
}
|
|
128
212
|
|
|
129
213
|
/** Enforced policy for an instance (from its recorded metadata) or a soul
|
package/package-catalog.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.10",
|
|
4
4
|
"description": "OATS (Open Agent Team Specification) — durable souls, disposable instances, targetable capability packages, and the runtime-neutral oats CLI/kernel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|