audit-tools 0.50.11 → 0.50.13
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/remediate/contractPipeline/derive.d.ts +7 -7
- package/dist/remediate/contractPipeline/derive.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/derive.js +19 -21
- package/dist/remediate/contractPipeline/derive.js.map +1 -1
- package/dist/remediate/contractPipeline/phaseCut.d.ts +46 -8
- package/dist/remediate/contractPipeline/phaseCut.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/phaseCut.js +71 -40
- package/dist/remediate/contractPipeline/phaseCut.js.map +1 -1
- package/dist/remediate/contractPipeline/phaseCutArtifact.d.ts +1 -1
- package/dist/remediate/contractPipeline/phaseCutArtifact.js +1 -1
- package/dist/remediate/contractPipeline/repairState.d.ts +108 -0
- package/dist/remediate/contractPipeline/repairState.d.ts.map +1 -0
- package/dist/remediate/contractPipeline/repairState.js +184 -0
- package/dist/remediate/contractPipeline/repairState.js.map +1 -0
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +4 -0
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/state/types.d.ts +62 -0
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/remediate/state/types.js +17 -0
- package/dist/remediate/state/types.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +8 -0
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +192 -58
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.js +3 -2
- package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +8 -0
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +73 -2
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +94 -1
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/prompts.d.ts.map +1 -1
- package/dist/remediate/steps/prompts.js +20 -2
- package/dist/remediate/steps/prompts.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +2 -2
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js +6 -2
- package/dist/remediate/steps/types.js.map +1 -1
- package/dist/remediate/validation/artifacts.d.ts.map +1 -1
- package/dist/remediate/validation/artifacts.js +4 -0
- package/dist/remediate/validation/artifacts.js.map +1 -1
- package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipeline.js +14 -0
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.d.ts +9 -2
- package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.js +13 -7
- package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,17 +24,17 @@ import { mkdir, readFile, rename, rm } from "node:fs/promises";
|
|
|
24
24
|
import { isAbsolute, join, resolve } from "node:path";
|
|
25
25
|
import { writeJsonFile, readOptionalJsonFile, formatValidationIssues, hashContent, isRecord, withFsRetry, projectApprovedFindings, captureStepBoundaryFriction, climbOutOfAuditTools, partitionCommandsByDeclaredShape, normalizeRepoPath, repoRelativePath, toPosixPath, } from "audit-tools/shared";
|
|
26
26
|
import { createStepEmissionScaffold, } from "../../shared/steps/stepEmissionScaffold.js";
|
|
27
|
-
import { counterexampleFingerprint } from "../contractPipeline/counterexampleFingerprint.js";
|
|
28
27
|
import { OBLIGATION_KIND_PRIORITY, } from "../contractPipeline/obligationKinds.js";
|
|
29
28
|
import { CP_ARTIFACT_NAMES, contractArtifactExists, contractArtifactFilePath, contractInputFilePath, contractPipelineDir, detectStaleArtifacts, envelopePayload, envelopeSemanticHash, isEnvelope, pathASeedFilePath, payloadSemanticHash, readContractArtifact, stampToolCreatedAt, writeContractArtifact, writeDerivedContractArtifact, } from "../contractPipeline/artifactStore.js";
|
|
30
29
|
import { readIntakeRiskSignal, writeIntakeRiskSignal, escalateRiskSignal, decompositionRiskEvidence, adversarialDepthForTier, roundTripGranularityForTier, } from "../riskSignal.js";
|
|
31
|
-
import { phaseOrdinalForObligations, moduleSlug, renderPhaseCutSection, } from "../contractPipeline/phaseCut.js";
|
|
30
|
+
import { phaseOrdinalForObligations, moduleSlug, moduleSlugForObligationId, renderPhaseCutSection, detectContractTokenCycles, } from "../contractPipeline/phaseCut.js";
|
|
32
31
|
import { ensurePhaseCutArtifact, readPhaseCutArtifact } from "../contractPipeline/phaseCutArtifact.js";
|
|
33
32
|
import { detectCyclicSeamObligations, validateAuthoredCycleBreak, } from "../contractPipeline/cyclicSeamResolution.js";
|
|
34
33
|
import { deriveObligationLedger, deriveFinalizedModuleContracts, buildTestValidatorPlanScaffold, buildImplementationDagScaffold, acceptedCounterexampleIds, advisoryCritiqueItems, } from "../contractPipeline/derive.js";
|
|
35
34
|
import { ensureNodeId, toBlockId } from "../contractPipeline/idRegistry.js";
|
|
36
35
|
import { captureReviewSnapshot, computeReReviewDelta, isReviewArtifact, readReviewSnapshot, renderReReviewSection, reviewSnapshotExists, } from "../contractPipeline/reviewSnapshot.js";
|
|
37
36
|
import { captureTestPlanCarry, readTestPlanCarry, } from "../contractPipeline/testPlanCarry.js";
|
|
37
|
+
import { readRepairState, writeRepairState, counterexamplesByIdOf, counterexampleKeyOf, counterexampleWaiversPath, foldCounterexampleWaivers, waivedAcceptedIds, waivedJudgeAcceptedIds, } from "../contractPipeline/repairState.js";
|
|
38
38
|
import { renderContractPipelinePrompt, renderContractRepairPrompt, CONTRACT_PIPELINE_PHASE_ORDER, PHASE_TO_ARTIFACT, } from "./contractPipelinePrompts.js";
|
|
39
39
|
// The seven cross-artifact validators this module used to call one by one are
|
|
40
40
|
// gone from this list on purpose: every one of them is now reached through
|
|
@@ -123,22 +123,6 @@ export const MAX_DAG_REGENERATION_ATTEMPTS = 2;
|
|
|
123
123
|
* (and, if that also fails, to `blocked`).
|
|
124
124
|
*/
|
|
125
125
|
export const MAX_CYCLIC_SEAM_RESOLUTION_ATTEMPTS = 2;
|
|
126
|
-
function repairStatePath(artifactsDir) {
|
|
127
|
-
return join(contractPipelineDir(artifactsDir), "repair-state.json");
|
|
128
|
-
}
|
|
129
|
-
async function readRepairState(artifactsDir) {
|
|
130
|
-
const state = await readOptionalJsonFile(repairStatePath(artifactsDir));
|
|
131
|
-
return (state ?? {
|
|
132
|
-
schema_version: "remediate-code-contract-pipeline/repair-state/v1alpha1",
|
|
133
|
-
repairs: [],
|
|
134
|
-
critique_repairs: [],
|
|
135
|
-
dag_regenerations: [],
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
async function writeRepairState(artifactsDir, state) {
|
|
139
|
-
await mkdir(contractPipelineDir(artifactsDir), { recursive: true });
|
|
140
|
-
await writeJsonFile(repairStatePath(artifactsDir), state);
|
|
141
|
-
}
|
|
142
126
|
function cyclicSeamRepairStatePath(artifactsDir) {
|
|
143
127
|
return join(contractPipelineDir(artifactsDir), "cyclic-seam-repair-state.json");
|
|
144
128
|
}
|
|
@@ -561,7 +545,47 @@ async function evaluateJudgeGate(artifactsDir) {
|
|
|
561
545
|
const judge = envelopePayload(judgeEnvelope);
|
|
562
546
|
if (!judge || judge.verdict === "approved")
|
|
563
547
|
return { kind: "proceed" };
|
|
548
|
+
// Content-fingerprint keying (not raw id): two independent adversarial
|
|
549
|
+
// rounds may each label their genuinely-distinct top counterexample with
|
|
550
|
+
// the SAME reviewer id string (e.g. "CE-001", the prompt schema's own
|
|
551
|
+
// example value). Keying convergence on the raw id would then read "same CE
|
|
552
|
+
// re-accepted after a repair" and falsely escalate while a real new defect
|
|
553
|
+
// is being correctly repaired. Resolve each accepted id against the live
|
|
554
|
+
// counterexample artifact and key on content instead; an id with no
|
|
555
|
+
// matching counterexample falls back to raw-id keying — today's behavior —
|
|
556
|
+
// so nothing regresses when content can't be resolved. The keying is
|
|
557
|
+
// single-sourced with the waiver ledger (counterexampleKeyOf).
|
|
558
|
+
const cePayload = envelopePayload(await readContractArtifact(artifactsDir, "counterexample"));
|
|
559
|
+
const ceById = counterexamplesByIdOf(cePayload);
|
|
560
|
+
const keyOf = (rawId) => counterexampleKeyOf(ceById, rawId);
|
|
561
|
+
const acceptedIds = acceptedCeIdsOf(judge);
|
|
562
|
+
// Owner waivers (open-bugs.md:108, the recorded resolution verb): fold the
|
|
563
|
+
// host-written waiver file BEFORE any convergence math, so a waiver recorded
|
|
564
|
+
// against a blocked escalation unblocks this same invocation — including one
|
|
565
|
+
// recorded after a repair for this judge hash was already dispatched. An
|
|
566
|
+
// invalid file escalates loudly and applies NOTHING (never half-applied).
|
|
567
|
+
const fold = await foldCounterexampleWaivers(artifactsDir, {
|
|
568
|
+
counterexamplesById: ceById,
|
|
569
|
+
judgeAcceptedIds: new Set(acceptedIds),
|
|
570
|
+
});
|
|
571
|
+
if (fold.issues.length > 0) {
|
|
572
|
+
return {
|
|
573
|
+
kind: "escalate",
|
|
574
|
+
reason: "invalid_waivers",
|
|
575
|
+
outstanding: acceptedIds,
|
|
576
|
+
waiverIssues: fold.issues,
|
|
577
|
+
note: "The counterexample waiver file was refused and nothing was applied. " +
|
|
578
|
+
"Fix or delete it, then re-run next-step.",
|
|
579
|
+
};
|
|
580
|
+
}
|
|
564
581
|
const repairState = await readRepairState(artifactsDir);
|
|
582
|
+
const waived = waivedAcceptedIds(repairState, ceById, acceptedIds);
|
|
583
|
+
const unwaivedAccepted = acceptedIds.filter((id) => !waived.has(id));
|
|
584
|
+
// Every accepted counterexample carries a recorded owner waiver → the
|
|
585
|
+
// needs_repair verdict is resolved by decision: proceed.
|
|
586
|
+
if (acceptedIds.length > 0 && unwaivedAccepted.length === 0) {
|
|
587
|
+
return { kind: "proceed" };
|
|
588
|
+
}
|
|
565
589
|
const judgeHash = judgeEnvelope.content_hash;
|
|
566
590
|
const alreadyHandled = repairState.repairs.some((repair) => repair.judge_hash === judgeHash);
|
|
567
591
|
// Map judge.repair_directive.target if present; if absent, infer from classifications.
|
|
@@ -574,25 +598,9 @@ async function evaluateJudgeGate(artifactsDir) {
|
|
|
574
598
|
instruction: rawDirective.instruction,
|
|
575
599
|
}
|
|
576
600
|
: inferRepairDirective(judge);
|
|
577
|
-
const acceptedIds = acceptedCeIdsOf(judge);
|
|
578
|
-
// Content-fingerprint keying (not raw id): two independent adversarial
|
|
579
|
-
// rounds may each label their genuinely-distinct top counterexample with
|
|
580
|
-
// the SAME reviewer id string (e.g. "CE-001", the prompt schema's own
|
|
581
|
-
// example value). Keying convergence on the raw id would then read "same CE
|
|
582
|
-
// re-accepted after a repair" and falsely escalate while a real new defect
|
|
583
|
-
// is being correctly repaired. Resolve each accepted id against the live
|
|
584
|
-
// counterexample artifact and key on content instead; an id with no
|
|
585
|
-
// matching counterexample falls back to raw-id keying — today's behavior —
|
|
586
|
-
// so nothing regresses when content can't be resolved.
|
|
587
|
-
const cePayload = envelopePayload(await readContractArtifact(artifactsDir, "counterexample"));
|
|
588
|
-
const ceById = new Map((cePayload?.counterexamples ?? []).map((ce) => [ce.id, ce]));
|
|
589
|
-
const keyOf = (rawId) => {
|
|
590
|
-
const ce = ceById.get(rawId);
|
|
591
|
-
return ce ? `fp:${counterexampleFingerprint(ce)}` : `id:${rawId}`;
|
|
592
|
-
};
|
|
593
601
|
const addressed = new Set(repairState.repairs.flatMap((r) => r.addressed_ce_fingerprints ??
|
|
594
602
|
(r.accepted_ce_ids ?? []).map((id) => `id:${id}`)));
|
|
595
|
-
const newAccepted =
|
|
603
|
+
const newAccepted = unwaivedAccepted.filter((id) => !addressed.has(keyOf(id)));
|
|
596
604
|
const newAcceptedFingerprints = newAccepted.map(keyOf);
|
|
597
605
|
// Idempotent re-entry: this exact judge report already drove a repair (its hash
|
|
598
606
|
// is recorded). Re-emit the same repair directive; do not re-evaluate convergence
|
|
@@ -611,7 +619,7 @@ async function evaluateJudgeGate(artifactsDir) {
|
|
|
611
619
|
return {
|
|
612
620
|
kind: "escalate",
|
|
613
621
|
reason: "runaway",
|
|
614
|
-
outstanding:
|
|
622
|
+
outstanding: unwaivedAccepted,
|
|
615
623
|
note: `The judge↔repair loop reached its runaway backstop (${repairState.repairs.length} repair rounds) without converging. Each round was still surfacing accepted counterexamples. This is pathological non-convergence — review the outstanding counterexamples and the contract design with the user before proceeding.`,
|
|
616
624
|
};
|
|
617
625
|
}
|
|
@@ -630,8 +638,8 @@ async function evaluateJudgeGate(artifactsDir) {
|
|
|
630
638
|
return {
|
|
631
639
|
kind: "escalate",
|
|
632
640
|
reason: "stall",
|
|
633
|
-
outstanding:
|
|
634
|
-
note: `The judge re-accepted counterexample(s) that a prior repair already addressed (${
|
|
641
|
+
outstanding: unwaivedAccepted,
|
|
642
|
+
note: `The judge re-accepted counterexample(s) that a prior repair already addressed (${unwaivedAccepted.join(", ") || "none newly accepted"}), with no new accepted counterexample this round. The repair loop is not converging on these items. Resolve them with the user — adjust the contract design, or record an owner waiver accepting them as known limitations — before the plan can be promoted.`,
|
|
635
643
|
};
|
|
636
644
|
}
|
|
637
645
|
/** Blocking-severity critique item ids from a conceptual_design_critique payload. */
|
|
@@ -1485,7 +1493,16 @@ async function readCrossGateInputs(artifactsDir, root) {
|
|
|
1485
1493
|
payloads.set(name, envelopePayload(envelope));
|
|
1486
1494
|
}
|
|
1487
1495
|
const findingEnumeration = await readOptionalJsonFile(intakePaths(artifactsDir).findingEnumeration);
|
|
1488
|
-
|
|
1496
|
+
// Waived counterexamples (open-bugs.md:108): the coverage gates must not
|
|
1497
|
+
// demand DAG nodes for a counterexample the owner recorded as an accepted
|
|
1498
|
+
// limitation — that would recreate the judge-gate wedge one gate later.
|
|
1499
|
+
const repairState = await readRepairState(artifactsDir);
|
|
1500
|
+
return {
|
|
1501
|
+
payloads,
|
|
1502
|
+
findingEnumeration,
|
|
1503
|
+
root,
|
|
1504
|
+
waivedCounterexampleIds: waivedJudgeAcceptedIds(repairState, payloads.get("judge_report"), payloads.get("counterexample")),
|
|
1505
|
+
};
|
|
1489
1506
|
}
|
|
1490
1507
|
/**
|
|
1491
1508
|
* Read every contract-pipeline payload FRESH for the shared cross-gates.
|
|
@@ -1922,21 +1939,56 @@ const degenerateSeamReconciliationGate = async (ctx) => {
|
|
|
1922
1939
|
});
|
|
1923
1940
|
return { via: "rederive" };
|
|
1924
1941
|
};
|
|
1942
|
+
/** Render the cycle section for the LLM finalization step: every declared-graph
|
|
1943
|
+
* cycle, its members, and the exact artifact tokens forming each edge. */
|
|
1944
|
+
function renderTokenCycleSection(cycles) {
|
|
1945
|
+
const parts = cycles.map((cycle, i) => {
|
|
1946
|
+
const edges = cycle.edges
|
|
1947
|
+
.map((e) => `- \`${e.consumer}\` depends on \`${e.producer}\` via \`artifact:${e.artifact}\` ` +
|
|
1948
|
+
`(${e.consumer} consumes it; ${e.producer} produces it)`)
|
|
1949
|
+
.join("\n");
|
|
1950
|
+
return `### Cycle ${i + 1}: [${cycle.members.join(", ")}]\n\n${edges}`;
|
|
1951
|
+
});
|
|
1952
|
+
return `## Cyclic Artifact-Token Dependencies — Resolve These In Your Output
|
|
1953
|
+
|
|
1954
|
+
The drafted contracts declare a CYCLIC artifact-token flow. Implementation ordering derives from
|
|
1955
|
+
producer/consumer \`artifact:<name>\` tokens ALONE, so the declared flow must be acyclic — a cycle
|
|
1956
|
+
cannot be phased, and the finalized contracts are REJECTED at validation while one remains.
|
|
1957
|
+
|
|
1958
|
+
${parts.join("\n\n")}
|
|
1959
|
+
|
|
1960
|
+
Rewrite the finalized \`inputs\`/\`outputs\` so one direction owns each flow: move an artifact token
|
|
1961
|
+
to the module that genuinely produces it, split a shared primitive into an earlier module's output,
|
|
1962
|
+
or drop a token that does not describe a real data handoff. Keep the module SET unchanged — do not
|
|
1963
|
+
add, drop, rename, or merge modules.`;
|
|
1964
|
+
}
|
|
1925
1965
|
/**
|
|
1926
1966
|
* Deterministic contract_finalization (all module counts). Finalization is a
|
|
1927
|
-
* mechanical merge, not fresh authoring: carry each drafted module contract
|
|
1928
|
-
* verbatim (
|
|
1967
|
+
* mechanical merge, not fresh authoring: carry each drafted module contract's
|
|
1968
|
+
* interface fields verbatim (dropping neighbor_needs — ordering derives from
|
|
1969
|
+
* the artifact-token graph alone, open-bugs.md:106) and attach
|
|
1929
1970
|
* the agreed_interface of every seam that touches the module as a
|
|
1930
1971
|
* seam_adjustment. The judgment already happened at seam_reconciliation.
|
|
1931
1972
|
* Attaching each agreed interface verbatim guarantees the INV-CO-12
|
|
1932
1973
|
* reconciliation-derivation gate passes. A downstream gate that still finds the
|
|
1933
1974
|
* merge inadequate re-emits contract_finalization as an LLM step — the only path
|
|
1934
|
-
* that still needs judgment.
|
|
1975
|
+
* that still needs judgment. A CYCLIC declared token graph takes that LLM path
|
|
1976
|
+
* up front: the mechanical merge would carry the cycle verbatim into an
|
|
1977
|
+
* artifact validation refuses, so the gate emits the finalization step with the
|
|
1978
|
+
* cycle named instead of deriving.
|
|
1935
1979
|
*/
|
|
1936
1980
|
const contractFinalizationDerivationGate = async (ctx) => {
|
|
1937
1981
|
if (ctx.nextPhase !== "contract_finalization")
|
|
1938
1982
|
return null;
|
|
1939
1983
|
const drafted = envelopePayload(await readContractArtifact(ctx.artifactsDir, "module_contracts"));
|
|
1984
|
+
const cycles = detectContractTokenCycles(drafted);
|
|
1985
|
+
if (cycles.length > 0) {
|
|
1986
|
+
return {
|
|
1987
|
+
via: "phase",
|
|
1988
|
+
phase: "contract_finalization",
|
|
1989
|
+
extraSection: renderTokenCycleSection(cycles),
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1940
1992
|
const seamReport = envelopePayload(await readContractArtifact(ctx.artifactsDir, "seam_reconciliation_report"));
|
|
1941
1993
|
await writeDerivedContractArtifact(ctx.artifactsDir, "finalized_module_contracts", deriveFinalizedModuleContracts(drafted, seamReport));
|
|
1942
1994
|
return { via: "rederive" };
|
|
@@ -1988,19 +2040,44 @@ const judgeRepairGate = async (ctx) => {
|
|
|
1988
2040
|
note: `Judge↔repair loop escalated (${gate.reason}): ${gate.note}`,
|
|
1989
2041
|
category: "trap",
|
|
1990
2042
|
}, "remediate-code");
|
|
2043
|
+
const waiversPath = counterexampleWaiversPath(ctx.artifactsDir);
|
|
2044
|
+
const waiverIssuesSection = gate.waiverIssues && gate.waiverIssues.length > 0
|
|
2045
|
+
? `\n\n## Waiver file refused — fix these first\n\n${gate.waiverIssues
|
|
2046
|
+
.map((issue) => `- ${issue}`)
|
|
2047
|
+
.join("\n")}`
|
|
2048
|
+
: "";
|
|
2049
|
+
const heading = gate.reason === "invalid_waivers"
|
|
2050
|
+
? "# The Counterexample Waiver File Was Refused"
|
|
2051
|
+
: "# Judge↔Repair Loop Did Not Converge";
|
|
1991
2052
|
return {
|
|
1992
2053
|
via: "blocked",
|
|
1993
|
-
prompt:
|
|
2054
|
+
prompt: `${heading}
|
|
1994
2055
|
|
|
1995
|
-
${gate.note}
|
|
2056
|
+
${gate.note}${waiverIssuesSection}
|
|
1996
2057
|
|
|
1997
|
-
## Outstanding accepted counterexamples
|
|
2058
|
+
## Outstanding accepted counterexamples (unwaived)
|
|
1998
2059
|
|
|
1999
2060
|
${gate.outstanding.length > 0
|
|
2000
2061
|
? gate.outstanding.map((id) => `- ${id}`).join("\n")
|
|
2001
2062
|
: "_(none newly accepted this round)_"}
|
|
2002
2063
|
|
|
2003
|
-
|
|
2064
|
+
## Record an owner waiver (the recorded resolution verb)
|
|
2065
|
+
|
|
2066
|
+
To accept an outstanding counterexample as a KNOWN LIMITATION of this run, write the operator's decision to:
|
|
2067
|
+
|
|
2068
|
+
\`${waiversPath}\`
|
|
2069
|
+
|
|
2070
|
+
\`\`\`json
|
|
2071
|
+
{
|
|
2072
|
+
"waivers": [
|
|
2073
|
+
{ "ce_id": "<id from the list above>", "rationale": "<why this is acceptable>", "waived_by": "<who decided>" }
|
|
2074
|
+
]
|
|
2075
|
+
}
|
|
2076
|
+
\`\`\`
|
|
2077
|
+
|
|
2078
|
+
The next next-step validates the file, records each waiver in repair-state.json (attributable, content-fingerprint-keyed), consumes the file, and proceeds once every outstanding counterexample is repaired or waived. Record a waiver ONLY for a decision the operator actually made — the record names its decider.
|
|
2079
|
+
|
|
2080
|
+
Read the judge_report and counterexample artifacts, decide with the user how to resolve each outstanding counterexample (revise the contract design and re-run, or record a waiver as above), then re-run next-step.`,
|
|
2004
2081
|
stopCondition: "Stop — the contract pipeline is blocked on a non-converging judge↔repair loop pending a user decision.",
|
|
2005
2082
|
};
|
|
2006
2083
|
}
|
|
@@ -2585,7 +2662,7 @@ const parallelModuleWaveGate = async (ctx) => {
|
|
|
2585
2662
|
* dependency-ordered foundations→consumers phasing, instead of rejecting an
|
|
2586
2663
|
* arbitrary N-goal change as "over-scoped" and forcing the host to re-scope by
|
|
2587
2664
|
* hand at intake. The cut is derived from the finalized module contracts'
|
|
2588
|
-
*
|
|
2665
|
+
* producer/consumer artifact-token edges and PERSISTED as `phase_cut.json`, so the cut
|
|
2589
2666
|
* the critic sees and the cut the implementation-DAG promotion enforces are one
|
|
2590
2667
|
* source. Only injected when there is a genuine multi-phase cut to communicate.
|
|
2591
2668
|
*/
|
|
@@ -2927,20 +3004,43 @@ export async function evaluatePromotedPlanWriteScope(artifactsDir, root) {
|
|
|
2927
3004
|
for (const block of Array.isArray(plan?.blocks) ? plan.blocks : []) {
|
|
2928
3005
|
const blockId = typeof block.block_id === "string" ? block.block_id : "(unnamed block)";
|
|
2929
3006
|
const touched = Array.isArray(block.touched_files) ? block.touched_files : [];
|
|
2930
|
-
|
|
2931
|
-
if (typeof path !== "string")
|
|
2932
|
-
continue;
|
|
2933
|
-
const key = normalizeRepoPath(path);
|
|
2934
|
-
const parent = key.includes("/") ? key.slice(0, key.lastIndexOf("/")) : "";
|
|
2935
|
-
if (corpus.files.has(key) || parent === "" || corpus.directories.has(parent)) {
|
|
2936
|
-
continue;
|
|
2937
|
-
}
|
|
2938
|
-
violations.push(`Block "${blockId}" declares the write-scope path "${path}", whose directory does ` +
|
|
2939
|
-
`not exist in the tracked tree.`);
|
|
2940
|
-
}
|
|
3007
|
+
violations.push(...writeScopeCorpusViolations(corpus, touched.filter((path) => typeof path === "string"), `Block "${blockId}"`));
|
|
2941
3008
|
}
|
|
2942
3009
|
return violations.length > 0 ? { violations } : null;
|
|
2943
3010
|
}
|
|
3011
|
+
/**
|
|
3012
|
+
* The ONE tracked-tree membership rule for a declared write-scope path: legal
|
|
3013
|
+
* when the file is tracked, sits at the repo root, or its parent directory
|
|
3014
|
+
* exists in the tracked tree (a block legitimately creates NEW files in
|
|
3015
|
+
* existing directories). Shared by the promotion gate above and the
|
|
3016
|
+
* clarification scope-delta validation, so the two cannot drift.
|
|
3017
|
+
*/
|
|
3018
|
+
function writeScopeCorpusViolations(corpus, paths, label) {
|
|
3019
|
+
const violations = [];
|
|
3020
|
+
for (const path of paths) {
|
|
3021
|
+
const key = normalizeRepoPath(path);
|
|
3022
|
+
const parent = key.includes("/") ? key.slice(0, key.lastIndexOf("/")) : "";
|
|
3023
|
+
if (corpus.files.has(key) || parent === "" || corpus.directories.has(parent)) {
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
violations.push(`${label} declares the write-scope path "${path}", whose directory does ` +
|
|
3027
|
+
`not exist in the tracked tree.`);
|
|
3028
|
+
}
|
|
3029
|
+
return violations;
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Tracked-tree parity for a POST-promotion write-scope widening
|
|
3033
|
+
* (open-bugs.md:110): a clarification scope delta must clear the same rule the
|
|
3034
|
+
* promotion gate enforced, or the delta lane becomes a bypass of it. Returns
|
|
3035
|
+
* violation lines; [] on an unreadable tree (fail-open, exactly as the
|
|
3036
|
+
* promotion gate degrades).
|
|
3037
|
+
*/
|
|
3038
|
+
export async function checkWriteScopePathsAgainstTrackedTree(root, paths, label) {
|
|
3039
|
+
const corpus = await readTrackedWriteScopeCorpus(root);
|
|
3040
|
+
if (!corpus)
|
|
3041
|
+
return [];
|
|
3042
|
+
return writeScopeCorpusViolations(corpus, paths, label);
|
|
3043
|
+
}
|
|
2944
3044
|
export function normalizeBlockTargetedCommands(commands, blockId) {
|
|
2945
3045
|
const partitioned = partitionCommandsByDeclaredShape(commands, (kind, raw) => kind === "empty"
|
|
2946
3046
|
? `Block "${blockId}" declares an empty targeted_commands entry.`
|
|
@@ -3078,6 +3178,36 @@ export async function promoteImplementationDagToExtractedPlan(artifactsDir, root
|
|
|
3078
3178
|
}
|
|
3079
3179
|
const lastOrdinal = Math.max(0, ...slugToOrdinal.values());
|
|
3080
3180
|
const hasMultiPhase = phaseCut ? phaseCut.phases.length > 1 : false;
|
|
3181
|
+
// Approved-contract attachment (open-bugs.md:474): resolve each node's
|
|
3182
|
+
// obligation-id slugs against the finalized module contracts, so every
|
|
3183
|
+
// promoted block carries VERBATIM the contract(s) it implements and the
|
|
3184
|
+
// dispatch prompt can bind the worker to the approved interface — the
|
|
3185
|
+
// workflow must never depend on the DAG author restating declared values.
|
|
3186
|
+
const finalizedForBlocks = envelopePayload(await readContractArtifact(artifactsDir, "finalized_module_contracts"));
|
|
3187
|
+
const contractByModuleName = new Map();
|
|
3188
|
+
const contractSlugToName = new Map();
|
|
3189
|
+
for (const mod of finalizedForBlocks?.module_contracts ?? []) {
|
|
3190
|
+
if (isRecord(mod) && typeof mod.name === "string" && mod.name.length > 0) {
|
|
3191
|
+
contractByModuleName.set(mod.name, mod);
|
|
3192
|
+
contractSlugToName.set(moduleSlug(mod.name), mod.name);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
const contractSlugsByLength = [...contractSlugToName.keys()].sort((a, b) => b.length - a.length);
|
|
3196
|
+
const moduleContractsForNode = (node) => {
|
|
3197
|
+
const names = new Set();
|
|
3198
|
+
for (const obligationId of [
|
|
3199
|
+
...(node.satisfies_obligations ?? []),
|
|
3200
|
+
...(node.verification_obligation_ids ?? []),
|
|
3201
|
+
]) {
|
|
3202
|
+
const slug = moduleSlugForObligationId(obligationId, contractSlugsByLength);
|
|
3203
|
+
const name = slug === null ? undefined : contractSlugToName.get(slug);
|
|
3204
|
+
if (name !== undefined)
|
|
3205
|
+
names.add(name);
|
|
3206
|
+
}
|
|
3207
|
+
return [...names]
|
|
3208
|
+
.sort((left, right) => compareCodeUnits(left, right))
|
|
3209
|
+
.map((name) => ({ module: name, contract: contractByModuleName.get(name) }));
|
|
3210
|
+
};
|
|
3081
3211
|
// Root-cause fix for scope-less nodes: the DAG's write scope
|
|
3082
3212
|
// (`output_files`/`files_likely_touched`) is host-authored and a coarse
|
|
3083
3213
|
// "Remediate <module>" decomposition can leave it EMPTY, which promotes a
|
|
@@ -3271,6 +3401,7 @@ export async function promoteImplementationDagToExtractedPlan(artifactsDir, root
|
|
|
3271
3401
|
...(node.verification_obligation_ids ?? []),
|
|
3272
3402
|
], slugToOrdinal, lastOrdinal)
|
|
3273
3403
|
: undefined;
|
|
3404
|
+
const blockModuleContracts = moduleContractsForNode(node);
|
|
3274
3405
|
return {
|
|
3275
3406
|
block_id: toBlockId(nodeId),
|
|
3276
3407
|
items: canonicalItemsByNodeId.get(nodeId) ?? [nodeId],
|
|
@@ -3283,6 +3414,9 @@ export async function promoteImplementationDagToExtractedPlan(artifactsDir, root
|
|
|
3283
3414
|
touched_files: touchedFiles,
|
|
3284
3415
|
...(phaseOrdinal !== undefined ? { phase_ordinal: phaseOrdinal } : {}),
|
|
3285
3416
|
...(targetedCommands.length > 0 ? { targeted_commands: targetedCommands } : {}),
|
|
3417
|
+
...(blockModuleContracts.length > 0
|
|
3418
|
+
? { module_contracts: blockModuleContracts }
|
|
3419
|
+
: {}),
|
|
3286
3420
|
};
|
|
3287
3421
|
});
|
|
3288
3422
|
const extractedPlan = {
|