@bridge_gpt/mcp-server 0.2.18 → 0.2.20

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 (100) hide show
  1. package/CONDUCTOR.md +75 -0
  2. package/README.md +8 -5
  3. package/build/agent-capabilities/probe-context.js +13 -3
  4. package/build/agent-capabilities/probes.js +262 -11
  5. package/build/agent-capabilities/reporter.js +1 -0
  6. package/build/agents.generated.js +1 -1
  7. package/build/backend-warnings.js +44 -0
  8. package/build/claude-settings.js +129 -0
  9. package/build/commands.generated.js +5 -3
  10. package/build/conductor/bridge-api-client.js +7 -7
  11. package/build/conductor/cli.js +65 -12
  12. package/build/conductor/deny-enforcement-preflight.js +96 -0
  13. package/build/conductor/doctor.js +183 -2
  14. package/build/conductor/epic-reconcile.js +9 -1
  15. package/build/conductor/epic-runtime.js +403 -43
  16. package/build/conductor/epic-state.js +7 -0
  17. package/build/conductor/errors.js +115 -3
  18. package/build/conductor/event-accessors.js +28 -10
  19. package/build/conductor/local-merge.js +458 -95
  20. package/build/conductor/merge-ledger.js +6 -4
  21. package/build/conductor/pr-ci-producer.js +17 -2
  22. package/build/conductor/producer-ledger.js +1 -1
  23. package/build/conductor/store.js +161 -18
  24. package/build/conductor/supervisor-merge.js +32 -5
  25. package/build/conductor/taxonomy.js +8 -0
  26. package/build/conductor/tools.js +28 -6
  27. package/build/conductor/worker-ledger-cli.js +244 -0
  28. package/build/conductor-bin.js +1884 -6917
  29. package/build/doctor.js +8 -0
  30. package/build/estimate-epic.js +84 -0
  31. package/build/executor/cli.js +229 -0
  32. package/build/executor/credentials.js +65 -0
  33. package/build/executor/deps.js +117 -0
  34. package/build/executor/env.js +79 -0
  35. package/build/executor/heartbeat.js +59 -0
  36. package/build/executor/http-client.js +131 -0
  37. package/build/executor/index.js +10 -0
  38. package/build/executor/job-errors.js +55 -0
  39. package/build/executor/job-log-registry.js +110 -0
  40. package/build/executor/job-runner.js +822 -0
  41. package/build/executor/job-types.js +60 -0
  42. package/build/executor/merge-job.js +229 -0
  43. package/build/executor/observation.js +123 -0
  44. package/build/executor/permissions.js +79 -0
  45. package/build/executor/preflight.js +144 -0
  46. package/build/executor/process.js +81 -0
  47. package/build/executor/prompt-spec.js +235 -0
  48. package/build/executor/results.js +134 -0
  49. package/build/executor/resume-pre-spawn.js +179 -0
  50. package/build/executor/runner.js +98 -0
  51. package/build/executor/terminal-mutation.js +34 -0
  52. package/build/executor/test-clock.js +109 -0
  53. package/build/executor/types.js +18 -0
  54. package/build/executor/verdict-artifact.js +53 -0
  55. package/build/executor/viewer-tabs.js +78 -0
  56. package/build/executor/watch-cli.js +113 -0
  57. package/build/executor/worker-command.js +106 -0
  58. package/build/executor/worker-finalization.js +177 -0
  59. package/build/executor/worker-log.js +92 -0
  60. package/build/executor/worktree-gc.js +134 -0
  61. package/build/executor/worktree-inspection.js +86 -0
  62. package/build/executor/worktree.js +103 -0
  63. package/build/index.js +14534 -10416
  64. package/build/mcp-invoke.js +19 -3
  65. package/build/mcp-provisioning.js +31 -25
  66. package/build/mcp-registration-doctor.js +27 -7
  67. package/build/mcp-server-invocation.js +152 -0
  68. package/build/pipelines.generated.js +16 -20
  69. package/build/readme.generated.js +1 -1
  70. package/build/sfcc/client.js +192 -50
  71. package/build/sfcc/ocapi-write-faults.js +94 -0
  72. package/build/sfcc/permissions.js +7 -22
  73. package/build/sfcc/reads-site-preference.js +52 -19
  74. package/build/sfcc/register.js +9 -0
  75. package/build/sfcc/write-grants.js +80 -0
  76. package/build/sfcc/write-guard.js +39 -0
  77. package/build/sfcc/write-result.js +47 -0
  78. package/build/sfcc/write-tool-common.js +85 -0
  79. package/build/sfcc/writes-custom-object-def.js +141 -0
  80. package/build/sfcc/writes-object-attribute-payloads.js +97 -0
  81. package/build/sfcc/writes-site-preference-payloads.js +59 -0
  82. package/build/sfcc/writes-site-preference.js +96 -0
  83. package/build/sfcc/writes-system-object-payloads.js +213 -0
  84. package/build/sfcc/writes-system-object.js +348 -0
  85. package/build/sfcc/writes.js +66 -0
  86. package/build/start-tickets-conductor.js +25 -93
  87. package/build/start-tickets-prereqs.js +152 -1
  88. package/build/start-tickets.js +96 -158
  89. package/build/version.generated.js +1 -1
  90. package/build/visual-diff-worker.js +313 -0
  91. package/build/visual-diff.js +632 -0
  92. package/build/worktree-core.js +202 -0
  93. package/package.json +8 -4
  94. package/pipelines/idea-to-ticket.json +7 -0
  95. package/pipelines/review-ticket.json +5 -18
  96. package/public/css/main.min.css +1612 -107
  97. package/public/css/main.min.css.map +1 -1
  98. package/public/js/main.min.js +10267 -1
  99. package/public/js/main.min.js.map +1 -1
  100. package/smoke-test/SMOKE-TEST.md +2 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * SFCC custom-object attribute-definition write tools (BAPI-584).
3
+ *
4
+ * Implements:
5
+ * custom_object_definition_attribute_create — PUT /custom_object_definitions/{type}/attribute_definitions/{id}
6
+ * custom_object_definition_attribute_update — PATCH /custom_object_definitions/{type}/attribute_definitions/{id}
7
+ *
8
+ * Both tools write ATTRIBUTE DEFINITIONS on an already-known custom object type.
9
+ * Custom object TYPE creation is intentionally NOT attempted: OCAPI cannot
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.
12
+ *
13
+ * Every handler is call-time gated by `withSfccGate`, sandbox-guarded via
14
+ * `rejectIfNotSandboxForWrite`, and routes its OCAPI result through
15
+ * `formatOcapiWriteToolResult` (403 → paste-ready grant JSON; 409/412 conflicts
16
+ * surfaced verbatim).
17
+ */
18
+ import { z } from "zod";
19
+ import { ocapiPut, ocapiPatch } from "./client.js";
20
+ import { withSfccGate } from "./tool-wrapper.js";
21
+ import { rejectIfNotSandboxForWrite } from "./write-guard.js";
22
+ import { formatOcapiWriteToolResult } from "./write-result.js";
23
+ import { objectAttributeDefinitionCreateBodySchema, objectAttributeDefinitionPatchBodySchema, buildObjectAttributeDefinitionCreatePayload, buildObjectAttributeDefinitionPatchPayload, } from "./writes-object-attribute-payloads.js";
24
+ import { WRITE_ANNOTATIONS, encodedSegment, preTransportErrorEnvelope, unexpectedEnvelope, validationEnvelope, } from "./write-tool-common.js";
25
+ // ---------------------------------------------------------------------------
26
+ // Input schemas
27
+ // ---------------------------------------------------------------------------
28
+ const INSTANCE_DESCRIBE = "OCAPI instance context. SFCC writes are sandbox-only; omit for sandbox. Any " +
29
+ "other value is rejected before OCAPI is called.";
30
+ const createCustomObjectAttributeDefinitionInput = z.object({
31
+ object_type: z
32
+ .string()
33
+ .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."),
35
+ attribute_id: z
36
+ .string()
37
+ .describe("URL attribute-definition id. If the body also carries `id`, it must match."),
38
+ definition: objectAttributeDefinitionCreateBodySchema.describe("ObjectAttributeDefinition body; `value_type` is required for a create."),
39
+ instance: z.string().optional().describe(INSTANCE_DESCRIBE),
40
+ });
41
+ const updateCustomObjectAttributeDefinitionInput = z.object({
42
+ object_type: z
43
+ .string()
44
+ .describe("Known custom object type identifier (must already exist). OCAPI cannot create types."),
45
+ attribute_id: z.string().describe("URL attribute-definition id to update."),
46
+ patch: objectAttributeDefinitionPatchBodySchema.describe("Partial ObjectAttributeDefinition body; must change at least one field."),
47
+ instance: z.string().optional().describe(INSTANCE_DESCRIBE),
48
+ });
49
+ // ---------------------------------------------------------------------------
50
+ // Path builder
51
+ // ---------------------------------------------------------------------------
52
+ /** Build the attribute-definition resource path with both dynamic segments encoded. */
53
+ export function customObjectAttributeDefinitionPath(objectType, attributeId) {
54
+ return (`/custom_object_definitions/${encodedSegment(objectType)}` +
55
+ `/attribute_definitions/${encodedSegment(attributeId)}`);
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Handlers
59
+ // ---------------------------------------------------------------------------
60
+ export function buildCreateCustomObjectAttributeDefinitionHandler(gateDeps) {
61
+ return withSfccGate(gateDeps, async (args, credentials) => {
62
+ let parsed;
63
+ try {
64
+ parsed = createCustomObjectAttributeDefinitionInput.parse(args);
65
+ }
66
+ catch (err) {
67
+ return preTransportErrorEnvelope(err);
68
+ }
69
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
70
+ if (guard)
71
+ return guard;
72
+ // The URL attribute id is authoritative; a mismatched body `id` is a caller bug.
73
+ if (parsed.definition.id !== undefined && parsed.definition.id !== parsed.attribute_id) {
74
+ return validationEnvelope(`Body id '${parsed.definition.id}' does not match URL attribute_id '${parsed.attribute_id}'.`);
75
+ }
76
+ const path = customObjectAttributeDefinitionPath(parsed.object_type, parsed.attribute_id);
77
+ const body = buildObjectAttributeDefinitionCreatePayload(parsed.attribute_id, parsed.definition);
78
+ try {
79
+ const result = await ocapiPut(path, body, credentials);
80
+ return formatOcapiWriteToolResult(result, "PUT", path);
81
+ }
82
+ catch {
83
+ return unexpectedEnvelope();
84
+ }
85
+ });
86
+ }
87
+ export function buildUpdateCustomObjectAttributeDefinitionHandler(gateDeps) {
88
+ return withSfccGate(gateDeps, async (args, credentials) => {
89
+ let parsed;
90
+ try {
91
+ parsed = updateCustomObjectAttributeDefinitionInput.parse(args);
92
+ }
93
+ catch (err) {
94
+ return preTransportErrorEnvelope(err);
95
+ }
96
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
97
+ if (guard)
98
+ return guard;
99
+ const path = customObjectAttributeDefinitionPath(parsed.object_type, parsed.attribute_id);
100
+ const body = buildObjectAttributeDefinitionPatchPayload(parsed.patch);
101
+ try {
102
+ // ocapiPatch performs the GET-then-If-Match ETag round trip; 409/412
103
+ // (IfMatchRequired / InvalidIfMatch) responses flow through formatOcapiWriteToolResult.
104
+ const result = await ocapiPatch(path, body, credentials);
105
+ return formatOcapiWriteToolResult(result, "PATCH", path);
106
+ }
107
+ catch {
108
+ return unexpectedEnvelope();
109
+ }
110
+ });
111
+ }
112
+ // ---------------------------------------------------------------------------
113
+ // Registration
114
+ // ---------------------------------------------------------------------------
115
+ /** Tool names registered by this module. */
116
+ export const CUSTOM_OBJECT_DEF_WRITE_TOOL_NAMES = [
117
+ "custom_object_definition_attribute_create",
118
+ "custom_object_definition_attribute_update",
119
+ ];
120
+ /**
121
+ * Register the two SFCC custom-object attribute-definition write tools.
122
+ * Called from `registerSfccWriteTools` — no direct `index.ts` edits needed.
123
+ */
124
+ export function registerSfccCustomObjectDefWriteTools(registerTool, deps) {
125
+ const { gateDeps } = deps;
126
+ registerTool("custom_object_definition_attribute_create", {
127
+ description: "Create an attribute definition on a KNOWN custom object type via " +
128
+ "PUT /custom_object_definitions/{type}/attribute_definitions/{id}. Sandbox-only, " +
129
+ "destructive; the type must pre-exist (OCAPI cannot create types). Echoes paste-ready " +
130
+ "grant JSON on 403.",
131
+ inputSchema: createCustomObjectAttributeDefinitionInput,
132
+ annotations: WRITE_ANNOTATIONS,
133
+ }, buildCreateCustomObjectAttributeDefinitionHandler(gateDeps));
134
+ registerTool("custom_object_definition_attribute_update", {
135
+ description: "Update an attribute definition on a KNOWN custom object type via an ETag-conditional " +
136
+ "PATCH /custom_object_definitions/{type}/attribute_definitions/{id}. Sandbox-only, " +
137
+ "destructive; surfaces 409/412 conflicts and echoes grant JSON on 403.",
138
+ inputSchema: updateCustomObjectAttributeDefinitionInput,
139
+ annotations: WRITE_ANNOTATIONS,
140
+ }, buildUpdateCustomObjectAttributeDefinitionHandler(gateDeps));
141
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Pure payload builders + schemas for OCAPI ObjectAttributeDefinition writes
3
+ * (BAPI-584).
4
+ *
5
+ * This module performs NO network calls. It exists to keep the
6
+ * ObjectAttributeDefinition request-body construction decoupled from OCAPI
7
+ * transport (client.ts) so the write handlers stay thin and the body shape is
8
+ * independently testable.
9
+ *
10
+ * The ObjectAttributeDefinition document class is shared by OCAPI system-object
11
+ * and custom-object attribute-definition writes. BAPI-583 (system-object
12
+ * attribute writes) is the sibling surface; if/when it lands it can reuse these
13
+ * builders rather than re-deriving them. The schema is intentionally permissive
14
+ * (`.passthrough()`): OCAPI defines many optional attribute properties and this
15
+ * layer must not reject a valid one it does not happen to enumerate.
16
+ */
17
+ import { z } from "zod";
18
+ // ---------------------------------------------------------------------------
19
+ // Value types
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * The OCAPI ObjectAttributeDefinition `value_type` enum. Kept broad to cover
23
+ * the documented set; unknown-but-valid future types are not the common case,
24
+ * and the write still fails loud at OCAPI if a type is rejected server-side.
25
+ */
26
+ export const objectAttributeValueTypeSchema = z.enum([
27
+ "string",
28
+ "int",
29
+ "double",
30
+ "boolean",
31
+ "date",
32
+ "datetime",
33
+ "email",
34
+ "enum_of_int",
35
+ "enum_of_string",
36
+ "html",
37
+ "image",
38
+ "money",
39
+ "password",
40
+ "quantity",
41
+ "set_of_int",
42
+ "set_of_string",
43
+ "set_of_double",
44
+ "text",
45
+ ]);
46
+ // ---------------------------------------------------------------------------
47
+ // Create / patch body schemas
48
+ // ---------------------------------------------------------------------------
49
+ /**
50
+ * Body for creating an attribute definition (PUT). OCAPI requires `value_type`
51
+ * for a create; `id` is optional here because the URL segment is authoritative
52
+ * (the handler reconciles the two). All other ObjectAttributeDefinition
53
+ * properties pass through unchanged.
54
+ */
55
+ export const objectAttributeDefinitionCreateBodySchema = z
56
+ .object({
57
+ id: z.string().min(1).optional(),
58
+ value_type: objectAttributeValueTypeSchema,
59
+ })
60
+ .passthrough();
61
+ /**
62
+ * Body for updating an attribute definition (PATCH). Every property is
63
+ * optional, but the patch must change at least one field — an empty patch is
64
+ * rejected before the ETag round trip.
65
+ */
66
+ export const objectAttributeDefinitionPatchBodySchema = z
67
+ .object({
68
+ value_type: objectAttributeValueTypeSchema.optional(),
69
+ })
70
+ .passthrough()
71
+ .superRefine((body, ctx) => {
72
+ if (Object.keys(body).length === 0) {
73
+ ctx.addIssue({
74
+ code: z.ZodIssueCode.custom,
75
+ message: "Patch body must contain at least one attribute-definition field to update.",
76
+ });
77
+ }
78
+ });
79
+ // ---------------------------------------------------------------------------
80
+ // Builders (pure)
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Build the create (PUT) payload. Returns a shallow copy of the caller body
84
+ * with `id` forced to the authoritative URL attribute id, so the URL and body
85
+ * can never disagree. No wrapper object is added — OCAPI expects the
86
+ * ObjectAttributeDefinition document at the top level.
87
+ */
88
+ export function buildObjectAttributeDefinitionCreatePayload(attributeId, body) {
89
+ return { ...body, id: attributeId };
90
+ }
91
+ /**
92
+ * Build the patch (PATCH) payload — a shallow copy of the caller body with no
93
+ * wrapper. The ETag round trip is performed by the transport layer, not here.
94
+ */
95
+ export function buildObjectAttributeDefinitionPatchPayload(body) {
96
+ return { ...body };
97
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Pure payload builders + schemas for OCAPI site-preference value writes
3
+ * (BAPI-584).
4
+ *
5
+ * This module performs NO network calls. It keeps site-preference request-body
6
+ * construction decoupled from OCAPI transport (client.ts). The write endpoint
7
+ * (`PATCH /site_preferences/preference_groups/{group}/{instance}`) accepts a
8
+ * FLAT map of custom preference ids (`c_`-prefixed) to primitive/set values —
9
+ * there is no wrapper object such as `{ values: ... }`.
10
+ */
11
+ import { z } from "zod";
12
+ // ---------------------------------------------------------------------------
13
+ // Value + map schemas
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Accepted site-preference value shapes: string, finite number (int/double),
17
+ * boolean, or a set-of-string (string[]). Nested objects and other shapes are
18
+ * rejected before any OCAPI call.
19
+ */
20
+ export const sitePreferenceValueSchema = z.union([
21
+ z.string(),
22
+ z.number().finite(),
23
+ z.boolean(),
24
+ z.array(z.string()),
25
+ ]);
26
+ /**
27
+ * A flat map of custom preference ids to values. Every key must be
28
+ * `c_`-prefixed (custom preferences only) and the map must be non-empty.
29
+ */
30
+ export const sitePreferenceValuesPatchBodySchema = z
31
+ .record(z.string(), sitePreferenceValueSchema)
32
+ .superRefine((values, ctx) => {
33
+ const keys = Object.keys(values);
34
+ if (keys.length === 0) {
35
+ ctx.addIssue({
36
+ code: z.ZodIssueCode.custom,
37
+ message: "At least one preference value is required.",
38
+ });
39
+ }
40
+ for (const key of keys) {
41
+ if (!key.startsWith("c_")) {
42
+ ctx.addIssue({
43
+ code: z.ZodIssueCode.custom,
44
+ path: [key],
45
+ message: `Preference id '${key}' must be a custom preference starting with 'c_'.`,
46
+ });
47
+ }
48
+ }
49
+ });
50
+ // ---------------------------------------------------------------------------
51
+ // Builder (pure)
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Build the site-preference value PATCH payload — a shallow copy of the flat
55
+ * map exactly as OCAPI expects it, with no wrapper object.
56
+ */
57
+ export function buildSitePreferenceValuesPatchPayload(values) {
58
+ return { ...values };
59
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * SFCC site-preference value write tool (BAPI-584).
3
+ *
4
+ * Implements:
5
+ * site_preference_values_set — PATCH /site_preferences/preference_groups/{group}/sandbox
6
+ *
7
+ * Sets custom site-preference VALUES through the OCAPI Data API. Sandbox-only:
8
+ * the value endpoint would otherwise accept `production`, so the `{instance}`
9
+ * segment is forced to the `sandbox` literal AFTER the shared sandbox guard has
10
+ * validated the requested instance. The body is a FLAT map of `c_`-prefixed
11
+ * custom preference ids to primitive/set-of-string values. Mirrors
12
+ * reads-site-preference.ts.
13
+ *
14
+ * The handler is call-time gated by `withSfccGate`, sandbox-guarded, and routes
15
+ * its OCAPI result through `formatOcapiWriteToolResult` (403 → paste-ready grant
16
+ * JSON; a bad group surfaces as 404 CustomPreferenceGroupNotFoundException).
17
+ */
18
+ import { z } from "zod";
19
+ import { ocapiPatchDirect } from "./client.js";
20
+ import { withSfccGate } from "./tool-wrapper.js";
21
+ import { rejectIfNotSandboxForWrite } from "./write-guard.js";
22
+ import { formatOcapiWriteToolResult } from "./write-result.js";
23
+ import { sitePreferenceValuesPatchBodySchema, buildSitePreferenceValuesPatchPayload, } from "./writes-site-preference-payloads.js";
24
+ import { WRITE_ANNOTATIONS, encodedSegment, preTransportErrorEnvelope, unexpectedEnvelope, } from "./write-tool-common.js";
25
+ // ---------------------------------------------------------------------------
26
+ // Input schema
27
+ // ---------------------------------------------------------------------------
28
+ // Mirror reads-site-preference.ts so callers see the same instance enum, while
29
+ // the write remains sandbox-only (enforced by the guard + the literal path).
30
+ const INSTANCE_ENUM = z.enum(["staging", "development", "sandbox", "production"]);
31
+ const INSTANCE_DESCRIBE = "OCAPI instance context. v1 supports the 'sandbox' context only; any other " +
32
+ "value is rejected before OCAPI is called. Defaults to 'sandbox'.";
33
+ const sitePreferenceValuesSetInput = z.object({
34
+ group: z.string().describe("Custom site preference group id, e.g. 'LLMIntegration'."),
35
+ instance: INSTANCE_ENUM.optional().default("sandbox").describe(INSTANCE_DESCRIBE),
36
+ values: sitePreferenceValuesPatchBodySchema.describe("Flat map of c_-prefixed preference ids to values (string, number, boolean, or string[])."),
37
+ });
38
+ // ---------------------------------------------------------------------------
39
+ // Path builder
40
+ // ---------------------------------------------------------------------------
41
+ /**
42
+ * Build the site-preference group PATCH path. The `{instance}` segment is the
43
+ * `sandbox` literal — a non-sandbox instance is rejected by the guard before
44
+ * this is called, so a non-sandbox value can never leak into transport.
45
+ */
46
+ export function sitePreferenceGroupPath(group) {
47
+ return `/site_preferences/preference_groups/${encodedSegment(group)}/sandbox`;
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Handler
51
+ // ---------------------------------------------------------------------------
52
+ export function buildSitePreferenceValuesSetHandler(gateDeps) {
53
+ return withSfccGate(gateDeps, async (args, credentials) => {
54
+ let parsed;
55
+ try {
56
+ parsed = sitePreferenceValuesSetInput.parse(args);
57
+ }
58
+ catch (err) {
59
+ return preTransportErrorEnvelope(err);
60
+ }
61
+ const guard = rejectIfNotSandboxForWrite(parsed.instance);
62
+ if (guard)
63
+ return guard;
64
+ const path = sitePreferenceGroupPath(parsed.group);
65
+ const body = buildSitePreferenceValuesPatchPayload(parsed.values);
66
+ try {
67
+ // Direct PATCH (no ETag round trip): the value endpoint does not gate on
68
+ // If-Match, and a bad group's 404 CustomPreferenceGroupNotFoundException
69
+ // is surfaced verbatim through formatOcapiWriteToolResult.
70
+ const result = await ocapiPatchDirect(path, body, credentials);
71
+ return formatOcapiWriteToolResult(result, "PATCH", path);
72
+ }
73
+ catch {
74
+ return unexpectedEnvelope();
75
+ }
76
+ });
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Registration
80
+ // ---------------------------------------------------------------------------
81
+ /** Tool names registered by this module. */
82
+ export const SITE_PREFERENCE_WRITE_TOOL_NAMES = ["site_preference_values_set"];
83
+ /**
84
+ * Register the SFCC site-preference value write tool.
85
+ * Called from `registerSfccWriteTools` — no direct `index.ts` edits needed.
86
+ */
87
+ export function registerSitePreferenceWriteTools(registerTool, deps) {
88
+ const { gateDeps } = deps;
89
+ registerTool("site_preference_values_set", {
90
+ description: "Set custom site-preference VALUES via PATCH /site_preferences/preference_groups/{group}/sandbox. " +
91
+ "Sandbox-only, destructive; body is a flat map of c_-prefixed ids to string/number/boolean/string[] " +
92
+ "values. A bad group returns 404 CustomPreferenceGroupNotFoundException; echoes grant JSON on 403.",
93
+ inputSchema: sitePreferenceValuesSetInput,
94
+ annotations: WRITE_ANNOTATIONS,
95
+ }, buildSitePreferenceValuesSetHandler(gateDeps));
96
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Pure OCAPI payload builders for the SFCC system-object write tools (BAPI-583).
3
+ *
4
+ * This module contains ONLY request-body construction and shape validation for
5
+ * the system-object write surface (attribute definitions, attribute groups,
6
+ * attribute-to-group assignment, and custom preference definitions). It performs
7
+ * NO network calls and imports NO transport code — transport lives in
8
+ * `client.ts` (`ocapiPut`/`ocapiPatch`). Keeping the builders decoupled from
9
+ * transport lets the same `ObjectAttributeDefinition` body helper back both the
10
+ * system-object writes here and later custom-object writes without duplication.
11
+ */
12
+ import { z } from "zod";
13
+ // ---------------------------------------------------------------------------
14
+ // Shared field schemas
15
+ // ---------------------------------------------------------------------------
16
+ /** Localized OCAPI string: a map of locale key → string value (e.g. `{ default: "Color" }`). */
17
+ export const localizedStringSchema = z.record(z.string(), z.string());
18
+ /**
19
+ * OCAPI attribute value types recognized by the read surface (mirrors the set
20
+ * in `ocapi-shape.ts`/`reads-system-object.ts`). Note `html`/`image` are valid
21
+ * *value types* even though `html`/`image` are output-only *properties* — the
22
+ * two restrictions are independent (see the create-body superRefine below).
23
+ */
24
+ export const objectAttributeValueTypeSchema = z.enum([
25
+ "string",
26
+ "int",
27
+ "double",
28
+ "text",
29
+ "html",
30
+ "date",
31
+ "image",
32
+ "boolean",
33
+ "money",
34
+ "quantity",
35
+ "datetime",
36
+ "email",
37
+ "password",
38
+ "set_of_string",
39
+ "set_of_int",
40
+ "set_of_double",
41
+ "enum_of_string",
42
+ "enum_of_int",
43
+ ]);
44
+ /** OCAPI output-only response properties that must never appear in a write body. */
45
+ const OUTPUT_ONLY_BODY_PROPERTIES = ["html", "image"];
46
+ /**
47
+ * Flag any output-only property (`html`/`image`) present as a request-body key.
48
+ * Attached to both the create and patch body schemas so a caller cannot smuggle
49
+ * a server-computed field into a write.
50
+ */
51
+ function rejectOutputOnlyProperties(body, ctx) {
52
+ for (const key of OUTPUT_ONLY_BODY_PROPERTIES) {
53
+ if (Object.prototype.hasOwnProperty.call(body, key)) {
54
+ ctx.addIssue({
55
+ code: z.ZodIssueCode.custom,
56
+ path: [key],
57
+ message: `'${key}' is an output-only OCAPI property and cannot be set on a write body. ` +
58
+ `(Use value_type: "${key}" to declare an ${key} attribute instead.)`,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // ObjectAttributeDefinition bodies
65
+ // ---------------------------------------------------------------------------
66
+ /**
67
+ * A curated subset of common OCAPI `ObjectAttributeDefinition` metadata fields.
68
+ * Kept intentionally small to bound the generated JSON-schema token cost;
69
+ * `.passthrough()` on the schemas below still preserves any other caller-provided
70
+ * OCAPI field (bounds, length, regexp, enum value_definitions, etc.) verbatim.
71
+ */
72
+ const objectAttributeDefinitionCommonShape = {
73
+ id: z.string().optional(),
74
+ system: z.boolean().optional(),
75
+ display_name: localizedStringSchema.optional(),
76
+ description: localizedStringSchema.optional(),
77
+ mandatory: z.boolean().optional(),
78
+ localizable: z.boolean().optional(),
79
+ site_specific: z.boolean().optional(),
80
+ default_value: z.any().optional(),
81
+ };
82
+ /**
83
+ * Create body for `PUT .../attribute_definitions/{id}`. `value_type` is required;
84
+ * every other field is optional. Additional OCAPI fields pass through untouched.
85
+ */
86
+ export const objectAttributeDefinitionCreateBodySchema = z
87
+ .object({
88
+ value_type: objectAttributeValueTypeSchema.describe("Required OCAPI attribute value type."),
89
+ ...objectAttributeDefinitionCommonShape,
90
+ })
91
+ .passthrough()
92
+ .superRefine(rejectOutputOnlyProperties);
93
+ /**
94
+ * Patch body for `PATCH .../attribute_definitions/{id}`. Every field is optional
95
+ * (`value_type` included), but an empty patch is rejected and the same
96
+ * output-only property restriction applies.
97
+ */
98
+ export const objectAttributeDefinitionPatchBodySchema = z
99
+ .object({
100
+ value_type: objectAttributeValueTypeSchema.optional(),
101
+ ...objectAttributeDefinitionCommonShape,
102
+ })
103
+ .passthrough()
104
+ .superRefine((body, ctx) => {
105
+ rejectOutputOnlyProperties(body, ctx);
106
+ if (Object.keys(body).length === 0) {
107
+ ctx.addIssue({
108
+ code: z.ZodIssueCode.custom,
109
+ message: "Patch body must contain at least one field to update.",
110
+ });
111
+ }
112
+ });
113
+ // ---------------------------------------------------------------------------
114
+ // Attribute-group bodies
115
+ // ---------------------------------------------------------------------------
116
+ /** Create body for `PUT .../attribute_groups/{id}` — localized `display_name` + `internal`. */
117
+ export const attributeGroupPutBodySchema = z.object({
118
+ display_name: localizedStringSchema.describe("Localized group display name."),
119
+ internal: z.boolean().describe("Whether the group is internal (BM-only)."),
120
+ });
121
+ /** Patch body for `PATCH .../attribute_groups/{id}` — both fields optional, no empty patch. */
122
+ export const attributeGroupPatchBodySchema = z
123
+ .object({
124
+ display_name: localizedStringSchema.optional(),
125
+ internal: z.boolean().optional(),
126
+ })
127
+ .superRefine((body, ctx) => {
128
+ if (Object.keys(body).length === 0) {
129
+ ctx.addIssue({
130
+ code: z.ZodIssueCode.custom,
131
+ message: "Patch body must contain at least one field to update.",
132
+ });
133
+ }
134
+ });
135
+ // ---------------------------------------------------------------------------
136
+ // Local payload faults (mirror OCAPI fault shape for pre-transport rejections)
137
+ // ---------------------------------------------------------------------------
138
+ /**
139
+ * A local, pre-transport payload-shape failure that should surface to callers in
140
+ * the same OCAPI-fault vocabulary the server would use (e.g. `IdConflictException`).
141
+ * Carries an HTTP `status` and an OCAPI-style `faultType` so the handler can build
142
+ * a fault envelope without a network round-trip.
143
+ */
144
+ export class SfccWritePayloadFault extends Error {
145
+ status;
146
+ faultType;
147
+ constructor(faultType, message, status = 400) {
148
+ super(message);
149
+ this.name = "SfccWritePayloadFault";
150
+ this.faultType = faultType;
151
+ this.status = status;
152
+ }
153
+ }
154
+ // ---------------------------------------------------------------------------
155
+ // Attribute-definition payload builders
156
+ // ---------------------------------------------------------------------------
157
+ /**
158
+ * Build the create payload for `PUT .../attribute_definitions/{urlId}`.
159
+ *
160
+ * Enforces the two write-time invariants the ticket requires BEFORE any transport:
161
+ * - a supplied `id` must equal the URL id (else `IdConflictException`);
162
+ * - `system` must not be `true` (else `AttributeDefinitionKeyReadOnlyException`).
163
+ * Returns a new object with `id` forced to the URL id and `system` forced to
164
+ * `false`, preserving every other caller-provided OCAPI field verbatim.
165
+ */
166
+ export function buildObjectAttributeDefinitionCreatePayload(urlId, body) {
167
+ if (body.id !== undefined && body.id !== urlId) {
168
+ throw new SfccWritePayloadFault("IdConflictException", `Attribute definition id '${body.id}' does not match the URL id '${urlId}'. ` +
169
+ `Omit 'id' or set it equal to the URL id.`);
170
+ }
171
+ if (body.system === true) {
172
+ throw new SfccWritePayloadFault("AttributeDefinitionKeyReadOnlyException", "Cannot create a system attribute definition (system: true) over OCAPI writes.");
173
+ }
174
+ return { ...body, id: urlId, system: false };
175
+ }
176
+ /**
177
+ * Build the patch payload for `PATCH .../attribute_definitions/{urlId}`.
178
+ *
179
+ * Applies the same id-match and non-system guards as the create builder, then
180
+ * returns the caller's patch fields unchanged — it invents no transport fields
181
+ * and does not force `id`/`system` onto a partial update.
182
+ */
183
+ export function buildObjectAttributeDefinitionPatchPayload(urlId, body) {
184
+ if (body.id !== undefined && body.id !== urlId) {
185
+ throw new SfccWritePayloadFault("IdConflictException", `Attribute definition id '${body.id}' does not match the URL id '${urlId}'. ` +
186
+ `Omit 'id' or set it equal to the URL id.`);
187
+ }
188
+ if (body.system === true) {
189
+ throw new SfccWritePayloadFault("AttributeDefinitionKeyReadOnlyException", "Cannot patch an attribute definition to system: true over OCAPI writes.");
190
+ }
191
+ return { ...body };
192
+ }
193
+ // ---------------------------------------------------------------------------
194
+ // Attribute-group payload builders
195
+ // ---------------------------------------------------------------------------
196
+ /** Build the minimal create body for an attribute group: exactly `{ display_name, internal }`. */
197
+ export function buildAttributeGroupPutPayload(body) {
198
+ return { display_name: body.display_name, internal: body.internal };
199
+ }
200
+ /** Build the attribute-group patch body from only the supplied fields. */
201
+ export function buildAttributeGroupPatchPayload(body) {
202
+ return { ...body };
203
+ }
204
+ // ---------------------------------------------------------------------------
205
+ // Relation payload builder
206
+ // ---------------------------------------------------------------------------
207
+ /**
208
+ * Build the (empty) body for the attribute→group assignment PUT. The relation is
209
+ * fully expressed by the URL, so OCAPI expects an empty JSON object body.
210
+ */
211
+ export function buildEmptyRelationPayload() {
212
+ return {};
213
+ }