@intx/hub-sessions 0.2.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.
- package/README.md +3 -5
- package/dist/agent-repo.d.ts +9 -5
- package/dist/agent-repo.js +2 -2
- package/dist/agent-state-kind.js +4 -0
- package/dist/asset-service.d.ts +1 -20
- package/dist/asset-service.js +9 -91
- package/dist/committed-source-tree.d.ts +10 -0
- package/dist/committed-source-tree.js +35 -0
- package/dist/credential-push.d.ts +7 -6
- package/dist/credential-push.js +42 -18
- package/dist/event-collector-registry.d.ts +1 -1
- package/dist/event-collector-registry.js +4 -4
- package/dist/event-collector.d.ts +1 -1
- package/dist/event-collector.js +10 -2
- package/dist/hub-session-lookups.d.ts +125 -7
- package/dist/hub-session-lookups.js +539 -80
- package/dist/hub-session-orchestrator.js +14 -49
- package/dist/index.d.ts +17 -8
- package/dist/index.js +14 -6
- package/dist/repo-store/index.d.ts +1 -1
- 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/session-service.d.ts +277 -96
- package/dist/session-service.js +741 -547
- package/dist/sidecar-allocation/contracts.d.ts +78 -0
- package/dist/sidecar-allocation/contracts.js +21 -0
- package/dist/sidecar-allocation/index.d.ts +4 -0
- package/dist/sidecar-allocation/index.js +3 -0
- package/dist/sidecar-allocation/placement-policy.d.ts +11 -0
- package/dist/sidecar-allocation/placement-policy.js +21 -0
- package/dist/sidecar-allocation/plugin-registry.d.ts +11 -0
- package/dist/sidecar-allocation/plugin-registry.js +37 -0
- package/dist/sidecar-allocation/reconciler.d.ts +42 -0
- package/dist/sidecar-allocation/reconciler.js +431 -0
- package/dist/skill-kind.js +4 -0
- package/dist/substrate.d.ts +3 -3
- package/dist/substrate.js +1 -1
- package/dist/workflow-allocation-service.d.ts +58 -0
- package/dist/workflow-allocation-service.js +239 -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 +40 -0
- package/dist/workflow-dispatch-service.js +146 -0
- package/dist/workflow-dispatch-settlement.d.ts +29 -0
- package/dist/workflow-dispatch-settlement.js +140 -0
- package/dist/workflow-kind.d.ts +17 -1
- package/dist/workflow-kind.js +127 -80
- package/dist/workflow-probe-gate.d.ts +214 -0
- package/dist/workflow-probe-gate.js +207 -0
- package/dist/workflow-run-kind.d.ts +128 -14
- package/dist/workflow-run-kind.js +353 -83
- package/dist/workflow-run-reader.d.ts +1 -1
- package/dist/workflow-run-reader.js +3 -7
- package/dist/workflow-run-restore.d.ts +15 -0
- package/dist/workflow-run-restore.js +26 -0
- package/dist/workflow-source-closure.d.ts +35 -0
- package/dist/workflow-source-closure.js +342 -0
- package/dist/ws/index.d.ts +3 -3
- package/dist/ws/index.js +1 -1
- package/dist/ws/sidecar-events.d.ts +100 -12
- package/dist/ws/sidecar-events.js +2 -0
- package/dist/ws/sidecar-handler.d.ts +128 -7
- package/dist/ws/sidecar-handler.js +1069 -135
- package/dist/ws/sidecar-token-authenticator.d.ts +3 -1
- package/dist/ws/sidecar-token-authenticator.js +64 -7
- package/package.json +14 -13
- package/dist/available-skills-stanza.d.ts +0 -21
- package/dist/available-skills-stanza.js +0 -32
|
@@ -11,12 +11,11 @@
|
|
|
11
11
|
// with a small stub and an isolated emitter.
|
|
12
12
|
import { eq } from "drizzle-orm";
|
|
13
13
|
import { type } from "arktype";
|
|
14
|
-
import {
|
|
14
|
+
import { workflowRun } from "@intx/db/schema";
|
|
15
15
|
import { parseMailToEmail } from "@intx/mime";
|
|
16
16
|
import { parseInferenceEvent } from "@intx/types/runtime";
|
|
17
17
|
import { getLogger } from "@intx/log";
|
|
18
|
-
import {
|
|
19
|
-
import { findInstance, parseAgentId, requireInstance, } from "./hub-session-lookups.js";
|
|
18
|
+
import { parseAgentId } from "./hub-session-lookups.js";
|
|
20
19
|
const log = getLogger(["hub", "orchestrator"]);
|
|
21
20
|
export function createHubSessionOrchestrator(deps) {
|
|
22
21
|
const { events, router, db, eventCollectors, agentRepoStore } = deps;
|
|
@@ -45,55 +44,21 @@ export function createHubSessionOrchestrator(deps) {
|
|
|
45
44
|
recipients: recipients.join(", "),
|
|
46
45
|
});
|
|
47
46
|
}));
|
|
48
|
-
unsubscribers.push(events.on("agent.deploy.ack", async (
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// keyed by address. Persist it there so the reconnect ownership
|
|
54
|
-
// challenge can verify the deployment address. Only the
|
|
55
|
-
// deployment-level address has a row, so a stray per-step ack updates
|
|
56
|
-
// nothing. (This was previously a no-op, which is what left
|
|
57
|
-
// workflow-deployment addresses un-verifiable on reconnect.)
|
|
58
|
-
if (isWorkflowDerivedAddress(agentAddress)) {
|
|
59
|
-
await db
|
|
60
|
-
.update(workflowDeployment)
|
|
61
|
-
.set({ publicKey })
|
|
62
|
-
.where(eq(workflowDeployment.address, agentAddress));
|
|
47
|
+
unsubscribers.push(events.on("agent.deploy.ack", async (event) => {
|
|
48
|
+
const { agentAddress, publicKey, allocated } = event;
|
|
49
|
+
// Exclusive initialization publishes its key only after every deploy
|
|
50
|
+
// and asset pack succeeds under the allocation generation fence.
|
|
51
|
+
if (allocated !== undefined)
|
|
63
52
|
return;
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
throw new Error(`No active instance found for deploy ack on address "${agentAddress}"`);
|
|
70
|
-
}
|
|
53
|
+
// Every deploy address names a workflow run whose public key lives on its
|
|
54
|
+
// single self-anchored workflow_run row, keyed by address. Persist it
|
|
55
|
+
// there so the reconnect ownership challenge can verify the address off
|
|
56
|
+
// the same row `lookupPublicKey` reads. Only the deployment-level address
|
|
57
|
+
// owns a row, so a stray per-step ack updates nothing.
|
|
71
58
|
await db
|
|
72
|
-
.update(
|
|
59
|
+
.update(workflowRun)
|
|
73
60
|
.set({ publicKey })
|
|
74
|
-
.where(eq(
|
|
75
|
-
}));
|
|
76
|
-
unsubscribers.push(events.on("agent.reconnected", async ({ agentAddress }) => {
|
|
77
|
-
const instance = await requireInstance(db, agentAddress);
|
|
78
|
-
if (!instance.sessionId) {
|
|
79
|
-
throw new Error(`Agent "${agentAddress}" reconnected but has no active session`);
|
|
80
|
-
}
|
|
81
|
-
const sessionId = instance.sessionId;
|
|
82
|
-
// A supervised deployment carries its grants and sources in the
|
|
83
|
-
// deploy pack and refreshes them over the supervisor's IPC
|
|
84
|
-
// credentials snapshot at spawn and recycle, so reconnect does not
|
|
85
|
-
// re-push them over the wire.
|
|
86
|
-
const now = new Date();
|
|
87
|
-
if (instance.status !== "running") {
|
|
88
|
-
await db
|
|
89
|
-
.update(agentInstance)
|
|
90
|
-
.set({ status: "running", updatedAt: now })
|
|
91
|
-
.where(eq(agentInstance.id, instance.id));
|
|
92
|
-
}
|
|
93
|
-
if (!eventCollectors.has(agentAddress)) {
|
|
94
|
-
eventCollectors.create(agentAddress, instance.tenantId, sessionId, instance.id);
|
|
95
|
-
log.info("Restored event collector for reconnected agent {agentAddress}", { agentAddress });
|
|
96
|
-
}
|
|
61
|
+
.where(eq(workflowRun.address, agentAddress));
|
|
97
62
|
}));
|
|
98
63
|
unsubscribers.push(events.on("deploy.ref.stale", async ({ agentAddress }) => {
|
|
99
64
|
const agentId = parseAgentId(agentAddress);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
export { createAgentRepoStore, type AgentRepoStore, type DeployContent, } from "./agent-repo.js";
|
|
2
|
-
export { createSessionService, SessionLaunchError, bridgeOrchestratorDeployContent, type SessionService, type
|
|
2
|
+
export { createSessionService, SessionLaunchError, bridgeOrchestratorDeployContent, deployCodeSourcedWorkflow, type SessionService, type DeployWorkflowDefinitionResult, type DeployWorkflowFromSourceParams, type DeployPreparedCodeSourcedWorkflowParams, type InstallAndApproveWorkflowSourceParams, type PreparedWorkflowDeployer, type DeployCodeSourcedWorkflowArgs, } from "./session-service.js";
|
|
3
|
+
export { installAndApproveWorkflowDefinition, createDbFrozenApprovalWriter, type InstallAndApproveArgs, type InstallAndApproveResult, type ProbeGateResult, type ProbeApprovalPolicy, type ApproveProbedGrants, } from "./workflow-probe-gate.js";
|
|
4
|
+
export { committedReadsToSourceTree } from "./committed-source-tree.js";
|
|
5
|
+
export type { SourceTreeReads } from "./workflow-source-closure.js";
|
|
3
6
|
export type { WorkflowDefinition } from "@intx/workflow/definition";
|
|
4
7
|
export { createEventCollectorRegistry, type EventCollectorRegistry, } from "./event-collector-registry.js";
|
|
5
|
-
export { createSidecarRouter, type SidecarRouter, type SidecarRouterConfig, type SidecarAuthIdentity, type SidecarAuthenticator, createSidecarTokenAuthenticator, type CreateSidecarTokenAuthenticatorDeps, type WsHandle, createSidecarEmitter, type SidecarEventEmitter, type SidecarEventMap, type SidecarEventType, type SidecarEventListener, type SidecarLookups, type SidecarMailPersistedPayload, type SidecarMailPersistedRow, } from "./ws/index.js";
|
|
6
|
-
export { createHubSessionLookups, parseAgentId, type HubSessionLookupsDeps, } from "./hub-session-lookups.js";
|
|
8
|
+
export { createSidecarRouter, type SidecarRouter, type SidecarRouterConfig, type SidecarAuthIdentity, type SidecarAuthenticator, type AllocatedSidecarTarget, type SidecarAllocationRouter, createSidecarCredentialResolver, createSidecarTokenAuthenticator, type CreateSidecarTokenAuthenticatorDeps, type WsHandle, createSidecarEmitter, type SidecarEventEmitter, type SidecarEventMap, type SidecarEventType, type SidecarEventListener, type SidecarLookups, type SidecarMailPersistedPayload, type SidecarMailPersistedRow, type MailTriggeredRunGrantsResult, type WorkflowRunPackSource, } from "./ws/index.js";
|
|
9
|
+
export { createHubSessionLookups, findRoutableById, parseAgentId, resolveRoutableAddress, resolveRunIdForSession, resolveRunSessionId, runRowToRoutableRecord, type HubSessionLookupsDeps, type RoutableEndpoint, type RoutableRecord, } from "./hub-session-lookups.js";
|
|
7
10
|
export { createHubSessionOrchestrator, type HubSessionOrchestrator, type HubSessionOrchestratorDeps, type HubSessionRouterFacade, } from "./hub-session-orchestrator.js";
|
|
8
11
|
export { pushSourceUpdates, pushSourceUpdatesSubtree } from "./credential-push.js";
|
|
12
|
+
export { createSidecarPluginRegistry, createSidecarAllocationReconciler, resolveEffectiveSidecarPlacement, type CreateSidecarPluginRegistryOpts, type DestroySidecarRequest, type DestroySidecarResult, type EnsureSidecarRequest, type EnsureSidecarResult, type ResolveEffectiveSidecarPlacementOpts, type SidecarCredentialIdentity, type SidecarCredentialResolver, type SidecarOperationFailure, type SidecarPluginRegistry, type SidecarProvisioner, type SidecarAllocationReconciler, type SidecarAllocationReconcilerDeps, } from "./sidecar-allocation/index.js";
|
|
13
|
+
export { ensureWorkflowDefinitionForAsset } from "./workflow-definition-ensure.js";
|
|
14
|
+
export { workflowSourceAssetMountPath } from "./workflow-closure-resolution.js";
|
|
15
|
+
export { createWorkflowAllocationService, ExclusiveWorkflowPlacementError, resolveWorkflowSidecarPlacement, type PrepareExclusiveWorkflowDeploymentArgs, type PreparedExclusiveWorkflowDeployment, type WorkflowAllocationService, type WorkflowAllocationServiceDeps, } from "./workflow-allocation-service.js";
|
|
16
|
+
export { createWorkflowDispatchService, type WorkflowDispatchAcknowledgement, type WorkflowDispatchService, type WorkflowDispatchServiceDeps, } from "./workflow-dispatch-service.js";
|
|
17
|
+
export { listAcceptedWorkflowDispatches, listConsumedWorkflowDispatches, listReceivedWorkflowSignals, type AcceptedWorkflowDispatch, type ConsumedWorkflowDispatch, type ReceivedWorkflowSignal, } from "./workflow-dispatch-settlement.js";
|
|
9
18
|
export { skillKindHandler, skillAuthorize, skillFrontmatterSchema, getSkillIndex, type SkillIndexEntry, type SkillFrontmatter, type SkillPrincipal, type SkillHubPrincipal, type SkillSidecarPrincipal, } from "./skill-kind.js";
|
|
10
19
|
export { packageRegistryKindHandler, packageRegistryAuthorize, asTarballEntry, validateTarballPackageJSON, TARBALLS_PREFIX, TARBALL_FILENAME_PATTERN, REGISTRY_INDEX_PATH, WORKSPACE_BUILTINS_REGISTRY, } from "./package-registry-kind.js";
|
|
11
|
-
export { workflowKindHandler, workflowAuthorize, workflowDefinitionEnvelopeSchema, WORKFLOW_JSON_PATH, CAPABILITY_DECLARATIONS_JSON_PATH,
|
|
12
|
-
export { workflowRunKindHandler, workflowRunAuthorize, enqueueInbox, dequeueToProcessing, readProcessingEntry, markConsumed, readOwnedMessageIds, replayProcessingToInbox, WORKFLOW_RUN_GITIGNORE_PATH, WORKFLOW_RUN_RUNS_PREFIX, WORKFLOW_RUN_EVENTS_DIR, WORKFLOW_RUN_AGENT_STATE_PREFIX, WORKFLOW_RUN_ADDRESSES_PREFIX, WORKFLOW_RUN_CONTROL_PREFIX, WORKFLOW_RUN_INBOX_DIR, WORKFLOW_RUN_PROCESSING_DIR, WORKFLOW_RUN_CONSUMED_DIR, WORKFLOW_RUN_WATERMARK_FILE, DEFAULT_CONSUMED_RETENTION_MS, type ClaimCheckEnvelope, type ConsumedEnvelope, type EnqueueInboxArgs, type EnqueueInboxResult, type DequeueToProcessingResult, type ReadProcessingEntryResult, type MarkConsumedArgs, type MarkConsumedResult, type ReplayProcessingToInboxOpts, type ReplayProcessingToInboxResult, type WorkflowRunPrincipal, type WorkflowRunHubPrincipal, type WorkflowRunSidecarPrincipal, type WorkflowRunWorkflowProcessPrincipal, type WorkflowRunSupervisorPrincipal, } from "./workflow-run-kind.js";
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
20
|
+
export { workflowKindHandler, workflowAuthorize, workflowDefinitionEnvelopeSchema, WORKFLOW_JSON_PATH, CAPABILITY_DECLARATIONS_JSON_PATH, type WorkflowPrincipal, type WorkflowHubPrincipal, type WorkflowSidecarPrincipal, } from "./workflow-kind.js";
|
|
21
|
+
export { workflowRunKindHandler, workflowRunAuthorize, enqueueInbox, StaleInboxEnqueueError, dequeueToProcessing, readProcessingEntry, markConsumed, readOwnedMessageIds, readCommittedWorkflowRunLifecycle, readWorkflowRunLifecycle, replayProcessingToInbox, WORKFLOW_RUN_GITIGNORE_PATH, WORKFLOW_RUN_RUNS_PREFIX, WORKFLOW_RUN_EVENTS_DIR, WORKFLOW_RUN_GRANTS_FILE, WORKFLOW_RUN_AGENT_STATE_PREFIX, WORKFLOW_RUN_ADDRESSES_PREFIX, WORKFLOW_RUN_CONTROL_PREFIX, WORKFLOW_RUN_INBOX_DIR, WORKFLOW_RUN_PROCESSING_DIR, WORKFLOW_RUN_CONSUMED_DIR, WORKFLOW_RUN_WATERMARK_FILE, DEFAULT_CONSUMED_RETENTION_MS, type ClaimCheckEnvelope, type ConsumedEnvelope, type EnqueueAlreadyPresentReason, type EnqueueInboxArgs, type EnqueueInboxOutcome, type EnqueueInboxResult, type DequeueToProcessingResult, type ReadProcessingEntryResult, type MarkConsumedArgs, type MarkConsumedResult, type WorkflowRunLifecycle, type ReplayProcessingToInboxOpts, type ReplayProcessingToInboxResult, type WorkflowRunPrincipal, type WorkflowRunHubPrincipal, type WorkflowRunSidecarPrincipal, type WorkflowRunWorkflowProcessPrincipal, type WorkflowRunSupervisorPrincipal, } from "./workflow-run-kind.js";
|
|
22
|
+
export { restoreWorkflowRunToAllocation, WORKFLOW_RUN_RESTORE_REFS, } from "./workflow-run-restore.js";
|
|
23
|
+
export { createAssetService, AssetServiceError, DEFAULT_ASSET_REF, type AssetService, type Asset, type CreateAssetParams, type PopulateAssetParams, type AssetServiceErrorReason, type ReadAssetBlobParams, type ListAssetBlobsParams, } from "./asset-service.js";
|
|
15
24
|
export { createWorkflowRunReader, type WorkflowRunReader, type WorkflowRunEvent, } from "./workflow-run-reader.js";
|
|
16
25
|
export { WORKFLOW_RUN_EVENTS_FILE, splitCombinedEventLog, encodeCombinedEventLog, } from "./workflow-run-event-log.js";
|
|
17
|
-
export type { AuthorizeFn, CreateRepoStoreConfig, InitRepoOpts, KindHandler, NewlyTerminalRun, Principal, RefEntry, RepoAction, RepoId, RepoStore, SubscribeKindEntry, SubscribeKindOpts, ValidatePushResult, WriteResult, } from "./repo-store/index.js";
|
|
26
|
+
export type { AuthorizeFn, CommittedReads, CommittedTreeEntry, CreateRepoStoreConfig, InitRepoOpts, KindHandler, NewlyTerminalRun, Principal, RefEntry, RepoAction, RepoId, RepoStore, SubscribeKindEntry, SubscribeKindOpts, ValidatePushResult, WriteResult, } from "./repo-store/index.js";
|
|
18
27
|
export { createRepoStore, subscribeKind, UserPrincipal } from "./repo-store/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
export { createAgentRepoStore, } from "./agent-repo.js";
|
|
2
|
-
export { createSessionService, SessionLaunchError, bridgeOrchestratorDeployContent, } from "./session-service.js";
|
|
2
|
+
export { createSessionService, SessionLaunchError, bridgeOrchestratorDeployContent, deployCodeSourcedWorkflow, } from "./session-service.js";
|
|
3
|
+
export { installAndApproveWorkflowDefinition, createDbFrozenApprovalWriter, } from "./workflow-probe-gate.js";
|
|
4
|
+
export { committedReadsToSourceTree } from "./committed-source-tree.js";
|
|
3
5
|
export { createEventCollectorRegistry, } from "./event-collector-registry.js";
|
|
4
|
-
export { createSidecarRouter, createSidecarTokenAuthenticator, createSidecarEmitter, } from "./ws/index.js";
|
|
5
|
-
export { createHubSessionLookups, parseAgentId, } from "./hub-session-lookups.js";
|
|
6
|
+
export { createSidecarRouter, createSidecarCredentialResolver, createSidecarTokenAuthenticator, createSidecarEmitter, } from "./ws/index.js";
|
|
7
|
+
export { createHubSessionLookups, findRoutableById, parseAgentId, resolveRoutableAddress, resolveRunIdForSession, resolveRunSessionId, runRowToRoutableRecord, } from "./hub-session-lookups.js";
|
|
6
8
|
export { createHubSessionOrchestrator, } from "./hub-session-orchestrator.js";
|
|
7
9
|
export { pushSourceUpdates, pushSourceUpdatesSubtree } from "./credential-push.js";
|
|
10
|
+
export { createSidecarPluginRegistry, createSidecarAllocationReconciler, resolveEffectiveSidecarPlacement, } from "./sidecar-allocation/index.js";
|
|
11
|
+
export { ensureWorkflowDefinitionForAsset } from "./workflow-definition-ensure.js";
|
|
12
|
+
export { workflowSourceAssetMountPath } from "./workflow-closure-resolution.js";
|
|
13
|
+
export { createWorkflowAllocationService, ExclusiveWorkflowPlacementError, resolveWorkflowSidecarPlacement, } from "./workflow-allocation-service.js";
|
|
14
|
+
export { createWorkflowDispatchService, } from "./workflow-dispatch-service.js";
|
|
15
|
+
export { listAcceptedWorkflowDispatches, listConsumedWorkflowDispatches, listReceivedWorkflowSignals, } from "./workflow-dispatch-settlement.js";
|
|
8
16
|
export { skillKindHandler, skillAuthorize, skillFrontmatterSchema, getSkillIndex, } from "./skill-kind.js";
|
|
9
17
|
export { packageRegistryKindHandler, packageRegistryAuthorize, asTarballEntry, validateTarballPackageJSON, TARBALLS_PREFIX, TARBALL_FILENAME_PATTERN, REGISTRY_INDEX_PATH, WORKSPACE_BUILTINS_REGISTRY, } from "./package-registry-kind.js";
|
|
10
|
-
export { workflowKindHandler, workflowAuthorize, workflowDefinitionEnvelopeSchema, WORKFLOW_JSON_PATH, CAPABILITY_DECLARATIONS_JSON_PATH,
|
|
11
|
-
export { workflowRunKindHandler, workflowRunAuthorize, enqueueInbox, dequeueToProcessing, readProcessingEntry, markConsumed, readOwnedMessageIds, replayProcessingToInbox, WORKFLOW_RUN_GITIGNORE_PATH, WORKFLOW_RUN_RUNS_PREFIX, WORKFLOW_RUN_EVENTS_DIR, WORKFLOW_RUN_AGENT_STATE_PREFIX, WORKFLOW_RUN_ADDRESSES_PREFIX, WORKFLOW_RUN_CONTROL_PREFIX, WORKFLOW_RUN_INBOX_DIR, WORKFLOW_RUN_PROCESSING_DIR, WORKFLOW_RUN_CONSUMED_DIR, WORKFLOW_RUN_WATERMARK_FILE, DEFAULT_CONSUMED_RETENTION_MS, } from "./workflow-run-kind.js";
|
|
18
|
+
export { workflowKindHandler, workflowAuthorize, workflowDefinitionEnvelopeSchema, WORKFLOW_JSON_PATH, CAPABILITY_DECLARATIONS_JSON_PATH, } from "./workflow-kind.js";
|
|
19
|
+
export { workflowRunKindHandler, workflowRunAuthorize, enqueueInbox, StaleInboxEnqueueError, dequeueToProcessing, readProcessingEntry, markConsumed, readOwnedMessageIds, readCommittedWorkflowRunLifecycle, readWorkflowRunLifecycle, replayProcessingToInbox, WORKFLOW_RUN_GITIGNORE_PATH, WORKFLOW_RUN_RUNS_PREFIX, WORKFLOW_RUN_EVENTS_DIR, WORKFLOW_RUN_GRANTS_FILE, WORKFLOW_RUN_AGENT_STATE_PREFIX, WORKFLOW_RUN_ADDRESSES_PREFIX, WORKFLOW_RUN_CONTROL_PREFIX, WORKFLOW_RUN_INBOX_DIR, WORKFLOW_RUN_PROCESSING_DIR, WORKFLOW_RUN_CONSUMED_DIR, WORKFLOW_RUN_WATERMARK_FILE, DEFAULT_CONSUMED_RETENTION_MS, } from "./workflow-run-kind.js";
|
|
20
|
+
export { restoreWorkflowRunToAllocation, WORKFLOW_RUN_RESTORE_REFS, } from "./workflow-run-restore.js";
|
|
12
21
|
export { createAssetService, AssetServiceError, DEFAULT_ASSET_REF, } from "./asset-service.js";
|
|
13
|
-
export { buildAvailableSkillsStanza, } from "./available-skills-stanza.js";
|
|
14
22
|
export { createWorkflowRunReader, } from "./workflow-run-reader.js";
|
|
15
23
|
export { WORKFLOW_RUN_EVENTS_FILE, splitCombinedEventLog, encodeCombinedEventLog, } from "./workflow-run-event-log.js";
|
|
16
24
|
export { createRepoStore, subscribeKind, UserPrincipal } from "./repo-store/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AuthorizeFn, InitRepoOpts, KindHandler, NewlyTerminalRun, PriorDeltaReads, Principal, RefEntry, RepoAction, RepoId, RepoKind, RepoStore, RepoStoreSubscribeEvent, TreeContent, ValidatePushResult, WriteResult, WriteTreePreservingPrefixArgs, } from "./types.js";
|
|
1
|
+
export type { AuthorizeFn, CommittedReads, CommittedTreeEntry, InitRepoOpts, KindHandler, NewlyTerminalRun, PriorDeltaReads, Principal, RefEntry, RepoAction, RepoId, RepoKind, RepoStore, RepoStoreSubscribeEvent, TreeContent, ValidatePushResult, WriteResult, WriteTreePreservingPrefixArgs, } from "./types.js";
|
|
2
2
|
export { UserPrincipal } from "./types.js";
|
|
3
3
|
export { createRepoStore, type CreateRepoStoreConfig } from "./store.js";
|
|
4
4
|
export { subscribeKind, type SubscribeKindOpts, type SubscribeKindEntry, } from "./subscribe-kind.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CommitSigner, type GCPolicy } from "@intx/storage-isogit";
|
|
1
|
+
import { type CommitSigner, type GCPolicy } from "@intx/storage-isogit/node";
|
|
2
2
|
import type { AuthorizeFn, KindHandler, RepoId, RepoKind, RepoStore } from "./types.js";
|
|
3
3
|
type SigningKey = {
|
|
4
4
|
privateKey: Uint8Array;
|
package/dist/repo-store/store.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import git, {} from "isomorphic-git";
|
|
4
4
|
import { createSSHSignature } from "@intx/crypto";
|
|
5
|
-
import { initRepo as storageInitRepo, createDeployPack, receivePackObjects, collectReachableObjects, maybeGC, } from "@intx/storage-isogit";
|
|
5
|
+
import { initRepo as storageInitRepo, createDeployPack, receivePackObjects, collectReachableObjects, maybeGC, } from "@intx/storage-isogit/node";
|
|
6
6
|
import { hasCode } from "@intx/types";
|
|
7
7
|
import { getLogger } from "@intx/log";
|
|
8
8
|
import { SAFE_REPO_ID } from "./types.js";
|
|
@@ -638,6 +638,44 @@ export function createRepoStore(config) {
|
|
|
638
638
|
out.set(e.path, e.oid);
|
|
639
639
|
return out;
|
|
640
640
|
}
|
|
641
|
+
// git tree-entry modes an admitted asset tree must not carry: a symlink can
|
|
642
|
+
// escape its directory when the tree is checked out, and a submodule gitlink
|
|
643
|
+
// is always a dangling reference (a pushed pack ships no submodule object).
|
|
644
|
+
const GIT_MODE_SYMLINK = "120000";
|
|
645
|
+
const GIT_MODE_SUBMODULE = "160000";
|
|
646
|
+
// Walk a commit's tree and return the first entry whose mode is disallowed
|
|
647
|
+
// (a symlink or a submodule), or null when the tree is clean. This mirrors
|
|
648
|
+
// the materialization backstop in `writeTreeToDisk`, but at the PUSH boundary
|
|
649
|
+
// so a directory-escaping symlink (or a dangling gitlink) is refused at
|
|
650
|
+
// admission rather than only caught later at checkout. It reads trees only,
|
|
651
|
+
// never blobs; `seen` deduplicates shared subtree oids across the commits of
|
|
652
|
+
// one pack so a subtree is walked once.
|
|
653
|
+
async function findDisallowedTreeMode(dir, treeOid, seen, prefix = "") {
|
|
654
|
+
if (seen.has(treeOid))
|
|
655
|
+
return null;
|
|
656
|
+
seen.add(treeOid);
|
|
657
|
+
const { tree } = await git.readTree({
|
|
658
|
+
fs,
|
|
659
|
+
dir,
|
|
660
|
+
cache: cacheFor(dir),
|
|
661
|
+
oid: treeOid,
|
|
662
|
+
});
|
|
663
|
+
for (const entry of tree) {
|
|
664
|
+
const entryPath = prefix === "" ? entry.path : `${prefix}/${entry.path}`;
|
|
665
|
+
if (entry.mode === GIT_MODE_SYMLINK) {
|
|
666
|
+
return { path: entryPath, kind: "symlink" };
|
|
667
|
+
}
|
|
668
|
+
if (entry.type === "commit" || entry.mode === GIT_MODE_SUBMODULE) {
|
|
669
|
+
return { path: entryPath, kind: "submodule" };
|
|
670
|
+
}
|
|
671
|
+
if (entry.type === "tree") {
|
|
672
|
+
const nested = await findDisallowedTreeMode(dir, entry.oid, seen, entryPath);
|
|
673
|
+
if (nested !== null)
|
|
674
|
+
return nested;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
641
679
|
// Bound the set of paths a received-pack commit may have changed
|
|
642
680
|
// relative to its parent, as repo-root-relative POSIX prefixes ending
|
|
643
681
|
// in `/`. Git is content-addressed: a subtree whose object id is
|
|
@@ -1081,6 +1119,13 @@ export function createRepoStore(config) {
|
|
|
1081
1119
|
const { priorReadBlob, priorListDir, priorListDirOids } = buildPriorTreeClosures(dir, parentCommitSha);
|
|
1082
1120
|
const prospective = buildTreeReadClosures(dir, newRootTreeOid);
|
|
1083
1121
|
const changedPathPrefixes = w.changedPathPrefixes;
|
|
1122
|
+
// No disallowed-mode (symlink/submodule) gate here on purpose: the
|
|
1123
|
+
// substrate mode gate lives on the pack-receive path. This write path
|
|
1124
|
+
// cannot introduce a `120000`/`160000` entry -- `assembleTree` writes puts
|
|
1125
|
+
// as `100644` and passes base-tree entries through unchanged, and no
|
|
1126
|
+
// ingress ever admits such a mode into a base tree (the pack path is now
|
|
1127
|
+
// gated; this path only writes `100644`). Adding a walk here would gate a
|
|
1128
|
+
// tree that cannot carry those modes.
|
|
1084
1129
|
const validation = await handler.validatePush({
|
|
1085
1130
|
repoId,
|
|
1086
1131
|
ref,
|
|
@@ -1325,6 +1370,7 @@ export function createRepoStore(config) {
|
|
|
1325
1370
|
existingCommitsCache.set(existingKey, existingCommits);
|
|
1326
1371
|
}
|
|
1327
1372
|
const newCommitsFromPack = [];
|
|
1373
|
+
const newlyTerminalRuns = [];
|
|
1328
1374
|
const oldSha = await receivePackObjects(dir, pack, ref, commitSha, transferId, expectedOldSha,
|
|
1329
1375
|
// A pack may carry more than one new commit (e.g. supervisor
|
|
1330
1376
|
// bootstrap that batches enqueue + dequeue before the hub has
|
|
@@ -1340,6 +1386,9 @@ export function createRepoStore(config) {
|
|
|
1340
1386
|
async () => {
|
|
1341
1387
|
const newCommits = await collectNewCommits(dir, commitSha, expectedOldSha, existingCommits);
|
|
1342
1388
|
newCommitsFromPack.push(...newCommits);
|
|
1389
|
+
// Shared across the pack's commits so a subtree common to several is
|
|
1390
|
+
// walked once by the disallowed-mode gate below.
|
|
1391
|
+
const seenTreeOids = new Set();
|
|
1343
1392
|
for (const newCommit of newCommits) {
|
|
1344
1393
|
const { commit: parsed } = await git.readCommit({
|
|
1345
1394
|
fs,
|
|
@@ -1391,6 +1440,22 @@ export function createRepoStore(config) {
|
|
|
1391
1440
|
const { priorReadBlob, priorListDir, priorListDirOids } = buildPriorTreeClosures(dir, parentSha);
|
|
1392
1441
|
const { topLevelTreePaths, readBlob, listDir } = await buildCommitTreeClosures(dir, newCommit);
|
|
1393
1442
|
const changedPathPrefixes = await computeChangedPathPrefixes(dir, newCommit, parentSha);
|
|
1443
|
+
// Admission gate for EVERY kind, before the per-kind validatePush:
|
|
1444
|
+
// an asset tree carrying a symlink (which can escape its directory
|
|
1445
|
+
// at checkout) or a submodule (a dangling gitlink the pack cannot
|
|
1446
|
+
// reproduce) is refused here, so the ref never advances. No kind
|
|
1447
|
+
// legitimately pushes either; `writeTreeToDisk` rejects both at
|
|
1448
|
+
// materialization as the defense-in-depth backstop. Per-commit, so
|
|
1449
|
+
// an intermediate commit that adds a symlink a later commit deletes
|
|
1450
|
+
// is still caught.
|
|
1451
|
+
const disallowed = await findDisallowedTreeMode(dir, parsed.tree, seenTreeOids);
|
|
1452
|
+
if (disallowed !== null) {
|
|
1453
|
+
// The substrate owns the `path_violation:` prefix on the thrown
|
|
1454
|
+
// message, so the reason omits it (matching every handler reason).
|
|
1455
|
+
const reason = `${disallowed.kind} at ${disallowed.path} is not allowed in a pushed asset tree (commit ${newCommit})`;
|
|
1456
|
+
logger.debug `validatePush rejected ${repoId.kind}/${repoId.id} on ${ref} at commit ${newCommit}: ${reason}`;
|
|
1457
|
+
return { ok: false, reason };
|
|
1458
|
+
}
|
|
1394
1459
|
const result = await handler.validatePush({
|
|
1395
1460
|
repoId,
|
|
1396
1461
|
ref,
|
|
@@ -1407,6 +1472,9 @@ export function createRepoStore(config) {
|
|
|
1407
1472
|
logger.debug `validatePush rejected ${repoId.kind}/${repoId.id} on ${ref} at commit ${newCommit}: ${result.reason}`;
|
|
1408
1473
|
return { ok: false, reason: result.reason };
|
|
1409
1474
|
}
|
|
1475
|
+
if (result.newlyTerminalRuns !== undefined) {
|
|
1476
|
+
newlyTerminalRuns.push(...result.newlyTerminalRuns);
|
|
1477
|
+
}
|
|
1410
1478
|
}
|
|
1411
1479
|
return true;
|
|
1412
1480
|
});
|
|
@@ -1420,6 +1488,7 @@ export function createRepoStore(config) {
|
|
|
1420
1488
|
await handler.onRefUpdated({ repoId, ref, oldSha, newSha: commitSha });
|
|
1421
1489
|
await emitRefUpdate(repoId, ref, oldSha, commitSha);
|
|
1422
1490
|
await maybeRunGC(repoId);
|
|
1491
|
+
return newlyTerminalRuns;
|
|
1423
1492
|
});
|
|
1424
1493
|
}
|
|
1425
1494
|
async function createPack(principal, repoId, ref) {
|
|
@@ -1528,6 +1597,72 @@ export function createRepoStore(config) {
|
|
|
1528
1597
|
gateAccess(principal, repoId, ref, "resolveRef");
|
|
1529
1598
|
return resolveRefSha(repoDir(repoId), ref);
|
|
1530
1599
|
}
|
|
1600
|
+
// Build the committed-read closures pinned to a commit. Shared by the
|
|
1601
|
+
// by-ref (`openCommittedReads`) and by-commit
|
|
1602
|
+
// (`openCommittedReadsAtCommit`) entrypoints, which differ only in how
|
|
1603
|
+
// they obtain and validate the commit SHA.
|
|
1604
|
+
function committedReadsAt(dir, commitSha) {
|
|
1605
|
+
const { readBlobByOid } = buildPriorTreeClosures(dir, commitSha);
|
|
1606
|
+
const treeOid = async (relPath) => {
|
|
1607
|
+
// "." and "" both name the root tree; resolveTreeEntry treats the
|
|
1608
|
+
// empty string as the root, so normalize "." to it.
|
|
1609
|
+
const normalized = relPath === "." ? "" : relPath;
|
|
1610
|
+
return resolveTreeEntry(dir, commitSha, normalized, "tree");
|
|
1611
|
+
};
|
|
1612
|
+
const listDir = async (relPath) => {
|
|
1613
|
+
const oid = await treeOid(relPath);
|
|
1614
|
+
if (oid === null)
|
|
1615
|
+
return [];
|
|
1616
|
+
const { tree } = await git.readTree({
|
|
1617
|
+
fs,
|
|
1618
|
+
dir,
|
|
1619
|
+
cache: cacheFor(dir),
|
|
1620
|
+
oid,
|
|
1621
|
+
});
|
|
1622
|
+
return tree.map((e) => ({ name: e.path, oid: e.oid, type: e.type }));
|
|
1623
|
+
};
|
|
1624
|
+
return { listDir, readBlobByOid, treeOid };
|
|
1625
|
+
}
|
|
1626
|
+
async function repoHasGitDir(dir) {
|
|
1627
|
+
return fs.promises
|
|
1628
|
+
.stat(path.join(dir, ".git"))
|
|
1629
|
+
.then(() => true)
|
|
1630
|
+
.catch(() => false);
|
|
1631
|
+
}
|
|
1632
|
+
async function openCommittedReads(principal, repoId, ref) {
|
|
1633
|
+
gateAccess(principal, repoId, ref, "resolveRef");
|
|
1634
|
+
const dir = repoDir(repoId);
|
|
1635
|
+
if (!(await repoHasGitDir(dir)))
|
|
1636
|
+
return null;
|
|
1637
|
+
const commitSha = await resolveRefSha(dir, ref);
|
|
1638
|
+
if (commitSha === null)
|
|
1639
|
+
return null;
|
|
1640
|
+
return committedReadsAt(dir, commitSha);
|
|
1641
|
+
}
|
|
1642
|
+
async function openCommittedReadsAtCommit(principal, repoId, commitSha) {
|
|
1643
|
+
// No single ref to gate on; use the same `"*"`/`resolveRef` bulk-read
|
|
1644
|
+
// gate that `listRefs` and `resolveHead` use.
|
|
1645
|
+
gateAccess(principal, repoId, "*", "resolveRef");
|
|
1646
|
+
if (!/^[0-9a-f]{40}$/.test(commitSha)) {
|
|
1647
|
+
throw new Error(`commit_sha_invalid: ${commitSha}`);
|
|
1648
|
+
}
|
|
1649
|
+
const dir = repoDir(repoId);
|
|
1650
|
+
if (!(await repoHasGitDir(dir)))
|
|
1651
|
+
return null;
|
|
1652
|
+
// Confirm the commit object is present so a pruned commit reads as a
|
|
1653
|
+
// clean `null` rather than a lazy throw on the first tree walk.
|
|
1654
|
+
const present = await git
|
|
1655
|
+
.readCommit({ fs, dir, cache: cacheFor(dir), oid: commitSha })
|
|
1656
|
+
.then(() => true)
|
|
1657
|
+
.catch((err) => {
|
|
1658
|
+
if (hasCode(err) && err.code === "NotFoundError")
|
|
1659
|
+
return false;
|
|
1660
|
+
throw err;
|
|
1661
|
+
});
|
|
1662
|
+
if (!present)
|
|
1663
|
+
return null;
|
|
1664
|
+
return committedReadsAt(dir, commitSha);
|
|
1665
|
+
}
|
|
1531
1666
|
function subscribe(principal, repoId, ref, opts) {
|
|
1532
1667
|
gateAccess(principal, repoId, ref, "resolveRef");
|
|
1533
1668
|
const bufferLimit = opts.bufferLimit ?? DEFAULT_SUBSCRIBE_BUFFER_LIMIT;
|
|
@@ -1687,6 +1822,8 @@ export function createRepoStore(config) {
|
|
|
1687
1822
|
listRefs,
|
|
1688
1823
|
resolveHead,
|
|
1689
1824
|
getRepoDir,
|
|
1825
|
+
openCommittedReads,
|
|
1826
|
+
openCommittedReadsAtCommit,
|
|
1690
1827
|
subscribe,
|
|
1691
1828
|
};
|
|
1692
1829
|
}
|
|
@@ -40,9 +40,12 @@ export type SubscribeKindEntry<T> = {
|
|
|
40
40
|
* kinds filter against the inner `type` discriminator, and yields one
|
|
41
41
|
* `{ seq, event }` entry per matching blob.
|
|
42
42
|
*
|
|
43
|
-
* The path
|
|
44
|
-
* `
|
|
45
|
-
*
|
|
43
|
+
* The path-layout vocabulary (the `runs/`/`events/` prefixes and the
|
|
44
|
+
* `<seq>.json` filename shape) lives in the workflow-run kind handler,
|
|
45
|
+
* which is the authority for what may land under this prefix; this
|
|
46
|
+
* helper imports it rather than re-encoding it. The substrate
|
|
47
|
+
* `subscribe` it wraps is a generic ref-tail primitive and knows
|
|
48
|
+
* nothing about workflow-runs.
|
|
46
49
|
*
|
|
47
50
|
* Diff scope: the helper enumerates event blobs present in the new
|
|
48
51
|
* commit but absent from the old commit. A commit that does not add
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import git from "isomorphic-git";
|
|
3
1
|
import { type } from "arktype";
|
|
4
|
-
import {
|
|
5
|
-
/**
|
|
6
|
-
* Path-layout constants for the workflow-run repo's event log. The
|
|
7
|
-
* workflow-run kind handler commits one JSON blob per event under
|
|
8
|
-
* `runs/<runId>/events/<seq>.json` (the layout decided in the
|
|
9
|
-
* pre-build interface decisions for the workflow-run repo). The
|
|
10
|
-
* `subscribeKind` helper owns this layout so the substrate `subscribe`
|
|
11
|
-
* stays generic and free of workflow-event vocabulary.
|
|
12
|
-
*/
|
|
13
|
-
const RUNS_PREFIX = "runs";
|
|
14
|
-
const EVENTS_DIR = "events";
|
|
2
|
+
import { requireEventSeq, WORKFLOW_RUN_EVENTS_DIR, WORKFLOW_RUN_RUNS_PREFIX, } from "../workflow-run-kind.js";
|
|
15
3
|
/**
|
|
16
4
|
* Substrate envelope shape. Imported textually rather than from
|
|
17
5
|
* `types.ts` so this helper has a local, validator-checked view of
|
|
@@ -24,11 +12,6 @@ const SubstrateEvent = type({
|
|
|
24
12
|
"oldSha?": "string | null",
|
|
25
13
|
newSha: "string",
|
|
26
14
|
});
|
|
27
|
-
/**
|
|
28
|
-
* Per-event filename shape. Filenames are `<seq>.json` under
|
|
29
|
-
* `runs/<runId>/events/`. `<seq>` is a non-negative decimal integer.
|
|
30
|
-
*/
|
|
31
|
-
const EVENT_FILENAME_RE = /^(0|[1-9][0-9]*)\.json$/;
|
|
32
15
|
const KindEnvelope = type({
|
|
33
16
|
type: "string",
|
|
34
17
|
});
|
|
@@ -41,9 +24,12 @@ const KindEnvelope = type({
|
|
|
41
24
|
* kinds filter against the inner `type` discriminator, and yields one
|
|
42
25
|
* `{ seq, event }` entry per matching blob.
|
|
43
26
|
*
|
|
44
|
-
* The path
|
|
45
|
-
* `
|
|
46
|
-
*
|
|
27
|
+
* The path-layout vocabulary (the `runs/`/`events/` prefixes and the
|
|
28
|
+
* `<seq>.json` filename shape) lives in the workflow-run kind handler,
|
|
29
|
+
* which is the authority for what may land under this prefix; this
|
|
30
|
+
* helper imports it rather than re-encoding it. The substrate
|
|
31
|
+
* `subscribe` it wraps is a generic ref-tail primitive and knows
|
|
32
|
+
* nothing about workflow-runs.
|
|
47
33
|
*
|
|
48
34
|
* Diff scope: the helper enumerates event blobs present in the new
|
|
49
35
|
* commit but absent from the old commit. A commit that does not add
|
|
@@ -53,7 +39,6 @@ const KindEnvelope = type({
|
|
|
53
39
|
*/
|
|
54
40
|
export async function* subscribeKind(store, principal, repoId, ref, validator, opts) {
|
|
55
41
|
const kindsAllowed = new Set(opts.kinds);
|
|
56
|
-
const dir = store.getRepoDir(repoId);
|
|
57
42
|
const subscribeOpts = {
|
|
58
43
|
signal: opts.signal,
|
|
59
44
|
from: opts.from,
|
|
@@ -67,9 +52,17 @@ export async function* subscribeKind(store, principal, repoId, ref, validator, o
|
|
|
67
52
|
if (envelope instanceof type.errors) {
|
|
68
53
|
throw new Error(`subscribe_kind_envelope_invalid: ${envelope.summary}`);
|
|
69
54
|
}
|
|
70
|
-
const
|
|
55
|
+
const newReads = await store.openCommittedReadsAtCommit(principal, repoId, envelope.newSha);
|
|
56
|
+
// A commit a concurrent GC pruned between the ref-update event and
|
|
57
|
+
// this read yields nothing; the substrate surfaces that as `null`.
|
|
58
|
+
if (newReads === null)
|
|
59
|
+
continue;
|
|
60
|
+
const oldReads = envelope.oldSha === undefined || envelope.oldSha === null
|
|
61
|
+
? null
|
|
62
|
+
: await store.openCommittedReadsAtCommit(principal, repoId, envelope.oldSha);
|
|
63
|
+
const candidates = await collectAddedEventBlobs(newReads, oldReads);
|
|
71
64
|
for (const candidate of candidates) {
|
|
72
|
-
const raw = await
|
|
65
|
+
const raw = await newReads.readBlobByOid(candidate.oid);
|
|
73
66
|
let parsedJson;
|
|
74
67
|
try {
|
|
75
68
|
parsedJson = JSON.parse(new TextDecoder().decode(raw));
|
|
@@ -94,20 +87,22 @@ export async function* subscribeKind(store, principal, repoId, ref, validator, o
|
|
|
94
87
|
}
|
|
95
88
|
}
|
|
96
89
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* the
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
90
|
+
* Diff the `runs/<runId>/events/` subtree between the new and old commit
|
|
91
|
+
* reads and return the event candidates present in the new commit but
|
|
92
|
+
* not the old. `oldReads` is `null` for the ref's first commit (no prior
|
|
93
|
+
* tip) or when the prior commit is no longer in the object store, in
|
|
94
|
+
* which case every event in the new commit is treated as added.
|
|
95
|
+
* Filenames that fail the `<seq>.json` shape are surfaced as errors
|
|
96
|
+
* rather than silently skipped — the workflow-run kind handler's
|
|
97
|
+
* validatePush is the authority for what may land under this prefix, so
|
|
98
|
+
* an unexpected shape here is a substrate-side invariant violation.
|
|
104
99
|
*/
|
|
105
|
-
async function collectAddedEventBlobs(
|
|
106
|
-
const newSet = await enumerateEventBlobs(
|
|
107
|
-
if (
|
|
100
|
+
async function collectAddedEventBlobs(newReads, oldReads) {
|
|
101
|
+
const newSet = await enumerateEventBlobs(newReads);
|
|
102
|
+
if (oldReads === null) {
|
|
108
103
|
return [...newSet.values()].sort(byRunThenSeq);
|
|
109
104
|
}
|
|
110
|
-
const oldSet = await enumerateEventBlobs(
|
|
105
|
+
const oldSet = await enumerateEventBlobs(oldReads);
|
|
111
106
|
const out = [];
|
|
112
107
|
for (const [key, candidate] of newSet) {
|
|
113
108
|
if (oldSet.has(key))
|
|
@@ -124,56 +119,26 @@ function byRunThenSeq(a, b) {
|
|
|
124
119
|
return 1;
|
|
125
120
|
return a.seq - b.seq;
|
|
126
121
|
}
|
|
127
|
-
async function enumerateEventBlobs(
|
|
122
|
+
async function enumerateEventBlobs(reads) {
|
|
128
123
|
const out = new Map();
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
commit = await git.readCommit({ fs, dir, oid: commitOid });
|
|
132
|
-
}
|
|
133
|
-
catch (err) {
|
|
134
|
-
if (hasCode(err) && err.code === "NotFoundError")
|
|
135
|
-
return out;
|
|
136
|
-
throw err;
|
|
137
|
-
}
|
|
138
|
-
const runsOid = await lookupSubtree(dir, commit.commit.tree, RUNS_PREFIX);
|
|
139
|
-
if (runsOid === null)
|
|
140
|
-
return out;
|
|
141
|
-
const { tree: runs } = await git.readTree({ fs, dir, oid: runsOid });
|
|
124
|
+
const runs = await reads.listDir(WORKFLOW_RUN_RUNS_PREFIX);
|
|
142
125
|
for (const runEntry of runs) {
|
|
143
126
|
if (runEntry.type !== "tree")
|
|
144
127
|
continue;
|
|
145
|
-
const runId = runEntry.
|
|
146
|
-
const
|
|
147
|
-
if (eventsOid === null)
|
|
148
|
-
continue;
|
|
149
|
-
const { tree: events } = await git.readTree({ fs, dir, oid: eventsOid });
|
|
128
|
+
const runId = runEntry.name;
|
|
129
|
+
const events = await reads.listDir(`${WORKFLOW_RUN_RUNS_PREFIX}/${runId}/${WORKFLOW_RUN_EVENTS_DIR}`);
|
|
150
130
|
for (const blob of events) {
|
|
151
131
|
if (blob.type !== "blob")
|
|
152
132
|
continue;
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const blobPath = `${RUNS_PREFIX}/${runId}/${EVENTS_DIR}/${blob.path}`;
|
|
162
|
-
out.set(`${runId}/${blob.path}`, { seq, runId, blobPath });
|
|
133
|
+
const blobPath = `${WORKFLOW_RUN_RUNS_PREFIX}/${runId}/${WORKFLOW_RUN_EVENTS_DIR}/${blob.name}`;
|
|
134
|
+
const seq = requireEventSeq(blob.name, blobPath);
|
|
135
|
+
out.set(`${runId}/${blob.name}`, {
|
|
136
|
+
seq,
|
|
137
|
+
runId,
|
|
138
|
+
blobPath,
|
|
139
|
+
oid: blob.oid,
|
|
140
|
+
});
|
|
163
141
|
}
|
|
164
142
|
}
|
|
165
143
|
return out;
|
|
166
144
|
}
|
|
167
|
-
async function lookupSubtree(dir, parentTreeOid, name) {
|
|
168
|
-
const { tree } = await git.readTree({ fs, dir, oid: parentTreeOid });
|
|
169
|
-
const entry = tree.find((e) => e.path === name);
|
|
170
|
-
if (entry === undefined)
|
|
171
|
-
return null;
|
|
172
|
-
if (entry.type !== "tree")
|
|
173
|
-
return null;
|
|
174
|
-
return entry.oid;
|
|
175
|
-
}
|
|
176
|
-
async function readBlobAtCommit(dir, commitOid, filepath) {
|
|
177
|
-
const { blob } = await git.readBlob({ fs, dir, oid: commitOid, filepath });
|
|
178
|
-
return blob;
|
|
179
|
-
}
|