@intx/hub-sessions 0.2.2 → 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 +3 -5
- package/dist/agent-repo.d.ts +23 -7
- package/dist/agent-repo.js +19 -6
- package/dist/agent-state-kind.js +18 -63
- package/dist/asset-service.d.ts +1 -20
- package/dist/asset-service.js +19 -97
- package/dist/committed-source-tree.d.ts +10 -0
- package/dist/committed-source-tree.js +35 -0
- package/dist/credential-push.d.ts +52 -7
- package/dist/credential-push.js +178 -22
- package/dist/event-collector-registry.d.ts +3 -2
- package/dist/event-collector-registry.js +42 -13
- package/dist/event-collector.d.ts +12 -2
- package/dist/event-collector.js +45 -4
- package/dist/hub-session-lookups.d.ts +125 -7
- package/dist/hub-session-lookups.js +541 -86
- package/dist/hub-session-orchestrator.d.ts +2 -3
- package/dist/hub-session-orchestrator.js +22 -56
- package/dist/index.d.ts +19 -9
- package/dist/index.js +16 -7
- package/dist/reconciliation-scheduler.d.ts +14 -0
- package/dist/reconciliation-scheduler.js +55 -0
- package/dist/repo-store/index.d.ts +2 -1
- package/dist/repo-store/index.js +1 -0
- package/dist/repo-store/store.d.ts +1 -1
- package/dist/repo-store/store.js +138 -1
- package/dist/repo-store/subscribe-kind.d.ts +6 -3
- package/dist/repo-store/subscribe-kind.js +42 -77
- package/dist/repo-store/types.d.ts +94 -6
- package/dist/repo-store/user-principal-gate.d.ts +26 -0
- package/dist/repo-store/user-principal-gate.js +78 -0
- package/dist/session-service.d.ts +252 -126
- package/dist/session-service.js +851 -624
- package/dist/sidecar-allocation/capability-policy.d.ts +27 -0
- package/dist/sidecar-allocation/capability-policy.js +124 -0
- package/dist/sidecar-allocation/contracts.d.ts +101 -0
- package/dist/sidecar-allocation/contracts.js +26 -0
- package/dist/sidecar-allocation/index.d.ts +5 -0
- package/dist/sidecar-allocation/index.js +4 -0
- package/dist/sidecar-allocation/operation.d.ts +10 -0
- package/dist/sidecar-allocation/operation.js +54 -0
- package/dist/sidecar-allocation/plugin-registry.d.ts +24 -0
- package/dist/sidecar-allocation/plugin-registry.js +61 -0
- package/dist/sidecar-allocation/reconciler.d.ts +54 -0
- package/dist/sidecar-allocation/reconciler.js +825 -0
- package/dist/skill-kind.js +12 -62
- package/dist/substrate.d.ts +3 -3
- package/dist/substrate.js +1 -1
- package/dist/workflow-allocation-service.d.ts +64 -0
- package/dist/workflow-allocation-service.js +554 -0
- package/dist/workflow-closure-resolution.d.ts +106 -0
- package/dist/workflow-closure-resolution.js +123 -0
- package/dist/workflow-definition-ensure.d.ts +24 -0
- package/dist/workflow-definition-ensure.js +75 -0
- package/dist/workflow-dispatch-service.d.ts +42 -0
- package/dist/workflow-dispatch-service.js +209 -0
- package/dist/workflow-dispatch-settlement.d.ts +29 -0
- package/dist/workflow-dispatch-settlement.js +140 -0
- package/dist/workflow-kind.d.ts +29 -1
- package/dist/workflow-kind.js +143 -139
- package/dist/workflow-probe-gate.d.ts +286 -0
- package/dist/workflow-probe-gate.js +382 -0
- package/dist/workflow-run-kind.d.ts +239 -32
- package/dist/workflow-run-kind.js +874 -188
- package/dist/workflow-run-reader.d.ts +1 -1
- package/dist/workflow-run-reader.js +3 -7
- package/dist/workflow-run-restore.d.ts +16 -0
- package/dist/workflow-run-restore.js +30 -0
- package/dist/workflow-source-closure.d.ts +35 -0
- package/dist/workflow-source-closure.js +342 -0
- package/dist/workflow-source-pins.d.ts +8 -0
- package/dist/workflow-source-pins.js +14 -0
- package/dist/ws/index.d.ts +3 -3
- package/dist/ws/index.js +2 -2
- package/dist/ws/pending-tracker.d.ts +93 -0
- package/dist/ws/pending-tracker.js +132 -0
- package/dist/ws/sidecar-events.d.ts +139 -37
- package/dist/ws/sidecar-events.js +2 -2
- package/dist/ws/sidecar-handler.d.ts +218 -60
- package/dist/ws/sidecar-handler.js +1713 -732
- package/dist/ws/sidecar-handler.test-helpers.d.ts +38 -0
- package/dist/ws/sidecar-handler.test-helpers.js +95 -0
- package/dist/ws/sidecar-token-authenticator.d.ts +3 -1
- package/dist/ws/sidecar-token-authenticator.js +78 -7
- package/package.json +14 -13
- package/dist/available-skills-stanza.d.ts +0 -21
- package/dist/available-skills-stanza.js +0 -32
package/dist/workflow-kind.d.ts
CHANGED
|
@@ -9,13 +9,41 @@ export type WorkflowSidecarPrincipal = {
|
|
|
9
9
|
export type WorkflowPrincipal = WorkflowHubPrincipal | WorkflowSidecarPrincipal;
|
|
10
10
|
export declare const WORKFLOW_JSON_PATH = "workflow.json";
|
|
11
11
|
export declare const CAPABILITY_DECLARATIONS_JSON_PATH = "capability-declarations.json";
|
|
12
|
-
export declare const
|
|
12
|
+
export declare const PACKAGE_JSON_PATH = "package.json";
|
|
13
|
+
export declare const NODE_MODULES_PATH = "node_modules";
|
|
14
|
+
export declare const PNPM_WORKSPACE_PATH = "pnpm-workspace.yaml";
|
|
13
15
|
export declare const workflowDefinitionEnvelopeSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
14
16
|
id: string;
|
|
15
17
|
triggers: unknown[];
|
|
16
18
|
steps: Record<string, unknown>;
|
|
17
19
|
stepOrder: string[];
|
|
18
20
|
state?: Record<string, unknown>;
|
|
21
|
+
grantRequirements?: {
|
|
22
|
+
resource: string;
|
|
23
|
+
action: string;
|
|
24
|
+
source: "creator" | "invoker";
|
|
25
|
+
effect?: "allow" | "deny" | "ask";
|
|
26
|
+
conditions?: Record<string, unknown> | null;
|
|
27
|
+
}[];
|
|
28
|
+
credentialBindings?: {
|
|
29
|
+
package: string;
|
|
30
|
+
handle: string;
|
|
31
|
+
provider: string;
|
|
32
|
+
locator: "tenant";
|
|
33
|
+
name?: string;
|
|
34
|
+
}[];
|
|
35
|
+
sidecarPlacement?: {
|
|
36
|
+
capabilities?: {
|
|
37
|
+
capability: string;
|
|
38
|
+
effect: "require" | "block";
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
inboundMailPolicy?: {
|
|
42
|
+
untrustedFrom?: "reject" | "admit";
|
|
43
|
+
invalid?: "reject" | "admit";
|
|
44
|
+
missing?: "reject" | "admit";
|
|
45
|
+
unknown?: "reject" | "admit";
|
|
46
|
+
};
|
|
19
47
|
}, {}>;
|
|
20
48
|
export declare const workflowKindHandler: KindHandler;
|
|
21
49
|
export declare const workflowAuthorize: AuthorizeFn;
|
package/dist/workflow-kind.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
// KindHandler for the `workflow` asset kind.
|
|
2
2
|
//
|
|
3
|
-
// A workflow asset is a
|
|
4
|
-
//
|
|
3
|
+
// A workflow asset is a codebase: a top-level `package.json` declaring an
|
|
4
|
+
// `interchange.workflow` entry module plus arbitrary source files. The sidecar
|
|
5
|
+
// materializes the codebase into a closure and evaluates the pinned entry to the
|
|
6
|
+
// definition. `validatePush` requires the `package.json`; a tree that lacks one
|
|
7
|
+
// is rejected. The legacy `workflow.json` envelope form is no longer accepted at
|
|
8
|
+
// the push boundary.
|
|
5
9
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// - `.gitignore` — supplied by the asset routes' genesis init body.
|
|
14
|
-
//
|
|
15
|
-
// Any top-level entry outside this set fails the push.
|
|
10
|
+
// Source files are unconstrained, but the push validates the manifest's shape
|
|
11
|
+
// and the entry-path's containment, and refuses an envelope-only
|
|
12
|
+
// `capability-declarations.json`, a committed `node_modules`, and an ambiguous
|
|
13
|
+
// tree that also carries an envelope-valid `workflow.json`, so one asset resolves
|
|
14
|
+
// to exactly one definition. The codebase shape accepts both a single package and
|
|
15
|
+
// a `workspaces` monorepo; for a monorepo the push validates only the root's
|
|
16
|
+
// well-formedness and leaves per-member validation to the resolver.
|
|
16
17
|
//
|
|
17
18
|
// Authz:
|
|
18
19
|
// - hub principal: full access.
|
|
@@ -22,17 +23,16 @@
|
|
|
22
23
|
// used by skill assets.
|
|
23
24
|
import { type } from "arktype";
|
|
24
25
|
import { getLogger } from "@intx/log";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
26
|
+
import { CredentialBinding, GrantRequirement, SidecarCapabilityPolicy, } from "@intx/types";
|
|
27
|
+
import { InboundMailPolicy } from "@intx/types/runtime";
|
|
28
|
+
import { PackageJSON, isContainedEntryPath } from "@intx/types/package-json";
|
|
29
|
+
import { authorizeUserPrincipal, } from "./repo-store/index.js";
|
|
27
30
|
const logger = getLogger(["hub-sessions", "workflow-kind"]);
|
|
28
31
|
export const WORKFLOW_JSON_PATH = "workflow.json";
|
|
29
32
|
export const CAPABILITY_DECLARATIONS_JSON_PATH = "capability-declarations.json";
|
|
30
|
-
export const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
CAPABILITY_DECLARATIONS_JSON_PATH,
|
|
34
|
-
WORKFLOW_GITIGNORE_PATH,
|
|
35
|
-
]);
|
|
33
|
+
export const PACKAGE_JSON_PATH = "package.json";
|
|
34
|
+
export const NODE_MODULES_PATH = "node_modules";
|
|
35
|
+
export const PNPM_WORKSPACE_PATH = "pnpm-workspace.yaml";
|
|
36
36
|
/**
|
|
37
37
|
* Structural arktype validator for the `workflow.json` envelope. The
|
|
38
38
|
* substrate checks the cross-cutting shape of `WorkflowDefinition`
|
|
@@ -40,10 +40,10 @@ const ALLOWED_TOP_LEVEL = new Set([
|
|
|
40
40
|
* `stepOrder`) but does not re-derive `defineWorkflow`'s DAG-level
|
|
41
41
|
* validation here — primitive-level shape, default-input application,
|
|
42
42
|
* and `after`-ref resolution belong to the runtime layer that hydrates
|
|
43
|
-
* the definition.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* the definition. The codebase push uses this validator to detect an
|
|
44
|
+
* ambiguous tree that also carries an envelope-valid `workflow.json`,
|
|
45
|
+
* and the hydrate-time definition loaders reuse it to validate a
|
|
46
|
+
* materialized definition before instantiation.
|
|
47
47
|
*/
|
|
48
48
|
const StepsObject = type("Record<string, unknown>").narrow((value, ctx) => {
|
|
49
49
|
if (Array.isArray(value)) {
|
|
@@ -63,20 +63,29 @@ export const workflowDefinitionEnvelopeSchema = type({
|
|
|
63
63
|
steps: StepsObject,
|
|
64
64
|
stepOrder: "string[]",
|
|
65
65
|
"state?": StateObject,
|
|
66
|
+
// `grantRequirements` passes through the envelope whether or not it is
|
|
67
|
+
// declared here: arktype's `.onUndeclaredKey("ignore")` below is
|
|
68
|
+
// passthrough, not stripping (only `"delete"` strips), so the hydrate read
|
|
69
|
+
// sees the field either way. Declaring it here VALIDATES declared
|
|
70
|
+
// requirements at the deploy boundary — a malformed `source` is rejected
|
|
71
|
+
// rather than passed through unchecked — as defense in depth alongside the
|
|
72
|
+
// trigger route's own `GrantRequirements` re-validation. Compose the
|
|
73
|
+
// exported `GrantRequirement` arktype rather than restating its shape so the
|
|
74
|
+
// envelope and the definition stay in lockstep.
|
|
75
|
+
"grantRequirements?": GrantRequirement.array(),
|
|
76
|
+
// `credentialBindings` is validated here too -- same defense-in-depth
|
|
77
|
+
// rationale as grantRequirements above: a malformed binding (bad locator,
|
|
78
|
+
// authority, or handle) is rejected at the deploy boundary rather than
|
|
79
|
+
// passed through to launch-time resolution unchecked.
|
|
80
|
+
"credentialBindings?": CredentialBinding.array(),
|
|
81
|
+
"sidecarPlacement?": SidecarCapabilityPolicy,
|
|
82
|
+
// `inboundMailPolicy` is validated here too -- same defense-in-depth
|
|
83
|
+
// rationale as credentialBindings above: a malformed policy (an unknown
|
|
84
|
+
// outcome key or a value that is not reject/admit) is rejected at the deploy
|
|
85
|
+
// boundary rather than passed through to later admission resolution
|
|
86
|
+
// unchecked.
|
|
87
|
+
"inboundMailPolicy?": InboundMailPolicy,
|
|
66
88
|
}).onUndeclaredKey("ignore");
|
|
67
|
-
/**
|
|
68
|
-
* Capability-declarations.json is held to "is a JSON object" at this
|
|
69
|
-
* commit; the per-step structure is owned by the capability-walk
|
|
70
|
-
* module that authors the file. `Record<string, unknown>` on its own
|
|
71
|
-
* accepts arrays under arktype's structural-object semantics, so the
|
|
72
|
-
* push validator pairs it with an array-rejection narrow.
|
|
73
|
-
*/
|
|
74
|
-
const CapabilityDeclarationsObject = type("Record<string, unknown>").narrow((value, ctx) => {
|
|
75
|
-
if (Array.isArray(value)) {
|
|
76
|
-
return ctx.mustBe("a JSON object, not an array");
|
|
77
|
-
}
|
|
78
|
-
return true;
|
|
79
|
-
});
|
|
80
89
|
const SidecarPrincipal = type({
|
|
81
90
|
kind: "'sidecar'",
|
|
82
91
|
agentId: "string",
|
|
@@ -105,58 +114,99 @@ async function readJSONBlob(path, readBlob) {
|
|
|
105
114
|
}
|
|
106
115
|
return { ok: true, value: parsed };
|
|
107
116
|
}
|
|
117
|
+
function isJSONObject(value) {
|
|
118
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
119
|
+
}
|
|
120
|
+
function rejectPush(repoId, ref, reason) {
|
|
121
|
+
logger.debug `workflow validatePush rejected ${repoId.kind}/${repoId.id} on ${ref}: ${reason}`;
|
|
122
|
+
return { ok: false, reason };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Validate the codebase shape: a top-level `package.json` declaring a contained
|
|
126
|
+
* `interchange.workflow` entry (single package), or a `workspaces` monorepo
|
|
127
|
+
* whose root well-formedness is checked and whose members are deferred to the
|
|
128
|
+
* resolver, plus arbitrary source files. Entered when the tree carries a
|
|
129
|
+
* `package.json`. Source files are unconstrained, but the push refuses the
|
|
130
|
+
* envelope-only `capability-declarations.json`, a committed `node_modules`, and
|
|
131
|
+
* an ambiguous tree that also carries an envelope-valid `workflow.json`, so one
|
|
132
|
+
* asset resolves to exactly one definition.
|
|
133
|
+
*
|
|
134
|
+
* The push validates STRUCTURE only. It never imports or evaluates the entry
|
|
135
|
+
* module -- that runs author code and is the sidecar's sandboxed job. The
|
|
136
|
+
* entry-path containment check is the string-level half of the loader's rule
|
|
137
|
+
* (`isContainedEntryPath`); the realpath-based symlink half runs at load time
|
|
138
|
+
* against the materialized directory, which the hub does not have here.
|
|
139
|
+
*/
|
|
140
|
+
async function validateWorkflowCodebasePush(repoId, ref, topLevelTreePaths, readBlob) {
|
|
141
|
+
if (topLevelTreePaths.includes(CAPABILITY_DECLARATIONS_JSON_PATH)) {
|
|
142
|
+
return rejectPush(repoId, ref, `${CAPABILITY_DECLARATIONS_JSON_PATH} is an envelope-only artifact and cannot appear in a codebase workflow asset`);
|
|
143
|
+
}
|
|
144
|
+
if (topLevelTreePaths.includes(NODE_MODULES_PATH)) {
|
|
145
|
+
return rejectPush(repoId, ref, `a committed top-level ${NODE_MODULES_PATH} directory is not allowed; the sidecar materializes dependencies from the resolved closure`);
|
|
146
|
+
}
|
|
147
|
+
// A `workflow.json` that also parses as a valid envelope makes the asset
|
|
148
|
+
// advertise two definitions; reject that. A `workflow.json` present but not a
|
|
149
|
+
// valid envelope is an ordinary source file and is allowed.
|
|
150
|
+
if (topLevelTreePaths.includes(WORKFLOW_JSON_PATH)) {
|
|
151
|
+
const envelopeOutcome = await readJSONBlob(WORKFLOW_JSON_PATH, readBlob);
|
|
152
|
+
if (envelopeOutcome.ok &&
|
|
153
|
+
!(workflowDefinitionEnvelopeSchema(envelopeOutcome.value) instanceof
|
|
154
|
+
type.errors)) {
|
|
155
|
+
return rejectPush(repoId, ref, `tree carries both ${PACKAGE_JSON_PATH} and an envelope-valid ${WORKFLOW_JSON_PATH}; a workflow asset must be a codebase or an envelope, not both`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const pkgOutcome = await readJSONBlob(PACKAGE_JSON_PATH, readBlob);
|
|
159
|
+
if (!pkgOutcome.ok) {
|
|
160
|
+
return rejectPush(repoId, ref, pkgOutcome.reason);
|
|
161
|
+
}
|
|
162
|
+
// `PackageJSON` does not declare `workspaces`, so it is read off the raw
|
|
163
|
+
// parsed value. A monorepo is a distinct codebase shape: the workflow lives
|
|
164
|
+
// in one member, selected at resolve time by `packageName`, so this gate does
|
|
165
|
+
// NOT descend into members or require a root `interchange.workflow`. It
|
|
166
|
+
// validates ROOT well-formedness only -- `workspaces` is an array of glob
|
|
167
|
+
// strings -- and defers per-member validation to the resolver, which re-reads
|
|
168
|
+
// every member and fails loud there (one enumeration owner, not two).
|
|
169
|
+
if (isJSONObject(pkgOutcome.value) && "workspaces" in pkgOutcome.value) {
|
|
170
|
+
const workspaces = pkgOutcome.value["workspaces"];
|
|
171
|
+
if (!Array.isArray(workspaces) ||
|
|
172
|
+
!workspaces.every((w) => typeof w === "string")) {
|
|
173
|
+
return rejectPush(repoId, ref, `${PACKAGE_JSON_PATH} "workspaces" must be an array of glob strings; the object form ({ packages, catalog, catalogs }) is not supported`);
|
|
174
|
+
}
|
|
175
|
+
return { ok: true };
|
|
176
|
+
}
|
|
177
|
+
// A pnpm monorepo declares its members in `pnpm-workspace.yaml`, not the
|
|
178
|
+
// package.json `workspaces` field, so a pnpm root has no `workspaces` and
|
|
179
|
+
// would fall through to the single-package check below. Reject that layout at
|
|
180
|
+
// the boundary with a clear message rather than letting it fail obscurely at
|
|
181
|
+
// resolve time (full pnpm support is tracked in INTR-461).
|
|
182
|
+
if (topLevelTreePaths.includes(PNPM_WORKSPACE_PATH)) {
|
|
183
|
+
return rejectPush(repoId, ref, `tree declares a ${PNPM_WORKSPACE_PATH}; the pnpm workspace layout is not supported -- declare members via a package.json "workspaces" array`);
|
|
184
|
+
}
|
|
185
|
+
const pkg = PackageJSON(pkgOutcome.value);
|
|
186
|
+
if (pkg instanceof type.errors) {
|
|
187
|
+
return rejectPush(repoId, ref, `${PACKAGE_JSON_PATH} failed validation: ${pkg.summary}`);
|
|
188
|
+
}
|
|
189
|
+
const entry = pkg.interchange?.workflow;
|
|
190
|
+
if (entry === undefined || entry === "") {
|
|
191
|
+
return rejectPush(repoId, ref, `${PACKAGE_JSON_PATH} must declare a non-empty "interchange.workflow" entry`);
|
|
192
|
+
}
|
|
193
|
+
if (!isContainedEntryPath(entry)) {
|
|
194
|
+
return rejectPush(repoId, ref, `"interchange.workflow" entry ${JSON.stringify(entry)} must be a package-relative path that does not escape the package`);
|
|
195
|
+
}
|
|
196
|
+
return { ok: true };
|
|
197
|
+
}
|
|
108
198
|
export const workflowKindHandler = {
|
|
109
199
|
kind: "workflow",
|
|
110
200
|
directoryPrefix: "assets/workflow",
|
|
111
201
|
async validatePush({ repoId, ref, topLevelTreePaths, readBlob, }) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
ok: false,
|
|
116
|
-
reason: `unexpected top-level entry ${JSON.stringify(entry)}; allowed: "${WORKFLOW_JSON_PATH}", "${CAPABILITY_DECLARATIONS_JSON_PATH}", "${WORKFLOW_GITIGNORE_PATH}"`,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// A workflow asset without `workflow.json` is structurally
|
|
121
|
-
// incoherent: there is nothing for the deploy orchestrator to
|
|
122
|
-
// hydrate. Reject so the push surfaces the missing envelope at
|
|
123
|
-
// the boundary rather than at hydrate time.
|
|
124
|
-
if (!topLevelTreePaths.includes(WORKFLOW_JSON_PATH)) {
|
|
125
|
-
return {
|
|
126
|
-
ok: false,
|
|
127
|
-
reason: `tree is missing required ${WORKFLOW_JSON_PATH}`,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
const workflowOutcome = await readJSONBlob(WORKFLOW_JSON_PATH, readBlob);
|
|
131
|
-
if (!workflowOutcome.ok) {
|
|
132
|
-
logger.debug `workflow validatePush rejected ${repoId.kind}/${repoId.id} on ${ref}: ${workflowOutcome.reason}`;
|
|
133
|
-
return { ok: false, reason: workflowOutcome.reason };
|
|
134
|
-
}
|
|
135
|
-
const validated = workflowDefinitionEnvelopeSchema(workflowOutcome.value);
|
|
136
|
-
if (validated instanceof type.errors) {
|
|
137
|
-
const reason = `${WORKFLOW_JSON_PATH} failed validation: ${validated.summary}`;
|
|
138
|
-
logger.debug `workflow validatePush rejected ${repoId.kind}/${repoId.id} on ${ref}: ${reason}`;
|
|
139
|
-
return { ok: false, reason };
|
|
202
|
+
if (topLevelTreePaths.includes(PACKAGE_JSON_PATH)) {
|
|
203
|
+
return validateWorkflowCodebasePush(repoId, ref, topLevelTreePaths, readBlob);
|
|
140
204
|
}
|
|
141
|
-
|
|
142
|
-
const capOutcome = await readJSONBlob(CAPABILITY_DECLARATIONS_JSON_PATH, readBlob);
|
|
143
|
-
if (!capOutcome.ok) {
|
|
144
|
-
logger.debug `workflow validatePush rejected ${repoId.kind}/${repoId.id} on ${ref}: ${capOutcome.reason}`;
|
|
145
|
-
return { ok: false, reason: capOutcome.reason };
|
|
146
|
-
}
|
|
147
|
-
const capValidated = CapabilityDeclarationsObject(capOutcome.value);
|
|
148
|
-
if (capValidated instanceof type.errors) {
|
|
149
|
-
const reason = `${CAPABILITY_DECLARATIONS_JSON_PATH} must be a JSON object: ${capValidated.summary}`;
|
|
150
|
-
logger.debug `workflow validatePush rejected ${repoId.kind}/${repoId.id} on ${ref}: ${reason}`;
|
|
151
|
-
return { ok: false, reason };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return { ok: true };
|
|
205
|
+
return rejectPush(repoId, ref, `a workflow asset must be a codebase declaring a ${PACKAGE_JSON_PATH} with an "interchange.workflow" entry; the ${WORKFLOW_JSON_PATH} envelope form is no longer supported`);
|
|
155
206
|
},
|
|
156
207
|
onRefUpdated() {
|
|
157
|
-
// No cached index today. Consumers read the
|
|
158
|
-
//
|
|
159
|
-
// API at session time.
|
|
208
|
+
// No cached index today. Consumers read the asset's tree through the
|
|
209
|
+
// substrate's blob-read API at session time.
|
|
160
210
|
},
|
|
161
211
|
};
|
|
162
212
|
export const workflowAuthorize = (principal, repoId, ref, action) => {
|
|
@@ -198,64 +248,18 @@ export const workflowAuthorize = (principal, repoId, ref, action) => {
|
|
|
198
248
|
}
|
|
199
249
|
}
|
|
200
250
|
if (principal.kind === "user") {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const parsed = UserPrincipal(principal);
|
|
209
|
-
if (parsed instanceof type.errors) {
|
|
210
|
-
return {
|
|
211
|
-
allowed: false,
|
|
212
|
-
reason: `user principal is malformed: ${parsed.summary}`,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
if (!parsed.tokenClaims.actions.includes(action)) {
|
|
216
|
-
return {
|
|
217
|
-
allowed: false,
|
|
218
|
-
reason: `token does not grant action ${action}`,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
// `ref === "*"` is the substrate's sentinel for the bulk read
|
|
222
|
-
// performed by `listRefs`. Per-ref filtering is the advertise-refs
|
|
223
|
-
// layer's responsibility, so the bulk read is gated on action and
|
|
224
|
-
// expiry alone.
|
|
225
|
-
if (ref !== "*" && !glob.match(parsed.tokenClaims.refPattern, ref)) {
|
|
226
|
-
return {
|
|
227
|
-
allowed: false,
|
|
228
|
-
reason: `token refPattern ${parsed.tokenClaims.refPattern} does not match ${ref}`,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
if (Date.now() >= parsed.tokenClaims.expiresAt) {
|
|
232
|
-
return {
|
|
233
|
-
allowed: false,
|
|
234
|
-
reason: `token expired at ${parsed.tokenClaims.expiresAt}`,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
const expectedResource = `asset:${repoId.id}`;
|
|
238
|
-
if (parsed.authz.resource !== expectedResource) {
|
|
239
|
-
return {
|
|
240
|
-
allowed: false,
|
|
241
|
-
reason: `authz verdict resource ${parsed.authz.resource} does not match ${expectedResource}`,
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
const expectedGrantVerb = repoActionToGrantVerb(action);
|
|
245
|
-
if (parsed.authz.grantVerb !== expectedGrantVerb) {
|
|
246
|
-
return {
|
|
247
|
-
allowed: false,
|
|
248
|
-
reason: `authz verdict grantVerb ${parsed.authz.grantVerb} does not match ${expectedGrantVerb}`,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
if (parsed.authz.effect === "allow") {
|
|
252
|
-
return { allowed: true };
|
|
253
|
-
}
|
|
254
|
-
return {
|
|
255
|
-
allowed: false,
|
|
256
|
-
reason: `authz verdict denied for ${expectedResource} ${expectedGrantVerb}`,
|
|
257
|
-
};
|
|
251
|
+
return authorizeUserPrincipal({
|
|
252
|
+
principal,
|
|
253
|
+
repoId,
|
|
254
|
+
ref,
|
|
255
|
+
action,
|
|
256
|
+
resourcePrefix: "asset",
|
|
257
|
+
});
|
|
258
258
|
}
|
|
259
|
+
// Fail closed on any kind not handled above. The tenant-level
|
|
260
|
+
// `workflow` principal kind (`@intx/types` principalKinds) is a
|
|
261
|
+
// grant owner, not a git/asset bearer, and never carries a workflow
|
|
262
|
+
// repo push here -- so it is intentionally left denied.
|
|
259
263
|
return {
|
|
260
264
|
allowed: false,
|
|
261
265
|
reason: `unknown principal kind: ${principal.kind}`,
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import type { DBExecutor } from "@intx/db";
|
|
2
|
+
import type { ApprovalItem, GrantRequirement, GrantWalkSnapshot } from "@intx/types";
|
|
3
|
+
import type { PackumentFetcher, RegistryConfig } from "@intx/tool-packaging";
|
|
4
|
+
import type { WorkflowProjectionDefinition } from "@intx/types/sidecar";
|
|
5
|
+
import type { ToolPackageManifest } from "@intx/types/tool-packages";
|
|
6
|
+
import type { WorkflowDefinitionAssetSource, WorkflowDefinitionRegistrySource } from "@intx/types/workflow-sources";
|
|
7
|
+
import { type ApprovalSet } from "@intx/workflow-deploy";
|
|
8
|
+
import { type ResolveAssetAttachmentFn } from "./workflow-closure-resolution.js";
|
|
9
|
+
import type { SourceTreeReads } from "./workflow-source-closure.js";
|
|
10
|
+
import type { SendProbeArgs, WorkflowProbeResult } from "./ws/sidecar-handler.js";
|
|
11
|
+
type WorkflowProbeRouter = {
|
|
12
|
+
sendProbe(args: SendProbeArgs): Promise<WorkflowProbeResult>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The frozen record an approval writes: the definition's asset selector, the
|
|
16
|
+
* approved wire hash (the freeze anchor), the approved surface, and the
|
|
17
|
+
* grant-walk snapshot the run path materializes grants from. The approved
|
|
18
|
+
* surface is a deterministic projection of the content the hash addresses and
|
|
19
|
+
* rides the deploy hand-off in memory; the snapshot is persisted onto the
|
|
20
|
+
* version row so a run derives its grants from the frozen walk without
|
|
21
|
+
* re-reading and re-walking the workflow's `workflow.json`.
|
|
22
|
+
*
|
|
23
|
+
* `approvedGrants` carries both kinds of approved item: the walk's grant-shape
|
|
24
|
+
* strings and the definition's declared grant requirements. The requirements
|
|
25
|
+
* belong in the same record because the run path mints real grant rows from
|
|
26
|
+
* them, so an account of the approval that listed only the walk strings would
|
|
27
|
+
* under-report the authority the definition will actually carry. It is the flat
|
|
28
|
+
* `ApprovalItem` list rather than the gate's partitioned `ApprovalSet` because
|
|
29
|
+
* this record is the input to persistence, and the persisted form is flat.
|
|
30
|
+
*/
|
|
31
|
+
export type FrozenApproval = {
|
|
32
|
+
readonly assetId: string;
|
|
33
|
+
readonly approvedWireHash: string;
|
|
34
|
+
readonly approvedGrants: readonly ApprovalItem[];
|
|
35
|
+
readonly grantSnapshot: GrantWalkSnapshot;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Persists a frozen approval and returns the definition it was recorded
|
|
39
|
+
* against. Bound to a `DBExecutor` in production via
|
|
40
|
+
* `createDbFrozenApprovalWriter`; a test double records the call.
|
|
41
|
+
*/
|
|
42
|
+
export type PersistFrozenApprovalFn = (approval: FrozenApproval) => Promise<{
|
|
43
|
+
definitionId: string;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* The outcome of gating and freezing a probe result. `ok: true` is the frozen
|
|
47
|
+
* approval the deploy hand-off consumes. The `ok: false` arms name the five
|
|
48
|
+
* fail-closed paths: a shipped hash that does not match the hub recompute
|
|
49
|
+
* (tamper-evidence), advisory grants the operator did not approve, declared
|
|
50
|
+
* grant requirements the operator did not approve, a trigger type the runtime
|
|
51
|
+
* does not implement, and an executable step the grant walk left no record for.
|
|
52
|
+
*/
|
|
53
|
+
export type ProbeGateResult = {
|
|
54
|
+
readonly ok: true;
|
|
55
|
+
readonly definitionId: string;
|
|
56
|
+
readonly approvedWireHash: string;
|
|
57
|
+
readonly approvedSurface: ApprovalSet;
|
|
58
|
+
/**
|
|
59
|
+
* The inert wire projection the freeze hashed. Rides the ok-arm so the
|
|
60
|
+
* deploy hand-off carries the exact content the frozen hash addresses,
|
|
61
|
+
* never a re-projection of a registry that may have moved since approval.
|
|
62
|
+
*/
|
|
63
|
+
readonly projection: WorkflowProjectionDefinition;
|
|
64
|
+
} | {
|
|
65
|
+
readonly ok: false;
|
|
66
|
+
readonly reason: "wire_hash_mismatch";
|
|
67
|
+
readonly shippedWireHash: string;
|
|
68
|
+
readonly recomputedWireHash: string;
|
|
69
|
+
} | {
|
|
70
|
+
readonly ok: false;
|
|
71
|
+
readonly reason: "grants_not_approved";
|
|
72
|
+
readonly unapprovedGrants: readonly string[];
|
|
73
|
+
} | {
|
|
74
|
+
readonly ok: false;
|
|
75
|
+
readonly reason: "grant_requirements_not_approved";
|
|
76
|
+
/**
|
|
77
|
+
* The declared requirements the operator's approval does not cover, in
|
|
78
|
+
* the order the probe declared them. Named in full so the operator can
|
|
79
|
+
* see exactly which authority the definition asked to delegate.
|
|
80
|
+
*/
|
|
81
|
+
readonly unapprovedGrantRequirements: readonly GrantRequirement[];
|
|
82
|
+
} | {
|
|
83
|
+
readonly ok: false;
|
|
84
|
+
readonly reason: "unimplemented_trigger";
|
|
85
|
+
/** The distinct reserved-but-unimplemented trigger types the projection declared. */
|
|
86
|
+
readonly unimplementedTriggerTypes: readonly string[];
|
|
87
|
+
} | {
|
|
88
|
+
readonly ok: false;
|
|
89
|
+
readonly reason: "steps_without_grant_record";
|
|
90
|
+
/** Every executable step the grant-walk snapshot accounts for nothing at. */
|
|
91
|
+
readonly stepsWithoutGrantRecord: readonly StepWithoutGrantRecord[];
|
|
92
|
+
/**
|
|
93
|
+
* One sentence naming every miss. A deploy that trips this is a defect in
|
|
94
|
+
* the deploy path rather than in the author's workflow, so the sentence
|
|
95
|
+
* carries what whoever maintains that path needs: the step, the position,
|
|
96
|
+
* and the record that was absent.
|
|
97
|
+
*/
|
|
98
|
+
readonly message: string;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* One step the deployment can execute that the probe's grant-walk snapshot
|
|
102
|
+
* carries no record for.
|
|
103
|
+
*/
|
|
104
|
+
export type StepWithoutGrantRecord = {
|
|
105
|
+
/** The executable step id with no approved grants behind it. */
|
|
106
|
+
readonly stepId: string;
|
|
107
|
+
/**
|
|
108
|
+
* The chain of step ids the executable walk reached `stepId` through,
|
|
109
|
+
* outermost first and `stepId` itself last. Two nested bodies may
|
|
110
|
+
* legitimately carry the same step id, so the chain -- not the id alone --
|
|
111
|
+
* is what names the position in the closure.
|
|
112
|
+
*/
|
|
113
|
+
readonly reachedThrough: readonly string[];
|
|
114
|
+
/**
|
|
115
|
+
* The top-level step whose snapshot record was supposed to account for
|
|
116
|
+
* `stepId`. The capability walk folds every nested body's grants into the
|
|
117
|
+
* record of the top-level step that carries the body, so this names the
|
|
118
|
+
* `perStep` key the absent record would have had.
|
|
119
|
+
*/
|
|
120
|
+
readonly recordStepId: string;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Build the production persistence step of the freeze. Records identity through
|
|
124
|
+
* the selector-keyed ensure helper (a definition keyed by `(assetId,
|
|
125
|
+
* wireHash)`) and writes the approved wire hash and the grant-walk snapshot onto
|
|
126
|
+
* that definition's version row in one transaction. The grant SET is not written
|
|
127
|
+
* to a version-row column -- the approved wire hash already pins the content the
|
|
128
|
+
* grants project from -- so it travels with the returned frozen approval; the
|
|
129
|
+
* snapshot is written because the run path reads it back to materialize grants.
|
|
130
|
+
*/
|
|
131
|
+
export declare function createDbFrozenApprovalWriter(db: DBExecutor): PersistFrozenApprovalFn;
|
|
132
|
+
/**
|
|
133
|
+
* Approve exactly the grant surface the probe reports, without a pre-walked
|
|
134
|
+
* operator `ApprovalSet` to gate against. Under this mode the gate skips the
|
|
135
|
+
* per-grant and per-requirement membership checks and freezes exactly what the
|
|
136
|
+
* probe advertised.
|
|
137
|
+
*
|
|
138
|
+
* This is the code-sourced analogue of the live-authored self-approve: the hub
|
|
139
|
+
* has no live definition to pre-walk, so the probe's advertised grants ARE the
|
|
140
|
+
* declared surface. It does NOT relax tamper-evidence -- the wire-hash check
|
|
141
|
+
* still runs and can still fail closed.
|
|
142
|
+
*/
|
|
143
|
+
export type ApproveProbedGrants = {
|
|
144
|
+
readonly kind: "approve-probed";
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* How the gate turns the probe's advertised surface into an approved one.
|
|
148
|
+
* Either an explicit operator `ApprovalSet` -- every advertised grant and every
|
|
149
|
+
* declared grant requirement must appear in it or the gate fails closed -- or
|
|
150
|
+
* `approve-probed`, which approves exactly the surface the probe reported.
|
|
151
|
+
*/
|
|
152
|
+
export type ProbeApprovalPolicy = ApprovalSet | ApproveProbedGrants;
|
|
153
|
+
export type GateAndFreezeArgs = {
|
|
154
|
+
/** The `workflow`-kind asset the frozen definition projects over. */
|
|
155
|
+
readonly assetId: string;
|
|
156
|
+
/** The sidecar's inert probe answer: projection, advisory grants, shipped hash. */
|
|
157
|
+
readonly probeResult: WorkflowProbeResult;
|
|
158
|
+
/**
|
|
159
|
+
* The approval policy. An `ApprovalSet` gates the advisory set and the
|
|
160
|
+
* declared grant requirements against the operator-approved items;
|
|
161
|
+
* `approve-probed` approves exactly the surface the probe reported.
|
|
162
|
+
*/
|
|
163
|
+
readonly approvals: ProbeApprovalPolicy;
|
|
164
|
+
/** Persistence step for the freeze; `createDbFrozenApprovalWriter` in production. */
|
|
165
|
+
readonly persist: PersistFrozenApprovalFn;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Gate a probe result and, on approval, freeze it. Operates purely over the
|
|
169
|
+
* inert projection and grant set -- no author code runs here and the capability
|
|
170
|
+
* walk is never re-run.
|
|
171
|
+
*
|
|
172
|
+
* Fails closed on the three security-load-bearing checks before it writes
|
|
173
|
+
* anything: the recomputed wire hash must match the hash the sidecar shipped
|
|
174
|
+
* (tamper-evidence), every advisory grant must be operator-approved, and every
|
|
175
|
+
* declared grant requirement must be operator-approved. It also refuses a
|
|
176
|
+
* projection whose triggers include a reserved-but-unimplemented type -- not a
|
|
177
|
+
* security check, but the layer a pinned closure cannot carry a stale copy of,
|
|
178
|
+
* so it is where a workflow that could only sit inert is caught -- and one
|
|
179
|
+
* whose executable closure reaches a step the grant walk left no record for --
|
|
180
|
+
* the layer holding both halves of the probe answer at once, and a presence
|
|
181
|
+
* check on those records rather than a check that any record's contents are
|
|
182
|
+
* sufficient.
|
|
183
|
+
* Only then does it freeze the recomputed hash onto the version row and return
|
|
184
|
+
* the approved grant set.
|
|
185
|
+
*/
|
|
186
|
+
export declare function gateAndFreezeProbeResult(args: GateAndFreezeArgs): Promise<ProbeGateResult>;
|
|
187
|
+
type InstallAndApproveCommonArgs = {
|
|
188
|
+
/** The `interchange.workflow` entry-module path the sidecar evaluates to project the definition. */
|
|
189
|
+
readonly entry: string;
|
|
190
|
+
/** The `workflow`-kind asset the frozen definition projects over. */
|
|
191
|
+
readonly assetId: string;
|
|
192
|
+
/**
|
|
193
|
+
* The approval policy threaded to the gate: an operator `ApprovalSet` to gate
|
|
194
|
+
* the advisory set against, or `approve-probed` to approve exactly the
|
|
195
|
+
* surface the probe reports.
|
|
196
|
+
*/
|
|
197
|
+
readonly approvals: ProbeApprovalPolicy;
|
|
198
|
+
/** The sidecar router carrying the probe transport. */
|
|
199
|
+
readonly router: WorkflowProbeRouter;
|
|
200
|
+
/** Executor the freeze writes through. */
|
|
201
|
+
readonly db: DBExecutor;
|
|
202
|
+
/** Optional durable handoff invoked before the approval gate writes. */
|
|
203
|
+
readonly onProbeResult?: (result: WorkflowProbeResult) => Promise<void>;
|
|
204
|
+
};
|
|
205
|
+
/** Install a definition published to an npm registry. */
|
|
206
|
+
export type InstallAndApproveRegistryArgs = InstallAndApproveCommonArgs & {
|
|
207
|
+
readonly source: WorkflowDefinitionRegistrySource;
|
|
208
|
+
/** A `name@range` spec for the workflow definition package. */
|
|
209
|
+
readonly pin: string;
|
|
210
|
+
/** URL and credentials for the registry `source` names. */
|
|
211
|
+
readonly registryConfig: RegistryConfig;
|
|
212
|
+
/** Test seam for packument fetches, threaded to closure resolution. Omitted in production. */
|
|
213
|
+
readonly fetchPackument?: PackumentFetcher;
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Install a definition published as a tarball inside a hub `package-registry`
|
|
217
|
+
* asset. The caller mints the asset-read closures (`readBlob`/`listBlobs`) and
|
|
218
|
+
* `resolveAttachment`; this glue never imports the asset service, so hub-service
|
|
219
|
+
* ownership stays at the caller.
|
|
220
|
+
*/
|
|
221
|
+
export type InstallAndApproveAssetTarballArgs = InstallAndApproveCommonArgs & {
|
|
222
|
+
readonly source: WorkflowDefinitionAssetSource;
|
|
223
|
+
/** A `name@range` spec for the workflow definition package. */
|
|
224
|
+
readonly pin: string;
|
|
225
|
+
/** Reads a blob at `path` from the asset the definition is sourced from. */
|
|
226
|
+
readonly readBlob: (path: string) => Promise<Uint8Array>;
|
|
227
|
+
/** Lists the blob names directly under `dir` in that asset. */
|
|
228
|
+
readonly listBlobs: (dir: string) => Promise<string[]>;
|
|
229
|
+
/** Resolves each asset the closure references to the pack the probe delivers. */
|
|
230
|
+
readonly resolveAttachment: ResolveAssetAttachmentFn;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Install a definition whose package lives as a git subtree of a hub asset at a
|
|
234
|
+
* pinned commit. The caller binds a `SourceTreeReads` to that commit and
|
|
235
|
+
* supplies the npm `registryConfig` for the external deps; there is no
|
|
236
|
+
* `name@range` pin (the member is selected from `source.package.packageName`).
|
|
237
|
+
* `resolveAttachment` delivers the same git pack the tarball arm does, so the
|
|
238
|
+
* sidecar checks the subtree out of it.
|
|
239
|
+
*/
|
|
240
|
+
export type InstallAndApproveAssetSourceArgs = InstallAndApproveCommonArgs & {
|
|
241
|
+
readonly source: WorkflowDefinitionAssetSource;
|
|
242
|
+
/** Git-tree reads pinned to `source.package.commitSha`. */
|
|
243
|
+
readonly reads: SourceTreeReads;
|
|
244
|
+
/**
|
|
245
|
+
* The registry name external deps are stamped with in the frozen closure.
|
|
246
|
+
* Must be a name the sidecar's registry map is keyed by (its npm registry).
|
|
247
|
+
*/
|
|
248
|
+
readonly registryName: string;
|
|
249
|
+
/** URL and credentials for the npm registry external deps resolve against. */
|
|
250
|
+
readonly registryConfig: RegistryConfig;
|
|
251
|
+
/** Test seam for packument fetches, threaded to closure resolution. Omitted in production. */
|
|
252
|
+
readonly fetchPackument?: PackumentFetcher;
|
|
253
|
+
/** Resolves each asset the closure references to the pack the probe delivers. */
|
|
254
|
+
readonly resolveAttachment: ResolveAssetAttachmentFn;
|
|
255
|
+
};
|
|
256
|
+
export type InstallAndApproveArgs = InstallAndApproveRegistryArgs | InstallAndApproveAssetTarballArgs | InstallAndApproveAssetSourceArgs;
|
|
257
|
+
/**
|
|
258
|
+
* The frozen hand-off `installAndApproveWorkflowDefinition` produces. It carries
|
|
259
|
+
* the gate outcome plus the two values the source-ref deploy frame needs and
|
|
260
|
+
* must NOT recompute at deploy: the inert `projection` the freeze hashed and the
|
|
261
|
+
* frozen dependency `closure` the pin resolved to. Re-resolving either at deploy
|
|
262
|
+
* would reintroduce the non-determinism the freeze eliminates -- a registry that
|
|
263
|
+
* moved between approve and deploy would pin different bytes and project
|
|
264
|
+
* differently, failing the child re-verify -- so both ride from approve verbatim.
|
|
265
|
+
*/
|
|
266
|
+
export type InstallAndApproveResult = {
|
|
267
|
+
readonly approval: ProbeGateResult;
|
|
268
|
+
readonly projection: WorkflowProjectionDefinition;
|
|
269
|
+
readonly closure: ToolPackageManifest;
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* The install/approve orchestration entrypoint the end-to-end flow drives:
|
|
273
|
+
* resolve the frozen closure, probe the sidecar, then gate and freeze the
|
|
274
|
+
* result. This is production glue, not test-only wiring.
|
|
275
|
+
*
|
|
276
|
+
* The operator-approval decision is an input (`approvals`): the caller supplies
|
|
277
|
+
* either the `ApprovalSet` the operator approved, which the gate holds the
|
|
278
|
+
* advisory set to, or `approve-probed` to approve exactly the surface the probe
|
|
279
|
+
* reports.
|
|
280
|
+
*
|
|
281
|
+
* Returns the gate outcome alongside the inert projection and the frozen
|
|
282
|
+
* closure so the deploy hand-off consumes them verbatim rather than re-probing
|
|
283
|
+
* or re-resolving.
|
|
284
|
+
*/
|
|
285
|
+
export declare function installAndApproveWorkflowDefinition(args: InstallAndApproveArgs): Promise<InstallAndApproveResult>;
|
|
286
|
+
export {};
|