@kici-dev/engine 0.1.27 → 0.2.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 (148) hide show
  1. package/dist/approval/types.d.ts +8 -1
  2. package/dist/approval/types.js +8 -1
  3. package/dist/artifacts/name.d.ts +60 -0
  4. package/dist/artifacts/name.js +71 -0
  5. package/dist/audit/access-log-policy.d.ts +1 -1
  6. package/dist/audit/access-log-policy.js +8 -0
  7. package/dist/audit/activity.d.ts +12 -12
  8. package/dist/audit/retention-policy.js +16 -0
  9. package/dist/billing/plan-type.d.ts +53 -0
  10. package/dist/billing/plan-type.js +70 -0
  11. package/dist/bundler/rolldown-config.d.ts +3 -3
  12. package/dist/check-mode.d.ts +1 -1
  13. package/dist/context/concurrency-strategy.d.ts +34 -0
  14. package/dist/context/concurrency-strategy.js +35 -0
  15. package/dist/context/held-run-job-id.d.ts +45 -0
  16. package/dist/context/held-run-job-id.js +56 -0
  17. package/dist/context/held-run-status.d.ts +33 -0
  18. package/dist/context/held-run-status.js +37 -0
  19. package/dist/context/hold-expiry.d.ts +20 -0
  20. package/dist/context/hold-expiry.js +25 -0
  21. package/dist/context/hold-reason.d.ts +33 -0
  22. package/dist/context/hold-reason.js +23 -0
  23. package/dist/context/hold-type.d.ts +40 -0
  24. package/dist/context/hold-type.js +61 -0
  25. package/dist/context/index.d.ts +9 -1
  26. package/dist/context/index.js +9 -2
  27. package/dist/context/multi-context.d.ts +3 -3
  28. package/dist/context/scope-name.d.ts +29 -0
  29. package/dist/context/scope-name.js +49 -0
  30. package/dist/context/scope-resolver.d.ts +33 -0
  31. package/dist/context/scope-resolver.js +45 -15
  32. package/dist/context/types.d.ts +8 -5
  33. package/dist/dev-ops/operations.d.ts +8 -0
  34. package/dist/dev-ops/operations.js +36 -19
  35. package/dist/diagnostics/infra-alert.d.ts +71 -0
  36. package/dist/diagnostics/infra-alert.js +60 -0
  37. package/dist/env/environment-allowlist.d.ts +4 -4
  38. package/dist/fanout/materialize.d.ts +16 -0
  39. package/dist/fanout/materialize.js +32 -3
  40. package/dist/index.d.ts +15 -6
  41. package/dist/index.js +34 -19
  42. package/dist/inputs/descriptor.d.ts +9 -9
  43. package/dist/inventory.d.ts +4 -4
  44. package/dist/labels.d.ts +82 -2
  45. package/dist/labels.js +119 -1
  46. package/dist/matrix/expand.d.ts +49 -2
  47. package/dist/matrix/expand.js +102 -13
  48. package/dist/matrix/format.d.ts +6 -1
  49. package/dist/matrix/format.js +6 -1
  50. package/dist/mcp/held-run-resolve.d.ts +7 -10
  51. package/dist/mcp/held-run-resolve.js +33 -7
  52. package/dist/metrics/catalog-policy.js +4 -4
  53. package/dist/metrics/metric-catalog.generated.d.ts +954 -739
  54. package/dist/metrics/metric-catalog.generated.js +278 -20
  55. package/dist/metrics/metric-kind-compat.d.ts +26 -0
  56. package/dist/metrics/metric-kind-compat.js +33 -0
  57. package/dist/protocol/analytics-events.d.ts +6 -6
  58. package/dist/protocol/dashboard-api-errors.d.ts +1 -0
  59. package/dist/protocol/dashboard-api-errors.js +5 -1
  60. package/dist/protocol/dashboard-write-operations.d.ts +143 -86
  61. package/dist/protocol/dashboard-write-operations.js +91 -17
  62. package/dist/protocol/messages/access-log.d.ts +437 -392
  63. package/dist/protocol/messages/access-log.js +11 -2
  64. package/dist/protocol/messages/actor.d.ts +2 -2
  65. package/dist/protocol/messages/agent-dev-ops.d.ts +4 -2
  66. package/dist/protocol/messages/agent-dev-ops.js +2 -5
  67. package/dist/protocol/messages/agent-run-result.d.ts +72 -62
  68. package/dist/protocol/messages/auth.d.ts +35 -23
  69. package/dist/protocol/messages/browser.d.ts +81 -5
  70. package/dist/protocol/messages/browser.js +53 -2
  71. package/dist/protocol/messages/capabilities.d.ts +106 -31
  72. package/dist/protocol/messages/capabilities.js +94 -10
  73. package/dist/protocol/messages/common.d.ts +45 -2
  74. package/dist/protocol/messages/common.js +64 -3
  75. package/dist/protocol/messages/dashboard-sealed-write.d.ts +52 -0
  76. package/dist/protocol/messages/dashboard-sealed-write.js +57 -0
  77. package/dist/protocol/messages/dashboard.d.ts +1022 -498
  78. package/dist/protocol/messages/dashboard.js +198 -31
  79. package/dist/protocol/messages/deployment-identity.d.ts +37 -10
  80. package/dist/protocol/messages/deployment-identity.js +30 -3
  81. package/dist/protocol/messages/event-log.d.ts +5 -5
  82. package/dist/protocol/messages/execution-status.d.ts +189 -53
  83. package/dist/protocol/messages/execution-status.js +108 -40
  84. package/dist/protocol/messages/heartbeat-health.d.ts +83 -0
  85. package/dist/protocol/messages/heartbeat-health.js +87 -0
  86. package/dist/protocol/messages/log-stream.d.ts +18 -0
  87. package/dist/protocol/messages/log-stream.js +19 -0
  88. package/dist/protocol/messages/oidc-mint.d.ts +7 -0
  89. package/dist/protocol/messages/oidc-mint.js +7 -0
  90. package/dist/protocol/messages/orchestrator-agent.d.ts +264 -67
  91. package/dist/protocol/messages/orchestrator-agent.js +170 -5
  92. package/dist/protocol/messages/pat-kind.d.ts +1 -1
  93. package/dist/protocol/messages/peer.d.ts +122 -58
  94. package/dist/protocol/messages/peer.js +37 -2
  95. package/dist/protocol/messages/platform-orchestrator.d.ts +561 -217
  96. package/dist/protocol/messages/platform-orchestrator.js +131 -21
  97. package/dist/protocol/messages/run-events.d.ts +5 -5
  98. package/dist/protocol/messages/scaler-event.d.ts +3 -3
  99. package/dist/protocol/messages/source-registration.d.ts +56 -13
  100. package/dist/protocol/messages/source-registration.js +49 -6
  101. package/dist/protocol/source-origin.d.ts +1 -1
  102. package/dist/provenance/attestation-origin.d.ts +1 -1
  103. package/dist/provenance/schema.d.ts +11 -11
  104. package/dist/provenance/verify.js +2 -1
  105. package/dist/provider/changed-files-fetcher.d.ts +21 -2
  106. package/dist/provider/check-run-conclusion.d.ts +2 -2
  107. package/dist/provider/check-status-poster.d.ts +23 -0
  108. package/dist/provider/index.d.ts +2 -2
  109. package/dist/registration/registerable-trigger-type.d.ts +14 -13
  110. package/dist/registration/registerable-trigger-type.js +1 -0
  111. package/dist/sandbox/capabilities.d.ts +6 -0
  112. package/dist/sandbox/capabilities.js +57 -0
  113. package/dist/scaler/scaler-backend-type.d.ts +1 -1
  114. package/dist/status/presentation.d.ts +89 -0
  115. package/dist/status/presentation.js +163 -0
  116. package/dist/trigger/compiled-matchers.d.ts +13 -0
  117. package/dist/trigger/compiled-matchers.js +44 -0
  118. package/dist/trigger/event-buckets.d.ts +44 -0
  119. package/dist/trigger/event-buckets.js +93 -0
  120. package/dist/trigger/matcher.d.ts +7 -3
  121. package/dist/trigger/matcher.js +53 -15
  122. package/dist/trigger/schedule-key.d.ts +14 -0
  123. package/dist/trigger/schedule-key.js +21 -0
  124. package/dist/trigger/trigger-event-type.d.ts +1 -1
  125. package/dist/trigger/trigger-event-type.js +2 -0
  126. package/dist/trigger/types.d.ts +173 -43
  127. package/dist/trigger/types.js +82 -25
  128. package/dist/webhook/event-types.d.ts +56 -0
  129. package/dist/webhook/event-types.js +59 -0
  130. package/dist/ws/ws-like.d.ts +13 -0
  131. package/package.json +12 -4
  132. package/sbom.spdx.json +10 -10
  133. package/dist/environment/host-match.d.ts +0 -25
  134. package/dist/environment/index.d.ts +0 -6
  135. package/dist/environment/multi-env.d.ts +0 -30
  136. package/dist/environment/scope-resolver.d.ts +0 -46
  137. package/dist/environment/scope-template.d.ts +0 -18
  138. package/dist/environment/types.d.ts +0 -119
  139. package/dist/package-manager.d.ts +0 -20
  140. package/dist/protocol/messages/observe.d.ts +0 -76
  141. package/dist/protocol/messages/saas-orchestrator.d.ts +0 -2179
  142. package/dist/protocol/messages/test-run.d.ts +0 -59
  143. package/dist/state-machine/index.d.ts +0 -2
  144. package/dist/state-machine/index.js +0 -3
  145. package/dist/state-machine/machine.d.ts +0 -34
  146. package/dist/state-machine/machine.js +0 -103
  147. package/dist/state-machine/types.d.ts +0 -34
  148. package/dist/state-machine/types.js +0 -15
@@ -0,0 +1,56 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/context/held-run-job-id.ts
3
+ /**
4
+ * Run-wide sentinel values for `held_runs.job_id`.
5
+ *
6
+ * The column normally carries the held job's expanded name (a matrix child
7
+ * keeps its own `build (18)` form), which is what the dashboard approval queue
8
+ * renders and what `kici approve --job <name>` resolves. Some holds are not
9
+ * scoped to a single job and use a sentinel instead: the workflow install gate,
10
+ * and the PR-wide security holds the org trust policy raises.
11
+ *
12
+ * The orchestrator writes these values and the dashboard reads them back to
13
+ * render a human-meaningful row label, so they live here rather than as a
14
+ * literal on each side — a rename on the writer must not silently stop the
15
+ * reader from recognising the sentinel.
16
+ */
17
+ /** Sentinel `job_id` for the PR-wide workflow-modification security hold. */
18
+ const WORKFLOW_MODIFICATION_JOB_ID = "__workflow_modification__";
19
+ /**
20
+ * Sentinel `job_id` per PR-wide security-hold reason, written when the org
21
+ * trust policy holds a run.
22
+ *
23
+ * `workflow_modification` keeps its historical value so holds created before
24
+ * the policy was enforced still resolve by job id.
25
+ *
26
+ * Covers the org trust policy's own three arms ONLY. `context_trust` is
27
+ * deliberately absent: that hold comes from the per-context minimum-trust gate,
28
+ * which writes the real expanded job name (a matrix child keeps its `build (18)`
29
+ * form) so the operator can approve that job specifically. A sentinel for it
30
+ * would be a value nothing ever writes.
31
+ *
32
+ * Keyed by the orchestrator's `TrustPolicyHoldReason`; the key set is asserted
33
+ * against that type in the orchestrator's own test rather than importing it
34
+ * here (the engine must not depend on the orchestrator).
35
+ */
36
+ const SECURITY_HOLD_JOB_IDS = {
37
+ workflow_modification: WORKFLOW_MODIFICATION_JOB_ID,
38
+ fork_pr: "__fork_pr__",
39
+ unknown_contributor: "__unknown_contributor__"
40
+ };
41
+ /** Human-meaningful label per security-hold sentinel, rendered by the dashboard. */
42
+ const SECURITY_HOLD_JOB_LABELS = {
43
+ [SECURITY_HOLD_JOB_IDS.workflow_modification]: "workflow modification",
44
+ [SECURITY_HOLD_JOB_IDS.fork_pr]: "fork pull request",
45
+ [SECURITY_HOLD_JOB_IDS.unknown_contributor]: "unknown contributor"
46
+ };
47
+ /** Sentinel `job_id` prefix for the workflow install gate, suffixed by the workflow name. */
48
+ const INSTALL_JOB_ID_PREFIX = "__install__";
49
+ /** The install-gate sentinel `job_id` for a workflow. */
50
+ function installGateJobId(workflowName) {
51
+ return `${INSTALL_JOB_ID_PREFIX}${workflowName}`;
52
+ }
53
+ //#endregion
54
+ export { INSTALL_JOB_ID_PREFIX, SECURITY_HOLD_JOB_IDS, SECURITY_HOLD_JOB_LABELS, WORKFLOW_MODIFICATION_JOB_ID, installGateJobId };
55
+
56
+ //# sourceMappingURL=held-run-job-id.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Held-run status vocabulary — the single definition of what a held run's
3
+ * `status` can be.
4
+ *
5
+ * Lives beside `hold-type.ts` and `concurrency-strategy.ts` (the other shared
6
+ * context vocabularies) rather than in the protocol module, so both the domain
7
+ * types here and the wire schema in `protocol/messages/dashboard.ts` name the
8
+ * same enum instead of each carrying its own copy. A duplicated copy is exactly
9
+ * how `released` came to be persisted by the orchestrator but absent from the
10
+ * wire schema.
11
+ *
12
+ * This is the *known* vocabulary — the set the dashboard renders a labelled
13
+ * badge and a queue tab for, and the set a client may filter a list by. It is
14
+ * deliberately NOT the wire type of the response field: `held_runs.status` is a
15
+ * plain-text column owned by a customer-deployed orchestrator, so the response
16
+ * carries `z.string()`.
17
+ */
18
+ import { z } from 'zod';
19
+ /**
20
+ * Known held-run statuses.
21
+ *
22
+ * `released` is written by the orchestrator's `releaseDueWaitHolds()` when a
23
+ * workflow-scope wait timer elapses and the run proceeds.
24
+ */
25
+ export declare const HeldRunStatus: z.ZodEnum<{
26
+ approved: "approved";
27
+ expired: "expired";
28
+ pending: "pending";
29
+ rejected: "rejected";
30
+ released: "released";
31
+ }>;
32
+ export type HeldRunStatus = z.infer<typeof HeldRunStatus>;
33
+ //# sourceMappingURL=held-run-status.d.ts.map
@@ -0,0 +1,37 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { z } from "zod";
3
+ //#region src/context/held-run-status.ts
4
+ /**
5
+ * Held-run status vocabulary — the single definition of what a held run's
6
+ * `status` can be.
7
+ *
8
+ * Lives beside `hold-type.ts` and `concurrency-strategy.ts` (the other shared
9
+ * context vocabularies) rather than in the protocol module, so both the domain
10
+ * types here and the wire schema in `protocol/messages/dashboard.ts` name the
11
+ * same enum instead of each carrying its own copy. A duplicated copy is exactly
12
+ * how `released` came to be persisted by the orchestrator but absent from the
13
+ * wire schema.
14
+ *
15
+ * This is the *known* vocabulary — the set the dashboard renders a labelled
16
+ * badge and a queue tab for, and the set a client may filter a list by. It is
17
+ * deliberately NOT the wire type of the response field: `held_runs.status` is a
18
+ * plain-text column owned by a customer-deployed orchestrator, so the response
19
+ * carries `z.string()`.
20
+ */
21
+ /**
22
+ * Known held-run statuses.
23
+ *
24
+ * `released` is written by the orchestrator's `releaseDueWaitHolds()` when a
25
+ * workflow-scope wait timer elapses and the run proceeds.
26
+ */
27
+ const HeldRunStatus = z.enum([
28
+ "pending",
29
+ "approved",
30
+ "rejected",
31
+ "expired",
32
+ "released"
33
+ ]);
34
+ //#endregion
35
+ export { HeldRunStatus };
36
+
37
+ //# sourceMappingURL=held-run-status.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Hold window applied when a context carries no explicit hold expiry.
3
+ *
4
+ * `contexts.hold_expiry_seconds` is nullable, and a cleared column means "no
5
+ * explicit expiry" rather than "expire instantly" — without this fallback a
6
+ * cleared value would reach `evaluateReviewerGate` as `null`, whose
7
+ * `null * 1000` puts `holdUntil` at the current instant, so every reviewer hold
8
+ * would be created already overdue and swept to `expired` on the next stale
9
+ * scan, cancelling the job the hold was meant to gate.
10
+ *
11
+ * It lives here, beside `DEFAULT_CONCURRENCY_STRATEGY`, because the same three
12
+ * modules resolve both: the orchestrator's context store, its protection
13
+ * aggregate, and the dispatch path. The column carries no DDL default, so a
14
+ * context created without a hold expiry and one whose expiry was cleared both
15
+ * land on NULL and resolve through this single constant.
16
+ *
17
+ * Plain number, no `node:*` import — safe for the browser-facing engine barrel.
18
+ */
19
+ export declare const DEFAULT_HOLD_EXPIRY_SECONDS = 3600;
20
+ //# sourceMappingURL=hold-expiry.d.ts.map
@@ -0,0 +1,25 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/context/hold-expiry.ts
3
+ /**
4
+ * Hold window applied when a context carries no explicit hold expiry.
5
+ *
6
+ * `contexts.hold_expiry_seconds` is nullable, and a cleared column means "no
7
+ * explicit expiry" rather than "expire instantly" — without this fallback a
8
+ * cleared value would reach `evaluateReviewerGate` as `null`, whose
9
+ * `null * 1000` puts `holdUntil` at the current instant, so every reviewer hold
10
+ * would be created already overdue and swept to `expired` on the next stale
11
+ * scan, cancelling the job the hold was meant to gate.
12
+ *
13
+ * It lives here, beside `DEFAULT_CONCURRENCY_STRATEGY`, because the same three
14
+ * modules resolve both: the orchestrator's context store, its protection
15
+ * aggregate, and the dispatch path. The column carries no DDL default, so a
16
+ * context created without a hold expiry and one whose expiry was cleared both
17
+ * land on NULL and resolve through this single constant.
18
+ *
19
+ * Plain number, no `node:*` import — safe for the browser-facing engine barrel.
20
+ */
21
+ const DEFAULT_HOLD_EXPIRY_SECONDS = 3600;
22
+ //#endregion
23
+ export { DEFAULT_HOLD_EXPIRY_SECONDS };
24
+
25
+ //# sourceMappingURL=hold-expiry.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Protection-gate hold-reason templates — the free-text sentence the trust gate
3
+ * persists into `held_runs.reason` when a contributor is below a context's
4
+ * `minimumTrust`.
5
+ *
6
+ * Single source of truth for the two sentences: the orchestrator's trust gate
7
+ * emits them, the ci-security DB fixture seeds them, and the unit and E2E
8
+ * assertions compare against them. One copy means a copy edit moves every site
9
+ * at once (`.claude/rules/code-style.md` § "Enums over hardcoded strings").
10
+ *
11
+ * `held_runs.reason` is a free-text column, so these are functions rather than
12
+ * an enum: the sentence carries the context name, which is per-hold data.
13
+ *
14
+ * Carries no runtime import on purpose — this module reaches the browser
15
+ * through the engine barrel, which must pull in no Node built-ins
16
+ * (`.claude/rules/engine.md` § "Browser-safe barrel export"). The one import
17
+ * below is type-only and is erased at compile time.
18
+ */
19
+ import type { TrustTier } from './types.js';
20
+ /**
21
+ * Reason a run is held because the context requires `minimumTrust: 'known'`
22
+ * and the contributor's tier is `unknown`.
23
+ */
24
+ export declare function unknownContributorHoldReason(contextName: string): string;
25
+ /**
26
+ * Reason a run is held because the context requires `minimumTrust: 'trusted'`
27
+ * and the contributor's tier is below that. `trustTier` is the contributor's
28
+ * actual tier, which the gate has already narrowed to a tier below `trusted` —
29
+ * so the type excludes `'trusted'`, whose sentence would read as a
30
+ * contradiction.
31
+ */
32
+ export declare function trustedContributorHoldReason(contextName: string, trustTier: Exclude<TrustTier, 'trusted'>): string;
33
+ //# sourceMappingURL=hold-reason.d.ts.map
@@ -0,0 +1,23 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/context/hold-reason.ts
3
+ /**
4
+ * Reason a run is held because the context requires `minimumTrust: 'known'`
5
+ * and the contributor's tier is `unknown`.
6
+ */
7
+ function unknownContributorHoldReason(contextName) {
8
+ return `Context '${contextName}' requires known contributors (contributor is unknown)`;
9
+ }
10
+ /**
11
+ * Reason a run is held because the context requires `minimumTrust: 'trusted'`
12
+ * and the contributor's tier is below that. `trustTier` is the contributor's
13
+ * actual tier, which the gate has already narrowed to a tier below `trusted` —
14
+ * so the type excludes `'trusted'`, whose sentence would read as a
15
+ * contradiction.
16
+ */
17
+ function trustedContributorHoldReason(contextName, trustTier) {
18
+ return `Context '${contextName}' requires trusted contributors (contributor is ${trustTier})`;
19
+ }
20
+ //#endregion
21
+ export { trustedContributorHoldReason, unknownContributorHoldReason };
22
+
23
+ //# sourceMappingURL=hold-reason.js.map
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Protection-gate hold-type vocabulary — the reason a run is paused by a
4
+ * context protection gate. Single source of truth for the four gate hold
5
+ * types shared across the engine domain types, the orchestrator gates, and the
6
+ * dashboard held-run UI.
7
+ *
8
+ * The orchestrator persists these verbatim into `held_runs.hold_type`, so the
9
+ * column, the wire and the dashboard all speak one vocabulary. The column and
10
+ * the wire field it rides on stay typed as `string` (see the held-runs list
11
+ * response schema) so an older/newer orchestrator's hold type never rejects the
12
+ * relayed message.
13
+ */
14
+ export declare const HoldType: z.ZodEnum<{
15
+ concurrency: "concurrency";
16
+ reviewer: "reviewer";
17
+ security: "security";
18
+ timer: "timer";
19
+ }>;
20
+ export type HoldType = z.infer<typeof HoldType>;
21
+ /**
22
+ * Map a persisted hold type onto the gate vocabulary.
23
+ *
24
+ * An unrecognised value passes through unchanged rather than being coerced to a
25
+ * default: the wire field is `z.string()` precisely so a newer orchestrator's
26
+ * hold type survives an older reader, and the dashboard's gray fallback badge
27
+ * is the correct rendering for a genuinely unknown type.
28
+ */
29
+ export declare function normalizePersistedHoldType(raw: string): string;
30
+ /**
31
+ * Every spelling `held_runs.hold_type` may hold for one gate hold type — the
32
+ * current one first, then any legacy alias that normalizes onto it.
33
+ *
34
+ * For queries that must match a hold type in SQL, where the row-by-row
35
+ * `normalizePersistedHoldType` cannot run. Feeding this into an `IN (…)` filter
36
+ * keeps a sweep matching rows an un-upgraded orchestrator wrote, and keeps the
37
+ * legacy spellings out of call sites as bare literals.
38
+ */
39
+ export declare function persistedHoldTypeSpellings(type: HoldType): string[];
40
+ //# sourceMappingURL=hold-type.d.ts.map
@@ -0,0 +1,61 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ import { z } from "zod";
3
+ //#region src/context/hold-type.ts
4
+ /**
5
+ * Protection-gate hold-type vocabulary — the reason a run is paused by a
6
+ * context protection gate. Single source of truth for the four gate hold
7
+ * types shared across the engine domain types, the orchestrator gates, and the
8
+ * dashboard held-run UI.
9
+ *
10
+ * The orchestrator persists these verbatim into `held_runs.hold_type`, so the
11
+ * column, the wire and the dashboard all speak one vocabulary. The column and
12
+ * the wire field it rides on stay typed as `string` (see the held-runs list
13
+ * response schema) so an older/newer orchestrator's hold type never rejects the
14
+ * relayed message.
15
+ */
16
+ const HoldType = z.enum([
17
+ "reviewer",
18
+ "timer",
19
+ "concurrency",
20
+ "security"
21
+ ]);
22
+ /**
23
+ * Legacy persisted `held_runs.hold_type` spellings → the gate vocabulary.
24
+ *
25
+ * A row written by an un-upgraded orchestrator can carry `approval` for a
26
+ * reviewer hold or `wait_timer` for a workflow-install wait hold. Writers emit
27
+ * the gate vocabulary and a migration backfills the column, so this map exists
28
+ * for those older rows — it is what lets the two sides deploy independently
29
+ * instead of in lockstep.
30
+ */
31
+ const LEGACY_HOLD_TYPES = Object.freeze({
32
+ approval: HoldType.enum.reviewer,
33
+ wait_timer: HoldType.enum.timer
34
+ });
35
+ /**
36
+ * Map a persisted hold type onto the gate vocabulary.
37
+ *
38
+ * An unrecognised value passes through unchanged rather than being coerced to a
39
+ * default: the wire field is `z.string()` precisely so a newer orchestrator's
40
+ * hold type survives an older reader, and the dashboard's gray fallback badge
41
+ * is the correct rendering for a genuinely unknown type.
42
+ */
43
+ function normalizePersistedHoldType(raw) {
44
+ return Object.hasOwn(LEGACY_HOLD_TYPES, raw) ? LEGACY_HOLD_TYPES[raw] : raw;
45
+ }
46
+ /**
47
+ * Every spelling `held_runs.hold_type` may hold for one gate hold type — the
48
+ * current one first, then any legacy alias that normalizes onto it.
49
+ *
50
+ * For queries that must match a hold type in SQL, where the row-by-row
51
+ * `normalizePersistedHoldType` cannot run. Feeding this into an `IN (…)` filter
52
+ * keeps a sweep matching rows an un-upgraded orchestrator wrote, and keeps the
53
+ * legacy spellings out of call sites as bare literals.
54
+ */
55
+ function persistedHoldTypeSpellings(type) {
56
+ return [type, ...Object.keys(LEGACY_HOLD_TYPES).filter((spelling) => LEGACY_HOLD_TYPES[spelling] === type)];
57
+ }
58
+ //#endregion
59
+ export { HoldType, normalizePersistedHoldType, persistedHoldTypeSpellings };
60
+
61
+ //# sourceMappingURL=hold-type.js.map
@@ -1,6 +1,14 @@
1
1
  export type { Context, ContextBinding, ContextVariable, ContextSourceOverride, ScopedSecret, HeldRun, ProtectionGateResult, TrustTier, } from './types.js';
2
2
  export { TrustTierSchema } from './types.js';
3
- export { resolveSecretsForContext, matchScopePattern, stripScopePrefix } from './scope-resolver.js';
3
+ export { HoldType, normalizePersistedHoldType, persistedHoldTypeSpellings } from './hold-type.js';
4
+ export { trustedContributorHoldReason, unknownContributorHoldReason } from './hold-reason.js';
5
+ export { HeldRunStatus } from './held-run-status.js';
6
+ export { WORKFLOW_MODIFICATION_JOB_ID, SECURITY_HOLD_JOB_IDS, SECURITY_HOLD_JOB_LABELS, INSTALL_JOB_ID_PREFIX, installGateJobId, } from './held-run-job-id.js';
7
+ export { ConcurrencyStrategy, DEFAULT_CONCURRENCY_STRATEGY } from './concurrency-strategy.js';
8
+ export { DEFAULT_HOLD_EXPIRY_SECONDS } from './hold-expiry.js';
9
+ export { resolveSecretsForContext, resolveSecretsWithProvenance, matchScopePattern, stripScopePrefix, } from './scope-resolver.js';
10
+ export type { ResolvedSecretCandidate } from './scope-resolver.js';
4
11
  export type { HostFacts } from './host-match.js';
5
12
  export { mergeOrderedMaps, ContextGateRejectReason } from './multi-context.js';
13
+ export { validateScopeName, assertValidScopeName, ScopeNameError, SCOPE_SEGMENT_PATTERN, SCOPE_NAME_MAX_LENGTH, } from './scope-name.js';
6
14
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,12 @@
1
1
  import "../rolldown-runtime-ClRpJifh.js";
2
+ import { HeldRunStatus } from "./held-run-status.js";
2
3
  import { TrustTierSchema } from "./types.js";
3
- import { matchScopePattern, resolveSecretsForContext, stripScopePrefix } from "./scope-resolver.js";
4
+ import { ConcurrencyStrategy, DEFAULT_CONCURRENCY_STRATEGY } from "./concurrency-strategy.js";
5
+ import { HoldType, normalizePersistedHoldType, persistedHoldTypeSpellings } from "./hold-type.js";
6
+ import { trustedContributorHoldReason, unknownContributorHoldReason } from "./hold-reason.js";
7
+ import { INSTALL_JOB_ID_PREFIX, SECURITY_HOLD_JOB_IDS, SECURITY_HOLD_JOB_LABELS, WORKFLOW_MODIFICATION_JOB_ID, installGateJobId } from "./held-run-job-id.js";
8
+ import { DEFAULT_HOLD_EXPIRY_SECONDS } from "./hold-expiry.js";
9
+ import { matchScopePattern, resolveSecretsForContext, resolveSecretsWithProvenance, stripScopePrefix } from "./scope-resolver.js";
4
10
  import { ContextGateRejectReason, mergeOrderedMaps } from "./multi-context.js";
5
- export { ContextGateRejectReason, TrustTierSchema, matchScopePattern, mergeOrderedMaps, resolveSecretsForContext, stripScopePrefix };
11
+ import { SCOPE_NAME_MAX_LENGTH, SCOPE_SEGMENT_PATTERN, ScopeNameError, assertValidScopeName, validateScopeName } from "./scope-name.js";
12
+ export { ConcurrencyStrategy, ContextGateRejectReason, DEFAULT_CONCURRENCY_STRATEGY, DEFAULT_HOLD_EXPIRY_SECONDS, HeldRunStatus, HoldType, INSTALL_JOB_ID_PREFIX, SCOPE_NAME_MAX_LENGTH, SCOPE_SEGMENT_PATTERN, SECURITY_HOLD_JOB_IDS, SECURITY_HOLD_JOB_LABELS, ScopeNameError, TrustTierSchema, WORKFLOW_MODIFICATION_JOB_ID, assertValidScopeName, installGateJobId, matchScopePattern, mergeOrderedMaps, normalizePersistedHoldType, persistedHoldTypeSpellings, resolveSecretsForContext, resolveSecretsWithProvenance, stripScopePrefix, trustedContributorHoldReason, unknownContributorHoldReason, validateScopeName };
@@ -14,11 +14,11 @@ import { z } from 'zod';
14
14
  */
15
15
  export declare const ContextGateRejectReason: z.ZodEnum<{
16
16
  branch_restricted: "branch_restricted";
17
- trigger_filtered: "trigger_filtered";
18
- repo_unmatched: "repo_unmatched";
19
- trust_too_low: "trust_too_low";
20
17
  context_disabled: "context_disabled";
21
18
  context_not_found: "context_not_found";
19
+ repo_unmatched: "repo_unmatched";
20
+ trigger_filtered: "trigger_filtered";
21
+ trust_too_low: "trust_too_low";
22
22
  }>;
23
23
  export type ContextGateRejectReason = z.infer<typeof ContextGateRejectReason>;
24
24
  /**
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canonical secret-scope-name validation, shared by the orchestrator write
3
+ * paths and the dashboard scope form. The rule runs on the PATH portion of a
4
+ * scope (after any `backend:` prefix has been stripped): non-empty, length
5
+ * bounded, `/`-separated non-empty segments, each segment restricted to
6
+ * `[A-Za-z0-9_.-]`, and no `.`/`..` segments. Kept dependency-free so the
7
+ * engine barrel stays browser-safe.
8
+ *
9
+ * Callers pass a bare path, so that precondition holds trivially: a writer
10
+ * never supplies a backend qualifier. The `<backend>:` prefix is added by the
11
+ * resolver when it merges secrets across backends at read time, and stripped
12
+ * again before any per-backend call.
13
+ */
14
+ /** Allowed characters in a single `/`-separated scope segment. */
15
+ export declare const SCOPE_SEGMENT_PATTERN: RegExp;
16
+ /** Maximum total length of a scope path. */
17
+ export declare const SCOPE_NAME_MAX_LENGTH = 512;
18
+ /**
19
+ * Validate the path portion of a scope name. Returns a human-readable error
20
+ * message, or `null` when the name is valid.
21
+ */
22
+ export declare function validateScopeName(path: string): string | null;
23
+ /** Error thrown by {@link assertValidScopeName} for an invalid scope name. */
24
+ export declare class ScopeNameError extends Error {
25
+ constructor(message: string);
26
+ }
27
+ /** Throw {@link ScopeNameError} when `path` is not a valid scope name. */
28
+ export declare function assertValidScopeName(path: string): void;
29
+ //# sourceMappingURL=scope-name.d.ts.map
@@ -0,0 +1,49 @@
1
+ import "../rolldown-runtime-ClRpJifh.js";
2
+ //#region src/context/scope-name.ts
3
+ /**
4
+ * Canonical secret-scope-name validation, shared by the orchestrator write
5
+ * paths and the dashboard scope form. The rule runs on the PATH portion of a
6
+ * scope (after any `backend:` prefix has been stripped): non-empty, length
7
+ * bounded, `/`-separated non-empty segments, each segment restricted to
8
+ * `[A-Za-z0-9_.-]`, and no `.`/`..` segments. Kept dependency-free so the
9
+ * engine barrel stays browser-safe.
10
+ *
11
+ * Callers pass a bare path, so that precondition holds trivially: a writer
12
+ * never supplies a backend qualifier. The `<backend>:` prefix is added by the
13
+ * resolver when it merges secrets across backends at read time, and stripped
14
+ * again before any per-backend call.
15
+ */
16
+ /** Allowed characters in a single `/`-separated scope segment. */
17
+ const SCOPE_SEGMENT_PATTERN = /^[A-Za-z0-9_.-]+$/;
18
+ /** Maximum total length of a scope path. */
19
+ const SCOPE_NAME_MAX_LENGTH = 512;
20
+ /**
21
+ * Validate the path portion of a scope name. Returns a human-readable error
22
+ * message, or `null` when the name is valid.
23
+ */
24
+ function validateScopeName(path) {
25
+ if (path.length === 0) return "Scope name must not be empty";
26
+ if (path.length > 512) return `Scope name must be at most 512 characters`;
27
+ for (const segment of path.split("/")) {
28
+ if (segment.length === 0) return "Scope name must not contain empty path segments";
29
+ if (segment === "." || segment === "..") return "Scope name segments must not be '.' or '..'";
30
+ if (!SCOPE_SEGMENT_PATTERN.test(segment)) return "Scope name may only contain letters, digits, and _ . - characters, separated by /";
31
+ }
32
+ return null;
33
+ }
34
+ /** Error thrown by {@link assertValidScopeName} for an invalid scope name. */
35
+ var ScopeNameError = class extends Error {
36
+ constructor(message) {
37
+ super(message);
38
+ this.name = "ScopeNameError";
39
+ }
40
+ };
41
+ /** Throw {@link ScopeNameError} when `path` is not a valid scope name. */
42
+ function assertValidScopeName(path) {
43
+ const error = validateScopeName(path);
44
+ if (error !== null) throw new ScopeNameError(error);
45
+ }
46
+ //#endregion
47
+ export { SCOPE_NAME_MAX_LENGTH, SCOPE_SEGMENT_PATTERN, ScopeNameError, assertValidScopeName, validateScopeName };
48
+
49
+ //# sourceMappingURL=scope-name.js.map
@@ -20,6 +20,36 @@ export declare function matchScopePattern(scope: string, pattern: string): boole
20
20
  * Returns the path after the first colon, or the full scope if no prefix.
21
21
  */
22
22
  export declare function stripScopePrefix(scope: string): string;
23
+ /** The winning scoped secret for a key, with the precedence tuple that selected it. */
24
+ export interface ResolvedSecretCandidate {
25
+ secret: ScopedSecret;
26
+ scopeDepth: number;
27
+ hostSpec: number;
28
+ }
29
+ /**
30
+ * Resolve the winning secret per key by matching bindings against scoped secrets,
31
+ * applying the host gate and `(host specificity, scope depth)` precedence — WITHOUT
32
+ * decrypting, keeping this module crypto-free. This is the single source of truth for
33
+ * scope precedence; `resolveSecretsForContext` delegates to it and adds the decrypt step.
34
+ *
35
+ * Takes already-filtered bindings (for the target context). For each binding, finds
36
+ * secrets whose (substituted) scope matches the binding's scopePattern via picomatch.
37
+ * When multiple scopes provide the same key, precedence is the tuple
38
+ * `(host specificity, scope depth)` — a per-host binding (exact host) overrides a
39
+ * fleet-wide one, then longest scope path wins. Scope depth is computed AFTER stripping
40
+ * the backend prefix.
41
+ *
42
+ * When `hostFacts` is supplied (a fan-out child's identity), each binding is gated by
43
+ * its `host_pattern` and its `scope_pattern` is templated per-child
44
+ * (`${agentId}`/`${host}`/`${label:NAME}`). When omitted, only fleet-wide (`'**'`/NULL)
45
+ * non-templated bindings contribute — preserving the workflow-level (no-host) behaviour.
46
+ *
47
+ * @param bindings - Bindings already filtered for the target context
48
+ * @param allSecrets - All scoped secrets in the org
49
+ * @param hostFacts - Optional fan-out child identity for per-host resolution
50
+ * @returns Map of secret key → winning candidate (secret + precedence tuple)
51
+ */
52
+ export declare function resolveSecretsWithProvenance(bindings: ContextBinding[], allSecrets: ScopedSecret[], hostFacts?: HostFacts): Map<string, ResolvedSecretCandidate>;
23
53
  /**
24
54
  * Resolve secrets for a context by matching bindings against scoped secrets.
25
55
  *
@@ -36,6 +66,9 @@ export declare function stripScopePrefix(scope: string): string;
36
66
  * (`'**'`/NULL) non-templated bindings contribute — preserving the workflow-level
37
67
  * (no-host) behaviour.
38
68
  *
69
+ * Delegates precedence to `resolveSecretsWithProvenance` and applies `decryptFn`
70
+ * to each winning secret.
71
+ *
39
72
  * @param bindings - Bindings already filtered for the target context
40
73
  * @param allSecrets - All scoped secrets in the org
41
74
  * @param decryptFn - Pure decryption function (keeps this module crypto-free)
@@ -70,28 +70,29 @@ function candidateWins(c, existing) {
70
70
  return c.scopeDepth > existing.scopeDepth;
71
71
  }
72
72
  /**
73
- * Resolve secrets for a context by matching bindings against scoped secrets.
73
+ * Resolve the winning secret per key by matching bindings against scoped secrets,
74
+ * applying the host gate and `(host specificity, scope depth)` precedence — WITHOUT
75
+ * decrypting, keeping this module crypto-free. This is the single source of truth for
76
+ * scope precedence; `resolveSecretsForContext` delegates to it and adds the decrypt step.
74
77
  *
75
- * Takes already-filtered bindings (for the target context). For each binding,
76
- * finds secrets whose (substituted) scope matches the binding's scopePattern via
77
- * picomatch. When multiple scopes provide the same key, precedence is the tuple
78
+ * Takes already-filtered bindings (for the target context). For each binding, finds
79
+ * secrets whose (substituted) scope matches the binding's scopePattern via picomatch.
80
+ * When multiple scopes provide the same key, precedence is the tuple
78
81
  * `(host specificity, scope depth)` — a per-host binding (exact host) overrides a
79
- * fleet-wide one, then longest scope path wins. Scope depth is computed AFTER
80
- * stripping the backend prefix.
82
+ * fleet-wide one, then longest scope path wins. Scope depth is computed AFTER stripping
83
+ * the backend prefix.
81
84
  *
82
- * When `hostFacts` is supplied (a fan-out child's identity), each binding is
83
- * gated by its `host_pattern` and its `scope_pattern` is templated per-child
84
- * (`${agentId}`/`${host}`/`${label:NAME}`). When omitted, only fleet-wide
85
- * (`'**'`/NULL) non-templated bindings contribute — preserving the workflow-level
86
- * (no-host) behaviour.
85
+ * When `hostFacts` is supplied (a fan-out child's identity), each binding is gated by
86
+ * its `host_pattern` and its `scope_pattern` is templated per-child
87
+ * (`${agentId}`/`${host}`/`${label:NAME}`). When omitted, only fleet-wide (`'**'`/NULL)
88
+ * non-templated bindings contribute — preserving the workflow-level (no-host) behaviour.
87
89
  *
88
90
  * @param bindings - Bindings already filtered for the target context
89
91
  * @param allSecrets - All scoped secrets in the org
90
- * @param decryptFn - Pure decryption function (keeps this module crypto-free)
91
92
  * @param hostFacts - Optional fan-out child identity for per-host resolution
92
- * @returns Flat record of decrypted secret key-value pairs
93
+ * @returns Map of secret key → winning candidate (secret + precedence tuple)
93
94
  */
94
- function resolveSecretsForContext(bindings, allSecrets, decryptFn, hostFacts) {
95
+ function resolveSecretsWithProvenance(bindings, allSecrets, hostFacts) {
95
96
  const resolved = /* @__PURE__ */ new Map();
96
97
  for (const binding of bindings) {
97
98
  const scopePattern = bindingScopeForHost(binding, hostFacts);
@@ -107,11 +108,40 @@ function resolveSecretsForContext(bindings, allSecrets, decryptFn, hostFacts) {
107
108
  if (candidateWins(candidate, resolved.get(secret.key))) resolved.set(secret.key, candidate);
108
109
  }
109
110
  }
111
+ return resolved;
112
+ }
113
+ /**
114
+ * Resolve secrets for a context by matching bindings against scoped secrets.
115
+ *
116
+ * Takes already-filtered bindings (for the target context). For each binding,
117
+ * finds secrets whose (substituted) scope matches the binding's scopePattern via
118
+ * picomatch. When multiple scopes provide the same key, precedence is the tuple
119
+ * `(host specificity, scope depth)` — a per-host binding (exact host) overrides a
120
+ * fleet-wide one, then longest scope path wins. Scope depth is computed AFTER
121
+ * stripping the backend prefix.
122
+ *
123
+ * When `hostFacts` is supplied (a fan-out child's identity), each binding is
124
+ * gated by its `host_pattern` and its `scope_pattern` is templated per-child
125
+ * (`${agentId}`/`${host}`/`${label:NAME}`). When omitted, only fleet-wide
126
+ * (`'**'`/NULL) non-templated bindings contribute — preserving the workflow-level
127
+ * (no-host) behaviour.
128
+ *
129
+ * Delegates precedence to `resolveSecretsWithProvenance` and applies `decryptFn`
130
+ * to each winning secret.
131
+ *
132
+ * @param bindings - Bindings already filtered for the target context
133
+ * @param allSecrets - All scoped secrets in the org
134
+ * @param decryptFn - Pure decryption function (keeps this module crypto-free)
135
+ * @param hostFacts - Optional fan-out child identity for per-host resolution
136
+ * @returns Flat record of decrypted secret key-value pairs
137
+ */
138
+ function resolveSecretsForContext(bindings, allSecrets, decryptFn, hostFacts) {
139
+ const resolved = resolveSecretsWithProvenance(bindings, allSecrets, hostFacts);
110
140
  const result = {};
111
141
  for (const [key, { secret }] of resolved) result[key] = decryptFn(secret);
112
142
  return result;
113
143
  }
114
144
  //#endregion
115
- export { matchScopePattern, resolveSecretsForContext, stripScopePrefix };
145
+ export { matchScopePattern, resolveSecretsForContext, resolveSecretsWithProvenance, stripScopePrefix };
116
146
 
117
147
  //# sourceMappingURL=scope-resolver.js.map
@@ -6,6 +6,9 @@
6
6
  */
7
7
  import { z } from 'zod';
8
8
  import type { ApproverClause } from '../approval/types.js';
9
+ import type { HoldType } from './hold-type.js';
10
+ import type { HeldRunStatus } from './held-run-status.js';
11
+ import type { ConcurrencyStrategy } from './concurrency-strategy.js';
9
12
  /** Context entity — org-level deployment target with protection rules. */
10
13
  export interface Context {
11
14
  id: string;
@@ -17,7 +20,7 @@ export interface Context {
17
20
  triggerTypeFilters: string[];
18
21
  repoPatterns: string[];
19
22
  concurrencyLimit: number | null;
20
- concurrencyStrategy: 'queue' | 'cancel-pending';
23
+ concurrencyStrategy: ConcurrencyStrategy;
21
24
  concurrencyTimeoutMs: number;
22
25
  requiredReviewers: string[] | null;
23
26
  waitTimerSeconds: number | null;
@@ -82,9 +85,9 @@ export interface ContextSourceOverride {
82
85
  }
83
86
  /** Trust tier for contributor-based CI execution gating (single source of truth). */
84
87
  export declare const TrustTierSchema: z.ZodEnum<{
85
- unknown: "unknown";
86
88
  known: "known";
87
89
  trusted: "trusted";
90
+ unknown: "unknown";
88
91
  }>;
89
92
  export type TrustTier = z.infer<typeof TrustTierSchema>;
90
93
  /** Held run record for protection gate enforcement. */
@@ -94,8 +97,8 @@ export interface HeldRun {
94
97
  runId: string;
95
98
  jobId: string;
96
99
  contextId: string;
97
- holdType: 'reviewer' | 'timer' | 'concurrency' | 'security';
98
- status: 'pending' | 'approved' | 'rejected' | 'expired';
100
+ holdType: HoldType;
101
+ status: HeldRunStatus;
99
102
  reason: string | null;
100
103
  approvedBy: string | null;
101
104
  createdAt: string;
@@ -107,7 +110,7 @@ export interface ProtectionGateResult {
107
110
  action: 'pass' | 'reject' | 'hold' | 'queue' | 'wait';
108
111
  reason?: string;
109
112
  holdUntil?: string;
110
- holdType?: 'reviewer' | 'timer' | 'concurrency' | 'security';
113
+ holdType?: HoldType;
111
114
  /**
112
115
  * Approver clauses for a reviewer hold, mapped from the context's
113
116
  * `requiredReviewers`. Each reviewer string maps to a `{ user }` clause