@granular-software/sdk 0.4.63 → 0.4.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-evals.d.mts +2 -2
- package/dist/agent-evals.d.ts +2 -2
- package/dist/agent-evals.js +713 -65
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +713 -65
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/cli/index.js +713 -65
- package/dist/{client-lk-dmdKR.d.mts → client-S-z8bri1.d.mts} +47 -1
- package/dist/{client-KqJIH7WE.d.ts → client-z6shQivt.d.ts} +47 -1
- package/dist/index.d.mts +72 -5
- package/dist/index.d.ts +72 -5
- package/dist/index.js +1058 -83
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1052 -84
- package/dist/index.mjs.map +1 -1
- package/dist/{spend-DtsHRNb5.d.mts → spend-CESUvrZC.d.mts} +239 -4
- package/dist/{spend-DtsHRNb5.d.ts → spend-CESUvrZC.d.ts} +239 -4
- package/dist/spend.d.mts +1 -1
- package/dist/spend.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -14048,8 +14048,12 @@ external_exports.union([
|
|
|
14048
14048
|
external_exports.array(external_exports.string()),
|
|
14049
14049
|
external_exports.object({
|
|
14050
14050
|
values: external_exports.array(external_exports.string()),
|
|
14051
|
+
labels: external_exports.array(external_exports.string().min(1)).optional(),
|
|
14051
14052
|
message: external_exports.string().optional()
|
|
14052
|
-
}).strict()
|
|
14053
|
+
}).strict().refine(
|
|
14054
|
+
(rule) => !rule.labels || rule.labels.length === rule.values.length,
|
|
14055
|
+
{ message: "Enum labels must match enum values one-for-one" }
|
|
14056
|
+
)
|
|
14053
14057
|
]);
|
|
14054
14058
|
external_exports.union([
|
|
14055
14059
|
external_exports.boolean(),
|
|
@@ -14077,7 +14081,7 @@ var StateMachineStateSchema = external_exports.union([
|
|
|
14077
14081
|
external_exports.string(),
|
|
14078
14082
|
external_exports.object({
|
|
14079
14083
|
name: external_exports.string().min(1),
|
|
14080
|
-
label: external_exports.string().optional(),
|
|
14084
|
+
label: external_exports.string().min(1).optional(),
|
|
14081
14085
|
description: external_exports.string().optional(),
|
|
14082
14086
|
isFinal: external_exports.boolean().optional()
|
|
14083
14087
|
}).strict()
|
|
@@ -14163,6 +14167,12 @@ var StateTransitionExpectedOutcomeSchema = external_exports.union([
|
|
|
14163
14167
|
summary: external_exports.string().optional()
|
|
14164
14168
|
}).strict()
|
|
14165
14169
|
]);
|
|
14170
|
+
var StateTransitionOutcomeSchema = external_exports.object({
|
|
14171
|
+
label: external_exports.string().min(1).optional(),
|
|
14172
|
+
to: external_exports.string().min(1),
|
|
14173
|
+
primary: external_exports.boolean().optional(),
|
|
14174
|
+
disposition: external_exports.enum(["continue", "error"])
|
|
14175
|
+
}).strict();
|
|
14166
14176
|
var StateMachineTransitionSchema = external_exports.object({
|
|
14167
14177
|
name: external_exports.string().min(1),
|
|
14168
14178
|
from: external_exports.string().min(1),
|
|
@@ -14174,7 +14184,8 @@ var StateMachineTransitionSchema = external_exports.object({
|
|
|
14174
14184
|
requirements: StateTransitionRequirementsSchema.optional(),
|
|
14175
14185
|
permission: StateTransitionPermissionSchema.optional(),
|
|
14176
14186
|
risk: external_exports.enum(["low", "medium", "high"]).optional(),
|
|
14177
|
-
expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
|
|
14187
|
+
expectedOutcome: StateTransitionExpectedOutcomeSchema.optional(),
|
|
14188
|
+
outcomes: external_exports.record(external_exports.string().min(1), StateTransitionOutcomeSchema).optional()
|
|
14178
14189
|
}).strict();
|
|
14179
14190
|
external_exports.object({
|
|
14180
14191
|
name: external_exports.string().min(1),
|
|
@@ -14866,18 +14877,24 @@ function normalizeEnumInput(enumSpec) {
|
|
|
14866
14877
|
(value) => typeof value === "string" && value.length > 0
|
|
14867
14878
|
);
|
|
14868
14879
|
if (values.length === 0) return null;
|
|
14869
|
-
|
|
14880
|
+
const labels = Array.isArray(config.labels) && config.labels.length === values.length ? config.labels : values;
|
|
14881
|
+
return {
|
|
14882
|
+
values,
|
|
14883
|
+
labels,
|
|
14884
|
+
...config.message ? { message: config.message } : {}
|
|
14885
|
+
};
|
|
14870
14886
|
}
|
|
14871
14887
|
function buildEnumFieldMutations(fieldPath, enumSpec) {
|
|
14872
14888
|
const normalized = normalizeEnumInput(enumSpec);
|
|
14873
14889
|
if (!normalized) return [];
|
|
14874
14890
|
const messageArg = normalized.message ? `, message: ${JSON.stringify(normalized.message)}` : "";
|
|
14891
|
+
const labelsArg = normalized.labels ? `, labels: ${JSON.stringify(normalized.labels)}` : "";
|
|
14875
14892
|
return [
|
|
14876
14893
|
{
|
|
14877
14894
|
label: `set enum on ${fieldPath}`,
|
|
14878
14895
|
query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_enum(values: ${JSON.stringify(
|
|
14879
14896
|
normalized.values
|
|
14880
|
-
)}${messageArg}) { values } } }`
|
|
14897
|
+
)}${labelsArg}${messageArg}) { values labels } } }`
|
|
14881
14898
|
}
|
|
14882
14899
|
];
|
|
14883
14900
|
}
|
|
@@ -14887,7 +14904,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
|
|
|
14887
14904
|
fieldRows: [
|
|
14888
14905
|
{
|
|
14889
14906
|
key: "enum",
|
|
14890
|
-
description: 'Allowed values.
|
|
14907
|
+
description: 'Allowed values. Use `{ "values": [...], "labels": [...] }` for authored display labels; otherwise each unchanged value is its display fallback.'
|
|
14891
14908
|
}
|
|
14892
14909
|
]
|
|
14893
14910
|
},
|
|
@@ -14897,6 +14914,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
|
|
|
14897
14914
|
type EnumMetamodel {
|
|
14898
14915
|
model: Model!
|
|
14899
14916
|
values: [String!]!
|
|
14917
|
+
labels: [String!]!
|
|
14900
14918
|
message: String
|
|
14901
14919
|
}
|
|
14902
14920
|
|
|
@@ -14905,7 +14923,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
|
|
|
14905
14923
|
}
|
|
14906
14924
|
|
|
14907
14925
|
extend type ModelMutation {
|
|
14908
|
-
set_enum(values: [String!]!, message: String): EnumMetamodel
|
|
14926
|
+
set_enum(values: [String!]!, labels: [String!], message: String): EnumMetamodel
|
|
14909
14927
|
}
|
|
14910
14928
|
`
|
|
14911
14929
|
],
|
|
@@ -14914,15 +14932,19 @@ var enumMetamodelPackage = defineMetamodelPackage({
|
|
|
14914
14932
|
EnumMetamodel: {
|
|
14915
14933
|
model: (value) => value.model,
|
|
14916
14934
|
values: (value) => value.values,
|
|
14935
|
+
labels: (value) => value.labels || [],
|
|
14917
14936
|
message: (value) => value.message || null
|
|
14918
14937
|
},
|
|
14919
14938
|
Model: {
|
|
14920
14939
|
enum_rule: async (ant) => await run(ant.enum_rule())
|
|
14921
14940
|
},
|
|
14922
14941
|
ModelMutation: {
|
|
14923
|
-
set_enum: async (ant, { values, message }) => {
|
|
14924
|
-
const
|
|
14925
|
-
|
|
14942
|
+
set_enum: async (ant, { values, labels, message }) => {
|
|
14943
|
+
const resolvedLabels = Array.isArray(labels) && labels.length === values.length ? labels : values;
|
|
14944
|
+
const model = await run(
|
|
14945
|
+
ant.set_enum(values, resolvedLabels, message)
|
|
14946
|
+
);
|
|
14947
|
+
return { model, values, labels: resolvedLabels, message };
|
|
14926
14948
|
}
|
|
14927
14949
|
}
|
|
14928
14950
|
};
|
|
@@ -14935,7 +14957,7 @@ var enumMetamodelPackage = defineMetamodelPackage({
|
|
|
14935
14957
|
},
|
|
14936
14958
|
summary: {
|
|
14937
14959
|
selections: {
|
|
14938
|
-
propertyFields: [`enum_rule { values message }`]
|
|
14960
|
+
propertyFields: [`enum_rule { values labels message }`]
|
|
14939
14961
|
},
|
|
14940
14962
|
readPropertySummary(rawProperty) {
|
|
14941
14963
|
const values = Array.isArray(rawProperty.enum_rule?.values) ? rawProperty.enum_rule.values.filter(
|
|
@@ -14943,8 +14965,15 @@ var enumMetamodelPackage = defineMetamodelPackage({
|
|
|
14943
14965
|
) : [];
|
|
14944
14966
|
if (values.length === 0) return { enumRule: null };
|
|
14945
14967
|
const message = typeof rawProperty.enum_rule?.message === "string" ? rawProperty.enum_rule.message : null;
|
|
14968
|
+
const labels = Array.isArray(rawProperty.enum_rule?.labels) ? rawProperty.enum_rule.labels.filter(
|
|
14969
|
+
(label) => typeof label === "string" && label.length > 0
|
|
14970
|
+
) : [];
|
|
14946
14971
|
return {
|
|
14947
|
-
enumRule:
|
|
14972
|
+
enumRule: {
|
|
14973
|
+
values,
|
|
14974
|
+
...labels.length === values.length ? { labels } : {},
|
|
14975
|
+
...message ? { message } : {}
|
|
14976
|
+
}
|
|
14948
14977
|
};
|
|
14949
14978
|
}
|
|
14950
14979
|
},
|
|
@@ -15507,7 +15536,8 @@ function normalizeStateMachines(values) {
|
|
|
15507
15536
|
requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
|
|
15508
15537
|
permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
|
|
15509
15538
|
risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
|
|
15510
|
-
expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
|
|
15539
|
+
expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json),
|
|
15540
|
+
outcomes: parseJsonRecord(transition?.outcomes) || parseJsonRecord(transition?.outcomes_json)
|
|
15511
15541
|
})).filter(
|
|
15512
15542
|
(transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
|
|
15513
15543
|
);
|
|
@@ -15600,6 +15630,11 @@ function transitionMetadataGraphqlArgs(transition) {
|
|
|
15600
15630
|
`expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
|
|
15601
15631
|
);
|
|
15602
15632
|
}
|
|
15633
|
+
if (transition.outcomes) {
|
|
15634
|
+
args.push(
|
|
15635
|
+
`outcomes_json: ${JSON.stringify(JSON.stringify(transition.outcomes))}`
|
|
15636
|
+
);
|
|
15637
|
+
}
|
|
15603
15638
|
return args.length > 0 ? `, ${args.join(", ")}` : "";
|
|
15604
15639
|
}
|
|
15605
15640
|
function buildStateMachineModelMutations(modelPath, machines) {
|
|
@@ -15878,7 +15913,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
15878
15913
|
name: String!
|
|
15879
15914
|
state_machine: StateMachine!
|
|
15880
15915
|
add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
|
|
15881
|
-
add_transition(name: String!, from: String!, to: String!, label: String, description: String, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String): StateMachineMutation!
|
|
15916
|
+
add_transition(name: String!, from: String!, to: String!, label: String, description: String, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String, outcomes_json: String): StateMachineMutation!
|
|
15882
15917
|
activate_transition(name: String!): StateMachineMutation!
|
|
15883
15918
|
}
|
|
15884
15919
|
|
|
@@ -15895,6 +15930,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
15895
15930
|
type StateMachineSnapshotMutation {
|
|
15896
15931
|
snapshot: StateMachineSnapshot!
|
|
15897
15932
|
activate_transition(name: String!): StateMachineSnapshotMutation!
|
|
15933
|
+
commit_transition(name: String!, outcome: String!, to: String!, commit_id: String!, source_version: String): StateMachineSnapshotMutation!
|
|
15898
15934
|
observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
|
|
15899
15935
|
}
|
|
15900
15936
|
|
|
@@ -15941,6 +15977,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
15941
15977
|
permission_json: String
|
|
15942
15978
|
risk: String
|
|
15943
15979
|
expected_outcome_json: String
|
|
15980
|
+
outcomes_json: String
|
|
15944
15981
|
}
|
|
15945
15982
|
|
|
15946
15983
|
type StateMachinePath {
|
|
@@ -15951,6 +15988,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
15951
15988
|
type StateMachineTransitionEvent {
|
|
15952
15989
|
sequence: Int!
|
|
15953
15990
|
occurred_at: Float!
|
|
15991
|
+
commit_id: String
|
|
15992
|
+
outcome: String
|
|
15993
|
+
source_version: String
|
|
15994
|
+
projected_from_mismatch: String
|
|
15954
15995
|
transition: StateMachineTransition!
|
|
15955
15996
|
from: StateMachineState!
|
|
15956
15997
|
to: StateMachineState!
|
|
@@ -16016,7 +16057,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
16016
16057
|
requirements_json,
|
|
16017
16058
|
permission_json,
|
|
16018
16059
|
risk,
|
|
16019
|
-
expected_outcome_json
|
|
16060
|
+
expected_outcome_json,
|
|
16061
|
+
outcomes_json
|
|
16020
16062
|
}) => {
|
|
16021
16063
|
await run(
|
|
16022
16064
|
value.target.add_state_machine_transition(
|
|
@@ -16032,7 +16074,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
16032
16074
|
requirementsJson: requirements_json,
|
|
16033
16075
|
permissionJson: permission_json,
|
|
16034
16076
|
risk,
|
|
16035
|
-
expectedOutcomeJson: expected_outcome_json
|
|
16077
|
+
expectedOutcomeJson: expected_outcome_json,
|
|
16078
|
+
outcomesJson: outcomes_json
|
|
16036
16079
|
}
|
|
16037
16080
|
)
|
|
16038
16081
|
);
|
|
@@ -16053,6 +16096,19 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
16053
16096
|
);
|
|
16054
16097
|
return value;
|
|
16055
16098
|
},
|
|
16099
|
+
commit_transition: async (value, { name, outcome, to, commit_id, source_version }) => {
|
|
16100
|
+
await run(
|
|
16101
|
+
value.target.commit_state_machine_transition(
|
|
16102
|
+
value.name,
|
|
16103
|
+
name,
|
|
16104
|
+
outcome,
|
|
16105
|
+
to,
|
|
16106
|
+
commit_id,
|
|
16107
|
+
source_version
|
|
16108
|
+
)
|
|
16109
|
+
);
|
|
16110
|
+
return value;
|
|
16111
|
+
},
|
|
16056
16112
|
observe_state: async (value, { state, force, source }) => {
|
|
16057
16113
|
await run(
|
|
16058
16114
|
value.target.observe_state_machine_state(
|
|
@@ -16082,7 +16138,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
16082
16138
|
requirements_json: (value) => value.requirements_json || null,
|
|
16083
16139
|
permission_json: (value) => value.permission_json || null,
|
|
16084
16140
|
risk: (value) => value.risk || null,
|
|
16085
|
-
expected_outcome_json: (value) => value.expected_outcome_json || null
|
|
16141
|
+
expected_outcome_json: (value) => value.expected_outcome_json || null,
|
|
16142
|
+
outcomes_json: (value) => value.outcomes_json || null
|
|
16086
16143
|
},
|
|
16087
16144
|
StateMachinePath: {
|
|
16088
16145
|
states: (value) => value.states,
|
|
@@ -16091,6 +16148,10 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
16091
16148
|
StateMachineTransitionEvent: {
|
|
16092
16149
|
sequence: (value) => value.sequence,
|
|
16093
16150
|
occurred_at: (value) => value.occurred_at,
|
|
16151
|
+
commit_id: (value) => value.commit_id || null,
|
|
16152
|
+
outcome: (value) => value.outcome || null,
|
|
16153
|
+
source_version: (value) => value.source_version || null,
|
|
16154
|
+
projected_from_mismatch: (value) => value.projected_from_mismatch || null,
|
|
16094
16155
|
transition: (value) => value.transition,
|
|
16095
16156
|
from: (value) => value.from,
|
|
16096
16157
|
to: (value) => value.to
|
|
@@ -16164,6 +16225,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
16164
16225
|
permission_json
|
|
16165
16226
|
risk
|
|
16166
16227
|
expected_outcome_json
|
|
16228
|
+
outcomes_json
|
|
16167
16229
|
}
|
|
16168
16230
|
}`
|
|
16169
16231
|
]
|
|
@@ -22073,6 +22135,11 @@ var Session = class {
|
|
|
22073
22135
|
}
|
|
22074
22136
|
}
|
|
22075
22137
|
buildDirectedInvocationEffectContext(params, feedbackContext) {
|
|
22138
|
+
const commitUnavailable = async () => {
|
|
22139
|
+
throw new Error(
|
|
22140
|
+
"This directed browser tool invocation has no product commit transport"
|
|
22141
|
+
);
|
|
22142
|
+
};
|
|
22076
22143
|
return {
|
|
22077
22144
|
effectClientId: this.clientId,
|
|
22078
22145
|
sandboxId: params.sandboxId || "",
|
|
@@ -22085,6 +22152,10 @@ var Session = class {
|
|
|
22085
22152
|
userId: "",
|
|
22086
22153
|
subjectId: ""
|
|
22087
22154
|
},
|
|
22155
|
+
commit: {
|
|
22156
|
+
effect: commitUnavailable,
|
|
22157
|
+
transition: commitUnavailable
|
|
22158
|
+
},
|
|
22088
22159
|
...feedbackContext ? {
|
|
22089
22160
|
feedback: feedbackContext.feedback,
|
|
22090
22161
|
transientFeedback: feedbackContext.transientFeedback
|
|
@@ -23614,6 +23685,238 @@ function buildSessionTranscript(input) {
|
|
|
23614
23685
|
});
|
|
23615
23686
|
}
|
|
23616
23687
|
|
|
23688
|
+
// src/commit.ts
|
|
23689
|
+
var MAX_PROJECTION_CHANGES = 1e3;
|
|
23690
|
+
var MAX_SAFE_SUMMARY_KEYS = 32;
|
|
23691
|
+
var MAX_SAFE_TEXT_LENGTH = 2e3;
|
|
23692
|
+
var definedTransitionMetadata = /* @__PURE__ */ new WeakMap();
|
|
23693
|
+
function getDefinedTransitionMetadata(transition) {
|
|
23694
|
+
return definedTransitionMetadata.get(transition);
|
|
23695
|
+
}
|
|
23696
|
+
function requireNonEmptyString(value, path7) {
|
|
23697
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
23698
|
+
throw new Error(`${path7} must be a non-empty string`);
|
|
23699
|
+
}
|
|
23700
|
+
return value.trim();
|
|
23701
|
+
}
|
|
23702
|
+
function validateObjectReference(value, path7) {
|
|
23703
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
23704
|
+
throw new Error(`${path7} must be an object reference`);
|
|
23705
|
+
}
|
|
23706
|
+
const reference = value;
|
|
23707
|
+
requireNonEmptyString(reference.className, `${path7}.className`);
|
|
23708
|
+
requireNonEmptyString(reference.id, `${path7}.id`);
|
|
23709
|
+
if (reference.path !== void 0) {
|
|
23710
|
+
requireNonEmptyString(reference.path, `${path7}.path`);
|
|
23711
|
+
}
|
|
23712
|
+
}
|
|
23713
|
+
function validateScalarRecord(value, path7) {
|
|
23714
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
|
|
23715
|
+
throw new Error(`${path7} must be an object of scalar values`);
|
|
23716
|
+
}
|
|
23717
|
+
for (const [key, item] of Object.entries(value)) {
|
|
23718
|
+
if (item !== null && typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
|
|
23719
|
+
throw new Error(
|
|
23720
|
+
`${path7}.${key} must be a string, finite number, boolean, or null`
|
|
23721
|
+
);
|
|
23722
|
+
}
|
|
23723
|
+
if (typeof item === "number" && !Number.isFinite(item)) {
|
|
23724
|
+
throw new Error(`${path7}.${key} must be finite`);
|
|
23725
|
+
}
|
|
23726
|
+
}
|
|
23727
|
+
}
|
|
23728
|
+
function validateProjectedRecord(value, path7) {
|
|
23729
|
+
validateObjectReference(value, path7);
|
|
23730
|
+
const record = value;
|
|
23731
|
+
if (record.label !== void 0) {
|
|
23732
|
+
if (typeof record.label !== "string") {
|
|
23733
|
+
throw new Error(`${path7}.label must be a string`);
|
|
23734
|
+
}
|
|
23735
|
+
if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
|
|
23736
|
+
throw new Error(
|
|
23737
|
+
`${path7}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
|
|
23738
|
+
);
|
|
23739
|
+
}
|
|
23740
|
+
}
|
|
23741
|
+
validateScalarRecord(record.fields, `${path7}.fields`);
|
|
23742
|
+
}
|
|
23743
|
+
function validateBoundedJson(value, path7, depth = 0) {
|
|
23744
|
+
if (depth > 12) throw new Error(`${path7} is nested too deeply`);
|
|
23745
|
+
if (value === null || typeof value === "boolean") return;
|
|
23746
|
+
if (typeof value === "number") {
|
|
23747
|
+
if (!Number.isFinite(value)) throw new Error(`${path7} must be finite`);
|
|
23748
|
+
return;
|
|
23749
|
+
}
|
|
23750
|
+
if (typeof value === "string") {
|
|
23751
|
+
if (value.length > MAX_SAFE_TEXT_LENGTH) {
|
|
23752
|
+
throw new Error(`${path7} exceeds ${MAX_SAFE_TEXT_LENGTH} characters`);
|
|
23753
|
+
}
|
|
23754
|
+
return;
|
|
23755
|
+
}
|
|
23756
|
+
if (Array.isArray(value)) {
|
|
23757
|
+
if (value.length > MAX_PROJECTION_CHANGES) {
|
|
23758
|
+
throw new Error(`${path7} contains too many values`);
|
|
23759
|
+
}
|
|
23760
|
+
value.forEach(
|
|
23761
|
+
(item, index) => validateBoundedJson(item, `${path7}[${index}]`, depth + 1)
|
|
23762
|
+
);
|
|
23763
|
+
return;
|
|
23764
|
+
}
|
|
23765
|
+
if (!value || typeof value !== "object") {
|
|
23766
|
+
throw new Error(`${path7} contains an unsupported value`);
|
|
23767
|
+
}
|
|
23768
|
+
const entries = Object.entries(value);
|
|
23769
|
+
if (entries.length > 256) throw new Error(`${path7} contains too many keys`);
|
|
23770
|
+
for (const [key, item] of entries) {
|
|
23771
|
+
if (/token|secret|password|authorization|cookie/i.test(key)) {
|
|
23772
|
+
throw new Error(`${path7}.${key} is not allowed in a commit projection`);
|
|
23773
|
+
}
|
|
23774
|
+
validateBoundedJson(item, `${path7}.${key}`, depth + 1);
|
|
23775
|
+
}
|
|
23776
|
+
}
|
|
23777
|
+
function validateProjectionResult(declaration, projection) {
|
|
23778
|
+
if (!projection || typeof projection !== "object") {
|
|
23779
|
+
throw new Error("Projection mapper must return an object");
|
|
23780
|
+
}
|
|
23781
|
+
requireNonEmptyString(
|
|
23782
|
+
projection.source?.reference,
|
|
23783
|
+
"projection.source.reference"
|
|
23784
|
+
);
|
|
23785
|
+
if (projection.source.version !== void 0) {
|
|
23786
|
+
requireNonEmptyString(
|
|
23787
|
+
projection.source.version,
|
|
23788
|
+
"projection.source.version"
|
|
23789
|
+
);
|
|
23790
|
+
}
|
|
23791
|
+
if (!Array.isArray(projection.changes)) {
|
|
23792
|
+
throw new Error("projection.changes must be an array");
|
|
23793
|
+
}
|
|
23794
|
+
if (projection.changes.length > MAX_PROJECTION_CHANGES) {
|
|
23795
|
+
throw new Error(
|
|
23796
|
+
`projection.changes exceeds the ${MAX_PROJECTION_CHANGES} change limit`
|
|
23797
|
+
);
|
|
23798
|
+
}
|
|
23799
|
+
if (projection.primaryTarget) {
|
|
23800
|
+
validateObjectReference(
|
|
23801
|
+
projection.primaryTarget,
|
|
23802
|
+
"projection.primaryTarget"
|
|
23803
|
+
);
|
|
23804
|
+
}
|
|
23805
|
+
if (projection.safeSummary) {
|
|
23806
|
+
const entries = Object.entries(projection.safeSummary);
|
|
23807
|
+
if (entries.length > MAX_SAFE_SUMMARY_KEYS) {
|
|
23808
|
+
throw new Error(
|
|
23809
|
+
`projection.safeSummary exceeds the ${MAX_SAFE_SUMMARY_KEYS} key limit`
|
|
23810
|
+
);
|
|
23811
|
+
}
|
|
23812
|
+
validateBoundedJson(projection.safeSummary, "projection.safeSummary");
|
|
23813
|
+
validateScalarRecord(projection.safeSummary, "projection.safeSummary");
|
|
23814
|
+
}
|
|
23815
|
+
projection.changes.forEach((change, index) => {
|
|
23816
|
+
const changePath = `projection.changes[${index}]`;
|
|
23817
|
+
validateBoundedJson(change, changePath);
|
|
23818
|
+
if (!change || typeof change !== "object" || Array.isArray(change)) {
|
|
23819
|
+
throw new Error(`${changePath} must be an object`);
|
|
23820
|
+
}
|
|
23821
|
+
const rawChange = change;
|
|
23822
|
+
const kind = requireNonEmptyString(rawChange.kind, `${changePath}.kind`);
|
|
23823
|
+
if (kind === "object") {
|
|
23824
|
+
const operation = requireNonEmptyString(
|
|
23825
|
+
rawChange.operation,
|
|
23826
|
+
`${changePath}.operation`
|
|
23827
|
+
);
|
|
23828
|
+
if (operation !== "created" && operation !== "updated" && operation !== "deleted") {
|
|
23829
|
+
throw new Error(
|
|
23830
|
+
`${changePath}.operation must be created, updated, or deleted`
|
|
23831
|
+
);
|
|
23832
|
+
}
|
|
23833
|
+
if (operation === "deleted") {
|
|
23834
|
+
validateObjectReference(rawChange.target, `${changePath}.target`);
|
|
23835
|
+
} else {
|
|
23836
|
+
validateProjectedRecord(rawChange.record, `${changePath}.record`);
|
|
23837
|
+
}
|
|
23838
|
+
} else if (kind === "relationship") {
|
|
23839
|
+
const operation = requireNonEmptyString(
|
|
23840
|
+
rawChange.operation,
|
|
23841
|
+
`${changePath}.operation`
|
|
23842
|
+
);
|
|
23843
|
+
if (operation !== "connected" && operation !== "disconnected") {
|
|
23844
|
+
throw new Error(
|
|
23845
|
+
`${changePath}.operation must be connected or disconnected`
|
|
23846
|
+
);
|
|
23847
|
+
}
|
|
23848
|
+
requireNonEmptyString(
|
|
23849
|
+
rawChange.relationship,
|
|
23850
|
+
`${changePath}.relationship`
|
|
23851
|
+
);
|
|
23852
|
+
validateObjectReference(rawChange.from, `${changePath}.from`);
|
|
23853
|
+
validateObjectReference(rawChange.to, `${changePath}.to`);
|
|
23854
|
+
} else if (kind === "state_observation") {
|
|
23855
|
+
if (rawChange.operation !== void 0) {
|
|
23856
|
+
throw new Error(
|
|
23857
|
+
`${changePath}.operation is not valid for an observation`
|
|
23858
|
+
);
|
|
23859
|
+
}
|
|
23860
|
+
validateObjectReference(rawChange.target, `${changePath}.target`);
|
|
23861
|
+
requireNonEmptyString(rawChange.machine, `${changePath}.machine`);
|
|
23862
|
+
requireNonEmptyString(rawChange.state, `${changePath}.state`);
|
|
23863
|
+
} else {
|
|
23864
|
+
throw new Error(`${changePath}.kind is unsupported: ${kind}`);
|
|
23865
|
+
}
|
|
23866
|
+
});
|
|
23867
|
+
const objectOperations = /* @__PURE__ */ new Map();
|
|
23868
|
+
for (const change of projection.changes) {
|
|
23869
|
+
if (change.kind !== "object") continue;
|
|
23870
|
+
const reference = change.operation === "deleted" ? change.target : change.record;
|
|
23871
|
+
const key = `${reference.className}\0${reference.id}`;
|
|
23872
|
+
const operations = objectOperations.get(key) || {
|
|
23873
|
+
deleted: false,
|
|
23874
|
+
upserted: false
|
|
23875
|
+
};
|
|
23876
|
+
if (change.operation === "deleted") operations.deleted = true;
|
|
23877
|
+
else operations.upserted = true;
|
|
23878
|
+
if (operations.deleted && operations.upserted) {
|
|
23879
|
+
throw new Error(
|
|
23880
|
+
`projection.changes cannot both delete and upsert ${reference.className}/${reference.id}; return only its canonical final state`
|
|
23881
|
+
);
|
|
23882
|
+
}
|
|
23883
|
+
objectOperations.set(key, operations);
|
|
23884
|
+
}
|
|
23885
|
+
const outcome = projection.outcome;
|
|
23886
|
+
if (declaration.kind === "transition") {
|
|
23887
|
+
if (!outcome) {
|
|
23888
|
+
throw new Error("A transition projection must return an outcome");
|
|
23889
|
+
}
|
|
23890
|
+
requireNonEmptyString(outcome.key, "projection.outcome.key");
|
|
23891
|
+
if (outcome.error) {
|
|
23892
|
+
validateBoundedJson(outcome.error, "projection.outcome.error");
|
|
23893
|
+
}
|
|
23894
|
+
} else if (projection.outcome !== void 0) {
|
|
23895
|
+
throw new Error("An effect projection cannot declare a transition outcome");
|
|
23896
|
+
}
|
|
23897
|
+
}
|
|
23898
|
+
function canonicalizeCommitValue(value) {
|
|
23899
|
+
const normalize = (current) => {
|
|
23900
|
+
if (current === null || typeof current === "boolean" || typeof current === "string") {
|
|
23901
|
+
return typeof current === "string" ? current.normalize("NFC") : current;
|
|
23902
|
+
}
|
|
23903
|
+
if (typeof current === "number") {
|
|
23904
|
+
if (!Number.isFinite(current)) {
|
|
23905
|
+
throw new Error("Cannot canonicalize a non-finite number");
|
|
23906
|
+
}
|
|
23907
|
+
return Object.is(current, -0) ? 0 : current;
|
|
23908
|
+
}
|
|
23909
|
+
if (Array.isArray(current)) return current.map(normalize);
|
|
23910
|
+
if (current && typeof current === "object") {
|
|
23911
|
+
return Object.fromEntries(
|
|
23912
|
+
Object.entries(current).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key.normalize("NFC"), normalize(item)])
|
|
23913
|
+
);
|
|
23914
|
+
}
|
|
23915
|
+
throw new Error(`Cannot canonicalize ${typeof current}`);
|
|
23916
|
+
};
|
|
23917
|
+
return JSON.stringify(normalize(value));
|
|
23918
|
+
}
|
|
23919
|
+
|
|
23617
23920
|
// src/effect-runtime.ts
|
|
23618
23921
|
function computeEffectKey(effect) {
|
|
23619
23922
|
const attachedClass = effect.className?.trim();
|
|
@@ -23678,6 +23981,45 @@ function resolveInvocationMode(context) {
|
|
|
23678
23981
|
}
|
|
23679
23982
|
return "execute";
|
|
23680
23983
|
}
|
|
23984
|
+
async function sha256Hex(value) {
|
|
23985
|
+
const digest = await globalThis.crypto.subtle.digest(
|
|
23986
|
+
"SHA-256",
|
|
23987
|
+
new TextEncoder().encode(value)
|
|
23988
|
+
);
|
|
23989
|
+
return Array.from(
|
|
23990
|
+
new Uint8Array(digest),
|
|
23991
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
23992
|
+
).join("");
|
|
23993
|
+
}
|
|
23994
|
+
async function resolveInvocationIdempotencyKey(request) {
|
|
23995
|
+
const supplied = request.context?.idempotencyKey?.trim();
|
|
23996
|
+
if (supplied) return supplied;
|
|
23997
|
+
const invocationId = request.context?.invocationId?.trim();
|
|
23998
|
+
if (!invocationId) {
|
|
23999
|
+
throw new Error(
|
|
24000
|
+
`Committed effect ${request.effectKey} requires an invocation id`
|
|
24001
|
+
);
|
|
24002
|
+
}
|
|
24003
|
+
const digest = await sha256Hex(
|
|
24004
|
+
canonicalizeCommitValue({
|
|
24005
|
+
sandboxId: request.context?.sandboxId || "",
|
|
24006
|
+
environmentId: request.context?.environmentId || "",
|
|
24007
|
+
effectKey: request.effectKey,
|
|
24008
|
+
invocationId,
|
|
24009
|
+
input: request.input
|
|
24010
|
+
})
|
|
24011
|
+
);
|
|
24012
|
+
return `gci_${digest}`;
|
|
24013
|
+
}
|
|
24014
|
+
function createUnavailableCommitContext(message) {
|
|
24015
|
+
const unavailable = async () => {
|
|
24016
|
+
throw new Error(message);
|
|
24017
|
+
};
|
|
24018
|
+
return {
|
|
24019
|
+
effect: unavailable,
|
|
24020
|
+
transition: unavailable
|
|
24021
|
+
};
|
|
24022
|
+
}
|
|
23681
24023
|
function resolveReverseEffect(effectMap, currentEffect, request, behaviors) {
|
|
23682
24024
|
const explicitReverseHandler = request.context?.invocation?.reverseHandler?.trim();
|
|
23683
24025
|
const configuredReverseHandler = behaviors.reverse?.handler?.trim();
|
|
@@ -23730,7 +24072,7 @@ function resolveHandlerForMode(effectMap, effect, request) {
|
|
|
23730
24072
|
if (mode === "artifactOptions") {
|
|
23731
24073
|
if (!effect.artifactOptionsHandler) {
|
|
23732
24074
|
throw new Error(
|
|
23733
|
-
`Artifact
|
|
24075
|
+
`Artifact field options are not supported for ${request.effectKey}`
|
|
23734
24076
|
);
|
|
23735
24077
|
}
|
|
23736
24078
|
return {
|
|
@@ -23749,22 +24091,32 @@ function resolveHandlerForMode(effectMap, effect, request) {
|
|
|
23749
24091
|
throw new Error(`Dry run is not supported for ${request.effectKey}`);
|
|
23750
24092
|
}
|
|
23751
24093
|
if (mode === "reverse") {
|
|
24094
|
+
const sourceEffectKey = request.context?.invocation?.sourceEffectKey?.trim();
|
|
24095
|
+
if (sourceEffectKey && sourceEffectKey !== request.effectKey) {
|
|
24096
|
+
return { effect, mode, handler: effect.handler };
|
|
24097
|
+
}
|
|
24098
|
+
const namedReverseHandler = request.context?.invocation?.reverseHandler?.trim() || behaviors.reverse?.handler?.trim();
|
|
24099
|
+
if (namedReverseHandler) {
|
|
24100
|
+
const reverseEffect = resolveReverseEffect(
|
|
24101
|
+
effectMap,
|
|
24102
|
+
effect,
|
|
24103
|
+
request,
|
|
24104
|
+
behaviors
|
|
24105
|
+
);
|
|
24106
|
+
if (reverseEffect) {
|
|
24107
|
+
return {
|
|
24108
|
+
effect: reverseEffect,
|
|
24109
|
+
mode,
|
|
24110
|
+
handler: reverseEffect.handler
|
|
24111
|
+
};
|
|
24112
|
+
}
|
|
24113
|
+
throw new Error(
|
|
24114
|
+
`Reverse effect ${namedReverseHandler} is not registered for ${request.effectKey}`
|
|
24115
|
+
);
|
|
24116
|
+
}
|
|
23752
24117
|
if (effect.reverseHandler) {
|
|
23753
24118
|
return { effect, mode, handler: effect.reverseHandler };
|
|
23754
24119
|
}
|
|
23755
|
-
const reverseEffect = resolveReverseEffect(
|
|
23756
|
-
effectMap,
|
|
23757
|
-
effect,
|
|
23758
|
-
request,
|
|
23759
|
-
behaviors
|
|
23760
|
-
);
|
|
23761
|
-
if (reverseEffect) {
|
|
23762
|
-
return {
|
|
23763
|
-
effect: reverseEffect,
|
|
23764
|
-
mode,
|
|
23765
|
-
handler: reverseEffect.reverseHandler || reverseEffect.handler
|
|
23766
|
-
};
|
|
23767
|
-
}
|
|
23768
24120
|
throw new Error(
|
|
23769
24121
|
`Reverse execution is not supported for ${request.effectKey}`
|
|
23770
24122
|
);
|
|
@@ -23865,18 +24217,142 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
|
|
|
23865
24217
|
throw new Error(`Effect handler not found: ${request.effectKey}`);
|
|
23866
24218
|
}
|
|
23867
24219
|
const resolved = resolveHandlerForMode(effectMap, effect, request);
|
|
24220
|
+
const isPreResolvedNamedReverse = resolved.mode === "reverse" && Boolean(request.context?.invocation?.sourceEffectKey?.trim()) && request.context?.invocation?.sourceEffectKey?.trim() !== request.effectKey;
|
|
24221
|
+
const resolvedEffectKey = computeEffectKey(resolved.effect);
|
|
24222
|
+
if (resolved.mode === "reverse" && resolved.effect !== effect && (effect.commit || resolved.effect.commit)) {
|
|
24223
|
+
throw new Error(
|
|
24224
|
+
`Committed named reverse effect ${resolvedEffectKey} must be reserved and dispatched using its own effect key; customer code was not invoked`
|
|
24225
|
+
);
|
|
24226
|
+
}
|
|
24227
|
+
const declaration = resolved.effect.commit;
|
|
24228
|
+
const commitTransport = options.commit;
|
|
24229
|
+
const commitRequired = Boolean(declaration) && (resolved.mode === "execute" || resolved.mode === "reverse");
|
|
24230
|
+
if (commitRequired && !commitTransport) {
|
|
24231
|
+
throw new Error(
|
|
24232
|
+
`Committed effect ${request.effectKey} has no durable commit transport; customer code was not invoked`
|
|
24233
|
+
);
|
|
24234
|
+
}
|
|
24235
|
+
if (commitRequired && declaration?.kind === "transition" && !request.context?.invocation?.transition) {
|
|
24236
|
+
throw new Error(
|
|
24237
|
+
`Transition effect ${request.effectKey} has no resolved transition context; customer code was not invoked`
|
|
24238
|
+
);
|
|
24239
|
+
}
|
|
24240
|
+
const idempotencyKey = commitRequired ? await resolveInvocationIdempotencyKey(request) : request.context?.idempotencyKey;
|
|
24241
|
+
let commitStarted = false;
|
|
24242
|
+
let commitPromise = null;
|
|
24243
|
+
const beginCommit = (requestedKind, productResult) => {
|
|
24244
|
+
if (!declaration || !commitRequired || !commitTransport) {
|
|
24245
|
+
return Promise.reject(
|
|
24246
|
+
new Error(
|
|
24247
|
+
`Effect ${request.effectKey} does not declare an active ${requestedKind} commit`
|
|
24248
|
+
)
|
|
24249
|
+
);
|
|
24250
|
+
}
|
|
24251
|
+
if (declaration.kind !== requestedKind) {
|
|
24252
|
+
return Promise.reject(
|
|
24253
|
+
new Error(
|
|
24254
|
+
`Effect ${request.effectKey} declares ${declaration.kind} commit, not ${requestedKind}`
|
|
24255
|
+
)
|
|
24256
|
+
);
|
|
24257
|
+
}
|
|
24258
|
+
if (commitStarted) {
|
|
24259
|
+
return Promise.reject(
|
|
24260
|
+
new Error(
|
|
24261
|
+
`Effect invocation ${request.context?.invocationId || request.effectKey} already emitted its commit`
|
|
24262
|
+
)
|
|
24263
|
+
);
|
|
24264
|
+
}
|
|
24265
|
+
commitStarted = true;
|
|
24266
|
+
commitPromise = (async () => {
|
|
24267
|
+
let projection;
|
|
24268
|
+
try {
|
|
24269
|
+
projection = declaration.project(productResult);
|
|
24270
|
+
validateProjectionResult(declaration, projection);
|
|
24271
|
+
} catch (error2) {
|
|
24272
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
24273
|
+
if (commitTransport.mappingFailed) {
|
|
24274
|
+
await commitTransport.mappingFailed({
|
|
24275
|
+
effectKey: request.effectKey,
|
|
24276
|
+
effectName: request.effectName,
|
|
24277
|
+
invocationId: request.context?.invocationId || "",
|
|
24278
|
+
idempotencyKey: idempotencyKey || "",
|
|
24279
|
+
environmentId: request.context?.environmentId || "",
|
|
24280
|
+
message
|
|
24281
|
+
});
|
|
24282
|
+
}
|
|
24283
|
+
throw new Error(
|
|
24284
|
+
`Product mutation may have succeeded, but its commit projection is invalid: ${message}`
|
|
24285
|
+
);
|
|
24286
|
+
}
|
|
24287
|
+
if (requestedKind === "transition") {
|
|
24288
|
+
const transition = request.context?.invocation?.transition;
|
|
24289
|
+
const outcome = projection.outcome;
|
|
24290
|
+
if (!transition || !outcome?.key) {
|
|
24291
|
+
throw new Error(
|
|
24292
|
+
`Transition effect ${request.effectKey} did not resolve an authored outcome`
|
|
24293
|
+
);
|
|
24294
|
+
}
|
|
24295
|
+
if (!Object.prototype.hasOwnProperty.call(
|
|
24296
|
+
transition.outcomes,
|
|
24297
|
+
outcome.key
|
|
24298
|
+
)) {
|
|
24299
|
+
throw new Error(
|
|
24300
|
+
`Transition ${transition.machine}.${transition.transition} does not declare outcome ${outcome.key}`
|
|
24301
|
+
);
|
|
24302
|
+
}
|
|
24303
|
+
}
|
|
24304
|
+
const invocationId = request.context?.invocationId || "";
|
|
24305
|
+
const environmentId = request.context?.environmentId || "";
|
|
24306
|
+
const sandboxId = request.context?.sandboxId || "";
|
|
24307
|
+
if (!invocationId || !environmentId || !sandboxId || !idempotencyKey) {
|
|
24308
|
+
throw new Error(
|
|
24309
|
+
`Committed effect ${request.effectKey} is missing its trusted invocation scope`
|
|
24310
|
+
);
|
|
24311
|
+
}
|
|
24312
|
+
const commitRequest = {
|
|
24313
|
+
kind: requestedKind,
|
|
24314
|
+
effectKey: request.effectKey,
|
|
24315
|
+
effectName: request.effectName,
|
|
24316
|
+
operationLabel: resolved.effect.label || resolved.effect.name,
|
|
24317
|
+
invocationId,
|
|
24318
|
+
idempotencyKey,
|
|
24319
|
+
sandboxId,
|
|
24320
|
+
environmentId,
|
|
24321
|
+
...request.context?.sessionId ? { sessionId: request.context.sessionId } : {},
|
|
24322
|
+
...request.context?.jobId ? { jobId: request.context.jobId } : {},
|
|
24323
|
+
...request.context?.buildId ? { buildId: request.context.buildId } : {},
|
|
24324
|
+
...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
|
|
24325
|
+
projection,
|
|
24326
|
+
...requestedKind === "transition" && request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
|
|
24327
|
+
};
|
|
24328
|
+
const receipt = await commitTransport.persist(commitRequest);
|
|
24329
|
+
await options.commitAcknowledged?.(receipt);
|
|
24330
|
+
return receipt;
|
|
24331
|
+
})();
|
|
24332
|
+
return commitPromise;
|
|
24333
|
+
};
|
|
24334
|
+
const commitContext = commitRequired ? {
|
|
24335
|
+
effect: (productResult) => beginCommit("effect", productResult),
|
|
24336
|
+
transition: (productResult) => beginCommit("transition", productResult)
|
|
24337
|
+
} : createUnavailableCommitContext(
|
|
24338
|
+
`Effect ${request.effectKey} is not executing a declared product commit`
|
|
24339
|
+
);
|
|
23868
24340
|
const context = {
|
|
23869
24341
|
...request.context || {},
|
|
24342
|
+
...idempotencyKey ? { idempotencyKey } : {},
|
|
24343
|
+
commit: commitContext,
|
|
23870
24344
|
behaviors: normalizeEffectBehaviors(
|
|
23871
24345
|
request.context?.behaviors || effect.metamodels || void 0
|
|
23872
24346
|
),
|
|
23873
24347
|
invocation: {
|
|
23874
24348
|
mode: resolved.mode,
|
|
23875
|
-
|
|
23876
|
-
|
|
24349
|
+
...commitRequired && declaration ? { commitKind: declaration.kind } : {},
|
|
24350
|
+
sourceEffectKey: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectKey || request.effectKey : request.effectKey,
|
|
24351
|
+
sourceEffectName: isPreResolvedNamedReverse ? request.context?.invocation?.sourceEffectName || request.effectName : request.effectName,
|
|
23877
24352
|
...request.context?.invocation?.reverseHandler ? { reverseHandler: request.context.invocation.reverseHandler } : {},
|
|
23878
24353
|
...request.context?.invocation?.artifactId ? { artifactId: request.context.invocation.artifactId } : {},
|
|
23879
|
-
...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {}
|
|
24354
|
+
...request.context?.invocation?.artifactOptions ? { artifactOptions: request.context.invocation.artifactOptions } : {},
|
|
24355
|
+
...request.context?.invocation?.transition ? { transition: request.context.invocation.transition } : {}
|
|
23880
24356
|
}
|
|
23881
24357
|
};
|
|
23882
24358
|
const feedbackContext = options.feedback ? createInvocationFeedbackContext(options.feedback) : null;
|
|
@@ -23900,6 +24376,16 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
|
|
|
23900
24376
|
handlerFailed = true;
|
|
23901
24377
|
handlerError = error2;
|
|
23902
24378
|
}
|
|
24379
|
+
let commitError;
|
|
24380
|
+
let commitFailed = false;
|
|
24381
|
+
if (commitPromise) {
|
|
24382
|
+
try {
|
|
24383
|
+
await commitPromise;
|
|
24384
|
+
} catch (error2) {
|
|
24385
|
+
commitFailed = true;
|
|
24386
|
+
commitError = error2;
|
|
24387
|
+
}
|
|
24388
|
+
}
|
|
23903
24389
|
let feedbackError;
|
|
23904
24390
|
let feedbackFailed = false;
|
|
23905
24391
|
if (feedbackContext) {
|
|
@@ -23913,9 +24399,17 @@ async function invokeRegisteredEffect(effectMap, request, options = {}) {
|
|
|
23913
24399
|
if (handlerFailed) {
|
|
23914
24400
|
throw handlerError;
|
|
23915
24401
|
}
|
|
24402
|
+
if (commitFailed) {
|
|
24403
|
+
throw commitError;
|
|
24404
|
+
}
|
|
23916
24405
|
if (feedbackFailed) {
|
|
23917
24406
|
throw feedbackError;
|
|
23918
24407
|
}
|
|
24408
|
+
if (commitRequired && !commitStarted) {
|
|
24409
|
+
throw new Error(
|
|
24410
|
+
`Mutating effect ${request.effectKey} returned without acknowledging its product mutation`
|
|
24411
|
+
);
|
|
24412
|
+
}
|
|
23919
24413
|
return handlerResult;
|
|
23920
24414
|
}
|
|
23921
24415
|
|
|
@@ -23984,12 +24478,7 @@ function toRecordSearchResult(className, node) {
|
|
|
23984
24478
|
(field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
|
|
23985
24479
|
);
|
|
23986
24480
|
const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
|
|
23987
|
-
const
|
|
23988
|
-
if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path7)) {
|
|
23989
|
-
return null;
|
|
23990
|
-
}
|
|
23991
|
-
const fallbackLabel = displayLabelFromFields(fields);
|
|
23992
|
-
const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path7) ? rawLabel : fallbackLabel || rawLabel || id;
|
|
24481
|
+
const label = typeof node.label === "string" && node.label.trim() ? node.label.trim() : path7 || id;
|
|
23993
24482
|
return {
|
|
23994
24483
|
path: path7,
|
|
23995
24484
|
className,
|
|
@@ -23999,30 +24488,6 @@ function toRecordSearchResult(className, node) {
|
|
|
23999
24488
|
fields
|
|
24000
24489
|
};
|
|
24001
24490
|
}
|
|
24002
|
-
function isPlaceholderRecordLabel(label, id, path7) {
|
|
24003
|
-
const normalizedLabel = normalizeGraphPathSegment(label);
|
|
24004
|
-
return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path7);
|
|
24005
|
-
}
|
|
24006
|
-
function displayLabelFromFields(fields) {
|
|
24007
|
-
const preferredFieldNames = [
|
|
24008
|
-
"name",
|
|
24009
|
-
"title",
|
|
24010
|
-
"label",
|
|
24011
|
-
"display_name",
|
|
24012
|
-
"file_name",
|
|
24013
|
-
"number",
|
|
24014
|
-
"code"
|
|
24015
|
-
];
|
|
24016
|
-
for (const preferred of preferredFieldNames) {
|
|
24017
|
-
const match = fields.find(
|
|
24018
|
-
(field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
|
|
24019
|
-
);
|
|
24020
|
-
if (typeof match?.value === "string") {
|
|
24021
|
-
return match.value.trim();
|
|
24022
|
-
}
|
|
24023
|
-
}
|
|
24024
|
-
return null;
|
|
24025
|
-
}
|
|
24026
24491
|
function normalizeRecordSearchText(value) {
|
|
24027
24492
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
24028
24493
|
}
|
|
@@ -24652,6 +25117,133 @@ var Environment = class _Environment {
|
|
|
24652
25117
|
getAwaitingCount: async () => this.getAwaitingRecordCount()
|
|
24653
25118
|
};
|
|
24654
25119
|
}
|
|
25120
|
+
/**
|
|
25121
|
+
* Acknowledge a declared product mutation that already happened outside a
|
|
25122
|
+
* Granular-run effect (for example, in a webhook consumer). These methods
|
|
25123
|
+
* run the declaration's pure projection mapper; they never call its handler.
|
|
25124
|
+
*/
|
|
25125
|
+
get commit() {
|
|
25126
|
+
return {
|
|
25127
|
+
effect: async (effect, productResult, options = {}) => this.persistExternalEffect(effect, productResult, options),
|
|
25128
|
+
transition: async (transition, productResult, options = {}) => this.persistExternalTransition(transition, productResult, options),
|
|
25129
|
+
get: async (commitId) => this.controlPlaneRequest(
|
|
25130
|
+
`/control/environments/${this.environmentId}/commits/${encodeURIComponent(
|
|
25131
|
+
commitId
|
|
25132
|
+
)}`
|
|
25133
|
+
),
|
|
25134
|
+
retry: async (commitId) => this.controlPlaneRequest(
|
|
25135
|
+
`/control/environments/${this.environmentId}/commits/${encodeURIComponent(
|
|
25136
|
+
commitId
|
|
25137
|
+
)}/retry`,
|
|
25138
|
+
{ method: "POST" }
|
|
25139
|
+
)
|
|
25140
|
+
};
|
|
25141
|
+
}
|
|
25142
|
+
/** Inspect or retry the agent synchronization of a product snapshot. */
|
|
25143
|
+
get observation() {
|
|
25144
|
+
return {
|
|
25145
|
+
get: async (observationId) => this.controlPlaneRequest(
|
|
25146
|
+
`/control/environments/${this.environmentId}/observations/${encodeURIComponent(
|
|
25147
|
+
observationId
|
|
25148
|
+
)}`
|
|
25149
|
+
),
|
|
25150
|
+
retry: async (observationId) => this.controlPlaneRequest(
|
|
25151
|
+
`/control/environments/${this.environmentId}/observations/${encodeURIComponent(
|
|
25152
|
+
observationId
|
|
25153
|
+
)}/retry`,
|
|
25154
|
+
{ method: "POST" }
|
|
25155
|
+
)
|
|
25156
|
+
};
|
|
25157
|
+
}
|
|
25158
|
+
async persistExternalEffect(effect, productResult, options) {
|
|
25159
|
+
const projection = effect.commit.project(productResult);
|
|
25160
|
+
validateProjectionResult(effect.commit, projection);
|
|
25161
|
+
this.requireExternalIdentity(projection.source.version, options);
|
|
25162
|
+
return this.controlPlaneRequest(
|
|
25163
|
+
`/control/environments/${this.environmentId}/external-commits`,
|
|
25164
|
+
{
|
|
25165
|
+
method: "POST",
|
|
25166
|
+
body: JSON.stringify({
|
|
25167
|
+
kind: "effect",
|
|
25168
|
+
effectKey: computeEffectKey2(effect),
|
|
25169
|
+
...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
|
|
25170
|
+
projection
|
|
25171
|
+
})
|
|
25172
|
+
}
|
|
25173
|
+
);
|
|
25174
|
+
}
|
|
25175
|
+
async persistExternalTransition(transition, productResult, options) {
|
|
25176
|
+
const metadata = getDefinedTransitionMetadata(transition);
|
|
25177
|
+
if (!metadata) {
|
|
25178
|
+
throw new Error(
|
|
25179
|
+
"environment.commit.transition requires a transition returned by defineStateMachine"
|
|
25180
|
+
);
|
|
25181
|
+
}
|
|
25182
|
+
const projection = transition.effect.commit.project(productResult);
|
|
25183
|
+
validateProjectionResult(transition.effect.commit, projection);
|
|
25184
|
+
this.requireExternalIdentity(projection.source.version, options);
|
|
25185
|
+
if (!Object.prototype.hasOwnProperty.call(
|
|
25186
|
+
transition.outcomes,
|
|
25187
|
+
projection.outcome.key
|
|
25188
|
+
)) {
|
|
25189
|
+
throw new Error(
|
|
25190
|
+
`Transition ${metadata.machine}.${metadata.transition} does not declare outcome ${projection.outcome.key}`
|
|
25191
|
+
);
|
|
25192
|
+
}
|
|
25193
|
+
if (!projection.primaryTarget) {
|
|
25194
|
+
throw new Error(
|
|
25195
|
+
"An external transition projection requires primaryTarget to identify the transitioned product record"
|
|
25196
|
+
);
|
|
25197
|
+
}
|
|
25198
|
+
return this.controlPlaneRequest(
|
|
25199
|
+
`/control/environments/${this.environmentId}/external-commits`,
|
|
25200
|
+
{
|
|
25201
|
+
method: "POST",
|
|
25202
|
+
body: JSON.stringify({
|
|
25203
|
+
kind: "transition",
|
|
25204
|
+
effectKey: computeEffectKey2(transition.effect),
|
|
25205
|
+
...options.sourceEventId?.trim() ? { sourceEventId: options.sourceEventId.trim() } : {},
|
|
25206
|
+
projection,
|
|
25207
|
+
transition: {
|
|
25208
|
+
className: projection.primaryTarget.className,
|
|
25209
|
+
objectId: projection.primaryTarget.id,
|
|
25210
|
+
...projection.primaryTarget.path ? { objectPath: projection.primaryTarget.path } : {},
|
|
25211
|
+
machine: metadata.machine,
|
|
25212
|
+
transition: metadata.transition,
|
|
25213
|
+
from: transition.from
|
|
25214
|
+
}
|
|
25215
|
+
})
|
|
25216
|
+
}
|
|
25217
|
+
);
|
|
25218
|
+
}
|
|
25219
|
+
requireExternalIdentity(sourceVersion, options) {
|
|
25220
|
+
if (!options.sourceEventId?.trim() && !sourceVersion?.trim()) {
|
|
25221
|
+
throw new Error(
|
|
25222
|
+
"An external commit requires sourceEventId or a source version from its projection mapper"
|
|
25223
|
+
);
|
|
25224
|
+
}
|
|
25225
|
+
}
|
|
25226
|
+
/**
|
|
25227
|
+
* Synchronize a versioned product snapshot without claiming an effect or
|
|
25228
|
+
* lifecycle transition. This records no transition history.
|
|
25229
|
+
*/
|
|
25230
|
+
async observe(mapper, productResult) {
|
|
25231
|
+
const declaration = { kind: "effect"};
|
|
25232
|
+
const projection = mapper(productResult);
|
|
25233
|
+
validateProjectionResult(declaration, projection);
|
|
25234
|
+
if (!projection.source.version?.trim()) {
|
|
25235
|
+
throw new Error(
|
|
25236
|
+
"environment.observe requires a monotonic source version or serialized adapter sequence"
|
|
25237
|
+
);
|
|
25238
|
+
}
|
|
25239
|
+
return this.controlPlaneRequest(
|
|
25240
|
+
`/control/environments/${this.environmentId}/observations`,
|
|
25241
|
+
{
|
|
25242
|
+
method: "POST",
|
|
25243
|
+
body: JSON.stringify({ projection })
|
|
25244
|
+
}
|
|
25245
|
+
);
|
|
25246
|
+
}
|
|
24655
25247
|
/**
|
|
24656
25248
|
* Mirror product-owned workflow state into Granular without making Granular
|
|
24657
25249
|
* own the customer application's state machine.
|
|
@@ -26058,6 +26650,14 @@ var EnvironmentSession = class extends Session {
|
|
|
26058
26650
|
}
|
|
26059
26651
|
};
|
|
26060
26652
|
}
|
|
26653
|
+
get mutations() {
|
|
26654
|
+
return {
|
|
26655
|
+
list: (options = {}) => this.sessionDataRequest(
|
|
26656
|
+
"/mutations",
|
|
26657
|
+
options
|
|
26658
|
+
)
|
|
26659
|
+
};
|
|
26660
|
+
}
|
|
26061
26661
|
get artifacts() {
|
|
26062
26662
|
return {
|
|
26063
26663
|
list: (options = {}) => {
|
|
@@ -27557,6 +28157,7 @@ var Granular = class _Granular {
|
|
|
27557
28157
|
const serialized = {
|
|
27558
28158
|
effectKey: computeEffectKey2(effect),
|
|
27559
28159
|
name: effect.name,
|
|
28160
|
+
...effect.label ? { label: effect.label } : {},
|
|
27560
28161
|
description: effect.description,
|
|
27561
28162
|
inputSchema: effect.inputSchema,
|
|
27562
28163
|
stability: effect.stability || "stable",
|
|
@@ -27580,6 +28181,9 @@ var Granular = class _Granular {
|
|
|
27580
28181
|
if (effect.metamodels !== void 0) {
|
|
27581
28182
|
serialized.metamodels = effect.metamodels;
|
|
27582
28183
|
}
|
|
28184
|
+
if (effect.commit !== void 0) {
|
|
28185
|
+
serialized.commit = { kind: effect.commit.kind };
|
|
28186
|
+
}
|
|
27583
28187
|
return serialized;
|
|
27584
28188
|
}
|
|
27585
28189
|
async publishSandboxEffectCatalog(host) {
|
|
@@ -27800,16 +28404,60 @@ var Granular = class _Granular {
|
|
|
27800
28404
|
};
|
|
27801
28405
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
27802
28406
|
const request = params;
|
|
27803
|
-
|
|
28407
|
+
let commitReceipt;
|
|
28408
|
+
const result = await invokeRegisteredEffect(
|
|
27804
28409
|
this.getSandboxEffectMap(sandboxId),
|
|
27805
28410
|
request,
|
|
27806
28411
|
{
|
|
28412
|
+
commitAcknowledged: (receipt) => {
|
|
28413
|
+
commitReceipt = receipt;
|
|
28414
|
+
},
|
|
28415
|
+
commit: {
|
|
28416
|
+
persist: (commitRequest) => this.request(
|
|
28417
|
+
`/control/environments/${encodeURIComponent(
|
|
28418
|
+
commitRequest.environmentId
|
|
28419
|
+
)}/commits`,
|
|
28420
|
+
{
|
|
28421
|
+
method: "POST",
|
|
28422
|
+
body: JSON.stringify({
|
|
28423
|
+
kind: commitRequest.kind,
|
|
28424
|
+
invocationId: commitRequest.invocationId,
|
|
28425
|
+
idempotencyKey: commitRequest.idempotencyKey,
|
|
28426
|
+
effectKey: commitRequest.effectKey,
|
|
28427
|
+
projection: commitRequest.projection
|
|
28428
|
+
})
|
|
28429
|
+
}
|
|
28430
|
+
),
|
|
28431
|
+
mappingFailed: (failure) => this.request(
|
|
28432
|
+
`/control/environments/${encodeURIComponent(
|
|
28433
|
+
failure.environmentId
|
|
28434
|
+
)}/commit-invocations/${encodeURIComponent(
|
|
28435
|
+
failure.invocationId
|
|
28436
|
+
)}`,
|
|
28437
|
+
{
|
|
28438
|
+
method: "PATCH",
|
|
28439
|
+
body: JSON.stringify({
|
|
28440
|
+
status: "mapping_failed",
|
|
28441
|
+
error: {
|
|
28442
|
+
code: "commit_projection_mapping_failed",
|
|
28443
|
+
message: failure.message,
|
|
28444
|
+
retryable: false
|
|
28445
|
+
}
|
|
28446
|
+
})
|
|
28447
|
+
}
|
|
28448
|
+
)
|
|
28449
|
+
},
|
|
27807
28450
|
feedback: {
|
|
27808
28451
|
invocationId: request.callId,
|
|
27809
28452
|
publish: (method, publishParams) => wsClient.call(method, publishParams)
|
|
27810
28453
|
}
|
|
27811
28454
|
}
|
|
27812
28455
|
);
|
|
28456
|
+
return {
|
|
28457
|
+
__granularEffectInvocationResult: true,
|
|
28458
|
+
result,
|
|
28459
|
+
...commitReceipt ? { commit: commitReceipt } : {}
|
|
28460
|
+
};
|
|
27813
28461
|
});
|
|
27814
28462
|
wsClient.on("open", () => {
|
|
27815
28463
|
void this.synchronizeEffectHost(host).catch((error2) => {
|