@kici-dev/orchestrator 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__test-helpers__/test-db.d.ts +21 -0
- package/dist/app.d.ts +6 -5
- package/dist/artifacts/artifact-store.d.ts +6 -10
- package/dist/cache/legacy-prefixes.d.ts +19 -0
- package/dist/cache/pending-precursor-db-watcher.d.ts +79 -0
- package/dist/cache/pending-tracker.d.ts +10 -0
- package/dist/cache/precursor-result.d.ts +68 -0
- package/dist/cache/source-cache.d.ts +2 -1
- package/dist/cache/user-cache.d.ts +37 -58
- package/dist/cli/api-client.d.ts +3 -6
- package/dist/cli/commands/cache.d.ts +51 -0
- package/dist/cli/commands/cluster-settings.d.ts +0 -11
- package/dist/cli/commands/trust-policy.d.ts +12 -42
- package/dist/cli/service/privilege.d.ts +27 -6
- package/dist/cli.js +1171 -860
- package/dist/cluster/cluster-settings-reader.d.ts +1 -3
- package/dist/cluster/coordinator.d.ts +2 -3
- package/dist/cluster/join-client.d.ts +1 -39
- package/dist/config.d.ts +0 -6
- package/dist/contexts/context-store.d.ts +3 -3
- package/dist/contexts/held-runs.d.ts +6 -6
- package/dist/contexts/protection/aggregate.d.ts +5 -5
- package/dist/dashboard/handler.d.ts +1 -1
- package/dist/db/migrations/142_sweep_deprecated_columns.d.ts +4 -0
- package/dist/db/migrations/143_execution_jobs_precursor_result.d.ts +26 -0
- package/dist/db/migrations/144_execution_runs_registration_window.d.ts +32 -0
- package/dist/db/types.d.ts +27 -13
- package/dist/index.js +115 -156
- package/dist/metrics/prometheus.d.ts +2 -2
- package/dist/oidc/id-token-claims.d.ts +9 -10
- package/dist/oidc/local-mint.d.ts +9 -12
- package/dist/oidc/oidc-mint-registration.d.ts +15 -28
- package/dist/oidc/orchestrator-mint.d.ts +10 -10
- package/dist/oidc/resolve-signer.d.ts +40 -0
- package/dist/orchestrator-core.d.ts +11 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +1 -2
- package/dist/pipeline/job-contexts.d.ts +2 -2
- package/dist/provenance/attestation-retrier.d.ts +11 -1
- package/dist/provenance/retrier-mint.d.ts +35 -0
- package/dist/providers/github/normalizer.d.ts +1 -28
- package/dist/providers/local/lock-file-fetcher.d.ts +8 -0
- package/dist/providers/local/normalizer.d.ts +1 -15
- package/dist/queue/job-queue.d.ts +2 -6
- package/dist/queue/terminalize-unroutable.d.ts +1 -1
- package/dist/reporting/execution-tracker.d.ts +107 -0
- package/dist/scaler/claim-store.d.ts +0 -2
- package/dist/scaler/event-backend.d.ts +7 -6
- package/dist/scaler/label-matcher.d.ts +0 -1
- package/dist/scaler/manager.d.ts +20 -26
- package/dist/scaler/scaler-state-store.d.ts +9 -2
- package/dist/scaler/types.d.ts +10 -0
- package/dist/scaler/warm-pool.d.ts +6 -4
- package/dist/security/global-workflow-policy.d.ts +3 -28
- package/dist/security/identity-link.d.ts +1 -1
- package/dist/security/lock-source.d.ts +1 -1
- package/dist/security/trust-directory-store.d.ts +1 -1
- package/dist/security/trust-policy-gate.d.ts +0 -14
- package/dist/security/trust-policy-store.d.ts +2 -3
- package/dist/security/trust-resolver.d.ts +1 -2
- package/dist/server.js +2810 -2524
- package/dist/stale-detector/all-terminal-runs.d.ts +31 -0
- package/dist/stale-detector/stale-run-detector.d.ts +20 -0
- package/dist/standalone.js +2956 -2610
- package/dist/webhook/ingest-overflow-replayer.d.ts +1 -1
- package/dist/webhook/relay-reinject.d.ts +1 -1
- package/dist/worker/in-memory-job-queue.d.ts +9 -0
- package/dist/ws/dashboard-global-workflows-handler.d.ts +3 -4
- package/dist/ws/fleet-agent-collector.d.ts +7 -0
- package/dist/ws/platform-client.d.ts +0 -15
- package/dist/ws/test-relay-handlers.d.ts +2 -0
- package/installer-image-digests.json +3 -3
- package/package.json +18 -17
- package/sbom.spdx.json +1178 -1352
- package/dist/ws/oidc-token-relay.d.ts +0 -79
- package/dist/ws/orch-rpc.d.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -629,7 +629,8 @@ function createCacheStorage(config) {
|
|
|
629
629
|
* the workflow entry file alone rather than the tree the tarball holds — the
|
|
630
630
|
* defect this layout closes — so serving it would serve the bug. For a v6 lock
|
|
631
631
|
* the fallback is unreachable anyway, since the schema bump moves every hash.
|
|
632
|
-
* Old objects are orphaned
|
|
632
|
+
* Old objects are orphaned. The cache TTL is enforced lazily on access, so
|
|
633
|
+
* nothing here removes them; `kici-admin cache purge-legacy` does.
|
|
633
634
|
*/
|
|
634
635
|
const logger$3 = createLogger({ prefix: "source-cache" });
|
|
635
636
|
/**
|
|
@@ -1030,10 +1031,32 @@ const DEFAULT_USER_CACHE_QUOTA_BYTES = 5368709120;
|
|
|
1030
1031
|
const DEFAULT_USER_CACHE_TTL_MS = 6048e5;
|
|
1031
1032
|
/** Tarball suffix for committed cache entries. */
|
|
1032
1033
|
const TAR_SUFFIX = ".tar.gz";
|
|
1034
|
+
/**
|
|
1035
|
+
* Stem prefix of an in-flight upload, `.tmp-<uuid>.tar.gz`: the presigned PUT
|
|
1036
|
+
* target a save lands on before commit copies it to its final key. Shared with
|
|
1037
|
+
* the retired-layout classifier so both agree on what an upload looks like.
|
|
1038
|
+
*/
|
|
1039
|
+
const TEMP_UPLOAD_STEM = ".tmp-";
|
|
1040
|
+
/** Whether a listed object is an in-flight upload rather than a cache entry. */
|
|
1041
|
+
function isTempUpload(key) {
|
|
1042
|
+
return key.slice(key.lastIndexOf("/") + 1).startsWith(TEMP_UPLOAD_STEM);
|
|
1043
|
+
}
|
|
1033
1044
|
/** Matches a stem ending in the fixed-width discriminator this module appends. */
|
|
1034
1045
|
const KEY_DISCRIMINATOR_TAIL = new RegExp(`^(.*)-([0-9a-f]{32})$`);
|
|
1035
1046
|
/**
|
|
1036
|
-
*
|
|
1047
|
+
* Whether a listed object is a committed entry, `<stem>-<discriminator>.tar.gz`.
|
|
1048
|
+
*
|
|
1049
|
+
* This is the filter the `restoreKeys` prefix scan applies to a listing, and it
|
|
1050
|
+
* is what keeps two other kinds of object out of a restore: an in-flight
|
|
1051
|
+
* `.tmp-<uuid>.tar.gz` upload, which is a partial tarball until commit copies
|
|
1052
|
+
* it to its final key, and an object under the retired un-discriminated layout,
|
|
1053
|
+
* which nothing writes and which `kici-admin cache purge-legacy` removes.
|
|
1054
|
+
*/
|
|
1055
|
+
function isCommittedEntry(key) {
|
|
1056
|
+
return key.endsWith(TAR_SUFFIX) && KEY_DISCRIMINATOR_TAIL.test(key.slice(0, -7));
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Recover the reported cache key from a committed entry's stem.
|
|
1037
1060
|
*
|
|
1038
1061
|
* The value on this path is already the sanitized, lossy form, so this only
|
|
1039
1062
|
* needs to undo the discriminator the key format appends. A stem that itself
|
|
@@ -1043,28 +1066,74 @@ const KEY_DISCRIMINATOR_TAIL = new RegExp(`^(.*)-([0-9a-f]{32})$`);
|
|
|
1043
1066
|
function stripKeyDiscriminator(stem) {
|
|
1044
1067
|
return KEY_DISCRIMINATOR_TAIL.exec(stem)?.[1] ?? stem;
|
|
1045
1068
|
}
|
|
1046
|
-
/** Device names NTFS reserves; a file cannot actually be created under one. */
|
|
1047
|
-
const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/;
|
|
1048
|
-
/** Keys that cannot have been part of a case collision in the old key format. */
|
|
1049
|
-
const UNAMBIGUOUS_LEGACY_KEY = /^[a-z0-9_][a-z0-9._-]*$/;
|
|
1050
1069
|
/**
|
|
1051
|
-
*
|
|
1070
|
+
* Sanitize a path segment so a key can never escape its org/repo/scope
|
|
1071
|
+
* namespace. Beyond stripping disallowed characters, a segment consisting
|
|
1072
|
+
* only of dots (`.`, `..`, …) is replaced wholesale: such a segment is a
|
|
1073
|
+
* dot-segment that HTTP/S3 path canonicalization collapses (`a/./b` → `a/b`,
|
|
1074
|
+
* `a/../b` → `b`), which both corrupts the namespace and breaks the SigV4
|
|
1075
|
+
* signature on a pre-signed PUT/GET. Repo identifiers like `.` (the internal
|
|
1076
|
+
* provider's repo id) hit exactly this case, so the all-dots guard keeps the
|
|
1077
|
+
* object key canonical and the namespace boundary intact.
|
|
1052
1078
|
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1079
|
+
* Exported so an operator tool that addresses an org's prefix from a raw org id
|
|
1080
|
+
* (`kici-admin cache purge-legacy --org`) lands on the segment the writer used.
|
|
1081
|
+
*/
|
|
1082
|
+
function sanitizeSegment(s) {
|
|
1083
|
+
const cleaned = s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1084
|
+
return /^\.+$/.test(cleaned) ? `_${cleaned}` : cleaned;
|
|
1085
|
+
}
|
|
1086
|
+
/** Org-level prefix: the per-tenant isolation boundary and quota scope. */
|
|
1087
|
+
function orgPrefix(ref) {
|
|
1088
|
+
return `cache/${sanitizeSegment(ref.org)}/`;
|
|
1089
|
+
}
|
|
1090
|
+
/** Org + repo prefix shared by every scope of a repo. */
|
|
1091
|
+
function repoPrefix(ref) {
|
|
1092
|
+
return `${orgPrefix(ref)}${sanitizeSegment(ref.repo)}`;
|
|
1093
|
+
}
|
|
1094
|
+
/** Namespace prefix for the WRITE scope of a ref (shared OR per-run isolated). */
|
|
1095
|
+
function writePrefix(ref) {
|
|
1096
|
+
const base = repoPrefix(ref);
|
|
1097
|
+
if (ref.scope === "isolated") {
|
|
1098
|
+
if (!ref.runId) throw new Error("isolated cache scope requires a runId");
|
|
1099
|
+
return `${base}/iso/${sanitizeSegment(ref.runId)}/`;
|
|
1100
|
+
}
|
|
1101
|
+
return `${base}/shared/`;
|
|
1102
|
+
}
|
|
1103
|
+
/** Namespace prefixes the ref may READ, in priority order. Isolated reads its own run scope, then shared. */
|
|
1104
|
+
function readPrefixes(ref) {
|
|
1105
|
+
const base = repoPrefix(ref);
|
|
1106
|
+
if (ref.scope === "isolated") {
|
|
1107
|
+
if (!ref.runId) throw new Error("isolated cache scope requires a runId");
|
|
1108
|
+
return [`${base}/iso/${sanitizeSegment(ref.runId)}/`, `${base}/shared/`];
|
|
1109
|
+
}
|
|
1110
|
+
return [`${base}/shared/`];
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Object key for a committed cache entry.
|
|
1055
1114
|
*
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
* created there at all.
|
|
1115
|
+
* The trailing discriminator is a hash of the EXACT cache key, and it is what
|
|
1116
|
+
* keeps `build` and `Build` apart on a case-insensitive namespace — the
|
|
1117
|
+
* filesystem backend on a macOS or Windows host, where the two would
|
|
1118
|
+
* otherwise resolve to one object and a restore could return the other key's
|
|
1119
|
+
* tarball.
|
|
1062
1120
|
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1121
|
+
* It is deliberately a SUFFIX: `restoreByPrefix` matches `restoreKeys` by
|
|
1122
|
+
* string prefix, so appending leaves those semantics untouched. The only
|
|
1123
|
+
* parse that has to know about it is the `matchedKey` slice.
|
|
1124
|
+
*/
|
|
1125
|
+
function finalKey(prefix, key) {
|
|
1126
|
+
return `${prefix}${sanitizeSegment(key)}-${keyDiscriminator(key)}${TAR_SUFFIX}`;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* The object key a committed save under `key` lands on for this ref. This is
|
|
1130
|
+
* the writer's own path — `beginSave` and `commitSave` address the entry
|
|
1131
|
+
* through it — so anything that must recognise a current-layout key (the
|
|
1132
|
+
* legacy-layout classifier tests) takes it from here rather than restating
|
|
1133
|
+
* the format.
|
|
1065
1134
|
*/
|
|
1066
|
-
function
|
|
1067
|
-
return
|
|
1135
|
+
function userCacheEntryKey(ref, key) {
|
|
1136
|
+
return finalKey(writePrefix(ref), key);
|
|
1068
1137
|
}
|
|
1069
1138
|
var UserCache = class {
|
|
1070
1139
|
storage;
|
|
@@ -1105,105 +1174,10 @@ var UserCache = class {
|
|
|
1105
1174
|
ttlMs: limits.ttlMs ?? this.defaultTtlMs
|
|
1106
1175
|
};
|
|
1107
1176
|
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Sanitize a path segment so a key can never escape its org/repo/scope
|
|
1110
|
-
* namespace. Beyond stripping disallowed characters, a segment consisting
|
|
1111
|
-
* only of dots (`.`, `..`, …) is replaced wholesale: such a segment is a
|
|
1112
|
-
* dot-segment that HTTP/S3 path canonicalization collapses (`a/./b` → `a/b`,
|
|
1113
|
-
* `a/../b` → `b`), which both corrupts the namespace and breaks the SigV4
|
|
1114
|
-
* signature on a pre-signed PUT/GET. Repo identifiers like `.` (the internal
|
|
1115
|
-
* provider's repo id) hit exactly this case, so the all-dots guard keeps the
|
|
1116
|
-
* object key canonical and the namespace boundary intact.
|
|
1117
|
-
*/
|
|
1118
|
-
seg(s) {
|
|
1119
|
-
const cleaned = s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
1120
|
-
return /^\.+$/.test(cleaned) ? `_${cleaned}` : cleaned;
|
|
1121
|
-
}
|
|
1122
|
-
/** Org-level prefix: the per-tenant isolation boundary and quota scope. */
|
|
1123
|
-
orgPrefix(ref) {
|
|
1124
|
-
return `cache/${this.seg(ref.org)}/`;
|
|
1125
|
-
}
|
|
1126
|
-
/** Org + repo prefix shared by every scope of a repo. */
|
|
1127
|
-
repoPrefix(ref) {
|
|
1128
|
-
return `${this.orgPrefix(ref)}${this.seg(ref.repo)}`;
|
|
1129
|
-
}
|
|
1130
|
-
/** Namespace prefix for the WRITE scope of a ref (shared OR per-run isolated). */
|
|
1131
|
-
writePrefix(ref) {
|
|
1132
|
-
const base = this.repoPrefix(ref);
|
|
1133
|
-
if (ref.scope === "isolated") {
|
|
1134
|
-
if (!ref.runId) throw new Error("isolated cache scope requires a runId");
|
|
1135
|
-
return `${base}/iso/${this.seg(ref.runId)}/`;
|
|
1136
|
-
}
|
|
1137
|
-
return `${base}/shared/`;
|
|
1138
|
-
}
|
|
1139
|
-
/** Namespace prefixes the ref may READ, in priority order. Isolated reads its own run scope, then shared. */
|
|
1140
|
-
readPrefixes(ref) {
|
|
1141
|
-
const base = this.repoPrefix(ref);
|
|
1142
|
-
if (ref.scope === "isolated") {
|
|
1143
|
-
if (!ref.runId) throw new Error("isolated cache scope requires a runId");
|
|
1144
|
-
return [`${base}/iso/${this.seg(ref.runId)}/`, `${base}/shared/`];
|
|
1145
|
-
}
|
|
1146
|
-
return [`${base}/shared/`];
|
|
1147
|
-
}
|
|
1148
|
-
/**
|
|
1149
|
-
* Object key for a committed cache entry.
|
|
1150
|
-
*
|
|
1151
|
-
* The trailing discriminator is a hash of the EXACT cache key, and it is what
|
|
1152
|
-
* keeps `build` and `Build` apart on a case-insensitive namespace — the
|
|
1153
|
-
* filesystem backend on a macOS or Windows host, where the two would
|
|
1154
|
-
* otherwise resolve to one object and a restore could return the other key's
|
|
1155
|
-
* tarball.
|
|
1156
|
-
*
|
|
1157
|
-
* It is deliberately a SUFFIX: `restoreByPrefix` matches `restoreKeys` by
|
|
1158
|
-
* string prefix, so appending leaves those semantics untouched. The only
|
|
1159
|
-
* parse that has to know about it is the `matchedKey` slice.
|
|
1160
|
-
*/
|
|
1161
|
-
finalKey(prefix, key) {
|
|
1162
|
-
return `${prefix}${this.seg(key)}-${keyDiscriminator(key)}${TAR_SUFFIX}`;
|
|
1163
|
-
}
|
|
1164
|
-
/**
|
|
1165
|
-
* The pre-discriminator object key.
|
|
1166
|
-
*
|
|
1167
|
-
* @deprecated Read-only compatibility path for entries written before the key
|
|
1168
|
-
* carried a discriminator. Nothing writes this format; it is removed at the
|
|
1169
|
-
* next major, by which point every such entry has aged out of the cache TTL.
|
|
1170
|
-
*/
|
|
1171
|
-
legacyFinalKey(prefix, key) {
|
|
1172
|
-
return `${prefix}${this.seg(key)}${TAR_SUFFIX}`;
|
|
1173
|
-
}
|
|
1174
|
-
/**
|
|
1175
|
-
* Restore an entry still stored in the pre-discriminator key format.
|
|
1176
|
-
*
|
|
1177
|
-
* Two gates, and BOTH are load-bearing — dropping either re-creates the
|
|
1178
|
-
* wrong-cache-hit this change fixes:
|
|
1179
|
-
*
|
|
1180
|
-
* 1. `unambiguousLegacyKey` statically rules out any key that could have
|
|
1181
|
-
* collided with a case variant in the old format.
|
|
1182
|
-
* 2. The listed name must match byte-exactly. A case-insensitive backend
|
|
1183
|
-
* resolves `getUrl('build')` to an object created as `Build`, but its
|
|
1184
|
-
* *listing* reports the real created name — so comparing against the
|
|
1185
|
-
* listing is what detects that the object is not really ours.
|
|
1186
|
-
*
|
|
1187
|
-
* @deprecated Removed at the next major; see `legacyFinalKey`.
|
|
1188
|
-
*/
|
|
1189
|
-
async restoreLegacyExact(ref, prefix, ttlMs) {
|
|
1190
|
-
if (!unambiguousLegacyKey(ref.key)) return null;
|
|
1191
|
-
const legacyKey = this.legacyFinalKey(prefix, ref.key);
|
|
1192
|
-
if (!(await this.storage.list(prefix)).includes(legacyKey)) return null;
|
|
1193
|
-
const url = await this.storage.getUrl(legacyKey, ttlMs);
|
|
1194
|
-
if (!url) return null;
|
|
1195
|
-
await this.storage.touch(legacyKey);
|
|
1196
|
-
return {
|
|
1197
|
-
hit: true,
|
|
1198
|
-
matchedKey: ref.key,
|
|
1199
|
-
downloadUrl: url,
|
|
1200
|
-
tarHash: await this.readHash(legacyKey)
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
1177
|
/** Restore: try the exact key across read prefixes, then restoreKeys prefix scan (newest wins). */
|
|
1204
1178
|
async restore(ref) {
|
|
1205
1179
|
const { ttlMs } = await this.resolveLimits(ref.org);
|
|
1206
|
-
const prefixes =
|
|
1180
|
+
const prefixes = readPrefixes(ref);
|
|
1207
1181
|
const exact = await this.restoreExact(ref, prefixes, ttlMs);
|
|
1208
1182
|
if (exact) return exact;
|
|
1209
1183
|
return await this.restoreByPrefix(ref, prefixes, ttlMs) ?? { hit: false };
|
|
@@ -1211,7 +1185,7 @@ var UserCache = class {
|
|
|
1211
1185
|
/** Try the exact key in read-prefix priority order. */
|
|
1212
1186
|
async restoreExact(ref, prefixes, ttlMs) {
|
|
1213
1187
|
for (const prefix of prefixes) {
|
|
1214
|
-
const key =
|
|
1188
|
+
const key = finalKey(prefix, ref.key);
|
|
1215
1189
|
const url = await this.storage.getUrl(key, ttlMs);
|
|
1216
1190
|
if (url) {
|
|
1217
1191
|
await this.storage.touch(key);
|
|
@@ -1222,15 +1196,13 @@ var UserCache = class {
|
|
|
1222
1196
|
tarHash: await this.readHash(key)
|
|
1223
1197
|
};
|
|
1224
1198
|
}
|
|
1225
|
-
const legacy = await this.restoreLegacyExact(ref, prefix, ttlMs);
|
|
1226
|
-
if (legacy) return legacy;
|
|
1227
1199
|
}
|
|
1228
1200
|
return null;
|
|
1229
1201
|
}
|
|
1230
1202
|
/** restoreKeys prefix fallback (ordered); within a prefix, list() returns newest-first. */
|
|
1231
1203
|
async restoreByPrefix(ref, prefixes, ttlMs) {
|
|
1232
1204
|
for (const rk of ref.restoreKeys ?? []) for (const prefix of prefixes) {
|
|
1233
|
-
const matches = (await this.storage.list(`${prefix}${
|
|
1205
|
+
const matches = (await this.storage.list(`${prefix}${sanitizeSegment(rk)}`)).filter(isCommittedEntry);
|
|
1234
1206
|
if (matches.length === 0) continue;
|
|
1235
1207
|
const winner = matches[0];
|
|
1236
1208
|
const url = await this.storage.getUrl(winner, ttlMs);
|
|
@@ -1247,13 +1219,12 @@ var UserCache = class {
|
|
|
1247
1219
|
}
|
|
1248
1220
|
/** Begin a save: presigned PUT to a temp key, or skip=true when the immutable key exists. */
|
|
1249
1221
|
async beginSave(ref) {
|
|
1250
|
-
const
|
|
1251
|
-
const final = this.finalKey(prefix, ref.key);
|
|
1222
|
+
const final = userCacheEntryKey(ref, ref.key);
|
|
1252
1223
|
if (await this.storage.has(final)) {
|
|
1253
1224
|
logger$1.info("user-cache save skipped (immutable key exists)", { key: ref.key });
|
|
1254
1225
|
return { skip: true };
|
|
1255
1226
|
}
|
|
1256
|
-
const tempKey = `${
|
|
1227
|
+
const tempKey = `${writePrefix(ref)}${TEMP_UPLOAD_STEM}${randomUUID()}${TAR_SUFFIX}`;
|
|
1257
1228
|
return {
|
|
1258
1229
|
skip: false,
|
|
1259
1230
|
uploadUrl: await this.storage.getUploadUrl(tempKey),
|
|
@@ -1262,8 +1233,7 @@ var UserCache = class {
|
|
|
1262
1233
|
}
|
|
1263
1234
|
/** Commit a save: copy temp -> final, init metadata, store companion hash/size, delete temp, enforce quota. */
|
|
1264
1235
|
async commitSave(ref) {
|
|
1265
|
-
const
|
|
1266
|
-
const final = this.finalKey(prefix, ref.key);
|
|
1236
|
+
const final = userCacheEntryKey(ref, ref.key);
|
|
1267
1237
|
if (await this.storage.has(final)) return;
|
|
1268
1238
|
if (ref.tempKey) {
|
|
1269
1239
|
await this.storage.copy(ref.tempKey, final);
|
|
@@ -1281,11 +1251,18 @@ var UserCache = class {
|
|
|
1281
1251
|
async readHash(key) {
|
|
1282
1252
|
return (await this.storage.get(`${key}.hash`))?.toString("utf-8") || void 0;
|
|
1283
1253
|
}
|
|
1284
|
-
/**
|
|
1254
|
+
/**
|
|
1255
|
+
* Evict least-recently-used entries for the org until total tarball size <= the per-org quota.
|
|
1256
|
+
*
|
|
1257
|
+
* An in-flight `.tmp-` upload is never a candidate. A presigned PUT carries
|
|
1258
|
+
* no metadata, so it would rank oldest and be reclaimed first — freeing
|
|
1259
|
+
* nothing, since it has no `.size` — and the concurrent commit would then
|
|
1260
|
+
* fail on its copy to the final key. Retired un-discriminated entries stay
|
|
1261
|
+
* in the set: they hold accounted bytes and are evictable like any other.
|
|
1262
|
+
*/
|
|
1285
1263
|
async enforceQuota(ref) {
|
|
1286
1264
|
const { quotaBytes } = await this.resolveLimits(ref.org);
|
|
1287
|
-
const
|
|
1288
|
-
const keys = (await this.storage.list(orgPrefix)).filter((k) => k.endsWith(TAR_SUFFIX));
|
|
1265
|
+
const keys = (await this.storage.list(orgPrefix(ref))).filter((k) => k.endsWith(TAR_SUFFIX) && !isTempUpload(k));
|
|
1289
1266
|
const sized = [];
|
|
1290
1267
|
let total = 0;
|
|
1291
1268
|
for (const k of keys) {
|
|
@@ -1818,12 +1795,12 @@ function deriveKeys(secret) {
|
|
|
1818
1795
|
*
|
|
1819
1796
|
* - `fork_pr` — the org trust policy's fork switch held the run.
|
|
1820
1797
|
* - `context_trust` — a context's minimum-trust gate held the run.
|
|
1821
|
-
* - `workflow_modification` —
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1824
|
-
* - `unknown_contributor` —
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1798
|
+
* - `workflow_modification` — no longer raised. Modifications to `.kici/`
|
|
1799
|
+
* are surfaced on their own informational check and no longer feed a policy
|
|
1800
|
+
* arm. Kept so stored `held_runs` rows with this reason still render.
|
|
1801
|
+
* - `unknown_contributor` — no longer raised. The policy turns on whether the
|
|
1802
|
+
* pull request came from a fork, not on who opened it. Kept so stored
|
|
1803
|
+
* `held_runs` rows with this reason still render.
|
|
1827
1804
|
*/
|
|
1828
1805
|
const SecurityHoldReason = z.enum([
|
|
1829
1806
|
"workflow_modification",
|
|
@@ -2176,20 +2153,15 @@ function createHeldRunStoreFromUrl(databaseUrl, opts) {
|
|
|
2176
2153
|
}
|
|
2177
2154
|
};
|
|
2178
2155
|
}
|
|
2179
|
-
z.enum(["policy", "legacy"]);
|
|
2180
2156
|
/** The fork switch applied when no policy row is stored. */
|
|
2181
2157
|
const DEFAULT_FORK_POLICY = ForkPolicy.enum.ignore;
|
|
2182
2158
|
Object.freeze({
|
|
2183
2159
|
forkPolicy: DEFAULT_FORK_POLICY,
|
|
2184
|
-
unknownContributorPolicy: "hold",
|
|
2185
|
-
workflowChangePolicy: "hold",
|
|
2186
2160
|
approvalExpiryHours: DEFAULT_APPROVAL_EXPIRY_HOURS,
|
|
2187
2161
|
approvalExpirySeconds: DEFAULT_APPROVAL_EXPIRY_SECONDS
|
|
2188
2162
|
});
|
|
2189
2163
|
Object.freeze({
|
|
2190
2164
|
forkPolicy: ForkPolicy.enum.hold,
|
|
2191
|
-
unknownContributorPolicy: "hold",
|
|
2192
|
-
workflowChangePolicy: "hold",
|
|
2193
2165
|
approvalExpiryHours: DEFAULT_APPROVAL_EXPIRY_HOURS,
|
|
2194
2166
|
approvalExpirySeconds: DEFAULT_APPROVAL_EXPIRY_SECONDS
|
|
2195
2167
|
});
|
|
@@ -2220,8 +2192,6 @@ const TrustPolicySource = z.enum(["platform", "local"]);
|
|
|
2220
2192
|
*/
|
|
2221
2193
|
const DEFAULT_TRUST_POLICY = {
|
|
2222
2194
|
forkPolicy: DEFAULT_FORK_POLICY,
|
|
2223
|
-
unknownContributorPolicy: "hold",
|
|
2224
|
-
workflowChangePolicy: "hold",
|
|
2225
2195
|
approvalExpiryHours: DEFAULT_APPROVAL_EXPIRY_HOURS,
|
|
2226
2196
|
approvalExpirySeconds: DEFAULT_APPROVAL_EXPIRY_SECONDS
|
|
2227
2197
|
};
|
|
@@ -2265,8 +2235,6 @@ var TrustPolicyStore = class {
|
|
|
2265
2235
|
if (!row) return null;
|
|
2266
2236
|
return {
|
|
2267
2237
|
forkPolicy: row.fork_policy,
|
|
2268
|
-
unknownContributorPolicy: row.unknown_contributor_policy,
|
|
2269
|
-
workflowChangePolicy: row.workflow_change_policy,
|
|
2270
2238
|
approvalExpiryHours: Number(row.approval_expiry_hours),
|
|
2271
2239
|
approvalExpirySeconds: approvalExpirySecondsOf({
|
|
2272
2240
|
approvalExpiryHours: Number(row.approval_expiry_hours),
|
|
@@ -2295,9 +2263,8 @@ var TrustPolicyStore = class {
|
|
|
2295
2263
|
* COMMITTED two concurrent PATCHes both read the pre-existing row, and the
|
|
2296
2264
|
* second `ON CONFLICT DO UPDATE` then overwrites every column from its own
|
|
2297
2265
|
* stale merge — silently dropping the first operator's change (a tightened
|
|
2298
|
-
* `
|
|
2299
|
-
*
|
|
2300
|
-
* leak.
|
|
2266
|
+
* `forkPolicy` reverting to whatever the second caller last saw). The lock
|
|
2267
|
+
* releases on commit or rollback, so there is no unlock to leak.
|
|
2301
2268
|
*
|
|
2302
2269
|
* `onWrite` receives the same transaction and the merged result, so an audit
|
|
2303
2270
|
* row written there commits or rolls back with the policy itself — a
|
|
@@ -2312,8 +2279,6 @@ var TrustPolicyStore = class {
|
|
|
2312
2279
|
const existing = await this.get(orgId, trx);
|
|
2313
2280
|
const merged = {
|
|
2314
2281
|
forkPolicy: patch.forkPolicy ?? existing?.forkPolicy ?? DEFAULT_TRUST_POLICY.forkPolicy,
|
|
2315
|
-
unknownContributorPolicy: patch.unknownContributorPolicy ?? existing?.unknownContributorPolicy ?? DEFAULT_TRUST_POLICY.unknownContributorPolicy,
|
|
2316
|
-
workflowChangePolicy: patch.workflowChangePolicy ?? existing?.workflowChangePolicy ?? DEFAULT_TRUST_POLICY.workflowChangePolicy,
|
|
2317
2282
|
...mergedExpiry(patch, existing)
|
|
2318
2283
|
};
|
|
2319
2284
|
await this.write(orgId, merged, TrustPolicySource.enum.local, trx);
|
|
@@ -2325,8 +2290,6 @@ var TrustPolicyStore = class {
|
|
|
2325
2290
|
const seconds = approvalExpirySecondsOf(policy);
|
|
2326
2291
|
const columns = {
|
|
2327
2292
|
fork_policy: policy.forkPolicy,
|
|
2328
|
-
unknown_contributor_policy: policy.unknownContributorPolicy,
|
|
2329
|
-
workflow_change_policy: policy.workflowChangePolicy,
|
|
2330
2293
|
approval_expiry_hours: approvalExpiryHoursOf(seconds),
|
|
2331
2294
|
approval_expiry_seconds: seconds,
|
|
2332
2295
|
source,
|
|
@@ -2525,15 +2488,11 @@ var AdminApiClient = class {
|
|
|
2525
2488
|
fs.writeFileSync(outPath, buf);
|
|
2526
2489
|
}
|
|
2527
2490
|
/**
|
|
2528
|
-
* List secret scopes
|
|
2529
|
-
*
|
|
2530
|
-
* @param allBackends - Aggregate every registered backend and return scopes
|
|
2531
|
-
* in qualified `<backend>:<path>` form. Default (false) returns the bare,
|
|
2532
|
-
* pg-only listing; that default flips at v1.0.0.
|
|
2491
|
+
* List an org's secret scopes across every registered backend, each in
|
|
2492
|
+
* qualified `<backend>:<path>` form.
|
|
2533
2493
|
*/
|
|
2534
|
-
async listScopes(orgId
|
|
2494
|
+
async listScopes(orgId) {
|
|
2535
2495
|
const params = new URLSearchParams({ orgId });
|
|
2536
|
-
if (allBackends) params.set("allBackends", "true");
|
|
2537
2496
|
return this.request("GET", `/api/v1/admin/secrets/scopes?${params}`);
|
|
2538
2497
|
}
|
|
2539
2498
|
async listKeys(orgId, scope) {
|
|
@@ -625,7 +625,7 @@ export declare const installSecretsRegistryUsedTotal: import("@opentelemetry/api
|
|
|
625
625
|
* from leaving the orchestrator for those dispatches.
|
|
626
626
|
*
|
|
627
627
|
* Labels:
|
|
628
|
-
* - trust_tier: unknown
|
|
628
|
+
* - trust_tier: unknown (the `trusted` tier does not strip and therefore never fires this counter)
|
|
629
629
|
*/
|
|
630
630
|
export declare const installSecretsContributorStrippedTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
|
|
631
631
|
/**
|
|
@@ -638,7 +638,7 @@ export declare const installSecretsContributorStrippedTotal: import("@openteleme
|
|
|
638
638
|
* and registry auth is per-job, so one run can strip both, either, or neither.
|
|
639
639
|
*
|
|
640
640
|
* Labels:
|
|
641
|
-
* - trust_tier: unknown
|
|
641
|
+
* - trust_tier: unknown (the `trusted` tier does not strip and therefore never fires this counter)
|
|
642
642
|
*/
|
|
643
643
|
export declare const containerRegistryAuthContributorStrippedTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
|
|
644
644
|
/**
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* differs only in the local sentinel issuer (`kici-local`) and the org anchor
|
|
9
9
|
* (`__default__`, the plane's default org).
|
|
10
10
|
*/
|
|
11
|
-
import { AttestationOrigin, SourceOrigin, type EventClaims } from '@kici-dev/engine';
|
|
11
|
+
import { AttestationOrigin, ID_TOKEN_CLAIM_NAMES, SourceOrigin, type EventClaims } from '@kici-dev/engine';
|
|
12
12
|
/** The subset of an execution_runs row the claims need. */
|
|
13
13
|
export interface RunClaimSource {
|
|
14
14
|
run_id: string;
|
|
@@ -61,15 +61,6 @@ export interface BuildClaimsOpts {
|
|
|
61
61
|
statementHash: string;
|
|
62
62
|
origin: 'deferred' | 'offline-backfill';
|
|
63
63
|
};
|
|
64
|
-
/**
|
|
65
|
-
* Restore the pre-split pull-request `sub` — the branch-shaped form that
|
|
66
|
-
* collides with a push to the same base branch. An escape hatch for an
|
|
67
|
-
* operator whose cloud trust policy breaks mid-migration, set by
|
|
68
|
-
* `KICI_OIDC_LEGACY_PR_SUB`.
|
|
69
|
-
*
|
|
70
|
-
* @deprecated Removed at v1.0.0. Move the policy to `repo:<repo>:pull_request`.
|
|
71
|
-
*/
|
|
72
|
-
legacyPullRequestSubject?: boolean;
|
|
73
64
|
}
|
|
74
65
|
/**
|
|
75
66
|
* Extends {@link EventClaims}, so every token carries the event-context claims
|
|
@@ -96,6 +87,14 @@ export interface IdTokenClaims extends EventClaims {
|
|
|
96
87
|
statement_hash: string | null;
|
|
97
88
|
attestation_origin: AttestationOrigin;
|
|
98
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The claim names shared by both discovery documents live in `@kici-dev/engine`;
|
|
92
|
+
* re-exported here for the route and the drift test. The binding below fails
|
|
93
|
+
* to compile when the engine list names a claim this type does not carry, and
|
|
94
|
+
* the drift test in `routes/provenance-oidc.test.ts` fails when the builder
|
|
95
|
+
* emits a claim the list does not name — together they keep the two in step.
|
|
96
|
+
*/
|
|
97
|
+
export { ID_TOKEN_CLAIM_NAMES };
|
|
99
98
|
/**
|
|
100
99
|
* Build the OIDC ID-token claims for a build job from the orchestrator's own
|
|
101
100
|
* execution_runs/execution_jobs rows. A live (synchronous) mint leaves
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local dev-signed OIDC mint (independent mode only).
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* execution_jobs rows
|
|
9
|
-
* issuer `kici-local`.
|
|
4
|
+
* The production sibling is `orchestrator-mint.ts`, which signs with the
|
|
5
|
+
* orchestrator's own long-lived key under its real issuer. The offline local dev
|
|
6
|
+
* plane has no provenance issuer configured, so this module mints with the
|
|
7
|
+
* in-process `LocalDevSigner` under the issuer `kici-local`, reading the
|
|
8
|
+
* orchestrator's OWN execution_runs/execution_jobs rows.
|
|
10
9
|
*
|
|
11
10
|
* Security: this path is registered ONLY when the orchestrator runs in
|
|
12
|
-
* `independent` mode with a `LocalDevSigner` present
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* signer — it keeps minting via the Platform exactly as today. The agent never
|
|
16
|
-
* asserts its own claims; every identity claim is read server-side from the
|
|
11
|
+
* `independent` mode with a `LocalDevSigner` present and no orchestrator-owned
|
|
12
|
+
* signer is configured (`selectOidcMintRegistration`). The agent never asserts
|
|
13
|
+
* its own claims; every identity claim is read server-side from the
|
|
17
14
|
* orchestrator's rows, and the job-ownership check is against dispatch state.
|
|
18
15
|
*/
|
|
19
16
|
import type { Kysely } from 'kysely';
|
|
@@ -21,7 +18,7 @@ import { type OidcTokenResult } from '@kici-dev/engine/protocol/messages/oidc-to
|
|
|
21
18
|
import type { Database } from '../db/types.js';
|
|
22
19
|
import { type IdTokenClaims } from './id-token-claims.js';
|
|
23
20
|
import { type LocalSigner } from './local-dev-signer.js';
|
|
24
|
-
/** ID-token lifetime (seconds). Matches the
|
|
21
|
+
/** ID-token lifetime (seconds). Matches the production mint's 10-minute cap. */
|
|
25
22
|
export declare const LOCAL_ID_TOKEN_TTL_SECONDS = 600;
|
|
26
23
|
/** The org anchor stamped into a local-plane token (the plane's default org). */
|
|
27
24
|
export declare const LOCAL_ORG_ID = "__default__";
|
|
@@ -6,14 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 1. **Orchestrator-owned signing** (a resolved signer + the orchestrator's own
|
|
8
8
|
* provenance issuer) → the **orchestrator** handler, minting + signing
|
|
9
|
-
* locally. Wins whenever signing is configured —
|
|
10
|
-
*
|
|
11
|
-
* 2. **
|
|
12
|
-
* **
|
|
13
|
-
*
|
|
14
|
-
* 3. **Offline local dev plane** (independent identity + a local signer, AND
|
|
15
|
-
* NOT Platform-connected) → the **local** dev-signed handler (`kici-local`).
|
|
16
|
-
* 4. None → **no** registration (the method returns "unknown method").
|
|
9
|
+
* locally. Wins whenever signing is configured — the orchestrator is the
|
|
10
|
+
* root of trust.
|
|
11
|
+
* 2. **Offline local dev plane** (independent identity + a local signer) → the
|
|
12
|
+
* **local** dev-signed handler (`kici-local`).
|
|
13
|
+
* 3. None → **no** registration (the method returns "unknown method").
|
|
17
14
|
*
|
|
18
15
|
* The identity claims are always read SERVER-SIDE from the orchestrator's own
|
|
19
16
|
* rows and job ownership from dispatch state — no forgeable agent-asserted mint
|
|
@@ -22,29 +19,25 @@
|
|
|
22
19
|
import type { Kysely } from 'kysely';
|
|
23
20
|
import type { OidcTokenResult } from '@kici-dev/engine/protocol/messages/oidc-token-relay';
|
|
24
21
|
import type { Database } from '../db/types.js';
|
|
25
|
-
import { type MintPlatformClient } from '../ws/oidc-token-relay.js';
|
|
26
22
|
import { type LocalMintOwnershipResolver } from './local-mint.js';
|
|
27
23
|
import type { LocalSigner } from './local-dev-signer.js';
|
|
28
24
|
import type { Signer } from './signer.js';
|
|
29
25
|
export type OidcMintHandler = (agentId: string, params: Record<string, unknown>) => Promise<OidcTokenResult>;
|
|
30
26
|
export interface OidcMintRegistration {
|
|
31
27
|
/**
|
|
32
|
-
* `orchestrator` = orchestrator-owned signing (the
|
|
33
|
-
* `
|
|
28
|
+
* `orchestrator` = orchestrator-owned signing (the root of trust);
|
|
29
|
+
* `local` = offline dev-signed.
|
|
34
30
|
*/
|
|
35
|
-
kind: 'orchestrator' | '
|
|
31
|
+
kind: 'orchestrator' | 'local';
|
|
36
32
|
handler: OidcMintHandler;
|
|
37
33
|
}
|
|
38
34
|
export interface OidcMintRegistrationInput {
|
|
39
|
-
platformUrl?: string;
|
|
40
|
-
platformToken?: string;
|
|
41
|
-
platformClient?: MintPlatformClient;
|
|
42
35
|
independentIdentity: boolean;
|
|
43
36
|
localOidcSigner?: LocalSigner;
|
|
44
37
|
/**
|
|
45
|
-
* Lazily resolve the orchestrator's own provenance signer
|
|
46
|
-
*
|
|
47
|
-
*
|
|
38
|
+
* Lazily resolve the orchestrator's own provenance signer. Present whenever
|
|
39
|
+
* orchestrator-owned signing is configured; the signer itself is resolved on
|
|
40
|
+
* first mint (leader-election-race-safe).
|
|
48
41
|
*/
|
|
49
42
|
resolveOrchestratorSigner?: () => Promise<Signer | null>;
|
|
50
43
|
/** The orchestrator's own provenance issuer identity (config.provenanceSigningIssuer). */
|
|
@@ -57,16 +50,10 @@ export interface OidcMintRegistrationInput {
|
|
|
57
50
|
db: Kysely<Database>;
|
|
58
51
|
orchestratorId: string;
|
|
59
52
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
legacyPullRequestSubject?: boolean;
|
|
65
|
-
/**
|
|
66
|
-
* Test-only fault-injection predicate forwarded to the relay mint handler,
|
|
67
|
-
* supplied only by the build-time test double. Only ever defers — the
|
|
68
|
-
* anti-forgery choke point is preserved. Undefined (the shipped default) means
|
|
69
|
-
* no fault injection.
|
|
53
|
+
* Test-only fault-injection predicate forwarded to the orchestrator mint
|
|
54
|
+
* handler, supplied only by the build-time test double. Only ever defers —
|
|
55
|
+
* the anti-forgery choke point is preserved. Undefined (the shipped default)
|
|
56
|
+
* means no fault injection.
|
|
70
57
|
*/
|
|
71
58
|
initialMintFault?: (audience: string) => boolean;
|
|
72
59
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* execution_jobs rows, signed with its own long-lived signing key (`Signer`),
|
|
7
7
|
* issued under its own real issuer. Zero hosted-Platform dependency.
|
|
8
8
|
*
|
|
9
|
-
* Anti-forgery is identical to the
|
|
9
|
+
* Anti-forgery is identical to the local dev plane:
|
|
10
10
|
* every identity claim (run/job/repo/ref/sha/org) is read SERVER-SIDE from the
|
|
11
11
|
* orchestrator's own rows — never from the agent's wire input — and job ownership
|
|
12
12
|
* is resolved from dispatch state, never agent-asserted. The token is never
|
|
@@ -19,7 +19,7 @@ import { type IdTokenClaims } from './id-token-claims.js';
|
|
|
19
19
|
import type { Signer } from './signer.js';
|
|
20
20
|
/** The org anchor stamped when a run has no routing key (sourceless / local plane). */
|
|
21
21
|
export declare const DEFAULT_ORG_ID = "__default__";
|
|
22
|
-
/** ID-token lifetime (seconds).
|
|
22
|
+
/** ID-token lifetime (seconds). */
|
|
23
23
|
export declare const ORCHESTRATOR_ID_TOKEN_TTL_SECONDS = 600;
|
|
24
24
|
export declare class OrchestratorMintRunNotFoundError extends Error {
|
|
25
25
|
}
|
|
@@ -36,14 +36,6 @@ export interface OrchestratorMintDeps {
|
|
|
36
36
|
ttlSeconds?: number;
|
|
37
37
|
/** Injectable clock (seconds). Defaults to wall-clock. */
|
|
38
38
|
nowSeconds?: () => number;
|
|
39
|
-
/**
|
|
40
|
-
* Restore the pre-split pull-request `sub`, from `KICI_OIDC_LEGACY_PR_SUB`.
|
|
41
|
-
* An escape hatch for an operator whose cloud trust policy breaks mid-
|
|
42
|
-
* migration; leaves the collision with a same-branch push in place.
|
|
43
|
-
*
|
|
44
|
-
* @deprecated Removed at v1.0.0.
|
|
45
|
-
*/
|
|
46
|
-
legacyPullRequestSubject?: boolean;
|
|
47
39
|
}
|
|
48
40
|
export interface OrchestratorMintInput {
|
|
49
41
|
runId: string;
|
|
@@ -93,6 +85,14 @@ export interface OrchestratorOidcTokenHandlerDeps {
|
|
|
93
85
|
resolveSigner: () => Promise<Signer | null>;
|
|
94
86
|
/** Mint deps except the signer (supplied lazily via `resolveSigner`). */
|
|
95
87
|
mint: Omit<OrchestratorMintDeps, 'signer'>;
|
|
88
|
+
/**
|
|
89
|
+
* Test-only fault-injection predicate over the requested OIDC `audience`,
|
|
90
|
+
* supplied only by the build-time test double. Returning `true` short-circuits
|
|
91
|
+
* the *initial* mint to a transient defer BEFORE the signer is resolved — it
|
|
92
|
+
* can only defer, never forge or weaken a signature. Undefined (the shipped
|
|
93
|
+
* default) means no fault injection.
|
|
94
|
+
*/
|
|
95
|
+
initialMintFault?: (audience: string) => boolean;
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
98
|
* Build the agent.api handler for `OIDC_TOKEN_REQUEST_METHOD` when the
|