@kici-dev/agent 0.6.0 → 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 +1117 -185
  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
@@ -1,4 +1,4 @@
1
- import type { CheckMode, CheckStepOutcome, LogStream } from '@kici-dev/engine';
1
+ import type { CheckMode, CheckStepOutcome, LogStream, ProvenanceContext } from '@kici-dev/engine';
2
2
  import type { SandboxStepResult } from './types.js';
3
3
  /**
4
4
  * Structured clone auth. Wire-compatible with `gitAuthSchema` on the
@@ -500,6 +500,11 @@ export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResp
500
500
  * The runner uses this to clone, install deps, compile, and execute steps.
501
501
  */
502
502
  export interface JobExecutionRequest {
503
+ /**
504
+ * Operator opt-out from `--ignore-scripts` on the `.kici/` dependency install
505
+ * (`KICI_ALLOW_INSTALL_SCRIPTS` on the agent). Absent means scripts stay off.
506
+ */
507
+ allowInstallScripts?: boolean;
503
508
  /**
504
509
  * Run UUID. Threaded into the step context so the OIDC token relay can name
505
510
  * the job/run a request is bound to. Correlation only — the orchestrator
@@ -550,6 +555,7 @@ export interface JobExecutionRequest {
550
555
  credentialHelperPath?: string;
551
556
  /** SHA-256 hash of the source tarball bytes for integrity verification. */
552
557
  sourceTarHash?: string;
558
+ sourceTarDigest?: string;
553
559
  /** URL to pre-built dependency tarball (skip install if present). */
554
560
  depsUrl?: string;
555
561
  /** SHA-256 hash of the dependency tarball for integrity verification. */
@@ -627,14 +633,16 @@ export interface JobExecutionRequest {
627
633
  /** Git provider that originated the triggering event (e.g. 'github', 'forgejo'). */
628
634
  provider?: string;
629
635
  /**
630
- * Platform provenance issuer, threaded from the orchestrator for a deferred
631
- * attestation's frozen `builder.id`. Best-effort: absent when the orchestrator
632
- * has no issuer wired (e.g. never authenticated to the Platform), in which
633
- * case the frozen statement records an unknown issuer honestly. Not
634
- * verification load-bearing a deferred bundle's later token binds to the
635
- * frozen statement by hash, and the authoritative org id lives in that token.
636
+ * The orchestrator's own view of this build, from the job dispatch.
637
+ *
638
+ * Verification load-bearing. A deferred attestation's frozen statement is
639
+ * built from this so it is field-for-field what a live mint would have
640
+ * produced, and the orchestrator therefore cross-checks it against its own
641
+ * run row before storing it. Absent from an older orchestrator's dispatch, in
642
+ * which case the agent falls back to its local guess — a statement the
643
+ * capture check rejects, so the defer is dropped rather than stored unchecked.
636
644
  */
637
- provenanceIssuer?: string;
645
+ provenanceContext?: ProvenanceContext;
638
646
  /** Whether to checkout the repo (default: true). */
639
647
  checkout?: boolean;
640
648
  /** Whether this job is part of a developer-initiated run triggered by `kici run`. */
@@ -739,5 +747,71 @@ export interface JobExecutionRequest {
739
747
  declaredNeeds?: readonly unknown[];
740
748
  };
741
749
  }
750
+ /** Which evaluation the child should perform. */
751
+ export type EvalRequestKind = 'init' | 'dynamic-job' | 'build-verify' | 'global-eval';
752
+ /**
753
+ * One evaluation request. `config` is the dispatch's own `jobConfig` and
754
+ * `dispatch` the fields the evaluation reads, both passed through verbatim so
755
+ * the child builds the same contexts the agent used to build in-process.
756
+ *
757
+ * Carries no `secrets` and no `namespacedSecrets`: no evaluation surface
758
+ * consumes them, and adding them would put job secrets in the one process that
759
+ * runs customer module code.
760
+ */
761
+ export interface EvalRequest {
762
+ kind: EvalRequestKind;
763
+ /** Absolute path of the already-materialized working directory. */
764
+ workDir: string;
765
+ /** The dispatch's `jobConfig`, verbatim. */
766
+ config: Record<string, unknown>;
767
+ /** The dispatch fields an evaluation reads (repo URL, ref, sha, auth, …). */
768
+ dispatch: Record<string, unknown>;
769
+ }
770
+ /** Instruct the eval child to run one evaluation. */
771
+ interface EvalExecuteMessage {
772
+ type: 'eval';
773
+ request: EvalRequest;
774
+ }
775
+ /** Response to an `eval.api.request` the child relayed. */
776
+ interface EvalApiResponseMessage {
777
+ type: 'eval.api.response';
778
+ id: string;
779
+ result?: unknown;
780
+ error?: string;
781
+ }
782
+ export type AgentToEvalMessage = EvalExecuteMessage | EvalApiResponseMessage;
783
+ /** The eval child is initialized and ready to receive a request. */
784
+ interface EvalReadyMessage {
785
+ type: 'ready';
786
+ }
787
+ /** One captured console / subprocess line, streamed onto the synthetic step-0 log. */
788
+ interface EvalLogLineMessage {
789
+ type: 'log.line';
790
+ line: string;
791
+ stream?: LogStream;
792
+ }
793
+ /**
794
+ * Relay for `ctx.kici`. The one privileged message on this union, and it is
795
+ * privilege preservation rather than expansion: a `DynamicJobFn` and a
796
+ * global-eval generator are already handed a `kici` API built on this exact
797
+ * transport, so withholding it would break a documented SDK surface.
798
+ */
799
+ interface EvalApiRequestMessage {
800
+ type: 'eval.api.request';
801
+ id: string;
802
+ method: string;
803
+ params: Record<string, unknown>;
804
+ }
805
+ /** The evaluation finished. `result` is the kind's own serialized outcome. */
806
+ interface EvalResultMessage {
807
+ type: 'eval.result';
808
+ result: unknown;
809
+ }
810
+ /** The evaluation threw. */
811
+ interface EvalErrorMessage {
812
+ type: 'eval.error';
813
+ error: string;
814
+ }
815
+ export type EvalToAgentMessage = EvalReadyMessage | EvalLogLineMessage | EvalApiRequestMessage | EvalResultMessage | EvalErrorMessage;
742
816
  export {};
743
817
  //# sourceMappingURL=ipc-protocol.d.ts.map
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Egress filtering for nested job containers.
3
+ *
4
+ * A job container joins a dedicated `kici-jobs` bridge network, and the agent
5
+ * installs the same RFC1918 + cloud-metadata nftables drops the orchestrator's
6
+ * scaler installs for agent containers. Without them a job container keeps the
7
+ * runtime's default bridge, which reaches `169.254.169.254` and the host's whole
8
+ * private network — a step can take the instance role or sweep the customer's
9
+ * internal services.
10
+ *
11
+ * The rule builder is `@kici-dev/shared`'s, so the agent and the scaler apply
12
+ * one implementation rather than two copies that drift.
13
+ *
14
+ * `network: 'host'` (from `KICI_SANDBOX_NETWORK=host` or a per-job
15
+ * `sandbox: { network: 'host' }` grant) bypasses this by construction: asking
16
+ * for the host namespace is asking for the host's network.
17
+ */
18
+ import type Docker from 'dockerode';
19
+ import { JOB_NETWORK_SUBNET, type NetworkPolicy } from '@kici-dev/shared/net';
20
+ /** Bridge network every filtered job container joins. */
21
+ export declare const JOB_NETWORK_NAME = "kici-jobs";
22
+ /**
23
+ * Subnet for {@link JOB_NETWORK_NAME}. Distinct from the scaler's agent network.
24
+ *
25
+ * Defined in `@kici-dev/shared/net` and re-exported here: the Firecracker host
26
+ * provisioner must recognise this subnet's rules to leave them alone, and it
27
+ * cannot import from the agent package.
28
+ */
29
+ export { JOB_NETWORK_SUBNET };
30
+ /** Gateway for {@link JOB_NETWORK_NAME} — the one destination the rules allow. */
31
+ export declare const JOB_NETWORK_GATEWAY = "172.31.0.1";
32
+ /**
33
+ * Resolve what a job container may reach on the host.
34
+ *
35
+ * The default is DNS on the bridge gateway and nothing else. A job container
36
+ * talks to the agent through `docker exec` rather than over the network, and it
37
+ * holds none of the agent's credentials, so name resolution is the one host
38
+ * service it genuinely needs. That carve-out is not cosmetic: rootful podman
39
+ * runs its resolver on the gateway, a host address, so a container with no such
40
+ * rule cannot resolve any name at all. Docker answers inside the container's
41
+ * own netns and is unaffected either way.
42
+ *
43
+ * The set is derived HERE, from the agent's own configuration, and takes no
44
+ * argument that could widen it. A `NetworkPolicy` reaching
45
+ * {@link applyJobEgressRules} governs the FORWARD hook only — what the job
46
+ * reaches through the host — so its `hostAccess` is deliberately not consulted:
47
+ * a job that could name its own host reachability is the escape hatch this
48
+ * boundary exists to close, and a signature that cannot carry one is what makes
49
+ * that structural rather than a convention.
50
+ */
51
+ export declare function resolveJobHostAccess(gateway?: string): string[];
52
+ /**
53
+ * Ensure the `kici-jobs` network exists, the nftables table is ready, and the
54
+ * subnet's default egress drops are installed.
55
+ *
56
+ * The drops land here — before any job container is created — and are keyed on
57
+ * the subnet rather than on a container address, so a customer image's own
58
+ * `ENTRYPOINT` never runs unfiltered.
59
+ *
60
+ * Returns whether egress filtering is actually in force. A host without `nft`
61
+ * or without `NET_ADMIN` — a rootless agent, most commonly — warns loudly and
62
+ * degrades to the network with no rules rather than refusing to run any job:
63
+ * `kici-admin agent install` is a compat-protected surface, and hard-requiring
64
+ * NET_ADMIN would break every existing rootless install with no additive path.
65
+ * The same trade-off the scaler already made for agent containers.
66
+ */
67
+ export declare function ensureJobNetwork(docker: Docker): Promise<boolean>;
68
+ /**
69
+ * Apply the per-container egress rules once the container has an IP.
70
+ *
71
+ * This is the per-job layer, not the control: the subnet drop set
72
+ * {@link ensureJobNetwork} installs is already in the chain and already covers
73
+ * this container. What a per-container rule set adds is a place to key a future
74
+ * per-job policy — an allowlist or `denyAll` — on one address rather than on
75
+ * the whole bridge.
76
+ *
77
+ * Returns the container IP the rules are keyed on, so teardown can remove
78
+ * exactly those rules; `undefined` when no IP could be read, which is logged
79
+ * rather than thrown — a job whose rules could not be keyed still runs behind
80
+ * the subnet drops, and the operator sees why.
81
+ */
82
+ export declare function applyJobEgressRules(docker: Docker, containerId: string, policy: NetworkPolicy | undefined): Promise<string | undefined>;
83
+ /**
84
+ * Remove the per-container rules at teardown.
85
+ *
86
+ * Never throws: a failed cleanup must not turn a finished job into a failed
87
+ * one. A leaked rule set is keyed on an IP the network will reuse, so it is
88
+ * reported at `warn` for the operator rather than swallowed.
89
+ */
90
+ export declare function removeJobEgressRules(containerIp: string | undefined): Promise<void>;
91
+ //# sourceMappingURL=job-network.d.ts.map
@@ -7,6 +7,7 @@
7
7
  * Performance: Builds a single combined regex from all secret values, so each
8
8
  * log line is scanned in a single pass (not O(secrets * lines)).
9
9
  */
10
+ import type { JobExecutionRequest, RunnerToAgentMessage } from './ipc-protocol.js';
10
11
  /**
11
12
  * Masks secret values in log lines.
12
13
  *
@@ -29,6 +30,21 @@ export declare class LogMasker {
29
30
  * Authorization: Basic headers, base64-encoded config values).
30
31
  * Values are sorted by length descending so longer values are matched first
31
32
  * (prevents partial masking when one secret is a substring of another).
33
+ *
34
+ * Multi-line values additionally register each of their individual lines.
35
+ * Log output is split into lines before it reaches the masker, so a value
36
+ * containing a newline can never match as a whole — a PEM private key or a
37
+ * kubeconfig would otherwise stream in clear text. Two consequences of the
38
+ * per-line registration are deliberate:
39
+ *
40
+ * - `MIN_MASK_LENGTH` is 3, so short structural lines of a structured secret
41
+ * are registered too. A `---` YAML separator or a bare `{` from a
42
+ * service-account JSON is masked wherever it appears in that job's logs.
43
+ * - PEM header and footer lines (`-----BEGIN OPENSSH PRIVATE KEY-----`) are
44
+ * not secret on their own and are masked as a side effect.
45
+ *
46
+ * Both are strictly safer than leaking the body, and no heuristic separates a
47
+ * structural line from a body line without risking the reverse mistake.
32
48
  */
33
49
  registerSecrets(secrets: Record<string, string>): void;
34
50
  /**
@@ -42,4 +58,26 @@ export declare class LogMasker {
42
58
  */
43
59
  hasSecrets(): boolean;
44
60
  }
61
+ /**
62
+ * Create a LogMasker initialized with all secret values from the request.
63
+ *
64
+ * Collects values from both flat secrets and all namespaced context secrets,
65
+ * deduplicating before registration.
66
+ *
67
+ * Both the runner child and the agent-side fork runner build a masker from the
68
+ * same request, so the crash tail the agent assembles from the child's stderr is
69
+ * masked with the same value set the child used for its own log lines.
70
+ */
71
+ export declare function createSecretMasker(request: JobExecutionRequest): LogMasker;
72
+ /**
73
+ * Mask every operator-visible text field of an outbound runner message.
74
+ *
75
+ * Each message type carrying free text is named here, so a new text-bearing
76
+ * message type is a visible omission rather than a silent leak. `step.complete`
77
+ * error text and the `job.complete` failure reason are persisted on the step and
78
+ * run rows the dashboard renders, so they need the same masking `log.line` gets.
79
+ *
80
+ * Returns the message unchanged when no secrets are registered.
81
+ */
82
+ export declare function maskMessageText(msg: RunnerToAgentMessage, masker: LogMasker): RunnerToAgentMessage;
45
83
  //# sourceMappingURL=log-masker.d.ts.map
@@ -155,6 +155,12 @@ export interface JobExecutionOptions {
155
155
  * see the dual-mode container work.
156
156
  */
157
157
  credentialHelperPath?: string;
158
+ /**
159
+ * Operator opt-out from `--ignore-scripts` on the `.kici/` dependency install
160
+ * (`KICI_ALLOW_INSTALL_SCRIPTS`). Sourced from agent config only, so a
161
+ * dispatch payload or a workflow cannot re-enable lifecycle scripts.
162
+ */
163
+ allowInstallScripts?: boolean;
158
164
  /**
159
165
  * Callback for relaying a provenance bundle upload request from the sandbox to
160
166
  * the orchestrator. The sandbox runner sends `provenance.request` IPC; the
@@ -74,7 +74,7 @@ export declare function buildStepNeedsContext(declaredNeeds: readonly unknown[]
74
74
  * step$.log would only set it on the function object and NOT propagate to the
75
75
  * AsyncLocalStorage store that zx uses for ProcessPromise snapshots.
76
76
  */
77
- export declare function buildSandboxShell(cwd: string, stepIndex: number, maskedSendFn: (msg: RunnerToAgentMessage) => void): typeof $;
77
+ export declare function buildSandboxShell(cwd: string, stepIndex: number, maskedSendFn: (msg: RunnerToAgentMessage) => void, signal?: AbortSignal): typeof $;
78
78
  /**
79
79
  * Resolve the on-the-wire event name for `ctx.emit`. Accepts either an ad-hoc
80
80
  * event-name string or a `defineEvent()` definition object (passed by the typed
@@ -6,10 +6,10 @@
6
6
  * into a deterministic gzip tarball. The tarball bytes are hashed for
7
7
  * integrity verification on the execution-job side.
8
8
  *
9
- * The artifact is content-addressed by the compiler's `contentHash` (hash of
10
- * the raw workflow entry + asset digest), not by the tarball bytes — so the
11
- * orchestrator's cache lookup keys unchanged while the stored bytes switch
12
- * from a Rolldown bundle to a raw-source tarball.
9
+ * The returned hash is the tarball's OWN digest. The orchestrator stores the
10
+ * object under it and the restoring agent verifies the downloaded bytes against
11
+ * it, the same contract the dependency tarball has always had; a separate
12
+ * pointer resolves the workflow `contentHash` to it.
13
13
  */
14
14
  export declare function packKiciSource(workDir: string): Promise<{
15
15
  tarball: Buffer;
@@ -2,22 +2,37 @@
2
2
  * `.kici/` source tarball restoration for execution agents.
3
3
  *
4
4
  * Downloads a pre-built `.kici/` source tarball from the orchestrator's cache
5
- * and extracts it into `workDir/` so the workflow entry point becomes
5
+ * and installs it at `workDir/.kici` so the workflow entry point becomes
6
6
  * importable. Mirrors the shape of `dep-restore.ts` but without the streaming
7
7
  * optimization — source tarballs are tiny (kilobytes, not the hundreds of
8
8
  * megabytes a `node_modules/` tarball carries).
9
9
  *
10
- * Note on integrity: `dispatch.sourceTarHash` is the workflow `contentHash`
11
- * (computed over the raw source per `workflow-loader.ts::computeContentHash`),
12
- * not the SHA-256 of the tarball bytes. The shared S3 cache key is derived
13
- * from that same contentHash, so a signed GET URL from the orchestrator
14
- * already establishes provenance for restored tarballs. Every
15
- * `loadWorkflowSource` call site build, init, and dynamic eval — passes
16
- * the dispatched `contentHash` (and `resolvedHashFiles` when present) so
17
- * the lock-vs-source drift gate fires at each author-TS load site, not
18
- * only the build phase. That closes the corner cases where init or eval
19
- * runs without a preceding build (cache infrastructure unavailable, or a
20
- * build job that failed but left dynamic dispatch in flight).
10
+ * Two properties this path is responsible for, both of which it previously
11
+ * lacked:
12
+ *
13
+ * **Verification.** `dispatch.sourceTarDigest` is the SHA-256 of the tarball's
14
+ * own bytes, so the download is checked before anything is extracted — the same
15
+ * contract `restoreDeps` has always had via `depsHash`. The older
16
+ * `dispatch.sourceTarHash` field carries the workflow `contentHash` instead, so
17
+ * it never could serve this purpose; it stays on the wire for older peers and
18
+ * is deliberately not used as a verification input here. When no digest is
19
+ * dispatched (an older orchestrator, or a source that did not come from the
20
+ * content-addressed cache) the restore proceeds unverified rather than failing,
21
+ * so a mixed-version rollout still runs.
22
+ *
23
+ * **Replacement, not overlay.** Extraction lands in a scratch directory and the
24
+ * result REPLACES `workDir/.kici` wholesale, save for `node_modules/` — the one
25
+ * directory the tarball deliberately omits, which the deps restore has already
26
+ * written by the time this runs. Extracting over the existing tree left any file
27
+ * the tarball no longer carries in place, so a helper the author deleted
28
+ * survived every warm-cache run and kept being imported.
29
+ */
30
+ /**
31
+ * Download, verify, and install the `.kici/` source tree.
32
+ *
33
+ * @param workDir - Root of the cloned repository; `.kici` is replaced under it
34
+ * @param sourceTarUrl - `http://`, `https://`, or `file://` URL to the tarball
35
+ * @param sourceTarDigest - Expected SHA-256 of the tarball bytes, when known
21
36
  */
22
- export declare function restoreSource(workDir: string, sourceTarUrl: string): Promise<void>;
37
+ export declare function restoreSource(workDir: string, sourceTarUrl: string, sourceTarDigest?: string): Promise<void>;
23
38
  //# sourceMappingURL=source-restore.d.ts.map
@@ -34,20 +34,21 @@ export interface SdkOutputSetters {
34
34
  * ESM modules by resolved URL, so importing that path yields the workflow's live
35
35
  * singleton, not a fresh copy.
36
36
  *
37
- * Falls back to the agent's bundled setters when resolution fails (mirrors
37
+ * Both specifiers resolve to the same module-global maps `internal.ts` and the
38
+ * root barrel re-export the same `outputs.js` bindings — so the fallback below
39
+ * changes which entry is imported, never which singleton is mutated.
40
+ *
41
+ * Falls back to the agent's bundled setters when neither resolves (mirrors
38
42
  * `resolveSdkSetters` in the compiler's test runner).
39
43
  */
40
44
  export declare function resolveWorkflowSdkSetters(workflowFilePath: string): Promise<SdkOutputSetters>;
41
45
  /**
42
- * Compile schema version must match `@kici-dev/compiler` lockfile/hasher.ts.
43
- * Mixed into the content hash so compilation-approach changes produce different
44
- * hashes. Bumped 3 4 when the artifact model switched from a Rolldown-bundled
45
- * `.compiled.mjs` to a raw-source tarball consumed by the oxc-transform ESM
46
- * loader hook. Bumped 4 → 5 when the hash input started normalizing line
47
- * endings (CRLF → LF) so a Windows agent's checked-out CRLF source matches a
48
- * Linux compiler's LF source.
46
+ * Compile schema version, re-exported from its single definition in
47
+ * `@kici-dev/core/kici-source-digest`. The compiler mixes the same constant
48
+ * into every `contentHash` it writes, so a second copy here is exactly how a
49
+ * producer and its verifier drift apart.
49
50
  */
50
- export declare const COMPILE_SCHEMA_VERSION = 5;
51
+ export { COMPILE_SCHEMA_VERSION } from '@kici-dev/core/kici-source-digest';
51
52
  export declare function ensureLoaderHookRegistered(): void;
52
53
  /**
53
54
  * Load a workflow module by dynamic-importing its source file.
@@ -57,10 +58,12 @@ export declare function ensureLoaderHookRegistered(): void;
57
58
  * `node_modules/` the same way any `tsx`-style runner would — so host-repo
58
59
  * helpers and `@kici-dev/sdk` Just Work.
59
60
  *
60
- * When `expectedContentHash` is provided, verifies the raw source matches
61
- * the hash in the lock file. Drift between source and lock file produces a
62
- * descriptive error that surfaces the baked agent SDK fingerprint (useful
63
- * when debugging "is the agent running a stale build?").
61
+ * When `expectedContentHash` is provided, verifies the extracted `.kici/` tree
62
+ * matches the hash in the lock file. It re-hashes the whole tree, not the entry
63
+ * file alone, so an edit to an imported helper is caught — that was the gap
64
+ * that let a warm cache restore a stale tarball and run the OLD helper green.
65
+ * Drift produces a descriptive error that surfaces the baked agent SDK
66
+ * fingerprint (useful when debugging "is the agent running a stale build?").
64
67
  */
65
68
  export declare function loadWorkflowSource(workDir: string, sourceFile: string, expectedContentHash?: string, resolvedHashFiles?: string[]): Promise<{
66
69
  module: Record<string, unknown>;
@@ -10,10 +10,12 @@
10
10
  *
11
11
  * `nodeLinker: node-modules` makes berry lay down a real `node_modules` tree
12
12
  * (no PnP `.pnp.cjs`), so the agent's packer / restore / sibling-walk /
13
- * workflow-loader work unchanged. `enableScripts: false` (when a private
14
- * registry is configured) keeps dependency lifecycle scripts from seeing the
15
- * synthesized token env vars — the same security model as npm/pnpm/classic
16
- * `--ignore-scripts`.
13
+ * workflow-loader work unchanged. `enableScripts: false` keeps dependency
14
+ * lifecycle scripts from running at all for every install, not only one
15
+ * against a private registry — the same security model as npm/pnpm/classic
16
+ * `--ignore-scripts`. An operator opts back in with
17
+ * `KICI_ALLOW_INSTALL_SCRIPTS=true`, which arrives here as
18
+ * `ignoreScripts: false`.
17
19
  *
18
20
  * Reuses the same `ApplyNpmRegistryConfigArgs` / `ApplyNpmRegistryConfigResult`
19
21
  * shapes as the npm overlay so `dep-installer` can pick either by flavor.