@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.
- package/build-info.json +4 -3
- package/dist/audit-phase.d.ts +43 -6
- package/dist/audit-phase.d.ts.map +1 -1
- package/dist/audit-phase.js +124 -23
- package/dist/audit-phase.js.map +1 -1
- package/dist/cascade-restatement.d.ts +13 -147
- package/dist/cascade-restatement.d.ts.map +1 -1
- package/dist/cascade-restatement.js +13 -429
- package/dist/cascade-restatement.js.map +1 -1
- package/dist/focus-engine.d.ts.map +1 -1
- package/dist/focus-engine.js +15 -0
- package/dist/focus-engine.js.map +1 -1
- package/dist/focus-loop-registry.d.ts.map +1 -1
- package/dist/focus-loop-registry.js +16 -90
- package/dist/focus-loop-registry.js.map +1 -1
- package/dist/focus-loop.d.ts +0 -2
- package/dist/focus-loop.d.ts.map +1 -1
- package/dist/focus-loop.js +33 -25
- package/dist/focus-loop.js.map +1 -1
- package/dist/focus-phase.d.ts +26 -0
- package/dist/focus-phase.d.ts.map +1 -1
- package/dist/focus-phase.js +38 -0
- package/dist/focus-phase.js.map +1 -1
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +40 -9
- package/dist/listener.js.map +1 -1
- package/dist/loops/llm-client.d.ts.map +1 -1
- package/dist/loops/llm-client.js +17 -4
- package/dist/loops/llm-client.js.map +1 -1
- package/dist/queries/audit-runs.d.ts +56 -2
- package/dist/queries/audit-runs.d.ts.map +1 -1
- package/dist/queries/audit-runs.js +40 -0
- package/dist/queries/audit-runs.js.map +1 -1
- package/dist/queries/deliveries.d.ts +27 -0
- package/dist/queries/deliveries.d.ts.map +1 -1
- package/dist/queries/deliveries.js.map +1 -1
- package/dist/queries/focuses.d.ts +0 -9
- package/dist/queries/focuses.d.ts.map +1 -1
- package/dist/queries/focuses.js +0 -14
- package/dist/queries/focuses.js.map +1 -1
- package/dist/resolvers/index.d.ts +1 -1
- package/dist/resolvers/index.d.ts.map +1 -1
- package/dist/resolvers/index.js +1 -1
- package/dist/resolvers/index.js.map +1 -1
- package/dist/resolvers/shared/audit.d.ts +5 -0
- package/dist/resolvers/shared/audit.d.ts.map +1 -1
- package/dist/resolvers/shared/audit.js +8 -3
- package/dist/resolvers/shared/audit.js.map +1 -1
- package/dist/resolvers/shared/git-diff.d.ts +13 -0
- package/dist/resolvers/shared/git-diff.d.ts.map +1 -1
- package/dist/resolvers/shared/git-diff.js +30 -0
- package/dist/resolvers/shared/git-diff.js.map +1 -1
- package/dist/spawn/review-role-fallback.d.ts +74 -0
- package/dist/spawn/review-role-fallback.d.ts.map +1 -0
- package/dist/spawn/review-role-fallback.js +62 -0
- package/dist/spawn/review-role-fallback.js.map +1 -0
- package/dist/verification-engine.d.ts +15 -1
- package/dist/verification-engine.d.ts.map +1 -1
- package/dist/verification-engine.js +142 -69
- package/dist/verification-engine.js.map +1 -1
- package/dist/verification-feedback.d.ts +26 -1
- package/dist/verification-feedback.d.ts.map +1 -1
- package/dist/verification-feedback.js +82 -0
- package/dist/verification-feedback.js.map +1 -1
- package/dist/verify-reconciliation.d.ts +16 -1
- package/dist/verify-reconciliation.d.ts.map +1 -1
- package/dist/verify-reconciliation.js +18 -2
- package/dist/verify-reconciliation.js.map +1 -1
- package/dist/verify-restatement.d.ts +7 -0
- package/dist/verify-restatement.d.ts.map +1 -1
- package/dist/verify-restatement.js +1 -0
- package/dist/verify-restatement.js.map +1 -1
- package/package.json +2 -2
- package/dist/drift-ratify-loop.d.ts +0 -68
- package/dist/drift-ratify-loop.d.ts.map +0 -1
- package/dist/drift-ratify-loop.js +0 -161
- package/dist/drift-ratify-loop.js.map +0 -1
- package/dist/negative-branch.d.ts +0 -136
- package/dist/negative-branch.d.ts.map +0 -1
- package/dist/negative-branch.js +0 -241
- package/dist/negative-branch.js.map +0 -1
- package/dist/shortfall-disposition-config.d.ts +0 -34
- package/dist/shortfall-disposition-config.d.ts.map +0 -1
- package/dist/shortfall-disposition-config.js +0 -46
- package/dist/shortfall-disposition-config.js.map +0 -1
- package/dist/shortfall-disposition-loop.d.ts +0 -86
- package/dist/shortfall-disposition-loop.d.ts.map +0 -1
- package/dist/shortfall-disposition-loop.js +0 -309
- 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;
|
|
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 {
|
|
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
|
|
22
|
-
return
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
//
|
|
356
|
-
//
|
|
357
|
-
|
|
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
|
}
|