@telora/daemon 0.20.21 → 0.20.28

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 (89) hide show
  1. package/build-info.json +4 -3
  2. package/dist/audit-phase.d.ts +43 -6
  3. package/dist/audit-phase.d.ts.map +1 -1
  4. package/dist/audit-phase.js +124 -23
  5. package/dist/audit-phase.js.map +1 -1
  6. package/dist/cascade-restatement.d.ts +13 -147
  7. package/dist/cascade-restatement.d.ts.map +1 -1
  8. package/dist/cascade-restatement.js +13 -429
  9. package/dist/cascade-restatement.js.map +1 -1
  10. package/dist/focus-engine.d.ts.map +1 -1
  11. package/dist/focus-engine.js +15 -0
  12. package/dist/focus-engine.js.map +1 -1
  13. package/dist/focus-loop-registry.d.ts.map +1 -1
  14. package/dist/focus-loop-registry.js +16 -90
  15. package/dist/focus-loop-registry.js.map +1 -1
  16. package/dist/focus-loop.d.ts +0 -2
  17. package/dist/focus-loop.d.ts.map +1 -1
  18. package/dist/focus-loop.js +33 -25
  19. package/dist/focus-loop.js.map +1 -1
  20. package/dist/focus-phase.d.ts +26 -0
  21. package/dist/focus-phase.d.ts.map +1 -1
  22. package/dist/focus-phase.js +38 -0
  23. package/dist/focus-phase.js.map +1 -1
  24. package/dist/listener.d.ts.map +1 -1
  25. package/dist/listener.js +40 -9
  26. package/dist/listener.js.map +1 -1
  27. package/dist/loops/llm-client.d.ts.map +1 -1
  28. package/dist/loops/llm-client.js +17 -4
  29. package/dist/loops/llm-client.js.map +1 -1
  30. package/dist/queries/audit-runs.d.ts +56 -2
  31. package/dist/queries/audit-runs.d.ts.map +1 -1
  32. package/dist/queries/audit-runs.js +40 -0
  33. package/dist/queries/audit-runs.js.map +1 -1
  34. package/dist/queries/deliveries.d.ts +27 -0
  35. package/dist/queries/deliveries.d.ts.map +1 -1
  36. package/dist/queries/deliveries.js.map +1 -1
  37. package/dist/queries/focuses.d.ts +0 -9
  38. package/dist/queries/focuses.d.ts.map +1 -1
  39. package/dist/queries/focuses.js +0 -14
  40. package/dist/queries/focuses.js.map +1 -1
  41. package/dist/resolvers/index.d.ts +1 -1
  42. package/dist/resolvers/index.d.ts.map +1 -1
  43. package/dist/resolvers/index.js +1 -1
  44. package/dist/resolvers/index.js.map +1 -1
  45. package/dist/resolvers/shared/audit.d.ts +5 -0
  46. package/dist/resolvers/shared/audit.d.ts.map +1 -1
  47. package/dist/resolvers/shared/audit.js +8 -3
  48. package/dist/resolvers/shared/audit.js.map +1 -1
  49. package/dist/resolvers/shared/git-diff.d.ts +13 -0
  50. package/dist/resolvers/shared/git-diff.d.ts.map +1 -1
  51. package/dist/resolvers/shared/git-diff.js +30 -0
  52. package/dist/resolvers/shared/git-diff.js.map +1 -1
  53. package/dist/spawn/review-role-fallback.d.ts +74 -0
  54. package/dist/spawn/review-role-fallback.d.ts.map +1 -0
  55. package/dist/spawn/review-role-fallback.js +62 -0
  56. package/dist/spawn/review-role-fallback.js.map +1 -0
  57. package/dist/verification-engine.d.ts +15 -1
  58. package/dist/verification-engine.d.ts.map +1 -1
  59. package/dist/verification-engine.js +142 -69
  60. package/dist/verification-engine.js.map +1 -1
  61. package/dist/verification-feedback.d.ts +26 -1
  62. package/dist/verification-feedback.d.ts.map +1 -1
  63. package/dist/verification-feedback.js +82 -0
  64. package/dist/verification-feedback.js.map +1 -1
  65. package/dist/verify-reconciliation.d.ts +16 -1
  66. package/dist/verify-reconciliation.d.ts.map +1 -1
  67. package/dist/verify-reconciliation.js +18 -2
  68. package/dist/verify-reconciliation.js.map +1 -1
  69. package/dist/verify-restatement.d.ts +7 -0
  70. package/dist/verify-restatement.d.ts.map +1 -1
  71. package/dist/verify-restatement.js +1 -0
  72. package/dist/verify-restatement.js.map +1 -1
  73. package/package.json +2 -2
  74. package/dist/drift-ratify-loop.d.ts +0 -68
  75. package/dist/drift-ratify-loop.d.ts.map +0 -1
  76. package/dist/drift-ratify-loop.js +0 -161
  77. package/dist/drift-ratify-loop.js.map +0 -1
  78. package/dist/negative-branch.d.ts +0 -136
  79. package/dist/negative-branch.d.ts.map +0 -1
  80. package/dist/negative-branch.js +0 -241
  81. package/dist/negative-branch.js.map +0 -1
  82. package/dist/shortfall-disposition-config.d.ts +0 -34
  83. package/dist/shortfall-disposition-config.d.ts.map +0 -1
  84. package/dist/shortfall-disposition-config.js +0 -46
  85. package/dist/shortfall-disposition-config.js.map +0 -1
  86. package/dist/shortfall-disposition-loop.d.ts +0 -86
  87. package/dist/shortfall-disposition-loop.d.ts.map +0 -1
  88. package/dist/shortfall-disposition-loop.js +0 -309
  89. package/dist/shortfall-disposition-loop.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/resolvers/shared/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUrF"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/resolvers/shared/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOrF"}
@@ -8,18 +8,23 @@
8
8
  * If no prior audit exists or the lookup fails, the resolvers fall back
9
9
  * to "all since focus start" (no lower bound).
10
10
  */
11
- import { callApi } from '../../queries/shared.js';
11
+ import { getRecentAuditRun } from '../../queries/audit-runs.js';
12
12
  /**
13
13
  * Best-effort cycle window. Returns the previous cycle's completed_at as
14
14
  * the lower bound for "things that happened in the current cycle". Null
15
15
  * means "no prior cycle, include everything since focus start". Failures
16
16
  * fall back to null so a missing audit_runs table or transient API error
17
17
  * does not blank out the resolver output.
18
+ *
19
+ * The snake_case -> camelCase transform lives in getRecentAuditRun (the API
20
+ * returns the Supabase row verbatim -- neither successResponse nor callApi
21
+ * maps keys). Reading `completedAt` off the raw response here always yielded
22
+ * undefined, so this window silently never narrowed.
18
23
  */
19
24
  export async function getAuditCycleLowerBound(focusId) {
20
25
  try {
21
- const result = await callApi('daemon_get_recent_audit_run_for_focus', { focusId });
22
- return result.auditRun?.completedAt ?? null;
26
+ const run = await getRecentAuditRun(focusId);
27
+ return run?.completedAt ?? null;
23
28
  }
24
29
  catch {
25
30
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/resolvers/shared/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AASlD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAe;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,uCAAuC,EACvC,EAAE,OAAO,EAAE,CACZ,CAAC;QACF,OAAO,MAAM,CAAC,QAAQ,EAAE,WAAW,IAAI,IAAI,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/resolvers/shared/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAe;IAC3D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC7C,OAAO,GAAG,EAAE,WAAW,IAAI,IAAI,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -11,4 +11,17 @@ export declare function getGitDiff(worktreePath: string, baseBranch: string): st
11
11
  * Get a summary of files changed in a diff (for truncation header).
12
12
  */
13
13
  export declare function getDiffStatSummary(worktreePath: string, baseBranch: string): string;
14
+ /**
15
+ * List the files changed between the base branch and HEAD (name-only).
16
+ * Returns an empty array on failure (missing worktree / bad range).
17
+ */
18
+ export declare function getChangedFiles(worktreePath: string, baseBranch: string): string[];
19
+ /**
20
+ * Count commits on HEAD that are not reachable from the base branch
21
+ * (`git rev-list --count base..HEAD`). This is the delivery's own commit
22
+ * count relative to the integration base -- distinct from the diff, which
23
+ * can be empty even when commits exist if it is resolved in the wrong
24
+ * worktree. Returns 0 on failure so callers treat "unknown" as "no commits".
25
+ */
26
+ export declare function getCommitsAhead(worktreePath: string, baseBranch: string): number;
14
27
  //# sourceMappingURL=git-diff.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff.d.ts","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,cAAc,QAAY,CAAC;AAExC;;;GAGG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAU3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CASnF"}
1
+ {"version":3,"file":"git-diff.d.ts","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,cAAc,QAAY,CAAC;AAExC;;;GAGG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAU3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CASnF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAWhF"}
@@ -27,4 +27,34 @@ export function getDiffStatSummary(worktreePath, baseBranch) {
27
27
  return '';
28
28
  }
29
29
  }
30
+ /**
31
+ * List the files changed between the base branch and HEAD (name-only).
32
+ * Returns an empty array on failure (missing worktree / bad range).
33
+ */
34
+ export function getChangedFiles(worktreePath, baseBranch) {
35
+ try {
36
+ const out = execSync(`git diff --name-only ${baseBranch}...HEAD`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
37
+ return out.split('\n').map((l) => l.trim()).filter((l) => l !== '');
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ }
43
+ /**
44
+ * Count commits on HEAD that are not reachable from the base branch
45
+ * (`git rev-list --count base..HEAD`). This is the delivery's own commit
46
+ * count relative to the integration base -- distinct from the diff, which
47
+ * can be empty even when commits exist if it is resolved in the wrong
48
+ * worktree. Returns 0 on failure so callers treat "unknown" as "no commits".
49
+ */
50
+ export function getCommitsAhead(worktreePath, baseBranch) {
51
+ try {
52
+ const out = execSync(`git rev-list --count ${baseBranch}..HEAD`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
53
+ const n = Number.parseInt(out.trim(), 10);
54
+ return Number.isFinite(n) && n > 0 ? n : 0;
55
+ }
56
+ catch {
57
+ return 0;
58
+ }
59
+ }
30
60
  //# sourceMappingURL=git-diff.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff.js","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,uCAAuC;AAEhF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,UAAkB;IACjE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CACnB,YAAY,UAAU,SAAS,EAC/B,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACtE,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,YAAoB,EAAE,UAAkB;IACzE,IAAI,CAAC;QACH,OAAO,QAAQ,CACb,mBAAmB,UAAU,SAAS,EACtC,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"git-diff.js","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,uCAAuC;AAEhF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,UAAkB;IACjE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CACnB,YAAY,UAAU,SAAS,EAC/B,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACtE,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,YAAoB,EAAE,UAAkB;IACzE,IAAI,CAAC;QACH,OAAO,QAAQ,CACb,mBAAmB,UAAU,SAAS,EACtC,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,UAAkB;IACtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAClB,wBAAwB,UAAU,SAAS,EAC3C,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;QACF,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,UAAkB;IACtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAClB,wBAAwB,UAAU,QAAQ,EAC1C,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;QACF,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Review-spawn reviewer-role fallback.
3
+ *
4
+ * When a coding team completes, the auto_terminate_focus_team() Postgres trigger
5
+ * nulls product_focuses.assigned_agent_role_id -- correct team-lifecycle
6
+ * shutdown (the Play/queue toggle cleared). But a review is a SECOND team that
7
+ * must spawn AFTER that completion, and the pending review directive it left
8
+ * behind still needs a role to spawn under. With assigned_agent_role_id null,
9
+ * the listener's pending-spawn path used to skip the queued review directive
10
+ * every poll, and the directive-executor suppressed re-firing (an equivalent
11
+ * directive is "already queued") -- a review that can never spawn, spin-logging
12
+ * until review_window_stalled escalates to a human at ~6h.
13
+ *
14
+ * This module holds the prevent-at-source decision: for a REVIEW directive with
15
+ * a null role, resolve a layered reviewer-role fallback (the org's seeded
16
+ * Developer role) and spawn. It mirrors nexus intake's resolveIntakeRoleLayered
17
+ * (the org's seeded 'Developer' role is the zero-config fallback). The fallback
18
+ * is scoped STRICTLY to review spawns: a null role on a CODING spawn is the
19
+ * intended off-state and must NOT auto-spawn. Defense-in-depth: if even the
20
+ * fallback resolves nothing, the caller clears the pending directive so it stops
21
+ * spin-logging and lets the stall detector escalate promptly.
22
+ */
23
+ import type { AgentRole } from '../types/session.js';
24
+ /**
25
+ * Name of the org's auto-seeded, full-codebase agent role used as the reviewer
26
+ * fallback. Every organization gets one on creation (migration
27
+ * seed_default_agent_roles: 'Developer'). Mirrors nexus intake's
28
+ * DEFAULT_INTAKE_ROLE_NAME so review-spawn and intake-spawn share the same
29
+ * zero-config fallback identity.
30
+ */
31
+ export declare const DEFAULT_REVIEWER_ROLE_NAME = "Developer";
32
+ /**
33
+ * What to do with a pending spawn directive whose focus has a null
34
+ * assigned_agent_role_id.
35
+ *
36
+ * - skip: a CODING directive (or no directive) -- the intended off-state
37
+ * (Play/queue toggle cleared). Never auto-spawn.
38
+ * - spawn: a REVIEW directive with a resolved fallback role -- spawn the
39
+ * review team under it.
40
+ * - clear: a REVIEW directive with NO resolvable fallback role -- drop the
41
+ * directive so the stranded-review re-fire suppression cannot spin
42
+ * forever; review_window_stalled remains the escalation path.
43
+ */
44
+ export type NullRoleSpawnDecision = {
45
+ action: 'skip';
46
+ } | {
47
+ action: 'spawn';
48
+ role: AgentRole;
49
+ } | {
50
+ action: 'clear';
51
+ };
52
+ /**
53
+ * Resolve the org's seeded Developer role from an already-loaded roles
54
+ * collection (the listener keeps every org agent role in memory). Returns the
55
+ * first role named DEFAULT_REVIEWER_ROLE_NAME, or undefined when the org has no
56
+ * seeded Developer role (the degenerate case the 'clear' decision handles).
57
+ *
58
+ * Pure and exported for unit testing.
59
+ */
60
+ export declare function resolveReviewerFallbackRole(roles: Iterable<AgentRole>): AgentRole | undefined;
61
+ /**
62
+ * Decide what to do with a null-role pending spawn directive.
63
+ *
64
+ * The layered fallback applies ONLY to review directives; a coding directive
65
+ * with a null role is the intended off-state and is always skipped. A review
66
+ * directive spawns under the fallback role when one resolves, else clears.
67
+ *
68
+ * Pure and exported for unit testing (peer of resolveReviewerFallbackRole).
69
+ */
70
+ export declare function decideNullRoleSpawn(input: {
71
+ sessionType: 'coding' | 'review' | undefined;
72
+ fallbackRole: AgentRole | undefined;
73
+ }): NullRoleSpawnDecision;
74
+ //# sourceMappingURL=review-role-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-role-fallback.d.ts","sourceRoot":"","sources":["../../src/spawn/review-role-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,cAAc,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAExB;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,GACzB,SAAS,GAAG,SAAS,CAKvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,WAAW,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7C,YAAY,EAAE,SAAS,GAAG,SAAS,CAAC;CACrC,GAAG,qBAAqB,CAIxB"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Review-spawn reviewer-role fallback.
3
+ *
4
+ * When a coding team completes, the auto_terminate_focus_team() Postgres trigger
5
+ * nulls product_focuses.assigned_agent_role_id -- correct team-lifecycle
6
+ * shutdown (the Play/queue toggle cleared). But a review is a SECOND team that
7
+ * must spawn AFTER that completion, and the pending review directive it left
8
+ * behind still needs a role to spawn under. With assigned_agent_role_id null,
9
+ * the listener's pending-spawn path used to skip the queued review directive
10
+ * every poll, and the directive-executor suppressed re-firing (an equivalent
11
+ * directive is "already queued") -- a review that can never spawn, spin-logging
12
+ * until review_window_stalled escalates to a human at ~6h.
13
+ *
14
+ * This module holds the prevent-at-source decision: for a REVIEW directive with
15
+ * a null role, resolve a layered reviewer-role fallback (the org's seeded
16
+ * Developer role) and spawn. It mirrors nexus intake's resolveIntakeRoleLayered
17
+ * (the org's seeded 'Developer' role is the zero-config fallback). The fallback
18
+ * is scoped STRICTLY to review spawns: a null role on a CODING spawn is the
19
+ * intended off-state and must NOT auto-spawn. Defense-in-depth: if even the
20
+ * fallback resolves nothing, the caller clears the pending directive so it stops
21
+ * spin-logging and lets the stall detector escalate promptly.
22
+ */
23
+ /**
24
+ * Name of the org's auto-seeded, full-codebase agent role used as the reviewer
25
+ * fallback. Every organization gets one on creation (migration
26
+ * seed_default_agent_roles: 'Developer'). Mirrors nexus intake's
27
+ * DEFAULT_INTAKE_ROLE_NAME so review-spawn and intake-spawn share the same
28
+ * zero-config fallback identity.
29
+ */
30
+ export const DEFAULT_REVIEWER_ROLE_NAME = 'Developer';
31
+ /**
32
+ * Resolve the org's seeded Developer role from an already-loaded roles
33
+ * collection (the listener keeps every org agent role in memory). Returns the
34
+ * first role named DEFAULT_REVIEWER_ROLE_NAME, or undefined when the org has no
35
+ * seeded Developer role (the degenerate case the 'clear' decision handles).
36
+ *
37
+ * Pure and exported for unit testing.
38
+ */
39
+ export function resolveReviewerFallbackRole(roles) {
40
+ for (const role of roles) {
41
+ if (role.name === DEFAULT_REVIEWER_ROLE_NAME)
42
+ return role;
43
+ }
44
+ return undefined;
45
+ }
46
+ /**
47
+ * Decide what to do with a null-role pending spawn directive.
48
+ *
49
+ * The layered fallback applies ONLY to review directives; a coding directive
50
+ * with a null role is the intended off-state and is always skipped. A review
51
+ * directive spawns under the fallback role when one resolves, else clears.
52
+ *
53
+ * Pure and exported for unit testing (peer of resolveReviewerFallbackRole).
54
+ */
55
+ export function decideNullRoleSpawn(input) {
56
+ if (input.sessionType !== 'review')
57
+ return { action: 'skip' };
58
+ if (input.fallbackRole)
59
+ return { action: 'spawn', role: input.fallbackRole };
60
+ return { action: 'clear' };
61
+ }
62
+ //# sourceMappingURL=review-role-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-role-fallback.js","sourceRoot":"","sources":["../../src/spawn/review-role-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,WAAW,CAAC;AAmBtD;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAA0B;IAE1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAGnC;IACC,IAAI,KAAK,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC9D,IAAI,KAAK,CAAC,YAAY;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC"}
@@ -26,7 +26,7 @@ import { runDeterministicStrategy, type StrategyResult } from './verification-de
26
26
  export { runDeterministicStrategy, type DeterministicLogContext } from './verification-deterministic.js';
27
27
  import { getPendingVerifications, type PendingVerification, type VerificationOutcome } from './queries/verification.js';
28
28
  import type { NodeWriteback } from './reality-writeback.js';
29
- import { type ReconcileTargetsResult, type TargetRestatement, type VerifyEvidence } from './verify-restatement.js';
29
+ import { type ReconcileTargetsResult, type TargetRestatement, type TargetShortfall, type VerifyEvidence } from './verify-restatement.js';
30
30
  import { type FileEscalationInput } from './escalations.js';
31
31
  import type { DaemonConfig } from './types.js';
32
32
  import { type ResolvedDefaultVerification } from './verification-config.js';
@@ -125,8 +125,22 @@ export interface VerificationDeps {
125
125
  getFocusDeliveries: typeof getFocusDeliveries;
126
126
  /** Failed-run feedback (gate_state + live inject). Optional; defaultDeps provides it. */
127
127
  deliverFailureFeedback?: (v: PendingVerification, result: StrategyResult) => Promise<void>;
128
+ /**
129
+ * Shortfall-origin gate-hold feedback (gate_state under the shortfall guard +
130
+ * live inject describing the declared-vs-achieved gap). Distinct from
131
+ * deliverFailureFeedback so a shortfall hold never reports under the
132
+ * deterministic guard. Optional; defaultDeps provides it.
133
+ */
134
+ deliverShortfallFeedback?: (v: PendingVerification, shortfalls: readonly TargetShortfall[]) => Promise<void>;
128
135
  /** cwd for the HEAD run -- the focus worktree. Optional; dispatch falls back to resolveCwd. */
129
136
  resolveVerificationCwd?: (v: PendingVerification) => string;
137
+ /**
138
+ * Count of commits the delivery branch is ahead of the integration base.
139
+ * Used by the evidence-integrity guard to tell a broken diff MEASUREMENT
140
+ * (commits present but diff empty -> hold) from genuine inaction (no commits
141
+ * -> a real shortfall). Optional; defaultDeps resolves it in the focus worktree.
142
+ */
143
+ resolveCommitsAhead?: (v: PendingVerification) => number;
130
144
  /** Baseline-diff attribution; null => no baseline resolvable (gate all). Optional. */
131
145
  attributeFailures?: (v: PendingVerification, result: StrategyResult) => Promise<FailureClassification | null>;
132
146
  }
@@ -1 +1 @@
1
- {"version":3,"file":"verification-engine.d.ts","sourceRoot":"","sources":["../src/verification-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,wBAAwB,EAAE,KAAK,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAChG,OAAO,EAAE,wBAAwB,EAAE,KAAK,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AACzG,OAAO,EACL,uBAAuB,EAIvB,KAAK,mBAAmB,EAExB,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EAEtB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAA2C,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGrG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AASrE,OAAO,EAAgF,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAItJ,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,gCAAgC,EAChC,qBAAqB,EACrB,qBAAqB,EACrB,kCAAkC,EAClC,6BAA6B,EAC7B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,eAAe,IAAI,sBAAsB,EACzC,KAAK,gBAAgB,EACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAYpC,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,cAAc,CAAC,CAKrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,gBAAgB,EACtB,CAAC,EAAE,mBAAmB,GACrB,OAAO,CAAC,cAAc,GAAG;IAAE,UAAU,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAmB7D;AAMD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE;QACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,IAAI,EAAE,kBAAkB,GAAG,UAAU,GAAG,aAAa,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;QAC9F,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,eAAe,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,iBAAiB,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChG;;;;;;;;OAQG;IACH,wBAAwB,CAAC,EAAE,CACzB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACrC;;;;;OAKG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,uCAAuC;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,iBAAiB,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E;;;;;;;OAOG;IACH,eAAe,EAAE,CACf,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,aAAa,CAAC,EAAE,MAAM,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,gBAAgB,EAAE,OAAO,wBAAwB,CAAC;IAClD,eAAe,EAAE,OAAO,sBAAsB,CAAC;IAC/C,yBAAyB,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC1F,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC;IACjD,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,mBAAmB,EAAE,uBAAuB,CAAC;IAE7C;;;;OAIG;IACH,0BAA0B,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,2BAA2B,CAAC;IAClF,+EAA+E;IAC/E,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,mEAAmE;IACnE,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,yFAAyF;IACzF,sBAAsB,CAAC,EAAE,CAAC,CAAC,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,+FAA+F;IAC/F,sBAAsB,CAAC,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,MAAM,CAAC;IAC5D,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;CAC/G;AAyID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAC9C,CAAC,EAAE,mBAAmB,EACtB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,cAAc,CAAC,CA2LzB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,EACpB,IAAI,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAC/B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwFlG"}
1
+ {"version":3,"file":"verification-engine.d.ts","sourceRoot":"","sources":["../src/verification-engine.ts"],"names":[],"mappings":"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"}
@@ -28,12 +28,12 @@ import { getPendingVerifications, recordDeliveryVerificationOutcome, verifyInjec
28
28
  import { configForProduct } from './config.js';
29
29
  import { authorTargetRestatements as defaultAuthorTargetRestatements, } from './verify-restatement.js';
30
30
  import { fileEscalation as defaultFileEscalation } from './escalations.js';
31
- import { getGitDiff } from './assembly-resolvers.js';
31
+ import { getGitDiff, getDiffStatSummary, getChangedFiles, getCommitsAhead } from './assembly-resolvers.js';
32
32
  import { ESCALATION_REASONS, ESCALATION_DISPOSITIONS } from '@telora/daemon-core';
33
33
  import { resolveDefaultVerification as defaultResolveDefaultVerification, } from './verification-config.js';
34
34
  import { getActiveFocuses, getFocusDeliveries } from './supabase.js';
35
35
  import { collectDefaultOnVerifications, markDefaultOnRan, resolveDaemonWideVerifyCap, resolveProductVerificationScopes, verificationSemaphore, } from './verification-default-on.js';
36
- import { deliverVerificationFailureFeedback } from './verification-feedback.js';
36
+ import { deliverVerificationFailureFeedback, deliverShortfallFailureFeedback } from './verification-feedback.js';
37
37
  import { buildDefaultAttributeFailures, buildResolveVerificationCwd, applyAttribution } from './verification-baseline.js';
38
38
  // Re-export the default-on selection + concurrency surface so existing
39
39
  // importers (and tests) can reach it through verification-engine.
@@ -107,6 +107,7 @@ const defaultDeps = (config) => {
107
107
  return config.repoPath;
108
108
  return configForProduct(config, product).repoPath;
109
109
  };
110
+ const resolveVerificationCwd = buildResolveVerificationCwd(resolveCwd);
110
111
  return {
111
112
  getPendingVerifications,
112
113
  recordOutcome: recordDeliveryVerificationOutcome,
@@ -130,7 +131,9 @@ const defaultDeps = (config) => {
130
131
  getActiveFocuses,
131
132
  getFocusDeliveries,
132
133
  deliverFailureFeedback: (v, result) => deliverVerificationFailureFeedback(v, result, config.organizationId).then(() => undefined),
133
- resolveVerificationCwd: buildResolveVerificationCwd(resolveCwd),
134
+ deliverShortfallFeedback: (v, shortfalls) => deliverShortfallFailureFeedback(v, shortfalls, config.organizationId).then(() => undefined),
135
+ resolveVerificationCwd,
136
+ resolveCommitsAhead: (v) => getCommitsAhead(resolveVerificationCwd(v), config.integrationBranch),
134
137
  attributeFailures: buildDefaultAttributeFailures(config),
135
138
  };
136
139
  };
@@ -139,6 +142,17 @@ const STRATEGIES = {
139
142
  behavioral: () => runBehavioralStrategy(),
140
143
  ai_inspection: (deps, v) => runAiInspectionStrategy(deps, v),
141
144
  };
145
+ /**
146
+ * Resolve the cwd the delivery's diff must be read in: the FOCUS WORKTREE
147
+ * (HEAD of the focus branch), the SAME cwd the deterministic strategy runs in.
148
+ * NOT the product repo path -- the product repo's HEAD is the integration
149
+ * branch, so `git diff base...HEAD` there is empty even when the focus branch
150
+ * has real commits. Reading the diff in the wrong cwd is the resolution bug
151
+ * that manufactures a false-negative shortfall (2026-07-23 incident).
152
+ */
153
+ function resolveDiffCwd(v, deps) {
154
+ return (deps.resolveVerificationCwd ?? ((pv) => deps.resolveCwd(pv.productId)))(v);
155
+ }
142
156
  /**
143
157
  * Build the cited-evidence bundle the achieved-DE authoring consumes. Sourced
144
158
  * from the verification verdict (NOT the UDE problem text):
@@ -146,20 +160,36 @@ const STRATEGIES = {
146
160
  * - deterministic: the check exit code + stdout/stderr tails, plus the diff
147
161
  * against integration (fetched best-effort; an empty diff just yields less
148
162
  * evidence, it never blocks).
163
+ *
164
+ * The raw diff is accompanied by STRUCTURED signals (changed-files list + diff
165
+ * stat) resolved in the same focus-worktree cwd, so delivered behavior is
166
+ * legible to the judge without parsing the full diff.
149
167
  */
150
168
  function buildVerifyEvidence(result, v, deps) {
169
+ const cwd = resolveDiffCwd(v, deps);
151
170
  let diff = result.deliveryDiff ?? '';
152
171
  if (diff === '') {
153
172
  try {
154
- diff = getGitDiff(deps.resolveCwd(v.productId), deps.integrationBranch);
173
+ diff = getGitDiff(cwd, deps.integrationBranch);
155
174
  }
156
175
  catch (err) {
157
176
  // best-effort: no diff is fine, the other evidence slots still carry signal
158
177
  log.debug('best-effort: no diff is fine, the other evidence slots still carry signal', { error: String(err?.message ?? err) });
159
178
  }
160
179
  }
180
+ let changedFiles = [];
181
+ let diffStat = '';
182
+ try {
183
+ changedFiles = getChangedFiles(cwd, deps.integrationBranch);
184
+ diffStat = getDiffStatSummary(cwd, deps.integrationBranch);
185
+ }
186
+ catch (err) {
187
+ log.debug('best-effort: structured diff signals unavailable', { error: String(err?.message ?? err) });
188
+ }
161
189
  return {
162
190
  diff: diff || undefined,
191
+ changedFiles: changedFiles.length > 0 ? changedFiles : undefined,
192
+ diffStat: diffStat.trim() !== '' ? diffStat : undefined,
163
193
  testStdout: result.stdoutTail,
164
194
  testStderr: result.stderrTail,
165
195
  exitCode: result.exitCode,
@@ -173,7 +203,8 @@ function buildVerifyEvidence(result, v, deps) {
173
203
  * crash the tick (the gate is already held by the demoted 'failed' outcome).
174
204
  */
175
205
  async function escalateShortfalls(v, shortfalls, deps) {
176
- const lines = shortfalls.map((s) => `- node ${s.nodeId}\n declared: ${s.declaredStatement}\n achieved: ${s.achievedStatement}\n shortfall: ${s.reason}`);
206
+ const lines = shortfalls.map((s) => `- node ${s.nodeId}\n declared: ${s.declaredStatement}\n achieved: ${s.achievedStatement}\n shortfall: ${s.reason}` +
207
+ (s.diffCitation ? `\n diff citation: ${s.diffCitation}` : ''));
177
208
  try {
178
209
  await deps.fileEscalation({
179
210
  organizationId: deps.organizationId,
@@ -194,6 +225,7 @@ async function escalateShortfalls(v, shortfalls, deps) {
194
225
  declared: s.declaredStatement,
195
226
  achieved: s.achievedStatement,
196
227
  reason: s.reason,
228
+ ...(s.diffCitation ? { citation: s.diffCitation } : {}),
197
229
  })),
198
230
  },
199
231
  });
@@ -245,6 +277,11 @@ export async function runVerificationForDelivery(v, deps) {
245
277
  log.warn(`[verification] baseline attribution failed for delivery ${v.deliveryId}:`, err.message);
246
278
  }
247
279
  }
280
+ // Shortfall-origin tracking: set when the reconciliation demotes a PASSED
281
+ // outcome to 'failed'. Feedback for a shortfall hold routes under its own
282
+ // guard (not deterministic_verification), so the origin must survive to the
283
+ // feedback dispatch below.
284
+ let shortfallHold = [];
248
285
  // Pass path FIRST so a verifyInjection failure short-circuits before we
249
286
  // record an outcome that does not match graph state.
250
287
  if (result.outcome === 'passed' && v.injectionNodeId) {
@@ -280,81 +317,117 @@ export async function runVerificationForDelivery(v, deps) {
280
317
  // declared DE. If ANY target falls short, we hold the gate (demote to
281
318
  // 'failed'), skip the flip, and escalate the gap to a human. Reality is
282
319
  // moved toward intent, never the reverse.
283
- let restatements = [];
284
- let shortfalls = [];
285
- let realityTreeId;
286
- if (deps.authorTargetRestatements) {
287
- const evidence = buildVerifyEvidence(result, v, deps);
288
- try {
289
- const reconciled = await deps.authorTargetRestatements(v.injectionNodeId, v.focusId, evidence);
290
- restatements = reconciled.restatements;
291
- shortfalls = reconciled.shortfalls;
292
- realityTreeId = reconciled.realityTreeId;
293
- }
294
- catch (err) {
295
- // A reconciliation we could not run at all is asymmetric-unsafe: do not
296
- // flip an injection whose achievement we could not verify. Treat the
297
- // whole injection as a shortfall and hold the gate.
298
- log.warn(`[verification] verify-time reconciliation failed for ${v.injectionNodeId}:`, err.message);
299
- shortfalls = [{
300
- nodeId: v.injectionNodeId,
301
- declaredStatement: '(unknown -- reconciliation failed)',
302
- achievedStatement: '(reconciliation failed)',
303
- reason: `Reconciliation failed: ${err.message}`,
304
- }];
305
- }
306
- }
307
- if (shortfalls.length > 0) {
308
- // Hold the gate, do NOT flip, escalate the declared-vs-achieved gap.
309
- result.outcome = 'failed';
320
+ const evidence = buildVerifyEvidence(result, v, deps);
321
+ // Evidence-INTEGRITY guard (fail closed on a broken measurement): a
322
+ // delivery branch with commits ahead of the integration base MUST resolve
323
+ // to a non-empty diff. If it does not, the diff was resolved in the wrong
324
+ // range/worktree -- a broken MEASUREMENT, not a genuine shortfall. Hold the
325
+ // gate for retry ('pending') and NEVER author a shortfall or escalate from
326
+ // the remaining (non-diff) evidence. A genuine no-work delivery (no commits
327
+ // AND empty diff) is NOT held here -- it flows into reconciliation below and
328
+ // can still be classified a real shortfall.
329
+ const commitsAhead = deps.resolveCommitsAhead ? deps.resolveCommitsAhead(v) : 0;
330
+ const diffEmpty = !evidence.diff || evidence.diff.trim() === '';
331
+ if (commitsAhead > 0 && diffEmpty) {
332
+ result.outcome = 'pending';
310
333
  result.rationale =
311
- `verify reconciliation found ${shortfalls.length} shortfall(s) vs declared intent; ` +
312
- `injection ${v.injectionNodeId} not flipped (escalated to human)`;
313
- await escalateShortfalls(v, shortfalls, deps);
334
+ `verify held: delivery branch is ${commitsAhead} commit(s) ahead of ` +
335
+ `${deps.integrationBranch} but the resolved diff is empty -- broken diff ` +
336
+ `measurement, not a shortfall; retrying (injection ${v.injectionNodeId} not flipped)`;
337
+ log.warn(`[verification] ${result.rationale} (delivery ${v.deliveryId})`);
314
338
  }
315
339
  else {
316
- let flipped = false;
317
- try {
318
- await deps.verifyInjection(v.injectionNodeId, restatements);
319
- flipped = true;
340
+ let restatements = [];
341
+ let shortfalls = [];
342
+ let realityTreeId;
343
+ if (deps.authorTargetRestatements) {
344
+ try {
345
+ const reconciled = await deps.authorTargetRestatements(v.injectionNodeId, v.focusId, evidence);
346
+ restatements = reconciled.restatements;
347
+ shortfalls = reconciled.shortfalls;
348
+ realityTreeId = reconciled.realityTreeId;
349
+ }
350
+ catch (err) {
351
+ // A reconciliation we could not run at all is asymmetric-unsafe: do not
352
+ // flip an injection whose achievement we could not verify. Treat the
353
+ // whole injection as a shortfall and hold the gate.
354
+ log.warn(`[verification] verify-time reconciliation failed for ${v.injectionNodeId}:`, err.message);
355
+ shortfalls = [{
356
+ nodeId: v.injectionNodeId,
357
+ declaredStatement: '(unknown -- reconciliation failed)',
358
+ achievedStatement: '(reconciliation failed)',
359
+ reason: `Reconciliation failed: ${err.message}`,
360
+ }];
361
+ }
320
362
  }
321
- catch (err) {
322
- result.outcome = 'pending';
323
- result.rationale = `verifyInjection failed for ${v.injectionNodeId}: ${err.message}`;
363
+ if (shortfalls.length > 0) {
364
+ // Hold the gate, do NOT flip, escalate the declared-vs-achieved gap.
365
+ shortfallHold = shortfalls;
366
+ result.outcome = 'failed';
367
+ result.rationale =
368
+ `verify reconciliation found ${shortfalls.length} shortfall(s) vs declared intent; ` +
369
+ `injection ${v.injectionNodeId} not flipped (escalated to human)`;
370
+ await escalateShortfalls(v, shortfalls, deps);
324
371
  }
325
- // INWARD drift (TEL-3/TEL-4): any target the reconciliation stamped
326
- // `legitimate_divergence` is a tree CLAIM reality contradicted. Surface
327
- // each as an inward drift proposal -- AFTER the flip landed -- so a
328
- // confident-but-wrong assertion is corrected by drift rather than
329
- // frozen. observedState carries the tree's ORIGINAL claim (not the
330
- // achieved reality); proposedCorrection carries what reality showed.
331
- // Best-effort + non-fatal: a failure must not undo the verify flip.
332
- if (flipped) {
333
- const divergedWritebacks = restatements
334
- .filter((r) => r.classification === 'legitimate_divergence')
335
- .map((r) => ({
336
- nodeId: r.nodeId,
337
- classification: 'legitimate_divergence',
338
- statement: r.statement,
339
- originalClaim: r.originalClaim,
340
- ...(r.divergenceReason ? { verifyDivergenceReason: r.divergenceReason } : {}),
341
- }));
342
- if (divergedWritebacks.length > 0) {
343
- try {
344
- await deps.emitInwardDrift(v.injectionNodeId, divergedWritebacks, realityTreeId);
345
- }
346
- catch (err) {
347
- log.warn(`[verification] inward drift emit failed for ${v.injectionNodeId}:`, err.message);
372
+ else {
373
+ let flipped = false;
374
+ try {
375
+ await deps.verifyInjection(v.injectionNodeId, restatements);
376
+ flipped = true;
377
+ }
378
+ catch (err) {
379
+ result.outcome = 'pending';
380
+ result.rationale = `verifyInjection failed for ${v.injectionNodeId}: ${err.message}`;
381
+ }
382
+ // INWARD drift (TEL-3/TEL-4): any target the reconciliation stamped
383
+ // `legitimate_divergence` is a tree CLAIM reality contradicted. Surface
384
+ // each as an inward drift proposal -- AFTER the flip landed -- so a
385
+ // confident-but-wrong assertion is corrected by drift rather than
386
+ // frozen. observedState carries the tree's ORIGINAL claim (not the
387
+ // achieved reality); proposedCorrection carries what reality showed.
388
+ // Best-effort + non-fatal: a failure must not undo the verify flip.
389
+ if (flipped) {
390
+ const divergedWritebacks = restatements
391
+ .filter((r) => r.classification === 'legitimate_divergence')
392
+ .map((r) => ({
393
+ nodeId: r.nodeId,
394
+ classification: 'legitimate_divergence',
395
+ statement: r.statement,
396
+ originalClaim: r.originalClaim,
397
+ ...(r.divergenceReason ? { verifyDivergenceReason: r.divergenceReason } : {}),
398
+ }));
399
+ if (divergedWritebacks.length > 0) {
400
+ try {
401
+ await deps.emitInwardDrift(v.injectionNodeId, divergedWritebacks, realityTreeId);
402
+ }
403
+ catch (err) {
404
+ log.warn(`[verification] inward drift emit failed for ${v.injectionNodeId}:`, err.message);
405
+ }
348
406
  }
349
407
  }
350
408
  }
351
- }
409
+ } // end evidence-integrity else (measurement OK -> reconcile)
352
410
  }
353
411
  }
354
412
  await deps.recordOutcome(v.deliveryId, result.outcome);
355
- // Failure feedback: on a failed deterministic run, write gate_state + inject
356
- // into the live team. Best-effort -- never let feedback failure crash the tick.
357
- if (result.outcome === 'failed' && v.verificationMethod === 'deterministic' && deps.deliverFailureFeedback) {
413
+ // Feedback dispatch by FAILURE ORIGIN (the two are mutually exclusive: a
414
+ // shortfall hold only demotes a PASSED outcome, so a deterministic-failed run
415
+ // never carries a shortfallHold).
416
+ //
417
+ // Shortfall-origin hold: report under the shortfall guard (never
418
+ // deterministic_verification) with the declared-vs-achieved detail, and inject
419
+ // a gap-framed message -- never "fix the failing tests". Independent of method.
420
+ if (shortfallHold.length > 0 && deps.deliverShortfallFeedback) {
421
+ try {
422
+ await deps.deliverShortfallFeedback(v, shortfallHold);
423
+ }
424
+ catch (err) {
425
+ log.warn(`[verification] shortfall feedback failed for delivery ${v.deliveryId}:`, err.message);
426
+ }
427
+ }
428
+ else if (result.outcome === 'failed' && v.verificationMethod === 'deterministic' && deps.deliverFailureFeedback) {
429
+ // Genuine deterministic failure (the shell command actually failed): write
430
+ // gate_state under deterministic_verification + inject. Best-effort.
358
431
  try {
359
432
  await deps.deliverFailureFeedback(v, result);
360
433
  }