@ai-sdlc/orchestrator 0.6.0 → 0.9.0
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/admission-composite.d.ts +67 -0
- package/dist/admission-composite.js +158 -0
- package/dist/admission-enrichment.d.ts +142 -0
- package/dist/admission-enrichment.js +331 -0
- package/dist/admission-hc.d.ts +62 -0
- package/dist/admission-hc.js +83 -0
- package/dist/admission-score.d.ts +95 -5
- package/dist/admission-score.js +84 -11
- package/dist/analysis/analyzer.js +3 -2
- package/dist/analysis/convention-detector.d.ts +85 -2
- package/dist/analysis/convention-detector.js +375 -70
- package/dist/analysis/diff-analyzer.d.ts +33 -0
- package/dist/analysis/diff-analyzer.js +122 -0
- package/dist/analysis/hotspot-analyzer.js +3 -1
- package/dist/analysis/index.d.ts +2 -1
- package/dist/analysis/index.js +2 -1
- package/dist/artifacts/index.d.ts +65 -0
- package/dist/artifacts/index.js +142 -0
- package/dist/backlog-adapter.d.ts +121 -0
- package/dist/backlog-adapter.js +438 -0
- package/dist/calibration.d.ts +43 -0
- package/dist/calibration.js +76 -0
- package/dist/cli/commands/agents.d.ts +24 -0
- package/dist/cli/commands/agents.js +66 -1
- package/dist/cli/commands/git-remote.d.ts +49 -0
- package/dist/cli/commands/git-remote.js +91 -0
- package/dist/cli/commands/health.d.ts +4 -0
- package/dist/cli/commands/health.js +13 -1
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +164 -22
- package/dist/cli/commands/mcp-setup.d.ts +31 -0
- package/dist/cli/commands/mcp-setup.js +78 -8
- package/dist/cli/formatters/table.js +15 -2
- package/dist/cli/index.d.ts +14 -1
- package/dist/cli/index.js +81 -20
- package/dist/cli/versions.d.ts +57 -0
- package/dist/cli/versions.js +128 -0
- package/dist/code-area-classifier.d.ts +21 -0
- package/dist/code-area-classifier.js +48 -0
- package/dist/config.d.ts +33 -1
- package/dist/config.js +78 -8
- package/dist/database/adapters/external.d.ts +24 -0
- package/dist/database/adapters/external.js +80 -0
- package/dist/database/adapters/neon.d.ts +41 -0
- package/dist/database/adapters/neon.js +98 -0
- package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
- package/dist/database/adapters/pg-snapshot-restore.js +68 -0
- package/dist/database/adapters/sqlite-copy.d.ts +32 -0
- package/dist/database/adapters/sqlite-copy.js +145 -0
- package/dist/database/connection-injection.d.ts +35 -0
- package/dist/database/connection-injection.js +93 -0
- package/dist/database/index.d.ts +12 -0
- package/dist/database/index.js +23 -0
- package/dist/database/registry.d.ts +13 -0
- package/dist/database/registry.js +27 -0
- package/dist/database/topology.d.ts +52 -0
- package/dist/database/topology.js +44 -0
- package/dist/database/types.d.ts +89 -0
- package/dist/database/types.js +26 -0
- package/dist/design-authority.d.ts +40 -0
- package/dist/design-authority.js +71 -0
- package/dist/design-lookahead.d.ts +64 -0
- package/dist/design-lookahead.js +86 -0
- package/dist/design-quality-trend.d.ts +87 -0
- package/dist/design-quality-trend.js +190 -0
- package/dist/design-system-context.d.ts +46 -0
- package/dist/design-system-context.js +80 -0
- package/dist/design-system-correction-loop.d.ts +64 -0
- package/dist/design-system-correction-loop.js +128 -0
- package/dist/design-system-metrics.d.ts +61 -0
- package/dist/design-system-metrics.js +104 -0
- package/dist/design-system-stewardship.d.ts +22 -0
- package/dist/design-system-stewardship.js +85 -0
- package/dist/design-system-validation.d.ts +37 -0
- package/dist/design-system-validation.js +88 -0
- package/dist/dispatch/index.d.ts +4 -0
- package/dist/dispatch/index.js +4 -0
- package/dist/dispatch/merge-gate.d.ts +46 -0
- package/dist/dispatch/merge-gate.js +90 -0
- package/dist/dispatch/requeue.d.ts +57 -0
- package/dist/dispatch/requeue.js +131 -0
- package/dist/dispatch/worker-pool.d.ts +62 -0
- package/dist/dispatch/worker-pool.js +60 -0
- package/dist/execute.d.ts +40 -0
- package/dist/execute.js +185 -15
- package/dist/fix-ci.js +8 -1
- package/dist/fix-review.js +8 -1
- package/dist/harness/adapters/claude-code.d.ts +29 -0
- package/dist/harness/adapters/claude-code.js +191 -0
- package/dist/harness/adapters/codex.d.ts +25 -0
- package/dist/harness/adapters/codex.js +61 -0
- package/dist/harness/independence.d.ts +51 -0
- package/dist/harness/independence.js +67 -0
- package/dist/harness/index.d.ts +14 -0
- package/dist/harness/index.js +20 -0
- package/dist/harness/registry.d.ts +17 -0
- package/dist/harness/registry.js +31 -0
- package/dist/harness/types.d.ts +123 -0
- package/dist/harness/types.js +8 -0
- package/dist/harness/version-probe.d.ts +14 -0
- package/dist/harness/version-probe.js +123 -0
- package/dist/index.d.ts +30 -4
- package/dist/index.js +35 -2
- package/dist/models/classifier.d.ts +76 -0
- package/dist/models/classifier.js +221 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.js +3 -0
- package/dist/models/registry.d.ts +97 -0
- package/dist/models/registry.js +173 -0
- package/dist/pillar-breakdown.d.ts +63 -0
- package/dist/pillar-breakdown.js +153 -0
- package/dist/priority.d.ts +5 -0
- package/dist/priority.js +18 -5
- package/dist/review-meta.d.ts +65 -0
- package/dist/review-meta.js +149 -0
- package/dist/review.d.ts +4 -0
- package/dist/review.js +12 -2
- package/dist/runners/claude-code-sdk.d.ts +31 -0
- package/dist/runners/claude-code-sdk.js +238 -0
- package/dist/runners/claude-code.d.ts +3 -0
- package/dist/runners/claude-code.js +30 -66
- package/dist/runners/codex.js +4 -1
- package/dist/runners/copilot.js +4 -1
- package/dist/runners/cursor.js +4 -1
- package/dist/runners/git-utils.d.ts +81 -0
- package/dist/runners/git-utils.js +201 -0
- package/dist/runners/index.d.ts +3 -0
- package/dist/runners/index.js +3 -0
- package/dist/runners/review-agent.d.ts +20 -0
- package/dist/runners/review-agent.js +171 -41
- package/dist/runners/runner-registry.js +10 -0
- package/dist/runners/sdk-review-runner.d.ts +65 -0
- package/dist/runners/sdk-review-runner.js +185 -0
- package/dist/runners/security-triage.d.ts +20 -4
- package/dist/runners/security-triage.js +39 -15
- package/dist/runners/types.d.ts +6 -0
- package/dist/runtime/attestations.d.ts +274 -0
- package/dist/runtime/attestations.js +460 -0
- package/dist/runtime/git-env.d.ts +53 -0
- package/dist/runtime/git-env.js +60 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/parallelism-flag.d.ts +10 -0
- package/dist/runtime/parallelism-flag.js +18 -0
- package/dist/runtime/port-allocator.d.ts +21 -0
- package/dist/runtime/port-allocator.js +66 -0
- package/dist/runtime/worktree-pool.d.ts +86 -0
- package/dist/runtime/worktree-pool.js +204 -0
- package/dist/runtime/worktree.d.ts +25 -0
- package/dist/runtime/worktree.js +111 -0
- package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
- package/dist/sa-scoring/auto-calibrate.js +107 -0
- package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
- package/dist/sa-scoring/c1-sa2-computable.js +59 -0
- package/dist/sa-scoring/composite.d.ts +107 -0
- package/dist/sa-scoring/composite.js +139 -0
- package/dist/sa-scoring/depparse-client.d.ts +79 -0
- package/dist/sa-scoring/depparse-client.js +187 -0
- package/dist/sa-scoring/did-compiler.d.ts +122 -0
- package/dist/sa-scoring/did-compiler.js +286 -0
- package/dist/sa-scoring/drift-monitor.d.ts +84 -0
- package/dist/sa-scoring/drift-monitor.js +186 -0
- package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
- package/dist/sa-scoring/exemplar-bank.js +154 -0
- package/dist/sa-scoring/feedback-store.d.ts +100 -0
- package/dist/sa-scoring/feedback-store.js +156 -0
- package/dist/sa-scoring/index.d.ts +71 -0
- package/dist/sa-scoring/index.js +158 -0
- package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
- package/dist/sa-scoring/layer1-deterministic.js +298 -0
- package/dist/sa-scoring/layer2-structural.d.ts +71 -0
- package/dist/sa-scoring/layer2-structural.js +151 -0
- package/dist/sa-scoring/layer3-llm.d.ts +86 -0
- package/dist/sa-scoring/layer3-llm.js +282 -0
- package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
- package/dist/sa-scoring/rescore-orchestrator.js +47 -0
- package/dist/scheduling/burn-down.d.ts +27 -0
- package/dist/scheduling/burn-down.js +43 -0
- package/dist/scheduling/calibration.d.ts +42 -0
- package/dist/scheduling/calibration.js +150 -0
- package/dist/scheduling/index.d.ts +8 -0
- package/dist/scheduling/index.js +8 -0
- package/dist/scheduling/ledger.d.ts +59 -0
- package/dist/scheduling/ledger.js +216 -0
- package/dist/scheduling/off-peak.d.ts +27 -0
- package/dist/scheduling/off-peak.js +112 -0
- package/dist/scheduling/schedule-decision.d.ts +39 -0
- package/dist/scheduling/schedule-decision.js +89 -0
- package/dist/scheduling/tier-analysis.d.ts +47 -0
- package/dist/scheduling/tier-analysis.js +81 -0
- package/dist/scheduling/types.d.ts +140 -0
- package/dist/scheduling/types.js +11 -0
- package/dist/shared.d.ts +13 -0
- package/dist/shared.js +32 -0
- package/dist/state/schema.d.ts +5 -1
- package/dist/state/schema.js +223 -1
- package/dist/state/store.d.ts +55 -1
- package/dist/state/store.js +342 -7
- package/dist/state/types.d.ts +139 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate-agent-output.js +4 -1
- package/dist/watch.js +6 -0
- package/package.json +2 -2
package/dist/config.js
CHANGED
|
@@ -9,9 +9,8 @@ import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
|
9
9
|
import { resolve, join } from 'node:path';
|
|
10
10
|
import { parse as parseYaml } from 'yaml';
|
|
11
11
|
import { validateResource, createAdapterRegistry, scanLocalAdapters, } from '@ai-sdlc/reference';
|
|
12
|
-
/** Resource kinds that allow only a single instance. */
|
|
12
|
+
/** Resource kinds that allow only a single instance. Multi-instance kinds are excluded. */
|
|
13
13
|
const KIND_KEY = {
|
|
14
|
-
Pipeline: 'pipeline',
|
|
15
14
|
AgentRole: 'agentRole',
|
|
16
15
|
QualityGate: 'qualityGate',
|
|
17
16
|
AutonomyPolicy: 'autonomyPolicy',
|
|
@@ -27,21 +26,54 @@ export function loadConfig(configDir) {
|
|
|
27
26
|
}
|
|
28
27
|
const files = readdirSync(dir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
29
28
|
const config = {};
|
|
29
|
+
const warnings = [];
|
|
30
30
|
for (const file of files) {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
let doc;
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync(resolve(dir, file), 'utf-8');
|
|
34
|
+
doc = parseYaml(raw);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// Malformed YAML — record and continue. One bad file should not
|
|
38
|
+
// poison the rest of the config (forward-looking YAMLs in active
|
|
39
|
+
// adoption are common).
|
|
40
|
+
warnings.push({ file, error: `parse error: ${err.message}` });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Skip non-resource YAML files (review-exemplars.yaml, manifest.yaml, etc.)
|
|
44
|
+
// AI-SDLC resources always have an apiVersion field.
|
|
45
|
+
if (!doc || typeof doc !== 'object' || !('apiVersion' in doc) || !('kind' in doc)) {
|
|
33
46
|
continue;
|
|
34
|
-
|
|
35
|
-
const doc = parseYaml(raw);
|
|
47
|
+
}
|
|
36
48
|
const result = validateResource(doc);
|
|
37
49
|
if (!result.valid) {
|
|
38
|
-
const msgs = (result.errors ?? []).map((e) =>
|
|
39
|
-
|
|
50
|
+
const msgs = (result.errors ?? []).map((e) => `${e.path}: ${e.message}`).join('; ');
|
|
51
|
+
// Forward-looking schemas are common during incremental adoption
|
|
52
|
+
// (RFC-0008 §A.4 readers landing in phases). Record + skip rather
|
|
53
|
+
// than throw — callers see the warning, the rest of the config
|
|
54
|
+
// (the parts that DO validate) continues to drive admission.
|
|
55
|
+
warnings.push({ file, error: `validation failed: ${msgs}` });
|
|
56
|
+
continue;
|
|
40
57
|
}
|
|
41
58
|
const resource = result.data;
|
|
42
59
|
if (resource.kind === 'AdapterBinding') {
|
|
43
60
|
(config.adapterBindings ??= []).push(resource);
|
|
44
61
|
}
|
|
62
|
+
else if (resource.kind === 'DesignSystemBinding') {
|
|
63
|
+
(config.designSystemBindings ??= []).push(resource);
|
|
64
|
+
}
|
|
65
|
+
else if (resource.kind === 'DesignIntentDocument') {
|
|
66
|
+
(config.designIntentDocuments ??= []).push(resource);
|
|
67
|
+
}
|
|
68
|
+
else if (resource.kind === 'Pipeline') {
|
|
69
|
+
(config.pipelines ??= []).push(resource);
|
|
70
|
+
// Backward-compat: `config.pipeline` (singular) = the canonical one.
|
|
71
|
+
// Prefer the file literally named `pipeline.yaml` (the default workflow).
|
|
72
|
+
// Fall back to the first loaded otherwise.
|
|
73
|
+
if (file === 'pipeline.yaml' || file === 'pipeline.yml' || !config.pipeline) {
|
|
74
|
+
config.pipeline = resource;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
45
77
|
else {
|
|
46
78
|
const key = KIND_KEY[resource.kind];
|
|
47
79
|
if (key) {
|
|
@@ -50,12 +82,50 @@ export function loadConfig(configDir) {
|
|
|
50
82
|
}
|
|
51
83
|
}
|
|
52
84
|
}
|
|
85
|
+
if (warnings.length > 0) {
|
|
86
|
+
config.warnings = warnings;
|
|
87
|
+
}
|
|
53
88
|
// Backward compat: set adapterBinding to the first binding
|
|
54
89
|
if (config.adapterBindings?.length) {
|
|
55
90
|
config.adapterBinding = config.adapterBindings[0];
|
|
56
91
|
}
|
|
92
|
+
// RFC-0008: validate DID → DSB cross-references
|
|
93
|
+
if (config.designIntentDocuments?.length) {
|
|
94
|
+
validateDesignIntentDocumentReferences(config);
|
|
95
|
+
}
|
|
57
96
|
return config;
|
|
58
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate that every DesignIntentDocument's `spec.designSystemRef.name`
|
|
100
|
+
* resolves to a loaded DesignSystemBinding. Namespace match is required
|
|
101
|
+
* only when both resources declare a namespace (RFC-0008 §4.5).
|
|
102
|
+
*
|
|
103
|
+
* Unidirectional: DID → DSB. DesignSystemBinding surface is not modified.
|
|
104
|
+
*/
|
|
105
|
+
export function validateDesignIntentDocumentReferences(config) {
|
|
106
|
+
const dids = config.designIntentDocuments ?? [];
|
|
107
|
+
const dsbs = config.designSystemBindings ?? [];
|
|
108
|
+
const unresolved = [];
|
|
109
|
+
for (const did of dids) {
|
|
110
|
+
const targetName = did.spec.designSystemRef.name;
|
|
111
|
+
const targetNamespace = did.spec.designSystemRef.namespace;
|
|
112
|
+
const match = dsbs.find((dsb) => {
|
|
113
|
+
if (dsb.metadata.name !== targetName)
|
|
114
|
+
return false;
|
|
115
|
+
if (targetNamespace && dsb.metadata.namespace && dsb.metadata.namespace !== targetNamespace) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
});
|
|
120
|
+
if (!match) {
|
|
121
|
+
const nsSuffix = targetNamespace ? `/${targetNamespace}` : '';
|
|
122
|
+
unresolved.push(` DesignIntentDocument "${did.metadata.name}" references "${targetName}${nsSuffix}" which does not resolve to any loaded DesignSystemBinding`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (unresolved.length > 0) {
|
|
126
|
+
throw new Error(`DID → DesignSystemBinding reference validation failed:\n${unresolved.join('\n')}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
59
129
|
/**
|
|
60
130
|
* Async variant of loadConfig that also scans for local adapter plugins.
|
|
61
131
|
*/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalAdapter per RFC §15.2. Operator-declared shell hooks for proprietary or
|
|
3
|
+
* custom branching mechanisms. Pipeline-load REQUIRES `acknowledgeUntrusted: true`
|
|
4
|
+
* because shell hooks execute with full credential scope.
|
|
5
|
+
*/
|
|
6
|
+
import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
|
|
7
|
+
export interface ExternalAdapterDeps {
|
|
8
|
+
exec?: (cmd: string, args: string[]) => Promise<{
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare class ExternalAdapter implements DatabaseBranchAdapter {
|
|
14
|
+
private readonly deps;
|
|
15
|
+
readonly name: DatabaseAdapterName;
|
|
16
|
+
readonly capabilities: DatabaseBranchCapabilities;
|
|
17
|
+
constructor(deps?: ExternalAdapterDeps);
|
|
18
|
+
validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
|
|
19
|
+
allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
|
|
20
|
+
reclaim(handle: DatabaseBranchHandle): Promise<void>;
|
|
21
|
+
list(_pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
|
|
22
|
+
isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=external.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExternalAdapter per RFC §15.2. Operator-declared shell hooks for proprietary or
|
|
3
|
+
* custom branching mechanisms. Pipeline-load REQUIRES `acknowledgeUntrusted: true`
|
|
4
|
+
* because shell hooks execute with full credential scope.
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { DatabaseBranchAdapterError, } from '../types.js';
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
export class ExternalAdapter {
|
|
11
|
+
deps;
|
|
12
|
+
name = 'external';
|
|
13
|
+
capabilities = {
|
|
14
|
+
branchCreationLatencyP50Ms: 0, // declared by the hook script's behavior
|
|
15
|
+
maxBranches: 0, // declared by operator config
|
|
16
|
+
supportsMigrations: true,
|
|
17
|
+
supportsReadOnlyBranches: false,
|
|
18
|
+
supportsBranchFromBranch: true,
|
|
19
|
+
multiDatabase: true,
|
|
20
|
+
costModel: 'free',
|
|
21
|
+
};
|
|
22
|
+
constructor(deps = {}) {
|
|
23
|
+
this.deps = deps;
|
|
24
|
+
}
|
|
25
|
+
async validate(pool) {
|
|
26
|
+
const creds = pool.credentials ?? {};
|
|
27
|
+
if (creds.acknowledgeUntrusted !== true) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: 'external adapter requires credentials.acknowledgeUntrusted: true. Operator-controlled shell hooks execute with full credential scope; this opt-in is intentional friction.',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (typeof creds.allocateCommand !== 'string' || !creds.allocateCommand) {
|
|
34
|
+
return { ok: false, error: 'credentials.allocateCommand (string) is required' };
|
|
35
|
+
}
|
|
36
|
+
if (typeof creds.reclaimCommand !== 'string' || !creds.reclaimCommand) {
|
|
37
|
+
return { ok: false, error: 'credentials.reclaimCommand (string) is required' };
|
|
38
|
+
}
|
|
39
|
+
return { ok: true };
|
|
40
|
+
}
|
|
41
|
+
async allocate(pool, branchKey) {
|
|
42
|
+
const cmd = pool.credentials?.allocateCommand;
|
|
43
|
+
const exec = this.deps.exec ?? ((c, a) => execFileAsync(c, a));
|
|
44
|
+
let stdout;
|
|
45
|
+
try {
|
|
46
|
+
const result = await exec('sh', ['-c', `${cmd} ${branchKey}`]);
|
|
47
|
+
stdout = result.stdout;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
throw new DatabaseBranchAdapterError(`external allocate hook failed for branch ${branchKey}: ${err.message}`, this.name, branchKey, err);
|
|
51
|
+
}
|
|
52
|
+
const connectionString = stdout.trim();
|
|
53
|
+
if (!connectionString) {
|
|
54
|
+
throw new DatabaseBranchAdapterError(`external allocate hook produced empty stdout (expected connection string)`, this.name, branchKey);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
branchKey,
|
|
58
|
+
connectionString,
|
|
59
|
+
createdAt: new Date(),
|
|
60
|
+
upstream: pool.upstream.branchFrom ?? 'external',
|
|
61
|
+
metadata: { hook: 'external' },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async reclaim(handle) {
|
|
65
|
+
// The pool isn't passed here; the orchestrator MUST track (handle, pool) pairs and
|
|
66
|
+
// call this via the dispatcher. For test purposes, callers that use ExternalAdapter
|
|
67
|
+
// directly must provide their own dispatcher that supplies the reclaim command.
|
|
68
|
+
void handle;
|
|
69
|
+
}
|
|
70
|
+
async list(_pool) {
|
|
71
|
+
// External hooks don't expose a list mechanism; orchestrator tracks active branches
|
|
72
|
+
// in its own state.
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
async isAvailable(pool) {
|
|
76
|
+
const v = await this.validate(pool);
|
|
77
|
+
return v.ok;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=external.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeonAdapter per RFC §15.2. Wraps Neon REST API for branch lifecycle. Phase 6 ships
|
|
3
|
+
* the adapter shell + capability matrix + validation; the actual REST calls (allocate,
|
|
4
|
+
* reclaim, list) are stubbed until end-to-end integration testing against a real Neon
|
|
5
|
+
* sandbox project lands as a follow-up.
|
|
6
|
+
*
|
|
7
|
+
* Operators wire this up via:
|
|
8
|
+
* credentials:
|
|
9
|
+
* apiTokenEnv: NEON_API_TOKEN
|
|
10
|
+
* projectId: prj_abc123
|
|
11
|
+
*/
|
|
12
|
+
import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
|
|
13
|
+
export interface NeonAdapterDeps {
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
/** Override for tests; replaces the actual fetch calls. */
|
|
16
|
+
api?: {
|
|
17
|
+
createBranch: (projectId: string, branchName: string, parentBranch: string) => Promise<{
|
|
18
|
+
branchId: string;
|
|
19
|
+
connectionString: string;
|
|
20
|
+
}>;
|
|
21
|
+
deleteBranch: (projectId: string, branchId: string) => Promise<void>;
|
|
22
|
+
listBranches: (projectId: string) => Promise<Array<{
|
|
23
|
+
branchId: string;
|
|
24
|
+
branchName: string;
|
|
25
|
+
parent: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}>>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export declare class NeonAdapter implements DatabaseBranchAdapter {
|
|
31
|
+
private readonly deps;
|
|
32
|
+
readonly name: DatabaseAdapterName;
|
|
33
|
+
readonly capabilities: DatabaseBranchCapabilities;
|
|
34
|
+
constructor(deps?: NeonAdapterDeps);
|
|
35
|
+
validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
|
|
36
|
+
allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
|
|
37
|
+
reclaim(handle: DatabaseBranchHandle): Promise<void>;
|
|
38
|
+
list(pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
|
|
39
|
+
isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=neon.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeonAdapter per RFC §15.2. Wraps Neon REST API for branch lifecycle. Phase 6 ships
|
|
3
|
+
* the adapter shell + capability matrix + validation; the actual REST calls (allocate,
|
|
4
|
+
* reclaim, list) are stubbed until end-to-end integration testing against a real Neon
|
|
5
|
+
* sandbox project lands as a follow-up.
|
|
6
|
+
*
|
|
7
|
+
* Operators wire this up via:
|
|
8
|
+
* credentials:
|
|
9
|
+
* apiTokenEnv: NEON_API_TOKEN
|
|
10
|
+
* projectId: prj_abc123
|
|
11
|
+
*/
|
|
12
|
+
import { DatabaseBranchAdapterError, } from '../types.js';
|
|
13
|
+
export class NeonAdapter {
|
|
14
|
+
deps;
|
|
15
|
+
name = 'neon';
|
|
16
|
+
capabilities = {
|
|
17
|
+
branchCreationLatencyP50Ms: 1500,
|
|
18
|
+
maxBranches: 5000,
|
|
19
|
+
supportsMigrations: true,
|
|
20
|
+
supportsReadOnlyBranches: true,
|
|
21
|
+
supportsBranchFromBranch: true,
|
|
22
|
+
multiDatabase: false,
|
|
23
|
+
costModel: 'per-branch-storage',
|
|
24
|
+
};
|
|
25
|
+
constructor(deps = {}) {
|
|
26
|
+
this.deps = deps;
|
|
27
|
+
}
|
|
28
|
+
async validate(pool) {
|
|
29
|
+
const env = this.deps.env ?? process.env;
|
|
30
|
+
const creds = pool.credentials ?? {};
|
|
31
|
+
const apiTokenEnv = creds.apiTokenEnv;
|
|
32
|
+
const projectId = creds.projectId;
|
|
33
|
+
if (!apiTokenEnv) {
|
|
34
|
+
return { ok: false, error: 'credentials.apiTokenEnv is required for neon adapter' };
|
|
35
|
+
}
|
|
36
|
+
if (!env[apiTokenEnv]) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
error: `${apiTokenEnv} is not set in env; cannot authenticate against Neon API`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!projectId) {
|
|
43
|
+
return { ok: false, error: 'credentials.projectId is required for neon adapter' };
|
|
44
|
+
}
|
|
45
|
+
if (!pool.upstream.branchFrom) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: 'upstream.branchFrom is required for neon (named branch to fork from)',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|
|
53
|
+
async allocate(pool, branchKey) {
|
|
54
|
+
if (!this.deps.api) {
|
|
55
|
+
throw new DatabaseBranchAdapterError('NeonAdapter.allocate is not wired against the live Neon REST API yet (Phase 6 follow-up). ' +
|
|
56
|
+
'Tests should inject deps.api; production deployments require the integration test pass before use.', this.name, branchKey);
|
|
57
|
+
}
|
|
58
|
+
const projectId = pool.credentials?.projectId;
|
|
59
|
+
const parent = pool.upstream.branchFrom;
|
|
60
|
+
const result = await this.deps.api.createBranch(projectId, branchKey, parent);
|
|
61
|
+
return {
|
|
62
|
+
branchKey,
|
|
63
|
+
connectionString: result.connectionString,
|
|
64
|
+
createdAt: new Date(),
|
|
65
|
+
upstream: parent,
|
|
66
|
+
metadata: { branchId: result.branchId, projectId },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async reclaim(handle) {
|
|
70
|
+
if (!this.deps.api) {
|
|
71
|
+
throw new DatabaseBranchAdapterError('NeonAdapter.reclaim not wired (Phase 6 follow-up)', this.name, handle.branchKey);
|
|
72
|
+
}
|
|
73
|
+
const projectId = handle.metadata.projectId;
|
|
74
|
+
const branchId = handle.metadata.branchId;
|
|
75
|
+
if (!projectId || !branchId) {
|
|
76
|
+
throw new DatabaseBranchAdapterError(`handle missing projectId/branchId in metadata: ${JSON.stringify(handle.metadata)}`, this.name, handle.branchKey);
|
|
77
|
+
}
|
|
78
|
+
await this.deps.api.deleteBranch(projectId, branchId);
|
|
79
|
+
}
|
|
80
|
+
async list(pool) {
|
|
81
|
+
if (!this.deps.api)
|
|
82
|
+
return [];
|
|
83
|
+
const projectId = pool.credentials?.projectId;
|
|
84
|
+
const branches = await this.deps.api.listBranches(projectId);
|
|
85
|
+
return branches.map((b) => ({
|
|
86
|
+
branchKey: b.branchName,
|
|
87
|
+
connectionString: '<masked — re-fetch via API>',
|
|
88
|
+
createdAt: new Date(b.createdAt),
|
|
89
|
+
upstream: b.parent,
|
|
90
|
+
metadata: { projectId, branchId: b.branchId },
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
async isAvailable(pool) {
|
|
94
|
+
const v = await this.validate(pool);
|
|
95
|
+
return v.ok;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=neon.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PgSnapshotRestoreAdapter per RFC §15.2. For vanilla Postgres or AWS RDS — uses
|
|
3
|
+
* `pg_dump` + `pg_restore` for vanilla, or RDS snapshot/restore APIs for AWS.
|
|
4
|
+
*
|
|
5
|
+
* Phase 6 ships the adapter shell with capability matrix + validation. End-to-end
|
|
6
|
+
* integration test against local Postgres in CI is a follow-up.
|
|
7
|
+
*/
|
|
8
|
+
import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
|
|
9
|
+
export interface PgSnapshotRestoreAdapterDeps {
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
/** Override for tests; mocks the dump+restore execution. */
|
|
12
|
+
exec?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => Promise<{
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare class PgSnapshotRestoreAdapter implements DatabaseBranchAdapter {
|
|
18
|
+
private readonly deps;
|
|
19
|
+
readonly name: DatabaseAdapterName;
|
|
20
|
+
readonly capabilities: DatabaseBranchCapabilities;
|
|
21
|
+
constructor(deps?: PgSnapshotRestoreAdapterDeps);
|
|
22
|
+
validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
|
|
23
|
+
allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
|
|
24
|
+
reclaim(handle: DatabaseBranchHandle): Promise<void>;
|
|
25
|
+
list(_pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
|
|
26
|
+
isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=pg-snapshot-restore.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PgSnapshotRestoreAdapter per RFC §15.2. For vanilla Postgres or AWS RDS — uses
|
|
3
|
+
* `pg_dump` + `pg_restore` for vanilla, or RDS snapshot/restore APIs for AWS.
|
|
4
|
+
*
|
|
5
|
+
* Phase 6 ships the adapter shell with capability matrix + validation. End-to-end
|
|
6
|
+
* integration test against local Postgres in CI is a follow-up.
|
|
7
|
+
*/
|
|
8
|
+
import { DatabaseBranchAdapterError, } from '../types.js';
|
|
9
|
+
export class PgSnapshotRestoreAdapter {
|
|
10
|
+
deps;
|
|
11
|
+
name = 'pg-snapshot-restore';
|
|
12
|
+
capabilities = {
|
|
13
|
+
branchCreationLatencyP50Ms: 30_000,
|
|
14
|
+
maxBranches: 100,
|
|
15
|
+
supportsMigrations: true,
|
|
16
|
+
supportsReadOnlyBranches: false,
|
|
17
|
+
supportsBranchFromBranch: false,
|
|
18
|
+
multiDatabase: false,
|
|
19
|
+
costModel: 'per-snapshot',
|
|
20
|
+
};
|
|
21
|
+
constructor(deps = {}) {
|
|
22
|
+
this.deps = deps;
|
|
23
|
+
}
|
|
24
|
+
async validate(pool) {
|
|
25
|
+
const env = this.deps.env ?? process.env;
|
|
26
|
+
const creds = pool.credentials ?? {};
|
|
27
|
+
const adminEnv = creds.adminConnectionStringEnv;
|
|
28
|
+
const storageVolume = creds.storageVolume;
|
|
29
|
+
if (!adminEnv) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: 'credentials.adminConnectionStringEnv is required (privileged user with CREATEDB)',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (!env[adminEnv]) {
|
|
36
|
+
return { ok: false, error: `${adminEnv} not set in env` };
|
|
37
|
+
}
|
|
38
|
+
if (!storageVolume) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
error: 'credentials.storageVolume is required (where snapshots are restored)',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { ok: true };
|
|
45
|
+
}
|
|
46
|
+
async allocate(pool, branchKey) {
|
|
47
|
+
if (!this.deps.exec) {
|
|
48
|
+
throw new DatabaseBranchAdapterError('PgSnapshotRestoreAdapter.allocate not wired (Phase 6 follow-up against fixture Postgres in CI)', this.name, branchKey);
|
|
49
|
+
}
|
|
50
|
+
// Placeholder — actual implementation: pg_dump from upstream, createdb branch_<key>, pg_restore into branch.
|
|
51
|
+
throw new DatabaseBranchAdapterError('PgSnapshotRestoreAdapter integration pending', this.name, branchKey);
|
|
52
|
+
}
|
|
53
|
+
async reclaim(handle) {
|
|
54
|
+
if (!this.deps.exec)
|
|
55
|
+
return;
|
|
56
|
+
// Placeholder — actual: dropdb branch_<key>
|
|
57
|
+
void handle;
|
|
58
|
+
}
|
|
59
|
+
async list(_pool) {
|
|
60
|
+
// Placeholder — actual: query pg_database for branch_-prefixed databases.
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
async isAvailable(pool) {
|
|
64
|
+
const v = await this.validate(pool);
|
|
65
|
+
return v.ok;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=pg-snapshot-restore.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SqliteCopyAdapter per RFC §15.2. Copies the upstream `.sqlite` file to a per-branch
|
|
3
|
+
* file in the worktree. Hardlinks for read-only branches; full copy for write/migrate.
|
|
4
|
+
*
|
|
5
|
+
* Connection-string format (the orchestrator's existing convention): plain absolute
|
|
6
|
+
* file path, optionally prefixed with `file://`.
|
|
7
|
+
*/
|
|
8
|
+
import { type DatabaseBranchAdapter, type DatabaseBranchCapabilities, type DatabaseBranchHandle, type DatabaseAdapterName, type ResolvedDatabaseBranchPool, type ValidationResult } from '../types.js';
|
|
9
|
+
export interface SqliteCopyAdapterDeps {
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
/** Override the per-branch storage root for tests. Defaults to `<cwd>/.ai-sdlc/db`. */
|
|
12
|
+
storageRoot?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class SqliteCopyAdapter implements DatabaseBranchAdapter {
|
|
15
|
+
private readonly deps;
|
|
16
|
+
readonly name: DatabaseAdapterName;
|
|
17
|
+
readonly capabilities: DatabaseBranchCapabilities;
|
|
18
|
+
constructor(deps?: SqliteCopyAdapterDeps);
|
|
19
|
+
private storageRootFor;
|
|
20
|
+
validate(pool: ResolvedDatabaseBranchPool): Promise<ValidationResult>;
|
|
21
|
+
allocate(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
|
|
22
|
+
/**
|
|
23
|
+
* Read-only branch via hardlink (saves disk space when many readers share the same
|
|
24
|
+
* upstream snapshot). Caller is responsible for never opening the resulting file
|
|
25
|
+
* for write — SQLite will happily corrupt the hardlinked upstream if treated as RW.
|
|
26
|
+
*/
|
|
27
|
+
allocateReadOnly(pool: ResolvedDatabaseBranchPool, branchKey: string): Promise<DatabaseBranchHandle>;
|
|
28
|
+
reclaim(handle: DatabaseBranchHandle): Promise<void>;
|
|
29
|
+
list(pool: ResolvedDatabaseBranchPool): Promise<DatabaseBranchHandle[]>;
|
|
30
|
+
isAvailable(pool: ResolvedDatabaseBranchPool): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=sqlite-copy.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SqliteCopyAdapter per RFC §15.2. Copies the upstream `.sqlite` file to a per-branch
|
|
3
|
+
* file in the worktree. Hardlinks for read-only branches; full copy for write/migrate.
|
|
4
|
+
*
|
|
5
|
+
* Connection-string format (the orchestrator's existing convention): plain absolute
|
|
6
|
+
* file path, optionally prefixed with `file://`.
|
|
7
|
+
*/
|
|
8
|
+
import { copyFile, link, mkdir, readdir, rm, stat } from 'node:fs/promises';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import { DatabaseBranchAdapterError, } from '../types.js';
|
|
11
|
+
export class SqliteCopyAdapter {
|
|
12
|
+
deps;
|
|
13
|
+
name = 'sqlite-copy';
|
|
14
|
+
capabilities = {
|
|
15
|
+
branchCreationLatencyP50Ms: 50,
|
|
16
|
+
maxBranches: 10_000,
|
|
17
|
+
supportsMigrations: true,
|
|
18
|
+
supportsReadOnlyBranches: true,
|
|
19
|
+
supportsBranchFromBranch: true,
|
|
20
|
+
multiDatabase: false,
|
|
21
|
+
costModel: 'free',
|
|
22
|
+
};
|
|
23
|
+
constructor(deps = {}) {
|
|
24
|
+
this.deps = deps;
|
|
25
|
+
}
|
|
26
|
+
storageRootFor(_pool) {
|
|
27
|
+
return this.deps.storageRoot ?? join(process.cwd(), '.ai-sdlc', 'db');
|
|
28
|
+
}
|
|
29
|
+
async validate(pool) {
|
|
30
|
+
const env = this.deps.env ?? process.env;
|
|
31
|
+
const upstreamPath = env[pool.upstream.connectionStringEnv];
|
|
32
|
+
if (!upstreamPath) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: `${pool.upstream.connectionStringEnv} is not set in env; cannot locate upstream SQLite file`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const s = await stat(stripFileScheme(upstreamPath));
|
|
40
|
+
if (!s.isFile()) {
|
|
41
|
+
return { ok: false, error: `upstream SQLite path is not a file: ${upstreamPath}` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { ok: false, error: `upstream SQLite file not found: ${upstreamPath}` };
|
|
46
|
+
}
|
|
47
|
+
return { ok: true };
|
|
48
|
+
}
|
|
49
|
+
async allocate(pool, branchKey) {
|
|
50
|
+
const env = this.deps.env ?? process.env;
|
|
51
|
+
const upstreamRaw = env[pool.upstream.connectionStringEnv];
|
|
52
|
+
if (!upstreamRaw) {
|
|
53
|
+
throw new DatabaseBranchAdapterError(`${pool.upstream.connectionStringEnv} not set`, this.name, branchKey);
|
|
54
|
+
}
|
|
55
|
+
const upstreamPath = stripFileScheme(upstreamRaw);
|
|
56
|
+
const branchPath = join(this.storageRootFor(pool), `${branchKey}.sqlite`);
|
|
57
|
+
await mkdir(dirname(branchPath), { recursive: true });
|
|
58
|
+
try {
|
|
59
|
+
await copyFile(upstreamPath, branchPath);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw new DatabaseBranchAdapterError(`failed to copy SQLite database for branch '${branchKey}': ${err.message}`, this.name, branchKey, err);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
branchKey,
|
|
66
|
+
connectionString: `file://${branchPath}`,
|
|
67
|
+
createdAt: new Date(),
|
|
68
|
+
upstream: upstreamPath,
|
|
69
|
+
metadata: { branchPath, mode: 'copy' },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read-only branch via hardlink (saves disk space when many readers share the same
|
|
74
|
+
* upstream snapshot). Caller is responsible for never opening the resulting file
|
|
75
|
+
* for write — SQLite will happily corrupt the hardlinked upstream if treated as RW.
|
|
76
|
+
*/
|
|
77
|
+
async allocateReadOnly(pool, branchKey) {
|
|
78
|
+
const env = this.deps.env ?? process.env;
|
|
79
|
+
const upstreamRaw = env[pool.upstream.connectionStringEnv];
|
|
80
|
+
if (!upstreamRaw) {
|
|
81
|
+
throw new DatabaseBranchAdapterError(`${pool.upstream.connectionStringEnv} not set`, this.name, branchKey);
|
|
82
|
+
}
|
|
83
|
+
const upstreamPath = stripFileScheme(upstreamRaw);
|
|
84
|
+
const branchPath = join(this.storageRootFor(pool), `${branchKey}.ro.sqlite`);
|
|
85
|
+
await mkdir(dirname(branchPath), { recursive: true });
|
|
86
|
+
try {
|
|
87
|
+
await link(upstreamPath, branchPath);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Fall back to a copy if hardlink fails (e.g., across filesystems).
|
|
91
|
+
await copyFile(upstreamPath, branchPath);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
branchKey,
|
|
95
|
+
connectionString: `file://${branchPath}?mode=ro`,
|
|
96
|
+
createdAt: new Date(),
|
|
97
|
+
upstream: upstreamPath,
|
|
98
|
+
metadata: { branchPath, mode: 'hardlink-ro' },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async reclaim(handle) {
|
|
102
|
+
const path = handle.metadata.branchPath;
|
|
103
|
+
if (!path)
|
|
104
|
+
return;
|
|
105
|
+
await rm(path, { force: true });
|
|
106
|
+
}
|
|
107
|
+
async list(pool) {
|
|
108
|
+
const root = this.storageRootFor(pool);
|
|
109
|
+
let entries;
|
|
110
|
+
try {
|
|
111
|
+
entries = await readdir(root);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err.code === 'ENOENT')
|
|
115
|
+
return [];
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
const handles = [];
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (!entry.endsWith('.sqlite'))
|
|
121
|
+
continue;
|
|
122
|
+
const branchPath = join(root, entry);
|
|
123
|
+
const s = await stat(branchPath);
|
|
124
|
+
const branchKey = entry.replace(/\.(?:ro\.)?sqlite$/, '');
|
|
125
|
+
handles.push({
|
|
126
|
+
branchKey,
|
|
127
|
+
connectionString: `file://${branchPath}`,
|
|
128
|
+
createdAt: new Date(s.birthtimeMs || s.mtimeMs),
|
|
129
|
+
upstream: 'unknown',
|
|
130
|
+
metadata: { branchPath, mode: entry.includes('.ro.') ? 'hardlink-ro' : 'copy' },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return handles;
|
|
134
|
+
}
|
|
135
|
+
async isAvailable(pool) {
|
|
136
|
+
const validation = await this.validate(pool);
|
|
137
|
+
return validation.ok;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function stripFileScheme(path) {
|
|
141
|
+
if (path.startsWith('file://'))
|
|
142
|
+
return path.slice('file://'.length);
|
|
143
|
+
return path;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=sqlite-copy.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection-string injection per RFC §15.6. The agent sees the per-worktree branch
|
|
3
|
+
* via the same env-var name (`DATABASE_URL` etc.) the application expects. Adapters
|
|
4
|
+
* provide the rewritten string; this module derives optional component env vars
|
|
5
|
+
* (PGHOST, PGDATABASE, etc.) from it.
|
|
6
|
+
*/
|
|
7
|
+
import type { DatabaseBranchHandle, ResolvedDatabaseBranchPool } from './types.js';
|
|
8
|
+
export interface InjectedEnvOverrides {
|
|
9
|
+
[envName: string]: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build the env-var overlay for a single (handle, pool) pair. The agent's process
|
|
13
|
+
* env is the orchestrator's env merged with this overlay.
|
|
14
|
+
*
|
|
15
|
+
* Component derivation supports postgres:// connection strings — for non-Postgres
|
|
16
|
+
* URLs (e.g., sqlite file paths), additionalEnvs is silently ignored.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildInjectionOverrides(handle: DatabaseBranchHandle, pool: ResolvedDatabaseBranchPool): InjectedEnvOverrides;
|
|
19
|
+
interface ConnectionComponents {
|
|
20
|
+
host: string;
|
|
21
|
+
port: string;
|
|
22
|
+
database: string;
|
|
23
|
+
user: string;
|
|
24
|
+
password: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function parsePostgresUrl(url: string): ConnectionComponents | null;
|
|
27
|
+
/**
|
|
28
|
+
* Mask a connection string for safe logging. Replaces the password segment with
|
|
29
|
+
* `****`. Per RFC §15.1 the connection string itself MUST NEVER appear in any
|
|
30
|
+
* observability output — this helper exists for adapter error messages where
|
|
31
|
+
* the URL form might leak.
|
|
32
|
+
*/
|
|
33
|
+
export declare function maskConnectionString(url: string): string;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=connection-injection.d.ts.map
|