@awebai/oats 0.25.9 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +8 -6
  2. package/bin/oats.mjs +576 -1714
  3. package/capabilities/oats-authoring/oats-package.json +2 -2
  4. package/capabilities/oats-authoring/oats.json +2 -2
  5. package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
  6. package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
  7. package/capabilities/oats-aweb/bin/oats-aweb.mjs +279 -93
  8. package/capabilities/oats-aweb/injects/aweb.md +7 -2
  9. package/capabilities/oats-aweb/lib/binding-wire.mjs +89 -13
  10. package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
  11. package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
  12. package/capabilities/oats-aweb/oats.json +8 -4
  13. package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
  14. package/capabilities/oats-jira/oats.json +2 -2
  15. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
  16. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
  17. package/capabilities/oats-linear/oats.json +2 -2
  18. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
  19. package/capabilities/oats-review/oats.json +3 -2
  20. package/docs/capabilities.md +218 -47
  21. package/docs/capability-manifest.schema.json +13 -4
  22. package/docs/configuration.md +17 -5
  23. package/docs/conventions.md +16 -26
  24. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
  25. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
  26. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
  27. package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
  28. package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
  29. package/docs/design/2026-09-20-redesign-program-board.md +2 -2
  30. package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
  31. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
  32. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +34 -1
  33. package/docs/design/2026-09-24-phase-d-plan.md +57 -0
  34. package/docs/design/2026-09-25-teams-contract.md +226 -0
  35. package/docs/design/README.md +3 -3
  36. package/docs/design/launch-configurations.md +20 -16
  37. package/docs/design/operations-contract.md +27 -10
  38. package/docs/desktop-cli-api.md +537 -261
  39. package/docs/desktop-instance-start.md +1 -1
  40. package/docs/desktop.md +7 -13
  41. package/docs/execution-targets.md +16 -18
  42. package/docs/first-team.md +14 -17
  43. package/docs/implementation.md +28 -59
  44. package/docs/integrations.md +64 -33
  45. package/docs/knowledge-capability-authoring.md +1 -1
  46. package/docs/knowledge-reference/package-craft.md +10 -8
  47. package/docs/knowledge-theory.md +1 -1
  48. package/docs/knowledge.md +10 -11
  49. package/docs/layers.md +16 -17
  50. package/docs/oats-local.schema.json +29 -1
  51. package/docs/oats-membership.schema.json +5 -3
  52. package/docs/oats-package.schema.json +2 -2
  53. package/docs/oats-workspace.schema.json +1 -1
  54. package/docs/{official-marketplace.md → official-catalog.md} +15 -16
  55. package/docs/packages.md +75 -52
  56. package/docs/release-notes/v0.22.0.md +1 -1
  57. package/docs/release-notes/v0.23.1.md +1 -1
  58. package/docs/release-notes/v0.26.0.md +670 -0
  59. package/docs/schedules.md +48 -126
  60. package/docs/soul.schema.json +11 -4
  61. package/docs/souls-and-instances.md +56 -43
  62. package/docs/workspaces.md +80 -58
  63. package/injects/instance-boundary.md +1 -1
  64. package/injects/work-attached.md +1 -1
  65. package/injects/work-workspace.md +2 -2
  66. package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
  67. package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
  68. package/lib/capability-contract.mjs +110 -0
  69. package/lib/config-data.mjs +2 -2
  70. package/lib/core.mjs +700 -4824
  71. package/lib/digest.mjs +12 -0
  72. package/lib/instance-inspect.mjs +396 -0
  73. package/lib/instance-lifecycle.mjs +3 -4
  74. package/lib/instance-resolution.mjs +212 -26
  75. package/lib/instruction-composition.mjs +0 -20
  76. package/lib/materialize.mjs +6 -4
  77. package/lib/operator-dispatch.mjs +33 -13
  78. package/lib/packages.mjs +25 -190
  79. package/lib/provider-binding.mjs +4 -2
  80. package/lib/provider-reasons.mjs +3 -68
  81. package/lib/resolve.mjs +204 -68
  82. package/lib/schedule.mjs +97 -272
  83. package/lib/servers.mjs +13 -13
  84. package/lib/{portable-shape.mjs → shape.mjs} +4 -3
  85. package/lib/tree-copy.mjs +44 -0
  86. package/lib/workspace.mjs +125 -20
  87. package/package-catalog.json +6 -6
  88. package/package.json +1 -1
  89. package/skills/integration-authoring/SKILL.md +48 -40
  90. package/skills/oats-getting-started/SKILL.md +105 -110
  91. package/skills/oats-support/SKILL.md +2 -2
  92. package/skills/soul-craft/SKILL.md +13 -6
  93. package/bin/oats-pi-sdk-host.mjs +0 -17
  94. package/docs/2026-09-03-architecture-proposal.md +0 -642
  95. package/docs/artifact-approvals.schema.json +0 -7
  96. package/docs/captured-invocation-context.schema.json +0 -7
  97. package/docs/captured-resolution.schema.json +0 -7
  98. package/docs/design/package-engine-contract.md +0 -813
  99. package/docs/design/package-runtime-api.md +0 -588
  100. package/docs/desktop-succession.md +0 -57
  101. package/docs/execution-capsule.schema.json +0 -108
  102. package/docs/first-team-demo.md +0 -92
  103. package/docs/knowledge-migration.md +0 -147
  104. package/docs/migration-from-oas.md +0 -103
  105. package/docs/oats-config.schema.json +0 -172
  106. package/docs/oats-lock-v3.schema.json +0 -7
  107. package/docs/oats-lock.schema.json +0 -175
  108. package/docs/operating-team-migration.md +0 -470
  109. package/docs/portable.schema.json +0 -2512
  110. package/docs/provider-check-input.schema.json +0 -7
  111. package/docs/rebuild-to-v2.md +0 -511
  112. package/docs/workspace-adoption.md +0 -74
  113. package/injects/framework-workspace.md +0 -7
  114. package/injects/local-soul.md +0 -19
  115. package/injects/oats-portable.md +0 -20
  116. package/injects/oats.md +0 -11
  117. package/injects/portable-instance-boundary.md +0 -39
  118. package/injects/portable-work-directory.md +0 -29
  119. package/lib/artifact-approvals.mjs +0 -120
  120. package/lib/artifact-tree.mjs +0 -141
  121. package/lib/capability-artifacts.mjs +0 -179
  122. package/lib/capability-execution.mjs +0 -15
  123. package/lib/capability-inputs.mjs +0 -39
  124. package/lib/capability-provenance.mjs +0 -231
  125. package/lib/captured-action-shape.mjs +0 -21
  126. package/lib/captured-admission-shape.mjs +0 -20
  127. package/lib/captured-binding-file.mjs +0 -36
  128. package/lib/captured-dispatch.mjs +0 -66
  129. package/lib/captured-instance-index.mjs +0 -277
  130. package/lib/captured-invocation-context.mjs +0 -130
  131. package/lib/captured-launch-request.mjs +0 -66
  132. package/lib/captured-operation-process.mjs +0 -15
  133. package/lib/captured-pi-custody.mjs +0 -29
  134. package/lib/captured-pi-host.mjs +0 -167
  135. package/lib/captured-pi-outcome.mjs +0 -172
  136. package/lib/captured-resolutions.mjs +0 -275
  137. package/lib/captured-scaffold.mjs +0 -87
  138. package/lib/captured-selector.mjs +0 -28
  139. package/lib/captured-session-backend.mjs +0 -52
  140. package/lib/captured-source-receipt-file.mjs +0 -72
  141. package/lib/helper-injection-policy.mjs +0 -104
  142. package/lib/legacy-lock-codec.mjs +0 -106
  143. package/lib/manifest-settings.mjs +0 -84
  144. package/lib/package-closure.mjs +0 -48
  145. package/lib/package-materialization.mjs +0 -83
  146. package/lib/pi-sdk-host.mjs +0 -229
  147. package/lib/portable-artifacts.mjs +0 -115
  148. package/lib/portable-choices.mjs +0 -82
  149. package/lib/portable-composition.mjs +0 -136
  150. package/lib/portable-digest.mjs +0 -105
  151. package/lib/portable-identity.mjs +0 -40
  152. package/lib/portable-lock.mjs +0 -117
  153. package/lib/portable-onboarding-request.mjs +0 -49
  154. package/lib/portable-onboarding.mjs +0 -256
  155. package/lib/portable-package-preparation.mjs +0 -188
  156. package/lib/portable-policy.mjs +0 -44
  157. package/lib/portable-soul.mjs +0 -42
  158. package/lib/portable-state.mjs +0 -80
  159. package/lib/prepare-composition.mjs +0 -170
  160. package/lib/prepared-bindings.mjs +0 -92
  161. package/lib/prepared-resources.mjs +0 -127
  162. package/lib/provider-binding-broker.mjs +0 -65
  163. package/lib/provider-binding-wire.mjs +0 -116
  164. package/lib/readiness.mjs +0 -225
  165. package/lib/repository-observation.mjs +0 -226
  166. package/lib/resolution-shape.mjs +0 -393
  167. package/lib/schedule-capsule.mjs +0 -206
  168. package/lib/soul-constraints.mjs +0 -40
  169. package/lib/source-projection.mjs +0 -84
  170. package/lib/source-spec.mjs +0 -189
  171. package/lib/workspace-definition.mjs +0 -126
  172. package/lib/workspace-discovery.mjs +0 -146
  173. package/skills/oats/SKILL.md +0 -162
  174. package/skills/oats-config/SKILL.md +0 -164
  175. package/skills/oats-packages/SKILL.md +0 -184
  176. package/skills/oats-portable/SKILL.md +0 -115
  177. package/skills/oats-portable-artifacts/SKILL.md +0 -63
@@ -1,231 +0,0 @@
1
- /** Existing materialized-capability data contract: layout/identity, lock-row
2
- * validation and installation provenance. No scope discovery, lock writes,
3
- * acquisition, approval, resolver or lifecycle dispatch. */
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { isAbsolute, join } from "node:path";
6
- import { oatsError } from "./errors.mjs";
7
-
8
- /** Scope-relative capability store subtrees. */
9
- export const CAPABILITIES_DIRNAME = join(".agents", "capabilities");
10
- export const INSTALLED_SUBDIR = "installed";
11
-
12
- /** THE identity grammar for a MATERIALIZED (revised-v2) capability.
13
- *
14
- * A materialized capability id is not merely a label: it becomes a DIRECTORY
15
- * NAME directly under `installed/`, so anything that can steer a filesystem
16
- * join must be impossible before the join happens. The grammar is deliberately
17
- * the package-id grammar — namespaced dots are fine, and `/`, `\`, `..`,
18
- * absolute forms, `@`, and percent-encoded spellings are all outside it.
19
- *
20
- * The LEGACY v1 / owned / `from: path:` grammar (loadManifestAt) stays looser
21
- * on purpose: those artifacts are named by `basename()` of their source, never
22
- * by the declared id, so the id never reaches a path there. Tightening it would
23
- * strand already-published standalone capabilities. */
24
- export const CAPABILITY_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
25
- export const isMaterializedCapabilityId = (id) => typeof id === "string" && CAPABILITY_ID_RE.test(id);
26
- /** Why a capability id was refused, in one sentence, for every caller's own
27
- * typed error (the lock parser raises invalid-lock, manifest validation raises
28
- * invalid-package-manifest — the code each consumer already branches on). */
29
- export const capabilityIdViolation = (id) =>
30
- `${JSON.stringify(id)} is not a valid capability identity — expected ${CAPABILITY_ID_RE.source} (a namespaced id such as "oats.okf"; path separators, "..", absolute paths, "@" and encoded forms are refused because the id names a directory under ${INSTALLED_SUBDIR}/)`;
31
-
32
- /** Generated provenance file inside every materialized artifact. It is INSIDE
33
- * the hashed tree, so tampering with it is integrity drift; the lock stays
34
- * authoritative. */
35
- export const CAPABILITY_INSTALLATION_FILE = ".oats-installation.json";
36
-
37
- export const PACKAGE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
38
- /** Own-property PRESENCE of any of these on a package row is forbidden
39
- * transitional evidence (contract §4.1) — never truthiness and never array
40
- * length, so an empty `capabilities: []` or a dependency-free old row still
41
- * classifies. Package-row `path`/`dependencies` are NEVER tells: the current
42
- * shape retains both. */
43
- export const TRANSITIONAL_ROW_FIELDS = ["capabilities", "trustedCapabilities", "depsIntegrity"];
44
-
45
- /** Normalize a configured package path to its canonical form, or throw.
46
- *
47
- * Canonical form is a POSIX-relative path with no redundant or trailing
48
- * separators; every spelling of the repository root ("", ".", "./", "./.")
49
- * normalizes to the single canonical "." so a root selection round-trips
50
- * identically through spec → lock → JSON → doctor/list/update (contract §4).
51
- *
52
- * Fail-closed: absolute paths, Windows drive paths, host-ambient "~" spellings,
53
- * backslash separators (ambiguous — a backslash is a legal POSIX filename
54
- * character, so accepting it as a separator would make containment checks
55
- * disagree with the filesystem) and NUL are rejected as invalid-source; ".."
56
- * traversal is path-escape. Returns undefined ONLY for an absent value, so the
57
- * caller can apply the source-appropriate default. */
58
- export function normalizePackagePath(raw, { where = "package path", code = "invalid-source" } = {}) {
59
- // ABSENT means absent. A present `null` (JSON's way of spelling a malformed
60
- // value) is a violation, not a fall-through to the caller's default — a
61
- // catalog entry that says `"path": null` must fail, not silently install
62
- // DEFAULT_PACKAGE_PATH.
63
- if (raw === undefined) return undefined;
64
- if (typeof raw !== "string") throw oatsError(code, `${where} must be a string (got ${Array.isArray(raw) ? "array" : raw === null ? "null" : typeof raw})`);
65
- const s = raw.trim();
66
- if (s.includes("\0")) throw oatsError(code, `${where} contains a NUL byte`);
67
- if (s.startsWith("~")) throw oatsError(code, `${where} "${s}" is a host-ambient path — package paths are repository-relative`);
68
- if (s.includes("\\")) throw oatsError(code, `${where} "${s}" uses backslashes — package paths are POSIX-relative (use "/")`);
69
- if (/^[A-Za-z]:[/\\]/.test(s)) throw oatsError(code, `${where} "${s}" is an absolute drive path — package paths are repository-relative`);
70
- if (isAbsolute(s)) throw oatsError(code, `${where} "${s}" is absolute — package paths are repository-relative`);
71
- const segments = s.split("/").filter((seg) => seg !== "" && seg !== ".");
72
- if (segments.includes("..")) throw oatsError("path-escape", `${where} "${s}" escapes the source root with ".."`);
73
- return segments.length ? segments.join("/") : ".";
74
- }
75
-
76
- /** Parse a lock entry's `source` against the EXACT normalized grammar the
77
- * writer produces. Strict on purpose: `updatePackage` turns this back into a
78
- * source spec, so a payload that merely "starts with catalog:" but is not a
79
- * valid catalog id gets RECLASSIFIED downstream — `catalog:../evil` would be
80
- * re-parsed as a host-relative local path and acquired from the operator's
81
- * filesystem. A lock also never carries a `#<path>` fragment: the selected
82
- * root is the entry's own `path` field, and a fragment here would produce a
83
- * double-fragment spec on update. */
84
- export function parseLockSource(src) {
85
- const s = String(src || "");
86
- const bad = (why) => oatsError("invalid-source", `unknown lock source "${src}" — ${why}`);
87
- if (s.includes("#")) throw bad(`lock sources carry no "#<path>" fragment; the selected package root is the entry's "path" field`);
88
- if (s.startsWith("path:")) {
89
- const p = s.slice(5);
90
- if (!p) throw bad("empty path source");
91
- if (!isAbsolute(p)) throw bad("path source must be an absolute directory (the writer always resolves it)");
92
- return { kind: "path", path: p, normalized: s };
93
- }
94
- if (s.startsWith("catalog:")) {
95
- const body = s.slice(8);
96
- // Split at the FIRST "@", mirroring the public parser's regex: the catalog
97
- // id grammar cannot contain "@", so everything after the first one is the
98
- // selector. Splitting at the LAST "@" misreads a legitimate ref spelling
99
- // such as `oats.okf@release@candidate` — which the writer does produce —
100
- // as the id `oats.okf@release`.
101
- const at = body.indexOf("@");
102
- const id = at > 0 ? body.slice(0, at) : body;
103
- const selector = at > 0 ? body.slice(at + 1) : undefined;
104
- if (!PACKAGE_ID_RE.test(id)) throw bad(`"${id}" is not a valid official catalog id`);
105
- if (at > 0 && !selector) throw bad("empty catalog selector");
106
- return { kind: "catalog", id, selector, normalized: s };
107
- }
108
- if (s.startsWith("git:")) {
109
- const body = s.slice(4);
110
- const at = body.lastIndexOf("@") > body.lastIndexOf("/") ? body.lastIndexOf("@") : -1;
111
- const url = at > 0 ? body.slice(0, at) : body;
112
- const ref = at > 0 ? body.slice(at + 1) : undefined;
113
- if (!url) throw bad("empty git url");
114
- if (at > 0 && !ref) throw bad("empty git ref");
115
- if (!/^(https?:\/\/|file:\/\/|git@|ssh:\/\/|git:\/\/)/.test(url)) throw bad(`"${url}" is not an http(s)/ssh/file/git URL`);
116
- return { kind: "git", url, ref, normalized: s };
117
- }
118
- throw oatsError("invalid-source", `unknown lock source "${src}"`);
119
- }
120
-
121
- /** Semantic lock-entry validation for a PACKAGE row (runtime API addendum §4):
122
- * source/commit pairing, canonical path, dependency references (incl. self and
123
- * cycle over the locked graph), digest shapes, uniqueness. Run BEFORE restore,
124
- * trust/approval, update/remove/migration planning, the locked-template reader,
125
- * and doctor/list consumption. Fails closed with code "invalid-lock" carrying
126
- * file/package provenance; never normalizes or auto-repairs on read. */
127
- export function validateLockEntry(packageId, entry, allPackages = {}, opts = {}) {
128
- const where = opts.file ? ` (${opts.file})` : "";
129
- const bad = (msg) => oatsError("invalid-lock", `lock entry for package "${packageId}"${where} is invalid: ${msg}`, [{ package: packageId, file: opts.file, violation: msg }]);
130
- if (!entry || typeof entry !== "object") throw bad("not an object");
131
- for (const k of ["source", "path", "version", "commit", "integrity"]) if (!entry[k] || typeof entry[k] !== "string") throw bad(`missing ${k}`);
132
- // The selected package root is a STRICT separate field (contract §1.1) stored
133
- // in canonical form only — a lock is never normalized or repaired on read, so
134
- // a non-canonical spelling ("./sub", "sub/", "") is invalid, not silently
135
- // accepted. That is what makes the root representation round-trip.
136
- {
137
- let canonical;
138
- try { canonical = normalizePackagePath(entry.path, { where: "path", code: "invalid-lock" }); }
139
- catch (e) { throw bad(`invalid path ${JSON.stringify(entry.path)} — ${e.message}`); }
140
- if (canonical !== entry.path) throw bad(`path ${JSON.stringify(entry.path)} is not in canonical form (expected ${JSON.stringify(canonical)})`);
141
- }
142
- if (!/^sha256-[0-9a-f]{64}$/.test(entry.integrity)) throw bad(`malformed integrity "${entry.integrity}"`);
143
- // Present-but-wrong-typed optional fields are invalid — default ONLY when absent.
144
- // `dependencies` is ALWAYS recorded (empty array when none), so a reader never
145
- // has to distinguish absent from empty.
146
- if (!Array.isArray(entry.dependencies)) throw bad("dependencies must be an array (empty when the package has none)");
147
- for (const d of entry.dependencies) if (typeof d !== "string" || !PACKAGE_ID_RE.test(d)) throw bad(`dependencies contains an invalid package id ${JSON.stringify(d)}`);
148
- if (new Set(entry.dependencies).size !== entry.dependencies.length) throw bad("dependencies contains duplicates");
149
- // Package rows lock transport only. A capability list, a trust list or a
150
- // dependency-closure digest here is the unsupported transitional shape — the
151
- // central parser rejects those documents outright (contract §4.1); this is
152
- // the entry-level backstop for a row reaching validation another way.
153
- for (const gone of TRANSITIONAL_ROW_FIELDS) {
154
- if (Object.hasOwn(entry, gone)) throw bad(`"${gone}" is a transitional package-root field — package rows lock transport only (capabilities and trust live on capability rows)`);
155
- }
156
- let src;
157
- try { src = parseLockSource(entry.source); } catch { throw bad(`unrecognized source "${entry.source}"`); }
158
- if (src.kind === "path" && !src.path) throw bad("empty path source");
159
- if (src.kind === "git" && !src.url) throw bad("empty git source");
160
- if (src.kind === "catalog" && !src.id) throw bad("empty catalog source");
161
- if (src.kind === "path") {
162
- if (entry.commit !== "local") throw bad(`path source requires commit "local", got "${entry.commit}"`);
163
- // Local acquisition is exact-directory: the source string already names the
164
- // package root, so the only valid contained path is the root itself.
165
- if (entry.path !== ".") throw bad(`path source requires path "." (local sources are exact directories), got ${JSON.stringify(entry.path)}`);
166
- }
167
- else if (!/^[0-9a-f]{40}$/.test(entry.commit)) throw bad(`${src.kind} source requires an exact 40-hex commit, got "${entry.commit}"`);
168
- for (const d of entry.dependencies || []) {
169
- if (d === packageId) throw bad(`self-dependency "${d}"`);
170
- // Object.hasOwn: a dependency literally named "constructor"/"__proto__"
171
- // must not pass via Object.prototype.
172
- if (!Object.hasOwn(allPackages, d)) throw bad(`dependency "${d}" is not locked in the same packages map`);
173
- }
174
- // Cycle over the locked dependency graph reachable from this entry.
175
- const visiting = new Set();
176
- const visited = new Set();
177
- const walk = (id, chain) => {
178
- if (visited.has(id)) return;
179
- if (visiting.has(id)) throw bad(`dependency cycle in the locked graph: ${[...chain, id].join(" → ")}`);
180
- visiting.add(id);
181
- const deps = Object.hasOwn(allPackages, id) && Array.isArray(allPackages[id]?.dependencies) ? allPackages[id].dependencies : [];
182
- for (const d of deps) if (Object.hasOwn(allPackages, d) || d === packageId) walk(d, [...chain, id]);
183
- visiting.delete(id); visited.add(id);
184
- };
185
- walk(packageId, []);
186
- return true;
187
- }
188
-
189
- /** Semantic validation of one CAPABILITY row against the whole document
190
- * (contract §4). The `package` back-reference must name a locked package: it is
191
- * the single provider truth, so a dangling reference would leave a materialized
192
- * artifact with no provenance to restore or verify it from. */
193
- export function validateCapabilityLockEntry(capabilityId, entry, allPackages = {}, opts = {}) {
194
- const where = opts.file ? ` (${opts.file})` : "";
195
- const bad = (msg) => oatsError("invalid-lock", `capability lock entry for "${capabilityId}"${where} is invalid: ${msg}`, [{ package: capabilityId, file: opts.file, violation: msg }]);
196
- if (typeof capabilityId !== "string" || !capabilityId) throw bad("empty capability id");
197
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw bad("not an object");
198
- for (const k of ["version", "package", "path", "integrity"]) if (!entry[k] || typeof entry[k] !== "string") throw bad(`missing ${k}`);
199
- if (!PACKAGE_ID_RE.test(entry.package)) throw bad(`provider package ${JSON.stringify(entry.package)} is not a valid package identity`);
200
- if (!Object.hasOwn(allPackages, entry.package)) throw bad(`provider package "${entry.package}" is not locked in the same packages map`);
201
- {
202
- let canonical;
203
- try { canonical = normalizePackagePath(entry.path, { where: "path", code: "invalid-lock" }); }
204
- catch (e) { throw bad(`invalid path ${JSON.stringify(entry.path)} — ${e.message}`); }
205
- if (canonical !== entry.path) throw bad(`path ${JSON.stringify(entry.path)} is not in canonical form (expected ${JSON.stringify(canonical)})`);
206
- }
207
- if (!/^sha256-[0-9a-f]{64}$/.test(entry.integrity)) throw bad(`malformed integrity "${entry.integrity}"`);
208
- if (typeof entry.trusted !== "boolean") throw bad(`"trusted" must be a boolean (got ${JSON.stringify(entry.trusted)})`);
209
- return true;
210
- }
211
-
212
- /** Read an artifact's provenance and check it AGREES with the lock rows it was
213
- * projected from. Disagreement is invalid-lock: the artifact and the lock claim
214
- * different origins, and neither may silently win. (A modified file also fails
215
- * integrity, but this gives the precise diagnosis.) */
216
- export function verifyCapabilityInstallation(dir, capabilityId, capRow, pkgRow) {
217
- const file = join(dir, CAPABILITY_INSTALLATION_FILE);
218
- if (!existsSync(file)) throw oatsError("invalid-lock", `materialized capability ${capabilityId} has no ${CAPABILITY_INSTALLATION_FILE} provenance — reproject it with \`oats install\``, [{ package: capabilityId, file }]);
219
- let doc;
220
- try { doc = JSON.parse(readFileSync(file, "utf8")); }
221
- catch (e) { throw oatsError("invalid-lock", `materialized capability ${capabilityId} has malformed ${CAPABILITY_INSTALLATION_FILE}: ${e.message}`, [{ package: capabilityId, file }]); }
222
- const expected = {
223
- schemaVersion: 1, capability: capabilityId, version: capRow.version, package: capRow.package,
224
- packageVersion: pkgRow.version, source: pkgRow.source, commit: pkgRow.commit,
225
- packagePath: pkgRow.path, capabilityPath: capRow.path,
226
- };
227
- for (const [k, want] of Object.entries(expected)) {
228
- if (doc?.[k] !== want) throw oatsError("invalid-lock", `materialized capability ${capabilityId}: ${CAPABILITY_INSTALLATION_FILE} "${k}" is ${JSON.stringify(doc?.[k])} but the lock records ${JSON.stringify(want)}`, [{ package: capabilityId, file, violation: k }]);
229
- }
230
- return doc;
231
- }
@@ -1,21 +0,0 @@
1
- /** The existing captured loader action wire, shared with provider invocation
2
- * validation. This is a data codec, not a dispatch table or provider policy. */
3
- import { canonicalJson } from "./portable-values.mjs";
4
- import { objectAt, stringAt } from "./portable-shape.mjs";
5
- import { oatsError } from "./errors.mjs";
6
-
7
- export function validateCapturedAction(action) {
8
- canonicalJson(action);
9
- if (!["inspect", "compose", "command", "operation", "hook"].includes(action?.kind)) throw oatsError("unsupported-action", "captured action is not supported by this loader");
10
- if (["inspect", "compose"].includes(action.kind)) objectAt(action, ["kind"], ["kind"]);
11
- else if (action.kind === "command") {
12
- objectAt(action, ["kind", "capability", "namespace", "name"], ["kind", "name"]);
13
- if ((action.capability === undefined) === (action.namespace === undefined)) throw oatsError("invalid-declaration", "command needs exactly one capability or namespace");
14
- } else {
15
- const fields = action.kind === "hook" ? ["kind", "capability", "name"] : ["kind", "slot", "name"];
16
- objectAt(action, fields, fields);
17
- }
18
- for (const key of ["name", "namespace", "capability", "slot"]) if (action[key] !== undefined) stringAt(action[key], `/action/${key}`);
19
- if (action.kind === "operation" && !["knowledge", "messaging", "tasks"].includes(action.slot)) throw oatsError("invalid-declaration", "captured operation slot is invalid");
20
- return action;
21
- }
@@ -1,20 +0,0 @@
1
- /** Generic incarnation/admitted-intent identities. Composition references are
2
- * deliberately not accepted as incarnation or logical-request identities. */
3
- import { canonicalJson } from "./portable-values.mjs";
4
- import { objectAt, stringAt } from "./portable-shape.mjs";
5
- import { oatsError } from "./errors.mjs";
6
-
7
- export function validateIncarnationId(value) {
8
- stringAt(value, "/incarnationId", { pattern: /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/ });
9
- return value;
10
- }
11
-
12
- export function validateIntentRef(value) {
13
- canonicalJson(value, { maxBytes: 4096, maxDepth: 4, maxEntries: 16 });
14
- objectAt(value, ["schemaVersion", "executionId", "incarnationId", "attempt"], ["schemaVersion", "executionId", "incarnationId", "attempt"]);
15
- if (value.schemaVersion !== 1) throw oatsError("unsupported-wire-version", "unsupported captured intent version");
16
- stringAt(value.executionId, "/executionId", { pattern: /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/ });
17
- validateIncarnationId(value.incarnationId);
18
- if (!Number.isSafeInteger(value.attempt) || value.attempt < 1) throw oatsError("invalid-declaration", "intent attempt must be a positive integer");
19
- return value;
20
- }
@@ -1,36 +0,0 @@
1
- /** Private invocation projection, not a new binding store or action permit.
2
- * The synchronous caller has already loaded/qualified its exact captured action. */
3
- import { lstatSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { portableStateDirectory } from './portable-state.mjs';
6
- import { canonicalJson } from './portable-values.mjs';
7
- import { validateProviderBinding } from './resolution-shape.mjs';
8
- import { BINDING_LIMITS } from './provider-binding-wire.mjs';
9
- import { oatsError } from './errors.mjs';
10
-
11
- export function withCapturedBindingFile({deployment,record,capability},run) {
12
- const bindings=Object.values(record.bindings).filter(binding=>binding.capability===capability.id);
13
- if (!bindings.length) return run({});
14
- if (bindings.length !== 1) throw oatsError('invalid-resolution','one capability cannot consume contradictory slot bindings');
15
- const binding=validateProviderBinding(bindings[0]),bytes=canonicalJson(binding,BINDING_LIMITS);
16
- const directory=mkdtempSync(join(portableStateDirectory(deployment,true),'.binding-'));
17
- const owned=lstatSync(directory),file=join(directory,'binding.json');let primary,result,completed=false;
18
- try {
19
- writeFileSync(file,bytes,{flag:'wx',mode:0o600});
20
- result=run({OATS_BINDING_FILE:file});completed=true;return result;
21
- } catch(error) {primary=error;throw error;}
22
- finally {
23
- try {
24
- const current=lstatSync(directory);
25
- if (!current.isDirectory() || current.dev!==owned.dev || current.ino!==owned.ino) throw oatsError('integrity-drift','binding invocation directory ownership changed');
26
- rmSync(directory,{recursive:true});
27
- } catch(cleanup) {
28
- if (!primary) {
29
- const error=new AggregateError([cleanup],'captured binding cleanup failed after invocation',{cause:cleanup});
30
- error.code=cleanup.code;error.invocationCompleted=completed;error.invocationResult=result;throw error;
31
- }
32
- const error=new AggregateError([primary,cleanup],'captured command and binding cleanup failed',{cause:primary});
33
- error.code=primary.code;if(primary.invocationCompleted){error.invocationCompleted=true;error.invocationResult=primary.invocationResult;}throw error;
34
- }
35
- }
36
- }
@@ -1,66 +0,0 @@
1
- /** One exact action-loading boundary. No current config, lock or marketplace
2
- * lookup. Core supplies its complete codecs and native host checks. */
3
- import { canonicalJson } from "./portable-values.mjs";
4
- import { validateCapturedAction } from "./captured-action-shape.mjs";
5
- import { verifyResolutionInputs } from "./captured-resolutions.mjs";
6
- import { readApprovalLedger, evaluateCapturedApprovals } from "./artifact-approvals.mjs";
7
- import { composeCapturedInstructions } from "./instruction-composition.mjs";
8
- import { oatsError } from "./errors.mjs";
9
-
10
- export function loadCapturedAction({ deployment, resolution, action }, codecs) {
11
- validateCapturedAction(action);
12
- const verified = verifyResolutionInputs(deployment, resolution), { record } = verified;
13
- if (record.capture !== "prepared") throw oatsError("migration-required", "reconstructed dispatch requires the dedicated migration evidence verifier");
14
- const manifests = new Map(), capabilities = new Map();
15
- for (const [id, row] of Object.entries(record.artifacts.capabilities)) {
16
- const root = verified.roots.get(canonicalJson(row.artifact));
17
- const manifest = codecs.manifest(root);
18
- if (!manifest || manifest.capability !== id || manifest.version !== row.version) throw oatsError("invalid-resolution", "compiled manifest differs from captured identity/version");
19
- const settings = Object.fromEntries(Object.entries(record.dispatch.settingsChoices[id] ?? {}).map(([name, key]) => [name, record.choices[key].value]));
20
- codecs.settings(manifest, settings);
21
- manifests.set(id, manifest); capabilities.set(id, { id, manifest, settings });
22
- }
23
- // Complete static validation applies even to inspection; neither inspection
24
- // nor input verification is an excuse to accept an unsafe launch recipe.
25
- if (record.dispatch.launch !== null) codecs.launch(record.dispatch.launch);
26
- const approvals = evaluateCapturedApprovals({ ...verified, manifests }, readApprovalLedger(deployment).ledger);
27
- const base = { ...verified, manifests, capabilities, approvals, resolution, deployment };
28
- if (action.kind === "inspect") return base;
29
- if (action.kind === "compose") return { ...base, composition: composeCapturedInstructions(verified) };
30
- let id = action.capability;
31
- if (action.kind === "operation") id = record.bindings[action.slot]?.capability;
32
- if (action.namespace !== undefined) {
33
- const matches = [...manifests].filter(([, manifest]) => manifest.command === action.namespace);
34
- if (matches.length > 1) throw oatsError("duplicate-namespace", "captured command namespace is ambiguous");
35
- id = matches[0]?.[0];
36
- }
37
- if (typeof id !== "string" || !capabilities.has(id)) throw oatsError("capability-not-selected", "action capability is not selected by this resolution");
38
- const capability = capabilities.get(id), manifest = capability.manifest;
39
- let name = action.name, operation;
40
- if (action.kind === "operation") {
41
- operation = codecs.operations(manifest).find((entry) => entry.name === name);
42
- if (!operation) throw oatsError("operation-not-found", "captured provider does not declare this operation");
43
- name = operation.command;
44
- }
45
- const declarations = action.kind === "hook" ? codecs.hooks(manifest) : manifest.commands;
46
- if (!declarations || !Object.hasOwn(declarations, name)) throw oatsError("command-not-found", "captured capability does not declare this action");
47
- const approval = approvals.find((entry) => entry.artifact.capability === id);
48
- if (approval.status !== "approved") throw oatsError("approval-required", "action needs current approval for its exact capability artifact");
49
- // Provider-owned payload/credential/member qualification is a separate ABI.
50
- // Until its adapter is supplied, report the missing gate rather than execute
51
- // using an ambient binding file or treating captured choices as enrollment.
52
- const invocation = typeof codecs.invocation === "function" ? codecs.invocation(base, action, capability) : undefined;
53
- const bindings = Object.entries(record.bindings).filter(([, binding]) => binding.capability === id);
54
- if (bindings.length) {
55
- if (typeof codecs.bindings !== "function") throw oatsError("provider-not-qualified", "captured provider binding qualification is unavailable");
56
- codecs.bindings(bindings, capability, record, invocation);
57
- }
58
- const missing = codecs.host(capability);
59
- if (missing.length) throw oatsError("host-requirement-missing", "captured action has unmet host command requirements", missing);
60
- const spec = declarations[name];
61
- if (typeof spec !== "string" || !spec.trim() || spec.includes("\0")) throw oatsError("invalid-resolution", "captured executable declaration is not valid text");
62
- const [script, ...args] = spec.trim().split(/\s+/), file = codecs.executable(manifest, script);
63
- if (!file) throw oatsError("resource-not-found", "captured executable is absent");
64
- if (![...verified.resources.values()].includes(file)) throw oatsError("resolution-incomplete", "captured executable is absent from the resource inventory");
65
- return { ...base, action, capability, operation, executable: { file, args }, ...(invocation ? { invocation } : {}) };
66
- }