Distinguish declared goals, open sessions and overlap risk in coordination inventory (#3028)

* feat: add read-only coordination inventory and overlap evaluation

* test: make coordination process fixtures platform explicit

* test: report bounded Stop wrapper failure diagnostics

* test: clean up failed memory MCP sessions deterministically

* fix: update js-yaml to patched 4.3.2

* feat(coordination): distinguish declared goals from open sessions
This commit is contained in:
Affaan Mustafa
2026-09-10 14:11:51 +03:00
committed by GitHub
parent 052ddcb988
commit c7d62c0c6a
13 changed files with 1405 additions and 105 deletions
+150
View File
@@ -0,0 +1,150 @@
# Read-only coordination inventory
One local JSON report joins declared task IDs and parent IDs, heartbeat age,
optional process metadata, OS RAM, declared resource leases and path/import
warnings. It reuses ECC's orchestration status parser and agent-proximity
scoring. It does not start a server or send messages.
From the repository root, with Node 18 or newer and no dependency install:
```sh
node scripts/coordination-inventory.js --manifest examples/coordination-inventory/manifest.json --now 2026-09-08T06:30:00.000Z
node scripts/coordination-inventory.js --manifest examples/coordination-inventory/goals.json --now 2026-09-08T06:30:00.000Z
node scripts/coordination-inventory.js --coordination /path/to/coordination --live
node examples/coordination-inventory/evaluate.js
node --test tests/scripts/coordination-inventory.test.js
node --test tests/scripts/coordination-goals.test.js
node examples/coordination-inventory/benchmark.js
```
The first command uses a **synthetic** fixed-time fixture. It demonstrates a
parent/child pair with an import dependency, a stale heartbeat and conflicting
browser ownership declarations. The file grants no browser access.
`--coordination` reads direct child directories with `STATUS.md` or legacy
`status.md`. Structured `- State:` and UTC `- Updated:` fields use the existing
orchestration parser. Freeform status has unknown state/heartbeat; modification
time is reported separately. Symlink task directories and final status files
are not followed. Unreadable child directories make discovery partial; an
unavailable root is explicit, not an empty successful inventory.
`--live` samples OS total/free bytes and, for explicitly declared positive PIDs,
`ps` PID, parent PID, RSS, elapsed time and state flags on macOS/Linux. It uses a
two-second timeout without shell expansion. It never reads argv, environment,
transcripts or process executable names. Unsupported platforms and inaccessible
process telemetry are explicit. Free memory is not macOS memory pressure or a
safe allocation budget. No PID supplied means no process scan. PID identity and
PID reuse are not verified. An old heartbeat means inspection is useful; it
cannot prove that a process is stuck.
## Manifest contract
See `manifest.json`. Version 1 accepts repositories with IDs and source snippet
maps, tasks with IDs, optional parent IDs, repository IDs, repo-relative declared
paths, optional PIDs/status/UTC heartbeat times, and leases with resource, owner
and UTC expiry. Parent IDs can reference an external orchestrator. Repository
IDs scope warnings across separate checkouts; use the same logical repo ID for
workers editing the same repository. Duplicate task IDs are rejected, including
when combining a manifest with discovered status files.
Bounds: 1 MiB JSON, 64 tasks/repositories, 128 paths per task, 128 snippets per
repository, 1 KiB per snippet and 32 KiB snippets total, 128 leases. Snippets can
be just import statements plus empty entries for known targets. They are parsed
as text, never executed or emitted in the report. An aggregate comparison budget
rejects excessive pair/graph work; split large inputs into smaller inventories.
Only provide nonsensitive metadata in task IDs, status fields and paths.
Every result identifies coverage. Paths are declared intentions, not a scan of
all current edits. Only supplied relative JS/TS imports resolve. Missing paths
or source snippets mean incomplete visibility. Existing control-pane default
working sets use committed `base...HEAD` differences and can miss dirty and
untracked work; this example does not claim to fix that separate adapter.
Leases are owner declarations, not enforced locks. Expired entries are visible
but excluded from simultaneous-owner conflicts. An unexpired entry does not
prove the owner is alive or authorized. The caller supplies those declarations;
the inventory never acquires, renews or releases leases. No lease records means
ownership is unknown. No pause, steer, kill, settings change or allocation occurs.
## Declared goals and sessions
Optional `goals` and `sessions` collections add observations to the v1 manifest.
Each accepts at most 64 records, within the same 1 MiB total input budget. IDs
are unique within each collection. A goal accepts `id`, optional `taskId`,
`kind` (`native` or `unknown`), `status` (`active`, `complete`, `blocked` or
`unknown`), and optional UTC `updatedAt`. A session accepts `id`, optional
`taskId`/`goalId`, `status` (`open`, `closed` or `unknown`) and optional UTC
`updatedAt`. Omitted kind/status defaults to `unknown`; invalid supplied enum
values and scalar collection types are rejected. Supplied non-null links must
reference a supplied task or goal. These are associations, not exclusive owners;
multiple sessions may reference one goal without counting that goal twice.
`goals.json` is synthetic: three open sessions reference one active goal, one
completed goal and one missing goal declaration. At its fixed example time the
report has one `freshActiveNativeGoalDeclarations` and one
`openSessionsWithoutGoalDeclaration`. An open session linked to a completed goal
stays open while the goal stays complete. Neither status overwrites the other.
Every goal/session record has `authority: "declared-only"`. Even `kind: "native"`
is the caller's claim, not a native goal-tool verification. Supply a nonsensitive
observation derived from an authorized tool receipt; do not paste raw tool blobs,
objective text, transcripts or credentials. Unrecognized fields are omitted from
reports. The inventory never reads private thread stores or automatically imports
GOAL-STATE files. The caller retains the receipt and its provenance separately.
`coverage.goals` and `coverage.sessions` distinguish `missing` collections from
`declared-only` collections, including explicitly empty arrays. Neither proves
global absence. `activity` contains declaration counts by status, native-kind
declaration counts, open sessions without goal links and the number of fresh
active native-kind declarations. These count records, not task associations or
verified running processes. No goal is inferred from a terminal, task `status`,
heartbeat, PID, resource lease or status-file modification time.
Freshness uses the existing five-minute observation threshold: exactly five
minutes old is fresh, older is stale, future observations are `clock-skew`, and
missing timestamps are unknown. It does not rewrite declared state, and even a
fresh active declaration does not prove current execution. Goal/session state
never suppresses overlap warnings or expands process probing. Ownership remains
in declared paths and resource leases; no pause, message, steer or permission
grant is triggered by any count or warning.
Existing task, warning, resource and lease outputs are unchanged. The new arrays,
activity summary and coverage keys are additive v1 output; consumers that reject
unknown fields need updating. Older consumers will ignore these declarations.
This remains a source-checkout example; these commands/examples are not claimed
to be shipped in the npm package.
## Evaluation and limitations
Eight authored synthetic pairs compare an exact-path baseline with ECC's
existing overlap/import/tree heuristic, using threshold 0.35. Tree proximity
alone does not trigger a warning. The score is not a calibrated probability.
| Detector | True positive | False positive | True negative | False negative |
| --- | ---: | ---: | ---: | ---: |
| Exact path | 1 | 0 | 4 | 3 |
| Path and import | 2 | 1 | 3 | 2 |
The extra detection is a direct relative import. A commented import produces
one false positive; an alias and a cross-artifact relationship are missed. These
are explicit characterization cases, not a held-out benchmark. Source parsing
is regex-based and incomplete; hashed visual coordinates, semantic/PCA proximity,
predictive proximity and 85% conflict reduction are not validated here.
Next experiment: freeze 20 paired isolated tasks and collect declared intent,
actual changed paths and import edges in shadow mode. Have a human label which
pairs needed coordination before inspecting scores. Report precision, recall,
alerts per pair and p50/p95 overhead against exact-path and isolation-only
baselines. After that, randomize warning display and measure conflict/rework
rate with the same task mix. No automatic pause until warning usefulness and
ownership enforcement are separately established.
The dependency-free `benchmark.js` characterizes the legacy fixture, declared
fixture and 64-goal/64-session limit with five warmup batches and 31 measured
batches of ten inventory builds each. It reports median/p95 batch-average
milliseconds, sample counts, fixed input hashes and the same eight overlap
controls. It excludes process startup and CLI I/O; the declaration-limit workload
is not a worst-case graph benchmark. Compare identical input hashes, Node runtime
and parameters before/after on the same machine. Historical one-shot elapsed
time is not a comparable speedup baseline. No performance improvement or conflict
reduction is asserted from merely adding these observations.
@@ -0,0 +1,58 @@
#!/usr/bin/env node
'use strict';
const { performance } = require('node:perf_hooks');
const { createHash } = require('node:crypto');
const { buildInventory } = require('../../scripts/lib/coordination-inventory');
const legacy = require('./manifest.json');
const declared = require('./goals.json');
const controls = require('./fixtures.json');
const now = '2026-09-08T06:30:00.000Z';
const parameters = { warmupBatches: 5, samples: 31, iterationsPerSample: 10 };
const atLimit = { ...legacy,
goals: Array.from({ length: 64 }, (_, i) => ({ id: `g${i}`, taskId: 'a',
kind: 'native', status: 'active', updatedAt: now })),
sessions: Array.from({ length: 64 }, (_, i) => ({ id: `s${i}`, taskId: 'a',
goalId: `g${i}`, status: 'open', updatedAt: now }))
};
function measure(name, manifest) {
const batch = () => {
for (let i = 0; i < parameters.iterationsPerSample; i += 1) buildInventory(manifest, { now });
};
for (let i = 0; i < parameters.warmupBatches; i += 1) batch();
const samples = Array.from({ length: parameters.samples }, () => {
const start = performance.now(); batch();
return (performance.now() - start) / parameters.iterationsPerSample;
}).sort((a, b) => a - b);
const report = buildInventory(manifest, { now });
const input = JSON.stringify(manifest);
return { name, inputBytes: Buffer.byteLength(input),
inputSha256: createHash('sha256').update(input).digest('hex'),
medianMs: samples[Math.floor(samples.length / 2)],
p95Ms: samples[Math.ceil(samples.length * 0.95) - 1], samplesMs: samples,
warnings: report.warnings, activity: report.activity ?? null };
}
const rows = controls.map(control => {
const [a, b] = control.manifest.tasks;
return { id: control.id, needsReview: control.needsReview,
exactPath: a.repoId === b.repoId && a.paths.some(p => b.paths.includes(p)),
pathAndImport: buildInventory(control.manifest, { now }).warnings.length > 0 };
});
const matrix = detector => rows.reduce((result, row) => {
const key = row.needsReview ? (row[detector] ? 'truePositive' : 'falseNegative')
: (row[detector] ? 'falsePositive' : 'trueNegative');
return { ...result, [key]: result[key] + 1 };
}, { truePositive: 0, falsePositive: 0, trueNegative: 0, falseNegative: 0 });
const report = {
version: 1, mode: 'synthetic-local-characterization', node: process.version,
platform: process.platform, parameters,
workloads: [measure('legacy', legacy), measure('declared', declared), measure('declaration-limit', atLimit)],
overlapControls: { dataset: 'eight-authored-synthetic-pairs-v1', rows,
baseline: matrix('exactPath'), candidate: matrix('pathAndImport') },
limits: ['Batch average buildInventory time excludes process startup and CLI I/O.',
'Declaration-limit uses 64 goals and 64 sessions; it is not a maximum graph-work benchmark.',
'Timing is machine-dependent; no production conflict reduction or 85% improvement claim.',
'Declarations are caller input, not verified native goal or session execution.']
};
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
@@ -0,0 +1,19 @@
#!/usr/bin/env node
'use strict';
const { performance } = require('node:perf_hooks');
const { buildInventory } = require('../../scripts/lib/coordination-inventory');
const cases = require('./fixtures.json');
function matrix() { return { truePositive: 0, falsePositive: 0, trueNegative: 0, falseNegative: 0 }; }
function add(m, expected, actual) { m[expected ? actual ? 'truePositive' : 'falseNegative' : actual ? 'falsePositive' : 'trueNegative'] += 1; }
const baseline = matrix(); const candidate = matrix();
const started = performance.now();
const rows = cases.map(c => {
const report = buildInventory(c.manifest, { now: '2026-09-08T06:30:00.000Z' });
const [a,b] = c.manifest.tasks;
const exactPath = a.repoId === b.repoId && a.paths.some(p => b.paths.includes(p));
const warning = report.warnings.length > 0;
add(baseline,c.needsReview,exactPath); add(candidate,c.needsReview,warning);
return { id: c.id, needsReview: c.needsReview, exactPath, pathAndImport: warning };
});
process.stdout.write(`${JSON.stringify({ version:1, dataset:'eight-authored-synthetic-pairs-v1', rows, baseline, candidate,
elapsedMs: performance.now()-started, conclusion:'Fixture detection only. Not a measured reduction in conflicts or validation of semantic/PCA proximity.' },null,2)}\n`);
@@ -0,0 +1,255 @@
[
{
"id": "same-path",
"needsReview": true,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"src/a.js"
]
}
],
"leases": []
}
},
{
"id": "direct-relative-import",
"needsReview": true,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {
"src/a.js": "require('../lib/b')",
"lib/b.js": ""
}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"lib/b.js"
]
}
],
"leases": []
}
},
{
"id": "independent",
"needsReview": false,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"docs/guide.md"
]
}
],
"leases": []
}
},
{
"id": "same-directory",
"needsReview": false,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"src/b.js"
]
}
],
"leases": []
}
},
{
"id": "separate-repositories",
"needsReview": false,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {}
},
{
"id": "other",
"sources": {}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "other",
"paths": [
"src/a.js"
]
}
],
"leases": []
}
},
{
"id": "comment-false-positive",
"needsReview": false,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {
"src/a.js": "// require('../lib/b')",
"lib/b.js": ""
}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"lib/b.js"
]
}
],
"leases": []
}
},
{
"id": "alias-false-negative",
"needsReview": true,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {
"src/a.js": "import b from '@lib/b'",
"lib/b.js": ""
}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"lib/b.js"
]
}
],
"leases": []
}
},
{
"id": "cross-artifact-false-negative",
"needsReview": true,
"manifest": {
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"specs/login.md"
]
},
{
"id": "b",
"repoId": "repo",
"paths": [
"ui/login.html"
]
}
],
"leases": []
}
}
]
@@ -0,0 +1,18 @@
{
"version": 1,
"repositories": [{ "id": "repo", "sources": { "src/a.js": "require('../lib/b')", "lib/b.js": "" } }],
"tasks": [
{ "id": "a", "repoId": "repo", "paths": ["src/a.js"], "status": "running" },
{ "id": "b", "repoId": "repo", "paths": ["lib/b.js"], "parentId": "a" }
],
"goals": [
{ "id": "goal-active", "taskId": "a", "kind": "native", "status": "active", "updatedAt": "2026-09-08T06:30:00.000Z" },
{ "id": "goal-complete", "taskId": "b", "kind": "native", "status": "complete", "updatedAt": "2026-09-08T06:30:00.000Z" }
],
"sessions": [
{ "id": "session-active", "taskId": "a", "goalId": "goal-active", "status": "open", "updatedAt": "2026-09-08T06:30:00.000Z" },
{ "id": "session-open-complete", "taskId": "b", "goalId": "goal-complete", "status": "open" },
{ "id": "terminal-only", "status": "open" }
],
"leases": []
}
@@ -0,0 +1,42 @@
{
"version": 1,
"repositories": [
{
"id": "repo",
"sources": {
"src/a.js": "require('../lib/b')",
"lib/b.js": ""
}
}
],
"tasks": [
{
"id": "a",
"repoId": "repo",
"paths": [
"src/a.js"
],
"heartbeatAt": "2026-09-08T06:00:00Z"
},
{
"id": "b",
"repoId": "repo",
"paths": [
"lib/b.js"
],
"parentId": "a"
}
],
"leases": [
{
"resource": "browser:chrome",
"owner": "root",
"expiresAt": "2026-09-08T07:00:00Z"
},
{
"resource": "browser:chrome",
"owner": "worker",
"expiresAt": "2026-09-08T07:00:00Z"
}
]
}
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env node
'use strict';
const { normalizeManifest, buildInventory, collectResources, collectTaskFiles, readJson } = require('./lib/coordination-inventory');
function main(argv = process.argv.slice(2)) {
if (argv.length === 1 && ['--help', '-h'].includes(argv[0])) {
process.stdout.write('Usage: node scripts/coordination-inventory.js [--manifest file.json] [--coordination directory] [--live] [--now ISO-UTC]\nRead-only JSON inventory. Live probes only OS memory and declared PIDs. No processes are executed from input.\n');
return;
}
const options = {};
for (let i = 0; i < argv.length; i += 1) {
const flag = argv[i];
if (flag === '--live' && !options.live) options.live = true;
else if (['--manifest', '--coordination', '--now'].includes(flag) && !options[flag.slice(2)] && argv[i+1] && !argv[i+1].startsWith('--')) options[flag.slice(2)] = argv[++i];
else throw new Error('Invalid inventory arguments. Use --help.');
}
let manifest = options.manifest ? readJson(options.manifest) : { version: 1, tasks: [], repositories: [], leases: [] };
let discovery = null;
if (options.coordination) {
discovery = collectTaskFiles(options.coordination);
// Duplicate IDs are rejected; never silently replace declared ownership.
manifest = { ...manifest, tasks: [...(manifest.tasks || []), ...discovery.tasks] };
}
const normalized = normalizeManifest(manifest);
const resources = options.live ? collectResources(normalized.tasks) : undefined;
const report = buildInventory(manifest, { now: options.now, resources });
if (discovery) report.discovery = { status: discovery.status, unreadable: discovery.unreadable };
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
}
if (require.main === module) {
try { main(); } catch { process.stderr.write('Inventory failed: invalid arguments or unreadable/invalid input. Use --help.\n'); process.exitCode = 1; }
}
module.exports = { main };
+3 -1
View File
@@ -25,9 +25,11 @@ function toRepoRel(repoRoot, absPath) {
// Match relative specifiers only (./ or ../). Bare specifiers are node_modules
// and never the target of an in-repo collision.
// Consume import whitespace once; a word boundary before `from` avoids
// overlapping whitespace quantifiers on incomplete import statements.
const SPEC_PATTERNS = [
/require\(\s*['"](\.[^'"]+)['"]\s*\)/g,
/import\s+(?:[^'"]*?\s+from\s+)?['"](\.[^'"]+)['"]/g,
/import\s+(?!\s)(?:[^'"]*?\bfrom\s+)?['"](\.[^'"]+)['"]/g,
/import\(\s*['"](\.[^'"]+)['"]\s*\)/g,
/export\s+(?:\*|\{[^}]*\})\s+from\s+['"](\.[^'"]+)['"]/g
];
+261
View File
@@ -0,0 +1,261 @@
'use strict';
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { execFileSync } = require('node:child_process');
const { collisionRisk } = require('./agent-proximity/distance');
const { buildDependencyGraphFromSources } = require('./agent-proximity/graph');
const { parseWorkerStatus } = require('./orchestration-session');
const MAX_BYTES = 1024 * 1024;
const STALE_MS = 5 * 60 * 1000;
function invalid() { throw new Error('Invalid coordination input.'); }
function record(value) {
if (!value || typeof value !== 'object' || Array.isArray(value)) invalid();
return value;
}
function list(value, max = 64) {
if (!Array.isArray(value) || value.length > max) invalid();
return value;
}
function text(value, max = 200) {
if (typeof value !== 'string' || !value.length || value.length > max || [...value].some(c => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127)) invalid();
return value;
}
function missing(value) { return value === null || value === undefined; }
function identifier(value) {
text(value);
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(value) || ['__proto__', 'constructor', 'prototype'].includes(value)) invalid();
return value;
}
function timestamp(value) {
text(value);
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(value) || !Number.isFinite(Date.parse(value))) invalid();
const canonical = value.replace(/(?:\.(\d{1,3}))?Z$/, (_, fraction) => `.${(fraction || '').padEnd(3, '0')}Z`);
if (new Date(value).toISOString() !== canonical) invalid();
return value;
}
function relativePath(value) {
const p = text(value, 1024).replace(/\\/g, '/').replace(/^\.\//, '');
const parts = p.split('/');
if (p.startsWith('/') || /^[A-Za-z]:/.test(p) || parts.some(x => !x || ['.', '..', '__proto__', 'constructor', 'prototype'].includes(x))) invalid();
return p;
}
function unique(items, key) {
if (new Set(items.map(x => x[key])).size !== items.length) invalid();
return items;
}
function normalizeTask(value) {
const t = record(value);
if (!missing(t.pid) && (!Number.isSafeInteger(t.pid) || t.pid <= 0)) invalid();
const id = identifier(t.id);
const parentId = t.parentId ? identifier(t.parentId) : null;
if (parentId === id) invalid();
return {
id, parentId, repoId: missing(t.repoId) ? null : identifier(t.repoId),
paths: [...new Set(list(t.paths || [], 128).map(relativePath))].sort(),
pid: t.pid ?? null, status: missing(t.status) ? 'unknown' : text(t.status),
heartbeatAt: missing(t.heartbeatAt) ? null : timestamp(t.heartbeatAt),
statusFileModifiedAt: missing(t.statusFileModifiedAt) ? null : timestamp(t.statusFileModifiedAt)
};
}
function declarationStatus(value, allowed) {
if (value === undefined) return 'unknown';
if (!allowed.includes(value)) invalid();
return value;
}
function normalizeDeclarations(manifest, tasks) {
const taskIds = new Set(tasks.map(t => t.id));
const link = (value, ids) => {
if (missing(value)) return null;
const id = identifier(value);
if (!ids.has(id)) invalid();
return id;
};
const common = value => ({ id: identifier(value.id), taskId: link(value.taskId, taskIds),
updatedAt: missing(value.updatedAt) ? null : timestamp(value.updatedAt) });
const goals = unique(list(manifest.goals === undefined ? [] : manifest.goals).map(value => {
const g = record(value);
return { ...common(g), kind: declarationStatus(g.kind, ['native', 'unknown']),
status: declarationStatus(g.status, ['active', 'complete', 'blocked', 'unknown']) };
}), 'id');
const goalIds = new Set(goals.map(g => g.id));
const sessions = unique(list(manifest.sessions === undefined ? [] : manifest.sessions).map(value => {
const s = record(value);
return { ...common(s), goalId: link(s.goalId, goalIds),
status: declarationStatus(s.status, ['open', 'closed', 'unknown']) };
}), 'id');
return { goals, sessions, declarationCoverage: {
goals: manifest.goals === undefined ? 'missing' : 'declared-only',
sessions: manifest.sessions === undefined ? 'missing' : 'declared-only'
} };
}
function normalizeManifest(value) {
const m = record(value);
if (m.version !== 1 || Buffer.byteLength(JSON.stringify(m)) > MAX_BYTES) invalid();
let sourceBytes = 0;
const repositories = unique(list(m.repositories ?? []).map(value => {
const r = record(value); const sourceEntries = Object.entries(record(r.sources ?? {}));
if (sourceEntries.length > 128) invalid();
const entries = sourceEntries.map(([p, source]) => {
// Existing regex extractor is for snippets, not arbitrary full source files.
if (typeof source !== 'string' || Buffer.byteLength(source) > 1024) invalid();
sourceBytes += Buffer.byteLength(source);
if (sourceBytes > 32768) invalid();
return [relativePath(p), source];
});
if (new Set(entries.map(([p]) => p)).size !== entries.length) invalid();
return { id: identifier(r.id), sources: Object.fromEntries(entries) };
}), 'id');
const tasks = unique(list(m.tasks).map(normalizeTask), 'id');
const ids = new Set(repositories.map(r => r.id));
if (tasks.some(t => t.repoId !== null && !ids.has(t.repoId))) invalid();
const leases = list(m.leases ?? [], 128).map(value => {
const l = record(value);
return { resource: identifier(l.resource), owner: identifier(l.owner), expiresAt: timestamp(l.expiresAt) };
});
return { version: 1, repositories, tasks, leases, ...normalizeDeclarations(m, tasks) };
}
function heartbeat(value, nowMs) {
if (!value) return { state: 'unknown', ageMs: null };
const ageMs = nowMs - Date.parse(value);
return { state: ageMs < 0 ? 'clock-skew' : ageMs > STALE_MS ? 'stale' : 'fresh', ageMs };
}
function declarationInventory(manifest, nowMs) {
const observe = item => ({ ...item, authority: 'declared-only', freshness: heartbeat(item.updatedAt, nowMs) });
const goals = manifest.goals.map(observe);
const sessions = manifest.sessions.map(observe);
const counts = (items, statuses) => Object.fromEntries(statuses.map(status =>
[status, items.filter(item => item.status === status).length]));
const statuses = ['active', 'complete', 'blocked', 'unknown'];
const native = goals.filter(g => g.kind === 'native');
return { goals, sessions, activity: {
declaredGoalsByStatus: counts(goals, statuses),
declaredNativeGoalsByStatus: counts(native, statuses),
declaredSessionsByStatus: counts(sessions, ['open', 'closed', 'unknown']),
openSessionsWithoutGoalDeclaration: sessions.filter(s => s.status === 'open' && s.goalId === null).length,
freshActiveNativeGoalDeclarations: native.filter(g => g.status === 'active' && g.freshness.state === 'fresh').length
} };
}
function proximityWarnings(manifest) {
const warnings = [];
let workBudget = 200000;
for (const repo of manifest.repositories) {
const tasks = manifest.tasks.filter(t => t.repoId === repo.id && t.paths.length > 0).sort((a,b) => a.id < b.id ? -1 : 1);
if (tasks.length < 2) continue;
const parsed = buildDependencyGraphFromSources(repo.sources);
const graph = { ...parsed, adjacency: Object.assign(Object.create(null), parsed.adjacency) };
const graphCost = 1 + graph.files.length + Object.values(graph.adjacency).reduce((sum, edges) => sum + edges.length, 0);
const pathPairs = tasks.reduce((sum, task, i) => sum + task.paths.length * tasks.slice(i + 1).reduce((n, other) => n + other.paths.length, 0), 0);
workBudget -= pathPairs * graphCost;
if (workBudget < 0) throw new Error('Inventory comparison budget exceeded; split the manifest.');
for (let i = 0; i < tasks.length; i += 1) {
for (let j = i + 1; j < tasks.length; j += 1) {
const a = tasks[i]; const b = tasks[j];
const score = collisionRisk({ files: a.paths.map(p => ({ path: p })) }, { files: b.paths.map(p => ({ path: p })) }, graph);
if (score.risk < 0.35) continue;
const reasons = [];
if (score.channels.overlap) reasons.push('path_overlap');
if (score.channels.dependency) reasons.push('import_dependency');
warnings.push({ repoId: repo.id, tasks: [a.id, b.id], reasons, score: score.risk, channels: score.channels, action: 'review-declared-work' });
}
}
}
return warnings;
}
function buildInventory(input, options = {}) {
const m = normalizeManifest(input);
const now = timestamp(options.now || new Date().toISOString());
const nowMs = Date.parse(now);
const resources = options.resources || { memory: null, processStatus: 'not-requested', processes: [] };
const processes = new Map(resources.processes.map(p => [p.pid, p]));
const tasks = m.tasks.map(t => ({ ...t, heartbeat: heartbeat(t.heartbeatAt, nowMs),
process: processes.has(t.pid) ? { ...processes.get(t.pid), state: 'observed' }
: { state: t.pid && resources.processStatus === 'ok' ? 'not-observed' : 'unknown' }
}));
const leases = m.leases.map(l => ({ ...l, state: Date.parse(l.expiresAt) > nowMs ? 'unexpired' : 'expired', authority: 'declared-only' }));
const active = new Map();
for (const l of leases.filter(l => l.state === 'unexpired')) {
active.set(l.resource, new Set([...(active.get(l.resource) || []), l.owner]));
}
const leaseConflicts = [...active].filter(([,owners]) => owners.size > 1)
.map(([resource,owners]) => ({ resource, owners: [...owners].sort() })).sort((a,b) => a.resource < b.resource ? -1 : 1);
return {
version: 1, mode: 'read-only', observedAt: now, tasks, leases, leaseConflicts,
...declarationInventory(m, nowMs),
resources, warnings: proximityWarnings(m),
coverage: { tasks: 'declared-or-status-files-only', workingSets: 'declared-paths-only', imports: 'provided-source-map-relative-js-ts-only', leases: 'declared-only', processes: 'declared-pids-only', ...m.declarationCoverage },
limits: ['Score is a heuristic, not a calibrated probability.', 'No warning does not establish collision-free work.',
'Goal/session states and native kind are caller declarations, not verified execution or authority.',
'Open sessions, task status and observed PIDs do not establish an active native goal.',
'Missing declarations and empty lists do not establish global absence; fresh declarations do not prove current execution.',
'Stale heartbeat is not proof of a stuck process; PID reuse is not resolved.',
'Import regex may match comments and misses aliases, nonliteral and non-JS imports.',
'No semantic/PCA proximity or conflict-reduction claim is validated.',
'Leases are observations, not locks or permission grants.']
};
}
function collectResources(tasks, deps = {}) {
const memory = { totalBytes: (deps.totalmem || os.totalmem)(), freeBytes: (deps.freemem || os.freemem)(),
source: 'os', note: 'OS free memory is not application headroom or macOS memory pressure.' };
const pids = [...new Set(tasks.map(t => t.pid).filter(pid => Number.isSafeInteger(pid) && pid > 0))];
if (!pids.length) return { memory, processStatus: 'not-requested', processes: [] };
if (!['darwin', 'linux'].includes(deps.platform || process.platform)) return { memory, processStatus: 'unsupported', processes: [] };
try {
const result = (deps.execFileSync || execFileSync)('ps', ['-p', pids.join(','), '-o', 'pid=,ppid=,rss=,etime=,stat='],
{ encoding: 'utf8', timeout: 2000, maxBuffer: 65536, shell: false, stdio: ['ignore','pipe','pipe'] });
const processes = String(result).split('\n').filter(l => l.trim()).map(line => {
const match = line.trim().match(/^(\d+)\s+(\d+)\s+(\d+)\s+([\d:-]+)\s+([A-Za-z+<>NsElLW]+)$/);
if (!match) throw new Error('Invalid process metadata.');
const values = match.slice(1,4).map(Number);
if (values.some(v => !Number.isSafeInteger(v)) || !pids.includes(values[0])) throw new Error('Invalid process metadata.');
return { pid: values[0], parentPid: values[1], rssBytes: values[2] * 1024, elapsed: match[4], flags: match[5] };
});
return { memory, processStatus: 'ok', processes };
} catch { return { memory, processStatus: 'unavailable', processes: [] }; }
}
function readBounded(file, limit = MAX_BYTES) {
// Refuse symlink final components, devices and files beyond the byte budget.
const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
try {
const stat = fs.fstatSync(fd);
if (!stat.isFile() || stat.size > limit) throw new Error('Input exceeds file limit.');
const buffer = Buffer.alloc(limit + 1);
let size = 0; let count;
do { count = fs.readSync(fd, buffer, size, buffer.length - size, null); size += count; } while (count && size < buffer.length);
if (size > limit) throw new Error('Input exceeds file limit.');
return { content: buffer.subarray(0,size).toString('utf8'), modifiedAt: stat.mtime.toISOString() };
} finally { fs.closeSync(fd); }
}
function readJson(file) {
try { return JSON.parse(readBounded(file).content); }
catch (error) { throw new Error(error.message === 'Input exceeds file limit.' ? error.message : 'Cannot read coordination JSON.'); }
}
function collectTaskFiles(directory) {
try {
const entries = fs.readdirSync(directory, { withFileTypes: true }).filter(e => e.isDirectory() && !e.name.startsWith('.')).sort((a,b) => a.name < b.name ? -1 : 1);
if (entries.length > 64) throw new Error('Too many task directories.');
const tasks = []; const unreadable = [];
for (const entry of entries) {
let loaded = false;
for (const name of ['STATUS.md', 'status.md']) {
try {
const data = readBounded(path.join(directory, entry.name, name), 65536);
const parsed = parseWorkerStatus(data.content);
let heartbeatAt = null;
try { if (parsed.updated) heartbeatAt = timestamp(parsed.updated); } catch { /* Unknown timestamp, not a heartbeat. */ }
tasks.push(normalizeTask({ id: entry.name, paths: [], status: parsed.state || 'unknown', heartbeatAt, statusFileModifiedAt: data.modifiedAt }));
loaded = true; break;
} catch { /* Try legacy lowercase status filename; report unreadable below. */ }
}
if (!loaded) unreadable.push(entry.name);
}
return { status: unreadable.length ? 'partial' : 'ok', tasks, unreadable };
} catch { return { status: 'unavailable', tasks: [], unreadable: [] }; }
}
module.exports = { normalizeManifest, buildInventory, collectResources, collectTaskFiles, readJson };
+18 -1
View File
@@ -127,6 +127,21 @@ function assertStdoutContract(result, label) {
}
}
function formatSpawnFailure(result, elapsedMs) {
const token = value => typeof value === 'string' && /^[A-Z][A-Z0-9_]{0,47}$/.test(value)
? value : null;
// Keep decoded UTF-8 byte counts, never stream contents or error messages.
const byteCount = value => typeof value === 'string' ? Buffer.byteLength(value, 'utf8') : null;
return JSON.stringify({
elapsedMs: Number.isSafeInteger(elapsedMs) && elapsedMs >= 0 ? elapsedMs : null,
status: Number.isSafeInteger(result.status) ? result.status : null,
signal: token(result.signal),
errorCode: token(result.error && result.error.code),
stdoutBytes: byteCount(result.stdout),
stderrBytes: byteCount(result.stderr)
});
}
// All registered Stop hooks (hooks/hooks.json).
const STOP_HOOKS = [
['stop:format-typecheck', 'scripts/hooks/stop-format-typecheck.js'],
@@ -163,11 +178,13 @@ const realisticPayload = stopPayload(100 * 1024);
for (const entry of hooksConfig.hooks.Stop) {
if (
test(`${entry.id} registered wrapper flushes a 100KB Stop payload`, () => {
const startedAt = process.hrtime.bigint();
const result = runRegisteredStopHook(entry, realisticPayload);
const elapsedMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
assert.strictEqual(
result.status,
0,
`${entry.id}: expected exit 0, got ${result.status}: ${result.stderr}`
result.status === 0 ? undefined : `${entry.id}: expected exit 0; ${formatSpawnFailure(result, elapsedMs)}`
);
assert.ok(
result.stdout === realisticPayload,
+124
View File
@@ -0,0 +1,124 @@
'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { buildInventory, normalizeManifest } = require('../../scripts/lib/coordination-inventory');
const now = '2026-09-09T01:00:00.000Z';
const fixture = () => ({ version: 1,
repositories: [{ id: 'repo', sources: {} }],
tasks: [{ id: 'worker', repoId: 'repo', paths: ['src/shared.js'], status: 'running', pid: 42 },
{ id: 'peer', repoId: 'repo', paths: ['src/shared.js'] }], leases: [] });
const inventory = value => buildInventory(value, { now });
test('goal collections distinguish missing observations from explicit empty declarations', () => {
const missing = inventory(fixture());
const empty = inventory({ ...fixture(), goals: [], sessions: [] });
assert.equal(missing.coverage.goals, 'missing');
assert.equal(missing.coverage.sessions, 'missing');
assert.equal(empty.coverage.goals, 'declared-only');
assert.equal(empty.coverage.sessions, 'declared-only');
assert.deepEqual(missing.goals, []);
assert.deepEqual(missing.sessions, []);
assert.deepEqual(missing.activity, empty.activity);
assert.equal(missing.activity.freshActiveNativeGoalDeclarations, 0);
});
test('goal activity is never inferred from an open session, running task, heartbeat or observed PID', () => {
const input = fixture(); input.tasks[0].heartbeatAt = now;
input.sessions = [{ id: 'terminal', taskId: 'worker', status: 'open', updatedAt: now }];
const report = buildInventory(input, { now, resources: {
memory: null, processStatus: 'ok', processes: [{ pid: 42, ppid: 1, rssBytes: 1024 }] } });
assert.equal(report.tasks[0].process.state, 'observed');
assert.equal(report.tasks[0].heartbeat.state, 'fresh');
assert.equal(report.activity.declaredSessionsByStatus.open, 1);
assert.equal(report.activity.openSessionsWithoutGoalDeclaration, 1);
assert.deepEqual(report.activity.declaredGoalsByStatus, { active: 0, complete: 0, blocked: 0, unknown: 0 });
assert.equal(report.coverage.goals, 'missing');
});
test('goal and session declarations remain independent and count a shared goal once', () => {
const input = { ...fixture(), goals: [
{ id: 'active', taskId: 'worker', kind: 'native', status: 'active', updatedAt: now },
{ id: 'done', kind: 'native', status: 'complete', updatedAt: now },
{ id: 'unverified', status: 'active', updatedAt: now },
{ id: 'blocked', kind: 'native', status: 'blocked' }, { id: 'unknown' }
], sessions: [
{ id: 'closed', goalId: 'active', status: 'closed' },
{ id: 'other', goalId: 'active', taskId: 'peer', status: 'open' },
{ id: 'open-done', goalId: 'done', status: 'open' }, { id: 'unknown-session' }
] };
const before = JSON.stringify(input); const report = inventory(input);
assert.deepEqual(report.activity.declaredGoalsByStatus, { active: 2, complete: 1, blocked: 1, unknown: 1 });
assert.deepEqual(report.activity.declaredNativeGoalsByStatus, { active: 1, complete: 1, blocked: 1, unknown: 0 });
assert.deepEqual(report.activity.declaredSessionsByStatus, { open: 2, closed: 1, unknown: 1 });
assert.equal(report.activity.freshActiveNativeGoalDeclarations, 1);
assert.equal(report.activity.openSessionsWithoutGoalDeclaration, 0);
assert.equal(report.goals[2].kind, 'unknown');
assert.equal(report.goals[4].status, 'unknown');
assert.equal(report.sessions[3].status, 'unknown');
assert.equal(report.goals[0].authority, 'declared-only');
assert.equal(report.sessions[0].authority, 'declared-only');
assert.equal(JSON.stringify(input), before);
assert.deepEqual(inventory(input), report);
});
test('goal freshness exposes missing stale future and boundary observations without rewriting status', () => {
const times = [null, '2026-09-09T00:54:59.999Z', '2026-09-09T01:00:00.001Z',
'2026-09-09T00:55:00.000Z', now];
const report = inventory({ ...fixture(), goals: times.map((updatedAt, i) =>
({ id: `g${i}`, kind: 'native', status: 'active', updatedAt })) });
assert.deepEqual(report.goals.map(g => g.freshness.state), ['unknown', 'stale', 'clock-skew', 'fresh', 'fresh']);
assert.equal(report.activity.declaredNativeGoalsByStatus.active, 5);
assert.equal(report.activity.freshActiveNativeGoalDeclarations, 2);
assert.ok(report.goals.every(g => g.status === 'active'));
});
test('goal declarations do not change existing task resource lease or overlap outputs', () => {
const base = fixture();
base.leases = [{ resource: 'browser', owner: 'worker', expiresAt: now }];
const legacy = inventory(base);
const report = inventory({ ...base, goals: [{ id: 'completed', status: 'complete' }],
sessions: [{ id: 'closed', status: 'closed', goalId: 'completed' }] });
for (const key of ['tasks', 'warnings', 'resources', 'leases', 'leaseConflicts']) {
assert.deepEqual(report[key], legacy[key]);
}
assert.equal(report.warnings.length, 1);
assert.equal(report.warnings[0].action, 'review-declared-work');
});
test('goal metadata drops objectives commands native blobs and other unrecognized fields', () => {
const report = inventory({ ...fixture(), goals: [{ id: 'g', objective: 'CANARY',
tool_result: { secret: 'CANARY' }, status: 'active', authority: 'CANARY' }],
sessions: [{ id: 's', goalId: 'g', command: 'CANARY', environment: 'CANARY' }] });
assert.ok(!JSON.stringify(report).includes('CANARY'));
assert.equal(report.goals[0].authority, 'declared-only');
});
test('goal input rejects malformed scalars enums dates duplicate IDs and dangling links', () => {
for (const collection of ['goals', 'sessions']) {
for (const value of [null, false, '', {}, 1]) {
assert.throws(() => normalizeManifest({ ...fixture(), [collection]: value }), /Invalid coordination input/);
}
for (const value of [null, false, [], 1, { id: 'bad/id' }, { id: '__proto__' },
{ id: 'x', status: null }, { id: 'x', status: true }, { id: 'x', status: 'running' },
{ id: 'x', updatedAt: '2026-02-30T00:00:00Z' }, { id: 'x', updatedAt: true },
{ id: 'x', taskId: 'missing' }, { id: 'x', taskId: 1 }]) {
assert.throws(() => normalizeManifest({ ...fixture(), [collection]: [value] }), /Invalid coordination input/);
}
assert.throws(() => normalizeManifest({ ...fixture(), [collection]: [{ id: 'same' }, { id: 'same' }] }));
}
for (const kind of [null, true, 1, 'verified', 'declared']) {
assert.throws(() => normalizeManifest({ ...fixture(), goals: [{ id: 'g', kind }] }));
}
assert.throws(() => normalizeManifest({ ...fixture(), sessions: [{ id: 's', goalId: 'missing' }] }));
assert.throws(() => normalizeManifest({ ...fixture(), sessions: [{ id: 's', goalId: 1 }] }));
});
test('goal and session cardinality and total input bounds remain enforced', () => {
const declarations = Array.from({ length: 64 }, (_, i) => ({ id: `item${i}` }));
const report = inventory({ ...fixture(), goals: declarations, sessions: declarations });
assert.equal(report.goals.length, 64); assert.equal(report.sessions.length, 64);
for (const collection of ['goals', 'sessions']) {
assert.throws(() => inventory({ ...fixture(), [collection]: [...declarations, { id: 'extra' }] }));
}
assert.throws(() => inventory({ ...fixture(), goals: [{ id: 'g', ignored: 'x'.repeat(1024 * 1024) }] }));
});
@@ -0,0 +1,155 @@
'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { normalizeManifest, buildInventory, collectResources, collectTaskFiles, readJson } = require('../../scripts/lib/coordination-inventory');
const now = '2026-09-08T06:30:00.000Z';
const task = (id, paths, extra = {}) => ({ id, repoId: 'repo', paths, ...extra });
const fixture = () => ({ version: 1, repositories: [{ id: 'repo', sources: { 'src/a.js': "require('../lib/b')", 'lib/b.js': '' } }], tasks: [task('a', ['src/a.js']), task('b', ['lib/b.js'])], leases: [] });
const run = value => buildInventory(value, { now });
test('direct import warns when exact-path baseline would miss it; deterministic JSON', () => {
const f = fixture(); const before = JSON.stringify(f); const r = run(f);
assert.equal(r.warnings.length, 1); assert.deepEqual(r.warnings[0].reasons, ['import_dependency']);
assert.equal(r.warnings[0].channels.dependency, 1);
assert.equal(JSON.stringify(run(f)), JSON.stringify(r)); assert.equal(JSON.stringify(f), before);
});
test('normalized exact paths warn, tree-only neighbors and cross-repo pairs do not', () => {
const f = fixture(); f.tasks[1].paths = ['./src/a.js'];
assert.deepEqual(run(f).warnings[0].reasons, ['path_overlap']);
f.tasks[1].paths = ['src/c.js']; assert.equal(run(f).warnings.length, 0);
f.repositories.push({ id: 'other', sources: {} }); f.tasks[1] = task('b', ['src/a.js'], { repoId: 'other' });
assert.equal(run(f).warnings.length, 0);
});
test('leases show owner, expiry, conflicts and do not grant authority', () => {
const f = fixture(); f.leases = [
{ resource: 'browser:chrome', owner: 'root', expiresAt: '2026-09-08T07:00:00Z' },
{ resource: 'browser:chrome', owner: 'worker', expiresAt: '2026-09-08T07:00:00Z' },
{ resource: 'browser:chrome', owner: 'old', expiresAt: now }
]; const r = run(f);
assert.equal(r.leases[2].state, 'expired');
assert.deepEqual(r.leaseConflicts, [{ resource: 'browser:chrome', owners: ['root', 'worker'] }]);
assert.equal(r.mode, 'read-only'); assert.equal(r.leases[0].authority, 'declared-only');
});
test('stale heartbeat is not a proven stuck process; absent/future telemetry stays unknown', () => {
const f = fixture(); f.tasks = [task('a', [], { heartbeatAt: '2026-09-08T06:00:00Z', pid: 12 }), task('b', [], { heartbeatAt: '2026-09-08T07:00:00Z' }), task('c', [])];
const r = run(f); assert.equal(r.tasks[0].heartbeat.state, 'stale'); assert.equal(r.tasks[0].process.state, 'unknown');
assert.equal(r.tasks[1].heartbeat.state, 'clock-skew'); assert.equal(r.tasks[2].heartbeat.state, 'unknown');
});
test('task parents, status and bounded observations survive without source payload', () => {
const f = fixture(); f.tasks[1].parentId = 'a'; f.tasks[0].status = 'running'; f.tasks[0].unexpectedSecret = 'CANARY_SECRET';
f.repositories[0].sources['lib/b.js'] = 'CANARY_SOURCE';
const r = run(f); assert.equal(r.tasks[1].parentId, 'a'); assert.equal(r.tasks[0].status, 'running');
assert.ok(!JSON.stringify(r).includes('CANARY')); assert.equal(r.coverage.workingSets, 'declared-paths-only');
});
test('invalid shapes, IDs, paths, dates and missing repos fail closed', () => {
for (const mutate of [
f => { f.version = 2; }, f => { f.tasks = null; }, f => { f.tasks.push(f.tasks[0]); },
f => { f.tasks[0].paths = ['../escape']; }, f => { f.tasks[0].paths = ['/absolute']; },
f => { f.tasks[0].paths = ['C:\\secret']; }, f => { f.tasks[0].paths = ['a/../b']; },
f => { f.tasks[0].paths = ['__proto__']; }, f => { f.tasks[0].pid = '-1'; },
f => { f.tasks[0].heartbeatAt = 'yesterday'; }, f => { f.tasks[0].repoId = 'absent'; },
f => { f.repositories[0].sources = []; }, f => { f.tasks[0].id = '\n'; },
f => { f.tasks[0].parentId = 'a'; }, f => { f.tasks = Array(65).fill(f.tasks[0]); },
f => { f.leases = [{resource:'chrome',owner:'root',expiresAt:'bad'}]; }
]) { const f = fixture(); mutate(f); assert.throws(() => normalizeManifest(f), /Invalid/); }
});
test('process collection uses metadata-only argv, bounded timeout and no shell', () => {
let call; const r = collectResources([task('a', [], { pid: 12 })], { platform: 'darwin', totalmem: () => 1024, freemem: () => 512, execFileSync: (...args) => { call = args; return '12 1 32 01:30 S\n'; } });
assert.equal(call[0], 'ps'); assert.deepEqual(call[1], ['-p','12','-o','pid=,ppid=,rss=,etime=,stat=']);
assert.equal(call[2].timeout, 2000); assert.equal(call[2].shell, false);
assert.equal(r.processes[0].rssBytes, 32768); assert.equal(r.memory.freeBytes, 512);
});
test('unavailable, empty, malformed and unsupported process snapshots remain explicit', () => {
const tasks = [task('a', [], { pid: 12 })];
let runnerCalls = 0;
const unsupportedDeps = { platform: 'win32', execFileSync: () => { runnerCalls += 1; return ''; } };
const unsupported = collectResources(tasks, unsupportedDeps);
assert.equal(unsupported.processStatus, 'unsupported');
assert.equal(buildInventory({ ...fixture(), tasks }, { now, resources: unsupported }).tasks[0].process.state, 'unknown');
// Runner fixtures must select a supported platform independently of the host.
assert.equal(collectResources(tasks, { platform: 'darwin', execFileSync: () => { throw new Error('SECRET'); } }).processStatus, 'unavailable');
assert.equal(collectResources(tasks, { platform: 'darwin', execFileSync: () => '' }).processStatus, 'ok');
assert.equal(collectResources(tasks, { platform: 'darwin', execFileSync: () => 'bad row' }).processStatus, 'unavailable');
assert.equal(collectResources([], unsupportedDeps).processStatus, 'not-requested');
assert.equal(runnerCalls, 0);
});
test('live process snapshot enriches matching tasks and marks missing PID as unobserved', () => {
const f = fixture(); f.tasks[0].pid = 12; f.tasks[1].pid = 13;
const resources = collectResources(f.tasks, { platform: 'linux', execFileSync: () => '12 1 32 01:30 S\n' });
const r = buildInventory(f, { now, resources });
assert.equal(r.tasks[0].process.state, 'observed'); assert.equal(r.tasks[1].process.state, 'not-observed');
});
test('task file adapter reads structured status, labels mtime, skips symlinks and rejects oversized JSON', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coordination-test-'));
try {
fs.mkdirSync(path.join(dir, 'worker')); fs.writeFileSync(path.join(dir, 'worker', 'STATUS.md'), '- State: running\n- Updated: 2026-09-08T06:29:00Z\n');
fs.symlinkSync(path.join(dir, 'worker'), path.join(dir, 'linked'));
const r = collectTaskFiles(dir); assert.equal(r.tasks.length, 1); assert.equal(r.tasks[0].status, 'running');
assert.ok(r.tasks[0].statusFileModifiedAt); assert.equal(r.tasks[0].heartbeatAt, '2026-09-08T06:29:00Z');
fs.writeFileSync(path.join(dir, 'large.json'), ' '.repeat(1024 * 1024 + 1));
assert.throws(() => readJson(path.join(dir, 'large.json')), /limit/);
assert.equal(collectTaskFiles(path.join(dir, 'missing')).status, 'unavailable');
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
});
test('CLI JSON end to end, no output file changes and safe errors', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coordination-cli-'));
const cli = path.resolve(__dirname, '../../scripts/coordination-inventory.js');
try {
const file = path.join(dir,'input.json'); fs.writeFileSync(file, JSON.stringify(fixture()));
const r = spawnSync(process.execPath, [cli, '--manifest', file, '--now', now], { encoding:'utf8' });
assert.equal(r.status,0,r.stderr); assert.equal(JSON.parse(r.stdout).warnings.length,1);
assert.deepEqual(fs.readdirSync(dir),['input.json']);
const bad = spawnSync(process.execPath,[cli,'--unknown','CANARY_SECRET'],{encoding:'utf8'});
assert.equal(bad.status,1); assert.ok(!bad.stderr.includes('CANARY_SECRET'));
const help = spawnSync(process.execPath,[cli,'--help'],{encoding:'utf8'}); assert.equal(help.status,0);
} finally { fs.rmSync(dir,{recursive:true,force:true}); }
});
test('prototype-named paths and strict calendar dates are safe', () => {
const f = fixture(); f.repositories[0].sources = {}; f.tasks[0].paths = ['toString']; f.tasks[1].paths = ['valueOf'];
assert.equal(run(f).warnings.length, 0);
for (const invalid of ['2026-02-30T00:00:00Z', '2026-09-08T24:00:00Z']) {
f.tasks[0].heartbeatAt = invalid; assert.throws(() => run(f), /Invalid/);
}
f.tasks[0].heartbeatAt = '2026-09-08T06:00:00.1Z'; assert.equal(run(f).tasks[0].heartbeat.state, 'stale');
});
test('aggregate comparison budget rejects compact but computationally excessive input', () => {
const f = fixture(); f.repositories[0].sources = {};
f.tasks = Array.from({length:64}, (_,i) => task(`task${i}`, Array.from({length:128}, (_,j) => `src/${i}/${j}.js`)));
assert.throws(() => run(f), /budget/);
});
test('CLI discovery composes normalized tasks and reports missing telemetry honestly', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'coordination-discovery-'));
try {
fs.mkdirSync(path.join(dir,'worker')); fs.writeFileSync(path.join(dir,'worker','STATUS.md'),'Freeform progress.\n');
const r = spawnSync(process.execPath,[path.resolve(__dirname,'../../scripts/coordination-inventory.js'),'--coordination',dir,'--now',now],{encoding:'utf8'});
assert.equal(r.status,0,r.stderr); const report=JSON.parse(r.stdout);
assert.equal(report.tasks[0].status,'unknown'); assert.equal(report.tasks[0].heartbeat.state,'unknown');
assert.ok(report.tasks[0].statusFileModifiedAt); assert.equal(report.tasks[0].process.state,'unknown');
} finally { fs.rmSync(dir,{recursive:true,force:true}); }
});
test('source snippets are bounded before invoking inherited regex extractor', () => {
const f = fixture(); f.repositories[0].sources = { 'a.js': `import ${' '.repeat(32000)}x` }; f.tasks=[];
assert.throws(() => run(f), /Invalid/);
f.repositories[0].sources = Object.fromEntries(Array.from({length:33},(_,i) => [`${i}.js`, ' '.repeat(1024)]));
assert.throws(() => run(f), /Invalid/);
});
test('maximum accepted whitespace snippets complete within bounded subprocess timeout', () => {
const code = `const {buildInventory}=require('./scripts/lib/coordination-inventory');
const source='import '+' '.repeat(1016)+'x';
const sources=Object.fromEntries(Array.from({length:32},(_,i)=>[i+'.js',source]));
const r=buildInventory({version:1,repositories:[{id:'r',sources}],tasks:[{id:'a',repoId:'r',paths:['0.js']},{id:'b',repoId:'r',paths:['1.js']}]});
if(r.warnings.length) process.exitCode=1;`;
const r=spawnSync(process.execPath,['-e',code],{cwd:path.resolve(__dirname,'../..'),encoding:'utf8',timeout:2000});
assert.equal(r.status,0,r.error?.message || r.stderr);
});
test('bounded import parsing preserves supported JS and TS import forms', () => {
const { buildDependencyGraphFromSources } = require('../../scripts/lib/agent-proximity/graph');
for (const source of ["import './b'", "import b from './b'", "import { b as c } from './b'", "import * as b from './b'", "import b, { c } from './b'", "import type { B } from './b'", "import {\n b\n} from './b'", "import('./b')"]) {
assert.deepEqual(buildDependencyGraphFromSources({'a.js':source,'b.js':''}).adjacency['a.js'],['b.js']);
}
});
+269 -103
View File
@@ -25,32 +25,43 @@ async function test(name, fn) {
passed += 1;
} catch (error) {
console.log(` FAIL ${name}`);
console.log(` ${error.stack || error.message}`);
console.log(` ${error.mcpDiagnostic ? JSON.stringify(error.mcpDiagnostic) : error.stack || error.message}`);
failed += 1;
}
}
function createFixture(extraEnv = {}) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-memory-mcp-'));
const projectRoot = path.join(root, 'project');
const homeDir = path.join(root, 'home');
fs.mkdirSync(path.join(projectRoot, '.git'), { recursive: true });
fs.mkdirSync(homeDir, { recursive: true });
return {
root,
projectRoot,
env: Object.fromEntries(
Object.entries({
...process.env,
HOME: homeDir,
USERPROFILE: homeDir,
ECC_MEMORY_PROJECT_ROOT: path.join(projectRoot, '.ecc', 'memory'),
ECC_MEMORY_USER_ROOT: path.join(homeDir, '.ecc', 'memory'),
ECC_MEMORY_HARNESS: 'claude',
...extraEnv,
}).filter(([, value]) => typeof value === 'string')
),
};
try {
const projectRoot = path.join(root, 'project');
const homeDir = path.join(root, 'home');
fs.mkdirSync(path.join(projectRoot, '.git'), { recursive: true });
fs.mkdirSync(homeDir, { recursive: true });
return {
root,
projectRoot,
env: Object.fromEntries(
Object.entries({
...process.env,
HOME: homeDir,
USERPROFILE: homeDir,
ECC_MEMORY_PROJECT_ROOT: path.join(projectRoot, '.ecc', 'memory'),
ECC_MEMORY_USER_ROOT: path.join(homeDir, '.ecc', 'memory'),
ECC_MEMORY_HARNESS: 'claude',
ECC_MEMORY_ALLOW_USER_SCOPE: '0',
...extraEnv,
}).filter(([, value]) => typeof value === 'string')
),
};
} catch (error) {
try { fs.rmSync(root, { recursive: true, force: true }); }
catch {
const failure = new Error('MCP fixture cleanup failed', { cause: error });
failure.mcpCleanupFailure = 'fixture_removal_error';
throw failure;
}
throw error;
}
}
function parseTextResult(result) {
@@ -60,105 +71,260 @@ function parseTextResult(result) {
}
async function withClient(fn, options = {}) {
const fixture = createFixture(options.env);
const child = spawn(process.execPath, [options.server || SERVER], {
cwd: fixture.projectRoot,
env: fixture.env,
stdio: ['pipe', 'pipe', 'pipe'],
});
const started = Date.now();
const pending = new Map();
const mode = options.env?.ECC_MEMORY_ALLOW_USER_SCOPE === '1' ? 'allow' : 'deny';
let fixture;
let child;
let phase = 'setup';
let nextId = 1;
let stdout = '';
let stderr = '';
let stdout = Buffer.alloc(0);
let stdoutBytes = 0;
let stderrBytes = 0;
let closed = false;
let tearingDown = false;
let transportError;
let primaryError;
let primaryFailed = false;
let failureKind;
let failureElapsedMs;
let teardownStarted;
let failurePhase;
let cleanupFailure;
let killStatus = 'not_attempted';
let notifyClose;
const closePromise = new Promise(resolve => { notifyClose = resolve; });
let rejectTransport;
const transportFailure = new Promise((_, reject) => { rejectTransport = reject; });
// The child may fail before the initialize or callback race is installed.
transportFailure.catch(() => {});
child.stdout.on('data', chunk => {
stdout += chunk.toString('utf8');
let newlineIndex = stdout.indexOf('\n');
while (newlineIndex >= 0) {
const line = stdout.slice(0, newlineIndex);
stdout = stdout.slice(newlineIndex + 1);
if (line.trim()) {
const message = JSON.parse(line);
const bounded = value => Math.min(2147483647, Math.max(0, Math.trunc(value)));
const safeCode = error => [
'EPIPE', 'ENOENT', 'EACCES', 'EPERM', 'EINVAL', 'ECONNRESET',
'ERR_STREAM_DESTROYED', 'ERR_STREAM_WRITE_AFTER_END', 'ERR_ASSERTION',
].includes(error?.code) ? error.code : null;
const diagnostic = () => ({
phase: failurePhase || phase,
mode,
reason: failureKind || cleanupFailure || 'assertion_or_callback',
failureElapsedMs: failureElapsedMs ?? null,
teardownElapsedMs: bounded(Date.now() - teardownStarted),
elapsedMs: bounded(Date.now() - started),
stdoutBytes,
stderrBytes,
pendingRequests: pending.size,
childStarted: Boolean(child?.pid),
childClosed: closed,
exitCode: Number.isInteger(child?.exitCode) ? child.exitCode : null,
signal: ['SIGTERM', 'SIGKILL', 'SIGINT'].includes(child?.signalCode) ? child.signalCode : null,
errorCode: safeCode(primaryError),
cleanupFailure: cleanupFailure || null,
killStatus,
});
function settleAll(error) {
for (const waiter of pending.values()) waiter.reject(error);
pending.clear();
}
function fail(kind, cause) {
if (tearingDown) {
cleanupFailure ||= kind;
return;
}
if (transportError) return;
transportError = new Error(`MCP test client ${kind}`);
if (safeCode(cause)) transportError.code = safeCode(cause);
failurePhase = phase;
failureKind = kind;
settleAll(transportError);
rejectTransport(transportError);
}
function send(message) {
if (transportError) throw transportError;
try {
child.stdin.write(`${JSON.stringify(message)}\n`, error => {
if (error) fail('stdin_write_error', error);
});
} catch (error) {
fail('stdin_write_error', error);
throw transportError;
}
}
function request(method, params = {}) {
const id = nextId++;
const promise = new Promise((resolve, reject) => {
if (transportError || tearingDown || closed) {
reject(transportError || new Error('MCP test client is closed'));
return;
}
const timer = setTimeout(() => {
fail('request_timeout');
}, 5000);
function settle(fn, value) {
clearTimeout(timer);
pending.delete(id);
fn(value);
}
pending.set(id, {
resolve: value => settle(resolve, value),
reject: error => settle(reject, error),
});
send({ jsonrpc: '2.0', id, method, params });
});
// Teardown rejects abandoned requests too, without an unhandled rejection.
promise.catch(() => {});
return promise;
}
try {
fixture = createFixture(options.env);
phase = 'spawn';
child = spawn(process.execPath, [options.server || SERVER], {
cwd: fixture.projectRoot,
env: fixture.env,
stdio: ['pipe', 'pipe', 'pipe'],
});
child.on('error', error => fail('child_error', error));
child.on('exit', () => {
if (!tearingDown) fail('child_exit');
});
child.once('close', () => {
closed = true;
notifyClose();
if (!tearingDown) fail('child_close');
});
for (const stream of ['stdin', 'stdout', 'stderr']) {
child[stream].on('error', error => fail(`${stream}_error`, error));
}
child.stdout.on('end', () => { if (!tearingDown) fail('stdout_end'); });
for (const stream of ['stdin', 'stdout']) {
child[stream].on('close', () => { if (!tearingDown) fail(`${stream}_close`); });
}
child.stderr.on('data', chunk => {
stderrBytes = bounded(stderrBytes + chunk.length);
});
child.stdout.on('data', chunk => {
stdoutBytes = bounded(stdoutBytes + chunk.length);
if (transportError || tearingDown) return;
// Decode complete lines, so a UTF-8 character split across chunks survives.
stdout = Buffer.concat([stdout, chunk]);
let newlineIndex;
while ((newlineIndex = stdout.indexOf(10)) >= 0) {
if (newlineIndex > 1024 * 1024) { fail('oversized_frame'); return; }
const line = stdout.subarray(0, newlineIndex).toString('utf8');
stdout = stdout.subarray(newlineIndex + 1);
if (!line.trim()) continue;
let message;
try {
message = JSON.parse(line);
if (!message || message.jsonrpc !== '2.0' || !Number.isInteger(message.id)
|| (Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error'))
|| (Object.hasOwn(message, 'error') && (!message.error
|| !Number.isInteger(message.error.code) || typeof message.error.message !== 'string'))) {
fail('invalid_frame');
return;
}
} catch {
fail('malformed_frame');
return;
}
const waiter = pending.get(message.id);
if (waiter) {
pending.delete(message.id);
if (message.error) {
// Existing authorization/protocol assertions inspect this RPC error.
// The test logger emits only mcpDiagnostic when it escapes the helper.
waiter.reject(new Error(`${message.error.code}: ${message.error.message}`));
} else {
waiter.resolve(message.result);
}
}
}
newlineIndex = stdout.indexOf('\n');
if (stdout.length > 1024 * 1024) fail('oversized_frame');
});
phase = 'initialize';
const initialized = await request('initialize', {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'ecc-memory-test', version: '1.0.0' },
});
phase = 'protocol';
assert.strictEqual(initialized.protocolVersion, '2025-11-25');
phase = 'notification';
send({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} });
const client = {
listTools: () => request('tools/list'),
listToolsRaw: params => request('tools/list', params),
callTool: ({ name, arguments: toolArguments }) => request(
'tools/call',
{ name, arguments: toolArguments }
),
callToolRaw: params => request('tools/call', params),
};
phase = 'callback';
await Promise.race([Promise.resolve().then(() => fn(client, fixture)), transportFailure]);
if (transportError) throw transportError;
assert.strictEqual(pending.size, 0, 'MCP callback must await its requests');
} catch (error) {
primaryError = error;
primaryFailed = true;
failurePhase ||= phase;
failureElapsedMs = bounded(Date.now() - started);
if (error?.mcpCleanupFailure === 'fixture_removal_error') {
cleanupFailure ||= 'fixture_removal_error';
}
});
child.stderr.on('data', chunk => {
stderr += chunk.toString('utf8');
});
function send(message) {
child.stdin.write(`${JSON.stringify(message)}\n`);
}
function request(method, params = {}) {
const id = nextId;
nextId += 1;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
pending.delete(id);
reject(new Error(`Timed out waiting for ${method}. stderr: ${stderr}`));
}, 5000);
pending.set(id, {
resolve: value => {
clearTimeout(timeout);
resolve(value);
},
reject: error => {
clearTimeout(timeout);
reject(error);
},
});
send({ jsonrpc: '2.0', id, method, params });
});
}
const initialized = await request('initialize', {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'ecc-memory-test', version: '1.0.0' },
});
assert.strictEqual(initialized.protocolVersion, '2025-11-25');
send({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} });
const client = {
listTools: () => request('tools/list'),
listToolsRaw: params => request('tools/list', params),
callTool: ({ name, arguments: toolArguments }) => request(
'tools/call',
{ name, arguments: toolArguments }
),
callToolRaw: params => request('tools/call', params),
};
try {
await fn(client, fixture);
} finally {
child.stdin.end();
await new Promise(resolve => {
if (child.exitCode !== null) {
resolve();
return;
tearingDown = true;
teardownStarted = Date.now();
phase = 'teardown';
settleAll(new Error('MCP test client is closing'));
stdout = Buffer.alloc(0);
if (child && !closed) {
// Keep the original total 2000 ms budget. Reserve its latter half for
// direct-child termination and stdio close, including on Windows.
let killTimer;
let deadlineTimer;
function terminate() {
try { killStatus = child.kill() ? 'requested' : 'not_sent'; }
catch { killStatus = 'error'; }
}
const timeout = setTimeout(() => {
child.kill();
resolve();
}, 2000);
child.once('exit', () => {
clearTimeout(timeout);
resolve();
const deadline = new Promise(resolve => {
deadlineTimer = setTimeout(resolve, 2000);
killTimer = setTimeout(terminate, 1000);
});
});
fs.rmSync(fixture.root, { recursive: true, force: true });
try {
try { child.stdin.end(); }
catch {
cleanupFailure ||= 'stdin_end_error';
clearTimeout(killTimer);
terminate();
}
await Promise.race([closePromise, deadline]);
} finally {
clearTimeout(killTimer);
clearTimeout(deadlineTimer);
}
if (!closed) cleanupFailure ||= 'child_close_timeout';
}
if (fixture && (!child || closed)) {
try { fs.rmSync(fixture.root, { recursive: true, force: true }); }
catch { cleanupFailure ||= 'fixture_removal_error'; }
}
}
if (primaryFailed || cleanupFailure) {
if (!primaryFailed) primaryError = new Error('MCP test client cleanup failed');
// Keep the primary assertion/RPC/callback error; cleanup must not replace it.
// A wrapper retains non-extensible or non-Error thrown values as its cause.
if (!primaryError || typeof primaryError !== 'object' || !Object.isExtensible(primaryError)
|| Object.getOwnPropertyDescriptor(primaryError, 'mcpDiagnostic')?.configurable === false
|| Object.getOwnPropertyDescriptor(primaryError, 'mcpCleanupFailure')?.configurable === false) {
primaryError = new Error('MCP test client failed', { cause: primaryError });
}
Object.defineProperty(primaryError, 'mcpDiagnostic', { value: diagnostic(), configurable: true });
if (cleanupFailure) {
Object.defineProperty(primaryError, 'mcpCleanupFailure', { value: cleanupFailure, configurable: true });
}
throw primaryError;
}
}