codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.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.
Files changed (191) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  7. package/contracts/v1/fixtures/operation-action.json +13 -0
  8. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  9. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  10. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  11. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  12. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  13. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  14. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  15. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  16. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  17. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  18. package/contracts/v1/fixtures/operation-event.json +18 -0
  19. package/contracts/v1/fixtures/operation-handle.json +10 -0
  20. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  21. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  22. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  23. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  24. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  25. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  26. package/contracts/v1/fixtures/operation-request.json +33 -0
  27. package/contracts/v1/fixtures/operation-state.json +144 -0
  28. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  29. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  30. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  31. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  32. package/contracts/v1/manifest.json +155 -1
  33. package/contracts/v1/parity-suite.json +96 -1
  34. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  35. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  36. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  37. package/contracts/v1/schemas/manifest.schema.json +57 -0
  38. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  39. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  40. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  41. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  42. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  43. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  44. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  45. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  46. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  47. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  48. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  49. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  50. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  51. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  52. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  53. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  54. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  55. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  56. package/contracts/v1/surface-drift-policy.json +50 -0
  57. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  58. package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
  59. package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
  60. package/dist/src/backend/client.d.ts +103 -2
  61. package/dist/src/backend/client.js +1436 -97
  62. package/dist/src/backend/compatibility.d.ts +12 -0
  63. package/dist/src/backend/compatibility.js +208 -0
  64. package/dist/src/backend/protocol.d.ts +84 -1
  65. package/dist/src/backend/protocol.js +31 -5
  66. package/dist/src/backend/runtime-identity.d.ts +10 -0
  67. package/dist/src/backend/runtime-identity.js +141 -0
  68. package/dist/src/backend/session.d.ts +10 -2
  69. package/dist/src/backend/session.js +478 -7
  70. package/dist/src/backend/stdio-server.d.ts +4 -1
  71. package/dist/src/backend/stdio-server.js +240 -28
  72. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  73. package/dist/src/browser/active-composer-file-input.js +33 -0
  74. package/dist/src/browser/attach.d.ts +4 -1
  75. package/dist/src/browser/attach.js +136 -28
  76. package/dist/src/browser/downloads.d.ts +2 -0
  77. package/dist/src/client.d.ts +38 -0
  78. package/dist/src/client.js +1222 -59
  79. package/dist/src/commands/configuration.js +13 -7
  80. package/dist/src/commands/doctor.d.ts +1 -1
  81. package/dist/src/commands/doctor.js +26 -3
  82. package/dist/src/commands/experience.js +35 -0
  83. package/dist/src/commands/files.js +4 -27
  84. package/dist/src/commands/modes.js +49 -17
  85. package/dist/src/commands/power-discovery.d.ts +130 -0
  86. package/dist/src/commands/power-discovery.js +627 -0
  87. package/dist/src/commands/project-sources.js +325 -31
  88. package/dist/src/commands/sequence.js +59 -32
  89. package/dist/src/commands/session.js +7 -2
  90. package/dist/src/commands/work.js +31 -0
  91. package/dist/src/errors.d.ts +9 -0
  92. package/dist/src/errors.js +22 -0
  93. package/dist/src/index.d.ts +6 -0
  94. package/dist/src/index.js +9 -0
  95. package/dist/src/operations/artifact-output.d.ts +69 -0
  96. package/dist/src/operations/artifact-output.js +1748 -0
  97. package/dist/src/operations/artifact-stream.d.ts +19 -0
  98. package/dist/src/operations/artifact-stream.js +35 -0
  99. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  100. package/dist/src/operations/artifact-transfer.js +1312 -0
  101. package/dist/src/operations/browser-adapter.d.ts +161 -0
  102. package/dist/src/operations/browser-adapter.js +2023 -0
  103. package/dist/src/operations/browser-observation.d.ts +110 -0
  104. package/dist/src/operations/browser-observation.js +1208 -0
  105. package/dist/src/operations/browser-target.d.ts +103 -0
  106. package/dist/src/operations/browser-target.js +489 -0
  107. package/dist/src/operations/canonical.d.ts +4 -0
  108. package/dist/src/operations/canonical.js +412 -0
  109. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  110. package/dist/src/operations/chatgpt-runtime.js +1150 -0
  111. package/dist/src/operations/client.d.ts +138 -0
  112. package/dist/src/operations/client.js +1143 -0
  113. package/dist/src/operations/collector.d.ts +195 -0
  114. package/dist/src/operations/collector.js +1039 -0
  115. package/dist/src/operations/control.d.ts +372 -0
  116. package/dist/src/operations/control.js +1498 -0
  117. package/dist/src/operations/file-identity.d.ts +33 -0
  118. package/dist/src/operations/file-identity.js +153 -0
  119. package/dist/src/operations/handle.d.ts +19 -0
  120. package/dist/src/operations/handle.js +727 -0
  121. package/dist/src/operations/index.d.ts +22 -0
  122. package/dist/src/operations/index.js +22 -0
  123. package/dist/src/operations/journal.d.ts +142 -0
  124. package/dist/src/operations/journal.js +1931 -0
  125. package/dist/src/operations/production-attachments.d.ts +116 -0
  126. package/dist/src/operations/production-attachments.js +1018 -0
  127. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  128. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  129. package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
  130. package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
  131. package/dist/src/operations/production-configuration.d.ts +42 -0
  132. package/dist/src/operations/production-configuration.js +1363 -0
  133. package/dist/src/operations/production-primitives.d.ts +53 -0
  134. package/dist/src/operations/production-primitives.js +1073 -0
  135. package/dist/src/operations/production-work-steer.d.ts +203 -0
  136. package/dist/src/operations/production-work-steer.js +1218 -0
  137. package/dist/src/operations/recovery.d.ts +80 -0
  138. package/dist/src/operations/recovery.js +174 -0
  139. package/dist/src/operations/runtime-adapter.d.ts +118 -0
  140. package/dist/src/operations/runtime-adapter.js +786 -0
  141. package/dist/src/operations/send-once.d.ts +224 -0
  142. package/dist/src/operations/send-once.js +1081 -0
  143. package/dist/src/operations/service.d.ts +281 -0
  144. package/dist/src/operations/service.js +2810 -0
  145. package/dist/src/operations/staging.d.ts +136 -0
  146. package/dist/src/operations/staging.js +630 -0
  147. package/dist/src/operations/state-machine.d.ts +29 -0
  148. package/dist/src/operations/state-machine.js +2047 -0
  149. package/dist/src/operations/submission.d.ts +423 -0
  150. package/dist/src/operations/submission.js +1676 -0
  151. package/dist/src/operations/turn-ownership.d.ts +202 -0
  152. package/dist/src/operations/turn-ownership.js +700 -0
  153. package/dist/src/operations/types.d.ts +454 -0
  154. package/dist/src/operations/types.js +20 -0
  155. package/dist/src/operations/wire-requests.d.ts +21 -0
  156. package/dist/src/operations/wire-requests.js +396 -0
  157. package/dist/src/operations/wire-results.d.ts +97 -0
  158. package/dist/src/operations/wire-results.js +818 -0
  159. package/dist/src/runner/responses.d.ts +3 -1
  160. package/dist/src/runner/responses.js +19 -1
  161. package/dist/src/runner/result.js +227 -55
  162. package/dist/src/runner/types.d.ts +12 -0
  163. package/dist/src/runtime/command-routing.d.ts +149 -0
  164. package/dist/src/runtime/command-routing.js +431 -0
  165. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  166. package/dist/src/runtime/coordinated-browser.js +315 -0
  167. package/dist/src/runtime/coordinated-page.d.ts +39 -0
  168. package/dist/src/runtime/coordinated-page.js +634 -0
  169. package/dist/src/runtime/operation-context.d.ts +142 -0
  170. package/dist/src/runtime/operation-context.js +410 -0
  171. package/dist/src/runtime/runtime-session.d.ts +95 -0
  172. package/dist/src/runtime/runtime-session.js +314 -0
  173. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  174. package/dist/src/runtime/tab-coordinator.js +1200 -0
  175. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  176. package/dist/src/runtime/value-boundaries.js +49 -0
  177. package/dist/src/safety/untrusted-output.js +2 -1
  178. package/dist/src/scripts/backend-server.js +4 -1
  179. package/dist/src/scripts/live-smoke/harness.js +79 -7
  180. package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
  181. package/dist/src/scripts/live-smoke/scenarios.js +110 -18
  182. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  183. package/dist/src/scripts/release-canary-module.js +17 -4
  184. package/dist/src/types.d.ts +38 -1
  185. package/package.json +1 -1
  186. package/references/2026-08-16-transactional-operations.md +459 -0
  187. package/references/agents-runner.md +34 -0
  188. package/references/backend-protocol.md +73 -0
  189. package/references/python-parity.md +72 -0
  190. package/references/responses-adapter.md +19 -0
  191. package/references/streaming.md +6 -0
@@ -0,0 +1,630 @@
1
+ /**
2
+ * Adapter-free coordinator for operation-owned set-to-value staging.
3
+ *
4
+ * The browser adapter closes over the requested configuration/tool/composer/
5
+ * Power value. The coordinator only receives its keyed digest and opaque
6
+ * evidence, so neither the durable journal port nor a callback request can
7
+ * accidentally contain a prompt, path, display name, or provider content.
8
+ *
9
+ * A staging action is deliberately different from Send or file handoff:
10
+ * staging is reversible and may be reconciled. It is nevertheless not a
11
+ * blind retry loop. The exact current value is read first, an intent is
12
+ * persisted, and there is at most one set-to-value callback for a given
13
+ * action ID. Every path after an intent uses observation only.
14
+ */
15
+ export const OPERATION_STAGING_SCHEMA_VERSION = "chatgpt.browser_control.operation_staging.v1";
16
+ export const OPERATION_STAGING_RECEIPT_SCHEMA_VERSION = "chatgpt.browser_control.operation_staging_receipt.v1";
17
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
18
+ const DIGEST_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/u;
19
+ const CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
20
+ const INSTANT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
21
+ const MAX_DEADLINE_AT = Date.UTC(2100, 0, 1);
22
+ const MAX_BLOCKER_CODE_BYTES = 128;
23
+ // These markers are deliberately outside the UUID/digest grammars. They are
24
+ // used only when an invalid request must still produce the public
25
+ // result-shaped blocker; malformed evidence must never become a valid-looking
26
+ // cryptographic identity.
27
+ const INVALID_OPERATION_ID = "invalid-operation";
28
+ const INVALID_ACTION_ID = "invalid-action";
29
+ const INVALID_DIGEST = "invalid-digest";
30
+ const INVALID_DESIRED_DIGEST = "invalid-desired-state-digest";
31
+ export class OperationStagingInputError extends Error {
32
+ code;
33
+ constructor(code, message) {
34
+ super(message);
35
+ this.code = code;
36
+ this.name = "OperationStagingInputError";
37
+ }
38
+ }
39
+ /**
40
+ * Run one set-to-value staging action.
41
+ *
42
+ * The function never retries `mutateOnce`. If the callback rejects after a
43
+ * provider-side mutation may have happened, `observe` is called exactly once
44
+ * and the resulting receipt is authoritative. A later invocation with the
45
+ * same action ID is controlled by `persistIntent`, which must return
46
+ * `existing_unsettled`/`existing_settled` rather than authorizing another
47
+ * mutation.
48
+ */
49
+ export async function runOperationStaging(request, ports, options = {}) {
50
+ let normalized;
51
+ try {
52
+ // Detect accessors without reading them. This keeps a hostile request
53
+ // from executing code while it is being converted into a redacted
54
+ // fail-closed result.
55
+ if (hasAccessorInPlainData(request) || hasAccessorInPlainData(options)) {
56
+ throw new OperationStagingInputError("port_protocol_violation", "Staging input contains accessor-backed identity data.");
57
+ }
58
+ normalized = normalizeInput(request, ports, options);
59
+ }
60
+ catch (error) {
61
+ const identity = safeIdentity(request);
62
+ return blockedResult(identity, errorCode(error, "port_protocol_violation"), false, "not_attempted");
63
+ }
64
+ const { identity, signal, deadlineAt } = normalized;
65
+ const callbackRequest = Object.freeze({ ...identity, signal, deadlineAt });
66
+ const initialCancellation = cancellationCode(normalized);
67
+ if (initialCancellation !== undefined) {
68
+ return blockedResult(identity, initialCancellation, false, "not_attempted");
69
+ }
70
+ let initial;
71
+ try {
72
+ initial = await readAndValidate(normalized.ports.readCurrent(callbackRequest), identity);
73
+ }
74
+ catch (error) {
75
+ return blockedResult(identity, errorCode(error, "port_protocol_violation"), false, "not_attempted");
76
+ }
77
+ if (initial.status === "unavailable" || initial.status === "uncertain") {
78
+ return blockedResult(identity, initial.blockerCode, true, "not_attempted", initial.evidenceDigest);
79
+ }
80
+ const preIntentCancellation = cancellationCode(normalized);
81
+ if (preIntentCancellation !== undefined) {
82
+ return blockedResult(identity, preIntentCancellation, false, "not_attempted", initial.evidenceDigest);
83
+ }
84
+ let intent;
85
+ try {
86
+ intent = await normalized.ports.persistIntent(Object.freeze({ identity }));
87
+ validateIntentResult(intent, identity);
88
+ }
89
+ catch (error) {
90
+ return blockedResult(identity, errorCode(error, "journal_unavailable"), true, "not_attempted");
91
+ }
92
+ if (intent.status === "existing_settled") {
93
+ return resultFromReceipt(identity, intent.receipt);
94
+ }
95
+ const intentAlreadyExisted = intent.status === "existing_unsettled";
96
+ if (intentAlreadyExisted) {
97
+ // A prior intent, even for a reversible action, is observation-only.
98
+ const observation = await observeSafely(normalized, callbackRequest);
99
+ return await settleObservation(normalized, observation, "not_attempted");
100
+ }
101
+ const afterIntentCancellation = cancellationCode(normalized);
102
+ if (afterIntentCancellation !== undefined) {
103
+ if (initial.status === "satisfied") {
104
+ // The only satisfying read predates the intent and may have drifted
105
+ // while it was being persisted. Do not turn stale evidence into a
106
+ // durable success when cancellation forbids the required fresh read.
107
+ return await settleKnownObservation(normalized, {
108
+ status: "uncertain",
109
+ desiredStateDigest: identity.desiredStateDigest,
110
+ blockerCode: afterIntentCancellation,
111
+ evidenceDigest: initial.evidenceDigest,
112
+ currentStateDigest: initial.currentStateDigest
113
+ }, "not_attempted", afterIntentCancellation);
114
+ }
115
+ if (initial.status !== "not_satisfied") {
116
+ return blockedResult(identity, "port_protocol_violation", true, "not_attempted", initial.evidenceDigest);
117
+ }
118
+ return await settleKnownObservation(normalized, {
119
+ status: "not_satisfied",
120
+ desiredStateDigest: identity.desiredStateDigest,
121
+ currentStateDigest: initial.currentStateDigest,
122
+ evidenceDigest: initial.evidenceDigest
123
+ }, "not_attempted", afterIntentCancellation);
124
+ }
125
+ // Reconcile after the durable intent and immediately before the one
126
+ // permitted mutation. This fresh read is required even when the pre-intent
127
+ // value looked satisfied: a human or concurrent operation may have changed
128
+ // it while the journal write was in flight.
129
+ const preMutationObservation = await observeSafely(normalized, callbackRequest);
130
+ if (preMutationObservation.status === "satisfied") {
131
+ return await settleObservation(normalized, preMutationObservation, "not_attempted");
132
+ }
133
+ if (preMutationObservation.status === "unavailable" || preMutationObservation.status === "uncertain") {
134
+ return await settleObservation(normalized, preMutationObservation, "not_attempted");
135
+ }
136
+ const preMutationCancellation = cancellationCode(normalized);
137
+ if (preMutationCancellation !== undefined) {
138
+ return await settleKnownObservation(normalized, preMutationObservation, "not_attempted", preMutationCancellation);
139
+ }
140
+ let mutationProtocolError = false;
141
+ try {
142
+ const result = await normalized.ports.mutateOnce(callbackRequest);
143
+ try {
144
+ validateMutationResult(result);
145
+ }
146
+ catch {
147
+ // The callback returned, but violated its closed result contract. The
148
+ // operation may still have acted, so observation below remains required.
149
+ mutationProtocolError = true;
150
+ }
151
+ }
152
+ catch {
153
+ // A rejected browser bridge call may have acted before throwing. Do not
154
+ // retry. The read-only reconciliation below is the sole next action.
155
+ // A provider/bridge rejection is not itself a protocol violation: an
156
+ // exact postcondition can still prove that the set-to-value action won.
157
+ }
158
+ const observation = await observeSafely(normalized, callbackRequest);
159
+ if (mutationProtocolError && (observation.status === "unavailable" || observation.status === "uncertain")) {
160
+ return await settleKnownObservation(normalized, observation, "attempted", "port_protocol_violation");
161
+ }
162
+ if (mutationProtocolError && observation.status === "not_satisfied") {
163
+ return await settleKnownObservation(normalized, observation, "attempted", "port_protocol_violation");
164
+ }
165
+ if (mutationProtocolError && observation.status === "satisfied") {
166
+ // A malformed return value is not enough to discard exact postcondition
167
+ // evidence, but keep the protocol violation visible in the result path.
168
+ return await settleKnownObservation(normalized, observation, "attempted", "port_protocol_violation");
169
+ }
170
+ return await settleObservation(normalized, observation, "attempted");
171
+ }
172
+ async function settleObservation(normalized, observation, mutation) {
173
+ if (observation.status === "unavailable" || observation.status === "uncertain") {
174
+ return await settleKnownObservation(normalized, observation, mutation, observation.blockerCode);
175
+ }
176
+ return await settleKnownObservation(normalized, observation, mutation);
177
+ }
178
+ async function settleKnownObservation(normalized, observation, mutation, forcedBlockerCode) {
179
+ const { identity } = normalized;
180
+ let observedAt;
181
+ try {
182
+ observedAt = timestamp(normalized.now);
183
+ }
184
+ catch {
185
+ return {
186
+ ...resultBase(identity),
187
+ kind: "uncertain",
188
+ blocker: {
189
+ code: "port_protocol_violation",
190
+ observationRequired: true,
191
+ mutation,
192
+ ...(observation.evidenceDigest === undefined ? {} : { evidenceDigest: observation.evidenceDigest })
193
+ }
194
+ };
195
+ }
196
+ const outcome = observation.status === "satisfied"
197
+ ? "satisfied"
198
+ : observation.status === "not_satisfied"
199
+ ? "not_satisfied"
200
+ : "uncertain";
201
+ const blockerCode = forcedBlockerCode ?? (observation.status === "unavailable" || observation.status === "uncertain"
202
+ ? observation.blockerCode
203
+ : outcome === "not_satisfied"
204
+ ? "staging_not_satisfied"
205
+ : undefined);
206
+ const receipt = Object.freeze({
207
+ schemaVersion: OPERATION_STAGING_RECEIPT_SCHEMA_VERSION,
208
+ operationId: identity.operationId,
209
+ requestDigest: identity.requestDigest,
210
+ targetBindingDigest: identity.targetBindingDigest,
211
+ actionId: identity.actionId,
212
+ kind: identity.kind,
213
+ desiredStateDigest: identity.desiredStateDigest,
214
+ outcome,
215
+ mutation,
216
+ ...(observation.currentStateDigest === undefined ? {} : { currentStateDigest: observation.currentStateDigest }),
217
+ ...(observation.evidenceDigest === undefined ? {} : { evidenceDigest: observation.evidenceDigest }),
218
+ ...(blockerCode === undefined ? {} : { blockerCode }),
219
+ observedAt
220
+ });
221
+ try {
222
+ validateReceipt(receipt, identity);
223
+ await normalized.ports.persistReceipt(Object.freeze({ receipt }));
224
+ }
225
+ catch {
226
+ return {
227
+ ...resultBase(identity),
228
+ kind: "uncertain",
229
+ blocker: {
230
+ code: "journal_unavailable",
231
+ observationRequired: true,
232
+ mutation,
233
+ ...(observation.evidenceDigest === undefined ? {} : { evidenceDigest: observation.evidenceDigest })
234
+ }
235
+ };
236
+ }
237
+ if (outcome === "satisfied")
238
+ return { ...resultBase(identity), kind: "completed", receipt };
239
+ if (outcome === "uncertain") {
240
+ return {
241
+ ...resultBase(identity),
242
+ kind: "uncertain",
243
+ blocker: {
244
+ code: blockerCode ?? "staging_uncertain",
245
+ observationRequired: true,
246
+ mutation,
247
+ ...(observation.evidenceDigest === undefined ? {} : { evidenceDigest: observation.evidenceDigest })
248
+ },
249
+ receipt
250
+ };
251
+ }
252
+ return {
253
+ ...resultBase(identity),
254
+ kind: "blocked",
255
+ blocker: {
256
+ code: blockerCode ?? "staging_not_satisfied",
257
+ observationRequired: true,
258
+ mutation,
259
+ ...(observation.evidenceDigest === undefined ? {} : { evidenceDigest: observation.evidenceDigest })
260
+ },
261
+ receipt
262
+ };
263
+ }
264
+ async function observeSafely(normalized, request) {
265
+ try {
266
+ return await readAndValidate(normalized.ports.observe(request), normalized.identity);
267
+ }
268
+ catch (error) {
269
+ return {
270
+ status: "uncertain",
271
+ desiredStateDigest: normalized.identity.desiredStateDigest,
272
+ blockerCode: errorCode(error, "port_protocol_violation")
273
+ };
274
+ }
275
+ }
276
+ function resultFromReceipt(identity, receipt) {
277
+ if (receipt.outcome === "satisfied")
278
+ return { ...resultBase(identity), kind: "completed", receipt };
279
+ const blocker = {
280
+ code: receipt.blockerCode ?? (receipt.outcome === "uncertain" ? "staging_uncertain" : "staging_not_satisfied"),
281
+ observationRequired: true,
282
+ mutation: receipt.mutation,
283
+ ...(receipt.evidenceDigest === undefined ? {} : { evidenceDigest: receipt.evidenceDigest })
284
+ };
285
+ return {
286
+ ...resultBase(identity),
287
+ kind: receipt.outcome === "uncertain" ? "uncertain" : "blocked",
288
+ blocker,
289
+ receipt
290
+ };
291
+ }
292
+ function normalizeInput(request, ports, options) {
293
+ const identity = normalizeIdentity(request);
294
+ if (!ports || typeof ports !== "object" || Array.isArray(ports)) {
295
+ throw new OperationStagingInputError("port_protocol_violation", "Staging ports must be an object.");
296
+ }
297
+ for (const key of ["readCurrent", "persistIntent", "mutateOnce", "observe", "persistReceipt"]) {
298
+ if (typeof ports[key] !== "function") {
299
+ throw new OperationStagingInputError("port_protocol_violation", `Staging port ${key} is missing.`);
300
+ }
301
+ }
302
+ if (options.signal !== undefined && !isAbortSignal(options.signal)) {
303
+ throw new OperationStagingInputError("port_protocol_violation", "Cancellation signal is invalid.");
304
+ }
305
+ const signal = options.signal ?? new AbortController().signal;
306
+ const now = options.now ?? Date.now;
307
+ if (typeof now !== "function") {
308
+ throw new OperationStagingInputError("port_protocol_violation", "Clock callback is invalid.");
309
+ }
310
+ const nowValue = now();
311
+ if (!Number.isSafeInteger(nowValue) || nowValue < 0 || nowValue > MAX_DEADLINE_AT) {
312
+ throw new OperationStagingInputError("port_protocol_violation", "Clock value is invalid.");
313
+ }
314
+ const deadlineAt = options.deadlineAt ?? MAX_DEADLINE_AT;
315
+ if (!Number.isSafeInteger(deadlineAt) || deadlineAt < 0 || deadlineAt > MAX_DEADLINE_AT) {
316
+ throw new OperationStagingInputError("port_protocol_violation", "Deadline is invalid.");
317
+ }
318
+ return { identity, ports, signal, deadlineAt, now };
319
+ }
320
+ function normalizeIdentity(request) {
321
+ if (!request || typeof request !== "object" || Array.isArray(request)) {
322
+ throw new OperationStagingInputError("port_protocol_violation", "Staging request must be an object.");
323
+ }
324
+ const expected = [
325
+ "operationId",
326
+ "requestDigest",
327
+ "targetBindingDigest",
328
+ "actionId",
329
+ "kind",
330
+ "desiredStateDigest"
331
+ ];
332
+ if (Object.keys(request).length !== expected.length || expected.some(key => !Object.hasOwn(request, key))) {
333
+ throw new OperationStagingInputError("port_protocol_violation", "Staging request shape is invalid.");
334
+ }
335
+ const value = request;
336
+ for (const key of ["operationId", "actionId"]) {
337
+ if (typeof value[key] !== "string" || !UUID_PATTERN.test(value[key])) {
338
+ throw new OperationStagingInputError("operation_state_corrupt", `${key} is not a canonical UUID.`);
339
+ }
340
+ }
341
+ for (const key of ["requestDigest", "targetBindingDigest", "desiredStateDigest"]) {
342
+ if (typeof value[key] !== "string" || !DIGEST_PATTERN.test(value[key])) {
343
+ throw new OperationStagingInputError("operation_state_corrupt", `${key} is not a canonical digest.`);
344
+ }
345
+ }
346
+ const kinds = [
347
+ "configuration_set",
348
+ "tool_set",
349
+ "composer_set",
350
+ "power_select"
351
+ ];
352
+ if (!kinds.includes(value.kind)) {
353
+ throw new OperationStagingInputError("operation_state_corrupt", "Staging kind is unsupported.");
354
+ }
355
+ return Object.freeze({
356
+ operationId: value.operationId,
357
+ requestDigest: value.requestDigest,
358
+ targetBindingDigest: value.targetBindingDigest,
359
+ actionId: value.actionId,
360
+ kind: value.kind,
361
+ desiredStateDigest: value.desiredStateDigest
362
+ });
363
+ }
364
+ function readAndValidate(promise, identity) {
365
+ return promise.then(value => {
366
+ validateObservation(value, identity);
367
+ return value;
368
+ });
369
+ }
370
+ function validateObservation(value, identity) {
371
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
372
+ throw new OperationStagingInputError("port_protocol_violation", "Staging observation is not an object.");
373
+ }
374
+ const record = value;
375
+ if (record.desiredStateDigest !== identity.desiredStateDigest || typeof record.desiredStateDigest !== "string") {
376
+ throw new OperationStagingInputError("target_binding_mismatch", "Staging observation is for a different desired value.");
377
+ }
378
+ if (record.status === "satisfied" || record.status === "not_satisfied") {
379
+ assertExactKeys(record, ["status", "desiredStateDigest", "currentStateDigest", "evidenceDigest"]);
380
+ assertDigest(record.currentStateDigest, "currentStateDigest");
381
+ assertDigest(record.evidenceDigest, "evidenceDigest");
382
+ return;
383
+ }
384
+ if (record.status === "unavailable" || record.status === "uncertain") {
385
+ assertExactKeys(record, ["status", "desiredStateDigest", "blockerCode", "evidenceDigest", "currentStateDigest"]);
386
+ assertCode(record.blockerCode, "blockerCode");
387
+ if (record.evidenceDigest !== undefined)
388
+ assertDigest(record.evidenceDigest, "evidenceDigest");
389
+ if (record.currentStateDigest !== undefined)
390
+ assertDigest(record.currentStateDigest, "currentStateDigest");
391
+ return;
392
+ }
393
+ throw new OperationStagingInputError("port_protocol_violation", "Staging observation status is invalid.");
394
+ }
395
+ function validateMutationResult(value) {
396
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
397
+ throw new OperationStagingInputError("port_protocol_violation", "Mutation callback result is invalid.");
398
+ }
399
+ const record = value;
400
+ assertExactKeys(record, ["status"]);
401
+ if (record.status !== "started") {
402
+ throw new OperationStagingInputError("port_protocol_violation", "Mutation callback did not report started.");
403
+ }
404
+ }
405
+ function validateIntentResult(value, identity) {
406
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
407
+ throw new OperationStagingInputError("port_protocol_violation", "Intent persistence result is invalid.");
408
+ }
409
+ const record = value;
410
+ if (record.status === "created" || record.status === "existing_unsettled") {
411
+ assertExactKeys(record, ["status"]);
412
+ return;
413
+ }
414
+ if (record.status === "existing_settled") {
415
+ assertExactKeys(record, ["status", "receipt"]);
416
+ validateReceipt(record.receipt, identity);
417
+ return;
418
+ }
419
+ throw new OperationStagingInputError("port_protocol_violation", "Intent persistence status is invalid.");
420
+ }
421
+ function validateReceipt(value, identity) {
422
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
423
+ throw new OperationStagingInputError("operation_state_corrupt", "Staging receipt is invalid.");
424
+ }
425
+ const record = value;
426
+ assertExactKeys(record, [
427
+ "schemaVersion",
428
+ "operationId",
429
+ "requestDigest",
430
+ "targetBindingDigest",
431
+ "actionId",
432
+ "kind",
433
+ "desiredStateDigest",
434
+ "outcome",
435
+ "mutation",
436
+ "currentStateDigest",
437
+ "evidenceDigest",
438
+ "blockerCode",
439
+ "observedAt"
440
+ ]);
441
+ if (record.schemaVersion !== OPERATION_STAGING_RECEIPT_SCHEMA_VERSION
442
+ || record.operationId !== identity.operationId
443
+ || record.requestDigest !== identity.requestDigest
444
+ || record.targetBindingDigest !== identity.targetBindingDigest
445
+ || record.actionId !== identity.actionId
446
+ || record.kind !== identity.kind
447
+ || record.desiredStateDigest !== identity.desiredStateDigest) {
448
+ throw new OperationStagingInputError("operation_state_corrupt", "Staging receipt identity does not match the request.");
449
+ }
450
+ if (record.outcome !== "satisfied" && record.outcome !== "not_satisfied" && record.outcome !== "uncertain") {
451
+ throw new OperationStagingInputError("operation_state_corrupt", "Staging receipt outcome is invalid.");
452
+ }
453
+ if (record.mutation !== "not_attempted" && record.mutation !== "attempted") {
454
+ throw new OperationStagingInputError("operation_state_corrupt", "Staging receipt mutation state is invalid.");
455
+ }
456
+ if (record.currentStateDigest !== undefined)
457
+ assertDigest(record.currentStateDigest, "receipt.currentStateDigest");
458
+ if (record.evidenceDigest !== undefined)
459
+ assertDigest(record.evidenceDigest, "receipt.evidenceDigest");
460
+ if (record.outcome !== "uncertain" && record.currentStateDigest === undefined) {
461
+ throw new OperationStagingInputError("operation_state_corrupt", "Settled staging receipt requires current-state evidence.");
462
+ }
463
+ if (record.outcome === "satisfied" && record.evidenceDigest === undefined) {
464
+ throw new OperationStagingInputError("operation_state_corrupt", "Satisfied staging receipt requires evidence.");
465
+ }
466
+ if (record.blockerCode !== undefined)
467
+ assertCode(record.blockerCode, "receipt.blockerCode");
468
+ assertInstant(record.observedAt, "receipt.observedAt");
469
+ }
470
+ function assertExactKeys(record, optionalKeys) {
471
+ const allowed = new Set(optionalKeys);
472
+ for (const key of Object.keys(record)) {
473
+ if (!allowed.has(key))
474
+ throw new OperationStagingInputError("port_protocol_violation", "Callback returned an unexpected field.");
475
+ }
476
+ // The callback objects intentionally use undefined for optional fields only
477
+ // when JavaScript callers constructed them that way; omit such fields in
478
+ // ordinary JSON-facing adapters. Required keys are checked separately by
479
+ // each validator.
480
+ }
481
+ function assertDigest(value, label) {
482
+ if (typeof value !== "string" || !DIGEST_PATTERN.test(value)) {
483
+ throw new OperationStagingInputError("port_protocol_violation", `${label} is not a canonical digest.`);
484
+ }
485
+ }
486
+ function assertCode(value, label) {
487
+ if (typeof value !== "string"
488
+ || Buffer.byteLength(value, "utf8") > MAX_BLOCKER_CODE_BYTES
489
+ || !CODE_PATTERN.test(value)) {
490
+ throw new OperationStagingInputError("port_protocol_violation", `${label} is not a bounded blocker code.`);
491
+ }
492
+ }
493
+ function assertInstant(value, label) {
494
+ const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
495
+ if (typeof value !== "string"
496
+ || !INSTANT_PATTERN.test(value)
497
+ || !Number.isFinite(parsed)
498
+ || new Date(parsed).toISOString() !== value) {
499
+ throw new OperationStagingInputError("operation_state_corrupt", `${label} is not an ISO instant.`);
500
+ }
501
+ }
502
+ function cancellationCode(normalized) {
503
+ if (normalized.signal.aborted)
504
+ return "operation_cancelled";
505
+ if (normalized.now() >= normalized.deadlineAt)
506
+ return "operation_timeout";
507
+ return undefined;
508
+ }
509
+ function timestamp(now) {
510
+ const value = now();
511
+ if (!Number.isSafeInteger(value) || value < 0 || value > MAX_DEADLINE_AT) {
512
+ throw new OperationStagingInputError("port_protocol_violation", "Clock value is invalid.");
513
+ }
514
+ return new Date(value).toISOString();
515
+ }
516
+ function isAbortSignal(value) {
517
+ return !!value
518
+ && typeof value === "object"
519
+ && typeof value.aborted === "boolean"
520
+ && typeof value.addEventListener === "function";
521
+ }
522
+ function errorCode(error, fallback) {
523
+ if (error instanceof OperationStagingInputError && CODE_PATTERN.test(error.code))
524
+ return error.code;
525
+ if (error && typeof error === "object" && "code" in error) {
526
+ const code = error.code;
527
+ if (typeof code === "string" && CODE_PATTERN.test(code))
528
+ return code;
529
+ }
530
+ return fallback;
531
+ }
532
+ function safeIdentity(request) {
533
+ const value = safeDataRecord(request);
534
+ const operationId = readOwnData(value, "operationId");
535
+ const requestDigest = readOwnData(value, "requestDigest");
536
+ const targetBindingDigest = readOwnData(value, "targetBindingDigest");
537
+ const actionId = readOwnData(value, "actionId");
538
+ const kind = readOwnData(value, "kind");
539
+ const desiredStateDigest = readOwnData(value, "desiredStateDigest");
540
+ return {
541
+ operationId: isUuid(operationId) ? operationId : INVALID_OPERATION_ID,
542
+ requestDigest: isDigest(requestDigest) ? requestDigest : INVALID_DIGEST,
543
+ targetBindingDigest: isDigest(targetBindingDigest) ? targetBindingDigest : INVALID_DIGEST,
544
+ actionId: isUuid(actionId) ? actionId : INVALID_ACTION_ID,
545
+ kind: isStagingKind(kind) ? kind : "configuration_set",
546
+ desiredStateDigest: isDigest(desiredStateDigest) ? desiredStateDigest : INVALID_DESIRED_DIGEST
547
+ };
548
+ }
549
+ function isStagingKind(value) {
550
+ return value === "configuration_set" || value === "tool_set" || value === "composer_set" || value === "power_select";
551
+ }
552
+ function isUuid(value) {
553
+ return typeof value === "string" && UUID_PATTERN.test(value);
554
+ }
555
+ function isDigest(value) {
556
+ return typeof value === "string" && DIGEST_PATTERN.test(value);
557
+ }
558
+ function readOwnData(value, key) {
559
+ if (!isRecord(value))
560
+ return undefined;
561
+ try {
562
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
563
+ return descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined;
564
+ }
565
+ catch {
566
+ return undefined;
567
+ }
568
+ }
569
+ function safeDataRecord(value) {
570
+ return isRecord(value) ? value : undefined;
571
+ }
572
+ function hasAccessorInPlainData(value, seen = new Set(), depth = 0) {
573
+ if (value === null || typeof value !== "object")
574
+ return false;
575
+ let prototype;
576
+ try {
577
+ prototype = Object.getPrototypeOf(value);
578
+ }
579
+ catch {
580
+ return true;
581
+ }
582
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null)
583
+ return false;
584
+ if (seen.has(value))
585
+ return false;
586
+ if (depth > 32)
587
+ return true;
588
+ seen.add(value);
589
+ let descriptors;
590
+ try {
591
+ descriptors = Object.getOwnPropertyDescriptors(value);
592
+ }
593
+ catch {
594
+ return true;
595
+ }
596
+ for (const descriptor of Object.values(descriptors)) {
597
+ if (!("value" in descriptor))
598
+ return true;
599
+ if (hasAccessorInPlainData(descriptor.value, seen, depth + 1))
600
+ return true;
601
+ }
602
+ return false;
603
+ }
604
+ function isRecord(value) {
605
+ if (value === null || typeof value !== "object" || Array.isArray(value))
606
+ return false;
607
+ try {
608
+ const prototype = Object.getPrototypeOf(value);
609
+ return prototype === Object.prototype || prototype === null;
610
+ }
611
+ catch {
612
+ return false;
613
+ }
614
+ }
615
+ function blockedResult(identity, code, observationRequired, mutation, evidenceDigest) {
616
+ return {
617
+ ...resultBase(identity),
618
+ kind: "blocked",
619
+ blocker: {
620
+ code,
621
+ observationRequired,
622
+ mutation,
623
+ ...(evidenceDigest === undefined ? {} : { evidenceDigest })
624
+ }
625
+ };
626
+ }
627
+ function resultBase(identity) {
628
+ const { kind: stagingKind, ...base } = identity;
629
+ return { ...base, stagingKind };
630
+ }
@@ -0,0 +1,29 @@
1
+ import { type MutationBoundary, type OperationActionKind, type OperationCapturePolicyV1, type OperationDurableCapturePolicyV1, type OperationEventV1, type OperationOwnershipBaselineV1, type OperationRepeatPolicy, type OperationStateV1 } from "./types.js";
2
+ /**
3
+ * Copy only the closed capture contract into durable state. In particular,
4
+ * the request-local outputDirectory is intentionally not read or returned.
5
+ * Missing capture options preserve the historical collect defaults while the
6
+ * response format receives its explicit Markdown default.
7
+ */
8
+ export declare function durableCapturePolicyFromRequest(capture: OperationCapturePolicyV1 | undefined): OperationDurableCapturePolicyV1;
9
+ export declare class OperationStateError extends Error {
10
+ readonly code: string;
11
+ constructor(code: string, message: string);
12
+ }
13
+ export declare function assertOperationId(value: string, label?: string): void;
14
+ export declare function reduceOperationEvents(events: readonly OperationEventV1[]): OperationStateV1;
15
+ export declare function applyOperationEvent(state: OperationStateV1 | undefined, event: OperationEventV1, revision: number): OperationStateV1;
16
+ /**
17
+ * Rejects unknown or request-only fields before an event can enter the durable
18
+ * journal. TypeScript's structural types do not protect JavaScript callers at
19
+ * runtime, so the privacy boundary must be closed explicitly.
20
+ */
21
+ export declare function assertOperationEventShape(value: unknown): asserts value is OperationEventV1;
22
+ /** Validates the closed durable-state shape used by authenticated compaction. */
23
+ export declare function assertOperationStateShape(value: unknown): asserts value is OperationStateV1;
24
+ export declare function requiredRepeatPolicy(kind: OperationActionKind): OperationRepeatPolicy;
25
+ export declare function boundaryForAction(kind: OperationActionKind): MutationBoundary | undefined;
26
+ /** Closed-shape validation for the redacted pre-Send baseline. */
27
+ export declare function assertOwnershipBaselineShape(value: unknown): asserts value is OperationOwnershipBaselineV1;
28
+ /** Validate the closed, path-free policy carried by creation/state records. */
29
+ export declare function assertDurableCapturePolicyShape(value: unknown): asserts value is OperationDurableCapturePolicyV1;