@kungfu-tech/buildchain 3.0.3 → 3.0.4-alpha.1

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.
@@ -0,0 +1,361 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ PAPER_PATHS,
6
+ PAPER_WORK_BRANCH_PATTERN,
7
+ gitResult,
8
+ gitValue,
9
+ paperDevelopmentRef,
10
+ readJson,
11
+ sha256Text,
12
+ stableJson,
13
+ workCheck,
14
+ } from "./paper-repository.js";
15
+
16
+ export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
17
+ export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
18
+ export const PAPER_AGENT_ENTRY_SECTION_START =
19
+ "<!-- buildchain:paper-agent-entry:v1:start -->";
20
+ export const PAPER_AGENT_ENTRY_SECTION_END =
21
+ "<!-- buildchain:paper-agent-entry:v1:end -->";
22
+
23
+ const EXACT_PAPER_SCRIPTS = Object.freeze({
24
+ "paper:agent:verify": "buildchain paper agent verify --json",
25
+ "paper:work:start": "buildchain paper work start",
26
+ "paper:work:submit": "buildchain paper work submit",
27
+ });
28
+
29
+ const GIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
30
+ const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/i;
31
+
32
+ function jsonText(value) {
33
+ return `${JSON.stringify(value, null, 2)}\n`;
34
+ }
35
+
36
+ export function paperAgentEntryInstructions({ developmentRef }) {
37
+ return `${PAPER_AGENT_ENTRY_SECTION_START}
38
+ ## Paper work entry (required)
39
+
40
+ This repository uses the Buildchain-managed Paper path. Before changing any
41
+ tracked file, inspect the current branch and follow the matching rule:
42
+
43
+ - On \`main\`, \`${developmentRef}\`, or another protected channel, do not edit.
44
+ Switch to the exact current \`${developmentRef}\`, install the pinned pnpm
45
+ dependencies when needed, then run
46
+ \`pnpm paper:work:start -- <topic> --execute --json\`.
47
+ - On an existing \`feature/*\`, \`fix/*\`, \`docs/*\`, \`chore/*\`, \`ci/*\`, or
48
+ \`refactor/*\` branch, run \`pnpm paper:agent:verify\` before continuing.
49
+ - Before opening or updating a pull request, commit the intended bytes and run
50
+ \`pnpm paper:work:submit -- --execute --json\`. The target must be
51
+ \`${developmentRef}\`; direct protected-branch pushes and force pushes are
52
+ forbidden.
53
+
54
+ The required remote check independently verifies this contract, the exact
55
+ Buildchain package and runtime authority, and feature-to-development lineage.
56
+ Passing local commands alone is not acceptance, and manually bypassing them
57
+ does not bypass remote policy.
58
+ ${PAPER_AGENT_ENTRY_SECTION_END}`;
59
+ }
60
+
61
+ export function mergePaperAgentEntryInstructions(
62
+ current = "",
63
+ { developmentRef },
64
+ ) {
65
+ const source = String(current || "");
66
+ const section = paperAgentEntryInstructions({ developmentRef });
67
+ const start = source.indexOf(PAPER_AGENT_ENTRY_SECTION_START);
68
+ const end = source.indexOf(PAPER_AGENT_ENTRY_SECTION_END);
69
+ if ((start === -1) !== (end === -1)) {
70
+ throw new Error(
71
+ "AGENTS.md has an incomplete Buildchain Paper agent-entry managed section",
72
+ );
73
+ }
74
+ if (start === -1) {
75
+ return source.trim()
76
+ ? `${source.trimEnd()}\n\n${section}\n`
77
+ : `# AGENTS.md\n\n${section}\n`;
78
+ }
79
+ if (
80
+ source.indexOf(PAPER_AGENT_ENTRY_SECTION_START, start + 1) !== -1 ||
81
+ source.indexOf(PAPER_AGENT_ENTRY_SECTION_END, end + 1) !== -1 ||
82
+ end < start
83
+ ) {
84
+ throw new Error(
85
+ "AGENTS.md has ambiguous Buildchain Paper agent-entry managed sections",
86
+ );
87
+ }
88
+ return `${source.slice(0, start)}${section}${source.slice(
89
+ end + PAPER_AGENT_ENTRY_SECTION_END.length,
90
+ )}`;
91
+ }
92
+
93
+ export function createPaperAgentEntry({
94
+ buildchainVersion,
95
+ buildchainSha,
96
+ developmentRef,
97
+ }) {
98
+ const version = String(buildchainVersion || "").trim();
99
+ const sourceSha = String(buildchainSha || "").trim();
100
+ if (!/^3\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
101
+ throw new Error(
102
+ "paper agent entry requires an exact Buildchain v3 version",
103
+ );
104
+ }
105
+ if (!GIT_SHA_PATTERN.test(sourceSha)) {
106
+ throw new Error(
107
+ "paper agent entry requires an exact Buildchain source SHA",
108
+ );
109
+ }
110
+ if (!/^dev\/v\d+\/v\d+\.\d+$/.test(developmentRef)) {
111
+ throw new Error("paper agent entry requires an exact development ref");
112
+ }
113
+ const payload = {
114
+ schemaVersion: PAPER_AGENT_ENTRY_SCHEMA_VERSION,
115
+ contract: PAPER_AGENT_ENTRY_CONTRACT,
116
+ runtime: {
117
+ package: "@kungfu-tech/buildchain",
118
+ version,
119
+ sourceSha,
120
+ },
121
+ repository: {
122
+ developmentRef,
123
+ pullRequestTarget: developmentRef,
124
+ },
125
+ commands: {
126
+ verify: "pnpm paper:agent:verify",
127
+ start: "pnpm paper:work:start -- <topic> --execute --json",
128
+ submit: "pnpm paper:work:submit -- --execute --json",
129
+ },
130
+ policy: {
131
+ localMutation: "dry-run-first",
132
+ protectedBranchPatterns: [
133
+ "main",
134
+ "dev/**",
135
+ "alpha/**",
136
+ "release/**",
137
+ "publish-gate/**",
138
+ ],
139
+ workBranchPattern: PAPER_WORK_BRANCH_PATTERN.source,
140
+ forcePush: false,
141
+ remoteAcceptance: "required-buildchain-check",
142
+ },
143
+ };
144
+ return {
145
+ ...payload,
146
+ entryDigest: sha256Text(stableJson(payload)),
147
+ };
148
+ }
149
+
150
+ export function paperAgentEntryFiles({
151
+ cwd,
152
+ buildchainVersion,
153
+ buildchainSha,
154
+ developmentRef = "",
155
+ }) {
156
+ const resolvedDevelopmentRef = developmentRef || paperDevelopmentRef(cwd);
157
+ const agentsPath = path.resolve(cwd, PAPER_PATHS.agentInstructions);
158
+ const currentAgents = fs.existsSync(agentsPath)
159
+ ? fs.readFileSync(agentsPath, "utf8")
160
+ : "";
161
+ const entry = createPaperAgentEntry({
162
+ buildchainVersion,
163
+ buildchainSha,
164
+ developmentRef: resolvedDevelopmentRef,
165
+ });
166
+ return new Map([
167
+ [PAPER_PATHS.agentEntry, jsonText(entry)],
168
+ [
169
+ PAPER_PATHS.agentInstructions,
170
+ mergePaperAgentEntryInstructions(currentAgents, {
171
+ developmentRef: resolvedDevelopmentRef,
172
+ }),
173
+ ],
174
+ ]);
175
+ }
176
+
177
+ export function resolvePaperBuildchainSha(buildchainRoot, buildchainSha = "") {
178
+ if (buildchainSha) return buildchainSha;
179
+ if (!fs.existsSync(path.resolve(buildchainRoot, ".git"))) return "";
180
+ const observed = gitValue(buildchainRoot, ["rev-parse", "HEAD"]);
181
+ return GIT_SHA_PATTERN.test(observed) ? observed : "";
182
+ }
183
+
184
+ function expectedCiBranches(developmentRef) {
185
+ return [
186
+ developmentRef,
187
+ developmentRef.replace(/^dev\//, "alpha/"),
188
+ developmentRef.replace(/^dev\//, "release/"),
189
+ ];
190
+ }
191
+
192
+ function ciContext({ env, developmentRef }) {
193
+ const event = String(env.GITHUB_EVENT_NAME || "");
194
+ const sourceBranch = String(
195
+ env.GITHUB_HEAD_REF || env.BUILDCHAIN_PAPER_SOURCE_BRANCH || "",
196
+ );
197
+ const targetBranch = String(
198
+ env.GITHUB_BASE_REF || env.BUILDCHAIN_PAPER_TARGET_BRANCH || "",
199
+ );
200
+ const refName = String(
201
+ env.GITHUB_REF_NAME || env.BUILDCHAIN_PAPER_REF_NAME || "",
202
+ );
203
+ const pullRequest = ["pull_request", "pull_request_target"].includes(event);
204
+ const branchOk = pullRequest
205
+ ? PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
206
+ targetBranch === developmentRef
207
+ : expectedCiBranches(developmentRef).includes(refName);
208
+ return {
209
+ mode: "ci",
210
+ event,
211
+ sourceBranch,
212
+ targetBranch,
213
+ refName,
214
+ pullRequest,
215
+ ok: branchOk,
216
+ message: pullRequest
217
+ ? `Pull requests must use an allowed work branch and target ${developmentRef}.`
218
+ : `Channel checks must run on ${expectedCiBranches(developmentRef).join(", ")}.`,
219
+ };
220
+ }
221
+
222
+ function localContext({ cwd, developmentRef }) {
223
+ const branch = gitValue(cwd, ["branch", "--show-current"]);
224
+ const developmentSha = gitValue(cwd, [
225
+ "rev-parse",
226
+ "--verify",
227
+ `refs/remotes/origin/${developmentRef}`,
228
+ ]);
229
+ const containsDevelopment =
230
+ PAPER_WORK_BRANCH_PATTERN.test(branch) &&
231
+ GIT_SHA_PATTERN.test(developmentSha) &&
232
+ gitResult(cwd, ["merge-base", "--is-ancestor", developmentSha, "HEAD"]).ok;
233
+ return {
234
+ mode: "local",
235
+ branch,
236
+ developmentRef,
237
+ developmentSha,
238
+ ok: containsDevelopment,
239
+ message: PAPER_WORK_BRANCH_PATTERN.test(branch)
240
+ ? `The work branch must contain origin/${developmentRef}.`
241
+ : `Run paper work start from the exact ${developmentRef} channel before editing.`,
242
+ };
243
+ }
244
+
245
+ export function collectPaperAgentEntry({
246
+ cwd = process.cwd(),
247
+ buildchainSha = "",
248
+ mode = "contract",
249
+ env = process.env,
250
+ } = {}) {
251
+ const resolvedCwd = path.resolve(cwd);
252
+ const developmentRef = paperDevelopmentRef(resolvedCwd);
253
+ const source = readJson(path.resolve(resolvedCwd, PAPER_PATHS.agentEntry));
254
+ const entry = source.value || {};
255
+ const effectiveBuildchainSha =
256
+ buildchainSha || String(entry.runtime?.sourceSha || "");
257
+ const packageJson =
258
+ readJson(path.resolve(resolvedCwd, "package.json")).value || {};
259
+ const pinPath = path.resolve(resolvedCwd, PAPER_PATHS.versionPin);
260
+ const versionPin = fs.existsSync(pinPath)
261
+ ? fs.readFileSync(pinPath, "utf8").trim()
262
+ : "";
263
+ const agentsPath = path.resolve(resolvedCwd, PAPER_PATHS.agentInstructions);
264
+ const agents = fs.existsSync(agentsPath)
265
+ ? fs.readFileSync(agentsPath, "utf8")
266
+ : "";
267
+ const expectedInstructions = paperAgentEntryInstructions({ developmentRef });
268
+ const { entryDigest: observedDigest, ...entryPayload } = entry;
269
+ const checks = [
270
+ workCheck(
271
+ "agent-entry.contract",
272
+ source.exists &&
273
+ !source.error &&
274
+ entry.contract === PAPER_AGENT_ENTRY_CONTRACT &&
275
+ entry.schemaVersion === PAPER_AGENT_ENTRY_SCHEMA_VERSION,
276
+ "The versioned Paper agent-entry contract exists and parses.",
277
+ "buildchain paper migrate --write --json",
278
+ ),
279
+ workCheck(
280
+ "agent-entry.digest",
281
+ SHA256_PATTERN.test(String(observedDigest || "")) &&
282
+ observedDigest === sha256Text(stableJson(entryPayload)),
283
+ "The Paper agent-entry contract digest matches its exact payload.",
284
+ "buildchain paper migrate --write --json",
285
+ ),
286
+ workCheck(
287
+ "agent-entry.instructions",
288
+ agents.includes(expectedInstructions) &&
289
+ agents.split(PAPER_AGENT_ENTRY_SECTION_START).length === 2 &&
290
+ agents.split(PAPER_AGENT_ENTRY_SECTION_END).length === 2,
291
+ "AGENTS.md contains exactly one current Buildchain-managed Paper entry section.",
292
+ "buildchain paper migrate --write --json",
293
+ ),
294
+ workCheck(
295
+ "agent-entry.package-version",
296
+ Boolean(versionPin) &&
297
+ entry.runtime?.version === versionPin &&
298
+ packageJson.devDependencies?.["@kungfu-tech/buildchain"] === versionPin,
299
+ "The agent-entry contract, version pin, and exact Buildchain dependency agree.",
300
+ "buildchain paper migrate --write --json && pnpm install --lockfile-only",
301
+ ),
302
+ workCheck(
303
+ "agent-entry.runtime-source",
304
+ GIT_SHA_PATTERN.test(effectiveBuildchainSha) &&
305
+ entry.runtime?.sourceSha === effectiveBuildchainSha,
306
+ "The agent-entry contract is bound to the exact executing Buildchain source SHA.",
307
+ "buildchain paper migrate --write --json",
308
+ ),
309
+ workCheck(
310
+ "agent-entry.development-ref",
311
+ entry.repository?.developmentRef === developmentRef &&
312
+ entry.repository?.pullRequestTarget === developmentRef,
313
+ `The agent-entry contract targets ${developmentRef}.`,
314
+ "buildchain paper migrate --write --json",
315
+ ),
316
+ ...Object.entries(EXACT_PAPER_SCRIPTS).map(([name, command]) =>
317
+ workCheck(
318
+ `agent-entry.script.${name}`,
319
+ packageJson.scripts?.[name] === command,
320
+ `package.json exposes the exact ${name} command.`,
321
+ "buildchain paper migrate --write --json",
322
+ ),
323
+ ),
324
+ ];
325
+ const context =
326
+ mode === "ci"
327
+ ? ciContext({ env, developmentRef })
328
+ : mode === "local"
329
+ ? localContext({ cwd: resolvedCwd, developmentRef })
330
+ : { mode: "contract", ok: true };
331
+ if (mode !== "contract") {
332
+ checks.push(
333
+ workCheck(
334
+ "agent-entry.work-context",
335
+ context.ok,
336
+ context.message,
337
+ mode === "ci"
338
+ ? "retarget the pull request to the configured development channel"
339
+ : `pnpm paper:work:start -- <topic> --execute --json`,
340
+ ),
341
+ );
342
+ }
343
+ const ok = checks.every((entryCheck) => entryCheck.status === "pass");
344
+ return {
345
+ schemaVersion: 1,
346
+ contract: PAPER_AGENT_ENTRY_CONTRACT,
347
+ ok,
348
+ cwd: resolvedCwd,
349
+ mode,
350
+ entry,
351
+ context,
352
+ checks,
353
+ nextActions: checks
354
+ .filter((entryCheck) => entryCheck.status === "fail")
355
+ .map((entryCheck) => ({
356
+ id: `repair-${entryCheck.id}`,
357
+ command: entryCheck.correctiveCommand,
358
+ description: entryCheck.message,
359
+ })),
360
+ };
361
+ }
@@ -6,6 +6,8 @@ import { loadBuildchainConfig } from "./buildchain-config.js";
6
6
 
7
7
  export const PAPER_PATHS = Object.freeze({
8
8
  config: ".buildchain/buildchain.toml",
9
+ agentEntry: ".buildchain/paper/agent-entry.json",
10
+ agentInstructions: "AGENTS.md",
9
11
  versionPin: ".buildchain-version",
10
12
  contractLock: ".buildchain/contract-lock.json",
11
13
  buildWorkflow: ".github/workflows/build.yml",
@@ -0,0 +1,163 @@
1
+ function jsonText(value) {
2
+ return `${JSON.stringify(value, null, 2)}\n`;
3
+ }
4
+
5
+ function texEscape(value) {
6
+ return String(value || "")
7
+ .replaceAll("\\", "\\textbackslash{}")
8
+ .replaceAll("&", "\\&")
9
+ .replaceAll("%", "\\%")
10
+ .replaceAll("$", "\\$")
11
+ .replaceAll("#", "\\#")
12
+ .replaceAll("_", "\\_")
13
+ .replaceAll("{", "\\{")
14
+ .replaceAll("}", "\\}");
15
+ }
16
+
17
+ export function scaffoldMakefile({ image, digest, command }) {
18
+ return `.PHONY: check pdf clean
19
+
20
+ BUILDER_IMAGE := ${image}@${digest}
21
+ SOURCE_DATE_EPOCH ?= 0
22
+
23
+ check:
24
+ \t@test -f paper/main.tex
25
+ \t@test -f paper/references.bib
26
+ \t@git diff --check
27
+
28
+ pdf:
29
+ \t@mkdir -p _build
30
+ \tdocker run --rm --network=none \\
31
+ \t\t-e SOURCE_DATE_EPOCH="$(SOURCE_DATE_EPOCH)" \\
32
+ \t\t-e TZ=UTC -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 -e HOME=/tmp \\
33
+ \t\t-v "$(CURDIR):/workspace" -w /workspace \\
34
+ \t\t"$(BUILDER_IMAGE)" bash -lc '${command}'
35
+
36
+ clean:
37
+ \trm -rf _build
38
+ `;
39
+ }
40
+
41
+ export function paperPackageScripts(current = {}) {
42
+ return {
43
+ ...current,
44
+ "buildchain:paper": "buildchain paper",
45
+ "paper:preflight": "buildchain paper preflight --json",
46
+ "paper:agent:verify": "buildchain paper agent verify --json",
47
+ "paper:work:start": "buildchain paper work start",
48
+ "paper:work:submit": "buildchain paper work submit",
49
+ "paper:status": "buildchain paper status --json",
50
+ };
51
+ }
52
+
53
+ export function managedPaperPackageJson(current, buildchainVersion) {
54
+ if (!/^3\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(buildchainVersion)) {
55
+ throw new Error(
56
+ "paper repositories require an exact Buildchain v3 version",
57
+ );
58
+ }
59
+ const packageManager = current.packageManager || "pnpm@11.7.0";
60
+ if (!packageManager.startsWith("pnpm@")) {
61
+ throw new Error("paper repositories require a pnpm packageManager");
62
+ }
63
+ return {
64
+ ...current,
65
+ private: true,
66
+ scripts: paperPackageScripts(current.scripts),
67
+ devDependencies: {
68
+ ...(current.devDependencies || {}),
69
+ "@kungfu-tech/buildchain": buildchainVersion,
70
+ },
71
+ packageManager,
72
+ };
73
+ }
74
+
75
+ export function scaffoldPackageJson({
76
+ name,
77
+ packageName,
78
+ repository,
79
+ buildchainVersion,
80
+ }) {
81
+ return jsonText({
82
+ ...managedPaperPackageJson(
83
+ {
84
+ name: packageName,
85
+ description: `${name} publication source repository.`,
86
+ repository: {
87
+ type: "git",
88
+ url: `git+https://github.com/${repository}.git`,
89
+ },
90
+ license: "Apache-2.0",
91
+ },
92
+ buildchainVersion,
93
+ ),
94
+ });
95
+ }
96
+
97
+ export function scaffoldReadme({ title, packageName }) {
98
+ return `# ${title}
99
+
100
+ This repository is a Buildchain-governed publication artifact source.
101
+
102
+ ## Local workflow
103
+
104
+ \`\`\`sh
105
+ pnpm paper:preflight
106
+ pnpm paper:agent:verify
107
+ pnpm paper:work:start -- <topic>
108
+ pnpm paper:work:submit
109
+ make pdf
110
+ \`\`\`
111
+
112
+ The public package identity is \`${packageName}\`. Buildchain owns reproducible
113
+ artifact generation, sealed publication, npm Trusted Publishing, and recovery;
114
+ this repository owns the paper source and review history.
115
+
116
+ See [docs/MAP.md](docs/MAP.md) for the repository map.
117
+ `;
118
+ }
119
+
120
+ export function scaffoldMap() {
121
+ return `# Repository Map
122
+
123
+ - \`paper/main.tex\`: paper source entrypoint.
124
+ - \`paper/references.bib\`: bibliography source.
125
+ - \`AGENTS.md\`: mandatory managed entry instructions for people and coding agents.
126
+ - \`.buildchain/paper/agent-entry.json\`: versioned, digest-bound local and CI entry policy.
127
+ - \`.buildchain/buildchain.toml\`: publication identity, toolchain, package, and lifecycle contract.
128
+ - \`.buildchain/contract-lock.json\`: accepted Buildchain runtime contract.
129
+ - \`.github/workflows/build.yml\`: thin read-only build and reproducibility caller.
130
+ - \`.github/workflows/verify.yml\`: thin required check that enforces the Paper entry and acceptance policy.
131
+ - \`.github/workflows/paper-release.yml\`: thin protected sealed-release caller.
132
+
133
+ The repository does not own npm transaction logic, publication authority,
134
+ release-state recovery, or site deployment mechanics. Those remain Buildchain,
135
+ npm/GitHub, and downstream site responsibilities respectively.
136
+ `;
137
+ }
138
+
139
+ export function scaffoldMainTex(title) {
140
+ return `\\documentclass[11pt]{article}
141
+ \\usepackage[T1]{fontenc}
142
+ \\usepackage{lmodern}
143
+ \\usepackage{hyperref}
144
+
145
+ \\title{${texEscape(title)}}
146
+ \\author{}
147
+ \\date{}
148
+
149
+ \\begin{document}
150
+ \\maketitle
151
+
152
+ \\begin{abstract}
153
+ Replace this paragraph with the paper abstract.
154
+ \\end{abstract}
155
+
156
+ \\section{Introduction}
157
+ Replace this section with the reviewed paper content.
158
+
159
+ \\bibliographystyle{plain}
160
+ \\bibliography{paper/references}
161
+ \\end{document}
162
+ `;
163
+ }
@@ -1,11 +1,14 @@
1
1
  import path from "node:path";
2
+ import { collectPaperAgentEntry } from "./paper-agent-entry.js";
2
3
  import {
4
+ PAPER_PATHS,
3
5
  PAPER_WORK_BRANCH_PATTERN,
4
6
  gitResult,
5
7
  gitValue,
6
8
  normalizedWorkBranch,
7
9
  paperDevelopmentRef,
8
10
  paperWorkSource,
11
+ readJson,
9
12
  remoteBranchObservation,
10
13
  rootedPlan,
11
14
  workCheck,
@@ -26,10 +29,20 @@ function failedActions(checks) {
26
29
  }));
27
30
  }
28
31
 
32
+ function agentEntryForWork(cwd, buildchainSha) {
33
+ const policy = readJson(path.resolve(cwd, PAPER_PATHS.agentEntry)).value;
34
+ return collectPaperAgentEntry({
35
+ cwd,
36
+ buildchainSha: buildchainSha || policy?.runtime?.sourceSha || "",
37
+ mode: "contract",
38
+ });
39
+ }
40
+
29
41
  export function createPaperWorkStartPlan({
30
42
  cwd = process.cwd(),
31
43
  topic = "",
32
44
  branch = "",
45
+ buildchainSha = "",
33
46
  } = {}) {
34
47
  const resolvedCwd = path.resolve(cwd);
35
48
  const source = paperWorkSource(resolvedCwd);
@@ -56,7 +69,14 @@ export function createPaperWorkStartPlan({
56
69
  `${remoteDevelopment.sha}^{commit}`,
57
70
  ]).ok
58
71
  : false;
72
+ const agentEntry = agentEntryForWork(resolvedCwd, buildchainSha);
59
73
  const checks = [
74
+ workCheck(
75
+ "agent-entry.current",
76
+ agentEntry.ok,
77
+ "The mandatory Buildchain Paper agent-entry contract is current.",
78
+ "buildchain paper migrate --write --json",
79
+ ),
60
80
  workCheck(
61
81
  "repository.canonical-origin",
62
82
  source.canonical,
@@ -121,6 +141,9 @@ export function createPaperWorkStartPlan({
121
141
  developmentRef,
122
142
  remoteDevelopmentSha: remoteDevelopment.sha,
123
143
  },
144
+ runtime: {
145
+ sourceSha: buildchainSha || agentEntry.entry?.runtime?.sourceSha || "",
146
+ },
124
147
  target: { branch: targetBranch, startSha: remoteDevelopment.sha },
125
148
  checks,
126
149
  mutation: {
@@ -155,6 +178,7 @@ export function executePaperWorkStart(plan) {
155
178
  const fresh = createPaperWorkStartPlan({
156
179
  cwd: plan.cwd,
157
180
  branch: plan.target.branch,
181
+ buildchainSha: plan.runtime?.sourceSha || "",
158
182
  });
159
183
  if (!fresh.ok || fresh.planRoot !== plan.planRoot) {
160
184
  return {
@@ -184,6 +208,7 @@ export function createPaperWorkSubmitPlan({
184
208
  cwd = process.cwd(),
185
209
  pullRequests = [],
186
210
  pullRequestObservation = { ok: true },
211
+ buildchainSha = "",
187
212
  } = {}) {
188
213
  const resolvedCwd = path.resolve(cwd);
189
214
  const source = paperWorkSource(resolvedCwd);
@@ -222,7 +247,14 @@ export function createPaperWorkSubmitPlan({
222
247
  entry.headRefName === source.branch &&
223
248
  entry.baseRefName === developmentRef,
224
249
  );
250
+ const agentEntry = agentEntryForWork(resolvedCwd, buildchainSha);
225
251
  const checks = [
252
+ workCheck(
253
+ "agent-entry.current",
254
+ agentEntry.ok,
255
+ "The mandatory Buildchain Paper agent-entry contract is current.",
256
+ "buildchain paper migrate --write --json",
257
+ ),
226
258
  workCheck(
227
259
  "repository.canonical-origin",
228
260
  source.canonical,
@@ -292,6 +324,9 @@ export function createPaperWorkSubmitPlan({
292
324
  remoteSha: remoteWork.sha,
293
325
  },
294
326
  target: { branch: developmentRef, sha: remoteDevelopment.sha },
327
+ runtime: {
328
+ sourceSha: buildchainSha || agentEntry.entry?.runtime?.sourceSha || "",
329
+ },
295
330
  pullRequest: matchingPullRequest || null,
296
331
  checks,
297
332
  mutation: {