@awebai/oats 0.25.8 → 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 (185) 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 +334 -72
  8. package/capabilities/oats-aweb/injects/aweb.md +7 -2
  9. package/capabilities/oats-aweb/lib/binding-wire.mjs +107 -5
  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 +14 -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-okf/bin/oats-okf.mjs +1 -1
  20. package/capabilities/oats-okf/lib/binding-wire.mjs +1 -1
  21. package/capabilities/oats-okf/lib/migration.mjs +2 -2
  22. package/capabilities/oats-okf/lib/sources.mjs +5 -4
  23. package/capabilities/oats-okf/lib/stores.mjs +40 -9
  24. package/capabilities/oats-okf/lib/worker.mjs +3 -3
  25. package/capabilities/oats-okf/oats.json +1 -1
  26. package/capabilities/oats-review/oats.json +3 -2
  27. package/docs/capabilities.md +218 -47
  28. package/docs/capability-manifest.schema.json +13 -4
  29. package/docs/configuration.md +17 -5
  30. package/docs/conventions.md +16 -26
  31. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
  32. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
  33. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
  34. package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
  35. package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
  36. package/docs/design/2026-09-20-redesign-program-board.md +2 -2
  37. package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
  38. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
  39. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +54 -10
  40. package/docs/design/2026-09-24-phase-d-plan.md +77 -0
  41. package/docs/design/2026-09-25-teams-contract.md +226 -0
  42. package/docs/design/README.md +3 -3
  43. package/docs/design/launch-configurations.md +20 -16
  44. package/docs/design/operations-contract.md +27 -10
  45. package/docs/desktop-cli-api.md +546 -264
  46. package/docs/desktop-instance-start.md +1 -1
  47. package/docs/desktop.md +7 -13
  48. package/docs/execution-targets.md +16 -18
  49. package/docs/first-team.md +14 -17
  50. package/docs/implementation.md +28 -59
  51. package/docs/integrations.md +88 -32
  52. package/docs/knowledge-capability-authoring.md +1 -1
  53. package/docs/knowledge-reference/package-craft.md +10 -8
  54. package/docs/knowledge-theory.md +1 -1
  55. package/docs/knowledge.md +10 -11
  56. package/docs/layers.md +16 -17
  57. package/docs/oats-local.schema.json +29 -1
  58. package/docs/oats-membership.schema.json +5 -3
  59. package/docs/oats-package.schema.json +2 -2
  60. package/docs/oats-workspace.schema.json +1 -1
  61. package/docs/{official-marketplace.md → official-catalog.md} +15 -16
  62. package/docs/packages.md +75 -52
  63. package/docs/release-notes/v0.22.0.md +1 -1
  64. package/docs/release-notes/v0.23.1.md +1 -1
  65. package/docs/release-notes/v0.25.9.md +23 -0
  66. package/docs/release-notes/v0.26.0.md +670 -0
  67. package/docs/schedules.md +48 -126
  68. package/docs/soul.schema.json +11 -4
  69. package/docs/souls-and-instances.md +56 -43
  70. package/docs/workspaces.md +80 -58
  71. package/injects/instance-boundary.md +1 -1
  72. package/injects/work-attached.md +1 -1
  73. package/injects/work-workspace.md +2 -2
  74. package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
  75. package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
  76. package/lib/capability-contract.mjs +110 -0
  77. package/lib/config-data.mjs +2 -2
  78. package/lib/core.mjs +700 -4824
  79. package/lib/digest.mjs +12 -0
  80. package/lib/instance-inspect.mjs +396 -0
  81. package/lib/instance-lifecycle.mjs +3 -4
  82. package/lib/instance-resolution.mjs +212 -26
  83. package/lib/instruction-composition.mjs +0 -20
  84. package/lib/materialize.mjs +6 -4
  85. package/lib/operator-dispatch.mjs +33 -13
  86. package/lib/packages.mjs +25 -190
  87. package/lib/provider-binding.mjs +4 -2
  88. package/lib/provider-reasons.mjs +3 -68
  89. package/lib/resolve.mjs +204 -68
  90. package/lib/schedule.mjs +97 -272
  91. package/lib/servers.mjs +13 -13
  92. package/lib/{portable-shape.mjs → shape.mjs} +4 -3
  93. package/lib/tree-copy.mjs +44 -0
  94. package/lib/workspace.mjs +125 -20
  95. package/package-catalog.json +7 -7
  96. package/package.json +1 -1
  97. package/skills/integration-authoring/SKILL.md +48 -40
  98. package/skills/oats-getting-started/SKILL.md +105 -110
  99. package/skills/oats-support/SKILL.md +2 -2
  100. package/skills/soul-craft/SKILL.md +13 -6
  101. package/bin/oats-pi-sdk-host.mjs +0 -17
  102. package/docs/2026-09-03-architecture-proposal.md +0 -642
  103. package/docs/artifact-approvals.schema.json +0 -7
  104. package/docs/captured-invocation-context.schema.json +0 -7
  105. package/docs/captured-resolution.schema.json +0 -7
  106. package/docs/design/package-engine-contract.md +0 -813
  107. package/docs/design/package-runtime-api.md +0 -588
  108. package/docs/desktop-succession.md +0 -57
  109. package/docs/execution-capsule.schema.json +0 -108
  110. package/docs/first-team-demo.md +0 -92
  111. package/docs/knowledge-migration.md +0 -147
  112. package/docs/migration-from-oas.md +0 -103
  113. package/docs/oats-config.schema.json +0 -172
  114. package/docs/oats-lock-v3.schema.json +0 -7
  115. package/docs/oats-lock.schema.json +0 -175
  116. package/docs/operating-team-migration.md +0 -470
  117. package/docs/portable.schema.json +0 -2512
  118. package/docs/provider-check-input.schema.json +0 -7
  119. package/docs/rebuild-to-v2.md +0 -511
  120. package/docs/workspace-adoption.md +0 -74
  121. package/injects/framework-workspace.md +0 -7
  122. package/injects/local-soul.md +0 -19
  123. package/injects/oats-portable.md +0 -20
  124. package/injects/oats.md +0 -11
  125. package/injects/portable-instance-boundary.md +0 -39
  126. package/injects/portable-work-directory.md +0 -29
  127. package/lib/artifact-approvals.mjs +0 -120
  128. package/lib/artifact-tree.mjs +0 -141
  129. package/lib/capability-artifacts.mjs +0 -179
  130. package/lib/capability-execution.mjs +0 -15
  131. package/lib/capability-inputs.mjs +0 -39
  132. package/lib/capability-provenance.mjs +0 -231
  133. package/lib/captured-action-shape.mjs +0 -21
  134. package/lib/captured-admission-shape.mjs +0 -20
  135. package/lib/captured-binding-file.mjs +0 -36
  136. package/lib/captured-dispatch.mjs +0 -66
  137. package/lib/captured-instance-index.mjs +0 -277
  138. package/lib/captured-invocation-context.mjs +0 -130
  139. package/lib/captured-launch-request.mjs +0 -66
  140. package/lib/captured-operation-process.mjs +0 -15
  141. package/lib/captured-pi-custody.mjs +0 -29
  142. package/lib/captured-pi-host.mjs +0 -167
  143. package/lib/captured-pi-outcome.mjs +0 -172
  144. package/lib/captured-resolutions.mjs +0 -275
  145. package/lib/captured-scaffold.mjs +0 -87
  146. package/lib/captured-selector.mjs +0 -28
  147. package/lib/captured-session-backend.mjs +0 -52
  148. package/lib/captured-source-receipt-file.mjs +0 -72
  149. package/lib/helper-injection-policy.mjs +0 -104
  150. package/lib/legacy-lock-codec.mjs +0 -106
  151. package/lib/manifest-settings.mjs +0 -84
  152. package/lib/package-closure.mjs +0 -48
  153. package/lib/package-materialization.mjs +0 -83
  154. package/lib/pi-sdk-host.mjs +0 -229
  155. package/lib/portable-artifacts.mjs +0 -115
  156. package/lib/portable-choices.mjs +0 -82
  157. package/lib/portable-composition.mjs +0 -136
  158. package/lib/portable-digest.mjs +0 -105
  159. package/lib/portable-identity.mjs +0 -40
  160. package/lib/portable-lock.mjs +0 -117
  161. package/lib/portable-onboarding-request.mjs +0 -49
  162. package/lib/portable-onboarding.mjs +0 -256
  163. package/lib/portable-package-preparation.mjs +0 -188
  164. package/lib/portable-policy.mjs +0 -44
  165. package/lib/portable-soul.mjs +0 -42
  166. package/lib/portable-state.mjs +0 -80
  167. package/lib/prepare-composition.mjs +0 -170
  168. package/lib/prepared-bindings.mjs +0 -92
  169. package/lib/prepared-resources.mjs +0 -127
  170. package/lib/provider-binding-broker.mjs +0 -65
  171. package/lib/provider-binding-wire.mjs +0 -116
  172. package/lib/readiness.mjs +0 -225
  173. package/lib/repository-observation.mjs +0 -226
  174. package/lib/resolution-shape.mjs +0 -393
  175. package/lib/schedule-capsule.mjs +0 -206
  176. package/lib/soul-constraints.mjs +0 -40
  177. package/lib/source-projection.mjs +0 -84
  178. package/lib/source-spec.mjs +0 -189
  179. package/lib/workspace-definition.mjs +0 -126
  180. package/lib/workspace-discovery.mjs +0 -146
  181. package/skills/oats/SKILL.md +0 -162
  182. package/skills/oats-config/SKILL.md +0 -164
  183. package/skills/oats-packages/SKILL.md +0 -184
  184. package/skills/oats-portable/SKILL.md +0 -115
  185. package/skills/oats-portable-artifacts/SKILL.md +0 -63
@@ -1,277 +0,0 @@
1
- /** Bounded lifecycle custody in the existing deployment index. No source/config
2
- * discovery, user database, provider policy or automatic legacy backfill. */
3
- import { closeSync, constants, fsyncSync, lstatSync, openSync } from "node:fs";
4
- import { dirname } from "node:path";
5
- import { randomUUID } from "node:crypto";
6
- import { isAbsolute, join, resolve } from "node:path";
7
- import { readPortableBytes } from "./portable-files.mjs";
8
- import { compareUtf8, canonicalJson, parseStrictJson } from "./portable-values.mjs";
9
- import { jsonIntegrity } from "./portable-digest.mjs";
10
- import { portableScope, portableStateDirectory, withPortableStateWrite, writeGuardedPortableDocument } from "./portable-state.mjs";
11
- import { validateExecutionBinding } from "./schedule-capsule.mjs";
12
- import { validateIncarnationId, validateIntentRef } from "./captured-admission-shape.mjs";
13
- import { validateCapturedAction } from "./captured-action-shape.mjs";
14
- import { isMaterializedCapabilityId } from "./capability-provenance.mjs";
15
- import { objectAt, stringAt } from "./portable-shape.mjs";
16
- import { oatsError } from "./errors.mjs";
17
-
18
- const FILE = "instance-references.json";
19
- const STATUSES = new Set(["scaffolded-hooks-pending", "spawn-hooks-running", "spawned-launch-pending", "spawned-cleanup-required", "spawn-failed-cleanup-required", "retire-running", "retire-failed-cleanup-required", "start-running", "start-dispatched", "start-failed-cleanup-required"]);
20
- const INTENT_STATES = new Set(["admitted", "running", "completed", "unconfirmed", "blocked"]);
21
- const LIMITS = Object.freeze({ maxBytes: 4 * 1024 * 1024, maxDepth: 32, maxEntries: 40_000 });
22
- const RECEIPT_LIMITS = Object.freeze({ maxBytes: 128 * 1024, maxDepth: 24, maxEntries: 8192 });
23
- const same = (a,b) => canonicalJson(a) === canonicalJson(b);
24
-
25
- export function capturedDirectoryIdentity(path) {
26
- const stat = lstatSync(path);
27
- if (!stat.isDirectory()) throw oatsError("integrity-drift", "captured home/work must remain real directories");
28
- return { dev: stat.dev, ino: stat.ino };
29
- }
30
- function directoryIdentity(value) {
31
- objectAt(value,["dev","ino"],["dev","ino"]);
32
- for (const key of ["dev","ino"]) if (!Number.isSafeInteger(value[key]) || value[key] < 0) throw oatsError("invalid-declaration","invalid directory identity witness");
33
- }
34
- function human(value) {
35
- if (value === null) return;
36
- objectAt(value,["provider","id"],["provider","id"]);stringAt(value.provider,"/human/provider");stringAt(value.id,"/human/id");
37
- }
38
- function intentRef(row,intent) { return {schemaVersion:1,executionId:intent.executionId,incarnationId:row.incarnationId,attempt:intent.attempt}; }
39
- function validateAdmittedAction(action,capability) {
40
- if(action?.kind==='session'){
41
- objectAt(action,['kind','name'],['kind','name']);
42
- if(!['start','restart'].includes(action.name)||capability!==null)throw oatsError('invalid-declaration','invalid kernel session admission');
43
- }else{
44
- validateCapturedAction(action);
45
- if(!isMaterializedCapabilityId(capability)||action.capability!==undefined&&action.capability!==capability)throw oatsError('invalid-declaration','invalid intent capability owner');
46
- }
47
- }
48
- function validateIntent(intent) {
49
- objectAt(intent,["executionId","capability","action","inputIntegrity","state","attempt","receipt","replayable"],["executionId","capability","action","inputIntegrity","state","attempt","receipt","replayable"]);
50
- if(typeof intent.replayable!=="boolean")throw oatsError("invalid-declaration","invalid intent replay evidence");
51
- validateAdmittedAction(intent.action,intent.capability);
52
- stringAt(intent.executionId,"/executionId",{pattern:/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/});
53
- if (!["command","operation","hook","session"].includes(intent.action.kind) || !INTENT_STATES.has(intent.state)) throw oatsError("invalid-declaration","invalid admitted action/state");
54
- if (!Number.isSafeInteger(intent.attempt) || intent.attempt < 1) throw oatsError("invalid-declaration","invalid intent attempt");
55
- objectAt(intent.inputIntegrity,["format","value"],["format","value"]);
56
- if (intent.inputIntegrity.format !== "oats.json.v1" || !/^sha256-[a-f0-9]{64}$/.test(intent.inputIntegrity.value)) throw oatsError("invalid-declaration","invalid intent input integrity");
57
- canonicalJson(intent.receipt,RECEIPT_LIMITS);
58
- }
59
- export function validateCapturedInstanceReference(row) {
60
- objectAt(row,["home","instance","agent","kind","status","executionBinding","responsibleHuman","incarnationId","custody","intents","custodyFailure","nativeScaffold"],
61
- ["home","instance","agent","kind","status","executionBinding","responsibleHuman","incarnationId","custody","intents"]);
62
- for(const key of ["home","instance","agent"]) stringAt(row[key],`/${key}`);
63
- if(!isAbsolute(row.home)||resolve(row.home)!==row.home) throw oatsError("invalid-declaration","captured home must be normalized absolute");
64
- if(!['persistent','helper'].includes(row.kind)||!STATUSES.has(row.status)) throw oatsError("invalid-declaration","invalid captured instance kind/status");
65
- validateIncarnationId(row.incarnationId);validateExecutionBinding(row.executionBinding);human(row.responsibleHuman);
66
- if(row.nativeScaffold!==undefined){
67
- objectAt(row.nativeScaffold,['schemaVersion','directories'],['schemaVersion','directories']);
68
- if(row.nativeScaffold.schemaVersion!==1)throw oatsError('invalid-declaration','invalid native scaffold witness');
69
- const names=['historyRoot','history','retirementRoot','baselines'];objectAt(row.nativeScaffold.directories,names,names);
70
- for(const value of Object.values(row.nativeScaffold.directories))directoryIdentity(value);
71
- }
72
- if(row.custodyFailure!==undefined){
73
- objectAt(row.custodyFailure,["code","message"],["code","message"]);
74
- for(const key of ["code","message"]){stringAt(row.custodyFailure[key],`/custodyFailure/${key}`);if(row.custodyFailure[key].length>200)throw oatsError("resource-limit","custody diagnostic exceeds bound");}
75
- }
76
- objectAt(row.custody,["home","work"],["home","work"]);directoryIdentity(row.custody.home);directoryIdentity(row.custody.work);
77
- if(!Array.isArray(row.intents)||row.intents.length>128) throw oatsError("resource-limit","captured instance intent retention limit reached");
78
- const ids=new Set();let pending=0;
79
- for(const intent of row.intents){validateIntent(intent);if(ids.has(intent.executionId))throw oatsError("invalid-declaration","duplicate intent ID");ids.add(intent.executionId);if(intent.state!=="completed")pending++;}
80
- if(pending>1) throw oatsError("invalid-declaration","multiple unsettled captured intents");
81
- return row;
82
- }
83
- function validateIndex(value) {
84
- canonicalJson(value,LIMITS);objectAt(value,["schemaVersion","instances"],["schemaVersion","instances"]);
85
- if(value.schemaVersion!==2) throw oatsError("migration-required","captured instance index lacks incarnation/admission evidence; no automatic backfill");
86
- if(!Array.isArray(value.instances)) throw oatsError("invalid-declaration","invalid captured instance index");
87
- const ids=new Set();let prior;
88
- for(const row of value.instances){validateCapturedInstanceReference(row);if(ids.has(row.incarnationId)||(prior!==undefined&&compareUtf8(prior,row.home)>=0))throw oatsError("invalid-declaration","duplicate/unordered captured instance references");ids.add(row.incarnationId);prior=row.home;}
89
- return value;
90
- }
91
- export function readCapturedInstanceIndex(deployment) {
92
- const root=portableStateDirectory(deployment,false);
93
- if(!root)return {schemaVersion:2,instances:[]};
94
- const bytes=readPortableBytes(join(root,FILE),{allowMissing:true});
95
- return bytes===null?{schemaVersion:2,instances:[]}:validateIndex(parseStrictJson(bytes,LIMITS));
96
- }
97
-
98
- /** Read and verify current owned metadata; old/unwitnessed homes are evidence,
99
- * never newly minted identities. Recheck roots after the descriptor read. */
100
- export function readCapturedInstanceMetadata(home) {
101
- if(typeof home!=="string"||!isAbsolute(home))throw oatsError("invalid-declaration","captured home must be explicit and absolute");
102
- const actual=portableScope(home),homeIdentity=capturedDirectoryIdentity(home),workIdentity=capturedDirectoryIdentity(join(home,"work"));
103
- const metadata=parseStrictJson(readPortableBytes(join(actual,"instance.json")),LIMITS);
104
- if(!metadata.incarnationId||!metadata.captured?.custody)throw oatsError("migration-required","captured home lacks incarnation custody; explicit reprovisioning required without deleting prior data");
105
- validateIncarnationId(metadata.incarnationId);validateExecutionBinding(metadata.executionBinding);
106
- if(typeof metadata.home!=="string"||portableScope(metadata.home)!==actual||metadata.work!=="directory"
107
- ||!same(metadata.captured.custody,{home:homeIdentity,work:workIdentity})
108
- ||!same(capturedDirectoryIdentity(home),homeIdentity)||!same(capturedDirectoryIdentity(join(home,"work")),workIdentity))throw oatsError("integrity-drift","captured home/work ownership changed");
109
- return metadata;
110
- }
111
- function matchedRow(index,home) {
112
- const metadata=readCapturedInstanceMetadata(home),actual=portableScope(home),row=index.instances.find(item=>item.home===actual);
113
- if(!row)throw oatsError("invalid-resolution","captured incarnation is not indexed");
114
- if(row.incarnationId!==metadata.incarnationId||row.instance!==metadata.instance||row.agent!==metadata.agent||row.kind!==metadata.kind
115
- ||!same(row.custody,metadata.captured.custody)||!same(row.executionBinding,metadata.executionBinding)||!same(row.responsibleHuman,metadata.responsibleHuman))throw oatsError("invalid-resolution","captured index differs from current incarnation metadata");
116
- return {row,metadata};
117
- }
118
- export function readCapturedInstanceAuthority(deployment,home) {
119
- return matchedRow(readCapturedInstanceIndex(deployment),home);
120
- }
121
- const AUTHORITY_FIELDS = ["home","instance","agent","kind","executionBinding","responsibleHuman","incarnationId","custody"];
122
- function sameAuthority(a,b){return AUTHORITY_FIELDS.every(key=>same(a[key],b[key]))&&same(a.nativeScaffold??null,b.nativeScaffold??null);}
123
- export function assertCapturedInstanceCustody(original) {
124
- for(const [key,path] of [["home",original.home],["work",join(original.home,"work")]]){
125
- if(!same(capturedDirectoryIdentity(path),original.custody[key]))throw oatsError("integrity-drift","captured home/work changed after preflight");
126
- }
127
- const {row}=readCapturedInstanceAuthority(original.executionBinding.deployment,original.home);
128
- if(!sameAuthority(original,row))throw oatsError("integrity-drift","captured instance authority changed after preflight");
129
- return row;
130
- }
131
-
132
- /** Failure reporting deliberately does NOT read/resolve the home: it may now
133
- * belong to someone else. Match the original indexed authority and attempt,
134
- * preserve receipts there, and never use this path to certify success. */
135
- export function recordCapturedCustodyFailure(original,{meta={},intents={},code="integrity-drift",message="captured home/work custody changed"}={}) {
136
- validateCapturedInstanceReference(original);objectAt(meta,null,[]);objectAt(intents,null,[]);
137
- return withPortableStateWrite(original.executionBinding.deployment,context=>{
138
- const index=readCapturedInstanceIndex(context.deployment),row=index.instances.find(item=>item.incarnationId===original.incarnationId);
139
- if(!row||!sameAuthority(original,row))throw oatsError("selection-changed","original indexed custody no longer matches failure report");
140
- // This reporter owns only the activation transition it acquired before
141
- // hooks. Check it before changing ANY receipt/status, even on failure.
142
- if(row.status!=="spawn-hooks-running")throw oatsError("selection-changed","activation no longer owns the indexed lifecycle state");
143
- for(const [capability,ref] of Object.entries(intents)){
144
- const intent=matchedIntent(row,ref);
145
- if(intent.capability!==capability)throw oatsError("invalid-resolution","observed receipt owner differs from admitted capability");
146
- if(Object.hasOwn(meta,capability)){canonicalJson(meta[capability],RECEIPT_LIMITS);intent.receipt=meta[capability];}
147
- if(intent.state==="running"||intent.state==="admitted"){intent.state="unconfirmed";intent.replayable=false;}
148
- }
149
- for(const capability of Object.keys(meta))if(!Object.hasOwn(intents,capability))throw oatsError("invalid-resolution","observed receipt lacks its admitted intent");
150
- row.status="spawn-failed-cleanup-required";
151
- row.custodyFailure={code:String(code).slice(0,200)||"integrity-drift",message:String(message).slice(0,200)||"custody failure"};
152
- save(context,index);return {home:row.home,incarnationId:row.incarnationId,executionBinding:row.executionBinding,custodyFailure:row.custodyFailure};
153
- });
154
- }
155
- /** Sync only already-owned metadata paths. If synchronization fails after
156
- * publication, callers retain custody rather than claim admission completed. */
157
- export function syncCapturedMetadata(path) {
158
- const stat=lstatSync(path);
159
- if(!stat.isFile()&&!stat.isDirectory())throw oatsError("integrity-drift","captured metadata sync target changed");
160
- const fd=openSync(path,constants.O_RDONLY|(constants.O_NOFOLLOW??0));
161
- try{fsyncSync(fd);}finally{closeSync(fd);}
162
- }
163
- function save(context,index){
164
- validateIndex(index);const file=join(context.root,FILE);
165
- writeGuardedPortableDocument(context,file,index);
166
- for(const path of [file,context.root,dirname(context.root),context.deployment])syncCapturedMetadata(path);
167
- }
168
- export function registerCapturedInstance(deployment,home) {
169
- return withPortableStateWrite(deployment,context=>{
170
- const index=readCapturedInstanceIndex(context.deployment),metadata=readCapturedInstanceMetadata(home),actual=portableScope(home);
171
- if(index.instances.some(row=>row.home===actual))throw oatsError("selection-changed","captured home has retained lifecycle obligations; no replacement/backfill");
172
- if(portableScope(metadata.executionBinding.deployment)!==context.deployment)throw oatsError("invalid-resolution","incarnation deployment mismatch");
173
- const row={home:actual,instance:metadata.instance,agent:metadata.agent,kind:metadata.kind,status:"scaffolded-hooks-pending",executionBinding:metadata.executionBinding,
174
- responsibleHuman:metadata.responsibleHuman,incarnationId:metadata.incarnationId,custody:metadata.captured.custody,intents:[]};
175
- validateCapturedInstanceReference(row);index.instances.push(row);index.instances.sort((a,b)=>compareUtf8(a.home,b.home));save(context,index);return row;
176
- });
177
- }
178
- export function markCapturedNativeScaffold(deployment,home,proof) {
179
- return withPortableStateWrite(deployment,context=>{
180
- const index=readCapturedInstanceIndex(context.deployment),{row,metadata}=matchedRow(index,home);
181
- if(row.nativeScaffold||row.status!=='scaffolded-hooks-pending'||row.intents.length)throw oatsError('selection-changed','native scaffold cannot be backfilled after admission');
182
- if(!same(metadata.captured.nativeScaffold,proof))throw oatsError('invalid-resolution','native scaffold proof differs from owned metadata');
183
- row.nativeScaffold=proof;save(context,index);return row;
184
- });
185
- }
186
- export function setCapturedInstanceStatus(deployment,home,status,{expectedStatus}={}) {
187
- if(!STATUSES.has(status))throw oatsError("invalid-declaration","invalid captured lifecycle status");
188
- return withPortableStateWrite(deployment,context=>{
189
- const index=readCapturedInstanceIndex(context.deployment),{row}=matchedRow(index,home);
190
- if(expectedStatus!==undefined&&row.status!==expectedStatus)throw oatsError("selection-changed","captured lifecycle status changed");
191
- row.status=status;save(context,index);return row;
192
- });
193
- }
194
-
195
- /** New intent or EXPLICIT retry; input commits only bounded nonsecret request
196
- * data, never runtime environment or credential values. */
197
- export function admitCapturedInstanceAction({deployment,home,executionBinding,capability,action,input={},retryExecutionId,expectedStatus}) {
198
- validateExecutionBinding(executionBinding);
199
- if(expectedStatus!==undefined&&!STATUSES.has(expectedStatus))throw oatsError('invalid-declaration','invalid admission source state');
200
- validateAdmittedAction(action,capability);canonicalJson(input,{maxBytes:128*1024,maxDepth:24,maxEntries:8192});
201
- const inputIntegrity=jsonIntegrity(input);
202
- return withPortableStateWrite(deployment,context=>{
203
- const index=readCapturedInstanceIndex(context.deployment),{row,metadata}=matchedRow(index,home);
204
- if(!same(row.executionBinding,executionBinding))throw oatsError("invalid-resolution","admission selection differs from current indexed incarnation");
205
- if(expectedStatus!==undefined&&(row.status!==expectedStatus||metadata.captured.lifecycle!==expectedStatus)){
206
- const error=oatsError('selection-changed','native admission no longer owns matching source lifecycle');error.home=row.home;
207
- error.capturedCustody={incarnationId:row.incarnationId,executionBinding:row.executionBinding,indexedStatus:row.status,metadataStatus:metadata.captured.lifecycle,expectedStatus,held:true};
208
- throw error;
209
- }
210
- let intent;
211
- if(retryExecutionId!==undefined){
212
- intent=row.intents.find(item=>item.executionId===retryExecutionId);
213
- if(!intent||intent.capability!==capability||!same(intent.action,action)||!same(intent.inputIntegrity,inputIntegrity))throw oatsError("invalid-resolution","retry does not match the admitted action/input");
214
- if(intent.state==="completed")return {intent:intentRef(row,intent),replayed:true,replayable:intent.replayable,receipt:intent.receipt};
215
- if(!["unconfirmed","blocked"].includes(intent.state))throw oatsError("selection-changed","intent is admitted/running; explicit reconciliation is required before retry");
216
- intent.attempt++;intent.state="admitted";
217
- }else{
218
- if(row.intents.some(item=>item.state!=="completed"))throw oatsError("selection-changed","an unsettled captured intent must be reconciled/retried, not replaced");
219
- if(row.intents.length>=128)throw oatsError("resource-limit","captured intent history is full; no automatic pruning");
220
- const previous=row.intents.findLast(item=>item.capability===capability);
221
- const receipt=previous?previous.receipt:capability!==null&&metadata.capabilityMeta&&Object.hasOwn(metadata.capabilityMeta,capability)?metadata.capabilityMeta[capability]:null;
222
- canonicalJson(receipt,RECEIPT_LIMITS);
223
- intent={executionId:randomUUID(),capability,action,inputIntegrity,state:"admitted",attempt:1,receipt,replayable:false};row.intents.push(intent);
224
- }
225
- save(context,index);return {intent:intentRef(row,intent),replayed:false,receipt:intent.receipt};
226
- });
227
- }
228
- function matchedIntent(row,ref,action) {
229
- validateIntentRef(ref);const intent=row.intents.find(item=>item.executionId===ref.executionId);
230
- if(row.incarnationId!==ref.incarnationId||!intent||intent.attempt!==ref.attempt||(action&&!same(intent.action,action)))throw oatsError("invalid-resolution","intent reference differs from indexed incarnation/action/attempt");
231
- return intent;
232
- }
233
- export function readCapturedIntent({deployment,home,intent:ref,action}) {
234
- const {row}=matchedRow(readCapturedInstanceIndex(deployment),home),intent=matchedIntent(row,ref,action);
235
- return {...intent,incarnationId:row.incarnationId};
236
- }
237
- export function failCapturedNativeIntent(original,ref,receipt) {
238
- canonicalJson(receipt,RECEIPT_LIMITS);
239
- return withPortableStateWrite(original.executionBinding.deployment,context=>{
240
- const index=readCapturedInstanceIndex(context.deployment),row=index.instances.find(item=>item.incarnationId===original.incarnationId);
241
- if(!row||!sameAuthority(original,row))throw oatsError('selection-changed','native failure lost original indexed authority');
242
- if(row.status!=='start-running')throw oatsError('selection-changed','native failure no longer owns lifecycle state');
243
- const intent=matchedIntent(row,ref);
244
- if(intent.action.kind!=='session'||intent.capability!==null)throw oatsError('invalid-resolution','native failure is not its session intent');
245
- if(intent.state!=='completed'){intent.state='unconfirmed';intent.receipt=receipt;intent.replayable=false;}
246
- row.status='start-failed-cleanup-required';save(context,index);return intentRef(row,intent);
247
- });
248
- }
249
- /** An allocated native target is retained under the running logical intent
250
- * BEFORE launch. It is an observation, not completion or a new identity. */
251
- export function observeCapturedNativeTarget(original,ref,receipt) {
252
- validateCapturedInstanceReference(original);canonicalJson(receipt,RECEIPT_LIMITS);
253
- return withPortableStateWrite(original.executionBinding.deployment,context=>{
254
- const index=readCapturedInstanceIndex(context.deployment),{row}=matchedRow(index,original.home);
255
- if(!sameAuthority(original,row)||row.status!=='start-running')throw oatsError('selection-changed','native target observation lost original lifecycle custody');
256
- const intent=matchedIntent(row,ref);
257
- if(intent.capability!==null||intent.action.kind!=='session'||intent.state!=='running')throw oatsError('selection-changed','native target observation needs the running admitted session');
258
- intent.receipt=receipt;intent.replayable=false;save(context,index);return intentRef(row,intent);
259
- });
260
- }
261
- export function beginCapturedIntent({deployment,home,intent:ref,action}) {
262
- return withPortableStateWrite(deployment,context=>{
263
- const index=readCapturedInstanceIndex(context.deployment),{row}=matchedRow(index,home),intent=matchedIntent(row,ref,action);
264
- if(intent.state!=="admitted")throw oatsError("selection-changed","captured intent has already begun or settled");
265
- intent.state="running";save(context,index);return intentRef(row,intent);
266
- });
267
- }
268
- export function settleCapturedIntent({deployment,home,intent:ref,action,state,receipt,replayable=false}) {
269
- if(typeof replayable!=="boolean")throw oatsError("invalid-declaration","invalid intent replay evidence");
270
- if(!["completed","unconfirmed","blocked"].includes(state))throw oatsError("invalid-declaration","invalid captured intent outcome");
271
- canonicalJson(receipt,RECEIPT_LIMITS);
272
- return withPortableStateWrite(deployment,context=>{
273
- const index=readCapturedInstanceIndex(context.deployment),{row}=matchedRow(index,home),intent=matchedIntent(row,ref,action);
274
- if((state==="blocked"&&intent.state!=="admitted")||(state!=="blocked"&&intent.state!=="running"))throw oatsError("selection-changed","intent outcome does not match its admitted/running attempt");
275
- intent.state=state;intent.receipt=receipt;intent.replayable=state==="completed"&&replayable;save(context,index);return intentRef(row,intent);
276
- });
277
- }
@@ -1,130 +0,0 @@
1
- /** Provider-neutral captured action/lifecycle context. Provider bindings remain in
2
- * OATS_BINDING_FILE; this companion supplies record/target intent and prior
3
- * opaque provider receipt without provider-specific kernel fields. */
4
- import { lstatSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
5
- import { readCapturedInstanceAuthority, readCapturedIntent } from "./captured-instance-index.mjs";
6
- import { validateIncarnationId, validateIntentRef } from "./captured-admission-shape.mjs";
7
- import { isAbsolute, join, resolve } from "node:path";
8
- import { canonicalJson } from "./portable-values.mjs";
9
- import { objectAt, stringAt } from "./portable-shape.mjs";
10
- import { portableScope, portableStateDirectory } from "./portable-state.mjs";
11
- import { validateExecutionBinding } from "./schedule-capsule.mjs";
12
- import { validateCapturedSubject, validateCapturedContext, validateMessagingChoice } from "./resolution-shape.mjs";
13
- import { validateCapturedAction } from "./captured-action-shape.mjs";
14
- import { isMaterializedCapabilityId } from "./capability-provenance.mjs";
15
- import { oatsError } from "./errors.mjs";
16
-
17
- export const CAPTURED_INVOCATION_CONTEXT_LIMITS = Object.freeze({ maxBytes: 512 * 1024, maxDepth: 32, maxEntries: 16_384 });
18
- export const CAPTURED_PRIOR_RECEIPT_LIMITS = Object.freeze({ maxBytes: 128 * 1024, maxDepth: 24, maxEntries: 8192 });
19
- const FIELDS = ["schemaVersion", "executionBinding", "subject", "instance", "context", "responsibleHuman", "messagingChoice", "capability", "action", "priorReceipt", "intent"];
20
-
21
- function human(value, pointer) {
22
- if (value === null) return;
23
- objectAt(value, ["provider", "id"], ["provider", "id"], pointer);
24
- stringAt(value.provider, `${pointer}/provider`); stringAt(value.id, `${pointer}/id`);
25
- }
26
-
27
- export function validateCapturedInvocationContext(value) {
28
- canonicalJson(value, CAPTURED_INVOCATION_CONTEXT_LIMITS); objectAt(value, FIELDS, FIELDS);
29
- if (value.schemaVersion !== 1) throw oatsError("unsupported-wire-version", "captured invocation context schemaVersion must be 1");
30
- validateExecutionBinding(value.executionBinding);
31
- validateCapturedSubject(value.subject);
32
- const subjectName = value.subject.kind === "persistent" ? value.subject.soul.alias : value.subject.name;
33
- if (value.instance !== null) {
34
- objectAt(value.instance, ["home", "work", "name", "agent", "incarnationId"], ["home", "work", "name", "agent", "incarnationId"], "/instance");
35
- validateIncarnationId(value.instance.incarnationId);
36
- for (const key of ["home", "work"]) {
37
- stringAt(value.instance[key], `/instance/${key}`);
38
- if (!isAbsolute(value.instance[key]) || resolve(value.instance[key]) !== value.instance[key] || value.instance[key].includes("\0")) throw oatsError("invalid-declaration", `captured invocation ${key} must be normalized absolute`);
39
- }
40
- stringAt(value.instance.name, "/instance/name"); stringAt(value.instance.agent, "/instance/agent");
41
- if (value.instance.agent !== subjectName || value.instance.work !== join(value.instance.home, "work")) throw oatsError("invalid-resolution", "captured invocation target differs from its subject/home");
42
- }
43
- validateCapturedContext(value.context);
44
- human(value.responsibleHuman, "/responsibleHuman");
45
- // Reuse the existing messaging-choice codec, not another privacy/policy model.
46
- const messagingProvider = value.messagingChoice?.enabled === true ? value.messagingChoice.privateKey?.provider : undefined;
47
- if (messagingProvider !== undefined && !isMaterializedCapabilityId(messagingProvider)) throw oatsError("invalid-declaration", "invalid invocation messaging provider");
48
- validateMessagingChoice(value.messagingChoice, { context: value.context,
49
- bindings: messagingProvider === undefined ? {} : { messaging: { capability: messagingProvider } } });
50
- const expectedHuman = value.messagingChoice.enabled ? value.messagingChoice.privateKey.human : null;
51
- if (canonicalJson(value.responsibleHuman) !== canonicalJson(expectedHuman)) throw oatsError("invalid-resolution", "invocation responsible human differs from captured messaging choice");
52
- if (!isMaterializedCapabilityId(value.capability)) throw oatsError("invalid-declaration", "invalid invocation capability");
53
- validateCapturedAction(value.action);
54
- if (value.action.capability !== undefined && value.action.capability !== value.capability) throw oatsError("invalid-resolution", "invocation action capability differs from its owner");
55
- if (value.action.kind === "hook" && value.instance === null) throw oatsError("invalid-resolution", "captured instance hook requires instance facts");
56
- if (value.intent !== null) {
57
- validateIntentRef(value.intent);
58
- if (value.instance === null || value.intent.incarnationId !== value.instance.incarnationId) throw oatsError("invalid-resolution", "intent belongs to a different incarnation");
59
- }
60
- canonicalJson(value.priorReceipt, CAPTURED_PRIOR_RECEIPT_LIMITS);
61
- return value;
62
- }
63
-
64
- export function buildCapturedInvocationContext(input) {
65
- const { loaded, action, instance = null } = input;
66
- if (!loaded?.record || !loaded?.resolution || !loaded?.capability) throw oatsError("invalid-resolution", "captured invocation context requires one verified action");
67
- const { record } = loaded, subject = record.subject;
68
- const subjectName = subject.kind === "persistent" ? subject.soul.alias : subject.name;
69
- if (instance !== null && instance.agent !== subjectName) throw oatsError("invalid-resolution", "captured invocation target agent differs from retained subject");
70
- const expectedHuman = record.messagingChoice.enabled ? record.messagingChoice.privateKey.human : null;
71
- let priorReceipt = null, target = null;
72
- const intent = input.intent ?? null;
73
- if (instance !== null) {
74
- canonicalJson(instance);
75
- objectAt(instance, ["home", "work", "name", "agent"], ["home", "work", "name", "agent"]);
76
- if (typeof instance.home !== "string" || !isAbsolute(instance.home) || instance.work !== join(instance.home, "work")) throw oatsError("invalid-resolution", "captured invocation needs an explicit home/work target");
77
- const home = portableScope(instance.home);
78
- if (!lstatSync(instance.home).isDirectory()) throw oatsError("invalid-resolution", "captured invocation home must not be a symlink");
79
- const { metadata, row } = readCapturedInstanceAuthority(loaded.deployment, home);
80
- target = { ...instance, incarnationId: metadata.incarnationId };
81
- validateExecutionBinding(metadata.executionBinding);
82
- if (typeof metadata.home !== "string" || portableScope(metadata.home) !== home || metadata.instance !== instance.name
83
- || metadata.agent !== instance.agent || metadata.kind !== subject.kind
84
- || portableScope(metadata.executionBinding.deployment) !== portableScope(loaded.deployment)
85
- || canonicalJson(metadata.executionBinding.resolution) !== canonicalJson(loaded.resolution)
86
- || canonicalJson(metadata.responsibleHuman) !== canonicalJson(expectedHuman)) throw oatsError("invalid-resolution", "invocation target differs from captured instance metadata/record");
87
- if (metadata.capabilityMeta !== undefined) objectAt(metadata.capabilityMeta, null, []);
88
- const previous = row.intents.findLast(entry => entry.capability === loaded.capability.id);
89
- priorReceipt = previous ? previous.receipt : metadata.capabilityMeta && Object.hasOwn(metadata.capabilityMeta, loaded.capability.id) ? metadata.capabilityMeta[loaded.capability.id] : null;
90
- }
91
- if (intent !== null) {
92
- if (!target) throw oatsError("invalid-resolution", "admitted action requires an owned instance");
93
- const admitted = readCapturedIntent({ deployment: loaded.deployment, home: instance.home, intent, action });
94
- if (admitted.capability !== loaded.capability.id || !["admitted", "running"].includes(admitted.state)) throw oatsError("invalid-resolution", "invocation does not match an active admitted capability action");
95
- priorReceipt = admitted.receipt;
96
- }
97
- if (Object.hasOwn(input, "priorReceipt") && canonicalJson(input.priorReceipt) !== canonicalJson(priorReceipt)) throw oatsError("invalid-resolution", "invocation prior receipt differs from stored provider receipt");
98
- const value = { schemaVersion: 1,
99
- executionBinding: { schemaVersion: 1, deployment: portableScope(loaded.deployment), resolution: loaded.resolution },
100
- subject, instance: target, intent, context: record.context, responsibleHuman: expectedHuman,
101
- messagingChoice: record.messagingChoice, capability: loaded.capability.id, action, priorReceipt };
102
- return validateCapturedInvocationContext(structuredClone(value));
103
- }
104
-
105
- export function withCapturedInvocationContextFile(invocation, run) {
106
- validateCapturedInvocationContext(invocation);
107
- if (typeof run !== "function") throw oatsError("invalid-declaration", "captured invocation context consumer must be a function");
108
- const directory = mkdtempSync(join(portableStateDirectory(invocation.executionBinding.deployment, true), ".invocation-context-"));
109
- const owned = lstatSync(directory), file = join(directory, "context.json"); let primary, result, completed = false;
110
- try {
111
- writeFileSync(file, canonicalJson(invocation, CAPTURED_INVOCATION_CONTEXT_LIMITS), { flag: "wx", mode: 0o600 });
112
- result = run({ OATS_INVOCATION_CONTEXT_FILE: file }); completed = true; return result;
113
- } catch (error) { primary = error; throw error; }
114
- finally {
115
- try {
116
- const current = lstatSync(directory);
117
- if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError("integrity-drift", "invocation context directory ownership changed");
118
- rmSync(directory, { recursive: true });
119
- } catch (cleanup) {
120
- if (!primary) {
121
- const error = new AggregateError([cleanup], "captured invocation context cleanup failed after invocation", { cause: cleanup });
122
- error.code = cleanup.code; error.invocationCompleted = completed; error.invocationResult = result; throw error;
123
- }
124
- const error = new AggregateError([primary, cleanup], "captured invocation context and cleanup failed", { cause: primary });
125
- error.code = primary.code;
126
- if (primary.invocationCompleted) { error.invocationCompleted = true; error.invocationResult = primary.invocationResult; }
127
- throw error;
128
- }
129
- }
130
- }
@@ -1,66 +0,0 @@
1
- /** Explicit portable launch input, not a current launch-config/model resolver.
2
- * OATS-managed entrypoints are retained; external host tools are explicitly
3
- * selected/preflighted, not forced into a new capability layer. */
4
- import { isAbsolute, resolve } from 'node:path';
5
- import { canonicalJson } from './portable-values.mjs';
6
- import { objectAt, stringAt } from './portable-shape.mjs';
7
- import { isMaterializedCapabilityId } from './capability-provenance.mjs';
8
- import { oatsError } from './errors.mjs';
9
- import { PI_SDK_HOST, validatePiHostRecipe } from './pi-sdk-host.mjs';
10
-
11
- export function validateCapturedLaunchRequest(value, validateConfig) {
12
- canonicalJson(value,{maxBytes:128*1024,maxDepth:16,maxEntries:4096});
13
- objectAt(value,['runtime','executable','args','env','model','yolo'],['runtime','executable','args','env','model','yolo']);
14
- if(typeof value.executable==='string'){
15
- stringAt(value.executable,'/launch/executable');
16
- if(!isAbsolute(value.executable)||resolve(value.executable)!==value.executable||value.executable.includes('\0'))throw oatsError('invalid-declaration','host launch executable must be explicit normalized absolute text');
17
- }else{
18
- objectAt(value.executable,['capability','command'],['capability','command']);
19
- if(!isMaterializedCapabilityId(value.executable.capability))throw oatsError('invalid-declaration','managed launch entrypoint must name a selected capability');
20
- stringAt(value.executable.command,'/launch/executable/command');
21
- }
22
- stringAt(value.model,'/launch/model');
23
- if(typeof value.yolo!=='boolean')throw oatsError('invalid-declaration','captured launch requires explicit yolo');
24
- validateConfig('captured',{...value,executable:'captured-resource'},'explicit launch request');
25
- if(value.executable===PI_SDK_HOST || value.args?.includes('--oats-pi-host'))validatePiHostRecipe(value);
26
- // All kernel/provider invocation aliases remain kernel-owned on this new
27
- // surface. The legacy launch-config validator has a narrower historical list.
28
- for(const name of Object.keys(value.env))if(/^(OATS_|PI_AGENT_|OAS_)/.test(name)||name==='PI_AGENTS_ROOT')throw oatsError('invalid-declaration','launch environment cannot override captured authority');
29
- return value;
30
- }
31
-
32
- export function compileCapturedLaunchRequest(request,{artifacts,manifests,settings,resources},kernel) {
33
- if(request===undefined)return null;
34
- validateCapturedLaunchRequest(request,kernel.validateLaunchConfig);
35
- const providers=[...manifests].filter(([id])=>Object.hasOwn(artifacts.capabilities,id)).map(([id,manifest])=>({id,manifest,settings:settings[id]}));
36
- // `ifInstalled: true` rows constrain a package that may legitimately be absent (a version floor
37
- // for an ambient extension, if any). Captured preparation retains no ambient packages, so such a
38
- // row is satisfied by absence — treating it as a hard requirement made a Pi launch with aweb
39
- // `delivery: session` unpublishable (second-operator finding, 2026-09-21). Hard rows refuse
40
- // through the preparation problem shape, naming the capability and package; the remedy text is
41
- // the manifest's own `install` string — declared data, never free text.
42
- // Resolve each applicable row back to its declaration, honouring the same `when` predicate the
43
- // kernel's requirement selection uses, so two rows for one package (channel vs session) stay distinct.
44
- const holds=(provider,row)=>!row.when || Object.entries(row.when).every(([k,v])=>String(provider.settings?.[k] ?? '')===String(v));
45
- const declarationOf=(row)=>{const provider=providers.find(p=>p.id===row.capability);
46
- return {provider,declared:provider?.manifest?.requires?.find(r=>r && typeof r==='object' && r.runtime===request.runtime && r.package===row.package && holds(provider,r))};};
47
- const required=kernel.runtimeRequirements(request.runtime,providers).filter(row=>declarationOf(row).declared?.ifInstalled!==true);
48
- if(required.length){
49
- const error=oatsError('needs-configuration','runtime package requirements need retained runtime roots and a qualified loader; no ambient package discovery was used');
50
- error.problems=required.map(row=>{
51
- const {provider,declared}=declarationOf(row);
52
- return {code:'needs-configuration',message:`${request.runtime} launch requires runtime package ${row.package}, which captured preparation has not retained`,capability:row.capability,
53
- ...(provider?.manifest?.layer?{slot:provider.manifest.layer}:{}),runtime:request.runtime,package:row.package,...(typeof declared?.install==='string'?{install:declared.install}:{})};
54
- });
55
- throw error;
56
- }
57
- const base={version:1,runtime:request.runtime,args:request.args,env:request.env,model:request.model,yolo:request.yolo,
58
- hooks:{launch:{},env:{},contributions:[],pending:true},prompt:{kind:'task-file',file:'TASK.md'}};
59
- if(typeof request.executable==='string')return {...base,executable:request.executable,executableResolvedFrom:'explicit-host'};
60
- const {capability:id,command}=request.executable,manifest=manifests.get(id);
61
- if(!Object.hasOwn(artifacts.capabilities,id)||!manifest?.commands||!Object.hasOwn(manifest.commands,command))throw oatsError('capability-not-selected','launch entrypoint is not declared by a selected capability');
62
- const key=`executable:${id}:command:${command}`;
63
- if(!Object.hasOwn(resources,key)||resources[key].kind!=='file')throw oatsError('resolution-incomplete','launch entrypoint lacks a retained executable resource');
64
- const [, ...declaredArgs]=manifest.commands[command].trim().split(/\s+/);
65
- return {...base,executable:'captured-resource',executableResource:key,entrypoint:{capability:id,command},args:[...declaredArgs,...request.args]};
66
- }
@@ -1,15 +0,0 @@
1
- /** Bounded runner for one already-verified captured provider operation. The
2
- * caller owns receipt interpretation and invocation-snapshot cleanup. */
3
- import { spawnSync } from "node:child_process";
4
- import { oatsError } from "./errors.mjs";
5
-
6
- export const CAPTURED_OPERATION_TIMEOUT_MS = 4 * 60 * 1000;
7
-
8
- export function runCapturedOperationProcess({ file, args = [], cwd, env, timeoutMs = CAPTURED_OPERATION_TIMEOUT_MS }) {
9
- if (typeof file !== "string" || !file || !Array.isArray(args) || args.some((arg) => typeof arg !== "string")) throw oatsError("invalid-declaration", "captured operation executable and arguments must be text");
10
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > CAPTURED_OPERATION_TIMEOUT_MS) throw oatsError("invalid-declaration", `captured operation timeout must be 1-${CAPTURED_OPERATION_TIMEOUT_MS}ms`);
11
- return spawnSync(process.execPath, [file, ...args], {
12
- cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
13
- maxBuffer: 16 * 1024 * 1024, timeout: timeoutMs, killSignal: "SIGKILL",
14
- });
15
- }
@@ -1,29 +0,0 @@
1
- /** Kernel side of the Pi record-witness seam. Record support is deliberately
2
- * capability-checked before admission: older path-only readers cannot launch a
3
- * protected host. The record-library implementation is separately owned. */
4
- import { basename, dirname, join } from "node:path";
5
- import * as records from "../packages/record/lib/native-history.mjs";
6
- import { oatsError } from "./errors.mjs";
7
-
8
- export function capturedPiSessionDirectory(home) {
9
- const history = records.nativeHistoryPath(home);
10
- return join(dirname(history), `${basename(history)}.pi`);
11
- }
12
-
13
- export function requireCapturedPiRecordSupport() {
14
- if (records.CAPTURED_PI_RECORD_VERSION !== 2
15
- || ["inspectCapturedPiRoot", "prepareCapturedPiStart", "assertCapturedPiStart"].some(name => typeof records[name] !== "function")) {
16
- throw oatsError("E_PI_HOST_RECORD_UNAVAILABLE", "captured Pi requires complete versioned native-root discovery/read/capture guards; path-only record support cannot launch it");
17
- }
18
- return records;
19
- }
20
-
21
- export function inspectCapturedPiRoot(home, authority) {
22
- return requireCapturedPiRecordSupport().inspectCapturedPiRoot(home, authority);
23
- }
24
- export function prepareCapturedPiStart(home, authority) {
25
- return requireCapturedPiRecordSupport().prepareCapturedPiStart(home, authority);
26
- }
27
- export function assertCapturedPiStart(home, nativeRecordId, authority) {
28
- return requireCapturedPiRecordSupport().assertCapturedPiStart(home, nativeRecordId, authority);
29
- }