@fourier-labs/harbour-contracts 0.3.1 → 0.4.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/README.md +1 -1
- package/dist/deployment-status-response.d.ts +2 -2
- package/dist/error-category-registry.d.ts +7 -0
- package/dist/error-category-registry.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/promotion-request.d.ts +17 -0
- package/dist/promotion-request.js +3 -0
- package/dist/promotion-response.d.ts +17 -0
- package/dist/promotion-response.js +3 -0
- package/dist/version-state-machine.d.ts +7 -2
- package/dist/version-state-machine.js +23 -13
- package/package.json +1 -1
- package/schema/deploy-response.schema.json +1 -1
- package/schema/deployment-status-request.schema.json +1 -1
- package/schema/deployment-status-response.schema.json +2 -2
- package/schema/error-category.enum.json +7 -0
- package/schema/promotion-request.schema.json +26 -0
- package/schema/promotion-response.schema.json +50 -0
- package/schema/version-state.schema.json +25 -24
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Two schemas carry logic beyond structural types, generated into helpers:
|
|
|
33
33
|
- **`error-category.enum.json`** → `IsRetryable(category)` (Go) / `isRetryable(category)` (TS),
|
|
34
34
|
handling both concrete categories and family prefixes (`storage.unsupported-engine.<engine>`).
|
|
35
35
|
Unknown categories fail closed (non-retryable).
|
|
36
|
-
- **`version-state.schema.json`** → `CanTransition(from, to)` / `IsTerminal(state)` (Go) and
|
|
36
|
+
- **`version-state.schema.json`** → `CanTransition(environment, from, to)` / `IsTerminal(state)` (Go) and
|
|
37
37
|
`canTransition` / `isTerminal` (TS), driven by the `x-transitions` edge list — so control-plane
|
|
38
38
|
and any consumer agree on legality from one source.
|
|
39
39
|
|
|
@@ -6,8 +6,8 @@ export interface DeploymentStatusResponse {
|
|
|
6
6
|
versionId: string;
|
|
7
7
|
tenant: string;
|
|
8
8
|
app: string;
|
|
9
|
-
environment: 'preview';
|
|
10
|
-
status: 'QUEUED' | 'ANALYZING' | 'TRANSFORMING' | 'VALIDATING' | 'BUILDING' | 'VERIFYING' | 'GATING' | 'LIVE' | 'FAILED' | 'STALE';
|
|
9
|
+
environment: 'preview' | 'prod';
|
|
10
|
+
status: 'QUEUED' | 'ANALYZING' | 'TRANSFORMING' | 'VALIDATING' | 'BUILDING' | 'VERIFYING' | 'GATING' | 'LIVE' | 'AWAITING-PROMOTION' | 'PROMOTING' | 'FAILED' | 'STALE';
|
|
11
11
|
message: string;
|
|
12
12
|
branch: string;
|
|
13
13
|
commit: string;
|
|
@@ -27,6 +27,13 @@ export declare const ErrorCategories: {
|
|
|
27
27
|
readonly AdmissionAppNotRegistered: "admission.app-not-registered";
|
|
28
28
|
readonly DeployCommitNotOnBranch: "deploy.commit-not-on-branch";
|
|
29
29
|
readonly DeployQueuePublicationFailed: "deploy.queue-publication-failed";
|
|
30
|
+
readonly PromotionPreviewNotPromotable: "promotion.preview-not-promotable";
|
|
31
|
+
readonly PromotionProductionBranchMoved: "promotion.production-branch-moved";
|
|
32
|
+
readonly PromotionSourceTreeMismatch: "promotion.source-tree-mismatch";
|
|
33
|
+
readonly PromotionArtifactMismatch: "promotion.artifact-mismatch";
|
|
34
|
+
readonly PromotionIdempotencyConflict: "promotion.idempotency-conflict";
|
|
35
|
+
readonly PromotionInProgress: "promotion.in-progress";
|
|
36
|
+
readonly PromotionQueuePublicationFailed: "promotion.queue-publication-failed";
|
|
30
37
|
readonly CutoverFlipFailed: "cutover.flip-failed";
|
|
31
38
|
};
|
|
32
39
|
/** Retryable lookup for a concrete or family-suffixed category. Unknown → false (fail closed). */
|
|
@@ -29,6 +29,13 @@ export const ErrorCategories = {
|
|
|
29
29
|
AdmissionAppNotRegistered: "admission.app-not-registered",
|
|
30
30
|
DeployCommitNotOnBranch: "deploy.commit-not-on-branch",
|
|
31
31
|
DeployQueuePublicationFailed: "deploy.queue-publication-failed",
|
|
32
|
+
PromotionPreviewNotPromotable: "promotion.preview-not-promotable",
|
|
33
|
+
PromotionProductionBranchMoved: "promotion.production-branch-moved",
|
|
34
|
+
PromotionSourceTreeMismatch: "promotion.source-tree-mismatch",
|
|
35
|
+
PromotionArtifactMismatch: "promotion.artifact-mismatch",
|
|
36
|
+
PromotionIdempotencyConflict: "promotion.idempotency-conflict",
|
|
37
|
+
PromotionInProgress: "promotion.in-progress",
|
|
38
|
+
PromotionQueuePublicationFailed: "promotion.queue-publication-failed",
|
|
32
39
|
CutoverFlipFailed: "cutover.flip-failed",
|
|
33
40
|
};
|
|
34
41
|
const retryableExact = {
|
|
@@ -60,6 +67,13 @@ const retryableExact = {
|
|
|
60
67
|
"admission.app-not-registered": false,
|
|
61
68
|
"deploy.commit-not-on-branch": false,
|
|
62
69
|
"deploy.queue-publication-failed": true,
|
|
70
|
+
"promotion.preview-not-promotable": false,
|
|
71
|
+
"promotion.production-branch-moved": false,
|
|
72
|
+
"promotion.source-tree-mismatch": false,
|
|
73
|
+
"promotion.artifact-mismatch": false,
|
|
74
|
+
"promotion.idempotency-conflict": false,
|
|
75
|
+
"promotion.in-progress": true,
|
|
76
|
+
"promotion.queue-publication-failed": true,
|
|
63
77
|
"cutover.flip-failed": false,
|
|
64
78
|
};
|
|
65
79
|
const retryableFamilies = {
|
package/dist/index.d.ts
CHANGED
|
@@ -12,5 +12,7 @@ export * from './error-category-registry.js';
|
|
|
12
12
|
export * from './error-category.js';
|
|
13
13
|
export * from './error-envelope.js';
|
|
14
14
|
export * from './kvs-routing-entry.js';
|
|
15
|
+
export * from './promotion-request.js';
|
|
16
|
+
export * from './promotion-response.js';
|
|
15
17
|
export * from './version-state-machine.js';
|
|
16
18
|
export * from './version-state.js';
|
package/dist/index.js
CHANGED
|
@@ -14,5 +14,7 @@ export * from './error-category-registry.js';
|
|
|
14
14
|
export * from './error-category.js';
|
|
15
15
|
export * from './error-envelope.js';
|
|
16
16
|
export * from './kvs-routing-entry.js';
|
|
17
|
+
export * from './promotion-request.js';
|
|
18
|
+
export * from './promotion-response.js';
|
|
17
19
|
export * from './version-state-machine.js';
|
|
18
20
|
export * from './version-state.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON body for POST /v1/tenants/{tenant}/apps/{app}/promotions. Repository identity is loaded from stored application and preview records, not supplied by the caller.
|
|
3
|
+
*/
|
|
4
|
+
export interface PromotionRequest {
|
|
5
|
+
/**
|
|
6
|
+
* Immutable preview version selected by the caller. The API also verifies that its tenant and app match the route.
|
|
7
|
+
*/
|
|
8
|
+
previewVersionId: string;
|
|
9
|
+
/**
|
|
10
|
+
* Current commit at the configured production branch head. Its root Git tree must equal the preview tree.
|
|
11
|
+
*/
|
|
12
|
+
commit: string;
|
|
13
|
+
/**
|
|
14
|
+
* Durable caller-generated key. Identical replays return the original production version; reuse with a changed request is rejected.
|
|
15
|
+
*/
|
|
16
|
+
idempotencyKey: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 202 Accepted body returned after the production version and its durable idempotency binding exist.
|
|
3
|
+
*/
|
|
4
|
+
export interface PromotionResponse {
|
|
5
|
+
idempotencyKey: string;
|
|
6
|
+
/**
|
|
7
|
+
* Immutable identity of the admitted production version.
|
|
8
|
+
*/
|
|
9
|
+
versionId: string;
|
|
10
|
+
previewVersionId: string;
|
|
11
|
+
tenant: string;
|
|
12
|
+
app: string;
|
|
13
|
+
environment: 'prod';
|
|
14
|
+
status: 'QUEUED';
|
|
15
|
+
acceptedAt: string;
|
|
16
|
+
statusUrl: string;
|
|
17
|
+
}
|
|
@@ -13,7 +13,12 @@ export declare const States: {
|
|
|
13
13
|
readonly STALE: "STALE";
|
|
14
14
|
};
|
|
15
15
|
export type StateName = (typeof States)[keyof typeof States];
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
export declare const Environments: {
|
|
17
|
+
readonly Preview: "preview";
|
|
18
|
+
readonly Prod: "prod";
|
|
19
|
+
};
|
|
20
|
+
export type EnvironmentName = (typeof Environments)[keyof typeof Environments];
|
|
21
|
+
/** True iff from→to is a legal edge for environment in the T1a state machine. */
|
|
22
|
+
export declare function canTransition(environment: string, from: string, to: string): boolean;
|
|
18
23
|
/** True iff the state is terminal (FAILED, STALE). */
|
|
19
24
|
export declare function isTerminal(state: string): boolean;
|
|
@@ -14,22 +14,32 @@ export const States = {
|
|
|
14
14
|
FAILED: "FAILED",
|
|
15
15
|
STALE: "STALE",
|
|
16
16
|
};
|
|
17
|
+
export const Environments = {
|
|
18
|
+
Preview: "preview",
|
|
19
|
+
Prod: "prod",
|
|
20
|
+
};
|
|
17
21
|
const legalTransitions = {
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
"preview": {
|
|
23
|
+
"QUEUED": { "ANALYZING": true, "FAILED": true, "STALE": true },
|
|
24
|
+
"ANALYZING": { "BUILDING": true, "TRANSFORMING": true, "FAILED": true, "STALE": true },
|
|
25
|
+
"TRANSFORMING": { "VALIDATING": true, "FAILED": true, "STALE": true },
|
|
26
|
+
"VALIDATING": { "BUILDING": true, "TRANSFORMING": true, "FAILED": true, "STALE": true },
|
|
27
|
+
"BUILDING": { "VERIFYING": true, "FAILED": true, "STALE": true },
|
|
28
|
+
"VERIFYING": { "GATING": true, "FAILED": true, "STALE": true },
|
|
29
|
+
"GATING": { "LIVE": true, "FAILED": true, "STALE": true },
|
|
30
|
+
"LIVE": { "AWAITING-PROMOTION": true },
|
|
31
|
+
"AWAITING-PROMOTION": { "PROMOTING": true },
|
|
32
|
+
"PROMOTING": { "LIVE": true, "FAILED": true },
|
|
33
|
+
},
|
|
34
|
+
"prod": {
|
|
35
|
+
"QUEUED": { "PROMOTING": true, "FAILED": true },
|
|
36
|
+
"PROMOTING": { "LIVE": true, "FAILED": true },
|
|
37
|
+
},
|
|
28
38
|
};
|
|
29
39
|
const terminalStates = new Set(["FAILED", "STALE"]);
|
|
30
|
-
/** True iff from→to is a legal edge in the T1a state machine. */
|
|
31
|
-
export function canTransition(from, to) {
|
|
32
|
-
return Boolean(legalTransitions[
|
|
40
|
+
/** True iff from→to is a legal edge for environment in the T1a state machine. */
|
|
41
|
+
export function canTransition(environment, from, to) {
|
|
42
|
+
return Boolean(legalTransitions[environment]?.[from]?.[to]);
|
|
33
43
|
}
|
|
34
44
|
/** True iff the state is terminal (FAILED, STALE). */
|
|
35
45
|
export function isTerminal(state) { return terminalStates.has(state); }
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"versionId": {
|
|
17
17
|
"type": "string",
|
|
18
|
-
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#preview#[0-9]{6}$",
|
|
18
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#preview#[0-9]{6,}$",
|
|
19
19
|
"description": "Immutable identity of the admitted preview version."
|
|
20
20
|
},
|
|
21
21
|
"tenant": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"versionId": {
|
|
21
21
|
"type": "string",
|
|
22
|
-
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#preview#[0-9]{6}$"
|
|
22
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#(?:preview|prod)#[0-9]{6,}$"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
},
|
|
22
22
|
"environment": {
|
|
23
23
|
"type": "string",
|
|
24
|
-
"enum": ["preview"]
|
|
24
|
+
"enum": ["preview", "prod"]
|
|
25
25
|
},
|
|
26
26
|
"status": {
|
|
27
27
|
"type": "string",
|
|
28
|
-
"enum": ["QUEUED", "ANALYZING", "TRANSFORMING", "VALIDATING", "BUILDING", "VERIFYING", "GATING", "LIVE", "FAILED", "STALE"]
|
|
28
|
+
"enum": ["QUEUED", "ANALYZING", "TRANSFORMING", "VALIDATING", "BUILDING", "VERIFYING", "GATING", "LIVE", "AWAITING-PROMOTION", "PROMOTING", "FAILED", "STALE"]
|
|
29
29
|
},
|
|
30
30
|
"message": {
|
|
31
31
|
"type": "string",
|
|
@@ -42,6 +42,13 @@
|
|
|
42
42
|
{ "id": "admission.app-not-registered", "retryable": false, "description": "Admission rejected: the app directory does not know this app, so there is no sourceRef to fetch a commit from. Fails closed rather than creating the app on demand — a deploy request must not become an app-creation side effect, or app naming stops being a reviewed act. Non-retryable: the directory is authoritative, so retrying cannot make an unknown app known." },
|
|
43
43
|
{ "id": "deploy.commit-not-on-branch", "retryable": false, "description": "Admission rejected: the deploy request's commit is not reachable from the app's branch." },
|
|
44
44
|
{ "id": "deploy.queue-publication-failed", "retryable": true, "description": "The version was created but its version-qualified SQS message definitively could not be published. The lane is released when this version still owns it; submitting a new deployment is safe." },
|
|
45
|
+
{ "id": "promotion.preview-not-promotable", "retryable": false, "description": "The referenced preview is missing, belongs to another route, is not LIVE, or lacks immutable artifact and source-tree evidence." },
|
|
46
|
+
{ "id": "promotion.production-branch-moved", "retryable": false, "description": "The requested production commit is not the current configured production branch head." },
|
|
47
|
+
{ "id": "promotion.source-tree-mismatch", "retryable": false, "description": "The production commit root Git tree identity differs from the referenced preview tree identity." },
|
|
48
|
+
{ "id": "promotion.artifact-mismatch", "retryable": false, "description": "The stored preview artifact is missing, mutable, or resolves to a different backend digest." },
|
|
49
|
+
{ "id": "promotion.idempotency-conflict", "retryable": false, "description": "An idempotency key was reused with a different preview version or production commit." },
|
|
50
|
+
{ "id": "promotion.in-progress", "retryable": true, "description": "A different production promotion currently owns the application's production lane." },
|
|
51
|
+
{ "id": "promotion.queue-publication-failed", "retryable": true, "description": "The production version was durably accepted but its version-qualified queue event could not be published." },
|
|
45
52
|
{ "id": "cutover.flip-failed", "retryable": false, "description": "The atomic alias flip failed at cutover; previous version keeps serving." },
|
|
46
53
|
{ "id": "quota.exceeded", "retryable": false, "family": true, "description": "A provisioning/account limit was hit (cluster/IAM-role quota) — NOT runtime traffic quotas. Concrete token names which limit." },
|
|
47
54
|
{ "id": "uncategorized", "retryable": false, "family": true, "description": "A failure that fit no category — this is a bug: file uncategorized.<stage> AND a spec PR to this file." }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/promotion-request.schema.json",
|
|
4
|
+
"title": "PromotionRequest",
|
|
5
|
+
"description": "JSON body for POST /v1/tenants/{tenant}/apps/{app}/promotions. Repository identity is loaded from stored application and preview records, not supplied by the caller.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["previewVersionId", "commit", "idempotencyKey"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"previewVersionId": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#preview#[0-9]{6,}$",
|
|
13
|
+
"description": "Immutable preview version selected by the caller. The API also verifies that its tenant and app match the route."
|
|
14
|
+
},
|
|
15
|
+
"commit": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"pattern": "^[0-9a-f]{40}$",
|
|
18
|
+
"description": "Current commit at the configured production branch head. Its root Git tree must equal the preview tree."
|
|
19
|
+
},
|
|
20
|
+
"idempotencyKey": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$",
|
|
23
|
+
"description": "Durable caller-generated key. Identical replays return the original production version; reuse with a changed request is rejected."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/promotion-response.schema.json",
|
|
4
|
+
"title": "PromotionResponse",
|
|
5
|
+
"description": "202 Accepted body returned after the production version and its durable idempotency binding exist.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["idempotencyKey", "versionId", "previewVersionId", "tenant", "app", "environment", "status", "acceptedAt", "statusUrl"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"idempotencyKey": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$"
|
|
13
|
+
},
|
|
14
|
+
"versionId": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#prod#[0-9]{6,}$",
|
|
17
|
+
"description": "Immutable identity of the admitted production version."
|
|
18
|
+
},
|
|
19
|
+
"previewVersionId": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?#preview#[0-9]{6,}$"
|
|
22
|
+
},
|
|
23
|
+
"tenant": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 1,
|
|
26
|
+
"maxLength": 63
|
|
27
|
+
},
|
|
28
|
+
"app": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1,
|
|
31
|
+
"maxLength": 63
|
|
32
|
+
},
|
|
33
|
+
"environment": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": ["prod"]
|
|
36
|
+
},
|
|
37
|
+
"status": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": ["QUEUED"]
|
|
40
|
+
},
|
|
41
|
+
"acceptedAt": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"format": "date-time"
|
|
44
|
+
},
|
|
45
|
+
"statusUrl": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"pattern": "^/v1/tenants/[^/]+/apps/[^/]+/deployments/[^/]+$"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -35,32 +35,33 @@
|
|
|
35
35
|
"x-terminal": ["FAILED", "STALE"],
|
|
36
36
|
"x-terminalStable": ["LIVE", "AWAITING-PROMOTION"],
|
|
37
37
|
"x-transitions": [
|
|
38
|
-
{ "from": "QUEUED", "to": "ANALYZING", "trigger": "admission: slot free AND commit reachable from branch" },
|
|
38
|
+
{ "from": "QUEUED", "to": "ANALYZING", "environments": ["preview"], "trigger": "admission: slot free AND commit reachable from branch" },
|
|
39
|
+
{ "from": "QUEUED", "to": "PROMOTING", "environments": ["prod"], "trigger": "production promotion admission: stored preview and source-fidelity guards pass" },
|
|
39
40
|
{ "from": "QUEUED", "to": "FAILED", "trigger": "admission validation fails — commit not reachable from branch" },
|
|
40
|
-
{ "from": "ANALYZING", "to": "BUILDING", "trigger": "nothing to transform (fast path)" },
|
|
41
|
-
{ "from": "ANALYZING", "to": "TRANSFORMING", "trigger": "transform-relevant findings; 15-min wall deadline stamped" },
|
|
42
|
-
{ "from": "ANALYZING", "to": "FAILED", "trigger": "hard intake failure" },
|
|
43
|
-
{ "from": "TRANSFORMING", "to": "VALIDATING", "trigger": "agent produced candidate" },
|
|
44
|
-
{ "from": "TRANSFORMING", "to": "FAILED", "trigger": "non-retryable failure or deadline exceeded" },
|
|
45
|
-
{ "from": "VALIDATING", "to": "BUILDING", "trigger": "validation passed" },
|
|
46
|
-
{ "from": "VALIDATING", "to": "TRANSFORMING", "trigger": "retryable failure AND now < deadline" },
|
|
47
|
-
{ "from": "VALIDATING", "to": "FAILED", "trigger": "non-retryable failure or deadline exceeded" },
|
|
48
|
-
{ "from": "BUILDING", "to": "VERIFYING", "trigger": "artifact produced + artifact secret scan clean" },
|
|
49
|
-
{ "from": "BUILDING", "to": "FAILED", "trigger": "build recipe failed or secret scan hit" },
|
|
50
|
-
{ "from": "VERIFYING", "to": "GATING", "trigger": "all candidate checks pass" },
|
|
51
|
-
{ "from": "VERIFYING", "to": "FAILED", "trigger": "a verification check fails (e.g. auth.app-publicly-invocable)" },
|
|
52
|
-
{ "from": "GATING", "to": "LIVE", "trigger": "all gates pass -> atomic alias flip" },
|
|
53
|
-
{ "from": "GATING", "to": "FAILED", "trigger": "a blocking gate fails" },
|
|
54
|
-
{ "from": "LIVE", "to": "AWAITING-PROMOTION", "trigger": "preview only — maker confirms; platform raises PR" },
|
|
55
|
-
{ "from": "AWAITING-PROMOTION", "to": "PROMOTING", "trigger": "human merges PR (no agent-callable path)" },
|
|
41
|
+
{ "from": "ANALYZING", "to": "BUILDING", "environments": ["preview"], "trigger": "nothing to transform (fast path)" },
|
|
42
|
+
{ "from": "ANALYZING", "to": "TRANSFORMING", "environments": ["preview"], "trigger": "transform-relevant findings; 15-min wall deadline stamped" },
|
|
43
|
+
{ "from": "ANALYZING", "to": "FAILED", "environments": ["preview"], "trigger": "hard intake failure" },
|
|
44
|
+
{ "from": "TRANSFORMING", "to": "VALIDATING", "environments": ["preview"], "trigger": "agent produced candidate" },
|
|
45
|
+
{ "from": "TRANSFORMING", "to": "FAILED", "environments": ["preview"], "trigger": "non-retryable failure or deadline exceeded" },
|
|
46
|
+
{ "from": "VALIDATING", "to": "BUILDING", "environments": ["preview"], "trigger": "validation passed" },
|
|
47
|
+
{ "from": "VALIDATING", "to": "TRANSFORMING", "environments": ["preview"], "trigger": "retryable failure AND now < deadline" },
|
|
48
|
+
{ "from": "VALIDATING", "to": "FAILED", "environments": ["preview"], "trigger": "non-retryable failure or deadline exceeded" },
|
|
49
|
+
{ "from": "BUILDING", "to": "VERIFYING", "environments": ["preview"], "trigger": "artifact produced + artifact secret scan clean" },
|
|
50
|
+
{ "from": "BUILDING", "to": "FAILED", "environments": ["preview"], "trigger": "build recipe failed or secret scan hit" },
|
|
51
|
+
{ "from": "VERIFYING", "to": "GATING", "environments": ["preview"], "trigger": "all candidate checks pass" },
|
|
52
|
+
{ "from": "VERIFYING", "to": "FAILED", "environments": ["preview"], "trigger": "a verification check fails (e.g. auth.app-publicly-invocable)" },
|
|
53
|
+
{ "from": "GATING", "to": "LIVE", "environments": ["preview"], "trigger": "all gates pass -> atomic alias flip" },
|
|
54
|
+
{ "from": "GATING", "to": "FAILED", "environments": ["preview"], "trigger": "a blocking gate fails" },
|
|
55
|
+
{ "from": "LIVE", "to": "AWAITING-PROMOTION", "environments": ["preview"], "trigger": "preview only — maker confirms; platform raises PR" },
|
|
56
|
+
{ "from": "AWAITING-PROMOTION", "to": "PROMOTING", "environments": ["preview"], "trigger": "human merges PR (no agent-callable path)" },
|
|
56
57
|
{ "from": "PROMOTING", "to": "LIVE", "trigger": "prod candidate verified -> flip" },
|
|
57
58
|
{ "from": "PROMOTING", "to": "FAILED", "trigger": "candidate verify/migration fails; prod alias never moved" },
|
|
58
|
-
{ "from": "QUEUED", "to": "STALE", "trigger": "newer deploy request passes its guard" },
|
|
59
|
-
{ "from": "ANALYZING", "to": "STALE", "trigger": "newer deploy request passes its guard" },
|
|
60
|
-
{ "from": "TRANSFORMING", "to": "STALE", "trigger": "newer deploy request; survives unless new commit touches transform-relevant files" },
|
|
61
|
-
{ "from": "VALIDATING", "to": "STALE", "trigger": "newer deploy request passes its guard" },
|
|
62
|
-
{ "from": "BUILDING", "to": "STALE", "trigger": "newer deploy request; let finish, skip flip" },
|
|
63
|
-
{ "from": "VERIFYING", "to": "STALE", "trigger": "newer deploy request; let finish, skip flip" },
|
|
64
|
-
{ "from": "GATING", "to": "STALE", "trigger": "newer deploy request passes its guard" }
|
|
59
|
+
{ "from": "QUEUED", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request passes its guard" },
|
|
60
|
+
{ "from": "ANALYZING", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request passes its guard" },
|
|
61
|
+
{ "from": "TRANSFORMING", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request; survives unless new commit touches transform-relevant files" },
|
|
62
|
+
{ "from": "VALIDATING", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request passes its guard" },
|
|
63
|
+
{ "from": "BUILDING", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request; let finish, skip flip" },
|
|
64
|
+
{ "from": "VERIFYING", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request; let finish, skip flip" },
|
|
65
|
+
{ "from": "GATING", "to": "STALE", "environments": ["preview"], "trigger": "newer deploy request passes its guard" }
|
|
65
66
|
]
|
|
66
67
|
}
|