Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ peer content or as a production security boundary.
install Ajv and native `better-sqlite3`.
- **Reference runtime:** authenticated JSON-RPC + SQLite coordinator for local,
trusted-process experiments.
- **Validation:** 384 tests, plus 55 schema cases and 7 transition cases;
- **Validation:** 385 tests, plus 55 schema cases and 7 transition cases;
documentation lint passes. These are separate counts, not one combined total.
- **Default:** proactive coordination remains off unless a maintainer explicitly
opts into the bounded experimental profile.
Expand Down
13 changes: 13 additions & 0 deletions docs/09-reviews/2026-09-02-m5-2-real-effects-live-attempt.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,16 @@ Do not repeat the run blindly. First retain a public, SQLite-derived partial
manifest that exposes the exact phase, durable dispatch/action counts, bounded
reconciliation reason, and cleanup without raw prompts, receipts, paths, or
session identifiers. Then correct the observed boundary and run once.

## Follow-up implementation

The requested failure manifest is now implemented for the next run. It is
captured from the live coordinator database before cleanup and then passed
through an exact public projector. The output is restricted to a schema
version, source marker, derived stage, nine bounded counts, and—only for an
ambiguous recovery—one fixed-enum reason code. Any extra key, unknown reason,
string, impossible count, or stage/count mismatch suppresses the projection.

The historical aggregates above remain operator observations from attempt 11;
they are not retroactively upgraded into machine evidence. The next live run is
the first one that can produce the new machine-verifiable partial projection.
23 changes: 16 additions & 7 deletions docs/09-reviews/2026-09-02-mainline-checkpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ critical path.

## Repository state

- branch: `main`, clean and synchronized with `origin/main`;
- commit: `0c7165ead39499497827737c17e94e95f0f3286b`;
- branch base: `main`, synchronized with `origin/main` before this diagnostic
slice;
- base commit: `e8b8ce62dd177dde09557b7311939f871b3e3954`;
- latest product work: real-effects integration #133 and product-proof update
#134;
- public discovery: one star, zero forks, and no external operator report;
Expand All @@ -21,9 +22,9 @@ critical path.

The following passed on the exact checkpoint commit:

- 384/384 unit and subtests;
- 385/385 unit and subtests;
- 55 schema cases and 7 transition cases;
- 112 Markdown files with zero lint issues;
- 114 Markdown files with zero lint issues;
- focused autonomous real-effects scenario: 9/9;
- one-command attention-router demo: `state=passed`;
- modeled manual lower bound: 9 user actions;
Expand Down Expand Up @@ -57,11 +58,19 @@ and zero remaining journals. The exact empty caller-owned artifacts directory
was then removed. This is a failed partial attempt, not a product pass. See the
[attempt record](2026-09-02-m5-2-real-effects-live-attempt.md).

The follow-up diagnostic slice now captures a strict pre-cleanup SQLite
projection on scenario failure. It exposes only a fixed stage enum, nine
bounded aggregate counts, and an allowlisted ambiguous-reconciliation reason;
raw prompts, receipts, paths, thread IDs, and journal contents are not included.
Schema drift, excess counts, inconsistent stages, and unsafe reason strings are
rejected by the CLI projector. Failure-stage, tamper, privacy, and exact cleanup
tests pass.

## Ordered next gates

1. Explain the reviewer reconciliation failure with a bounded partial-stage
manifest, correct the observed blocker, and retain one successful traversal
of the merged real-effects Codex path.
1. Run the merged real-effects Codex path once with the new partial-stage
manifest, use its exact bounded reconciliation reason to correct the
observed blocker, and retain one successful traversal.
2. One same-condition manual/ThreadMesh comparison with action, elapsed, usage,
interruption, duplicate-delivery, and cleanup measurements.
3. The complete M5.3 matrix: relevant 3/3, irrelevant, stale/unverified,
Expand Down
7 changes: 4 additions & 3 deletions docs/10-planning/mainline-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ Execute in this order:

1. Treat the real behavioral chain as passed and retain its exact evidence; do
not rerun it merely to polish counts or prose.
2. Diagnose the fresh reviewer admitted-turn reconciliation failure from
bounded product/coordinator evidence, retain a public partial-stage manifest,
and rerun the integrated bounded Git and child-verifier path once. Keep the
2. The public SQLite-derived partial-stage manifest is implemented and rejects
raw or inconsistent failure data. Rerun the integrated bounded Git and
child-verifier path once, use the emitted reconciliation reason to fix the
exact reviewer boundary, then retain one successful traversal. Keep the
process-scoped proxy, certificate validation, product version, and exact
cleanup boundary unchanged.
3. Retain the measured manual/live baseline. The executable demo already
Expand Down
11 changes: 9 additions & 2 deletions docs/10-planning/project-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ setup attempts are retained.
| Research and problem framing | Codex deep dive, community signals, ecosystem comparison, ADRs | Established |
| Community adoption | One GitHub star; zero forks and zero external issue comments or independent setup results as of 2026-09-02 | Positive discovery hint only; adoption unvalidated |
| Active product outcome | One-command lifecycle-event and dependency-handoff loop with inspector, manual-action accounting, active-receiver checkpoint negative, and 76-second walkthrough | Real Codex A/R/same-A/V/dependent behavioral chain completed after one kickoff; real Git/verifier path merged; combined network-valid rerun pending |
| Protocol draft | 14 JSON Schemas; 55 schema cases; 7 transition cases; 384 tests | Executable draft; counts are reported separately |
| Protocol draft | 14 JSON Schemas; 55 schema cases; 7 transition cases; 385 tests | Executable draft; counts are reported separately |
| Minimal adapter SDK | `@fyaic/threadmesh` `0.1.0-alpha.0`; six bounded client methods, per-turn proactive bridge, about 20 kB tarball, packed-consumer execution | Real Pi clean-consumer pass; not published to npm |
| Local binding | Schema-validated JSON-RPC, transport-derived principals, typed errors | Executable local reference |
| Local persistence | SQLite v10 registry, lifecycle state, append-only Git evidence, and durable per-dispatch event-pump selection/publication checkpoints | Experimental; global cross-dispatch pump chain absent |
Expand Down Expand Up @@ -312,7 +312,7 @@ Git effects and verifier custody were simulated; the merged integration is not
silently combined with that earlier run.

The 2026-09-02 mainline check retained a clean deterministic regression:
384/384 unit and subtests, 55 schema cases, 7 transition cases, 112 Markdown
385/385 unit and subtests, 55 schema cases, 7 transition cases, 114 Markdown
files with zero lint issues, and a passing one-command demo. Initial direct
connectivity failed after system DNS returned unexpected non-provider
addresses. A process-scoped local proxy then restored certificate-verified
Expand All @@ -324,6 +324,13 @@ journals, and removed the caller-owned empty artifacts directory. See the
[checkpoint](../09-reviews/2026-09-02-mainline-checkpoint.md) and
[attempt record](../09-reviews/2026-09-02-m5-2-real-effects-live-attempt.md).

The next-run diagnostic is now implemented: scenario failures snapshot a
strict SQLite-derived stage and aggregate counts before cleanup, while the CLI
publishes only an exact allowlisted projection. Ambiguous reconciliation may
include one fixed-enum reason code; raw prompts, receipts, paths, session IDs,
and journal data are excluded. The next real run is therefore diagnostic rather
than blind: its reason code selects the reviewer-boundary correction.

Community reports are now grouped into three public product backlogs rather
than one issue per upstream symptom: correlated handoff state
[#135](https://github.com/fyaic/threadmesh/issues/135), durable
Expand Down
4 changes: 4 additions & 0 deletions scripts/run-m5-2-event-pump-gate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";

import {
projectM52EventPumpFailureCleanup,
projectM52EventPumpFailureProgress,
runM52EventPumpCodexGate,
runM52OperatorSuppliedCodexEventPumpGate,
} from
Expand Down Expand Up @@ -129,12 +130,15 @@ try {
const preflight = preflightCodes.has(error?.code);
const cleanup = error?.cleanup === undefined
? null : projectM52EventPumpFailureCleanup(error.cleanup);
const partialProgress = error?.partialProgress === undefined
? null : projectM52EventPumpFailureProgress(error.partialProgress);
console.error(JSON.stringify({
state: preflight ? "not-run" : "failed",
code: shutdownSignal === null
? (error?.code ?? "threadmesh_m52_event_pump_runner_failed")
: `threadmesh_m52_event_pump_runner_${shutdownSignal.toLowerCase()}`,
liveAck: LIVE_ACK,
...(partialProgress === null ? {} : { partialProgress }),
...(cleanup === null ? {} : { cleanup }),
}, null, 2));
process.exitCode = preflight ? 3 : 1;
Expand Down
75 changes: 75 additions & 0 deletions src/validation/coordinator-driven-no-plan-scenario.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,76 @@ function scenarioError(code) {
return error;
}

const FAILURE_PROGRESS_COUNT_QUERIES = Object.freeze({
tasks: "SELECT COUNT(*) AS count FROM tasks",
dispatches: "SELECT COUNT(*) AS count FROM event_pump_dispatches",
turnIntents: "SELECT COUNT(*) AS count FROM turn_execution_intents",
toolActions: "SELECT COUNT(*) AS count FROM turn_tool_actions",
lifecyclePublications: "SELECT COUNT(*) AS count FROM lifecycle_action_publications",
gitEvidenceRecords: "SELECT COUNT(*) AS count FROM git_evidence_records",
dependencyFinalizations:
"SELECT COUNT(*) AS count FROM git_evidence_dependency_finalizations",
dependencySatisfactions: "SELECT COUNT(*) AS count FROM dependency_satisfactions",
cursorCommits: "SELECT COUNT(*) AS count FROM attention_cursor_commits",
});

export const COORDINATOR_FAILURE_RECONCILIATION_REASONS = Object.freeze([
"codex-native-turn-identity-mismatch",
"codex-native-turn-thread-not-idle",
"codex-native-turn-baseline-truncated",
"codex-native-turn-baseline-mutated",
"codex-native-turn-no-observable-delta",
"codex-native-turn-multiple-new-turns",
"codex-native-turn-client-id-mismatch",
"codex-native-turn-client-id-missing",
"codex-native-turn-completed-observation-only",
"codex-native-turn-still-in-progress",
"codex-native-turn-started-id-mismatch",
]);

export function deriveCoordinatorDrivenFailureStage(counts) {
const durableStages = Math.min(
counts.lifecyclePublications ?? 0,
counts.gitEvidenceRecords ?? 0,
);
if ((counts.dependencyFinalizations ?? 0) > 0 ||
(counts.dependencySatisfactions ?? 0) > 0) {
return "dependency-finalized";
}
if (durableStages >= 4) return "verification-published";
if (durableStages >= 3) return "fix-published";
if (durableStages >= 2) return "review-published";
if (durableStages >= 1 && (counts.turnIntents ?? 0) >= 3) {
return "reviewer-admitted-turn-partial";
}
if (durableStages >= 1) return "implementation-published";
if ((counts.turnIntents ?? 0) > 0) return "implementation-turn-partial";
if ((counts.tasks ?? 0) >= 5) return "roles-registered";
if ((counts.tasks ?? 0) > 0) return "roles-registering";
return "coordinator-ready";
}

export function captureCoordinatorDrivenFailureProgress(database, error) {
const counts = Object.fromEntries(Object.entries(FAILURE_PROGRESS_COUNT_QUERIES)
.map(([key, query]) => [key, database.prepare(query).get().count]));
const recovery = error?.recovery;
const reasonCode = recovery?.reasonCode;
const reconciliation = recovery?.state === "ambiguous" &&
COORDINATOR_FAILURE_RECONCILIATION_REASONS.includes(reasonCode)
? {
state: "ambiguous",
reasonCode,
}
: null;
return Object.freeze({
schemaVersion: 1,
source: "sqlite-pre-cleanup",
stage: deriveCoordinatorDrivenFailureStage(counts),
counts: Object.freeze(counts),
reconciliation: reconciliation === null ? null : Object.freeze(reconciliation),
});
}

function throwIfShutdownRequested(signal) {
if (signal?.aborted === true) {
throw scenarioError("threadmesh_coordinator_driven_shutdown_requested");
Expand Down Expand Up @@ -872,6 +942,7 @@ export async function runCoordinatorDrivenNoPlanScenario({
let adapter;
let result;
let failure;
let failureProgress = null;
let evidenceRevision = 0;
let evidenceHead = null;
let verification = null;
Expand Down Expand Up @@ -2179,6 +2250,9 @@ export async function runCoordinatorDrivenNoPlanScenario({
},
};
} catch (error) {
try {
failureProgress = captureCoordinatorDrivenFailureProgress(coordinator.db, error);
} catch {}
if (injectFinalizationFailure || injectPreverifiedTamper) {
let edgeStatus = "unavailable";
let taskState = "unavailable";
Expand Down Expand Up @@ -2338,6 +2412,7 @@ export async function runCoordinatorDrivenNoPlanScenario({
};
if (failure) {
failure.cleanup = cleanup;
if (failureProgress !== null) failure.partialProgress = failureProgress;
if (failure.failureEvidence) failure.failureEvidence.cleanupComplete = cleanup.complete;
throw failure;
}
Expand Down
66 changes: 64 additions & 2 deletions src/validation/m5-2-event-pump-codex-gate.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import path from "node:path";

import { canonicalJson, sha256Digest } from "../canonical-json.mjs";
import { runCoordinatorDrivenNoPlanScenario } from
"./coordinator-driven-no-plan-scenario.mjs";
import {
COORDINATOR_FAILURE_RECONCILIATION_REASONS,
deriveCoordinatorDrivenFailureStage,
runCoordinatorDrivenNoPlanScenario,
} from "./coordinator-driven-no-plan-scenario.mjs";
import {
CodexLiveAgentRuntime,
isCodexLiveAgentRuntime,
Expand Down Expand Up @@ -161,6 +164,65 @@ export function projectM52EventPumpFailureCleanup(value) {
});
}

const FAILURE_PROGRESS_COUNT_LIMITS = Object.freeze({
tasks: 5,
dispatches: 5,
turnIntents: 16,
toolActions: 32,
lifecyclePublications: 4,
gitEvidenceRecords: 4,
dependencyFinalizations: 1,
dependencySatisfactions: 1,
cursorCommits: 5,
});
export function projectM52EventPumpFailureProgress(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const expectedKeys = [
"schemaVersion", "source", "stage", "counts", "reconciliation",
];
if (canonicalJson(Object.keys(value).sort()) !== canonicalJson(expectedKeys.sort()) ||
value.schemaVersion !== 1 || value.source !== "sqlite-pre-cleanup" ||
!value.counts || typeof value.counts !== "object" ||
Array.isArray(value.counts)) {
return null;
}
const countKeys = Object.keys(FAILURE_PROGRESS_COUNT_LIMITS);
if (canonicalJson(Object.keys(value.counts).sort()) !==
canonicalJson([...countKeys].sort())) {
return null;
}
const counts = {};
for (const [key, limit] of Object.entries(FAILURE_PROGRESS_COUNT_LIMITS)) {
const count = value.counts[key];
if (!Number.isSafeInteger(count) || count < 0 || count > limit) return null;
counts[key] = count;
}
if (value.stage !== deriveCoordinatorDrivenFailureStage(counts)) return null;
let reconciliation = null;
if (value.reconciliation !== null) {
const source = value.reconciliation;
const reconciliationKeys = ["state", "reasonCode"];
if (!source || typeof source !== "object" || Array.isArray(source) ||
canonicalJson(Object.keys(source).sort()) !==
canonicalJson(reconciliationKeys.sort()) ||
source.state !== "ambiguous" ||
!COORDINATOR_FAILURE_RECONCILIATION_REASONS.includes(source.reasonCode)) {
return null;
}
reconciliation = Object.freeze({
state: source.state,
reasonCode: source.reasonCode,
});
}
return Object.freeze({
schemaVersion: 1,
source: "sqlite-pre-cleanup",
stage: value.stage,
counts: Object.freeze(counts),
reconciliation,
});
}

function exactObject(value, keys, label) {
if (!value || typeof value !== "object" || Array.isArray(value) ||
canonicalJson(Object.keys(value).sort()) !== canonicalJson([...keys].sort())) {
Expand Down
34 changes: 34 additions & 0 deletions test/coordinator-driven-no-plan-scenario.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,23 @@ test("failed trusted finalization starts no dependent business turn", async (t)
],
cleanupComplete: true,
});
assert.deepEqual(error.partialProgress, {
schemaVersion: 1,
source: "sqlite-pre-cleanup",
stage: "fix-published",
counts: {
tasks: 5,
dispatches: 5,
turnIntents: 8,
toolActions: 11,
lifecyclePublications: 4,
gitEvidenceRecords: 3,
dependencyFinalizations: 0,
dependencySatisfactions: 0,
cursorCommits: 3,
},
reconciliation: null,
});
assert.equal(error.cleanup?.complete, true);
return true;
},
Expand Down Expand Up @@ -528,4 +545,21 @@ test("a bounded shutdown after role bootstrap cleans every created role and run
assert.equal(failure.cleanup?.remainingJournalCount, 0);
assert.equal(failure.cleanup?.coordinatorRemoved, true);
assert.equal(failure.cleanup?.runRootRemoved, true);
assert.deepEqual(failure.partialProgress, {
schemaVersion: 1,
source: "sqlite-pre-cleanup",
stage: "coordinator-ready",
counts: {
tasks: 0,
dispatches: 0,
turnIntents: 0,
toolActions: 0,
lifecyclePublications: 0,
gitEvidenceRecords: 0,
dependencyFinalizations: 0,
dependencySatisfactions: 0,
cursorCommits: 0,
},
reconciliation: null,
});
});
Loading
Loading