pomerado 0.1.0 → 0.1.2
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/README.md +4 -2
- package/dist/typescript/authoring/core/SKILL.md +12 -0
- package/dist/typescript/authoring/examples/dates-and-dropdowns.js +5 -2
- package/dist/typescript/authoring/examples/dates-and-dropdowns.ts +5 -2
- package/dist/typescript/authoring/examples/dates-and-files.d.ts +4 -4
- package/dist/typescript/authoring/examples/dates-and-files.js +5 -5
- package/dist/typescript/authoring/examples/dates-and-files.ts +5 -5
- package/dist/typescript/authoring/examples/dialog-picker.d.ts +2 -2
- package/dist/typescript/authoring/examples/dialog-picker.js +3 -3
- package/dist/typescript/authoring/examples/dialog-picker.ts +3 -3
- package/dist/typescript/authoring/examples/native-dialog.d.ts +2 -2
- package/dist/typescript/authoring/examples/native-dialog.js +3 -3
- package/dist/typescript/authoring/examples/native-dialog.ts +3 -3
- package/dist/typescript/authoring/examples/navigation.d.ts +4 -4
- package/dist/typescript/authoring/examples/navigation.js +8 -5
- package/dist/typescript/authoring/examples/navigation.ts +8 -5
- package/dist/typescript/authoring/examples/parser.d.ts +2 -2
- package/dist/typescript/authoring/examples/parser.js +1 -1
- package/dist/typescript/authoring/examples/parser.ts +1 -1
- package/dist/typescript/authoring/examples/write-session.d.ts +13 -13
- package/dist/typescript/authoring/examples/write-session.js +9 -9
- package/dist/typescript/authoring/examples/write-session.ts +9 -9
- package/dist/typescript/src/guardian/upstream-policy.d.ts +2 -1
- package/dist/typescript/src/guardian/upstream-policy.js +10 -3
- package/dist/typescript/src/guardian/upstream-policy.md +9 -0
- package/dist/typescript/src/mint/contracts.d.ts +9 -2
- package/dist/typescript/src/mint/contracts.js +8 -0
- package/dist/typescript/src/mint/harness.js +5 -1
- package/dist/typescript/src/mint/incident-contracts.d.ts +6 -6
- package/dist/typescript/src/mint/publication-block.js +2 -1
- package/dist/typescript/src/mint/registry-feedback.js +4 -0
- package/dist/typescript/src/mint/sign-in-failure.d.ts +2 -2
- package/dist/typescript/src/registry/issues.d.ts +3 -1
- package/dist/typescript/src/registry/site-naming.d.ts +7 -0
- package/dist/typescript/src/registry/site-naming.js +6 -0
- package/dist/typescript/src/runtime/failure-detail.d.ts +10 -11
- package/dist/typescript/src/runtime/failure-detail.js +41 -45
- package/dist/typescript/tests/support/credential-masking-corpus.js +2 -2
- package/package.json +9 -2
- package/third-party/codex/LICENSE +201 -0
- package/third-party/codex/NOTICE +6 -0
package/README.md
CHANGED
|
@@ -246,9 +246,9 @@ corepack pnpm test:browser
|
|
|
246
246
|
|
|
247
247
|
This repository owns the portable tests for its shared core and local runtime, with synthetic fixtures and the existing Vitest and Playwright runners. Browser tests exercise native Playwright, minting through MCP, generated integration MCPs, authentication and autofill using local fixture sites and scripted model responses. They need no Cloud account or model API key. Tests for hosted services stay in the application repository.
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
Outside pull requests are not accepted until a Contributor License Agreement is in place. Issues are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for how changes land and [SECURITY.md](SECURITY.md) for reporting vulnerabilities privately.
|
|
250
250
|
|
|
251
|
-
Public tests use synthetic sites and data. Keep customer-specific incidents, private credentials and internal issue references out of public contributions.
|
|
251
|
+
Public tests use synthetic sites and data. Keep customer-specific incidents, private credentials and internal issue references out of public contributions. CI enforces this with a gitleaks secret scan and a public content scan. Run `node tools/check-public-content.ts` before you push. Link a public issue by its full URL.
|
|
252
252
|
|
|
253
253
|
- Build and test the package before publishing an explicit versioned release. The release workflow validates the packed artifact before npm publication.
|
|
254
254
|
- Adopt a tested release in Cloud through an exact dependency pin and locked integrity. Update the controller and sandbox images together.
|
|
@@ -259,3 +259,5 @@ The clone and build quickstart works independently of npm releases. Contributors
|
|
|
259
259
|
---
|
|
260
260
|
|
|
261
261
|
Copyright (c) 2026 Pomerado. Licensed under GNU Affero General Public License version 3 only (`AGPL-3.0-only`). See [LICENSE](LICENSE).
|
|
262
|
+
|
|
263
|
+
Third-party code keeps its own license. The Guardian policy in `typescript/src/guardian/upstream-policy.md` is adapted from [OpenAI Codex](https://github.com/openai/codex) under the Apache License 2.0. Its license and notice are in [third-party/codex/](third-party/codex/) and ship with the npm package.
|
|
@@ -140,6 +140,18 @@ request and the flow, never from one caller's account or example.
|
|
|
140
140
|
schema, inside `Schema.optional(...)` for an optional one; a `Schema.Date` keeps it only on
|
|
141
141
|
`Schema.optional(Schema.Date)` or `Schema.propertySignature(Schema.Date)`. Callers see each
|
|
142
142
|
beside its name.
|
|
143
|
+
- Shape inputs and outputs like Pomerado's own API, so every tool reads alike: field names in
|
|
144
|
+
snake_case; dates as `YYYY-MM-DD` and timestamps as ISO 8601 with an offset; money as an
|
|
145
|
+
integer in minor units with an ISO 4217 `currency` beside it, such as `total_minor` 12999 and
|
|
146
|
+
`currency` `"USD"`; enum values in lowercase snake_case (`"premium_economy"`); booleans named
|
|
147
|
+
as statements (`refundable`, not `is_refundable_flag`); lists named in the plural; and the
|
|
148
|
+
unit in the field name or its description (`duration_minutes`). Convert between these and the
|
|
149
|
+
site's own formats in code.
|
|
150
|
+
- Give every input field one `examples` annotation value, which callers, the docs and the Try it
|
|
151
|
+
form use to assemble a sample request: public, generic data such as a well-known airport code
|
|
152
|
+
(`examples: ["SFO"]`), a date a few weeks ahead or a common product category. Never use a value
|
|
153
|
+
from this session: not the caller's input, the owner's answers or anything the site showed
|
|
154
|
+
this account.
|
|
143
155
|
- Descriptions, titles, examples and defaults are published and reviewed for private data,
|
|
144
156
|
and an annotation never declares its contents public. Explain constraints without copying private input
|
|
145
157
|
or unneeded numeric identifiers; for a nonnegative safe integer,
|
|
@@ -20,8 +20,11 @@ const failures = [
|
|
|
20
20
|
export default defineOperation({
|
|
21
21
|
name: "set_departure",
|
|
22
22
|
input: Schema.Struct({
|
|
23
|
-
date: DateOnly.annotations({
|
|
24
|
-
|
|
23
|
+
date: DateOnly.annotations({
|
|
24
|
+
description: "Departure date, YYYY-MM-DD",
|
|
25
|
+
examples: ["2026-11-14"],
|
|
26
|
+
}),
|
|
27
|
+
cabin: Cabin.annotations({ description: "Cabin class to search", examples: ["economy"] }),
|
|
25
28
|
}),
|
|
26
29
|
output: Schema.Struct({
|
|
27
30
|
date: DateOnly.annotations({ description: "Departure date the form now holds, YYYY-MM-DD" }),
|
|
@@ -23,8 +23,11 @@ export default defineOperation(
|
|
|
23
23
|
{
|
|
24
24
|
name: "set_departure",
|
|
25
25
|
input: Schema.Struct({
|
|
26
|
-
date: DateOnly.annotations({
|
|
27
|
-
|
|
26
|
+
date: DateOnly.annotations({
|
|
27
|
+
description: "Departure date, YYYY-MM-DD",
|
|
28
|
+
examples: ["2026-11-14"],
|
|
29
|
+
}),
|
|
30
|
+
cabin: Cabin.annotations({ description: "Cabin class to search", examples: ["economy"] }),
|
|
28
31
|
}),
|
|
29
32
|
output: Schema.Struct({
|
|
30
33
|
date: DateOnly.annotations({ description: "Departure date the form now holds, YYYY-MM-DD" }),
|
|
@@ -9,11 +9,11 @@ export declare const pickTravelDate: import("../../src/runtime/kernel-operation.
|
|
|
9
9
|
}>;
|
|
10
10
|
declare const _default: import("../../src/runtime/kernel-operation.js").KernelOperation<{
|
|
11
11
|
readonly name: string;
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
12
|
+
readonly mime_type: string;
|
|
13
|
+
readonly content_base64: string;
|
|
14
14
|
}, {
|
|
15
|
-
readonly
|
|
16
|
-
readonly
|
|
15
|
+
readonly content_base64: string;
|
|
16
|
+
readonly mime_type: string;
|
|
17
17
|
readonly name: string;
|
|
18
18
|
}, {
|
|
19
19
|
readonly name: string;
|
|
@@ -59,10 +59,10 @@ export default defineOperation({
|
|
|
59
59
|
name: Schema.NonEmptyString.annotations({
|
|
60
60
|
description: "File name for the document, with its extension",
|
|
61
61
|
}),
|
|
62
|
-
|
|
62
|
+
mime_type: Schema.NonEmptyString.annotations({
|
|
63
63
|
description: "The document's media type, such as application/pdf",
|
|
64
64
|
}),
|
|
65
|
-
|
|
65
|
+
content_base64: Schema.String.annotations({ description: "The document's bytes, base64" }),
|
|
66
66
|
}),
|
|
67
67
|
output: Schema.Struct({
|
|
68
68
|
name: Schema.String.annotations({ description: "Name of the file the field now holds" }),
|
|
@@ -79,8 +79,8 @@ export default defineOperation({
|
|
|
79
79
|
await field.setInputFiles(
|
|
80
80
|
{
|
|
81
81
|
name: ${JSON.stringify(input.name)},
|
|
82
|
-
mimeType: ${JSON.stringify(input.
|
|
83
|
-
buffer: Buffer.from(${JSON.stringify(input.
|
|
82
|
+
mimeType: ${JSON.stringify(input.mime_type)},
|
|
83
|
+
buffer: Buffer.from(${JSON.stringify(input.content_base64)}, "base64"),
|
|
84
84
|
},
|
|
85
85
|
{ timeout: 30000 },
|
|
86
86
|
);
|
|
@@ -95,7 +95,7 @@ export default defineOperation({
|
|
|
95
95
|
if ("failure" in result)
|
|
96
96
|
throw new errors.OperationFailure(result.failure, { dispatch: "not_sent" });
|
|
97
97
|
const file = result.files[0];
|
|
98
|
-
const size = Buffer.from(input.
|
|
98
|
+
const size = Buffer.from(input.content_base64, "base64").byteLength;
|
|
99
99
|
// Selection is not proof the site accepted the upload; this reads back only the choice.
|
|
100
100
|
if (result.files.length !== 1 || file?.name !== input.name || file.size !== size)
|
|
101
101
|
throw new errors.OperationFailure("The chosen file did not read back", { dispatch: "sent" });
|
|
@@ -76,10 +76,10 @@ export default defineOperation(
|
|
|
76
76
|
name: Schema.NonEmptyString.annotations({
|
|
77
77
|
description: "File name for the document, with its extension",
|
|
78
78
|
}),
|
|
79
|
-
|
|
79
|
+
mime_type: Schema.NonEmptyString.annotations({
|
|
80
80
|
description: "The document's media type, such as application/pdf",
|
|
81
81
|
}),
|
|
82
|
-
|
|
82
|
+
content_base64: Schema.String.annotations({ description: "The document's bytes, base64" }),
|
|
83
83
|
}),
|
|
84
84
|
output: Schema.Struct({
|
|
85
85
|
name: Schema.String.annotations({ description: "Name of the file the field now holds" }),
|
|
@@ -97,8 +97,8 @@ export default defineOperation(
|
|
|
97
97
|
await field.setInputFiles(
|
|
98
98
|
{
|
|
99
99
|
name: ${JSON.stringify(input.name)},
|
|
100
|
-
mimeType: ${JSON.stringify(input.
|
|
101
|
-
buffer: Buffer.from(${JSON.stringify(input.
|
|
100
|
+
mimeType: ${JSON.stringify(input.mime_type)},
|
|
101
|
+
buffer: Buffer.from(${JSON.stringify(input.content_base64)}, "base64"),
|
|
102
102
|
},
|
|
103
103
|
{ timeout: 30000 },
|
|
104
104
|
);
|
|
@@ -118,7 +118,7 @@ export default defineOperation(
|
|
|
118
118
|
if ("failure" in result)
|
|
119
119
|
throw new errors.OperationFailure(result.failure, { dispatch: "not_sent" });
|
|
120
120
|
const file = result.files[0];
|
|
121
|
-
const size = Buffer.from(input.
|
|
121
|
+
const size = Buffer.from(input.content_base64, "base64").byteLength;
|
|
122
122
|
// Selection is not proof the site accepted the upload; this reads back only the choice.
|
|
123
123
|
if (result.files.length !== 1 || file?.name !== input.name || file.size !== size)
|
|
124
124
|
throw new errors.OperationFailure("The chosen file did not read back", { dispatch: "sent" });
|
|
@@ -5,8 +5,8 @@ declare const _default: import("../../src/runtime/kernel-operation.js").KernelOp
|
|
|
5
5
|
readonly key: string;
|
|
6
6
|
readonly query: string;
|
|
7
7
|
}, {
|
|
8
|
-
readonly
|
|
8
|
+
readonly selected_key: string;
|
|
9
9
|
}, {
|
|
10
|
-
readonly
|
|
10
|
+
readonly selected_key: string;
|
|
11
11
|
}>;
|
|
12
12
|
export default _default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { defineOperation } from "../../src/browser/index.js";
|
|
3
|
-
const Picked = Schema.Union(Schema.Struct({
|
|
3
|
+
const Picked = Schema.Union(Schema.Struct({ selected_key: Schema.NonEmptyString }), Schema.Struct({
|
|
4
4
|
failure: Schema.Literal("query_mismatch", "option_identity_mismatch", "committed_key_mismatch"),
|
|
5
5
|
}));
|
|
6
6
|
// These roles, names and data attributes describe one synthetic site's observed picker.
|
|
@@ -14,7 +14,7 @@ export default defineOperation({
|
|
|
14
14
|
}),
|
|
15
15
|
}),
|
|
16
16
|
output: Schema.Struct({
|
|
17
|
-
|
|
17
|
+
selected_key: Schema.NonEmptyString.annotations({
|
|
18
18
|
description: "Catalog key the page now shows as selected",
|
|
19
19
|
}),
|
|
20
20
|
}),
|
|
@@ -45,7 +45,7 @@ export default defineOperation({
|
|
|
45
45
|
const committed = page.getByRole("textbox", { name: "Selected catalog key", exact: true });
|
|
46
46
|
const until = Date.now() + 30000;
|
|
47
47
|
while (Date.now() < until) {
|
|
48
|
-
if ((await committed.inputValue({ timeout: 1000 })) === key) return {
|
|
48
|
+
if ((await committed.inputValue({ timeout: 1000 })) === key) return { selected_key: key };
|
|
49
49
|
await page.waitForTimeout(100);
|
|
50
50
|
}
|
|
51
51
|
return { failure: "committed_key_mismatch" };
|
|
@@ -2,7 +2,7 @@ import { Schema } from "effect";
|
|
|
2
2
|
import { defineOperation } from "../../src/browser/index.js";
|
|
3
3
|
|
|
4
4
|
const Picked = Schema.Union(
|
|
5
|
-
Schema.Struct({
|
|
5
|
+
Schema.Struct({ selected_key: Schema.NonEmptyString }),
|
|
6
6
|
Schema.Struct({
|
|
7
7
|
failure: Schema.Literal("query_mismatch", "option_identity_mismatch", "committed_key_mismatch"),
|
|
8
8
|
}),
|
|
@@ -20,7 +20,7 @@ export default defineOperation(
|
|
|
20
20
|
}),
|
|
21
21
|
}),
|
|
22
22
|
output: Schema.Struct({
|
|
23
|
-
|
|
23
|
+
selected_key: Schema.NonEmptyString.annotations({
|
|
24
24
|
description: "Catalog key the page now shows as selected",
|
|
25
25
|
}),
|
|
26
26
|
}),
|
|
@@ -52,7 +52,7 @@ export default defineOperation(
|
|
|
52
52
|
const committed = page.getByRole("textbox", { name: "Selected catalog key", exact: true });
|
|
53
53
|
const until = Date.now() + 30000;
|
|
54
54
|
while (Date.now() < until) {
|
|
55
|
-
if ((await committed.inputValue({ timeout: 1000 })) === key) return {
|
|
55
|
+
if ((await committed.inputValue({ timeout: 1000 })) === key) return { selected_key: key };
|
|
56
56
|
await page.waitForTimeout(100);
|
|
57
57
|
}
|
|
58
58
|
return { failure: "committed_key_mismatch" };
|
|
@@ -7,7 +7,7 @@ const InvoiceId = Schema.String.pipe(Schema.pattern(/^[A-Za-z0-9_-]{1,100}$/));
|
|
|
7
7
|
export default defineOperation({
|
|
8
8
|
name: "delete_invoice",
|
|
9
9
|
input: Schema.Struct({
|
|
10
|
-
|
|
10
|
+
invoice_id: InvoiceId.annotations({ description: "ID of the invoice to delete" }),
|
|
11
11
|
}),
|
|
12
12
|
output: Schema.Struct({
|
|
13
13
|
deleted: Schema.Boolean.annotations({
|
|
@@ -18,7 +18,7 @@ export default defineOperation({
|
|
|
18
18
|
const raised = await kernel.browsers.playwright.execute(sessionId, {
|
|
19
19
|
timeout_sec: 30,
|
|
20
20
|
code: `
|
|
21
|
-
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.
|
|
21
|
+
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.invoice_id)}, { exact: true }) });
|
|
22
22
|
if ((await row.count()) !== 1) return { failure: "invoice_missing" };
|
|
23
23
|
const shown = new Promise((resolve) => page.once("dialog", (dialog) => {
|
|
24
24
|
globalThis.dialog = dialog;
|
|
@@ -53,7 +53,7 @@ export default defineOperation({
|
|
|
53
53
|
? `await globalThis.dialog.accept(${JSON.stringify(decision.promptText ?? "")});`
|
|
54
54
|
: "await globalThis.dialog.dismiss();"}
|
|
55
55
|
delete globalThis.dialog;
|
|
56
|
-
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.
|
|
56
|
+
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.invoice_id)}, { exact: true }) });
|
|
57
57
|
// Read the result back: the row is gone only if the site deleted it.
|
|
58
58
|
await row.waitFor({ state: ${JSON.stringify(decision.choice === "accept" ? "detached" : "visible")}, timeout: 10000 });
|
|
59
59
|
return { deleted: (await row.count()) === 0 };
|
|
@@ -10,7 +10,7 @@ export default defineOperation(
|
|
|
10
10
|
{
|
|
11
11
|
name: "delete_invoice",
|
|
12
12
|
input: Schema.Struct({
|
|
13
|
-
|
|
13
|
+
invoice_id: InvoiceId.annotations({ description: "ID of the invoice to delete" }),
|
|
14
14
|
}),
|
|
15
15
|
output: Schema.Struct({
|
|
16
16
|
deleted: Schema.Boolean.annotations({
|
|
@@ -22,7 +22,7 @@ export default defineOperation(
|
|
|
22
22
|
const raised = await kernel.browsers.playwright.execute(sessionId, {
|
|
23
23
|
timeout_sec: 30,
|
|
24
24
|
code: `
|
|
25
|
-
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.
|
|
25
|
+
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.invoice_id)}, { exact: true }) });
|
|
26
26
|
if ((await row.count()) !== 1) return { failure: "invoice_missing" };
|
|
27
27
|
const shown = new Promise((resolve) => page.once("dialog", (dialog) => {
|
|
28
28
|
globalThis.dialog = dialog;
|
|
@@ -64,7 +64,7 @@ export default defineOperation(
|
|
|
64
64
|
: "await globalThis.dialog.dismiss();"
|
|
65
65
|
}
|
|
66
66
|
delete globalThis.dialog;
|
|
67
|
-
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.
|
|
67
|
+
const row = page.getByRole("row").filter({ has: page.getByText(${JSON.stringify(input.invoice_id)}, { exact: true }) });
|
|
68
68
|
// Read the result back: the row is gone only if the site deleted it.
|
|
69
69
|
await row.waitFor({ state: ${JSON.stringify(decision.choice === "accept" ? "detached" : "visible")}, timeout: 10000 });
|
|
70
70
|
return { deleted: (await row.count()) === 0 };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export declare const detailNavigation: import("../../src/runtime/kernel-operation.js").KernelOperation<{
|
|
2
|
-
readonly
|
|
2
|
+
readonly record_id: string;
|
|
3
3
|
}, {
|
|
4
|
-
readonly
|
|
4
|
+
readonly record_id: string;
|
|
5
5
|
}, {
|
|
6
|
-
readonly
|
|
6
|
+
readonly record_id: string;
|
|
7
7
|
readonly title: string;
|
|
8
8
|
}, {
|
|
9
|
-
readonly
|
|
9
|
+
readonly record_id: string;
|
|
10
10
|
readonly title: string;
|
|
11
11
|
}>;
|
|
12
12
|
declare const _default: import("../../src/runtime/kernel-operation.js").KernelOperation<{}, {}, {
|
|
@@ -9,16 +9,19 @@ const Detail = Schema.Union(Schema.Struct({ title: Schema.NonEmptyString }), Sch
|
|
|
9
9
|
export const detailNavigation = defineOperation({
|
|
10
10
|
name: "read_record_detail",
|
|
11
11
|
input: Schema.Struct({
|
|
12
|
-
|
|
12
|
+
record_id: RecordId.annotations({
|
|
13
|
+
description: "ID of the record to read, as in its URL",
|
|
14
|
+
examples: ["record_42"],
|
|
15
|
+
}),
|
|
13
16
|
}),
|
|
14
17
|
output: Schema.Struct({
|
|
15
|
-
|
|
18
|
+
record_id: RecordId.annotations({ description: "ID of the record read" }),
|
|
16
19
|
title: Schema.NonEmptyString.annotations({ description: "The record's title" }),
|
|
17
20
|
}),
|
|
18
21
|
}, async ({ kernel, sessionId, siteOrigin, siteDomain, input, errors }) => {
|
|
19
22
|
if (siteOrigin === undefined)
|
|
20
23
|
throw new errors.OperationFailure("No site origin for a live run", { dispatch: "not_sent" });
|
|
21
|
-
const target = new URL(`/records/${input.
|
|
24
|
+
const target = new URL(`/records/${input.record_id}`, siteOrigin);
|
|
22
25
|
const answer = await kernel.browsers.playwright.execute(sessionId, {
|
|
23
26
|
timeout_sec: 90,
|
|
24
27
|
code: `
|
|
@@ -28,7 +31,7 @@ export const detailNavigation = defineOperation({
|
|
|
28
31
|
const siteDomain = ${JSON.stringify(siteDomain ?? null)};
|
|
29
32
|
const onSite = (url) => siteDomain === null ? url.origin === ${JSON.stringify(siteOrigin)}
|
|
30
33
|
: url.protocol === "https:" && (url.hostname === siteDomain || url.hostname.endsWith("." + siteDomain));
|
|
31
|
-
const recordId = ${JSON.stringify(input.
|
|
34
|
+
const recordId = ${JSON.stringify(input.record_id)};
|
|
32
35
|
const detail = page.getByRole("region", { name: "Record details", exact: true });
|
|
33
36
|
const interstitial = page.getByRole("region", { name: "Continue to record", exact: true });
|
|
34
37
|
const proceed = interstitial.getByRole("button", { name: "Continue", exact: true });
|
|
@@ -81,7 +84,7 @@ export const detailNavigation = defineOperation({
|
|
|
81
84
|
const result = Schema.decodeUnknownSync(Detail)(answer.result);
|
|
82
85
|
if ("failure" in result)
|
|
83
86
|
throw new errors.OperationFailure(result.failure);
|
|
84
|
-
return {
|
|
87
|
+
return { record_id: input.record_id, title: result.title };
|
|
85
88
|
});
|
|
86
89
|
// A bounded first-page read. A fast exploration load does not remove the readiness wait.
|
|
87
90
|
export default defineOperation({
|
|
@@ -20,17 +20,20 @@ export const detailNavigation = defineOperation(
|
|
|
20
20
|
{
|
|
21
21
|
name: "read_record_detail",
|
|
22
22
|
input: Schema.Struct({
|
|
23
|
-
|
|
23
|
+
record_id: RecordId.annotations({
|
|
24
|
+
description: "ID of the record to read, as in its URL",
|
|
25
|
+
examples: ["record_42"],
|
|
26
|
+
}),
|
|
24
27
|
}),
|
|
25
28
|
output: Schema.Struct({
|
|
26
|
-
|
|
29
|
+
record_id: RecordId.annotations({ description: "ID of the record read" }),
|
|
27
30
|
title: Schema.NonEmptyString.annotations({ description: "The record's title" }),
|
|
28
31
|
}),
|
|
29
32
|
},
|
|
30
33
|
async ({ kernel, sessionId, siteOrigin, siteDomain, input, errors }) => {
|
|
31
34
|
if (siteOrigin === undefined)
|
|
32
35
|
throw new errors.OperationFailure("No site origin for a live run", { dispatch: "not_sent" });
|
|
33
|
-
const target = new URL(`/records/${input.
|
|
36
|
+
const target = new URL(`/records/${input.record_id}`, siteOrigin);
|
|
34
37
|
const answer = await kernel.browsers.playwright.execute(sessionId, {
|
|
35
38
|
timeout_sec: 90,
|
|
36
39
|
code: `
|
|
@@ -40,7 +43,7 @@ export const detailNavigation = defineOperation(
|
|
|
40
43
|
const siteDomain = ${JSON.stringify(siteDomain ?? null)};
|
|
41
44
|
const onSite = (url) => siteDomain === null ? url.origin === ${JSON.stringify(siteOrigin)}
|
|
42
45
|
: url.protocol === "https:" && (url.hostname === siteDomain || url.hostname.endsWith("." + siteDomain));
|
|
43
|
-
const recordId = ${JSON.stringify(input.
|
|
46
|
+
const recordId = ${JSON.stringify(input.record_id)};
|
|
44
47
|
const detail = page.getByRole("region", { name: "Record details", exact: true });
|
|
45
48
|
const interstitial = page.getByRole("region", { name: "Continue to record", exact: true });
|
|
46
49
|
const proceed = interstitial.getByRole("button", { name: "Continue", exact: true });
|
|
@@ -92,7 +95,7 @@ export const detailNavigation = defineOperation(
|
|
|
92
95
|
throw new errors.OperationFailure(String(answer.error), { stderr: answer.stderr });
|
|
93
96
|
const result = Schema.decodeUnknownSync(Detail)(answer.result);
|
|
94
97
|
if ("failure" in result) throw new errors.OperationFailure(result.failure);
|
|
95
|
-
return {
|
|
98
|
+
return { record_id: input.record_id, title: result.title };
|
|
96
99
|
},
|
|
97
100
|
);
|
|
98
101
|
|
|
@@ -5,7 +5,7 @@ declare const _default: import("../../src/runtime/kernel-operation.js").KernelOp
|
|
|
5
5
|
}, {
|
|
6
6
|
readonly invoices: readonly {
|
|
7
7
|
readonly id: string;
|
|
8
|
-
readonly
|
|
8
|
+
readonly total_minor: number;
|
|
9
9
|
readonly currency: "USD";
|
|
10
10
|
}[];
|
|
11
11
|
readonly complete: boolean;
|
|
@@ -14,7 +14,7 @@ declare const _default: import("../../src/runtime/kernel-operation.js").KernelOp
|
|
|
14
14
|
readonly invoices: readonly {
|
|
15
15
|
readonly currency: "USD";
|
|
16
16
|
readonly id: string;
|
|
17
|
-
readonly
|
|
17
|
+
readonly total_minor: number;
|
|
18
18
|
}[];
|
|
19
19
|
}>;
|
|
20
20
|
export default _default;
|
|
@@ -2,7 +2,7 @@ import { Schema } from "effect";
|
|
|
2
2
|
import { defineOperation } from "../../src/browser/index.js";
|
|
3
3
|
const Invoice = Schema.Struct({
|
|
4
4
|
id: Schema.NonEmptyString.annotations({ description: "The site's invoice ID" }),
|
|
5
|
-
|
|
5
|
+
total_minor: Schema.Int.annotations({ description: "Invoice total in minor units (cents)" }),
|
|
6
6
|
currency: Schema.Literal("USD").annotations({ description: "Currency of the total" }),
|
|
7
7
|
});
|
|
8
8
|
const Body = Schema.Struct({
|
|
@@ -3,7 +3,7 @@ import { defineOperation } from "../../src/browser/index.js";
|
|
|
3
3
|
|
|
4
4
|
const Invoice = Schema.Struct({
|
|
5
5
|
id: Schema.NonEmptyString.annotations({ description: "The site's invoice ID" }),
|
|
6
|
-
|
|
6
|
+
total_minor: Schema.Int.annotations({ description: "Invoice total in minor units (cents)" }),
|
|
7
7
|
currency: Schema.Literal("USD").annotations({ description: "Currency of the total" }),
|
|
8
8
|
});
|
|
9
9
|
const Body = Schema.Struct({
|
|
@@ -2,48 +2,48 @@ import { Schema } from "effect";
|
|
|
2
2
|
export declare const OrderInput: Schema.Struct<{
|
|
3
3
|
item: Schema.refine<string, typeof Schema.String>;
|
|
4
4
|
quantity: Schema.refine<number, typeof Schema.Int>;
|
|
5
|
-
|
|
5
|
+
gift_wrap: Schema.SchemaClass<boolean, boolean, never>;
|
|
6
6
|
}>;
|
|
7
7
|
/** Act step 1: fills the checkout on the site origin page and stops before the commit. */
|
|
8
8
|
export declare const fillStep: import("../../src/runtime/kernel-operation.js").KernelOperation<{
|
|
9
9
|
readonly item: string;
|
|
10
10
|
readonly quantity: number;
|
|
11
|
-
readonly
|
|
11
|
+
readonly gift_wrap: boolean;
|
|
12
12
|
}, {
|
|
13
|
-
readonly
|
|
13
|
+
readonly gift_wrap: boolean;
|
|
14
14
|
readonly item: string;
|
|
15
15
|
readonly quantity: number;
|
|
16
16
|
}, {
|
|
17
|
-
readonly
|
|
17
|
+
readonly review_shown: boolean;
|
|
18
18
|
}, {
|
|
19
|
-
readonly
|
|
19
|
+
readonly review_shown: boolean;
|
|
20
20
|
}>;
|
|
21
21
|
/** Act step 2: continues on the page step 1 left, commits once and reads the confirmation. */
|
|
22
22
|
export declare const placeStep: import("../../src/runtime/kernel-operation.js").KernelOperation<{
|
|
23
23
|
readonly item: string;
|
|
24
24
|
readonly quantity: number;
|
|
25
|
-
readonly
|
|
25
|
+
readonly gift_wrap: boolean;
|
|
26
26
|
}, {
|
|
27
|
-
readonly
|
|
27
|
+
readonly gift_wrap: boolean;
|
|
28
28
|
readonly item: string;
|
|
29
29
|
readonly quantity: number;
|
|
30
30
|
}, {
|
|
31
|
-
readonly
|
|
31
|
+
readonly order_number: string;
|
|
32
32
|
}, {
|
|
33
|
-
readonly
|
|
33
|
+
readonly order_number: string;
|
|
34
34
|
}>;
|
|
35
35
|
/** The composed script, published without another run: the whole flow from the site origin page. */
|
|
36
36
|
declare const _default: import("../../src/runtime/kernel-operation.js").KernelOperation<{
|
|
37
37
|
readonly item: string;
|
|
38
38
|
readonly quantity: number;
|
|
39
|
-
readonly
|
|
39
|
+
readonly gift_wrap: boolean;
|
|
40
40
|
}, {
|
|
41
|
-
readonly
|
|
41
|
+
readonly gift_wrap: boolean;
|
|
42
42
|
readonly item: string;
|
|
43
43
|
readonly quantity: number;
|
|
44
44
|
}, {
|
|
45
|
-
readonly
|
|
45
|
+
readonly order_number: string;
|
|
46
46
|
}, {
|
|
47
|
-
readonly
|
|
47
|
+
readonly order_number: string;
|
|
48
48
|
}>;
|
|
49
49
|
export default _default;
|
|
@@ -11,15 +11,15 @@ export const OrderInput = Schema.Struct({
|
|
|
11
11
|
quantity: Schema.Int.pipe(Schema.between(1, 99)).annotations({
|
|
12
12
|
description: "How many to order, 1 to 99",
|
|
13
13
|
}),
|
|
14
|
-
|
|
14
|
+
gift_wrap: Schema.Boolean.annotations({ description: "Whether to add gift wrap" }),
|
|
15
15
|
});
|
|
16
16
|
const Placed = Schema.Struct({
|
|
17
|
-
|
|
17
|
+
order_number: Schema.NonEmptyString.annotations({
|
|
18
18
|
description: "Order number from the confirmation",
|
|
19
19
|
}),
|
|
20
20
|
});
|
|
21
|
-
const Filled = Schema.Union(Schema.Struct({
|
|
22
|
-
const Confirmed = Schema.Union(Schema.Struct({
|
|
21
|
+
const Filled = Schema.Union(Schema.Struct({ review_shown: Schema.Literal(true) }), Schema.Struct({ failure: Schema.Literal("checkout_changed") }));
|
|
22
|
+
const Confirmed = Schema.Union(Schema.Struct({ order_number: Schema.NonEmptyString }), Schema.Struct({ failure: Schema.Literal("review_missing", "not_confirmed") }));
|
|
23
23
|
// The act steps and the composed script share these calls, so the published script repeats
|
|
24
24
|
// exactly what the session did.
|
|
25
25
|
/** From the site origin page: open the checkout, fill it from the input and stop at the review step. */
|
|
@@ -36,10 +36,10 @@ const fillCheckout = async ({ kernel, sessionId, siteOrigin, input, errors }) =>
|
|
|
36
36
|
if ((await form.count()) !== 1) return { failure: "checkout_changed" };
|
|
37
37
|
await form.getByLabel("Item", { exact: true }).fill(input.item, { timeout: 5000 });
|
|
38
38
|
await form.getByLabel("Quantity", { exact: true }).fill(String(input.quantity), { timeout: 5000 });
|
|
39
|
-
await form.getByLabel("Gift wrap", { exact: true }).setChecked(input.
|
|
39
|
+
await form.getByLabel("Gift wrap", { exact: true }).setChecked(input.gift_wrap, { timeout: 5000 });
|
|
40
40
|
await form.getByRole("button", { name: "Continue", exact: true }).click({ timeout: 5000 });
|
|
41
41
|
await page.getByRole("button", { name: "Place order", exact: true }).waitFor({ timeout: 10000 });
|
|
42
|
-
return {
|
|
42
|
+
return { review_shown: true };
|
|
43
43
|
`,
|
|
44
44
|
});
|
|
45
45
|
if (!answer.success)
|
|
@@ -63,7 +63,7 @@ const placeAndConfirm = async ({ kernel, sessionId, errors, verified, enteringCo
|
|
|
63
63
|
const confirmation = page.getByRole("status", { name: "Order confirmation", exact: true });
|
|
64
64
|
try { await confirmation.waitFor({ timeout: 15000 }); } catch { return { failure: "not_confirmed" }; }
|
|
65
65
|
const number = /^Order (ORD-[0-9]+)/.exec(await confirmation.innerText())?.[1];
|
|
66
|
-
return number === undefined ? { failure: "not_confirmed" } : {
|
|
66
|
+
return number === undefined ? { failure: "not_confirmed" } : { order_number: number };
|
|
67
67
|
`,
|
|
68
68
|
});
|
|
69
69
|
// A failure after the click leaves the write uncertain. Nothing here resubmits it.
|
|
@@ -83,11 +83,11 @@ export const fillStep = defineOperation({
|
|
|
83
83
|
name: "fill_checkout",
|
|
84
84
|
input: OrderInput,
|
|
85
85
|
output: Schema.Struct({
|
|
86
|
-
|
|
86
|
+
review_shown: Schema.Boolean.annotations({ description: "True once the review step shows" }),
|
|
87
87
|
}),
|
|
88
88
|
}, async (context) => {
|
|
89
89
|
await fillCheckout(context);
|
|
90
|
-
return {
|
|
90
|
+
return { review_shown: true };
|
|
91
91
|
});
|
|
92
92
|
/** Act step 2: continues on the page step 1 left, commits once and reads the confirmation. */
|
|
93
93
|
export const placeStep = defineOperation({ name: "place_order", input: OrderInput, output: Placed }, placeAndConfirm);
|
|
@@ -13,20 +13,20 @@ export const OrderInput = Schema.Struct({
|
|
|
13
13
|
quantity: Schema.Int.pipe(Schema.between(1, 99)).annotations({
|
|
14
14
|
description: "How many to order, 1 to 99",
|
|
15
15
|
}),
|
|
16
|
-
|
|
16
|
+
gift_wrap: Schema.Boolean.annotations({ description: "Whether to add gift wrap" }),
|
|
17
17
|
});
|
|
18
18
|
type OrderInput = typeof OrderInput.Type;
|
|
19
19
|
const Placed = Schema.Struct({
|
|
20
|
-
|
|
20
|
+
order_number: Schema.NonEmptyString.annotations({
|
|
21
21
|
description: "Order number from the confirmation",
|
|
22
22
|
}),
|
|
23
23
|
});
|
|
24
24
|
const Filled = Schema.Union(
|
|
25
|
-
Schema.Struct({
|
|
25
|
+
Schema.Struct({ review_shown: Schema.Literal(true) }),
|
|
26
26
|
Schema.Struct({ failure: Schema.Literal("checkout_changed") }),
|
|
27
27
|
);
|
|
28
28
|
const Confirmed = Schema.Union(
|
|
29
|
-
Schema.Struct({
|
|
29
|
+
Schema.Struct({ order_number: Schema.NonEmptyString }),
|
|
30
30
|
Schema.Struct({ failure: Schema.Literal("review_missing", "not_confirmed") }),
|
|
31
31
|
);
|
|
32
32
|
type Context = KernelOperationContext<OrderInput>;
|
|
@@ -48,10 +48,10 @@ const fillCheckout = async ({ kernel, sessionId, siteOrigin, input, errors }: Co
|
|
|
48
48
|
if ((await form.count()) !== 1) return { failure: "checkout_changed" };
|
|
49
49
|
await form.getByLabel("Item", { exact: true }).fill(input.item, { timeout: 5000 });
|
|
50
50
|
await form.getByLabel("Quantity", { exact: true }).fill(String(input.quantity), { timeout: 5000 });
|
|
51
|
-
await form.getByLabel("Gift wrap", { exact: true }).setChecked(input.
|
|
51
|
+
await form.getByLabel("Gift wrap", { exact: true }).setChecked(input.gift_wrap, { timeout: 5000 });
|
|
52
52
|
await form.getByRole("button", { name: "Continue", exact: true }).click({ timeout: 5000 });
|
|
53
53
|
await page.getByRole("button", { name: "Place order", exact: true }).waitFor({ timeout: 10000 });
|
|
54
|
-
return {
|
|
54
|
+
return { review_shown: true };
|
|
55
55
|
`,
|
|
56
56
|
});
|
|
57
57
|
if (!answer.success)
|
|
@@ -82,7 +82,7 @@ const placeAndConfirm = async ({
|
|
|
82
82
|
const confirmation = page.getByRole("status", { name: "Order confirmation", exact: true });
|
|
83
83
|
try { await confirmation.waitFor({ timeout: 15000 }); } catch { return { failure: "not_confirmed" }; }
|
|
84
84
|
const number = /^Order (ORD-[0-9]+)/.exec(await confirmation.innerText())?.[1];
|
|
85
|
-
return number === undefined ? { failure: "not_confirmed" } : {
|
|
85
|
+
return number === undefined ? { failure: "not_confirmed" } : { order_number: number };
|
|
86
86
|
`,
|
|
87
87
|
});
|
|
88
88
|
// A failure after the click leaves the write uncertain. Nothing here resubmits it.
|
|
@@ -104,12 +104,12 @@ export const fillStep = defineOperation(
|
|
|
104
104
|
name: "fill_checkout",
|
|
105
105
|
input: OrderInput,
|
|
106
106
|
output: Schema.Struct({
|
|
107
|
-
|
|
107
|
+
review_shown: Schema.Boolean.annotations({ description: "True once the review step shows" }),
|
|
108
108
|
}),
|
|
109
109
|
},
|
|
110
110
|
async (context) => {
|
|
111
111
|
await fillCheckout(context);
|
|
112
|
-
return {
|
|
112
|
+
return { review_shown: true };
|
|
113
113
|
},
|
|
114
114
|
);
|
|
115
115
|
|
|
@@ -10,7 +10,8 @@ export declare class UpstreamPolicySlotInvalid extends UpstreamPolicySlotInvalid
|
|
|
10
10
|
/**
|
|
11
11
|
* Puts Pomerado's policy in the upstream policy's one slot. An adapter calls it when it is built,
|
|
12
12
|
* so a policy file without the slot stops startup instead of Guardian reviewing without
|
|
13
|
-
* Pomerado's policy.
|
|
13
|
+
* Pomerado's policy. A leading HTML comment is dropped first, so the file's licence notice is
|
|
14
|
+
* neither sent to the model nor counted as a slot.
|
|
14
15
|
*/
|
|
15
16
|
export declare const withTenantPolicy: (upstreamPolicy: string, tenantPolicy: string) => string;
|
|
16
17
|
export {};
|