治理(08-11-internal-oa-finance-backoffice-positioning-and-navigation-reduction): 固化财务后台定位与写入定性
这个提交包含在:
@@ -0,0 +1,166 @@
|
||||
export const FEATURE_FLAG_APPLY_DECISION = Object.freeze({
|
||||
CONTINUE_POST: "CONTINUE_POST",
|
||||
STOP_NOT_APPLIED: "STOP_NOT_APPLIED",
|
||||
STOP_UNKNOWN: "STOP_UNKNOWN",
|
||||
STOP_FOR_MANUAL_DECISION: "STOP_FOR_MANUAL_DECISION",
|
||||
});
|
||||
|
||||
function normalizeTargets(targets) {
|
||||
if (!Array.isArray(targets) || targets.length === 0) {
|
||||
throw new TypeError("targets must be a non-empty array");
|
||||
}
|
||||
|
||||
const keys = targets.map((target) => String(target?.flagKey ?? "").trim());
|
||||
if (keys.some((key) => !key) || new Set(keys).size !== keys.length) {
|
||||
throw new TypeError("targets must contain unique non-empty flagKey values");
|
||||
}
|
||||
|
||||
return targets.map((target, index) => {
|
||||
const preVersion = Number(target.preVersion);
|
||||
if (!Number.isInteger(preVersion) || preVersion < 1) {
|
||||
throw new TypeError(`invalid preVersion for ${keys[index]}`);
|
||||
}
|
||||
|
||||
const preOverrideMode = String(target.preOverrideMode ?? "").trim();
|
||||
const postOverrideMode = String(target.postOverrideMode ?? "").trim();
|
||||
if (!preOverrideMode || !postOverrideMode) {
|
||||
throw new TypeError(`missing override mode for ${keys[index]}`);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
flagKey: keys[index],
|
||||
preVersion,
|
||||
preOverrideMode,
|
||||
postVersion: preVersion + 1,
|
||||
postOverrideMode,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeReadback(readback) {
|
||||
if (!Array.isArray(readback)) {
|
||||
throw new TypeError("readback must be an array");
|
||||
}
|
||||
|
||||
return new Map(
|
||||
readback.map((item) => [
|
||||
String(item?.flagKey ?? "").trim(),
|
||||
{
|
||||
version: Number(item?.version),
|
||||
overrideMode: String(item?.overrideMode ?? "").trim(),
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function matchesState(actual, version, overrideMode) {
|
||||
return actual?.version === version && actual?.overrideMode === overrideMode;
|
||||
}
|
||||
|
||||
export function classifyFeatureFlagApplyReadback({ targets, readback }) {
|
||||
const normalizedTargets = normalizeTargets(targets);
|
||||
const actualByKey = normalizeReadback(readback);
|
||||
const observations = normalizedTargets.map((target) => {
|
||||
const actual = actualByKey.get(target.flagKey);
|
||||
if (matchesState(actual, target.postVersion, target.postOverrideMode)) {
|
||||
return { flagKey: target.flagKey, state: "POST" };
|
||||
}
|
||||
if (matchesState(actual, target.preVersion, target.preOverrideMode)) {
|
||||
return { flagKey: target.flagKey, state: "PRE" };
|
||||
}
|
||||
return { flagKey: target.flagKey, state: "OTHER" };
|
||||
});
|
||||
|
||||
if (observations.every((item) => item.state === "POST")) {
|
||||
return Object.freeze({
|
||||
decision: FEATURE_FLAG_APPLY_DECISION.CONTINUE_POST,
|
||||
classification: "APPLIED",
|
||||
observations: Object.freeze(observations),
|
||||
});
|
||||
}
|
||||
|
||||
if (observations.every((item) => item.state === "PRE")) {
|
||||
return Object.freeze({
|
||||
decision: FEATURE_FLAG_APPLY_DECISION.STOP_NOT_APPLIED,
|
||||
classification: "NOT_APPLIED",
|
||||
observations: Object.freeze(observations),
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
decision: FEATURE_FLAG_APPLY_DECISION.STOP_FOR_MANUAL_DECISION,
|
||||
classification: "PARTIAL_OR_UNEXPECTED",
|
||||
observations: Object.freeze(observations),
|
||||
});
|
||||
}
|
||||
|
||||
export async function resolveFeatureFlagApplyUncertainty({ targets, readCurrent }) {
|
||||
if (typeof readCurrent !== "function") {
|
||||
throw new TypeError("readCurrent must be a function");
|
||||
}
|
||||
|
||||
try {
|
||||
const readback = await readCurrent();
|
||||
return classifyFeatureFlagApplyReadback({ targets, readback });
|
||||
} catch (error) {
|
||||
return Object.freeze({
|
||||
decision: FEATURE_FLAG_APPLY_DECISION.STOP_UNKNOWN,
|
||||
classification: "READBACK_FAILED",
|
||||
errorCode: error instanceof Error && error.message ? error.message : "readback_failed",
|
||||
observations: Object.freeze([]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function createSingleAttemptMutation(sendMutation) {
|
||||
if (typeof sendMutation !== "function") {
|
||||
throw new TypeError("sendMutation must be a function");
|
||||
}
|
||||
|
||||
let attemptCount = 0;
|
||||
return Object.freeze({
|
||||
get attemptCount() {
|
||||
return attemptCount;
|
||||
},
|
||||
async execute(payload) {
|
||||
if (attemptCount !== 0) {
|
||||
throw new Error("mutation_attempt_already_consumed");
|
||||
}
|
||||
attemptCount += 1;
|
||||
return sendMutation(payload);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function executeFeatureFlagApplyAttempt({
|
||||
targets,
|
||||
payload,
|
||||
sendMutation,
|
||||
readCurrent,
|
||||
observedRequestCount = null,
|
||||
}) {
|
||||
const mutation = createSingleAttemptMutation(sendMutation);
|
||||
let response = null;
|
||||
let responseError = null;
|
||||
|
||||
try {
|
||||
response = await mutation.execute(payload);
|
||||
} catch (error) {
|
||||
responseError = error instanceof Error && error.message ? error.message : "mutation_result_unknown";
|
||||
}
|
||||
|
||||
const resolution = await resolveFeatureFlagApplyUncertainty({ targets, readCurrent });
|
||||
const normalizedObservedCount = Number.isInteger(observedRequestCount) && observedRequestCount >= 0
|
||||
? observedRequestCount
|
||||
: null;
|
||||
|
||||
return Object.freeze({
|
||||
...resolution,
|
||||
sendAttemptCount: mutation.attemptCount,
|
||||
responseKnown: responseError === null,
|
||||
response,
|
||||
responseErrorCode: responseError,
|
||||
observedRequestCount: normalizedObservedCount,
|
||||
observerMismatch: normalizedObservedCount !== null && normalizedObservedCount !== mutation.attemptCount,
|
||||
});
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户