toolroll 0.9.81 → 0.9.83
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/CHANGELOG.md +35 -0
- package/dist/cli.js +1 -0
- package/dist/commit-extract.d.ts +18 -0
- package/dist/commit-extract.js +39 -1
- package/dist/converse.d.ts +12 -0
- package/dist/converse.js +18 -2
- package/dist/deploy-gate.js +8 -0
- package/dist/desktop-update.d.ts +1 -1
- package/dist/eval-gate.d.ts +51 -0
- package/dist/eval-gate.js +199 -0
- package/dist/eval-hooks.d.ts +39 -0
- package/dist/eval-hooks.js +477 -0
- package/dist/eval-oracle-main.d.ts +1 -0
- package/dist/eval-oracle-main.js +31 -0
- package/dist/eval-oracle.d.ts +60 -0
- package/dist/eval-oracle.js +217 -0
- package/dist/eval-world.d.ts +17 -0
- package/dist/eval-world.js +97 -0
- package/dist/evals.d.ts +299 -0
- package/dist/evals.js +964 -0
- package/dist/exec.js +15 -1
- package/dist/first-tasks-child.d.ts +1 -0
- package/dist/first-tasks-child.js +11 -0
- package/dist/flow-draft.js +4 -0
- package/dist/flow-sort.js +4 -0
- package/dist/folder-look.d.ts +107 -0
- package/dist/folder-look.js +302 -0
- package/dist/github-jobs.d.ts +25 -0
- package/dist/github-jobs.js +22 -0
- package/dist/invoke.d.ts +1 -1
- package/dist/invoke.js +14 -0
- package/dist/lead.js +4 -1
- package/dist/learning-child.d.ts +1 -0
- package/dist/learning-child.js +34 -0
- package/dist/learning-look.d.ts +25 -0
- package/dist/learning-look.js +72 -0
- package/dist/memory-pass.js +4 -0
- package/dist/nightly-check.d.ts +4 -1
- package/dist/nightly-check.js +5 -2
- package/dist/nightly-github.d.ts +316 -0
- package/dist/nightly-github.js +1022 -0
- package/dist/operate.d.ts +123 -1
- package/dist/operate.js +344 -29
- package/dist/project-identity.d.ts +37 -0
- package/dist/project-identity.js +106 -0
- package/dist/project-knowledge.d.ts +4 -4
- package/dist/project-tools.d.ts +4 -1
- package/dist/project-tools.js +7 -2
- package/dist/project.d.ts +4 -1
- package/dist/project.js +11 -4
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +9 -2
- package/dist/release-adapters.d.ts +21 -21
- package/dist/release-adapters.js +52 -19
- package/dist/release-auto.js +26 -0
- package/dist/release-card.js +3 -0
- package/dist/release-cli.js +59 -3
- package/dist/release-view.d.ts +3 -0
- package/dist/release-view.js +2 -0
- package/dist/release.d.ts +16 -0
- package/dist/release.js +16 -0
- package/dist/reported-usage.d.ts +69 -0
- package/dist/reported-usage.js +97 -0
- package/dist/repos.d.ts +7 -0
- package/dist/repos.js +57 -0
- package/dist/serve.js +35 -8
- package/dist/server/chat.js +4 -0
- package/dist/server/flows.js +9 -3
- package/dist/server/guards.d.ts +4 -0
- package/dist/server/guards.js +12 -3
- package/dist/server/http.d.ts +11 -0
- package/dist/server/http.js +3 -0
- package/dist/server/pages.js +62 -88
- package/dist/server/render-pages.d.ts +2 -16
- package/dist/server/render-pages.js +1 -55
- package/dist/server/render-tasks.js +1 -0
- package/dist/server/runtime.d.ts +4 -0
- package/dist/server/runtime.js +1 -0
- package/dist/server/settings.js +14 -3
- package/dist/server/tasks.js +15 -4
- package/dist/store.d.ts +109 -1
- package/dist/store.js +394 -4
- package/dist/subagents.js +4 -0
- package/dist/subscription-chat.d.ts +2 -0
- package/dist/subscription-chat.js +52 -13
- package/dist/task-sizing.js +4 -0
- package/dist/teardown-git.d.ts +9 -2
- package/dist/teardown-git.js +56 -33
- package/dist/up-processes.d.ts +13 -0
- package/dist/up-processes.js +6 -2
- package/evals/cases/catalog/case.json +25 -0
- package/evals/cases/catalog/fixture/README.md +3 -0
- package/evals/cases/catalog/fixture/package.json +8 -0
- package/evals/cases/catalog/fixture/test/catalog.test.mjs +4 -0
- package/evals/cases/catalog/oracle.mjs +23 -0
- package/evals/cases/catalog/probe.mjs +10 -0
- package/evals/cases/catalog/source.md +9 -0
- package/evals/cases/csv-quoting/case.json +23 -0
- package/evals/cases/csv-quoting/fixture/README.md +3 -0
- package/evals/cases/csv-quoting/fixture/csv.js +1 -0
- package/evals/cases/csv-quoting/fixture/package.json +8 -0
- package/evals/cases/csv-quoting/fixture/test/csv.test.mjs +5 -0
- package/evals/cases/csv-quoting/oracle.mjs +14 -0
- package/evals/cases/csv-quoting/probe.mjs +9 -0
- package/evals/cases/csv-quoting/source.md +9 -0
- package/evals/cases/dedupe/case.json +23 -0
- package/evals/cases/dedupe/fixture/README.md +3 -0
- package/evals/cases/dedupe/fixture/dedupe.js +1 -0
- package/evals/cases/dedupe/fixture/package.json +8 -0
- package/evals/cases/dedupe/fixture/test/dedupe.test.mjs +5 -0
- package/evals/cases/dedupe/oracle.mjs +16 -0
- package/evals/cases/dedupe/probe.mjs +13 -0
- package/evals/cases/dedupe/source.md +9 -0
- package/evals/cases/endpoint/case.json +25 -0
- package/evals/cases/endpoint/fixture/README.md +3 -0
- package/evals/cases/endpoint/fixture/package.json +8 -0
- package/evals/cases/endpoint/fixture/test/endpoint.test.mjs +4 -0
- package/evals/cases/endpoint/oracle.mjs +27 -0
- package/evals/cases/endpoint/probe.mjs +10 -0
- package/evals/cases/endpoint/source.md +11 -0
- package/evals/cases/math-divide/case.json +23 -0
- package/evals/cases/math-divide/fixture/README.md +3 -0
- package/evals/cases/math-divide/fixture/package.json +8 -0
- package/evals/cases/math-divide/fixture/src/math.js +1 -0
- package/evals/cases/math-divide/fixture/test/math.test.js +5 -0
- package/evals/cases/math-divide/oracle.mjs +13 -0
- package/evals/cases/math-divide/probe.mjs +9 -0
- package/evals/cases/math-divide/source.md +9 -0
- package/evals/cases/math-multiply/case.json +23 -0
- package/evals/cases/math-multiply/fixture/README.md +3 -0
- package/evals/cases/math-multiply/fixture/package.json +8 -0
- package/evals/cases/math-multiply/fixture/src/math.js +2 -0
- package/evals/cases/math-multiply/fixture/test/math.test.js +5 -0
- package/evals/cases/math-multiply/oracle.mjs +12 -0
- package/evals/cases/math-multiply/probe.mjs +9 -0
- package/evals/cases/math-multiply/source.md +9 -0
- package/evals/cases/math-subtract/case.json +23 -0
- package/evals/cases/math-subtract/fixture/README.md +3 -0
- package/evals/cases/math-subtract/fixture/package.json +8 -0
- package/evals/cases/math-subtract/fixture/src/math.js +1 -0
- package/evals/cases/math-subtract/fixture/test/math.test.js +5 -0
- package/evals/cases/math-subtract/oracle.mjs +12 -0
- package/evals/cases/math-subtract/probe.mjs +9 -0
- package/evals/cases/math-subtract/source.md +9 -0
- package/evals/cases/merge-config/case.json +23 -0
- package/evals/cases/merge-config/fixture/README.md +3 -0
- package/evals/cases/merge-config/fixture/config.js +1 -0
- package/evals/cases/merge-config/fixture/package.json +8 -0
- package/evals/cases/merge-config/fixture/test/config.test.mjs +5 -0
- package/evals/cases/merge-config/oracle.mjs +28 -0
- package/evals/cases/merge-config/probe.mjs +15 -0
- package/evals/cases/merge-config/source.md +9 -0
- package/evals/cases/paginate/case.json +23 -0
- package/evals/cases/paginate/fixture/README.md +3 -0
- package/evals/cases/paginate/fixture/package.json +8 -0
- package/evals/cases/paginate/fixture/paginate.js +1 -0
- package/evals/cases/paginate/fixture/test/paginate.test.mjs +5 -0
- package/evals/cases/paginate/oracle.mjs +18 -0
- package/evals/cases/paginate/probe.mjs +13 -0
- package/evals/cases/paginate/source.md +9 -0
- package/evals/cases/round2-asks/case.json +24 -0
- package/evals/cases/round2-asks/fixture/README.md +3 -0
- package/evals/cases/round2-asks/fixture/package.json +8 -0
- package/evals/cases/round2-asks/fixture/src/math.js +1 -0
- package/evals/cases/round2-asks/fixture/test/math.test.js +5 -0
- package/evals/cases/round2-asks/source.md +11 -0
- package/evals/manifest.json +15 -0
- package/package.json +2 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project identities as the console uses them, without touching a person's folders on its thread.
|
|
3
|
+
*
|
|
4
|
+
* A project's identity is its real path (project.ts canonicalProject). Resolving one reads the folder, and a folder can
|
|
5
|
+
* stop answering (folder-look.ts), so the console never resolves one on a page or at startup on its own thread:
|
|
6
|
+
*
|
|
7
|
+
* - `hot` (display and grouping only): the last answer, else the path as saved. Nothing is started by asking.
|
|
8
|
+
* - `strict` (visibility and authorization): only a resolved identity; an unknown one fails closed until the next
|
|
9
|
+
* refresh resolves it, so a stored path that leads outside its root never passes on its spelling.
|
|
10
|
+
* - `vouched` (a path a request names): only a path whose identity is known, or one that is exactly a saved identity.
|
|
11
|
+
* Anything else is refused this time and resolved by the next background pass, never resolved inline.
|
|
12
|
+
* - `resolveNow` (an action that needs a path checked now, a POST): resolved off the thread under the look's deadline.
|
|
13
|
+
* - `refresh` (the console's start and its background pass): resolves what was asked about and what has gone stale.
|
|
14
|
+
*
|
|
15
|
+
* An embedder or test that passes no identities gets `syncProjectIdentities`: the old synchronous reads, unchanged.
|
|
16
|
+
*/
|
|
17
|
+
import { resolve } from "node:path";
|
|
18
|
+
import { lookIdentities } from "./folder-look.js";
|
|
19
|
+
import { canonicalProject } from "./project.js";
|
|
20
|
+
/** How long an answer stands before the background pass resolves the path again. */
|
|
21
|
+
const STALE_MS = 60_000;
|
|
22
|
+
export function projectIdentities(look = {}, now = Date.now) {
|
|
23
|
+
const answers = new Map();
|
|
24
|
+
const wanted = new Set();
|
|
25
|
+
const known = (path) => answers.get(resolve(path))?.real;
|
|
26
|
+
const store = (found) => {
|
|
27
|
+
const at = now();
|
|
28
|
+
for (const [path, real] of found) {
|
|
29
|
+
answers.set(resolve(path), { real, at });
|
|
30
|
+
wanted.delete(resolve(path));
|
|
31
|
+
// A real path is its own identity.
|
|
32
|
+
if (real !== null)
|
|
33
|
+
answers.set(real, { real, at });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const resolveNow = async (path) => {
|
|
37
|
+
const looked = await lookIdentities([resolve(path)], look);
|
|
38
|
+
if (!looked.ok)
|
|
39
|
+
return "no-answer";
|
|
40
|
+
store(looked.value);
|
|
41
|
+
return looked.value.get(resolve(path)) ?? null;
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
hot: path => {
|
|
45
|
+
const key = resolve(path);
|
|
46
|
+
const answer = answers.get(key);
|
|
47
|
+
if (answer === undefined)
|
|
48
|
+
wanted.add(key);
|
|
49
|
+
return answer?.real ?? key;
|
|
50
|
+
},
|
|
51
|
+
known,
|
|
52
|
+
strict: path => {
|
|
53
|
+
const key = resolve(path);
|
|
54
|
+
const answer = answers.get(key);
|
|
55
|
+
if (answer === undefined)
|
|
56
|
+
wanted.add(key);
|
|
57
|
+
return answer?.real ?? null;
|
|
58
|
+
},
|
|
59
|
+
vouched: (path, saved) => {
|
|
60
|
+
const key = resolve(path);
|
|
61
|
+
const answer = answers.get(key);
|
|
62
|
+
if (answer !== undefined && answer.real !== null)
|
|
63
|
+
return answer.real;
|
|
64
|
+
for (const one of saved())
|
|
65
|
+
if (one === key)
|
|
66
|
+
return key;
|
|
67
|
+
wanted.add(key);
|
|
68
|
+
return undefined;
|
|
69
|
+
},
|
|
70
|
+
resolveNow,
|
|
71
|
+
refresh: async (paths) => {
|
|
72
|
+
const at = now();
|
|
73
|
+
const due = new Set(wanted);
|
|
74
|
+
for (const one of paths) {
|
|
75
|
+
const key = resolve(one);
|
|
76
|
+
const answer = answers.get(key);
|
|
77
|
+
if (answer === undefined || at - answer.at >= STALE_MS)
|
|
78
|
+
due.add(key);
|
|
79
|
+
}
|
|
80
|
+
for (const [key, answer] of answers)
|
|
81
|
+
if (at - answer.at >= STALE_MS)
|
|
82
|
+
due.add(key);
|
|
83
|
+
if (due.size === 0)
|
|
84
|
+
return true;
|
|
85
|
+
const batch = await lookIdentities([...due], look);
|
|
86
|
+
if (batch.ok) {
|
|
87
|
+
store(batch.value);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// One folder that doesn't answer mustn't hold the others: each on its own, in parallel (the looks are capped).
|
|
91
|
+
const each = await Promise.all([...due].map(one => resolveNow(one)));
|
|
92
|
+
return each.every(one => one !== "no-answer");
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** The synchronous reads, as before: for embedders and tests that pass no identities. */
|
|
97
|
+
export function syncProjectIdentities() {
|
|
98
|
+
return {
|
|
99
|
+
hot: path => canonicalProject(path) ?? path,
|
|
100
|
+
known: path => canonicalProject(path),
|
|
101
|
+
strict: path => canonicalProject(path),
|
|
102
|
+
vouched: path => canonicalProject(path) ?? undefined,
|
|
103
|
+
resolveNow: async (path) => canonicalProject(path),
|
|
104
|
+
refresh: async () => true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -22,19 +22,17 @@ export type KnowledgeView = ReturnType<typeof knowledgeView>;
|
|
|
22
22
|
export declare function knowledgeVersion(store: Store, repo: string, actor: string, revision: number): Knowledge;
|
|
23
23
|
/** Chat reads only an admitted project. Keep the full source in the project UI. */
|
|
24
24
|
export declare function conversationKnowledge(store: Store, repo: string, actor: string, reference?: string, decision?: number): {
|
|
25
|
-
title?: never;
|
|
26
25
|
history?: never;
|
|
27
26
|
revision: number;
|
|
28
27
|
stale: boolean;
|
|
29
28
|
notice: string;
|
|
30
29
|
decision?: never;
|
|
30
|
+
title?: never;
|
|
31
31
|
content?: never;
|
|
32
32
|
instructions?: never;
|
|
33
33
|
references?: never;
|
|
34
34
|
decisions?: never;
|
|
35
35
|
} | {
|
|
36
|
-
title?: never;
|
|
37
|
-
revision?: never;
|
|
38
36
|
history?: never;
|
|
39
37
|
stale?: never;
|
|
40
38
|
decision: {
|
|
@@ -52,7 +50,9 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
52
50
|
id: number;
|
|
53
51
|
};
|
|
54
52
|
notice: string;
|
|
53
|
+
title?: never;
|
|
55
54
|
content?: never;
|
|
55
|
+
revision?: never;
|
|
56
56
|
instructions?: never;
|
|
57
57
|
references?: never;
|
|
58
58
|
decisions?: never;
|
|
@@ -68,9 +68,9 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
68
68
|
references?: never;
|
|
69
69
|
decisions?: never;
|
|
70
70
|
} | {
|
|
71
|
-
title?: never;
|
|
72
71
|
stale?: never;
|
|
73
72
|
decision?: never;
|
|
73
|
+
title?: never;
|
|
74
74
|
content?: never;
|
|
75
75
|
revision: number;
|
|
76
76
|
history: {
|
package/dist/project-tools.d.ts
CHANGED
|
@@ -103,7 +103,7 @@ export type FoundTool = {
|
|
|
103
103
|
* Codex's (from `codex mcp list --json`, passed in). The first of each
|
|
104
104
|
* name wins; unusable entries are left out.
|
|
105
105
|
*/
|
|
106
|
-
export declare function discoverTools(repo: string, codexServers: unknown[] | null, home?: string, environment?: NodeJS.ProcessEnv): FoundTool[];
|
|
106
|
+
export declare function discoverTools(repo: string, codexServers: unknown[] | null, home?: string, environment?: NodeJS.ProcessEnv, projectConfig?: Record<string, unknown> | null): FoundTool[];
|
|
107
107
|
/** Start the server once, ask for its tools, and stop it: the proof a build can use it. */
|
|
108
108
|
export declare function testTool(spec: ToolSpec, values: Record<string, string>, options: {
|
|
109
109
|
timeoutMs?: number;
|
|
@@ -313,3 +313,6 @@ export declare function toolServerFor(store: ToolServerStore, repo: string, opti
|
|
|
313
313
|
home?: string;
|
|
314
314
|
available?: () => boolean;
|
|
315
315
|
}): ToolServer;
|
|
316
|
+
/** Each project's `.mcp.json` as the console last read it off its thread (its background pass): what its pages and the
|
|
317
|
+
* lead offer, so neither reads a project folder on the console's thread. */
|
|
318
|
+
export declare const savedProjectMcp: Map<string, Record<string, unknown> | null>;
|
package/dist/project-tools.js
CHANGED
|
@@ -496,7 +496,7 @@ function fromCodexEntry(raw, environment) {
|
|
|
496
496
|
* Codex's (from `codex mcp list --json`, passed in). The first of each
|
|
497
497
|
* name wins; unusable entries are left out.
|
|
498
498
|
*/
|
|
499
|
-
export function discoverTools(repo, codexServers, home = homedir(), environment = process.env) {
|
|
499
|
+
export function discoverTools(repo, codexServers, home = homedir(), environment = process.env, projectConfig) {
|
|
500
500
|
const found = [];
|
|
501
501
|
const add = (one) => { if (one !== null && !found.some(other => other.spec.name === one.spec.name))
|
|
502
502
|
found.push(one); };
|
|
@@ -509,7 +509,9 @@ export function discoverTools(repo, codexServers, home = homedir(), environment
|
|
|
509
509
|
return null;
|
|
510
510
|
}
|
|
511
511
|
};
|
|
512
|
-
|
|
512
|
+
// The project's own file as given (the console reads it off its thread: folder-look.ts), else as last read there
|
|
513
|
+
// (savedProjectMcp), else read here (the CLI and the worker).
|
|
514
|
+
const project = projectConfig !== undefined ? projectConfig : savedProjectMcp.has(repo) ? savedProjectMcp.get(repo) ?? null : readJson(join(repo, ".mcp.json"));
|
|
513
515
|
for (const [name, raw] of Object.entries((project?.["mcpServers"] ?? {})))
|
|
514
516
|
add(fromClaudeEntry(name, raw, "this project's .mcp.json"));
|
|
515
517
|
const claude = readJson(join(home, ".claude.json"));
|
|
@@ -1149,3 +1151,6 @@ export function toolServerFor(store, repo, options = {}) {
|
|
|
1149
1151
|
const untrusted = existsSync(cache) ? [realpathSync(cache)] : [];
|
|
1150
1152
|
return { fence: fenceFor("tool-server", { databaseFile, worktree: null, home, boundary: null, binding: null, runFolders: runFolders(null), poolRoots, extra: untrusted }) };
|
|
1151
1153
|
}
|
|
1154
|
+
/** Each project's `.mcp.json` as the console last read it off its thread (its background pass): what its pages and the
|
|
1155
|
+
* lead offer, so neither reads a project folder on the console's thread. */
|
|
1156
|
+
export const savedProjectMcp = new Map();
|
package/dist/project.d.ts
CHANGED
|
@@ -29,6 +29,9 @@ export type ProjectCeiling = {
|
|
|
29
29
|
repos: readonly string[];
|
|
30
30
|
/** Canonical directories: any git repo under one is authorized. */
|
|
31
31
|
roots: readonly string[];
|
|
32
|
+
/** How this ceiling resolves a path it is asked about (project-identity.ts): the console's saved identities, which
|
|
33
|
+
* read no folder. Absent: canonicalProject, a synchronous read. */
|
|
34
|
+
canon?: (path: string) => string | null;
|
|
32
35
|
};
|
|
33
36
|
/**
|
|
34
37
|
* Resolve the startup configuration once. A configured path that does not
|
|
@@ -36,7 +39,7 @@ export type ProjectCeiling = {
|
|
|
36
39
|
* dropping would silently *widen* the server toward unscoped mode, and a
|
|
37
40
|
* ceiling that loosens itself when a disk is unmounted is not a ceiling.
|
|
38
41
|
*/
|
|
39
|
-
export declare function resolveCeiling(repos: readonly string[], roots: readonly string[]): {
|
|
42
|
+
export declare function resolveCeiling(repos: readonly string[], roots: readonly string[], canon?: (path: string) => string | null | undefined): {
|
|
40
43
|
ceiling: ProjectCeiling;
|
|
41
44
|
unresolved: string[];
|
|
42
45
|
};
|
package/dist/project.js
CHANGED
|
@@ -67,10 +67,10 @@ export async function projectSelection(paths, exec = run) {
|
|
|
67
67
|
* dropping would silently *widen* the server toward unscoped mode, and a
|
|
68
68
|
* ceiling that loosens itself when a disk is unmounted is not a ceiling.
|
|
69
69
|
*/
|
|
70
|
-
export function resolveCeiling(repos, roots) {
|
|
70
|
+
export function resolveCeiling(repos, roots, canon = canonicalProject) {
|
|
71
71
|
const unresolved = [];
|
|
72
72
|
const keep = (paths) => [...new Set(paths.map(path => {
|
|
73
|
-
const canonical =
|
|
73
|
+
const canonical = canon(path) ?? null;
|
|
74
74
|
if (canonical !== null)
|
|
75
75
|
return canonical;
|
|
76
76
|
unresolved.push(path);
|
|
@@ -90,7 +90,7 @@ export function unscoped(ceiling) {
|
|
|
90
90
|
export async function authorizedProject(ceiling, path, exec = run) {
|
|
91
91
|
if (unscoped(ceiling))
|
|
92
92
|
return true;
|
|
93
|
-
const canonical = canonicalProject(path);
|
|
93
|
+
const canonical = (ceiling.canon ?? canonicalProject)(path);
|
|
94
94
|
if (canonical === null)
|
|
95
95
|
return false;
|
|
96
96
|
if (ceiling.repos.includes(canonical))
|
|
@@ -114,8 +114,15 @@ export function rowVisible(ceiling, repo) {
|
|
|
114
114
|
return true;
|
|
115
115
|
// Exact saved identities are common. Check the whole set before resolving
|
|
116
116
|
// aliases, so the hundredth project does not stat the other99 directories.
|
|
117
|
-
|
|
117
|
+
const canon = ceiling.canon;
|
|
118
|
+
if (ceiling.repos.includes(repo) || ceiling.repos.some(one => canon === undefined ? sameRepo(one, repo) : (canon(one) ?? null) !== null && canon(one) === canon(repo)))
|
|
118
119
|
return true;
|
|
120
|
+
// With a resolver (the console's saved identities), an identity it can't vouch for fails closed before any root is
|
|
121
|
+
// matched: a path's spelling never stands in for where it leads.
|
|
122
|
+
if (canon !== undefined) {
|
|
123
|
+
const resolved = canon(repo);
|
|
124
|
+
return resolved !== null && ceiling.roots.some(root => resolved.startsWith(root + sep));
|
|
125
|
+
}
|
|
119
126
|
const canonical = canonicalProject(repo) ?? repo;
|
|
120
127
|
return ceiling.roots.some(root => canonical.startsWith(root + sep));
|
|
121
128
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -177,6 +177,9 @@ export type ParsedEnvelope = {
|
|
|
177
177
|
structuralTerminal: import("./exhaustion.js").StructuralTerminal | null;
|
|
178
178
|
/** What an ACP agent itself reported it used — kept on the run, never priced. Absent for every other dialect. */
|
|
179
179
|
agentReportedUsage?: string | null;
|
|
180
|
+
/** v140: the exact model the provider itself reported for this turn (Claude: its session's init, or a buffered
|
|
181
|
+
* result's one `modelUsage` model). Absent or null: not reported — every other dialect today. */
|
|
182
|
+
modelReported?: string | null;
|
|
180
183
|
/** An ACP conversation's facts the conformance check reads (acp-client.ts AcpEnvelope): nothing else does. */
|
|
181
184
|
acp?: {
|
|
182
185
|
offered: string[];
|
package/dist/provider.js
CHANGED
|
@@ -30,6 +30,7 @@ import { ACP_AGENTS, isAcpProvider, withAcp } from "./acp-agents.js";
|
|
|
30
30
|
import { acpParse } from "./acp-client.js";
|
|
31
31
|
import { agentSpecSchema, MODEL_ID, modelIdSchema, PROVIDER_ID_VALUES, providerIdSchema } from "./contracts/provider.js";
|
|
32
32
|
import { primaryResultEvent, topLevelEvent } from "./stream-scope.js";
|
|
33
|
+
import { claudeResultReport, reportedModelOf } from "./reported-usage.js";
|
|
33
34
|
export const PROVIDER_IDS = PROVIDER_ID_VALUES;
|
|
34
35
|
export function isProviderId(value) {
|
|
35
36
|
return providerIdSchema.safeParse(value).success;
|
|
@@ -293,7 +294,7 @@ function claudeFinalMessage(result) {
|
|
|
293
294
|
}
|
|
294
295
|
return typeof result.result === "string" ? result.result : null;
|
|
295
296
|
}
|
|
296
|
-
function claudeEnvelopeOf(result, sessionFromInit, initObserved, initSessionConflict = false) {
|
|
297
|
+
function claudeEnvelopeOf(result, sessionFromInit, initObserved, initSessionConflict = false, initModel = { model: null, conflict: false }) {
|
|
297
298
|
const input = result?.usage?.input_tokens;
|
|
298
299
|
const output = result?.usage?.output_tokens;
|
|
299
300
|
const cost = result?.total_cost_usd;
|
|
@@ -322,6 +323,8 @@ function claudeEnvelopeOf(result, sessionFromInit, initObserved, initSessionConf
|
|
|
322
323
|
costUsd: typeof cost === "number" && cost >= 0 ? cost : null,
|
|
323
324
|
...(result !== null && result[CLAUDE_COST_UNKNOWN] === true ? { costUnknown: true } : {}),
|
|
324
325
|
usageRaw: result?.usage === undefined ? null : JSON.stringify(result.usage).slice(0, USAGE_JSON_CAP),
|
|
326
|
+
// The model only when the session's own events name it (never the agent's words); no result, no report.
|
|
327
|
+
modelReported: result === null ? (initModel.conflict ? null : initModel.model) : claudeResultReport(result, initModel).modelReported,
|
|
325
328
|
initObserved,
|
|
326
329
|
// Structural, never prose (finding 15): consumed means the primary
|
|
327
330
|
// result says SUCCESS. An error result keeps its text as diagnostics
|
|
@@ -354,6 +357,7 @@ function claudeParse(stdout) {
|
|
|
354
357
|
let initSeen = false;
|
|
355
358
|
let sessionFromInit = null;
|
|
356
359
|
let initSessionConflict = false;
|
|
360
|
+
const initModels = new Set();
|
|
357
361
|
let primary = null;
|
|
358
362
|
let transportFailure = null;
|
|
359
363
|
for (const line of stdout.split("\n")) {
|
|
@@ -379,6 +383,9 @@ function claudeParse(stdout) {
|
|
|
379
383
|
}
|
|
380
384
|
if (String(type) === "system" && String(event["subtype"] ?? "") === "init") {
|
|
381
385
|
initSeen = true;
|
|
386
|
+
const model = reportedModelOf(event["model"]);
|
|
387
|
+
if (model !== null)
|
|
388
|
+
initModels.add(model);
|
|
382
389
|
const id = sessionIdOf(event["session_id"]);
|
|
383
390
|
if (id !== null) {
|
|
384
391
|
if (sessionFromInit === null)
|
|
@@ -402,7 +409,7 @@ function claudeParse(stdout) {
|
|
|
402
409
|
primary = result;
|
|
403
410
|
}
|
|
404
411
|
}
|
|
405
|
-
const envelope = claudeEnvelopeOf(primary, sessionFromInit, initSeen, initSessionConflict);
|
|
412
|
+
const envelope = claudeEnvelopeOf(primary, sessionFromInit, initSeen, initSessionConflict, { model: initModels.size === 1 ? [...initModels][0] : null, conflict: initModels.size > 1 });
|
|
406
413
|
if (transportFailure === null)
|
|
407
414
|
return envelope;
|
|
408
415
|
return {
|
|
@@ -8,6 +8,8 @@ import type { CheckRun, GateState, Installed, MainProtection, ReleaseAdapters }
|
|
|
8
8
|
import { Store } from "./store.js";
|
|
9
9
|
import { type CiObservation } from "./release-auto.js";
|
|
10
10
|
import { type BuildRunner } from "./release-carry.js";
|
|
11
|
+
import { type GhRead } from "./nightly-github.js";
|
|
12
|
+
export { failedBeforeTests, type JobRecord } from "./github-jobs.js";
|
|
11
13
|
import { type ReleaseRepo, type ReleaseTools } from "./release-git.js";
|
|
12
14
|
export { RELEASE_PATH, RELEASE_REMOTE, releaseConfigProblem, releaseGitArgs, releaseGitEnv, releaseRemote, releaseTools, type ReleaseRepo, type ReleaseTools } from "./release-git.js";
|
|
13
15
|
export type Exec = (command: string, args: readonly string[], options?: {
|
|
@@ -112,7 +114,10 @@ export declare function bumpFormula(text: string, url: string, sha256: string):
|
|
|
112
114
|
export declare function storeReleaseAdapter(open: () => Store, done: (store: Store) => void, now: () => Date, runner?: BuildRunner,
|
|
113
115
|
/** Releases that approve themselves (auto-approve-green.md): the plane's own look at CI on a commit, and where the
|
|
114
116
|
* check receipts are kept. Absent: no automatic approval, every release asks a person. */
|
|
115
|
-
auto?: AutoObservers
|
|
117
|
+
auto?: AutoObservers,
|
|
118
|
+
/** GitHub's nightly (nightly-github.ts): the plane's own gh reads for a boundary's one bounded read. Absent: nothing
|
|
119
|
+
* is read at the boundary, and the stored debt still holds inside every reservation. */
|
|
120
|
+
nightlyReads?: GhRead): NonNullable<ReleaseAdapters["release"]>;
|
|
116
121
|
/** What the plane itself reads for an automatic release approval. */
|
|
117
122
|
export type AutoObservers = {
|
|
118
123
|
/** The plane's executor policy, never a report field or project setting. Unknown refuses. Production currently
|
|
@@ -135,28 +140,23 @@ export declare function readCheckRuns(run: Exec, gh: string, repo: string, sha:
|
|
|
135
140
|
options?: {
|
|
136
141
|
tag?: string;
|
|
137
142
|
}): Promise<CheckRun[]>;
|
|
138
|
-
/** One job's step outcomes as GitHub's Actions API reports them. */
|
|
139
|
-
export type JobRecord = {
|
|
140
|
-
id: number;
|
|
141
|
-
status: string;
|
|
142
|
-
conclusion: string | null;
|
|
143
|
-
steps: {
|
|
144
|
-
number: number;
|
|
145
|
-
name: string;
|
|
146
|
-
status: string;
|
|
147
|
-
conclusion: string | null;
|
|
148
|
-
}[];
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Whether a finished, unsuccessful job ended before any of the workflow's own steps ran: every step after GitHub's own
|
|
152
|
-
* first one ("Set up job", always number 1) never ran (no conclusion, or skipped) — leaving out only GitHub's own
|
|
153
|
-
* closing "Complete job" when it is the last step. Step numbers and outcomes are the runner's records; nothing the
|
|
154
|
-
* candidate prints is read. A job with no steps never reached one. Anything else (a step that ran, even the checkout)
|
|
155
|
-
* is not confirmed infrastructure.
|
|
156
|
-
*/
|
|
157
|
-
export declare function failedBeforeTests(job: JobRecord): boolean;
|
|
158
143
|
/** GitHub's job record for one check run (its job), read strictly: null when it can't be read whole. */
|
|
159
144
|
export declare function readJobBeforeTests(run: Exec, gh: string, repo: string, id: number): Promise<boolean | null>;
|
|
145
|
+
/**
|
|
146
|
+
* GitHub's REST API as GitHub's nightly reads it (nightly-github.ts): GET only, github.com only, a repository path, the
|
|
147
|
+
* plane's own gh with the allowlisted environment, each call bounded by its caller's deadline. A background read rides
|
|
148
|
+
* out a short network failure (release-net.ts, within that call's own share of the deadline); a boundary's read is
|
|
149
|
+
* plain exec. Never an answer that isn't GitHub's whole JSON.
|
|
150
|
+
*/
|
|
151
|
+
export declare function githubReads(run: Exec, gh: string, options?: {
|
|
152
|
+
retry?: boolean;
|
|
153
|
+
}): (path: string, timeoutMs: number) => Promise<{
|
|
154
|
+
ok: true;
|
|
155
|
+
body: unknown;
|
|
156
|
+
} | {
|
|
157
|
+
ok: false;
|
|
158
|
+
why: string;
|
|
159
|
+
}>;
|
|
160
160
|
/** GitHub lists check runs from at most the latest 1000 check suites of a commit: the history is whole only below that. */
|
|
161
161
|
export declare const CHECK_SUITE_CEILING = 1000;
|
|
162
162
|
export declare function readSuitesComplete(run: Exec, gh: string, repo: string, sha: string): Promise<boolean>;
|
package/dist/release-adapters.js
CHANGED
|
@@ -28,6 +28,9 @@ import { autoPublishHeld, holdAutoPublish, liftCiHold, authorizeAutoCheck, giveU
|
|
|
28
28
|
import { proveCarry } from "./release-carry.js";
|
|
29
29
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
30
30
|
import { deploymentGate } from "./deploy-gate.js";
|
|
31
|
+
import { failedBeforeTests } from "./github-jobs.js";
|
|
32
|
+
import { mainHistoryOf, nightlyBoundary } from "./nightly-github.js";
|
|
33
|
+
export { failedBeforeTests } from "./github-jobs.js";
|
|
31
34
|
import { extractCommit } from "./commit-extract.js";
|
|
32
35
|
import { teardownGit, refusal as gitRefusal } from "./teardown-git.js";
|
|
33
36
|
import { RELEASE_REMOTE, releaseConfigProblem, releaseGitArgs, releaseGitEnv, releaseRemote, releaseTools } from "./release-git.js";
|
|
@@ -422,7 +425,9 @@ export function releaseAdapters(options) {
|
|
|
422
425
|
upToDateRequired: () => readUpToDateRequired(reads, ciGh, releaseRepo().slug), evidenceRoot: join(dirname(database), "evidence"),
|
|
423
426
|
// A successful Actions job authenticates the job, not the candidate-writable JSON it uploads. Until the
|
|
424
427
|
// executor protects that reporting channel, production releases must take the person's approval road.
|
|
425
|
-
reportsProtected: false }
|
|
428
|
+
reportsProtected: false },
|
|
429
|
+
// A boundary's one bounded read of GitHub's nightly: plain exec, never the release's long retries (20 s in all).
|
|
430
|
+
githubReads(run, ciGh));
|
|
426
431
|
return {
|
|
427
432
|
...(existsSync(database) ? { release } : {}),
|
|
428
433
|
now: options.now ?? (() => new Date()),
|
|
@@ -925,7 +930,10 @@ function personAnswerOf(value) {
|
|
|
925
930
|
export function storeReleaseAdapter(open, done, now, runner,
|
|
926
931
|
/** Releases that approve themselves (auto-approve-green.md): the plane's own look at CI on a commit, and where the
|
|
927
932
|
* check receipts are kept. Absent: no automatic approval, every release asks a person. */
|
|
928
|
-
auto
|
|
933
|
+
auto,
|
|
934
|
+
/** GitHub's nightly (nightly-github.ts): the plane's own gh reads for a boundary's one bounded read. Absent: nothing
|
|
935
|
+
* is read at the boundary, and the stored debt still holds inside every reservation. */
|
|
936
|
+
nightlyReads) {
|
|
929
937
|
const withStore = (body) => { const store = open(); try {
|
|
930
938
|
return body(store);
|
|
931
939
|
}
|
|
@@ -962,6 +970,20 @@ auto) {
|
|
|
962
970
|
async live(taskId, action) {
|
|
963
971
|
return withStore(store => { const live = liveReleaseOf(store, taskId, action, now()); return live.applies ? { applies: true, ok: live.ok, why: live.ok ? null : live.why } : { applies: false, ok: false, why: null }; });
|
|
964
972
|
},
|
|
973
|
+
...(nightlyReads === undefined ? {} : {
|
|
974
|
+
async nightly(input) {
|
|
975
|
+
const store = open();
|
|
976
|
+
try {
|
|
977
|
+
const repo = store.lookupRef(input.taskId)?.repo ?? null;
|
|
978
|
+
if (repo === null)
|
|
979
|
+
return { ok: false, why: `Task ${input.taskId} is in no project, so GitHub's nightly debt can't be read for it.`, warning: null };
|
|
980
|
+
return await nightlyBoundary(store, { repo, taskId: input.taskId, candidate: input.candidate, pr: input.pr, phase: input.phase }, { ...mainHistoryOf(store, repo, now()), gh: nightlyReads, now });
|
|
981
|
+
}
|
|
982
|
+
finally {
|
|
983
|
+
done(store);
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
}),
|
|
965
987
|
async lease(authority, owner) { return withStore(store => takeReleaseLease(store, authority, owner, now())); },
|
|
966
988
|
async renew(authority, owner, epoch) { return withStore(store => renewReleaseLease(store, authority, owner, epoch, now())); },
|
|
967
989
|
async carry(authority, to, requestId, epoch) {
|
|
@@ -1169,23 +1191,6 @@ options = {}) {
|
|
|
1169
1191
|
}
|
|
1170
1192
|
return runs.sort((a, b) => a.id - b.id);
|
|
1171
1193
|
}
|
|
1172
|
-
/**
|
|
1173
|
-
* Whether a finished, unsuccessful job ended before any of the workflow's own steps ran: every step after GitHub's own
|
|
1174
|
-
* first one ("Set up job", always number 1) never ran (no conclusion, or skipped) — leaving out only GitHub's own
|
|
1175
|
-
* closing "Complete job" when it is the last step. Step numbers and outcomes are the runner's records; nothing the
|
|
1176
|
-
* candidate prints is read. A job with no steps never reached one. Anything else (a step that ran, even the checkout)
|
|
1177
|
-
* is not confirmed infrastructure.
|
|
1178
|
-
*/
|
|
1179
|
-
export function failedBeforeTests(job) {
|
|
1180
|
-
if (job.status !== "completed" || job.conclusion === "success" || job.conclusion === null)
|
|
1181
|
-
return false;
|
|
1182
|
-
const steps = [...job.steps].sort((a, b) => a.number - b.number);
|
|
1183
|
-
if (new Set(steps.map(one => one.number)).size !== steps.length)
|
|
1184
|
-
return false;
|
|
1185
|
-
const last = steps.at(-1);
|
|
1186
|
-
const own = steps.filter(one => one.number > 1 && !(one === last && one.name === "Complete job" && steps.length > 1));
|
|
1187
|
-
return own.every(one => one.status !== "in_progress" && (one.conclusion === null || one.conclusion === "skipped"));
|
|
1188
|
-
}
|
|
1189
1194
|
/** GitHub's job record for one check run (its job), read strictly: null when it can't be read whole. */
|
|
1190
1195
|
export async function readJobBeforeTests(run, gh, repo, id) {
|
|
1191
1196
|
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo) || !Number.isSafeInteger(id) || id <= 0)
|
|
@@ -1214,6 +1219,34 @@ export async function readJobBeforeTests(run, gh, repo, id) {
|
|
|
1214
1219
|
return failedBeforeTests({ id, status: String(job["status"]), conclusion: textOr(job["conclusion"]),
|
|
1215
1220
|
steps: steps.map(one => ({ number: Number(one["number"]), name: String(one["name"]), status: String(one["status"]), conclusion: textOr(one["conclusion"]) })) });
|
|
1216
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* GitHub's REST API as GitHub's nightly reads it (nightly-github.ts): GET only, github.com only, a repository path, the
|
|
1224
|
+
* plane's own gh with the allowlisted environment, each call bounded by its caller's deadline. A background read rides
|
|
1225
|
+
* out a short network failure (release-net.ts, within that call's own share of the deadline); a boundary's read is
|
|
1226
|
+
* plain exec. Never an answer that isn't GitHub's whole JSON.
|
|
1227
|
+
*/
|
|
1228
|
+
export function githubReads(run, gh, options = {}) {
|
|
1229
|
+
return async (path, timeoutMs) => {
|
|
1230
|
+
if (!/^repos\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_./?&=%-]+$/.test(path) || timeoutMs <= 0)
|
|
1231
|
+
return { ok: false, why: "not a read this makes" };
|
|
1232
|
+
const exec = options.retry === true ? retryingExec(run, { say: () => { }, sleep, budgetMs: timeoutMs }) : run;
|
|
1233
|
+
let result;
|
|
1234
|
+
try {
|
|
1235
|
+
result = await exec(gh, ["api", "--hostname", "github.com", "--method", "GET", path], { cwd: "/", env: ciEnv(), timeoutMs });
|
|
1236
|
+
}
|
|
1237
|
+
catch (error) {
|
|
1238
|
+
return { ok: false, why: String(error?.message ?? error).slice(0, 200) };
|
|
1239
|
+
}
|
|
1240
|
+
if (result.code !== 0)
|
|
1241
|
+
return { ok: false, why: result.code === 124 ? "GitHub didn't answer in time" : result.code === 127 ? "gh isn't installed" : (result.stderr || result.stdout).trim().split("\n").slice(-1)[0].slice(0, 200) || `gh exited ${result.code}` };
|
|
1242
|
+
try {
|
|
1243
|
+
return { ok: true, body: JSON.parse(result.stdout) };
|
|
1244
|
+
}
|
|
1245
|
+
catch {
|
|
1246
|
+
return { ok: false, why: "GitHub's answer couldn't be read" };
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1217
1250
|
/** GitHub lists check runs from at most the latest 1000 check suites of a commit: the history is whole only below that. */
|
|
1218
1251
|
export const CHECK_SUITE_CEILING = 1000;
|
|
1219
1252
|
export async function readSuitesComplete(run, gh, repo, sha) {
|
package/dist/release-auto.js
CHANGED
|
@@ -17,6 +17,7 @@ import { fullCheckProblem, projectGitDir } from "./check-report.js";
|
|
|
17
17
|
import { sealedRun } from "./release-recheck.js";
|
|
18
18
|
import { runCheckLevel } from "./check-levels.js";
|
|
19
19
|
import { readVerifiedCommit, verifiedBlobs, verifiedTreeDiff, verifiedTreeEntries } from "./commit-extract.js";
|
|
20
|
+
import { EVAL_HOOK_FILES, evalHookProblem, storeRewriteProblem } from "./eval-hooks.js";
|
|
20
21
|
import { mightName, testInfrastructure } from "./test-infra.js";
|
|
21
22
|
import { chatControlHref } from "./chat-controls.js";
|
|
22
23
|
import { INFRA_RERUNS_PER_CANDIDATE, REQUIRED_PR_CHECKS, REQUIRED_PR_CHECKS_WORKFLOW, prChecksVerdict } from "./release.js";
|
|
@@ -27,6 +28,7 @@ import { autoReleaseApprovalOf, personApprovalOf } from "./scope.js";
|
|
|
27
28
|
import { releaseOperations, revokeRelease } from "./store.js";
|
|
28
29
|
import { RELEASE_CI_HELD_ACTION, RELEASE_CI_PASSED_ACTION, RELEASE_CI_RUNNING_WORDS, releaseCiHoldOf, releaseCiRunningOf } from "./release-ci-hold.js";
|
|
29
30
|
import { verificationEvidence } from "./verification-evidence.js";
|
|
31
|
+
import { nightlyDebtHold } from "./nightly-github.js";
|
|
30
32
|
export { RELEASE_APPROVAL_ACTION, releaseApprovalSetting, releaseAutoApproves };
|
|
31
33
|
/** The setting's words, one place. */
|
|
32
34
|
export const RELEASE_APPROVAL_WORDS = { ask: "Ask me", auto: "Approve automatically when checks pass" };
|
|
@@ -177,6 +179,11 @@ export function authorizeAutoCheck(store, input) {
|
|
|
177
179
|
const SRC_TEST_INFRA = /^src\/(fixtures\/|(.*\/)?(test-[^/]*|[^/]*-test-support[^/]*|[^/]*fixtures?[^/]*)\.[cm]?[jt]sx?$)/;
|
|
178
180
|
/** A test file (normalized path): `*.test.ts`, never inside a fixtures folder. */
|
|
179
181
|
const TEST_FILE = /^(?!.*(^|\/)fixtures?\/)[^]*\.test\.ts$/;
|
|
182
|
+
/** The eval set's and GitHub nightly's policy code (docs/plans/evals-nightly.md, part C, finding 10): the cases and
|
|
183
|
+
* oracles, the evaluator, its gate, hooks and world, reviewer scoring, nightly ingestion, their contracts, and the
|
|
184
|
+
* accounting readings evals trust (reported-usage.ts, run-efficiency.ts). Where they are called from, and the store's
|
|
185
|
+
* accounting reads, are held by eval-hooks.ts instead (evalHookProblem). */
|
|
186
|
+
const EVAL_POLICY = /^(evals\/|src\/evals[^/]*\.ts$|src\/eval-[^/]*\.ts$|src\/reviewer-evals\.ts$|src\/nightly-github\.ts$|src\/contracts\/evals[^/]*\.ts$|src\/reported-usage\.ts$|src\/run-efficiency\.ts$)/;
|
|
180
187
|
/** Build or type configuration anywhere: a person approves it. */
|
|
181
188
|
const BUILD_CONFIG = /(^|\/)(tsconfig[^/]*\.json|[^/]*\.config\.[cm]?[jt]s|\.npmrc|\.nvmrc|\.node-version)$/;
|
|
182
189
|
/**
|
|
@@ -201,6 +208,8 @@ export function autoPathProblem(path) {
|
|
|
201
208
|
}
|
|
202
209
|
// Test files under test/ carry the same trust as src's (Alex: test authors are trusted) — only the `.test.ts` files
|
|
203
210
|
// themselves, never a helper, fixture, setup or config file beside them, nor one inside a fixtures folder.
|
|
211
|
+
if (EVAL_POLICY.test(normal))
|
|
212
|
+
return "it's eval or nightly policy code";
|
|
204
213
|
if (testUnderTest)
|
|
205
214
|
return null;
|
|
206
215
|
if (BUILD_CONFIG.test(normal))
|
|
@@ -312,6 +321,19 @@ function changeProblem(repo, base, candidate) {
|
|
|
312
321
|
scans.push(found);
|
|
313
322
|
}
|
|
314
323
|
const infra = new Set(scans.flatMap(one => [...one.infra]));
|
|
324
|
+
// The eval and nightly enforcement calls the base holds must all still be in place at the candidate (eval-hooks.ts).
|
|
325
|
+
// One verified read of every listed file per tree (an unreadable tree reads as every file missing: a person decides).
|
|
326
|
+
const blobAt = (tree) => { const blobs = verifiedBlobs(gitDir, tree, EVAL_HOOK_FILES); return (file) => blobs.ok ? blobs.value.get(file)?.bytes ?? null : null; };
|
|
327
|
+
const hooks = evalHookProblem(blobAt(after.value.tree), blobAt(before.value.tree));
|
|
328
|
+
if (hooks !== null)
|
|
329
|
+
return `${hooks}: a person decides`;
|
|
330
|
+
// No changed file may write over a pinned Store method from outside store.ts (eval-hooks.ts storeRewriteProblem).
|
|
331
|
+
const changed = diff.value.filter(change => change.after !== null).map(change => change.path);
|
|
332
|
+
// A failed read is no reader at all (a person decides), never files that read as absent.
|
|
333
|
+
const changedAt = (tree) => { const blobs = verifiedBlobs(gitDir, tree, changed); return blobs.ok ? (file) => blobs.value.get(file)?.bytes ?? null : null; };
|
|
334
|
+
const rewrites = storeRewriteProblem(changed, changedAt(after.value.tree), changedAt(before.value.tree));
|
|
335
|
+
if (rewrites !== null)
|
|
336
|
+
return `${rewrites}: a person decides`;
|
|
315
337
|
for (const change of diff.value) {
|
|
316
338
|
const modes = [change.before?.mode, change.after?.mode].filter((one) => one !== undefined);
|
|
317
339
|
if (modes.includes("120000") || modes.includes("160000"))
|
|
@@ -461,6 +483,10 @@ export function sealAutoGreen(store, input) {
|
|
|
461
483
|
if (changed !== null)
|
|
462
484
|
return refuse(changed);
|
|
463
485
|
// GitHub CI, as the plane's own gh saw it moments ago, on exactly this commit.
|
|
486
|
+
// GitHub's nightly lane debt (evals-nightly.md, part A2): dormant while automatic approval is refused, held here too.
|
|
487
|
+
const nightly = nightlyDebtHold(store, { repo: facts.repo, taskId: input.taskId, candidate: input.candidate, phase: "auto", release: true, now: input.now });
|
|
488
|
+
if (nightly !== null)
|
|
489
|
+
return refuse(nightly.why);
|
|
464
490
|
const ci = ciVerdict(input.ci, input.candidate, input.now);
|
|
465
491
|
if (!ci.ok)
|
|
466
492
|
return refuse(`CI: ${ci.problem}`);
|
package/dist/release-card.js
CHANGED
|
@@ -27,6 +27,7 @@ import { routeFromJson } from "./phase-routing.js";
|
|
|
27
27
|
import { projectName } from "./project.js";
|
|
28
28
|
import { RELEASE_CI_RUNNING_WORDS, releaseCiRunningOf } from "./release-ci-hold.js";
|
|
29
29
|
import { RELEASE_PATH, releaseConfigProblem, releaseGitEnv, releaseRemote, releaseTools } from "./release-git.js";
|
|
30
|
+
import { nightlyReleaseLines } from "./nightly-github.js";
|
|
30
31
|
const NONE = "none";
|
|
31
32
|
/** A budget in dollars from integer micro-dollars, in integer arithmetic: cents when it is whole cents, else every digit. */
|
|
32
33
|
const money = (micro) => {
|
|
@@ -258,6 +259,8 @@ export function releaseCardOf(store, scope, formDigest, words) {
|
|
|
258
259
|
...(terms !== null && terms.policy.carry && terms.access !== "sandboxed" ? ["This project's checks don't run in the sandbox: later test-only commits will run outside the sandbox without being shown to you first."] : []),
|
|
259
260
|
// Filed while CI runs (Alex, 2026-10-29): a person may approve now, and is told plainly what still waits.
|
|
260
261
|
...(releaseCiRunningOf(store, scope.taskId) === null ? [] : [RELEASE_CI_RUNNING_WORDS]),
|
|
262
|
+
// GitHub's nightly (nightly-github.ts): what holds this release, or why it can't be read. Not a signed term.
|
|
263
|
+
...(terms === null ? [] : nightlyReleaseLines(store, { repo: terms.repo, taskId: scope.taskId, candidate: scope.candidate ?? null, now: new Date() })),
|
|
261
264
|
"",
|
|
262
265
|
...changeWords(changes, terms, 5),
|
|
263
266
|
"",
|