@attalabs/vinaya 0.24.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +25 -2
  2. package/aeg-root/contracts/developer-reviewer.md +3 -0
  3. package/aeg-root/contracts/planner-brief.md +13 -0
  4. package/aeg-root/enforcement.md +10 -6
  5. package/aeg-root/process.md +4 -4
  6. package/aeg-root/roles/brief-author.md +4 -2
  7. package/aeg-root/roles/developer.md +20 -14
  8. package/aeg-root/roles/planner.md +14 -0
  9. package/aeg-root/roles/principal.md +3 -0
  10. package/aeg-root/roles/reviewer.md +23 -7
  11. package/aeg-root/roles/security.md +21 -6
  12. package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
  13. package/aeg-root/state-machine.md +2 -2
  14. package/aeg-root/templates/brief-template.md +20 -4
  15. package/aeg-root/templates/issue-rationale-template.md +34 -1
  16. package/aeg-root/templates/pr-report-template.md +5 -13
  17. package/dist/checks/bin/check-body-bare-digits.js +572 -127
  18. package/dist/checks/bin/check-branch-topology.js +629 -73
  19. package/dist/checks/bin/check-brief-shape.js +1675 -70
  20. package/dist/checks/bin/check-changeset-coverage.js +572 -127
  21. package/dist/checks/bin/check-closes-n.js +629 -73
  22. package/dist/checks/bin/check-coherence.js +696 -134
  23. package/dist/checks/bin/check-dead-branch-push.js +505 -66
  24. package/dist/checks/bin/check-dispatch-readiness.js +696 -134
  25. package/dist/checks/bin/check-doc-coverage-push.js +572 -127
  26. package/dist/checks/bin/check-doc-coverage.js +572 -127
  27. package/dist/checks/bin/check-doctrine-no-procedures.js +572 -127
  28. package/dist/checks/bin/check-doctrine-portability.js +572 -127
  29. package/dist/checks/bin/check-evidence-fresh.js +572 -127
  30. package/dist/checks/bin/check-exec-bits.js +572 -127
  31. package/dist/checks/bin/check-first-push-dispatch.js +696 -134
  32. package/dist/checks/bin/check-issue-assignment.js +629 -73
  33. package/dist/checks/bin/check-main-branch-refusal.js +505 -66
  34. package/dist/checks/bin/check-no-disk-state.js +505 -66
  35. package/dist/checks/bin/check-pr-report-density.js +505 -66
  36. package/dist/checks/bin/check-quoted-command.js +572 -127
  37. package/dist/checks/bin/check-reader-resolvable-prose.js +610 -133
  38. package/dist/checks/bin/check-registry-gates.js +516 -68
  39. package/dist/checks/bin/check-retired-vocabulary.js +572 -127
  40. package/dist/checks/bin/check-review-gate.js +646 -130
  41. package/dist/checks/bin/check-single-plan-pr.js +505 -66
  42. package/dist/checks/bin/check-test-plan.js +505 -66
  43. package/dist/checks/bin/check-token-collection-wired.js +505 -66
  44. package/dist/checks/bin/check-token-report.js +505 -66
  45. package/dist/checks/bin/check-workspace-escape.js +572 -127
  46. package/dist/index.js +7696 -4271
  47. package/package.json +1 -1
  48. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
  49. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  52. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1pndh3_._.js → [root-of-the-server]__0p8q38b._.js} +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0wxycau._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  107. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
  108. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
  110. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
  112. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
  114. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  115. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
  116. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
  117. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
@@ -1935,13 +1935,18 @@ function parseInlineFieldList(section) {
1935
1935
  }
1936
1936
  // ../../packages/aeg-core/src/verdict-extraction.ts
1937
1937
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1938
- function firstThreeLines(comment) {
1938
+ var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1939
+ function firstFiveLines(comment) {
1939
1940
  return comment.split(`
1940
- `).slice(0, 3).join(`
1941
+ `).slice(0, 5).join(`
1941
1942
  `);
1942
1943
  }
1943
1944
  function extractHeadSha(comment) {
1944
- const m = firstThreeLines(comment).match(HEAD_SHA_PATTERN);
1945
+ const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
1946
+ return m ? m[1].toLowerCase() : null;
1947
+ }
1948
+ function extractObjectivesVersion(comment) {
1949
+ const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1945
1950
  return m ? m[1].toLowerCase() : null;
1946
1951
  }
1947
1952
  function extractVerdict(comments, valuePattern, missingLabel) {
@@ -1950,21 +1955,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1950
1955
  return {
1951
1956
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1952
1957
  headSha: null,
1958
+ objectivesVersion: null,
1953
1959
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1954
1960
  };
1955
1961
  }
1956
1962
  const latest = candidates[candidates.length - 1];
1957
- const m = firstThreeLines(latest).match(valuePattern);
1963
+ const m = firstFiveLines(latest).match(valuePattern);
1958
1964
  if (!m) {
1959
1965
  return {
1960
- value: `the most recent ${missingLabel} comment's VERDICT line is not within its first three lines — DANGLING, see below`,
1966
+ value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1961
1967
  headSha: null,
1962
- danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-three-line read window`
1968
+ objectivesVersion: null,
1969
+ danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1963
1970
  };
1964
1971
  }
1965
1972
  return {
1966
1973
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1967
1974
  headSha: extractHeadSha(latest),
1975
+ objectivesVersion: extractObjectivesVersion(latest),
1968
1976
  danglingNote: null
1969
1977
  };
1970
1978
  }
@@ -2321,8 +2329,105 @@ function overrideActive(opts) {
2321
2329
  return true;
2322
2330
  return false;
2323
2331
  }
2324
- // ../../packages/aeg-core/src/premise-check.ts
2332
+ // ../../packages/aeg-core/src/objectives.ts
2325
2333
  import { createHash as createHash2 } from "node:crypto";
2334
+ var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2335
+ var NEXT_HEADING_RE = /^##[ \t]/m;
2336
+ var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2337
+ function hasBacktickedPath(text) {
2338
+ let i = 0;
2339
+ while (i < text.length) {
2340
+ const start = text.indexOf("`", i);
2341
+ if (start === -1)
2342
+ return false;
2343
+ const end = text.indexOf("`", start + 1);
2344
+ if (end === -1)
2345
+ return false;
2346
+ if (text.slice(start + 1, end).includes("/"))
2347
+ return true;
2348
+ i = end + 1;
2349
+ }
2350
+ return false;
2351
+ }
2352
+ var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
2353
+ function stripObjectiveBackticks(text) {
2354
+ return text.replace(/`[^`\n]*`/g, " ");
2355
+ }
2356
+ function wordCount(text) {
2357
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2358
+ }
2359
+ function objectivesSectionText(body) {
2360
+ const heading = HEADING_RE.exec(body);
2361
+ if (!heading)
2362
+ return null;
2363
+ const afterHeading = body.slice(heading.index + heading[0].length);
2364
+ const next = NEXT_HEADING_RE.exec(afterHeading);
2365
+ return next ? afterHeading.slice(0, next.index) : afterHeading;
2366
+ }
2367
+ function hasObjectivesHeading(body) {
2368
+ return HEADING_RE.test(body);
2369
+ }
2370
+ function objectivesOf(body) {
2371
+ const section = objectivesSectionText(body);
2372
+ if (section === null) {
2373
+ return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
2374
+ }
2375
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
2376
+ if (lines.length === 0) {
2377
+ return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
2378
+ }
2379
+ const objectives = [];
2380
+ const errors = [];
2381
+ for (const line of lines) {
2382
+ const m = OBJECTIVE_LINE_RE.exec(line);
2383
+ if (!m) {
2384
+ errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
2385
+ continue;
2386
+ }
2387
+ const n = m[1];
2388
+ const text = m[2].trim();
2389
+ if (text.length === 0) {
2390
+ errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
2391
+ continue;
2392
+ }
2393
+ if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
2394
+ errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Brief Author maps it to files).`);
2395
+ continue;
2396
+ }
2397
+ objectives.push({ id: `O${n}`, text });
2398
+ }
2399
+ for (let i = 0;i < objectives.length; i++) {
2400
+ const expected = i + 1;
2401
+ const actual = Number.parseInt(objectives[i].id.slice(1), 10);
2402
+ if (actual !== expected) {
2403
+ errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
2404
+ break;
2405
+ }
2406
+ }
2407
+ if (errors.length > 0)
2408
+ return { ok: false, errors };
2409
+ return { ok: true, objectives };
2410
+ }
2411
+ function normalizeText(text) {
2412
+ return text.trim().replace(/\s+/g, " ");
2413
+ }
2414
+ function objectivesVersion(objectives) {
2415
+ const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
2416
+ `);
2417
+ return createHash2("sha256").update(normalized).digest("hex");
2418
+ }
2419
+ function isIssueNotFoundError(err) {
2420
+ const stderr = err?.stderr;
2421
+ const haystack = [
2422
+ err?.message ?? "",
2423
+ typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
2424
+ ].join(`
2425
+ `);
2426
+ return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2427
+ }
2428
+
2429
+ // ../../packages/aeg-core/src/premise-check.ts
2430
+ import { createHash as createHash3 } from "node:crypto";
2326
2431
  var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
2327
2432
  function isPremiseHeader(line) {
2328
2433
  const stripped = line.replace(/[*#]/g, "").trim();
@@ -2393,7 +2498,7 @@ function checkPremises(assertions, fileReader) {
2393
2498
  } else if (a.kind === "absent" && content.includes(a.value)) {
2394
2499
  failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
2395
2500
  } else if (a.kind === "sha256") {
2396
- const actual = createHash2("sha256").update(content).digest("hex");
2501
+ const actual = createHash3("sha256").update(content).digest("hex");
2397
2502
  if (actual !== a.value.toLowerCase()) {
2398
2503
  failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
2399
2504
  }
@@ -2754,8 +2859,55 @@ function checkDefeatCases(prBody) {
2754
2859
  ]
2755
2860
  };
2756
2861
  }
2862
+ var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
2863
+ var OBJECTIVE_REF_RE = /O(\d+)/g;
2864
+ function citedObjectiveIds(section6) {
2865
+ const ids = new Set;
2866
+ for (const m of section6.matchAll(PART_CITATION_RE)) {
2867
+ for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
2868
+ ids.add(Number.parseInt(r[1], 10));
2869
+ }
2870
+ return ids;
2871
+ }
2872
+ function checkObjectivesCopy(prBody, issueObjectives) {
2873
+ const parsed = objectivesOf(prBody);
2874
+ if (!parsed.ok) {
2875
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
2876
+ }
2877
+ if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
2878
+ return {
2879
+ status: "fail",
2880
+ errors: [
2881
+ "brief-validation objectives copy: the brief's `## Objectives` section does not match the Issue's — copy the Issue's `## Objectives` section (aeg-root/roles/developer.md)."
2882
+ ]
2883
+ };
2884
+ }
2885
+ return { status: "pass", errors: [] };
2886
+ }
2887
+ function checkObjectivesCoverage(prBody) {
2888
+ const parsed = objectivesOf(prBody);
2889
+ if (!parsed.ok) {
2890
+ return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
2891
+ }
2892
+ const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
2893
+ const section6 = extractNumberedSection(prBody, 6) ?? "";
2894
+ const cited = citedObjectiveIds(section6);
2895
+ const maxDeclared = Math.max(...declaredIds, 0);
2896
+ const errors = [];
2897
+ for (const id of declaredIds) {
2898
+ if (!cited.has(id)) {
2899
+ errors.push(`brief-validation objectives coverage: O${id} is not cited by any Part in §6 — every objective must be cited by at least one Part.`);
2900
+ }
2901
+ }
2902
+ for (const id of cited) {
2903
+ if (!declaredIds.has(id)) {
2904
+ errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
2905
+ }
2906
+ }
2907
+ return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
2908
+ }
2757
2909
  function checkBriefSections(prBody, readTier, options = {}) {
2758
- const { requireClosesN = true, consumersOf = () => [] } = options;
2910
+ const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
2759
2911
  const results = [
2760
2912
  checkTierField(prBody, readTier),
2761
2913
  checkTestPlan(prBody),
@@ -2773,6 +2925,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
2773
2925
  checkCommandsCarryOutput(prBody),
2774
2926
  checkConsumerTests(prBody, consumersOf),
2775
2927
  checkDefeatCases(prBody),
2928
+ ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2776
2929
  ...requireClosesN ? [checkClosesN(prBody)] : []
2777
2930
  ];
2778
2931
  return { errors: results.flatMap((r) => r.errors) };
@@ -2971,14 +3124,14 @@ function checkSectionDensity(prBody, heading) {
2971
3124
  ]
2972
3125
  };
2973
3126
  }
2974
- function checkSummaryDensity(prBody) {
2975
- return checkSectionDensity(prBody, "Summary");
3127
+ function checkDecisionsDensity(prBody) {
3128
+ return checkSectionDensity(prBody, "Decisions");
2976
3129
  }
2977
3130
  function checkScopeDensity(prBody) {
2978
3131
  return checkSectionDensity(prBody, "Scope");
2979
3132
  }
2980
3133
  function checkPrReportDensity(prBody) {
2981
- const results = [checkSummaryDensity(prBody), checkScopeDensity(prBody)];
3134
+ const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
2982
3135
  return { errors: results.flatMap((r) => r.errors) };
2983
3136
  }
2984
3137
  // ../../packages/aeg-core/src/issue-validation.ts
@@ -3010,6 +3163,15 @@ function checkIssueRationale(body) {
3010
3163
  }
3011
3164
  return { status: errors.length > 0 ? "fail" : "pass", errors };
3012
3165
  }
3166
+ var OBJECTIVES_SINCE_ISSUE = 404;
3167
+ function checkIssueObjectives(body, issueNumber) {
3168
+ if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
3169
+ return { status: "pass", errors: [] };
3170
+ const result = objectivesOf(body);
3171
+ if (result.ok)
3172
+ return { status: "pass", errors: [] };
3173
+ return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
3174
+ }
3013
3175
  function isTaskIssueLabelSet(labels) {
3014
3176
  return hasLabel("tranche", labels);
3015
3177
  }
@@ -3271,7 +3433,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3271
3433
  continue;
3272
3434
  const errors = [
3273
3435
  ...checkIssueRationale(issue.body).errors,
3274
- ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
3436
+ ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3437
+ ...checkIssueObjectives(issue.body, issue.number).errors
3275
3438
  ];
3276
3439
  if (errors.length === 0)
3277
3440
  continue;
@@ -3386,6 +3549,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3386
3549
  }
3387
3550
  return { ok: true, expectedIssue };
3388
3551
  }
3552
+ // ../../packages/aeg-core/src/archive-task.ts
3553
+ function closesRefs(text) {
3554
+ return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
3555
+ }
3556
+ function extractIssue(body) {
3557
+ const stripped = stripCode(body);
3558
+ const bodyNums = closesRefs(stripped);
3559
+ const anchored = anchoredRegion(stripped, "CLOSES");
3560
+ if (anchored !== null) {
3561
+ const anchorNums = closesRefs(anchored);
3562
+ if (anchorNums.length === 0)
3563
+ return { issue: null, extraIssues: bodyNums, outsideHeader: false };
3564
+ const issue2 = anchorNums[0];
3565
+ return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
3566
+ }
3567
+ const headerNums = closesRefs(headerRegion(stripped));
3568
+ if (bodyNums.length === 0)
3569
+ return { issue: null, extraIssues: [], outsideHeader: false };
3570
+ const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3571
+ return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3572
+ }
3389
3573
  // ../../packages/aeg-core/src/review-gate.ts
3390
3574
  function isBoundToHead(extraction, headSha) {
3391
3575
  if (!extraction.headSha)
@@ -3404,6 +3588,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3404
3588
  function isBoundToPatch(extraction, headSha, patchIdOf) {
3405
3589
  return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3406
3590
  }
3591
+ function isBoundToObjectives(extraction, currentVersion) {
3592
+ if (currentVersion === null)
3593
+ return true;
3594
+ return extraction.objectivesVersion === currentVersion;
3595
+ }
3407
3596
  function checkReviewGate(input) {
3408
3597
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3409
3598
  const waived = isWaiverLabelActorVerified({
@@ -3430,7 +3619,9 @@ function checkReviewGate(input) {
3430
3619
  const securityClean = security.value === "PASS";
3431
3620
  const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3432
3621
  const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3433
- if (codeReviewClean && codeReviewBound && securityClean && securityBound && mechanicalChecksClean) {
3622
+ const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3623
+ const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3624
+ if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
3434
3625
  return {
3435
3626
  verdict: "pass",
3436
3627
  reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
@@ -3442,11 +3633,15 @@ function checkReviewGate(input) {
3442
3633
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
3443
3634
  } else if (!codeReviewBound) {
3444
3635
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3636
+ } else if (!codeReviewObjectivesBound) {
3637
+ problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3445
3638
  }
3446
3639
  if (!securityClean) {
3447
3640
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
3448
3641
  } else if (!securityBound) {
3449
3642
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3643
+ } else if (!securityObjectivesBound) {
3644
+ problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3450
3645
  }
3451
3646
  if (!mechanicalChecksClean) {
3452
3647
  problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
@@ -3682,55 +3877,20 @@ function checkG6(rows, registeredCheckNames) {
3682
3877
  }
3683
3878
  return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
3684
3879
  }
3685
- // ../../packages/aeg-core/src/dead-branch-push-guard.ts
3686
- function checkDeadBranchPush(input) {
3687
- const { branch, prState, prNumber } = input;
3688
- if (prState === "MERGED") {
3689
- return {
3690
- verdict: "refuse",
3691
- reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
3692
- };
3693
- }
3694
- if (prState === "CLOSED") {
3695
- return {
3696
- verdict: "refuse",
3697
- reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
3698
- };
3699
- }
3700
- if (prState === "OPEN") {
3701
- return {
3702
- verdict: "allow",
3703
- reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
3704
- };
3705
- }
3706
- if (prState === "NONE") {
3707
- return {
3708
- verdict: "allow",
3709
- reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
3710
- };
3711
- }
3712
- return {
3713
- verdict: "allow",
3714
- reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
3715
- };
3716
- }
3717
- // ../../packages/aeg-core/src/baseline-capture.ts
3718
- function captureBaseline(counts, capturedAt) {
3719
- return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
3880
+ // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3881
+ var PRODUCT_SLUG_SCOPE = [
3882
+ "apps/cli/src",
3883
+ ".github/workflows",
3884
+ ".vinaya",
3885
+ "apps/cli/README.md",
3886
+ "packages/sources/README.md"
3887
+ ];
3888
+ function isUnderOrEqual(path, prefix) {
3889
+ return path === prefix || path.startsWith(`${prefix}/`);
3720
3890
  }
3721
- function compareToBaseline(current, baseline) {
3722
- const perTool = current.map((c) => {
3723
- const baselineEntry = baseline.find((b) => b.tool === c.tool);
3724
- const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
3725
- return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
3726
- });
3727
- return {
3728
- withinBudget: perTool.every((t) => t.delta <= 0),
3729
- delta: perTool.reduce((sum, t) => sum + t.delta, 0),
3730
- perTool
3731
- };
3891
+ function isProductScopeFile(path) {
3892
+ return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
3732
3893
  }
3733
- // ../../packages/aeg-core/src/reader-resolvable-prose.ts
3734
3894
  var SHIPS_PREFIX = "aeg-root/";
3735
3895
  function shipsArchivePrefix(shipsPrefix) {
3736
3896
  return `${shipsPrefix}tranches/completed/`;
@@ -3750,6 +3910,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
3750
3910
  }
3751
3911
  if (isSpecFile2(path) || isClaudeMdFile(path))
3752
3912
  return "internal";
3913
+ if (isProductScopeFile(path))
3914
+ return "product";
3753
3915
  return null;
3754
3916
  }
3755
3917
  var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3762,6 +3924,9 @@ function stripNonProse(path, content) {
3762
3924
  }
3763
3925
  return content;
3764
3926
  }
3927
+ function stripNonProseForProduct(path, content) {
3928
+ return path.endsWith(".md") ? stripNonProse(path, content) : content;
3929
+ }
3765
3930
  function lineAt(content, index) {
3766
3931
  let line = 1;
3767
3932
  for (let i = 0;i < index; i++) {
@@ -3786,12 +3951,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3786
3951
  { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
3787
3952
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
3788
3953
  ];
3954
+ const productPatterns = [
3955
+ { pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
3956
+ ];
3789
3957
  for (const file of files) {
3790
3958
  const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
3791
- if (!cls || !SWEPT_CLASSES.has(cls))
3959
+ if (!cls || cls === "internal")
3792
3960
  continue;
3793
- const scrubbed = stripNonProse(file.path, file.content);
3794
- for (const { pattern, what, group } of patterns) {
3961
+ if (cls !== "product" && !SWEPT_CLASSES.has(cls))
3962
+ continue;
3963
+ const blocking = cls === "product";
3964
+ const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
3965
+ const patternsToRun = blocking ? productPatterns : patterns;
3966
+ for (const { pattern, what, group } of patternsToRun) {
3795
3967
  pattern.lastIndex = 0;
3796
3968
  let match = pattern.exec(scrubbed);
3797
3969
  while (match !== null) {
@@ -3799,7 +3971,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
3799
3971
  findings.push({
3800
3972
  file: file.path,
3801
3973
  line: lineAt(scrubbed, match.index),
3802
- message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`
3974
+ message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
3975
+ blocking
3803
3976
  });
3804
3977
  match = pattern.exec(scrubbed);
3805
3978
  }
@@ -3849,7 +4022,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
3849
4022
  findings.push({
3850
4023
  file: file.path,
3851
4024
  line,
3852
- message: `uses coined term "${term}" without defining it inline or linking the glossary`
4025
+ message: `uses coined term "${term}" without defining it inline or linking the glossary`,
4026
+ blocking: false
3853
4027
  });
3854
4028
  }
3855
4029
  }
@@ -3872,6 +4046,7 @@ function parseGlossaryTerms(glossaryContent) {
3872
4046
  }
3873
4047
  return terms;
3874
4048
  }
4049
+
3875
4050
  // ../../packages/aeg-core/src/quoted-command.ts
3876
4051
  var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
3877
4052
  var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
@@ -3966,6 +4141,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
3966
4141
  }
3967
4142
  return findings;
3968
4143
  }
4144
+ // ../../packages/aeg-core/src/dead-branch-push-guard.ts
4145
+ function checkDeadBranchPush(input) {
4146
+ const { branch, prState, prNumber } = input;
4147
+ if (prState === "MERGED") {
4148
+ return {
4149
+ verdict: "refuse",
4150
+ reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
4151
+ };
4152
+ }
4153
+ if (prState === "CLOSED") {
4154
+ return {
4155
+ verdict: "refuse",
4156
+ reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
4157
+ };
4158
+ }
4159
+ if (prState === "OPEN") {
4160
+ return {
4161
+ verdict: "allow",
4162
+ reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
4163
+ };
4164
+ }
4165
+ if (prState === "NONE") {
4166
+ return {
4167
+ verdict: "allow",
4168
+ reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
4169
+ };
4170
+ }
4171
+ return {
4172
+ verdict: "allow",
4173
+ reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
4174
+ };
4175
+ }
4176
+ // ../../packages/aeg-core/src/baseline-capture.ts
4177
+ function captureBaseline(counts, capturedAt) {
4178
+ return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
4179
+ }
4180
+ function compareToBaseline(current, baseline) {
4181
+ const perTool = current.map((c) => {
4182
+ const baselineEntry = baseline.find((b) => b.tool === c.tool);
4183
+ const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
4184
+ return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
4185
+ });
4186
+ return {
4187
+ withinBudget: perTool.every((t) => t.delta <= 0),
4188
+ delta: perTool.reduce((sum, t) => sum + t.delta, 0),
4189
+ perTool
4190
+ };
4191
+ }
3969
4192
  // ../../packages/aeg-core/src/retired-vocabulary.ts
3970
4193
  var RETIRED_PATTERNS = [
3971
4194
  String.raw`\bD-[0-9]{3}\b`,
@@ -4435,6 +4658,222 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4435
4658
  }
4436
4659
  return findings;
4437
4660
  }
4661
+ // ../../packages/aeg-core/src/log/schema.ts
4662
+ import { z } from "zod";
4663
+ var ROLE_VALUES = [
4664
+ "planner",
4665
+ "brief-author",
4666
+ "developer",
4667
+ "code-reviewer",
4668
+ "security",
4669
+ "principal",
4670
+ "archivist",
4671
+ "architect"
4672
+ ];
4673
+ var RoleSchema = z.enum(ROLE_VALUES);
4674
+ var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4675
+ var HostSchema = z.enum(HOST_VALUES);
4676
+ var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4677
+ var HeaderMetaSchema = z.object({
4678
+ schema: z.literal(1),
4679
+ ts: z.string(),
4680
+ run_id: z.string().regex(RUN_ID_PATTERN),
4681
+ seq: z.number().int().nonnegative(),
4682
+ repo: z.string().nullable(),
4683
+ vinaya: z.string(),
4684
+ doctrine: z.string(),
4685
+ host: HostSchema,
4686
+ machine: z.string()
4687
+ }).strict();
4688
+ var SubjectSchema = z.object({
4689
+ issue: z.number().int().nullable(),
4690
+ pr: z.number().int().optional(),
4691
+ sha: z.string().optional(),
4692
+ role: z.union([RoleSchema, z.literal("unattributed")]),
4693
+ round: z.number().int().optional(),
4694
+ objectives_version: z.string().optional()
4695
+ }).strict();
4696
+ var HeaderSchema = z.object({
4697
+ meta: HeaderMetaSchema,
4698
+ subject: SubjectSchema
4699
+ }).strict();
4700
+ var envelopeTail = {
4701
+ duration_ms: z.number().nonnegative().optional(),
4702
+ payload: z.object({}).strict()
4703
+ };
4704
+ var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4705
+ z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4706
+ z.object({
4707
+ type: z.literal("round_pushed"),
4708
+ pr: z.number().int(),
4709
+ head: z.string(),
4710
+ comment_id: z.number().int()
4711
+ }).strict(),
4712
+ z.object({
4713
+ type: z.literal("verdict"),
4714
+ verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4715
+ head: z.string(),
4716
+ comment_id: z.number().int(),
4717
+ objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4718
+ findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
4719
+ }).strict(),
4720
+ z.object({
4721
+ type: z.literal("escalation"),
4722
+ class: z.enum(["authority", "strategy", "product"]),
4723
+ comment_id: z.number().int()
4724
+ }).strict(),
4725
+ z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4726
+ z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4727
+ z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
4728
+ ]);
4729
+ var dispatchShared = {
4730
+ meta: HeaderMetaSchema,
4731
+ subject: SubjectSchema,
4732
+ kind: z.literal("dispatch"),
4733
+ ...envelopeTail,
4734
+ target_role: RoleSchema,
4735
+ model: z.string(),
4736
+ round: z.number().int().optional(),
4737
+ effect_id: z.string()
4738
+ };
4739
+ var DispatchEventSchema = z.discriminatedUnion("event", [
4740
+ z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4741
+ z.object({
4742
+ ...dispatchShared,
4743
+ event: z.literal("outcome_received"),
4744
+ outcome: DispatchOutcomeSchema,
4745
+ usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
4746
+ }).strict(),
4747
+ z.object({
4748
+ ...dispatchShared,
4749
+ event: z.literal("dispatch_failed"),
4750
+ reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
4751
+ }).strict()
4752
+ ]);
4753
+ var loopShared = {
4754
+ meta: HeaderMetaSchema,
4755
+ subject: SubjectSchema,
4756
+ kind: z.literal("dev_review_loop"),
4757
+ ...envelopeTail,
4758
+ loop_id: z.string()
4759
+ };
4760
+ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4761
+ z.object({
4762
+ ...loopShared,
4763
+ event: z.literal("loop_started"),
4764
+ task: z.number().int(),
4765
+ policy: z.object({
4766
+ max_rounds: z.number().int().nonnegative(),
4767
+ reviewers: z.array(RoleSchema),
4768
+ models: z.record(RoleSchema, z.string())
4769
+ }).strict()
4770
+ }).strict(),
4771
+ z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4772
+ z.object({
4773
+ ...loopShared,
4774
+ event: z.literal("gate_result_read"),
4775
+ round: z.number().int(),
4776
+ head: z.string(),
4777
+ green: z.boolean()
4778
+ }).strict(),
4779
+ z.object({
4780
+ ...loopShared,
4781
+ event: z.literal("verdicts_read"),
4782
+ round: z.number().int(),
4783
+ head: z.string(),
4784
+ all_approve: z.boolean(),
4785
+ blockers: z.number().int().nonnegative()
4786
+ }).strict(),
4787
+ z.object({
4788
+ ...loopShared,
4789
+ event: z.literal("findings_compared"),
4790
+ round: z.number().int(),
4791
+ open: z.array(z.string()),
4792
+ resolved: z.array(z.string()),
4793
+ new: z.array(z.string()),
4794
+ recurring: z.array(z.string())
4795
+ }).strict(),
4796
+ z.object({
4797
+ ...loopShared,
4798
+ event: z.literal("stop_condition_met"),
4799
+ round: z.number().int(),
4800
+ condition: z.enum([
4801
+ "green",
4802
+ "max_rounds",
4803
+ "no_progress",
4804
+ "escalated",
4805
+ "principal_stop",
4806
+ "confidence",
4807
+ "reappearance"
4808
+ ])
4809
+ }).strict(),
4810
+ z.object({
4811
+ ...loopShared,
4812
+ event: z.literal("paused"),
4813
+ round: z.number().int(),
4814
+ reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
4815
+ }).strict(),
4816
+ z.object({
4817
+ ...loopShared,
4818
+ event: z.literal("resumed"),
4819
+ round: z.number().int(),
4820
+ by: z.literal("principal")
4821
+ }).strict(),
4822
+ z.object({
4823
+ ...loopShared,
4824
+ event: z.literal("round_ended"),
4825
+ round: z.number().int(),
4826
+ base_head: z.string(),
4827
+ head: z.string(),
4828
+ files_changed: z.number().int().nonnegative(),
4829
+ insertions: z.number().int().nonnegative(),
4830
+ deletions: z.number().int().nonnegative(),
4831
+ wall_ms: z.number().nonnegative(),
4832
+ outcome: z.enum(["green", "changes_requested", "escalated"])
4833
+ }).strict(),
4834
+ z.object({
4835
+ ...loopShared,
4836
+ event: z.literal("journal_finalized"),
4837
+ rounds: z.number().int().nonnegative(),
4838
+ total_wall_ms: z.number().nonnegative(),
4839
+ time_to_green_ms: z.number().nonnegative().nullable(),
4840
+ files_changed_total: z.number().int().nonnegative(),
4841
+ final_head: z.string(),
4842
+ result: z.enum(["merged_ready", "stopped"])
4843
+ }).strict()
4844
+ ]);
4845
+ var ForgeOpSchema = z.enum([
4846
+ "pr.create",
4847
+ "pr.comment",
4848
+ "pr.body.replace",
4849
+ "pr.refreeze",
4850
+ "issue.create",
4851
+ "issue.edit",
4852
+ "issue.comment",
4853
+ "milestone.create",
4854
+ "milestone.edit",
4855
+ "milestone.close",
4856
+ "label.add",
4857
+ "label.remove"
4858
+ ]);
4859
+ var ForgeWriteTargetSchema = z.object({
4860
+ issue: z.number().int().optional(),
4861
+ pr: z.number().int().optional()
4862
+ }).strict();
4863
+ var forgeWriteShared = {
4864
+ meta: HeaderMetaSchema,
4865
+ subject: SubjectSchema,
4866
+ kind: z.literal("forge_write"),
4867
+ ...envelopeTail,
4868
+ op: ForgeOpSchema,
4869
+ target: ForgeWriteTargetSchema
4870
+ };
4871
+ var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4872
+ z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4873
+ z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4874
+ z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
4875
+ ]);
4876
+ var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
4438
4877
  // src/checks/contract.ts
4439
4878
  var CHECK_SCHEMA_VERSION = 1;
4440
4879
  function emitCheckError(error) {