@intx/hub-sessions 0.1.2 → 0.3.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.
Files changed (113) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +82 -1
  3. package/dist/agent-repo.d.ts +93 -0
  4. package/dist/agent-repo.js +109 -0
  5. package/dist/agent-state-kind.d.ts +12 -0
  6. package/dist/agent-state-kind.js +189 -0
  7. package/dist/asset-service.d.ts +104 -0
  8. package/dist/asset-service.js +267 -0
  9. package/dist/committed-source-tree.d.ts +10 -0
  10. package/dist/committed-source-tree.js +35 -0
  11. package/dist/credential-push.d.ts +33 -0
  12. package/dist/credential-push.js +109 -0
  13. package/dist/event-collector-registry.d.ts +20 -0
  14. package/dist/event-collector-registry.js +115 -0
  15. package/dist/event-collector.d.ts +39 -0
  16. package/dist/event-collector.js +365 -0
  17. package/dist/hub-session-lookups.d.ts +135 -0
  18. package/dist/hub-session-lookups.js +663 -0
  19. package/dist/hub-session-orchestrator.d.ts +25 -0
  20. package/dist/hub-session-orchestrator.js +87 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.js +24 -0
  23. package/dist/package-registry-kind.d.ts +70 -0
  24. package/dist/package-registry-kind.js +260 -0
  25. package/dist/repo-store/index.d.ts +4 -0
  26. package/dist/repo-store/index.js +3 -0
  27. package/dist/repo-store/store.d.ts +41 -0
  28. package/dist/repo-store/store.js +1829 -0
  29. package/dist/repo-store/subscribe-kind.d.ts +56 -0
  30. package/dist/repo-store/subscribe-kind.js +144 -0
  31. package/dist/repo-store/types.d.ts +571 -0
  32. package/dist/repo-store/types.js +42 -0
  33. package/dist/session-service.d.ts +416 -0
  34. package/dist/session-service.js +1191 -0
  35. package/dist/sidecar-allocation/contracts.d.ts +78 -0
  36. package/dist/sidecar-allocation/contracts.js +21 -0
  37. package/dist/sidecar-allocation/index.d.ts +4 -0
  38. package/dist/sidecar-allocation/index.js +3 -0
  39. package/dist/sidecar-allocation/placement-policy.d.ts +11 -0
  40. package/dist/sidecar-allocation/placement-policy.js +21 -0
  41. package/dist/sidecar-allocation/plugin-registry.d.ts +11 -0
  42. package/dist/sidecar-allocation/plugin-registry.js +37 -0
  43. package/dist/sidecar-allocation/reconciler.d.ts +42 -0
  44. package/dist/sidecar-allocation/reconciler.js +431 -0
  45. package/dist/skill-kind.d.ts +41 -0
  46. package/dist/skill-kind.js +292 -0
  47. package/dist/substrate.d.ts +8 -0
  48. package/dist/substrate.js +21 -0
  49. package/dist/workflow-allocation-service.d.ts +58 -0
  50. package/dist/workflow-allocation-service.js +239 -0
  51. package/dist/workflow-closure-resolution.d.ts +106 -0
  52. package/dist/workflow-closure-resolution.js +123 -0
  53. package/dist/workflow-definition-ensure.d.ts +24 -0
  54. package/dist/workflow-definition-ensure.js +75 -0
  55. package/dist/workflow-dispatch-service.d.ts +40 -0
  56. package/dist/workflow-dispatch-service.js +146 -0
  57. package/dist/workflow-dispatch-settlement.d.ts +29 -0
  58. package/dist/workflow-dispatch-settlement.js +140 -0
  59. package/dist/workflow-kind.d.ts +37 -0
  60. package/dist/workflow-kind.js +310 -0
  61. package/dist/workflow-probe-gate.d.ts +214 -0
  62. package/dist/workflow-probe-gate.js +207 -0
  63. package/dist/workflow-run-event-log.d.ts +21 -0
  64. package/dist/workflow-run-event-log.js +51 -0
  65. package/dist/workflow-run-kind.d.ts +440 -0
  66. package/dist/workflow-run-kind.js +2916 -0
  67. package/dist/workflow-run-reader.d.ts +47 -0
  68. package/dist/workflow-run-reader.js +153 -0
  69. package/dist/workflow-run-restore.d.ts +15 -0
  70. package/dist/workflow-run-restore.js +26 -0
  71. package/dist/workflow-source-closure.d.ts +35 -0
  72. package/dist/workflow-source-closure.js +342 -0
  73. package/dist/ws/index.d.ts +3 -0
  74. package/dist/ws/index.js +3 -0
  75. package/dist/ws/sidecar-events.d.ts +222 -0
  76. package/dist/ws/sidecar-events.js +72 -0
  77. package/dist/ws/sidecar-handler.d.ts +305 -0
  78. package/dist/ws/sidecar-handler.js +2537 -0
  79. package/dist/ws/sidecar-token-authenticator.d.ts +17 -0
  80. package/dist/ws/sidecar-token-authenticator.js +81 -0
  81. package/package.json +35 -12
  82. package/src/agent-repo.test.ts +0 -310
  83. package/src/agent-repo.ts +0 -165
  84. package/src/agent-state-kind.test.ts +0 -247
  85. package/src/agent-state-kind.ts +0 -204
  86. package/src/asset-service.test.ts +0 -540
  87. package/src/asset-service.ts +0 -378
  88. package/src/available-skills-stanza.test.ts +0 -87
  89. package/src/available-skills-stanza.ts +0 -47
  90. package/src/credential-push.ts +0 -65
  91. package/src/event-collector-registry.test.ts +0 -73
  92. package/src/event-collector-registry.ts +0 -171
  93. package/src/event-collector.test.ts +0 -1387
  94. package/src/event-collector.ts +0 -424
  95. package/src/hub-session-lookups.ts +0 -206
  96. package/src/hub-session-orchestrator.test.ts +0 -510
  97. package/src/hub-session-orchestrator.ts +0 -213
  98. package/src/index.ts +0 -78
  99. package/src/repo-store/index.ts +0 -15
  100. package/src/repo-store/store.test.ts +0 -1169
  101. package/src/repo-store/store.ts +0 -428
  102. package/src/repo-store/types.ts +0 -253
  103. package/src/session-service.test.ts +0 -895
  104. package/src/session-service.ts +0 -464
  105. package/src/skill-kind.test.ts +0 -599
  106. package/src/skill-kind.ts +0 -350
  107. package/src/ws/index.ts +0 -18
  108. package/src/ws/sidecar-events.test.ts +0 -96
  109. package/src/ws/sidecar-events.ts +0 -231
  110. package/src/ws/sidecar-handler.test.ts +0 -2217
  111. package/src/ws/sidecar-handler.ts +0 -1574
  112. package/tsconfig.json +0 -4
  113. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,214 @@
1
+ import type { DBExecutor } from "@intx/db";
2
+ import type { 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 { SidecarRouter, WorkflowProbeResult } from "./ws/sidecar-handler.js";
11
+ /**
12
+ * The frozen record an approval writes: the definition's asset selector, the
13
+ * approved wire hash (the freeze anchor), the approved grant set, and the
14
+ * grant-walk snapshot the run path materializes grants from. The grant set is a
15
+ * deterministic projection of the content the hash addresses and rides the
16
+ * deploy hand-off in memory; the snapshot is persisted onto the version row so a
17
+ * run derives its grants from the frozen walk without re-reading and re-walking
18
+ * the workflow's `workflow.json`.
19
+ */
20
+ export type FrozenApproval = {
21
+ readonly assetId: string;
22
+ readonly approvedWireHash: string;
23
+ readonly approvedGrants: readonly string[];
24
+ readonly grantSnapshot: GrantWalkSnapshot;
25
+ };
26
+ /**
27
+ * Persists a frozen approval and returns the definition it was recorded
28
+ * against. Bound to a `DBExecutor` in production via
29
+ * `createDbFrozenApprovalWriter`; a test double records the call.
30
+ */
31
+ export type PersistFrozenApprovalFn = (approval: FrozenApproval) => Promise<{
32
+ definitionId: string;
33
+ }>;
34
+ /**
35
+ * The outcome of gating and freezing a probe result. `ok: true` is the frozen
36
+ * approval the deploy hand-off consumes. The `ok: false` arms name the two
37
+ * fail-closed paths: a shipped hash that does not match the hub recompute
38
+ * (tamper-evidence), and advisory grants the operator did not approve.
39
+ */
40
+ export type ProbeGateResult = {
41
+ readonly ok: true;
42
+ readonly definitionId: string;
43
+ readonly approvedWireHash: string;
44
+ readonly approvedGrants: ReadonlySet<string>;
45
+ /**
46
+ * The inert wire projection the freeze hashed. Rides the ok-arm so the
47
+ * deploy hand-off carries the exact content the frozen hash addresses,
48
+ * never a re-projection of a registry that may have moved since approval.
49
+ */
50
+ readonly projection: WorkflowProjectionDefinition;
51
+ } | {
52
+ readonly ok: false;
53
+ readonly reason: "wire_hash_mismatch";
54
+ readonly shippedWireHash: string;
55
+ readonly recomputedWireHash: string;
56
+ } | {
57
+ readonly ok: false;
58
+ readonly reason: "grants_not_approved";
59
+ readonly unapprovedGrants: readonly string[];
60
+ };
61
+ /**
62
+ * Build the production persistence step of the freeze. Records identity through
63
+ * the selector-keyed ensure helper (a definition keyed by `(assetId,
64
+ * wireHash)`) and writes the approved wire hash and the grant-walk snapshot onto
65
+ * that definition's version row in one transaction. The grant SET is not written
66
+ * to a version-row column -- the approved wire hash already pins the content the
67
+ * grants project from -- so it travels with the returned frozen approval; the
68
+ * snapshot is written because the run path reads it back to materialize grants.
69
+ */
70
+ export declare function createDbFrozenApprovalWriter(db: DBExecutor): PersistFrozenApprovalFn;
71
+ /**
72
+ * Approve exactly the grant surface the probe reports, without a pre-walked
73
+ * operator `ApprovalSet` to gate against. Under this mode the gate skips the
74
+ * per-grant membership check and freezes exactly what the probe advertised.
75
+ *
76
+ * This is the code-sourced analogue of the live-authored self-approve: the hub
77
+ * has no live definition to pre-walk, so the probe's advertised grants ARE the
78
+ * declared surface. It does NOT relax tamper-evidence -- the wire-hash check
79
+ * still runs and can still fail closed.
80
+ */
81
+ export type ApproveProbedGrants = {
82
+ readonly mode: "approve-probed";
83
+ };
84
+ /**
85
+ * How the gate turns the probe's advisory grant set into an approved set.
86
+ * Either an explicit operator `ApprovalSet` -- every advertised grant must
87
+ * appear in it or the gate fails closed -- or `approve-probed`, which approves
88
+ * exactly the surface the probe reported.
89
+ */
90
+ export type ProbeApprovalPolicy = ApprovalSet | ApproveProbedGrants;
91
+ export type GateAndFreezeArgs = {
92
+ /** The `workflow`-kind asset the frozen definition projects over. */
93
+ readonly assetId: string;
94
+ /** The sidecar's inert probe answer: projection, advisory grants, shipped hash. */
95
+ readonly probeResult: WorkflowProbeResult;
96
+ /**
97
+ * The approval policy. An `ApprovalSet` gates the advisory set against the
98
+ * operator-approved grant-shape strings; `approve-probed` approves exactly
99
+ * the surface the probe reported.
100
+ */
101
+ readonly approvals: ProbeApprovalPolicy;
102
+ /** Persistence step for the freeze; `createDbFrozenApprovalWriter` in production. */
103
+ readonly persist: PersistFrozenApprovalFn;
104
+ };
105
+ /**
106
+ * Gate a probe result and, on approval, freeze it. Operates purely over the
107
+ * inert projection and grant set -- no author code runs here and the capability
108
+ * walk is never re-run.
109
+ *
110
+ * Fails closed on the two security-load-bearing checks before it writes
111
+ * anything: the recomputed wire hash must match the hash the sidecar shipped
112
+ * (tamper-evidence), and every advisory grant must be operator-approved. Only
113
+ * then does it freeze the recomputed hash onto the version row and return the
114
+ * approved grant set.
115
+ */
116
+ export declare function gateAndFreezeProbeResult(args: GateAndFreezeArgs): Promise<ProbeGateResult>;
117
+ type InstallAndApproveCommonArgs = {
118
+ /** The `interchange.workflow` entry-module path the sidecar evaluates to project the definition. */
119
+ readonly entry: string;
120
+ /** The `workflow`-kind asset the frozen definition projects over. */
121
+ readonly assetId: string;
122
+ /**
123
+ * The approval policy threaded to the gate: an operator `ApprovalSet` to gate
124
+ * the advisory set against, or `approve-probed` to approve exactly the
125
+ * surface the probe reports.
126
+ */
127
+ readonly approvals: ProbeApprovalPolicy;
128
+ /** The sidecar router carrying the probe transport. */
129
+ readonly router: Pick<SidecarRouter, "sendProbe">;
130
+ /** Executor the freeze writes through. */
131
+ readonly db: DBExecutor;
132
+ };
133
+ /** Install a definition published to an npm registry. */
134
+ export type InstallAndApproveRegistryArgs = InstallAndApproveCommonArgs & {
135
+ readonly source: WorkflowDefinitionRegistrySource;
136
+ /** A `name@range` spec for the workflow definition package. */
137
+ readonly pin: string;
138
+ /** URL and credentials for the registry `source` names. */
139
+ readonly registryConfig: RegistryConfig;
140
+ /** Test seam for packument fetches, threaded to closure resolution. Omitted in production. */
141
+ readonly fetchPackument?: PackumentFetcher;
142
+ };
143
+ /**
144
+ * Install a definition published as a tarball inside a hub `package-registry`
145
+ * asset. The caller mints the asset-read closures (`readBlob`/`listBlobs`) and
146
+ * `resolveAttachment`; this glue never imports the asset service, so hub-service
147
+ * ownership stays at the caller.
148
+ */
149
+ export type InstallAndApproveAssetTarballArgs = InstallAndApproveCommonArgs & {
150
+ readonly source: WorkflowDefinitionAssetSource;
151
+ /** A `name@range` spec for the workflow definition package. */
152
+ readonly pin: string;
153
+ /** Reads a blob at `path` from the asset the definition is sourced from. */
154
+ readonly readBlob: (path: string) => Promise<Uint8Array>;
155
+ /** Lists the blob names directly under `dir` in that asset. */
156
+ readonly listBlobs: (dir: string) => Promise<string[]>;
157
+ /** Resolves each asset the closure references to the pack the probe delivers. */
158
+ readonly resolveAttachment: ResolveAssetAttachmentFn;
159
+ };
160
+ /**
161
+ * Install a definition whose package lives as a git subtree of a hub asset at a
162
+ * pinned commit. The caller binds a `SourceTreeReads` to that commit and
163
+ * supplies the npm `registryConfig` for the external deps; there is no
164
+ * `name@range` pin (the member is selected from `source.package.packageName`).
165
+ * `resolveAttachment` delivers the same git pack the tarball arm does, so the
166
+ * sidecar checks the subtree out of it.
167
+ */
168
+ export type InstallAndApproveAssetSourceArgs = InstallAndApproveCommonArgs & {
169
+ readonly source: WorkflowDefinitionAssetSource;
170
+ /** Git-tree reads pinned to `source.package.commitSha`. */
171
+ readonly reads: SourceTreeReads;
172
+ /**
173
+ * The registry name external deps are stamped with in the frozen closure.
174
+ * Must be a name the sidecar's registry map is keyed by (its npm registry).
175
+ */
176
+ readonly registryName: string;
177
+ /** URL and credentials for the npm registry external deps resolve against. */
178
+ readonly registryConfig: RegistryConfig;
179
+ /** Test seam for packument fetches, threaded to closure resolution. Omitted in production. */
180
+ readonly fetchPackument?: PackumentFetcher;
181
+ /** Resolves each asset the closure references to the pack the probe delivers. */
182
+ readonly resolveAttachment: ResolveAssetAttachmentFn;
183
+ };
184
+ export type InstallAndApproveArgs = InstallAndApproveRegistryArgs | InstallAndApproveAssetTarballArgs | InstallAndApproveAssetSourceArgs;
185
+ /**
186
+ * The frozen hand-off `installAndApproveWorkflowDefinition` produces. It carries
187
+ * the gate outcome plus the two values the source-ref deploy frame needs and
188
+ * must NOT recompute at deploy: the inert `projection` the freeze hashed and the
189
+ * frozen dependency `closure` the pin resolved to. Re-resolving either at deploy
190
+ * would reintroduce the non-determinism the freeze eliminates -- a registry that
191
+ * moved between approve and deploy would pin different bytes and project
192
+ * differently, failing the child re-verify -- so both ride from approve verbatim.
193
+ */
194
+ export type InstallAndApproveResult = {
195
+ readonly approval: ProbeGateResult;
196
+ readonly projection: WorkflowProjectionDefinition;
197
+ readonly closure: ToolPackageManifest;
198
+ };
199
+ /**
200
+ * The install/approve orchestration entrypoint the end-to-end flow drives:
201
+ * resolve the frozen closure, probe the sidecar, then gate and freeze the
202
+ * result. This is production glue, not test-only wiring.
203
+ *
204
+ * The operator-approval decision is an input (`approvals`): the caller supplies
205
+ * either the `ApprovalSet` the operator approved, which the gate holds the
206
+ * advisory set to, or `approve-probed` to approve exactly the surface the probe
207
+ * reports.
208
+ *
209
+ * Returns the gate outcome alongside the inert projection and the frozen
210
+ * closure so the deploy hand-off consumes them verbatim rather than re-probing
211
+ * or re-resolving.
212
+ */
213
+ export declare function installAndApproveWorkflowDefinition(args: InstallAndApproveArgs): Promise<InstallAndApproveResult>;
214
+ export {};
@@ -0,0 +1,207 @@
1
+ // Hub-side install/approve orchestration and gate for a code-sourced workflow
2
+ // definition.
3
+ //
4
+ // This is the production glue that turns a code-sourced workflow install into
5
+ // an approved, frozen definition WITHOUT executing any author code on the hub.
6
+ // Everything here operates over the inert projection and grant set the sidecar
7
+ // returns from a probe:
8
+ //
9
+ // 1. Resolve the frozen dependency closure for the definition's pin.
10
+ // 2. Probe the sidecar for the definition's inert needs-surface projection,
11
+ // its advisory grant set, and the content hash the sidecar shipped.
12
+ // 3. RECOMPUTE the wire hash over the RECEIVED projection as tamper-evidence:
13
+ // a shipped hash that differs from the hub recompute is rejected, fail
14
+ // closed, no coercion.
15
+ // 4. Gate the advisory grant set against the approval policy: an operator
16
+ // `ApprovalSet` requires every grant the probe surfaced to be approved or
17
+ // the gate fails, while `approve-probed` approves exactly what the probe
18
+ // surfaced.
19
+ // 5. Freeze the approved wire hash onto the definition version row, keyed by
20
+ // the definition's selector, and return the frozen approved grant set.
21
+ //
22
+ // The frozen approved set is the single source of truth for the definition's
23
+ // grants: the deploy path materializes deploy grants as a SUBSET of it (never a
24
+ // fresh walk), so a workflow can never acquire at deploy or run time a grant it
25
+ // did not have frozen at approval. The wire hash is that freeze's anchor -- the
26
+ // grant set is a deterministic projection of the exact content the hash
27
+ // addresses, so pinning the hash pins the set.
28
+ import { and, eq } from "drizzle-orm";
29
+ import { workflowDefinitionVersion } from "@intx/db/schema";
30
+ import { computeWireDefinitionHash } from "@intx/types/wire-definition-hash";
31
+ import { buildSourceAssetMounts, resolveWorkflowClosure, } from "./workflow-closure-resolution.js";
32
+ import { ensureWorkflowDefinitionForAsset } from "./workflow-definition-ensure.js";
33
+ // The version `ensureWorkflowDefinitionForAsset` projects for a fresh
34
+ // definition, and therefore the row the approval freeze targets. Kept in step
35
+ // with the ensure helper: if that helper ever projects a different initial
36
+ // version, this must follow.
37
+ const FROZEN_VERSION = "1";
38
+ /**
39
+ * Build the production persistence step of the freeze. Records identity through
40
+ * the selector-keyed ensure helper (a definition keyed by `(assetId,
41
+ * wireHash)`) and writes the approved wire hash and the grant-walk snapshot onto
42
+ * that definition's version row in one transaction. The grant SET is not written
43
+ * to a version-row column -- the approved wire hash already pins the content the
44
+ * grants project from -- so it travels with the returned frozen approval; the
45
+ * snapshot is written because the run path reads it back to materialize grants.
46
+ */
47
+ export function createDbFrozenApprovalWriter(db) {
48
+ return async ({ assetId, approvedWireHash, grantSnapshot }) => {
49
+ // Ensure-then-stamp is one freeze: a crash between the two would persist a
50
+ // version row with a NULL `approvedWireHash`, which the schema treats as
51
+ // the legitimate "not yet approved" state -- indistinguishable from an
52
+ // un-approved definition. Wrap both writes in one transaction so the freeze
53
+ // is all-or-nothing.
54
+ return db.transaction(async (tx) => {
55
+ const { definitionId } = await ensureWorkflowDefinitionForAsset(tx, {
56
+ assetId,
57
+ wireHash: approvedWireHash,
58
+ });
59
+ // `FROZEN_VERSION` is hand-coupled to the version the ensure helper
60
+ // projects; if that coupling ever drifts, the update would silently stamp
61
+ // zero rows and no hash would persist. Assert exactly one row so a drift
62
+ // fails loud instead of open.
63
+ const stamped = await tx
64
+ .update(workflowDefinitionVersion)
65
+ .set({ approvedWireHash, grantSnapshot })
66
+ .where(and(eq(workflowDefinitionVersion.definitionId, definitionId), eq(workflowDefinitionVersion.version, FROZEN_VERSION)))
67
+ .returning({ id: workflowDefinitionVersion.id });
68
+ if (stamped.length !== 1) {
69
+ throw new Error(`createDbFrozenApprovalWriter: expected to stamp exactly one ${FROZEN_VERSION} version row for definition ${definitionId}, but updated ${String(stamped.length)}`);
70
+ }
71
+ return { definitionId };
72
+ });
73
+ };
74
+ }
75
+ function isApproveProbed(policy) {
76
+ return "mode" in policy;
77
+ }
78
+ /**
79
+ * Gate a probe result and, on approval, freeze it. Operates purely over the
80
+ * inert projection and grant set -- no author code runs here and the capability
81
+ * walk is never re-run.
82
+ *
83
+ * Fails closed on the two security-load-bearing checks before it writes
84
+ * anything: the recomputed wire hash must match the hash the sidecar shipped
85
+ * (tamper-evidence), and every advisory grant must be operator-approved. Only
86
+ * then does it freeze the recomputed hash onto the version row and return the
87
+ * approved grant set.
88
+ */
89
+ export async function gateAndFreezeProbeResult(args) {
90
+ const { assetId, probeResult, approvals, persist } = args;
91
+ // Tamper-evidence: recompute over the RECEIVED projection and compare to the
92
+ // shipped hash. A mismatch means the projection the hub is approving is not
93
+ // the one the sidecar hashed, so reject rather than freeze a hash that does
94
+ // not describe the approved content.
95
+ const recomputedWireHash = await computeWireDefinitionHash(probeResult.projection);
96
+ if (recomputedWireHash !== probeResult.wireHash) {
97
+ return {
98
+ ok: false,
99
+ reason: "wire_hash_mismatch",
100
+ shippedWireHash: probeResult.wireHash,
101
+ recomputedWireHash,
102
+ };
103
+ }
104
+ // Gate the advisory grant set. Under an `ApprovalSet` every grant the probe
105
+ // surfaced must appear in the operator's approved set; any miss fails the
106
+ // gate closed. Under `approve-probed` there is no set to gate against -- the
107
+ // probe's surface IS the approved set -- so nothing is ever unapproved.
108
+ const unapprovedGrants = isApproveProbed(approvals)
109
+ ? []
110
+ : probeResult.grants.filter((grant) => !approvals.has(grant));
111
+ if (unapprovedGrants.length > 0) {
112
+ return { ok: false, reason: "grants_not_approved", unapprovedGrants };
113
+ }
114
+ // Freeze: the approved set is exactly what the workflow advertised (all of it
115
+ // now operator-approved), pinned to the recomputed hash. Persisting the hash
116
+ // is the freeze; the grant set is returned for the deploy hand-off.
117
+ const approvedGrants = [...probeResult.grants];
118
+ const { definitionId } = await persist({
119
+ assetId,
120
+ approvedWireHash: recomputedWireHash,
121
+ approvedGrants,
122
+ grantSnapshot: probeResult.grantWalkSnapshot,
123
+ });
124
+ return {
125
+ ok: true,
126
+ definitionId,
127
+ approvedWireHash: recomputedWireHash,
128
+ approvedGrants: new Set(approvedGrants),
129
+ projection: probeResult.projection,
130
+ };
131
+ }
132
+ // Both asset arms carry an identical `source` field type, so narrow on the
133
+ // source's own `package.format` discriminant rather than adding a redundant
134
+ // discriminant to the args.
135
+ function isAssetSourceInstallArgs(args) {
136
+ return (args.source.kind === "asset" && args.source.package.format === "source");
137
+ }
138
+ function isAssetTarballInstallArgs(args) {
139
+ return (args.source.kind === "asset" && args.source.package.format === "tarball");
140
+ }
141
+ /**
142
+ * The install/approve orchestration entrypoint the end-to-end flow drives:
143
+ * resolve the frozen closure, probe the sidecar, then gate and freeze the
144
+ * result. This is production glue, not test-only wiring.
145
+ *
146
+ * The operator-approval decision is an input (`approvals`): the caller supplies
147
+ * either the `ApprovalSet` the operator approved, which the gate holds the
148
+ * advisory set to, or `approve-probed` to approve exactly the surface the probe
149
+ * reports.
150
+ *
151
+ * Returns the gate outcome alongside the inert projection and the frozen
152
+ * closure so the deploy hand-off consumes them verbatim rather than re-probing
153
+ * or re-resolving.
154
+ */
155
+ export async function installAndApproveWorkflowDefinition(args) {
156
+ let closure;
157
+ let assets;
158
+ if (isAssetSourceInstallArgs(args)) {
159
+ closure = await resolveWorkflowClosure({
160
+ source: args.source,
161
+ reads: args.reads,
162
+ registryName: args.registryName,
163
+ registryConfig: args.registryConfig,
164
+ ...(args.fetchPackument !== undefined
165
+ ? { fetchPackument: args.fetchPackument }
166
+ : {}),
167
+ });
168
+ assets = await buildSourceAssetMounts(closure, args.resolveAttachment);
169
+ }
170
+ else if (isAssetTarballInstallArgs(args)) {
171
+ closure = await resolveWorkflowClosure({
172
+ source: args.source,
173
+ pin: args.pin,
174
+ readBlob: args.readBlob,
175
+ listBlobs: args.listBlobs,
176
+ });
177
+ assets = await buildSourceAssetMounts(closure, args.resolveAttachment);
178
+ }
179
+ else {
180
+ closure = await resolveWorkflowClosure({
181
+ source: args.source,
182
+ pin: args.pin,
183
+ registryConfig: args.registryConfig,
184
+ ...(args.fetchPackument !== undefined
185
+ ? { fetchPackument: args.fetchPackument }
186
+ : {}),
187
+ });
188
+ assets = [];
189
+ }
190
+ const { sendProbe } = args.router;
191
+ if (sendProbe === undefined) {
192
+ throw new Error("installAndApproveWorkflowDefinition: router does not support sendProbe");
193
+ }
194
+ const probeResult = await sendProbe({
195
+ source: args.source,
196
+ closure,
197
+ entry: args.entry,
198
+ ...(assets.length > 0 ? { assets } : {}),
199
+ });
200
+ const approval = await gateAndFreezeProbeResult({
201
+ assetId: args.assetId,
202
+ probeResult,
203
+ approvals: args.approvals,
204
+ persist: createDbFrozenApprovalWriter(args.db),
205
+ });
206
+ return { approval, projection: probeResult.projection, closure };
207
+ }
@@ -0,0 +1,21 @@
1
+ /** Filename of a run's combined event log, a sibling of its `events/` dir. */
2
+ export declare const WORKFLOW_RUN_EVENTS_FILE = "events.jsonl";
3
+ /**
4
+ * Split a combined event-log file's content into the per-event JSON texts it
5
+ * holds, in file order. Each non-empty line is the verbatim text of what was
6
+ * an `events/<seq>.json` blob; the trailing newline yields no extra entry.
7
+ * Event JSON never contains a literal newline (JSON escapes them), so a line
8
+ * split is a faithful inverse of the encode side.
9
+ */
10
+ export declare function splitCombinedEventLog(content: string): string[];
11
+ /**
12
+ * Join per-event blobs (in seq order) into a combined event-log file: each
13
+ * blob's exact bytes followed by a newline. Operating on bytes -- not
14
+ * decoded strings -- keeps the sealed file a *verbatim* concatenation of
15
+ * the per-event blobs, which matters because each event is signed over its
16
+ * own bytes; a decode/re-encode round-trip could alter them. This is the
17
+ * single source of the combined-file byte layout shared by the compaction
18
+ * writer and the validator's byte-equality bridge, so the two cannot
19
+ * drift. An empty input yields an empty file.
20
+ */
21
+ export declare function encodeCombinedEventLog(perEventBlobs: readonly Uint8Array[]): Uint8Array;
@@ -0,0 +1,51 @@
1
+ // The sealed-form event log for a terminated run. Compaction folds a run's
2
+ // per-event `runs/<runId>/events/<seq>.json` blobs into a single combined
3
+ // file, `runs/<runId>/events.jsonl`, once the run reaches a terminal event.
4
+ // Each line of the combined file is the verbatim text of the per-event blob
5
+ // it replaced, in seq order, so the fold is a byte-for-byte transition that
6
+ // the workflow-run kind handler can validate against the prior per-event
7
+ // tree. Readers handle both shapes: per-event files for in-flight runs, the
8
+ // combined file for sealed (terminal) runs.
9
+ /** Filename of a run's combined event log, a sibling of its `events/` dir. */
10
+ export const WORKFLOW_RUN_EVENTS_FILE = "events.jsonl";
11
+ /**
12
+ * Split a combined event-log file's content into the per-event JSON texts it
13
+ * holds, in file order. Each non-empty line is the verbatim text of what was
14
+ * an `events/<seq>.json` blob; the trailing newline yields no extra entry.
15
+ * Event JSON never contains a literal newline (JSON escapes them), so a line
16
+ * split is a faithful inverse of the encode side.
17
+ */
18
+ export function splitCombinedEventLog(content) {
19
+ const out = [];
20
+ for (const line of content.split("\n")) {
21
+ if (line.length === 0)
22
+ continue;
23
+ out.push(line);
24
+ }
25
+ return out;
26
+ }
27
+ /**
28
+ * Join per-event blobs (in seq order) into a combined event-log file: each
29
+ * blob's exact bytes followed by a newline. Operating on bytes -- not
30
+ * decoded strings -- keeps the sealed file a *verbatim* concatenation of
31
+ * the per-event blobs, which matters because each event is signed over its
32
+ * own bytes; a decode/re-encode round-trip could alter them. This is the
33
+ * single source of the combined-file byte layout shared by the compaction
34
+ * writer and the validator's byte-equality bridge, so the two cannot
35
+ * drift. An empty input yields an empty file.
36
+ */
37
+ export function encodeCombinedEventLog(perEventBlobs) {
38
+ const NEWLINE = 0x0a;
39
+ let total = 0;
40
+ for (const blob of perEventBlobs)
41
+ total += blob.byteLength + 1;
42
+ const out = new Uint8Array(total);
43
+ let offset = 0;
44
+ for (const blob of perEventBlobs) {
45
+ out.set(blob, offset);
46
+ offset += blob.byteLength;
47
+ out[offset] = NEWLINE;
48
+ offset += 1;
49
+ }
50
+ return out;
51
+ }