@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.
Files changed (92) hide show
  1. package/README.md +48 -8
  2. package/build/base-url.js +79 -0
  3. package/build/bridge-api-urls.js +9 -0
  4. package/build/chain-orchestrator.js +93 -15
  5. package/build/claude-user-config-doctor.js +317 -0
  6. package/build/commands.generated.js +2 -1
  7. package/build/conductor/bridge-api-client.js +178 -4
  8. package/build/conductor-bin.js +1 -1
  9. package/build/conductor-bundle-artifacts.js +7 -6
  10. package/build/credential-store.js +205 -4
  11. package/build/direct-ticket-tools.js +70 -0
  12. package/build/doctor.js +239 -80
  13. package/build/executor/cli.js +51 -1
  14. package/build/executor/credentials.js +1 -7
  15. package/build/executor/deps.js +18 -1
  16. package/build/executor/env.js +51 -25
  17. package/build/executor/heartbeat.js +138 -17
  18. package/build/executor/http-client.js +49 -8
  19. package/build/executor/job-errors.js +4 -0
  20. package/build/executor/job-runner.js +422 -22
  21. package/build/executor/observation.js +130 -0
  22. package/build/executor/permissions.js +104 -8
  23. package/build/executor/preflight.js +32 -0
  24. package/build/executor/runner.js +8 -0
  25. package/build/executor/test-clock.js +67 -3
  26. package/build/executor/types.js +4 -1
  27. package/build/executor/worker-command.js +11 -3
  28. package/build/executor/worker-config-isolation.js +287 -0
  29. package/build/executor/worker-finalization.js +68 -14
  30. package/build/executor/worktree.js +46 -4
  31. package/build/index.js +614 -244
  32. package/build/init.js +363 -73
  33. package/build/install-bridge.js +568 -80
  34. package/build/launcher-config-inspection.js +351 -0
  35. package/build/mcp-invoke.js +49 -6
  36. package/build/mcp-provisioning.js +30 -7
  37. package/build/mcp-registration-doctor.js +14 -5
  38. package/build/notifications.js +553 -0
  39. package/build/pipeline-orchestrator.js +146 -4
  40. package/build/pipeline-utils.js +3 -0
  41. package/build/pipelines.generated.js +22 -9
  42. package/build/plan-execution-ledger.js +550 -0
  43. package/build/plan-phase-routing.js +272 -0
  44. package/build/plane/alembic-head.js +110 -0
  45. package/build/plane/build-freshness.js +167 -0
  46. package/build/plane/cli.js +480 -0
  47. package/build/plane/defaults.js +266 -0
  48. package/build/plane/manifest.js +377 -0
  49. package/build/plane/member-logs.js +147 -0
  50. package/build/plane/member-roster.js +147 -0
  51. package/build/plane/preflight.js +289 -0
  52. package/build/plane/shutdown.js +195 -0
  53. package/build/plane/status.js +125 -0
  54. package/build/plane/supervisor.js +569 -0
  55. package/build/plane/test-fakes.js +156 -0
  56. package/build/plane/types.js +75 -0
  57. package/build/readme.generated.js +1 -1
  58. package/build/run-unit-tests-launcher.js +2 -0
  59. package/build/setup-epic.js +662 -27
  60. package/build/sfcc/log-gate.js +38 -11
  61. package/build/sfcc/log-query.js +55 -15
  62. package/build/sfcc/ocapi-shape.js +70 -14
  63. package/build/sfcc/output.js +41 -11
  64. package/build/sfcc/permissions.js +24 -2
  65. package/build/sfcc/read-body.js +92 -0
  66. package/build/sfcc/read-projection.js +185 -0
  67. package/build/sfcc/read-result.js +158 -0
  68. package/build/sfcc/reads-custom-object-def.js +57 -34
  69. package/build/sfcc/reads-site-preference.js +86 -33
  70. package/build/sfcc/reads-system-object.js +50 -38
  71. package/build/sfcc/sfcc-result.js +106 -0
  72. package/build/sfcc/tool-wrapper.js +56 -13
  73. package/build/sfcc/write-grants.js +45 -22
  74. package/build/sfcc/write-guard.js +21 -13
  75. package/build/sfcc/write-result.js +71 -15
  76. package/build/sfcc/write-tool-common.js +126 -32
  77. package/build/sfcc/writes-custom-object-def.js +6 -2
  78. package/build/sfcc/writes-system-object.js +11 -50
  79. package/build/start-tickets-prereqs.js +129 -0
  80. package/build/start-tickets.js +17 -13
  81. package/build/ticket-backend-metadata.js +59 -0
  82. package/build/ticket-key-utils.js +92 -0
  83. package/build/tool-error-envelope.js +71 -0
  84. package/build/tool-surface-gating.js +72 -0
  85. package/build/update-status.js +102 -0
  86. package/build/upgrade-advice.js +47 -0
  87. package/build/upgrade-cli.js +417 -101
  88. package/build/version.generated.js +1 -1
  89. package/build/worktree-core.js +73 -0
  90. package/docs/CONDUCTOR.md +23 -8
  91. package/package.json +3 -3
  92. 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 future write handlers on a 403 and printed by the
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
- * Build the human-facing 403 remediation text for a failed write operation.
57
- * Includes the 403 status, the failed operation/path, and the exact write-grant
58
- * JSON operators must paste in Business Manager.
59
- */
60
- export function buildOcapiWriteGrant403Text(params) {
61
- const { operation, path, ocapiVersion, body } = params;
62
- const bodyLine = body === undefined
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
- * MCP-shaped wrapper around `buildOcapiWriteGrant403Text` so future write
74
- * handlers can return the grant UX without re-implementing content wrapping.
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
- return {
78
- content: [{ type: "text", text: buildOcapiWriteGrant403Text(params) }],
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 a
26
- * structured JSON validation error naming the rejected instance; a sandbox
27
- * returns `null` so the caller proceeds.
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 textResult(JSON.stringify({
34
- error: "VALIDATION_ERROR",
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 future SFCC write tools. Handlers
5
- * pass the raw `OcapiGetResult` from `ocapiPut`/`ocapiPatch` here rather than
6
- * inventing their own 403/fault/success response logic, guaranteeing a
7
- * consistent surface across every write tool.
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
- /** Wrap text in the standard MCP text-result shape. */
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 remediation (via write-grants.ts).
22
- * - success JSON with `status`, `outcome`, and `body`.
23
- * - other failure → JSON with `error: "OCAPI_WRITE_ERROR"`, `status`, `fault`, `body`.
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 textResult(JSON.stringify({
42
- error: "OCAPI_WRITE_ERROR",
91
+ return formatSfccWriteFailure({
92
+ code: "OCAPI_WRITE_ERROR",
43
93
  status: result.status,
44
- fault: result.fault,
45
- body: result.body,
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
- * The two write-surface modules (`writes-custom-object-def.ts` and
5
- * `writes-site-preference.ts`) both need the same small set of primitives:
6
- * the destructive tool annotations, the MCP text-result shape, a consistent
7
- * set of safe pre-transport error envelopes (validation / unexpected), and a
8
- * single path-segment encoder. Centralizing them here guarantees every write
9
- * tool returns an identical envelope shape and never leaks a stack trace or a
10
- * raw thrown value to the caller.
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
- // Pre-transport error envelopes
43
+ // Fixed messages
33
44
  // ---------------------------------------------------------------------------
34
45
  /**
35
- * Convert a Zod validation failure into a safe `VALIDATION_ERROR` envelope.
36
- * The flattened issues are surfaced so callers can see which field failed,
37
- * without exposing any thrown-error internals.
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 function zodValidationEnvelope(err) {
40
- return textResult(JSON.stringify({
41
- error: "VALIDATION_ERROR",
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: "Input failed schema validation before any OCAPI call.",
44
- issues: err.issues.map((issue) => ({
45
- path: issue.path.join("."),
46
- message: issue.message,
47
- })),
48
- }, null, 2));
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. Same envelope shape as the Zod variant.
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 textResult(JSON.stringify({ error: "VALIDATION_ERROR", status: 400, message }, null, 2));
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 textResult(JSON.stringify({
64
- error: "INTERNAL_ERROR",
125
+ return formatSfccWriteFailure({
126
+ code: "INTERNAL_ERROR",
65
127
  status: 500,
66
- message: "Unexpected SFCC write tool failure.",
67
- }, null, 2));
128
+ message: SFCC_WRITE_INTERNAL_ERROR_MESSAGE,
129
+ source: "tool",
130
+ details: {},
131
+ });
68
132
  }
69
133
  /**
70
- * Map a pre-transport error to its envelope: Zod validation errors become a
71
- * `VALIDATION_ERROR`, everything else becomes the generic `INTERNAL_ERROR`.
72
- * Used for the `.parse()` boundary before any OCAPI mutation.
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
- export function preTransportErrorEnvelope(err) {
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. Mirrors reads-custom-object-def.ts.
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
- "enumerate or create custom object types — only attribute definitions on a known type."),
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` (which echoes the OCAPI write-grant JSON on 403).
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, SfccWritePayloadFault, buildObjectAttributeDefinitionCreatePayload, buildObjectAttributeDefinitionPatchPayload, buildAttributeGroupPutPayload, buildAttributeGroupPatchPayload, buildEmptyRelationPayload, } from "./writes-system-object-payloads.js";
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
  }