@apilift/cli 1.5.0 → 1.5.1
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/dist/apilift-collector.js +6 -4
- package/dist/apilift-native-host.js +17 -5
- package/dist/apilift.js +217 -98
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/skills/discover/SKILL.md +1 -1
- package/dist/plugin/skills/discover/references/operation-contract.md +1 -1
- package/dist/plugin/skills/discover/references/recovery.md +1 -1
- package/package.json +2 -2
|
@@ -28011,8 +28011,8 @@ var init_schema = __esm({
|
|
|
28011
28011
|
if (v2.generated && (v2.path !== void 0 || v2.fields !== void 0))
|
|
28012
28012
|
ctx.addIssue({ code: "custom", message: "generated excludes path and fields" });
|
|
28013
28013
|
});
|
|
28014
|
-
proofPointer = pointerSchema.refine((value) => !/\{(?:input|result|generated)\.[
|
|
28015
|
-
proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result:
|
|
28014
|
+
proofPointer = pointerSchema.refine((value) => value.split("/").every((part) => !/\{(?:input|result|generated)\./u.test(part) || /^\{input\.[A-Za-z][A-Za-z0-9_-]*\}$/u.test(part)), "A dynamic JSON Pointer segment must be exactly {input.name}");
|
|
28015
|
+
proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result: pointerSchema }), external_exports.strictObject({ path: proofPointer, value: valueSchema })]);
|
|
28016
28016
|
proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), absent: external_exports.literal(true).optional() }).superRefine((v2, ctx) => {
|
|
28017
28017
|
if (!!v2.equals === !!v2.absent)
|
|
28018
28018
|
ctx.addIssue({ code: "custom", message: "Exactly one of equals and absent is required" });
|
|
@@ -28092,7 +28092,9 @@ var init_schema = __esm({
|
|
|
28092
28092
|
if (!v2.allowedOrigins.includes(origin))
|
|
28093
28093
|
ctx.addIssue({ code: "custom", path: ["probes", origin], message: "Probe origin must be allowed" });
|
|
28094
28094
|
const probeUrl = parseUrl(probe.request.url);
|
|
28095
|
-
if (!["GET", "HEAD"].includes(probe.request.method)
|
|
28095
|
+
if (!probe.effect && !["GET", "HEAD"].includes(probe.request.method))
|
|
28096
|
+
ctx.addIssue({ code: "custom", path: ["probes", origin, "request", "method"], message: "Non-GET/HEAD read probes require effect: read" });
|
|
28097
|
+
if (probeUrl && probeUrl.origin !== origin)
|
|
28096
28098
|
ctx.addIssue({ code: "custom", path: ["probes", origin, "request"], message: "Probe must be a read request to its origin" });
|
|
28097
28099
|
}
|
|
28098
28100
|
});
|
|
@@ -28354,7 +28356,7 @@ function createCliApi(baseUrl, fetcher = fetch, token, sessionId) {
|
|
|
28354
28356
|
const transport = async (input2, init) => {
|
|
28355
28357
|
const method = init?.method ?? "GET";
|
|
28356
28358
|
const headers = new Headers(init?.headers);
|
|
28357
|
-
headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2");
|
|
28359
|
+
headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2,dynamic-bindings-v1");
|
|
28358
28360
|
headers.set("Apilift-Client", `cli/${cliVersion}`);
|
|
28359
28361
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
28360
28362
|
if (sessionId) headers.set("Apilift-Session", sessionId);
|
|
@@ -29457,8 +29457,8 @@ var responseSchema = external_exports.strictObject({ schema: jsonSchema.optional
|
|
|
29457
29457
|
if (v.generated && (v.path !== void 0 || v.fields !== void 0))
|
|
29458
29458
|
ctx.addIssue({ code: "custom", message: "generated excludes path and fields" });
|
|
29459
29459
|
});
|
|
29460
|
-
var proofPointer = pointerSchema.refine((value) => !/\{(?:input|result|generated)\.[
|
|
29461
|
-
var proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result:
|
|
29460
|
+
var proofPointer = pointerSchema.refine((value) => value.split("/").every((part) => !/\{(?:input|result|generated)\./u.test(part) || /^\{input\.[A-Za-z][A-Za-z0-9_-]*\}$/u.test(part)), "A dynamic JSON Pointer segment must be exactly {input.name}");
|
|
29461
|
+
var proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result: pointerSchema }), external_exports.strictObject({ path: proofPointer, value: valueSchema })]);
|
|
29462
29462
|
var proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), absent: external_exports.literal(true).optional() }).superRefine((v, ctx) => {
|
|
29463
29463
|
if (!!v.equals === !!v.absent)
|
|
29464
29464
|
ctx.addIssue({ code: "custom", message: "Exactly one of equals and absent is required" });
|
|
@@ -29538,7 +29538,9 @@ var transportDefinitionSchema = external_exports.strictObject({ title, descripti
|
|
|
29538
29538
|
if (!v.allowedOrigins.includes(origin))
|
|
29539
29539
|
ctx.addIssue({ code: "custom", path: ["probes", origin], message: "Probe origin must be allowed" });
|
|
29540
29540
|
const probeUrl = parseUrl(probe.request.url);
|
|
29541
|
-
if (!["GET", "HEAD"].includes(probe.request.method)
|
|
29541
|
+
if (!probe.effect && !["GET", "HEAD"].includes(probe.request.method))
|
|
29542
|
+
ctx.addIssue({ code: "custom", path: ["probes", origin, "request", "method"], message: "Non-GET/HEAD read probes require effect: read" });
|
|
29543
|
+
if (probeUrl && probeUrl.origin !== origin)
|
|
29542
29544
|
ctx.addIssue({ code: "custom", path: ["probes", origin, "request"], message: "Probe must be a read request to its origin" });
|
|
29543
29545
|
}
|
|
29544
29546
|
});
|
|
@@ -29587,8 +29589,13 @@ function inspectRequest(request2, inputs, generated, proof, base2, issues) {
|
|
|
29587
29589
|
issues.push({ path: path2, message: "Unknown binding" });
|
|
29588
29590
|
return;
|
|
29589
29591
|
}
|
|
29590
|
-
for (const [key, child] of entries)
|
|
29592
|
+
for (const [key, child] of entries) {
|
|
29593
|
+
if ((path2 === `${base2}/body` || path2.startsWith(`${base2}/body/`)) && /\{(?:input|generated|result|unresolved)\./u.test(key))
|
|
29594
|
+
inspectText(key, `${path2}/${key}`);
|
|
29595
|
+
else if (/\{(?:input|generated|result)\./u.test(key))
|
|
29596
|
+
issues.push({ path: `${path2}/${key}`, message: "Dynamic keys are supported only in JSON request bodies" });
|
|
29591
29597
|
walk(child, `${path2}/${key}`);
|
|
29598
|
+
}
|
|
29592
29599
|
};
|
|
29593
29600
|
inspectText(request2.url, `${base2}/url`);
|
|
29594
29601
|
for (const [header, value] of Object.entries(request2.headers ?? {})) {
|
|
@@ -29637,6 +29644,11 @@ function validateDefinition(kind, name, value) {
|
|
|
29637
29644
|
for (const [i, comparison] of (definition2.proof?.select?.where ?? []).entries())
|
|
29638
29645
|
if ("input" in comparison && !names2.includes(comparison.input))
|
|
29639
29646
|
issues.push({ path: `/proof/select/where/${i}/input`, message: "Unknown input" });
|
|
29647
|
+
const proofPaths = [...definition2.proof?.equals ?? [], ...definition2.proof?.select?.where ?? [], ...definition2.proof?.select ? [definition2.proof.select] : []];
|
|
29648
|
+
for (const item of proofPaths)
|
|
29649
|
+
for (const match of item.path.matchAll(/\{input\.([^{}]+)\}/gu))
|
|
29650
|
+
if (!names2.includes(match[1]))
|
|
29651
|
+
issues.push({ path: "/proof", message: `Unknown pointer input ${match[1]}` });
|
|
29640
29652
|
if (definition2.response?.generated && !generated.has(definition2.response.generated))
|
|
29641
29653
|
issues.push({ path: "/response/generated", message: "Unknown generated value" });
|
|
29642
29654
|
}
|
|
@@ -29821,7 +29833,7 @@ function createCliApi(baseUrl, fetcher = fetch, token, sessionId) {
|
|
|
29821
29833
|
const transport = async (input2, init) => {
|
|
29822
29834
|
const method = init?.method ?? "GET";
|
|
29823
29835
|
const headers = new Headers(init?.headers);
|
|
29824
|
-
headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2");
|
|
29836
|
+
headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2,dynamic-bindings-v1");
|
|
29825
29837
|
headers.set("Apilift-Client", `cli/${cliVersion}`);
|
|
29826
29838
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
29827
29839
|
if (sessionId) headers.set("Apilift-Session", sessionId);
|
package/dist/apilift.js
CHANGED
|
@@ -28075,8 +28075,8 @@ var init_schema = __esm({
|
|
|
28075
28075
|
if (v.generated && (v.path !== void 0 || v.fields !== void 0))
|
|
28076
28076
|
ctx.addIssue({ code: "custom", message: "generated excludes path and fields" });
|
|
28077
28077
|
});
|
|
28078
|
-
proofPointer = pointerSchema.refine((value) => !/\{(?:input|result|generated)\.[
|
|
28079
|
-
proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result:
|
|
28078
|
+
proofPointer = pointerSchema.refine((value) => value.split("/").every((part) => !/\{(?:input|result|generated)\./u.test(part) || /^\{input\.[A-Za-z][A-Za-z0-9_-]*\}$/u.test(part)), "A dynamic JSON Pointer segment must be exactly {input.name}");
|
|
28079
|
+
proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result: pointerSchema }), external_exports.strictObject({ path: proofPointer, value: valueSchema })]);
|
|
28080
28080
|
proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), absent: external_exports.literal(true).optional() }).superRefine((v, ctx) => {
|
|
28081
28081
|
if (!!v.equals === !!v.absent)
|
|
28082
28082
|
ctx.addIssue({ code: "custom", message: "Exactly one of equals and absent is required" });
|
|
@@ -28156,7 +28156,9 @@ var init_schema = __esm({
|
|
|
28156
28156
|
if (!v.allowedOrigins.includes(origin))
|
|
28157
28157
|
ctx.addIssue({ code: "custom", path: ["probes", origin], message: "Probe origin must be allowed" });
|
|
28158
28158
|
const probeUrl = parseUrl(probe.request.url);
|
|
28159
|
-
if (!["GET", "HEAD"].includes(probe.request.method)
|
|
28159
|
+
if (!probe.effect && !["GET", "HEAD"].includes(probe.request.method))
|
|
28160
|
+
ctx.addIssue({ code: "custom", path: ["probes", origin, "request", "method"], message: "Non-GET/HEAD read probes require effect: read" });
|
|
28161
|
+
if (probeUrl && probeUrl.origin !== origin)
|
|
28160
28162
|
ctx.addIssue({ code: "custom", path: ["probes", origin, "request"], message: "Probe must be a read request to its origin" });
|
|
28161
28163
|
}
|
|
28162
28164
|
});
|
|
@@ -28439,8 +28441,13 @@ function inspectRequest(request2, inputs, generated, proof, base2, issues) {
|
|
|
28439
28441
|
issues.push({ path: path2, message: "Unknown binding" });
|
|
28440
28442
|
return;
|
|
28441
28443
|
}
|
|
28442
|
-
for (const [key, child] of entries)
|
|
28444
|
+
for (const [key, child] of entries) {
|
|
28445
|
+
if ((path2 === `${base2}/body` || path2.startsWith(`${base2}/body/`)) && /\{(?:input|generated|result|unresolved)\./u.test(key))
|
|
28446
|
+
inspectText(key, `${path2}/${key}`);
|
|
28447
|
+
else if (/\{(?:input|generated|result)\./u.test(key))
|
|
28448
|
+
issues.push({ path: `${path2}/${key}`, message: "Dynamic keys are supported only in JSON request bodies" });
|
|
28443
28449
|
walk(child, `${path2}/${key}`);
|
|
28450
|
+
}
|
|
28444
28451
|
};
|
|
28445
28452
|
inspectText(request2.url, `${base2}/url`);
|
|
28446
28453
|
for (const [header, value] of Object.entries(request2.headers ?? {})) {
|
|
@@ -28489,6 +28496,11 @@ function validateDefinition(kind, name, value) {
|
|
|
28489
28496
|
for (const [i, comparison] of (definition2.proof?.select?.where ?? []).entries())
|
|
28490
28497
|
if ("input" in comparison && !names2.includes(comparison.input))
|
|
28491
28498
|
issues.push({ path: `/proof/select/where/${i}/input`, message: "Unknown input" });
|
|
28499
|
+
const proofPaths = [...definition2.proof?.equals ?? [], ...definition2.proof?.select?.where ?? [], ...definition2.proof?.select ? [definition2.proof.select] : []];
|
|
28500
|
+
for (const item of proofPaths)
|
|
28501
|
+
for (const match of item.path.matchAll(/\{input\.([^{}]+)\}/gu))
|
|
28502
|
+
if (!names2.includes(match[1]))
|
|
28503
|
+
issues.push({ path: "/proof", message: `Unknown pointer input ${match[1]}` });
|
|
28492
28504
|
if (definition2.response?.generated && !generated.has(definition2.response.generated))
|
|
28493
28505
|
issues.push({ path: "/response/generated", message: "Unknown generated value" });
|
|
28494
28506
|
}
|
|
@@ -28813,7 +28825,7 @@ function createCliApi(baseUrl, fetcher = fetch, token, sessionId) {
|
|
|
28813
28825
|
const transport = async (input2, init) => {
|
|
28814
28826
|
const method = init?.method ?? "GET";
|
|
28815
28827
|
const headers = new Headers(init?.headers);
|
|
28816
|
-
headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2");
|
|
28828
|
+
headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2,dynamic-bindings-v1");
|
|
28817
28829
|
headers.set("Apilift-Client", `cli/${cliVersion}`);
|
|
28818
28830
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
28819
28831
|
if (sessionId) headers.set("Apilift-Session", sessionId);
|
|
@@ -30782,7 +30794,7 @@ function draftTemplate(kind) {
|
|
|
30782
30794
|
const probeGuidance = kind === "transport" ? [
|
|
30783
30795
|
"// probes maps every exact allowedOrigins origin to a harmless documented read request and response contract.",
|
|
30784
30796
|
'// Shape for each origin: { "request": { "method": "GET", "url": "<documented read URL>" }, "response": {} }',
|
|
30785
|
-
|
|
30797
|
+
'// The origin key has no path or trailing slash. request.method is GET or HEAD, or set effect: "read" on that probe for a documented/observed read through POST or another method; request.url may be relative to baseUrl or an absolute URL at that origin.',
|
|
30786
30798
|
"// response may be empty, or contain schema (JSON Schema) and path (JSON Pointer selecting the result). It has no status, body or json fields.",
|
|
30787
30799
|
"// A documented HTML or plain-text read can use response: {}; a successful non-JSON body is reported only as a format/status summary, never copied into verification output.",
|
|
30788
30800
|
"// A non-JSON summary proves reachability only; inspect the signed-in UI and confirm the page's meaning before accepting verification.review.",
|
|
@@ -30794,7 +30806,7 @@ function draftTemplate(kind) {
|
|
|
30794
30806
|
""
|
|
30795
30807
|
].join("\n") : "";
|
|
30796
30808
|
const tabGuidance = ["action", "transport"].includes(kind) ? "// capture.tab is a selector returned by browser tabs; it selects the same signed-in Chrome tab for capture and discover run, including connection probes. Leave other sessions' tabs untouched.\n" : "";
|
|
30797
|
-
const captureGuidance = kind === "action" ? '// Public descriptions explain behavior, consequences and limits; do not instruct consumers to repeat runtime proof or browser checks. Author verification is separate from consumer judgment.\n// Request JSON/query uses typed {"$input":"name"} bindings; URL segments use {input.name}. Resolve every {unresolved.N} marker from retained evidence before running.\n// Discovery-only mutation proof compares a readback path with exactly one input, primary result path or internal JSON value: equals: [{"path":"/ready","value":true}]. Public literals require semantic review; fixed proof expectations are not caller flags.\n// Proof URLs bind returned identifiers with {result./id}; JSON bodies/query use {"$result":"/id"}.
|
|
30809
|
+
const captureGuidance = kind === "action" ? '// Public descriptions explain behavior, consequences and limits; do not instruct consumers to repeat runtime proof or browser checks. Author verification is separate from consumer judgment.\n// Request JSON/query uses typed {"$input":"name"} bindings; URL segments use {input.name}. Resolve every {unresolved.N} marker from retained evidence before running.\n// A readback through POST requires proof.effect: "read" alongside proof.request; HTTP method alone does not determine semantic effect.\n// JSON object keys may use {input.name}, e.g. {"{input.attribute_id}":{"$input":"value"}}.\n// Discovery-only mutation proof compares a readback path with exactly one input, primary result path or internal JSON value: equals: [{"path":"/ready","value":true}]. Public literals require semantic review; fixed proof expectations are not caller flags.\n// Proof URLs bind returned identifiers with {result./id}; JSON bodies/query use {"$result":"/id"}. Readback pointers may use a complete {input.name} segment for a dynamic key; slashes and tildes in its value are escaped.\n// For one member of a nested list: select: {"path":"/ticket/fields","where":[{"path":"/id","result":"/id"}]}; equals paths then refer to the single matching member. Compare identity and meaningful changed fields, not entire objects.\n// Repair a failed response/proof with discover run --proof-only; it reuses the saved primary response and sends only the readback. Publication still needs a successful test and accepted semantic review.\n// For large collections, investigate native search/filter/detail routes before enumeration. Search saved results locally and inspect only matching records; preserve structured dynamic fields.\n// After selecting a captured request, discover capture auth runs a bounded default search without authField or authScope. Set either only to narrow an observed field or location; browser-managed cookies need no locator.\n' : "";
|
|
30798
30810
|
return `${head}${probeGuidance}${tabGuidance}${captureGuidance}${JSON.stringify(templates[kind], null, 2)}
|
|
30799
30811
|
`;
|
|
30800
30812
|
}
|
|
@@ -30813,7 +30825,7 @@ var init_envelope = __esm({
|
|
|
30813
30825
|
|
|
30814
30826
|
// src/discovery/drafts.ts
|
|
30815
30827
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
30816
|
-
import { chmodSync as chmodSync4, existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync,
|
|
30828
|
+
import { chmodSync as chmodSync4, existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, rmSync as rmSync3, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
30817
30829
|
import { basename, dirname as dirname2, join as join5 } from "node:path";
|
|
30818
30830
|
function pathFor(root, appSlug, kind, name) {
|
|
30819
30831
|
if (!slugPattern.test(appSlug)) throw new Error("App slug is invalid");
|
|
@@ -30845,6 +30857,7 @@ var init_drafts = __esm({
|
|
|
30845
30857
|
this.state = state;
|
|
30846
30858
|
this.root = root;
|
|
30847
30859
|
this.state.db.exec("CREATE TABLE IF NOT EXISTS discovery_draft (id TEXT PRIMARY KEY, app_slug TEXT NOT NULL, app_id TEXT, kind TEXT NOT NULL, name TEXT NOT NULL, owner_session_id TEXT, file_path TEXT NOT NULL, base_version INTEGER, UNIQUE(app_slug,kind,name)); CREATE TABLE IF NOT EXISTS selected_discovery_draft (session_id TEXT PRIMARY KEY, draft_id TEXT NOT NULL REFERENCES discovery_draft(id) ON DELETE CASCADE); CREATE TABLE IF NOT EXISTS discovery_session (session_id TEXT PRIMARY KEY, app_slug TEXT NOT NULL, app_id TEXT, title TEXT NOT NULL); CREATE TABLE IF NOT EXISTS discovery_publication (draft_id TEXT PRIMARY KEY REFERENCES discovery_draft(id) ON DELETE CASCADE, request_id TEXT NOT NULL, request_body TEXT NOT NULL); CREATE TABLE IF NOT EXISTS discovery_evidence (draft_id TEXT PRIMARY KEY REFERENCES discovery_draft(id) ON DELETE CASCADE, fingerprint TEXT NOT NULL, verified_at TEXT NOT NULL, result_path TEXT NOT NULL, data_json TEXT NOT NULL); CREATE TABLE IF NOT EXISTS discovery_attempt (draft_id TEXT PRIMARY KEY REFERENCES discovery_draft(id) ON DELETE CASCADE, fingerprint TEXT NOT NULL, state TEXT NOT NULL, recorded_at TEXT NOT NULL)");
|
|
30860
|
+
this.state.db.exec("CREATE TABLE IF NOT EXISTS discovery_deferred (draft_id TEXT PRIMARY KEY REFERENCES discovery_draft(id) ON DELETE CASCADE, session_id TEXT NOT NULL); CREATE INDEX IF NOT EXISTS discovery_deferred_session ON discovery_deferred(session_id); CREATE TABLE IF NOT EXISTS discovery_finished (session_id TEXT PRIMARY KEY, result_json TEXT NOT NULL); CREATE TABLE IF NOT EXISTS discovery_cleanup (draft_id TEXT PRIMARY KEY, file_path TEXT NOT NULL UNIQUE, paths_json TEXT NOT NULL, scope_id TEXT)");
|
|
30848
30861
|
this.state.db.exec("SAVEPOINT discovery_draft_schema");
|
|
30849
30862
|
try {
|
|
30850
30863
|
this.state.db.prepare("UPDATE discovery_draft SET id=id WHERE 0").run();
|
|
@@ -30933,7 +30946,24 @@ var init_drafts = __esm({
|
|
|
30933
30946
|
close(sessionId) {
|
|
30934
30947
|
const current = this.session(sessionId);
|
|
30935
30948
|
if (!current) return null;
|
|
30936
|
-
this.
|
|
30949
|
+
this.discard(sessionId);
|
|
30950
|
+
while (true) {
|
|
30951
|
+
this.state.db.exec("BEGIN IMMEDIATE");
|
|
30952
|
+
let next;
|
|
30953
|
+
try {
|
|
30954
|
+
next = this.state.db.prepare("SELECT d.id FROM discovery_draft d JOIN discovery_deferred r ON r.draft_id=d.id WHERE r.session_id=? AND d.owner_session_id IS NULL LIMIT 1").get(sessionId);
|
|
30955
|
+
if (next) {
|
|
30956
|
+
this.state.db.prepare("UPDATE discovery_draft SET owner_session_id=? WHERE id=?").run(sessionId, next.id);
|
|
30957
|
+
this.state.db.prepare("INSERT INTO selected_discovery_draft (session_id,draft_id) VALUES (?,?)").run(sessionId, next.id);
|
|
30958
|
+
}
|
|
30959
|
+
this.state.db.exec("COMMIT");
|
|
30960
|
+
} catch (error62) {
|
|
30961
|
+
this.state.db.exec("ROLLBACK");
|
|
30962
|
+
throw error62;
|
|
30963
|
+
}
|
|
30964
|
+
if (!next) break;
|
|
30965
|
+
this.discard(sessionId);
|
|
30966
|
+
}
|
|
30937
30967
|
this.state.db.prepare("DELETE FROM discovery_session WHERE session_id=?").run(sessionId);
|
|
30938
30968
|
return current;
|
|
30939
30969
|
}
|
|
@@ -30984,40 +31014,64 @@ var init_drafts = __esm({
|
|
|
30984
31014
|
this.requestAttempts();
|
|
30985
31015
|
this.state.db.prepare("INSERT INTO discovery_request_attempt (draft_id,fingerprint,state,response_path,response_digest) VALUES (?,?,?,?,?) ON CONFLICT(draft_id,fingerprint) DO UPDATE SET state=excluded.state,response_path=COALESCE(excluded.response_path,discovery_request_attempt.response_path),response_digest=COALESCE(excluded.response_digest,discovery_request_attempt.response_digest)").run(draftId, fingerprint2, state, responsePath ?? null, responseDigest ?? null);
|
|
30986
31016
|
}
|
|
30987
|
-
|
|
31017
|
+
artifactPath(draftId, name) {
|
|
31018
|
+
const directory = join5(this.root, "draft-evidence", draftId, randomUUID4());
|
|
31019
|
+
mkdirSync5(directory, { recursive: true, mode: 448 });
|
|
31020
|
+
return join5(directory, name);
|
|
31021
|
+
}
|
|
31022
|
+
finished(sessionId) {
|
|
31023
|
+
const row = this.state.db.prepare("SELECT result_json FROM discovery_finished WHERE session_id=?").get(sessionId);
|
|
31024
|
+
return row ? JSON.parse(row.result_json) : null;
|
|
31025
|
+
}
|
|
31026
|
+
retire(sessionId, publishedResult) {
|
|
30988
31027
|
const draft = this.selected(sessionId);
|
|
30989
31028
|
if (!draft) return null;
|
|
30990
|
-
if (this.publication(draft.id)) throw new DraftConflict("selected-draft", "A publication request may have been sent; reconcile it before discarding this draft");
|
|
31029
|
+
if (publishedResult === void 0 && this.publication(draft.id)) throw new DraftConflict("selected-draft", "A publication request may have been sent; reconcile it before discarding this draft");
|
|
30991
31030
|
withBaselines(this.root, (baselines) => baselines.closeObservation(draft.id, "released"));
|
|
31031
|
+
const paths2 = [draft.filePath, `${draft.filePath}.current.json`, join5(this.root, "draft-evidence", draft.id)];
|
|
31032
|
+
const evidence = this.evidence(draft.id);
|
|
31033
|
+
if (evidence) paths2.push(evidence.resultPath);
|
|
31034
|
+
if (this.hasTable("discovery_request_attempt")) for (const row of this.state.db.prepare("SELECT response_path FROM discovery_request_attempt WHERE draft_id=? AND response_path IS NOT NULL").all(draft.id)) paths2.push(row.response_path);
|
|
31035
|
+
const scope2 = this.hasTable("discovery_capture") ? this.state.db.prepare("SELECT evidence_scope_id FROM discovery_capture WHERE draft_id=?").get(draft.id) : null;
|
|
30992
31036
|
this.state.db.exec("BEGIN IMMEDIATE");
|
|
30993
31037
|
try {
|
|
30994
|
-
this.state.db.prepare("
|
|
31038
|
+
this.state.db.prepare("INSERT INTO discovery_cleanup (draft_id,file_path,paths_json,scope_id) VALUES (?,?,?,?)").run(draft.id, draft.filePath, JSON.stringify(paths2), scope2?.evidence_scope_id ?? null);
|
|
31039
|
+
if (publishedResult !== void 0) this.state.db.prepare("INSERT INTO discovery_finished (session_id,result_json) VALUES (?,?) ON CONFLICT(session_id) DO UPDATE SET result_json=excluded.result_json").run(sessionId, JSON.stringify(publishedResult));
|
|
31040
|
+
if (this.hasTable("discovery_capture")) this.state.db.prepare("DELETE FROM discovery_capture WHERE draft_id=?").run(draft.id);
|
|
30995
31041
|
this.state.db.prepare("DELETE FROM discovery_draft WHERE id=? AND owner_session_id=?").run(draft.id, sessionId);
|
|
30996
31042
|
this.state.db.exec("COMMIT");
|
|
30997
|
-
try {
|
|
30998
|
-
unlinkSync2(draft.filePath);
|
|
30999
|
-
} catch {
|
|
31000
|
-
}
|
|
31001
31043
|
return draft;
|
|
31002
31044
|
} catch (error62) {
|
|
31003
31045
|
this.state.db.exec("ROLLBACK");
|
|
31004
31046
|
throw error62;
|
|
31005
31047
|
}
|
|
31006
31048
|
}
|
|
31007
|
-
|
|
31008
|
-
|
|
31009
|
-
|
|
31010
|
-
|
|
31011
|
-
this.
|
|
31012
|
-
|
|
31013
|
-
|
|
31014
|
-
|
|
31015
|
-
|
|
31016
|
-
|
|
31017
|
-
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31049
|
+
discard(sessionId) {
|
|
31050
|
+
return this.retire(sessionId);
|
|
31051
|
+
}
|
|
31052
|
+
published(sessionId, result3) {
|
|
31053
|
+
return this.retire(sessionId, result3);
|
|
31054
|
+
}
|
|
31055
|
+
hasCleanup() {
|
|
31056
|
+
return Boolean(this.state.db.prepare("SELECT 1 FROM discovery_cleanup LIMIT 1").get());
|
|
31057
|
+
}
|
|
31058
|
+
/** Durable retirement precedes filesystem/broker cleanup, so a retry never republishes. */
|
|
31059
|
+
async cleanup(closeScope) {
|
|
31060
|
+
for (const row of this.state.db.prepare("SELECT * FROM discovery_cleanup").all()) {
|
|
31061
|
+
for (const path2 of new Set(JSON.parse(row.paths_json))) {
|
|
31062
|
+
rmSync3(path2, { force: true, recursive: path2 === join5(this.root, "draft-evidence", row.draft_id) });
|
|
31063
|
+
let parent = dirname2(path2);
|
|
31064
|
+
while (parent !== this.root && parent.startsWith(`${join5(this.root, "drafts")}/`)) {
|
|
31065
|
+
try {
|
|
31066
|
+
rmdirSync(parent);
|
|
31067
|
+
parent = dirname2(parent);
|
|
31068
|
+
} catch {
|
|
31069
|
+
break;
|
|
31070
|
+
}
|
|
31071
|
+
}
|
|
31072
|
+
}
|
|
31073
|
+
if (row.scope_id) await closeScope(row.scope_id);
|
|
31074
|
+
this.state.db.prepare("DELETE FROM discovery_cleanup WHERE draft_id=?").run(row.draft_id);
|
|
31021
31075
|
}
|
|
31022
31076
|
}
|
|
31023
31077
|
selected(sessionId) {
|
|
@@ -31029,6 +31083,7 @@ var init_drafts = __esm({
|
|
|
31029
31083
|
let filePath = pathFor(this.root, input2.appSlug, input2.kind, name);
|
|
31030
31084
|
this.state.db.exec("BEGIN IMMEDIATE");
|
|
31031
31085
|
try {
|
|
31086
|
+
if (this.state.db.prepare("SELECT 1 FROM discovery_cleanup WHERE file_path=?").get(filePath)) throw new DraftConflict("selected-draft", "Draft cleanup is pending; finish cleanup before reopening this resource");
|
|
31032
31087
|
const selected = this.selected(input2.sessionId);
|
|
31033
31088
|
if (selected && (selected.appSlug !== input2.appSlug || selected.kind !== input2.kind || selected.name !== name)) throw new DraftConflict("selected-draft", `This agent already selected ${selected.kind} ${selected.name}; defer or finish it before opening another resource`);
|
|
31034
31089
|
const existing = this.state.db.prepare("SELECT * FROM discovery_draft WHERE app_slug=? AND kind=? AND name=?").get(input2.appSlug, input2.kind, name);
|
|
@@ -31038,6 +31093,7 @@ var init_drafts = __esm({
|
|
|
31038
31093
|
const id2 = existing ? String(existing.id) : randomUUID4();
|
|
31039
31094
|
if (!existing) this.state.db.prepare("INSERT INTO discovery_draft (id,app_slug,app_id,kind,name,owner_session_id,file_path,base_version) VALUES (?,?,?,?,?,?,?,?)").run(id2, input2.appSlug, input2.appId ?? null, input2.kind, name, input2.sessionId, filePath, input2.baseVersion ?? null);
|
|
31040
31095
|
else this.state.db.prepare("UPDATE discovery_draft SET owner_session_id=? WHERE id=?").run(input2.sessionId, id2);
|
|
31096
|
+
this.state.db.prepare("DELETE FROM discovery_deferred WHERE draft_id=?").run(id2);
|
|
31041
31097
|
this.state.db.prepare("INSERT INTO selected_discovery_draft (session_id,draft_id) VALUES (?,?) ON CONFLICT(session_id) DO UPDATE SET draft_id=excluded.draft_id").run(input2.sessionId, id2);
|
|
31042
31098
|
mkdirSync5(dirname2(filePath), { recursive: true, mode: 448 });
|
|
31043
31099
|
if (!existsSync2(filePath) && existing?.intent !== "remove") writeFileSync5(filePath, draftTemplate(input2.kind), { flag: "wx", mode: 384 });
|
|
@@ -31054,6 +31110,7 @@ var init_drafts = __esm({
|
|
|
31054
31110
|
const filePath = pathFor(this.root, input2.appSlug, input2.kind, input2.name).replace(/\.jsonc$/, ".delete.jsonc");
|
|
31055
31111
|
this.state.db.exec("BEGIN IMMEDIATE");
|
|
31056
31112
|
try {
|
|
31113
|
+
if (this.state.db.prepare("SELECT 1 FROM discovery_cleanup WHERE file_path=?").get(filePath)) throw new DraftConflict("selected-draft", "Draft cleanup is pending; finish cleanup before reopening this resource");
|
|
31057
31114
|
const selected = this.selected(input2.sessionId);
|
|
31058
31115
|
const existing = this.state.db.prepare("SELECT * FROM discovery_draft WHERE app_slug=? AND kind=? AND name=?").get(input2.appSlug, input2.kind, input2.name);
|
|
31059
31116
|
if (selected && selected.id !== existing?.id) throw new DraftConflict("selected-draft", "Finish or defer the selected draft before preparing a removal");
|
|
@@ -31085,6 +31142,7 @@ ${JSON.stringify({ remove: true, expectedVersion: input2.baseVersion }, null, 2)
|
|
|
31085
31142
|
const draft = this.selected(sessionId);
|
|
31086
31143
|
if (draft) {
|
|
31087
31144
|
withBaselines(this.root, (baselines) => baselines.closeObservation(draft.id, "released"));
|
|
31145
|
+
this.state.db.prepare("INSERT INTO discovery_deferred (draft_id,session_id) VALUES (?,?) ON CONFLICT(draft_id) DO UPDATE SET session_id=excluded.session_id").run(draft.id, sessionId);
|
|
31088
31146
|
this.state.db.prepare("DELETE FROM selected_discovery_draft WHERE session_id=?").run(sessionId);
|
|
31089
31147
|
this.state.db.prepare("UPDATE discovery_draft SET owner_session_id=NULL WHERE id=? AND owner_session_id=?").run(draft.id, sessionId);
|
|
31090
31148
|
}
|
|
@@ -31710,7 +31768,7 @@ var init_process = __esm({
|
|
|
31710
31768
|
|
|
31711
31769
|
// src/capture-server/client.ts
|
|
31712
31770
|
import { createHash as createHash4 } from "node:crypto";
|
|
31713
|
-
import { chmodSync as chmodSync5, existsSync as existsSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as
|
|
31771
|
+
import { chmodSync as chmodSync5, existsSync as existsSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync4 } from "node:fs";
|
|
31714
31772
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
31715
31773
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31716
31774
|
function knownLegacyBroker(version2) {
|
|
@@ -31800,7 +31858,7 @@ async function ensureCaptureServerUnderUpdateLock(options2) {
|
|
|
31800
31858
|
if (error62.code === "capture-server-busy") throw error62;
|
|
31801
31859
|
}
|
|
31802
31860
|
try {
|
|
31803
|
-
|
|
31861
|
+
rmSync4(join6(root, "server.json"));
|
|
31804
31862
|
} catch {
|
|
31805
31863
|
}
|
|
31806
31864
|
}
|
|
@@ -32443,7 +32501,7 @@ var init_vault = __esm({
|
|
|
32443
32501
|
|
|
32444
32502
|
// src/commands/run.ts
|
|
32445
32503
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
32446
|
-
import { readFileSync as readFileSync7, renameSync as renameSync2, unlinkSync as
|
|
32504
|
+
import { readFileSync as readFileSync7, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "node:fs";
|
|
32447
32505
|
import { dirname as dirname5, resolve as resolve4, join as join10 } from "node:path";
|
|
32448
32506
|
function pointer2(value, path2) {
|
|
32449
32507
|
if (path2 === "") return value;
|
|
@@ -32569,10 +32627,18 @@ function bind(value, inputs, generated, output2, insideArray = false) {
|
|
|
32569
32627
|
if (typeof object4.$generated === "string") return generated[object4.$generated];
|
|
32570
32628
|
if (typeof object4.$result === "string") return pointer2(output2, object4.$result);
|
|
32571
32629
|
}
|
|
32572
|
-
const target =
|
|
32630
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
32573
32631
|
for (const [name, item] of Object.entries(object4)) {
|
|
32574
32632
|
const resolved = bind(item, inputs, generated, output2);
|
|
32575
|
-
if (resolved !== omit2)
|
|
32633
|
+
if (resolved !== omit2) {
|
|
32634
|
+
const key = name.replace(/\{(input|generated|result)\.([^{}]*)\}/gu, (_, kind, ref) => {
|
|
32635
|
+
const selected = kind === "input" ? inputs[ref] : kind === "generated" ? generated[ref] : pointer2(output2, ref);
|
|
32636
|
+
if (typeof selected !== "string" && typeof selected !== "number") throw new Error(`Object key ${kind}.${ref} needs a string or number`);
|
|
32637
|
+
return String(selected);
|
|
32638
|
+
});
|
|
32639
|
+
if (Object.hasOwn(target, key)) throw new Error("Object key bindings collide");
|
|
32640
|
+
target[key] = resolved;
|
|
32641
|
+
}
|
|
32576
32642
|
}
|
|
32577
32643
|
if (!Object.keys(target).length && Object.keys(object4).length && !insideArray) return omit2;
|
|
32578
32644
|
return target;
|
|
@@ -32663,7 +32729,7 @@ function writeOutput(path2, content) {
|
|
|
32663
32729
|
renameSync2(pending, path2);
|
|
32664
32730
|
} catch (error62) {
|
|
32665
32731
|
try {
|
|
32666
|
-
|
|
32732
|
+
unlinkSync2(pending);
|
|
32667
32733
|
} catch {
|
|
32668
32734
|
}
|
|
32669
32735
|
throw error62;
|
|
@@ -32776,6 +32842,13 @@ async function sendBrowser(root, appId, contextName, pageOrigin, effect2, transp
|
|
|
32776
32842
|
if (!selection.execution) await client.closeScope(scope2.id);
|
|
32777
32843
|
}
|
|
32778
32844
|
}
|
|
32845
|
+
function proofPath(path2, inputs) {
|
|
32846
|
+
return path2.replace(/\{input\.([^{}]+)\}/gu, (_, name) => {
|
|
32847
|
+
const value = inputs[name];
|
|
32848
|
+
if (typeof value !== "string" && typeof value !== "number") throw new Error(`Pointer input ${name} needs a string or number`);
|
|
32849
|
+
return String(value).replaceAll("~", "~0").replaceAll("/", "~1");
|
|
32850
|
+
});
|
|
32851
|
+
}
|
|
32779
32852
|
function confirmProof(proof, body, primary, inputs, status) {
|
|
32780
32853
|
if (proof.absent) {
|
|
32781
32854
|
if (status !== 404) throw new ResponseContractFailure(`Absence proof expected HTTP 404; received HTTP ${status}`);
|
|
@@ -32786,7 +32859,7 @@ function confirmProof(proof, body, primary, inputs, status) {
|
|
|
32786
32859
|
const expected = "input" in comparison ? inputs[comparison.input] : "result" in comparison ? pointer2(primary, comparison.result) : comparison.value;
|
|
32787
32860
|
let actual;
|
|
32788
32861
|
try {
|
|
32789
|
-
actual = pointer2(selected2, comparison.path);
|
|
32862
|
+
actual = pointer2(selected2, proofPath(comparison.path, inputs));
|
|
32790
32863
|
} catch (error62) {
|
|
32791
32864
|
if (missingIsMismatch) return false;
|
|
32792
32865
|
throw error62;
|
|
@@ -32795,7 +32868,7 @@ function confirmProof(proof, body, primary, inputs, status) {
|
|
|
32795
32868
|
};
|
|
32796
32869
|
let selected = body;
|
|
32797
32870
|
if (proof.select) {
|
|
32798
|
-
const collection = pointer2(body, proof.select.path);
|
|
32871
|
+
const collection = pointer2(body, proofPath(proof.select.path, inputs));
|
|
32799
32872
|
if (!Array.isArray(collection)) throw new ResponseContractFailure(`Readback selection ${proof.select.path || "/"} must be an array`);
|
|
32800
32873
|
const found = collection.filter((item) => proof.select.where.every((comparison) => matches(comparison, item, true)));
|
|
32801
32874
|
if (found.length !== 1) throw new ResponseContractFailure(`Readback selection matched ${found.length} items; expected exactly one`);
|
|
@@ -32999,7 +33072,7 @@ Inspect saved entries: ${prefix(`credentials list ${appName} ${action.transport}
|
|
|
32999
33072
|
`);
|
|
33000
33073
|
} else output2 = result(serialized, true);
|
|
33001
33074
|
try {
|
|
33002
|
-
|
|
33075
|
+
unlinkSync2(recoveryPath);
|
|
33003
33076
|
} catch {
|
|
33004
33077
|
}
|
|
33005
33078
|
} catch (error62) {
|
|
@@ -33047,6 +33120,7 @@ async function executeDraftVerification(input2) {
|
|
|
33047
33120
|
const generated = input2.primaryEvidence?.generated ?? (input2.kind === "action" && action && "generated" in action ? Object.fromEntries(Object.entries(action.generated ?? {}).map(([name, spec]) => [name, `${spec.prefix ?? ""}${randomUUID7()}`])) : {});
|
|
33048
33121
|
try {
|
|
33049
33122
|
values = effectiveInputs(schema, input2.primaryEvidence?.inputs ?? input2.sampleInputs, {}, true).values;
|
|
33123
|
+
if (action && "proof" in action && action.proof) for (const item of [...action.proof.equals ?? [], ...action.proof.select?.where ?? [], ...action.proof.select ? [action.proof.select] : []]) proofPath(item.path, values);
|
|
33050
33124
|
const request2 = input2.kind === "action" ? action.request : probe.request;
|
|
33051
33125
|
prepared = prepareRequest(request2, input2.kind === "transport" && input2.probeOrigin ? { ...transport, baseUrl: input2.probeOrigin } : transport, values, generated);
|
|
33052
33126
|
} catch (error62) {
|
|
@@ -33863,7 +33937,10 @@ function deriveRequest(entry2, inputs) {
|
|
|
33863
33937
|
if (name) return { $input: name };
|
|
33864
33938
|
if (value === null) return null;
|
|
33865
33939
|
if (Array.isArray(value)) return value.map((child, index) => bind2(child, `${path3}/${index}`, wireText));
|
|
33866
|
-
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, child]) =>
|
|
33940
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
33941
|
+
const keyInput = match(key, true);
|
|
33942
|
+
return [keyInput ? `{input.${keyInput}}` : key, sensitive.test(key) ? pending(`${path3}/${key}`) : bind2(child, `${path3}/${key}`)];
|
|
33943
|
+
}));
|
|
33867
33944
|
return pending(path3);
|
|
33868
33945
|
};
|
|
33869
33946
|
const path2 = url3.pathname.split("/").map((segment, index) => {
|
|
@@ -33955,14 +34032,38 @@ function inferResponseSchema(value, limit = 2e4) {
|
|
|
33955
34032
|
};
|
|
33956
34033
|
return { schema: visit4(value, 0), complete };
|
|
33957
34034
|
}
|
|
33958
|
-
function rankCandidates(entries, inputs) {
|
|
33959
|
-
const samples = Object.values(inputs).filter((value) => ["string", "number"].includes(typeof value)
|
|
34035
|
+
function rankCandidates(entries, inputs, effect2, allowedOrigins = []) {
|
|
34036
|
+
const samples = [...new Set(Object.values(inputs).filter((value) => ["string", "number", "boolean"].includes(typeof value)).map((value) => canonicalJson(value)))];
|
|
33960
34037
|
const score = ({ entry: entry2 }) => {
|
|
33961
34038
|
let value = 0;
|
|
33962
34039
|
const url3 = String(entry2.url ?? ""), body = typeof entry2.reqBody === "string" ? entry2.reqBody : "";
|
|
33963
|
-
|
|
34040
|
+
const observed = /* @__PURE__ */ new Set();
|
|
34041
|
+
const collect = (item) => {
|
|
34042
|
+
if (Array.isArray(item)) item.forEach(collect);
|
|
34043
|
+
else if (item && typeof item === "object") for (const [key, child] of Object.entries(item)) {
|
|
34044
|
+
observed.add(canonicalJson(key));
|
|
34045
|
+
collect(child);
|
|
34046
|
+
}
|
|
34047
|
+
else observed.add(canonicalJson(item));
|
|
34048
|
+
};
|
|
34049
|
+
try {
|
|
34050
|
+
collect(JSON.parse(body));
|
|
34051
|
+
} catch {
|
|
34052
|
+
}
|
|
34053
|
+
try {
|
|
34054
|
+
const target = new URL(url3);
|
|
34055
|
+
if (allowedOrigins.includes(target.origin)) value += 25;
|
|
34056
|
+
for (const item of [...target.pathname.split("/").map(decodeURIComponent), ...target.searchParams.values()]) {
|
|
34057
|
+
observed.add(canonicalJson(item));
|
|
34058
|
+
for (const sample of samples) if (String(JSON.parse(sample)) === item) observed.add(sample);
|
|
34059
|
+
}
|
|
34060
|
+
} catch {
|
|
34061
|
+
}
|
|
34062
|
+
value += samples.filter((sample) => observed.has(sample)).length * 100;
|
|
34063
|
+
const methods = effect2 === "create" ? ["POST"] : effect2 === "update" ? ["PATCH", "PUT", "POST"] : effect2 === "delete" ? ["DELETE"] : [];
|
|
34064
|
+
if (methods.includes(String(entry2.method))) value += effect2 === "update" && entry2.method === "POST" ? 10 : 30;
|
|
33964
34065
|
const contentType = Object.entries(entry2.resHeaders ?? {}).find(([key]) => key.toLowerCase() === "content-type")?.[1];
|
|
33965
|
-
if (String(contentType ?? "").includes("json") ||
|
|
34066
|
+
if (String(contentType ?? "").includes("json") || typeof entry2.resBody === "string" && /^\s*(?:\[|{)/.test(entry2.resBody)) value += 20;
|
|
33966
34067
|
if (Number(entry2.status) >= 200 && Number(entry2.status) < 300) value += 5;
|
|
33967
34068
|
if (/\.(?:js|css|png|jpg|svg|woff2?)(?:[?#]|$)/i.test(url3)) value -= 50;
|
|
33968
34069
|
if (entry2.method === "OPTIONS") value -= 50;
|
|
@@ -33982,9 +34083,8 @@ var init_capture_derivation = __esm({
|
|
|
33982
34083
|
});
|
|
33983
34084
|
|
|
33984
34085
|
// src/commands/discover.ts
|
|
33985
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync9,
|
|
34086
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync9, readFileSync as readFileSync8, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
33986
34087
|
import { createHash as createHash5, randomUUID as randomUUID9 } from "node:crypto";
|
|
33987
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
33988
34088
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
33989
34089
|
async function currentResource(api, appId, kind, name) {
|
|
33990
34090
|
try {
|
|
@@ -34116,7 +34216,7 @@ Abandon removal: ${command4(invocation, "discover discard")}
|
|
|
34116
34216
|
const local = `Check edits: ${command4(invocation, "discover check")}
|
|
34117
34217
|
Publish: ${command4(invocation, "discover publish")}
|
|
34118
34218
|
Recover this file: ${command4(invocation, "discover status")}
|
|
34119
|
-
Keep for later: ${command4(invocation, "discover defer")}
|
|
34219
|
+
Keep for later (including evidence): ${command4(invocation, "discover defer")}
|
|
34120
34220
|
Abandon unpublished edits: ${command4(invocation, "discover discard")}
|
|
34121
34221
|
`;
|
|
34122
34222
|
if (draft.kind === "app") return `Created an app draft for ${draft.appSlug}. Nothing has been published.
|
|
@@ -34124,7 +34224,7 @@ Edit: ${draft.filePath}
|
|
|
34124
34224
|
|
|
34125
34225
|
Fill the display name, product description, main website, category and visibility. The file lists supported fields and allowed values. Do not put credentials or local context values in this definition.
|
|
34126
34226
|
|
|
34127
|
-
${local}
|
|
34227
|
+
${local}Close and abandon this session\u2019s drafts: ${command4(invocation, "discover close")}. Deferred drafts survive action switches until discovery closes.
|
|
34128
34228
|
`;
|
|
34129
34229
|
if (draft.kind === "action" && unavailable) return `Editing documented unavailability for ${draft.name}.
|
|
34130
34230
|
Edit: ${draft.filePath}
|
|
@@ -34305,7 +34405,8 @@ async function handleDiscover(invocation, api, state, session, fetcher, root, in
|
|
|
34305
34405
|
if (!session) return sessionRequired(invocation);
|
|
34306
34406
|
let release;
|
|
34307
34407
|
try {
|
|
34308
|
-
release =
|
|
34408
|
+
release = ["appsHistory", "discoverCaptureShow", "discoverCaptureList"].includes(invocation.key) ? () => {
|
|
34409
|
+
} : lockDiscovery(root, session.id);
|
|
34309
34410
|
} catch (error62) {
|
|
34310
34411
|
return fail5(`${error62 instanceof Error ? error62.message : "discovery-command-busy"}. No state changed.
|
|
34311
34412
|
`, 8);
|
|
@@ -34364,7 +34465,7 @@ Inspect prior publications for this app:
|
|
|
34364
34465
|
${command4(invocation, `apps history ${app2.slug}`)}
|
|
34365
34466
|
Narrow history to a known published resource with --resource <kind:name>.
|
|
34366
34467
|
Resume current work: ${command4(invocation, "discover status")}
|
|
34367
|
-
|
|
34468
|
+
Close and abandon this session\u2019s drafts: ${command4(invocation, "discover close")}. Deferred drafts survive action switches until discovery closes.
|
|
34368
34469
|
`);
|
|
34369
34470
|
}
|
|
34370
34471
|
if (invocation.key === "appsHistory") {
|
|
@@ -34409,7 +34510,7 @@ ${pending ? `A publication request may have been sent. Restore the original inte
|
|
|
34409
34510
|
` : `Nothing has been removed.
|
|
34410
34511
|
Check dependencies: ${command4(invocation, "discover check")}
|
|
34411
34512
|
Publish removal: ${command4(invocation, "discover publish")}
|
|
34412
|
-
Keep for later: ${command4(invocation, "discover defer")}
|
|
34513
|
+
Keep for later (including evidence): ${command4(invocation, "discover defer")}
|
|
34413
34514
|
Abandon removal: ${command4(invocation, "discover discard")}
|
|
34414
34515
|
`}`);
|
|
34415
34516
|
}
|
|
@@ -34423,7 +34524,7 @@ ${evidence ? `Latest saved execution: ${evidence.resultPath}
|
|
|
34423
34524
|
` : ""}
|
|
34424
34525
|
Check: ${command4(invocation, "discover check")}
|
|
34425
34526
|
Publish: ${command4(invocation, "discover publish")}
|
|
34426
|
-
Keep for later: ${command4(invocation, "discover defer")}
|
|
34527
|
+
Keep for later (including evidence): ${command4(invocation, "discover defer")}
|
|
34427
34528
|
Abandon unpublished edits: ${command4(invocation, "discover discard")}
|
|
34428
34529
|
`);
|
|
34429
34530
|
}
|
|
@@ -34441,23 +34542,38 @@ ${retained.map((item) => ` ${item.kind} ${item.name}: ${item.filePath}
|
|
|
34441
34542
|
if (store.publication(store.selected(session.id)?.id ?? "")) return fail5(`A publication request may have been sent. Reconcile with ${command4(invocation, "discover publish")} before deferring.
|
|
34442
34543
|
`);
|
|
34443
34544
|
const draft = store.defer(session.id);
|
|
34444
|
-
return draft ? ok4(`Kept ${draft.name} and its selected evidence; released editing ownership.
|
|
34545
|
+
return draft ? ok4(`Kept ${draft.name} and its selected evidence until discovery closes; released editing ownership.
|
|
34445
34546
|
Resume: ${command4(invocation, `discover ${draft.intent === "remove" ? "remove" : "draft"} ${draft.kind}${draft.kind === "app" ? "" : ` ${draft.name}`}`)}
|
|
34446
34547
|
`) : fail5("No owned draft is selected.\n");
|
|
34447
34548
|
}
|
|
34448
34549
|
if (invocation.key === "discoverDiscard") {
|
|
34449
34550
|
if (captureRow(state, store.selected(session.id)?.id ?? "")?.scope_id) return fail5(`A recording is active. Stop it with ${command4(invocation, "discover capture stop")} or cancel it with ${command4(invocation, "discover capture cancel")} before discarding.
|
|
34450
34551
|
`);
|
|
34451
|
-
const
|
|
34552
|
+
const pending = store.hasCleanup();
|
|
34452
34553
|
const draft = store.discard(session.id);
|
|
34453
|
-
|
|
34554
|
+
try {
|
|
34555
|
+
await store.cleanup(async (id2) => (await captureClient(root, false)).closeScope(id2));
|
|
34556
|
+
} catch {
|
|
34557
|
+
return fail5(`local-state-error \u2014 Draft retirement is saved; cleanup remains pending. Retry ${command4(invocation, "discover discard")}.
|
|
34558
|
+
`, 12);
|
|
34559
|
+
}
|
|
34560
|
+
if (!draft && pending) return ok4("Discard cleanup completed.\n");
|
|
34454
34561
|
return draft ? ok4(`Discarded the owned unpublished draft for ${draft.name}. Its published definition is unchanged.
|
|
34455
34562
|
Start again: ${command4(invocation, `discover ${draft.intent === "remove" ? "remove" : "draft"} ${draft.kind}${draft.kind === "app" ? "" : ` ${draft.name}`}`)}
|
|
34456
34563
|
`) : fail5("No owned draft is selected.\n");
|
|
34457
34564
|
}
|
|
34458
34565
|
if (invocation.key === "discoverClose") {
|
|
34459
34566
|
const current = store.session(session.id);
|
|
34460
|
-
if (!current)
|
|
34567
|
+
if (!current) {
|
|
34568
|
+
if (!store.hasCleanup()) return fail5("No discovery session is open.\n");
|
|
34569
|
+
try {
|
|
34570
|
+
await store.cleanup(async (id2) => (await captureClient(root, false)).closeScope(id2));
|
|
34571
|
+
} catch {
|
|
34572
|
+
return fail5(`local-state-error \u2014 Discovery is closed; cleanup remains pending. Retry ${command4(invocation, "discover close")}.
|
|
34573
|
+
`, 12);
|
|
34574
|
+
}
|
|
34575
|
+
return ok4("Closed discovery cleanup completed.\n");
|
|
34576
|
+
}
|
|
34461
34577
|
if (store.publication(store.selected(session.id)?.id ?? "")) return fail5(`A publication request may be in flight. Reconcile with ${command4(invocation, "discover publish")} before closing.
|
|
34462
34578
|
`);
|
|
34463
34579
|
if (captureRow(state, store.selected(session.id)?.id ?? "")?.scope_id) return fail5(`A recording is active. Stop it with ${command4(invocation, "discover capture stop")} or cancel it with ${command4(invocation, "discover capture cancel")} before closing.
|
|
@@ -34465,7 +34581,13 @@ Start again: ${command4(invocation, `discover ${draft.intent === "remove" ? "rem
|
|
|
34465
34581
|
const installation = state.installation();
|
|
34466
34582
|
if (installation) await api.call("releaseSession", { params: { id: apiSessionId(installation.id, session) } });
|
|
34467
34583
|
store.close(session.id);
|
|
34468
|
-
|
|
34584
|
+
try {
|
|
34585
|
+
await store.cleanup(async (id2) => (await captureClient(root, false)).closeScope(id2));
|
|
34586
|
+
} catch {
|
|
34587
|
+
return fail5(`local-state-error \u2014 Discovery is closed; cleanup remains pending. Retry ${command4(invocation, "discover close")}.
|
|
34588
|
+
`, 12);
|
|
34589
|
+
}
|
|
34590
|
+
return ok4(`Closed discovery for ${current.title}; discarded this session\u2019s selected and deferred drafts. Published actions remain usable.
|
|
34469
34591
|
To continue later: ${command4(invocation, `apps update ${current.appSlug}`)}
|
|
34470
34592
|
`);
|
|
34471
34593
|
}
|
|
@@ -34498,16 +34620,12 @@ Abandon removal: ${command4(invocation, "discover discard")}
|
|
|
34498
34620
|
if (invocation.key === "discoverPublish") {
|
|
34499
34621
|
if (captureRow(state, store.selected(session.id)?.id ?? "")?.scope_id) return fail5(`A recording is active. Stop or cancel it before publication. No app request was sent.
|
|
34500
34622
|
`);
|
|
34501
|
-
const
|
|
34502
|
-
|
|
34503
|
-
|
|
34504
|
-
|
|
34505
|
-
|
|
34506
|
-
|
|
34507
|
-
state.db.prepare("DELETE FROM discovery_capture WHERE draft_id=?").run(draftId);
|
|
34508
|
-
} catch {
|
|
34509
|
-
result3.stderr += "Publication succeeded; private capture cleanup remains pending. No app request was repeated.\n";
|
|
34510
|
-
}
|
|
34623
|
+
const result3 = !store.selected(session.id) && store.finished(session.id) ? store.finished(session.id) : await publishDraft(invocation, api, store, session, root);
|
|
34624
|
+
try {
|
|
34625
|
+
await store.cleanup(async (id2) => (await captureClient(root, false)).closeScope(id2));
|
|
34626
|
+
} catch {
|
|
34627
|
+
return fail5(`local-state-error \u2014 Publication result is retained; cleanup remains pending. Retry ${command4(invocation, "discover publish")} to finish cleanup without publishing again.
|
|
34628
|
+
`, 12, result3.dispatched);
|
|
34511
34629
|
}
|
|
34512
34630
|
return result3;
|
|
34513
34631
|
}
|
|
@@ -34565,6 +34683,10 @@ File: ${checked.draft.filePath}
|
|
|
34565
34683
|
}
|
|
34566
34684
|
const { draft } = checked;
|
|
34567
34685
|
const removing = draft.intent === "remove";
|
|
34686
|
+
const complete = (result3) => {
|
|
34687
|
+
store.published(session.id, result3);
|
|
34688
|
+
return result3;
|
|
34689
|
+
};
|
|
34568
34690
|
if (!removing && !checked.definition) return fail5(`The file is incomplete: ${draft.filePath}
|
|
34569
34691
|
`);
|
|
34570
34692
|
let verification;
|
|
@@ -34639,13 +34761,12 @@ Fix the named issue, then ${command4(invocation, "discover check")} and ${comman
|
|
|
34639
34761
|
}
|
|
34640
34762
|
const appId = String(created.id);
|
|
34641
34763
|
store.setAppId(draft.appSlug, appId);
|
|
34642
|
-
|
|
34643
|
-
return ok4(`Published ${checked.definition && "title" in checked.definition ? checked.definition.title : draft.appSlug}'s metadata. No executable action exists yet.
|
|
34764
|
+
return complete(ok4(`Published ${checked.definition && "title" in checked.definition ? checked.definition.title : draft.appSlug}'s metadata. No executable action exists yet.
|
|
34644
34765
|
${actionNameHelp}
|
|
34645
34766
|
Check the requested operation in official API and access documentation:
|
|
34646
34767
|
${command4(invocation, "discover api assess <entity.verb>")}
|
|
34647
34768
|
If no usable public route is found, record the evidence and use browser transport.
|
|
34648
|
-
`, true);
|
|
34769
|
+
`, true));
|
|
34649
34770
|
}
|
|
34650
34771
|
let published;
|
|
34651
34772
|
try {
|
|
@@ -34653,29 +34774,28 @@ If no usable public route is found, record the evidence and use browser transpor
|
|
|
34653
34774
|
} catch (error62) {
|
|
34654
34775
|
return publicationError(error62);
|
|
34655
34776
|
}
|
|
34656
|
-
store.published(session.id, published.resource.version);
|
|
34657
34777
|
if (!removing && draft.kind === "action" && published.executableHash) withBaselines(root, (baselines) => baselines.published(draft.id, draft.name, published.executableHash, /* @__PURE__ */ new Date(), published.resource.version));
|
|
34658
|
-
if (published.distribution && published.distribution.state !== "available") return ok4(`Published ${label(draft.kind)} ${draft.name}. Distribution is ${published.distribution.state}. ${published.distribution.reason === "confirmed-defect" ? "This executable revision was withdrawn. Continue developing and submit a corrected definition." : "You can continue developing and using your accepted revision. Wider distribution advances automatically as independent workspaces use it."}${published.distribution.until ? ` Protective pause ends ${published.distribution.until}.` : ""}
|
|
34778
|
+
if (published.distribution && published.distribution.state !== "available") return complete(ok4(`Published ${label(draft.kind)} ${draft.name}. Distribution is ${published.distribution.state}. ${published.distribution.reason === "confirmed-defect" ? "This executable revision was withdrawn. Continue developing and submit a corrected definition." : "You can continue developing and using your accepted revision. Wider distribution advances automatically as independent workspaces use it."}${published.distribution.until ? ` Protective pause ends ${published.distribution.until}.` : ""}
|
|
34659
34779
|
Inspect distribution: ${command4(invocation, `apps history ${draft.appSlug} --resource ${backendKind(draft.kind)}:${draft.name}`)}
|
|
34660
|
-
`, true);
|
|
34661
|
-
if (removing) return ok4(`Removed ${draft.kind} ${draft.name} from the published interface. App records were unchanged.
|
|
34662
|
-
`, true);
|
|
34663
|
-
if (draft.kind === "action" && checked.definition && "unavailable" in checked.definition) return ok4(`Published the documented unavailability of ${draft.name}. No app request was sent and this action is not executable.
|
|
34780
|
+
`, true));
|
|
34781
|
+
if (removing) return complete(ok4(`Removed ${draft.kind} ${draft.name} from the published interface. App records were unchanged.
|
|
34782
|
+
`, true));
|
|
34783
|
+
if (draft.kind === "action" && checked.definition && "unavailable" in checked.definition) return complete(ok4(`Published the documented unavailability of ${draft.name}. No app request was sent and this action is not executable.
|
|
34664
34784
|
Inspect the finding: ${command4(invocation, `actions show ${draft.appSlug} ${draft.name}`)}
|
|
34665
34785
|
When finished authoring: ${command4(invocation, "discover close")}
|
|
34666
|
-
`, true);
|
|
34667
|
-
if (draft.kind === "action") return ok4(`Published ${draft.name}. Its reviewed test already returned the requested data
|
|
34786
|
+
`, true));
|
|
34787
|
+
if (draft.kind === "action") return complete(ok4(`Published ${draft.name}. Its reviewed test already returned the requested data. Temporary draft and verification files were cleared; do not repeat the test.
|
|
34668
34788
|
Inspect arguments for future use: ${command4(invocation, `actions show ${draft.appSlug} ${draft.name}`)}
|
|
34669
34789
|
When finished authoring: ${command4(invocation, "discover close")}
|
|
34670
|
-
`, true);
|
|
34671
|
-
if (draft.kind === "transport") return ok4(`Published connection ${draft.name}. Credential values and local test inputs were not shared.
|
|
34790
|
+
`, true));
|
|
34791
|
+
if (draft.kind === "transport") return complete(ok4(`Published connection ${draft.name}. Credential values and local test inputs were not shared.
|
|
34672
34792
|
${actionNameHelp}
|
|
34673
34793
|
Create an operation that uses it: ${command4(invocation, "discover draft action <entity.verb>")}
|
|
34674
34794
|
Set dependencies.transport to ${draft.name} in that action file.
|
|
34675
|
-
`, true);
|
|
34676
|
-
return ok4(`Published ${label(draft.kind)} ${draft.name}.
|
|
34795
|
+
`, true));
|
|
34796
|
+
return complete(ok4(`Published ${label(draft.kind)} ${draft.name}.
|
|
34677
34797
|
Continue with ${command4(invocation, "discover draft <kind> <name>")} or ${command4(invocation, "discover close")}
|
|
34678
|
-
`, true);
|
|
34798
|
+
`, true));
|
|
34679
34799
|
}
|
|
34680
34800
|
async function rebaseDraft(invocation, api, store, session) {
|
|
34681
34801
|
const draft = store.selected(session.id);
|
|
@@ -34695,7 +34815,7 @@ async function rebaseDraft(invocation, api, store, session) {
|
|
|
34695
34815
|
if (checked.issues.length) return checkResponse(checked, invocation);
|
|
34696
34816
|
const source = readFileSync8(draft.filePath, "utf8");
|
|
34697
34817
|
const updated = applyEdits(source, modify(source, ["expectedVersion"], current.version, { formattingOptions: { insertSpaces: true, tabSize: 2 } }));
|
|
34698
|
-
const temporary =
|
|
34818
|
+
const temporary = store.artifactPath(draft.id, "rebase.jsonc");
|
|
34699
34819
|
writeFileSync8(temporary, updated, { flag: "wx", mode: 384 });
|
|
34700
34820
|
renameSync3(temporary, draft.filePath);
|
|
34701
34821
|
store.setBaseVersion(draft.id, current.version);
|
|
@@ -34765,8 +34885,7 @@ Review it and publish; no app request was sent.
|
|
|
34765
34885
|
let responsePath = previous?.responsePath;
|
|
34766
34886
|
let readbackPath;
|
|
34767
34887
|
const savePrivate = (value, name) => {
|
|
34768
|
-
const
|
|
34769
|
-
const path2 = join12(directory, name);
|
|
34888
|
+
const path2 = store.artifactPath(draft.id, name);
|
|
34770
34889
|
writeFileSync8(path2, `${JSON.stringify(value, null, 2)}
|
|
34771
34890
|
`, { flag: "wx", mode: 384 });
|
|
34772
34891
|
return path2;
|
|
@@ -34791,8 +34910,7 @@ ${responsePath ? `Saved primary response and test inputs: ${responsePath}
|
|
|
34791
34910
|
}
|
|
34792
34911
|
withBaselines(root, (baselines) => baselines.finishPreparation(session.id, draft.appSlug, draft.appId, transport.mode));
|
|
34793
34912
|
if (write) store.saveRequestAttempt(draft.id, primaryFingerprint, "success");
|
|
34794
|
-
const
|
|
34795
|
-
const resultPath = join12(dir, "verification.json");
|
|
34913
|
+
const resultPath = store.artifactPath(draft.id, "verification.json");
|
|
34796
34914
|
writeFileSync8(resultPath, `${JSON.stringify(executed.output, null, 2)}
|
|
34797
34915
|
`, { flag: "wx", mode: 384 });
|
|
34798
34916
|
const prior = store.evidence(draft.id);
|
|
@@ -34816,6 +34934,7 @@ function captureTable(state) {
|
|
|
34816
34934
|
state.db.exec("CREATE TABLE IF NOT EXISTS discovery_capture (draft_id TEXT PRIMARY KEY REFERENCES discovery_draft(id) ON DELETE CASCADE, scope_id TEXT, job_id TEXT, phase TEXT NOT NULL, entries_json TEXT)");
|
|
34817
34935
|
state.db.prepare("UPDATE discovery_capture SET draft_id=draft_id WHERE 0").run();
|
|
34818
34936
|
const columns = state.db.prepare("PRAGMA table_info(discovery_capture)").all();
|
|
34937
|
+
if (!columns.some((column) => column.name === "origins_json")) state.db.exec("ALTER TABLE discovery_capture ADD COLUMN origins_json TEXT");
|
|
34819
34938
|
if (!columns.some((column) => column.name === "evidence_scope_id")) state.db.exec("ALTER TABLE discovery_capture ADD COLUMN evidence_scope_id TEXT");
|
|
34820
34939
|
state.db.exec("RELEASE discovery_capture_schema");
|
|
34821
34940
|
} catch (error62) {
|
|
@@ -35104,7 +35223,7 @@ ${detail}
|
|
|
35104
35223
|
if (previousEvidence) await client.closeScope(previousEvidence);
|
|
35105
35224
|
const id2 = randomUUID9();
|
|
35106
35225
|
state.db.prepare("INSERT INTO discovery_capture (draft_id,scope_id,job_id,phase,entries_json) VALUES (?,?,?,?,NULL) ON CONFLICT(draft_id) DO UPDATE SET scope_id=excluded.scope_id,job_id=excluded.job_id,phase=excluded.phase,entries_json=NULL,evidence_scope_id=excluded.scope_id").run(draft.id, scope2.id, id2, "starting");
|
|
35107
|
-
state.db.prepare("UPDATE discovery_capture SET evidence_scope_id=? WHERE draft_id=?").run(scope2.id, draft.id);
|
|
35226
|
+
state.db.prepare("UPDATE discovery_capture SET evidence_scope_id=?,origins_json=? WHERE draft_id=?").run(scope2.id, JSON.stringify(transport.allowedOrigins), draft.id);
|
|
35108
35227
|
await client.enqueueJob(scope2.id, { job: { id: id2, kind: "begin", label: draft.name, entity: nameParts[0], operation: nameParts[1], effect: effect2, inputs: envelope.verification.inputs } });
|
|
35109
35228
|
const job = await awaitCaptureJob(client, id2);
|
|
35110
35229
|
if (job.status !== "delivered" || job.result?.state !== "recording") return fail5(`Recording start is unconfirmed (${job.status}). Do not operate the app. Retry ${command4(invocation, "discover capture start")} after inspecting ${command4(invocation, "discover status")}.
|
|
@@ -35182,7 +35301,7 @@ Review: ${command4(invocation, "discover status")}
|
|
|
35182
35301
|
}
|
|
35183
35302
|
withBaselines(root, (baselines) => baselines.stopCapture(draft.id));
|
|
35184
35303
|
return ok4(`Recording stopped; admission is confirmed closed. ${entries2.length} requests captured; showing up to five ranked candidates. Ranking is a review aid; all metadata remains available.
|
|
35185
|
-
${rankCandidates(entries2, parseDraftEnvelope(source).envelope?.verification?.inputs ?? {}).map(candidateLine).join("\n")}
|
|
35304
|
+
${rankCandidates(entries2, parseDraftEnvelope(source).envelope?.verification?.inputs ?? {}, String(parseDraftEnvelope(source).envelope?.definition.effect ?? ""), row.origins_json ? JSON.parse(row.origins_json) : []).map(candidateLine).join("\n")}
|
|
35186
35305
|
Use a cN candidate ID returned in this recording:
|
|
35187
35306
|
Inspect its retained request or response: ${command4(invocation, "discover capture show <candidate>")}
|
|
35188
35307
|
Select the causal request for the draft: ${command4(invocation, "discover capture select <candidate>")}
|
|
@@ -35262,7 +35381,7 @@ Inspect a returned candidate: ${command4(invocation, `discover capture show c${p
|
|
|
35262
35381
|
const result3 = await awaitCaptureJob(client, id2);
|
|
35263
35382
|
if (result3.status !== "delivered") return fail5(`Auth source probe is ${result3.status}; no source was confirmed. Retry only this probe while evidence is live.
|
|
35264
35383
|
`, 12);
|
|
35265
|
-
const path2 =
|
|
35384
|
+
const path2 = store.artifactPath(draft.id, "source.json");
|
|
35266
35385
|
writeFileSync8(path2, `${JSON.stringify(result3.result, null, 2)}
|
|
35267
35386
|
`, { mode: 384 });
|
|
35268
35387
|
await client.acknowledgeJob(id2);
|
|
@@ -35320,7 +35439,7 @@ var init_discover = __esm({
|
|
|
35320
35439
|
// src/output.ts
|
|
35321
35440
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
35322
35441
|
import { chmod, mkdtemp, open as open3, rename } from "node:fs/promises";
|
|
35323
|
-
import { tmpdir as
|
|
35442
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
35324
35443
|
import { join as join13 } from "node:path";
|
|
35325
35444
|
async function completedFile(directory, name, content) {
|
|
35326
35445
|
const target = join13(directory, name);
|
|
@@ -35336,7 +35455,7 @@ async function completedFile(directory, name, content) {
|
|
|
35336
35455
|
await chmod(target, 384);
|
|
35337
35456
|
return target;
|
|
35338
35457
|
}
|
|
35339
|
-
async function finalizeOutput(output2, sink, directory =
|
|
35458
|
+
async function finalizeOutput(output2, sink, directory = tmpdir2()) {
|
|
35340
35459
|
let stdout = output2.stdout;
|
|
35341
35460
|
let stderr = output2.stderr;
|
|
35342
35461
|
const size = () => Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
|
|
@@ -35783,7 +35902,7 @@ var init_main2 = __esm({
|
|
|
35783
35902
|
|
|
35784
35903
|
// src/plugin.ts
|
|
35785
35904
|
import { createHash as createHash6 } from "node:crypto";
|
|
35786
|
-
import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync10, readFileSync as readFileSync9, readdirSync, renameSync as renameSync4, rmSync as
|
|
35905
|
+
import { cpSync, existsSync as existsSync5, mkdirSync as mkdirSync10, readFileSync as readFileSync9, readdirSync, renameSync as renameSync4, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
35787
35906
|
import { homedir as homedir2 } from "node:os";
|
|
35788
35907
|
import { dirname as dirname8, join as join14, resolve as resolve7 } from "node:path";
|
|
35789
35908
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -35885,7 +36004,7 @@ function ensureAgentPlugin(agent2, env, sourceOverride) {
|
|
|
35885
36004
|
const current = marker?.bundleDigest === bundleDigest && marker.version === version2 && completeCopy(source, plugin);
|
|
35886
36005
|
if (!current) {
|
|
35887
36006
|
const staging = `${plugin}.stage-${process.pid}`;
|
|
35888
|
-
|
|
36007
|
+
rmSync5(staging, { recursive: true, force: true });
|
|
35889
36008
|
mkdirSync10(dirname8(staging), { recursive: true });
|
|
35890
36009
|
cpSync(source, staging, { recursive: true });
|
|
35891
36010
|
const manifestPath = join14(staging, `.${agent2}-plugin`, "plugin.json");
|
|
@@ -35902,7 +36021,7 @@ function ensureAgentPlugin(agent2, env, sourceOverride) {
|
|
|
35902
36021
|
if (existsSync5(backup)) renameSync4(backup, plugin);
|
|
35903
36022
|
throw error62;
|
|
35904
36023
|
}
|
|
35905
|
-
|
|
36024
|
+
rmSync5(backup, { recursive: true, force: true });
|
|
35906
36025
|
}
|
|
35907
36026
|
const marketplacePath = join14(marketplace, "marketplace.json");
|
|
35908
36027
|
const document = readJson(marketplacePath) ?? { name: "personal", interface: { displayName: "Personal" }, plugins: [] };
|
|
@@ -36129,7 +36248,7 @@ var init_analytics2 = __esm({
|
|
|
36129
36248
|
});
|
|
36130
36249
|
|
|
36131
36250
|
// src/usage/ownership.ts
|
|
36132
|
-
import { mkdirSync as mkdirSync11, chmodSync as chmodSync7, existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync10, renameSync as renameSync5, unlinkSync as
|
|
36251
|
+
import { mkdirSync as mkdirSync11, chmodSync as chmodSync7, existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync10, renameSync as renameSync5, unlinkSync as unlinkSync3, statSync as statSync3 } from "node:fs";
|
|
36133
36252
|
import { createHash as createHash7, randomUUID as randomUUID12 } from "node:crypto";
|
|
36134
36253
|
import { join as join15, dirname as dirname9, isAbsolute as isAbsolute2 } from "node:path";
|
|
36135
36254
|
import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
|
|
@@ -36143,7 +36262,7 @@ function requestCollectorTarget(root, entry2, env) {
|
|
|
36143
36262
|
renameSync5(pending, targetFile(root));
|
|
36144
36263
|
} finally {
|
|
36145
36264
|
try {
|
|
36146
|
-
|
|
36265
|
+
unlinkSync3(pending);
|
|
36147
36266
|
} catch {
|
|
36148
36267
|
}
|
|
36149
36268
|
}
|
|
@@ -21,6 +21,6 @@ Check the selected file with `apilift discover check`; it sends no app request a
|
|
|
21
21
|
|
|
22
22
|
## Recover and finish
|
|
23
23
|
|
|
24
|
-
Use `apilift discover status` to recover the selected draft, result path, recording phase, or pending publication. A lost publication response is reconciled with the exact retained `discover publish` request before editing, deferring, or discarding. `discover rebase` is for a version conflict after reviewing the current resource. `discover defer` retains owned work, `discover discard` abandons only its unpublished draft, and `discover close` leaves the app when owned in-flight work has settled. A stopped recording must be confirmed or canceled before changing drafts or tabs. Follow [recovery.md](references/recovery.md) before treating a failed route as a terminal blocker.
|
|
24
|
+
Use `apilift discover status` to recover the selected draft, result path, recording phase, or pending publication. A lost publication response is reconciled with the exact retained `discover publish` request before editing, deferring, or discarding. `discover rebase` is for a version conflict after reviewing the current resource. `discover defer` retains owned work, `discover discard` abandons only its unpublished draft, and `discover close` abandons that session’s selected and deferred drafts and leaves the app when owned in-flight work has settled. Publication and abandonment delete that draft and temporary verification/capture evidence. Use `discover defer` before switching resources to preserve unfinished work and its evidence while discovery stays open; resuming it requires no new capture. Closing discovery ends that retention and preserves other sessions’ drafts. A stopped recording must be confirmed or canceled before changing drafts or tabs. Follow [recovery.md](references/recovery.md) before treating a failed route as a terminal blocker.
|
|
25
25
|
|
|
26
26
|
Completion means the requested actions are published and the original authorized outcome is handled, or a specific human-only or external prerequisite is identified. Review consumer descriptions for behavior, consequences and limits: author verification establishes publication evidence, while published execution validates its response without automatic readback. Do not prescribe routine browser inspection. Consumers decide whether their task needs additional verification. Inspect published consumer help from a fresh session and obtain required values through its published source actions; the author's local verification inputs are not consumer coverage. Do not send an extra mutation merely to demonstrate publication. Report actual verification, authorized test state, one optional cleanup attempt if relevant, and remaining gaps.
|
|
@@ -12,7 +12,7 @@ Select the useful response subtree with `response.path` when needed and validate
|
|
|
12
12
|
|
|
13
13
|
During discovery, a mutation needs a separate read-only proof request and meaningful comparisons to the intended state. A proof failure leaves the write possibly applied. Repair the proof and use `apilift discover run --proof-only` against the saved primary response; this sends only the readback. Never repeat a possibly sent write just to get stronger proof. For a delete, an explicit absence proof may establish success. A readback or probe using POST may declare semantic `effect: "read"`; method alone is not the effect.
|
|
14
14
|
|
|
15
|
-
Bind a returned identifier in the proof URL with `{result./id}`, or in its JSON body/query with `{"$result":"/id"}`; result pointers refer to the projected primary result. For a nested collection, use `proof.select: {"path":"/ticket/fields","where":[{"path":"/id","result":"/id"}]}` and compare `equals` paths relative to its single matching member. The selector must match exactly one item. Check identity and the fields that establish the intended effect, not an entire server-maintained object or a fixed array position.
|
|
15
|
+
Bind a returned identifier in the proof URL with `{result./id}`, or in its JSON body/query with `{"$result":"/id"}`; result pointers refer to the projected primary result. For a nested collection, use `proof.select: {"path":"/ticket/fields","where":[{"path":"/id","result":"/id"}]}` and compare `equals` paths relative to its single matching member. The selector must match exactly one item. Check identity and the fields that establish the intended effect, not an entire server-maintained object or a fixed array position. For a dynamic object key, use `{"attributes":{"{input.attribute_id}":{"$input":"value"}}}` in the request body. A proof path such as `/attributes/{input.attribute_id}` substitutes one complete pointer segment and escapes `/` and `~` in its value; other pointer segments remain literal. Prefer a targeted detail or search read; absence from a partial list does not prove deletion.
|
|
16
16
|
|
|
17
17
|
When the task needs a few records from a large collection, look for native search, filters or a detail endpoint in documentation or observed UI requests before enumerating the list. Expose observed parameters and verify that they narrow the results. If only a full response is available, search its saved file and extract matching records or fields; do not load the entire catalog into context. Keep stable identity and summary fields in list results; inspect variable configuration only for the selected item. Preserve dynamic configuration as structured JSON with an appropriate schema, rather than enumerating catalog entries or removing validation to pass.
|
|
18
18
|
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
2. Read the complete bounded diagnostic and exact continuation. Use `apilift doctor`, selected CLI help or the editable file only for the uncertainty at hand. Retry after a relevant state change or an explicitly safe recovery instruction; never infer success from a shell wrapper without the process exit status.
|
|
5
5
|
3. Reassess the requested outcome. A local command, browser or credential failure is evidence about that route, not proof that the app lacks the capability. Continue through another authorized documented route or independent action when it can advance the outcome without weakening proof or policy.
|
|
6
6
|
4. Ask the person only for login, MFA, CAPTCHA, WebAuthn, credential provisioning in the secure UI, new permission or a material scope choice they alone can supply. Do not ask them to paste a secret into chat or a command.
|
|
7
|
-
5. If no authorized route remains, report the precise missing outcome, retained owned state, materially different routes checked and exact prerequisite. Use `discover defer` to retain retryable work
|
|
7
|
+
5. If no authorized route remains, report the precise missing outcome, retained owned state, materially different routes checked and exact prerequisite. Use `discover defer` to retain retryable work while continuing discovery. `discover close` abandons the session’s selected and deferred drafts after selected in-flight work is settled.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apilift/cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=24.16.0 <27"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "7fcce0197a43f41c8f4e5dfa909650baf4be49de",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
42
|
"url": "git+https://github.com/egor-sergeev/apilift.git",
|