@akagilnc/pi-workflow-roles 0.1.2157 → 0.1.2173
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/dist/public-cli/main.js +230 -61
- package/package.json +1 -1
- package/src/public-cli/auto-resume.ts +29 -35
- package/src/public-cli/cli.ts +57 -1
- package/src/public-cli/coder-run.ts +5 -1
- package/src/public-cli/collector-run.ts +1 -1
- package/src/public-cli/config.ts +53 -3
- package/src/public-cli/doctor-run.ts +1 -1
- package/src/public-cli/fixer-run.ts +5 -1
- package/src/public-cli/judge-run.ts +5 -1
- package/src/public-cli/merger-run.ts +5 -1
- package/src/public-cli/option-definitions.ts +2 -0
- package/src/public-cli/reviewer-run.ts +5 -1
- package/src/public-cli/run-lifecycle.ts +51 -2
- package/src/public-cli/settlement.ts +170 -15
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Controlled failures and audit human decisions settle here without washing causes.
|
|
5
5
|
*/
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import {
|
|
7
|
+
import { constants as fsConstants } from "node:fs";
|
|
8
|
+
import { appendFile, lstat, mkdir, open, readFile, readdir, writeFile } from "node:fs/promises";
|
|
8
9
|
import { dirname, join } from "node:path";
|
|
9
10
|
|
|
10
11
|
import { isAuditEscalationResult } from "../audit-escalation.ts";
|
|
@@ -14,6 +15,7 @@ import { JUDGE_AUDIT_TOOL_NAME } from "../judge-auditor.ts";
|
|
|
14
15
|
import { REVIEWER_AUDIT_TOOL_NAME } from "../reviewer-auditor.ts";
|
|
15
16
|
import { knownFailureFromProviderStop, type ExplicitInternalKnownFailure, readReviewerDispatchRejection } from "./explicit-internal.ts";
|
|
16
17
|
import {
|
|
18
|
+
RESUME_TRANSPORT_ENVELOPE,
|
|
17
19
|
isV1ResumableProvider,
|
|
18
20
|
readLatestTypedProviderHttpObservation,
|
|
19
21
|
readTypedHttp429Observation,
|
|
@@ -725,12 +727,24 @@ export async function readBoundAuditorKnownFailure(
|
|
|
725
727
|
}
|
|
726
728
|
const parentId = parentEntries.find((entry) => entry.type === "session")?.id;
|
|
727
729
|
if (parentId === undefined) return undefined;
|
|
730
|
+
const RESUME_ENVELOPE = RESUME_TRANSPORT_ENVELOPE;
|
|
731
|
+
const isResumeEnvelope = (msg: unknown): boolean => {
|
|
732
|
+
if (!isRecord(msg) || msg.role !== "user") return false;
|
|
733
|
+
const text = typeof msg.text === "string" ? msg.text : typeof (msg as { content?: unknown }).content === "string" ? (msg as { content: string }).content : undefined;
|
|
734
|
+
if (text === RESUME_ENVELOPE) return true;
|
|
735
|
+
const content = (msg as { content?: unknown }).content;
|
|
736
|
+
if (Array.isArray(content)) {
|
|
737
|
+
return content.some((p) => isRecord(p) && (p.text === RESUME_ENVELOPE || p.content === RESUME_ENVELOPE));
|
|
738
|
+
}
|
|
739
|
+
return false;
|
|
740
|
+
};
|
|
728
741
|
let latestParentUserIndex = -1;
|
|
729
742
|
for (let i = parentEntries.length - 1; i >= 0; i -= 1) {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
743
|
+
const entry = parentEntries[i];
|
|
744
|
+
if (entry?.type !== "message" || entry.message?.role !== "user") continue;
|
|
745
|
+
if (isResumeEnvelope(entry.message)) continue;
|
|
746
|
+
latestParentUserIndex = i;
|
|
747
|
+
break;
|
|
734
748
|
}
|
|
735
749
|
const childDirectory = join(dirname(sessionFile), "auditor-roles");
|
|
736
750
|
let names: string[];
|
|
@@ -740,6 +754,12 @@ export async function readBoundAuditorKnownFailure(
|
|
|
740
754
|
if (isMissingPathError(error)) return undefined;
|
|
741
755
|
throw sessionReadFailure(error, "failed to read bound auditor session directory");
|
|
742
756
|
}
|
|
757
|
+
// Auto-resume seam (owner A): stale check must ignore resume envelope and
|
|
758
|
+
// prioritize retention. Previous `attemptEntryIndex < latest` discarded the
|
|
759
|
+
// first attempt's child after resume advanced latest, losing retentionFailure
|
|
760
|
+
// when retry had no compliance entry. Fix: ignore envelope for staleness and
|
|
761
|
+
// prefer any valid compliance failure before falling back to primary.
|
|
762
|
+
const validAuditorFiles: Array<{ file: string; entries: SessionEntry[]; attemptEntryId?: string }> = [];
|
|
743
763
|
for (const file of names.filter((name) => name.endsWith(".jsonl")).sort().reverse()) {
|
|
744
764
|
let entries: SessionEntry[];
|
|
745
765
|
try {
|
|
@@ -754,7 +774,10 @@ export async function readBoundAuditorKnownFailure(
|
|
|
754
774
|
const attemptEntryId = typeof bindingParent?.attemptEntryId === "string" ? bindingParent.attemptEntryId : undefined;
|
|
755
775
|
const attemptEntryIndex = attemptEntryId === undefined ? -1 : parentEntries.findIndex((entry) => entry.id === attemptEntryId);
|
|
756
776
|
if (bindingParent?.sessionId !== parentId || bindingParent.sessionFile !== sessionFile || attemptEntryIndex < latestParentUserIndex) continue;
|
|
757
|
-
|
|
777
|
+
validAuditorFiles.push({ file, entries, ...(attemptEntryId === undefined ? {} : { attemptEntryId }) });
|
|
778
|
+
}
|
|
779
|
+
// Prefer compliance failure (retention) from any valid attempt, newest first.
|
|
780
|
+
for (const { entries, attemptEntryId } of validAuditorFiles) {
|
|
758
781
|
const stop = extractSessionProviderStop(entries);
|
|
759
782
|
if (stop === undefined) continue;
|
|
760
783
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
@@ -774,6 +797,11 @@ export async function readBoundAuditorKnownFailure(
|
|
|
774
797
|
...(isRecord(failure.details) ? { details: failure.details } : {}),
|
|
775
798
|
};
|
|
776
799
|
}
|
|
800
|
+
}
|
|
801
|
+
// No compliance failure: fall back to most recent provider stop (auto-resume latest attempt).
|
|
802
|
+
for (const { entries } of validAuditorFiles) {
|
|
803
|
+
const stop = extractSessionProviderStop(entries);
|
|
804
|
+
if (stop === undefined) continue;
|
|
777
805
|
const primary = knownFailureFromProviderStop(stop)!;
|
|
778
806
|
return {
|
|
779
807
|
...primary,
|
|
@@ -1852,26 +1880,131 @@ async function ensureAuditEvidenceDirectory(runDirectory: string): Promise<strin
|
|
|
1852
1880
|
return artifactsDir;
|
|
1853
1881
|
}
|
|
1854
1882
|
|
|
1855
|
-
/**
|
|
1883
|
+
/**
|
|
1884
|
+
* #419 per-attempt process history. 史必追加,指针可覆盖;指针可以覆盖的前提是史已落。
|
|
1885
|
+
* Reuses the run session principal's append-only JSONL custom-entry shape
|
|
1886
|
+
* (plain custom entries are state records and never enter LLM context), so no
|
|
1887
|
+
* second ledger mechanism is introduced.
|
|
1888
|
+
*/
|
|
1889
|
+
export const ATTEMPT_HISTORY_ENTRY_TYPE = "ak_run_attempt_history" as const;
|
|
1890
|
+
|
|
1891
|
+
/** Complete per-attempt result as recorded in the appended history. */
|
|
1892
|
+
type AttemptHistoryOutcome =
|
|
1893
|
+
| TerminalRoleOutcome
|
|
1894
|
+
| ({ kind: "failure"; role: string } & ControlledFailure);
|
|
1895
|
+
|
|
1896
|
+
type AttemptHistorySource = {
|
|
1897
|
+
readonly role: string;
|
|
1898
|
+
readonly runId: string;
|
|
1899
|
+
readonly sessionFile: string;
|
|
1900
|
+
};
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* Append one attempt's complete result to the run's session principal.
|
|
1904
|
+
* Append failure throws — callers must not overwrite a pointer artifact when
|
|
1905
|
+
* the history entry backing the overwrite did not land (fail closed).
|
|
1906
|
+
*/
|
|
1907
|
+
export async function appendRunAttemptHistory(
|
|
1908
|
+
admitted: AttemptHistorySource,
|
|
1909
|
+
outcome: AttemptHistoryOutcome,
|
|
1910
|
+
): Promise<void> {
|
|
1911
|
+
const entries = await readBoundSessionEntries(admitted.sessionFile);
|
|
1912
|
+
let parentId: string | null = null;
|
|
1913
|
+
let priorEntries = 0;
|
|
1914
|
+
for (const entry of entries) {
|
|
1915
|
+
if (typeof entry.id === "string" && entry.type !== "session") parentId = entry.id;
|
|
1916
|
+
if (
|
|
1917
|
+
entry.type === "custom" &&
|
|
1918
|
+
entry.customType === ATTEMPT_HISTORY_ENTRY_TYPE
|
|
1919
|
+
) {
|
|
1920
|
+
priorEntries += 1;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const timestamp = new Date().toISOString();
|
|
1924
|
+
// Shape mirrors pi SessionManager.appendCustomEntry.
|
|
1925
|
+
const line = `${JSON.stringify({
|
|
1926
|
+
type: "custom",
|
|
1927
|
+
customType: ATTEMPT_HISTORY_ENTRY_TYPE,
|
|
1928
|
+
data: {
|
|
1929
|
+
sequence: priorEntries + 1,
|
|
1930
|
+
role: admitted.role,
|
|
1931
|
+
runId: admitted.runId,
|
|
1932
|
+
recordedAt: timestamp,
|
|
1933
|
+
outcome,
|
|
1934
|
+
},
|
|
1935
|
+
id: randomUUID(),
|
|
1936
|
+
parentId,
|
|
1937
|
+
timestamp,
|
|
1938
|
+
})}\n`;
|
|
1939
|
+
await appendFile(admitted.sessionFile, line, "utf8");
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
/**
|
|
1943
|
+
* Publish the retained residual with complete-write semantics (#419: the
|
|
1944
|
+
* previous attempt's pointer file is a rebuildable view once this attempt's
|
|
1945
|
+
* complete result is in the appended history; planted symlinks/directories
|
|
1946
|
+
* still fail loudly with their #182-A identities).
|
|
1947
|
+
*/
|
|
1856
1948
|
export async function publishComplianceAuditIncompleteEvidence(
|
|
1857
1949
|
admitted: AdmittedRoleInvocation,
|
|
1858
1950
|
outcome: ReturnType<typeof buildAuditIncompleteTerminalOutcome>,
|
|
1859
1951
|
): Promise<TerminalArtifactRef> {
|
|
1952
|
+
await appendRunAttemptHistory(admitted, outcome);
|
|
1953
|
+
if (
|
|
1954
|
+
typeof fsConstants.O_NOFOLLOW !== "number" ||
|
|
1955
|
+
typeof fsConstants.O_NONBLOCK !== "number"
|
|
1956
|
+
) {
|
|
1957
|
+
// #418 fail-closed: on platforms without O_NOFOLLOW (e.g. Windows,
|
|
1958
|
+
// nodejs/node#41590) the JS bitwise-or below would silently drop the flag
|
|
1959
|
+
// and the #182-A anti-symlink protection would vanish. Refuse loudly via
|
|
1960
|
+
// the existing publication-failure channel instead of publishing
|
|
1961
|
+
// unprotected; the complete attempt result above stays in appended history.
|
|
1962
|
+
throw auditArtifactPublicationError(
|
|
1963
|
+
"audit evidence publication requires O_NOFOLLOW|O_NONBLOCK open-flag support (anti-symlink/anti-planted protection must not be silently dropped); refusing to publish",
|
|
1964
|
+
"ENOSYS",
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1860
1967
|
const artifactsDir = await ensureAuditEvidenceDirectory(admitted.runDirectory);
|
|
1861
1968
|
const evidencePath = join(artifactsDir, "audit-incomplete.json");
|
|
1969
|
+
let existing: Awaited<ReturnType<typeof lstat>> | undefined;
|
|
1862
1970
|
try {
|
|
1863
|
-
|
|
1864
|
-
throw auditArtifactPublicationError(
|
|
1865
|
-
existing.isSymbolicLink()
|
|
1866
|
-
? "audit evidence destination is a symlink"
|
|
1867
|
-
: "audit evidence destination collision",
|
|
1868
|
-
existing.isSymbolicLink() ? "ELOOP" : "EEXIST",
|
|
1869
|
-
);
|
|
1971
|
+
existing = await lstat(evidencePath);
|
|
1870
1972
|
} catch (error) {
|
|
1871
1973
|
if (!isMissingPathError(error)) throw error;
|
|
1872
1974
|
}
|
|
1873
|
-
|
|
1975
|
+
if (existing?.isSymbolicLink()) {
|
|
1976
|
+
throw auditArtifactPublicationError(
|
|
1977
|
+
"audit evidence destination is a symlink",
|
|
1978
|
+
"ELOOP",
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
if (existing && !existing.isFile()) {
|
|
1982
|
+
throw auditArtifactPublicationError(
|
|
1983
|
+
"audit evidence destination is not a regular file",
|
|
1984
|
+
"EEXIST",
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
// #182-A protection restored atomically at this open seam: O_NOFOLLOW makes
|
|
1988
|
+
// the open itself refuse symlinks (ELOOP), so one swapped in after the lstat
|
|
1989
|
+
// above can never be followed or written through; O_NONBLOCK keeps a
|
|
1990
|
+
// race-planted FIFO from blocking this open forever. The fstat below
|
|
1991
|
+
// backstops any non-regular object that wins the window.
|
|
1992
|
+
const handle = await open(
|
|
1993
|
+
evidencePath,
|
|
1994
|
+
fsConstants.O_WRONLY |
|
|
1995
|
+
fsConstants.O_CREAT |
|
|
1996
|
+
fsConstants.O_TRUNC |
|
|
1997
|
+
fsConstants.O_NOFOLLOW |
|
|
1998
|
+
fsConstants.O_NONBLOCK,
|
|
1999
|
+
0o600,
|
|
2000
|
+
);
|
|
1874
2001
|
try {
|
|
2002
|
+
if (!(await handle.stat()).isFile()) {
|
|
2003
|
+
throw auditArtifactPublicationError(
|
|
2004
|
+
"audit evidence destination is not a regular file",
|
|
2005
|
+
"EEXIST",
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
1875
2008
|
await handle.writeFile(`${JSON.stringify(outcome, null, 2)}\n`, "utf8");
|
|
1876
2009
|
await handle.sync();
|
|
1877
2010
|
} finally {
|
|
@@ -2213,6 +2346,9 @@ export async function publishJudgeArtifacts(
|
|
|
2213
2346
|
roleOutcome: TerminalRoleOutcome,
|
|
2214
2347
|
sessionDirectory: string,
|
|
2215
2348
|
): Promise<TerminalArtifactRef[]> {
|
|
2349
|
+
// #419: history first — report/evidence stay last-write-wins views only
|
|
2350
|
+
// because every attempt's complete result has already been appended.
|
|
2351
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
2216
2352
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
2217
2353
|
const reportPath = join(artifactsDir, "report.json");
|
|
2218
2354
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -2268,6 +2404,7 @@ export async function publishCoderArtifacts(
|
|
|
2268
2404
|
readonly coderOutput?: CoderOutput;
|
|
2269
2405
|
} = {},
|
|
2270
2406
|
): Promise<TerminalArtifactRef[]> {
|
|
2407
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
2271
2408
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
2272
2409
|
const reportPath = join(artifactsDir, "report.json");
|
|
2273
2410
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -2573,6 +2710,7 @@ export async function publishFixerArtifacts(
|
|
|
2573
2710
|
readonly fixerOutput?: FixerOutput;
|
|
2574
2711
|
},
|
|
2575
2712
|
): Promise<TerminalArtifactRef[]> {
|
|
2713
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
2576
2714
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
2577
2715
|
const reportPath = join(artifactsDir, "report.json");
|
|
2578
2716
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -2732,6 +2870,7 @@ export async function publishCollectorArtifacts(
|
|
|
2732
2870
|
readonly collectorReceipt?: CollectorReceipt;
|
|
2733
2871
|
} = {},
|
|
2734
2872
|
): Promise<TerminalArtifactRef[]> {
|
|
2873
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
2735
2874
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
2736
2875
|
const reportPath = join(artifactsDir, "report.json");
|
|
2737
2876
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -2891,6 +3030,7 @@ export async function publishDoctorArtifacts(
|
|
|
2891
3030
|
readonly doctorOutput?: DoctorOutput;
|
|
2892
3031
|
} = {},
|
|
2893
3032
|
): Promise<TerminalArtifactRef[]> {
|
|
3033
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
2894
3034
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
2895
3035
|
const reportPath = join(artifactsDir, "report.json");
|
|
2896
3036
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -3155,6 +3295,7 @@ export async function publishReviewerArtifacts(
|
|
|
3155
3295
|
readonly reviewerReceipt?: RuntimeReviewerReceiptV2;
|
|
3156
3296
|
},
|
|
3157
3297
|
): Promise<TerminalArtifactRef[]> {
|
|
3298
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
3158
3299
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
3159
3300
|
const reportPath = join(artifactsDir, "report.json");
|
|
3160
3301
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -3412,6 +3553,7 @@ export async function publishMergerArtifacts(
|
|
|
3412
3553
|
readonly mergerOutput?: MergerOutput;
|
|
3413
3554
|
},
|
|
3414
3555
|
): Promise<TerminalArtifactRef[]> {
|
|
3556
|
+
await appendRunAttemptHistory(admitted, roleOutcome);
|
|
3415
3557
|
const artifactsDir = await ensureRunArtifactsDir(admitted.runDirectory);
|
|
3416
3558
|
const reportPath = join(artifactsDir, "report.json");
|
|
3417
3559
|
const evidencePath = join(artifactsDir, "evidence.json");
|
|
@@ -3746,6 +3888,19 @@ export async function publishFailureArtifacts(
|
|
|
3746
3888
|
);
|
|
3747
3889
|
const priorIssues: PublicationAttempt[] =
|
|
3748
3890
|
baseAttempt === undefined ? [] : [baseAttempt];
|
|
3891
|
+
// #419: each attempt's complete failure result joins the appended history
|
|
3892
|
+
// before any fixed-name artifact view is rewritten. History failure must not
|
|
3893
|
+
// strand the original controlled failure outside settlement — it rides
|
|
3894
|
+
// publicationIssues instead of aborting durability.
|
|
3895
|
+
try {
|
|
3896
|
+
await appendRunAttemptHistory(admitted, {
|
|
3897
|
+
kind: "failure",
|
|
3898
|
+
role: admitted.role,
|
|
3899
|
+
...failure,
|
|
3900
|
+
});
|
|
3901
|
+
} catch (error) {
|
|
3902
|
+
priorIssues.push(publicationAttemptFromError(admitted.sessionFile, error));
|
|
3903
|
+
}
|
|
3749
3904
|
|
|
3750
3905
|
// Prefer conventional names; unique fallback dirs keep colliding fixed paths
|
|
3751
3906
|
// from stranding the original failure outside settlement. Include the ledger
|