docs: add next stage cicd dispatch matrix
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
const requiredDryRunMessage = "This first-phase commander contract only supports dry-run planning; live daemon/control operations are not implemented.";
|
||||
|
||||
const highRiskActions = [
|
||||
"code-queue-backend-restart",
|
||||
"code-queue-backend-rebuild",
|
||||
"code-queue-execution-plane-restart",
|
||||
"code-queue-task-interrupt",
|
||||
"code-queue-task-cancel",
|
||||
"prod-runtime-mutation",
|
||||
] as const;
|
||||
|
||||
type HighRiskAction = typeof highRiskActions[number];
|
||||
|
||||
function hasFlag(args: string[], flag: string): boolean {
|
||||
return args.includes(flag);
|
||||
}
|
||||
|
||||
function optionValue(args: string[], name: string): string | undefined {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) return undefined;
|
||||
const value = args[index + 1];
|
||||
if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function isHelpToken(value: string | undefined): boolean {
|
||||
return value === "help" || value === "--help" || value === "-h";
|
||||
}
|
||||
|
||||
function isHighRiskAction(value: string): value is HighRiskAction {
|
||||
return highRiskActions.some((action) => action === value);
|
||||
}
|
||||
|
||||
function commanderHelp(): Record<string, unknown> {
|
||||
return {
|
||||
command: "commander",
|
||||
output: "json",
|
||||
description: "First-phase source/contract stub for the host Codex commander control microservice; no daemon or live control action is implemented.",
|
||||
usage: [
|
||||
"bun scripts/cli.ts commander contract",
|
||||
"bun scripts/cli.ts commander plan --dry-run [--session-id id]",
|
||||
"bun scripts/cli.ts commander approval request --action <action> --dry-run [--reason text] [--task-id id]",
|
||||
],
|
||||
highRiskActions,
|
||||
reference: "docs/reference/host-codex-commander.md",
|
||||
};
|
||||
}
|
||||
|
||||
export function commanderContract(): Record<string, unknown> {
|
||||
return {
|
||||
ok: true,
|
||||
phase: "source-contract",
|
||||
serviceId: "host-codex-commander",
|
||||
currentImplementation: "cli-contract-stub-only",
|
||||
daemonImplemented: false,
|
||||
liveOperationsImplemented: false,
|
||||
purpose: "Keep a host Codex commander session observable and controllable through a future direct-managed microservice without replacing Code Queue runners.",
|
||||
ownershipBoundary: {
|
||||
hostCodexProcess: "Long-lived Codex process on the master server host.",
|
||||
controlMicroservice: "Future direct-managed bridge that records state, mediates PTY/stdio/SSH streams, injects prompts, and summarizes traces.",
|
||||
codeQueue: "Remains the task execution plane; the commander only supervises through existing safe CLI/API contracts.",
|
||||
claudeqq: "Approval and user-notification path for high-risk actions.",
|
||||
},
|
||||
requiredCapabilities: [
|
||||
"host-codex-process-discovery",
|
||||
"host-codex-start-plan",
|
||||
"ssh-bridge-contract",
|
||||
"pty-bridge-contract",
|
||||
"stdio-bridge-contract",
|
||||
"prompt-guidance-plan",
|
||||
"trace-summary-plan",
|
||||
"issue-20-board-read-write-entry",
|
||||
"issue-46-brief-read-write-entry",
|
||||
"claudeqq-high-risk-approval-entry",
|
||||
],
|
||||
apiContract: {
|
||||
health: "GET /health",
|
||||
contract: "GET /api/commander/contract",
|
||||
sessions: "GET /api/commander/sessions",
|
||||
sessionPlan: "POST /api/commander/sessions/:sessionId/plan-start",
|
||||
promptPlan: "POST /api/commander/sessions/:sessionId/prompt-plan",
|
||||
traceSummary: "GET /api/commander/trace-summary?taskId=<taskId>",
|
||||
issueWritePlan: "POST /api/commander/issues/:issueNumber/write-plan",
|
||||
approvalRequest: "POST /api/commander/approvals",
|
||||
},
|
||||
stateModel: {
|
||||
sessionStates: ["unknown", "discovered", "planned", "starting", "running", "attention_required", "stopping", "stopped", "degraded"],
|
||||
promptStates: ["draft", "planned", "queued_for_injection", "injected", "rejected", "failed"],
|
||||
approvalStates: ["draft", "requested", "approved", "rejected", "expired", "consumed"],
|
||||
storageRoot: ".state/commander/",
|
||||
redactionPolicy: "Never persist or print token, secret, password, key, cookie, or authorization values in cleartext.",
|
||||
},
|
||||
safetyBoundary: safetyBoundary(),
|
||||
};
|
||||
}
|
||||
|
||||
function safetyBoundary(): Record<string, unknown> {
|
||||
return {
|
||||
phaseOneMutationAllowed: false,
|
||||
forbiddenWithoutExplicitUserApproval: highRiskActions,
|
||||
alwaysForbidden: [
|
||||
"print-token-values",
|
||||
"read-token-files-for-display",
|
||||
"direct-database-state-patch",
|
||||
"bypass-code-queue-backend-confirmation-policy",
|
||||
"replace-code-queue-runner",
|
||||
"deploy-or-restart-production-runtime-from-this-contract-stub",
|
||||
],
|
||||
confirmationPolicy: "High-risk actions must draft a ClaudeQQ request, wait for explicit user approval, bind approval to one exact action, and record the decision before any future live executor may proceed.",
|
||||
};
|
||||
}
|
||||
|
||||
function processDiscoveryPlan(sessionId: string): Record<string, unknown> {
|
||||
return {
|
||||
sessionId,
|
||||
mutation: false,
|
||||
signals: [
|
||||
".state/commander/sessions/<sessionId>.json",
|
||||
"host process table filtered by executable and cwd markers",
|
||||
"PTY/stdio bridge heartbeat file",
|
||||
"last prompt/trace event sequence",
|
||||
],
|
||||
startPlan: {
|
||||
enabled: false,
|
||||
commandShape: "codex <reviewed host profile> --cwd /workspace/unidesk",
|
||||
supervisor: "future direct-managed microservice on the master server host",
|
||||
reason: requiredDryRunMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function bridgePlan(): Record<string, unknown> {
|
||||
return {
|
||||
mutation: false,
|
||||
adapters: {
|
||||
ssh: {
|
||||
purpose: "Reach provider hosts through existing UniDesk Host SSH / WSL SSH maintenance bridge.",
|
||||
allowedUse: ["readonly diagnostics", "bounded reviewed maintenance commands", "future approved recovery commands"],
|
||||
},
|
||||
pty: {
|
||||
purpose: "Keep the host Codex interactive session alive and observable.",
|
||||
allowedUse: ["windowed stdout/stderr capture", "prompt injection after policy checks", "heartbeat"],
|
||||
},
|
||||
stdio: {
|
||||
purpose: "Bridge non-interactive Codex or helper subprocesses with bounded transcript capture.",
|
||||
allowedUse: ["contracted command execution", "trace collection", "structured result capture"],
|
||||
},
|
||||
},
|
||||
guardrails: [
|
||||
"bounded output by default",
|
||||
"redact secret-like values before state persistence",
|
||||
"persist event sequence before acknowledging prompt injection",
|
||||
"never open an interactive shell from dry-run commands",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function issueEntryPlan(): Record<string, unknown> {
|
||||
return {
|
||||
mutation: false,
|
||||
issues: [
|
||||
{
|
||||
number: 20,
|
||||
role: "Code Queue total board",
|
||||
readEntrypoints: ["bun scripts/cli.ts gh issue board-audit --board-issue 20 --dry-run", "bun scripts/cli.ts gh issue board-row list --board-issue 20"],
|
||||
writeEntrypoints: ["bun scripts/cli.ts gh issue board-row update <issueNumber> --board-issue 20 --field <field> --value <text> --expect-body-sha <sha>"],
|
||||
},
|
||||
{
|
||||
number: 46,
|
||||
role: "daily commander brief",
|
||||
readEntrypoints: ["bun scripts/cli.ts gh issue read 46 --json body,title,state,updatedAt"],
|
||||
writeEntrypoints: ["bun scripts/cli.ts gh issue update 46 --body-profile commander-brief --body-file <file> --expect-updated-at <ts>"],
|
||||
},
|
||||
],
|
||||
writeGuards: ["body-file-only", "dry-run-first", "body-sha-or-updated-at-required", "no-token-output"],
|
||||
};
|
||||
}
|
||||
|
||||
function traceSummaryPlan(): Record<string, unknown> {
|
||||
return {
|
||||
mutation: false,
|
||||
sources: [
|
||||
"bounded host Codex PTY/stdio event window",
|
||||
"Code Queue task trace via codex task --trace",
|
||||
"Code Queue output pages via codex output",
|
||||
"commander approval and prompt event JSONL",
|
||||
],
|
||||
summaryShape: {
|
||||
taskId: "string|null",
|
||||
sessionId: "string",
|
||||
lastSeq: "number",
|
||||
status: "running|attention_required|blocked|terminal|unknown",
|
||||
keyEvents: "bounded array",
|
||||
openQuestions: "bounded array",
|
||||
recommendedNextActions: "bounded array",
|
||||
redactionsApplied: "number",
|
||||
},
|
||||
outputPolicy: "Default summaries must omit raw transcript text unless explicitly requested by a future reviewed endpoint.",
|
||||
};
|
||||
}
|
||||
|
||||
function promptGuidancePlan(): Record<string, unknown> {
|
||||
return {
|
||||
mutation: false,
|
||||
stages: [
|
||||
"classify intent and risk",
|
||||
"attach current #20/#46/task context summary",
|
||||
"apply forbidden-action guard",
|
||||
"redact secret-like strings",
|
||||
"persist prompt plan",
|
||||
"inject only through future live executor after policy checks",
|
||||
],
|
||||
highRiskEscalation: "code-queue restart/rebuild, task interrupt/cancel, production mutation, token access, and destructive Git operations require ClaudeQQ approval first.",
|
||||
};
|
||||
}
|
||||
|
||||
function commanderPlan(args: string[]): Record<string, unknown> {
|
||||
if (!hasFlag(args, "--dry-run")) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "dry-run-required",
|
||||
message: requiredDryRunMessage,
|
||||
command: "bun scripts/cli.ts commander plan --dry-run",
|
||||
};
|
||||
}
|
||||
const sessionId = optionValue(args, "--session-id") ?? "primary";
|
||||
return {
|
||||
ok: true,
|
||||
phase: "source-contract",
|
||||
mode: "dry-run",
|
||||
mutation: false,
|
||||
serviceId: "host-codex-commander",
|
||||
processDiscovery: processDiscoveryPlan(sessionId),
|
||||
bridge: bridgePlan(),
|
||||
promptGuidance: promptGuidancePlan(),
|
||||
traceSummary: traceSummaryPlan(),
|
||||
issueEntries: issueEntryPlan(),
|
||||
claudeqqApproval: {
|
||||
mutation: false,
|
||||
commandShape: "bun scripts/cli.ts commander approval request --action <action> --dry-run",
|
||||
highRiskActions,
|
||||
},
|
||||
safetyBoundary: safetyBoundary(),
|
||||
};
|
||||
}
|
||||
|
||||
function commanderApprovalRequest(args: string[]): Record<string, unknown> {
|
||||
if (!hasFlag(args, "--dry-run")) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "dry-run-required",
|
||||
message: requiredDryRunMessage,
|
||||
command: "bun scripts/cli.ts commander approval request --action <action> --dry-run",
|
||||
};
|
||||
}
|
||||
const action = optionValue(args, "--action");
|
||||
if (action === undefined) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "validation-failed",
|
||||
message: "--action is required",
|
||||
highRiskActions,
|
||||
};
|
||||
}
|
||||
if (!isHighRiskAction(action)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "validation-failed",
|
||||
message: `unsupported high-risk action: ${action}`,
|
||||
highRiskActions,
|
||||
};
|
||||
}
|
||||
const reason = optionValue(args, "--reason") ?? "operator-supplied reason required before live execution";
|
||||
const taskId = optionValue(args, "--task-id") ?? null;
|
||||
return {
|
||||
ok: true,
|
||||
phase: "source-contract",
|
||||
mode: "dry-run",
|
||||
mutation: false,
|
||||
action,
|
||||
taskId,
|
||||
reason,
|
||||
requiresExplicitUserApproval: true,
|
||||
claudeqq: {
|
||||
mutation: false,
|
||||
endpointShape: "POST /api/microservices/claudeqq/proxy/api/push/text",
|
||||
target: "configured primary user private chat",
|
||||
messageTemplate: `Approval required for ${action}. Reason: ${reason}. Reply with explicit approval id before execution.`,
|
||||
sendImplemented: false,
|
||||
},
|
||||
approvalRecordShape: {
|
||||
id: "commander-approval-<stable-id>",
|
||||
action,
|
||||
taskId,
|
||||
reason,
|
||||
status: "draft",
|
||||
approvedBy: null,
|
||||
approvedAt: null,
|
||||
expiresAt: "future reviewed timeout",
|
||||
},
|
||||
blockedUntilApproved: [action],
|
||||
};
|
||||
}
|
||||
|
||||
export function runCommanderCommand(args: string[]): Record<string, unknown> {
|
||||
const [sub, second] = args;
|
||||
if (sub === undefined || isHelpToken(sub)) return commanderHelp();
|
||||
if (sub === "contract") return commanderContract();
|
||||
if (sub === "plan") return commanderPlan(args.slice(1));
|
||||
if (sub === "approval" && second === "request") return commanderApprovalRequest(args.slice(2));
|
||||
return {
|
||||
ok: false,
|
||||
error: "unsupported-command",
|
||||
message: `Unsupported commander command: ${args.join(" ")}`,
|
||||
help: commanderHelp(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user