@oscharko-dev/keiko-server 0.2.8 → 0.2.9
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/.tsbuildinfo +1 -1
- package/dist/chat-handlers.d.ts +18 -2
- package/dist/chat-handlers.d.ts.map +1 -1
- package/dist/chat-handlers.js +185 -3
- package/dist/command-runner-errors.d.ts +17 -0
- package/dist/command-runner-errors.d.ts.map +1 -0
- package/dist/command-runner-errors.js +37 -0
- package/dist/command-runner-evidence.d.ts +23 -0
- package/dist/command-runner-evidence.d.ts.map +1 -0
- package/dist/command-runner-evidence.js +69 -0
- package/dist/command-runner-routes.d.ts +7 -0
- package/dist/command-runner-routes.d.ts.map +1 -0
- package/dist/command-runner-routes.js +175 -0
- package/dist/command-runner.d.ts +29 -0
- package/dist/command-runner.d.ts.map +1 -0
- package/dist/command-runner.js +348 -0
- package/dist/conversation-prompt.d.ts +2 -2
- package/dist/conversation-prompt.d.ts.map +1 -1
- package/dist/conversation-prompt.js +17 -1
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +3 -0
- package/dist/deps.d.ts +27 -1
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +288 -13
- package/dist/discussion-prompt.d.ts +4 -0
- package/dist/discussion-prompt.d.ts.map +1 -0
- package/dist/discussion-prompt.js +19 -0
- package/dist/editor/agentActionAudit.d.ts +18 -0
- package/dist/editor/agentActionAudit.d.ts.map +1 -0
- package/dist/editor/agentActionAudit.js +80 -0
- package/dist/editor/agentRoutes.d.ts +1 -0
- package/dist/editor/agentRoutes.d.ts.map +1 -1
- package/dist/editor/agentRoutes.js +292 -55
- package/dist/editor/agentSessionRegistry.d.ts +35 -0
- package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
- package/dist/editor/agentSessionRegistry.js +243 -0
- package/dist/editor/completionRoutes.d.ts.map +1 -1
- package/dist/editor/completionRoutes.js +5 -10
- package/dist/editor/languageRoutes.d.ts +12 -1
- package/dist/editor/languageRoutes.d.ts.map +1 -1
- package/dist/editor/languageRoutes.js +71 -8
- package/dist/editor/languageService.d.ts +3 -2
- package/dist/editor/languageService.d.ts.map +1 -1
- package/dist/editor/languageService.js +41 -3
- package/dist/editor/languageServiceHost.d.ts.map +1 -1
- package/dist/editor/languageServiceHost.js +2 -2
- package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
- package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageOperation.js +436 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageProviders.js +161 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
- package/dist/editor/lsp/lspFrameCodec.js +164 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
- package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
- package/dist/editor/lsp/lspLanguageProvider.js +29 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
- package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
- package/dist/editor/lsp/lspNodeAdapter.js +230 -0
- package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
- package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
- package/dist/editor/lsp/lspProcessManager.js +255 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
- package/dist/editor/lsp/lspRestartThrottle.js +24 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
- package/dist/editor/lsp/lspStatusRoute.js +22 -0
- package/dist/editor/lsp/lspTransport.d.ts +19 -0
- package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
- package/dist/editor/lsp/lspTransport.js +55 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
- package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
- package/dist/files.d.ts +45 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +631 -7
- package/dist/gateway-readiness.js +3 -3
- package/dist/gateway-setup.d.ts +2 -0
- package/dist/gateway-setup.d.ts.map +1 -1
- package/dist/gateway-setup.js +275 -11
- package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
- package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetProjection.js +206 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetRoutes.js +293 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
- package/dist/gitDelivery/commitRoutes.d.ts +23 -0
- package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/commitRoutes.js +204 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/evidenceRoutes.js +101 -0
- package/dist/gitDelivery/execution.d.ts +38 -0
- package/dist/gitDelivery/execution.d.ts.map +1 -0
- package/dist/gitDelivery/execution.js +117 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/localMutationRoutes.js +165 -0
- package/dist/gitDelivery/mergeExecution.d.ts +63 -0
- package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
- package/dist/gitDelivery/mergeExecution.js +168 -0
- package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
- package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/mergeRoutes.js +218 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
- package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
- package/dist/gitDelivery/prExecution.d.ts +54 -0
- package/dist/gitDelivery/prExecution.d.ts.map +1 -0
- package/dist/gitDelivery/prExecution.js +192 -0
- package/dist/gitDelivery/prRoutes.d.ts +12 -0
- package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/prRoutes.js +256 -0
- package/dist/gitDelivery/pushExecution.d.ts +43 -0
- package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
- package/dist/gitDelivery/pushExecution.js +124 -0
- package/dist/gitDelivery/pushRoutes.d.ts +12 -0
- package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/pushRoutes.js +200 -0
- package/dist/gitDelivery/requestGuards.d.ts +15 -0
- package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
- package/dist/gitDelivery/requestGuards.js +97 -0
- package/dist/gitDelivery/syncEvidence.d.ts +37 -0
- package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
- package/dist/gitDelivery/syncEvidence.js +85 -0
- package/dist/gitDelivery/syncExecution.d.ts +30 -0
- package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
- package/dist/gitDelivery/syncExecution.js +266 -0
- package/dist/gitDelivery/syncRoutes.d.ts +13 -0
- package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/syncRoutes.js +200 -0
- package/dist/gitPorcelainStatus.d.ts +15 -0
- package/dist/gitPorcelainStatus.d.ts.map +1 -0
- package/dist/gitPorcelainStatus.js +104 -0
- package/dist/gitRepositoryReads.d.ts +10 -0
- package/dist/gitRepositoryReads.d.ts.map +1 -0
- package/dist/gitRepositoryReads.js +314 -0
- package/dist/gitRepositoryRoutes.d.ts +7 -0
- package/dist/gitRepositoryRoutes.d.ts.map +1 -0
- package/dist/gitRepositoryRoutes.js +221 -0
- package/dist/gitRoutes.d.ts +66 -0
- package/dist/gitRoutes.d.ts.map +1 -0
- package/dist/gitRoutes.js +543 -0
- package/dist/governed-workflow.d.ts +2 -0
- package/dist/governed-workflow.d.ts.map +1 -1
- package/dist/governed-workflow.js +4 -0
- package/dist/grounded-qa.d.ts +11 -0
- package/dist/grounded-qa.d.ts.map +1 -1
- package/dist/grounded-qa.js +13 -4
- package/dist/headers.d.ts +4 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +11 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
- package/dist/read-handlers.d.ts +5 -0
- package/dist/read-handlers.d.ts.map +1 -1
- package/dist/read-handlers.js +57 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +259 -6
- package/dist/run-engine.d.ts.map +1 -1
- package/dist/run-engine.js +3 -0
- package/dist/run-handlers.d.ts.map +1 -1
- package/dist/run-handlers.js +74 -4
- package/dist/run-request.d.ts +11 -0
- package/dist/run-request.d.ts.map +1 -1
- package/dist/run-request.js +158 -10
- package/dist/runtime/capabilityDetector.d.ts +38 -0
- package/dist/runtime/capabilityDetector.d.ts.map +1 -0
- package/dist/runtime/capabilityDetector.js +443 -0
- package/dist/runtime/capabilityRoutes.d.ts +9 -0
- package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
- package/dist/runtime/capabilityRoutes.js +45 -0
- package/dist/runtime/containerEngineDetector.d.ts +17 -0
- package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
- package/dist/runtime/containerEngineDetector.js +222 -0
- package/dist/runtime/containerRoutes.d.ts +8 -0
- package/dist/runtime/containerRoutes.d.ts.map +1 -0
- package/dist/runtime/containerRoutes.js +207 -0
- package/dist/runtime/containerRunner-errors.d.ts +18 -0
- package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
- package/dist/runtime/containerRunner-errors.js +42 -0
- package/dist/runtime/containerRunner-evidence.d.ts +24 -0
- package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
- package/dist/runtime/containerRunner-evidence.js +74 -0
- package/dist/runtime/containerRunner.d.ts +37 -0
- package/dist/runtime/containerRunner.d.ts.map +1 -0
- package/dist/runtime/containerRunner.js +443 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -4
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +62 -1
- package/dist/task-workspace/active-store.d.ts +21 -0
- package/dist/task-workspace/active-store.d.ts.map +1 -0
- package/dist/task-workspace/active-store.js +55 -0
- package/dist/task-workspace/authorization.d.ts +7 -0
- package/dist/task-workspace/authorization.d.ts.map +1 -0
- package/dist/task-workspace/authorization.js +54 -0
- package/dist/task-workspace/binding.d.ts +3 -0
- package/dist/task-workspace/binding.d.ts.map +1 -0
- package/dist/task-workspace/binding.js +22 -0
- package/dist/task-workspace/cleanup.d.ts +4 -0
- package/dist/task-workspace/cleanup.d.ts.map +1 -0
- package/dist/task-workspace/cleanup.js +428 -0
- package/dist/task-workspace/errors.d.ts +14 -0
- package/dist/task-workspace/errors.d.ts.map +1 -0
- package/dist/task-workspace/errors.js +81 -0
- package/dist/task-workspace/evidence.d.ts +32 -0
- package/dist/task-workspace/evidence.d.ts.map +1 -0
- package/dist/task-workspace/evidence.js +52 -0
- package/dist/task-workspace/field-safety.d.ts +3 -0
- package/dist/task-workspace/field-safety.d.ts.map +1 -0
- package/dist/task-workspace/field-safety.js +42 -0
- package/dist/task-workspace/health.d.ts +4 -0
- package/dist/task-workspace/health.d.ts.map +1 -0
- package/dist/task-workspace/health.js +163 -0
- package/dist/task-workspace/lifecycle.d.ts +3 -0
- package/dist/task-workspace/lifecycle.d.ts.map +1 -0
- package/dist/task-workspace/lifecycle.js +248 -0
- package/dist/task-workspace/locks.d.ts +13 -0
- package/dist/task-workspace/locks.d.ts.map +1 -0
- package/dist/task-workspace/locks.js +44 -0
- package/dist/task-workspace/managed-root.d.ts +7 -0
- package/dist/task-workspace/managed-root.d.ts.map +1 -0
- package/dist/task-workspace/managed-root.js +98 -0
- package/dist/task-workspace/mutex.d.ts +8 -0
- package/dist/task-workspace/mutex.d.ts.map +1 -0
- package/dist/task-workspace/mutex.js +82 -0
- package/dist/task-workspace/naming.d.ts +15 -0
- package/dist/task-workspace/naming.d.ts.map +1 -0
- package/dist/task-workspace/naming.js +0 -0
- package/dist/task-workspace/provisioning.d.ts +3 -0
- package/dist/task-workspace/provisioning.d.ts.map +1 -0
- package/dist/task-workspace/provisioning.js +528 -0
- package/dist/task-workspace/reconciliation.d.ts +15 -0
- package/dist/task-workspace/reconciliation.d.ts.map +1 -0
- package/dist/task-workspace/reconciliation.js +274 -0
- package/dist/task-workspace/repair.d.ts +3 -0
- package/dist/task-workspace/repair.d.ts.map +1 -0
- package/dist/task-workspace/repair.js +286 -0
- package/dist/task-workspace/routes.d.ts +19 -0
- package/dist/task-workspace/routes.d.ts.map +1 -0
- package/dist/task-workspace/routes.js +481 -0
- package/dist/task-workspace/store.d.ts +12 -0
- package/dist/task-workspace/store.d.ts.map +1 -0
- package/dist/task-workspace/store.js +128 -0
- package/dist/task-workspace/types.d.ts +170 -0
- package/dist/task-workspace/types.d.ts.map +1 -0
- package/dist/task-workspace/types.js +5 -0
- package/dist/voice-action-governance.d.ts +23 -0
- package/dist/voice-action-governance.d.ts.map +1 -0
- package/dist/voice-action-governance.js +126 -0
- package/dist/voice-handlers.d.ts +6 -0
- package/dist/voice-handlers.d.ts.map +1 -0
- package/dist/voice-handlers.js +570 -0
- package/dist/voice-realtime-grounded-tool.d.ts +31 -0
- package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
- package/dist/voice-realtime-grounded-tool.js +322 -0
- package/dist/voice-realtime.d.ts +69 -0
- package/dist/voice-realtime.d.ts.map +1 -0
- package/dist/voice-realtime.js +787 -0
- package/dist/workspace-state-handlers.d.ts +5 -0
- package/dist/workspace-state-handlers.d.ts.map +1 -0
- package/dist/workspace-state-handlers.js +106 -0
- package/package.json +20 -19
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Governed merge execution core for the #478 merge routes (Epic #470, ADR-0065).
|
|
2
|
+
//
|
|
3
|
+
// The merge preview + execute routes share ONE path: resolve and authorize the project workspace, read
|
|
4
|
+
// the provider's content-free merge-readiness facts, and (for execute) drive the #478 merge gateway
|
|
5
|
+
// `runGitMerge` (the SEPARATE merge-orchestration authority — preflight + policy + final-approval gates +
|
|
6
|
+
// the readiness gate, executing through a narrow `gh api` adapter with a dedicated merge-only allowlist)
|
|
7
|
+
// and append a content-free evidence record through the #474 ledger for EVERY terminal outcome (allowed
|
|
8
|
+
// and blocked alike — AC4). No second orchestrator, no generic shell, no widening of the local mutation,
|
|
9
|
+
// publish, or PR allowlists. The Node `gh api` effect is injected via seams so route tests run
|
|
10
|
+
// deterministically against a fake.
|
|
11
|
+
import { deriveEligibleMergeStrategies, evaluateGitPolicy, GIT_DELIVERY_POLICY_SCHEMA_VERSION, GIT_DELIVERY_RISK_CLASS_SEVERITY, gitMergeBlockerActionHintFor, gitMergeReadinessFor, gitMergeRecommendationFor, } from "@oscharko-dev/keiko-contracts";
|
|
12
|
+
import { evaluateGitMergeEffectivePolicy, runGitMerge, } from "@oscharko-dev/keiko-tools";
|
|
13
|
+
import { createNodeGitMergeAdapter } from "@oscharko-dev/keiko-tools/internal/git-mutation";
|
|
14
|
+
import { defaultGitDeliveryActionId, gitDeliveryMutationResponse, persistGitDeliveryEvidence, readWorktreeSnapshotFor, } from "./execution.js";
|
|
15
|
+
// Default trusted merge policy: merge is APPROVAL-GATED — the explicit final, high-risk confirmation a
|
|
16
|
+
// merge requires (AC1). No approval token ⇒ approval-required; every other action kind is fail-closed via
|
|
17
|
+
// the blocked defaultRule. Base-branch and strategy enforcement live in the readiness layer (where merge
|
|
18
|
+
// prerequisites belong), not in this authorization pack. Applies only when governed git delivery is
|
|
19
|
+
// ENABLED and no stricter pack is configured; still EVALUATED every time. A deployment may override with a
|
|
20
|
+
// pack naming specific requiredApprovers.
|
|
21
|
+
export const KEIKO_DEFAULT_MERGE_POLICY_PACK = {
|
|
22
|
+
schemaVersion: GIT_DELIVERY_POLICY_SCHEMA_VERSION,
|
|
23
|
+
repoId: "keiko-merge-default",
|
|
24
|
+
rules: [{ actionKind: "merge", decision: "approval-gated", requiredApprovers: [] }],
|
|
25
|
+
defaultRule: { decision: "blocked" },
|
|
26
|
+
};
|
|
27
|
+
function mergeAdapterFor(workspace, seams, now) {
|
|
28
|
+
if (seams.mergeAdapterFactory !== undefined)
|
|
29
|
+
return seams.mergeAdapterFactory(workspace);
|
|
30
|
+
return createNodeGitMergeAdapter({ workspace, processEnv: process.env, now });
|
|
31
|
+
}
|
|
32
|
+
// Reads the provider's content-free merge-readiness facts. Never throws: a thrown read becomes a
|
|
33
|
+
// provider-error readiness so callers fail closed.
|
|
34
|
+
export async function readMergeProviderReadiness(command, workspace, seams, now) {
|
|
35
|
+
const adapter = mergeAdapterFor(workspace, seams, now);
|
|
36
|
+
try {
|
|
37
|
+
return await adapter.readMergeReadiness({
|
|
38
|
+
ownerAndRepo: command.ownerAndRepo,
|
|
39
|
+
prExternalId: command.prExternalId,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { providerCapableStrategies: [], providerError: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs ONE governed merge end-to-end: live snapshot → merge gateway (preflight + policy + approval +
|
|
48
|
+
* readiness gate + execute) → evidence. Returns the gateway lifecycle result; the caller projects it into
|
|
49
|
+
* a content-free HTTP body. Evidence is appended best-effort BEFORE the caller responds, for allowed AND
|
|
50
|
+
* blocked alike.
|
|
51
|
+
*/
|
|
52
|
+
export async function executeGovernedMerge(command, approval, workspace, deps, seams) {
|
|
53
|
+
const now = seams.now ?? Date.now;
|
|
54
|
+
const snapshot = await readWorktreeSnapshotFor(workspace, seams, now);
|
|
55
|
+
const adapter = mergeAdapterFor(workspace, seams, now);
|
|
56
|
+
const packs = seams.policyPacks ?? { repoPack: KEIKO_DEFAULT_MERGE_POLICY_PACK };
|
|
57
|
+
const newActionId = seams.newActionId ?? (() => defaultGitDeliveryActionId(command, now()));
|
|
58
|
+
const result = await runGitMerge({ command, approval }, {
|
|
59
|
+
adapter,
|
|
60
|
+
snapshot,
|
|
61
|
+
...(packs.orgPack !== undefined ? { orgPolicyPack: packs.orgPack } : {}),
|
|
62
|
+
...(packs.repoPack !== undefined ? { repoPolicyPack: packs.repoPack } : {}),
|
|
63
|
+
...(seams.strategyPolicy !== undefined ? { strategyPolicy: seams.strategyPolicy } : {}),
|
|
64
|
+
now,
|
|
65
|
+
newActionId,
|
|
66
|
+
});
|
|
67
|
+
persistGitDeliveryEvidence(deps, result.lifecycle, snapshot, workspace.root, now);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
function mergeBlockerView(blocker) {
|
|
71
|
+
const actionHint = gitMergeBlockerActionHintFor(blocker.code);
|
|
72
|
+
return {
|
|
73
|
+
code: blocker.code,
|
|
74
|
+
severity: blocker.severity,
|
|
75
|
+
remediation: blocker.remediation,
|
|
76
|
+
...(actionHint !== undefined ? { actionHint } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function mergeBlockerViews(summary) {
|
|
80
|
+
return summary.blockers.map(mergeBlockerView);
|
|
81
|
+
}
|
|
82
|
+
function deriveMergePreviewParts(command, provider, packs, strategyPolicy) {
|
|
83
|
+
const decision = evaluateGitPolicy(packs.orgPack, packs.repoPack, {
|
|
84
|
+
actionKind: "merge",
|
|
85
|
+
targetBranchName: command.baseBranchName,
|
|
86
|
+
activeProviderCapabilities: [],
|
|
87
|
+
});
|
|
88
|
+
const effective = evaluateGitMergeEffectivePolicy(decision, command.baseBranchName, []);
|
|
89
|
+
const eligibility = deriveEligibleMergeStrategies(command.mergeStrategy, strategyPolicy, provider.providerCapableStrategies);
|
|
90
|
+
const readiness = gitMergeReadinessFor({
|
|
91
|
+
...(provider.pullRequest !== undefined ? { pullRequest: provider.pullRequest } : {}),
|
|
92
|
+
...(provider.checks !== undefined ? { checks: provider.checks } : {}),
|
|
93
|
+
...(provider.branchProtection !== undefined
|
|
94
|
+
? { branchProtection: provider.branchProtection }
|
|
95
|
+
: {}),
|
|
96
|
+
strategyEligible: eligibility.requestedEligible,
|
|
97
|
+
...(provider.providerError === true ? { providerError: true } : {}),
|
|
98
|
+
});
|
|
99
|
+
const requiresApproval = effective.outcome === "approval-gated";
|
|
100
|
+
return {
|
|
101
|
+
effectiveOutcome: effective.outcome,
|
|
102
|
+
...(effective.blockReason !== undefined ? { blockReason: effective.blockReason } : {}),
|
|
103
|
+
requiresApproval,
|
|
104
|
+
eligibleStrategies: eligibility.eligible,
|
|
105
|
+
...(eligibility.selectedDefault !== undefined
|
|
106
|
+
? { selectedDefaultStrategy: eligibility.selectedDefault }
|
|
107
|
+
: {}),
|
|
108
|
+
requestedStrategyEligible: eligibility.requestedEligible,
|
|
109
|
+
readiness,
|
|
110
|
+
// The preview has no approval token, so approvalSatisfied is false.
|
|
111
|
+
recommendation: gitMergeRecommendationFor(readiness, {
|
|
112
|
+
requiresApproval,
|
|
113
|
+
approvalSatisfied: false,
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function buildGitDeliveryMergePreview(command, provider, packs, strategyPolicy) {
|
|
118
|
+
const parts = deriveMergePreviewParts(command, provider, packs, strategyPolicy);
|
|
119
|
+
const riskClass = "protected-or-merge";
|
|
120
|
+
return {
|
|
121
|
+
schemaVersion: "1",
|
|
122
|
+
actionKind: "merge",
|
|
123
|
+
baseBranchName: command.baseBranchName,
|
|
124
|
+
headBranchName: command.headBranchName,
|
|
125
|
+
prExternalId: command.prExternalId,
|
|
126
|
+
riskClass,
|
|
127
|
+
riskSeverity: GIT_DELIVERY_RISK_CLASS_SEVERITY[riskClass],
|
|
128
|
+
requestedStrategy: command.mergeStrategy,
|
|
129
|
+
eligibleStrategies: parts.eligibleStrategies,
|
|
130
|
+
...(parts.selectedDefaultStrategy !== undefined
|
|
131
|
+
? { selectedDefaultStrategy: parts.selectedDefaultStrategy }
|
|
132
|
+
: {}),
|
|
133
|
+
requestedStrategyEligible: parts.requestedStrategyEligible,
|
|
134
|
+
policyOutcome: parts.effectiveOutcome,
|
|
135
|
+
...(parts.blockReason !== undefined ? { policyBlockReason: parts.blockReason } : {}),
|
|
136
|
+
requiresApproval: parts.requiresApproval,
|
|
137
|
+
readiness: {
|
|
138
|
+
mergeable: parts.readiness.mergeable,
|
|
139
|
+
blockers: mergeBlockerViews(parts.readiness),
|
|
140
|
+
},
|
|
141
|
+
recommendation: parts.recommendation,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export function gitDeliveryMergeExecuteResponse(result) {
|
|
145
|
+
const base = gitDeliveryMutationResponse(result.lifecycle);
|
|
146
|
+
const withReadiness = {
|
|
147
|
+
...base,
|
|
148
|
+
...(result.readiness !== undefined
|
|
149
|
+
? {
|
|
150
|
+
mergeable: result.readiness.mergeable,
|
|
151
|
+
readinessBlockers: mergeBlockerViews(result.readiness),
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
154
|
+
...(result.merged !== undefined ? { merged: result.merged } : {}),
|
|
155
|
+
...(result.branchDeleted !== undefined ? { branchDeleted: result.branchDeleted } : {}),
|
|
156
|
+
};
|
|
157
|
+
if (result.rejection === undefined) {
|
|
158
|
+
return withReadiness;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
...withReadiness,
|
|
162
|
+
mergeRejectionReason: result.rejection.reason,
|
|
163
|
+
recoveryDisposition: result.rejection.disposition,
|
|
164
|
+
...(result.rejection.actionHint !== undefined
|
|
165
|
+
? { recoveryActionHint: result.rejection.actionHint }
|
|
166
|
+
: {}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RouteContext, RouteDefinition, RouteResult } from "../routes.js";
|
|
2
|
+
import type { UiHandlerDeps } from "../deps.js";
|
|
3
|
+
import { type GitDeliveryMergeSeams } from "./mergeExecution.js";
|
|
4
|
+
export type GitDeliveryMergeErrorCode = "GIT_DELIVERY_MERGE_BAD_REQUEST" | "GIT_DELIVERY_MERGE_PAYLOAD_TOO_LARGE" | "GIT_DELIVERY_MERGE_FORBIDDEN_PAYLOAD" | "GIT_DELIVERY_MERGE_UNKNOWN_PROJECT" | "GIT_DELIVERY_MERGE_WORKTREE_UNAVAILABLE";
|
|
5
|
+
export interface GitDeliveryMergeRouteOptions {
|
|
6
|
+
readonly execution?: GitDeliveryMergeSeams;
|
|
7
|
+
}
|
|
8
|
+
export declare const createHandleMergePreview: (options?: GitDeliveryMergeRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>);
|
|
9
|
+
export declare const createHandleMergeExecute: (options?: GitDeliveryMergeRouteOptions) => ((ctx: RouteContext, deps: UiHandlerDeps) => Promise<RouteResult>);
|
|
10
|
+
export declare const createGitDeliveryMergeRouteGroup: (options?: GitDeliveryMergeRouteOptions) => readonly RouteDefinition[];
|
|
11
|
+
export declare const GIT_DELIVERY_MERGE_ROUTE_GROUP: readonly RouteDefinition[];
|
|
12
|
+
//# sourceMappingURL=mergeRoutes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeRoutes.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/mergeRoutes.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,qBAAqB,CAAC;AAa7B,MAAM,MAAM,yBAAyB,GACjC,gCAAgC,GAChC,sCAAsC,GACtC,sCAAsC,GACtC,oCAAoC,GACpC,yCAAyC,CAAC;AAmB9C,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,CAAC;CAC5C;AA+ID,eAAO,MAAM,wBAAwB,GACnC,UAAS,4BAAiC,KACzC,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAqBnE,CAAC;AAIF,eAAO,MAAM,wBAAwB,GACnC,UAAS,4BAAiC,KACzC,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAmBnE,CAAC;AAIF,eAAO,MAAM,gCAAgC,GAC3C,UAAS,4BAAiC,KACzC,SAAS,eAAe,EAW1B,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,SAAS,eAAe,EACjC,CAAC"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Governed merge routes (Issue #478, Epic #470, ADR-0065).
|
|
2
|
+
//
|
|
3
|
+
// * POST /api/git-delivery/merge/preview — READ-ONLY. Reads the provider's content-free merge-readiness
|
|
4
|
+
// facts and builds the pre-merge context: the readiness summary (mergeable + severity-ranked
|
|
5
|
+
// blockers), the eligible merge strategies (policy ∩ provider capability — never a hard-coded UI
|
|
6
|
+
// default), the recommendation, the effective policy decision, and whether final approval is
|
|
7
|
+
// required. Never mutates, never records evidence.
|
|
8
|
+
// * POST /api/git-delivery/merge/execute — Governed. Drives the #478 merge gateway end-to-end through
|
|
9
|
+
// executeGovernedMerge (preflight + policy + final-approval + the readiness gate + the dedicated
|
|
10
|
+
// `gh api` merge adapter) and appends content-free evidence for the allowed AND blocked outcome
|
|
11
|
+
// alike. Returns the typed provider-rejection reason + reused recovery hint and the merged /
|
|
12
|
+
// branch-deleted flags.
|
|
13
|
+
//
|
|
14
|
+
// Content-free in evidence: only the merge inputs (PR number, strategy, delete flag) and outcome enter the
|
|
15
|
+
// ledger. CSRF + JSON content type are enforced centrally by server.ts.
|
|
16
|
+
import { isGitDeliveryApprovalRequirement, isGitDeliveryMergeStrategyHint, } from "@oscharko-dev/keiko-contracts";
|
|
17
|
+
import { resolveProjectWorkspace } from "./execution.js";
|
|
18
|
+
import { buildGitDeliveryMergePreview, executeGovernedMerge, gitDeliveryMergeExecuteResponse, KEIKO_DEFAULT_MERGE_POLICY_PACK, readMergeProviderReadiness, } from "./mergeExecution.js";
|
|
19
|
+
import { GitDeliveryBodyTooLargeError, hasOnlyAllowedKeys, isNonEmptyString, isPlainObject, readGitDeliveryBody, scanForbiddenStrings, scanUnsafeFormatChars, } from "./requestGuards.js";
|
|
20
|
+
const SAFE_MESSAGES = {
|
|
21
|
+
GIT_DELIVERY_MERGE_BAD_REQUEST: "The request body is not a valid governed merge.",
|
|
22
|
+
GIT_DELIVERY_MERGE_PAYLOAD_TOO_LARGE: "The governed merge request exceeds the maximum size.",
|
|
23
|
+
GIT_DELIVERY_MERGE_FORBIDDEN_PAYLOAD: "The request contained a forbidden field. Requests may not carry credentials or auth headers.",
|
|
24
|
+
GIT_DELIVERY_MERGE_UNKNOWN_PROJECT: "The requested project is not a known workspace.",
|
|
25
|
+
GIT_DELIVERY_MERGE_WORKTREE_UNAVAILABLE: "The repository worktree could not be inspected. Confirm the project is a Git repository.",
|
|
26
|
+
};
|
|
27
|
+
const errResult = (status, code) => ({
|
|
28
|
+
status,
|
|
29
|
+
body: { error: { code, message: SAFE_MESSAGES[code] } },
|
|
30
|
+
});
|
|
31
|
+
async function readParsed(req) {
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = await readGitDeliveryBody(req);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const result = error instanceof GitDeliveryBodyTooLargeError
|
|
38
|
+
? errResult(413, "GIT_DELIVERY_MERGE_PAYLOAD_TOO_LARGE")
|
|
39
|
+
: errResult(400, "GIT_DELIVERY_MERGE_BAD_REQUEST");
|
|
40
|
+
return { ok: false, result };
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return { ok: false, result: errResult(400, "GIT_DELIVERY_MERGE_BAD_REQUEST") };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// A git ref operand: non-empty, no whitespace, no leading "-" (flag-injection guard), no NUL/control.
|
|
50
|
+
// eslint-disable-next-line no-control-regex -- intentionally matches control chars to REJECT them
|
|
51
|
+
const REF_CONTROL_CHAR = new RegExp("[\u0000-\u001f\u007f]");
|
|
52
|
+
function isSafeGitRef(value) {
|
|
53
|
+
if (typeof value !== "string" || value.length === 0)
|
|
54
|
+
return false;
|
|
55
|
+
if (/\s/.test(value))
|
|
56
|
+
return false;
|
|
57
|
+
if (value.startsWith("-"))
|
|
58
|
+
return false;
|
|
59
|
+
if (REF_CONTROL_CHAR.test(value))
|
|
60
|
+
return false;
|
|
61
|
+
if (value.includes(":"))
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
const OWNER_REPO_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
66
|
+
const PR_NUMBER_RE = /^[1-9][0-9]{0,9}$/;
|
|
67
|
+
const SHA_RE = /^[0-9a-fA-F]{7,64}$/;
|
|
68
|
+
function isOwnerAndRepo(value) {
|
|
69
|
+
return typeof value === "string" && OWNER_REPO_RE.test(value);
|
|
70
|
+
}
|
|
71
|
+
function isPrNumberString(value) {
|
|
72
|
+
return typeof value === "string" && PR_NUMBER_RE.test(value);
|
|
73
|
+
}
|
|
74
|
+
const ALLOWED_KEYS = new Set([
|
|
75
|
+
"schemaVersion",
|
|
76
|
+
"projectId",
|
|
77
|
+
"kind",
|
|
78
|
+
"ownerAndRepo",
|
|
79
|
+
"prExternalId",
|
|
80
|
+
"baseBranchName",
|
|
81
|
+
"headBranchName",
|
|
82
|
+
"mergeStrategy",
|
|
83
|
+
"deleteBranchAfterMerge",
|
|
84
|
+
"expectedHeadRefHash",
|
|
85
|
+
"approval",
|
|
86
|
+
]);
|
|
87
|
+
const NO_APPROVAL = { required: false };
|
|
88
|
+
function parseApproval(value) {
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
return NO_APPROVAL;
|
|
91
|
+
return isGitDeliveryApprovalRequirement(value) ? value : undefined;
|
|
92
|
+
}
|
|
93
|
+
function optionalBool(value) {
|
|
94
|
+
if (value === undefined)
|
|
95
|
+
return false;
|
|
96
|
+
return typeof value === "boolean" ? value : undefined;
|
|
97
|
+
}
|
|
98
|
+
function parseExpectedHeadRefHash(value) {
|
|
99
|
+
if (value === undefined)
|
|
100
|
+
return { ok: true };
|
|
101
|
+
if (typeof value === "string" && SHA_RE.test(value))
|
|
102
|
+
return { ok: true, value };
|
|
103
|
+
return { ok: false };
|
|
104
|
+
}
|
|
105
|
+
function scanError(parsed) {
|
|
106
|
+
if (scanForbiddenStrings(parsed)) {
|
|
107
|
+
return errResult(400, "GIT_DELIVERY_MERGE_FORBIDDEN_PAYLOAD");
|
|
108
|
+
}
|
|
109
|
+
if (scanUnsafeFormatChars(parsed)) {
|
|
110
|
+
return errResult(400, "GIT_DELIVERY_MERGE_BAD_REQUEST");
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
function buildMergeCommand(parsed) {
|
|
115
|
+
if (parsed.kind !== "merge" ||
|
|
116
|
+
!isOwnerAndRepo(parsed.ownerAndRepo) ||
|
|
117
|
+
!isPrNumberString(parsed.prExternalId) ||
|
|
118
|
+
!isSafeGitRef(parsed.baseBranchName) ||
|
|
119
|
+
!isSafeGitRef(parsed.headBranchName) ||
|
|
120
|
+
!isGitDeliveryMergeStrategyHint(parsed.mergeStrategy)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const deleteBranchAfterMerge = optionalBool(parsed.deleteBranchAfterMerge);
|
|
124
|
+
const expectedHead = parseExpectedHeadRefHash(parsed.expectedHeadRefHash);
|
|
125
|
+
if (deleteBranchAfterMerge === undefined || !expectedHead.ok) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
kind: "merge",
|
|
130
|
+
ownerAndRepo: parsed.ownerAndRepo,
|
|
131
|
+
prExternalId: parsed.prExternalId,
|
|
132
|
+
baseBranchName: parsed.baseBranchName,
|
|
133
|
+
headBranchName: parsed.headBranchName,
|
|
134
|
+
mergeStrategy: parsed.mergeStrategy,
|
|
135
|
+
deleteBranchAfterMerge,
|
|
136
|
+
...(expectedHead.value !== undefined ? { expectedHeadRefHash: expectedHead.value } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function validate(parsed) {
|
|
140
|
+
const bad = { kind: "err", result: errResult(400, "GIT_DELIVERY_MERGE_BAD_REQUEST") };
|
|
141
|
+
if (!isPlainObject(parsed) || !hasOnlyAllowedKeys(parsed, ALLOWED_KEYS))
|
|
142
|
+
return bad;
|
|
143
|
+
if (parsed.schemaVersion !== "1" || !isNonEmptyString(parsed.projectId))
|
|
144
|
+
return bad;
|
|
145
|
+
const scanErr = scanError(parsed);
|
|
146
|
+
if (scanErr !== undefined)
|
|
147
|
+
return { kind: "err", result: scanErr };
|
|
148
|
+
const command = buildMergeCommand(parsed);
|
|
149
|
+
const approval = parseApproval(parsed.approval);
|
|
150
|
+
if (command === undefined || approval === undefined)
|
|
151
|
+
return bad;
|
|
152
|
+
return { kind: "ok", value: { projectId: parsed.projectId, command, approval } };
|
|
153
|
+
}
|
|
154
|
+
// ─── Preview handler (read-only) ────────────────────────────────────────────────────────────────
|
|
155
|
+
export const createHandleMergePreview = (options = {}) => {
|
|
156
|
+
const seams = options.execution ?? {};
|
|
157
|
+
const now = () => (seams.now ?? Date.now)();
|
|
158
|
+
return async (ctx, deps) => {
|
|
159
|
+
const read = await readParsed(ctx.req);
|
|
160
|
+
if (!read.ok)
|
|
161
|
+
return read.result;
|
|
162
|
+
const validation = validate(read.value);
|
|
163
|
+
if (validation.kind === "err")
|
|
164
|
+
return validation.result;
|
|
165
|
+
const { projectId, command } = validation.value;
|
|
166
|
+
const workspace = resolveProjectWorkspace(deps, projectId);
|
|
167
|
+
if (workspace === undefined)
|
|
168
|
+
return errResult(404, "GIT_DELIVERY_MERGE_UNKNOWN_PROJECT");
|
|
169
|
+
const packs = seams.policyPacks ?? { repoPack: KEIKO_DEFAULT_MERGE_POLICY_PACK };
|
|
170
|
+
const strategyPolicy = seams.strategyPolicy ?? {
|
|
171
|
+
allowedStrategies: ["squash", "rebase", "merge-commit", "provider-default"],
|
|
172
|
+
};
|
|
173
|
+
const provider = await readMergeProviderReadiness(command, workspace, seams, now);
|
|
174
|
+
return {
|
|
175
|
+
status: 200,
|
|
176
|
+
body: deps.redactor(buildGitDeliveryMergePreview(command, provider, packs, strategyPolicy)),
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
// ─── Execute handler (governed) ───────────────────────────────────────────────────────────────
|
|
181
|
+
export const createHandleMergeExecute = (options = {}) => {
|
|
182
|
+
const seams = options.execution ?? {};
|
|
183
|
+
return async (ctx, deps) => {
|
|
184
|
+
const read = await readParsed(ctx.req);
|
|
185
|
+
if (!read.ok)
|
|
186
|
+
return read.result;
|
|
187
|
+
const validation = validate(read.value);
|
|
188
|
+
if (validation.kind === "err")
|
|
189
|
+
return validation.result;
|
|
190
|
+
const { projectId, command, approval } = validation.value;
|
|
191
|
+
const workspace = resolveProjectWorkspace(deps, projectId);
|
|
192
|
+
if (workspace === undefined)
|
|
193
|
+
return errResult(404, "GIT_DELIVERY_MERGE_UNKNOWN_PROJECT");
|
|
194
|
+
let result;
|
|
195
|
+
try {
|
|
196
|
+
result = await executeGovernedMerge(command, approval, workspace, deps, seams);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Only the read-only snapshot step can throw (not a git repository); the gateway never throws.
|
|
200
|
+
return errResult(409, "GIT_DELIVERY_MERGE_WORKTREE_UNAVAILABLE");
|
|
201
|
+
}
|
|
202
|
+
return { status: 200, body: deps.redactor(gitDeliveryMergeExecuteResponse(result)) };
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
// ─── Route group ───────────────────────────────────────────────────────────────────────────────
|
|
206
|
+
export const createGitDeliveryMergeRouteGroup = (options = {}) => [
|
|
207
|
+
{
|
|
208
|
+
method: "POST",
|
|
209
|
+
pattern: "/api/git-delivery/merge/preview",
|
|
210
|
+
handler: createHandleMergePreview(options),
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
method: "POST",
|
|
214
|
+
pattern: "/api/git-delivery/merge/execute",
|
|
215
|
+
handler: createHandleMergeExecute(options),
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
export const GIT_DELIVERY_MERGE_ROUTE_GROUP = createGitDeliveryMergeRouteGroup();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GitDeliveryEvidenceRecord } from "@oscharko-dev/keiko-contracts";
|
|
2
|
+
import { GIT_DELIVERY_EVIDENCE_SCHEMA_VERSION } from "@oscharko-dev/keiko-contracts";
|
|
3
|
+
import type { EvidenceStore } from "@oscharko-dev/keiko-evidence";
|
|
4
|
+
export declare const GIT_DELIVERY_EVIDENCE_RUNID_PREFIX: "git-delivery-evidence-";
|
|
5
|
+
export declare const GIT_DELIVERY_EVIDENCE_DEFAULT_BUCKET_CAP = 500;
|
|
6
|
+
export interface GitDeliveryEvidenceLedgerDoc {
|
|
7
|
+
readonly schemaVersion: typeof GIT_DELIVERY_EVIDENCE_SCHEMA_VERSION;
|
|
8
|
+
readonly records: readonly GitDeliveryEvidenceRecord[];
|
|
9
|
+
}
|
|
10
|
+
export declare function gitDeliveryEvidenceRunIdFor(nowMs: number): string;
|
|
11
|
+
export interface RecordGitDeliveryEvidenceOptions {
|
|
12
|
+
readonly evidenceStore: EvidenceStore;
|
|
13
|
+
readonly redactString: (input: string) => string;
|
|
14
|
+
readonly maxRecordsPerBucket?: number | undefined;
|
|
15
|
+
readonly onPersistError?: ((error: unknown) => void) | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Appends one governed Git mutation evidence record to its date-bucketed ledger. Redacts every string
|
|
19
|
+
* leaf, bounds the bucket, and never throws into the caller's path. Returns nothing — audit recording
|
|
20
|
+
* is best-effort and is reported (not propagated) on failure.
|
|
21
|
+
*/
|
|
22
|
+
export declare function recordGitDeliveryMutationEvidence(options: RecordGitDeliveryEvidenceOptions, record: GitDeliveryEvidenceRecord): void;
|
|
23
|
+
//# sourceMappingURL=mutationEvidenceLedger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutationEvidenceLedger.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/mutationEvidenceLedger.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,oCAAoC,EAAE,MAAM,+BAA+B,CAAC;AAErF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,eAAO,MAAM,kCAAkC,EAAG,wBAAiC,CAAC;AAGpF,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,aAAa,EAAE,OAAO,oCAAoC,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACxD;AAID,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IAGtC,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAElD,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAClE;AA6DD;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,gCAAgC,EACzC,MAAM,EAAE,yBAAyB,GAChC,IAAI,CAUN"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Governed Git mutation EVIDENCE ledger (Issue #474, Epic #470).
|
|
2
|
+
//
|
|
3
|
+
// The durable, bounded, append-only sink the #472 orchestrator's idempotency-journal comment
|
|
4
|
+
// anticipates ("a durable journal — the evidence ledger, #474"). It records the content-free
|
|
5
|
+
// GitDeliveryEvidenceRecord produced by the keiko-tools builder for EVERY terminal outcome of a
|
|
6
|
+
// governed Git mutation — succeeded, blocked, rejected, failed, recovery-required, or held for
|
|
7
|
+
// approval — so a governance decision is auditable even when no mutation executed.
|
|
8
|
+
//
|
|
9
|
+
// Persistence shape: ONE ledger document per UTC date bucket (runId `git-delivery-evidence-YYYY-MM-DD`),
|
|
10
|
+
// mirroring memory-audit-handler.ts. Stores that expose EvidenceStore.update() serialize the
|
|
11
|
+
// read-append-write step so concurrent appenders never drop a record; stores without it fall back to
|
|
12
|
+
// get+put. The bucket is bounded to the most recent N records so a long-lived ledger cannot grow
|
|
13
|
+
// without limit.
|
|
14
|
+
//
|
|
15
|
+
// Redaction: each record is run through deepRedactStrings before it is serialized (defence-in-depth on
|
|
16
|
+
// top of the builder's by-construction content-free hashing). Persistence failures are caught and
|
|
17
|
+
// reported through an injectable sink; an audit-write failure must NEVER break the user's mutation.
|
|
18
|
+
// Corrupt ledger documents are never reset or overwritten — an append against a corrupt bucket fails
|
|
19
|
+
// closed and preserves the existing artifact for operator investigation.
|
|
20
|
+
import { GIT_DELIVERY_EVIDENCE_SCHEMA_VERSION } from "@oscharko-dev/keiko-contracts";
|
|
21
|
+
import { deepRedactStrings } from "@oscharko-dev/keiko-evidence";
|
|
22
|
+
export const GIT_DELIVERY_EVIDENCE_RUNID_PREFIX = "git-delivery-evidence-";
|
|
23
|
+
// Default bound on records retained per UTC date bucket. The most recent records are kept.
|
|
24
|
+
export const GIT_DELIVERY_EVIDENCE_DEFAULT_BUCKET_CAP = 500;
|
|
25
|
+
// UTC date-bucket runId. 22 chars (`git-delivery-evidence-` is 22) + 10 (`YYYY-MM-DD`) = 32, well
|
|
26
|
+
// under the evidence-store run-id length cap.
|
|
27
|
+
export function gitDeliveryEvidenceRunIdFor(nowMs) {
|
|
28
|
+
const iso = new Date(nowMs).toISOString();
|
|
29
|
+
return `${GIT_DELIVERY_EVIDENCE_RUNID_PREFIX}${iso.slice(0, 10)}`;
|
|
30
|
+
}
|
|
31
|
+
function defaultOnPersistError(error) {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.error("git-delivery evidence ledger: persistence failed", error);
|
|
34
|
+
}
|
|
35
|
+
function isPlainObject(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
// Parses an existing bucket document. Returns the records faithfully (they were written valid).
|
|
39
|
+
// Throws on gross corruption so the caller fails closed and preserves the artifact.
|
|
40
|
+
function parseExistingRecords(json) {
|
|
41
|
+
if (json === undefined) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(json);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error("git-delivery evidence ledger is corrupt; refusing to overwrite existing audit evidence", { cause: error });
|
|
50
|
+
}
|
|
51
|
+
if (!isPlainObject(parsed) || !Array.isArray(parsed.records)) {
|
|
52
|
+
throw new Error("git-delivery evidence ledger has an unexpected shape; refusing to overwrite existing audit evidence");
|
|
53
|
+
}
|
|
54
|
+
return parsed.records;
|
|
55
|
+
}
|
|
56
|
+
function boundedDoc(existing, record, cap) {
|
|
57
|
+
const records = [...existing, record];
|
|
58
|
+
return {
|
|
59
|
+
schemaVersion: GIT_DELIVERY_EVIDENCE_SCHEMA_VERSION,
|
|
60
|
+
records: cap > 0 && records.length > cap ? records.slice(records.length - cap) : records,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function appendRecord(store, runId, record, cap) {
|
|
64
|
+
const append = (existingJson) => JSON.stringify(boundedDoc(parseExistingRecords(existingJson), record, cap));
|
|
65
|
+
if (store.update !== undefined) {
|
|
66
|
+
store.update(runId, append);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
store.put(runId, append(store.get(runId)));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Appends one governed Git mutation evidence record to its date-bucketed ledger. Redacts every string
|
|
73
|
+
* leaf, bounds the bucket, and never throws into the caller's path. Returns nothing — audit recording
|
|
74
|
+
* is best-effort and is reported (not propagated) on failure.
|
|
75
|
+
*/
|
|
76
|
+
export function recordGitDeliveryMutationEvidence(options, record) {
|
|
77
|
+
const onPersistError = options.onPersistError ?? defaultOnPersistError;
|
|
78
|
+
const cap = options.maxRecordsPerBucket ?? GIT_DELIVERY_EVIDENCE_DEFAULT_BUCKET_CAP;
|
|
79
|
+
const safe = deepRedactStrings(record, options.redactString);
|
|
80
|
+
const runId = gitDeliveryEvidenceRunIdFor(record.recordedAtMs);
|
|
81
|
+
try {
|
|
82
|
+
appendRecord(options.evidenceStore, runId, safe, cap);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
onPersistError(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { WorkspaceInfo } from "@oscharko-dev/keiko-workspace";
|
|
2
|
+
import { type GitDeliveryApprovalRequirement, type GitDeliveryRepoPolicyPack, type GitDeliveryRiskClass } from "@oscharko-dev/keiko-contracts";
|
|
3
|
+
import { type GitPullRequestAdapter, type GitPullRequestCommand, type GitPullRequestLifecycleResult, type GitWorktreeSnapshot } from "@oscharko-dev/keiko-tools";
|
|
4
|
+
import type { UiHandlerDeps } from "../deps.js";
|
|
5
|
+
import type { GitDeliveryTrustedPolicyPacks } from "./actionSheetProjection.js";
|
|
6
|
+
import { type GitDeliveryMutationResponseBody } from "./execution.js";
|
|
7
|
+
export declare const KEIKO_DEFAULT_PR_POLICY_PACK: GitDeliveryRepoPolicyPack;
|
|
8
|
+
export interface GitDeliveryPullRequestSeams {
|
|
9
|
+
readonly prAdapterFactory?: ((workspace: WorkspaceInfo) => GitPullRequestAdapter) | undefined;
|
|
10
|
+
readonly snapshotReader?: ((workspace: WorkspaceInfo) => Promise<GitWorktreeSnapshot>) | undefined;
|
|
11
|
+
readonly policyPacks?: GitDeliveryTrustedPolicyPacks | undefined;
|
|
12
|
+
readonly now?: (() => number) | undefined;
|
|
13
|
+
readonly newActionId?: (() => string) | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Runs ONE governed PR operation end-to-end: live snapshot → PR gateway (preflight + policy + approval +
|
|
17
|
+
* execute) → evidence. Returns the gateway lifecycle result; the caller projects it into a content-free
|
|
18
|
+
* HTTP body. Evidence is appended best-effort BEFORE the caller responds, for allowed AND blocked alike.
|
|
19
|
+
*/
|
|
20
|
+
export declare function executeGovernedPullRequest(command: GitPullRequestCommand, approval: GitDeliveryApprovalRequirement, workspace: WorkspaceInfo, deps: Pick<UiHandlerDeps, "evidenceStore" | "redactor">, seams: GitDeliveryPullRequestSeams): Promise<GitPullRequestLifecycleResult>;
|
|
21
|
+
export interface GitDeliveryPrReadinessBody {
|
|
22
|
+
readonly objectExists: boolean;
|
|
23
|
+
readonly reviewReady: boolean;
|
|
24
|
+
readonly blockerCodes: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
export interface GitDeliveryPrPreviewBody {
|
|
27
|
+
readonly schemaVersion: "1";
|
|
28
|
+
readonly actionKind: "pr-create" | "pr-update";
|
|
29
|
+
readonly headBranchName: string;
|
|
30
|
+
readonly baseBranchName: string;
|
|
31
|
+
readonly riskClass: GitDeliveryRiskClass;
|
|
32
|
+
readonly riskSeverity: number;
|
|
33
|
+
readonly isDraft: boolean;
|
|
34
|
+
readonly policyOutcome: string;
|
|
35
|
+
readonly policyBlockReason?: string;
|
|
36
|
+
readonly composedTitle: string;
|
|
37
|
+
readonly composedBody: string;
|
|
38
|
+
readonly riskNarrative: string;
|
|
39
|
+
readonly recommendation: string;
|
|
40
|
+
readonly readiness: GitDeliveryPrReadinessBody;
|
|
41
|
+
readonly suggestedLabels: readonly string[];
|
|
42
|
+
readonly suggestedIssueRefs: readonly string[];
|
|
43
|
+
readonly titleByteLength: number;
|
|
44
|
+
readonly bodyByteLength: number;
|
|
45
|
+
}
|
|
46
|
+
export declare function buildGitDeliveryPrPreview(command: GitPullRequestCommand, snapshot: GitWorktreeSnapshot, packs: GitDeliveryTrustedPolicyPacks): GitDeliveryPrPreviewBody;
|
|
47
|
+
export interface GitDeliveryPrExecuteResponseBody extends GitDeliveryMutationResponseBody {
|
|
48
|
+
readonly prRejectionReason?: string;
|
|
49
|
+
readonly recoveryDisposition?: string;
|
|
50
|
+
readonly recoveryActionHint?: string;
|
|
51
|
+
readonly createdPrExternalId?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function gitDeliveryPrExecuteResponse(result: GitPullRequestLifecycleResult): GitDeliveryPrExecuteResponseBody;
|
|
54
|
+
//# sourceMappingURL=prExecution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prExecution.d.ts","sourceRoot":"","sources":["../../src/gitDelivery/prExecution.ts"],"names":[],"mappings":"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"}
|