@awebai/oats 0.24.0 → 0.24.2

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.
Files changed (43) hide show
  1. package/README.md +224 -394
  2. package/bin/oats.mjs +192 -13
  3. package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +11 -0
  4. package/capabilities/oats-aweb/bin/oats-aweb.mjs +26 -0
  5. package/capabilities/oats-aweb/lib/binding-wire.mjs +214 -0
  6. package/capabilities/oats-aweb/lib/captured-execution.mjs +91 -0
  7. package/capabilities/oats-aweb/lib/captured-native.mjs +91 -0
  8. package/capabilities/oats-aweb/lib/invocation-shape.mjs +135 -0
  9. package/capabilities/oats-aweb/lib/portable-binding.mjs +146 -0
  10. package/capabilities/oats-aweb/lib/session-readiness.mjs +56 -0
  11. package/capabilities/oats-aweb/oats.json +12 -3
  12. package/capabilities/oats-okf/lib/captured-worker.mjs +12 -4
  13. package/capabilities/oats-okf/oats.json +1 -1
  14. package/docs/capabilities.md +4 -0
  15. package/docs/design/2026-09-20-redesign-program-board.md +83 -0
  16. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +287 -0
  17. package/docs/design/2026-09-20-workspace-onboarding-public.md +124 -0
  18. package/docs/design/README.md +42 -0
  19. package/docs/first-team.md +43 -1
  20. package/docs/knowledge-theory.md +353 -111
  21. package/docs/knowledge.md +10 -1
  22. package/docs/layers.md +89 -354
  23. package/docs/official-marketplace.md +84 -0
  24. package/docs/packages.md +15 -7
  25. package/docs/release-notes/v0.24.1.md +17 -0
  26. package/docs/release-notes/v0.24.2.md +21 -0
  27. package/docs/souls-and-instances.md +45 -7
  28. package/docs/workspace-adoption.md +314 -0
  29. package/docs/workspaces.md +154 -0
  30. package/injects/oats-portable.md +8 -5
  31. package/lib/core.mjs +89 -17
  32. package/lib/portable-onboarding.mjs +19 -0
  33. package/lib/prepared-resources.mjs +1 -1
  34. package/lib/provider-binding-broker.mjs +6 -1
  35. package/lib/setup-expert-source.mjs +76 -0
  36. package/package-catalog.json +9 -5
  37. package/package.json +3 -1
  38. package/skills/oats-config/SKILL.md +4 -5
  39. package/skills/oats-portable/SKILL.md +1 -2
  40. package/skills/oats-portable-artifacts/SKILL.md +2 -2
  41. package/souls/oats-setup-expert/AGENTS.md +60 -0
  42. package/souls/oats-setup-expert/soul.yaml +14 -0
  43. package/skills/oats-portable-setup/SKILL.md +0 -69
package/lib/core.mjs CHANGED
@@ -51,6 +51,7 @@ import { buildCapturedInvocationContext, withCapturedInvocationContextFile } fro
51
51
  import { readCapturedInstanceIndex, readCapturedInstanceMetadata, readCapturedInstanceAuthority, assertCapturedInstanceCustody,
52
52
  recordCapturedCustodyFailure, markCapturedNativeScaffold, failCapturedNativeIntent, observeCapturedNativeTarget, setCapturedInstanceStatus, admitCapturedInstanceAction, beginCapturedIntent, settleCapturedIntent } from "./captured-instance-index.mjs";
53
53
  import { validateExecutionBinding } from "./schedule-capsule.mjs";
54
+ import { parsePortableSource } from "./source-spec.mjs";
54
55
  import { validateIntentRef } from "./captured-admission-shape.mjs";
55
56
  export { readCapturedInstanceIndex, readCapturedInstanceMetadata, beginCapturedIntent, settleCapturedIntent };
56
57
  export { buildCapturedInvocationContext, withCapturedInvocationContextFile };
@@ -61,6 +62,7 @@ import { validateCapturedLaunchRequest } from "./captured-launch-request.mjs";
61
62
  import { validateCapabilityInputDeclarations } from "./capability-inputs.mjs";
62
63
  import { validateCapturedSessionBackend, validateCapturedSessionTarget, assertCapturedSessionPlacement } from "./captured-session-backend.mjs";
63
64
  import { createRepositoryTransaction } from "./repository-observation.mjs";
65
+ import { inspectPortableOnboarding as inspectOnboarding, describePortableOnboarding } from "./portable-onboarding.mjs";
64
66
  import { readLock3 } from "./portable-lock.mjs";
65
67
  import { portableScope, portableStateDirectory } from "./portable-state.mjs";
66
68
  import { objectAt } from "./portable-shape.mjs";
@@ -111,6 +113,18 @@ export const OATS_VERSION = JSON.parse(readFileSync(join(PKG_ROOT, "package.json
111
113
  /** Skills shipped with the kernel. Only oats-getting-started is ambient; spawn composes selected skills locally. */
112
114
  export const PACKAGED_SKILLS_DIR = join(PKG_ROOT, "skills");
113
115
 
116
+ function soulRequirements(soulDir) {
117
+ const file = soulDir && join(soulDir, "soul.yaml");
118
+ return file && existsSync(file) ? withConfigFile(file, () => parseYamlNested(readFileSync(file, "utf8"))).requires : undefined;
119
+ }
120
+ function declaredOperationalCapabilities(soulDir) {
121
+ const capabilities = soulRequirements(soulDir)?.capabilities ?? {};
122
+ return ["oats.core", "oats.setup"].filter(id => Object.hasOwn(capabilities, id));
123
+ }
124
+ function legacyOperationalSkills(soulDir) {
125
+ return declaredOperationalCapabilities(soulDir).length ? [] : ["oats", "oats-config", "oats-packages"].map(name => ({ id: "kernel", path: join(PACKAGED_SKILLS_DIR, name) }));
126
+ }
127
+
114
128
  // ---------- shell helpers ----------
115
129
  function sh(cmdline) { return execSync(cmdline, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim(); }
116
130
  function shTry(cmdline) { try { return sh(cmdline); } catch { return undefined; } }
@@ -1194,6 +1208,31 @@ export function approveAvailableCapability(deployment, artifactSet, capability,
1194
1208
  return approveAvailableArtifact(deployment, artifactSet, capability, origin, loadRetainedManifest);
1195
1209
  }
1196
1210
 
1211
+ /** Public read-only source/workspace inspection through the existing facade.
1212
+ * Owns only transient repository scratch, never deployment state, provisioning,
1213
+ * provider code, approvals or a serialized mutation witness. */
1214
+ export function inspectPortableOnboarding(input, { repositoryOptions = {} } = {}) {
1215
+ canonicalJson(input);
1216
+ const directory = mkdtempSync(join(realpathSync(tmpdir()), "oats-source-inspection-")), owned = lstatSync(directory);
1217
+ let repositories, primary;
1218
+ try {
1219
+ repositories = createRepositoryTransaction({ ...repositoryOptions, directory, accessContextKey: repositoryOptions.accessContextKey ?? "native" });
1220
+ return describePortableOnboarding(inspectOnboarding(input, { repositories }));
1221
+ } catch (error) { primary = error; throw error; }
1222
+ finally {
1223
+ try {
1224
+ repositories?.close();
1225
+ const current = lstatSync(directory);
1226
+ if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError("source-incomplete", "inspection scratch ownership changed");
1227
+ removeOwnedStaging(directory);
1228
+ } catch (cleanup) {
1229
+ if (!primary) throw cleanup;
1230
+ const error = new AggregateError([primary, cleanup], "inspection and owned scratch cleanup failed", { cause: primary });
1231
+ error.code = primary.code; error.stagingPath = directory; throw error;
1232
+ }
1233
+ }
1234
+ }
1235
+
1197
1236
  /** Explicit new-work preparation, with one frozen repository transaction.
1198
1237
  * This first adapter captures command/curriculum profiles; provider-owned
1199
1238
  * binding or distinct helper policy gaps return needs-configuration, never
@@ -4057,6 +4096,10 @@ export function composeInstanceAgentsMd(soulDir, contextDir, soulName, workMode,
4057
4096
  if (!existsSync(agentsMd)) throw new Error(`canonical soul instructions missing: ${agentsMd}`);
4058
4097
  const resolved = resolveOatsConfig(contextDir, soulName);
4059
4098
  const wanted = [];
4099
+ const declaredOperations = declaredOperationalCapabilities(soulDir);
4100
+ const oatsCoreDeclared = declaredOperations.includes("oats.core");
4101
+ if (declaredOperations.length) resolved.kernelInjection = { inject: undefined,
4102
+ provenance: `declared ${declaredOperations.join(", ")} ${declaredOperations.length === 1 ? "capability" : "capabilities"}` };
4060
4103
  const kernelInject = resolved.kernelInjection?.inject;
4061
4104
  if (kernelInject && existsSync(kernelInject)) wanted.push(["kernel:oats", kernelInject]);
4062
4105
  if (kind === "local") {
@@ -4078,7 +4121,7 @@ export function composeInstanceAgentsMd(soulDir, contextDir, soulName, workMode,
4078
4121
  }
4079
4122
  for (const inj of resolved.injects) wanted.push([`config:${inj.source}`, inj.file]);
4080
4123
  const blocks = wanted.map(([source, file]) => ({ source, file, content: readFileSync(file, "utf8").trim() }));
4081
- return { text: renderInstructionText(readFileSync(agentsMd, "utf8"), blocks), blocks, resolved };
4124
+ return { text: renderInstructionText(readFileSync(agentsMd, "utf8"), blocks), blocks, resolved, oatsCoreDeclared };
4082
4125
  }
4083
4126
 
4084
4127
  /** The skill entries a tree contributes — THE discovery rule, shared by preflight
@@ -4136,7 +4179,7 @@ export function planInstanceResources({ resolved, soulDir, agent, contextDir, co
4136
4179
  }
4137
4180
  };
4138
4181
 
4139
- for (const path of [join(PACKAGED_SKILLS_DIR, "oats"), join(PACKAGED_SKILLS_DIR, "oats-config"), join(PACKAGED_SKILLS_DIR, "oats-packages")]) {
4182
+ for (const { path } of legacyOperationalSkills(soulDir)) {
4140
4183
  add({ type: "skill-tree", source: "kernel", declared: basename(path), path: existsSync(path) ? path : undefined });
4141
4184
  }
4142
4185
  const soulSkills = soulDir && join(soulDir, "skills");
@@ -5084,15 +5127,31 @@ export function resolveYolo(value) {
5084
5127
  throw new Error("yolo must be true or false");
5085
5128
  }
5086
5129
 
5087
- export function writeSoul(root, { name, kind, repo, work, runtime, model, yolo, description, type, instructions }) {
5130
+ /** Creation chooses a declared source, never acquisition/activation or a made-up
5131
+ * future release. Existing definitions (including deliberate removal) are kept. */
5132
+ function defaultOatsCoreRequirement() {
5133
+ const mapping = officialCapabilityPackage("oats.core");
5134
+ const entry = mapping.available && mapping.migratedCapability === "oats.core" && officialPackageCatalog()[mapping.package];
5135
+ if (entry && typeof entry.url === "string" && typeof entry.ref === "string" && entry.ref) {
5136
+ const parsed = parsePortableSource(`git:${entry.url}@${entry.ref}#${entry.path ?? DEFAULT_PACKAGE_PATH}`);
5137
+ return { requires: { capabilities: { "oats.core": { source: `${parsed.source}#${parsed.path}` } } }, notes: [] };
5138
+ }
5139
+ return { requires: undefined, notes: [{ code: "needs-configuration", message: "oats.core has no published revision in the official package catalog; no source was invented. Add its explicit requirement after the catalog entry is available, or use --no-oats-core to opt out at creation." }] };
5140
+ }
5141
+
5142
+ export function writeSoul(root, { name, kind, repo, work, runtime, model, yolo, description, type, instructions, oatsCore = true }) {
5088
5143
  yolo = resolveYolo(yolo);
5144
+ if (typeof oatsCore !== "boolean") throw oatsError("E_BAD_ARGS", "oatsCore must be a boolean");
5089
5145
  const agentDir = agentDirOf(root, name, kind);
5090
- const soulDir = soulOf(agentDir);
5146
+ const soulDir = soulOf(agentDir), soulFile = join(soulDir, "soul.yaml");
5147
+ const { requires, notes } = existsSync(soulFile)
5148
+ ? { requires: soulRequirements(soulDir), notes: [] }
5149
+ : oatsCore ? defaultOatsCoreRequirement() : { requires: undefined, notes: [] };
5091
5150
  mkdirSync(soulDir, { recursive: true });
5092
5151
  mkdirSync(join(agentDir, "instances"), { recursive: true });
5093
- writeFileSync(join(soulDir, "soul.yaml"), yamlFlat({
5152
+ writeFileSync(soulFile, yamlFlat({
5094
5153
  name, kind, description, type, repo, work: work || "checkout", runtime: runtime || "pi", model, yolo,
5095
- }));
5154
+ }) + (requires === undefined ? "" : `requires: ${JSON.stringify(requires)}\n`));
5096
5155
  const agentsMd = join(soulDir, "AGENTS.md");
5097
5156
  if (instructions !== undefined || !existsSync(agentsMd)) {
5098
5157
  writeFileSync(agentsMd, instructions ?? defaultSoulAgentsMd(name, description));
@@ -5106,7 +5165,7 @@ export function writeSoul(root, { name, kind, repo, work, runtime, model, yolo,
5106
5165
  home: soulDir, instance: name, agentName: name, soulDir,
5107
5166
  contextDir: ctx, workspaceDir: workspaceOf(root), rootDir: root, resolved,
5108
5167
  });
5109
- return { agentDir, soulDir };
5168
+ return { agentDir, soulDir, ...(notes.length ? { notes } : {}) };
5110
5169
  }
5111
5170
  function defaultSoulAgentsMd(name, description) {
5112
5171
  return `# ${name}
@@ -5128,8 +5187,8 @@ export function createAgent(root, o) {
5128
5187
  // kind: "local" → a FULL soul (memory, skills, instances) under the scope's
5129
5188
  // local-agents/ — uncommitted by contract; otherwise a committed persistent soul.
5130
5189
  const kind = o.local || o.kind === "local" ? "local" : "persistent";
5131
- const { agentDir } = writeSoul(root, { ...o, name, kind });
5132
- return { agent: name, kind, soul: soulOf(agentDir) };
5190
+ const { agentDir, notes } = writeSoul(root, { ...o, name, kind });
5191
+ return { agent: name, kind, soul: soulOf(agentDir), ...(notes ? { notes } : {}) };
5133
5192
  }
5134
5193
 
5135
5194
  /** Upsert a local agent soul (from raw instructions or a Claude-style def file).
@@ -5156,13 +5215,13 @@ export function upsertLocalAgent(root, o) {
5156
5215
  const existing = findAgent(root, name);
5157
5216
  if (existing && existing.kind !== "local") throw new Error(`"${name}" is a persistent agent — spawn it instead`);
5158
5217
  if (!existing && instructions === undefined) throw new Error(`local agent "${name}" needs instructions (none on disk yet)`);
5159
- writeSoul(root, {
5160
- name, kind: "local",
5218
+ const { notes } = writeSoul(root, {
5219
+ name, kind: "local", oatsCore: o.oatsCore,
5161
5220
  repo: repo ?? existing?.repo, work: work ?? existing?.work,
5162
5221
  runtime: runtime ?? existing?.runtime, model: model ?? existing?.model, yolo: yolo ?? existing?.yolo,
5163
5222
  description: description ?? existing?.description, instructions,
5164
5223
  });
5165
- return findAgent(root, name);
5224
+ return { ...findAgent(root, name), ...(notes ? { notes } : {}) };
5166
5225
  }
5167
5226
  /** Back-compat alias: older installed capabilities (oats-okf ≤1.3.x) call this. */
5168
5227
  export const upsertTmpAgent = upsertLocalAgent;
@@ -6110,7 +6169,7 @@ export function spawnInstance(root, agent, o = {}) {
6110
6169
  symlinkSync("AGENTS.md", join(home, "CLAUDE.md"));
6111
6170
 
6112
6171
  // Runtime-neutral exact skill materialization. No harness receives ambient workspace/package skills.
6113
- const sources = [{ id: "kernel", path: join(PACKAGED_SKILLS_DIR, "oats") }, { id: "kernel", path: join(PACKAGED_SKILLS_DIR, "oats-config") }, { id: "kernel", path: join(PACKAGED_SKILLS_DIR, "oats-packages") }];
6172
+ const sources = legacyOperationalSkills(soulDir);
6114
6173
  const soulSkills = join(soulDir, "skills");
6115
6174
  if (existsSync(soulSkills)) sources.push({ id: "soul", path: soulSkills });
6116
6175
  for (const cap of resolvedCfg.capabilities) for (const path of cap.skills || []) sources.push({ id: cap.id, path });
@@ -7121,9 +7180,11 @@ function runtimeAuthorityOf(baseline) {
7121
7180
  return { launched: true, tmux: { session: tmux.session, window: tmux.window, socket: resolve(tmux.socket) } };
7122
7181
  }
7123
7182
 
7124
- /** Session control uses the same independent endpoint receipt as retirement. */
7183
+ /** Session control uses the independent endpoint receipt and, for captured
7184
+ * homes, its original incarnation's existing home/work custody. */
7125
7185
  function instanceSessionTarget(home) {
7126
7186
  if (typeof home !== "string" || !isAbsolute(home)) throw oatsError("E_BAD_ARGS", "session needs an absolute instance home");
7187
+ const requestedHome = home;
7127
7188
  home = realPathOrNearest(home);
7128
7189
  let baseline, meta;
7129
7190
  try {
@@ -7132,18 +7193,29 @@ function instanceSessionTarget(home) {
7132
7193
  } catch (e) { throw oatsError("E_RUNTIME_ENDPOINT_UNKNOWN", `cannot read session receipt for ${home}: ${e.message}`); }
7133
7194
  const authority = baseline.version === RETIRE_BASELINE_VERSION && baseline.home === home && runtimeAuthorityOf(baseline);
7134
7195
  if (!authority) throw oatsError("E_RUNTIME_ENDPOINT_UNKNOWN", `independent session receipt is missing or invalid for ${home}`);
7196
+ let io;
7197
+ if ([baseline, meta].some(value => ["executionBinding", "incarnationId", "captured"].some(key => Object.hasOwn(value, key)))) {
7198
+ // The independent receipt remains the anchor even if mutable home markers
7199
+ // are removed. Missing/invalid captured evidence never falls back to legacy.
7200
+ validateExecutionBinding(baseline.executionBinding);
7201
+ const { row } = readCapturedInstanceAuthority(baseline.executionBinding.deployment, requestedHome);
7202
+ if (row.incarnationId !== baseline.incarnationId || canonicalJson(row.executionBinding) !== canonicalJson(baseline.executionBinding)) {
7203
+ throw oatsError("E_RUNTIME_AUTHORITY_MISMATCH", "captured incarnation disagrees with independent session receipt");
7204
+ }
7205
+ io = { exec: (...args) => { assertCapturedInstanceCustody(row); return execFileSync(...args); } };
7206
+ }
7135
7207
  const endpointAgrees = authority.sessionTarget
7136
7208
  ? !meta.tmux && ["backend", "binary", "socket", "workspaceId", "paneId", "terminalId", "protocol"].every((key) => meta.sessionTarget?.[key] === authority.sessionTarget[key])
7137
7209
  : !meta.sessionTarget && meta.tmux?.session === authority.tmux?.session && meta.tmux?.window === authority.tmux?.window && resolve(meta.tmux?.socket || ".") === authority.tmux?.socket;
7138
7210
  if (meta.launched !== authority.launched || (authority.launched && !endpointAgrees)) throw oatsError("E_RUNTIME_AUTHORITY_MISMATCH", "instance metadata disagrees with independent session receipt");
7139
- return { home, target: authority.launched ? authority.sessionTarget || { backend: "tmux", ...authority.tmux } : undefined };
7211
+ return { home, target: authority.launched ? authority.sessionTarget || { backend: "tmux", ...authority.tmux } : undefined, io };
7140
7212
  }
7141
7213
 
7142
7214
  export function inspectInstanceSession(home) {
7143
7215
  if (typeof home === "string" && isAbsolute(home) && !existsSync(home)) return { home: realPathOrNearest(home), backend: null, present: false, state: "stopped" };
7144
7216
  const s = instanceSessionTarget(home);
7145
7217
  if (!s.target) return { home: s.home, backend: null, present: false, state: "not-launched" };
7146
- try { return { home: s.home, ...inspectSessionTarget(s.target) }; }
7218
+ try { return { home: s.home, ...inspectSessionTarget(s.target, s.io) }; }
7147
7219
  catch (e) { throw oatsError("E_SESSION_UNAVAILABLE", `cannot inspect session: ${e.message}`); }
7148
7220
  }
7149
7221
 
@@ -7157,7 +7229,7 @@ export function inputInstanceSession(home, text) {
7157
7229
  if (typeof text !== "string" || !text.trim() || text.includes("\0") || Buffer.byteLength(text) > 256 * 1024) throw oatsError("E_BAD_ARGS", "session input must be nonempty text without NUL, at most 256 KiB");
7158
7230
  const s = instanceSessionTarget(home);
7159
7231
  if (!s.target) throw oatsError("E_SESSION_NOT_RUNNING", "instance was not launched");
7160
- try { return { home: s.home, ...inputSessionTarget(s.target, text) }; }
7232
+ try { return { home: s.home, ...inputSessionTarget(s.target, text, s.io) }; }
7161
7233
  catch (e) { throw oatsError("E_SESSION_INPUT_FAILED", `cannot submit session input: ${e.message}`); }
7162
7234
  }
7163
7235
 
@@ -189,6 +189,25 @@ export function inspectPortableOnboarding(input, { repositories } = {}) {
189
189
  return result;
190
190
  }
191
191
 
192
+ /** Metadata-only public view, NOT a serialized fresh-inspection witness or a
193
+ * preparation request. Provider payloads/adoption values have not gone through
194
+ * their owner's nonsecret classification and must not leak through inspection. */
195
+ export function describePortableOnboarding(inspection) {
196
+ if (!issued.has(inspection)) throw oatsError("invalid-declaration", "inspection summary requires an issued onboarding view");
197
+ const reference = value => ({ source: value.source, soul: value.soul, revision: value.revision, alias: value.alias,
198
+ adoptionPresent: Object.hasOwn(value, "adoption") });
199
+ const { reference: selected, exports, ...source } = inspection.source;
200
+ return freezeJson({ ...inspection,
201
+ source: { ...source, adoptionPresent: Object.hasOwn(selected, "adoption"), exports: {
202
+ souls: (exports.souls ?? []).map(({ path, definition, description }) => ({ path, definition, ...(description === undefined ? {} : { description }) })),
203
+ packages: (exports.packages ?? []).map(({ path, description }) => ({ path, ...(description === undefined ? {} : { description }) })),
204
+ knowledge: (exports.knowledge ?? []).map(({ contract, version }) => ({ contract, version, payloadOmitted: true })),
205
+ } },
206
+ workspace: inspection.workspace ? { ...inspection.workspace, imports: inspection.workspace.imports.map(reference) } : null,
207
+ omitted: { providerPayloads: true, adoptionValues: true },
208
+ });
209
+ }
210
+
192
211
  /** Ephemeral local root custody, not a new persistent authority/schema. Ordinary
193
212
  * project content changes are allowed; replacing the inspected directories or
194
213
  * provisioning a previously absent deployment requires a fresh inspection. */
@@ -23,7 +23,7 @@ export function completePreparedResources({ seed, plan, manifests, mode, deploym
23
23
  }
24
24
  if (problems.length) return { record: null, problems };
25
25
  const snapshot = join(directory, "kernel-resources"); mkdirSync(snapshot); mkdirSync(join(snapshot, "injects"));
26
- const kernelSkills = ["oats-portable", "oats-portable-setup", "oats-portable-artifacts"];
26
+ const kernelSkills = ["oats-portable", "oats-portable-artifacts"];
27
27
  const workInjection = (workMode) => workMode === "directory" ? "portable-work-directory.md" : `work-${workMode}.md`;
28
28
  const injectionFiles = ["oats-portable.md", "portable-instance-boundary.md", ...kernel.workModes.map(workInjection)];
29
29
  // Only known kernel resources, never a sweep of the checkout/node_modules,
@@ -1,6 +1,8 @@
1
1
  /** Execute only an approved retained provider command. Preparation does not need
2
2
  * a fabricated complete resolution merely to obtain its binding. */
3
3
  import { spawnSync } from 'node:child_process';
4
+ import { realpathSync } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
4
6
  import { canonicalJson } from './portable-values.mjs';
5
7
  import { objectAt } from './portable-shape.mjs';
6
8
  import { validateArtifactSet } from './resolution-shape.mjs';
@@ -12,6 +14,9 @@ import { validateBindingInterface } from './provider-binding.mjs';
12
14
  import { BINDING_LIMITS, validateBindingRequest, decodeBindingResponse } from './provider-binding-wire.mjs';
13
15
  import { oatsError } from './errors.mjs';
14
16
 
17
+ // Same kernel-owned CLI locator as lifecycle hooks; never caller env or PATH.
18
+ const CLI_BIN=fileURLToPath(new URL('../bin/oats.mjs',import.meta.url));
19
+
15
20
  export function invokeProviderBinding(options,codecs) {
16
21
  canonicalJson(options);
17
22
  objectAt(options,['deployment','artifacts','capability','phase','settings','input','timeoutMs'],['deployment','artifacts','capability','phase','settings','input']);
@@ -46,7 +51,7 @@ export function invokeProviderBinding(options,codecs) {
46
51
  const [script,...args]=spec.trim().split(/\s+/),file=codecs.executable(manifest,script);
47
52
  if (!file) throw oatsError('resource-not-found','provider codec executable is unavailable');
48
53
  const environment=Object.fromEntries(Object.entries(process.env).filter(([key])=>! /^(OATS_|OAS_|PI_)/.test(key)));
49
- Object.assign(environment,{OATS_CAPABILITY:capability,OATS_CAPABILITY_ROOT:root,OATS_SETTINGS:canonicalJson(settings,BINDING_LIMITS)});
54
+ Object.assign(environment,{OATS_CAPABILITY:capability,OATS_CAPABILITY_ROOT:root,OATS_SETTINGS:canonicalJson(settings,BINDING_LIMITS),OATS_CLI_BIN:realpathSync(CLI_BIN)});
50
55
  let result;
51
56
  try {
52
57
  result=spawnSync(process.execPath,[file,...args],{cwd:root,env:environment,input:canonicalJson(request,BINDING_LIMITS),
@@ -0,0 +1,76 @@
1
+ /** Read the setup edition as data for a CLASSIC local bootstrap copy.
2
+ * No provider code, workspace activation, enrollment or captured identity. */
3
+ import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { parsePortableSoul } from './portable-soul.mjs';
8
+ import { parseRepositorySource, parsePortableSource } from './source-spec.mjs';
9
+ import { createRepositoryTransaction } from './repository-observation.mjs';
10
+ import { createWorkspaceDiscovery } from './workspace-discovery.mjs';
11
+ import { packageIntegrity } from './core.mjs';
12
+ import { oatsError } from './errors.mjs';
13
+
14
+ export const SETUP_EXPERT = 'oats-setup-expert';
15
+ export const SETUP_CAPABILITIES = Object.freeze(['oats.core', 'oats.setup']);
16
+ const EXPORT = `souls/${SETUP_EXPERT}`;
17
+ function validateEdition(bytes) {
18
+ const { declaration: soul } = parsePortableSoul(bytes);
19
+ const required = soul.requires || {}, caps = required.capabilities || {};
20
+ if (soul.name !== SETUP_EXPERT || soul.work !== 'directory'
21
+ || Object.keys(required).some(key => key !== 'capabilities')
22
+ || Object.keys(caps).length !== 2 || SETUP_CAPABILITIES.some(id => caps[id]?.source !== 'repo:oats-package' || Object.keys(caps[id]).some(key => key !== 'source'))
23
+ || ['knowledge', 'messaging', 'tasks'].some(slot => soul.defaults?.[slot] !== 'none')
24
+ || Object.keys(soul.defaults || {}).some(key => !['knowledge', 'messaging', 'tasks'].includes(key))
25
+ || soul.knowledge || soul.teams?.length || soul.resources?.length || soul.yolo === true || soul.backend || soul['launch-config']) {
26
+ throw oatsError('needs-configuration', 'classic setup bootstrap needs the provider-independent directory edition with only oats.core/oats.setup; use explicit portable preparation for other requirements');
27
+ }
28
+ return soul;
29
+ }
30
+ function repositoryRequest(source) {
31
+ if (typeof source !== 'string' || source.includes('#')) throw oatsError('invalid-source', '--workspace needs a Git repository source, optionally @revision, without a package fragment');
32
+ try { return { source: parseRepositorySource(source).normalized }; }
33
+ catch {
34
+ const parsed = parsePortableSource(source);
35
+ if (parsed.kind !== 'git') throw oatsError('invalid-source', '--workspace needs an explicit Git repository source');
36
+ return { source: `git:${parsed.url}`, revision: parsed.selector };
37
+ }
38
+ }
39
+
40
+ export function loadSetupExpertEdition(workspace, repositoryOptions = {}) {
41
+ if (workspace === undefined) {
42
+ const root = fileURLToPath(new URL(`../${EXPORT}/`, import.meta.url));
43
+ return { declaration: validateEdition(readFileSync(join(root, 'soul.yaml'))), instructions: readFileSync(join(root, 'AGENTS.md'), 'utf8'),
44
+ source: { kind: 'packaged-definition', captured: false }, packageIntegrity: null };
45
+ }
46
+ const request = repositoryRequest(workspace), scratch = realpathSync(mkdtempSync(join(tmpdir(), 'oats-setup-source-'))), owned = lstatSync(scratch);
47
+ let transaction;
48
+ try {
49
+ transaction = createRepositoryTransaction({ ...repositoryOptions, directory: scratch, accessContextKey: 'explicit-setup-source' });
50
+ const discovery = createWorkspaceDiscovery(transaction), origin = { kind: 'operator', document: { kind: 'operator', id: 'oats-onboard' }, pointer: '/workspace' };
51
+ const observed = transaction.observe(request.source, { ...request, origin });
52
+ const workspaceDoc = transaction.readFile(observed, 'oats-workspace.yaml', { optional: true });
53
+ const view = workspaceDoc ? discovery.readWorkspace({ ...request, origin }) : null;
54
+ const reference = view?.parsed.imports.find(item => item.alias === SETUP_EXPERT)
55
+ ?? { source: request.source, revision: observed.source.commit, soul: EXPORT, alias: SETUP_EXPERT };
56
+ // A selected workspace import never falls back if its exact source fails.
57
+ const imported = discovery.importSoul(reference, { origin });
58
+ if (reference.adoption) throw oatsError('needs-configuration', 'classic setup copies an edition only; provider adoption values require the portable preparation path');
59
+ const declaration = validateEdition(transaction.readFile(imported.observation, imported.definition).bytes);
60
+ const projection = join(scratch, 'edition');
61
+ transaction.materialize(imported.observation, imported.roots, projection);
62
+ const soulRoot = join(projection, dirname(imported.definition)), body = join(soulRoot, 'AGENTS.md'), alias = join(soulRoot, 'CLAUDE.md');
63
+ if (!lstatSync(body).isFile() || !lstatSync(alias).isSymbolicLink() || readlinkSync(alias) !== 'AGENTS.md'
64
+ || readdirSync(soulRoot).some(name => !['soul.yaml', 'AGENTS.md', 'CLAUDE.md'].includes(name))) {
65
+ throw oatsError('source-incomplete', 'classic setup edition must have canonical AGENTS.md/CLAUDE.md and no omitted private skill or knowledge trees');
66
+ }
67
+ return { declaration, instructions: readFileSync(body, 'utf8'), packageIntegrity: packageIntegrity(join(projection, 'oats-package')),
68
+ source: { kind: 'exported-edition-copy', source: imported.reference.source, revision: imported.observation.source.commit,
69
+ path: imported.reference.soul, workspaceRevision: view?.source.commit ?? null, captured: false } };
70
+ } finally {
71
+ transaction?.close();
72
+ const current = lstatSync(scratch);
73
+ if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError('source-unavailable', 'setup source scratch ownership changed');
74
+ rmSync(scratch, { recursive: true });
75
+ }
76
+ }
@@ -1,13 +1,14 @@
1
1
  {
2
+ "policy": "docs/official-marketplace.md",
2
3
  "packages": {
3
4
  "oats.okf": {
4
5
  "url": "https://github.com/awebai/oats-okf.git",
5
- "ref": "v2.1.0",
6
+ "ref": "v2.1.1",
6
7
  "path": "oats-package"
7
8
  },
8
9
  "oats.aweb": {
9
10
  "url": "https://github.com/awebai/oats-aweb.git",
10
- "ref": "v1.10.3",
11
+ "ref": "v1.11.0",
11
12
  "path": "oats-package"
12
13
  },
13
14
  "oats.jira": {
@@ -30,9 +31,9 @@
30
31
  "ref": "v1.0.0",
31
32
  "path": "oats-package"
32
33
  },
33
- "oats.knowledge-theory": {
34
+ "oats.framework": {
34
35
  "url": "https://github.com/awebai/oats.git",
35
- "ref": "v0.23.0",
36
+ "ref": "oats-framework/v1.1.1",
36
37
  "path": "oats-package"
37
38
  }
38
39
  },
@@ -65,6 +66,9 @@
65
66
  "oas.review": {
66
67
  "package": "oats.dev",
67
68
  "capability": "oats.review"
68
- }
69
+ },
70
+ "oats.knowledge-theory": "oats.framework",
71
+ "oats.core": "oats.framework",
72
+ "oats.setup": "oats.framework"
69
73
  }
70
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awebai/oats",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
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",
@@ -36,6 +36,8 @@
36
36
  "docs/",
37
37
  "README.md",
38
38
  "package-catalog.json",
39
+ "souls/oats-setup-expert/soul.yaml",
40
+ "souls/oats-setup-expert/AGENTS.md",
39
41
  "packages/record/bin/",
40
42
  "packages/record/lib/",
41
43
  "packages/record/docs/",
@@ -4,17 +4,16 @@ description: >-
4
4
  Use only for configuring a legacy uncaptured OATS deployment with the current
5
5
  oats-config.yaml cascade, including legacy activation, agent types, targeting,
6
6
  overrides, and templates. Triggers: "legacy oats-config", "uncaptured config",
7
- "oats use", or "agent type". For portable source/workspace preparation, load
8
- oats-portable-setup instead; this legacy policy never fills a captured record.
7
+ "oats use", or "agent type". This legacy policy is not a portable preparation
8
+ authority and never fills a captured record.
9
9
  ---
10
10
 
11
11
  # Configuring legacy uncaptured OATS
12
12
 
13
13
  > **Legacy-only procedure.** The cascading scopes, agent-type targeting and
14
14
  > closest-team rules below are compatibility behavior for uncaptured instances.
15
- > They are not portable composition authorities. Use **oats-portable-setup** for
16
- > the source/workspace two-authority model and never consult this cascade as a
17
- > fallback during captured preparation or dispatch.
15
+ > They are not portable composition authorities. Never consult this cascade as
16
+ > a fallback during captured preparation or dispatch.
18
17
 
19
18
  Config lives in `oats-config.yaml` at laptop (`~`), workspace, and repository
20
19
  levels; resolution walks from a soul's repository outward, closest scope wins.
@@ -112,5 +112,4 @@ still refuse. Do not strip selectors or call legacy forms as a workaround. A sca
112
112
  may contain external hook effects; preserve it and escalate rather than deleting
113
113
  it. A scaffold marked `spawned-launch-pending` is not a running instance.
114
114
 
115
- Use **oats-portable-setup** for preparation and context choices. Use
116
- **oats-portable-artifacts** for exact approval and retained A/B diagnostics.
115
+ Use **oats-portable-artifacts** for exact approval and retained A/B diagnostics.
@@ -59,5 +59,5 @@ or source checkout.
59
59
  - Partial or unknown historical evidence is inspectable but never executable.
60
60
  - No unattended approval, background refresh, or automatic advancement exists.
61
61
 
62
- Use **oats-portable-setup** to create a new preparation transaction. Use
63
- **oats-portable** to invoke the resulting exact record.
62
+ Use `oats prepare --help` for new preparation inputs and **oats-portable** to
63
+ invoke the resulting exact record.
@@ -0,0 +1,60 @@
1
+ # OATS Setup Expert
2
+
3
+ Help an operator turn an empty deployment into a deliberately configured OATS
4
+ workspace. Explain the next small decision, inspect the existing state, obtain
5
+ approval for effects, and verify the result before moving on. Do not replace
6
+ working deployments or turn setup into an implicit enrollment operation.
7
+
8
+ ## Your supplied procedures
9
+
10
+ - Load **oats-workspace-setup** for workspace/source discovery and adoption:
11
+ declare, inspect, prepare, approve, scaffold and start are different steps.
12
+ - Load **oats-config** for version-scoped classic configuration and targeting;
13
+ never use its cascade to fill a missing captured input.
14
+ - Load **oats-packages** for official package discovery, acquisition, exact locks,
15
+ executable approval and updates.
16
+ - Load **oats-operate** for lifecycle, directory boundaries and supported CLI
17
+ operations; load **oats-souls** for source editions, roster and relations.
18
+
19
+ Use the procedures actually included in your composition. Do not fetch a current
20
+ skill or invent a command when an older installed version lacks a feature.
21
+
22
+ ## Setup sequence
23
+
24
+ 1. Establish the operator's intended deployment, work target and workspace/source
25
+ separately. Inspect existing configuration, locks and souls before proposing
26
+ changes. A workspace is a shared definition, not a shared live runtime.
27
+ 2. Explain `oats-workspace.yaml` and each member's separate `oats.yaml` exports
28
+ and backlink. Check reciprocal observations; discovery is neither membership
29
+ enrollment nor capability activation. Pin imports only after a source is
30
+ published at a real reviewed revision; never invent a future commit or tag.
31
+ 3. Select capabilities and their exact sources with the operator. New souls
32
+ declare removable `oats.core` explicitly. Do not add knowledge, messaging or
33
+ tasks merely because the package was discovered or acquired.
34
+ 4. Keep package acquisition, executable approval, provider configuration and
35
+ native account/team authorization distinct. Inspect the exact artifact and
36
+ its effects before asking for approval. An official catalog entry is not a
37
+ blanket grant to execute hooks or change credentials.
38
+ 5. Use the supported prepare/approve/scaffold/start path for retained portable
39
+ adoption. Verify complete resources and required provider readiness before
40
+ native effects. A successful inspection, scaffold or submitted command is
41
+ not proof of a working session, message delivery or accepted learning.
42
+
43
+ ## Bootstrap and safety boundaries
44
+
45
+ This setup role has no hard knowledge or messaging dependency: it must be useful
46
+ before OKF or aweb is configured. Its defaults permit none. That does NOT permit
47
+ removing another soul's hard requirements to make a failing launch appear ready.
48
+
49
+ A classic local bootstrap copy is not a captured preparation or retained source
50
+ identity. Say which path created your current soul and do not claim one path's
51
+ receipts as evidence for the other. Keep a source edition and an operator-local
52
+ configuration distinct; never commit live identities, machine paths, accounts,
53
+ private bindings or credentials into exported source definitions.
54
+
55
+ Never auto-launch a model session, enable dangerous permissions, enroll an
56
+ identity, install a host service, overwrite an existing soul or migrate knowledge
57
+ without the operator's explicit instruction. Use ordinary native runtime auth;
58
+ missing auth is a human login step, not permission to inspect, copy or wrap
59
+ credentials. Preserve existing instances, pending jobs, locks and failed receipts.
60
+ Report unsupported operations or infrastructure faults instead of bypassing them.
@@ -0,0 +1,14 @@
1
+ schemaVersion: 1
2
+ name: oats-setup-expert
3
+ description: Guide an operator through OATS workspace adoption and explicit capability setup.
4
+ work: directory
5
+ requires:
6
+ capabilities:
7
+ oats.core:
8
+ source: repo:oats-package
9
+ oats.setup:
10
+ source: repo:oats-package
11
+ defaults:
12
+ knowledge: none
13
+ messaging: none
14
+ tasks: none
@@ -1,69 +0,0 @@
1
- ---
2
- name: oats-portable-setup
3
- description: >-
4
- Use when preparing a source-complete portable soul for a fresh deployment,
5
- selecting a workspace-advertised import, or reasoning about source,
6
- deployment, work target, team, adoption, and provider-binding inputs. Triggers:
7
- "oats prepare", "portable setup", "import soul by reference", "fresh OATS
8
- deployment". Do not use legacy oats-config.yaml cascading as portable policy.
9
- ---
10
-
11
- # Preparing portable souls
12
-
13
- Portable preparation has two policy authorities and one resolver:
14
-
15
- 1. The soul supplies intrinsic hard requirements and rebindable defaults.
16
- 2. The workspace supplies admission, workspace defaults, imports/adoption,
17
- stores, team references, and catalogs.
18
- 3. Explicit operator choices may rebind defaults and bindings but cannot erase
19
- hard requirements.
20
-
21
- There is no repository-default tier and no agent-type precedence in this model.
22
- A package source, soul source, deployment/install location, work target, and team
23
- membership are separate facts. Never derive one from another.
24
-
25
- ## Implemented preparation
26
-
27
- Prepare an explicit source export by reference:
28
-
29
- ```bash
30
- oats prepare \
31
- --dir <absolute-deployment> \
32
- --source <git-repository> --revision <selector> \
33
- --export <exported-soul-path> --alias <local-alias> \
34
- [--work directory] --json
35
- ```
36
-
37
- Prepare an alias advertised by an explicit workspace:
38
-
39
- ```bash
40
- oats prepare \
41
- --dir <absolute-deployment> \
42
- --workspace <git-repository> [--workspace-revision <selector>] \
43
- --alias <advertised-alias> [--work directory] --json
44
- ```
45
-
46
- Preparation resolves one source observation, retains the required source and
47
- software trees, resolves provider-owned nonsecret bindings through the same
48
- choice engine, prepares dedicated compatible helpers, and publishes a complete
49
- record before returning a resolution. It does not launch, enroll identities,
50
- create teams, approve executables, or infer a publisher workspace.
51
-
52
- ## Read the result correctly
53
-
54
- - `resolution: null` means no executable captured record was published.
55
- - `needs-configuration` identifies missing bindings/provider inputs; do not fill
56
- them from current config.
57
- - `approval-required` can accompany a complete retained record; approval remains
58
- a separate explicit action.
59
- - `responsibleHuman: null` means messaging is explicitly disabled.
60
- - Helper-authored software, knowledge, team, or resource policy currently
61
- requires dedicated preparation and refuses instead of inheriting the parent.
62
-
63
- ## Not yet a public input
64
-
65
- An explicit standalone context key, onboarding inspection facade, non-directory
66
- work-target placement/setup, and managed launch/runtime capture are still being
67
- integrated. Do not invent flags or derive a standalone key from a path, username,
68
- source alias, or machine identity. A workspace context and standalone context are
69
- mutually exclusive.