@mnemom/mnemom 0.17.38-next.1 → 0.17.38-next.3
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/commands/agent-run.d.ts +10 -2
- package/dist/commands/agent-run.js +18 -7
- package/dist/commands/scan.d.ts +1 -1
- package/dist/commands/scan.js +7 -14
- package/dist/index.js +1 -0
- package/dist/lib/runner/executor-client.d.ts +6 -0
- package/dist/lib/runner/executor-client.js +6 -0
- package/dist/lib/runner/job.d.ts +12 -3
- package/dist/lib/runner/job.js +30 -2
- package/dist/lib/runner/loop.d.ts +7 -2
- package/dist/lib/runner/loop.js +15 -3
- package/dist/lib/runner/options.d.ts +7 -0
- package/dist/lib/runner/options.js +11 -0
- package/dist/lib/scan/caps.d.ts +1 -35
- package/dist/lib/scan/caps.js +2 -67
- package/dist/lib/scan/cost-per-pr.d.ts +1 -93
- package/dist/lib/scan/cost-per-pr.js +2 -101
- package/dist/lib/scan/credential.d.ts +2 -26
- package/dist/lib/scan/credential.js +5 -58
- package/dist/lib/scan/failure.d.ts +1 -10
- package/dist/lib/scan/failure.js +2 -43
- package/dist/lib/scan/pr-source.d.ts +1 -57
- package/dist/lib/scan/pr-source.js +2 -132
- package/dist/lib/scan/report-email.d.ts +7 -104
- package/dist/lib/scan/report-email.js +25 -97
- package/dist/lib/scan/scan.d.ts +1 -69
- package/dist/lib/scan/scan.js +2 -195
- package/dist/lib/scan/share.d.ts +3 -1
- package/dist/lib/scan/spend/ai-tool-detect.d.ts +1 -6
- package/dist/lib/scan/spend/ai-tool-detect.js +2 -18
- package/dist/lib/scan/spend/anthropic-usage.d.ts +1 -76
- package/dist/lib/scan/spend/anthropic-usage.js +2 -269
- package/dist/lib/scan/spend/prices.d.ts +1 -33
- package/dist/lib/scan/spend/prices.js +2 -57
- package/dist/lib/scan/spend/spend-estimate.d.ts +1 -30
- package/dist/lib/scan/spend/spend-estimate.js +2 -70
- package/dist/lib/scan/spend/spend-tiers.d.ts +1 -16
- package/dist/lib/scan/spend/spend-tiers.js +6 -26
- package/dist/lib/scan/spend/token-rate.d.ts +1 -30
- package/dist/lib/scan/spend/token-rate.js +2 -54
- package/dist/vendor/durable-contract/index.js +19 -2
- package/dist/vendor/shared/durable-contract/cli-surface.js +2 -1
- package/dist/vendor/shared/durable-contract/codes.d.ts +12 -0
- package/dist/vendor/shared/durable-contract/codes.js +11 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +7 -1
- package/dist/vendor/shared/durable-contract/constants.js +7 -1
- package/dist/vendor/shared/durable-contract/doors.d.ts +9 -1
- package/dist/vendor/shared/durable-contract/doors.js +13 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +1 -1
- package/dist/vendor/shared/durable-contract/schema.d.ts +9 -0
- package/dist/vendor/shared/durable-contract/schema.js +11 -0
- package/dist/vendor/shared/org-scan-core/caps.d.ts +35 -0
- package/dist/vendor/shared/org-scan-core/caps.js +68 -0
- package/dist/vendor/shared/org-scan-core/cost-per-pr.d.ts +93 -0
- package/dist/vendor/shared/org-scan-core/cost-per-pr.js +101 -0
- package/dist/vendor/shared/org-scan-core/credential.d.ts +43 -0
- package/dist/vendor/shared/org-scan-core/credential.js +74 -0
- package/dist/vendor/shared/org-scan-core/failure.d.ts +10 -0
- package/dist/vendor/shared/org-scan-core/failure.js +43 -0
- package/dist/vendor/shared/org-scan-core/index.d.ts +18 -0
- package/dist/vendor/shared/org-scan-core/index.js +19 -0
- package/dist/vendor/shared/org-scan-core/pr-source.d.ts +61 -0
- package/dist/vendor/shared/org-scan-core/pr-source.js +138 -0
- package/dist/vendor/shared/org-scan-core/report-build.d.ts +120 -0
- package/dist/vendor/shared/org-scan-core/report-build.js +119 -0
- package/dist/vendor/shared/org-scan-core/scan.d.ts +75 -0
- package/dist/vendor/shared/org-scan-core/scan.js +202 -0
- package/dist/vendor/shared/org-scan-core/spend/ai-tool-detect.d.ts +6 -0
- package/dist/vendor/shared/org-scan-core/spend/ai-tool-detect.js +18 -0
- package/dist/vendor/shared/org-scan-core/spend/anthropic-usage.d.ts +76 -0
- package/dist/vendor/shared/org-scan-core/spend/anthropic-usage.js +270 -0
- package/dist/{lib/scan → vendor/shared/org-scan-core}/spend/cost-corpus.v1.js +1 -0
- package/dist/vendor/shared/org-scan-core/spend/prices.d.ts +33 -0
- package/dist/vendor/shared/org-scan-core/spend/prices.js +58 -0
- package/dist/vendor/shared/org-scan-core/spend/spend-estimate.d.ts +30 -0
- package/dist/vendor/shared/org-scan-core/spend/spend-estimate.js +71 -0
- package/dist/vendor/shared/org-scan-core/spend/spend-select.d.ts +16 -0
- package/dist/vendor/shared/org-scan-core/spend/spend-select.js +26 -0
- package/dist/vendor/shared/org-scan-core/spend/token-rate.d.ts +30 -0
- package/dist/vendor/shared/org-scan-core/spend/token-rate.js +55 -0
- package/package.json +1 -1
- /package/dist/{lib/scan → vendor/shared/org-scan-core}/spend/cost-corpus.v1.d.ts +0 -0
|
@@ -1065,6 +1065,7 @@ var CLAIM_UNAVAILABLE_CAUSES = [
|
|
|
1065
1065
|
/** A pool claim with no valid registry default cap (ruling 34.05.1 (3)). */
|
|
1066
1066
|
"lease_cap_config"
|
|
1067
1067
|
];
|
|
1068
|
+
var SEAL_REPO_MISMATCH_CAUSES = ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
|
|
1068
1069
|
var UNSUPPORTED_FEATURES = [
|
|
1069
1070
|
"tool_classes",
|
|
1070
1071
|
"launch_spec_change",
|
|
@@ -1173,6 +1174,11 @@ var DURABLE_CODES = {
|
|
|
1173
1174
|
// The per-org claim rate limit (ruling 34.24): nothing written, no key minted. retry_after_s is
|
|
1174
1175
|
// the number of seconds to wait (claimRateLimitedDetailsOk).
|
|
1175
1176
|
durable_claim_rate_limited: { status: 429, surfaces: ["api"], fields: ["retry_after_s"] },
|
|
1177
|
+
// An OIDC executor key (bound to the repo whose GitHub Actions token minted it) claiming a session
|
|
1178
|
+
// not sealed for that repo (mnemom-test#1329; mnemom-api#4530). Its own code, not a
|
|
1179
|
+
// durable_claim_ineligible reason: the session is claimable, only not by this executor, and a
|
|
1180
|
+
// runner never skips past it to another session. cause is a SEAL_REPO_MISMATCH_CAUSES value.
|
|
1181
|
+
seal_repo_mismatch: { status: 403, surfaces: ["api"], fields: ["cause"] },
|
|
1176
1182
|
// ---- api journal (C10-R0 (i), (j), (k)) ----
|
|
1177
1183
|
/** Lost CAS race on (session_id, seq). */
|
|
1178
1184
|
durable_append_conflict: { status: 409, surfaces: ["api"], fields: ["tip_seq"] },
|
|
@@ -1670,7 +1676,8 @@ var M1_CLI_SURFACE = Object.freeze([
|
|
|
1670
1676
|
entry(["agent", "pause"], ["--json"]),
|
|
1671
1677
|
entry(["agent", "resume"], ["--json"]),
|
|
1672
1678
|
// Doors thin runner (W-L13), ruling 34.12 (1): --watch or --once, --fail-mode, --json, --oidc.
|
|
1673
|
-
|
|
1679
|
+
// --session (#1329, approved by the Durable lead): with --once, claim only the wake-up's session.
|
|
1680
|
+
entry(["agent", "run"], ["--fail-mode", "--json", "--oidc", "--once", "--session", "--watch"]),
|
|
1674
1681
|
// BACKBONE C4 (line 147: buildDurableLaunchSpec serves 'sessions adopt'); options: C14 spec
|
|
1675
1682
|
// (`sessions adopt --dry-run --json`, `sessions adopt --own-agent`); ruling 32.33.
|
|
1676
1683
|
entry(["agent", "sessions", "adopt"], ["--dry-run", "--json", "--own-agent"]),
|
|
@@ -2393,12 +2400,18 @@ var DISPATCH_SESSION_CID_PREFIX = "dispatch-";
|
|
|
2393
2400
|
var DISPATCH_SESSION_NAME_PREFIX = "job-";
|
|
2394
2401
|
var CREATE_READY_SESSION_REFUSALS = [
|
|
2395
2402
|
"no_dispatch_agent",
|
|
2403
|
+
// amendment 63 (signed off by Wassim 2026-11-05): neither the seal's confirmer nor the dispatch agent's
|
|
2404
|
+
// creator is a user (NULL, empty or scheme-prefixed, such as github:<login>); nothing written
|
|
2405
|
+
"dispatch_owner_missing",
|
|
2396
2406
|
"seal_not_found",
|
|
2397
2407
|
"cell_unmapped",
|
|
2398
2408
|
// 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
|
|
2399
2409
|
"charter_mismatch"
|
|
2400
2410
|
];
|
|
2401
2411
|
var CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
|
|
2412
|
+
function isUserCreator(createdBy) {
|
|
2413
|
+
return typeof createdBy === "string" && createdBy !== "" && !createdBy.includes(":");
|
|
2414
|
+
}
|
|
2402
2415
|
var CREATE_READY_SESSION_ANSWERS = Object.freeze({
|
|
2403
2416
|
created: ["outcome", "session_id"],
|
|
2404
2417
|
existing: ["outcome", "session_id"],
|
|
@@ -4092,7 +4105,9 @@ var M1_COLUMNS = {
|
|
|
4092
4105
|
"last_seen_at",
|
|
4093
4106
|
"risk_line_version",
|
|
4094
4107
|
"created_at",
|
|
4095
|
-
"scope"
|
|
4108
|
+
"scope",
|
|
4109
|
+
"oidc_repo",
|
|
4110
|
+
"oidc_bound"
|
|
4096
4111
|
],
|
|
4097
4112
|
agent_durable_profiles: [
|
|
4098
4113
|
"id",
|
|
@@ -6572,6 +6587,7 @@ export {
|
|
|
6572
6587
|
RUNNER_OUTCOME_FIELDS,
|
|
6573
6588
|
RUNNER_REFUSED_REASONS,
|
|
6574
6589
|
SEALED_CONTRACT_FIELDS,
|
|
6590
|
+
SEAL_REPO_MISMATCH_CAUSES,
|
|
6575
6591
|
SEAL_VECTORS,
|
|
6576
6592
|
SEAT_DAEMON_FLAGS,
|
|
6577
6593
|
SEAT_MAX_ATTEMPTS_NONRETRY,
|
|
@@ -6776,6 +6792,7 @@ export {
|
|
|
6776
6792
|
isTerminalState,
|
|
6777
6793
|
isTestAllowlisted,
|
|
6778
6794
|
isUnchainedCountTokens,
|
|
6795
|
+
isUserCreator,
|
|
6779
6796
|
isValidCommitment,
|
|
6780
6797
|
isValidSessionName,
|
|
6781
6798
|
isValidStateReason,
|
|
@@ -60,7 +60,8 @@ export const M1_CLI_SURFACE = Object.freeze([
|
|
|
60
60
|
entry(["agent", "pause"], ["--json"]),
|
|
61
61
|
entry(["agent", "resume"], ["--json"]),
|
|
62
62
|
// Doors thin runner (W-L13), ruling 34.12 (1): --watch or --once, --fail-mode, --json, --oidc.
|
|
63
|
-
|
|
63
|
+
// --session (#1329, approved by the Durable lead): with --once, claim only the wake-up's session.
|
|
64
|
+
entry(["agent", "run"], ["--fail-mode", "--json", "--oidc", "--once", "--session", "--watch"]),
|
|
64
65
|
// BACKBONE C4 (line 147: buildDurableLaunchSpec serves 'sessions adopt'); options: C14 spec
|
|
65
66
|
// (`sessions adopt --dry-run --json`, `sessions adopt --own-agent`); ruling 32.33.
|
|
66
67
|
entry(["agent", "sessions", "adopt"], ["--dry-run", "--json", "--own-agent"]),
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const CLAIM_UNAVAILABLE_CAUSES: readonly ["schema", "arm_probe", "entitlement_lookup", "protection_probe", "guard", "lease_cap_config"];
|
|
10
10
|
export type ClaimUnavailableCause = (typeof CLAIM_UNAVAILABLE_CAUSES)[number];
|
|
11
|
+
/**
|
|
12
|
+
* seal_repo_mismatch details.cause (mnemom-test#1329; mnemom-api#4530 claim.ts oidcRepoBinding): the
|
|
13
|
+
* bound executor names no repo, the session has no dispatch seal naming a repo, or the seal names
|
|
14
|
+
* another repo than the executor's (owner/name, case-insensitive).
|
|
15
|
+
*/
|
|
16
|
+
export declare const SEAL_REPO_MISMATCH_CAUSES: readonly ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
|
|
17
|
+
export type SealRepoMismatchCause = (typeof SEAL_REPO_MISMATCH_CAUSES)[number];
|
|
11
18
|
export declare const UNSUPPORTED_FEATURES: readonly ["tool_classes", "launch_spec_change", "above_limit_raise", "remote_control"];
|
|
12
19
|
export type UnsupportedFeature = (typeof UNSUPPORTED_FEATURES)[number];
|
|
13
20
|
export declare const CWD_KINDS: readonly ["subdirectory", "not_git", "symlink_transcript"];
|
|
@@ -261,6 +268,11 @@ export declare const DURABLE_CODES: {
|
|
|
261
268
|
readonly surfaces: readonly ["api"];
|
|
262
269
|
readonly fields: readonly ["retry_after_s"];
|
|
263
270
|
};
|
|
271
|
+
readonly seal_repo_mismatch: {
|
|
272
|
+
readonly status: 403;
|
|
273
|
+
readonly surfaces: readonly ["api"];
|
|
274
|
+
readonly fields: readonly ["cause"];
|
|
275
|
+
};
|
|
264
276
|
/** Lost CAS race on (session_id, seq). */
|
|
265
277
|
readonly durable_append_conflict: {
|
|
266
278
|
readonly status: 409;
|
|
@@ -18,6 +18,12 @@ export const CLAIM_UNAVAILABLE_CAUSES = [
|
|
|
18
18
|
/** A pool claim with no valid registry default cap (ruling 34.05.1 (3)). */
|
|
19
19
|
"lease_cap_config",
|
|
20
20
|
];
|
|
21
|
+
/**
|
|
22
|
+
* seal_repo_mismatch details.cause (mnemom-test#1329; mnemom-api#4530 claim.ts oidcRepoBinding): the
|
|
23
|
+
* bound executor names no repo, the session has no dispatch seal naming a repo, or the seal names
|
|
24
|
+
* another repo than the executor's (owner/name, case-insensitive).
|
|
25
|
+
*/
|
|
26
|
+
export const SEAL_REPO_MISMATCH_CAUSES = ["no_repo_claim", "no_seal_repo", "repo_mismatch"];
|
|
21
27
|
export const UNSUPPORTED_FEATURES = [
|
|
22
28
|
"tool_classes",
|
|
23
29
|
"launch_spec_change",
|
|
@@ -134,6 +140,11 @@ export const DURABLE_CODES = {
|
|
|
134
140
|
// The per-org claim rate limit (ruling 34.24): nothing written, no key minted. retry_after_s is
|
|
135
141
|
// the number of seconds to wait (claimRateLimitedDetailsOk).
|
|
136
142
|
durable_claim_rate_limited: { status: 429, surfaces: ["api"], fields: ["retry_after_s"] },
|
|
143
|
+
// An OIDC executor key (bound to the repo whose GitHub Actions token minted it) claiming a session
|
|
144
|
+
// not sealed for that repo (mnemom-test#1329; mnemom-api#4530). Its own code, not a
|
|
145
|
+
// durable_claim_ineligible reason: the session is claimable, only not by this executor, and a
|
|
146
|
+
// runner never skips past it to another session. cause is a SEAL_REPO_MISMATCH_CAUSES value.
|
|
147
|
+
seal_repo_mismatch: { status: 403, surfaces: ["api"], fields: ["cause"] },
|
|
137
148
|
// ---- api journal (C10-R0 (i), (j), (k)) ----
|
|
138
149
|
/** Lost CAS race on (session_id, seq). */
|
|
139
150
|
durable_append_conflict: { status: 409, surfaces: ["api"], fields: ["tip_seq"] },
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* freeze record" are chosen here; each carries its reason. `freeze-record.ts` copies them into
|
|
5
5
|
* the JSON record the eval runner reads as `$S1.<NAME>`.
|
|
6
6
|
*/
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The contract version of this package. The per-file hashes in FREEZE.json are the change signal
|
|
9
|
+
* for S1; this number moves only on a breaking change to the contract's shape, by ruling.
|
|
10
|
+
* Precedent: amendments 59, 60 and 34.03.4 all changed FREEZE.json at 1.0.0 (lead ruling 34.35.1,
|
|
11
|
+
* 2026-11-05: the contract version is held at 1.0.0 while FREEZE hashes carry change; a bump is a
|
|
12
|
+
* separate ruling).
|
|
13
|
+
*/
|
|
8
14
|
export declare const DURABLE_CONTRACT_VERSION = "1.0.0";
|
|
9
15
|
/**
|
|
10
16
|
* `durable_schema_version()` must return exactly this. A mismatch makes the entitlement `durable`
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
* freeze record" are chosen here; each carries its reason. `freeze-record.ts` copies them into
|
|
6
6
|
* the JSON record the eval runner reads as `$S1.<NAME>`.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The contract version of this package. The per-file hashes in FREEZE.json are the change signal
|
|
10
|
+
* for S1; this number moves only on a breaking change to the contract's shape, by ruling.
|
|
11
|
+
* Precedent: amendments 59, 60 and 34.03.4 all changed FREEZE.json at 1.0.0 (lead ruling 34.35.1,
|
|
12
|
+
* 2026-11-05: the contract version is held at 1.0.0 while FREEZE hashes carry change; a bump is a
|
|
13
|
+
* separate ruling).
|
|
14
|
+
*/
|
|
9
15
|
export const DURABLE_CONTRACT_VERSION = "1.0.0";
|
|
10
16
|
/**
|
|
11
17
|
* `durable_schema_version()` must return exactly this. A mismatch makes the entitlement `durable`
|
|
@@ -60,8 +60,16 @@ export declare function effectiveLeaseCap(orgLimits: {
|
|
|
60
60
|
} | null, defaultCap: number | null): number;
|
|
61
61
|
export declare const DISPATCH_SESSION_CID_PREFIX = "dispatch-";
|
|
62
62
|
export declare const DISPATCH_SESSION_NAME_PREFIX = "job-";
|
|
63
|
-
export declare const CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "seal_not_found", "cell_unmapped", "charter_mismatch"];
|
|
63
|
+
export declare const CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "dispatch_owner_missing", "seal_not_found", "cell_unmapped", "charter_mismatch"];
|
|
64
64
|
export declare const CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused"];
|
|
65
|
+
/**
|
|
66
|
+
* Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
|
|
67
|
+
* api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
|
|
68
|
+
* public.durable_is_user_creator. A creator is a user unless it is null, empty or scheme-prefixed
|
|
69
|
+
* (contains ':', as github:<login> does). No id shape is read: uuids, Zitadel numeric ids and
|
|
70
|
+
* test-mode harness-<x> are users, so an identity-provider change does not move it.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isUserCreator(createdBy: unknown): boolean;
|
|
65
73
|
/** durable_create_ready_session's answer keys per outcome, closed (34.10 (g)(1)). */
|
|
66
74
|
export declare const CREATE_READY_SESSION_ANSWERS: Readonly<{
|
|
67
75
|
readonly created: readonly ["outcome", "session_id"];
|
|
@@ -91,12 +91,25 @@ export const DISPATCH_SESSION_CID_PREFIX = "dispatch-";
|
|
|
91
91
|
export const DISPATCH_SESSION_NAME_PREFIX = "job-";
|
|
92
92
|
export const CREATE_READY_SESSION_REFUSALS = [
|
|
93
93
|
"no_dispatch_agent",
|
|
94
|
+
// amendment 63 (signed off by Wassim 2026-11-05): neither the seal's confirmer nor the dispatch agent's
|
|
95
|
+
// creator is a user (NULL, empty or scheme-prefixed, such as github:<login>); nothing written
|
|
96
|
+
"dispatch_owner_missing",
|
|
94
97
|
"seal_not_found",
|
|
95
98
|
"cell_unmapped",
|
|
96
99
|
// 34.10.1 (4): the api-built charter v1 or its envelope does not match the seal; nothing written
|
|
97
100
|
"charter_mismatch",
|
|
98
101
|
];
|
|
99
102
|
export const CREATE_READY_SESSION_OUTCOMES = ["created", "existing", "refused"];
|
|
103
|
+
/**
|
|
104
|
+
* Amendment 63 (ruling 34.35): the one test for whether a session creator is a user, shared by the
|
|
105
|
+
* api (owner choice at dispatch, the claim's 403 not_owner, the repair) and its SQL twin
|
|
106
|
+
* public.durable_is_user_creator. A creator is a user unless it is null, empty or scheme-prefixed
|
|
107
|
+
* (contains ':', as github:<login> does). No id shape is read: uuids, Zitadel numeric ids and
|
|
108
|
+
* test-mode harness-<x> are users, so an identity-provider change does not move it.
|
|
109
|
+
*/
|
|
110
|
+
export function isUserCreator(createdBy) {
|
|
111
|
+
return typeof createdBy === "string" && createdBy !== "" && !createdBy.includes(":");
|
|
112
|
+
}
|
|
100
113
|
/** durable_create_ready_session's answer keys per outcome, closed (34.10 (g)(1)). */
|
|
101
114
|
export const CREATE_READY_SESSION_ANSWERS = Object.freeze({
|
|
102
115
|
created: ["outcome", "session_id"],
|
|
@@ -737,7 +737,7 @@ export declare const FREEZE_STRUCTURED: Readonly<{
|
|
|
737
737
|
OIDC_EXCHANGE_REFUSALS: readonly ["bad_issuer", "repo_not_enrolled", "ref_not_allowed", "pool_flag_off"];
|
|
738
738
|
ORG_POLICY_LIMIT_KEYS: readonly ["per_session_budget_usd", "live_lease_cap"];
|
|
739
739
|
POOL_CLAIM_FIELDS: readonly ["contract_sha256", "contract_version", "cap"];
|
|
740
|
-
CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "seal_not_found", "cell_unmapped", "charter_mismatch"];
|
|
740
|
+
CREATE_READY_SESSION_REFUSALS: readonly ["no_dispatch_agent", "dispatch_owner_missing", "seal_not_found", "cell_unmapped", "charter_mismatch"];
|
|
741
741
|
CREATE_READY_SESSION_OUTCOMES: readonly ["created", "existing", "refused"];
|
|
742
742
|
CREATE_READY_SESSION_ANSWERS: Readonly<{
|
|
743
743
|
readonly created: readonly ["outcome", "session_id"];
|
|
@@ -27,6 +27,15 @@
|
|
|
27
27
|
* - agent_sessions.job_failures (INTEGER NOT NULL DEFAULT 0, CHECK >= 0): the pool job's accepted
|
|
28
28
|
* runner_failed count, never reset. Separate from resume_failures, so a lease lapse never spends
|
|
29
29
|
* a job retry and a job retry never counts towards respawn_failed.
|
|
30
|
+
*
|
|
31
|
+
* OIDC repo binding (mnemom-test#1329; S1 amendment approved by the Durable lead; mnemom-api#4530
|
|
32
|
+
* migration durable_m1_doors_48_w17_oidc_repo_binding), appended to its table:
|
|
33
|
+
* - agent_executors.oidc_repo (TEXT NULL): the owner/name repo whose GitHub Actions token minted
|
|
34
|
+
* the executor in the OIDC exchange (durable_apply_oidc_enrollment); NULL on every other row;
|
|
35
|
+
* - agent_executors.oidc_bound (BOOLEAN NOT NULL DEFAULT false): true only on a row the OIDC
|
|
36
|
+
* exchange wrote with oidc_repo. A bound executor claims only sessions whose dispatch seal names
|
|
37
|
+
* that repo, else 403 seal_repo_mismatch (SEAL_REPO_MISMATCH_CAUSES); an unbound row (every
|
|
38
|
+
* row written before the migration) is not checked.
|
|
30
39
|
*/
|
|
31
40
|
export declare const M1_TABLES: readonly ["agent_sessions", "durable_lease_keys", "agent_session_charters", "agent_session_events", "durable_event_kinds", "agent_session_chains", "agent_executors", "agent_durable_profiles", "durable_cell_state", "durable_org_policy", "durable_user_policy", "durable_approvals", "durable_approval_attestations"];
|
|
32
41
|
export type M1Table = (typeof M1_TABLES)[number];
|
|
@@ -28,6 +28,15 @@
|
|
|
28
28
|
* - agent_sessions.job_failures (INTEGER NOT NULL DEFAULT 0, CHECK >= 0): the pool job's accepted
|
|
29
29
|
* runner_failed count, never reset. Separate from resume_failures, so a lease lapse never spends
|
|
30
30
|
* a job retry and a job retry never counts towards respawn_failed.
|
|
31
|
+
*
|
|
32
|
+
* OIDC repo binding (mnemom-test#1329; S1 amendment approved by the Durable lead; mnemom-api#4530
|
|
33
|
+
* migration durable_m1_doors_48_w17_oidc_repo_binding), appended to its table:
|
|
34
|
+
* - agent_executors.oidc_repo (TEXT NULL): the owner/name repo whose GitHub Actions token minted
|
|
35
|
+
* the executor in the OIDC exchange (durable_apply_oidc_enrollment); NULL on every other row;
|
|
36
|
+
* - agent_executors.oidc_bound (BOOLEAN NOT NULL DEFAULT false): true only on a row the OIDC
|
|
37
|
+
* exchange wrote with oidc_repo. A bound executor claims only sessions whose dispatch seal names
|
|
38
|
+
* that repo, else 403 seal_repo_mismatch (SEAL_REPO_MISMATCH_CAUSES); an unbound row (every
|
|
39
|
+
* row written before the migration) is not checked.
|
|
31
40
|
*/
|
|
32
41
|
import { canonicalJson, sha256Hex, UUID_RE } from "./canonical.js";
|
|
33
42
|
import { CANONICAL_CELL_IDS } from "./cell-id.js";
|
|
@@ -150,6 +159,8 @@ export const M1_COLUMNS = {
|
|
|
150
159
|
"risk_line_version",
|
|
151
160
|
"created_at",
|
|
152
161
|
"scope",
|
|
162
|
+
"oidc_repo",
|
|
163
|
+
"oidc_bound",
|
|
153
164
|
],
|
|
154
165
|
agent_durable_profiles: [
|
|
155
166
|
"id",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage caps and sampling. Port of mnemom-api src/org-scan/scan/capped-scan.ts (PR #4208).
|
|
3
|
+
*
|
|
4
|
+
* Coverage decision (Shraddha, 2026-10-28): each scan covers the last 3 months OR the last 2,000 merged
|
|
5
|
+
* PRs, whichever is MORE; above 2,000 in the window, an even deterministic sample of 2,000.
|
|
6
|
+
*/
|
|
7
|
+
export declare const COVERAGE_MONTHS = 3;
|
|
8
|
+
export interface ScanCaps {
|
|
9
|
+
maxPrs: number;
|
|
10
|
+
/** A PR with more additions+deletions than this is skipped, never read in full. */
|
|
11
|
+
maxPrSizeLines: number;
|
|
12
|
+
/** Upstream read calls. */
|
|
13
|
+
maxReads: number;
|
|
14
|
+
/** Wall-clock budget. */
|
|
15
|
+
maxMillis: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_SCAN_CAPS: ScanCaps;
|
|
18
|
+
export type StopReason = "pr-cap" | "read-cap" | "time-cap";
|
|
19
|
+
/** The day `months` calendar months before `end` (YYYY-MM-DD, UTC), clamped to the month's last day. */
|
|
20
|
+
export declare function monthsBefore(end: string, months: number): string;
|
|
21
|
+
/** `k` items spread evenly across `items` (order kept): index floor(i*n/k). Deterministic; n <= k returns all. */
|
|
22
|
+
export declare function evenSample<T>(items: readonly T[], k: number): T[];
|
|
23
|
+
export declare function sanitiseCaps(c: Partial<ScanCaps>): ScanCaps;
|
|
24
|
+
/** Running read budget. Every call is told how many reads it may spend (`room`). */
|
|
25
|
+
export declare class Budget {
|
|
26
|
+
private caps;
|
|
27
|
+
private now;
|
|
28
|
+
private startedAt;
|
|
29
|
+
reads: number;
|
|
30
|
+
constructor(caps: ScanCaps, now: () => number, startedAt: number);
|
|
31
|
+
block(n?: number): StopReason | null;
|
|
32
|
+
room(): number;
|
|
33
|
+
overrun(): StopReason | null;
|
|
34
|
+
charge(n: number): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// GENERATED from shared/org-scan-core/src/caps.ts by cli/scripts/sync-org-scan-core.mjs; do not edit.
|
|
2
|
+
/**
|
|
3
|
+
* Coverage caps and sampling. Port of mnemom-api src/org-scan/scan/capped-scan.ts (PR #4208).
|
|
4
|
+
*
|
|
5
|
+
* Coverage decision (Shraddha, 2026-10-28): each scan covers the last 3 months OR the last 2,000 merged
|
|
6
|
+
* PRs, whichever is MORE; above 2,000 in the window, an even deterministic sample of 2,000.
|
|
7
|
+
*/
|
|
8
|
+
export const COVERAGE_MONTHS = 3;
|
|
9
|
+
export const DEFAULT_SCAN_CAPS = {
|
|
10
|
+
maxPrs: 2_000,
|
|
11
|
+
maxPrSizeLines: 20_000,
|
|
12
|
+
maxReads: 4_500,
|
|
13
|
+
maxMillis: 15 * 60_000,
|
|
14
|
+
};
|
|
15
|
+
/** The day `months` calendar months before `end` (YYYY-MM-DD, UTC), clamped to the month's last day. */
|
|
16
|
+
export function monthsBefore(end, months) {
|
|
17
|
+
const [y, m, d] = end.split("-").map(Number);
|
|
18
|
+
const target = new Date(Date.UTC(y, m - 1 - months, 1));
|
|
19
|
+
const last = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
|
|
20
|
+
target.setUTCDate(Math.min(d, last));
|
|
21
|
+
return target.toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
/** `k` items spread evenly across `items` (order kept): index floor(i*n/k). Deterministic; n <= k returns all. */
|
|
24
|
+
export function evenSample(items, k) {
|
|
25
|
+
const n = items.length;
|
|
26
|
+
if (n <= k)
|
|
27
|
+
return [...items];
|
|
28
|
+
return Array.from({ length: k }, (_, i) => items[Math.floor((i * n) / k)]);
|
|
29
|
+
}
|
|
30
|
+
export function sanitiseCaps(c) {
|
|
31
|
+
const pos = (n, d) => typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : d;
|
|
32
|
+
return {
|
|
33
|
+
maxPrs: pos(c.maxPrs, DEFAULT_SCAN_CAPS.maxPrs),
|
|
34
|
+
maxPrSizeLines: pos(c.maxPrSizeLines, DEFAULT_SCAN_CAPS.maxPrSizeLines),
|
|
35
|
+
maxReads: pos(c.maxReads, DEFAULT_SCAN_CAPS.maxReads),
|
|
36
|
+
maxMillis: pos(c.maxMillis, DEFAULT_SCAN_CAPS.maxMillis),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Running read budget. Every call is told how many reads it may spend (`room`). */
|
|
40
|
+
export class Budget {
|
|
41
|
+
caps;
|
|
42
|
+
now;
|
|
43
|
+
startedAt;
|
|
44
|
+
reads = 0;
|
|
45
|
+
constructor(caps, now, startedAt) {
|
|
46
|
+
this.caps = caps;
|
|
47
|
+
this.now = now;
|
|
48
|
+
this.startedAt = startedAt;
|
|
49
|
+
}
|
|
50
|
+
block(n = 1) {
|
|
51
|
+
if (this.now() - this.startedAt >= this.caps.maxMillis)
|
|
52
|
+
return "time-cap";
|
|
53
|
+
if (this.reads + n > this.caps.maxReads)
|
|
54
|
+
return "read-cap";
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
room() {
|
|
58
|
+
if (this.now() - this.startedAt >= this.caps.maxMillis)
|
|
59
|
+
return 0;
|
|
60
|
+
return Math.max(0, this.caps.maxReads - this.reads);
|
|
61
|
+
}
|
|
62
|
+
overrun() {
|
|
63
|
+
return this.reads > this.caps.maxReads ? "read-cap" : null;
|
|
64
|
+
}
|
|
65
|
+
charge(n) {
|
|
66
|
+
this.reads += n;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost per merged PR and cost by PR size, for the Org Scan report. Pure: numbers in, numbers out.
|
|
3
|
+
*
|
|
4
|
+
* Source of the spend. Measured per-request cost cannot be tied to a PR with the data the scan has:
|
|
5
|
+
* the Anthropic usage reads return one whole-org total (no per-request or per-PR rows) and the Mnemom
|
|
6
|
+
* Record totals are one window sum (model cost and a request count, no PR key). So every figure here is
|
|
7
|
+
* `label: "estimate"`: the period's spend split across the merged PRs in proportion to lines changed.
|
|
8
|
+
* Each figure names the spend it was split from (`spend_source`); sources are never mixed in one report.
|
|
9
|
+
*
|
|
10
|
+
* anthropic_usage the whole-org Anthropic cost for the period (the CTO's admin key)
|
|
11
|
+
* modelled_range our modelled estimate (a low to high range, so the figures are ranges)
|
|
12
|
+
*
|
|
13
|
+
* Size is per PR, in lines changed = additions + deletions, exactly as GitHub reports them. Lockfiles and
|
|
14
|
+
* generated files are NOT excluded. small < 50; medium 50..500 inclusive; large > 500.
|
|
15
|
+
*
|
|
16
|
+
* Each size figure is the average cost of ONE PR of that size: spend * (mean lines of the bucket) /
|
|
17
|
+
* (total lines of the PRs in scope). Scope: the modelled estimate prices AI-assisted PRs only, so it is
|
|
18
|
+
* split across those only (`pr_scope: "ai_assisted"`, buckets too); the others use every merged PR
|
|
19
|
+
* (`pr_scope: "all_merged"`). `pr_count` counts PRs in that scope. Usage-key figures also carry
|
|
20
|
+
* `spend_scope: "whole_org"` (the key covers the whole organization, not just the scanned repos). The cost per merged PR is the spend over the count of merged PRs.
|
|
21
|
+
*
|
|
22
|
+
* Missing data is an absent field, never 0 or NaN: no usable spend, zero merged PRs in the period, an
|
|
23
|
+
* empty bucket, a scan that did not read every merged PR in the period, or a result past safe-integer
|
|
24
|
+
* range. Reverts are listed without sizes, so they are not counted as merged PRs here.
|
|
25
|
+
*/
|
|
26
|
+
import type { SpendEstimate } from "./spend/spend-estimate.js";
|
|
27
|
+
/** Strictly below this is small. 50 is medium. */
|
|
28
|
+
export declare const SMALL_MAX_EXCLUSIVE = 50;
|
|
29
|
+
/** At or below this (and not small) is medium. 500 is medium; 501 is large. */
|
|
30
|
+
export declare const MEDIUM_MAX_INCLUSIVE = 500;
|
|
31
|
+
export type SizeBucket = "small" | "medium" | "large";
|
|
32
|
+
export type CostSpendSourceName = "anthropic_usage" | "modelled_range";
|
|
33
|
+
export interface PrSize {
|
|
34
|
+
/** YYYY-MM-DD, UTC. */
|
|
35
|
+
mergedDay: string;
|
|
36
|
+
/** additions + deletions. */
|
|
37
|
+
lines: number;
|
|
38
|
+
/** The PR carries an AI-tool mark (the same rule the modelled spend uses to count AI-assisted PRs). */
|
|
39
|
+
aiAssisted: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** Where the spend comes from. USD, never cents. */
|
|
42
|
+
export type CostSpend = {
|
|
43
|
+
source: "anthropic_usage";
|
|
44
|
+
usd: number;
|
|
45
|
+
} | {
|
|
46
|
+
source: "modelled_range";
|
|
47
|
+
lowUsd: number;
|
|
48
|
+
highUsd: number;
|
|
49
|
+
};
|
|
50
|
+
/** One figure: a single value, or a low to high range for the modelled source. Integer cents. */
|
|
51
|
+
export type PrScope = "ai_assisted" | "all_merged";
|
|
52
|
+
export type CostFigure = {
|
|
53
|
+
/** "measured" is not possible with current data (see header); every figure is an estimate. */
|
|
54
|
+
label: "estimate";
|
|
55
|
+
spend_source: CostSpendSourceName;
|
|
56
|
+
/**
|
|
57
|
+
* Which PRs the average is over. The modelled estimate prices AI-assisted PRs only, so it is split
|
|
58
|
+
* across those only ("ai_assisted"); the usage key covers all spend, so they use "all_merged".
|
|
59
|
+
*/
|
|
60
|
+
pr_scope: PrScope;
|
|
61
|
+
/** PRs in that scope. */
|
|
62
|
+
pr_count: number;
|
|
63
|
+
/** Present on anthropic_usage only: the key's usage is the whole organization's, not just these PRs'. */
|
|
64
|
+
spend_scope?: "whole_org";
|
|
65
|
+
} & ({
|
|
66
|
+
value_cents: number;
|
|
67
|
+
} | {
|
|
68
|
+
low_cents: number;
|
|
69
|
+
high_cents: number;
|
|
70
|
+
});
|
|
71
|
+
export interface CostPerPr {
|
|
72
|
+
cost_per_merged_pr?: CostFigure;
|
|
73
|
+
cost_by_pr_size?: Partial<Record<SizeBucket, CostFigure>>;
|
|
74
|
+
}
|
|
75
|
+
export declare const sizeBucket: (lines: number) => SizeBucket;
|
|
76
|
+
/** Inclusive day count of YYYY-MM-DD..YYYY-MM-DD, or null if unparsable or reversed. */
|
|
77
|
+
export declare function periodDays(p: {
|
|
78
|
+
start: string;
|
|
79
|
+
end: string;
|
|
80
|
+
}): number | null;
|
|
81
|
+
/** The period's spend in USD as [low, high]; null when there is no usable spend. */
|
|
82
|
+
export declare function spendForPeriod(spend: CostSpend): [number, number] | null;
|
|
83
|
+
/** The modelled estimate as a spend source, or null when the estimate is "none". */
|
|
84
|
+
export declare const modelledSpend: (e: SpendEstimate) => CostSpend | null;
|
|
85
|
+
/**
|
|
86
|
+
* @param prs every merged PR the scan read, with merge day and size
|
|
87
|
+
* @param exhaustive true only if the scan read EVERY merged PR in the period (no sample, no skipped
|
|
88
|
+
* large PR, none unreadable, no cap hit). Otherwise the average is not defined.
|
|
89
|
+
*/
|
|
90
|
+
export declare function buildCostPerPr(prs: readonly PrSize[] | undefined, period: {
|
|
91
|
+
start: string;
|
|
92
|
+
end: string;
|
|
93
|
+
}, spend: CostSpend | null, exhaustive: boolean): CostPerPr;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Strictly below this is small. 50 is medium. */
|
|
2
|
+
export const SMALL_MAX_EXCLUSIVE = 50;
|
|
3
|
+
/** At or below this (and not small) is medium. 500 is medium; 501 is large. */
|
|
4
|
+
export const MEDIUM_MAX_INCLUSIVE = 500;
|
|
5
|
+
export const sizeBucket = (lines) => lines < SMALL_MAX_EXCLUSIVE ? "small" : lines <= MEDIUM_MAX_INCLUSIVE ? "medium" : "large";
|
|
6
|
+
const DAY_MS = 86_400_000;
|
|
7
|
+
/** Inclusive day count of YYYY-MM-DD..YYYY-MM-DD, or null if unparsable or reversed. */
|
|
8
|
+
export function periodDays(p) {
|
|
9
|
+
const s = Date.parse(`${p.start}T00:00:00Z`);
|
|
10
|
+
const e = Date.parse(`${p.end}T00:00:00Z`);
|
|
11
|
+
if (!Number.isFinite(s) || !Number.isFinite(e) || e < s)
|
|
12
|
+
return null;
|
|
13
|
+
return Math.round((e - s) / DAY_MS) + 1;
|
|
14
|
+
}
|
|
15
|
+
const pos = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
|
|
16
|
+
/** The period's spend in USD as [low, high]; null when there is no usable spend. */
|
|
17
|
+
export function spendForPeriod(spend) {
|
|
18
|
+
let r;
|
|
19
|
+
if (spend.source === "anthropic_usage") {
|
|
20
|
+
if (!pos(spend.usd))
|
|
21
|
+
return null;
|
|
22
|
+
r = [spend.usd, spend.usd];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
if (!pos(spend.highUsd) || !(spend.lowUsd >= 0) || spend.lowUsd > spend.highUsd)
|
|
26
|
+
return null;
|
|
27
|
+
r = [spend.lowUsd, spend.highUsd];
|
|
28
|
+
}
|
|
29
|
+
return r.every((x) => Number.isFinite(x) && x * 100 <= Number.MAX_SAFE_INTEGER) ? r : null;
|
|
30
|
+
}
|
|
31
|
+
/** The modelled estimate as a spend source, or null when the estimate is "none". */
|
|
32
|
+
export const modelledSpend = (e) => e.kind === "range" ? { source: "modelled_range", lowUsd: e.lowUsd, highUsd: e.highUsd } : null;
|
|
33
|
+
/**
|
|
34
|
+
* `share` is the fraction of the period's spend one average PR here carries: always in (0, 1] by
|
|
35
|
+
* construction, and the spend is already bounded to safe-integer cents by spendForPeriod, so the
|
|
36
|
+
* rounded cents are finite safe integers without a further guard.
|
|
37
|
+
*/
|
|
38
|
+
function figure(spend, [low, high], count, share, scope) {
|
|
39
|
+
const c = (usd) => Math.round(usd * share * 100);
|
|
40
|
+
const base = {
|
|
41
|
+
label: "estimate",
|
|
42
|
+
spend_source: spend.source,
|
|
43
|
+
pr_scope: scope,
|
|
44
|
+
pr_count: count,
|
|
45
|
+
...(spend.source === "anthropic_usage" ? { spend_scope: "whole_org" } : {}),
|
|
46
|
+
};
|
|
47
|
+
return spend.source === "modelled_range"
|
|
48
|
+
? { ...base, low_cents: c(low), high_cents: c(high) }
|
|
49
|
+
: { ...base, value_cents: c(low) };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @param prs every merged PR the scan read, with merge day and size
|
|
53
|
+
* @param exhaustive true only if the scan read EVERY merged PR in the period (no sample, no skipped
|
|
54
|
+
* large PR, none unreadable, no cap hit). Otherwise the average is not defined.
|
|
55
|
+
*/
|
|
56
|
+
export function buildCostPerPr(prs, period, spend, exhaustive) {
|
|
57
|
+
if (!exhaustive || !spend || !Array.isArray(prs))
|
|
58
|
+
return {};
|
|
59
|
+
if (periodDays(period) === null)
|
|
60
|
+
return {};
|
|
61
|
+
const total = spendForPeriod(spend);
|
|
62
|
+
if (!total)
|
|
63
|
+
return {};
|
|
64
|
+
const inPeriod = prs.filter((p) => p.mergedDay >= period.start &&
|
|
65
|
+
p.mergedDay <= period.end &&
|
|
66
|
+
Number.isSafeInteger(p.lines) &&
|
|
67
|
+
p.lines >= 0);
|
|
68
|
+
// The modelled estimate is AI-tool spend, so it is split across AI-assisted PRs only; the usage key is
|
|
69
|
+
// the whole spend, so it is split across every merged PR.
|
|
70
|
+
const scope = spend.source === "modelled_range" ? "ai_assisted" : "all_merged";
|
|
71
|
+
const scoped = scope === "ai_assisted" ? inPeriod.filter((p) => p.aiAssisted) : inPeriod;
|
|
72
|
+
const n = scoped.length;
|
|
73
|
+
if (n === 0)
|
|
74
|
+
return {};
|
|
75
|
+
const out = {};
|
|
76
|
+
out.cost_per_merged_pr = figure(spend, total, n, 1 / n, scope);
|
|
77
|
+
let allLines = 0;
|
|
78
|
+
const agg = {
|
|
79
|
+
small: { n: 0, lines: 0 },
|
|
80
|
+
medium: { n: 0, lines: 0 },
|
|
81
|
+
large: { n: 0, lines: 0 },
|
|
82
|
+
};
|
|
83
|
+
for (const p of scoped) {
|
|
84
|
+
allLines += p.lines;
|
|
85
|
+
const a = agg[sizeBucket(p.lines)];
|
|
86
|
+
a.n += 1;
|
|
87
|
+
a.lines += p.lines;
|
|
88
|
+
}
|
|
89
|
+
// Splitting by lines needs lines: with none (or a sum past safe range) there is no size figure.
|
|
90
|
+
if (allLines <= 0 || !Number.isSafeInteger(allLines))
|
|
91
|
+
return out;
|
|
92
|
+
const bySize = {};
|
|
93
|
+
for (const b of ["small", "medium", "large"]) {
|
|
94
|
+
const a = agg[b];
|
|
95
|
+
if (a.n === 0)
|
|
96
|
+
continue;
|
|
97
|
+
bySize[b] = figure(spend, total, a.n, a.lines / a.n / allLines, scope);
|
|
98
|
+
}
|
|
99
|
+
out.cost_by_pr_size = bySize;
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHubCredential: the one seam through which a scan obtains a GitHub token.
|
|
3
|
+
*
|
|
4
|
+
* The scan core only ever sees this interface. The CLI's default is the GitHub login of whoever runs the
|
|
5
|
+
* scan (`gh auth token`, cli/src/lib/scan/credential.ts); the Mnemom-side run (mnemom-api, MNE-9743)
|
|
6
|
+
* supplies a read-only installation token. Port of the read-only rule in mnemom-api
|
|
7
|
+
* src/org-scan/github-scan-app.ts. Runtime-neutral: no Node or Worker API.
|
|
8
|
+
*/
|
|
9
|
+
/** The complete permission set the CLI's scan credential may hold. Read only. */
|
|
10
|
+
export declare const SCAN_READ_PERMISSIONS: Readonly<Record<string, "read">>;
|
|
11
|
+
/**
|
|
12
|
+
* The complete permission set the Mnemom-side run's App token may hold (org-scan threat model): no
|
|
13
|
+
* `contents`. `checks` is what the check-runs read needs on an App installation token.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SERVER_SCAN_READ_PERMISSIONS: Readonly<Record<string, "read">>;
|
|
16
|
+
export interface GitHubCredential {
|
|
17
|
+
/** Fixed label for diagnostics ("app", "personal"): never a token, login or URL. */
|
|
18
|
+
readonly kind: string;
|
|
19
|
+
/** A bearer token for reading `repo` (owner/name). A rejection fails that read closed. */
|
|
20
|
+
tokenFor(repo: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* The permissions GitHub granted this credential, when the source can tell. Null = unknown (for
|
|
23
|
+
* example a classic token); the scan then relies on GET-only egress and does not claim more.
|
|
24
|
+
*/
|
|
25
|
+
grantedPermissions?(): Promise<Record<string, string> | null>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Violations (empty when the map is exactly the read-only set `allowed`, by default the CLI's set).
|
|
29
|
+
* Port of readOnlyViolations.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readOnlyViolations(permissions: Record<string, string> | null | undefined, allowed?: Readonly<Record<string, "read">>): string[];
|
|
32
|
+
/** owner/name, no dot-only segment. */
|
|
33
|
+
export declare function isValidRepoSlug(s: unknown): s is string;
|
|
34
|
+
/** Fixed-text failure: the message never carries a token, a body or a URL. */
|
|
35
|
+
export declare class ScanCredentialError extends Error {
|
|
36
|
+
constructor(message: "scan credential refused" | "scan credential not configured");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Refuses a credential GitHub granted anything other than exactly `allowed` (read only) to. Throws a
|
|
40
|
+
* fixed-text error and nothing else. Unknown (null) permissions pass: the source cannot tell, and the
|
|
41
|
+
* scan is GET-only regardless.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertReadOnlyCredential(c: GitHubCredential, allowed?: Readonly<Record<string, "read">>): Promise<void>;
|