feat: preserve passive scoped coordination receipts from PR 3128

This commit is contained in:
haelyra
2026-10-11 01:55:20 -04:00
3 changed files with 659 additions and 20 deletions
+60 -20
View File
@@ -1,8 +1,9 @@
# Agent-space distance metric & collision avoidance (Layer 4)
# Agent-space proximity and advisory prototypes (Layer 4)
> Status: v0 implemented in `scripts/lib/agent-proximity/`. This is the moat
> layer of ECC 2.0 — *spatial deconfliction for multiple agents (and humans)
> working the same codebase*, modeled on aircraft collision avoidance (TCAS).
> Status: v0 proximity scoring exists in `scripts/lib/agent-proximity/`.
> A separate v1 shadow trail records bounded advisories and declared responses.
> Current scores and drawings have no calibrated conflict probability or live
> conflict-reduction result.
## The analogy
@@ -28,16 +29,16 @@ also declare an **intent set** *I_a* of files it is about to touch (look-ahead).
## 2. Collision is multi-channel (noisy-OR)
Two agents can collide through several independent channels. Each channel *i*
yields a collision probability *r_i ∈ [0,1]*; we combine them as the probability
of colliding through **at least one** channel:
Several channels contribute bounded heuristic scores *r_i ∈ [0,1]*. The shipped
noisy-OR-shaped combination is:
```
R(a,b) = 1 − Π_i ( 1 − ω_i · r_i ) (2)
```
with channel weights *ω_i ∈ [0,1]*. The reported **distance** is the dual
*D(a,b) = 1 − R(a,b)*.
with channel weights *ω_i ∈ [0,1]*. The reported **distance score** is the dual
*D(a,b) = 1 − R(a,b)*. Correlated channels and unvalidated weights mean *R* is
not a calibrated probability, and *D* need not satisfy metric axioms.
### Channel 1 — edit overlap *r_overlap*
@@ -119,10 +120,9 @@ rate near *τ_TA* can pre-emptively escalate before the protected zone is entere
Each file gets a coordinate via a **space-filling embedding of its path** (files
sharing a long directory prefix share most of their coordinate), then pulled
toward its dependency neighbours by one averaging step. An agent sits at the
recency-weighted centroid of its files' coordinates. The result: `‖v_a − v_b‖`
tracks the collision risk *R*, so a **3D "where are the agents" view** renders
agents as moving points in a file-cloud — you literally watch them crawl toward
each other, see the advisory line light up, and watch one steer away.
recency-weighted centroid of its files' coordinates. The resulting **3D display**
illustrates path and dependency proximity. Its Euclidean separation has not
been shown to preserve the risk score or predict collisions.
`scanAirspace(agents, graph)` returns, in one pass: the non-clear `advisories`
(what the trigger layer acts on), the 3D `positions` and `fileCoordinates` (what
@@ -133,18 +133,58 @@ the renderer draws), and pairwise `links` with risk (the edges to color).
- **Inputs** come from the session/work state: each running session's worktree
diff gives its working set *W_a*; the dependency graph is built from the repo
(`buildDependencyGraph`).
- **Triggers**: the control-pane tick calls `scanAirspace`; a Traffic Advisory
injects a "transmit intent" message between the two agents' sessions; a
Resolution Advisory tells the lower-priority agent to steer (re-target to a
different file/subtree) — the first concrete realization of *just-in-time
multi-agent (and multi-human) deconfliction*.
- **Triggers**: control-pane prototype code can derive messages from
`scanAirspace`. Delivery, acknowledgement, scope authorization and actual
conflict resolution are separate evidence. The shadow trail below calls no
sink, hook or steer path.
- **Board**: advisories surface on the kanban as proximity warnings, extending
the agent/human JIT assignment layer already in the control pane.
## Passive shadow receipt trail (v1)
`scripts/lib/agent-proximity/shadow-trail.js` accepts two supplied, versioned
observations with task/repository/base/scope identity, read/write path sets,
completeness and expiry. It computes a local advisory using the shipped
three-channel scorer. An exact write/write or write/read path overlap takes
precedence over a low score. Incomplete, expired, future or incomparable critical
observations produce `unknown`; an absent advisory means only that no advisory
was found **within the complete, comparable supplied scope and supplied dependency
graph**. The optional graph defaults to empty and may be supplied anew for
re-observation; a quiet advisory says nothing about unsupplied dependencies.
Call `createShadowTrail`, then `appendAcknowledgement` for both observed agents,
`appendScopeRevision` for one externally declared revision and its new footprint,
and `appendReobservation` with a fresh matching pair. `summarizeShadowTrail`
reports the latest scoped advisory, its own advisory-bound ACKs, separate
historical acknowledgers and `outcome: unadjudicated`. Both original observations
must still be current when the scope-change declaration is recorded.
`verifyShadowTrail` checks the bounded local receipt chain after JSON persistence.
Each receipt hash binds the trail ID, event ID, time, predecessor hash and
canonical payload. The verifier rejects noncanonical persisted payload field
and path order before comparing hashes. Append results copy earlier receipts,
so changing an earlier returned object cannot change a later returned trail.
Returned data can still be mutated; verification detects inconsistent local
content. A party able to
rewrite and rehash the entire trail can forge a consistent chain. These hashes
do not establish trusted identity, permission, delivery, whole-log immutability
or an independently verified result.
The module issues no grants and sends no messages. The scope-change receipt's
`authorizationRef` is a caller declaration and is labelled `declared-only`;
the actual authority owner must validate it before any real work changes. It
records path metadata locally, so a caller must apply its own publication policy.
It accepts no memory summary or required-state payload. Approximate summaries
belong to the separately owned memory envelope; exact task constraints and
authority remain outside this heuristic score. The trail is limited to one
two-agent change, so it cannot grant coherent ownership across a three-agent
conflict component.
## Roadmap
- v0 (done): tree + overlap + dependency channels, noisy-OR risk, TCAS advisories,
priority/steer, 3D embedding, full test coverage.
- v0 (source present): tree + overlap + dependency channels, noisy-OR-shaped
risk score, advisory/priority prototype and display embedding.
- v1 shadow (source present): passive two-agent observation/advisory/ACK/scope
revision/re-observation receipts; no active steering or measured benefit.
- v1: call-graph & symbol read/write channels; intent look-ahead; closure-rate
escalation wired to live session diffs.
- v2: cross-machine airspace over Tailscale (teammate agents enter the same
+366
View File
@@ -0,0 +1,366 @@
'use strict';
// Passive, local event trail. Receipt hashes detect accidental changes to these
// bytes; they do not authenticate a producer, authorize a scope or prove delivery.
const crypto = require('node:crypto');
const { collisionRisk, DEFAULTS } = require('./distance');
const VERSION = 1;
const ZERO_HASH = '0'.repeat(64);
const MAX_RECEIPTS = 16;
function invalid() { throw new Error('Invalid shadow coordination input.'); }
function object(value) {
if (!value || typeof value !== 'object' || Array.isArray(value) ||
![Object.prototype, null].includes(Object.getPrototypeOf(value))) invalid();
const keys = Reflect.ownKeys(value);
if (keys.length > 64) invalid();
for (const key of keys) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (typeof key !== 'string' || !Object.hasOwn(value, key) ||
!Object.hasOwn(descriptor, 'value') || !descriptor.enumerable) invalid();
}
return value;
}
function shape(value, allowed, required = allowed) {
const row = object(value);
if (Reflect.ownKeys(row).some(key => !allowed.includes(key)) ||
required.some(key => !Object.hasOwn(row, key))) invalid();
return row;
}
function array(value, max) {
if (!Array.isArray(value) || value.length > max ||
Object.getPrototypeOf(value) !== Array.prototype) invalid();
const keys = Reflect.ownKeys(value);
if (keys.length !== value.length + 1 || !keys.includes('length')) invalid();
for (let index = 0; index < value.length; index += 1) {
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
if (!descriptor || !Object.hasOwn(descriptor, 'value') || !descriptor.enumerable) invalid();
}
return value;
}
function identifier(value) {
if (typeof value !== 'string' || value.length > 120 ||
!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(value) ||
['__proto__', 'constructor', 'prototype'].includes(value)) invalid();
return value;
}
function timestamp(value) {
if (typeof value !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(value) ||
!Number.isFinite(Date.parse(value)) || new Date(value).toISOString() !== value) invalid();
return value;
}
function path(value) {
if (typeof value !== 'string' || !value || value.length > 256 ||
value.startsWith('/') || /^[A-Za-z]:/.test(value) || value.includes('\\') ||
value.split('/').some(part => !part || ['.', '..', '__proto__', 'constructor', 'prototype'].includes(part)) ||
[...value].some(char => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) invalid();
return value;
}
function paths(value, max = 32) {
array(value, max);
const result = value.map(path);
if (new Set(result).size !== result.length) invalid();
return result.sort();
}
function observation(value) {
const row = shape(value, ['agentId', 'taskId', 'repoId', 'baseRevision', 'scopeRevision',
'footprint', 'readPaths', 'writePaths', 'observedAt', 'expiresAt']);
if (typeof row.baseRevision !== 'string' || !/^[a-f0-9]{40}$/.test(row.baseRevision) ||
!Number.isSafeInteger(row.scopeRevision) || row.scopeRevision < 1 ||
!['complete', 'incomplete'].includes(row.footprint)) invalid();
const observedAt = timestamp(row.observedAt);
const expiresAt = timestamp(row.expiresAt);
if (Date.parse(expiresAt) <= Date.parse(observedAt)) invalid();
return { agentId: identifier(row.agentId), taskId: identifier(row.taskId),
repoId: identifier(row.repoId), baseRevision: row.baseRevision,
scopeRevision: row.scopeRevision, footprint: row.footprint,
readPaths: paths(row.readPaths), writePaths: paths(row.writePaths), observedAt, expiresAt };
}
function pair(value) {
array(value, 2);
if (value.length !== 2) invalid();
const result = value.map(observation).sort((a, b) => a.agentId < b.agentId ? -1 : a.agentId > b.agentId ? 1 : 0);
if (result[0].agentId === result[1].agentId) invalid();
return result;
}
function graph(value = { adjacency: {} }) {
const row = shape(value, ['adjacency']);
const adjacency = object(row.adjacency);
const entries = Object.entries(adjacency).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
if (entries.length > 64) invalid();
const normalized = Object.create(null);
let edgeCount = 0;
for (const [name, edges] of entries) {
edgeCount += Array.isArray(edges) ? edges.length : 257;
if (edgeCount > 256) invalid();
normalized[path(name)] = paths(edges, 16);
}
return { adjacency: normalized };
}
function sameTask(a, b) {
return a.agentId === b.agentId && a.taskId === b.taskId &&
a.repoId === b.repoId && a.baseRevision === b.baseRevision;
}
function intersection(a, b) { return a.some(item => b.includes(item)); }
function samePaths(a, b) { return a.length === b.length && a.every((item, index) => item === b[index]); }
function directHazard(a, b) {
if (intersection(a.writePaths, b.writePaths)) return 'write-write';
if (intersection(a.writePaths, b.readPaths) || intersection(b.writePaths, a.readPaths)) return 'write-read';
return null;
}
function advisory(observations, dependencyGraph, at) {
const [a, b] = observations;
const hazard = directHazard(a, b);
const aAll = [...new Set([...a.readPaths, ...a.writePaths])].sort();
const bAll = [...new Set([...b.readPaths, ...b.writePaths])].sort();
const first = collisionRisk({ files: a.writePaths.map(name => ({ path: name })) },
{ files: bAll.map(name => ({ path: name })) }, dependencyGraph).risk;
const second = collisionRisk({ files: b.writePaths.map(name => ({ path: name })) },
{ files: aAll.map(name => ({ path: name })) }, dependencyGraph).risk;
const risk = Math.max(first, second);
let reason = null;
if (a.repoId !== b.repoId || a.baseRevision !== b.baseRevision) reason = 'incomparable-context';
else if (observations.some(item => Date.parse(item.observedAt) > Date.parse(at) ||
Date.parse(item.expiresAt) <= Date.parse(at))) reason = 'stale-or-future-observation';
else if (observations.some(item => item.footprint === 'incomplete')) reason = 'incomplete-footprint';
const state = reason ? 'unknown' : hazard || risk >= DEFAULTS.thresholds.ta
? 'advisory' : 'no-advisory-in-scope';
return { state, reason, hazard: reason === 'incomparable-context' ? null : hazard,
heuristicRisk: risk, scoreKind: 'uncalibrated-three-channel-heuristic',
authority: 'observation-only', outcome: 'unadjudicated' };
}
function encode(value) {
if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) {
return JSON.stringify(value);
}
if (Array.isArray(value)) {
const items = [];
for (let index = 0; index < value.length; index += 1) items.push(encode(value[index]));
return `[${items.join(',')}]`;
}
if (!value || typeof value !== 'object') invalid();
const fields = [];
for (const key of Object.keys(value)) fields.push(`${JSON.stringify(key)}:${encode(value[key])}`);
return `{${fields.join(',')}}`;
}
function digest(trailId, receipt, payload = receipt.payload) {
const encoded = encode([VERSION, trailId, receipt.id, receipt.kind, receipt.at,
receipt.previousHash, payload]);
return crypto.createHash('sha256').update('ecc.tcas.shadow.v1\n').update(encoded).digest('hex');
}
function newReceipt(trail, id, kind, at, payload) {
if (trail.receipts.length >= MAX_RECEIPTS) invalid();
const previousHash = trail.receipts.at(-1)?.hash || ZERO_HASH;
const row = { version: VERSION, id: identifier(id), kind, at: timestamp(at), previousHash, payload };
return { ...row, hash: digest(trail.trailId, row) };
}
function extend(trail, id, kind, at, payload) {
verifyShadowTrail(trail);
const copied = copyTrail(trail);
const next = { ...copied, receipts: [...copied.receipts, newReceipt(copied, id, kind, at, payload)] };
verifyShadowTrail(next);
return next;
}
function normalizePayload(kind, value) {
if (kind === 'observation' || kind === 'reobservation') {
const row = shape(value, ['observations', 'graph']);
return { observations: pair(row.observations), graph: graph(row.graph) };
}
if (kind === 'advisory') {
const row = shape(value, ['state', 'reason', 'hazard', 'heuristicRisk', 'scoreKind', 'authority', 'outcome']);
if (!['unknown', 'advisory', 'no-advisory-in-scope'].includes(row.state) ||
![null, 'incomparable-context', 'stale-or-future-observation',
'incomplete-footprint'].includes(row.reason) ||
![null, 'write-write', 'write-read'].includes(row.hazard) ||
!Number.isFinite(row.heuristicRisk) || row.heuristicRisk < 0 || row.heuristicRisk > 1 ||
row.scoreKind !== 'uncalibrated-three-channel-heuristic' ||
row.authority !== 'observation-only' || row.outcome !== 'unadjudicated') invalid();
return { state: row.state, reason: row.reason, hazard: row.hazard,
heuristicRisk: row.heuristicRisk, scoreKind: row.scoreKind,
authority: row.authority, outcome: row.outcome };
}
if (kind === 'acknowledgement') {
const row = shape(value, ['agentId', 'scopeRevision', 'advisoryId']);
if (!Number.isSafeInteger(row.scopeRevision) || row.scopeRevision < 1) invalid();
return { agentId: identifier(row.agentId), scopeRevision: row.scopeRevision,
advisoryId: identifier(row.advisoryId) };
}
if (kind === 'scope-revision') {
const row = shape(value, ['agentId', 'taskId', 'repoId', 'baseRevision',
'previousScopeRevision', 'newScopeRevision', 'newReadPaths', 'newWritePaths',
'authorizationRef', 'authority']);
if (typeof row.baseRevision !== 'string' || !/^[a-f0-9]{40}$/.test(row.baseRevision) ||
!Number.isSafeInteger(row.previousScopeRevision) || row.previousScopeRevision < 1 ||
!Number.isSafeInteger(row.newScopeRevision) || row.newScopeRevision <= row.previousScopeRevision ||
row.authority !== 'declared-only') invalid();
return { agentId: identifier(row.agentId), taskId: identifier(row.taskId),
repoId: identifier(row.repoId), baseRevision: row.baseRevision,
previousScopeRevision: row.previousScopeRevision, newScopeRevision: row.newScopeRevision,
newReadPaths: paths(row.newReadPaths), newWritePaths: paths(row.newWritePaths),
authorizationRef: identifier(row.authorizationRef), authority: row.authority };
}
invalid();
}
function copyTrail(trail) {
return { version: VERSION, trailId: trail.trailId,
receipts: trail.receipts.map(receipt => ({ version: receipt.version,
id: receipt.id, kind: receipt.kind, at: receipt.at,
previousHash: receipt.previousHash, payload: normalizePayload(receipt.kind, receipt.payload),
hash: receipt.hash })) };
}
function transition(state, receipt) {
const payload = normalizePayload(receipt.kind, receipt.payload);
if (receipt.kind === 'observation') {
if (state.observations) invalid();
return { ...state, observations: payload.observations, graph: payload.graph, awaitingAdvisory: true };
}
if (receipt.kind === 'advisory') {
if (!state.awaitingAdvisory || !state.observations) invalid();
const expected = advisory(state.observations, state.graph, receipt.at);
if (encode(payload) !== encode(expected)) invalid();
return { ...state, latestAdvisory: expected, advisoryId: receipt.id,
awaitingAdvisory: false, acknowledgements: [] };
}
if (state.awaitingAdvisory || !state.latestAdvisory) invalid();
if (receipt.kind === 'acknowledgement') {
if (state.latestAdvisory.state !== 'advisory' || state.scopeChange || state.reobserved ||
payload.advisoryId !== state.advisoryId ||
state.acknowledgements.includes(payload.agentId)) invalid();
const member = state.observations.find(item => item.agentId === payload.agentId);
if (!member || member.scopeRevision !== payload.scopeRevision ||
Date.parse(member.expiresAt) <= Date.parse(receipt.at)) invalid();
return { ...state, acknowledgements: [...state.acknowledgements, payload.agentId],
acknowledgedBy: [...state.acknowledgedBy, payload.agentId] };
}
if (receipt.kind === 'scope-revision') {
if (state.latestAdvisory.state !== 'advisory' || state.scopeChange ||
state.reobserved || state.acknowledgements.length !== 2) invalid();
const member = state.observations.find(item => item.agentId === payload.agentId);
if (!member || !sameTask(member, payload) ||
member.scopeRevision !== payload.previousScopeRevision ||
state.observations.some(item => Date.parse(item.expiresAt) <= Date.parse(receipt.at) ||
Date.parse(item.observedAt) > Date.parse(receipt.at))) invalid();
return { ...state, scopeChange: { ...payload, at: receipt.at } };
}
if (receipt.kind === 'reobservation') {
if (!state.scopeChange || state.reobserved) invalid();
for (const member of payload.observations) {
const old = state.observations.find(item => item.agentId === member.agentId);
if (!old || !sameTask(old, member) ||
member.scopeRevision !== (member.agentId === state.scopeChange.agentId
? state.scopeChange.newScopeRevision : old.scopeRevision) ||
!samePaths(member.readPaths, member.agentId === state.scopeChange.agentId
? state.scopeChange.newReadPaths : old.readPaths) ||
!samePaths(member.writePaths, member.agentId === state.scopeChange.agentId
? state.scopeChange.newWritePaths : old.writePaths) ||
Date.parse(member.observedAt) < Date.parse(state.scopeChange.at)) invalid();
}
return { ...state, observations: payload.observations, graph: payload.graph,
reobserved: true, awaitingAdvisory: true };
}
invalid();
}
function inspect(trail) {
const row = shape(trail, ['version', 'trailId', 'receipts']);
if (row.version !== VERSION) invalid();
array(row.receipts, MAX_RECEIPTS);
if (row.receipts.length < 2) invalid();
identifier(row.trailId);
let previousHash = ZERO_HASH;
let previousAt = -Infinity;
let state = { observations: null, awaitingAdvisory: false,
acknowledgements: [], acknowledgedBy: [] };
const ids = new Set();
for (const receipt of row.receipts) {
shape(receipt, ['version', 'id', 'kind', 'at', 'previousHash', 'payload', 'hash']);
const id = identifier(receipt.id);
const at = timestamp(receipt.at);
const payload = normalizePayload(receipt.kind, receipt.payload);
if (encode(receipt.payload) !== encode(payload)) invalid();
if (receipt.version !== VERSION || ids.has(id) || Date.parse(at) < previousAt ||
typeof receipt.previousHash !== 'string' || typeof receipt.hash !== 'string' ||
!/^[a-f0-9]{64}$/.test(receipt.previousHash) ||
!/^[a-f0-9]{64}$/.test(receipt.hash) ||
receipt.previousHash !== previousHash || receipt.hash !== digest(row.trailId, receipt, payload)) invalid();
state = transition(state, { ...receipt, payload });
ids.add(id);
previousAt = Date.parse(at);
previousHash = receipt.hash;
}
if (state.awaitingAdvisory) invalid();
return state;
}
function guard(fn) {
try { return fn(); } catch { invalid(); }
}
function verifyShadowTrail(trail) { return guard(() => { inspect(trail); return true; }); }
function createShadowTrail(input) {
const row = shape(input, ['trailId', 'observations', 'at', 'graph'],
['trailId', 'observations', 'at']);
const trailId = identifier(row.trailId);
const at = timestamp(row.at);
const observations = pair(row.observations);
const dependencyGraph = graph(row.graph);
const blank = { version: VERSION, trailId, receipts: [] };
const first = newReceipt(blank, `${trailId}:observation-0`, 'observation', at,
{ observations, graph: dependencyGraph });
const partial = { ...blank, receipts: [first] };
const second = newReceipt(partial, `${trailId}:advisory-0`, 'advisory', at,
advisory(observations, dependencyGraph, at));
const result = { ...blank, receipts: [first, second] };
verifyShadowTrail(result);
return result;
}
function appendAcknowledgement(trail, input) {
const row = shape(input, ['id', 'agentId', 'scopeRevision', 'at']);
const state = inspect(trail);
return extend(trail, row.id, 'acknowledgement', row.at,
{ agentId: row.agentId, scopeRevision: row.scopeRevision, advisoryId: state.advisoryId });
}
function appendScopeRevision(trail, input) {
const row = shape(input, ['id', 'agentId', 'previousScopeRevision',
'newScopeRevision', 'newReadPaths', 'newWritePaths', 'authorizationRef', 'at']);
const state = inspect(trail);
const member = state.observations.find(item => item.agentId === row.agentId);
if (!member) invalid();
return extend(trail, row.id, 'scope-revision', row.at,
{ agentId: row.agentId, taskId: member.taskId, repoId: member.repoId,
baseRevision: member.baseRevision, previousScopeRevision: row.previousScopeRevision,
newScopeRevision: row.newScopeRevision, newReadPaths: paths(row.newReadPaths),
newWritePaths: paths(row.newWritePaths), authorizationRef: row.authorizationRef,
authority: 'declared-only' });
}
function appendReobservation(trail, input) {
const row = shape(input, ['id', 'observations', 'at', 'graph'], ['id', 'observations', 'at']);
const state = inspect(trail);
const at = timestamp(row.at);
const observations = pair(row.observations);
const dependencyGraph = graph(row.graph === undefined ? state.graph : row.graph);
if (trail.receipts.length > MAX_RECEIPTS - 2) invalid();
const copied = copyTrail(trail);
const first = newReceipt(copied, row.id, 'reobservation', at, { observations, graph: dependencyGraph });
const partial = { ...copied, receipts: [...copied.receipts, first] };
const second = newReceipt(partial, `${row.id}:advisory`, 'advisory', at,
advisory(observations, dependencyGraph, at));
const result = { ...copied, receipts: [...partial.receipts, second] };
verifyShadowTrail(result);
return result;
}
function summarizeShadowTrail(trail) {
const state = inspect(trail);
return { version: VERSION, mode: 'shadow-read-only', trailId: trail.trailId,
receiptCount: trail.receipts.length, latestAdvisory: { ...state.latestAdvisory },
latestAdvisoryId: state.advisoryId,
latestAdvisoryAcknowledgedBy: [...state.acknowledgements].sort(),
historicalAcknowledgedBy: [...new Set(state.acknowledgedBy)].sort(),
scopeChange: state.scopeChange ? { ...state.scopeChange } : null,
outcome: 'unadjudicated' };
}
module.exports = {
createShadowTrail: input => guard(() => createShadowTrail(input)),
appendAcknowledgement: (trail, input) => guard(() => appendAcknowledgement(trail, input)),
appendScopeRevision: (trail, input) => guard(() => appendScopeRevision(trail, input)),
appendReobservation: (trail, input) => guard(() => appendReobservation(trail, input)),
verifyShadowTrail,
summarizeShadowTrail: trail => guard(() => summarizeShadowTrail(trail))
};
@@ -0,0 +1,233 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const {
createShadowTrail, appendAcknowledgement, appendScopeRevision,
appendReobservation, verifyShadowTrail, summarizeShadowTrail
} = require('../../scripts/lib/agent-proximity/shadow-trail');
const at = '2026-09-15T12:00:00.000Z';
const later = '2026-09-15T12:01:00.000Z';
const latest = '2026-09-15T12:02:00.000Z';
const baseRevision = 'a'.repeat(40);
function observation(agentId, overrides = {}) {
return {
agentId, taskId: `task-${agentId}`, repoId: 'ecc', baseRevision,
scopeRevision: 1, footprint: 'complete', readPaths: [],
writePaths: ['src/shared.js'], observedAt: '2026-09-15T11:59:00.000Z',
expiresAt: '2026-09-15T12:05:00.000Z', ...overrides
};
}
function start(a = observation('a'), b = observation('b'), options = {}) {
return createShadowTrail({ trailId: 'shadow-1', observations: [a, b], at, ...options });
}
function bothAcknowledge(trail) {
const first = appendAcknowledgement(trail, { id: 'ack-a', agentId: 'a', scopeRevision: 1, at: later });
return appendAcknowledgement(first, { id: 'ack-b', agentId: 'b', scopeRevision: 1, at: later });
}
test('records a copy-on-append passive observation → advisory → ACK → scope revision → re-observation chain', () => {
const initial = start();
assert.equal(summarizeShadowTrail(initial).latestAdvisory.state, 'advisory');
assert.equal(summarizeShadowTrail(initial).latestAdvisory.hazard, 'write-write');
const acknowledged = bothAcknowledge(initial);
assert.deepEqual(summarizeShadowTrail(acknowledged).latestAdvisoryAcknowledgedBy, ['a', 'b']);
const revised = appendScopeRevision(acknowledged, {
id: 'scope-b2', agentId: 'b', previousScopeRevision: 1,
newScopeRevision: 2, newReadPaths: [], newWritePaths: ['docs/guide.md'],
authorizationRef: 'declared-contract-42', at: later
});
const complete = appendReobservation(revised, {
id: 'reobserve-1', at: latest,
observations: [observation('a', { writePaths: ['src/shared.js'], observedAt: later }),
observation('b', { scopeRevision: 2, writePaths: ['docs/guide.md'], observedAt: later })]
});
const summary = summarizeShadowTrail(complete);
assert.equal(summary.mode, 'shadow-read-only');
assert.equal(summary.latestAdvisory.state, 'no-advisory-in-scope');
assert.equal(summary.outcome, 'unadjudicated');
assert.equal(summary.scopeChange.authority, 'declared-only');
assert.deepEqual(summary.historicalAcknowledgedBy, ['a', 'b']);
assert.deepEqual(summary.latestAdvisoryAcknowledgedBy, []);
assert.equal(verifyShadowTrail(JSON.parse(JSON.stringify(complete))), true);
assert.equal(initial.receipts.length, 2);
assert.equal(acknowledged.receipts.length, 4);
assert.equal(complete.receipts.length, 7);
for (let i = 1; i < complete.receipts.length; i += 1) {
assert.equal(complete.receipts[i].previousHash, complete.receipts[i - 1].hash);
}
assert.ok(!JSON.stringify(complete).includes('CANARY_SECRET'));
});
test('read/read is quiet, write/read is a direct hazard, and dependency proximity reuses shipped scorer', () => {
const quiet = start(observation('a', { readPaths: ['src/shared.js'], writePaths: [] }),
observation('b', { readPaths: ['src/shared.js'], writePaths: [] }));
assert.equal(summarizeShadowTrail(quiet).latestAdvisory.state, 'no-advisory-in-scope');
const hazard = start(observation('a', { readPaths: ['src/shared.js'], writePaths: [] }),
observation('b', { readPaths: [], writePaths: ['src/shared.js'] }));
assert.equal(summarizeShadowTrail(hazard).latestAdvisory.hazard, 'write-read');
const graph = { adjacency: { 'apps/web/page.js': ['packages/core/util.js'], 'packages/core/util.js': [] } };
const coupled = start(observation('a', { writePaths: ['apps/web/page.js'] }),
observation('b', { writePaths: ['packages/core/util.js'] }), { graph });
assert.equal(summarizeShadowTrail(coupled).latestAdvisory.state, 'advisory');
assert.equal(summarizeShadowTrail(coupled).latestAdvisory.hazard, null);
});
test('known overlap cannot be clear, while incomplete, stale and incomparable critical data remain unknown', () => {
const partialHazard = start(observation('a', { footprint: 'incomplete' }), observation('b'));
assert.equal(summarizeShadowTrail(partialHazard).latestAdvisory.state, 'unknown');
assert.equal(summarizeShadowTrail(partialHazard).latestAdvisory.hazard, 'write-write');
const partialQuiet = start(observation('a', { footprint: 'incomplete', writePaths: [] }),
observation('b', { writePaths: ['docs/guide.md'] }));
assert.equal(summarizeShadowTrail(partialQuiet).latestAdvisory.state, 'unknown');
const stale = start(observation('a', { expiresAt: '2026-09-15T11:59:30.000Z' }), observation('b'));
assert.equal(summarizeShadowTrail(stale).latestAdvisory.state, 'unknown');
const foreignBase = start(observation('a'), observation('b', { baseRevision: 'b'.repeat(40) }));
assert.equal(summarizeShadowTrail(foreignBase).latestAdvisory.state, 'unknown');
});
test('stale or crossed ACK, unacknowledged scope change and mismatched re-observation fail closed', () => {
const initial = start();
assert.throws(() => appendAcknowledgement(initial, { id: 'bad-ack', agentId: 'a', scopeRevision: 2, at: later }), /Invalid shadow/);
assert.throws(() => appendAcknowledgement(initial, { id: 'bad-ack', agentId: 'c', scopeRevision: 1, at: later }), /Invalid shadow/);
assert.throws(() => appendScopeRevision(initial, { id: 'premature', agentId: 'b', previousScopeRevision: 1,
newScopeRevision: 2, newReadPaths: [], newWritePaths: ['docs/guide.md'],
authorizationRef: 'evidence-1', at: later }), /Invalid shadow/);
const ack = bothAcknowledge(initial);
assert.throws(() => appendAcknowledgement(ack, { id: 'third-ack', agentId: 'a', scopeRevision: 1, at: later }), /Invalid shadow/);
const revised = appendScopeRevision(ack, { id: 'scope-b2', agentId: 'b', previousScopeRevision: 1,
newScopeRevision: 2, newReadPaths: [], newWritePaths: ['docs/guide.md'],
authorizationRef: 'evidence-1', at: later });
assert.throws(() => appendReobservation(revised, { id: 'wrong-scope', at: latest,
observations: [observation('a'), observation('b')] }), /Invalid shadow/);
assert.throws(() => appendReobservation(revised, { id: 'foreign-task', at: latest,
observations: [observation('a'), observation('b', { scopeRevision: 2, taskId: 'other-task' })] }), /Invalid shadow/);
assert.throws(() => appendReobservation(revised, { id: 'different-declaration', at: latest,
observations: [observation('a'), observation('b', { scopeRevision: 2,
writePaths: ['src/not-declared.js'], observedAt: later })] }), /Invalid shadow/);
assert.throws(() => appendReobservation(revised, { id: 'changed-other-agent', at: latest,
observations: [observation('a', { writePaths: ['src/changed-without-revision.js'], observedAt: later }),
observation('b', { scopeRevision: 2, writePaths: ['docs/guide.md'], observedAt: later })] }), /Invalid shadow/);
});
test('unknown critical footprints cannot be used as an acknowledged clear-scoped resolution', () => {
const unknown = start(observation('a', { footprint: 'incomplete', writePaths: [] }), observation('b'));
assert.throws(() => appendAcknowledgement(unknown, { id: 'ack-unknown', agentId: 'a',
scopeRevision: 1, at: later }), /Invalid shadow/);
});
test('receipt hashes bind the trail identity and reject cross-trail relabeling', () => {
const relabeled = JSON.parse(JSON.stringify(start()));
relabeled.trailId = 'different-shadow-trail';
assert.throws(() => verifyShadowTrail(relabeled), /Invalid shadow/);
});
test('a persisted receipt with reordered path bytes is noncanonical and rejected', () => {
const trail = start(observation('a', { writePaths: ['src/a.js', 'src/b.js'] }),
observation('b', { writePaths: ['src/b.js'] }));
const reordered = JSON.parse(JSON.stringify(trail));
reordered.receipts[0].payload.observations[0].writePaths.reverse();
assert.throws(() => verifyShadowTrail(reordered), /Invalid shadow/);
});
test('dependency graph insertion order canonicalizes to the same receipt content', () => {
const a = start(observation('a'), observation('b'), { graph: { adjacency: {
'src/z.js': ['src/a.js'], 'src/a.js': []
} } });
const b = start(observation('a'), observation('b'), { graph: { adjacency: {
'src/a.js': [], 'src/z.js': ['src/a.js']
} } });
assert.equal(a.receipts[0].hash, b.receipts[0].hash);
});
test('append results keep independent copies of earlier receipt payloads', () => {
const initial = start();
const appended = appendAcknowledgement(initial, {
id: 'copy-ack', agentId: 'a', scopeRevision: 1, at: later
});
initial.receipts[0].payload.observations[0].writePaths[0] = 'src/changed.js';
assert.throws(() => verifyShadowTrail(initial), /Invalid shadow/);
assert.equal(verifyShadowTrail(appended), true);
});
test('verification rejects nested serialization hooks, sparse and accessor arrays before execution', () => {
const original = start();
let callbackRuns = 0;
const hooked = JSON.parse(JSON.stringify(original));
hooked.receipts[0].payload.observations[0].writePaths.toJSON = () => {
callbackRuns += 1;
return ['src/shared.js'];
};
assert.throws(() => verifyShadowTrail(hooked), /Invalid shadow/);
assert.equal(callbackRuns, 0);
const accessor = JSON.parse(JSON.stringify(original));
Object.defineProperty(accessor.receipts[0].payload.observations, '0', {
enumerable: true, get() { callbackRuns += 1; return observation('a'); }
});
assert.throws(() => verifyShadowTrail(accessor), /Invalid shadow/);
assert.equal(callbackRuns, 0);
const sparse = JSON.parse(JSON.stringify(original));
delete sparse.receipts[0].payload.observations[0].writePaths[0];
assert.throws(() => verifyShadowTrail(sparse), /Invalid shadow/);
});
test('hash fields reject coercible objects without invoking their conversion hooks', () => {
const trail = JSON.parse(JSON.stringify(start()));
let callbackRuns = 0;
trail.receipts[0].hash = { toString() { callbackRuns += 1; return '0'.repeat(64); } };
assert.throws(() => verifyShadowTrail(trail), /Invalid shadow/);
assert.equal(callbackRuns, 0);
});
test('receipt verification never invokes inherited JSON serialization hooks', () => {
const persisted = JSON.parse(JSON.stringify(start()));
let callbackRuns = 0;
Object.defineProperty(Array.prototype, 'toJSON', { configurable: true,
value() { callbackRuns += 1; return []; } });
try {
assert.equal(verifyShadowTrail(persisted), true);
assert.equal(callbackRuns, 0);
} finally {
delete Array.prototype.toJSON;
}
});
test('exotic verification failures are generic and do not echo private text', () => {
const trail = start();
const receipts = new Proxy(trail.receipts, {
getPrototypeOf() { throw new Error('CANARY_SECRET'); }
});
assert.throws(() => verifyShadowTrail({ ...trail, receipts }), error =>
/Invalid shadow/.test(error.message) && !error.message.includes('CANARY_SECRET'));
});
test('scope revision requires both acknowledged observations to remain current', () => {
const initial = start(observation('a'), observation('b', {
expiresAt: '2026-09-15T12:01:30.000Z'
}));
const acknowledged = bothAcknowledge(initial);
assert.throws(() => appendScopeRevision(acknowledged, {
id: 'scope-after-peer-expiry', agentId: 'a', previousScopeRevision: 1,
newScopeRevision: 2, newReadPaths: [], newWritePaths: ['docs/guide.md'],
authorizationRef: 'declared-contract-42', at: latest
}), /Invalid shadow/);
});
test('tampering, duplicate IDs, unknown fields and unbounded input cannot be accepted or echoed', () => {
const initial = start();
const tampered = JSON.parse(JSON.stringify(initial));
tampered.receipts[0].payload.observations[0].writePaths = ['CANARY_SECRET'];
assert.throws(() => verifyShadowTrail(tampered), /Invalid shadow/);
const poisoned = observation('a', { summary: 'CANARY_SECRET' });
assert.throws(() => start(poisoned, observation('b')), error =>
/Invalid shadow/.test(error.message) && !error.message.includes('CANARY_SECRET'));
assert.throws(() => start(observation('a', { writePaths: ['../escape'] }), observation('b')), /Invalid shadow/);
assert.throws(() => start(observation('a', { writePaths: Array(33).fill('src/a.js') }), observation('b')), /Invalid shadow/);
assert.throws(() => start(observation('a'), observation('b'), { graph: { adjacency: { '__proto__': ['src/a.js'] } } }), /Invalid shadow/);
assert.throws(() => appendAcknowledgement(initial, { id: initial.receipts[0].id, agentId: 'a',
scopeRevision: 1, at: later }), /Invalid shadow/);
});