feat(orchestrator): harden zero-impact shadow rollout

Gate and bound Portal shadow observations while preserving Native execution. Add fail-closed service boundaries, terminal retention controls, Canary readiness telemetry, ops visibility, and isolated regression coverage.
This commit is contained in:
john
2026-07-25 07:28:37 +08:00
parent 08a48e4849
commit 6df82818c5
33 changed files with 1569 additions and 108 deletions
@@ -79,6 +79,11 @@ function gatewayError(code, message, status = 409) {
return error;
}
function normalizeWorkflowRunId(value) {
const runId = String(value ?? '').trim();
return /^[a-zA-Z0-9_-]{1,128}$/.test(runId) ? runId : null;
}
export function buildExecutorJobEvent({
eventId = crypto.randomUUID(),
jobId,
@@ -366,6 +371,36 @@ export function createInMemoryExecutorJobStore() {
const value = jobId ? byId.get(jobId) : null;
return value ? clone(value) : null;
},
async deleteById(jobId) {
const normalizedJobId = String(jobId ?? '').trim();
const existing = byId.get(normalizedJobId);
if (!existing) return false;
byId.delete(normalizedJobId);
byIdempotencyKey.delete(existing.idempotencyKey);
eventsByJobId.delete(normalizedJobId);
return true;
},
async listTerminalBefore({ before, limit = 100 } = {}) {
const cutoff = Number(before);
if (!Number.isFinite(cutoff)) return [];
return [...byId.values()]
.filter((record) => (
TERMINAL_JOB_STATUSES.has(record.status)
&& record.workflowRunId
&& Number(record.completedAt ?? 0) > 0
&& Number(record.completedAt) <= cutoff
))
.sort((left, right) => (
Number(left.completedAt) - Number(right.completedAt)
|| String(left.jobId).localeCompare(String(right.jobId))
))
.slice(0, clampInteger(limit, 100, 1, 500))
.map((record) => ({
jobId: record.jobId,
workflowRunId: record.workflowRunId,
completedAt: Number(record.completedAt),
}));
},
async createIfAbsent(record, { initialEvent = null } = {}) {
const existingId = byIdempotencyKey.get(record.idempotencyKey);
if (existingId) return { created: false, record: clone(byId.get(existingId)) };
@@ -553,6 +588,8 @@ function validateJobStore(store) {
'getById',
'getByIdempotencyKey',
'createIfAbsent',
'deleteById',
'listTerminalBefore',
'update',
'listEvents',
]) {
@@ -674,6 +711,7 @@ export function createExecutorGateway({
fallbackRegistered: decision.fallbackRegistered,
fallbackAvailable: decision.fallbackAvailable,
decision,
workflowRunId: normalizeWorkflowRunId(request.metadata?.workflowRunId),
request: dispatchAllowed ? request : null,
createdAt: now,
updatedAt: now,
@@ -741,10 +779,28 @@ export function createExecutorGateway({
return store.update(updated, { event: eventForCancellation(updated) });
}
async function deleteJob(jobId) {
const current = await store.getById(jobId);
if (!current) return false;
if (!TERMINAL_JOB_STATUSES.has(current.status)) {
throw gatewayError(
'EXECUTOR_JOB_NOT_TERMINAL',
`Executor job ${current.jobId} must be terminal before deletion`,
);
}
return store.deleteById(current.jobId);
}
async function listRetentionCandidates(options = {}) {
return store.listTerminalBefore(options);
}
return {
preview,
createJob,
cancel,
deleteJob,
listRetentionCandidates,
async getJob(jobId, { includeRequest = false } = {}) {
const record = await store.getById(jobId);
return includeRequest ? record : projectExecutorJobForRead(record);
@@ -793,6 +849,7 @@ export const executorGatewayInternals = {
PHASE3_EXECUTOR_DESCRIPTORS,
TERMINAL_JOB_STATUSES,
fingerprint,
normalizeWorkflowRunId,
projectExecutorJobForRead,
stableValue,
};