@kici-dev/agent 0.6.1 → 0.7.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 (32) hide show
  1. package/dist/config.d.ts +38 -38
  2. package/dist/eval-runner.js +1866 -0
  3. package/dist/execution/dep-installer.d.ts +28 -7
  4. package/dist/execution/eval-context.d.ts +114 -0
  5. package/dist/execution/global-eval-types.d.ts +26 -0
  6. package/dist/execution/job-runner.d.ts +23 -75
  7. package/dist/execution/npm-registry-config.d.ts +6 -0
  8. package/dist/execution/rule-evaluator.d.ts +2 -1
  9. package/dist/execution/sandbox/bare-metal-sandbox.d.ts +6 -0
  10. package/dist/execution/sandbox/container-hardening.d.ts +8 -0
  11. package/dist/execution/sandbox/container-sandbox.d.ts +9 -0
  12. package/dist/execution/sandbox/eval-dispatch.d.ts +28 -0
  13. package/dist/execution/sandbox/eval-fork-runner.d.ts +43 -0
  14. package/dist/execution/sandbox/eval-runner.d.ts +21 -0
  15. package/dist/execution/sandbox/fork-runner.d.ts +23 -0
  16. package/dist/execution/sandbox/ipc-protocol.d.ts +82 -8
  17. package/dist/execution/sandbox/job-network.d.ts +91 -0
  18. package/dist/execution/sandbox/log-masker.d.ts +38 -0
  19. package/dist/execution/sandbox/types.d.ts +6 -0
  20. package/dist/execution/sandbox/workflow-runner.d.ts +1 -1
  21. package/dist/execution/source-packer.d.ts +4 -4
  22. package/dist/execution/source-restore.d.ts +28 -13
  23. package/dist/execution/workflow-loader.d.ts +16 -13
  24. package/dist/execution/yarnrc-berry-config.d.ts +6 -4
  25. package/dist/index.js +83 -40
  26. package/dist/provenance/statement-builder.d.ts +19 -8
  27. package/dist/server.js +1527 -1665
  28. package/dist/workflow-runner-bundle.js +1105 -184
  29. package/dist/workflow-runner.js +395 -149
  30. package/dist/ws/orchestrator-client.d.ts +4 -0
  31. package/package.json +6 -5
  32. package/sbom.spdx.json +66 -66
@@ -185946,6 +185946,38 @@ var init_attestation_origin = __esmMin((() => {
185946
185946
  "deferred",
185947
185947
  "offline-backfill"
185948
185948
  ]);
185949
+ }));
185950
+ //#endregion
185951
+ //#region ../engine/dist/provenance/id-token-event-claims.js
185952
+ var provenanceContextSchema;
185953
+ var init_id_token_event_claims = __esmMin((() => {
185954
+ init_zod();
185955
+ provenanceContextSchema = object({
185956
+ /** `owner/repo`, the token's `repository` claim. */
185957
+ repository: string$1().nullable(),
185958
+ /** The branch the run PRESENTS — a pull request's BASE branch. */
185959
+ ref: string$1().nullable(),
185960
+ /** The run's commit SHA, the token's `sha` claim. */
185961
+ sha: string$1().nullable(),
185962
+ /**
185963
+ * The token's `workflow_ref` claim (`<workflow_name>@<sha>`) — NOT the git
185964
+ * ref used to clone a global workflow's repository. The statement's
185965
+ * `workflow.path` is compared against this.
185966
+ */
185967
+ workflowRef: string$1().nullable(),
185968
+ runId: string$1(),
185969
+ jobId: string$1(),
185970
+ /** The customer's public org id, resolved server-side from the routing key. */
185971
+ orgId: string$1(),
185972
+ /** `triggered` or `run-remote`, derived from the run's local-working-tree flag. */
185973
+ sourceOrigin: string$1(),
185974
+ /** Informational source provider (github / gitlab / …). */
185975
+ provider: string$1().nullable(),
185976
+ /** The orchestrator's provenance issuer, for the statement's `builder.id`. */
185977
+ issuer: string$1(),
185978
+ /** This orchestrator's instance id, also for `builder.id`. */
185979
+ orchestratorId: string$1()
185980
+ }).passthrough();
185949
185981
  })), heartbeatSchema, nackSchema;
185950
185982
  var init_common = __esmMin((() => {
185951
185983
  init_zod();
@@ -186562,6 +186594,7 @@ var init_access_log = __esmMin((() => {
186562
186594
  "runs.list.read",
186563
186595
  "runs.filters.read",
186564
186596
  "sources.list.read",
186597
+ "admin_tokens.list.read",
186565
186598
  "run.payload.read",
186566
186599
  "run.orch_logs.read",
186567
186600
  "step.logs.read",
@@ -186806,6 +186839,7 @@ var init_event_log = __esmMin((() => {
186806
186839
  init_zod();
186807
186840
  EventLogStatus = _enum([
186808
186841
  "received",
186842
+ "shed",
186809
186843
  "processed",
186810
186844
  "duplicate",
186811
186845
  "lockfile_missing",
@@ -186835,9 +186869,24 @@ var init_deployment_identity = __esmMin((() => {
186835
186869
  adminInvocation: string$1().min(1).optional(),
186836
186870
  adminPath: string$1().min(1).optional()
186837
186871
  });
186872
+ }));
186873
+ //#endregion
186874
+ //#region ../engine/dist/protocol/messages/config-paths.js
186875
+ var ConfigPathsSchema;
186876
+ var init_config_paths = __esmMin((() => {
186877
+ init_zod();
186878
+ ConfigPathsSchema = object({
186879
+ /** The env file the service manager loads (`EnvironmentFile=` / `env_file:`). */
186880
+ envFile: string$1().min(1).optional(),
186881
+ /** The scaler YAML file, or the directory when only a directory is configured. */
186882
+ scalerConfig: string$1().min(1).optional(),
186883
+ /** The generated compose file, for a compose deployment. */
186884
+ composeFile: string$1().min(1).optional()
186885
+ });
186838
186886
  })), SourceSubtype, OrchestratorMode, SourceProvider, sourceRegistrationSchema, acceptedSourceSchema, sourceRegistrationAckSchema, sourceDeregisterSchema, sourceDeregisterAckSchema;
186839
186887
  var init_source_registration = __esmMin((() => {
186840
186888
  init_deployment_identity();
186889
+ init_config_paths();
186841
186890
  init_zod();
186842
186891
  SourceSubtype = _enum([
186843
186892
  "github_app",
@@ -186924,6 +186973,13 @@ var init_source_registration = __esmMin((() => {
186924
186973
  * publish it, in which case the dashboard treats the shape as `unknown`.
186925
186974
  */
186926
186975
  deployment: DeploymentIdentitySchema.optional(),
186976
+ /**
186977
+ * Where this orchestrator's own config files live on its host, so the
186978
+ * dashboard can point an operator straight at them. Optional: an
186979
+ * orchestrator that predates the field omits it, and each member is omitted
186980
+ * independently when that path is not knowable.
186981
+ */
186982
+ configPaths: ConfigPathsSchema.optional(),
186927
186983
  /** Whether this orchestrator has S3 log storage configured. Used for multi-orch pool validation. */
186928
186984
  s3LogAccess: boolean$1().optional(),
186929
186985
  /** Queue timeout in ms. Platform uses this (with margin) for safety-net GC of stale queued jobs. */
@@ -187107,6 +187163,27 @@ var init_types$3 = __esmMin((() => {
187107
187163
  ]);
187108
187164
  }));
187109
187165
  //#endregion
187166
+ //#region ../engine/dist/regex-flags.js
187167
+ /**
187168
+ * Drop `g` and `y` from a flag string. Every other ECMAScript flag —
187169
+ * `d` (hasIndices), `i`, `m`, `s`, `u`, `v` — is meaningful to a single
187170
+ * `.test()` and is preserved.
187171
+ */
187172
+ function stripStatefulRegexFlags(flags) {
187173
+ return (flags ?? "").replace(STATEFUL_FLAGS, "");
187174
+ }
187175
+ /**
187176
+ * Same, but collapsing an empty result to `undefined` — the shape the SDK's
187177
+ * pattern types and the lock file use for "no flags".
187178
+ */
187179
+ function normalizeRegexFlags(flags) {
187180
+ return stripStatefulRegexFlags(flags) || void 0;
187181
+ }
187182
+ var STATEFUL_FLAGS;
187183
+ var init_regex_flags = __esmMin((() => {
187184
+ STATEFUL_FLAGS = /[gy]/g;
187185
+ }));
187186
+ //#endregion
187110
187187
  //#region ../engine/dist/labels-match.js
187111
187188
  var LabelMatcher, HostTargetValue, HostTargetSelector;
187112
187189
  var init_labels_match = __esmMin((() => {
@@ -187304,7 +187381,7 @@ var init_dashboard_global_workflows = __esmMin((() => {
187304
187381
  settings: globalWorkflowSettingsSchema.optional(),
187305
187382
  error: string$1().optional()
187306
187383
  });
187307
- })), dashboardRunDetailRequestSchema, dashboardRunStructuredRequestSchema, dashboardRunStructuredResponseSchema, dashboardRunStateRequestSchema, dashboardRunStateResponseSchema, dashboardStepLogsRequestSchema, dashboardStepDetailSchema, dashboardJobDetailSchema, trustContextSchema, dashboardRunDetailResponseSchema, dashboardStepLogsResponseSchema, dashboardAttestationsListRequestSchema, attestationVerifyStatusSchema, attestationListItemSchema, dashboardAttestationsListResponseSchema, dashboardArtifactsListRequestSchema, artifactListItemSchema, dashboardArtifactsListResponseSchema, attestationListSummarySchema, attestationListFiltersSchema, dashboardAttestationsListAllRequestSchema, dashboardAttestationsListAllResponseSchema, dashboardAttestationGetRequestSchema, dashboardAttestationGetResponseSchema, dashboardAttestationRetryRequestSchema, dashboardAttestationRetryResponseSchema, dashboardRunSummarySchema, dashboardRunsListRequestSchema, dashboardRunsListResponseSchema, dashboardRunsFiltersRequestSchema, dashboardRunsFiltersResponseSchema, dashboardSourceSummarySchema, dashboardSourcesListRequestSchema, dashboardSourcesListResponseSchema, runRerunRequestSchema, runRerunResponseSchema, manualScheduleRequestSchema, manualScheduleResponseSchema, runCancelRequestSchema, runCancelResponseSchema, dashboardPayloadRequestSchema, dashboardPayloadResponseSchema, runLineageSchema, eventLogListItemSchema, dashboardEventLogListRequestSchema, dashboardEventLogListResponseSchema, dashboardEventLogDetailRequestSchema, dashboardEventLogDetailResponseSchema, dashboardEventLogActivityRequestSchema, eventLogActivityCountsSchema, dashboardEventLogActivityResponseSchema, EventLogPayloadStreamError, dashboardEventLogPayloadStreamRequestSchema, dashboardEventLogPayloadChunkSchema, browserEventLogPayloadChunkSchema, dashboardEventDlqListItemSchema, dashboardEventDlqListRequestSchema, dashboardEventDlqListResponseSchema, dashboardEventDlqCountRequestSchema, dashboardEventDlqCountResponseSchema, dashboardEventDlqRetryRequestSchema, dashboardEventDlqRetryResponseSchema, dashboardEventDlqDiscardRequestSchema, dashboardEventDlqDiscardResponseSchema, contextTypeSchema, contextListRequestSchema, contextListResponseSchema, contextGetRequestSchema, contextGetResponseSchema, contextCreateRequestSchema, contextCreateResponseSchema, contextUpdateRequestSchema, contextUpdateResponseSchema, contextTestAccessSetRequestSchema, contextTestAccessSetResponseSchema, contextDeleteRequestSchema, ContextDeleteErrorCode, contextDeleteResponseSchema, contextVarsListRequestSchema, contextVarsListResponseSchema, contextVarSetRequestSchema, contextVarSetResponseSchema, contextVarDeleteRequestSchema, contextVarDeleteResponseSchema, contextSourceOverridesListRequestSchema, contextSourceOverridesListResponseSchema, contextSourceOverrideSetRequestSchema, contextSourceOverrideSetResponseSchema, contextSourceOverrideDeleteRequestSchema, contextSourceOverrideDeleteResponseSchema, contextBindingEntrySchema, contextBindingsListRequestSchema, contextBindingsListResponseSchema, contextBindingsSetRequestSchema, contextBindingsSetResponseSchema, contextSecretsListRequestSchema, contextSecretsListResponseSchema, contextSecretSetRequestSchema, contextSecretSetResponseSchema, contextSecretDeleteRequestSchema, contextSecretDeleteResponseSchema, contextSecretScopeCreateRequestSchema, contextSecretScopeCreateResponseSchema, contextSecretScopeRenameRequestSchema, contextSecretScopeRenameResponseSchema, contextSecretScopeDeleteRequestSchema, contextSecretScopeDeleteResponseSchema, contextHistoryRequestSchema, contextHistoryResponseSchema, HeldRunQueueType, heldRunsListRequestSchema, heldRunsListResponseSchema, heldRunApproveRequestSchema, heldRunApproveResponseSchema, heldRunRejectRequestSchema, heldRunRejectResponseSchema, dashboardDiagnosticsRequestSchema, fleetPinnedRunSchema, FleetHostDisposition, fleetPreviewHostSchema, dashboardFleetHostsRequestSchema, dashboardFleetHostRequestSchema, dashboardFleetPreviewRequestSchema, dashboardFleetHostsResponseSchema, dashboardFleetHostResponseSchema, dashboardFleetPreviewResponseSchema, dashboardFleetWorkflowsForHostRequestSchema, fleetHostWorkflowSchema, dashboardFleetWorkflowsForHostResponseSchema, fleetHostDeclareRequestSchema, fleetHostDeclareResponseSchema, fleetHostRemoveRequestSchema, fleetHostRemoveResponseSchema, diagnosticsAgentSchema, diagnosticsScalerSchema, diagnosticsPeerAgentSchema, diagnosticsPeerSchema, dashboardDiagnosticsResponseSchema, dashboardScalerCapacityRequestSchema, scalerCapacityItemSchema, dashboardScalerCapacityResponseSchema, dashboardScalerAgentsRequestSchema, dashboardScalerAgentsResponseSchema, registrationDisableRequestSchema, registrationDisableResponseSchema, registrationDeleteRequestSchema, registrationDeleteResponseSchema, registrationsListRequestSchema, registrationSourceSchema, registrationItemSchema, registrationsListResponseSchema, backendsListRequestSchema, backendItemSchema, backendsListResponseSchema, backendGetRequestSchema, backendGetResponseSchema, backendsSyncAllRequestSchema, backendsSyncAllResponseSchema, backendSyncRequestSchema, backendSyncResponseSchema, backendTestRequestSchema, backendTestResponseSchema, testRelayUploadsInitRequestSchema, testRelayUploadsInitResponseSchema, testRelayTriggerRequestSchema, testRelayTriggerResponseSchema, testRelayRunStatusRequestSchema, testRelayRunStatusResponseSchema, testRelayRunLogsRequestSchema, testRelayRunLogsResponseSchema, testRelayCancelRequestSchema, testRelayCancelResponseSchema, dashboardPlatformToOrchSchema, runListPrincipalUserSchema, runListSourceSchema, runListItemSchema, diagnosticsInfraAgentSchema, diagnosticsInfraScalerSchema, diagnosticsInfraOrchestratorSchema, diagnosticsInfraAlertSchema, diagnosticsExecutionMetricsSchema, identityLinkItemSchema, memberIdentityLinkSchema, memberRoleAssignmentSchema, orgMemberSchema, DASHBOARD_REQUEST_TYPES;
187384
+ })), dashboardRunDetailRequestSchema, dashboardRunStructuredRequestSchema, dashboardRunStructuredResponseSchema, dashboardRunStateRequestSchema, dashboardRunStateResponseSchema, dashboardStepLogsRequestSchema, dashboardStepDetailSchema, dashboardJobDetailSchema, trustContextSchema, dashboardRunDetailResponseSchema, dashboardStepLogsResponseSchema, dashboardAttestationsListRequestSchema, attestationVerifyStatusSchema, attestationListItemSchema, dashboardAttestationsListResponseSchema, dashboardArtifactsListRequestSchema, artifactListItemSchema, dashboardArtifactsListResponseSchema, attestationListSummarySchema, attestationListFiltersSchema, dashboardAttestationsListAllRequestSchema, dashboardAttestationsListAllResponseSchema, dashboardAttestationGetRequestSchema, dashboardAttestationGetResponseSchema, dashboardAttestationRetryRequestSchema, dashboardAttestationRetryResponseSchema, dashboardRunSummarySchema, dashboardRunsListRequestSchema, dashboardRunsListResponseSchema, dashboardRunsFiltersRequestSchema, dashboardRunsFiltersResponseSchema, dashboardSourceSummarySchema, dashboardSourcesListRequestSchema, dashboardSourcesListResponseSchema, dashboardAdminTokenSummarySchema, dashboardAdminTokensListRequestSchema, dashboardAdminTokensListResponseSchema, runRerunRequestSchema, runRerunResponseSchema, manualScheduleRequestSchema, manualScheduleResponseSchema, runCancelRequestSchema, runCancelResponseSchema, dashboardPayloadRequestSchema, dashboardPayloadResponseSchema, runLineageSchema, eventLogListItemSchema, dashboardEventLogListRequestSchema, dashboardEventLogListResponseSchema, dashboardEventLogDetailRequestSchema, dashboardEventLogDetailResponseSchema, dashboardEventLogActivityRequestSchema, eventLogActivityCountsSchema, dashboardEventLogActivityResponseSchema, EventLogPayloadStreamError, dashboardEventLogPayloadStreamRequestSchema, dashboardEventLogPayloadChunkSchema, browserEventLogPayloadChunkSchema, dashboardEventDlqListItemSchema, dashboardEventDlqListRequestSchema, dashboardEventDlqListResponseSchema, dashboardEventDlqCountRequestSchema, dashboardEventDlqCountResponseSchema, dashboardEventDlqRetryRequestSchema, dashboardEventDlqRetryResponseSchema, dashboardEventDlqDiscardRequestSchema, dashboardEventDlqDiscardResponseSchema, contextTypeSchema, contextListRequestSchema, contextListResponseSchema, contextGetRequestSchema, contextGetResponseSchema, contextCreateRequestSchema, contextCreateResponseSchema, contextUpdateRequestSchema, contextUpdateResponseSchema, contextTestAccessSetRequestSchema, contextTestAccessSetResponseSchema, contextDeleteRequestSchema, ContextDeleteErrorCode, contextDeleteResponseSchema, contextVarsListRequestSchema, contextVarsListResponseSchema, contextVarSetRequestSchema, contextVarSetResponseSchema, contextVarDeleteRequestSchema, contextVarDeleteResponseSchema, contextSourceOverridesListRequestSchema, contextSourceOverridesListResponseSchema, contextSourceOverrideSetRequestSchema, contextSourceOverrideSetResponseSchema, contextSourceOverrideDeleteRequestSchema, contextSourceOverrideDeleteResponseSchema, contextBindingEntrySchema, contextBindingsListRequestSchema, contextBindingsListResponseSchema, contextBindingsSetRequestSchema, contextBindingsSetResponseSchema, contextSecretsListRequestSchema, contextSecretsListResponseSchema, contextSecretSetRequestSchema, contextSecretSetResponseSchema, contextSecretDeleteRequestSchema, contextSecretDeleteResponseSchema, contextSecretScopeCreateRequestSchema, contextSecretScopeCreateResponseSchema, contextSecretScopeRenameRequestSchema, contextSecretScopeRenameResponseSchema, contextSecretScopeDeleteRequestSchema, contextSecretScopeDeleteResponseSchema, contextHistoryRequestSchema, contextHistoryResponseSchema, HeldRunQueueType, heldRunsListRequestSchema, heldRunsListResponseSchema, heldRunApproveRequestSchema, heldRunApproveResponseSchema, heldRunRejectRequestSchema, heldRunRejectResponseSchema, dashboardDiagnosticsRequestSchema, fleetPinnedRunSchema, FleetHostDisposition, fleetPreviewHostSchema, dashboardFleetHostsRequestSchema, dashboardFleetHostRequestSchema, dashboardFleetPreviewRequestSchema, dashboardFleetHostsResponseSchema, dashboardFleetHostResponseSchema, dashboardFleetPreviewResponseSchema, dashboardFleetWorkflowsForHostRequestSchema, fleetHostWorkflowSchema, dashboardFleetWorkflowsForHostResponseSchema, fleetHostDeclareRequestSchema, fleetHostDeclareResponseSchema, fleetHostRemoveRequestSchema, fleetHostRemoveResponseSchema, diagnosticsAgentSchema, diagnosticsScalerSchema, diagnosticsPeerAgentSchema, diagnosticsPeerSchema, dashboardDiagnosticsResponseSchema, dashboardScalerCapacityRequestSchema, scalerCapacityItemSchema, dashboardScalerCapacityResponseSchema, dashboardScalerAgentsRequestSchema, dashboardScalerAgentsResponseSchema, registrationDisableRequestSchema, registrationDisableResponseSchema, registrationDeleteRequestSchema, registrationDeleteResponseSchema, registrationsListRequestSchema, registrationSourceSchema, registrationItemSchema, registrationsListResponseSchema, backendsListRequestSchema, backendItemSchema, backendsListResponseSchema, backendGetRequestSchema, backendGetResponseSchema, backendsSyncAllRequestSchema, backendsSyncAllResponseSchema, backendSyncRequestSchema, backendSyncResponseSchema, backendTestRequestSchema, backendTestResponseSchema, testRelayUploadsInitRequestSchema, testRelayUploadsInitResponseSchema, testRelayTriggerRequestSchema, testRelayTriggerResponseSchema, testRelayRunStatusRequestSchema, testRelayRunStatusResponseSchema, testRelayRunLogsRequestSchema, testRelayRunLogsResponseSchema, testRelayCancelRequestSchema, testRelayCancelResponseSchema, dashboardPlatformToOrchSchema, runListPrincipalUserSchema, runListSourceSchema, runListItemSchema, diagnosticsInfraAgentSchema, diagnosticsInfraScalerSchema, diagnosticsInfraOrchestratorSchema, diagnosticsInfraAlertSchema, diagnosticsExecutionMetricsSchema, identityLinkItemSchema, memberIdentityLinkSchema, memberRoleAssignmentSchema, orgMemberSchema, DASHBOARD_REQUEST_TYPES;
187308
187385
  var init_dashboard = __esmMin((() => {
187309
187386
  init_check_mode();
187310
187387
  init_source_origin();
@@ -187780,6 +187857,40 @@ var init_dashboard = __esmMin((() => {
187780
187857
  nextCursor: string$1().optional(),
187781
187858
  error: string$1().optional()
187782
187859
  });
187860
+ dashboardAdminTokenSummarySchema = object({
187861
+ id: string$1(),
187862
+ label: string$1(),
187863
+ /** `owner`, `admin` or `auditor` — the orchestrator's three fixed roles. */
187864
+ role: string$1(),
187865
+ /**
187866
+ * The intended holder the operator recorded at `token create --subject` (an
187867
+ * OIDC `sub` or an email), or null when nobody did. Null is the report's
187868
+ * `unlinked` finding.
187869
+ */
187870
+ subject: string$1().nullable(),
187871
+ /**
187872
+ * The single routing key this token is restricted to, or null for an
187873
+ * unscoped token. Load-bearing for the report: a routing-key-scoped token is
187874
+ * refused outright on the secret, context, org-settings and trust-policy
187875
+ * routes, so it is materially less privileged than its role alone suggests.
187876
+ */
187877
+ routingKey: string$1().nullable(),
187878
+ createdAt: string$1(),
187879
+ expiresAt: string$1().nullable(),
187880
+ lastUsedAt: string$1().nullable(),
187881
+ revoked: boolean$1()
187882
+ });
187883
+ dashboardAdminTokensListRequestSchema = object({
187884
+ type: literal("dashboard.admin-tokens.list"),
187885
+ requestId: string$1(),
187886
+ actor: actorPrincipalSchema
187887
+ });
187888
+ dashboardAdminTokensListResponseSchema = object({
187889
+ type: literal("dashboard.admin-tokens.list.response"),
187890
+ requestId: string$1(),
187891
+ tokens: array(dashboardAdminTokenSummarySchema),
187892
+ error: string$1().optional()
187893
+ });
187783
187894
  runRerunRequestSchema = object({
187784
187895
  type: literal("run.rerun.request"),
187785
187896
  requestId: string$1(),
@@ -189093,6 +189204,7 @@ var init_dashboard = __esmMin((() => {
189093
189204
  dashboardRunsListRequestSchema,
189094
189205
  dashboardRunsFiltersRequestSchema,
189095
189206
  dashboardSourcesListRequestSchema,
189207
+ dashboardAdminTokensListRequestSchema,
189096
189208
  runRerunRequestSchema,
189097
189209
  manualScheduleRequestSchema,
189098
189210
  runCancelRequestSchema,
@@ -189169,6 +189281,7 @@ var init_dashboard = __esmMin((() => {
189169
189281
  dashboardRunsListResponseSchema,
189170
189282
  dashboardRunsFiltersResponseSchema,
189171
189283
  dashboardSourcesListResponseSchema,
189284
+ dashboardAdminTokensListResponseSchema,
189172
189285
  runRerunResponseSchema,
189173
189286
  manualScheduleResponseSchema,
189174
189287
  runCancelResponseSchema,
@@ -189392,6 +189505,26 @@ var init_dashboard = __esmMin((() => {
189392
189505
  adminInvocation: string$1().nullable(),
189393
189506
  adminPath: string$1().nullable()
189394
189507
  }),
189508
+ /**
189509
+ * Absolute host paths of this orchestrator's own config files, for an
189510
+ * operator who wants to inspect them.
189511
+ *
189512
+ * Optional, and every member nullable. The Platform always emits the object
189513
+ * — all-nulls when nothing is known — but the field is declared optional
189514
+ * because a customer-installed `kici` CLI validates this same schema and may
189515
+ * be pointed at a Platform that predates the field. Members are null rather
189516
+ * than absent so a reader never has to distinguish "not sent" from "not
189517
+ * known".
189518
+ *
189519
+ * Null for every connection served from the database rather than from a live
189520
+ * in-memory registry entry: these are live-host runtime paths and are not
189521
+ * persisted, the same treatment `deployment.adminInvocation` already gets.
189522
+ */
189523
+ configPaths: object({
189524
+ envFile: string$1().nullable(),
189525
+ scalerConfig: string$1().nullable(),
189526
+ composeFile: string$1().nullable()
189527
+ }).optional(),
189395
189528
  s3LogAccess: boolean$1().nullable().optional(),
189396
189529
  agentCount: number$1(),
189397
189530
  runningJobs: number$1(),
@@ -189732,7 +189865,7 @@ var init_dashboard_write_operations = __esmMin((() => {
189732
189865
  category: "Held runs",
189733
189866
  label: "Approve held run",
189734
189867
  sensitivity: "dispatch",
189735
- cliEquivalent: "kici-admin runs approve"
189868
+ cliEquivalent: "kici-admin held-run approve"
189736
189869
  },
189737
189870
  {
189738
189871
  name: "held_runs.reject",
@@ -189740,7 +189873,7 @@ var init_dashboard_write_operations = __esmMin((() => {
189740
189873
  category: "Held runs",
189741
189874
  label: "Reject held run",
189742
189875
  sensitivity: "dispatch",
189743
- cliEquivalent: "kici-admin runs reject"
189876
+ cliEquivalent: "kici-admin held-run reject"
189744
189877
  },
189745
189878
  {
189746
189879
  name: "event_dlq.retry",
@@ -189831,6 +189964,7 @@ var init_dashboard_write_operations = __esmMin((() => {
189831
189964
  cliEquivalent: "kici-admin host remove"
189832
189965
  }
189833
189966
  ]);
189967
+ Object.freeze(["held_runs.approve", "held_runs.reject"]);
189834
189968
  DASHBOARD_WRITE_OPERATIONS_BY_NAME = new Map(DASHBOARD_WRITE_OPERATIONS.map((d) => [d.name, d]));
189835
189969
  new Map(DASHBOARD_WRITE_OPERATIONS.map((d) => [d.wireMessageType, d]));
189836
189970
  DashboardWritePolicyState = _enum([
@@ -191123,8 +191257,13 @@ var init_peer = __esmMin((() => {
191123
191257
  providerContext: record(string$1(), unknown()).optional(),
191124
191258
  /** Pre-signed source tarball download URL (cache hit). */
191125
191259
  sourceTarUrl: string$1().optional(),
191126
- /** SHA-256 hash of the source tarball bytes for integrity verification. */
191260
+ /**
191261
+ * @deprecated Use `sourceTarDigest` — this carries the workflow
191262
+ * `contentHash`, not a hash of the tarball bytes.
191263
+ */
191127
191264
  sourceTarHash: string$1().optional(),
191265
+ /** SHA-256 of the source tarball's own bytes, for integrity verification. */
191266
+ sourceTarDigest: string$1().optional(),
191128
191267
  /** Pre-signed dependency tarball download URL (cache hit). */
191129
191268
  depsUrl: string$1().optional(),
191130
191269
  /** Dependency tarball hash for cache keying. */
@@ -191348,6 +191487,7 @@ var init_peer = __esmMin((() => {
191348
191487
  ]);
191349
191488
  })), CacheRefScope, invokeResultSchema, gitAuthSchema, jobDispatchSchema, jobCancelSchema, registerAckSchema, agentRegisterSchema, agentStatusSchema, jobStatusSchema, globalEvalCandidateResultSchema, JobRejectReason, jobRejectSchema, jobAckSchema, agentLogChunkSchema, agentStepStatusSchema, jobHeartbeatSchema, agentLogSchema, jobConcurrencyReportSchema, jobConcurrencyAckSchema, configAckSchema, cacheUploadRequestSchema, cacheUploadResponseSchema, cacheUploadCompleteSchema, cacheUserRestoreRequestSchema, cacheUserRestoreResponseSchema, cacheUserSaveRequestSchema, cacheUserSaveResponseSchema, cacheUserSaveCompleteSchema, provenanceUploadRequestSchema, provenanceUploadResponseSchema, provenanceUploadCompleteSchema, provenanceUploadDeferSchema, ArtifactUploadOutcome, ArtifactRejectReason, ArtifactDownloadOutcome, artifactsUploadRequestSchema, artifactsUploadResponseSchema, artifactsUploadCompleteSchema, ArtifactCompleteAckOutcome, artifactsUploadCompleteAckSchema, artifactsDownloadRequestSchema, artifactsDownloadResponseSchema, eventEmitSchema, eventEmitResponseSchema, scalerClaimCredentialsSchema, scalerClaimCredentialsResponseSchema, agentMetricsSchema, agentAuthRequestSchema, agentAuthSuccessSchema, agentAuthFailureSchema, agentApiRequestSchema, agentApiResponseSchema, fleetLogsRequestSchema, fleetBundleChunkSchema, fleetBundleErrorSchema, StepApprovalOutcome, stepApprovalPayloadSchema, stepApprovalRequestSchema, stepApprovalResolvedSchema;
191350
191489
  var init_orchestrator_agent = __esmMin((() => {
191490
+ init_id_token_event_claims();
191351
191491
  init_execution_status();
191352
191492
  init_dsse();
191353
191493
  init_types$4();
@@ -191413,8 +191553,19 @@ var init_orchestrator_agent = __esmMin((() => {
191413
191553
  concurrencyWaitTimeoutMs: number().optional(),
191414
191554
  /** URL or file:// path to a pre-packed `.kici/` source tarball. If present, agent extracts it into workDir instead of cloning the repo. */
191415
191555
  sourceTarUrl: string$1().optional(),
191416
- /** SHA-256 hash of the source tarball bytes for integrity verification on download. */
191556
+ /**
191557
+ * @deprecated Use `sourceTarDigest`. Despite its name this carries the
191558
+ * workflow `contentHash`, not a hash of the tarball bytes, so an agent
191559
+ * could not verify a restored tarball against it. Kept on the wire for
191560
+ * older agents; removed at v1.0.0.
191561
+ */
191417
191562
  sourceTarHash: string$1().optional(),
191563
+ /**
191564
+ * SHA-256 of the source tarball's own bytes, for integrity verification
191565
+ * before extraction. The sibling of `depsHash`, which has always carried
191566
+ * the dependency tarball's real digest.
191567
+ */
191568
+ sourceTarDigest: string$1().optional(),
191418
191569
  /** URL or file:// path to pre-built dependency tarball. If present, agent extracts to .kici/node_modules/ instead of running install. */
191419
191570
  depsUrl: string$1().optional(),
191420
191571
  /** SHA-256 hash of the dependency tarball for integrity verification. */
@@ -191423,6 +191574,22 @@ var init_orchestrator_agent = __esmMin((() => {
191423
191574
  requestId: string$1().optional(),
191424
191575
  /** Base64-encoded X25519 public key for the workflow run (for encrypting secret outputs). */
191425
191576
  runPublicKey: string$1().optional(),
191577
+ /**
191578
+ * The orchestrator's own view of the build, for a provenance statement the
191579
+ * agent has to freeze before its identity token exists (the deferred path).
191580
+ *
191581
+ * Every field is what `buildIdTokenClaims` derives from the run row, so a
191582
+ * frozen statement built from this is field-for-field what a live mint
191583
+ * would have produced — and the server can therefore cross-check it. The
191584
+ * agent's local guess is NOT: the job's checkout `ref` is a pull request's
191585
+ * HEAD branch where the claim is the BASE branch, and `workflowRef` here is
191586
+ * the `<name>@<sha>` claim rather than a global workflow's clone ref.
191587
+ *
191588
+ * Additive and optional: an older orchestrator omits it and the agent falls
191589
+ * back to its local guess. That fallback statement fails the capture
191590
+ * cross-check, so the defer is dropped rather than stored unchecked.
191591
+ */
191592
+ provenanceContext: provenanceContextSchema.optional(),
191426
191593
  /** Plain outputs from upstream jobs (keyed by job name, then by step name). Populated for downstream jobs with `needs` dependencies. */
191427
191594
  upstreamJobOutputs: record(string$1(), record(string$1(), unknown())).optional(),
191428
191595
  /** Terminal status of each upstream job (keyed by job name; per-child for fan-out). Powers `ctx.needs.<job>.status`. */
@@ -191783,7 +191950,18 @@ var init_orchestrator_agent = __esmMin((() => {
191783
191950
  * and hashed it by the time it asks. Optional so an older agent that omits it
191784
191951
  * still gets a usable (lockfile-keyed) URL during a mixed-version rollout.
191785
191952
  */
191786
- depsHash: string$1().optional()
191953
+ depsHash: string$1().optional(),
191954
+ /**
191955
+ * SHA-256 of the source tarball about to be uploaded. Source uploads only.
191956
+ *
191957
+ * The source tarball is stored under its own content hash, so the
191958
+ * orchestrator needs it to sign the upload URL — the agent has already packed
191959
+ * and hashed it by the time it asks. Optional so an older agent that omits it
191960
+ * still gets a usable URL during a mixed-version rollout.
191961
+ */
191962
+ sourceTarDigest: string$1().optional(),
191963
+ /** In-repo `workspace:` sibling closure digest; part of the dep pointer key. */
191964
+ siblingsDigest: string$1().optional()
191787
191965
  });
191788
191966
  cacheUploadResponseSchema = object({
191789
191967
  type: literal("cache.upload.response"),
@@ -191800,7 +191978,11 @@ var init_orchestrator_agent = __esmMin((() => {
191800
191978
  platform: string$1(),
191801
191979
  arch: string$1(),
191802
191980
  /** SHA-256 hash of the dependency tarball for integrity verification. Only present for deps uploads. */
191803
- depsHash: string$1().optional()
191981
+ depsHash: string$1().optional(),
191982
+ /** SHA-256 of the source tarball's own bytes. Only present for source uploads. */
191983
+ sourceTarDigest: string$1().optional(),
191984
+ /** In-repo `workspace:` sibling closure digest; part of the dep pointer key. */
191985
+ siblingsDigest: string$1().optional()
191804
191986
  });
191805
191987
  cacheUserRestoreRequestSchema = object({
191806
191988
  type: literal("cache.user.restore.request"),
@@ -191880,8 +192062,12 @@ var init_orchestrator_agent = __esmMin((() => {
191880
192062
  subjectName: string$1(),
191881
192063
  /** Primary subject digest (lowercase hex). */
191882
192064
  subjectDigest: string$1(),
191883
- /** Requested token audience for the later mint. */
191884
- audience: string$1(),
192065
+ /**
192066
+ * Requested token audience for the later mint. Bounded to match the LIVE
192067
+ * mint's `oidcTokenRequestParamsSchema` — the two are the same
192068
+ * agent-supplied value and had no reason to differ.
192069
+ */
192070
+ audience: string$1().min(1).max(255),
191885
192071
  /** Bundle media type. */
191886
192072
  mediaType: string$1(),
191887
192073
  /** SHA-256 of the frozen DSSE statement payload — the later-mint binding. */
@@ -202307,6 +202493,20 @@ var init_plan_type = __esmMin((() => {
202307
202493
  PlanType.exclude(["free"]);
202308
202494
  PlanType.options;
202309
202495
  PlanType.enum.free, PlanType.enum.pro, PlanType.enum.team, PlanType.enum.business;
202496
+ })), StripeSubscriptionStatus;
202497
+ var init_subscription_status = __esmMin((() => {
202498
+ init_zod();
202499
+ StripeSubscriptionStatus = _enum([
202500
+ "incomplete",
202501
+ "incomplete_expired",
202502
+ "trialing",
202503
+ "active",
202504
+ "past_due",
202505
+ "canceled",
202506
+ "unpaid",
202507
+ "paused"
202508
+ ]);
202509
+ StripeSubscriptionStatus.enum.active, StripeSubscriptionStatus.enum.trialing, StripeSubscriptionStatus.enum.past_due;
202310
202510
  })), InfraAlertType;
202311
202511
  var init_infra_alert = __esmMin((() => {
202312
202512
  init_zod();
@@ -202325,6 +202525,7 @@ var init_dist$1 = __esmMin((() => {
202325
202525
  init_check_mode();
202326
202526
  init_source_origin();
202327
202527
  init_attestation_origin();
202528
+ init_id_token_event_claims();
202328
202529
  init_common();
202329
202530
  init_actor();
202330
202531
  init_pat_kind();
@@ -202339,11 +202540,13 @@ var init_dist$1 = __esmMin((() => {
202339
202540
  init_run_events();
202340
202541
  init_event_log();
202341
202542
  init_deployment_identity();
202543
+ init_config_paths();
202342
202544
  init_source_registration();
202343
202545
  init_scaler_backend_type();
202344
202546
  init_types$4();
202345
202547
  init_presentation();
202346
202548
  init_types$3();
202549
+ init_regex_flags();
202347
202550
  init_labels_match();
202348
202551
  init_concurrency_strategy();
202349
202552
  init_inventory();
@@ -202385,6 +202588,7 @@ var init_dist$1 = __esmMin((() => {
202385
202588
  init_expand$1();
202386
202589
  init_name();
202387
202590
  init_plan_type();
202591
+ init_subscription_status();
202388
202592
  init_infra_alert();
202389
202593
  }));
202390
202594
  //#endregion
@@ -203456,6 +203660,25 @@ var init_outputs = __esmMin((() => {
203456
203660
  function assertSecretName(value, field, subject = "git credential") {
203457
203661
  if (value.startsWith("-----BEGIN") || /^gh[pousr]_/.test(value) || value.startsWith("github_pat_")) throw new Error(`${subject} '${field}' looks like the credential itself, not the name of a secret holding it. Store it with \`kici-admin secret set\` and name it here.`);
203458
203662
  }
203663
+ /**
203664
+ * Every `<name>Secret` field in a credential map names a secret in qualified
203665
+ * `<context>:<secret-name>` form, and never carries the material itself.
203666
+ *
203667
+ * `subject` is the declaring surface as the author wrote it — `job('build')` or
203668
+ * `dynamicJob('reports')` — so a message names the call the author can find.
203669
+ * A `<name>Value` sibling is material by design and is not checked here.
203670
+ */
203671
+ function validateGitCredentials(subject, credentials) {
203672
+ for (const [alias, ref] of Object.entries(credentials)) {
203673
+ const bag = ref;
203674
+ for (const [field, value] of Object.entries(bag)) {
203675
+ if (!field.endsWith("Secret") || typeof value !== "string") continue;
203676
+ assertSecretName(value, `${alias}.${field}`);
203677
+ const idx = value.indexOf(":");
203678
+ if (idx <= 0 || idx >= value.length - 1 || value.slice(idx + 1).includes(":")) throw new Error(`${subject}: gitCredentials.${alias}.${field} must use qualified <context>:<secret-name> syntax (got: ${value})`);
203679
+ }
203680
+ }
203681
+ }
203459
203682
  var init_git_types = __esmMin((() => {}));
203460
203683
  //#endregion
203461
203684
  //#region ../sdk/dist/job.js
@@ -203508,7 +203731,7 @@ function job(nameOrOptions, maybeOptions) {
203508
203731
  if (options.runsOn !== void 0 && options.runsOnAll !== void 0) throw new Error(`job('${name}'): runsOn and runsOnAll are mutually exclusive`);
203509
203732
  if (options.container !== void 0) validateContainerImageSource(name, options.container);
203510
203733
  if (options.container && typeof options.container === "object" && options.container.auth) validateContainerAuth(name, options.container.auth);
203511
- if (options.gitCredentials) validateGitCredentials(name, options.gitCredentials);
203734
+ if (options.gitCredentials) validateGitCredentials(`job('${name}')`, options.gitCredentials);
203512
203735
  if (options.invoke === void 0 && options.runsOn === void 0 && options.runsOnAll === void 0) throw new Error(`job('${name}'): one of runsOn or runsOnAll is required`);
203513
203736
  if (options.onUnreachable !== void 0 && options.runsOnAll === void 0) console.warn(`[kici] job('${name}'): onUnreachable is ignored without runsOnAll`);
203514
203737
  if (options.includeUninitialized !== void 0 && options.runsOnAll === void 0) console.warn(`[kici] job('${name}'): includeUninitialized is ignored without runsOnAll`);
@@ -203626,17 +203849,6 @@ function validateContainerAuth(name, auth) {
203626
203849
  if (idx <= 0 || idx >= value.length - 1 || value.slice(idx + 1).includes(":")) throw new Error(`job('${name}'): container.auth.${field} must use qualified <context>:<secret-name> syntax (got: ${value})`);
203627
203850
  }
203628
203851
  }
203629
- function validateGitCredentials(name, credentials) {
203630
- for (const [alias, ref] of Object.entries(credentials)) {
203631
- const bag = ref;
203632
- for (const [field, value] of Object.entries(bag)) {
203633
- if (!field.endsWith("Secret") || typeof value !== "string") continue;
203634
- assertSecretName(value, `${alias}.${field}`);
203635
- const idx = value.indexOf(":");
203636
- if (idx <= 0 || idx >= value.length - 1 || value.slice(idx + 1).includes(":")) throw new Error(`job('${name}'): gitCredentials.${alias}.${field} must use qualified <context>:<secret-name> syntax (got: ${value})`);
203637
- }
203638
- }
203639
- }
203640
203852
  var init_job = __esmMin((() => {
203641
203853
  init_outputs();
203642
203854
  init_git_types();
@@ -204135,12 +204347,17 @@ var init_invoke = __esmMin((() => {
204135
204347
  /**
204136
204348
  * Convert a string or RegExp to a BranchPattern.
204137
204349
  * Strings become glob patterns, RegExp becomes regex patterns.
204350
+ *
204351
+ * `g` and `y` are dropped from the author's flags: the matcher memoizes one
204352
+ * `RegExp` per (flags, pattern), and a sticky instance carries `lastIndex`
204353
+ * between calls — so `/^release-\d+/g` would match `release-1`, miss
204354
+ * `release-2`, and match `release-3`, silently skipping every second push.
204138
204355
  */
204139
204356
  function toBranchPattern(input) {
204140
204357
  if (input instanceof RegExp) return {
204141
204358
  type: "regex",
204142
204359
  pattern: input.source,
204143
- flags: input.flags || void 0
204360
+ flags: normalizeRegexFlags(input.flags)
204144
204361
  };
204145
204362
  return {
204146
204363
  type: "glob",
@@ -204155,6 +204372,7 @@ function asArray(value) {
204155
204372
  }
204156
204373
  var DEFAULT_PR_EVENTS;
204157
204374
  var init_types$2 = __esmMin((() => {
204375
+ init_dist$1();
204158
204376
  DEFAULT_PR_EVENTS = [
204159
204377
  "opened",
204160
204378
  "synchronize",
@@ -204294,12 +204512,16 @@ var init_tag = __esmMin((() => {
204294
204512
  /**
204295
204513
  * Convert a string or RegExp to a BodyMatchPattern.
204296
204514
  * Strings become glob patterns, RegExp becomes regex patterns.
204515
+ *
204516
+ * `g` and `y` are dropped for the same reason as `toBranchPattern`: the reader
204517
+ * memoizes the compiled instance, so a sticky one alternates verdicts between
204518
+ * comments.
204297
204519
  */
204298
204520
  function toBodyMatchPattern(input) {
204299
204521
  if (input instanceof RegExp) return {
204300
204522
  type: "regex",
204301
204523
  pattern: input.source,
204302
- flags: input.flags || void 0
204524
+ flags: normalizeRegexFlags(input.flags)
204303
204525
  };
204304
204526
  return {
204305
204527
  type: "glob",
@@ -204333,6 +204555,7 @@ function comment(config) {
204333
204555
  }
204334
204556
  var init_comment = __esmMin((() => {
204335
204557
  init_types$2();
204558
+ init_dist$1();
204336
204559
  }));
204337
204560
  //#endregion
204338
204561
  //#region ../sdk/dist/triggers/review.js
@@ -204882,6 +205105,27 @@ function defineDispatchInputs(map) {
204882
205105
  }
204883
205106
  var init_dispatch_inputs = __esmMin((() => {}));
204884
205107
  //#endregion
205108
+ //#region ../sdk/dist/triggers/index.js
205109
+ var init_triggers = __esmMin((() => {
205110
+ init_types$2();
205111
+ init_pr();
205112
+ init_push();
205113
+ init_tag();
205114
+ init_comment();
205115
+ init_review();
205116
+ init_review_comment();
205117
+ init_release();
205118
+ init_dispatch();
205119
+ init_create();
205120
+ init_delete();
205121
+ init_status();
205122
+ init_workflow_run();
205123
+ init_fork();
205124
+ init_star();
205125
+ init_watch();
205126
+ init_webhook();
205127
+ }));
205128
+ //#endregion
204885
205129
  //#region ../sdk/dist/hooks/index.js
204886
205130
  /**
204887
205131
  * Normalize HookInput to extract run function and optional timeout.
@@ -205236,12 +205480,16 @@ function isDynamicJobFn(item) {
205236
205480
  * Two forms:
205237
205481
  * - **Function form** (event-only): `dynamicJob('shards', async ({ ctx }) => [...])`.
205238
205482
  * Dispatched at webhook time; deterministic from `ctx.event` alone.
205239
- * - **Options form** (result-aware): `dynamicJob('reports', { needs, generate })`.
205240
- * Deferred until every job/group in `needs` completes, then `generate` is
205241
- * evaluated with the upstreams' frozen outputs available as `ctx.needs`.
205483
+ * - **Options form**: `dynamicJob('reports', { needs, generate, gitCredentials })`.
205484
+ * With `needs` it is result-aware — deferred until every job/group in `needs`
205485
+ * completes, then `generate` is evaluated with the upstreams' frozen outputs
205486
+ * available as `ctx.needs`. `needs` is optional: without it the generator is
205487
+ * dispatched at webhook time, as the function form is. `gitCredentials`
205488
+ * declares the named credentials every generated job inherits, and only the
205489
+ * options form can carry it.
205242
205490
  *
205243
205491
  * @param groupName - The group name (must match what static jobs reference)
205244
- * @param fnOrConfig - The generator function, or a result-aware `{ needs, generate }` config
205492
+ * @param fnOrConfig - The generator function, or a `{ needs, generate, gitCredentials }` config
205245
205493
  */
205246
205494
  function dynamicJob(groupName, fnOrConfig) {
205247
205495
  const isConfig = typeof fnOrConfig !== "function";
@@ -205250,10 +205498,17 @@ function dynamicJob(groupName, fnOrConfig) {
205250
205498
  value: groupName,
205251
205499
  enumerable: false
205252
205500
  });
205253
- if (isConfig) Object.defineProperty(tagged, DYNAMIC_JOB_NEEDS_TAG, {
205501
+ if (isConfig && fnOrConfig.needs) Object.defineProperty(tagged, DYNAMIC_JOB_NEEDS_TAG, {
205254
205502
  value: fnOrConfig.needs,
205255
205503
  enumerable: false
205256
205504
  });
205505
+ if (isConfig && fnOrConfig.gitCredentials) {
205506
+ validateGitCredentials(`dynamicJob('${groupName}')`, fnOrConfig.gitCredentials);
205507
+ Object.defineProperty(tagged, DYNAMIC_JOB_GIT_CREDENTIALS_TAG, {
205508
+ value: fnOrConfig.gitCredentials,
205509
+ enumerable: false
205510
+ });
205511
+ }
205257
205512
  return tagged;
205258
205513
  }
205259
205514
  /**
@@ -205270,10 +205525,19 @@ function getDynamicJobGroup(fn) {
205270
205525
  function getDynamicJobNeeds(fn) {
205271
205526
  return fn[DYNAMIC_JOB_NEEDS_TAG];
205272
205527
  }
205273
- var DYNAMIC_JOB_GROUP_TAG, DYNAMIC_JOB_NEEDS_TAG;
205528
+ /**
205529
+ * Read the named git credentials a generator declared.
205530
+ * Returns undefined for the bare function form, which cannot declare any.
205531
+ */
205532
+ function getDynamicJobGitCredentials(fn) {
205533
+ return fn[DYNAMIC_JOB_GIT_CREDENTIALS_TAG];
205534
+ }
205535
+ var DYNAMIC_JOB_GROUP_TAG, DYNAMIC_JOB_NEEDS_TAG, DYNAMIC_JOB_GIT_CREDENTIALS_TAG;
205274
205536
  var init_types$1 = __esmMin((() => {
205537
+ init_git_types();
205275
205538
  DYNAMIC_JOB_GROUP_TAG = Symbol.for("kici:dynamicJobGroup");
205276
205539
  DYNAMIC_JOB_NEEDS_TAG = Symbol.for("kici:dynamicJobNeeds");
205540
+ DYNAMIC_JOB_GIT_CREDENTIALS_TAG = Symbol.for("kici:dynamicJobGitCredentials");
205277
205541
  }));
205278
205542
  //#endregion
205279
205543
  //#region ../sdk/dist/needs-context.js
@@ -205590,6 +205854,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
205590
205854
  CacheSpecSchema: () => CacheSpecSchema,
205591
205855
  ChangedFilesUnavailableError: () => ChangedFilesUnavailableError,
205592
205856
  DYNAMIC_GROUP_TAG: () => DYNAMIC_GROUP_TAG,
205857
+ DYNAMIC_JOB_GIT_CREDENTIALS_TAG: () => DYNAMIC_JOB_GIT_CREDENTIALS_TAG,
205593
205858
  DYNAMIC_JOB_GROUP_TAG: () => DYNAMIC_JOB_GROUP_TAG,
205594
205859
  DYNAMIC_JOB_NEEDS_TAG: () => DYNAMIC_JOB_NEEDS_TAG,
205595
205860
  SCALER_EVENT_NAMES: () => SCALER_EVENT_NAMES,
@@ -205628,6 +205893,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
205628
205893
  flattenStepInputs: () => flattenStepInputs,
205629
205894
  fork: () => fork,
205630
205895
  genericWebhook: () => genericWebhook,
205896
+ getDynamicJobGitCredentials: () => getDynamicJobGitCredentials,
205631
205897
  getDynamicJobGroup: () => getDynamicJobGroup,
205632
205898
  getDynamicJobNeeds: () => getDynamicJobNeeds,
205633
205899
  getJobOutputsMap: () => getJobOutputsMap,
@@ -205736,6 +206002,7 @@ var init_dist = __esmMin((() => {
205736
206002
  init_schedule();
205737
206003
  init_lifecycle();
205738
206004
  init_dispatch_inputs();
206005
+ init_triggers();
205739
206006
  init_hooks();
205740
206007
  init_rule();
205741
206008
  init_context();
@@ -205756,6 +206023,21 @@ var init_dist = __esmMin((() => {
205756
206023
  init_zod();
205757
206024
  }));
205758
206025
  //#endregion
206026
+ //#region ../sdk/dist/internal.js
206027
+ init_dist();
206028
+ init_api_types();
206029
+ init_approval();
206030
+ init_cache_types();
206031
+ init_filter_context();
206032
+ init_outputs();
206033
+ init_context();
206034
+ init_evaluator();
206035
+ init_rules();
206036
+ init_needs_context();
206037
+ init_secrets();
206038
+ init_expand();
206039
+ init_matrix();
206040
+ //#endregion
205759
206041
  //#region ../../node_modules/.pnpm/jose@6.2.10/node_modules/jose/dist/webapi/lib/buffer_utils.js
205760
206042
  const encoder = new TextEncoder();
205761
206043
  const decoder = new TextDecoder();
@@ -206250,13 +206532,21 @@ async function generateKeyPair(alg, options) {
206250
206532
  }
206251
206533
  //#endregion
206252
206534
  //#region ../engine/dist/provenance/statement-hash.js
206253
- init_dist();
206254
206535
  /**
206255
- * Lowercase-hex SHA-256 of the DSSE statement payload bytes. This is the binding
206256
- * a deferred OIDC mint commits to (truth-contract property 2): the later token
206257
- * carries this hash as a claim so the Platform identity cannot be re-bound to a
206258
- * different frozen statement at retry time. Browser-safe: `crypto.subtle` only,
206259
- * so the verifier (dashboard + CLI) can recompute it.
206536
+ * Lowercase-hex SHA-256 of the DSSE statement payload bytes.
206537
+ *
206538
+ * One of the two bindings a deferred OIDC mint commits to: the later token
206539
+ * carries this hash as a claim, so the identity cannot be re-bound to a
206540
+ * different frozen statement at retry time.
206541
+ *
206542
+ * It is a binding, not a substitute for the build-context cross-check. The
206543
+ * verifier requires BOTH for a non-live origin — the hash proves the statement
206544
+ * has not been swapped, and the cross-check proves the statement agrees with
206545
+ * the run the token names. A hash-only rule verified a bundle whose statement
206546
+ * claimed a release SHA the build never touched.
206547
+ *
206548
+ * Browser-safe: `crypto.subtle` only, so the verifier (dashboard + CLI) can
206549
+ * recompute it.
206260
206550
  */
206261
206551
  async function computeStatementHash(payload) {
206262
206552
  const digest = await crypto.subtle.digest("SHA-256", payload);
@@ -206418,7 +206708,12 @@ inTotoStatementSchema.extend({
206418
206708
  * for a deferred attestation (no minted identity token yet). Marks
206419
206709
  * `attestationOrigin: 'deferred'` in the internal parameters. The caller
206420
206710
  * DSSE-signs the returned statement immediately and computes its statement hash
206421
- * — the binding the later OIDC mint commits to (truth-contract property 2).
206711
+ * — one of the two bindings the later OIDC mint commits to.
206712
+ *
206713
+ * Emits the same fields `buildProvenanceStatement` emits, so a statement frozen
206714
+ * from an orchestrator-supplied context is field-for-field what a live mint
206715
+ * would have produced. That is what lets the orchestrator cross-check the
206716
+ * statement against its own run row before signing anything that commits to it.
206422
206717
  */
206423
206718
  function buildLocalProvenanceStatement(input) {
206424
206719
  const c = input.context;
@@ -206432,11 +206727,14 @@ function buildLocalProvenanceStatement(input) {
206432
206727
  predicate: {
206433
206728
  buildDefinition: {
206434
206729
  buildType: KICI_WORKFLOW_BUILD_TYPE,
206435
- externalParameters: { workflow: {
206436
- repository: c.repository,
206437
- ref: c.ref,
206438
- path: c.workflowRef
206439
- } },
206730
+ externalParameters: {
206731
+ workflow: {
206732
+ repository: c.repository,
206733
+ ref: c.ref,
206734
+ path: c.workflowRef
206735
+ },
206736
+ ...c.provider ? { provider: c.provider } : {}
206737
+ },
206440
206738
  internalParameters: {
206441
206739
  ...c.sha ? { commit: c.sha } : {},
206442
206740
  runId: c.runId,
@@ -206448,7 +206746,7 @@ function buildLocalProvenanceStatement(input) {
206448
206746
  },
206449
206747
  runDetails: {
206450
206748
  builder: {
206451
- id: `${c.issuer}/orchestrator/unknown`,
206749
+ id: `${c.issuer}/orchestrator/${c.orchestratorId ?? "unknown"}`,
206452
206750
  version: input.builderVersions
206453
206751
  },
206454
206752
  metadata: {
@@ -210731,15 +211029,6 @@ function createArtifactsApi(workDir, transport, roots) {
210731
211029
  }
210732
211030
  //#endregion
210733
211031
  //#region src/execution/sandbox/log-masker.ts
210734
- /**
210735
- * Secret value masking for log lines.
210736
- *
210737
- * Replaces all occurrences of registered secret values with '***' in log output.
210738
- * Used by the workflow runner to prevent secret leaks in IPC log messages.
210739
- *
210740
- * Performance: Builds a single combined regex from all secret values, so each
210741
- * log line is scanned in a single pass (not O(secrets * lines)).
210742
- */
210743
211032
  /** Minimum length for a secret value to be maskable (avoids false positives). */
210744
211033
  const MIN_MASK_LENGTH = 3;
210745
211034
  /**
@@ -210770,18 +211059,38 @@ var LogMasker = class {
210770
211059
  * Authorization: Basic headers, base64-encoded config values).
210771
211060
  * Values are sorted by length descending so longer values are matched first
210772
211061
  * (prevents partial masking when one secret is a substring of another).
211062
+ *
211063
+ * Multi-line values additionally register each of their individual lines.
211064
+ * Log output is split into lines before it reaches the masker, so a value
211065
+ * containing a newline can never match as a whole — a PEM private key or a
211066
+ * kubeconfig would otherwise stream in clear text. Two consequences of the
211067
+ * per-line registration are deliberate:
211068
+ *
211069
+ * - `MIN_MASK_LENGTH` is 3, so short structural lines of a structured secret
211070
+ * are registered too. A `---` YAML separator or a bare `{` from a
211071
+ * service-account JSON is masked wherever it appears in that job's logs.
211072
+ * - PEM header and footer lines (`-----BEGIN OPENSSH PRIVATE KEY-----`) are
211073
+ * not secret on their own and are masked as a side effect.
211074
+ *
211075
+ * Both are strictly safer than leaking the body, and no heuristic separates a
211076
+ * structural line from a body line without risking the reverse mistake.
210773
211077
  */
210774
211078
  registerSecrets(secrets) {
210775
211079
  const seen = /* @__PURE__ */ new Set();
210776
211080
  const values = [];
210777
- for (const value of Object.values(secrets)) if (value.length >= MIN_MASK_LENGTH && !seen.has(value)) {
210778
- seen.add(value);
210779
- values.push(value);
210780
- const b64 = Buffer.from(value).toString("base64");
211081
+ const add = (candidate) => {
211082
+ if (candidate.length < MIN_MASK_LENGTH || seen.has(candidate)) return;
211083
+ seen.add(candidate);
211084
+ values.push(candidate);
211085
+ const b64 = Buffer.from(candidate).toString("base64");
210781
211086
  if (b64.length >= MIN_MASK_LENGTH && !seen.has(b64)) {
210782
211087
  seen.add(b64);
210783
211088
  values.push(b64);
210784
211089
  }
211090
+ };
211091
+ for (const value of Object.values(secrets)) {
211092
+ add(value);
211093
+ if (value.includes("\n")) for (const rawLine of value.split("\n")) add(rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine);
210785
211094
  }
210786
211095
  if (values.length === 0) {
210787
211096
  this.pattern = null;
@@ -210807,6 +211116,57 @@ var LogMasker = class {
210807
211116
  return this.pattern !== null;
210808
211117
  }
210809
211118
  };
211119
+ /**
211120
+ * Create a LogMasker initialized with all secret values from the request.
211121
+ *
211122
+ * Collects values from both flat secrets and all namespaced context secrets,
211123
+ * deduplicating before registration.
211124
+ *
211125
+ * Both the runner child and the agent-side fork runner build a masker from the
211126
+ * same request, so the crash tail the agent assembles from the child's stderr is
211127
+ * masked with the same value set the child used for its own log lines.
211128
+ */
211129
+ function createSecretMasker(request) {
211130
+ const masker = new LogMasker();
211131
+ const allSecrets = {};
211132
+ if (request.secrets) Object.assign(allSecrets, request.secrets);
211133
+ if (request.namespacedSecrets) for (const contextSecrets of Object.values(request.namespacedSecrets)) Object.assign(allSecrets, contextSecrets);
211134
+ masker.registerSecrets(allSecrets);
211135
+ return masker;
211136
+ }
211137
+ /**
211138
+ * Mask every operator-visible text field of an outbound runner message.
211139
+ *
211140
+ * Each message type carrying free text is named here, so a new text-bearing
211141
+ * message type is a visible omission rather than a silent leak. `step.complete`
211142
+ * error text and the `job.complete` failure reason are persisted on the step and
211143
+ * run rows the dashboard renders, so they need the same masking `log.line` gets.
211144
+ *
211145
+ * Returns the message unchanged when no secrets are registered.
211146
+ */
211147
+ function maskMessageText(msg, masker) {
211148
+ if (!masker.hasSecrets()) return msg;
211149
+ switch (msg.type) {
211150
+ case "log.line": return {
211151
+ ...msg,
211152
+ line: masker.mask(msg.line)
211153
+ };
211154
+ case "step.complete": return msg.error ? {
211155
+ ...msg,
211156
+ error: {
211157
+ ...msg.error,
211158
+ message: masker.mask(msg.error.message)
211159
+ }
211160
+ } : msg;
211161
+ case "job.complete": {
211162
+ const masked = { ...msg };
211163
+ if (masked.error !== void 0) masked.error = masker.mask(masked.error);
211164
+ if (masked.droppedJobs) masked.droppedJobs = masked.droppedJobs.map((j) => masker.mask(j));
211165
+ return masked;
211166
+ }
211167
+ default: return msg;
211168
+ }
211169
+ }
210810
211170
  //#endregion
210811
211171
  //#region src/execution/sandbox/env-delta.ts
210812
211172
  /**
@@ -210988,18 +211348,23 @@ async function executeHook(opts) {
210988
211348
  step_type: `hook:${hookType}`
210989
211349
  });
210990
211350
  const startTime = Date.now();
211351
+ const abortController = new AbortController();
211352
+ let rejectTimeout = () => {};
211353
+ const timeoutRace = new Promise((_, reject) => {
211354
+ rejectTimeout = reject;
211355
+ });
211356
+ const timeoutId = setTimeout(() => {
211357
+ rejectTimeout(/* @__PURE__ */ new Error(`Hook '${normalized.name}' timed out after ${timeoutMs}ms`));
211358
+ abortController.abort();
211359
+ }, timeoutMs);
210991
211360
  const mergedCtx = {
210992
211361
  ...stepContext,
210993
- outcome
211362
+ outcome,
211363
+ signal: AbortSignal.any([...stepContext.signal ? [stepContext.signal] : [], abortController.signal]),
211364
+ ...typeof stepContext.$ === "function" ? { $: stepContext.$({ signal: abortController.signal }) } : {}
210994
211365
  };
210995
- const abortController = new AbortController();
210996
- const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
210997
211366
  try {
210998
- await Promise.race([normalized.run(mergedCtx), new Promise((_, reject) => {
210999
- abortController.signal.addEventListener("abort", () => {
211000
- reject(/* @__PURE__ */ new Error(`Hook '${normalized.name}' timed out after ${timeoutMs}ms`));
211001
- });
211002
- })]);
211367
+ await Promise.race([normalized.run(mergedCtx), timeoutRace]);
211003
211368
  clearTimeout(timeoutId);
211004
211369
  sendIpc({
211005
211370
  type: "step.complete",
@@ -211141,7 +211506,6 @@ async function runParallelGroup(node, opts) {
211141
211506
  }
211142
211507
  //#endregion
211143
211508
  //#region src/execution/sandbox/step-loop.ts
211144
- init_dist();
211145
211509
  init_dist$1();
211146
211510
  init_idempotency();
211147
211511
  init_dist$4();
@@ -211268,12 +211632,12 @@ const STEP_FAILURE_LOG_MAX_CHARS = 8192;
211268
211632
  * One consequence to know: a `$({ quiet: true })` command's output is kept out
211269
211633
  * of the log by the `verbose` gate in `streaming-zx-log.ts`, but when such a
211270
211634
  * command FAILS, zx packs its captured output into the thrown error's message —
211271
- * which this function then writes to the log. That text is already persisted
211272
- * unmasked on `step.complete` (the step row the dashboard renders), so the copy
211273
- * written here is the more protected of the two, and surfacing it is the whole
211274
- * point: a quiet command that fails is exactly the failure an operator cannot
211275
- * otherwise diagnose. Registered secret values are masked; anything the masker
211276
- * has never been told about is not.
211635
+ * which this function then writes to the log. Surfacing it is the whole point:
211636
+ * a quiet command that fails is exactly the failure an operator cannot
211637
+ * otherwise diagnose. The same text also travels on `step.complete` for the step
211638
+ * row the dashboard renders, and both copies pass the masker `maskMessageText`
211639
+ * masks `step.complete.error.message` alongside `log.line`. Registered secret
211640
+ * values are masked; anything the masker has never been told about is not.
211277
211641
  */
211278
211642
  function emitStepFailureLog(stepName, stepIndex, message, sendFn) {
211279
211643
  const prefix = `[kici] Step '${stepName}' failed: `;
@@ -211310,16 +211674,29 @@ async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, output
211310
211674
  });
211311
211675
  const startTime = Date.now();
211312
211676
  const abortController = new AbortController();
211313
- const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
211677
+ let timedOut = false;
211678
+ let rejectTimeout = () => {};
211679
+ const timeoutRace = new Promise((_, reject) => {
211680
+ rejectTimeout = reject;
211681
+ });
211682
+ const timeoutId = setTimeout(() => {
211683
+ timedOut = true;
211684
+ rejectTimeout(/* @__PURE__ */ new Error(`Step '${step.name}' timed out after ${timeoutMs}ms`));
211685
+ abortController.abort();
211686
+ opts.abortStep?.(stepIndex);
211687
+ }, timeoutMs);
211314
211688
  const stepAbortSignal = opts.getStepAbortSignal?.(stepIndex);
211689
+ const stepPromise = runStepWithCheckMode(step, stepIndex, ctx, checkMode, sendFn, opts);
211690
+ let stepSettled = false;
211691
+ const stepSettledPromise = stepPromise.then(() => {
211692
+ stepSettled = true;
211693
+ }, () => {
211694
+ stepSettled = true;
211695
+ });
211315
211696
  try {
211316
211697
  const phase = await Promise.race([
211317
- runStepWithCheckMode(step, stepIndex, ctx, checkMode, sendFn, opts),
211318
- new Promise((_, reject) => {
211319
- abortController.signal.addEventListener("abort", () => {
211320
- reject(/* @__PURE__ */ new Error(`Step '${step.name}' timed out after ${timeoutMs}ms`));
211321
- });
211322
- }),
211698
+ stepPromise,
211699
+ timeoutRace,
211323
211700
  new Promise((_, reject) => {
211324
211701
  if (!jobDeadlineSignal) return;
211325
211702
  if (jobDeadlineSignal.aborted) {
@@ -211336,7 +211713,10 @@ async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, output
211336
211713
  reject(new StepCancelledError(step.name));
211337
211714
  return;
211338
211715
  }
211339
- stepAbortSignal.addEventListener("abort", () => reject(new StepCancelledError(step.name)));
211716
+ stepAbortSignal.addEventListener("abort", () => {
211717
+ if (timedOut) return;
211718
+ reject(new StepCancelledError(step.name));
211719
+ });
211340
211720
  })
211341
211721
  ]);
211342
211722
  clearTimeout(timeoutId);
@@ -211368,6 +211748,15 @@ async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, output
211368
211748
  clearTimeout(timeoutId);
211369
211749
  const durationMs = Date.now() - startTime;
211370
211750
  const error = e instanceof Error ? e : new Error(String(e));
211751
+ if (timedOut && !stepSettled) {
211752
+ await Promise.race([stepSettledPromise, delayUnref(STEP_ABORT_GRACE_MS)]);
211753
+ if (!stepSettled) sendFn({
211754
+ type: "log.line",
211755
+ stepIndex,
211756
+ line: `[timeout] Step '${step.name}' did not stop within ${STEP_ABORT_GRACE_MS}ms of its abort signal; continuing without it.`,
211757
+ stream: LogStream.enum.stderr
211758
+ });
211759
+ }
211371
211760
  if (e instanceof StepCancelledError) {
211372
211761
  const secretsAccessed = getSecretsAccessLog?.(stepIndex);
211373
211762
  emitSecretMountEvents(getSecretMountRecords?.(stepIndex), stepIndex, sendFn);
@@ -211416,6 +211805,17 @@ async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, output
211416
211805
  }
211417
211806
  }
211418
211807
  /**
211808
+ * How long a timed-out step is given to unwind after its abort signal fires,
211809
+ * before the loop reports that the step ignored it and moves on.
211810
+ */
211811
+ const STEP_ABORT_GRACE_MS = 2e3;
211812
+ /** A timer-backed delay that never keeps the process alive on its own. */
211813
+ function delayUnref(ms) {
211814
+ return new Promise((resolve) => {
211815
+ setTimeout(resolve, ms).unref?.();
211816
+ });
211817
+ }
211818
+ /**
211419
211819
  * Emit one `step.secret_mount` IPC event per `mountFile` / `exposeFile` call
211420
211820
  * the step performed. Called from both the success and failure paths so the
211421
211821
  * orchestrator's audit trail records every mount regardless of step outcome.
@@ -212858,10 +213258,12 @@ function redactNpmOutput(input, tokens) {
212858
213258
  *
212859
213259
  * `nodeLinker: node-modules` makes berry lay down a real `node_modules` tree
212860
213260
  * (no PnP `.pnp.cjs`), so the agent's packer / restore / sibling-walk /
212861
- * workflow-loader work unchanged. `enableScripts: false` (when a private
212862
- * registry is configured) keeps dependency lifecycle scripts from seeing the
212863
- * synthesized token env vars — the same security model as npm/pnpm/classic
212864
- * `--ignore-scripts`.
213261
+ * workflow-loader work unchanged. `enableScripts: false` keeps dependency
213262
+ * lifecycle scripts from running at all for every install, not only one
213263
+ * against a private registry — the same security model as npm/pnpm/classic
213264
+ * `--ignore-scripts`. An operator opts back in with
213265
+ * `KICI_ALLOW_INSTALL_SCRIPTS=true`, which arrives here as
213266
+ * `ignoreScripts: false`.
212865
213267
  *
212866
213268
  * Reuses the same `ApplyNpmRegistryConfigArgs` / `ApplyNpmRegistryConfigResult`
212867
213269
  * shapes as the npm overlay so `dep-installer` can pick either by flavor.
@@ -212910,8 +213312,8 @@ async function applyYarnrcBerryConfig(args) {
212910
213312
  };
212911
213313
  const tokenEnv = {};
212912
213314
  const tokensForRedaction = [];
213315
+ if (args.ignoreScripts !== false) merged.enableScripts = false;
212913
213316
  if (hasPrivateRegistry) {
212914
- merged.enableScripts = false;
212915
213317
  const npmScopes = { ...doc.npmScopes ?? {} };
212916
213318
  for (let i = 0; i < registries.length; i++) {
212917
213319
  const reg = registries[i];
@@ -213249,9 +213651,11 @@ function isAbsoluteRel(rel) {
213249
213651
  * Security: the install runs with an isolated per-invocation cache/store
213250
213652
  * directory to prevent cache poisoning across build jobs — a malicious
213251
213653
  * package.json in one repo cannot taint the cache used by subsequent builds.
213252
- * The same pressure rules out letting lifecycle scripts see synthesized auth
213253
- * env vars the install runs with `--ignore-scripts` whenever a private
213254
- * registry is configured.
213654
+ * The install runs with `--ignore-scripts` for every package manager. A
213655
+ * lifecycle script in a committed `package.json` is customer code the agent
213656
+ * never agreed to execute: it would run wherever the install runs, which for a
213657
+ * step-child install is the process holding the job's secrets. Operators who
213658
+ * genuinely need it set `KICI_ALLOW_INSTALL_SCRIPTS=true` on the agent.
213255
213659
  */
213256
213660
  init_tmp();
213257
213661
  init_dist$2();
@@ -213289,9 +213693,11 @@ async function detectKiciYarnFlavor(repoRoot, kiciDir) {
213289
213693
  * between build jobs; the directory is removed after installation.
213290
213694
  *
213291
213695
  * If `opts.npmRegistries` / `opts.installEnvSecrets` is provided, a job-scoped
213292
- * `.kici/.npmrc` overlay is synthesized for the install, restored in `finally`,
213293
- * and the install runs with `--ignore-scripts` so lifecycle scripts in a
213294
- * committed `package.json` cannot exfiltrate the synthesized token env vars.
213696
+ * `.kici/.npmrc` overlay is synthesized for the install and restored in
213697
+ * `finally`.
213698
+ *
213699
+ * Lifecycle scripts are disabled for every package manager unless the operator
213700
+ * set `opts.allowInstallScripts`.
213295
213701
  *
213296
213702
  * @param kiciDir - Path to the `.kici/` directory containing package.json.
213297
213703
  * @param opts - Optional registry / installEnv / repoRoot configuration.
@@ -213313,13 +213719,15 @@ async function installDeps(kiciDir, opts = {}) {
213313
213719
  yarnFlavor
213314
213720
  });
213315
213721
  const startTime = Date.now();
213316
- const hasPrivateRegistry = (opts.npmRegistries?.length ?? 0) > 0 || (opts.installEnvSecrets ? Object.keys(opts.installEnvSecrets).length > 0 : false);
213722
+ const ignoreScripts = opts.allowInstallScripts !== true;
213723
+ const baseEnv = opts.baseEnv ?? process.env;
213317
213724
  const isBerry = packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry;
213318
213725
  const registryConfig = isBerry ? await applyYarnrcBerryConfig({
213319
213726
  kiciDir,
213320
213727
  npmRegistries: opts.npmRegistries,
213321
213728
  installEnvSecrets: opts.installEnvSecrets,
213322
- jobIdShort: opts.jobIdShort ?? "00000000"
213729
+ jobIdShort: opts.jobIdShort ?? "00000000",
213730
+ ignoreScripts
213323
213731
  }) : await applyNpmRegistryConfig({
213324
213732
  kiciDir,
213325
213733
  npmRegistries: opts.npmRegistries,
@@ -213329,22 +213737,26 @@ async function installDeps(kiciDir, opts = {}) {
213329
213737
  try {
213330
213738
  if (packageManager === PackageManager.Pnpm) await runPnpmInstall({
213331
213739
  kiciDir,
213332
- hasPrivateRegistry,
213333
- registryConfig
213740
+ ignoreScripts,
213741
+ registryConfig,
213742
+ baseEnv
213334
213743
  });
213335
213744
  else if (isBerry) await runYarnBerryInstall({
213336
213745
  kiciDir,
213337
- registryConfig
213746
+ registryConfig,
213747
+ baseEnv
213338
213748
  });
213339
213749
  else if (packageManager === PackageManager.Yarn) await runYarnInstall({
213340
213750
  kiciDir,
213341
- hasPrivateRegistry,
213342
- registryConfig
213751
+ ignoreScripts,
213752
+ registryConfig,
213753
+ baseEnv
213343
213754
  });
213344
213755
  else await runNpmInstall({
213345
213756
  kiciDir,
213346
- hasPrivateRegistry,
213347
- registryConfig
213757
+ ignoreScripts,
213758
+ registryConfig,
213759
+ baseEnv
213348
213760
  });
213349
213761
  } catch (e) {
213350
213762
  const tokens = registryConfig.tokensForRedaction;
@@ -213354,8 +213766,8 @@ async function installDeps(kiciDir, opts = {}) {
213354
213766
  } finally {
213355
213767
  await registryConfig.cleanup();
213356
213768
  }
213357
- if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
213358
- if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor);
213769
+ if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot, baseEnv);
213770
+ if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor, baseEnv);
213359
213771
  const durationMs = Date.now() - startTime;
213360
213772
  process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
213361
213773
  logger$2.info("Deps installed inline", {
@@ -213363,20 +213775,26 @@ async function installDeps(kiciDir, opts = {}) {
213363
213775
  durationMs
213364
213776
  });
213365
213777
  }
213366
- /** Build the Node binary directory onto PATH so spawned tools find `node`. */
213367
- function envWithNodeOnPath(extraEnv, nodeDir) {
213368
- const { NODE_ENV: _NODE_ENV, ...restEnv } = process.env;
213778
+ /**
213779
+ * Build the Node binary directory onto PATH so spawned tools find `node`.
213780
+ *
213781
+ * `baseEnv` is the caller's declared environment for the subprocess. It
213782
+ * defaults to `process.env` because inside the runner child that IS the
213783
+ * sanitized job environment; an agent-process caller passes a sanitized base.
213784
+ */
213785
+ function envWithNodeOnPath(extraEnv, nodeDir, baseEnv = process.env) {
213786
+ const { NODE_ENV: _NODE_ENV, ...restEnv } = baseEnv;
213369
213787
  return {
213370
213788
  ...restEnv,
213371
213789
  ...extraEnv,
213372
- PATH: `${nodeDir}${process.platform === "win32" ? ";" : ":"}${process.env.PATH ?? ""}`
213790
+ PATH: `${nodeDir}${process.platform === "win32" ? ";" : ":"}${restEnv.PATH ?? ""}`
213373
213791
  };
213374
213792
  }
213375
213793
  /** Run `npm install` in `.kici/` with an isolated cache directory. */
213376
213794
  async function runNpmInstall(args) {
213377
213795
  const { npmCliPath, nodeExe, nodeDir } = resolveNpm();
213378
213796
  const { path: cacheDir, cleanup } = await makeTempDir("npm-cache");
213379
- const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
213797
+ const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir, args.baseEnv);
213380
213798
  const buildArgs = (...prefix) => {
213381
213799
  const a = [
213382
213800
  ...prefix,
@@ -213386,7 +213804,7 @@ async function runNpmInstall(args) {
213386
213804
  "--no-audit",
213387
213805
  "--no-fund"
213388
213806
  ];
213389
- if (args.hasPrivateRegistry) a.push("--ignore-scripts");
213807
+ if (args.ignoreScripts) a.push("--ignore-scripts");
213390
213808
  return a;
213391
213809
  };
213392
213810
  try {
@@ -213415,7 +213833,7 @@ async function runPnpmInstall(args) {
213415
213833
  await assertPnpmAvailable();
213416
213834
  const { nodeDir } = resolveNpm();
213417
213835
  const { path: storeDir, cleanup } = await makeTempDir("pnpm-store");
213418
- const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
213836
+ const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir, args.baseEnv);
213419
213837
  const argv = [
213420
213838
  "install",
213421
213839
  `--config.store-dir=${storeDir}`,
@@ -213424,7 +213842,7 @@ async function runPnpmInstall(args) {
213424
213842
  "--config.side-effects-cache=false",
213425
213843
  PNPM_IGNORE_BUILD_GATE_ARG
213426
213844
  ];
213427
- if (args.hasPrivateRegistry) argv.push("--ignore-scripts");
213845
+ if (args.ignoreScripts) argv.push("--ignore-scripts");
213428
213846
  try {
213429
213847
  process.stderr.write(`[dep-installer:trace] running: pnpm ${argv.join(" ")}\n`);
213430
213848
  await execFileAsync("pnpm", argv, {
@@ -213438,7 +213856,7 @@ async function runPnpmInstall(args) {
213438
213856
  }
213439
213857
  }
213440
213858
  /** Pure: argv for `yarn install` with an isolated cache folder. */
213441
- function buildYarnInstallArgs(cacheDir, hasPrivateRegistry) {
213859
+ function buildYarnInstallArgs(cacheDir, ignoreScripts) {
213442
213860
  const a = [
213443
213861
  "install",
213444
213862
  "--cache-folder",
@@ -213446,7 +213864,7 @@ function buildYarnInstallArgs(cacheDir, hasPrivateRegistry) {
213446
213864
  "--non-interactive",
213447
213865
  "--no-progress"
213448
213866
  ];
213449
- if (hasPrivateRegistry) a.push("--ignore-scripts");
213867
+ if (ignoreScripts) a.push("--ignore-scripts");
213450
213868
  return a;
213451
213869
  }
213452
213870
  /**
@@ -213461,8 +213879,8 @@ async function runYarnInstall(args) {
213461
213879
  await assertYarnAvailable();
213462
213880
  const { nodeDir } = resolveNpm();
213463
213881
  const { path: cacheDir, cleanup } = await makeTempDir("yarn-cache");
213464
- const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
213465
- const argv = buildYarnInstallArgs(cacheDir, args.hasPrivateRegistry);
213882
+ const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir, args.baseEnv);
213883
+ const argv = buildYarnInstallArgs(cacheDir, args.ignoreScripts);
213466
213884
  try {
213467
213885
  process.stderr.write(`[dep-installer:trace] running: yarn ${argv.join(" ")}\n`);
213468
213886
  await execFileAsync("yarn", argv, {
@@ -213492,7 +213910,7 @@ async function runYarnBerryInstall(args) {
213492
213910
  await assertYarnAvailable();
213493
213911
  const { nodeDir } = resolveNpm();
213494
213912
  const env = {
213495
- ...envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir),
213913
+ ...envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir, args.baseEnv),
213496
213914
  COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
213497
213915
  };
213498
213916
  const argv = buildYarnBerryInstallArgs();
@@ -213523,11 +213941,11 @@ async function assertYarnAvailable() {
213523
213941
  * Deep cross-sibling build chains may build out of strict topological order —
213524
213942
  * real `.kici` closures are shallow.
213525
213943
  */
213526
- async function buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor) {
213944
+ async function buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor, baseEnv) {
213527
213945
  const siblings = await collectInRepoSiblings(repoRoot, kiciDir, resolveYarnNodeModulesRoot(repoRoot, kiciDir));
213528
213946
  if (siblings.length === 0) return;
213529
213947
  const { nodeDir } = resolveNpm();
213530
- const env = envWithNodeOnPath({}, nodeDir);
213948
+ const env = envWithNodeOnPath({}, nodeDir, baseEnv);
213531
213949
  for (const rel of [...siblings].reverse()) {
213532
213950
  const sibDir = join(repoRoot, rel);
213533
213951
  if (!await siblingHasBuildScript(sibDir)) continue;
@@ -213571,9 +213989,9 @@ async function siblingHasBuildScript(sibDir) {
213571
213989
  * the subprocess stderr/stdout is folded into the thrown error so the job's
213572
213990
  * failure message names the real cause instead of a bare "Command failed".
213573
213991
  */
213574
- async function buildWorkspaceClosure(repoRoot) {
213992
+ async function buildWorkspaceClosure(repoRoot, baseEnv) {
213575
213993
  const { nodeDir } = resolveNpm();
213576
- const env = envWithNodeOnPath({}, nodeDir);
213994
+ const env = envWithNodeOnPath({}, nodeDir, baseEnv);
213577
213995
  const argv = [
213578
213996
  "--filter",
213579
213997
  "{.kici}^...",
@@ -213616,6 +214034,394 @@ function logSubprocessStreams(e, tokens) {
213616
214034
  if (e && typeof e === "object" && "stderr" in e) process.stderr.write(`[dep-installer:trace] stderr: ${redactNpmOutput(String(e.stderr), tokens).slice(0, 500)}\n`);
213617
214035
  }
213618
214036
  //#endregion
214037
+ //#region ../core/dist/kici-ignore.js
214038
+ /**
214039
+ * `.kici/.kiciignore` — the declared list of paths the source digest excludes.
214040
+ *
214041
+ * The digest names a workflow's identity, so *which files it covers* is part of
214042
+ * that identity's definition. Before this file that definition was three
214043
+ * hard-coded constants, and it was wrong: the agent rewrites
214044
+ * `.kici/package-lock.json` (`npm install`, deliberately not `npm ci`, so a
214045
+ * resolved URL baked into the lock file cannot point at the wrong registry) and
214046
+ * `.kici/.npmrc` (a managed auth block, applied for one install and restored
214047
+ * after) inside the very tree whose digest it must reproduce. Neither was
214048
+ * excluded, so the drift gate rejected runs whose source had not changed.
214049
+ *
214050
+ * Making the set declarative rather than hard-coded is what lets a customer
214051
+ * whose own tooling writes into `.kici/` fix the same class of problem without
214052
+ * a release.
214053
+ *
214054
+ * ## Not to be confused with the repo-root `.kiciignore`
214055
+ *
214056
+ * A repo **root** `.kiciignore` already exists and is unrelated: it selects
214057
+ * which working-tree files a `kici run --remote` overlay uploads
214058
+ * (`compiler/src/remote/uploader.ts`, glob-matched, relative to the repo root).
214059
+ * This one lives at `.kici/.kiciignore`, is matched relative to `.kici/`, and
214060
+ * affects only the digest. The two files never read each other.
214061
+ *
214062
+ * ## Semantics
214063
+ *
214064
+ * gitignore-style, and deliberately implemented here rather than delegated to a
214065
+ * glob library: a glob matcher reads `node_modules/` as a directory named
214066
+ * `node_modules` and NOT as everything beneath it, which would silently fail to
214067
+ * exclude the exact paths this file exists to exclude. The digest is a
214068
+ * compat-protected identity input, so its matching rules are spelled out and
214069
+ * tested rather than inherited.
214070
+ *
214071
+ * - Blank lines and `#` comments are dropped; `\#` escapes a leading `#`.
214072
+ * - A trailing `/` makes a pattern directory-only.
214073
+ * - A pattern containing a slash is anchored at `.kici/`; a bare name matches
214074
+ * at any depth.
214075
+ * - `*` matches within one segment, `**` across segments, `?` one character.
214076
+ * - A leading `!` re-includes; the last matching pattern wins.
214077
+ * - A path under an ignored directory is ignored.
214078
+ *
214079
+ * One deliberate deviation from gitignore: a **symlink to a directory counts as
214080
+ * a directory**, so `node_modules/` covers a symlinked `node_modules`. `git`
214081
+ * treats a symlink as a file and would not. The purpose of the entry is
214082
+ * "exclude the dependency tree, whatever shape it takes on disk" — and treating
214083
+ * the link as a file hashed a `symlink:<target>` member into the identity that
214084
+ * the agent's own tree, where the dependency install writes a real directory,
214085
+ * structurally could not hold. The resolution happens in `collectSourcePaths`,
214086
+ * which stats a link to classify it and never follows one to read what is
214087
+ * behind it; the matcher below is unchanged and merely receives a truthful
214088
+ * `isDir`.
214089
+ */
214090
+ /**
214091
+ * The exclusion set applied when `.kici/.kiciignore` is absent.
214092
+ *
214093
+ * `node_modules/` and `types/` are build outputs the tarball either omits or
214094
+ * regenerates; `.npmrc`, `package-lock.json` and `pnpm-lock.yaml` are rewritten
214095
+ * by the dependency install a run performs before it re-hashes;
214096
+ * `kici.lock.json` is the file the digest is written into.
214097
+ */
214098
+ const KICI_DIGEST_DEFAULT_EXCLUSIONS = [
214099
+ "node_modules/",
214100
+ "types/",
214101
+ ".npmrc",
214102
+ "package-lock.json",
214103
+ "pnpm-lock.yaml",
214104
+ "kici.lock.json"
214105
+ ];
214106
+ /**
214107
+ * Excluded no matter what `.kiciignore` says.
214108
+ *
214109
+ * The compiler writes the computed digest INTO `.kici/kici.lock.json`, so a
214110
+ * digest covering that file would be an input to itself and no fixed point
214111
+ * would exist. This is arithmetic, not policy — there is no configuration under
214112
+ * which hashing it could work, so honoring a `.kiciignore` that omits it would
214113
+ * only produce a gate that rejects every run.
214114
+ *
214115
+ * The lock file's own integrity comes from elsewhere: the orchestrator fetches
214116
+ * it at the commit SHA, so provenance vouches for it, never `contentHash`.
214117
+ */
214118
+ const KICI_DIGEST_FORCED_EXCLUSIONS = ["kici.lock.json"];
214119
+ /**
214120
+ * Paths a run rewrites inside `.kici/` before it re-hashes the tree.
214121
+ *
214122
+ * Under replace semantics a short `.kiciignore` silently re-includes these, and
214123
+ * the digest then changes on every run with nothing naming why. A compile whose
214124
+ * file omits one warns rather than failing: the customer keeps full control,
214125
+ * and the failure stops being mysterious.
214126
+ */
214127
+ const KICI_RUN_REWRITTEN_PATHS = [
214128
+ "node_modules/",
214129
+ ".npmrc",
214130
+ "package-lock.json"
214131
+ ];
214132
+ /**
214133
+ * The `.kiciignore` file's name, relative to `.kici/`. Only the copy at the
214134
+ * root of `.kici/` is consulted; a nested one is ordinary hashed source.
214135
+ */
214136
+ const KICI_IGNORE_FILENAME = ".kiciignore";
214137
+ /**
214138
+ * Hashed no matter what `.kiciignore` says — including when it names itself.
214139
+ *
214140
+ * The file declares which paths define a workflow's identity, so it is part of
214141
+ * that identity: editing it must move the digest and force a recompile. Were it
214142
+ * able to exclude itself, someone could change what a lock file attests to
214143
+ * without changing the lock file, which is the one outcome the digest exists to
214144
+ * prevent. Forced inclusion beats every exclusion, the forced ones included.
214145
+ */
214146
+ const KICI_DIGEST_FORCED_INCLUSIONS = [KICI_IGNORE_FILENAME];
214147
+ /**
214148
+ * Split a `.kiciignore` file into patterns: trimmed, comment- and blank-free.
214149
+ *
214150
+ * Returns an empty array for a file that declares nothing, which under replace
214151
+ * semantics genuinely means "exclude nothing but the forced entry" — distinct
214152
+ * from an absent file, which means "use the defaults".
214153
+ */
214154
+ function parseKiciIgnore(content) {
214155
+ const patterns = [];
214156
+ for (const rawLine of content.split("\n")) {
214157
+ const line = rawLine.trim();
214158
+ if (line.length === 0 || line.startsWith("#")) continue;
214159
+ patterns.push(line.startsWith("\\#") ? line.slice(1) : line);
214160
+ }
214161
+ return patterns;
214162
+ }
214163
+ /** Translate one gitignore-style pattern body into an anchored regular expression. */
214164
+ function globToRegExpSource(glob) {
214165
+ let out = "";
214166
+ for (let i = 0; i < glob.length; i++) {
214167
+ const ch = glob[i];
214168
+ if (ch === "*") {
214169
+ if (glob[i + 1] === "*") {
214170
+ out += ".*";
214171
+ i++;
214172
+ } else out += "[^/]*";
214173
+ } else if (ch === "?") out += "[^/]";
214174
+ else out += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
214175
+ }
214176
+ return out;
214177
+ }
214178
+ function compilePattern(pattern) {
214179
+ let body = pattern;
214180
+ const negated = body.startsWith("!");
214181
+ if (negated) body = body.slice(1);
214182
+ if (body.startsWith("\\!")) body = body.slice(1);
214183
+ if (body.length === 0) return null;
214184
+ const dirOnly = body.endsWith("/");
214185
+ if (dirOnly) body = body.slice(0, -1);
214186
+ if (body.length === 0) return null;
214187
+ const leadingSlash = body.startsWith("/");
214188
+ if (leadingSlash) body = body.slice(1);
214189
+ if (body.length === 0) return null;
214190
+ const anchored = leadingSlash || body.includes("/");
214191
+ const source = globToRegExpSource(body);
214192
+ return {
214193
+ re: new RegExp(anchored ? `^${source}$` : `(?:^|/)${source}$`),
214194
+ dirOnly,
214195
+ negated
214196
+ };
214197
+ }
214198
+ /**
214199
+ * Build a matcher over patterns already parsed from a `.kiciignore`.
214200
+ *
214201
+ * The returned predicate takes a POSIX path relative to `.kici/` and whether it
214202
+ * names a directory. It reports true when the path itself — or any directory
214203
+ * above it — is excluded, so a directory pattern covers its whole subtree.
214204
+ */
214205
+ function buildKiciIgnoreMatcher(patterns) {
214206
+ const compiled = [];
214207
+ for (const pattern of patterns) {
214208
+ const entry = compilePattern(pattern);
214209
+ if (entry) compiled.push(entry);
214210
+ }
214211
+ if (compiled.length === 0) return () => false;
214212
+ /** Decide one concrete path, ancestors not considered. Last match wins. */
214213
+ const decideSelf = (relPath, isDir) => {
214214
+ let ignored = false;
214215
+ for (const { re, dirOnly, negated } of compiled) {
214216
+ if (dirOnly && !isDir) continue;
214217
+ if (re.test(relPath)) ignored = !negated;
214218
+ }
214219
+ return ignored;
214220
+ };
214221
+ return (relPath, isDir) => {
214222
+ const segments = relPath.split("/");
214223
+ for (let i = 1; i < segments.length; i++) if (decideSelf(segments.slice(0, i).join("/"), true)) return true;
214224
+ return decideSelf(relPath, isDir);
214225
+ };
214226
+ }
214227
+ /**
214228
+ * Which of the run-rewritten paths a pattern set fails to cover.
214229
+ *
214230
+ * Asks the matcher rather than comparing strings, so a broader pattern that
214231
+ * genuinely does exclude a path (`*`, `*.json`) is credited and does not warn.
214232
+ * Returned sorted, so the warning text is deterministic.
214233
+ */
214234
+ function findUncoveredRunRewrittenPaths(patterns) {
214235
+ const matches = buildKiciIgnoreMatcher(patterns);
214236
+ const uncovered = [];
214237
+ for (const target of KICI_RUN_REWRITTEN_PATHS) {
214238
+ const isDir = target.endsWith("/");
214239
+ if (!matches(isDir ? target.slice(0, -1) : target, isDir)) uncovered.push(target);
214240
+ }
214241
+ return uncovered.sort();
214242
+ }
214243
+ /** The warning text for one run-rewritten path a `.kiciignore` omits. */
214244
+ function runRewrittenWarning(target) {
214245
+ return `.kici/.kiciignore does not exclude '${target}', which a run rewrites inside .kici/ before the agent re-hashes the tree. The workflow's contentHash will therefore change on every run and the drift gate will reject it. Add '${target}' to .kici/.kiciignore, or remove the file to fall back to the defaults.`;
214246
+ }
214247
+ //#endregion
214248
+ //#region ../core/dist/kici-source-digest.js
214249
+ init_crypto();
214250
+ /**
214251
+ * Resolve the exclusion rules for `kiciDir`, reading `.kici/.kiciignore`.
214252
+ *
214253
+ * Separate from `hashKiciSourceTree` because the compiler needs the warnings
214254
+ * without needing a digest — and because a caller that wants to explain the
214255
+ * exclusion set should not have to re-implement how it was chosen.
214256
+ *
214257
+ * An unreadable file falls back to the defaults: the digest's job is to be
214258
+ * computable, and a read failure that silently emptied the exclusion set would
214259
+ * make every subsequent run drift instead.
214260
+ */
214261
+ async function loadKiciIgnoreRules(kiciDir) {
214262
+ let patterns = KICI_DIGEST_DEFAULT_EXCLUSIONS;
214263
+ let source = "default";
214264
+ try {
214265
+ patterns = parseKiciIgnore(await ro.readFile(path$1.join(kiciDir, KICI_IGNORE_FILENAME), "utf-8"));
214266
+ source = "file";
214267
+ } catch {}
214268
+ const missingRunRewritten = source === "file" ? findUncoveredRunRewrittenPaths(patterns) : [];
214269
+ return {
214270
+ source,
214271
+ patterns,
214272
+ forced: KICI_DIGEST_FORCED_EXCLUSIONS,
214273
+ missingRunRewritten,
214274
+ warnings: missingRunRewritten.map(runRewrittenWarning)
214275
+ };
214276
+ }
214277
+ /**
214278
+ * Whether the exclusion patterns should see this entry as a directory.
214279
+ *
214280
+ * `Dirent.isDirectory()` describes the link itself, so a symlinked
214281
+ * `.kici/node_modules` reads as a file and the directory-only `node_modules/`
214282
+ * pattern misses it. The compiler then hashes a `symlink:<target>` member that
214283
+ * no agent can hold: the tarball drops `.kici/node_modules` by prefix, and the
214284
+ * restore materialises a real directory the exclusion does match. The producer
214285
+ * and its verifier disagree on a tree nobody edited, and recompiling reproduces
214286
+ * the same lock.
214287
+ *
214288
+ * So classification resolves the link and the walk does not. An entry that
214289
+ * survives exclusion is still recorded as a member and still contributes its
214290
+ * target string; nothing here follows a link to read what is behind it, which
214291
+ * is what keeps the walk terminating and keeps the hashed set equal to what the
214292
+ * tarball carries.
214293
+ *
214294
+ * A link that cannot be resolved — dangling, cyclic (the kernel's own
214295
+ * `ELOOP`), or unreadable — is a file, which is what it contributes anyway.
214296
+ *
214297
+ * This is a deliberate deviation from gitignore, where `node_modules/` does not
214298
+ * match a symlinked `node_modules`. The purpose of the entry is "exclude the
214299
+ * dependency tree, whatever shape it takes on disk", and a link to the
214300
+ * dependency tree is the dependency tree.
214301
+ */
214302
+ async function matchesAsDirectory(abs, entry) {
214303
+ if (entry.isDirectory()) return true;
214304
+ if (!entry.isSymbolicLink()) return false;
214305
+ try {
214306
+ return (await ro.stat(abs)).isDirectory();
214307
+ } catch {
214308
+ return false;
214309
+ }
214310
+ }
214311
+ /** Every file the source tarball carries, as `.kici/`-prefixed POSIX paths. */
214312
+ async function collectSourcePaths(kiciDir, rules) {
214313
+ const found = [];
214314
+ const isExcluded = buildKiciIgnoreMatcher(rules.patterns);
214315
+ const isForced = buildKiciIgnoreMatcher(rules.forced);
214316
+ async function walk(absDir, relDir) {
214317
+ const entries = await ro.readdir(absDir, { withFileTypes: true });
214318
+ for (const entry of entries) {
214319
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
214320
+ const member = `.kici/${rel}`;
214321
+ const abs = path$1.join(absDir, entry.name);
214322
+ const asDir = await matchesAsDirectory(abs, entry);
214323
+ if (!(!asDir && KICI_DIGEST_FORCED_INCLUSIONS.includes(rel)) && (isForced(rel, asDir) || isExcluded(rel, asDir))) continue;
214324
+ if (entry.isDirectory()) await walk(abs, rel);
214325
+ else found.push(member);
214326
+ }
214327
+ }
214328
+ await walk(kiciDir, "");
214329
+ return found.sort();
214330
+ }
214331
+ /**
214332
+ * Read one member's contribution. A symlink contributes its target; a file its
214333
+ * text. An unreadable entry contributes the empty string rather than throwing —
214334
+ * the digest's job is to change when the tree changes, and a read failure is
214335
+ * reported by the tar step that follows, not here.
214336
+ */
214337
+ async function readMember(abs) {
214338
+ if ((await ro.lstat(abs)).isSymbolicLink()) return `symlink:${await ro.readlink(abs)}`;
214339
+ return ro.readFile(abs, "utf-8");
214340
+ }
214341
+ /**
214342
+ * SHA-256 over every file in `kiciDir`, excluding `.kici/node_modules/`.
214343
+ *
214344
+ * Returns the empty string when the directory does not exist, which the callers
214345
+ * treat exactly as they already treat an unreadable entry file: no hash, so no
214346
+ * drift gate.
214347
+ */
214348
+ async function hashKiciSourceTree(kiciDir) {
214349
+ let members;
214350
+ try {
214351
+ members = await collectSourcePaths(kiciDir, await loadKiciIgnoreRules(kiciDir));
214352
+ } catch {
214353
+ return "";
214354
+ }
214355
+ const parts = [];
214356
+ for (const member of members) {
214357
+ const abs = path$1.join(kiciDir, member.slice(6));
214358
+ let content;
214359
+ try {
214360
+ content = await readMember(abs);
214361
+ } catch {
214362
+ content = "";
214363
+ }
214364
+ parts.push(`${member}\0${normalizeLineEndings(content)}\0`);
214365
+ }
214366
+ return sha256(parts.join(""));
214367
+ }
214368
+ /**
214369
+ * Every symlink the digest hashes, in member order.
214370
+ *
214371
+ * Reads the same member set the digest does, so a link an exclusion covers is
214372
+ * absent here for the same reason it is absent from the hash.
214373
+ */
214374
+ async function collectSourceSymlinks(kiciDir, rules) {
214375
+ let members;
214376
+ try {
214377
+ members = await collectSourcePaths(kiciDir, rules ?? await loadKiciIgnoreRules(kiciDir));
214378
+ } catch {
214379
+ return [];
214380
+ }
214381
+ const found = [];
214382
+ for (const member of members) {
214383
+ const abs = path$1.join(kiciDir, member.slice(6));
214384
+ try {
214385
+ if ((await ro.lstat(abs)).isSymbolicLink()) found.push({
214386
+ member,
214387
+ target: await ro.readlink(abs)
214388
+ });
214389
+ } catch {}
214390
+ }
214391
+ return found;
214392
+ }
214393
+ /**
214394
+ * The sentence a drift error adds when the hashed tree carries symlinks.
214395
+ *
214396
+ * "Run 'kici compile'" is the right remedy for ordinary drift and is actively
214397
+ * misleading here: a symlink the tarball omits or extraction rewrites makes the
214398
+ * producer's hash unreachable on this side, so recompiling reproduces the same
214399
+ * lock forever. Naming the links turns an unrecoverable loop into something the
214400
+ * error text alone explains. Empty when the tree carries none, so an ordinary
214401
+ * drift error is unchanged.
214402
+ */
214403
+ function hashedSymlinkDriftNote(symlinks) {
214404
+ if (symlinks.length === 0) return "";
214405
+ const named = symlinks.map(({ member, target }) => `${member} -> ${target}`).join(", ");
214406
+ return ` The hashed .kici/ tree carries ${symlinks.length} symlink${symlinks.length === 1 ? "" : "s"} (${named}). A symlink is hashed as its target string, not the bytes behind it, so a link the source tarball omits or extraction rewrites cannot reproduce the compiling machine's hash and recompiling will not change that. Remove it from .kici/, or exclude it in .kici/.kiciignore.`;
214407
+ }
214408
+ /**
214409
+ * Locate the `.kici` directory a workflow file lives under, or null when it
214410
+ * does not live under one (a config file outside the convention). Walks
214411
+ * ancestors rather than taking the directory as a parameter, so every entry
214412
+ * point that loads a workflow module resolves the same tree without threading
214413
+ * one more argument through each of them.
214414
+ */
214415
+ function findKiciDir(entryPath) {
214416
+ let dir = path$1.dirname(path$1.resolve(entryPath));
214417
+ for (;;) {
214418
+ if (path$1.basename(dir) === ".kici") return dir;
214419
+ const parent = path$1.dirname(dir);
214420
+ if (parent === dir) return null;
214421
+ dir = parent;
214422
+ }
214423
+ }
214424
+ //#endregion
213619
214425
  //#region src/execution/generator-context.ts
213620
214426
  /**
213621
214427
  * Build the context handed to a `DynamicJobFn`.
@@ -213669,12 +214475,17 @@ init_dist();
213669
214475
  * ESM modules by resolved URL, so importing that path yields the workflow's live
213670
214476
  * singleton, not a fresh copy.
213671
214477
  *
213672
- * Falls back to the agent's bundled setters when resolution fails (mirrors
214478
+ * Both specifiers resolve to the same module-global maps `internal.ts` and the
214479
+ * root barrel re-export the same `outputs.js` bindings — so the fallback below
214480
+ * changes which entry is imported, never which singleton is mutated.
214481
+ *
214482
+ * Falls back to the agent's bundled setters when neither resolves (mirrors
213673
214483
  * `resolveSdkSetters` in the compiler's test runner).
213674
214484
  */
213675
214485
  async function resolveWorkflowSdkSetters(workflowFilePath) {
213676
- try {
213677
- const sdkEntry = createRequire(workflowFilePath).resolve("@kici-dev/sdk");
214486
+ const req = createRequire(workflowFilePath);
214487
+ for (const specifier of ["@kici-dev/sdk/internal", "@kici-dev/sdk"]) try {
214488
+ const sdkEntry = req.resolve(specifier);
213678
214489
  const sdk = await import(pathToFileURL(sdkEntry).href);
213679
214490
  if (typeof sdk.setStepOutputsMap === "function" && typeof sdk.setStepRefMap === "function" && typeof sdk.setJobOutputsMap === "function") return {
213680
214491
  setStepOutputsMap: sdk.setStepOutputsMap,
@@ -213688,8 +214499,8 @@ async function resolveWorkflowSdkSetters(workflowFilePath) {
213688
214499
  setJobOutputsMap
213689
214500
  };
213690
214501
  }
213691
- const AGENT_SDK_VERSION = "0.6.1";
213692
- const AGENT_SDK_BUNDLE_HASH = "22faf0da45de7c243ce87f80fd33ee51b1df52809fde457b16bb821678f65eb3";
214502
+ const AGENT_SDK_VERSION = "0.7.0";
214503
+ const AGENT_SDK_BUNDLE_HASH = "065963c7765dc8d87e04d45f57d7e15be1613da705e4ff3ec3742fd1408b7bf5";
213693
214504
  /**
213694
214505
  * Register the ESM loader hook that transforms `.ts` / `.tsx` files on the fly
213695
214506
  * for subsequent dynamic `import()` calls. Idempotent at our level via the
@@ -213729,10 +214540,49 @@ function ensureLoaderHookRegistered() {
213729
214540
  * with lockfiles compiled on Linux (LF).
213730
214541
  */
213731
214542
  function computeContentHash(rawSource, assetDigest) {
213732
- let input = `5:${normalizeLineEndings(rawSource)}`;
214543
+ let input = `7:${normalizeLineEndings(rawSource)}`;
213733
214544
  if (assetDigest !== void 0 && assetDigest.length > 0) input += `\0${normalizeLineEndings(assetDigest)}`;
213734
214545
  return sha256(input);
213735
214546
  }
214547
+ /**
214548
+ * The compile schema version the lock file records for `sourceFile`, or null
214549
+ * when the tree carries no readable lock (a `file://` in-place run, a workflow
214550
+ * outside the `.kici/` convention, a hand-built fixture).
214551
+ *
214552
+ * The source tarball carries `.kici/kici.lock.json` — the digest excludes it,
214553
+ * but `source-packer.ts` packs it — so the agent can read the producing
214554
+ * compiler's schema version from the tree it already has, with no wire field
214555
+ * to plumb and no protocol change.
214556
+ *
214557
+ * A malformed or unreadable lock returns null rather than throwing: this is a
214558
+ * diagnostic gate in front of the real hash check, so it must never convert a
214559
+ * bad lock into a worse error than the hash comparison already gives.
214560
+ */
214561
+ async function readLockCompileSchemaVersion(kiciDir, sourceFile) {
214562
+ let parsed;
214563
+ try {
214564
+ parsed = JSON.parse(await ro.readFile(path$1.join(kiciDir, "kici.lock.json"), "utf-8"));
214565
+ } catch {
214566
+ return null;
214567
+ }
214568
+ const workflows = parsed?.workflows;
214569
+ if (!Array.isArray(workflows)) return null;
214570
+ const normalize = (p) => p.replaceAll("\\", "/").replace(/^\.\//, "");
214571
+ const target = normalize(sourceFile);
214572
+ const versionOf = (w) => {
214573
+ const v = w?.compileSchemaVersion;
214574
+ return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null;
214575
+ };
214576
+ for (const w of workflows) {
214577
+ const file = w?.source?.file;
214578
+ if (typeof file === "string" && normalize(file) === target) return versionOf(w);
214579
+ }
214580
+ for (const w of workflows) {
214581
+ const v = versionOf(w);
214582
+ if (v !== null) return v;
214583
+ }
214584
+ return null;
214585
+ }
213736
214586
  async function buildAssetDigestFromResolvedPaths(workDir, resolvedPaths) {
213737
214587
  const parts = [];
213738
214588
  for (const rel of resolvedPaths) {
@@ -213754,20 +214604,30 @@ async function buildAssetDigestFromResolvedPaths(workDir, resolvedPaths) {
213754
214604
  * `node_modules/` the same way any `tsx`-style runner would — so host-repo
213755
214605
  * helpers and `@kici-dev/sdk` Just Work.
213756
214606
  *
213757
- * When `expectedContentHash` is provided, verifies the raw source matches
213758
- * the hash in the lock file. Drift between source and lock file produces a
213759
- * descriptive error that surfaces the baked agent SDK fingerprint (useful
213760
- * when debugging "is the agent running a stale build?").
214607
+ * When `expectedContentHash` is provided, verifies the extracted `.kici/` tree
214608
+ * matches the hash in the lock file. It re-hashes the whole tree, not the entry
214609
+ * file alone, so an edit to an imported helper is caught — that was the gap
214610
+ * that let a warm cache restore a stale tarball and run the OLD helper green.
214611
+ * Drift produces a descriptive error that surfaces the baked agent SDK
214612
+ * fingerprint (useful when debugging "is the agent running a stale build?").
213761
214613
  */
213762
214614
  async function loadWorkflowSource(workDir, sourceFile, expectedContentHash, resolvedHashFiles) {
213763
214615
  ensureLoaderHookRegistered();
213764
214616
  const filePath = path$1.join(workDir, sourceFile);
213765
214617
  if (expectedContentHash) {
213766
- const rawSource = await ro.readFile(filePath, "utf-8");
214618
+ const kiciDir = findKiciDir(filePath);
214619
+ if (kiciDir) {
214620
+ const lockVersion = await readLockCompileSchemaVersion(kiciDir, sourceFile);
214621
+ if (lockVersion !== null && lockVersion !== 7) throw new Error(`kici.lock.json was compiled by an incompatible @kici-dev/compiler: the lock declares compile schema ${lockVersion}, this agent implements 7. The schema version is mixed into every contentHash, so recompiling cannot reconcile them. Align the versions: upgrade the agent to one implementing schema ${lockVersion}, or pin @kici-dev/compiler to a release implementing schema 7 and recompile (agent baked @kici-dev/sdk@${AGENT_SDK_VERSION} bundleHash=${AGENT_SDK_BUNDLE_HASH}).`);
214622
+ }
214623
+ const rawSource = (kiciDir ? await hashKiciSourceTree(kiciDir) : "") || await ro.readFile(filePath, "utf-8");
213767
214624
  let assetDigest;
213768
214625
  if (resolvedHashFiles?.length) assetDigest = await buildAssetDigestFromResolvedPaths(workDir, resolvedHashFiles);
213769
214626
  const actualHash = computeContentHash(rawSource, assetDigest);
213770
- if (actualHash !== expectedContentHash) throw new Error(`Lock file is out of date: workflow source changed without regenerating kici.lock.json (expected contentHash ${expectedContentHash}, got ${actualHash}, agent baked @kici-dev/sdk@${AGENT_SDK_VERSION} bundleHash=${AGENT_SDK_BUNDLE_HASH}). Run 'kici compile' and commit the updated lock file.`);
214627
+ if (actualHash !== expectedContentHash) {
214628
+ const symlinkNote = kiciDir ? hashedSymlinkDriftNote(await collectSourceSymlinks(kiciDir)) : "";
214629
+ throw new Error(`Lock file is out of date: workflow source changed without regenerating kici.lock.json (expected contentHash ${expectedContentHash}, got ${actualHash}, agent baked @kici-dev/sdk@${AGENT_SDK_VERSION} bundleHash=${AGENT_SDK_BUNDLE_HASH}). Run 'kici compile' and commit the updated lock file.${symlinkNote}`);
214630
+ }
213771
214631
  }
213772
214632
  return {
213773
214633
  module: await import(pathToFileURL(filePath).href + `?t=${Date.now()}`),
@@ -213930,22 +214790,30 @@ function applyGlobalWorkflowEnv(repos) {
213930
214790
  * `.kici/` source tarball restoration for execution agents.
213931
214791
  *
213932
214792
  * Downloads a pre-built `.kici/` source tarball from the orchestrator's cache
213933
- * and extracts it into `workDir/` so the workflow entry point becomes
214793
+ * and installs it at `workDir/.kici` so the workflow entry point becomes
213934
214794
  * importable. Mirrors the shape of `dep-restore.ts` but without the streaming
213935
214795
  * optimization — source tarballs are tiny (kilobytes, not the hundreds of
213936
214796
  * megabytes a `node_modules/` tarball carries).
213937
214797
  *
213938
- * Note on integrity: `dispatch.sourceTarHash` is the workflow `contentHash`
213939
- * (computed over the raw source per `workflow-loader.ts::computeContentHash`),
213940
- * not the SHA-256 of the tarball bytes. The shared S3 cache key is derived
213941
- * from that same contentHash, so a signed GET URL from the orchestrator
213942
- * already establishes provenance for restored tarballs. Every
213943
- * `loadWorkflowSource` call site build, init, and dynamic eval — passes
213944
- * the dispatched `contentHash` (and `resolvedHashFiles` when present) so
213945
- * the lock-vs-source drift gate fires at each author-TS load site, not
213946
- * only the build phase. That closes the corner cases where init or eval
213947
- * runs without a preceding build (cache infrastructure unavailable, or a
213948
- * build job that failed but left dynamic dispatch in flight).
214798
+ * Two properties this path is responsible for, both of which it previously
214799
+ * lacked:
214800
+ *
214801
+ * **Verification.** `dispatch.sourceTarDigest` is the SHA-256 of the tarball's
214802
+ * own bytes, so the download is checked before anything is extracted — the same
214803
+ * contract `restoreDeps` has always had via `depsHash`. The older
214804
+ * `dispatch.sourceTarHash` field carries the workflow `contentHash` instead, so
214805
+ * it never could serve this purpose; it stays on the wire for older peers and
214806
+ * is deliberately not used as a verification input here. When no digest is
214807
+ * dispatched (an older orchestrator, or a source that did not come from the
214808
+ * content-addressed cache) the restore proceeds unverified rather than failing,
214809
+ * so a mixed-version rollout still runs.
214810
+ *
214811
+ * **Replacement, not overlay.** Extraction lands in a scratch directory and the
214812
+ * result REPLACES `workDir/.kici` wholesale, save for `node_modules/` — the one
214813
+ * directory the tarball deliberately omits, which the deps restore has already
214814
+ * written by the time this runs. Extracting over the existing tree left any file
214815
+ * the tarball no longer carries in place, so a helper the author deleted
214816
+ * survived every warm-cache run and kept being imported.
213949
214817
  */
213950
214818
  init_index_min();
213951
214819
  init_dist$2();
@@ -213961,7 +214829,14 @@ async function extractSourceTarball(data, targetDir) {
213961
214829
  })).on("finish", resolve).on("error", reject);
213962
214830
  });
213963
214831
  }
213964
- async function restoreSource(workDir, sourceTarUrl) {
214832
+ /**
214833
+ * Download, verify, and install the `.kici/` source tree.
214834
+ *
214835
+ * @param workDir - Root of the cloned repository; `.kici` is replaced under it
214836
+ * @param sourceTarUrl - `http://`, `https://`, or `file://` URL to the tarball
214837
+ * @param sourceTarDigest - Expected SHA-256 of the tarball bytes, when known
214838
+ */
214839
+ async function restoreSource(workDir, sourceTarUrl, sourceTarDigest) {
213965
214840
  sourceTarUrl = resolveOrchestratorUrl(sourceTarUrl);
213966
214841
  logger$1.info("Restoring .kici/ source from tarball", { sourceTarUrl });
213967
214842
  const startTime = Date.now();
@@ -213971,11 +214846,34 @@ async function restoreSource(workDir, sourceTarUrl) {
213971
214846
  data = await ro.readFile(localPath);
213972
214847
  } else if (sourceTarUrl.startsWith("http://") || sourceTarUrl.startsWith("https://")) data = await downloadUrl(sourceTarUrl);
213973
214848
  else throw new Error(`Unsupported source tarball URL scheme: ${sourceTarUrl}`);
213974
- await extractSourceTarball(data, workDir);
214849
+ if (sourceTarDigest) {
214850
+ const actual = createHash("sha256").update(data).digest("hex");
214851
+ if (actual !== sourceTarDigest) throw new Error(`Source tarball hash mismatch: expected ${sourceTarDigest}, got ${actual}. The restored source does not match what the orchestrator dispatched.`);
214852
+ }
214853
+ const kiciDir = path$1.join(workDir, ".kici");
214854
+ const scratch = path$1.join(workDir, `.kici.restore-${process.pid}-${Date.now()}`);
214855
+ try {
214856
+ await extractSourceTarball(data, scratch);
214857
+ const extracted = path$1.join(scratch, ".kici");
214858
+ const src = (await ro.stat(extracted).catch(() => null))?.isDirectory() ? extracted : scratch;
214859
+ const installedDeps = path$1.join(kiciDir, "node_modules");
214860
+ if (await ro.stat(installedDeps).catch(() => null)) await rename(installedDeps, path$1.join(src, "node_modules"));
214861
+ await rm(kiciDir, {
214862
+ recursive: true,
214863
+ force: true
214864
+ });
214865
+ await rename(src, kiciDir);
214866
+ } finally {
214867
+ await rm(scratch, {
214868
+ recursive: true,
214869
+ force: true
214870
+ }).catch(() => {});
214871
+ }
213975
214872
  const durationMs = Date.now() - startTime;
213976
214873
  logger$1.info(".kici/ source restored", {
213977
214874
  sizeKB: (data.length / 1024).toFixed(2),
213978
- durationMs
214875
+ durationMs,
214876
+ verified: sourceTarDigest !== void 0
213979
214877
  });
213980
214878
  }
213981
214879
  //#endregion
@@ -214133,7 +215031,7 @@ init_oidc_token_relay();
214133
215031
  init_dist$4();
214134
215032
  init_download();
214135
215033
  init_dep_restore();
214136
- const AGENT_VERSION = "0.6.1";
215034
+ const AGENT_VERSION = "0.7.0";
214137
215035
  process.on("uncaughtException", (err) => {
214138
215036
  process.stderr.write(`[workflow-runner] UNCAUGHT EXCEPTION: ${err.message}\n`);
214139
215037
  if (err.stack) process.stderr.write(`[workflow-runner] Stack: ${err.stack}\n`);
@@ -214205,7 +215103,12 @@ function installOutputCapture() {
214205
215103
  line
214206
215104
  });
214207
215105
  }
214208
- if (isForkMode) return origStdoutWrite(chunk, encodingOrCb, cb);
215106
+ if (isForkMode) {
215107
+ if (!captureIsActive() || process.env.KICI_RUNNER_DEBUG_STDIO === "true") return origStdoutWrite(chunk, encodingOrCb, cb);
215108
+ const forkCallback = typeof encodingOrCb === "function" ? encodingOrCb : cb;
215109
+ if (forkCallback) forkCallback();
215110
+ return true;
215111
+ }
214209
215112
  const callback = typeof encodingOrCb === "function" ? encodingOrCb : cb;
214210
215113
  if (callback) callback();
214211
215114
  return true;
@@ -214795,6 +215698,46 @@ function extractRepoIdentifier(repoUrl) {
214795
215698
  * mint failure the statement is frozen + reported for later minting (deferred);
214796
215699
  * the step still completes.
214797
215700
  */
215701
+ /**
215702
+ * The context a deferred attestation's frozen statement is built from.
215703
+ *
215704
+ * Prefers the orchestrator's `provenanceContext` — server truth, derived from
215705
+ * the same run row the mint reads, so the frozen statement is field-for-field
215706
+ * what a live mint would have produced and the orchestrator can cross-check it.
215707
+ *
215708
+ * Falls back to the agent's local view when an older orchestrator sent none.
215709
+ * That fallback disagrees with the claims by construction: `request.ref` is the
215710
+ * job's CHECKOUT ref, which for a pull request is the HEAD branch where the
215711
+ * claim is the BASE branch, and `request.workflowRef` is a global workflow's
215712
+ * CLONE ref where the claim is `<name>@<sha>`. So a statement built from it
215713
+ * fails the capture cross-check and the defer is dropped — a green job with no
215714
+ * attestation, rather than an unchecked statement the orchestrator signs.
215715
+ */
215716
+ function buildLocalContext(request) {
215717
+ const ctx = request.provenanceContext;
215718
+ if (ctx) return {
215719
+ repository: ctx.repository ?? "",
215720
+ ref: ctx.ref ?? "",
215721
+ sha: ctx.sha,
215722
+ workflowRef: ctx.workflowRef ?? "",
215723
+ runId: ctx.runId,
215724
+ jobId: ctx.jobId,
215725
+ orgId: ctx.orgId,
215726
+ sourceOrigin: ctx.sourceOrigin,
215727
+ ...ctx.provider ? { provider: ctx.provider } : {},
215728
+ issuer: ctx.issuer,
215729
+ orchestratorId: ctx.orchestratorId
215730
+ };
215731
+ return {
215732
+ repository: extractRepoIdentifier(request.repoUrl),
215733
+ ref: request.ref,
215734
+ sha: request.sha || null,
215735
+ workflowRef: request.workflowRef ?? request.workflowName,
215736
+ runId: request.runId,
215737
+ jobId: request.jobId,
215738
+ issuer: ""
215739
+ };
215740
+ }
214798
215741
  function buildAttestProvenanceFn(request, workDir, getIdToken) {
214799
215742
  return async (opts) => {
214800
215743
  const subject = provenanceSubjectIsPath(opts.subject) ? {
@@ -214810,15 +215753,7 @@ function buildAttestProvenanceFn(request, workDir, getIdToken) {
214810
215753
  "kici-agent": AGENT_VERSION,
214811
215754
  "kici-orchestrator": "unknown"
214812
215755
  },
214813
- localContext: {
214814
- repository: extractRepoIdentifier(request.repoUrl),
214815
- ref: request.ref,
214816
- sha: request.sha || null,
214817
- workflowRef: request.workflowRef ?? request.workflowName,
214818
- runId: request.runId,
214819
- jobId: request.jobId,
214820
- issuer: request.provenanceIssuer ?? ""
214821
- },
215756
+ localContext: buildLocalContext(request),
214822
215757
  reportDeferred: async (report) => {
214823
215758
  await relayProvenanceIpc({
214824
215759
  op: "defer",
@@ -215123,20 +216058,6 @@ function buildStepSecrets(request, masker, onMaskerSecretsAdded) {
215123
216058
  });
215124
216059
  }
215125
216060
  /**
215126
- * Create a LogMasker initialized with all secret values from the request.
215127
- *
215128
- * Collects values from both flat secrets and all namespaced context secrets,
215129
- * deduplicating before registration.
215130
- */
215131
- function createSecretMasker(request) {
215132
- const masker = new LogMasker();
215133
- const allSecrets = {};
215134
- if (request.secrets) Object.assign(allSecrets, request.secrets);
215135
- if (request.namespacedSecrets) for (const contextSecrets of Object.values(request.namespacedSecrets)) Object.assign(allSecrets, contextSecrets);
215136
- masker.registerSecrets(allSecrets);
215137
- return masker;
215138
- }
215139
- /**
215140
216061
  * Build a fresh zx `$` shell bound to the sandbox working directory and the
215141
216062
  * sanitized environment (process.env was set by the parent via env-sanitizer
215142
216063
  * before spawning this process). This is the single shell-construction code
@@ -215167,12 +216088,13 @@ function createSecretMasker(request) {
215167
216088
  * step$.log would only set it on the function object and NOT propagate to the
215168
216089
  * AsyncLocalStorage store that zx uses for ProcessPromise snapshots.
215169
216090
  */
215170
- function buildSandboxShell(cwd, stepIndex, maskedSendFn) {
216091
+ function buildSandboxShell(cwd, stepIndex, maskedSendFn, signal) {
215171
216092
  return $$1({
215172
216093
  cwd,
215173
216094
  env: process.env,
215174
216095
  verbose: true,
215175
216096
  quiet: false,
216097
+ ...signal ? { signal } : {},
215176
216098
  log: makeStreamingZxLog((line, stream) => maskedSendFn({
215177
216099
  type: "log.line",
215178
216100
  stepIndex,
@@ -215257,7 +216179,7 @@ function initialJobStatus(loopStatus) {
215257
216179
  * inside this process with full shell access.
215258
216180
  */
215259
216181
  function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedSendFn, outputsMap, refMap, operatorSecretKeys, secretOutputs, jobOutputsMap, secrets, masker, signal, jobTempScope) {
215260
- const step$ = buildSandboxShell(workDir, stepIndex, maskedSendFn);
216182
+ const step$ = buildSandboxShell(workDir, stepIndex, maskedSendFn, signal);
215261
216183
  const log = createIpcLogger(stepIndex, stepName, maskedSendFn);
215262
216184
  const rawPayload = rawPayloadFromEvent(request.event);
215263
216185
  const kici = buildKiciApi(async (method, params) => {
@@ -215512,7 +216434,8 @@ async function installDependenciesIfNeeded(workflowDir, request) {
215512
216434
  await installDeps(kiciDir, {
215513
216435
  npmRegistries: request.npmRegistries,
215514
216436
  installEnvSecrets: request.installEnvSecrets,
215515
- jobIdShort: request.jobIdShort
216437
+ jobIdShort: request.jobIdShort,
216438
+ ...request.allowInstallScripts ? { allowInstallScripts: true } : {}
215516
216439
  });
215517
216440
  trace("fallback install complete");
215518
216441
  }
@@ -215537,7 +216460,8 @@ async function installDependenciesIfNeeded(workflowDir, request) {
215537
216460
  await installDeps(kiciDir, {
215538
216461
  npmRegistries: request.npmRegistries,
215539
216462
  installEnvSecrets: request.installEnvSecrets,
215540
- jobIdShort: request.jobIdShort
216463
+ jobIdShort: request.jobIdShort,
216464
+ ...request.allowInstallScripts ? { allowInstallScripts: true } : {}
215541
216465
  });
215542
216466
  trace("installDeps() returned successfully");
215543
216467
  } catch (depErr) {
@@ -215567,7 +216491,7 @@ async function restoreSourceTarballIfRequested(workflowRoot, request) {
215567
216491
  stepIndex: -1,
215568
216492
  line: "[workflow-runner] Restoring .kici/ source from cached tarball"
215569
216493
  });
215570
- await restoreSource(workflowRoot, request.sourceTarUrl);
216494
+ await restoreSource(workflowRoot, request.sourceTarUrl, request.sourceTarDigest);
215571
216495
  trace("source tarball restored");
215572
216496
  }
215573
216497
  /**
@@ -216068,7 +216992,7 @@ function buildJobRuleCompletion(ruleResult, normalizedSteps) {
216068
216992
  * success-skip). Returns false when the caller should continue to step
216069
216993
  * execution.
216070
216994
  */
216071
- async function maybeSkipJobOnRules(job, request, normalizedSteps, repos) {
216995
+ async function maybeSkipJobOnRules(job, request, normalizedSteps, send, repos) {
216072
216996
  if (!job?.rules || job.rules.length === 0) return false;
216073
216997
  const ev = request.event ?? {};
216074
216998
  const ruleCtx = createRuleContext({
@@ -216087,7 +217011,7 @@ async function maybeSkipJobOnRules(job, request, normalizedSteps, repos) {
216087
217011
  if (!completion) return false;
216088
217012
  flushOutputCapture();
216089
217013
  capturePrepareActive = false;
216090
- sendMessage(completion);
217014
+ send(completion);
216091
217015
  process.exit(0);
216092
217016
  }
216093
217017
  /**
@@ -216313,11 +217237,7 @@ async function main() {
216313
217237
  const sourceDir = isGlobal ? join(workDir, "source") : workDir;
216314
217238
  const masker = createSecretMasker(request);
216315
217239
  const maskedSend = (msg) => {
216316
- if (msg.type === "log.line" && masker.hasSecrets()) sendMessage({
216317
- ...msg,
216318
- line: masker.mask(msg.line)
216319
- });
216320
- else sendMessage(msg);
217240
+ sendMessage(maskMessageText(msg, masker));
216321
217241
  };
216322
217242
  const jobDeadline = armJobDeadline(request.jobTimeoutMs, (reason, timeoutMs) => {
216323
217243
  jobTimedOut = true;
@@ -216352,7 +217272,7 @@ async function main() {
216352
217272
  const jobHasRules = (job?.rules?.length ?? 0) > 0;
216353
217273
  const anyStepHasRules = normalizedSteps.some((s) => (s.rules?.length ?? 0) > 0);
216354
217274
  await resolveChangedFilesForRules(request, sourceDir, jobHasRules || anyStepHasRules);
216355
- await maybeSkipJobOnRules(job, request, normalizedSteps, globalRepoInfo);
217275
+ await maybeSkipJobOnRules(job, request, normalizedSteps, maskedSend, globalRepoInfo);
216356
217276
  if (aborted) abortAndExit("aborted after rules");
216357
217277
  }
216358
217278
  const jobHooks = collectJobHooks(job);
@@ -216461,7 +217381,8 @@ async function main() {
216461
217381
  jobTimedOut,
216462
217382
  jobTimeoutMs: request.jobTimeoutMs,
216463
217383
  cancelFailureReason,
216464
- driftDroppedJobs
217384
+ driftDroppedJobs,
217385
+ send: maskedSend
216465
217386
  });
216466
217387
  process.exit(finalStatus === ExecutionJobStatus.enum.success ? 0 : 1);
216467
217388
  }
@@ -216474,7 +217395,7 @@ async function main() {
216474
217395
  function emitJobComplete(args) {
216475
217396
  const aggregatedOutputs = {};
216476
217397
  for (const [stepName, outputs] of args.outputsMap) aggregatedOutputs[stepName] = outputs;
216477
- sendMessage({
217398
+ args.send({
216478
217399
  type: "job.complete",
216479
217400
  status: args.finalStatus,
216480
217401
  stepResults: args.loopResult.stepResults,