@treeseed/sdk 0.12.16 → 0.12.18
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/hosting/builtins.js +1 -1
- package/dist/reconcile/builtin-adapters.js +1 -1
- package/dist/reconcile/engine.js +5 -5
- package/dist/reconcile/state.d.ts +2 -2
- package/dist/reconcile/state.js +4 -4
- package/dist/workflow/operations.d.ts +15 -1
- package/dist/workflow/operations.js +51 -4
- package/package.json +1 -1
package/dist/hosting/builtins.js
CHANGED
|
@@ -54,7 +54,7 @@ function defaultVerify(input) {
|
|
|
54
54
|
issues: []
|
|
55
55
|
}
|
|
56
56
|
];
|
|
57
|
-
if (input.unit.host.id === "railway" && input.environment === "prod" && unitConfig(input).sourceMode === "image") {
|
|
57
|
+
if (input.unit.host.id === "railway" && input.environment === "prod" && unitConfig(input).sourceMode === "image" && input.unit.serviceType.id !== "relational-database" && unitConfig(input).resourceType !== "postgres") {
|
|
58
58
|
const imageRef = unitConfig(input).imageRef;
|
|
59
59
|
const hasImageRef = typeof imageRef === "string" && imageRef.trim().length > 0;
|
|
60
60
|
checks.push({
|
|
@@ -2134,7 +2134,7 @@ function getPersistedCustomDomainState(input, provider, domain) {
|
|
|
2134
2134
|
}
|
|
2135
2135
|
if (provider === "railway") {
|
|
2136
2136
|
try {
|
|
2137
|
-
const state = loadTreeseedReconcileState(input.context.tenantRoot, input.context.target);
|
|
2137
|
+
const state = loadTreeseedReconcileState(input.context.tenantRoot, input.context.target, input.context.launchEnv);
|
|
2138
2138
|
const unitId = createTreeseedReconcileUnitId("custom-domain:api", domain);
|
|
2139
2139
|
const unit = state.units[unitId];
|
|
2140
2140
|
const reconciled = unit?.lastReconciledState;
|
package/dist/reconcile/engine.js
CHANGED
|
@@ -204,7 +204,7 @@ async function refreshTreeseedUnits({
|
|
|
204
204
|
const units = filterUnitsBySelector(baseUnits, selector);
|
|
205
205
|
const deployConfig = derived.deployConfig;
|
|
206
206
|
const registry = createTreeseedReconcileRegistry(deployConfig);
|
|
207
|
-
const reconcileState = loadTreeseedReconcileState(tenantRoot, target);
|
|
207
|
+
const reconcileState = loadTreeseedReconcileState(tenantRoot, target, env);
|
|
208
208
|
const context = createRunContext(tenantRoot, target, env, write);
|
|
209
209
|
const observations = /* @__PURE__ */ new Map();
|
|
210
210
|
await runByDependencyLevel(topologicallySortDesiredUnits(units), async (unit) => {
|
|
@@ -294,7 +294,7 @@ async function reconcileTreeseedTarget({
|
|
|
294
294
|
const persistVerifiedResult = async (persisted, verifiedResult) => {
|
|
295
295
|
persistChain = persistChain.then(() => {
|
|
296
296
|
persistResult(planned.state, persisted, verifiedResult);
|
|
297
|
-
writeTreeseedReconcileState(tenantRoot, planned.state);
|
|
297
|
+
writeTreeseedReconcileState(tenantRoot, planned.state, env);
|
|
298
298
|
});
|
|
299
299
|
await persistChain;
|
|
300
300
|
};
|
|
@@ -458,7 +458,7 @@ async function reconcileTreeseedTarget({
|
|
|
458
458
|
results.push(verifiedResult);
|
|
459
459
|
});
|
|
460
460
|
if (!dryRun) {
|
|
461
|
-
writeTreeseedReconcileState(tenantRoot, planned.state);
|
|
461
|
+
writeTreeseedReconcileState(tenantRoot, planned.state, env);
|
|
462
462
|
}
|
|
463
463
|
return {
|
|
464
464
|
target,
|
|
@@ -477,10 +477,10 @@ async function destroyTreeseedTargetUnits({
|
|
|
477
477
|
units: explicitUnits,
|
|
478
478
|
write
|
|
479
479
|
}) {
|
|
480
|
-
const { units: allUnits, deployConfig } = deriveTreeseedDesiredUnits({ tenantRoot, target });
|
|
480
|
+
const { units: allUnits, deployConfig } = deriveTreeseedDesiredUnits({ tenantRoot, target, env });
|
|
481
481
|
const units = filterUnitsBySelector(explicitUnits ?? allUnits, selector);
|
|
482
482
|
const registry = createTreeseedReconcileRegistry(deployConfig);
|
|
483
|
-
const reconcileState = loadTreeseedReconcileState(tenantRoot, target);
|
|
483
|
+
const reconcileState = loadTreeseedReconcileState(tenantRoot, target, env);
|
|
484
484
|
const context = createRunContext(tenantRoot, target, env, write);
|
|
485
485
|
const results = [];
|
|
486
486
|
for (const unit of reverseTopologicallySortedUnits(units)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TreeseedDesiredUnit, TreeseedReconcileStateRecord, TreeseedReconcileTarget, TreeseedUnitPersistedState } from './contracts.js';
|
|
2
2
|
export declare function migrateLegacyDeployStateUnits(legacyState: Record<string, any>, target: TreeseedReconcileTarget): Record<string, TreeseedUnitPersistedState>;
|
|
3
|
-
export declare function loadTreeseedReconcileState(tenantRoot: string, target: TreeseedReconcileTarget): TreeseedReconcileStateRecord;
|
|
4
|
-
export declare function writeTreeseedReconcileState(tenantRoot: string, reconcileState: TreeseedReconcileStateRecord): void;
|
|
3
|
+
export declare function loadTreeseedReconcileState(tenantRoot: string, target: TreeseedReconcileTarget, env?: NodeJS.ProcessEnv | Record<string, string | undefined>): TreeseedReconcileStateRecord;
|
|
4
|
+
export declare function writeTreeseedReconcileState(tenantRoot: string, reconcileState: TreeseedReconcileStateRecord, env?: NodeJS.ProcessEnv | Record<string, string | undefined>): void;
|
|
5
5
|
export declare function ensureTreeseedPersistedUnitState(reconcileState: TreeseedReconcileStateRecord, unit: TreeseedDesiredUnit): TreeseedUnitPersistedState;
|
|
6
6
|
export declare function updateTreeseedPersistedUnitState(reconcileState: TreeseedReconcileStateRecord, state: TreeseedUnitPersistedState): void;
|
|
7
7
|
export declare function desiredUnitSpecHash(unit: TreeseedDesiredUnit): string;
|
package/dist/reconcile/state.js
CHANGED
|
@@ -227,8 +227,8 @@ function migrateLegacyDeployStateUnits(legacyState, target) {
|
|
|
227
227
|
}
|
|
228
228
|
return units;
|
|
229
229
|
}
|
|
230
|
-
function loadTreeseedReconcileState(tenantRoot, target) {
|
|
231
|
-
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot, environment: target.kind === "persistent" ? target.scope : "staging", env
|
|
230
|
+
function loadTreeseedReconcileState(tenantRoot, target, env = process.env) {
|
|
231
|
+
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot, environment: target.kind === "persistent" ? target.scope : "staging", env }).deployConfig;
|
|
232
232
|
const legacyState = loadDeployState(tenantRoot, deployConfig, { target });
|
|
233
233
|
const persistedUnits = legacyState.units && typeof legacyState.units === "object" ? legacyState.units : migrateLegacyDeployStateUnits(legacyState, target);
|
|
234
234
|
return {
|
|
@@ -238,8 +238,8 @@ function loadTreeseedReconcileState(tenantRoot, target) {
|
|
|
238
238
|
units: { ...persistedUnits }
|
|
239
239
|
};
|
|
240
240
|
}
|
|
241
|
-
function writeTreeseedReconcileState(tenantRoot, reconcileState) {
|
|
242
|
-
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot, environment: reconcileState.target.kind === "persistent" ? reconcileState.target.scope : "staging", env
|
|
241
|
+
function writeTreeseedReconcileState(tenantRoot, reconcileState, env = process.env) {
|
|
242
|
+
const deployConfig = loadTreeseedPlatformConfig({ tenantRoot, environment: reconcileState.target.kind === "persistent" ? reconcileState.target.scope : "staging", env }).deployConfig;
|
|
243
243
|
const legacyState = loadDeployState(tenantRoot, deployConfig, { target: reconcileState.target });
|
|
244
244
|
writeDeployState(tenantRoot, {
|
|
245
245
|
...legacyState,
|
|
@@ -12,7 +12,7 @@ import type { TreeseedCloseInput, TreeseedCiInput, TreeseedConfigInput, Treeseed
|
|
|
12
12
|
type WorkflowWrite = NonNullable<TreeseedWorkflowContext['write']>;
|
|
13
13
|
type WorkflowStatePayload = ReturnType<typeof resolveTreeseedWorkflowState>;
|
|
14
14
|
type ReleaseCandidateMode = TreeseedReleaseCandidateMode;
|
|
15
|
-
export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state' | 'workflow_locked' | 'resume_unavailable' | 'workflow_contract_missing' | 'github_workflow_failed' | 'github_auth_unavailable' | 'hosted_reconcile_failed' | 'hosted_live_verification_failed';
|
|
15
|
+
export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state' | 'workflow_locked' | 'resume_unavailable' | 'workflow_contract_missing' | 'github_workflow_failed' | 'github_auth_unavailable' | 'release_gate_failed' | 'hosted_reconcile_failed' | 'hosted_live_verification_failed';
|
|
16
16
|
export declare class TreeseedWorkflowError extends Error {
|
|
17
17
|
code: TreeseedWorkflowErrorCode;
|
|
18
18
|
operation: TreeseedWorkflowOperationId;
|
|
@@ -1153,6 +1153,20 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
1153
1153
|
liveVerification: import("../workflow-support.js").TreeseedLiveHostedServiceCheckReport;
|
|
1154
1154
|
reason?: undefined;
|
|
1155
1155
|
};
|
|
1156
|
+
productionApiGuarantees: {
|
|
1157
|
+
ok: true;
|
|
1158
|
+
environment: string;
|
|
1159
|
+
runId: string;
|
|
1160
|
+
outputRoot: string;
|
|
1161
|
+
counts: {
|
|
1162
|
+
planned: number;
|
|
1163
|
+
passed: number;
|
|
1164
|
+
failed: number;
|
|
1165
|
+
skipped: number;
|
|
1166
|
+
blocked: number;
|
|
1167
|
+
releaseBlockingFailures: number;
|
|
1168
|
+
};
|
|
1169
|
+
};
|
|
1156
1170
|
backMerge: {
|
|
1157
1171
|
packages: {
|
|
1158
1172
|
status: string;
|
|
@@ -159,6 +159,7 @@ import {
|
|
|
159
159
|
} from "./session.js";
|
|
160
160
|
import { checkedOutManagedWorkflowRepos } from "../operations/services/managed-repositories.js";
|
|
161
161
|
import { runTreeseedLocalCleanup } from "../operations/services/local-cleanup.js";
|
|
162
|
+
import { runTreeseedGuarantees } from "../guarantees/index.js";
|
|
162
163
|
import {
|
|
163
164
|
classifyTreeseedBranchRole,
|
|
164
165
|
resolveTreeseedWorkflowPaths
|
|
@@ -633,6 +634,49 @@ function productionReleaseImageRefEnv(selectedVersions) {
|
|
|
633
634
|
}
|
|
634
635
|
return refs;
|
|
635
636
|
}
|
|
637
|
+
async function runReleaseApiGuarantees(root, environment, helpers, operation, sceneArtifacts) {
|
|
638
|
+
const env = {
|
|
639
|
+
...helpers.context.env,
|
|
640
|
+
...collectTreeseedConfigSeedValues(root, environment, helpers.context.env)
|
|
641
|
+
};
|
|
642
|
+
env.TREESEED_ACCEPTANCE_SERVICE_ID ??= env.TREESEED_API_WEB_SERVICE_ID ?? env.TREESEED_WEB_SERVICE_ID;
|
|
643
|
+
env.TREESEED_ACCEPTANCE_SERVICE_SECRET ??= env.TREESEED_API_WEB_SERVICE_SECRET ?? env.TREESEED_WEB_SERVICE_SECRET;
|
|
644
|
+
if (!env.TREESEED_ACCEPTANCE_SERVICE_ID || !env.TREESEED_ACCEPTANCE_SERVICE_SECRET) {
|
|
645
|
+
workflowError(operation, "release_gate_failed", `${environment} API release guarantees cannot run because API acceptance service credentials are missing.`, {
|
|
646
|
+
details: {
|
|
647
|
+
environment,
|
|
648
|
+
missing: [
|
|
649
|
+
!env.TREESEED_ACCEPTANCE_SERVICE_ID ? "TREESEED_ACCEPTANCE_SERVICE_ID" : null,
|
|
650
|
+
!env.TREESEED_ACCEPTANCE_SERVICE_SECRET ? "TREESEED_ACCEPTANCE_SERVICE_SECRET" : null
|
|
651
|
+
].filter((value) => Boolean(value))
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
helpers.write(`[${operation}][workflow] Running ${environment} API release guarantees before root deployment.`);
|
|
656
|
+
return await withContextEnv(env, async () => {
|
|
657
|
+
const report = await runTreeseedGuarantees({
|
|
658
|
+
workspaceRoot: root,
|
|
659
|
+
filter: { ownerPackage: "@treeseed/api" },
|
|
660
|
+
environment,
|
|
661
|
+
evidenceTarget: "release",
|
|
662
|
+
sceneArtifacts
|
|
663
|
+
});
|
|
664
|
+
if (!report.ok) {
|
|
665
|
+
const diagnostics = report.diagnostics.filter((entry) => entry.severity === "error").slice(0, 20).map((entry) => `${entry.code}: ${entry.message}${entry.sourcePath ? ` (${entry.sourcePath})` : ""}`);
|
|
666
|
+
workflowError(operation, "release_gate_failed", `API release guarantees for ${environment} failed:
|
|
667
|
+
${diagnostics.join("\n") || `See ${report.outputRoot}`}`, {
|
|
668
|
+
details: { environment, outputRoot: report.outputRoot, counts: report.counts, diagnostics: report.diagnostics }
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
ok: report.ok,
|
|
673
|
+
environment: report.environment,
|
|
674
|
+
runId: report.runId,
|
|
675
|
+
outputRoot: report.outputRoot,
|
|
676
|
+
counts: report.counts
|
|
677
|
+
};
|
|
678
|
+
});
|
|
679
|
+
}
|
|
636
680
|
function recordHostedDeploymentStatesFromRootGates(root, rootRelease, workflowGates) {
|
|
637
681
|
const gates = Array.isArray(workflowGates) ? workflowGates.map((gate) => stringRecord(gate)).filter((gate) => Boolean(gate)) : [];
|
|
638
682
|
const releaseRecord = stringRecord(rootRelease) ?? {};
|
|
@@ -5372,10 +5416,11 @@ ${blockers.join("\n")}`, {
|
|
|
5372
5416
|
resumable: true
|
|
5373
5417
|
};
|
|
5374
5418
|
}),
|
|
5419
|
+
{ id: "verify-published-artifacts", description: "Verify immutable registry artifacts exist after publish workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5420
|
+
{ id: "production-hosting", description: "Reconcile and live-verify production hosted resources before root deploy", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5421
|
+
{ id: "production-api-guarantees", description: "Run production API release guarantees before root deploy", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5375
5422
|
{ id: "release-root", description: `Release market ${plannedRelease.rootVersion}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
5376
5423
|
{ id: "publish-wait", description: "Wait for production release workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5377
|
-
{ id: "verify-published-artifacts", description: "Verify immutable registry artifacts exist after publish workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5378
|
-
{ id: "production-hosting", description: "Reconcile and live-verify production hosted resources after publish", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
|
|
5379
5424
|
{ id: "release-back-merge", description: "Back-merge production release history into staging", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
|
|
5380
5425
|
{ id: "workspace-link", description: "Restore local workspace links after release", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }
|
|
5381
5426
|
],
|
|
@@ -5482,6 +5527,9 @@ ${rendered}`);
|
|
|
5482
5527
|
});
|
|
5483
5528
|
packageReleases.push(packageRelease);
|
|
5484
5529
|
}
|
|
5530
|
+
const publishedArtifacts = await executeJournalStep(root, workflowRun.runId, "verify-published-artifacts", () => verifyPublishedReleaseArtifacts(selectedVersions));
|
|
5531
|
+
const productionHosting = await executeJournalStep(root, workflowRun.runId, "production-hosting", () => reconcileSaveHostedEnvironment(root, "prod", helpers, workflowRun.runId, "release", productionReleaseImageRefEnv(selectedVersions)));
|
|
5532
|
+
const productionApiGuarantees = await executeJournalStep(root, workflowRun.runId, "production-api-guarantees", () => runReleaseApiGuarantees(root, "prod", helpers, "release", normalizeSceneArtifactsMode(effectiveInput.sceneArtifacts)));
|
|
5485
5533
|
const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
|
|
5486
5534
|
const rootInstall = runReleaseNpmInstall(root, { workspaceRoot: root });
|
|
5487
5535
|
const changelog = updateReleaseChangelog(repoRoot(root), {
|
|
@@ -5532,8 +5580,6 @@ ${rendered}`);
|
|
|
5532
5580
|
runId: workflowRun.runId,
|
|
5533
5581
|
onProgress: (line, stream) => helpers.write(line, stream)
|
|
5534
5582
|
}).then((workflowGates) => ({ workflowGates })));
|
|
5535
|
-
const publishedArtifacts = await executeJournalStep(root, workflowRun.runId, "verify-published-artifacts", () => verifyPublishedReleaseArtifacts(selectedVersions));
|
|
5536
|
-
const productionHosting = await executeJournalStep(root, workflowRun.runId, "production-hosting", () => reconcileSaveHostedEnvironment(root, "prod", helpers, workflowRun.runId, "release", productionReleaseImageRefEnv(selectedVersions)));
|
|
5537
5583
|
const backMerge = await executeJournalStep(root, workflowRun.runId, "release-back-merge", () => {
|
|
5538
5584
|
const packageBackMerges = checkedOutWorkspacePackageRepos(root).filter((pkg) => selectedPackageSet.has(pkg.name)).map((pkg) => backMergeProductionIntoStaging(pkg.dir, pkg.name, releaseAdminMessage({
|
|
5539
5585
|
subject: `release: back-merge ${PRODUCTION_BRANCH} into ${STAGING_BRANCH}`,
|
|
@@ -5561,6 +5607,7 @@ ${rendered}`);
|
|
|
5561
5607
|
publishWait: publishWait.workflowGates,
|
|
5562
5608
|
publishedArtifacts,
|
|
5563
5609
|
productionHosting,
|
|
5610
|
+
productionApiGuarantees,
|
|
5564
5611
|
backMerge,
|
|
5565
5612
|
workspaceLinks,
|
|
5566
5613
|
releasedCommit: String(rootRelease.commit.commitSha ?? ""),
|