@bridge_gpt/mcp-server 0.2.36 → 0.2.38
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 +48 -8
- package/build/base-url.js +79 -0
- package/build/bridge-api-urls.js +9 -0
- package/build/chain-orchestrator.js +93 -15
- package/build/claude-user-config-doctor.js +317 -0
- package/build/commands.generated.js +2 -1
- package/build/conductor/bridge-api-client.js +178 -4
- package/build/conductor-bin.js +1 -1
- package/build/conductor-bundle-artifacts.js +7 -6
- package/build/credential-store.js +205 -4
- package/build/direct-ticket-tools.js +70 -0
- package/build/doctor.js +239 -80
- package/build/executor/cli.js +51 -1
- package/build/executor/credentials.js +1 -7
- package/build/executor/deps.js +18 -1
- package/build/executor/env.js +51 -25
- package/build/executor/heartbeat.js +138 -17
- package/build/executor/http-client.js +49 -8
- package/build/executor/job-errors.js +4 -0
- package/build/executor/job-runner.js +422 -22
- package/build/executor/observation.js +130 -0
- package/build/executor/permissions.js +104 -8
- package/build/executor/preflight.js +32 -0
- package/build/executor/runner.js +8 -0
- package/build/executor/test-clock.js +67 -3
- package/build/executor/types.js +4 -1
- package/build/executor/worker-command.js +11 -3
- package/build/executor/worker-config-isolation.js +287 -0
- package/build/executor/worker-finalization.js +68 -14
- package/build/executor/worktree.js +46 -4
- package/build/index.js +614 -244
- package/build/init.js +363 -73
- package/build/install-bridge.js +568 -80
- package/build/launcher-config-inspection.js +351 -0
- package/build/mcp-invoke.js +49 -6
- package/build/mcp-provisioning.js +30 -7
- package/build/mcp-registration-doctor.js +14 -5
- package/build/notifications.js +553 -0
- package/build/pipeline-orchestrator.js +146 -4
- package/build/pipeline-utils.js +3 -0
- package/build/pipelines.generated.js +22 -9
- package/build/plan-execution-ledger.js +550 -0
- package/build/plan-phase-routing.js +272 -0
- package/build/plane/alembic-head.js +110 -0
- package/build/plane/build-freshness.js +167 -0
- package/build/plane/cli.js +480 -0
- package/build/plane/defaults.js +266 -0
- package/build/plane/manifest.js +377 -0
- package/build/plane/member-logs.js +147 -0
- package/build/plane/member-roster.js +147 -0
- package/build/plane/preflight.js +289 -0
- package/build/plane/shutdown.js +195 -0
- package/build/plane/status.js +125 -0
- package/build/plane/supervisor.js +569 -0
- package/build/plane/test-fakes.js +156 -0
- package/build/plane/types.js +75 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +2 -0
- package/build/setup-epic.js +662 -27
- package/build/sfcc/log-gate.js +38 -11
- package/build/sfcc/log-query.js +55 -15
- package/build/sfcc/ocapi-shape.js +70 -14
- package/build/sfcc/output.js +41 -11
- package/build/sfcc/permissions.js +24 -2
- package/build/sfcc/read-body.js +92 -0
- package/build/sfcc/read-projection.js +185 -0
- package/build/sfcc/read-result.js +158 -0
- package/build/sfcc/reads-custom-object-def.js +57 -34
- package/build/sfcc/reads-site-preference.js +86 -33
- package/build/sfcc/reads-system-object.js +50 -38
- package/build/sfcc/sfcc-result.js +106 -0
- package/build/sfcc/tool-wrapper.js +56 -13
- package/build/sfcc/write-grants.js +45 -22
- package/build/sfcc/write-guard.js +21 -13
- package/build/sfcc/write-result.js +71 -15
- package/build/sfcc/write-tool-common.js +126 -32
- package/build/sfcc/writes-custom-object-def.js +6 -2
- package/build/sfcc/writes-system-object.js +11 -50
- package/build/start-tickets-prereqs.js +129 -0
- package/build/start-tickets.js +17 -13
- package/build/ticket-backend-metadata.js +59 -0
- package/build/ticket-key-utils.js +92 -0
- package/build/tool-error-envelope.js +71 -0
- package/build/tool-surface-gating.js +72 -0
- package/build/update-status.js +102 -0
- package/build/upgrade-advice.js +47 -0
- package/build/upgrade-cli.js +417 -101
- package/build/version.generated.js +1 -1
- package/build/worktree-core.js +73 -0
- package/docs/CONDUCTOR.md +23 -8
- package/package.json +3 -3
- package/pipelines/implement-ticket.json +15 -5
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
* Shared OCAPI write-grant JSON helpers (BAPI-582 foundation).
|
|
3
3
|
*
|
|
4
4
|
* This module is the single source of truth for the OCAPI write-grant JSON that
|
|
5
|
-
* is both echoed by
|
|
5
|
+
* is both echoed by write handlers on a 403 and printed by the
|
|
6
6
|
* `check_permissions` diagnostic. Keeping one definition guarantees the
|
|
7
7
|
* remediation JSON an operator sees is identical no matter which surface it
|
|
8
8
|
* comes from.
|
|
9
|
+
*
|
|
10
|
+
* BAPI-766 converted the 403 handler response from prose to the universal SFCC
|
|
11
|
+
* failure envelope. The remediation is not lost — it moved from an English
|
|
12
|
+
* paragraph into structured, non-secret fields under `error.details.remediation`,
|
|
13
|
+
* where a caller can read it without parsing text and `result.isError === true`
|
|
14
|
+
* still detects the failure.
|
|
9
15
|
*/
|
|
16
|
+
import { formatSfccWriteFailure } from "./sfcc-result.js";
|
|
10
17
|
// ---------------------------------------------------------------------------
|
|
11
18
|
// Write resource families
|
|
12
19
|
// ---------------------------------------------------------------------------
|
|
@@ -52,29 +59,45 @@ export function formatOcapiWriteGrantJson(ocapiVersion, clientIdPlaceholder = "<
|
|
|
52
59
|
// ---------------------------------------------------------------------------
|
|
53
60
|
// 403 write-grant UX
|
|
54
61
|
// ---------------------------------------------------------------------------
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*/
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
: `\nResponse body:\n${JSON.stringify(body, null, 2)}\n`;
|
|
65
|
-
return (`HTTP 403: OCAPI write access denied for ${operation} ${path}.\n` +
|
|
66
|
-
bodyLine +
|
|
67
|
-
`\nTo grant write access, paste the JSON below in Business Manager:\n` +
|
|
68
|
-
` Administration > Site Development > Open Commerce API Settings → Data API tab\n\n` +
|
|
69
|
-
`${formatOcapiWriteGrantJson(ocapiVersion)}\n\n` +
|
|
70
|
-
`Replace <YOUR_CLIENT_ID> with the client_id from your dw.json.`);
|
|
62
|
+
/** Where an operator pastes the grant JSON in Business Manager. */
|
|
63
|
+
export const OCAPI_WRITE_GRANT_BUSINESS_MANAGER_PATH = "Administration > Site Development > Open Commerce API Settings → Data API tab";
|
|
64
|
+
/** The one instruction an operator must act on after pasting the grant JSON. */
|
|
65
|
+
export const OCAPI_WRITE_GRANT_CLIENT_ID_INSTRUCTION = "Replace <YOUR_CLIENT_ID> with the client_id from your dw.json.";
|
|
66
|
+
/** The fixed, Bridge-authored message for a 403 on an OCAPI write. */
|
|
67
|
+
export function ocapiWriteForbiddenMessage(operation, path) {
|
|
68
|
+
return (`OCAPI write access denied for ${operation} ${path}. The client is not granted ` +
|
|
69
|
+
`write access to this resource. See error.details.remediation for the exact ` +
|
|
70
|
+
`write-grant settings to paste in Business Manager.`);
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* Build the unified 403 failure for a denied OCAPI write.
|
|
74
|
+
*
|
|
75
|
+
* The remediation an operator needs — where to paste, what to paste, and the one
|
|
76
|
+
* substitution to make — is carried as structured fields under
|
|
77
|
+
* `error.details.remediation` rather than as prose, so the result is parseable
|
|
78
|
+
* and `isError: true` detects it like every other SFCC failure. The grant JSON
|
|
79
|
+
* keeps the `<YOUR_CLIENT_ID>` placeholder: the real resolved client_id is never
|
|
80
|
+
* substituted, so nothing secret enters the response.
|
|
81
|
+
*
|
|
82
|
+
* The upstream 403 body is preserved verbatim under `error.details.body`, the
|
|
83
|
+
* same way an OCAPI read fault body is preserved.
|
|
75
84
|
*/
|
|
76
85
|
export function writeGrantForbiddenResult(params) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
const { operation, path, ocapiVersion, body } = params;
|
|
87
|
+
return formatSfccWriteFailure({
|
|
88
|
+
code: "OCAPI_WRITE_ERROR",
|
|
89
|
+
status: 403,
|
|
90
|
+
message: ocapiWriteForbiddenMessage(operation, path),
|
|
91
|
+
source: "ocapi",
|
|
92
|
+
details: {
|
|
93
|
+
operation,
|
|
94
|
+
path,
|
|
95
|
+
remediation: {
|
|
96
|
+
business_manager_path: OCAPI_WRITE_GRANT_BUSINESS_MANAGER_PATH,
|
|
97
|
+
instruction: OCAPI_WRITE_GRANT_CLIENT_ID_INSTRUCTION,
|
|
98
|
+
write_grant: buildOcapiWriteGrantSettings(ocapiVersion),
|
|
99
|
+
},
|
|
100
|
+
...(body === undefined ? {} : { body }),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
80
103
|
}
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
* mutation. A non-null return value is
|
|
7
7
|
* the pre-formatted MCP rejection to return directly; a null return means the
|
|
8
8
|
* effective instance is a sandbox and the handler may proceed.
|
|
9
|
+
*
|
|
10
|
+
* BAPI-766 moved the rejection's *representation* to the shared SFCC formatter
|
|
11
|
+
* so the guardrail fails at the protocol level like every other SFCC failure.
|
|
12
|
+
* The guard's decision is untouched: `undefined` still defaults to `"sandbox"`,
|
|
13
|
+
* `"sandbox"` still proceeds, and every other value is still refused.
|
|
9
14
|
*/
|
|
10
|
-
|
|
11
|
-
// Helpers
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
/** Wrap text in the standard MCP text-result shape. */
|
|
14
|
-
function textResult(text) {
|
|
15
|
-
return { content: [{ type: "text", text }] };
|
|
16
|
-
}
|
|
15
|
+
import { formatSfccWriteFailure } from "./sfcc-result.js";
|
|
17
16
|
// ---------------------------------------------------------------------------
|
|
18
17
|
// Guard
|
|
19
18
|
// ---------------------------------------------------------------------------
|
|
@@ -22,18 +21,27 @@ function textResult(text) {
|
|
|
22
21
|
*
|
|
23
22
|
* SFCC write tools are sandbox-only in this foundation. The effective instance
|
|
24
23
|
* defaults to `"sandbox"` when `undefined` (some schemas default the value
|
|
25
|
-
* elsewhere), so an omitted instance is allowed. Any other value returns
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* elsewhere), so an omitted instance is allowed. Any other value returns the
|
|
25
|
+
* unified validation failure naming the rejected instance; a sandbox returns
|
|
26
|
+
* `null` so the caller proceeds.
|
|
27
|
+
*
|
|
28
|
+
* `details` carries non-sensitive guard metadata only — the rejected value is
|
|
29
|
+
* caller-supplied.
|
|
28
30
|
*/
|
|
29
31
|
export function rejectIfNotSandboxForWrite(instance) {
|
|
30
32
|
const effective = instance === undefined ? "sandbox" : instance;
|
|
31
33
|
if (effective === "sandbox")
|
|
32
34
|
return null;
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
+
return formatSfccWriteFailure({
|
|
36
|
+
code: "VALIDATION_ERROR",
|
|
35
37
|
status: 400,
|
|
36
38
|
message: `SFCC write tools are sandbox-only. Refusing to write against instance ` +
|
|
37
39
|
`'${effective}'. Re-run the write against a developer sandbox instance.`,
|
|
38
|
-
|
|
40
|
+
source: "tool",
|
|
41
|
+
details: {
|
|
42
|
+
guard: "sandbox_only",
|
|
43
|
+
received_instance: effective,
|
|
44
|
+
supported_instances: ["sandbox"],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
39
47
|
}
|
|
@@ -1,26 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared write-result formatter (BAPI-582 foundation).
|
|
2
|
+
* Shared write-result formatter (BAPI-582 foundation, unified by BAPI-766).
|
|
3
3
|
*
|
|
4
|
-
* This is the single formatting path for all
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* This is the single formatting path for all SFCC write tools. Handlers pass the
|
|
5
|
+
* raw `OcapiGetResult` from `ocapiPut`/`ocapiPatch` here rather than inventing
|
|
6
|
+
* their own 403/fault/success response logic, guaranteeing a consistent surface
|
|
7
|
+
* across every write tool.
|
|
8
|
+
*
|
|
9
|
+
* Successful results never gain `isError`, but their echoed `body` is redacted
|
|
10
|
+
* (BAPI-769): an attribute-definition write's success body can carry the same
|
|
11
|
+
* `default_value` a read can, so it is withheld via `read-body.ts`'s
|
|
12
|
+
* `redactSfccBody` before serialization. Every other *failure* branch —
|
|
13
|
+
* including the 403 write-grant remediation, which used to be prose — returns
|
|
14
|
+
* the universal nested envelope from `sfcc-result.ts`, so `result.isError ===
|
|
15
|
+
* true` is total across a write tool's post-transport failures as well as its
|
|
16
|
+
* pre-transport ones.
|
|
8
17
|
*/
|
|
9
18
|
import { writeGrantForbiddenResult } from "./write-grants.js";
|
|
10
19
|
import { DEFAULT_OCAPI_VERSION } from "./config.js";
|
|
11
|
-
|
|
20
|
+
import { formatSfccWriteFailure } from "./sfcc-result.js";
|
|
21
|
+
import { redactSfccBody } from "./read-body.js";
|
|
22
|
+
/** Wrap text in the standard MCP text-result shape (success path only). */
|
|
12
23
|
function textResult(text) {
|
|
13
24
|
return { content: [{ type: "text", text }] };
|
|
14
25
|
}
|
|
15
26
|
// ---------------------------------------------------------------------------
|
|
27
|
+
// Messages
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/** True for a plain (non-array, non-null) object. */
|
|
30
|
+
function isPlainObject(value) {
|
|
31
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
/** Bridge-authored fallback used when an OCAPI write body carries no usable `fault.message`. */
|
|
34
|
+
export function ocapiWriteFallbackMessage(status) {
|
|
35
|
+
return (`The SFCC OCAPI Data API returned HTTP ${status} for this write and supplied no ` +
|
|
36
|
+
`fault message. The upstream response body is preserved under error.details.`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Pull a usable human-readable message out of an OCAPI write fault body.
|
|
40
|
+
*
|
|
41
|
+
* Raw response text is never promoted into `error.message` — an unparsed body
|
|
42
|
+
* falls through to the stable Bridge-authored fallback instead.
|
|
43
|
+
*/
|
|
44
|
+
function ocapiWriteFaultMessage(body, status) {
|
|
45
|
+
if (isPlainObject(body) && isPlainObject(body.fault)) {
|
|
46
|
+
const faultMessage = body.fault.message;
|
|
47
|
+
if (typeof faultMessage === "string" && faultMessage.trim() !== "") {
|
|
48
|
+
return faultMessage;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return ocapiWriteFallbackMessage(status);
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
16
54
|
// Formatter
|
|
17
55
|
// ---------------------------------------------------------------------------
|
|
18
56
|
/**
|
|
19
57
|
* Format an OCAPI write result into the standard MCP tool response.
|
|
20
58
|
*
|
|
21
|
-
* - `403` → the paste-ready write-grant
|
|
22
|
-
*
|
|
23
|
-
* -
|
|
59
|
+
* - `403` → the unified failure carrying the paste-ready write-grant
|
|
60
|
+
* remediation as structured data (via write-grants.ts).
|
|
61
|
+
* - success → JSON with `status`, `outcome`, and `body`; no `isError`. `body`
|
|
62
|
+
* is redacted (BAPI-769) before serialization: a successful
|
|
63
|
+
* attribute-definition PUT/PATCH echoes the updated resource in the same
|
|
64
|
+
* shape a GET returns, so it can carry a real `default_value` exactly like a
|
|
65
|
+
* read can — `redactSfccBody` withholds it here the same way `readSfccBody`
|
|
66
|
+
* withholds it on the read path. Every other key survives untouched.
|
|
67
|
+
* - other failure → the unified `OCAPI_WRITE_ERROR` failure, source `ocapi`,
|
|
68
|
+
* with the structured fault mapping and the upstream body under
|
|
69
|
+
* `error.details`.
|
|
70
|
+
*
|
|
71
|
+
* No `upstreamHttpStatus` is supplied: `ocapiPut`/`ocapiPatch` surface
|
|
72
|
+
* `Response.status` verbatim, so the reported status *is* the upstream status
|
|
73
|
+
* and the shared formatter would rightly omit a duplicate.
|
|
24
74
|
*/
|
|
25
75
|
export function formatOcapiWriteToolResult(result, operation, path, ocapiVersion = DEFAULT_OCAPI_VERSION) {
|
|
26
76
|
if (result.status === 403) {
|
|
@@ -35,13 +85,19 @@ export function formatOcapiWriteToolResult(result, operation, path, ocapiVersion
|
|
|
35
85
|
return textResult(JSON.stringify({
|
|
36
86
|
status: result.status,
|
|
37
87
|
outcome: result.outcome,
|
|
38
|
-
body: result.body,
|
|
88
|
+
body: redactSfccBody(result.body),
|
|
39
89
|
}));
|
|
40
90
|
}
|
|
41
|
-
return
|
|
42
|
-
|
|
91
|
+
return formatSfccWriteFailure({
|
|
92
|
+
code: "OCAPI_WRITE_ERROR",
|
|
43
93
|
status: result.status,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
94
|
+
message: ocapiWriteFaultMessage(result.body, result.status),
|
|
95
|
+
source: "ocapi",
|
|
96
|
+
details: {
|
|
97
|
+
operation,
|
|
98
|
+
path,
|
|
99
|
+
fault: result.fault,
|
|
100
|
+
body: result.body,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
47
103
|
}
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared helpers for the SFCC write MCP tools (BAPI-584).
|
|
2
|
+
* Shared helpers for the SFCC write MCP tools (BAPI-584, unified by BAPI-766).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the destructive tool annotations, the MCP text-result
|
|
7
|
-
* set of safe pre-transport
|
|
8
|
-
* single path-segment encoder.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* All nine write tools — the two custom-object-definition tools, the
|
|
5
|
+
* site-preference value tool, and the six system-object tools — need the same
|
|
6
|
+
* small set of primitives: the destructive tool annotations, the MCP text-result
|
|
7
|
+
* shape, a consistent set of safe pre-transport failure envelopes (validation /
|
|
8
|
+
* payload fault / unexpected), and a single path-segment encoder.
|
|
9
|
+
*
|
|
10
|
+
* Every failure helper here returns the *universal* nested failure envelope
|
|
11
|
+
* built by `sfcc-result.ts` — `{ error: { code, status, message, source,
|
|
12
|
+
* details } }` with `isError: true`. There is no write-local envelope
|
|
13
|
+
* construction and no second serialization path, so a write tool's own
|
|
14
|
+
* pre-transport failure is indistinguishable in shape from a gate rejection, an
|
|
15
|
+
* OCAPI read fault, or a WebDAV log failure. That is what makes
|
|
16
|
+
* `result.isError === true` a total predicate across the SFCC surface.
|
|
17
|
+
*
|
|
18
|
+
* Nothing here ever serializes a stack trace, a raw thrown value, or a complete
|
|
19
|
+
* Zod issue object: validation issues are projected down to `{ path, message }`.
|
|
11
20
|
*/
|
|
12
21
|
import { z } from "zod";
|
|
22
|
+
import { formatSfccWriteFailure } from "./sfcc-result.js";
|
|
23
|
+
import { SfccWritePayloadFault } from "./writes-system-object-payloads.js";
|
|
13
24
|
// ---------------------------------------------------------------------------
|
|
14
25
|
// Annotations
|
|
15
26
|
// ---------------------------------------------------------------------------
|
|
@@ -24,35 +35,86 @@ export const WRITE_ANNOTATIONS = {
|
|
|
24
35
|
idempotentHint: false,
|
|
25
36
|
openWorldHint: true,
|
|
26
37
|
};
|
|
27
|
-
/** Wrap text in the standard MCP text-result shape. */
|
|
38
|
+
/** Wrap text in the standard MCP text-result shape (success path only). */
|
|
28
39
|
export function textResult(text) {
|
|
29
40
|
return { content: [{ type: "text", text }] };
|
|
30
41
|
}
|
|
31
42
|
// ---------------------------------------------------------------------------
|
|
32
|
-
//
|
|
43
|
+
// Fixed messages
|
|
33
44
|
// ---------------------------------------------------------------------------
|
|
34
45
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
46
|
+
* The one message returned when write arguments fail schema validation. The
|
|
47
|
+
* offending fields are reported under `error.details.issues`, never inline.
|
|
48
|
+
*/
|
|
49
|
+
export const SFCC_WRITE_VALIDATION_ERROR_MESSAGE = "Input failed schema validation before any OCAPI call. " +
|
|
50
|
+
"See error.details.issues for the offending fields.";
|
|
51
|
+
/**
|
|
52
|
+
* The one message returned for an unexpected throw inside a write handler.
|
|
53
|
+
* Bridge-authored and fixed: the thrown value is never serialized, so a
|
|
54
|
+
* credential, token, request header, or stack frame carried by the exception
|
|
55
|
+
* cannot reach the caller.
|
|
38
56
|
*/
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
export const SFCC_WRITE_INTERNAL_ERROR_MESSAGE = "An unexpected internal error occurred while handling this SFCC write. " +
|
|
58
|
+
"The underlying failure detail is withheld from this response to avoid " +
|
|
59
|
+
"leaking credentials or internal state.";
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Pre-transport failure envelopes
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/** Reduce Zod issues to field path + message; nothing else is disclosed. */
|
|
64
|
+
function safeZodIssues(err) {
|
|
65
|
+
const issues = Array.isArray(err.issues) ? err.issues : [];
|
|
66
|
+
return issues.map((issue) => ({
|
|
67
|
+
path: Array.isArray(issue.path) ? issue.path.map(String).join(".") : "",
|
|
68
|
+
message: typeof issue.message === "string" ? issue.message : "Invalid input.",
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert a Zod validation failure into the unified `VALIDATION_ERROR` envelope.
|
|
73
|
+
*
|
|
74
|
+
* Private: it has no caller outside this module — `preTransportErrorEnvelope`
|
|
75
|
+
* is the dispatcher every write handler actually uses. Issues are projected to
|
|
76
|
+
* the safe `{ path, message }` shape so no raw Zod issue object (with its
|
|
77
|
+
* `expected` / `received` / `unionErrors` internals) reaches a tool result.
|
|
78
|
+
*/
|
|
79
|
+
function zodValidationEnvelope(err) {
|
|
80
|
+
return formatSfccWriteFailure({
|
|
81
|
+
code: "VALIDATION_ERROR",
|
|
42
82
|
status: 400,
|
|
43
|
-
message:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
83
|
+
message: SFCC_WRITE_VALIDATION_ERROR_MESSAGE,
|
|
84
|
+
source: "tool",
|
|
85
|
+
details: { issues: safeZodIssues(err) },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A local, pre-transport payload-shape failure surfaced in OCAPI-fault
|
|
90
|
+
* vocabulary (e.g. `IdConflictException`), without a network round trip.
|
|
91
|
+
*
|
|
92
|
+
* Private for the same reason as the Zod variant: handlers reach it through the
|
|
93
|
+
* dispatcher. `source` is `"tool"` — the fault was decided locally, so labelling
|
|
94
|
+
* it `"ocapi"` would claim OCAPI said something it never did.
|
|
95
|
+
*/
|
|
96
|
+
function payloadFaultEnvelope(fault) {
|
|
97
|
+
return formatSfccWriteFailure({
|
|
98
|
+
code: "OCAPI_WRITE_ERROR",
|
|
99
|
+
status: fault.status,
|
|
100
|
+
message: fault.message,
|
|
101
|
+
source: "tool",
|
|
102
|
+
details: { fault: { type: fault.faultType, message: fault.message } },
|
|
103
|
+
});
|
|
49
104
|
}
|
|
50
105
|
/**
|
|
51
106
|
* A plain (non-Zod) validation rejection, e.g. a body/URL field mismatch that
|
|
52
|
-
* the schema alone cannot express.
|
|
107
|
+
* the schema alone cannot express. The caller's message is preserved verbatim;
|
|
108
|
+
* only the envelope around it changed.
|
|
53
109
|
*/
|
|
54
110
|
export function validationEnvelope(message) {
|
|
55
|
-
return
|
|
111
|
+
return formatSfccWriteFailure({
|
|
112
|
+
code: "VALIDATION_ERROR",
|
|
113
|
+
status: 400,
|
|
114
|
+
message,
|
|
115
|
+
source: "tool",
|
|
116
|
+
details: {},
|
|
117
|
+
});
|
|
56
118
|
}
|
|
57
119
|
/**
|
|
58
120
|
* Safe fallback envelope for an unexpected thrown error inside a write handler
|
|
@@ -60,20 +122,52 @@ export function validationEnvelope(message) {
|
|
|
60
122
|
* thrown value.
|
|
61
123
|
*/
|
|
62
124
|
export function unexpectedEnvelope() {
|
|
63
|
-
return
|
|
64
|
-
|
|
125
|
+
return formatSfccWriteFailure({
|
|
126
|
+
code: "INTERNAL_ERROR",
|
|
65
127
|
status: 500,
|
|
66
|
-
message:
|
|
67
|
-
|
|
128
|
+
message: SFCC_WRITE_INTERNAL_ERROR_MESSAGE,
|
|
129
|
+
source: "tool",
|
|
130
|
+
details: {},
|
|
131
|
+
});
|
|
68
132
|
}
|
|
69
133
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
134
|
+
* Recognize a Zod/schema failure without matching on the exception message.
|
|
135
|
+
*
|
|
136
|
+
* `instanceof` covers the normal case. The structural fallback covers a
|
|
137
|
+
* ZodError produced by a second copy of Zod (a duplicated dependency or a
|
|
138
|
+
* module-mocked import), where the prototype chain differs but the contract
|
|
139
|
+
* — a `ZodError` name plus an `issues` array — does not.
|
|
73
140
|
*/
|
|
74
|
-
|
|
141
|
+
function isSchemaFailure(err) {
|
|
75
142
|
if (err instanceof z.ZodError)
|
|
143
|
+
return true;
|
|
144
|
+
return (typeof err === "object" &&
|
|
145
|
+
err !== null &&
|
|
146
|
+
err.name === "ZodError" &&
|
|
147
|
+
Array.isArray(err.issues));
|
|
148
|
+
}
|
|
149
|
+
/** Recognize a local payload-shape fault, tolerating a duplicated module instance. */
|
|
150
|
+
function isPayloadFault(err) {
|
|
151
|
+
if (err instanceof SfccWritePayloadFault)
|
|
152
|
+
return true;
|
|
153
|
+
return (typeof err === "object" &&
|
|
154
|
+
err !== null &&
|
|
155
|
+
err.name === "SfccWritePayloadFault" &&
|
|
156
|
+
typeof err.faultType === "string" &&
|
|
157
|
+
typeof err.status === "number");
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Map a pre-transport error to its unified envelope: Zod validation failures
|
|
161
|
+
* become `VALIDATION_ERROR`, a recognized payload-shape fault becomes
|
|
162
|
+
* `OCAPI_WRITE_ERROR`, and everything else becomes the sanitized
|
|
163
|
+
* `INTERNAL_ERROR`. Used at the `.parse()` / payload-build boundary before any
|
|
164
|
+
* OCAPI mutation.
|
|
165
|
+
*/
|
|
166
|
+
export function preTransportErrorEnvelope(err) {
|
|
167
|
+
if (isSchemaFailure(err))
|
|
76
168
|
return zodValidationEnvelope(err);
|
|
169
|
+
if (isPayloadFault(err))
|
|
170
|
+
return payloadFaultEnvelope(err);
|
|
77
171
|
return unexpectedEnvelope();
|
|
78
172
|
}
|
|
79
173
|
// ---------------------------------------------------------------------------
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
* Both tools write ATTRIBUTE DEFINITIONS on an already-known custom object type.
|
|
9
9
|
* Custom object TYPE creation is intentionally NOT attempted: OCAPI cannot
|
|
10
10
|
* create custom object types (that is a v2 metadata-import capability), so the
|
|
11
|
-
* type must pre-exist.
|
|
11
|
+
* type must pre-exist. OCAPI also cannot enumerate custom object type IDs
|
|
12
|
+
* directly — see reads-custom-object-def.ts for the `system_object_list` +
|
|
13
|
+
* `projection: "full"` discovery path a caller can use to find one before
|
|
14
|
+
* writing to it. Mirrors reads-custom-object-def.ts.
|
|
12
15
|
*
|
|
13
16
|
* Every handler is call-time gated by `withSfccGate`, sandbox-guarded via
|
|
14
17
|
* `rejectIfNotSandboxForWrite`, and routes its OCAPI result through
|
|
@@ -31,7 +34,8 @@ const createCustomObjectAttributeDefinitionInput = z.object({
|
|
|
31
34
|
object_type: z
|
|
32
35
|
.string()
|
|
33
36
|
.describe('Known custom object type identifier (must already exist). OCAPI cannot ' +
|
|
34
|
-
"
|
|
37
|
+
"create types, and cannot enumerate type IDs directly — only attribute " +
|
|
38
|
+
"definitions on a known type."),
|
|
35
39
|
attribute_id: z
|
|
36
40
|
.string()
|
|
37
41
|
.describe("URL attribute-definition id. If the body also carries `id`, it must match."),
|
|
@@ -12,65 +12,26 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Every handler follows the BAPI-582 foundation write recipe: parse inputs →
|
|
14
14
|
* `rejectIfNotSandboxForWrite` → build a decoupled payload → `ocapiPut`/`ocapiPatch`
|
|
15
|
-
* → `formatOcapiWriteToolResult
|
|
15
|
+
* → `formatOcapiWriteToolResult`.
|
|
16
|
+
*
|
|
17
|
+
* This module owns no failure representation of its own. Its annotations, result
|
|
18
|
+
* type, path encoder, and every pre-transport failure envelope come from
|
|
19
|
+
* `write-tool-common.ts`, so all nine SFCC write tools emit byte-comparable
|
|
20
|
+
* bodies for equivalent failures (BAPI-766). The private near-duplicates this
|
|
21
|
+
* module used to carry leaked raw Zod issue objects and serialized compactly
|
|
22
|
+
* while the shared helpers pretty-printed — exactly the divergence that made
|
|
23
|
+
* `isError` non-total.
|
|
16
24
|
*/
|
|
17
25
|
import { z } from "zod";
|
|
18
26
|
import { ocapiPut, ocapiPatch } from "./client.js";
|
|
19
27
|
import { withSfccGate } from "./tool-wrapper.js";
|
|
20
28
|
import { rejectIfNotSandboxForWrite } from "./write-guard.js";
|
|
21
29
|
import { formatOcapiWriteToolResult } from "./write-result.js";
|
|
22
|
-
import { objectAttributeDefinitionCreateBodySchema, objectAttributeDefinitionPatchBodySchema, attributeGroupPatchBodySchema, localizedStringSchema,
|
|
23
|
-
|
|
24
|
-
// Annotations (destructive OCAPI writes against a developer sandbox)
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
const WRITE_ANNOTATIONS = {
|
|
27
|
-
readOnlyHint: false,
|
|
28
|
-
destructiveHint: true,
|
|
29
|
-
idempotentHint: false,
|
|
30
|
-
openWorldHint: true,
|
|
31
|
-
};
|
|
32
|
-
function textResult(text) {
|
|
33
|
-
return { content: [{ type: "text", text }] };
|
|
34
|
-
}
|
|
35
|
-
/** Safe 400 envelope for a Zod validation failure — no stacks, no raw thrown strings. */
|
|
36
|
-
function zodValidationEnvelope(err) {
|
|
37
|
-
return textResult(JSON.stringify({
|
|
38
|
-
error: "VALIDATION_ERROR",
|
|
39
|
-
status: 400,
|
|
40
|
-
message: "Invalid SFCC write tool input.",
|
|
41
|
-
issues: err.issues,
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
/** Safe 400 envelope for a local payload-shape fault, in OCAPI-fault vocabulary. */
|
|
45
|
-
function payloadFaultEnvelope(fault) {
|
|
46
|
-
return textResult(JSON.stringify({
|
|
47
|
-
error: "OCAPI_WRITE_ERROR",
|
|
48
|
-
status: fault.status,
|
|
49
|
-
fault: { type: fault.faultType, message: fault.message },
|
|
50
|
-
}));
|
|
51
|
-
}
|
|
52
|
-
/** Safe 500 envelope — never includes stacks, secrets, headers, or raw error text. */
|
|
53
|
-
function unexpectedEnvelope() {
|
|
54
|
-
return textResult(JSON.stringify({
|
|
55
|
-
error: "INTERNAL_ERROR",
|
|
56
|
-
status: 500,
|
|
57
|
-
message: "Unexpected SFCC write tool failure",
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
/** Map a pre-transport error (parse or payload-build) to its safe envelope. */
|
|
61
|
-
function preTransportErrorEnvelope(err) {
|
|
62
|
-
if (err instanceof z.ZodError)
|
|
63
|
-
return zodValidationEnvelope(err);
|
|
64
|
-
if (err instanceof SfccWritePayloadFault)
|
|
65
|
-
return payloadFaultEnvelope(err);
|
|
66
|
-
return unexpectedEnvelope();
|
|
67
|
-
}
|
|
30
|
+
import { objectAttributeDefinitionCreateBodySchema, objectAttributeDefinitionPatchBodySchema, attributeGroupPatchBodySchema, localizedStringSchema, buildObjectAttributeDefinitionCreatePayload, buildObjectAttributeDefinitionPatchPayload, buildAttributeGroupPutPayload, buildAttributeGroupPatchPayload, buildEmptyRelationPayload, } from "./writes-system-object-payloads.js";
|
|
31
|
+
import { WRITE_ANNOTATIONS, encodedSegment, preTransportErrorEnvelope, unexpectedEnvelope, } from "./write-tool-common.js";
|
|
68
32
|
// ---------------------------------------------------------------------------
|
|
69
33
|
// OCAPI path builders (every dynamic segment URL-encoded)
|
|
70
34
|
// ---------------------------------------------------------------------------
|
|
71
|
-
function encodedSegment(segment) {
|
|
72
|
-
return encodeURIComponent(segment);
|
|
73
|
-
}
|
|
74
35
|
function attributeDefinitionPath(objectType, attributeId) {
|
|
75
36
|
return `/system_object_definitions/${encodedSegment(objectType)}/attribute_definitions/${encodedSegment(attributeId)}`;
|
|
76
37
|
}
|