@akagilnc/pi-workflow-roles 0.1.1751
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/LICENSE +202 -0
- package/README.md +104 -0
- package/README.zh-CN.md +133 -0
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/dist/activation-ledger-git.js +68 -0
- package/dist/activation-ledger-session.js +120 -0
- package/dist/activation-ledger-topology.js +239 -0
- package/dist/activation-reconciliation.js +61 -0
- package/dist/audit-escalation.js +108 -0
- package/dist/auditor-dossier-tool.js +35 -0
- package/dist/canonical-json.js +78 -0
- package/dist/compliance-transport.js +77 -0
- package/dist/doctor-contracts.js +172 -0
- package/dist/dossier-resolution.js +103 -0
- package/dist/evidence-child-executor.js +661 -0
- package/dist/exact-utf8.js +12 -0
- package/dist/git-object-id.js +7 -0
- package/dist/in-process-session.js +50 -0
- package/dist/merger-contracts.js +76 -0
- package/dist/navigator-attendance.js +995 -0
- package/dist/navigator-invocation-identity.js +220 -0
- package/dist/open-tool-schema.js +39 -0
- package/dist/package-contracts/collector-output.js +50 -0
- package/dist/package-contracts/fixer-output.js +72 -0
- package/dist/package-contracts/fixer-packet.js +77 -0
- package/dist/package-contracts/judge-output.js +17 -0
- package/dist/package-contracts/reviewer-output.js +82 -0
- package/dist/package-contracts/terminating-tools.js +173 -0
- package/dist/package-contracts/worker-output.js +13 -0
- package/dist/package-owned-tool-idle.js +104 -0
- package/dist/packaged-role-registry.js +34 -0
- package/dist/public-cli/main.js +23867 -0
- package/dist/public-command-renderer.js +20 -0
- package/dist/reviewer-agent.js +93 -0
- package/dist/reviewer-child-executor.js +23 -0
- package/dist/reviewer-construction.js +95 -0
- package/dist/reviewer-dispatch.js +77 -0
- package/dist/reviewer-execution-ledger.js +160 -0
- package/dist/reviewer-failure-diagnostic.js +17 -0
- package/dist/reviewer-git-snapshot.js +38 -0
- package/dist/reviewer-pinned-git.js +146 -0
- package/dist/reviewer-preflight-error.js +15 -0
- package/dist/reviewer-prompt-identity.js +10 -0
- package/dist/reviewer-scope-prompt.js +21 -0
- package/dist/reviewer-workspace.js +151 -0
- package/dist/sha256.js +5 -0
- package/dist/sitian-record-entry.js +33 -0
- package/dist/stderr-jsonl.js +26 -0
- package/dist/stream-idle-guard.js +75 -0
- package/dist/tool-execution-observation.js +141 -0
- package/dist/uuidv7.js +21 -0
- package/dist/work-subject-identity.js +53 -0
- package/extensions/role-runtime.ts +303 -0
- package/package.json +69 -0
- package/packets/fixer-prerequisites.json +6 -0
- package/packets/fixer-repair.md +5 -0
- package/packets/judge-apply.md +77 -0
- package/packets/judge-authority.md +64 -0
- package/packets/judge-plan.md +55 -0
- package/packets/judge-review.md +49 -0
- package/packets/judge-submission.md +34 -0
- package/resources/methods/code-review/SKILL.md +92 -0
- package/resources/methods/code-review/agents/openai.yaml +3 -0
- package/resources/methods/code-review/provenance.json +26 -0
- package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
- package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
- package/resources/methods/diagnosing-bugs/provenance.json +31 -0
- package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
- package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
- package/resources/methods/tdd/SKILL.md +38 -0
- package/resources/methods/tdd/agents/openai.yaml +3 -0
- package/resources/methods/tdd/mocking.md +59 -0
- package/resources/methods/tdd/provenance.json +36 -0
- package/resources/methods/tdd/tests.md +77 -0
- package/resources/navigator-route-playbook.md +32 -0
- package/schemas/tool-execution-observation.schema.json +107 -0
- package/scripts/build-package.mjs +65 -0
- package/scripts/generate-tool-execution-observation-schema.ts +7 -0
- package/souls/coder.md +10 -0
- package/souls/collector.md +11 -0
- package/souls/doctor-auditor.md +23 -0
- package/souls/doctor.md +8 -0
- package/souls/fixer-auditor.md +33 -0
- package/souls/fixer.md +13 -0
- package/souls/judge-auditor.md +33 -0
- package/souls/judge.md +74 -0
- package/souls/merger.md +5 -0
- package/souls/navigator.md +5 -0
- package/souls/reviewer-auditor.md +25 -0
- package/souls/reviewer.md +11 -0
- package/src/activation-ledger-git.ts +96 -0
- package/src/activation-ledger-session.ts +188 -0
- package/src/activation-ledger-topology.ts +301 -0
- package/src/activation-ledger.ts +240 -0
- package/src/activation-reconciliation.ts +163 -0
- package/src/activation-trace.ts +38 -0
- package/src/audit-escalation.ts +177 -0
- package/src/auditor-dossier-tool.ts +48 -0
- package/src/auditor-soul.ts +28 -0
- package/src/canonical-json.ts +74 -0
- package/src/canonical-skill-binding.ts +107 -0
- package/src/collector-config.ts +89 -0
- package/src/collector-evidence.ts +461 -0
- package/src/collector-github.ts +656 -0
- package/src/collector-identity.ts +161 -0
- package/src/collector-ledger.ts +827 -0
- package/src/collector-receipt.ts +87 -0
- package/src/collector-role.ts +592 -0
- package/src/collector-tool-schemas.ts +19 -0
- package/src/compliance-transport.ts +130 -0
- package/src/doctor-auditor.ts +53 -0
- package/src/doctor-contracts.ts +166 -0
- package/src/doctor-evidence.ts +47 -0
- package/src/doctor-role.ts +18 -0
- package/src/dossier-resolution.ts +137 -0
- package/src/evidence-child-executor.ts +775 -0
- package/src/exact-utf8.ts +9 -0
- package/src/factory-board.ts +1822 -0
- package/src/git-object-id.ts +11 -0
- package/src/human-format.ts +65 -0
- package/src/in-process-session.ts +78 -0
- package/src/judge-auditor.ts +55 -0
- package/src/judge-recording-anti-forge.ts +53 -0
- package/src/judge-role.ts +160 -0
- package/src/merger-contracts.ts +71 -0
- package/src/merger-git-state.ts +76 -0
- package/src/merger-role.ts +60 -0
- package/src/navigator-attendance.ts +1254 -0
- package/src/navigator-invocation-identity.ts +446 -0
- package/src/open-tool-schema.ts +46 -0
- package/src/package-contracts/collector-output.ts +109 -0
- package/src/package-contracts/fixer-output.ts +81 -0
- package/src/package-contracts/fixer-packet.ts +93 -0
- package/src/package-contracts/judge-output.ts +39 -0
- package/src/package-contracts/reviewer-output.ts +115 -0
- package/src/package-contracts/terminating-tools.ts +259 -0
- package/src/package-contracts/worker-output.ts +36 -0
- package/src/package-owned-tool-idle.ts +134 -0
- package/src/package-resources/method-skill-binding.ts +87 -0
- package/src/package-resources/method-skill.ts +358 -0
- package/src/packaged-role-registry.ts +36 -0
- package/src/public-cli/cli-errors.ts +11 -0
- package/src/public-cli/cli-io.ts +4 -0
- package/src/public-cli/cli.ts +912 -0
- package/src/public-cli/coder-run.ts +575 -0
- package/src/public-cli/collector-run.ts +375 -0
- package/src/public-cli/command-renderer.ts +8 -0
- package/src/public-cli/config.ts +346 -0
- package/src/public-cli/doctor-run.ts +355 -0
- package/src/public-cli/explicit-internal.ts +274 -0
- package/src/public-cli/fixer-run.ts +587 -0
- package/src/public-cli/host-pi-runtime.ts +112 -0
- package/src/public-cli/invocation.ts +1958 -0
- package/src/public-cli/judge-run.ts +507 -0
- package/src/public-cli/main.ts +15 -0
- package/src/public-cli/merger-run.ts +681 -0
- package/src/public-cli/public-run-credentials.ts +71 -0
- package/src/public-cli/registry.ts +153 -0
- package/src/public-cli/reviewer-run.ts +561 -0
- package/src/public-cli/run-lifecycle.ts +884 -0
- package/src/public-cli/settlement.ts +3765 -0
- package/src/public-cli/terminal.ts +325 -0
- package/src/public-command-renderer.ts +43 -0
- package/src/reviewer-agent.ts +94 -0
- package/src/reviewer-auditor.ts +53 -0
- package/src/reviewer-child-executor.ts +31 -0
- package/src/reviewer-construction.ts +137 -0
- package/src/reviewer-dispatch.ts +94 -0
- package/src/reviewer-execution-ledger.ts +206 -0
- package/src/reviewer-failure-diagnostic.ts +18 -0
- package/src/reviewer-git-snapshot.ts +53 -0
- package/src/reviewer-pinned-git.ts +144 -0
- package/src/reviewer-preflight-error.ts +14 -0
- package/src/reviewer-prompt-identity.ts +17 -0
- package/src/reviewer-role.ts +193 -0
- package/src/reviewer-scope-prompt.ts +24 -0
- package/src/reviewer-settlement.ts +63 -0
- package/src/reviewer-workspace.ts +111 -0
- package/src/role-runtime.ts +884 -0
- package/src/sha256.ts +6 -0
- package/src/sitian-record-entry.ts +57 -0
- package/src/stderr-jsonl.ts +28 -0
- package/src/stream-idle-guard.ts +98 -0
- package/src/ticket-snapshot.ts +662 -0
- package/src/ticket-trajectory.ts +1000 -0
- package/src/tool-execution-observation.ts +168 -0
- package/src/uuidv7.ts +1 -0
- package/src/work-subject-identity.ts +94 -0
- package/src/worker-role.ts +434 -0
- package/src/worker-submission-gates.ts +225 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { executeAuditorChild, } from "./evidence-child-executor.js";
|
|
3
|
+
import { createAuditorDossierTool } from "./auditor-dossier-tool.js";
|
|
4
|
+
/** Zero-projection kickoff — soul already carries dossier-fetch duty; no hand-delivered materials. */
|
|
5
|
+
export const AUDITOR_DOSSIER_PROMPT = "Audit the current run dossier.";
|
|
6
|
+
const nonblank = Type.String({ minLength: 1, pattern: "\\S" });
|
|
7
|
+
const decisionGateSchema = Type.Object({ question: nonblank, options: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false });
|
|
8
|
+
// Transport must retain malformed candidates so they can settle as typed
|
|
9
|
+
// audit-incomplete outcomes; status values are guidance, not a schema gate.
|
|
10
|
+
export const complianceDecisionSchema = Type.Object({ status: Type.Unknown({ description: "Auditor decision status." }), violations: Type.Array(nonblank, { description: "Observed compliance violations." }), conflicts: Type.Array(nonblank, { description: "Unresolved authority or execution conflicts." }), decisionGate: Type.Union([decisionGateSchema, Type.Null()], { description: "Escalation question and available options." }) }, { additionalProperties: true, required: [] });
|
|
11
|
+
export function createComplianceDecisionTool(name, description) {
|
|
12
|
+
return { name, description, parameters: complianceDecisionSchema, async execute(_id, params) { return { content: [{ type: "text", text: "Compliance decision received" }], details: params, terminate: true }; } };
|
|
13
|
+
}
|
|
14
|
+
export async function prepareComplianceDispatch(model, context, label) {
|
|
15
|
+
const resolution = await context.modelRegistry.getProviderAuth(model.provider).catch((error) => { throw new Error(`${label} authentication failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); });
|
|
16
|
+
if (resolution === undefined)
|
|
17
|
+
throw new Error(`${label} authentication failed: provider is not configured: ${model.provider}`);
|
|
18
|
+
const auth = await context.modelRegistry.getApiKeyAndHeaders(model);
|
|
19
|
+
if (!auth.ok)
|
|
20
|
+
throw new Error(`${label} authentication failed: ${auth.error}`);
|
|
21
|
+
const env = auth.env ?? resolution.env;
|
|
22
|
+
return { model: resolution.auth.baseUrl ? { ...model, baseUrl: resolution.auth.baseUrl } : model, auth: { ...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }), ...(auth.headers === undefined ? {} : { headers: auth.headers }), ...(env === undefined ? {} : { env }) } };
|
|
23
|
+
}
|
|
24
|
+
export const COMPLIANCE_RESPONSE_ENTRY_TYPE = "ak_compliance_response";
|
|
25
|
+
export const AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE = "ak_auditor_parent_attempt_binding";
|
|
26
|
+
export const AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE = "ak_auditor_compliance_failure";
|
|
27
|
+
export class ComplianceResponseRetentionError extends Error {
|
|
28
|
+
constructor(message, options) { super(message, options); this.name = "ComplianceResponseRetentionError"; }
|
|
29
|
+
}
|
|
30
|
+
/** Unique owner for session custom-entry append with availability check and typed failure. */
|
|
31
|
+
export function appendActiveSessionCustomEntry(context, customType, data, labels = {}) {
|
|
32
|
+
const manager = context.sessionManager;
|
|
33
|
+
if (typeof manager?.appendCustomEntry !== "function") {
|
|
34
|
+
throw new ComplianceResponseRetentionError(labels.unavailable ?? "session custom entry append is unavailable");
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return manager.appendCustomEntry(customType, data);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new ComplianceResponseRetentionError(labels.failed ?? "session custom entry append failed", { cause: error });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function retainComplianceResponse(context, response) {
|
|
44
|
+
appendActiveSessionCustomEntry(context, COMPLIANCE_RESPONSE_ENTRY_TYPE, { version: 1, response }, {
|
|
45
|
+
unavailable: "compliance response retention is unavailable",
|
|
46
|
+
failed: "compliance response retention failed",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function readListField(value) { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
|
|
50
|
+
export function readComplianceCandidate(arguments_, usage) {
|
|
51
|
+
if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_))
|
|
52
|
+
return { status: "audit-incomplete", observation: { kind: "non-object-arguments", type: arguments_ === null ? "null" : Array.isArray(arguments_) ? "array" : typeof arguments_ }, candidate: arguments_, ...(usage === undefined ? {} : { usage }) };
|
|
53
|
+
const args = arguments_;
|
|
54
|
+
const status = args.status;
|
|
55
|
+
if (status === "pass")
|
|
56
|
+
return { status, ...(usage === undefined ? {} : { usage }) };
|
|
57
|
+
if (status === "revise")
|
|
58
|
+
return { status, violations: readListField(args.violations), ...(usage === undefined ? {} : { usage }) };
|
|
59
|
+
if (status === "escalate")
|
|
60
|
+
return { status, ...(Object.hasOwn(args, "conflicts") ? { conflicts: args.conflicts } : {}), ...(Object.hasOwn(args, "decisionGate") ? { decisionGate: args.decisionGate } : {}), ...(usage === undefined ? {} : { usage }) };
|
|
61
|
+
return { status: "audit-incomplete", observation: { kind: "object-status-unreadable", status: status === undefined ? "missing" : "unknown" }, candidate: arguments_, ...(usage === undefined ? {} : { usage }) };
|
|
62
|
+
}
|
|
63
|
+
export async function runComplianceAudit(options) {
|
|
64
|
+
const prompt = options.serializedInput ?? AUDITOR_DOSSIER_PROMPT;
|
|
65
|
+
const receipt = await executeAuditorChild({
|
|
66
|
+
tool: options.tool,
|
|
67
|
+
dossierTool: createAuditorDossierTool(options.runDirectory),
|
|
68
|
+
systemPrompt: options.systemPrompt,
|
|
69
|
+
prompt,
|
|
70
|
+
roleLabel: options.roleLabel,
|
|
71
|
+
context: options.context,
|
|
72
|
+
retainResponse: (response) => retainComplianceResponse(options.context, response),
|
|
73
|
+
...(options.runCompletion === undefined ? {} : { runCompletion: options.runCompletion }),
|
|
74
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
75
|
+
});
|
|
76
|
+
return readComplianceCandidate(receipt.decision, receipt.response.usage);
|
|
77
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { canonicalJson } from "./canonical-json.js";
|
|
3
|
+
import { openToolObjectFromUnion } from "./open-tool-schema.js";
|
|
4
|
+
export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
|
|
5
|
+
export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
|
|
6
|
+
export const DOCTOR_OUTPUT_TOOL_DESCRIPTION = "Submit the sole final typed single-case testimony. Use completed when findings is empty or contains only non-prescriptive case observations. The runtime adds its derived case cost to the accepted receipt. Refuse only when the evidence cannot support even truthful case testimony; unavailable reusable-asset or bounded-bite evidence blocks only the corresponding asset prescription.";
|
|
7
|
+
export const DOCTOR_TARGET_KINDS = ["law", "gate", "template", "station", "seat"];
|
|
8
|
+
const nonblank = Type.String({ minLength: 1, pattern: "\\S" });
|
|
9
|
+
const count = Type.Object({ count: Type.Integer({ minimum: 0 }), sources: Type.Array(nonblank) }, { additionalProperties: false });
|
|
10
|
+
const evidenceIds = Type.Array(nonblank, { minItems: 1 });
|
|
11
|
+
const guardrail = Type.Object({ answer: Type.Boolean(), evidenceIds, explanation: nonblank }, { additionalProperties: false });
|
|
12
|
+
const lastRealBite = Type.Union([
|
|
13
|
+
Type.Object({ kind: Type.Literal("actual"), targetKey: nonblank, evidenceId: nonblank }, { additionalProperties: false }),
|
|
14
|
+
Type.Object({ kind: Type.Literal("noRealBite"), targetKey: nonblank, eligibleEvidenceIds: evidenceIds }, { additionalProperties: false }),
|
|
15
|
+
]);
|
|
16
|
+
const assetKinds = DOCTOR_TARGET_KINDS;
|
|
17
|
+
const findingBody = {
|
|
18
|
+
evidenceIds, disposition: Type.Union([Type.Literal("keep"), Type.Literal("thin"), Type.Literal("delete")]),
|
|
19
|
+
guardrails: Type.Object({ reproducibleFailure: guardrail, owningSeamOrInvariant: guardrail, deletionOrSimplificationSuffices: guardrail }, { additionalProperties: true }),
|
|
20
|
+
prescription: Type.Object({ kind: Type.Union([Type.Literal("retain"), Type.Literal("delete"), Type.Literal("simplify"), Type.Literal("patch"), Type.Literal("addMechanism")]), recommendation: nonblank, necessityExplanation: Type.Optional(nonblank) }, { additionalProperties: false }), lastRealBite,
|
|
21
|
+
};
|
|
22
|
+
const finding = Type.Union([
|
|
23
|
+
Type.Object({ targetKey: nonblank, observation: nonblank, evidenceIds }, { additionalProperties: false }),
|
|
24
|
+
Type.Object({ targetKey: nonblank, targetKind: Type.Union(assetKinds.map((kind) => Type.Literal(kind))), assetEvidence: Type.Object({ targetKey: nonblank, targetKind: Type.Union(assetKinds.map((kind) => Type.Literal(kind))), evidenceId: nonblank }, { additionalProperties: false }), ...findingBody }, { additionalProperties: false }),
|
|
25
|
+
]);
|
|
26
|
+
const caseIdentity = Type.Object({ issueNumber: Type.Integer({ minimum: 1 }), runsPath: nonblank }, { additionalProperties: false });
|
|
27
|
+
const cost = Type.Object({
|
|
28
|
+
invocations: count, legs: count, modelApiTurns: count, outputTokens: count, toolCalls: count,
|
|
29
|
+
retries: Type.Object({ count: Type.Integer({ minimum: 0 }), sources: Type.Array(nonblank), evidence: Type.Literal("literal run-dir naming") }, { additionalProperties: false }),
|
|
30
|
+
statuses: Type.Array(Type.Object({ source: nonblank, status: nonblank }, { additionalProperties: false })),
|
|
31
|
+
commits: Type.Array(Type.Object({ source: nonblank, commit: nonblank }, { additionalProperties: false })),
|
|
32
|
+
sessions: Type.Array(Type.Union([
|
|
33
|
+
Type.Object({ source: nonblank, startedAt: nonblank, endedAt: nonblank, wallMilliseconds: Type.Number({ minimum: 0 }), completion: Type.Literal("accepted") }, { additionalProperties: false }),
|
|
34
|
+
Type.Object({ source: nonblank, startedAt: Type.Optional(nonblank), endedAt: Type.Optional(nonblank), wallMilliseconds: Type.Optional(Type.Number({ minimum: 0 })), completion: Type.Literal("incomplete"), degradationReason: Type.Optional(nonblank) }, { additionalProperties: false }),
|
|
35
|
+
])),
|
|
36
|
+
outputBytes: Type.Object({ count: Type.Integer({ minimum: 0 }), sources: Type.Array(nonblank), payload: Type.Literal("raw JSONL bytes"), providerWireBytes: Type.Literal("unavailable") }, { additionalProperties: false }),
|
|
37
|
+
}, { additionalProperties: false });
|
|
38
|
+
const doctorSubmissionVariants = Type.Union([
|
|
39
|
+
Type.Object({
|
|
40
|
+
status: Type.Literal("completed", { description: "Truthful single-case testimony was completed; the runtime adds derived cost to the receipt." }),
|
|
41
|
+
case: Type.Unsafe({ ...caseIdentity, description: "Identity of the retained Doctor case." }),
|
|
42
|
+
findings: Type.Array(finding, { description: "May be empty or contain non-prescriptive case observations. Missing reusable-asset or bounded-bite evidence excludes only the corresponding asset prescription." }),
|
|
43
|
+
}, { additionalProperties: false, description: "Single-case testimony, without requiring any prescription or reusable finding." }),
|
|
44
|
+
Type.Object({
|
|
45
|
+
status: Type.Literal("refused", { description: "Reserved for inability to support truthful case testimony, not for an unavailable prescription axis." }),
|
|
46
|
+
reason: Type.String({ minLength: 1, description: "Reason evidence is insufficient for truthful testimony." }),
|
|
47
|
+
missingEvidence: Type.Array(Type.Object({ need: nonblank, targetKeys: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false }), { minItems: 1, description: "Evidence required before truthful testimony is possible." }),
|
|
48
|
+
}, { additionalProperties: false, description: "Evidence is insufficient for truthful case testimony." }),
|
|
49
|
+
]);
|
|
50
|
+
export const doctorSubmissionSchema = openToolObjectFromUnion(doctorSubmissionVariants);
|
|
51
|
+
export const doctorOutputSchema = Type.Union([
|
|
52
|
+
Type.Object({ status: Type.Literal("completed"), case: caseIdentity, findings: Type.Array(finding), cost }, { additionalProperties: false }),
|
|
53
|
+
doctorSubmissionVariants.anyOf[1],
|
|
54
|
+
]);
|
|
55
|
+
export const doctorEvidenceReadSchema = Type.Object({ evidenceId: Type.String({ minLength: 1, description: "Identifier of the retained evidence to read." }), offset: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based byte offset at which to begin reading." })), limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 4096, description: "Maximum number of bytes to return." })) }, { additionalProperties: false });
|
|
56
|
+
export class DoctorSubmissionContractError extends Error {
|
|
57
|
+
name = "DoctorSubmissionContractError";
|
|
58
|
+
}
|
|
59
|
+
function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
|
|
60
|
+
function read(value, key) { if (!isRecord(value))
|
|
61
|
+
return undefined; try {
|
|
62
|
+
return value[key];
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
} }
|
|
67
|
+
export function validateDoctorSubmissionShape(value) {
|
|
68
|
+
const status = read(value, "status");
|
|
69
|
+
if (status !== "completed" && status !== "refused")
|
|
70
|
+
throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
export function validateRecordedDoctorOutput(value) {
|
|
74
|
+
const output = validateDoctorSubmissionShape(value);
|
|
75
|
+
if (read(output, "status") === "completed" && read(output, "cost") === undefined)
|
|
76
|
+
throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
export class DoctorEvidenceStore {
|
|
80
|
+
patient;
|
|
81
|
+
entries;
|
|
82
|
+
coverage = new Map();
|
|
83
|
+
constructor(patient) {
|
|
84
|
+
this.patient = patient;
|
|
85
|
+
this.entries = new Map(patient.evidence.map((entry) => [entry.id, entry]));
|
|
86
|
+
}
|
|
87
|
+
read(evidenceId, offset = 0, limit = 4096) { const entry = this.entries.get(evidenceId); if (!entry)
|
|
88
|
+
throw new Error(`Evidence ID is not admitted: ${evidenceId}`); if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(limit) || limit < 1 || limit > 4096)
|
|
89
|
+
throw new Error("Invalid evidence pagination"); if (offset > entry.contentLength)
|
|
90
|
+
throw new Error("Evidence offset exceeds content"); const end = Math.min(entry.contentLength, offset + limit); const ranges = [...(this.coverage.get(evidenceId) ?? []), [offset, end]].sort((a, b) => a[0] - b[0]); const merged = []; for (const range of ranges) {
|
|
91
|
+
const prior = merged.at(-1);
|
|
92
|
+
if (prior && range[0] <= prior[1])
|
|
93
|
+
prior[1] = Math.max(prior[1], range[1]);
|
|
94
|
+
else
|
|
95
|
+
merged.push([...range]);
|
|
96
|
+
} this.coverage.set(evidenceId, merged); return { evidenceId, kind: entry.kind, offset, content: entry.content.slice(offset, end), nextOffset: end < entry.contentLength ? end : null, contentLength: entry.contentLength, byteLength: entry.byteLength, sha256: entry.sha256 }; }
|
|
97
|
+
hasRead(id) { const entry = this.entries.get(id); const ranges = this.coverage.get(id); return !!entry && ranges?.length === 1 && ranges[0][0] === 0 && ranges[0][1] === entry.contentLength; }
|
|
98
|
+
readRecord() { return [...this.coverage.keys()].sort().map((evidenceId) => ({ evidenceId, fullyRead: this.hasRead(evidenceId) })); }
|
|
99
|
+
}
|
|
100
|
+
export function validateDoctorOutput(value, patient, store) {
|
|
101
|
+
const output = validateDoctorSubmissionShape(value);
|
|
102
|
+
const lawfulTargets = new Set(["case", ...patient.cost.invocations.sources]);
|
|
103
|
+
const assertTarget = (targetKey) => { if (typeof targetKey === "string" && !lawfulTargets.has(targetKey))
|
|
104
|
+
throw new Error(`Target key is not a lawful case target: ${targetKey}`); };
|
|
105
|
+
const readCitations = (ids, label) => { if (!Array.isArray(ids))
|
|
106
|
+
return; for (const id of ids)
|
|
107
|
+
if (typeof id === "string" && (!store.entries.has(id) || !store.hasRead(id)))
|
|
108
|
+
throw new Error(`${label} must cite admitted/read evidence: ${id}`); };
|
|
109
|
+
if (read(output, "status") === "refused") {
|
|
110
|
+
const missingEvidence = read(output, "missingEvidence");
|
|
111
|
+
if (Array.isArray(missingEvidence))
|
|
112
|
+
for (const missing of missingEvidence) {
|
|
113
|
+
const targets = read(missing, "targetKeys");
|
|
114
|
+
if (Array.isArray(targets))
|
|
115
|
+
for (const target of targets)
|
|
116
|
+
assertTarget(target);
|
|
117
|
+
}
|
|
118
|
+
return output;
|
|
119
|
+
}
|
|
120
|
+
const identity = read(output, "case");
|
|
121
|
+
const issueNumber = read(identity, "issueNumber");
|
|
122
|
+
const runsPath = read(identity, "runsPath");
|
|
123
|
+
if ((issueNumber !== undefined && issueNumber !== patient.identity.issueNumber) || (runsPath !== undefined && runsPath !== patient.identity.runsPath))
|
|
124
|
+
throw new Error("Doctor submission case must equal the activated case identity");
|
|
125
|
+
const findings = read(output, "findings");
|
|
126
|
+
if (!Array.isArray(findings))
|
|
127
|
+
return output;
|
|
128
|
+
for (const finding of findings) {
|
|
129
|
+
const targetKey = read(finding, "targetKey");
|
|
130
|
+
readCitations(read(finding, "evidenceIds"), "finding");
|
|
131
|
+
const assetEvidence = read(finding, "assetEvidence");
|
|
132
|
+
if (!isRecord(assetEvidence)) {
|
|
133
|
+
assertTarget(targetKey);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const assetTargetKey = read(assetEvidence, "targetKey");
|
|
137
|
+
const assetTargetKind = read(assetEvidence, "targetKind");
|
|
138
|
+
const assetEvidenceId = read(assetEvidence, "evidenceId");
|
|
139
|
+
if (typeof assetTargetKey === "string" && assetTargetKey !== targetKey)
|
|
140
|
+
throw new Error("Typed asset evidence must establish the finding target key");
|
|
141
|
+
if (typeof assetTargetKind === "string" && assetTargetKind !== read(finding, "targetKind"))
|
|
142
|
+
throw new Error("Typed asset evidence must establish the finding target kind");
|
|
143
|
+
if (typeof assetEvidenceId === "string")
|
|
144
|
+
readCitations([assetEvidenceId], "asset evidence");
|
|
145
|
+
const guardrails = read(finding, "guardrails");
|
|
146
|
+
for (const key of ["reproducibleFailure", "owningSeamOrInvariant", "deletionOrSimplificationSuffices"])
|
|
147
|
+
readCitations(read(read(guardrails, key), "evidenceIds"), "guardrail");
|
|
148
|
+
const bite = read(finding, "lastRealBite");
|
|
149
|
+
const biteKind = read(bite, "kind");
|
|
150
|
+
if (biteKind !== "actual" && biteKind !== "noRealBite")
|
|
151
|
+
continue;
|
|
152
|
+
if (read(bite, "targetKey") !== targetKey)
|
|
153
|
+
throw new Error("lastRealBite target mismatch");
|
|
154
|
+
if (biteKind === "actual") {
|
|
155
|
+
const evidenceId = read(bite, "evidenceId");
|
|
156
|
+
const entry = typeof evidenceId === "string" ? store.entries.get(evidenceId) : undefined;
|
|
157
|
+
if (!entry || entry.kind !== "session" || !store.hasRead(entry.id))
|
|
158
|
+
throw new Error("actual bite must cite an admitted/read retained session");
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const eligible = patient.evidence.map((entry) => entry.id).sort();
|
|
162
|
+
const ids = read(bite, "eligibleEvidenceIds");
|
|
163
|
+
if (Array.isArray(ids)) {
|
|
164
|
+
const claimed = ids.filter((id) => typeof id === "string").sort();
|
|
165
|
+
if (canonicalJson(claimed) !== canonicalJson(eligible))
|
|
166
|
+
throw new Error("noRealBite must prove the complete eligible single-case evidence population");
|
|
167
|
+
readCitations(eligible, "noRealBite");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unique dossier-resolution seam for 审刑院 (#233).
|
|
3
|
+
* Machine pointers only: cwd + AK_ROLE_RUN_DIR. No latest-run / mtime / global scan.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, statSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { JUDGE_OUTPUT_TOOL_NAME as JUDGE_OUTPUT_TOOL } from "./package-contracts/judge-output.js";
|
|
8
|
+
export const JUDGE_OUTPUT_TOOL_NAME = JUDGE_OUTPUT_TOOL;
|
|
9
|
+
export const AUDIT_RUN_DIR_ENV = "AK_ROLE_RUN_DIR";
|
|
10
|
+
export const REVIEWER_CANDIDATE_ENTRY_TYPE = "ak_reviewer_audit_candidate";
|
|
11
|
+
export const DOCTOR_CANDIDATE_ENTRY_TYPE = "ak_doctor_audit_candidate";
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the per-run dossier pointer injected by the public CLI.
|
|
14
|
+
*
|
|
15
|
+
* Absent pointer = bare Pi internal seam (ADR 0052): audit proceeds; the model
|
|
16
|
+
* self-locates the dossier from its own fall-volume position per soul. Public CLI
|
|
17
|
+
* always injects the pointer — only then does the machine validate the path.
|
|
18
|
+
* Concurrent runs stay isolated because a present pointer is per-process.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveAuditDossier(env = process.env) {
|
|
21
|
+
const raw = env[AUDIT_RUN_DIR_ENV];
|
|
22
|
+
// Bare Pi activation seam: no machine gate when the pointer was never injected.
|
|
23
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
24
|
+
return { status: "ok" };
|
|
25
|
+
}
|
|
26
|
+
const runDirectory = resolve(raw);
|
|
27
|
+
try {
|
|
28
|
+
if (!existsSync(runDirectory) || !statSync(runDirectory).isDirectory()) {
|
|
29
|
+
return { status: "incomplete", observation: { kind: "missing-dossier" } };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { status: "incomplete", observation: { kind: "missing-dossier" } };
|
|
34
|
+
}
|
|
35
|
+
return { status: "ok", runDirectory };
|
|
36
|
+
}
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Judge subjects must already be on the parent session books before audit starts:
|
|
42
|
+
* assignment (user message) + candidate verdict (sole judge output tool call).
|
|
43
|
+
*/
|
|
44
|
+
export function readJudgeAuditSubjects(context) {
|
|
45
|
+
const entries = context.sessionManager.getEntries?.() ?? [];
|
|
46
|
+
let hasAssignment = false;
|
|
47
|
+
let hasCandidate = false;
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (entry.type !== "message")
|
|
50
|
+
continue;
|
|
51
|
+
const message = entry.message;
|
|
52
|
+
if (message.role === "user") {
|
|
53
|
+
const text = typeof message.content === "string"
|
|
54
|
+
? message.content
|
|
55
|
+
: Array.isArray(message.content)
|
|
56
|
+
? message.content.map((part) => (part.type === "text" ? part.text : "")).join("")
|
|
57
|
+
: "";
|
|
58
|
+
if (text.trim().length > 0)
|
|
59
|
+
hasAssignment = true;
|
|
60
|
+
}
|
|
61
|
+
if (message.role === "assistant" && Array.isArray(message.content)) {
|
|
62
|
+
for (const part of message.content) {
|
|
63
|
+
if (part.type === "toolCall" && part.name === JUDGE_OUTPUT_TOOL_NAME && isRecord(part.arguments)) {
|
|
64
|
+
hasCandidate = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!hasAssignment) {
|
|
70
|
+
return { status: "incomplete", observation: { kind: "missing-subject", subject: "assignment" } };
|
|
71
|
+
}
|
|
72
|
+
if (!hasCandidate) {
|
|
73
|
+
return { status: "incomplete", observation: { kind: "missing-subject", subject: "candidate-verdict" } };
|
|
74
|
+
}
|
|
75
|
+
return { status: "ok" };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Reviewer candidate receipt must be recorded before audit (first-record-then-audit).
|
|
79
|
+
*/
|
|
80
|
+
export function readReviewerAuditSubjects(context) {
|
|
81
|
+
const entries = context.sessionManager.getEntries?.() ?? [];
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (entry.type === "custom" && entry.customType === REVIEWER_CANDIDATE_ENTRY_TYPE) {
|
|
84
|
+
return { status: "ok" };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { status: "incomplete", observation: { kind: "missing-subject", subject: "candidate-receipt" } };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Doctor candidate testimony must be recorded before audit.
|
|
91
|
+
*/
|
|
92
|
+
export function readDoctorAuditSubjects(context) {
|
|
93
|
+
const entries = context.sessionManager.getEntries?.() ?? [];
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
if (entry.type === "custom" && entry.customType === DOCTOR_CANDIDATE_ENTRY_TYPE) {
|
|
96
|
+
return { status: "ok" };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { status: "incomplete", observation: { kind: "missing-subject", subject: "candidate-testimony" } };
|
|
100
|
+
}
|
|
101
|
+
export function toAuditIncomplete(observation) {
|
|
102
|
+
return { status: "audit-incomplete", observation, candidate: undefined };
|
|
103
|
+
}
|