@awebai/oats 0.24.2 → 0.24.3
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 +64 -7
- package/docs/design/2026-09-20-redesign-program-board.md +3 -3
- package/docs/design/2026-09-20-workspace-onboarding-public.md +73 -7
- package/docs/release-notes/v0.24.3.md +13 -0
- package/lib/core.mjs +19 -6
- package/lib/portable-onboarding.mjs +3 -3
- package/lib/prepared-bindings.mjs +21 -8
- package/package.json +1 -1
package/bin/oats.mjs
CHANGED
|
@@ -49,6 +49,10 @@ import { receiveAttachment, uploadAttachment, readStreamBounded, MAX_ATTACHMENT_
|
|
|
49
49
|
import { capturedSelector } from "../lib/captured-selector.mjs";
|
|
50
50
|
import { inspectCapturedPiOutcome } from "../lib/captured-pi-host.mjs";
|
|
51
51
|
import { readCapturedResolution } from "../lib/captured-resolutions.mjs";
|
|
52
|
+
import { readLock3 } from "../lib/portable-lock.mjs";
|
|
53
|
+
import { oatsError } from "../lib/errors.mjs";
|
|
54
|
+
import { readPortableBytes } from "../lib/portable-files.mjs";
|
|
55
|
+
import { canonicalJson, parseStrictJson } from "../lib/portable-values.mjs";
|
|
52
56
|
import { readPortablePreparationRequest } from "../lib/portable-onboarding-request.mjs";
|
|
53
57
|
import { portableScope } from "../lib/portable-state.mjs";
|
|
54
58
|
import { CAPTURED_OPERATION_TIMEOUT_MS, runCapturedOperationProcess } from "../lib/captured-operation-process.mjs";
|
|
@@ -99,15 +103,40 @@ const jsonOk = (result) => { console.log(JSON.stringify({ schemaVersion: 1, ok:
|
|
|
99
103
|
|
|
100
104
|
function inspectOnboardingCmd() {
|
|
101
105
|
const fail = (code, message) => JSON_MODE ? jsonFail(code, message) : die(message);
|
|
102
|
-
|
|
106
|
+
const values = new Map();
|
|
103
107
|
for (let index = 1; index < args.length; index++) {
|
|
104
108
|
if (args[index] === "--json") continue;
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
const key = args[index];
|
|
110
|
+
if (!["--request", "--emit-prepare-request"].includes(key) || values.has(key) || !args[index + 1] || args[index + 1].startsWith("--")) fail("E_BAD_ARGS", "source inspection accepts one --request <absolute-json>, optional --emit-prepare-request <new-absolute-json>, and --json");
|
|
111
|
+
values.set(key, args[++index]);
|
|
107
112
|
}
|
|
108
113
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
114
|
+
const output = values.get("--emit-prepare-request");
|
|
115
|
+
let parent;
|
|
116
|
+
const checkOutput = () => {
|
|
117
|
+
if (!isAbsolute(output) || resolve(output) !== output || output.includes("\0")) throw oatsError("E_BAD_ARGS", "prepare-request output needs a normalized absolute path");
|
|
118
|
+
let stat;
|
|
119
|
+
try {
|
|
120
|
+
stat = lstatSync(dirname(output));
|
|
121
|
+
if (!stat.isDirectory() || realpathSync(dirname(output)) !== dirname(output)) throw new Error();
|
|
122
|
+
} catch { throw oatsError("E_BAD_ARGS", "prepare-request output parent must be an existing real directory"); }
|
|
123
|
+
if (parent && (parent.dev !== stat.dev || parent.ino !== stat.ino)) throw oatsError("selection-changed", "prepare-request output parent changed during inspection");
|
|
124
|
+
try { lstatSync(output); }
|
|
125
|
+
catch (error) { if (error.code === "ENOENT") return stat; throw oatsError("E_BAD_ARGS", "prepare-request output could not be checked"); }
|
|
126
|
+
throw oatsError("E_BAD_ARGS", "prepare-request output already exists; choose a new file (nothing overwritten)");
|
|
127
|
+
};
|
|
128
|
+
if (output !== undefined) parent = checkOutput();
|
|
129
|
+
const input = readPortablePreparationRequest({ file: values.get("--request") });
|
|
130
|
+
const { prepareRequest, ...view } = inspectPortableOnboarding(input, { includePrepareRequest: output !== undefined });
|
|
131
|
+
let result = view;
|
|
132
|
+
if (output !== undefined) {
|
|
133
|
+
checkOutput();
|
|
134
|
+
try { writeFileSync(output, canonicalJson(prepareRequest) + "\n", { flag: "wx", mode: 0o600 }); }
|
|
135
|
+
catch { throw oatsError("E_BAD_ARGS", "prepare-request output could not be created exclusively; no existing file was overwritten"); }
|
|
136
|
+
const deployment = view.deployment.deployment.path;
|
|
137
|
+
result = { ...view, prepareRequestFile: output, effects: { ...view.effects, requestFileWrite: true,
|
|
138
|
+
deploymentWrites: output === deployment || output.startsWith(deployment + sep) } };
|
|
139
|
+
}
|
|
111
140
|
if (JSON_MODE) jsonOk(result); else console.log(JSON.stringify(result, null, 2));
|
|
112
141
|
} catch (error) { fail(error.code || "E_INSPECT_FAILED", error.message); }
|
|
113
142
|
}
|
|
@@ -2446,6 +2475,28 @@ function trust() {
|
|
|
2446
2475
|
if (!id || id.startsWith("--")) { cmdFail("E_USAGE", "usage: oats trust <capability> [--dir <dir>] | oats trust <package> --all-capabilities [--dir <dir>]"); return; }
|
|
2447
2476
|
const dir = dirFlag();
|
|
2448
2477
|
const all = args.includes("--all-capabilities");
|
|
2478
|
+
// A v3 deployment needs an EXPLICIT immutable artifact-set selection. Never
|
|
2479
|
+
// guess one or reinterpret its lock through the classic approval engine.
|
|
2480
|
+
try {
|
|
2481
|
+
for (const scope of lockLevelsUp(dir).reverse()) {
|
|
2482
|
+
// Discriminate with the shared bounded decoder only. Classic readers
|
|
2483
|
+
// retain all v1/v2 validation, including implicit (versionless) v1 locks.
|
|
2484
|
+
let version;
|
|
2485
|
+
try { version = parseStrictJson(readPortableBytes(join(scope, OATS_LOCK_FILE)))?.lockfileVersion; }
|
|
2486
|
+
catch { continue; } // Let the existing classic reader diagnose its input.
|
|
2487
|
+
if (version !== 3) continue;
|
|
2488
|
+
const portable = readLock3(scope).lock;
|
|
2489
|
+
if (!portable) throw oatsError("selection-changed", "selection lock disappeared; repeat explicit trust selection");
|
|
2490
|
+
const sets = [...new Set(Object.values(portable.selections).map(row => row.available).filter(key => key && Object.hasOwn(portable.artifactSets[key].capabilities, id)))].sort();
|
|
2491
|
+
const commands = sets.map(artifactSet => ({ artifactSet,
|
|
2492
|
+
command: `oats trust ${shellQuote(id)} --deployment ${shellQuote(scope)} --artifact-set ${shellQuote(artifactSet)}` }));
|
|
2493
|
+
const message = commands.length && !all
|
|
2494
|
+
? `lock v3 requires exact artifact-set approval; run ${commands.map(item => item.command).join(" or ")}`
|
|
2495
|
+
: `lock v3 approvals are per capability; use prepare's selections[].artifactSet for each capability in approvalRequired[]: oats trust <capability> --deployment ${shellQuote(scope)} --artifact-set <sha256-artifact-set>`;
|
|
2496
|
+
if (JSON_MODE) jsonFail("needs-configuration", message, { deployment: scope, commands });
|
|
2497
|
+
die(message);
|
|
2498
|
+
}
|
|
2499
|
+
} catch (error) { cmdFail(error.code || "invalid-lock", error.message); return; }
|
|
2449
2500
|
// Package-backed approval path (per-capability, or explicit bulk on a package id).
|
|
2450
2501
|
let pkgs, locks;
|
|
2451
2502
|
try { pkgs = listInstalledPackages(dir); locks = readPackageLocks(dir); } catch (e) { cmdFail(e.code || "invalid-lock", e.message || e); return; }
|
|
@@ -5361,8 +5412,10 @@ The turn record (core — every conversation captured, searchable, replicated):
|
|
|
5361
5412
|
packages/experimental/README.md
|
|
5362
5413
|
|
|
5363
5414
|
oats inspect --request <absolute-json-file> [--json]
|
|
5364
|
-
|
|
5365
|
-
|
|
5415
|
+
[--emit-prepare-request <new-absolute-json-file>]
|
|
5416
|
+
fresh source/workspace/member metadata; optional private
|
|
5417
|
+
request export uses the existing fresh-request builder;
|
|
5418
|
+
no provider execution or preparation authority
|
|
5366
5419
|
oats prepare --request <absolute-json-file> [--json]
|
|
5367
5420
|
complete public preparation input; no mixed flags,
|
|
5368
5421
|
inherited binding, implicit setup or launch authority
|
|
@@ -5374,6 +5427,10 @@ The turn record (core — every conversation captured, searchable, replicated):
|
|
|
5374
5427
|
same preparation through workspace imports
|
|
5375
5428
|
oats inspect --deployment <abs> --resolution <id> [--composition] [--json]
|
|
5376
5429
|
[--helper <exact-map-key>] inspect retained source/helper inputs, not today's configuration
|
|
5430
|
+
oats trust <capability> --deployment <abs> --artifact-set <sha256-…> [--json]
|
|
5431
|
+
approve one exact prepared artifact; use each
|
|
5432
|
+
selections[].artifactSet for capability ids
|
|
5433
|
+
in prepare's approvalRequired[] at that selection
|
|
5377
5434
|
oats trust <capability> --deployment <abs> --resolution <id> [--json]
|
|
5378
5435
|
explicitly approve that exact captured artifact
|
|
5379
5436
|
oats <namespace> <command> --deployment <abs> --resolution <id> -- [args…]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Purpose:** the one accurate view of every work stream in the redesign, what is on main, what is in flight, who owns it, and what blocks it. Lead: `oats-expert` (redesign lead). Updated whenever anything merges, is returned, or reality changes. Older per-lane boards are superseded by this file.
|
|
4
4
|
|
|
5
|
-
**Last update:** 2026-09-21
|
|
5
|
+
**Last update:** 2026-09-21 05:00Z · main `04e930e7` · **OATS v0.24.2 published** · OKF v2.1.1 · oats-framework/v1.1.1 · aweb v1.11.0 · oats-knowledge 8d67eab4
|
|
6
6
|
|
|
7
7
|
Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, not adopted · ⬜ not started · ⛔ blocked
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, n
|
|
|
11
11
|
| # | Stream | State | Owner | Next action |
|
|
12
12
|
|---|---|---|---|---|
|
|
13
13
|
| S1 | Knowledge capability contract rework (kernel↔provider boundary, OKF 2.x) | ✅ OATS 0.24.1 / OKF 2.1.1 published | P | done for this phase |
|
|
14
|
-
| S2 | Workspace/Portable Souls adoption of the OATS repos | ✅ workspace + seven indexes +
|
|
14
|
+
| S2 | Workspace/Portable Souls adoption of the OATS repos | ✅ workspace + seven indexes + six imports · ✅ **0.24.2 published** (onboard verified from the npm tarball) · 🔄 Antares re-run requested · 🔄 five seams → L, 0.24.3 | lead, L, Antares | re-run report; seams PR |
|
|
15
15
|
| S3 | Messaging capability readiness on the new infrastructure (aweb) | ✅ aweb 1.11.0 released · ✅ **catalog + six editions pin v1.11.0 (0.24.2)** · ⬜ second-operator re-run | P, lead | Antares re-run |
|
|
16
16
|
| S4 | Official capabilities `oats.core` / `oats.setup` + explicit default + onboarding `oats-setup-expert` | ✅ D1, D2, **D3 merged (PR35)**: `oats onboard` verified live (acquire 1.1.1 → setup expert with both caps → scaffold composes the five capability skills, no legacy) · `oats.framework` 1.1.1 tagged | P, L | done; Desktop surfaces → S8 |
|
|
17
17
|
| S5 | Official marketplace = reviewed list in oats repo | ✅ D4 merged · ✅ `oats.framework` 1.1.1 listed (`oats.core`, `oats.setup`, `oats.knowledge-theory` aliases) | M | Desktop view → S8 |
|
|
@@ -38,7 +38,7 @@ Fresh dir, local `@awebai/oats@0.24.1`, no prior state. `inspect --request` →
|
|
|
38
38
|
2. `prepare` on the absent deployment inspect blessed → raw `ENOENT` + host path through the JSON envelope.
|
|
39
39
|
3. `prepare` writes lock v3; `oats trust <cap> --dir` rejects it (`unsupported lockfileVersion 3`) → dead end from `--help`.
|
|
40
40
|
4. The working `trust --deployment --artifact-set <sha256>` route is absent from `--help`.
|
|
41
|
-
5. Problems carry `origins: []` and no slot/capability
|
|
41
|
+
5. Problems carry `origins: []` and no slot/capability, so a valid and a bogus `stores.oats` binding produce byte-identical output. **Root cause (L's trace):** both OKF `normalize` calls refused because the operator request had no OKF runtime settings (`bindings-file`, `state-dir` — absolute host paths, no defaults); the message never said so. Fix = attribute problems by slot/capability and name the missing item; the identical pair is correct output for that input and stays identical. Guidance for the required settings → P (oats-workspace-setup).
|
|
42
42
|
|
|
43
43
|
## S3 — Messaging (aweb) on the new infrastructure
|
|
44
44
|
- ✅ **aweb PR3 merged → v1.11.0 (93f8ab96)**: `binding {normalize,bind,check}` on the existing wire; `check` = HOME-route operational custody only (explicit private team, `delivery: session`, kernel ≥0.24.2 via caller-owned `OATS_CLI_BIN`, retained `launchSelection` must be input-capable Claude/Codex; strict-Pi print → `needs-configuration`, never downgraded). Native adapter over existing `aw` commands with physical identity-dir custody and redacted tokens. Standalone 30/0; coupling 14/0 vs kernel b92f0d07. PR33 (launchSelection projection, OATS_CLI_BIN in codec env) merged b92f0d07.
|
|
@@ -9,9 +9,11 @@ owned; production capability/profile readiness is not established by the fixture
|
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
11
|
oats inspect --request /absolute/inspection.json --json
|
|
12
|
+
# Optional explicit request export (new private file; never overwrite):
|
|
13
|
+
oats inspect --request /absolute/inspection.json --emit-prepare-request /absolute/preparation.json --json
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
This mode accepts
|
|
16
|
+
This mode accepts one request file, optional `--emit-prepare-request`, and `--json`. Explicit captured selectors
|
|
15
17
|
or current-context flags conflict before file reads; inherited captured environment
|
|
16
18
|
is not new-work input. Other existing inspect modes are unchanged. The shared
|
|
17
19
|
bounded strict JSON request reader feeds the existing inspection validator intact:
|
|
@@ -66,21 +68,47 @@ have not been classified by their owner and are omitted. Import summaries expose
|
|
|
66
68
|
`payloadOmitted`. Top-level `omitted:{providerPayloads:true,adoptionValues:true}`
|
|
67
69
|
states that this is a metadata view, not a lossless request or a safe-payload claim.
|
|
68
70
|
It is not an issued `buildFreshPreparationRequest` witness, even in the same
|
|
69
|
-
process.
|
|
71
|
+
process. The opt-in `--emit-prepare-request` route calls that existing builder
|
|
72
|
+
on the real in-process inspection before dropping the private witness. It writes
|
|
73
|
+
only `.preparation` to a new mode-0600 file at an explicit normalized absolute
|
|
74
|
+
path with an existing real parent; existing files/symlinks are refused, never
|
|
75
|
+
overwritten. JSON output names `prepareRequestFile` and records the explicit
|
|
76
|
+
request-file write in `effects`; it does not echo the request contents. The
|
|
77
|
+
export can carry unclassified adoption declarations and must remain private;
|
|
78
|
+
requests must contain nonsecret values or credential references, never secrets.
|
|
79
|
+
A held inspection cannot emit a fresh preparation request. Core callers can
|
|
80
|
+
explicitly request this data via `{includePrepareRequest:true}`; the default
|
|
81
|
+
metadata projection and its omissions are unchanged.
|
|
82
|
+
|
|
83
|
+
The file is reusable new-work input, NOT a stored resolution, approval, admission,
|
|
84
|
+
or serialized ready-inspection permission. Preparation performs fresh validation
|
|
85
|
+
and observations, including re-resolving any mutable source selectors. Provider
|
|
86
|
+
configuration still requires explicit operator choices; conversion invents none.
|
|
70
87
|
|
|
71
88
|
Existing managed deployment state is preserved and reported, not repaired or
|
|
72
89
|
migrated. An absent selected path requires explicit operator provisioning and
|
|
73
|
-
reinspection.
|
|
90
|
+
reinspection. Prepare refuses it with typed `needs-configuration` and provisioning
|
|
91
|
+
guidance before fetching or writing managed state, not a raw ENOENT. Inspection's
|
|
92
|
+
`ready` means the path is eligible for fresh setup, not already provisioned.
|
|
93
|
+
The serialized inspection does not lock the filesystem or authorize
|
|
74
94
|
later mutation; preparation retains its own existing validation/custody rules.
|
|
75
95
|
The inspected work target does not become source identity or an implied placement
|
|
76
96
|
choice. Supported captured directory scaffolds own their separate H/work.
|
|
77
97
|
|
|
78
98
|
## Existing preparation and retained execution
|
|
79
99
|
|
|
80
|
-
`oats prepare --request`
|
|
81
|
-
OR standalone context, operator policy/bindings,
|
|
82
|
-
launch and helperLaunches.
|
|
83
|
-
|
|
100
|
+
`oats prepare --request` accepts deployment/source/origin, optional `workTarget`,
|
|
101
|
+
workspace/member OR standalone context, operator policy/bindings,
|
|
102
|
+
mode/local-input authorization, launch and helperLaunches. The original minimal
|
|
103
|
+
inspection request (without inspection-only `catalogIndexes`) is also accepted;
|
|
104
|
+
use the converter rather than stripping fields from a metadata/result wrapper.
|
|
105
|
+
Explicit `workTarget` is validated with the same physical existing-directory
|
|
106
|
+
validator as inspection and returned as work-context metadata. It takes precedence
|
|
107
|
+
over any caller assumption about cwd: no cwd/config fallback selects its value.
|
|
108
|
+
Omitting it preserves prior preparation behavior without inventing a placement.
|
|
109
|
+
It does not change source identity, `operator.localBase`, work mode, or captured
|
|
110
|
+
H/work placement. Do not pass an inspection result/catalog wrapper or private
|
|
111
|
+
scratch `directory` to preparation. Exact executable approval is separate. A required provider whose binding
|
|
84
112
|
code is unapproved may return `needs-configuration` with an `approval-required`
|
|
85
113
|
problem and exact artifact-set/capability requests, before any record exists:
|
|
86
114
|
|
|
@@ -92,6 +120,44 @@ oats spawn <subject> --deployment <D> --resolution <R> --home <new-H> --no-launc
|
|
|
92
120
|
oats session start --deployment <D> --resolution <R> --home <H> --request /absolute/native.json --json
|
|
93
121
|
```
|
|
94
122
|
|
|
123
|
+
Each `selections[]` row carries an `artifactSet` id and an `approvalRequired[]`
|
|
124
|
+
array of **capability ids**. Pair them: pass the capability to `trust` and that
|
|
125
|
+
row's artifact-set id to `--artifact-set`. These are not interchangeable ids.
|
|
126
|
+
`trust <capability> --dir <D>` against a v3 deployment now refuses with typed
|
|
127
|
+
`needs-configuration` and exact available-set commands; it never picks or approves
|
|
128
|
+
a set automatically. Classic v1/v2 trust remains the classic route.
|
|
129
|
+
|
|
130
|
+
Provider preparation problems carry `slot` and `capability`, plus original
|
|
131
|
+
provenance where available. A no-interface provider is identified with kernel-known
|
|
132
|
+
manifest/version facts. Other supported slots still normalize, resolve through
|
|
133
|
+
the same choice engine, and bind if their own choices are resolved; any required
|
|
134
|
+
slot problem still prevents publication. Provider free text is not passed through.
|
|
135
|
+
Different opaque inputs need not produce different public errors if both fail the
|
|
136
|
+
same provider prerequisite. In particular, missing OKF host runtime settings can
|
|
137
|
+
hold both syntactically valid Git locators; preparation does not test whether a
|
|
138
|
+
remote repository exists. Required readiness checks remain later, with the provider.
|
|
139
|
+
|
|
140
|
+
### Operator input
|
|
141
|
+
|
|
142
|
+
When present, `operator` requires both `policy` (object; `{}` is valid) and
|
|
143
|
+
`document` (`{ "kind": "operator", "id": "setup-attempt" }`). Its ONLY optional
|
|
144
|
+
fields are `localBase`, `allowLocalPaths`, `sourceContext`, and `bindings`:
|
|
145
|
+
|
|
146
|
+
- `policy`: explicit provider/additive selections with their selected sources and
|
|
147
|
+
settings, using the existing policy grammar. It cannot erase soul requirements.
|
|
148
|
+
- `localBase`: explicit absolute base for relative local policy sources. Work
|
|
149
|
+
context/cwd is not a substitute.
|
|
150
|
+
- `allowLocalPaths`: explicit boolean authorization for those local policy sources.
|
|
151
|
+
Top-level local acquisition authorization remains a separate input.
|
|
152
|
+
- `sourceContext`: existing qualified repository anchor for `repo:` policy sources;
|
|
153
|
+
not a new repository inferred from workTarget.
|
|
154
|
+
- `bindings`: provider-owned map. Kernel preserves it and its document pointers;
|
|
155
|
+
it does not interpret store names, Git destinations, credentials or private teams.
|
|
156
|
+
|
|
157
|
+
Selecting an inherited store does not replace required provider runtime settings.
|
|
158
|
+
Use the selected provider's instructions for those settings; kernel must not guess
|
|
159
|
+
host-owned durable paths or copy native authentication.
|
|
160
|
+
|
|
95
161
|
Repreparation after explicit approval is ordinary continuation in the selected,
|
|
96
162
|
now-managed deployment; do not delete its state to make fresh preflight pass.
|
|
97
163
|
Required hooks still run under their admitted custody with `--no-launch`; a parsed
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# OATS v0.24.3 — second-operator `prepare` seams
|
|
2
|
+
|
|
3
|
+
Kernel/Pi/Desktop **0.24.3**. Fixes the five usability seams an independent second operator hit adopting the shared workspace definition on 0.24.1/0.24.2 (see the [program board](../design/2026-09-20-redesign-program-board.md)). No contract, schema or authority change.
|
|
4
|
+
|
|
5
|
+
- **Attributed provider problems.** Every preparation problem now carries `slot`, `capability` and its `origins`, with kernel-fixed text that names the missing item (for example `oats.okf knowledge normalize binding could not be prepared` / `oats.aweb@1.10.3 declares no binding interface; messaging cannot be prepared`). One unqualified slot no longer masks another slot's diagnostics: every slot that has a binding interface is normalized, and the single resolver reports each invalid choice under its slot. Provider free text still never crosses the wire.
|
|
6
|
+
- **`oats trust <capability> --dir <deployment>` on a lock v3 deployment** returns a typed problem with the exact working command (`oats trust <cap> --deployment <abs> --artifact-set <sha256-…>`) instead of `unsupported lockfileVersion 3`. No auto-approval.
|
|
7
|
+
- **Help** documents `--artifact-set` and how `prepare`'s `selections[].artifactSet` / `approvalRequired[]` pair with it.
|
|
8
|
+
- **`prepare` on an absent deployment path** returns a typed explicit-provisioning hold, not a raw `ENOENT` with a host path.
|
|
9
|
+
- **One request file for both commands.** `prepare --request` accepts the explicit `workTarget` that `inspect --request` requires (explicit beats cwd; it never implies captured placement or current config), and `oats inspect --request <f> --emit-prepare-request <out>` writes the converted preparation request through the existing builder.
|
|
10
|
+
|
|
11
|
+
The second operator's recorded requests and results are preserved verbatim as regression fixtures (`test/fixtures/second-operator/`). Their valid-vs-bogus `stores.oats` pair remains identical by design: both refused on missing OKF runtime settings (`bindings-file`, `state-dir`), which the messages now say.
|
|
12
|
+
|
|
13
|
+
Setup guidance for those settings lands in the next `oats.framework` payload.
|
package/lib/core.mjs
CHANGED
|
@@ -62,7 +62,7 @@ import { validateCapturedLaunchRequest } from "./captured-launch-request.mjs";
|
|
|
62
62
|
import { validateCapabilityInputDeclarations } from "./capability-inputs.mjs";
|
|
63
63
|
import { validateCapturedSessionBackend, validateCapturedSessionTarget, assertCapturedSessionPlacement } from "./captured-session-backend.mjs";
|
|
64
64
|
import { createRepositoryTransaction } from "./repository-observation.mjs";
|
|
65
|
-
import { inspectPortableOnboarding as inspectOnboarding, describePortableOnboarding } from "./portable-onboarding.mjs";
|
|
65
|
+
import { inspectPortableOnboarding as inspectOnboarding, describePortableOnboarding, buildFreshPreparationRequest, inspectWorkTarget } from "./portable-onboarding.mjs";
|
|
66
66
|
import { readLock3 } from "./portable-lock.mjs";
|
|
67
67
|
import { portableScope, portableStateDirectory } from "./portable-state.mjs";
|
|
68
68
|
import { objectAt } from "./portable-shape.mjs";
|
|
@@ -1211,13 +1211,17 @@ export function approveAvailableCapability(deployment, artifactSet, capability,
|
|
|
1211
1211
|
/** Public read-only source/workspace inspection through the existing facade.
|
|
1212
1212
|
* Owns only transient repository scratch, never deployment state, provisioning,
|
|
1213
1213
|
* provider code, approvals or a serialized mutation witness. */
|
|
1214
|
-
export function inspectPortableOnboarding(input, { repositoryOptions = {} } = {}) {
|
|
1214
|
+
export function inspectPortableOnboarding(input, { repositoryOptions = {}, includePrepareRequest = false } = {}) {
|
|
1215
1215
|
canonicalJson(input);
|
|
1216
|
+
if (typeof includePrepareRequest !== "boolean") throw oatsError("invalid-declaration", "request conversion requires an explicit boolean option");
|
|
1216
1217
|
const directory = mkdtempSync(join(realpathSync(tmpdir()), "oats-source-inspection-")), owned = lstatSync(directory);
|
|
1217
1218
|
let repositories, primary;
|
|
1218
1219
|
try {
|
|
1219
1220
|
repositories = createRepositoryTransaction({ ...repositoryOptions, directory, accessContextKey: repositoryOptions.accessContextKey ?? "native" });
|
|
1220
|
-
|
|
1221
|
+
const inspection = inspectOnboarding(input, { repositories }), result = describePortableOnboarding(inspection);
|
|
1222
|
+
// Opt-in data export, not a serialized custody witness or executable grant.
|
|
1223
|
+
// Default metadata continues to omit unclassified adoption/provider values.
|
|
1224
|
+
return includePrepareRequest ? { ...result, prepareRequest: buildFreshPreparationRequest(inspection).preparation } : result;
|
|
1221
1225
|
} catch (error) { primary = error; throw error; }
|
|
1222
1226
|
finally {
|
|
1223
1227
|
try {
|
|
@@ -1239,21 +1243,28 @@ export function inspectPortableOnboarding(input, { repositoryOptions = {} } = {}
|
|
|
1239
1243
|
* a fabricated complete record. No lifecycle side effects run here. */
|
|
1240
1244
|
export function prepareCapturedComposition(input, { repositoryOptions = {} } = {}) {
|
|
1241
1245
|
canonicalJson(input);
|
|
1242
|
-
objectAt(input, ["deployment", "source", "origin", "workspace", "member", "operator", "mode", "allowLocalPaths", "standaloneContextKey", "launch", "helperLaunches"], ["deployment", "source"]);
|
|
1246
|
+
objectAt(input, ["deployment", "workTarget", "source", "origin", "workspace", "member", "operator", "mode", "allowLocalPaths", "standaloneContextKey", "launch", "helperLaunches"], ["deployment", "source"]);
|
|
1243
1247
|
if (input.launch !== undefined) validateCapturedLaunchRequest(input.launch, validateLaunchConfig);
|
|
1244
1248
|
if (input.helperLaunches !== undefined) {
|
|
1245
1249
|
objectAt(input.helperLaunches, null, []);
|
|
1246
1250
|
for (const request of Object.values(input.helperLaunches)) validateCapturedLaunchRequest(request, validateLaunchConfig);
|
|
1247
1251
|
}
|
|
1248
1252
|
if (input.mode !== undefined && !WORK_MODES.includes(input.mode)) throw oatsError("invalid-declaration", "invalid preparation work mode");
|
|
1249
|
-
const
|
|
1253
|
+
const { workTarget, ...preparationInput } = input;
|
|
1254
|
+
const target = Object.hasOwn(input, "workTarget") ? inspectWorkTarget(workTarget) : null;
|
|
1255
|
+
let deployment;
|
|
1256
|
+
try { deployment = portableScope(input.deployment); }
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
if (error.code === "ENOENT") throw oatsError("needs-configuration", "deployment directory is absent; provision the explicitly selected directory and inspect again before preparation");
|
|
1259
|
+
throw error;
|
|
1260
|
+
}
|
|
1250
1261
|
const previous = readLock3(deployment); // Old state refuses before scratch/fetch.
|
|
1251
1262
|
const directory = mkdtempSync(join(portableStateDirectory(deployment, true), ".prepare-")), owned = lstatSync(directory);
|
|
1252
1263
|
const origin = input.origin ?? { kind: "operator", document: { kind: "operator", id: "oats-prepare" }, pointer: "/source" };
|
|
1253
1264
|
let repositories, primary, catalog;
|
|
1254
1265
|
try {
|
|
1255
1266
|
repositories = createRepositoryTransaction({ ...repositoryOptions, directory, accessContextKey: repositoryOptions.accessContextKey ?? "native" });
|
|
1256
|
-
|
|
1267
|
+
const result = prepareComposition({ ...preparationInput, deployment, origin, directory }, { repositories, previous, kernel: {
|
|
1257
1268
|
loadPackageManifestAt, capabilityCompatibility, assertPlatformInvariantLocks, materializeCapability,
|
|
1258
1269
|
manifest: loadRetainedManifest,
|
|
1259
1270
|
binding: runCapturedProviderBinding,
|
|
@@ -1266,6 +1277,8 @@ export function prepareCapturedComposition(input, { repositoryOptions = {} } = {
|
|
|
1266
1277
|
settings: assertCapabilitySettingValues, skills: skillEntriesIn, hooks: manifestHookDeclarations,
|
|
1267
1278
|
validateLaunchConfig, runtimeRequirements: applicableRequirements }),
|
|
1268
1279
|
} });
|
|
1280
|
+
// Explicit work context never selects source/config, cwd, or H/work placement.
|
|
1281
|
+
return target ? { ...result, workTarget: target } : result;
|
|
1269
1282
|
} catch (error) { primary = error; throw error; }
|
|
1270
1283
|
finally {
|
|
1271
1284
|
try {
|
|
@@ -108,7 +108,7 @@ export function preflightFreshDeployment({ deployment, maxEntries = 4096 }) {
|
|
|
108
108
|
return result;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function
|
|
111
|
+
export function inspectWorkTarget(path) {
|
|
112
112
|
const canonical = canonicalExistingDirectory(path, "work target"), marker = present(join(canonical, ".git"));
|
|
113
113
|
return { path: canonical, state: "existing", git: marker ? { present: true, kind: marker.isDirectory() ? "directory" : marker.isFile() ? "file" : "other" } : { present: false, kind: null } };
|
|
114
114
|
}
|
|
@@ -146,7 +146,7 @@ export function inspectPortableOnboarding(input, { repositories } = {}) {
|
|
|
146
146
|
validateOrigin(input.origin);
|
|
147
147
|
const standalone = explicitContext(input);
|
|
148
148
|
const deployment = preflightFreshDeployment({ deployment: input.deployment });
|
|
149
|
-
const target =
|
|
149
|
+
const target = inspectWorkTarget(input.workTarget), discovery = createWorkspaceDiscovery(repositories);
|
|
150
150
|
const witness = { deployment: preflightWitnesses.get(deployment), work: directoryIdentity(target.path) };
|
|
151
151
|
const workspace = Object.hasOwn(input, "workspace") ? discovery.readWorkspace(input.workspace) : null;
|
|
152
152
|
const selected = typeof input.source === "string" ? sourceOrigin(workspace, input.source) : { reference: input.source, origin: input.origin };
|
|
@@ -236,7 +236,7 @@ export function buildFreshPreparationRequest(inspection, options = {}) {
|
|
|
236
236
|
const { operator, mode, allowLocalPaths = false } = options;
|
|
237
237
|
if (typeof allowLocalPaths !== "boolean") throw oatsError("invalid-declaration", "allowLocalPaths must be boolean");
|
|
238
238
|
if (mode !== undefined && (typeof mode !== "string" || !mode.trim())) throw oatsError("invalid-declaration", "preparation mode must be non-empty text");
|
|
239
|
-
const input = { deployment: inspection.deployment.deployment.path,
|
|
239
|
+
const input = { deployment: inspection.deployment.deployment.path, workTarget: inspection.workTarget.path,
|
|
240
240
|
source: inspection.source.reference,
|
|
241
241
|
origin: inspection.source.revision.provenance[0], allowLocalPaths,
|
|
242
242
|
...(inspection.workspace ? { workspace: inspection.workspace.request } : { standaloneContextKey: inspection.context.key }),
|
|
@@ -37,24 +37,34 @@ export function prepareProviderBindings({seed,plan,manifests,declarations},invok
|
|
|
37
37
|
const normalized=new Map(),problems=[],requirements=[...plan.requirements],candidates=[...plan.candidates];
|
|
38
38
|
const settingsFor=id=>Object.fromEntries(Object.entries(plan.settings[id] ?? {}).map(([name,key])=>[name,plan.choices[key].value]));
|
|
39
39
|
const run=(id,phase,input)=>invoke({artifacts:seed.artifacts,capability:id,phase,settings:settingsFor(id),input});
|
|
40
|
-
const
|
|
40
|
+
const originsFor=id=>[...new Map((plan.capabilities[id]?.choiceKeys ?? []).flatMap(key=>{
|
|
41
|
+
const choice=plan.choices[key];return choice?.selectedBy?[choice.selectedBy]:[];
|
|
42
|
+
}).map(origin=>[canonicalJson(origin),origin])).values()];
|
|
43
|
+
const problem=(slot,id,code,message,origins=originsFor(id))=>({code,message,origins,slot,capability:id});
|
|
44
|
+
const refuse=(slot,id,phase,error)=>problem(slot,id,safeCodes.has(error?.code)?error.code:'provider-not-qualified',
|
|
45
|
+
`${id} ${slot} ${phase} binding could not be prepared`); // Never provider free text.
|
|
41
46
|
for (const [id,manifest] of manifests) {
|
|
42
47
|
if (!Object.hasOwn(seed.artifacts.capabilities,id) || !manifest.layer) continue;
|
|
43
|
-
if (plan.providers[manifest.layer] !== id) problems.push(
|
|
48
|
+
if (plan.providers[manifest.layer] !== id) problems.push(problem(manifest.layer,id,'needs-configuration','fundamental capability must be selected in its own layer'));
|
|
44
49
|
}
|
|
45
|
-
if (problems.length) return {plan,seed,problems};
|
|
46
50
|
for (const [slot,id] of Object.entries(plan.providers)) {
|
|
47
51
|
if (id === null) continue;
|
|
48
52
|
const manifest=manifests.get(id);
|
|
49
|
-
if (manifest?.layer !== slot
|
|
53
|
+
if (manifest?.layer !== slot) { problems.push(problem(slot,id,'provider-not-qualified',`${id} does not declare the selected ${slot} layer`));continue; }
|
|
54
|
+
if (!manifest.binding) { problems.push(problem(slot,id,'provider-not-qualified',`${id}@${manifest.version} declares no binding interface; ${slot} cannot be prepared`));continue; }
|
|
50
55
|
try {
|
|
51
56
|
const value=run(id,'normalize',{declarations,context:seed.context});
|
|
52
57
|
normalized.set(slot,{id,value}); requirements.push(...value.requirements);candidates.push(...value.candidates);
|
|
53
|
-
} catch(error) { problems.push(refuse(error)); }
|
|
58
|
+
} catch(error) { problems.push(refuse(slot,id,'normalize',error)); }
|
|
54
59
|
}
|
|
55
|
-
|
|
60
|
+
// One unsupported slot must not mask another slot's normalized requirements
|
|
61
|
+
// or invalid choices. The same resolver still decides every value.
|
|
56
62
|
const resolved=resolveChoices({requirements,candidates});
|
|
57
|
-
|
|
63
|
+
for (const item of resolved.problems) {
|
|
64
|
+
const slot=Object.keys(plan.providers).find(slot=>bindingField(item.key,slot));
|
|
65
|
+
if (!slot) throw oatsError('invalid-binding-output','provider choice problem has no owned binding slot');
|
|
66
|
+
problems.push({...item,slot,capability:plan.providers[slot]});
|
|
67
|
+
}
|
|
58
68
|
const nextPlan={...plan,...resolved,requirements,candidates},bindings=Object.create(null);
|
|
59
69
|
let messagingChoice={schemaVersion:1,enabled:false};
|
|
60
70
|
const known=[];
|
|
@@ -64,6 +74,7 @@ export function prepareProviderBindings({seed,plan,manifests,declarations},invok
|
|
|
64
74
|
}
|
|
65
75
|
const witnessed=origin=>bindingOriginWitnessed(origin,declarations) || known.some(candidate=>same(candidate,origin));
|
|
66
76
|
for (const [slot,{id,value}] of normalized) {
|
|
77
|
+
if (resolved.problems.some(item=>bindingField(item.key,slot))) continue;
|
|
67
78
|
try {
|
|
68
79
|
const choices=Object.fromEntries(Object.entries(resolved.choices).filter(([key])=>bindingField(key,slot)));
|
|
69
80
|
const bound=run(id,'bind',{model:value.model,choices,context:seed.context});
|
|
@@ -72,7 +83,9 @@ export function prepareProviderBindings({seed,plan,manifests,declarations},invok
|
|
|
72
83
|
}
|
|
73
84
|
bindings[slot]=bound.binding;
|
|
74
85
|
if (slot === 'messaging') messagingChoice=bound.messagingChoice;
|
|
75
|
-
} catch(error) { problems.push(refuse(error)); }
|
|
86
|
+
} catch(error) { problems.push(refuse(slot,id,'bind',error)); }
|
|
76
87
|
}
|
|
88
|
+
nextPlan.problems=problems;
|
|
89
|
+
nextPlan.status=problems.some(item=>item.code==='requirement-conflict')?'conflict':problems.length?'needs-configuration':'resolved';
|
|
77
90
|
return {plan:nextPlan,seed:{...seed,choices:resolved.choices,bindings,messagingChoice},problems};
|
|
78
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.3",
|
|
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",
|