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.
- package/CHANGELOG.md +15 -0
- package/README.md +27 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +155 -1
- package/contracts/v1/parity-suite.json +96 -1
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
- package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/downloads.d.ts +2 -0
- package/dist/src/client.d.ts +38 -0
- package/dist/src/client.js +1222 -59
- package/dist/src/commands/configuration.js +13 -7
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +26 -3
- package/dist/src/commands/experience.js +35 -0
- package/dist/src/commands/files.js +4 -27
- package/dist/src/commands/modes.js +49 -17
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +627 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +7 -2
- package/dist/src/commands/work.js +31 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +161 -0
- package/dist/src/operations/browser-adapter.js +2023 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1208 -0
- package/dist/src/operations/browser-target.d.ts +103 -0
- package/dist/src/operations/browser-target.js +489 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1150 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1143 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +22 -0
- package/dist/src/operations/index.js +22 -0
- package/dist/src/operations/journal.d.ts +142 -0
- package/dist/src/operations/journal.js +1931 -0
- package/dist/src/operations/production-attachments.d.ts +116 -0
- package/dist/src/operations/production-attachments.js +1018 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1363 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +1073 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +118 -0
- package/dist/src/operations/runtime-adapter.js +786 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +281 -0
- package/dist/src/operations/service.js +2810 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +39 -0
- package/dist/src/runtime/coordinated-page.js +634 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
- package/dist/src/scripts/live-smoke/scenarios.js +110 -18
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +1 -1
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +73 -0
- package/references/python-parity.md +72 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
import { OPERATION_OWNERSHIP_BASELINE_SCHEMA_VERSION } from "./types.js";
|
|
2
|
+
import { assertOwnershipBaselineShape } from "./state-machine.js";
|
|
3
|
+
import { TURN_OWNERSHIP_SCHEMA_VERSION } from "./turn-ownership.js";
|
|
4
|
+
import { canonicalJson } from "./canonical.js";
|
|
5
|
+
/**
|
|
6
|
+
* Work-steer is a deliberately small browser capability. Durable action
|
|
7
|
+
* intent and the complete prepared baseline belong to the caller. Keeping
|
|
8
|
+
* those writes outside this module means a tab actor is never held across a
|
|
9
|
+
* journal transaction.
|
|
10
|
+
*/
|
|
11
|
+
export const PRODUCTION_WORK_STEER_SCHEMA_VERSION = "chatgpt.browser_control.production_work_steer.v1";
|
|
12
|
+
const DIGEST_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/u;
|
|
13
|
+
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;
|
|
14
|
+
const ID_PATTERN = /^[A-Za-z0-9._:-]{1,512}$/u;
|
|
15
|
+
const CAPABILITY_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,127}$/u;
|
|
16
|
+
const MAX_PROMPT_BYTES = 8 * 1024 * 1024;
|
|
17
|
+
const MAX_TIMEOUT_MS = 30_000;
|
|
18
|
+
const MAX_DEADLINE_AT = Date.UTC(2100, 0, 1);
|
|
19
|
+
const MAX_GRAPH_DEPTH = 32;
|
|
20
|
+
const MAX_GRAPH_NODES = 8_192;
|
|
21
|
+
const BASELINE_EPOCH = "1970-01-01T00:00:00.000Z";
|
|
22
|
+
export class ProductionWorkSteerPrimitiveError extends Error {
|
|
23
|
+
code;
|
|
24
|
+
constructor(code) {
|
|
25
|
+
// Keep all caller data, URLs, provider messages, and prompt text out of
|
|
26
|
+
// error strings. Integrations use the opaque code for diagnostics.
|
|
27
|
+
super("The Work steer production primitive could not validate its request.");
|
|
28
|
+
this.name = "ProductionWorkSteerPrimitiveError";
|
|
29
|
+
this.code = code;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Construct one request-scoped primitive. The public phase boundary is:
|
|
34
|
+
*
|
|
35
|
+
* prepare (read) -> caller persists intent + prepared.baseline
|
|
36
|
+
* -> executePrepared (one-shot mutation) -> verify/recover (read only).
|
|
37
|
+
*/
|
|
38
|
+
export function createProductionWorkSteerPrimitive(options) {
|
|
39
|
+
const captured = captureOptions(options);
|
|
40
|
+
const clock = makeClock(captured.now);
|
|
41
|
+
let executionConsumed = false;
|
|
42
|
+
const base = (phase, prepared) => Object.freeze({
|
|
43
|
+
schemaVersion: PRODUCTION_WORK_STEER_SCHEMA_VERSION,
|
|
44
|
+
operationId: captured.operationId,
|
|
45
|
+
parentRequestDigest: captured.parentRequestDigest,
|
|
46
|
+
targetBindingDigest: captured.targetBindingDigest,
|
|
47
|
+
controlActionId: captured.controlActionId,
|
|
48
|
+
action: "work_steer",
|
|
49
|
+
phase,
|
|
50
|
+
expectedAssistantTurnId: captured.expectedAssistantTurnId,
|
|
51
|
+
...(prepared === undefined ? {} : {
|
|
52
|
+
assistantBranchId: prepared.assistantBranchId,
|
|
53
|
+
assistantParentTurnId: prepared.assistantParentTurnId,
|
|
54
|
+
baselineSnapshotDigest: prepared.baselineSnapshotDigest,
|
|
55
|
+
preparedDigest: prepared.preparedDigest
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
const prepare = async (request) => {
|
|
59
|
+
let call;
|
|
60
|
+
try {
|
|
61
|
+
call = normalizeCall(request, captured, clock, "prepare");
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return blocked(base("prepare"), normalizeInputError(error), false, "none");
|
|
65
|
+
}
|
|
66
|
+
const initialCancellation = cancellationCode(call, clock);
|
|
67
|
+
if (initialCancellation !== undefined)
|
|
68
|
+
return blocked(base("prepare"), initialCancellation, false, "none");
|
|
69
|
+
const observed = await observeBounded(call, captured, clock, "prepare");
|
|
70
|
+
if (observed.kind === "cancelled")
|
|
71
|
+
return blocked(base("prepare"), observed.code, false, "none");
|
|
72
|
+
if (observed.kind === "error")
|
|
73
|
+
return blocked(base("prepare"), "target_evidence_unavailable", true, "none");
|
|
74
|
+
const prepared = makePrepared(observed.value.snapshot, captured);
|
|
75
|
+
if (prepared.kind === "failure") {
|
|
76
|
+
return blocked(base("prepare"), prepared.blockerCode, prepared.observationRequired, "none", prepared.evidenceDigest);
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
...base("prepare", prepared.value),
|
|
80
|
+
status: "prepared",
|
|
81
|
+
observationRequired: false,
|
|
82
|
+
mutationBoundary: "none",
|
|
83
|
+
prepared: prepared.value
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const executePrepared = async (request) => {
|
|
87
|
+
let call;
|
|
88
|
+
let prepared;
|
|
89
|
+
try {
|
|
90
|
+
call = normalizeCall(request, captured, clock, "execute_prepared");
|
|
91
|
+
prepared = validatePrepared(request.prepared, captured);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return blocked(base("execute_prepared"), normalizeInputError(error), false, "none");
|
|
95
|
+
}
|
|
96
|
+
if (executionConsumed)
|
|
97
|
+
return uncertain(base("execute_prepared", prepared), "send_control_unavailable", "caller");
|
|
98
|
+
executionConsumed = true;
|
|
99
|
+
const initialCancellation = cancellationCode(call, clock);
|
|
100
|
+
if (initialCancellation !== undefined)
|
|
101
|
+
return blocked(base("execute_prepared", prepared), initialCancellation, false, "none");
|
|
102
|
+
const finalRead = await observeBounded(call, captured, clock, "final_recheck", prepared);
|
|
103
|
+
if (finalRead.kind === "cancelled")
|
|
104
|
+
return blocked(base("execute_prepared", prepared), finalRead.code, false, "none");
|
|
105
|
+
if (finalRead.kind === "error")
|
|
106
|
+
return blocked(base("execute_prepared", prepared), "target_evidence_unavailable", true, "none");
|
|
107
|
+
const finalBaseline = makeBaseline(finalRead.value.snapshot, captured);
|
|
108
|
+
if (finalBaseline.kind === "failure") {
|
|
109
|
+
return blocked(base("execute_prepared", prepared), finalBaseline.blockerCode, finalBaseline.observationRequired, "none", finalBaseline.evidenceDigest);
|
|
110
|
+
}
|
|
111
|
+
const parentFailure = validateGeneratingParent(finalRead.value.snapshot, captured, prepared);
|
|
112
|
+
if (parentFailure !== undefined) {
|
|
113
|
+
return blocked(base("execute_prepared", prepared), parentFailure.blockerCode, parentFailure.observationRequired, "none", parentFailure.evidenceDigest);
|
|
114
|
+
}
|
|
115
|
+
if (canonicalJson(finalBaseline.value) !== canonicalJson(prepared.baseline)) {
|
|
116
|
+
return blocked(base("execute_prepared", prepared), "turn_ownership_ambiguous", true, "none", safeDigest(captured, "work-steer-final-baseline", {
|
|
117
|
+
baselineSnapshotDigest: prepared.baselineSnapshotDigest,
|
|
118
|
+
observedSnapshotDigest: finalBaseline.value.snapshotDigest
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
const resolverRequest = resolverRequestFor(call, captured, prepared);
|
|
122
|
+
const composer = await resolveComposer(resolverRequest, call, captured, clock);
|
|
123
|
+
if (composer.kind === "cancelled")
|
|
124
|
+
return blocked(base("execute_prepared", prepared), composer.code, false, "none");
|
|
125
|
+
if (composer.kind === "error" || composer.value === undefined)
|
|
126
|
+
return blocked(base("execute_prepared", prepared), "send_control_unavailable", false, "none");
|
|
127
|
+
const composerValidation = await validateComposer(composer.value, call, clock);
|
|
128
|
+
if (composerValidation !== undefined)
|
|
129
|
+
return blocked(base("execute_prepared", prepared), composerValidation, false, "none");
|
|
130
|
+
const send = await resolveSendControl(resolverRequest, call, captured, clock);
|
|
131
|
+
if (send.kind === "cancelled")
|
|
132
|
+
return blocked(base("execute_prepared", prepared), send.code, false, "none");
|
|
133
|
+
if (send.kind === "error" || send.value === undefined)
|
|
134
|
+
return blocked(base("execute_prepared", prepared), "send_control_unavailable", false, "none");
|
|
135
|
+
const sendValidation = await validateSend(send.value, call, clock, false);
|
|
136
|
+
if (sendValidation !== undefined)
|
|
137
|
+
return blocked(base("execute_prepared", prepared), sendValidation, false, "none");
|
|
138
|
+
const beforeFillCancellation = cancellationCode(call, clock);
|
|
139
|
+
if (beforeFillCancellation !== undefined)
|
|
140
|
+
return blocked(base("execute_prepared", prepared), beforeFillCancellation, false, "none");
|
|
141
|
+
// Resolver callbacks are not authority to mutate later. Recheck the exact
|
|
142
|
+
// composer immediately before fill so a stale locator/candidate set cannot
|
|
143
|
+
// turn the one-shot action into a best-effort write.
|
|
144
|
+
const finalComposerValidation = await validateComposer(composer.value, call, clock);
|
|
145
|
+
if (finalComposerValidation !== undefined)
|
|
146
|
+
return blocked(base("execute_prepared", prepared), finalComposerValidation, false, "none");
|
|
147
|
+
// Mutations are intentionally not bounded with Promise.race. A provider
|
|
148
|
+
// may accept timeout options, but this primitive always awaits settlement
|
|
149
|
+
// before it releases effect authority or returns to its caller.
|
|
150
|
+
const fillTimeout = remainingMs(call, clock);
|
|
151
|
+
const beforeFillEffect = cancellationCode(call, clock);
|
|
152
|
+
if (beforeFillEffect !== undefined)
|
|
153
|
+
return blocked(base("execute_prepared", prepared), beforeFillEffect, false, "none");
|
|
154
|
+
try {
|
|
155
|
+
await composer.value.fill(captured.prompt, { timeout: fillTimeout });
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return uncertain(base("execute_prepared", prepared), "send_control_unavailable", "provider");
|
|
159
|
+
}
|
|
160
|
+
const afterFillCancellation = cancellationCode(call, clock);
|
|
161
|
+
if (afterFillCancellation !== undefined)
|
|
162
|
+
return uncertain(base("execute_prepared", prepared), afterFillCancellation, "caller");
|
|
163
|
+
// Filling normally enables Send. Recheck uniqueness, visibility, and the
|
|
164
|
+
// actual disabled/ARIA/inert state after that local mutation and directly
|
|
165
|
+
// before the sole click.
|
|
166
|
+
const finalSendValidation = await validateSend(send.value, call, clock, true);
|
|
167
|
+
if (finalSendValidation !== undefined)
|
|
168
|
+
return uncertain(base("execute_prepared", prepared), finalSendValidation, "caller");
|
|
169
|
+
const clickTimeout = remainingMs(call, clock);
|
|
170
|
+
const beforeClickEffect = cancellationCode(call, clock);
|
|
171
|
+
if (beforeClickEffect !== undefined)
|
|
172
|
+
return uncertain(base("execute_prepared", prepared), beforeClickEffect, "caller");
|
|
173
|
+
try {
|
|
174
|
+
await send.value.click({ timeout: clickTimeout });
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return uncertain(base("execute_prepared", prepared), "send_control_unavailable", "provider");
|
|
178
|
+
}
|
|
179
|
+
const afterClickCancellation = cancellationCode(call, clock);
|
|
180
|
+
if (afterClickCancellation !== undefined)
|
|
181
|
+
return uncertain(base("execute_prepared", prepared), afterClickCancellation, "caller");
|
|
182
|
+
return Object.freeze({
|
|
183
|
+
...base("execute_prepared", prepared),
|
|
184
|
+
status: "executed",
|
|
185
|
+
observationRequired: true,
|
|
186
|
+
mutationBoundary: "control_may_have_occurred"
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const verify = async (request) => {
|
|
190
|
+
return verifyObservation(request, "verify", undefined);
|
|
191
|
+
};
|
|
192
|
+
const recover = async (request) => {
|
|
193
|
+
return verifyObservation(request, "recovery", request.baseline);
|
|
194
|
+
};
|
|
195
|
+
const verifyObservation = async (request, phase, suppliedBaseline) => {
|
|
196
|
+
let call;
|
|
197
|
+
let prepared;
|
|
198
|
+
try {
|
|
199
|
+
call = normalizeCall(request, captured, clock, phase);
|
|
200
|
+
prepared = validatePrepared(request.prepared, captured);
|
|
201
|
+
if (suppliedBaseline !== undefined) {
|
|
202
|
+
validateBaselineInput(suppliedBaseline, captured, prepared.baselineSnapshotDigest);
|
|
203
|
+
if (canonicalJson(suppliedBaseline) !== canonicalJson(prepared.baseline))
|
|
204
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return blocked(base(phase), normalizeInputError(error), true, "control_may_have_occurred");
|
|
209
|
+
}
|
|
210
|
+
const initialCancellation = cancellationCode(call, clock);
|
|
211
|
+
if (initialCancellation !== undefined)
|
|
212
|
+
return uncertain(base(phase, prepared), initialCancellation, "caller");
|
|
213
|
+
const observed = await observeBounded(call, captured, clock, phase, prepared, suppliedBaseline);
|
|
214
|
+
if (observed.kind === "cancelled")
|
|
215
|
+
return uncertain(base(phase, prepared), observed.code, "caller");
|
|
216
|
+
if (observed.kind === "error")
|
|
217
|
+
return uncertain(base(phase, prepared), "target_evidence_unavailable", "caller");
|
|
218
|
+
const exact = exactPostcondition(observed.value.snapshot, prepared, captured);
|
|
219
|
+
if (exact.kind === "failure")
|
|
220
|
+
return uncertain(base(phase, prepared), exact.blockerCode, "caller", exact.evidenceDigest);
|
|
221
|
+
return Object.freeze({
|
|
222
|
+
...base(phase, prepared),
|
|
223
|
+
status: "satisfied",
|
|
224
|
+
observationRequired: false,
|
|
225
|
+
mutationBoundary: "control_may_have_occurred",
|
|
226
|
+
...exact.value.receipt,
|
|
227
|
+
receipt: exact.value.receipt
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
return Object.freeze({ prepare, executePrepared, verify, recover });
|
|
231
|
+
}
|
|
232
|
+
export const createOperationProductionWorkSteer = createProductionWorkSteerPrimitive;
|
|
233
|
+
export const createProductionWorkSteer = createProductionWorkSteerPrimitive;
|
|
234
|
+
function captureOptions(options) {
|
|
235
|
+
if (!isPlainRecord(options) || hasAccessorInGraph(options))
|
|
236
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_options");
|
|
237
|
+
const allowed = new Set([
|
|
238
|
+
"evidenceDigest", "operationId", "parentRequestDigest", "targetBindingDigest", "controlActionId",
|
|
239
|
+
"expectedAssistantTurnId", "target", "prompt", "observe", "resolveComposer", "resolveSendControl",
|
|
240
|
+
"timeoutMs", "now"
|
|
241
|
+
]);
|
|
242
|
+
if (Reflect.ownKeys(options).some(key => typeof key !== "string" || !allowed.has(key))
|
|
243
|
+
|| !hasOwn(options, "evidenceDigest")
|
|
244
|
+
|| !hasOwn(options, "operationId")
|
|
245
|
+
|| !hasOwn(options, "parentRequestDigest")
|
|
246
|
+
|| !hasOwn(options, "targetBindingDigest")
|
|
247
|
+
|| !hasOwn(options, "controlActionId")
|
|
248
|
+
|| !hasOwn(options, "expectedAssistantTurnId")
|
|
249
|
+
|| !hasOwn(options, "target")
|
|
250
|
+
|| !hasOwn(options, "prompt")
|
|
251
|
+
|| !hasOwn(options, "observe")
|
|
252
|
+
|| !hasOwn(options, "resolveComposer")
|
|
253
|
+
|| !hasOwn(options, "resolveSendControl")) {
|
|
254
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_options");
|
|
255
|
+
}
|
|
256
|
+
const evidenceDigest = own(options, "evidenceDigest");
|
|
257
|
+
const operationId = own(options, "operationId");
|
|
258
|
+
const parentRequestDigest = own(options, "parentRequestDigest");
|
|
259
|
+
const targetBindingDigest = own(options, "targetBindingDigest");
|
|
260
|
+
const controlActionId = own(options, "controlActionId");
|
|
261
|
+
const expectedAssistantTurnId = own(options, "expectedAssistantTurnId");
|
|
262
|
+
const target = own(options, "target");
|
|
263
|
+
const prompt = own(options, "prompt");
|
|
264
|
+
const observe = own(options, "observe");
|
|
265
|
+
const resolveComposer = own(options, "resolveComposer");
|
|
266
|
+
const resolveSendControl = own(options, "resolveSendControl");
|
|
267
|
+
const timeoutMs = own(options, "timeoutMs");
|
|
268
|
+
const now = own(options, "now");
|
|
269
|
+
if (typeof evidenceDigest !== "function"
|
|
270
|
+
|| typeof observe !== "function"
|
|
271
|
+
|| typeof resolveComposer !== "function"
|
|
272
|
+
|| typeof resolveSendControl !== "function")
|
|
273
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_options");
|
|
274
|
+
assertUuid(operationId, "operationId");
|
|
275
|
+
assertDigest(parentRequestDigest, "parentRequestDigest");
|
|
276
|
+
assertDigest(targetBindingDigest, "targetBindingDigest");
|
|
277
|
+
assertUuid(controlActionId, "controlActionId");
|
|
278
|
+
assertIdentifier(expectedAssistantTurnId, "expectedAssistantTurnId");
|
|
279
|
+
if (typeof prompt !== "string" || prompt.length === 0 || prompt.includes("\u0000") || utf8Bytes(prompt) > MAX_PROMPT_BYTES) {
|
|
280
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_prompt");
|
|
281
|
+
}
|
|
282
|
+
const timeout = timeoutMs === undefined ? MAX_TIMEOUT_MS : timeoutMs;
|
|
283
|
+
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > MAX_TIMEOUT_MS)
|
|
284
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_timeout");
|
|
285
|
+
if (now !== undefined && typeof now !== "function")
|
|
286
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_clock");
|
|
287
|
+
return Object.freeze({
|
|
288
|
+
evidenceDigest: evidenceDigest,
|
|
289
|
+
operationId,
|
|
290
|
+
parentRequestDigest,
|
|
291
|
+
targetBindingDigest,
|
|
292
|
+
controlActionId,
|
|
293
|
+
expectedAssistantTurnId,
|
|
294
|
+
target: cloneTarget(target),
|
|
295
|
+
prompt,
|
|
296
|
+
observe: observe,
|
|
297
|
+
resolveComposer: resolveComposer,
|
|
298
|
+
resolveSendControl: resolveSendControl,
|
|
299
|
+
timeoutMs: timeout,
|
|
300
|
+
now: (now ?? Date.now)
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function normalizeCall(request, options, clock, phase) {
|
|
304
|
+
if (!isPlainRecord(request) || hasUnsafeRequestGraph(request))
|
|
305
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_call");
|
|
306
|
+
const allowed = phase === "prepare"
|
|
307
|
+
? new Set(["page", "signal", "deadlineAt"])
|
|
308
|
+
: phase === "recovery"
|
|
309
|
+
? new Set(["page", "prepared", "baseline", "signal", "deadlineAt"])
|
|
310
|
+
: new Set(["page", "prepared", "signal", "deadlineAt"]);
|
|
311
|
+
if (Reflect.ownKeys(request).some(key => typeof key !== "string" || !allowed.has(key)))
|
|
312
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_call");
|
|
313
|
+
const page = own(request, "page");
|
|
314
|
+
const signal = own(request, "signal");
|
|
315
|
+
if (page === null || (typeof page !== "object" && typeof page !== "function"))
|
|
316
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_call");
|
|
317
|
+
if (!isAbortSignal(signal))
|
|
318
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_signal");
|
|
319
|
+
const initialNow = readClock(clock);
|
|
320
|
+
const requestedDeadline = own(request, "deadlineAt");
|
|
321
|
+
const deadlineAt = requestedDeadline === undefined ? initialNow + options.timeoutMs : requestedDeadline;
|
|
322
|
+
if (!Number.isSafeInteger(deadlineAt) || deadlineAt < 0 || deadlineAt > MAX_DEADLINE_AT || deadlineAt < initialNow) {
|
|
323
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_deadline");
|
|
324
|
+
}
|
|
325
|
+
return Object.freeze({ page: page, signal, deadlineAt });
|
|
326
|
+
}
|
|
327
|
+
function resolverRequestFor(call, options, prepared) {
|
|
328
|
+
return Object.freeze({
|
|
329
|
+
schemaVersion: PRODUCTION_WORK_STEER_SCHEMA_VERSION,
|
|
330
|
+
operationId: options.operationId,
|
|
331
|
+
parentRequestDigest: options.parentRequestDigest,
|
|
332
|
+
targetBindingDigest: options.targetBindingDigest,
|
|
333
|
+
controlActionId: options.controlActionId,
|
|
334
|
+
expectedAssistantTurnId: options.expectedAssistantTurnId,
|
|
335
|
+
assistantBranchId: prepared.assistantBranchId,
|
|
336
|
+
assistantParentTurnId: prepared.assistantParentTurnId,
|
|
337
|
+
preparedDigest: prepared.preparedDigest,
|
|
338
|
+
page: call.page,
|
|
339
|
+
signal: call.signal,
|
|
340
|
+
deadlineAt: call.deadlineAt
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async function observeBounded(call, options, clock, phase, prepared, recoveryBaseline) {
|
|
344
|
+
const cancellation = cancellationCode(call, clock);
|
|
345
|
+
if (cancellation !== undefined)
|
|
346
|
+
return { kind: "cancelled", code: cancellation };
|
|
347
|
+
const request = Object.freeze({
|
|
348
|
+
schemaVersion: PRODUCTION_WORK_STEER_SCHEMA_VERSION,
|
|
349
|
+
phase,
|
|
350
|
+
operationId: options.operationId,
|
|
351
|
+
parentRequestDigest: options.parentRequestDigest,
|
|
352
|
+
targetBindingDigest: options.targetBindingDigest,
|
|
353
|
+
controlActionId: options.controlActionId,
|
|
354
|
+
expectedAssistantTurnId: options.expectedAssistantTurnId,
|
|
355
|
+
...(prepared === undefined ? {} : {
|
|
356
|
+
assistantBranchId: prepared.assistantBranchId,
|
|
357
|
+
assistantParentTurnId: prepared.assistantParentTurnId,
|
|
358
|
+
baselineSnapshotDigest: prepared.baselineSnapshotDigest,
|
|
359
|
+
preparedDigest: prepared.preparedDigest
|
|
360
|
+
}),
|
|
361
|
+
page: call.page,
|
|
362
|
+
signal: call.signal,
|
|
363
|
+
deadlineAt: call.deadlineAt,
|
|
364
|
+
...(prepared === undefined
|
|
365
|
+
? {}
|
|
366
|
+
: { baseline: cloneBaseline(recoveryBaseline ?? prepared.baseline) })
|
|
367
|
+
});
|
|
368
|
+
try {
|
|
369
|
+
const result = await boundedRead(() => options.observe(request), call, clock);
|
|
370
|
+
if (result.kind !== "ok")
|
|
371
|
+
return result;
|
|
372
|
+
const afterReadCancellation = cancellationCode(call, clock);
|
|
373
|
+
if (afterReadCancellation !== undefined)
|
|
374
|
+
return { kind: "cancelled", code: afterReadCancellation };
|
|
375
|
+
if (!isPlainRecord(result.value) || hasAccessorInGraph(result.value) || !hasOwn(result.value, "snapshot"))
|
|
376
|
+
return { kind: "error" };
|
|
377
|
+
const rawSnapshot = own(result.value, "snapshot");
|
|
378
|
+
const snapshot = cloneSnapshot(rawSnapshot);
|
|
379
|
+
validateSnapshotShape(snapshot);
|
|
380
|
+
if (!matchesTargetEvidence(options.target, snapshot.target))
|
|
381
|
+
return { kind: "error" };
|
|
382
|
+
return { kind: "ok", value: { snapshot } };
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return { kind: "error" };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async function boundedRead(invoke, call, clock) {
|
|
389
|
+
const cancellation = cancellationCode(call, clock);
|
|
390
|
+
if (cancellation !== undefined)
|
|
391
|
+
return { kind: "cancelled", code: cancellation };
|
|
392
|
+
try {
|
|
393
|
+
// Never race an in-flight provider/browser call. The outer tab actor owns
|
|
394
|
+
// deadline quarantine; returning while this promise is still running
|
|
395
|
+
// would hide it from the coordinator and allow unsafe overlap.
|
|
396
|
+
const value = await Promise.resolve().then(invoke);
|
|
397
|
+
const after = cancellationCode(call, clock);
|
|
398
|
+
return after === undefined ? { kind: "ok", value } : { kind: "cancelled", code: after };
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return { kind: "error" };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function resolveComposer(request, call, options, clock) {
|
|
405
|
+
const result = await boundedRead(() => options.resolveComposer(request), call, clock);
|
|
406
|
+
if (result.kind !== "ok")
|
|
407
|
+
return result;
|
|
408
|
+
if (result.value === undefined)
|
|
409
|
+
return { kind: "ok", value: undefined };
|
|
410
|
+
try {
|
|
411
|
+
return { kind: "ok", value: captureComposer(result.value) };
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return { kind: "error" };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async function resolveSendControl(request, call, options, clock) {
|
|
418
|
+
const result = await boundedRead(() => options.resolveSendControl(request), call, clock);
|
|
419
|
+
if (result.kind !== "ok")
|
|
420
|
+
return result;
|
|
421
|
+
if (result.value === undefined)
|
|
422
|
+
return { kind: "ok", value: undefined };
|
|
423
|
+
try {
|
|
424
|
+
return { kind: "ok", value: captureSend(result.value) };
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return { kind: "error" };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function validateComposer(composer, call, clock) {
|
|
431
|
+
const count = await boundedRead(composer.count, call, clock);
|
|
432
|
+
if (count.kind !== "ok" || count.value !== 1)
|
|
433
|
+
return count.kind === "cancelled" ? count.code : "send_control_unavailable";
|
|
434
|
+
const visible = await boundedRead(composer.isVisible, call, clock);
|
|
435
|
+
if (visible.kind !== "ok" || visible.value !== true)
|
|
436
|
+
return visible.kind === "cancelled" ? visible.code : "send_control_unavailable";
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
async function validateSend(send, call, clock, requireEnabled) {
|
|
440
|
+
const count = await boundedRead(send.count, call, clock);
|
|
441
|
+
if (count.kind !== "ok" || count.value !== 1)
|
|
442
|
+
return count.kind === "cancelled" ? count.code : "send_control_unavailable";
|
|
443
|
+
const visible = await boundedRead(send.isVisible, call, clock);
|
|
444
|
+
if (visible.kind !== "ok" || visible.value !== true)
|
|
445
|
+
return visible.kind === "cancelled" ? visible.code : "send_control_unavailable";
|
|
446
|
+
if (requireEnabled) {
|
|
447
|
+
const enabled = await boundedRead(send.isEnabled, call, clock);
|
|
448
|
+
if (enabled.kind !== "ok" || enabled.value !== true)
|
|
449
|
+
return enabled.kind === "cancelled" ? enabled.code : "send_control_unavailable";
|
|
450
|
+
}
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
function captureComposer(value) {
|
|
454
|
+
if (!isPlainRecord(value) || hasUnsafeCapabilityGraph(value, ["locator", "capabilityKey", "candidateCount"]) || !exactKeys(value, ["locator", "capabilityKey", "candidateCount"]))
|
|
455
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_composer");
|
|
456
|
+
const locator = own(value, "locator");
|
|
457
|
+
const capabilityKey = own(value, "capabilityKey");
|
|
458
|
+
const candidateCount = own(value, "candidateCount");
|
|
459
|
+
if (!isObject(locator) || !isSafeCapability(capabilityKey) || candidateCount !== 1)
|
|
460
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_composer");
|
|
461
|
+
const count = captureMethod(locator, "count");
|
|
462
|
+
const isVisible = captureMethod(locator, "isVisible");
|
|
463
|
+
const fill = captureMethod(locator, "fill");
|
|
464
|
+
return Object.freeze({
|
|
465
|
+
locator: locator,
|
|
466
|
+
count: async () => Number(await Reflect.apply(count, locator, [])),
|
|
467
|
+
isVisible: async () => (await Reflect.apply(isVisible, locator, [])) === true,
|
|
468
|
+
fill: async (prompt, fillOptions) => {
|
|
469
|
+
await Reflect.apply(fill, locator, [prompt, fillOptions]);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function captureSend(value) {
|
|
474
|
+
if (!isPlainRecord(value) || hasUnsafeCapabilityGraph(value, ["locator", "capabilityKey", "localeKey", "candidateCount"]) || !exactKeys(value, ["locator", "capabilityKey", "localeKey", "candidateCount"]))
|
|
475
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_send");
|
|
476
|
+
const locator = own(value, "locator");
|
|
477
|
+
const capabilityKey = own(value, "capabilityKey");
|
|
478
|
+
const localeKey = own(value, "localeKey");
|
|
479
|
+
const candidateCount = own(value, "candidateCount");
|
|
480
|
+
if (!isObject(locator) || !isSafeCapability(capabilityKey) || !isSafeCapability(localeKey) || candidateCount !== 1)
|
|
481
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_send");
|
|
482
|
+
const count = captureMethod(locator, "count");
|
|
483
|
+
const isVisible = captureMethod(locator, "isVisible");
|
|
484
|
+
const evaluate = captureMethod(locator, "evaluate");
|
|
485
|
+
const click = captureMethod(locator, "click");
|
|
486
|
+
return Object.freeze({
|
|
487
|
+
locator: locator,
|
|
488
|
+
count: async () => Number(await Reflect.apply(count, locator, [])),
|
|
489
|
+
isVisible: async () => (await Reflect.apply(isVisible, locator, [])) === true,
|
|
490
|
+
isEnabled: async () => (await Reflect.apply(evaluate, locator, [
|
|
491
|
+
(element) => {
|
|
492
|
+
const ariaDisabled = element.getAttribute("aria-disabled");
|
|
493
|
+
return element.disabled !== true
|
|
494
|
+
&& !element.hasAttribute("disabled")
|
|
495
|
+
&& ariaDisabled !== "true"
|
|
496
|
+
&& !element.hasAttribute("inert")
|
|
497
|
+
&& element.getAttribute("aria-hidden") !== "true";
|
|
498
|
+
}
|
|
499
|
+
])) === true,
|
|
500
|
+
click: async (clickOptions) => {
|
|
501
|
+
await Reflect.apply(click, locator, [clickOptions]);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
function captureMethod(value, key) {
|
|
506
|
+
let cursor = value;
|
|
507
|
+
while (cursor !== null) {
|
|
508
|
+
const descriptor = Object.getOwnPropertyDescriptor(cursor, key);
|
|
509
|
+
if (descriptor !== undefined) {
|
|
510
|
+
if (!("value" in descriptor) || typeof descriptor.value !== "function")
|
|
511
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_capability");
|
|
512
|
+
return descriptor.value;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_capability");
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_capability");
|
|
522
|
+
}
|
|
523
|
+
/** The locator is an opaque provider capability; only its captured methods are used. */
|
|
524
|
+
function hasUnsafeCapabilityGraph(value, allowed) {
|
|
525
|
+
try {
|
|
526
|
+
const keys = Reflect.ownKeys(value);
|
|
527
|
+
if (keys.some(key => typeof key !== "string" || !allowed.includes(key)))
|
|
528
|
+
return true;
|
|
529
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
530
|
+
for (const key of keys) {
|
|
531
|
+
const descriptor = descriptors[key];
|
|
532
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
533
|
+
return true;
|
|
534
|
+
if (key !== "locator" && hasAccessorInGraph(descriptor.value))
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function makePrepared(snapshot, options) {
|
|
544
|
+
const baselineResult = makeBaseline(snapshot, options);
|
|
545
|
+
if (baselineResult.kind === "failure")
|
|
546
|
+
return baselineResult;
|
|
547
|
+
const assistant = findExpectedAssistant(snapshot, options.expectedAssistantTurnId);
|
|
548
|
+
if (assistant === undefined || assistant.state !== "generating" || snapshot.terminalState !== "generating") {
|
|
549
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-parent", snapshot.snapshotDigest) };
|
|
550
|
+
}
|
|
551
|
+
if (assistant.parentStableId === undefined || assistant.branchStableId === undefined
|
|
552
|
+
|| !isSafeIdentifier(assistant.parentStableId) || !isSafeIdentifier(assistant.branchStableId)) {
|
|
553
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-parent", snapshot.snapshotDigest) };
|
|
554
|
+
}
|
|
555
|
+
if (!baselineResult.value.userTurns.some(turn => turn.stableId === assistant.parentStableId)) {
|
|
556
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-parent", snapshot.snapshotDigest) };
|
|
557
|
+
}
|
|
558
|
+
if (snapshot.assistantTurns.some(turn => turn !== assistant && turn.parentStableId === assistant.parentStableId && turn.branchStableId !== assistant.branchStableId)) {
|
|
559
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-branch", snapshot.snapshotDigest) };
|
|
560
|
+
}
|
|
561
|
+
const material = {
|
|
562
|
+
schemaVersion: PRODUCTION_WORK_STEER_SCHEMA_VERSION,
|
|
563
|
+
operationId: options.operationId,
|
|
564
|
+
parentRequestDigest: options.parentRequestDigest,
|
|
565
|
+
targetBindingDigest: options.targetBindingDigest,
|
|
566
|
+
controlActionId: options.controlActionId,
|
|
567
|
+
action: "work_steer",
|
|
568
|
+
expectedAssistantTurnId: options.expectedAssistantTurnId,
|
|
569
|
+
assistantBranchId: assistant.branchStableId,
|
|
570
|
+
assistantParentTurnId: assistant.parentStableId,
|
|
571
|
+
baselineSnapshotDigest: baselineResult.value.snapshotDigest,
|
|
572
|
+
baseline: baselineResult.value
|
|
573
|
+
};
|
|
574
|
+
const preparedDigest = safeDigest(options, "work-steer-prepared", material);
|
|
575
|
+
if (preparedDigest === undefined)
|
|
576
|
+
return { kind: "failure", blockerCode: "send_control_unavailable", observationRequired: true, evidenceDigest: undefined };
|
|
577
|
+
return {
|
|
578
|
+
kind: "ok",
|
|
579
|
+
value: deepFreeze({
|
|
580
|
+
...material,
|
|
581
|
+
preparedDigest
|
|
582
|
+
})
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function validatePrepared(value, options) {
|
|
586
|
+
if (!isPlainRecord(value) || hasAccessorInGraph(value) || !exactKeys(value, [
|
|
587
|
+
"schemaVersion", "operationId", "parentRequestDigest", "targetBindingDigest", "controlActionId", "action",
|
|
588
|
+
"expectedAssistantTurnId", "assistantBranchId", "assistantParentTurnId", "baselineSnapshotDigest", "preparedDigest", "baseline"
|
|
589
|
+
]))
|
|
590
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_prepared");
|
|
591
|
+
const cloned = cloneData(value, 0, { count: 0, active: new Set() });
|
|
592
|
+
if (!isPlainRecord(cloned))
|
|
593
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_prepared");
|
|
594
|
+
if (cloned.schemaVersion !== PRODUCTION_WORK_STEER_SCHEMA_VERSION
|
|
595
|
+
|| cloned.operationId !== options.operationId
|
|
596
|
+
|| cloned.parentRequestDigest !== options.parentRequestDigest
|
|
597
|
+
|| cloned.targetBindingDigest !== options.targetBindingDigest
|
|
598
|
+
|| cloned.controlActionId !== options.controlActionId
|
|
599
|
+
|| cloned.action !== "work_steer"
|
|
600
|
+
|| cloned.expectedAssistantTurnId !== options.expectedAssistantTurnId
|
|
601
|
+
|| !isSafeIdentifier(cloned.assistantBranchId)
|
|
602
|
+
|| !isSafeIdentifier(cloned.assistantParentTurnId)
|
|
603
|
+
|| typeof cloned.baselineSnapshotDigest !== "string"
|
|
604
|
+
|| !DIGEST_PATTERN.test(cloned.baselineSnapshotDigest)
|
|
605
|
+
|| typeof cloned.preparedDigest !== "string"
|
|
606
|
+
|| !DIGEST_PATTERN.test(cloned.preparedDigest))
|
|
607
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_prepared");
|
|
608
|
+
validateBaselineInput(cloned.baseline, {
|
|
609
|
+
...options,
|
|
610
|
+
// validateBaselineInput compares only the redacted baseline and identity.
|
|
611
|
+
}, cloned.baselineSnapshotDigest);
|
|
612
|
+
const material = {
|
|
613
|
+
schemaVersion: PRODUCTION_WORK_STEER_SCHEMA_VERSION,
|
|
614
|
+
operationId: cloned.operationId,
|
|
615
|
+
parentRequestDigest: cloned.parentRequestDigest,
|
|
616
|
+
targetBindingDigest: cloned.targetBindingDigest,
|
|
617
|
+
controlActionId: cloned.controlActionId,
|
|
618
|
+
action: "work_steer",
|
|
619
|
+
expectedAssistantTurnId: cloned.expectedAssistantTurnId,
|
|
620
|
+
assistantBranchId: cloned.assistantBranchId,
|
|
621
|
+
assistantParentTurnId: cloned.assistantParentTurnId,
|
|
622
|
+
baselineSnapshotDigest: cloned.baselineSnapshotDigest,
|
|
623
|
+
baseline: cloned.baseline
|
|
624
|
+
};
|
|
625
|
+
const expectedDigest = safeDigest(options, "work-steer-prepared", material);
|
|
626
|
+
if (expectedDigest === undefined || expectedDigest !== cloned.preparedDigest)
|
|
627
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_prepared");
|
|
628
|
+
const baseline = cloned.baseline;
|
|
629
|
+
const parent = baseline.assistantTurns.find(turn => turn.stableId === cloned.expectedAssistantTurnId);
|
|
630
|
+
if (parent === undefined || parent.state !== "generating" || parent.parentStableId !== cloned.assistantParentTurnId || parent.branchStableId !== cloned.assistantBranchId) {
|
|
631
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_prepared");
|
|
632
|
+
}
|
|
633
|
+
return deepFreeze(cloned);
|
|
634
|
+
}
|
|
635
|
+
function validateBaselineInput(value, options, expectedSnapshotDigest) {
|
|
636
|
+
if (!isPlainRecord(value) || hasAccessorInGraph(value))
|
|
637
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
638
|
+
const cloned = cloneData(value, 0, { count: 0, active: new Set() });
|
|
639
|
+
if (!isPlainRecord(cloned))
|
|
640
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
641
|
+
const wrapper = {
|
|
642
|
+
schemaVersion: OPERATION_OWNERSHIP_BASELINE_SCHEMA_VERSION,
|
|
643
|
+
operationId: options.operationId,
|
|
644
|
+
requestDigest: options.parentRequestDigest,
|
|
645
|
+
targetBindingDigest: options.targetBindingDigest,
|
|
646
|
+
actionId: options.controlActionId,
|
|
647
|
+
baseline: cloned,
|
|
648
|
+
observedAt: BASELINE_EPOCH
|
|
649
|
+
};
|
|
650
|
+
try {
|
|
651
|
+
assertOwnershipBaselineShape(wrapper);
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
655
|
+
}
|
|
656
|
+
const baseline = cloned;
|
|
657
|
+
if (expectedSnapshotDigest !== undefined && baseline.snapshotDigest !== expectedSnapshotDigest)
|
|
658
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
659
|
+
if (baseline.target.canonicalThreadUrl.status !== "unavailable" || baseline.target.canonicalThreadUrl.reason !== "redacted")
|
|
660
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
661
|
+
if (!matchesRedactedTarget(options.target, baseline.target))
|
|
662
|
+
throw new ProductionWorkSteerPrimitiveError("target_binding_mismatch");
|
|
663
|
+
}
|
|
664
|
+
function makeBaseline(snapshot, options) {
|
|
665
|
+
if (snapshot.completeness !== "complete" || snapshot.terminalState === "unknown") {
|
|
666
|
+
return { kind: "failure", blockerCode: "target_evidence_unavailable", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-snapshot", snapshot.snapshotDigest) };
|
|
667
|
+
}
|
|
668
|
+
const target = redactTargetEvidence(snapshot.target);
|
|
669
|
+
const baseline = deepFreeze({
|
|
670
|
+
schemaVersion: TURN_OWNERSHIP_SCHEMA_VERSION,
|
|
671
|
+
snapshotDigest: snapshot.snapshotDigest,
|
|
672
|
+
target,
|
|
673
|
+
userTurns: snapshot.userTurns,
|
|
674
|
+
assistantTurns: snapshot.assistantTurns,
|
|
675
|
+
completeness: "complete"
|
|
676
|
+
});
|
|
677
|
+
try {
|
|
678
|
+
const wrapper = {
|
|
679
|
+
schemaVersion: OPERATION_OWNERSHIP_BASELINE_SCHEMA_VERSION,
|
|
680
|
+
operationId: options.operationId,
|
|
681
|
+
requestDigest: options.parentRequestDigest,
|
|
682
|
+
targetBindingDigest: options.targetBindingDigest,
|
|
683
|
+
actionId: options.controlActionId,
|
|
684
|
+
baseline,
|
|
685
|
+
observedAt: BASELINE_EPOCH
|
|
686
|
+
};
|
|
687
|
+
assertOwnershipBaselineShape(wrapper);
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
return { kind: "failure", blockerCode: "target_evidence_unavailable", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-snapshot", snapshot.snapshotDigest) };
|
|
691
|
+
}
|
|
692
|
+
return { kind: "ok", value: baseline };
|
|
693
|
+
}
|
|
694
|
+
function validateGeneratingParent(snapshot, options, prepared) {
|
|
695
|
+
const assistant = findExpectedAssistant(snapshot, options.expectedAssistantTurnId);
|
|
696
|
+
if (assistant === undefined || assistant.state !== "generating" || snapshot.terminalState !== "generating"
|
|
697
|
+
|| assistant.parentStableId !== prepared.assistantParentTurnId
|
|
698
|
+
|| assistant.branchStableId !== prepared.assistantBranchId) {
|
|
699
|
+
return { blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-parent", snapshot.snapshotDigest) };
|
|
700
|
+
}
|
|
701
|
+
if (snapshot.assistantTurns.some(turn => turn !== assistant && turn.parentStableId === prepared.assistantParentTurnId && turn.branchStableId !== prepared.assistantBranchId)) {
|
|
702
|
+
return { blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-branch", snapshot.snapshotDigest) };
|
|
703
|
+
}
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
function exactPostcondition(snapshot, prepared, options) {
|
|
707
|
+
if (snapshot.completeness !== "complete" || snapshot.terminalState === "unknown")
|
|
708
|
+
return { kind: "failure", blockerCode: "target_evidence_unavailable", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-postcondition", snapshot.snapshotDigest) };
|
|
709
|
+
const baseline = prepared.baseline;
|
|
710
|
+
if (!preserveUsers(baseline.userTurns, snapshot.userTurns))
|
|
711
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-delta", snapshot.snapshotDigest) };
|
|
712
|
+
if (snapshot.userTurns.length !== baseline.userTurns.length + 1) {
|
|
713
|
+
return { kind: "failure", blockerCode: snapshot.userTurns.length > baseline.userTurns.length + 1 ? "concurrent_user_turn" : "ambiguous_submit", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-delta", snapshot.snapshotDigest) };
|
|
714
|
+
}
|
|
715
|
+
const addedUser = snapshot.userTurns[baseline.userTurns.length];
|
|
716
|
+
if (addedUser === undefined || addedUser.stableId === undefined || addedUser.ordinal !== baseline.userTurns.length) {
|
|
717
|
+
return { kind: "failure", blockerCode: "ambiguous_submit", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-delta", snapshot.snapshotDigest) };
|
|
718
|
+
}
|
|
719
|
+
const delta = snapshot.postSendDelta;
|
|
720
|
+
if (delta === undefined || delta.baselineSnapshotDigest !== prepared.baselineSnapshotDigest || delta.addedUserEvidenceDigests.length !== 1 || delta.addedUserEvidenceDigests[0] !== addedUser.evidenceDigest) {
|
|
721
|
+
return { kind: "failure", blockerCode: "ambiguous_submit", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-delta", snapshot.snapshotDigest) };
|
|
722
|
+
}
|
|
723
|
+
const expectedDelta = safeDigest(options, "browser-observation-post-send-delta", {
|
|
724
|
+
baselineSnapshotDigest: prepared.baselineSnapshotDigest,
|
|
725
|
+
addedUserEvidenceDigests: delta.addedUserEvidenceDigests
|
|
726
|
+
});
|
|
727
|
+
if (expectedDelta === undefined || expectedDelta !== delta.deltaDigest) {
|
|
728
|
+
return { kind: "failure", blockerCode: "ambiguous_submit", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-delta", snapshot.snapshotDigest) };
|
|
729
|
+
}
|
|
730
|
+
if (!preserveAssistants(baseline.assistantTurns, snapshot.assistantTurns, prepared))
|
|
731
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-branch", snapshot.snapshotDigest) };
|
|
732
|
+
const addedAssistants = snapshot.assistantTurns.slice(baseline.assistantTurns.length);
|
|
733
|
+
if (addedAssistants.length > 1 || addedAssistants.some(turn => turn.parentStableId !== addedUser.stableId || turn.branchStableId !== prepared.assistantBranchId || turn.ordinal !== baseline.assistantTurns.length))
|
|
734
|
+
return { kind: "failure", blockerCode: "turn_ownership_ambiguous", observationRequired: true, evidenceDigest: safeDigest(options, "work-steer-branch", snapshot.snapshotDigest) };
|
|
735
|
+
const evidenceDigest = safeDigest(options, "work-steer-postcondition", {
|
|
736
|
+
operationId: options.operationId,
|
|
737
|
+
targetBindingDigest: options.targetBindingDigest,
|
|
738
|
+
controlActionId: options.controlActionId,
|
|
739
|
+
expectedAssistantTurnId: options.expectedAssistantTurnId,
|
|
740
|
+
assistantBranchId: prepared.assistantBranchId,
|
|
741
|
+
assistantParentTurnId: prepared.assistantParentTurnId,
|
|
742
|
+
userTurnEvidenceDigest: addedUser.evidenceDigest,
|
|
743
|
+
postSendDeltaDigest: delta.deltaDigest
|
|
744
|
+
});
|
|
745
|
+
if (evidenceDigest === undefined)
|
|
746
|
+
return { kind: "failure", blockerCode: "send_control_unavailable", observationRequired: true, evidenceDigest: undefined };
|
|
747
|
+
const receipt = deepFreeze({
|
|
748
|
+
schemaVersion: PRODUCTION_WORK_STEER_SCHEMA_VERSION,
|
|
749
|
+
baselineSnapshotDigest: prepared.baselineSnapshotDigest,
|
|
750
|
+
preparedDigest: prepared.preparedDigest,
|
|
751
|
+
assistantTurnId: prepared.expectedAssistantTurnId,
|
|
752
|
+
assistantBranchId: prepared.assistantBranchId,
|
|
753
|
+
assistantParentTurnId: prepared.assistantParentTurnId,
|
|
754
|
+
userTurnId: addedUser.stableId,
|
|
755
|
+
userTurnEvidenceDigest: addedUser.evidenceDigest,
|
|
756
|
+
postSendDeltaDigest: delta.deltaDigest,
|
|
757
|
+
evidenceDigest
|
|
758
|
+
});
|
|
759
|
+
return { kind: "ok", value: { receipt } };
|
|
760
|
+
}
|
|
761
|
+
function preserveUsers(before, after) {
|
|
762
|
+
if (after.length < before.length)
|
|
763
|
+
return false;
|
|
764
|
+
for (let index = 0; index < before.length; index += 1) {
|
|
765
|
+
if (canonicalJson(before[index]) !== canonicalJson(after[index]))
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
function preserveAssistants(before, after, prepared) {
|
|
771
|
+
if (after.length < before.length)
|
|
772
|
+
return false;
|
|
773
|
+
for (let index = 0; index < before.length; index += 1) {
|
|
774
|
+
const prior = before[index];
|
|
775
|
+
const current = after[index];
|
|
776
|
+
if (prior === undefined || current === undefined)
|
|
777
|
+
return false;
|
|
778
|
+
if (prior.stableId !== prepared.expectedAssistantTurnId) {
|
|
779
|
+
if (canonicalJson(prior) !== canonicalJson(current))
|
|
780
|
+
return false;
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (current.stableId !== prior.stableId || current.ordinal !== prior.ordinal || current.parentStableId !== prepared.assistantParentTurnId || current.branchStableId !== prepared.assistantBranchId)
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
function findExpectedAssistant(snapshot, id) {
|
|
789
|
+
const found = snapshot.assistantTurns.filter(turn => turn.stableId === id);
|
|
790
|
+
return found.length === 1 ? found[0] : undefined;
|
|
791
|
+
}
|
|
792
|
+
function cloneSnapshot(value) {
|
|
793
|
+
const cloned = cloneData(value, 0, { count: 0, active: new Set() });
|
|
794
|
+
if (!isPlainRecord(cloned))
|
|
795
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_snapshot");
|
|
796
|
+
return cloned;
|
|
797
|
+
}
|
|
798
|
+
function validateSnapshotShape(value) {
|
|
799
|
+
if (!isPlainRecord(value) || !allowedKeys(value, ["schemaVersion", "snapshotDigest", "target", "userTurns", "assistantTurns", "completeness", "terminalState", "postSendDelta"]) || !["schemaVersion", "snapshotDigest", "target", "userTurns", "assistantTurns", "completeness", "terminalState"].every(key => hasOwn(value, key)))
|
|
800
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_snapshot");
|
|
801
|
+
const wrapper = {
|
|
802
|
+
schemaVersion: OPERATION_OWNERSHIP_BASELINE_SCHEMA_VERSION,
|
|
803
|
+
operationId: "11111111-1111-4111-8111-111111111111",
|
|
804
|
+
requestDigest: "hmac-sha256:" + "0".repeat(64),
|
|
805
|
+
targetBindingDigest: "hmac-sha256:" + "0".repeat(64),
|
|
806
|
+
actionId: "11111111-1111-4111-8111-111111111111",
|
|
807
|
+
baseline: {
|
|
808
|
+
schemaVersion: value.schemaVersion,
|
|
809
|
+
snapshotDigest: value.snapshotDigest,
|
|
810
|
+
target: value.target,
|
|
811
|
+
userTurns: value.userTurns,
|
|
812
|
+
assistantTurns: value.assistantTurns,
|
|
813
|
+
completeness: value.completeness
|
|
814
|
+
},
|
|
815
|
+
observedAt: BASELINE_EPOCH
|
|
816
|
+
};
|
|
817
|
+
try {
|
|
818
|
+
assertOwnershipBaselineShape(wrapper);
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_snapshot");
|
|
822
|
+
}
|
|
823
|
+
if (value.terminalState !== "idle" && value.terminalState !== "generating" && value.terminalState !== "terminal" && value.terminalState !== "unknown")
|
|
824
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_snapshot");
|
|
825
|
+
if (value.postSendDelta !== undefined) {
|
|
826
|
+
const delta = value.postSendDelta;
|
|
827
|
+
if (!isPlainRecord(delta) || !exactKeys(delta, ["baselineSnapshotDigest", "addedUserEvidenceDigests", "deltaDigest"]) || !DIGEST_PATTERN.test(delta.baselineSnapshotDigest) || !DIGEST_PATTERN.test(delta.deltaDigest) || !Array.isArray(delta.addedUserEvidenceDigests) || delta.addedUserEvidenceDigests.length > 256 || delta.addedUserEvidenceDigests.some(item => typeof item !== "string" || !DIGEST_PATTERN.test(item)))
|
|
828
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_snapshot");
|
|
829
|
+
}
|
|
830
|
+
const ids = new Set();
|
|
831
|
+
for (const turn of [...value.userTurns, ...value.assistantTurns]) {
|
|
832
|
+
if (turn.stableId !== undefined) {
|
|
833
|
+
if (ids.has(turn.stableId))
|
|
834
|
+
throw new ProductionWorkSteerPrimitiveError("duplicate_identity");
|
|
835
|
+
ids.add(turn.stableId);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function redactTargetEvidence(target) {
|
|
840
|
+
return {
|
|
841
|
+
...target,
|
|
842
|
+
canonicalThreadUrl: { status: "unavailable", reason: "redacted" }
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
function matchesTargetEvidence(target, evidence) {
|
|
846
|
+
return identityEquals(evidence.provider, target.providerId)
|
|
847
|
+
&& identityEquals(evidence.browser, target.browserId)
|
|
848
|
+
&& identityEquals(evidence.tab, target.tabId)
|
|
849
|
+
&& optionalIdentityEquals(evidence.conversation, target.conversationId)
|
|
850
|
+
&& optionalIdentityEquals(evidence.canonicalThreadUrl, target.canonicalThreadUrl)
|
|
851
|
+
&& evidence.coordinationScope === target.coordinationScope
|
|
852
|
+
&& (target.evidenceProfile.authoritativeTabClaim === "unavailable" || evidence.authoritativeTabClaim.status === "available");
|
|
853
|
+
}
|
|
854
|
+
function matchesRedactedTarget(target, evidence) {
|
|
855
|
+
return identityEquals(evidence.provider, target.providerId)
|
|
856
|
+
&& identityEquals(evidence.browser, target.browserId)
|
|
857
|
+
&& identityEquals(evidence.tab, target.tabId)
|
|
858
|
+
&& optionalIdentityEquals(evidence.conversation, target.conversationId)
|
|
859
|
+
&& evidence.canonicalThreadUrl.status === "unavailable"
|
|
860
|
+
&& evidence.coordinationScope === target.coordinationScope
|
|
861
|
+
&& (target.evidenceProfile.authoritativeTabClaim === "unavailable" || evidence.authoritativeTabClaim.status === "available");
|
|
862
|
+
}
|
|
863
|
+
function identityEquals(value, expected) {
|
|
864
|
+
return isPlainRecord(value) && value.status === "available" && value.value === expected;
|
|
865
|
+
}
|
|
866
|
+
function optionalIdentityEquals(value, expected) {
|
|
867
|
+
return expected === undefined || identityEquals(value, expected);
|
|
868
|
+
}
|
|
869
|
+
function cloneTarget(value) {
|
|
870
|
+
if (!isPlainRecord(value) || hasAccessorInGraph(value))
|
|
871
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
872
|
+
const allowed = new Set([
|
|
873
|
+
"providerId", "browserId", "tabId", "coordinationScope", "tabClaimEvidenceDigest", "canonicalThreadUrl", "conversationId",
|
|
874
|
+
"userTurnBaselineDigest", "assistantTurnBaselineDigest", "configurationReceiptDigest", "evidenceProfile", "targetLifecycle",
|
|
875
|
+
"newTargetAnchorDigest", "blankTaskEvidenceDigest", "targetEstablishment"
|
|
876
|
+
]);
|
|
877
|
+
if (Reflect.ownKeys(value).some(key => typeof key !== "string" || !allowed.has(key))
|
|
878
|
+
|| !hasOwn(value, "providerId") || !hasOwn(value, "browserId") || !hasOwn(value, "tabId") || !hasOwn(value, "coordinationScope") || !hasOwn(value, "evidenceProfile"))
|
|
879
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
880
|
+
const cloned = cloneData(value, 0, { count: 0, active: new Set() });
|
|
881
|
+
if (!isPlainRecord(cloned) || !isSafeIdentifier(cloned.providerId) || !isSafeIdentifier(cloned.browserId) || !isSafeIdentifier(cloned.tabId) || (cloned.coordinationScope !== "process" && cloned.coordinationScope !== "provider"))
|
|
882
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
883
|
+
if (!isPlainRecord(cloned.evidenceProfile) || !exactKeys(cloned.evidenceProfile, ["providerIdentity", "stableTabId", "stableConversationId", "stableUserTurnId", "authoritativeTabClaim", "replacementTabRecovery"]))
|
|
884
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
885
|
+
for (const key of ["providerIdentity", "stableTabId", "stableConversationId", "stableUserTurnId", "authoritativeTabClaim"])
|
|
886
|
+
if (cloned.evidenceProfile[key] !== "required" && cloned.evidenceProfile[key] !== "unavailable")
|
|
887
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
888
|
+
if (typeof cloned.evidenceProfile.replacementTabRecovery !== "boolean")
|
|
889
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
890
|
+
for (const key of ["tabClaimEvidenceDigest", "userTurnBaselineDigest", "assistantTurnBaselineDigest", "configurationReceiptDigest", "newTargetAnchorDigest", "blankTaskEvidenceDigest"]) {
|
|
891
|
+
if (cloned[key] !== undefined)
|
|
892
|
+
assertDigest(cloned[key], key);
|
|
893
|
+
}
|
|
894
|
+
if (cloned.canonicalThreadUrl !== undefined)
|
|
895
|
+
assertCanonicalUrl(cloned.canonicalThreadUrl);
|
|
896
|
+
if (cloned.conversationId !== undefined)
|
|
897
|
+
assertIdentifier(cloned.conversationId, "conversationId");
|
|
898
|
+
if (cloned.targetLifecycle !== undefined && cloned.targetLifecycle !== "fixed" && cloned.targetLifecycle !== "new_pending" && cloned.targetLifecycle !== "new_established")
|
|
899
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
900
|
+
return deepFreeze(cloned);
|
|
901
|
+
}
|
|
902
|
+
function cloneBaseline(value) {
|
|
903
|
+
const cloned = cloneData(value, 0, { count: 0, active: new Set() });
|
|
904
|
+
if (!isPlainRecord(cloned))
|
|
905
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_baseline");
|
|
906
|
+
return deepFreeze(cloned);
|
|
907
|
+
}
|
|
908
|
+
function cloneData(value, depth, state) {
|
|
909
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
|
|
910
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
911
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
914
|
+
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint" || value === undefined)
|
|
915
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
916
|
+
if (depth > MAX_GRAPH_DEPTH || ++state.count > MAX_GRAPH_NODES)
|
|
917
|
+
throw new ProductionWorkSteerPrimitiveError("data_bounds");
|
|
918
|
+
const object = value;
|
|
919
|
+
if (state.active.has(object))
|
|
920
|
+
throw new ProductionWorkSteerPrimitiveError("cyclic_data");
|
|
921
|
+
state.active.add(object);
|
|
922
|
+
try {
|
|
923
|
+
let prototype;
|
|
924
|
+
try {
|
|
925
|
+
prototype = Object.getPrototypeOf(object);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
929
|
+
}
|
|
930
|
+
if (prototype !== Object.prototype && prototype !== null && !Array.isArray(object))
|
|
931
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
932
|
+
let keys;
|
|
933
|
+
let descriptors;
|
|
934
|
+
try {
|
|
935
|
+
keys = Reflect.ownKeys(object);
|
|
936
|
+
descriptors = Object.getOwnPropertyDescriptors(object);
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
940
|
+
}
|
|
941
|
+
if (keys.some(key => typeof key !== "string"))
|
|
942
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
943
|
+
const output = Array.isArray(object) ? [] : {};
|
|
944
|
+
for (const rawKey of keys) {
|
|
945
|
+
if (typeof rawKey !== "string")
|
|
946
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
947
|
+
const key = rawKey;
|
|
948
|
+
if (Array.isArray(object) && key === "length")
|
|
949
|
+
continue;
|
|
950
|
+
const descriptor = descriptors[key];
|
|
951
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
952
|
+
throw new ProductionWorkSteerPrimitiveError("accessor_data");
|
|
953
|
+
if (Array.isArray(object) && !/^\d+$/u.test(key))
|
|
954
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_data");
|
|
955
|
+
if (Array.isArray(output))
|
|
956
|
+
output[Number(key)] = cloneData(descriptor.value, depth + 1, state);
|
|
957
|
+
else
|
|
958
|
+
output[key] = cloneData(descriptor.value, depth + 1, state);
|
|
959
|
+
}
|
|
960
|
+
return output;
|
|
961
|
+
}
|
|
962
|
+
finally {
|
|
963
|
+
state.active.delete(object);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function deepFreeze(value, seen = new Set()) {
|
|
967
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
968
|
+
return value;
|
|
969
|
+
seen.add(value);
|
|
970
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
971
|
+
if (typeof key === "string")
|
|
972
|
+
deepFreeze(value[key], seen);
|
|
973
|
+
}
|
|
974
|
+
return Object.freeze(value);
|
|
975
|
+
}
|
|
976
|
+
/** Page and AbortSignal are opaque capabilities; inspect only the data graph. */
|
|
977
|
+
function hasUnsafeRequestGraph(value) {
|
|
978
|
+
try {
|
|
979
|
+
const keys = Reflect.ownKeys(value);
|
|
980
|
+
if (keys.some(key => typeof key !== "string"))
|
|
981
|
+
return true;
|
|
982
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
983
|
+
for (const key of keys) {
|
|
984
|
+
const descriptor = descriptors[key];
|
|
985
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
986
|
+
return true;
|
|
987
|
+
if (key === "page" || key === "signal")
|
|
988
|
+
continue;
|
|
989
|
+
if (hasAccessorInGraph(descriptor.value))
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
catch {
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function hasAccessorInGraph(value, state = { seen: new Set(), active: new Set(), count: 0 }, depth = 0) {
|
|
999
|
+
if (value === null || (typeof value !== "object" && typeof value !== "function"))
|
|
1000
|
+
return false;
|
|
1001
|
+
if (typeof value === "function")
|
|
1002
|
+
return false;
|
|
1003
|
+
if (depth > MAX_GRAPH_DEPTH || ++state.count > MAX_GRAPH_NODES)
|
|
1004
|
+
return true;
|
|
1005
|
+
const object = value;
|
|
1006
|
+
if (state.active.has(object))
|
|
1007
|
+
return true;
|
|
1008
|
+
if (state.seen.has(object))
|
|
1009
|
+
return false;
|
|
1010
|
+
state.seen.add(object);
|
|
1011
|
+
state.active.add(object);
|
|
1012
|
+
try {
|
|
1013
|
+
const keys = Reflect.ownKeys(object);
|
|
1014
|
+
if (keys.some(key => typeof key !== "string"))
|
|
1015
|
+
return true;
|
|
1016
|
+
const descriptors = Object.getOwnPropertyDescriptors(object);
|
|
1017
|
+
for (const key of keys) {
|
|
1018
|
+
const descriptor = descriptors[key];
|
|
1019
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
1020
|
+
return true;
|
|
1021
|
+
if (hasAccessorInGraph(descriptor.value, state, depth + 1))
|
|
1022
|
+
return true;
|
|
1023
|
+
}
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
catch {
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
finally {
|
|
1030
|
+
state.active.delete(object);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function exactKeys(value, allowed) {
|
|
1034
|
+
try {
|
|
1035
|
+
const keys = Reflect.ownKeys(value);
|
|
1036
|
+
return keys.length === allowed.length && keys.every(key => typeof key === "string" && allowed.includes(key));
|
|
1037
|
+
}
|
|
1038
|
+
catch {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function allowedKeys(value, allowed) {
|
|
1043
|
+
try {
|
|
1044
|
+
return Reflect.ownKeys(value).every(key => typeof key === "string" && allowed.includes(key));
|
|
1045
|
+
}
|
|
1046
|
+
catch {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
function hasOwn(value, key) {
|
|
1051
|
+
try {
|
|
1052
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
function own(value, key) {
|
|
1059
|
+
try {
|
|
1060
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1061
|
+
return descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined;
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
return undefined;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function isPlainRecord(value) {
|
|
1068
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
1069
|
+
return false;
|
|
1070
|
+
try {
|
|
1071
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1072
|
+
return prototype === Object.prototype || prototype === null;
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function isObject(value) {
|
|
1079
|
+
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
1080
|
+
}
|
|
1081
|
+
function isAbortSignal(value) {
|
|
1082
|
+
try {
|
|
1083
|
+
return typeof AbortSignal !== "undefined" && value instanceof AbortSignal;
|
|
1084
|
+
}
|
|
1085
|
+
catch {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function isSafeCapability(value) {
|
|
1090
|
+
return typeof value === "string" && CAPABILITY_PATTERN.test(value);
|
|
1091
|
+
}
|
|
1092
|
+
function isSafeIdentifier(value) {
|
|
1093
|
+
return typeof value === "string" && ID_PATTERN.test(value) && value.trim().length > 0 && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
1094
|
+
}
|
|
1095
|
+
function assertIdentifier(value, label) {
|
|
1096
|
+
if (!isSafeIdentifier(value))
|
|
1097
|
+
throw new ProductionWorkSteerPrimitiveError(`invalid_${label}`);
|
|
1098
|
+
}
|
|
1099
|
+
function assertUuid(value, label) {
|
|
1100
|
+
if (typeof value !== "string" || !UUID_PATTERN.test(value))
|
|
1101
|
+
throw new ProductionWorkSteerPrimitiveError(`invalid_${label}`);
|
|
1102
|
+
}
|
|
1103
|
+
function assertDigest(value, label) {
|
|
1104
|
+
if (typeof value !== "string" || !DIGEST_PATTERN.test(value))
|
|
1105
|
+
throw new ProductionWorkSteerPrimitiveError(`invalid_${label}`);
|
|
1106
|
+
}
|
|
1107
|
+
function assertCanonicalUrl(value) {
|
|
1108
|
+
if (typeof value !== "string" || value.length > 4096)
|
|
1109
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
1110
|
+
try {
|
|
1111
|
+
const parsed = new URL(value);
|
|
1112
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || parsed.toString() !== value)
|
|
1113
|
+
throw new Error();
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_target");
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function utf8Bytes(value) {
|
|
1120
|
+
return Buffer.byteLength(value, "utf8");
|
|
1121
|
+
}
|
|
1122
|
+
function makeClock(source) {
|
|
1123
|
+
let last = -1;
|
|
1124
|
+
let faulted = false;
|
|
1125
|
+
const now = () => {
|
|
1126
|
+
if (faulted)
|
|
1127
|
+
throw new ProductionWorkSteerPrimitiveError("clock_fault");
|
|
1128
|
+
let value;
|
|
1129
|
+
try {
|
|
1130
|
+
value = source();
|
|
1131
|
+
}
|
|
1132
|
+
catch {
|
|
1133
|
+
faulted = true;
|
|
1134
|
+
throw new ProductionWorkSteerPrimitiveError("clock_fault");
|
|
1135
|
+
}
|
|
1136
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > MAX_DEADLINE_AT || (last >= 0 && value < last)) {
|
|
1137
|
+
faulted = true;
|
|
1138
|
+
throw new ProductionWorkSteerPrimitiveError("clock_fault");
|
|
1139
|
+
}
|
|
1140
|
+
last = value;
|
|
1141
|
+
return value;
|
|
1142
|
+
};
|
|
1143
|
+
return Object.freeze({ now, isFaulted: () => faulted });
|
|
1144
|
+
}
|
|
1145
|
+
function readClock(clock) {
|
|
1146
|
+
try {
|
|
1147
|
+
return clock.now();
|
|
1148
|
+
}
|
|
1149
|
+
catch {
|
|
1150
|
+
throw new ProductionWorkSteerPrimitiveError("invalid_clock");
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function cancellationCode(call, clock) {
|
|
1154
|
+
if (clock.isFaulted())
|
|
1155
|
+
return "operation_state_corrupt";
|
|
1156
|
+
if (call.signal.aborted)
|
|
1157
|
+
return "operation_cancelled";
|
|
1158
|
+
let now;
|
|
1159
|
+
try {
|
|
1160
|
+
now = clock.now();
|
|
1161
|
+
}
|
|
1162
|
+
catch {
|
|
1163
|
+
return "operation_state_corrupt";
|
|
1164
|
+
}
|
|
1165
|
+
return now >= call.deadlineAt ? "operation_timeout" : undefined;
|
|
1166
|
+
}
|
|
1167
|
+
function remainingMs(call, clock) {
|
|
1168
|
+
if (call.signal.aborted || clock.isFaulted())
|
|
1169
|
+
return 0;
|
|
1170
|
+
let now;
|
|
1171
|
+
try {
|
|
1172
|
+
now = clock.now();
|
|
1173
|
+
}
|
|
1174
|
+
catch {
|
|
1175
|
+
return 0;
|
|
1176
|
+
}
|
|
1177
|
+
return Math.max(0, Math.min(MAX_TIMEOUT_MS, call.deadlineAt - now));
|
|
1178
|
+
}
|
|
1179
|
+
function normalizeInputError(error) {
|
|
1180
|
+
if (error instanceof ProductionWorkSteerPrimitiveError) {
|
|
1181
|
+
if (error.code === "target_binding_mismatch")
|
|
1182
|
+
return "target_binding_mismatch";
|
|
1183
|
+
if (error.code === "invalid_clock" || error.code === "clock_fault")
|
|
1184
|
+
return "operation_state_corrupt";
|
|
1185
|
+
}
|
|
1186
|
+
return "operation_state_corrupt";
|
|
1187
|
+
}
|
|
1188
|
+
function blocked(base, blockerCode, observationRequired, mutationBoundary, evidenceDigest) {
|
|
1189
|
+
return Object.freeze({
|
|
1190
|
+
...base,
|
|
1191
|
+
status: "blocked",
|
|
1192
|
+
blockerCode,
|
|
1193
|
+
observationRequired,
|
|
1194
|
+
mutationBoundary,
|
|
1195
|
+
...(evidenceDigest === undefined ? {} : { evidenceDigest })
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
function uncertain(base, blockerCode, quarantine, evidenceDigest) {
|
|
1199
|
+
return Object.freeze({
|
|
1200
|
+
...base,
|
|
1201
|
+
status: "uncertain",
|
|
1202
|
+
blockerCode,
|
|
1203
|
+
observationRequired: true,
|
|
1204
|
+
mutationBoundary: "control_may_have_occurred",
|
|
1205
|
+
quarantine,
|
|
1206
|
+
...(evidenceDigest === undefined ? {} : { evidenceDigest })
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
function safeDigest(options, domain, material) {
|
|
1210
|
+
try {
|
|
1211
|
+
const safeMaterial = deepFreeze(cloneData(material, 0, { count: 0, active: new Set() }));
|
|
1212
|
+
const result = options.evidenceDigest(domain, safeMaterial);
|
|
1213
|
+
return typeof result === "string" && DIGEST_PATTERN.test(result) ? result : undefined;
|
|
1214
|
+
}
|
|
1215
|
+
catch {
|
|
1216
|
+
return undefined;
|
|
1217
|
+
}
|
|
1218
|
+
}
|