@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.
- package/CONDUCTOR.md +75 -0
- package/README.md +8 -5
- package/build/agent-capabilities/probe-context.js +13 -3
- package/build/agent-capabilities/probes.js +262 -11
- package/build/agent-capabilities/reporter.js +1 -0
- package/build/agents.generated.js +1 -1
- package/build/backend-warnings.js +44 -0
- package/build/claude-settings.js +129 -0
- package/build/commands.generated.js +5 -3
- package/build/conductor/bridge-api-client.js +7 -7
- package/build/conductor/cli.js +65 -12
- package/build/conductor/deny-enforcement-preflight.js +96 -0
- package/build/conductor/doctor.js +183 -2
- package/build/conductor/epic-reconcile.js +9 -1
- package/build/conductor/epic-runtime.js +403 -43
- package/build/conductor/epic-state.js +7 -0
- package/build/conductor/errors.js +115 -3
- package/build/conductor/event-accessors.js +28 -10
- package/build/conductor/local-merge.js +458 -95
- package/build/conductor/merge-ledger.js +6 -4
- package/build/conductor/pr-ci-producer.js +17 -2
- package/build/conductor/producer-ledger.js +1 -1
- package/build/conductor/store.js +161 -18
- package/build/conductor/supervisor-merge.js +32 -5
- package/build/conductor/taxonomy.js +8 -0
- package/build/conductor/tools.js +28 -6
- package/build/conductor/worker-ledger-cli.js +244 -0
- package/build/conductor-bin.js +1884 -6917
- package/build/doctor.js +8 -0
- package/build/estimate-epic.js +84 -0
- package/build/executor/cli.js +229 -0
- package/build/executor/credentials.js +65 -0
- package/build/executor/deps.js +117 -0
- package/build/executor/env.js +79 -0
- package/build/executor/heartbeat.js +59 -0
- package/build/executor/http-client.js +131 -0
- package/build/executor/index.js +10 -0
- package/build/executor/job-errors.js +55 -0
- package/build/executor/job-log-registry.js +110 -0
- package/build/executor/job-runner.js +822 -0
- package/build/executor/job-types.js +60 -0
- package/build/executor/merge-job.js +229 -0
- package/build/executor/observation.js +123 -0
- package/build/executor/permissions.js +79 -0
- package/build/executor/preflight.js +144 -0
- package/build/executor/process.js +81 -0
- package/build/executor/prompt-spec.js +235 -0
- package/build/executor/results.js +134 -0
- package/build/executor/resume-pre-spawn.js +179 -0
- package/build/executor/runner.js +98 -0
- package/build/executor/terminal-mutation.js +34 -0
- package/build/executor/test-clock.js +109 -0
- package/build/executor/types.js +18 -0
- package/build/executor/verdict-artifact.js +53 -0
- package/build/executor/viewer-tabs.js +78 -0
- package/build/executor/watch-cli.js +113 -0
- package/build/executor/worker-command.js +106 -0
- package/build/executor/worker-finalization.js +177 -0
- package/build/executor/worker-log.js +92 -0
- package/build/executor/worktree-gc.js +134 -0
- package/build/executor/worktree-inspection.js +86 -0
- package/build/executor/worktree.js +103 -0
- package/build/index.js +14534 -10416
- package/build/mcp-invoke.js +19 -3
- package/build/mcp-provisioning.js +31 -25
- package/build/mcp-registration-doctor.js +27 -7
- package/build/mcp-server-invocation.js +152 -0
- package/build/pipelines.generated.js +16 -20
- package/build/readme.generated.js +1 -1
- package/build/sfcc/client.js +192 -50
- package/build/sfcc/ocapi-write-faults.js +94 -0
- package/build/sfcc/permissions.js +7 -22
- package/build/sfcc/reads-site-preference.js +52 -19
- package/build/sfcc/register.js +9 -0
- package/build/sfcc/write-grants.js +80 -0
- package/build/sfcc/write-guard.js +39 -0
- package/build/sfcc/write-result.js +47 -0
- package/build/sfcc/write-tool-common.js +85 -0
- package/build/sfcc/writes-custom-object-def.js +141 -0
- package/build/sfcc/writes-object-attribute-payloads.js +97 -0
- package/build/sfcc/writes-site-preference-payloads.js +59 -0
- package/build/sfcc/writes-site-preference.js +96 -0
- package/build/sfcc/writes-system-object-payloads.js +213 -0
- package/build/sfcc/writes-system-object.js +348 -0
- package/build/sfcc/writes.js +66 -0
- package/build/start-tickets-conductor.js +25 -93
- package/build/start-tickets-prereqs.js +152 -1
- package/build/start-tickets.js +96 -158
- package/build/version.generated.js +1 -1
- package/build/visual-diff-worker.js +313 -0
- package/build/visual-diff.js +632 -0
- package/build/worktree-core.js +202 -0
- package/package.json +8 -4
- package/pipelines/idea-to-ticket.json +7 -0
- package/pipelines/review-ticket.json +5 -18
- package/public/css/main.min.css +1612 -107
- package/public/css/main.min.css.map +1 -1
- package/public/js/main.min.js +10267 -1
- package/public/js/main.min.js.map +1 -1
- 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
|
+
}
|