Compare commits

...
212 changed files with 21629 additions and 296 deletions

No files matched your search

+1 -3
View File
@@ -155,9 +155,7 @@ jobs:
cp -R packages/contracts/src "$STAGING/packages/contracts/"
cp backend/package.json backend/tsconfig.json backend/mcp-coverage.json "$STAGING/backend/"
cp -R backend/src "$STAGING/backend/"
mkdir -p "$STAGING/backend/scripts/writer-profile"
cp backend/scripts/build-database-operator.mjs backend/scripts/database-writer-bootstrap-operator.mjs backend/scripts/database-writer-profile-operator.mts "$STAGING/backend/scripts/"
cp backend/scripts/writer-profile/operator-files.mjs backend/scripts/writer-profile/operator-contract.mjs backend/scripts/writer-profile/publisher.mjs backend/scripts/writer-profile/context.mjs backend/scripts/writer-profile/catalogue.mts backend/scripts/writer-profile/catalogue-value-bridge.ts backend/scripts/writer-profile/catalogue-projection.ts backend/scripts/writer-profile/catalogue-leaves.ts backend/scripts/writer-profile/application-writer-profile-catalogue.ts backend/scripts/writer-profile/application-writer-profile-catalogue-values.ts backend/scripts/writer-profile/application-writer-profile-catalogue-parent-policy.ts backend/scripts/writer-profile/application-writer-profile-canonical.ts backend/scripts/writer-profile/fixed44-build-sql.ts backend/scripts/writer-profile/fixed44-build-definition.ts backend/scripts/writer-profile/expanded44-data-policy.ts "${STAGING}/backend/scripts/writer-profile/"
node backend/scripts/stage-database-operator.mjs "$STAGING/backend/scripts"
mkdir -p "$STAGING/deploy/netflow-agent"
cp deploy/netflow-agent/install.sh "$STAGING/deploy/netflow-agent/"
if [ -d backend/drizzle ]; then
+7 -5
View File
@@ -16,7 +16,7 @@ permissions:
packages: write
jobs:
backend-preview:
if: ${{ gitea.event_name == 'workflow_dispatch' }}
if: ${{ gitea.event_name == 'workflow_dispatch' && gitea.ref == 'refs/heads/codex/mcp-acceptance-current-20261103' }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
@@ -24,6 +24,7 @@ jobs:
EXPECTED_SHA: ${{ inputs.expected_sha }}
DISPATCH_SHA: ${{ gitea.sha }}
DISPATCH_EVENT: ${{ gitea.event_name }}
DISPATCH_REF: ${{ gitea.ref }}
REPOSITORY: ${{ gitea.repository }}
RUN_ID: ${{ gitea.run_id }}
RUN_ATTEMPT: ${{ gitea.run_attempt }}
@@ -42,6 +43,7 @@ jobs:
run: |
set -euo pipefail
test "$DISPATCH_EVENT" = workflow_dispatch
test "$DISPATCH_REF" = refs/heads/codex/mcp-acceptance-current-20261103
[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]]
test "$EXPECTED_SHA" = "$DISPATCH_SHA"
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
@@ -88,9 +90,7 @@ jobs:
if [ -d backend/drizzle ]; then
cp -R backend/drizzle "$STAGING/backend/"
fi
mkdir -p "$STAGING/backend/scripts/writer-profile"
cp backend/scripts/build-database-operator.mjs backend/scripts/database-writer-bootstrap-operator.mjs backend/scripts/database-writer-profile-operator.mts "$STAGING/backend/scripts/"
cp backend/scripts/writer-profile/operator-files.mjs backend/scripts/writer-profile/operator-contract.mjs backend/scripts/writer-profile/publisher.mjs backend/scripts/writer-profile/context.mjs backend/scripts/writer-profile/catalogue.mts backend/scripts/writer-profile/catalogue-value-bridge.ts backend/scripts/writer-profile/catalogue-projection.ts backend/scripts/writer-profile/catalogue-leaves.ts backend/scripts/writer-profile/application-writer-profile-catalogue.ts backend/scripts/writer-profile/application-writer-profile-catalogue-values.ts backend/scripts/writer-profile/application-writer-profile-catalogue-parent-policy.ts backend/scripts/writer-profile/application-writer-profile-canonical.ts backend/scripts/writer-profile/fixed44-build-sql.ts backend/scripts/writer-profile/fixed44-build-definition.ts backend/scripts/writer-profile/expanded44-data-policy.ts "${STAGING}/backend/scripts/writer-profile/"
node backend/scripts/stage-database-operator.mjs "$STAGING/backend/scripts"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to existing Gitea registry
@@ -139,7 +139,7 @@ jobs:
if-no-files-found: error
retention-days: 14
frontend-preview:
if: ${{ gitea.event_name == 'workflow_dispatch' }}
if: ${{ gitea.event_name == 'workflow_dispatch' && gitea.ref == 'refs/heads/codex/mcp-acceptance-current-20261103' }}
runs-on: ubuntu-latest
timeout-minutes: 40
env:
@@ -147,6 +147,7 @@ jobs:
EXPECTED_SHA: ${{ inputs.expected_sha }}
DISPATCH_SHA: ${{ gitea.sha }}
DISPATCH_EVENT: ${{ gitea.event_name }}
DISPATCH_REF: ${{ gitea.ref }}
REPOSITORY: ${{ gitea.repository }}
RUN_ID: ${{ gitea.run_id }}
RUN_ATTEMPT: ${{ gitea.run_attempt }}
@@ -178,6 +179,7 @@ jobs:
run: |
set -euo pipefail
test "$DISPATCH_EVENT" = workflow_dispatch
test "$DISPATCH_REF" = refs/heads/codex/mcp-acceptance-current-20261103
[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]]
test "$EXPECTED_SHA" = "$DISPATCH_SHA"
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
+9 -6
View File
@@ -924,18 +924,21 @@ export default function TrafficPage() {
})
return () => { cancelled = true }
}
void Promise.all([
getTrafficFlows(backendUrl, range),
getFlowExporters(backendUrl, range),
getFlowClients(backendUrl, range),
]).then(([stats, exporters, clients]) => {
const loadFlowCards = async () => {
// These reads share the collector's constrained admission budget.
const stats = await getTrafficFlows(backendUrl, range)
if (cancelled) return
const exporters = await getFlowExporters(backendUrl, range)
if (cancelled) return
const clients = await getFlowClients(backendUrl, range)
if (cancelled) return
setFlowResult({ key: flowKey, receivedAt: Date.now(), stats, exporters: exporters.exporters, clients: clients.clients })
setSelectedId(prev => {
const list = flowScope === "users" ? clients.clients : exporters.exporters
return list.some(x => x.id === prev) ? prev : (list[0]?.id ?? "")
})
}).catch(e => {
}
void loadFlowCards().catch(e => {
if (!cancelled) setFlowResult({ key: flowKey, error: e instanceof Error ? e.message : "Не удалось загрузить потоки" })
})
return () => { cancelled = true }
@@ -0,0 +1,13 @@
BEGIN;
CREATE TABLE public.mcp_operation_replay_tombstones (
operation_id uuid PRIMARY KEY,
plan_id uuid NOT NULL UNIQUE,
connection_id uuid NOT NULL REFERENCES public.mcp_connections(id),
idempotency_digest text NOT NULL CHECK (idempotency_digest ~ '^[0-9a-f]{64}$'),
action_id text NOT NULL CHECK (length(action_id) BETWEEN 1 AND 200),
target_server_ids bigint[] NOT NULL CHECK (array_position(target_server_ids, NULL) IS NULL),
target_global boolean NOT NULL,
UNIQUE (connection_id,idempotency_digest)
);
INSERT INTO public.schema_migrations(id) VALUES ('0045_mcp_operation_replay_tombstones') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,186 @@
-- Inactive poll-history V1-to-V2 retirement. Application owner authenticates
-- both ciphertext versions; SQL independently fences identity and lifetime.
BEGIN;
LOCK TABLE public.server_poll_archives,public.server_poll_storage_admissions IN ACCESS EXCLUSIVE MODE;
ALTER TABLE public.server_poll_archives DROP CONSTRAINT server_poll_archives_version_check;
ALTER TABLE public.server_poll_archives ADD CONSTRAINT server_poll_archives_version_check
CHECK(version=1 OR (version=2 AND decoded_bytes<=24576 AND compressed_bytes<=24576 AND ciphertext_bytes<=33000));
CREATE OR REPLACE FUNCTION public.server_poll_archive_immutable() RETURNS trigger
LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='DELETE' AND public.server_poll_storage_gc_allowed(OLD.receipt_id) THEN RETURN OLD; END IF;
IF TG_OP='UPDATE' AND OLD.version=1 AND NEW.version=2
AND current_setting('mm.poll_retire_receipt',true)=OLD.receipt_id::text
AND NEW.ciphertext_bytes<=OLD.ciphertext_bytes
AND NEW.ciphertext ~ '^[A-Za-z0-9+/]+={0,2}$'
AND replace(encode(decode(NEW.ciphertext,'base64'),'base64'),E'\n','')=NEW.ciphertext
AND octet_length(decode(NEW.ciphertext,'base64'))=NEW.compressed_bytes+28
AND (to_jsonb(OLD)-ARRAY['version','source_digest','source_sha256','decoded_bytes','compressed_bytes','ciphertext','ciphertext_bytes','archive_proof'])=
(to_jsonb(NEW)-ARRAY['version','source_digest','source_sha256','decoded_bytes','compressed_bytes','ciphertext','ciphertext_bytes','archive_proof'])
AND EXISTS(
SELECT 1 FROM public.server_poll_storage_admissions a JOIN public.mcp_operations o ON o.id=a.execution_id AND o.connection_id=a.connection_id
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN public.mcp_operation_steps s ON s.operation_id=o.id AND s.step_index=0
JOIN public.mcp_operation_replay_tombstones t ON t.operation_id=o.id AND t.plan_id=p.id AND t.connection_id=o.connection_id
JOIN public.mcp_connections c ON c.id=o.connection_id AND c.owner_id=a.owner_id
WHERE a.receipt_id=OLD.receipt_id AND a.identity_kind='mcp' AND a.storage_state='compacted'
AND a.execution_id=OLD.execution_id AND a.connection_id=OLD.connection_id AND a.owner_id=OLD.owner_id
AND a.binding_proof=OLD.binding_proof AND a.context_proof=OLD.context_proof AND a.archive_bytes=OLD.ciphertext_bytes
AND a.original_activity_at=OLD.original_activity_at AND a.authority_expires_at=OLD.original_expires_at
AND o.state='applied' AND o.verification='passed' AND o.error_code IS NULL AND isfinite(o.finished_at)
AND o.finished_at<transaction_timestamp()-interval '180 days'
AND p.state='completed' AND p.operation_id='servers.poll' AND p.operation_version=1 AND p.approved_by IS NOT NULL
AND s.state='verified' AND s.finished_at IS NOT NULL AND t.action_id=p.operation_id
AND t.target_global AND cardinality(t.target_server_ids)>0
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'poll archive immutable outside exact retirement or original lifetime GC';
END $$;
CREATE FUNCTION public.server_poll_retirement_final_guard() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
IF NOT EXISTS(SELECT 1 FROM public.mcp_operation_replay_tombstones t
WHERE t.operation_id=NEW.execution_id AND t.connection_id=NEW.connection_id AND t.action_id='servers.poll'
AND t.target_global AND NOT EXISTS(SELECT 1 FROM public.mcp_plans p WHERE p.id=t.plan_id))
OR EXISTS(SELECT 1 FROM public.mcp_operations o WHERE o.id=NEW.execution_id)
OR NOT public.server_poll_storage_accounting_valid()
THEN RAISE EXCEPTION 'poll retirement is incomplete'; END IF;
RETURN NULL;
END $$;
CREATE CONSTRAINT TRIGGER server_poll_retirement_final_guard AFTER UPDATE ON public.server_poll_archives
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN(OLD.version=1 AND NEW.version=2)
EXECUTE FUNCTION public.server_poll_retirement_final_guard();
CREATE OR REPLACE FUNCTION server_poll_storage_admission_guard() RETURNS trigger LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
DECLARE retention integer; charged integer; r public.server_poll_archives%ROWTYPE;
BEGIN
PERFORM pg_advisory_xact_lock(hashtextextended('server-poll-storage-v1',0));
IF TG_OP='DELETE' THEN
IF NOT public.server_poll_storage_gc_allowed(OLD.receipt_id) OR EXISTS(SELECT 1 FROM public.server_poll_executions WHERE receipt_id=OLD.receipt_id)
OR EXISTS(SELECT 1 FROM public.server_poll_intents WHERE receipt_id=OLD.receipt_id) OR EXISTS(SELECT 1 FROM public.server_poll_acks WHERE receipt_id=OLD.receipt_id)
OR EXISTS(SELECT 1 FROM public.server_poll_receipts WHERE receipt_id=OLD.receipt_id) OR EXISTS(SELECT 1 FROM public.server_poll_quarantines WHERE receipt_id=OLD.receipt_id)
OR EXISTS(SELECT 1 FROM public.server_poll_archives WHERE receipt_id=OLD.receipt_id) THEN RAISE EXCEPTION 'poll lifetime or children prevent admission deletion'; END IF;
UPDATE public.server_poll_storage_usage SET admissions=admissions-CASE WHEN OLD.storage_state='full' THEN 1 ELSE 0 END,
compacted_admissions=compacted_admissions-CASE WHEN OLD.storage_state='compacted' THEN 1 ELSE 0 END,
archive_bytes=archive_bytes-COALESCE(OLD.archive_bytes,0),
reserved_rows=reserved_rows-CASE WHEN OLD.storage_state='full' THEN 4019 ELSE 2 END,
reserved_bytes=reserved_bytes-CASE WHEN OLD.storage_state='full' THEN 8421376 ELSE 32768+OLD.archive_bytes END
WHERE scope IN('global','owner:'||OLD.owner_id);
GET DIAGNOSTICS charged=ROW_COUNT; IF charged<>2 THEN RAISE EXCEPTION 'poll quota refund mismatch'; END IF; RETURN OLD;
END IF;
IF TG_OP='UPDATE' THEN
IF OLD.storage_state='compacted' AND NEW.storage_state='compacted'
AND current_setting('mm.poll_retire_receipt',true)=OLD.receipt_id::text
AND (to_jsonb(OLD)-'archive_bytes')=(to_jsonb(NEW)-'archive_bytes') THEN
SELECT * INTO r FROM public.server_poll_archives WHERE receipt_id=OLD.receipt_id AND version=2;
IF NOT FOUND OR r.ciphertext_bytes<>NEW.archive_bytes OR NEW.archive_bytes>OLD.archive_bytes
OR r.execution_id<>OLD.execution_id OR r.connection_id<>OLD.connection_id OR r.owner_id<>OLD.owner_id
THEN RAISE EXCEPTION 'retired poll charge source mismatch'; END IF;
UPDATE public.server_poll_storage_usage
SET archive_bytes=archive_bytes-OLD.archive_bytes+NEW.archive_bytes,
reserved_bytes=reserved_bytes-OLD.archive_bytes+NEW.archive_bytes
WHERE scope IN ('global','owner:'||OLD.owner_id) AND compacted_admissions>=1 AND archive_bytes>=OLD.archive_bytes;
GET DIAGNOSTICS charged=ROW_COUNT;
IF charged<>2 THEN RAISE EXCEPTION 'retired poll charge adjustment mismatch'; END IF;
RETURN NEW;
END IF;
-- This durable one-shot marker is written and ACKed in its OWN transaction
-- before archive construction. A committed/unknown claim never resets and
-- the worker never retries destructive work against an already claimed full
-- source. Claim time is maintenance metadata, not renewed execution activity.
IF OLD.storage_state='full' AND NEW.storage_state='full' AND OLD.compaction_claim IS NULL AND OLD.compaction_claimed_at IS NULL AND OLD.compaction_claim_xid IS NULL
AND NEW.compaction_claim IS NOT NULL AND NEW.compaction_claim_xid IS NULL
AND(to_jsonb(OLD)-ARRAY['compaction_claim','compaction_claimed_at','compaction_claim_xid'])=(to_jsonb(NEW)-ARRAY['compaction_claim','compaction_claimed_at','compaction_claim_xid'])
AND public.server_poll_storage_compact_allowed(OLD.receipt_id)
THEN NEW.compaction_claimed_at:=clock_timestamp(); NEW.compaction_claim_xid:=pg_current_xact_id(); RETURN NEW; END IF;
IF(to_jsonb(OLD)-ARRAY['claim_token','claim_attempted_at','used_rows','used_bytes','storage_state','compact_at','archive_bytes','original_activity_at']) IS DISTINCT FROM
(to_jsonb(NEW)-ARRAY['claim_token','claim_attempted_at','used_rows','used_bytes','storage_state','compact_at','archive_bytes','original_activity_at']) THEN RAISE EXCEPTION 'poll admission identity is immutable'; END IF;
IF OLD.storage_state<>'full' THEN RAISE EXCEPTION 'compacted poll identity cannot reopen or change'; END IF;
IF NEW.storage_state='compacted' THEN
SELECT * INTO r FROM public.server_poll_archives WHERE receipt_id=OLD.receipt_id AND compaction_xid=pg_current_xact_id();
IF NOT FOUND OR NEW.used_rows<>OLD.used_rows OR NEW.used_bytes<>OLD.used_bytes OR NEW.claim_token IS DISTINCT FROM OLD.claim_token
OR NEW.claim_attempted_at IS DISTINCT FROM OLD.claim_attempted_at OR NEW.archive_bytes<>r.ciphertext_bytes OR NEW.original_activity_at<>r.original_activity_at
OR EXISTS(SELECT 1 FROM public.server_poll_executions WHERE receipt_id=OLD.receipt_id)
OR EXISTS(SELECT 1 FROM public.server_poll_intents WHERE receipt_id=OLD.receipt_id) OR EXISTS(SELECT 1 FROM public.server_poll_acks WHERE receipt_id=OLD.receipt_id)
OR EXISTS(SELECT 1 FROM public.server_poll_receipts WHERE receipt_id=OLD.receipt_id) OR EXISTS(SELECT 1 FROM public.server_poll_quarantines WHERE receipt_id=OLD.receipt_id)
THEN RAISE EXCEPTION 'poll compaction requires the complete current-transaction archive'; END IF;
NEW.compact_at:=r.created_at;
UPDATE public.server_poll_storage_usage SET admissions=admissions-1,compacted_admissions=compacted_admissions+1,
archive_bytes=archive_bytes+r.ciphertext_bytes,reserved_rows=reserved_rows-4019+2,reserved_bytes=reserved_bytes-8421376+32768+r.ciphertext_bytes
WHERE scope IN('global','owner:'||OLD.owner_id) AND admissions>=1;
GET DIAGNOSTICS charged=ROW_COUNT; IF charged<>2 THEN RAISE EXCEPTION 'poll compaction charge mismatch'; END IF;
RETURN NEW;
END IF;
IF NEW.compact_at IS NOT NULL OR NEW.archive_bytes IS NOT NULL OR NEW.original_activity_at IS NOT NULL THEN RAISE EXCEPTION 'full poll cannot carry archive metadata'; END IF;
IF OLD.claim_token IS NOT NULL AND NEW.claim_token IS NULL AND OLD.claim_attempted_at IS NULL AND NEW.claim_attempted_at IS NOT NULL
AND NEW.used_rows=OLD.used_rows AND NEW.used_bytes=OLD.used_bytes AND OLD.authority_expires_at>clock_timestamp()
THEN NEW.claim_attempted_at:=clock_timestamp(); RETURN NEW; END IF;
IF OLD.claim_token IS NOT DISTINCT FROM NEW.claim_token AND OLD.claim_attempted_at IS NOT DISTINCT FROM NEW.claim_attempted_at
AND NEW.used_rows=OLD.used_rows+1 AND NEW.used_bytes>OLD.used_bytes THEN RETURN NEW; END IF;
RAISE EXCEPTION 'poll admission cannot refresh, reopen or refund itself';
END IF;
IF NEW.storage_state<>'full' OR NEW.compact_at IS NOT NULL OR NEW.archive_bytes IS NOT NULL OR NEW.original_activity_at IS NOT NULL
OR NEW.compaction_claim IS NOT NULL OR NEW.compaction_claimed_at IS NOT NULL OR NEW.compaction_claim_xid IS NOT NULL
OR NOT public.server_poll_storage_accounting_valid() THEN RAISE EXCEPTION 'new poll requires full reservation and exact ledger'; END IF;
SELECT requests_retention_days INTO retention FROM public.mcp_settings WHERE id=1;
IF retention IS NULL OR retention NOT BETWEEN 1 AND 365 OR NEW.authority_issued_at>clock_timestamp() OR NEW.authority_expires_at<=clock_timestamp()
OR NEW.claim_token IS NULL OR NEW.claim_attempted_at IS NOT NULL OR NEW.used_rows<>0 OR NEW.used_bytes<>0 THEN RAISE EXCEPTION 'new poll admission needs original live authority'; END IF;
IF EXISTS(SELECT 1 FROM public.server_poll_storage_admissions WHERE receipt_id=NEW.receipt_id OR(identity_kind=NEW.identity_kind AND execution_id=NEW.execution_id)) THEN RAISE EXCEPTION 'poll identity already spent'; END IF;
NEW.created_at:=clock_timestamp(); NEW.retention_days:=retention; NEW.safety_ms:=60000;
NEW.gc_not_before:=NEW.authority_expires_at+make_interval(days=>retention)+interval '60 seconds';
INSERT INTO public.server_poll_storage_usage(scope) VALUES('owner:'||NEW.owner_id) ON CONFLICT DO NOTHING;
IF(SELECT count(*) FROM public.server_poll_storage_usage WHERE scope IN('global','owner:'||NEW.owner_id)
AND admissions<CASE WHEN scope='global' THEN 128 ELSE 8 END
AND reserved_rows<=CASE WHEN scope='global' THEN 514432 ELSE 32152 END-4019
AND reserved_bytes<=CASE WHEN scope='global' THEN 1077936128 ELSE 67371008 END-8421376)<>2 THEN RAISE EXCEPTION 'poll admission quota exhausted'; END IF;
UPDATE public.server_poll_storage_usage SET admissions=admissions+1,reserved_rows=reserved_rows+4019,reserved_bytes=reserved_bytes+8421376 WHERE scope IN('global','owner:'||NEW.owner_id);
GET DIAGNOSTICS charged=ROW_COUNT; IF charged<>2 THEN RAISE EXCEPTION 'poll admission quota unavailable'; END IF;
RETURN NEW;
END $$;
CREATE OR REPLACE FUNCTION server_poll_storage_gc_allowed(id uuid) RETURNS boolean LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
DECLARE a public.server_poll_storage_admissions%ROWTYPE; retention integer; activity timestamptz; terminal_at timestamptz;
BEGIN
IF current_setting('mm.poll_storage_gc',true) IS DISTINCT FROM 'on' THEN RETURN false; END IF;
PERFORM pg_advisory_xact_lock(hashtextextended('server-poll-storage-v1',0));
SELECT * INTO a FROM public.server_poll_storage_admissions WHERE receipt_id=id FOR UPDATE;
IF NOT FOUND OR a.authority_expires_at>=clock_timestamp() OR a.gc_not_before>=clock_timestamp() THEN RETURN false; END IF;
SELECT requests_retention_days INTO retention FROM public.mcp_settings WHERE mcp_settings.id=1 FOR SHARE;
IF retention IS NULL OR retention NOT BETWEEN 1 AND 365 THEN RETURN false; END IF;
terminal_at:=a.created_at;
IF a.identity_kind='mcp' THEN
IF NOT pg_try_advisory_xact_lock(hashtextextended('mcp-operation:'||a.execution_id::text,0)) THEN RETURN false; END IF;
BEGIN
SELECT GREATEST(o.finished_at,p.expires_at) INTO terminal_at FROM public.mcp_operations o
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id JOIN public.mcp_connections c ON c.id=o.connection_id
WHERE o.id=a.execution_id AND o.connection_id=a.connection_id AND c.owner_id=a.owner_id AND p.operation_id='servers.poll'
AND o.state IN('applied','failed','unknown','rejected','partial','cancelled') AND o.finished_at IS NOT NULL FOR UPDATE OF o,p NOWAIT;
IF NOT FOUND THEN
IF a.storage_state<>'compacted' OR EXISTS(SELECT 1 FROM public.mcp_operations o WHERE o.id=a.execution_id) OR NOT EXISTS (
SELECT 1 FROM public.mcp_operation_replay_tombstones t JOIN public.mcp_connections c ON c.id=t.connection_id
WHERE t.operation_id=a.execution_id AND t.connection_id=a.connection_id AND c.owner_id=a.owner_id
AND t.action_id='servers.poll' AND t.target_global AND cardinality(t.target_server_ids)>0
AND NOT EXISTS(SELECT 1 FROM public.mcp_plans WHERE mcp_plans.id=t.plan_id)
) OR EXISTS(SELECT 1 FROM public.server_poll_archives r WHERE r.receipt_id=id AND
(r.version<>2 OR r.execution_id<>a.execution_id OR r.connection_id<>a.connection_id OR r.owner_id<>a.owner_id
OR r.original_activity_at IS DISTINCT FROM a.original_activity_at OR r.original_expires_at<>a.authority_expires_at
OR r.binding_proof<>a.binding_proof OR r.context_proof<>a.context_proof OR r.ciphertext_bytes<>a.archive_bytes))
THEN RETURN false; END IF;
terminal_at:=a.original_activity_at;
END IF;
IF terminal_at IS NULL THEN RETURN false; END IF;
EXCEPTION WHEN lock_not_available THEN RETURN false; END;
END IF;
IF a.storage_state='compacted' THEN activity:=GREATEST(a.original_activity_at,a.authority_expires_at,terminal_at);
ELSE
SELECT GREATEST(a.authority_expires_at,a.created_at,COALESCE(a.claim_attempted_at,a.created_at),terminal_at,
COALESCE((SELECT created_at FROM public.server_poll_executions WHERE receipt_id=id),a.created_at),
COALESCE((SELECT max(created_at) FROM public.server_poll_intents WHERE receipt_id=id),a.created_at),
COALESCE((SELECT max(created_at) FROM public.server_poll_acks WHERE receipt_id=id),a.created_at),
COALESCE((SELECT created_at FROM public.server_poll_receipts WHERE receipt_id=id),a.created_at),
COALESCE((SELECT created_at FROM public.server_poll_quarantines WHERE receipt_id=id),a.created_at)) INTO activity;
END IF;
RETURN isfinite(activity) AND activity+make_interval(days=>GREATEST(a.retention_days,retention))+a.safety_ms*interval '1 millisecond'<clock_timestamp();
END $$;
INSERT INTO public.schema_migrations(id) VALUES ('0046_server_poll_operation_retirement') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,40 @@
BEGIN;
CREATE TABLE public.server_poll_retirement_claims (
receipt_id uuid PRIMARY KEY REFERENCES public.server_poll_storage_admissions(receipt_id) ON DELETE CASCADE,
old_archive_proof text NOT NULL CHECK(old_archive_proof ~ '^[0-9a-f]{64}$'),
claim_token uuid NOT NULL,
claim_xid xid8 NOT NULL DEFAULT pg_current_xact_id()
);
CREATE FUNCTION public.server_poll_retirement_claim_immutable() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.server_poll_archives r WHERE r.receipt_id=NEW.receipt_id
AND r.version=1 AND r.archive_proof=NEW.old_archive_proof
) THEN RETURN NEW; END IF;
IF TG_OP='DELETE' AND pg_trigger_depth()>1 AND NOT EXISTS(
SELECT 1 FROM public.server_poll_storage_admissions a WHERE a.receipt_id=OLD.receipt_id
) THEN RETURN OLD; END IF;
RAISE EXCEPTION 'poll retirement claim cannot be replaced or reopened';
END $$;
CREATE TRIGGER server_poll_retirement_claim_immutable
BEFORE INSERT OR UPDATE OR DELETE ON public.server_poll_retirement_claims
FOR EACH ROW EXECUTE FUNCTION public.server_poll_retirement_claim_immutable();
CREATE TRIGGER server_poll_retirement_claim_no_truncate
BEFORE TRUNCATE ON public.server_poll_retirement_claims
FOR EACH STATEMENT EXECUTE FUNCTION public.server_poll_retirement_claim_immutable();
CREATE FUNCTION public.server_poll_retirement_requires_claim() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
IF NOT EXISTS(SELECT 1 FROM public.server_poll_retirement_claims c
WHERE c.receipt_id=OLD.receipt_id AND c.old_archive_proof=OLD.archive_proof
AND c.claim_token::text=current_setting('mm.poll_retire_claim',true)
AND c.claim_xid<>pg_current_xact_id())
THEN RAISE EXCEPTION 'poll retirement requires prior exact claim'; END IF;
RETURN NEW;
END $$;
CREATE TRIGGER server_poll_retirement_requires_claim BEFORE UPDATE ON public.server_poll_archives
FOR EACH ROW WHEN(OLD.version=1 AND NEW.version=2)
EXECUTE FUNCTION public.server_poll_retirement_requires_claim();
INSERT INTO public.schema_migrations(id) VALUES ('0047_server_poll_retirement_claims') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,27 @@
BEGIN;
CREATE TABLE public.mcp_ordinary_retirement_claims (
operation_id uuid PRIMARY KEY,
plan_id uuid NOT NULL UNIQUE,
connection_id uuid NOT NULL REFERENCES public.mcp_connections(id),
source_proof text NOT NULL CHECK(source_proof ~ '^[0-9a-f]{64}$'),
claim_token uuid NOT NULL,
claim_xid xid8 NOT NULL DEFAULT pg_current_xact_id()
);
CREATE FUNCTION public.mcp_ordinary_retirement_claim_immutable() RETURNS trigger
LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.mcp_operations o JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
WHERE o.id=NEW.operation_id AND o.plan_id=NEW.plan_id AND o.connection_id=NEW.connection_id
AND o.state='applied' AND o.verification='passed' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND p.operation_id='logs.settings.update' AND p.operation_version=1 AND p.state='completed'
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'ordinary operation retirement claim cannot be replaced or reopened';
END $$;
CREATE TRIGGER mcp_ordinary_retirement_claim_immutable BEFORE INSERT OR UPDATE OR DELETE
ON public.mcp_ordinary_retirement_claims FOR EACH ROW EXECUTE FUNCTION public.mcp_ordinary_retirement_claim_immutable();
CREATE TRIGGER mcp_ordinary_retirement_claim_no_truncate BEFORE TRUNCATE
ON public.mcp_ordinary_retirement_claims FOR EACH STATEMENT EXECUTE FUNCTION public.mcp_ordinary_retirement_claim_immutable();
INSERT INTO public.schema_migrations(id) VALUES ('0048_mcp_ordinary_retirement_claims') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,18 @@
BEGIN;
CREATE OR REPLACE FUNCTION public.mcp_ordinary_retirement_claim_immutable() RETURNS trigger
LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.mcp_operations o JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
WHERE o.id=NEW.operation_id AND o.plan_id=NEW.plan_id AND o.connection_id=NEW.connection_id
AND o.state='applied' AND o.verification='passed' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND (p.operation_id,p.operation_version) IN (
('logs.settings.update',1),('traffic.settings.update',2),('uptime.settings.update',2),
('servers.api-ping.settings.update',2),('internet-path.settings.update',1)
) AND p.state='completed'
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'ordinary operation retirement claim cannot be replaced or reopened';
END $$;
INSERT INTO public.schema_migrations(id) VALUES ('0049_mcp_ordinary_telemetry_retirement') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,22 @@
BEGIN;
CREATE TABLE public.server_probe_retired_sources (
receipt_id uuid PRIMARY KEY REFERENCES public.server_probe_storage_admissions(receipt_id),
operation_id uuid NOT NULL UNIQUE REFERENCES public.mcp_operation_replay_tombstones(operation_id),
source jsonb NOT NULL CHECK(jsonb_typeof(source)='object' AND octet_length(source::text) BETWEEN 1 AND 8192),
proof text NOT NULL CHECK(proof ~ '^[0-9a-f]{64}$'),
CHECK((source->>'version') IS NOT DISTINCT FROM '1'
AND (source->>'receiptId') IS NOT DISTINCT FROM receipt_id::text
AND (source->>'executionId') IS NOT DISTINCT FROM operation_id::text)
);
CREATE FUNCTION public.server_probe_retired_source_immutable() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
RAISE EXCEPTION 'retired diagnostic source is immutable';
END $$;
CREATE TRIGGER server_probe_retired_source_immutable
BEFORE UPDATE OR DELETE ON public.server_probe_retired_sources
FOR EACH ROW EXECUTE FUNCTION public.server_probe_retired_source_immutable();
CREATE TRIGGER server_probe_retired_source_no_truncate BEFORE TRUNCATE ON public.server_probe_retired_sources
FOR EACH STATEMENT EXECUTE FUNCTION public.server_probe_retired_source_immutable();
INSERT INTO public.schema_migrations(id) VALUES ('0050_server_probe_retired_sources') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,52 @@
BEGIN;
CREATE TABLE public.server_probe_retirement_claims (
receipt_id uuid PRIMARY KEY,
operation_id uuid NOT NULL UNIQUE,
plan_id uuid NOT NULL UNIQUE,
connection_id uuid NOT NULL,
source_proof text NOT NULL CHECK(source_proof ~ '^[a-f0-9]{64}$'),
claim_token uuid NOT NULL,
claim_xid xid8 NOT NULL DEFAULT pg_current_xact_id()
);
CREATE FUNCTION public.server_probe_retirement_claim_guard() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public SET TimeZone='UTC' AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.server_probe_storage_admissions a
JOIN public.mcp_operations o ON o.id=a.execution_id
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN public.mcp_connections c ON c.id=o.connection_id AND c.owner_id=a.owner_id
WHERE a.receipt_id=NEW.receipt_id AND a.identity_kind='mcp' AND a.claim_token IS NULL
AND a.claim_attempted_at IS NOT NULL AND a.cancelled_at IS NULL AND a.purged_at IS NULL
AND o.id=NEW.operation_id AND p.id=NEW.plan_id AND c.id=NEW.connection_id
AND o.state='applied' AND o.verification='inconclusive' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND p.operation_id='servers.probes.run' AND p.operation_version=1 AND p.state='completed'
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'diagnostic retirement claim cannot be replaced or reopened';
END $$;
CREATE TRIGGER server_probe_retirement_claim_guard BEFORE INSERT OR UPDATE OR DELETE
ON public.server_probe_retirement_claims FOR EACH ROW EXECUTE FUNCTION public.server_probe_retirement_claim_guard();
CREATE TRIGGER server_probe_retirement_claim_no_truncate BEFORE TRUNCATE
ON public.server_probe_retirement_claims FOR EACH STATEMENT EXECUTE FUNCTION public.server_probe_retirement_claim_guard();
CREATE FUNCTION public.server_probe_retired_source_insert_guard() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
IF EXISTS(
SELECT 1 FROM public.server_probe_retirement_claims r
JOIN public.mcp_operation_replay_tombstones t ON t.operation_id=r.operation_id AND t.plan_id=r.plan_id
AND t.connection_id=r.connection_id AND t.action_id='servers.probes.run' AND NOT t.target_global
JOIN public.mcp_operations o ON o.id=r.operation_id AND o.plan_id=r.plan_id AND o.connection_id=r.connection_id
JOIN public.server_probe_storage_admissions a ON a.receipt_id=r.receipt_id AND a.execution_id=r.operation_id
WHERE r.receipt_id=NEW.receipt_id AND r.operation_id=NEW.operation_id AND r.source_proof=NEW.proof
AND r.claim_xid<>pg_current_xact_id() AND r.claim_token::text=current_setting('mm.probe_retirement_claim',true)
AND NEW.source->>'planId'=r.plan_id::text AND NEW.source->>'connectionId'=r.connection_id::text
AND NEW.source->>'ownerId'=a.owner_id AND NEW.source->>'bindingProof'=a.binding_proof
AND NEW.source->'serverIds'=to_jsonb(t.target_server_ids)
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'diagnostic archive requires an exact committed retirement claim';
END $$;
CREATE TRIGGER server_probe_retired_source_insert_guard BEFORE INSERT ON public.server_probe_retired_sources
FOR EACH ROW EXECUTE FUNCTION public.server_probe_retired_source_insert_guard();
INSERT INTO public.schema_migrations(id) VALUES ('0051_server_probe_retirement_claims') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,96 @@
BEGIN;
ALTER TABLE public.server_probe_retired_sources
DROP CONSTRAINT server_probe_retired_sources_receipt_id_fkey,
ADD CONSTRAINT server_probe_retired_sources_receipt_id_fkey
FOREIGN KEY(receipt_id) REFERENCES public.server_probe_storage_admissions(receipt_id) ON DELETE CASCADE;
CREATE FUNCTION public.server_probe_retired_gc_allowed(target_receipt uuid,current_retention integer) RETURNS boolean
LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
DECLARE activity timestamptz; retained_days integer; safety integer;
BEGIN
IF current_setting('mm.probe_storage_gc',true) IS DISTINCT FROM 'on'
OR current_setting('mm.probe_retired_gc_receipt',true) IS DISTINCT FROM target_receipt::text THEN RETURN false; END IF;
SELECT (r.source->>'activityAt')::timestamptz,a.retention_days,a.safety_ms
INTO activity,retained_days,safety
FROM public.server_probe_retired_sources r
JOIN public.server_probe_storage_admissions a ON a.receipt_id=r.receipt_id AND a.execution_id=r.operation_id
JOIN public.mcp_operation_replay_tombstones t ON t.operation_id=r.operation_id
JOIN public.mcp_connections c ON c.id=t.connection_id AND c.owner_id=a.owner_id
WHERE r.receipt_id=target_receipt AND r.proof=current_setting('mm.probe_retired_gc_proof',true)
AND a.identity_kind='mcp' AND a.cancelled_at IS NULL AND a.claim_token IS NULL AND isfinite(a.claim_attempted_at)
AND t.action_id='servers.probes.run' AND NOT t.target_global
AND r.source->>'planId'=t.plan_id::text AND r.source->>'connectionId'=c.id::text
AND r.source->'serverIds'=to_jsonb(t.target_server_ids) AND r.source->>'ownerId'=a.owner_id
AND r.source->>'bindingProof'=a.binding_proof
AND (r.source->>'authorityExpiresAt')::timestamptz=a.authority_expires_at
AND (r.source->>'gcNotBefore')::timestamptz=a.gc_not_before
AND (r.source->>'retentionDays')::int=a.retention_days AND (r.source->>'safetyMs')::int=a.safety_ms
AND isfinite(a.authority_expires_at) AND isfinite(a.gc_not_before)
AND a.authority_expires_at<clock_timestamp() AND a.gc_not_before<clock_timestamp()
AND NOT EXISTS(SELECT 1 FROM public.mcp_operations WHERE id=t.operation_id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_plans WHERE id=t.plan_id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_operation_steps WHERE operation_id=t.operation_id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_operation_checkpoints WHERE operation_id=t.operation_id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_diagnostic_cancellations WHERE operation_id=t.operation_id)
AND NOT EXISTS(SELECT 1 FROM public.server_probe_execution_quarantines q
WHERE q.receipt_id=r.receipt_id OR(q.identity_kind='mcp' AND q.execution_id=r.operation_id));
IF NOT FOUND OR activity IS NULL OR NOT isfinite(activity)
OR current_retention IS NULL OR current_retention NOT BETWEEN 1 AND 365 THEN RETURN false; END IF;
IF EXISTS(SELECT 1 FROM public.server_probe_executions j WHERE j.receipt_id=target_receipt
AND (j.finished_at IS NULL OR j.finished_at>activity))
OR EXISTS(SELECT 1 FROM public.server_probe_execution_stages s WHERE s.receipt_id=target_receipt
AND GREATEST(s.created_at,COALESCE(s.received_at,s.created_at))>activity)
THEN RETURN false; END IF;
RETURN activity+make_interval(days=>GREATEST(retained_days,current_retention))
+GREATEST(safety,60000)*interval '1 millisecond'<clock_timestamp();
END $$;
CREATE OR REPLACE FUNCTION public.server_probe_retired_source_immutable() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='DELETE' THEN
IF current_setting('mm.probe_storage_gc',true)='on'
AND current_setting('mm.probe_retired_gc_receipt',true)=OLD.receipt_id::text
AND current_setting('mm.probe_retired_gc_proof',true)=OLD.proof
AND NOT EXISTS(SELECT 1 FROM public.server_probe_storage_admissions WHERE receipt_id=OLD.receipt_id)
AND NOT EXISTS(SELECT 1 FROM public.server_probe_executions WHERE receipt_id=OLD.receipt_id)
AND NOT EXISTS(SELECT 1 FROM public.server_probe_execution_payloads WHERE receipt_id=OLD.receipt_id)
AND NOT EXISTS(SELECT 1 FROM public.server_probe_execution_stages WHERE receipt_id=OLD.receipt_id)
THEN RETURN OLD; END IF;
END IF;
RAISE EXCEPTION 'retired diagnostic source is immutable';
END $$;
CREATE OR REPLACE FUNCTION public.server_probe_storage_gc_allowed(id uuid) RETURNS boolean
LANGUAGE plpgsql SET TimeZone='UTC' AS $$
DECLARE a server_probe_storage_admissions%ROWTYPE; activity timestamptz; current_retention integer; terminal_at timestamptz;
BEGIN
IF current_setting('mm.probe_storage_gc',true) IS DISTINCT FROM 'on' THEN RETURN false; END IF;
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
SELECT * INTO a FROM server_probe_storage_admissions WHERE receipt_id=id FOR UPDATE;
IF NOT FOUND OR a.gc_not_before>=clock_timestamp() OR a.authority_expires_at>=clock_timestamp() THEN RETURN false; END IF;
SELECT settings.requests_retention_days INTO current_retention FROM mcp_settings settings WHERE settings.id=1;
IF current_retention IS NULL OR current_retention NOT BETWEEN 1 AND 365 THEN RETURN false; END IF;
IF EXISTS(SELECT 1 FROM public.server_probe_retired_sources WHERE receipt_id=id) THEN
RETURN public.server_probe_retired_gc_allowed(id,current_retention);
END IF;
terminal_at:=a.created_at;
IF a.identity_kind='mcp' THEN
SELECT GREATEST(o.finished_at,p.expires_at,COALESCE(cancellation.created_at,o.finished_at)) INTO terminal_at
FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN mcp_connections owner_connection ON owner_connection.id=o.connection_id
LEFT JOIN mcp_diagnostic_cancellations cancellation ON cancellation.operation_id=o.id
WHERE o.id=a.execution_id AND owner_connection.owner_id=a.owner_id AND p.operation_id='servers.probes.run'
AND o.state IN ('applied','failed','unknown','rejected','partial','cancelled') AND o.finished_at IS NOT NULL
AND (cancellation.operation_id IS NULL OR(cancellation.connection_id=o.connection_id AND cancellation.actor_id=a.owner_id))
FOR UPDATE OF o,p,owner_connection;
IF NOT FOUND OR terminal_at IS NULL THEN RETURN false; END IF;
END IF;
SELECT GREATEST(a.created_at,a.authority_expires_at,terminal_at,COALESCE(a.cancelled_at,a.created_at),
COALESCE((SELECT finished_at FROM server_probe_executions WHERE receipt_id=id),a.created_at),
COALESCE((SELECT created_at FROM server_probe_execution_quarantines WHERE receipt_id=id),a.created_at),
COALESCE((SELECT max(GREATEST(created_at,COALESCE(received_at,created_at))) FROM server_probe_execution_stages WHERE receipt_id=id),a.created_at)) INTO activity;
RETURN activity+make_interval(days=>GREATEST(a.retention_days,current_retention))+a.safety_ms*interval '1 millisecond'<clock_timestamp();
END $$;
INSERT INTO public.schema_migrations(id) VALUES ('0052_server_probe_retired_gc') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,74 @@
BEGIN;
CREATE TABLE public.server_probe_terminal_proofs (
operation_id uuid PRIMARY KEY REFERENCES public.mcp_operations(id),
receipt_id uuid NOT NULL UNIQUE,
source jsonb NOT NULL CHECK(jsonb_typeof(source)='object' AND octet_length(source::text) BETWEEN 1 AND 8192),
source_proof text NOT NULL CHECK(source_proof ~ '^[a-f0-9]{64}$'),
idempotency_digest text NOT NULL CHECK(idempotency_digest ~ '^[a-f0-9]{64}$'),
capture_proof text NOT NULL CHECK(capture_proof ~ '^[a-f0-9]{64}$'),
CHECK((source->>'version') IS NOT DISTINCT FROM '1'
AND (source->>'executionId') IS NOT DISTINCT FROM operation_id::text
AND (source->>'receiptId') IS NOT DISTINCT FROM receipt_id::text)
);
CREATE FUNCTION public.server_probe_terminal_proof_guard() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='INSERT' THEN
IF EXISTS(
SELECT 1 FROM public.mcp_operations o
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN public.mcp_connections c ON c.id=o.connection_id
JOIN public.server_probe_storage_admissions a ON a.execution_id=o.id AND a.identity_kind='mcp' AND a.owner_id=c.owner_id
JOIN public.server_probe_executions j ON j.receipt_id=a.receipt_id
JOIN public.server_probe_execution_payloads b ON b.receipt_id=a.receipt_id AND b.receipt_proof=j.receipt_proof
WHERE o.id=NEW.operation_id AND a.receipt_id=NEW.receipt_id AND a.purged_at IS NULL AND a.cancelled_at IS NULL
AND a.claim_token IS NULL AND a.claim_attempted_at IS NOT NULL
AND o.state='applied' AND o.verification='inconclusive' AND o.error_code IS NULL
AND p.state='completed' AND p.operation_id='servers.probes.run' AND p.operation_version=1
AND j.state='completed' AND j.execution_id=o.id AND j.mcp_connection_id=c.id AND j.actor_id=c.owner_id
AND NEW.source->>'connectionId'=c.id::text AND NEW.source->>'planId'=p.id::text
AND NEW.source->>'ownerId'=a.owner_id AND NEW.source->>'bindingProof'=a.binding_proof
AND NEW.source->>'receiptProof'=b.receipt_proof
AND NEW.source->>'ciphertextSha256'=encode(sha256(convert_to(b.receipt_ciphertext,'UTF8')),'hex')
) THEN RETURN NEW; END IF;
ELSIF TG_OP='DELETE' THEN
IF EXISTS(
SELECT 1 FROM public.server_probe_retirement_claims r
JOIN public.mcp_operation_replay_tombstones t ON t.operation_id=r.operation_id AND t.plan_id=r.plan_id AND t.connection_id=r.connection_id
WHERE r.operation_id=OLD.operation_id AND r.receipt_id=OLD.receipt_id AND r.source_proof=OLD.source_proof
AND r.claim_xid<>pg_current_xact_id() AND r.claim_token::text=current_setting('mm.probe_retirement_claim',true)
AND t.action_id='servers.probes.run' AND NOT t.target_global AND t.idempotency_digest=OLD.idempotency_digest
AND OLD.source->'serverIds'=to_jsonb(t.target_server_ids)
) THEN RETURN OLD; END IF;
END IF;
RAISE EXCEPTION 'diagnostic terminal proof must match its original source or exact retirement claim';
END $$;
CREATE TRIGGER server_probe_terminal_proof_guard BEFORE INSERT OR UPDATE OR DELETE
ON public.server_probe_terminal_proofs FOR EACH ROW EXECUTE FUNCTION public.server_probe_terminal_proof_guard();
CREATE TRIGGER server_probe_terminal_proof_no_truncate BEFORE TRUNCATE
ON public.server_probe_terminal_proofs FOR EACH STATEMENT EXECUTE FUNCTION public.server_probe_terminal_proof_guard();
CREATE OR REPLACE FUNCTION public.server_probe_retirement_claim_guard() RETURNS trigger
LANGUAGE plpgsql SET search_path=pg_catalog,public SET TimeZone='UTC' AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.mcp_operations o
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN public.mcp_connections c ON c.id=o.connection_id
WHERE o.id=NEW.operation_id AND p.id=NEW.plan_id AND c.id=NEW.connection_id
AND o.state='applied' AND o.verification='inconclusive' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND p.operation_id='servers.probes.run' AND p.operation_version=1 AND p.state='completed'
AND (EXISTS(SELECT 1 FROM public.server_probe_storage_admissions a
WHERE a.receipt_id=NEW.receipt_id AND a.execution_id=o.id AND a.identity_kind='mcp' AND a.owner_id=c.owner_id
AND a.claim_token IS NULL AND a.claim_attempted_at IS NOT NULL AND a.cancelled_at IS NULL AND a.purged_at IS NULL)
OR(EXISTS(SELECT 1 FROM public.server_probe_terminal_proofs f
WHERE f.operation_id=o.id AND f.receipt_id=NEW.receipt_id AND f.source_proof=NEW.source_proof
AND f.source->>'ownerId'=c.owner_id AND f.source->>'connectionId'=c.id::text AND f.source->>'planId'=p.id::text)
AND NOT EXISTS(SELECT 1 FROM public.server_probe_storage_admissions a
WHERE a.receipt_id=NEW.receipt_id OR(a.identity_kind='mcp' AND a.execution_id=o.id))))
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'diagnostic retirement claim cannot be replaced or reopened';
END $$;
INSERT INTO public.schema_migrations(id) VALUES ('0053_server_probe_terminal_proofs') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,18 @@
BEGIN;
CREATE OR REPLACE FUNCTION public.mcp_ordinary_retirement_claim_immutable() RETURNS trigger
LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.mcp_operations o JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
WHERE o.id=NEW.operation_id AND o.plan_id=NEW.plan_id AND o.connection_id=NEW.connection_id
AND o.state='applied' AND o.verification='passed' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND (p.operation_id,p.operation_version) IN (
('logs.settings.update',1),('traffic.settings.update',2),('uptime.settings.update',2),
('servers.api-ping.settings.update',2),('internet-path.settings.update',1),('geoip.settings.update',1)
) AND p.state='completed'
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'ordinary operation retirement claim cannot be replaced or reopened';
END $$;
INSERT INTO public.schema_migrations(id) VALUES ('0054_mcp_geoip_retirement') ON CONFLICT(id) DO NOTHING;
COMMIT;
@@ -0,0 +1,18 @@
BEGIN;
CREATE OR REPLACE FUNCTION public.mcp_ordinary_retirement_claim_immutable() RETURNS trigger
LANGUAGE plpgsql SET TimeZone='UTC' SET search_path=pg_catalog,public AS $$
BEGIN
IF TG_OP='INSERT' AND NEW.claim_xid=pg_current_xact_id() AND EXISTS(
SELECT 1 FROM public.mcp_operations o JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
WHERE o.id=NEW.operation_id AND o.plan_id=NEW.plan_id AND o.connection_id=NEW.connection_id
AND o.state='applied' AND o.verification='passed' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND (p.operation_id,p.operation_version) IN (
('logs.settings.update',1),('traffic.settings.update',2),('uptime.settings.update',2),
('servers.api-ping.settings.update',2),('internet-path.settings.update',1),('geoip.settings.update',1),('backups.schedule.update',1)
) AND p.state='completed'
) THEN RETURN NEW; END IF;
RAISE EXCEPTION 'ordinary operation retirement claim cannot be replaced or reopened';
END $$;
INSERT INTO public.schema_migrations(id) VALUES ('0055_mcp_backup_schedule_retirement') ON CONFLICT(id) DO NOTHING;
COMMIT;
+7
View File
@@ -576,6 +576,13 @@
"operations": [],
"reason": "Управление самим MCP, транспорт или машинная авторизация: отдельного AI-инструмента нет."
},
{
"method": "GET",
"path": "/api/mcp/database/receipts/:id/audit-status",
"status": "excluded",
"operations": [],
"reason": "Человеческая административная сверка аудита внешней квитанции восстановления: управление самим MCP, без отдельного AI-инструмента."
},
{
"method": "GET",
"path": "/api/mcp/devices/:serverId/:resource",
+7 -4
View File
@@ -12,6 +12,7 @@
"test:traffic-flow:rebuild:pg": "node scripts/test-mcp-postgres.mjs --flow-rebuild",
"test:traffic-flow:pg": "node scripts/test-mcp-postgres.mjs --flow-writes",
"test:traffic-flow:quality:pg": "node scripts/test-mcp-postgres.mjs --flow-quality",
"test:traffic-flow:chr-evidence": "tsx --test src/test/traffic-flow-chr-capture-check.test.ts src/test/traffic-flow-chr-wire.test.ts src/test/traffic-flow-chr-target-harness.test.ts src/test/traffic-flow-chr-owned-sequence.test.ts src/test/traffic-flow-chr-reconcile.test.ts",
"test:mcp:pg:all": "node scripts/test-mcp-postgres.mjs --all",
"mcp:stdio": "node dist/scripts/mcp-stdio.js",
"mcp:stdio:dev": "tsx src/scripts/mcp-stdio.ts",
@@ -54,7 +55,7 @@
"test:ospf-optimizer": "tsx src/services/ospf-cost-optimization.test.ts && tsx src/modules/mcp/ospf-legacy-source.test.ts && tsx src/services/ospf-legacy-optimization.test.ts && tsx src/services/ospf-template-flags.test.ts && tsx src/services/ospf-route-proof.test.ts && tsx src/services/ospf-link-catalog.test.ts && tsx src/services/ospf-identity-catalog.test.ts && tsx src/services/ospf-template-cost-write.test.ts && tsx src/services/ospf-template-cost-http.test.ts && tsx src/services/ospf-optimizer-identity.test.ts && tsx src/services/ospf-optimizer-metrics.test.ts && tsx src/services/ospf-optimizer-plan.test.ts && tsx src/services/ospf-effective-disabled.test.ts && tsx src/routes/ospf-optimizer.test.ts",
"test:containers": "tsx src/services/containers-live.test.ts && tsx src/services/containers-state.test.ts && tsx src/services/container-removal.test.ts && tsx src/routes/containers-state.test.ts",
"test:certificates": "tsx src/services/certificate-installation.test.ts && tsx src/services/certificate-renew-collector.test.ts && tsx src/services/certificate-issue-execution.test.ts && tsx src/services/certificate-issue-request.test.ts && tsx src/routes/certificates-issue.test.ts && tsx src/services/certificate-issue-dto.test.ts && tsx src/services/certificate-parse.test.ts && tsx src/services/certificates-state.test.ts && tsx src/routes/certificates-state.test.ts && tsx src/services/acme-settings-test.test.ts && tsx src/services/acme-settings-update-model.test.ts && tsx src/modules/mcp/acme-settings-actions.test.ts",
"test:system-database": "tsx src/services/bounded-database-dump.test.ts && tsx src/services/database-dump-connection.test.ts && tsx src/services/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/services/system-database-schema-readiness.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts && tsx src/services/database-control-archive-boundary.test.ts && tsx src/db/database-schema-control-model.test.ts && tsx src/db/database-schema-control.test.ts && tsx src/db/controlled-migrations.test.ts && tsx src/db/controlled-schema-verification.test.ts && tsx --test src/db/migration-data-inventory.test.ts",
"test:system-database": "tsx src/services/bounded-database-dump.test.ts && tsx src/services/database-dump-connection.test.ts && tsx src/services/system-database-partition-restore.test.ts && tsx src/services/system-database-cache.test.ts && tsx src/services/database-background-work.test.ts && tsx src/services/database-request-gate.test.ts && tsx --test src/services/detached-database-admission.test.ts && tsx src/services/traffic-flow-worker-shutdown.test.ts && tsx src/services/system-database-restore-execution.test.ts && tsx src/services/system-database-operation.test.ts && tsx src/services/system-database-schema-readiness.test.ts && tsx --test src/routes/mcp-oauth-database-admission.test.ts && tsx src/routes/system-database.test.ts && tsx src/plugins/auth.system-database.test.ts && tsx src/services/database-control-archive-boundary.test.ts && tsx src/db/database-schema-control-model.test.ts && tsx src/db/database-schema-control.test.ts && tsx src/db/controlled-migrations.test.ts && tsx src/db/controlled-schema-verification.test.ts && tsx --test src/db/migration-data-inventory.test.ts && tsx --test src/services/database-maintenance-startup.test.ts && tsx --test src/services/database-restore-audit.test.ts && tsx --test src/services/database-maintenance-resolution.test.ts && tsx --test scripts/database-maintenance-resolution.test.mts",
"test:all": "npm run test:logs && npm run test:probes && npm run test:exec && npm run test:alert-engine && npm run test:alerts-telegram && npm run test:auth && npm run test:wireguard && npm run test:ipsec && npm run test:traffic-rate && npm run test:traffic-flow && npm run test:users && npm run test:pg && npm run test:sidebar-counts && npm run test:backups && npm run test:scheduler && npm run test:live-maps && npm run test:ospf-optimizer && npm run test:recursive-routes && npm run test:config-sync && npm run test:ros-file && npm run test:containers && npm run test:certificates && npm run test:netflow-settings && npm run test:netflow-purge && npm run test:evolution && npm run test:system-database && npm run test:server-privacy && npm run test:mcp:module-completion && npm run test:mcp:loopback-agent && npm run test:server-probe-management && npm run test:server-poll-management && npm run test:mcp:routing-authority && npm run test:mcp:native-readers && npm run test:mcp:chr-acquisition && npm run test:mcp:shared-context && npm run test:mcp:telemetry-configuration",
"test:server-privacy": "tsx ../packages/contracts/src/servers.test.ts && tsx src/routes/server-secrets-sql.test.ts && tsx --test src/services/server-connection-test.test.ts",
"test:mcp:flow-rebuild:pg": "node scripts/test-mcp-postgres.mjs --flow-rebuild-mcp",
@@ -65,7 +66,7 @@
"test:mcp:ipsec-export-journal:pg": "node scripts/test-mcp-postgres.mjs --ipsec-export-journal --restore",
"test:mcp:ipsec-export:http:pg": "node scripts/test-mcp-postgres.mjs --ipsec-export-http --restore",
"test:mcp:diagnostic-cancel-host:pg": "node scripts/test-mcp-postgres.mjs --diagnostic-cancel-host --restore",
"test:server-probe-management": "tsx src/services/server-probe-authority.test.ts && tsx src/services/server-probe-public-result.test.ts && tsx src/services/server-probe-execution.test.ts && tsx src/services/server-probe-rest-contract.test.ts && tsx src/services/server-probe-rest-execution.test.ts && tsx src/services/server-probe-journal.test.ts && tsx src/services/server-probe-journal-history.test.ts && tsx src/modules/mcp/server-probe-actions.test.ts && tsx src/modules/mcp/diagnostic-cancellation.test.ts && tsx src/modules/mcp/service-diagnostic-progress.test.ts && tsx src/services/mikrotik-get-admission.test.ts && tsx src/services/server-probe-storage-journal-adapter.test.ts && tsx --test src/services/server-probe-storage-history.test.ts && npm run test:server-probe-storage-models",
"test:server-probe-management": "tsx src/services/server-probe-authority.test.ts && tsx src/services/server-probe-public-result.test.ts && tsx src/services/server-probe-execution.test.ts && tsx src/services/server-probe-rest-contract.test.ts && tsx src/services/server-probe-rest-execution.test.ts && tsx src/services/server-probe-journal.test.ts && tsx src/services/server-probe-journal-history.test.ts && tsx src/modules/mcp/server-probe-actions.test.ts && tsx src/modules/mcp/diagnostic-cancellation.test.ts && tsx src/modules/mcp/service-diagnostic-progress.test.ts && tsx src/services/mikrotik-get-admission.test.ts && tsx src/services/server-probe-storage-journal-adapter.test.ts && npm run test:server-probe-history && npm run test:server-probe-storage-models",
"test:server-poll-http:pg": "node scripts/test-server-poll-http-owned.mjs",
"test:server-poll-approval:pg": "node scripts/test-server-poll-approval-owned.mjs",
"test:server-poll-atomic:pg": "node scripts/test-server-poll-atomic-owned.mjs",
@@ -111,6 +112,7 @@
"test:mcp:routing-schema:pg": "node scripts/test-mcp-postgres.mjs --routing-reservation-schema",
"test:mcp:reserved-step": "tsx --test src/modules/mcp/routing-reserved-step.test.ts",
"test:mcp:routing-forward-rounds:pg": "node scripts/test-mcp-postgres.mjs --routing-network-forward-rounds",
"test:mcp:routing-original-effects:pg": "node scripts/test-mcp-postgres.mjs --routing-original-effects",
"test:flow-active-services:pg": "node scripts/test-mcp-postgres.mjs --flow-active-services",
"test:flow-active-rebuild:pg": "node scripts/test-mcp-postgres.mjs --flow-active-rebuild",
"test:flow-worker-ipc:pg": "node scripts/test-mcp-postgres.mjs --flow-worker-ipc",
@@ -140,7 +142,7 @@
"test:server-poll-storage-fresh:pg": "node scripts/test-mcp-postgres.mjs --server-poll-storage-fresh --restore",
"test:partition-writers": "tsx src/services/partition-write-transaction.test.ts && tsx src/services/collector-partition-writes.test.ts && tsx src/db/partitions-writer.test.ts && tsx src/db/migrate-partition-admission.test.ts && tsx src/db/migration-manifest.test.ts && tsx src/db/sqlite-import-partition-admission.test.ts && tsx src/services/system-database-partition-restore.test.ts && tsx src/services/traffic-flow-write-transaction.test.ts && tsx src/services/traffic-flow-facts-rebuild-cli.test.ts",
"test:mcp:routing-authority": "tsx --test src/modules/mcp/routing-switch-ledger-contract.test.ts && tsx src/modules/mcp/routing-profile-authority.test.ts && tsx src/modules/mcp/routing-recovery-authority.test.ts && tsx src/modules/mcp/routing-reservation.test.ts && tsx src/modules/mcp/routing-community-reserved-source.test.ts && tsx src/modules/mcp/routing-network-verifier-model.test.ts && tsx --test src/test/committed-scoped-checkpoint-observer.test.ts && node --test src/modules/mcp/routing-forward-transport-charge.test.cjs",
"test:mcp:native-readers": "tsx src/modules/mcp/routing-network-verifier-model.test.ts && tsx src/modules/mcp/routing-network-native-parser.test.ts && tsx src/modules/mcp/routing-network-native-session.test.ts && tsx src/db/migration-schema-inventory.test.ts && tsx src/modules/mcp/routing-network-observation-assembly.test.ts && tsx src/modules/mcp/routing-network-configuration-bridge.test.ts",
"test:mcp:native-readers": "tsx src/modules/mcp/routing-network-verifier-model.test.ts && tsx src/modules/mcp/routing-network-native-parser.test.ts && tsx src/modules/mcp/routing-network-native-session.test.ts && tsx src/db/migration-schema-inventory.test.ts && tsx src/modules/mcp/routing-network-observation-assembly.test.ts && tsx src/modules/mcp/routing-network-configuration-bridge.test.ts && tsx --test src/services/routing-table-fib.test.ts src/services/routing-topology-observation.test.ts && node --test src/modules/mcp/routing-original-effects.test.mjs src/modules/mcp/routing-original-effects-caller.test.mjs src/modules/mcp/routing-original-effects-controller.test.mjs src/modules/mcp/routing-network-execution-origin.test.mjs",
"test:mcp:native-readers:pg": "node scripts/test-mcp-postgres.mjs --routing-network-native --restore",
"test:mcp:schema-inventory:pg": "node scripts/test-mcp-postgres.mjs --migration-schema-inventory --restore",
"test:mcp:native-tls": "tsx src/modules/mcp/routing-network-native-tls.test.ts",
@@ -153,8 +155,9 @@
"test:server-poll-durable:pg": "node scripts/test-mcp-postgres.mjs --server-poll-durable --restore",
"test:server-probe-legacy:pg": "node scripts/test-mcp-postgres.mjs --probe-legacy-orphans",
"test:server-probe-storage:pg": "node scripts/test-mcp-postgres.mjs --probe-storage-journal",
"test:server-probe-history": "tsx --test src/services/server-probe-storage-history.test.ts",
"test:server-probe-history": "tsx --test src/services/server-probe-storage-history.test.ts src/services/server-probe-retired-source.test.ts",
"test:server-probe-history:pg": "node scripts/test-mcp-postgres.mjs --probe-storage-history",
"test:server-probe-retirement:pg": "node scripts/test-mcp-postgres.mjs --probe-operation-retirement",
"test:server-probe-storage-models": "tsx src/services/server-probe-journal-maintenance.test.ts && tsx src/modules/mcp/diagnostic-cancellation-source.test.ts && tsx src/services/server-probe-storage-source.test.ts && tsx src/services/server-probe-storage-connection.test.ts && tsx src/modules/mcp/server-probe-storage-host.test.ts && tsx src/services/server-probe-storage-rest.test.ts && tsx src/services/server-probe-storage-metadata.test.ts && tsx src/services/server-probe-legacy-verifier.test.ts && tsx src/services/server-probe-legacy-snapshot.test.ts && tsx src/services/server-probe-legacy-inspection.test.ts && tsx src/db/database-source-observation.test.ts && tsx src/services/server-probe-retention.test.ts && tsx src/modules/mcp/server-probe-registration.test.ts",
"test:mcp:migration-data:pg": "node scripts/test-mcp-postgres.mjs --migration-data-inventory --restore",
"test:mcp:poll-memory:pg": "npm run build -w @mmapp/contracts && node scripts/acceptance/build-poll-memory.mjs && node scripts/test-mcp-postgres.mjs --poll-memory",
@@ -0,0 +1,123 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { randomBytes } from 'node:crypto'
import { lstat, mkdtemp, rm } from 'node:fs/promises'
import { createServer } from 'node:net'
import { tmpdir } from 'node:os'
import { isAbsolute, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import EmbeddedPostgres from 'embedded-postgres'
import pg from 'pg'
import { applySqlMigrations } from '../../src/db/migrate.ts'
import { MIGRATION_MANIFEST } from '../../src/db/migration-manifest.ts'
assert.deepEqual(process.argv.slice(2), ['--owned-embedded'])
const bin = process.env.MMAPP_PG_CLIENT_BIN
assert(bin && isAbsolute(bin) && resolve(bin) === bin, 'Set MMAPP_PG_CLIENT_BIN to the exact local PostgreSQL client directory')
const psql = join(bin, process.platform === 'win32' ? 'psql.exe' : 'psql')
const executable = await lstat(psql)
assert(executable.isFile() && !executable.isSymbolicLink(), 'PostgreSQL client must be a regular file')
const clientEnv = Object.fromEntries(Object.entries(process.env).filter(([name]) => !/^PG/i.test(name)))
const run = (command, args, options = {}) => execFileSync(command, args, {
timeout: 10000, maxBuffer: 16384, encoding: 'utf8', windowsHide: true, ...options,
})
assert.match(run(psql, ['--version'], { env: clientEnv }), /^psql \(PostgreSQL\) (?:1[6-9]|[2-9][0-9])\./)
const witness = fileURLToPath(new URL('../../../tmp/mcp-netflow-linux-topology-20261104/collector-drain-witness.py', import.meta.url))
const { IDENTITY_SQL, SQL } = JSON.parse(run('python', ['-I', '-B', '-c',
'import json,runpy,sys; m=runpy.run_path(sys.argv[1]); print(json.dumps({k:m[k] for k in ("IDENTITY_SQL","SQL")}))', witness]))
assert(typeof SQL === 'string' && typeof IDENTITY_SQL === 'string')
const directory = await mkdtemp(join(tmpdir(), 'mm-netflow-drain-pg-'))
const password = randomBytes(24).toString('hex')
const host = '127.0.0.1', database = 'postgres', user = 'mcp_test'
const listener = createServer()
let embedded, pool, started = false
try {
await new Promise((done, reject) => { listener.once('error', reject); listener.listen(0, host, done) })
const { port } = listener.address()
await new Promise((done, reject) => listener.close(error => error ? reject(error) : done()))
embedded = new EmbeddedPostgres({
databaseDir: join(directory, 'pg'), port, user, password, persistent: true, createPostgresUser: false,
initdbFlags: ['--encoding=UTF8'], postgresFlags: ['-h', host, '-c', 'unix_socket_directories='],
onLog: () => {}, onError: () => {},
})
await embedded.initialise()
await embedded.start()
started = true
pool = new pg.Pool({ host, port, database, user, password, max: 1, connectionTimeoutMillis: 5000 })
await applySqlMigrations(pool)
const { rows: [version] } = await pool.query("SELECT current_setting('server_version_num')::int AS number, (SELECT count(*)::int FROM schema_migrations) AS migrations")
assert(version.number >= 180000 && version.number <= 189999)
assert.equal(version.migrations, MIGRATION_MANIFEST.length)
const runId = 'a012bcde', owner = 'netflow-operator-' + runId, collectorPort = 51000
const read = (sql, probePort = collectorPort) => JSON.parse(run(psql, ['-X', '-w', '-qAt',
'-h', host, '-p', String(port), '-U', user, '-d', database, '-v', 'ON_ERROR_STOP=1',
'-v', 'database=' + database, '-v', 'socket_dir=/tmp/mcp-netflow-' + runId + '/pg-socket',
'-v', 'run_id=' + runId, '-v', 'port=' + probePort],
{ env: { ...clientEnv, PGCONNECT_TIMEOUT: '2', PGPASSWORD: password }, input: sql }).trim())
const identity = read(IDENTITY_SQL)
assert.deepEqual(Object.keys(identity).sort(), ['oid', 'source'])
assert.match(identity.oid, /^[1-9][0-9]{0,9}$/)
// The production source predicate deliberately rejects this TCP fixture.
assert.equal(identity.source, false)
const expect = (planCount, enabled, verified, ordered = true, sqlPort = collectorPort) =>
assert.deepEqual(read(SQL, sqlPort), { ...identity, connectionCount: 1, planCount, enabled, verified, ordered })
const uuid = index => 'a012bcde-0000-4000-8000-' + String(index).padStart(12, '0')
const connection = uuid(1)
await pool.query(`INSERT INTO mcp_connections(id,name,owner_id,owner_permissions,policy,generation)
VALUES($1,$2,$3,'[]','{}','inert-witness')`, [connection, 'Owned NetFlow ' + runId, owner])
await pool.query("UPDATE traffic_flow_settings SET collector_ip='198.18.242.3', flow_listen_port=$1, enabled=false WHERE id=1", [collectorPort])
expect(0, false, [])
async function seed(index, enabled) {
const plan = uuid(index), operation = uuid(index + 1), receipt = uuid(index + 2)
const base = Date.UTC(2026, 0, 1) + index * 1000
const at = offset => new Date(base + offset)
const result = { receiptId: receipt, configurationConfirmed: true, receiptConfirmed: true,
listener: 'request-acknowledged', automaticReplay: false }
await pool.query(`INSERT INTO mcp_plans(id,connection_id,operation_id,operation_version,policy_version,generation,payload,evidence,state,expires_at,approved_by,approved_at,created_at)
VALUES($1,$2,'netflow.settings.update',3,1,'inert-witness','{}','{}','completed',$3,$4,$5,$6)`,
[plan, connection, at(9000), owner, at(100), at(0)])
await pool.query(`INSERT INTO mcp_operations(id,plan_id,connection_id,idempotency_key,state,verification,created_at,finished_at)
VALUES($1,$2,$3,$4,'applied','passed',$5,$6)`, [operation, plan, connection, 'inert-' + index, at(200), at(400)])
await pool.query(`INSERT INTO mcp_operation_steps(operation_id,step_index,state,intended_state,result,created_at,finished_at)
VALUES($1,0,'verified',$2,$3,$4,$5)`,
[operation, { service: 'netflow.settings.update', input: { receiptId: receipt, enabled } }, result, at(300), at(400)])
await pool.query(`INSERT INTO mcp_audit(actor_id,action,connection_id,payload,created_at)
VALUES($1,'plan.approve',$2,$3,$4)`, [owner, connection, { planId: plan }, at(100)])
await pool.query(`INSERT INTO flow_settings_intents(receipt_id,phase,request_proof,state,result,created_at)
VALUES($1,'configuration','inert-proof','completed',$2,$3),($1,'listener','inert-proof','completed',$2,$3)`, [receipt, result, at(300)])
await pool.query('UPDATE traffic_flow_settings SET enabled=$1 WHERE id=1', [enabled])
return [plan, operation, receipt]
}
const enabled = await seed(10, true)
expect(1, true, [enabled])
const disabled = await seed(20, false)
expect(2, false, [enabled, disabled])
expect(2, null, [enabled, disabled], true, collectorPort + 1)
await pool.query("UPDATE flow_settings_intents SET request_proof='foreign' WHERE receipt_id=$1 AND phase='listener'", [disabled[2]])
expect(2, false, [enabled])
await pool.query("UPDATE flow_settings_intents SET request_proof='inert-proof' WHERE receipt_id=$1 AND phase='listener'", [disabled[2]])
const { rows: [duplicate] } = await pool.query(`INSERT INTO mcp_audit(actor_id,action,connection_id,payload)
VALUES($1,'plan.approve',$2,$3) RETURNING id`, [owner, connection, { planId: disabled[0] }])
expect(2, false, [enabled])
await pool.query('DELETE FROM mcp_audit WHERE id=$1', [duplicate.id])
const { rows: [original] } = await pool.query(`UPDATE mcp_operations SET finished_at=(SELECT created_at + interval '1 second' FROM mcp_plans WHERE id=$2)
WHERE id=$1 RETURNING created_at`, [enabled[1], disabled[0]])
expect(2, false, [enabled, disabled], false)
await pool.query("UPDATE mcp_operations SET finished_at=$2::timestamptz + interval '200 milliseconds' WHERE id=$1", [enabled[1], original.created_at])
expect(2, false, [enabled, disabled])
console.log(JSON.stringify({ migrations: version.migrations, exactPsqlQueries: true, inertEnableDisableAndRejections: true,
linuxSocketSourceAccepted: false, realMcpExecutionClaimed: false }))
} finally {
try {
await pool?.end()
} finally {
if (started) await embedded.stop()
if (listener.listening) await new Promise(done => listener.close(done))
await rm(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
}
}
+3 -8
View File
@@ -7,14 +7,9 @@ import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import ts from 'typescript'
export const OPERATOR_SOURCES=Object.freeze([
'database-writer-bootstrap-operator.mjs','database-writer-profile-operator.mts',
'writer-profile/operator-files.mjs','writer-profile/operator-contract.mjs','writer-profile/publisher.mjs','writer-profile/context.mjs',
'writer-profile/catalogue.mts','writer-profile/catalogue-value-bridge.ts','writer-profile/catalogue-projection.ts','writer-profile/catalogue-leaves.ts',
'writer-profile/application-writer-profile-catalogue.ts','writer-profile/application-writer-profile-catalogue-values.ts',
'writer-profile/application-writer-profile-catalogue-parent-policy.ts','writer-profile/application-writer-profile-canonical.ts',
'writer-profile/fixed44-build-sql.ts','writer-profile/fixed44-build-definition.ts','writer-profile/expanded44-data-policy.ts',
])
import {OPERATOR_SOURCES} from './database-operator-sources.mjs'
export {OPERATOR_SOURCES}
const emitted=source=>source.replace(/\.mts$/,'.mjs').replace(/\.ts$/,'.js')
const digest=value=>createHash('sha256').update(value).digest('hex')
const packageName=specifier=>specifier.startsWith('@')?specifier.split('/').slice(0,2).join('/'):specifier.split('/')[0]
@@ -5,6 +5,7 @@ import {spawnSync} from 'node:child_process'
import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {buildDatabaseOperator,compileOperatorModule,moduleSpecifiers,OPERATOR_SOURCES} from './build-database-operator.mjs'
import {stageDatabaseOperatorScripts} from './stage-database-operator.mjs'
const backend=process.env.OPERATOR_BUILD_TEST_BACKEND??path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..')
test('AST rewrite changes static/export/dynamic module specifiers and preserves SQL/string values',()=>{
@@ -19,7 +20,7 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
const packageJson=JSON.parse(await readFile(path.join(backend,'package.json'),'utf8'))
await mkdir(projectBackend,{recursive:true});await writeFile(path.join(projectBackend,'package.json'),JSON.stringify({type:'module',dependencies:packageJson.dependencies}))
const first=await buildDatabaseOperator({backendDirectory:backend,outputDirectory:output}),second=await buildDatabaseOperator({backendDirectory:backend,outputDirectory:output})
assert.deepEqual(first,second);assert.equal(first.modules.length,OPERATOR_SOURCES.length);assert.equal(first.modules.length,17);assert.deepEqual(first.runtimeDependencies,['dotenv','pg','zod']);assert.equal(first.backendDistModules.length,9)
assert.deepEqual(first,second);assert.equal(first.modules.length,OPERATOR_SOURCES.length);assert.equal(first.modules.length,19);assert.deepEqual(first.runtimeDependencies,['dotenv','pg','zod']);assert.equal(first.backendDistModules.length,12)
const copied=new Set()
async function copyDist(relative){
if(copied.has(relative))return;copied.add(relative)
@@ -34,10 +35,11 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
for(const relative of first.backendDistImports)await copyDist(relative)
assert.deepEqual([...copied].sort(),first.backendDistModules.map(item=>item.file).sort())
// Exercise the real build entry point with its default backend/output paths.
for(const file of OPERATOR_SOURCES){const target=path.join(projectBackend,'scripts',file);await mkdir(path.dirname(target),{recursive:true});await copyFile(path.join(backend,'scripts',file),target)}
const buildScript=path.join(projectBackend,'scripts','build-database-operator.mjs');await copyFile(fileURLToPath(new URL('./build-database-operator.mjs',import.meta.url)),buildScript)
const scripts=path.join(projectBackend,'scripts')
assert.equal(await stageDatabaseOperatorScripts(scripts,path.join(backend,'scripts')),21)
const buildScript=path.join(scripts,'build-database-operator.mjs')
const built=spawnSync(process.execPath,[buildScript],{cwd:temporary,env:{...process.env,NODE_OPTIONS:''},encoding:'utf8',timeout:30000})
assert.equal(built.status,0,built.stderr);assert.equal(JSON.parse(built.stdout.trim()).modules,17)
assert.equal(built.status,0,built.stderr);assert.equal(JSON.parse(built.stdout.trim()).modules,19)
assert.deepEqual(JSON.parse(await readFile(path.join(output,'build-manifest.json'),'utf8')),first)
const configFile=path.join(projectBackend,'dist','config.js'),originalConfig=await readFile(configFile,'utf8')
for(const injected of ["import 'tsx';","import '../src/config.js';","import '../outside.js';"]){
@@ -59,10 +61,10 @@ test('fixed artifact is reproducible, resolves the compiled backend graph, and r
const modulesToImport=first.modules.map(item=>path.join(output,item.file))
const importer=path.join(temporary,'import-all.mjs');await writeFile(importer,`import {pathToFileURL} from 'node:url';for(const file of ${JSON.stringify(modulesToImport)})await import(pathToFileURL(file).href);console.log(JSON.stringify({imported:${modulesToImport.length},readyAuthorized:false}));`)
const imported=spawnSync(process.execPath,['--import',pathToFileURL(guard).href,importer],{cwd:temporary,env,encoding:'utf8',timeout:30000})
assert.equal(imported.status,0,imported.stderr);assert.equal(JSON.parse(imported.stdout.trim()).imported,17);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0)
assert.equal(imported.status,0,imported.stderr);assert.equal(JSON.parse(imported.stdout.trim()).imported,19);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0)
const refusals=[]
for(const [entry,event] of [['database-writer-bootstrap-operator.mjs','application-writer-bootstrap-refused'],['database-writer-profile-operator.mjs','application-writer-profile-publication-refused']]){
const child=spawnSync(process.execPath,['--import',pathToFileURL(guard).href,path.join(output,entry),'invalid'],{cwd:temporary,env,encoding:'utf8',timeout:30000})
for(const [entry,event,args] of [['database-writer-bootstrap-operator.mjs','application-writer-bootstrap-refused',['invalid']],['database-writer-profile-operator.mjs','application-writer-profile-publication-refused',['invalid']],['database-writer-profile-operator.mjs','database-maintenance-command-refused',['maintenance-inspect']],['database-writer-profile-operator.mjs','database-maintenance-command-refused',['maintenance-resolve']]]){
const child=spawnSync(process.execPath,['--import',pathToFileURL(guard).href,path.join(output,entry),...args],{cwd:temporary,env,encoding:'utf8',timeout:30000})
assert.equal(child.status,1,child.stderr);assert.equal(child.stdout,'');assert.equal(JSON.parse(child.stderr.trim()).event,event);assert.equal(JSON.parse(await readFile(telemetry,'utf8')).connections,0);refusals.push(event)
}
const receipt={node:process.version,projection:temporary,modules:first.modules.length,compiledBackendModules:copied.size,buildCliExitCode:built.status,importedModules:modulesToImport.length,invalidArgumentsRefused:refusals,developmentDependenciesBlocked:denied,networkConnections:0,productionActivated:false,readyAuthorized:false}
@@ -0,0 +1,100 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {mkdtemp,mkdir,writeFile,readFile,readdir,rm,unlink,lstat} from 'node:fs/promises'
import {createHash,randomUUID} from 'node:crypto'
import {rootCertificates} from 'node:tls'
import {EventEmitter} from 'node:events'
import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {runInNewContext} from 'node:vm'
import {createRequire} from 'node:module'
import * as strictReader from '../src/services/database-maintenance-startup.js'
import * as grammar from '../src/services/database-maintenance-resolution.js'
import * as manifest from '../src/db/migration-manifest.js'
import * as contract from './writer-profile/operator-contract.mjs'
const backend=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'),require=createRequire(import.meta.url),ts=require('typescript'),hash=(value:Buffer|string)=>createHash('sha256').update(value).digest('hex')
async function own(work:(root:string)=>Promise<void>){const temp=path.resolve(backend,'../tmp');await mkdir(temp,{recursive:true});const root=await mkdtemp(path.join(temp,'owned-resolution-command-'));try{await work(root)}finally{assert.equal(path.dirname(root),temp);assert(path.basename(root).startsWith('owned-resolution-command-'));await rm(root,{recursive:true,force:true})}}
async function model(root:string,localHost?:string){
let owners=0,sql=0,releaseFailure=false,poolEndFailure=false,published=0,wrongOwner=false,controlSchema=false,observationFailure:Error|undefined
const capturedScopes:unknown[]=[]
const state=path.join(root,'state');await mkdir(state);await mkdir(path.join(state,'restore-vault'));await writeFile(path.join(state,'access-generation'),Buffer.alloc(32,7));const uncertain=randomUUID(),at=new Date().toISOString(),record=(value:string)=>JSON.stringify({operationId:uncertain,state:value,at})+'\n';await writeFile(path.join(state,'database-maintenance.jsonl'),record('intent')+record('unconfirmed'))
const source={systemIdentifier:'1234567890',databaseOid:'12345',databaseName:'managed',serverVersionNum:180004,inRecovery:false}
const schema={sha256:'a'.repeat(64),observedRaw:{sha256:'b'.repeat(64)},observedPhysical:{sha256:'c'.repeat(64)},sections:{relations:manifest.CURRENT_REQUIRED_TABLES.map(name=>({namespace:'public',name,kind:'r',persistence:'p',row_security:false,force_row_security:false})),columns:[{namespace:'public',relation:'schema_migrations',name:'id',dropped:false,type:'text'}]}}
class Pool {on(){};constructor(options:unknown){const ssl=(options as {ssl:false|{rejectUnauthorized:boolean}}).ssl;if(ssl===false)assert(['127.0.0.1','::1'].includes((options as {host:string}).host));else assert(ssl.rejectUnauthorized)}async connect(){owners++;const client=new EventEmitter();return Object.assign(client,{release:()=>{owners--;if(releaseFailure)throw Error('Synthetic release uncertainty')},query:async(text:string)=>{sql++;if(text.includes('pg_try_advisory_lock'))return {rows:[{locked:true}],command:'SELECT'};if(text.includes('pg_advisory_unlock'))return {rows:[{unlocked:true}],command:'SELECT'};if(text.includes('pg_namespace'))return {rows:controlSchema?[{nspname:'mmapp_control'}]:[],command:'SELECT'};if(text.includes('pg_control_system')){if(observationFailure)throw observationFailure;return {rows:[source],command:'SELECT'}};if(text.startsWith('SELECT id FROM public.schema_migrations'))return {rows:manifest.MIGRATION_MANIFEST.map(pin=>({id:pin.file.slice(0,-4)})),command:'SELECT'};return {rows:[],command:text.split(' ')[0]}}})}async end(){assert.equal(owners,0);if(poolEndFailure)throw Error('Synthetic pool retirement uncertainty')}}
const protectedSource=await readFile(path.join(backend,'scripts/writer-profile/operator-files.mjs'),'utf8'),protectedExports:Record<string,unknown>={},actualUid=(await lstat(state)).uid
const protectedCode=ts.transpileModule(protectedSource,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(protectedCode,{exports:protectedExports,require,Buffer,process:{platform:'linux',getuid:()=>actualUid+1},Object,Error})
const protectedDirectory=async(directory:string)=>{assert(path.resolve(directory).startsWith(root+path.sep)||path.resolve(directory)===root);if(wrongOwner)await (protectedExports.protectedDirectory as (path:string)=>Promise<void>)(directory);await readdir(directory)}
// Explicit MODELED protection adapter: native POSIX mode/ownership and hardlink
// fsync publication remain separate Linux acceptance, never a Windows claim.
const files={sha:hash,protectedDirectory,readProtected:async(file:string,maximum:number,pin?:string)=>{assert(file.startsWith(root+path.sep));const raw=await readFile(file);assert(raw.length>0&&raw.length<=maximum);if(pin)assert.equal(hash(raw),pin);return raw},writeFile:async(file:string,raw:Buffer)=>{assert(file.startsWith(root+path.sep));await writeFile(file,raw,{flag:'wx'});published++},withJournalLease:async(directory:string,work:()=>Promise<unknown>)=>{const lock=path.join(directory,'publisher-lease.lock');await writeFile(lock,'owned',{flag:'wx'});try{return await work()}finally{await unlink(lock)}}}
const readRecords=async(directory:string)=>{const records=path.join(directory,'maintenance-acceptances');let names:string[];try{names=await readdir(records)}catch(error){if((error as NodeJS.ErrnoException).code==='ENOENT')return [];throw error};assert(names.every(name=>/^[a-f0-9-]{36}\.json$/.test(name)));return Promise.all(names.sort().map(name=>readFile(path.join(records,name))))}
const moduleFile=path.join(backend,'scripts/writer-profile/maintenance-resolution.mts'),sourceText=(await readFile(moduleFile,'utf8')).replaceAll('import.meta.url',JSON.stringify(pathToFileURL(moduleFile).href)),exports:Record<string,unknown>={}
const dependencies:Record<string,unknown>={'./operator-files.mjs':files,'./operator-contract.mjs':contract,'../../src/services/database-maintenance-startup.js':{...strictReader,readMaintenanceAcceptanceRecords:readRecords},'../../src/services/database-maintenance-resolution.js':grammar,'../../src/db/migration-schema-inventory.js':{captureMigrationSchema:async(_client:unknown,options:{scope:unknown})=>{capturedScopes.push(options.scope);return schema}},'../../src/db/migration-manifest.js':manifest,pg:{Pool}}
const code=ts.transpileModule(sourceText,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name in dependencies?dependencies[name]:require(name),Buffer,URL,process:{env:{}},Date,Object,Map,Set,Error,console})
const command=exports.maintenanceMain as (args:string[])=>Promise<Record<string,unknown>>
const tlsTransport={format:'database-maintenance-transport-v1',connection:{host:'db.example.invalid',port:5432,user:'existing-maintenance-user',password:'synthetic-only',database:'managed'},tls:{serverName:'db.example.invalid',spkiSha256:'f'.repeat(64)}},localTransport=(host:string)=>({format:'database-maintenance-transport-v1',mode:'local-loopback',connection:{...tlsTransport.connection,host}})
const configs={transport:localHost===undefined?tlsTransport:localTransport(localHost),'runtime-target':{format:'database-maintenance-runtime-target-v1',databaseUrl:'postgresql://db.example.invalid:5432/managed?sslmode=require'}}
const args=['--state-dir',state]
for(const [name,value]of Object.entries(configs)){const raw=Buffer.from(JSON.stringify(value)),file=path.join(root,name+'.json');await writeFile(file,raw);args.push('--'+name,file,'--'+name+'-sha256',hash(raw))}
const ca=Buffer.from(rootCertificates[0]),caFile=path.join(root,'ca.pem');await writeFile(caFile,ca);if(localHost===undefined)args.push('--ca',caFile,'--ca-sha256',hash(ca))
const output=path.join(root,'inspection.json')
async function inspect(destination=output){return command(['maintenance-inspect',...args,'--output',destination])}
async function approve(change?:(value:Record<string,unknown>)=>void){const raw=await readFile(output),inspection=JSON.parse(raw.toString()),approval={format:'database-maintenance-current-state-approval-v1',resolutionId:randomUUID(),decision:'accept-observed-current-state',actorId:'host-human-reviewer',inspectionSha256:hash(raw),issuedAt:new Date().toISOString(),expiresAt:new Date(Date.now()+60000).toISOString(),managedBackendStopped:true,historicOutcomesRemainUnknown:true,automaticRestore:false};assert(inspection.createdAt<=approval.issuedAt);change?.(approval);const bytes=Buffer.from(JSON.stringify(approval)),file=path.join(root,'approval.json');await writeFile(file,bytes);return ['maintenance-resolve',...args,'--inspection',output,'--inspection-sha256',hash(raw),'--approval',file,'--approval-sha256',hash(bytes)]}
const toLocal=async(host='127.0.0.1')=>{const raw=Buffer.from(JSON.stringify(localTransport(host)));await writeFile(path.join(root,'transport.json'),raw);args[args.indexOf('--transport-sha256')+1]=hash(raw);for(const flag of ['--ca','--ca-sha256']){const at=args.indexOf(flag);if(at>=0)args.splice(at,2)}}
const configuration=exports.maintenancePoolConfiguration as (value:unknown,ca?:Buffer,pin?:string)=>{options:Record<string,unknown>;transportBinding:{mode:string;endpointSha256:string}}
return {command,inspect,approve,toLocal,configuration,readRecords,state,output,sql:()=>sql,published:()=>published,failRelease:()=>{releaseFailure=true},failEnd:()=>{poolEndFailure=true},driftSchema:()=>{schema.sha256='0'.repeat(64)},driftTarget:()=>{source.databaseOid='54321'},wrongOwner:()=>{wrongOwner=true},withControlSchema:()=>{controlSchema=true},scopes:()=>capturedScopes,failObservation:(error:Error)=>{observationFailure=error}}
}
test('actual operator commands inspect then separately approved resolve; old uncertain bytes never rewritten',()=>own(async root=>{
const fixture=await model(root),journal=path.join(fixture.state,'database-maintenance.jsonl'),before=await readFile(journal),inspected=await fixture.inspect();assert.equal(inspected.humanApprovalRequired,true);assert.equal((await fixture.readRecords(fixture.state)).length,0)
const result=await fixture.command(await fixture.approve());assert.equal(result.outcome,'committed');assert.equal(result.historicOutcomesChanged,false);assert.equal(result.archiveDataEquivalenceProved,false);assert.equal(result.automaticRestore,false)
assert.deepEqual(await readFile(journal),before);const records=await fixture.readRecords(fixture.state),facts=await strictReader.readMaintenanceRecoveryFacts(fixture.state);assert.equal(records.length,1);const accepted=grammar.evaluateMaintenanceAcceptances(records,facts.journal,facts.vault.evidence,grammar.runtimeDatabaseTargetSha256('postgresql://db.example.invalid:5432/managed?sslmode=require'));assert.equal(accepted.count,1)
fixture.driftSchema();const afterStatus=await fixture.inspect(path.join(root,'after-inspection.json'));assert.equal(afterStatus.state,'historically-accepted');assert.equal(afterStatus.humanApprovalRequired,false);assert.equal(afterStatus.historicOutcomesChanged,false)
await assert.rejects(fixture.command(await fixture.approve()),/No new unresolved|already|assert|Assertion/i)
}))
for(const type of ['expired','not-stopped','wrong-inspection'])test('actual resolve refuses '+type+' approval before connecting SQL',()=>own(async root=>{
const fixture=await model(root);await fixture.inspect();const args=await fixture.approve(value=>{if(type==='expired')value.expiresAt=new Date(Date.now()-1).toISOString();if(type==='not-stopped')value.managedBackendStopped=false;if(type==='wrong-inspection')value.inspectionSha256='0'.repeat(64)}),before=fixture.sql();await assert.rejects(fixture.command(args));assert.equal(fixture.sql(),before);assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
for(const type of ['release','pool-end','schema-drift','target-drift','external-drift'])test('actual resolve refuses '+type+' before acceptance publication',()=>own(async root=>{
const fixture=await model(root);await fixture.inspect();const args=await fixture.approve();if(type==='release')fixture.failRelease();if(type==='pool-end')fixture.failEnd()
if(type==='schema-drift')fixture.driftSchema();if(type==='target-drift')fixture.driftTarget()
if(type==='external-drift')await appendFileSafe(path.join(fixture.state,'database-maintenance.jsonl'))
await assert.rejects(fixture.command(args));assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
async function appendFileSafe(file:string){await writeFile(file,await readFile(file,'utf8')+JSON.stringify({operationId:randomUUID(),state:'intent',at:new Date().toISOString()})+'\n')}
test('existing shipped main dispatches both commands before writer-profile role/source flags',async()=>{
const source=await readFile(path.join(backend,'scripts/database-writer-profile-operator.mts'),'utf8');assert(source.indexOf("['maintenance-inspect','maintenance-resolve']")<source.indexOf("const flags="));assert(source.includes("import('./writer-profile/maintenance-resolution.mjs')"))
})
test('existing protectedDirectory owner contract refuses a foreign CLI UID before SQL or writes',()=>own(async root=>{
const fixture=await model(root);fixture.wrongOwner();await assert.rejects(fixture.inspect());assert.equal(fixture.sql(),0);assert.equal(fixture.published(),0);assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
test('explicit local-loopback supports both IP families and binds selected mode in actual inspect/resolve',async()=>{
for(const host of ['127.0.0.1','::1'])await own(async root=>{const fixture=await model(root,host);await fixture.inspect();const result=await fixture.command(await fixture.approve());assert.equal(result.outcome,'committed');const records=await fixture.readRecords(fixture.state);assert.equal(JSON.parse(records[0].toString()).knownCurrent.transport.mode,'local-loopback')})
})
test('plaintext remote/DNS/unspecified/mapped hosts refuse before SQL and never fall back',async()=>{
for(const host of ['db.example.invalid','localhost','0.0.0.0','192.0.2.1','[::1]','::ffff:127.0.0.1'])await own(async root=>{const fixture=await model(root,host);await assert.rejects(fixture.inspect());assert.equal(fixture.sql(),0);assert.equal(fixture.published(),0)})
})
test('an approval for TLS observation cannot resolve through a changed local-loopback transport',()=>own(async root=>{
const fixture=await model(root);await fixture.inspect();await fixture.toLocal();await assert.rejects(fixture.command(await fixture.approve()));assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
test('local mode explicitly disables SSL/environment substitution; TLS remains verified default without fallback',()=>own(async root=>{
const fixture=await model(root),ConnectionParameters=require('pg/lib/connection-parameters'),raw={format:'database-maintenance-transport-v1',mode:'local-loopback',connection:{host:'127.0.0.1',port:5432,user:'existing-user',password:'synthetic-only',database:'managed'}}
const previous={PGHOST:process.env.PGHOST,PGSSLMODE:process.env.PGSSLMODE,PGOPTIONS:process.env.PGOPTIONS};process.env.PGHOST='remote.invalid';process.env.PGSSLMODE='require';process.env.PGOPTIONS='-c role=foreign'
try{const config=fixture.configuration(raw),actual=new ConnectionParameters(config.options);assert.equal(actual.host,'127.0.0.1');assert.equal(actual.ssl,false);assert.equal(actual.options,contract.STARTUP_OPTIONS);assert.equal(config.transportBinding.mode,'local-loopback')}
finally{for(const [key,value]of Object.entries(previous)){if(value===undefined)delete process.env[key];else process.env[key]=value}}
const ca=Buffer.from(rootCertificates[0]),tls={format:'database-maintenance-transport-v1',connection:{...raw.connection,host:'db.example.invalid'},tls:{serverName:'db.example.invalid',spkiSha256:'f'.repeat(64)}}
assert.throws(()=>fixture.configuration(tls));const secure=fixture.configuration(tls,ca,hash(ca));assert.equal(secure.transportBinding.mode,'tls');assert.equal((secure.options.ssl as {rejectUnauthorized:boolean}).rejectUnauthorized,true)
assert.throws(()=>fixture.configuration({...raw,mode:'plaintext'}));assert.throws(()=>fixture.configuration({...tls,mode:null},ca,hash(ca)));assert.throws(()=>fixture.configuration(raw,ca,hash(ca)))
}))
// A normal installation has no optional writer-profile schema. Observation must
// select its exact namespace set without requiring installation of that system.
test('maintenance observes with and without the optional control namespace',async()=>{
for(const present of [false,true])await own(async root=>{const fixture=await model(root);if(present)fixture.withControlSchema();await fixture.inspect();assert.equal(JSON.stringify(fixture.scopes()),JSON.stringify([{applicationNamespaces:['public'],excludedControlNamespaces:present?['mmapp_control']:[]}]))})
})
test('original observation failure survives confirmed rollback/unlock/release',()=>own(async root=>{
const fixture=await model(root),failure=Error('Synthetic original observation failure');fixture.failObservation(failure);await assert.rejects(fixture.inspect(),error=>error===failure);assert.equal(fixture.published(),0);assert.equal((await fixture.readRecords(fixture.state)).length,0)
}))
@@ -0,0 +1,10 @@
/** The fixed build inputs shared by the operator and Docker staging. */
export const OPERATOR_SOURCES=Object.freeze([
'database-writer-bootstrap-operator.mjs','database-writer-profile-operator.mts',
'writer-profile/operator-files.mjs','writer-profile/operator-contract.mjs','writer-profile/publisher.mjs','writer-profile/context.mjs',
'writer-profile/protected-source-registry.mts','writer-profile/maintenance-resolution.mts',
'writer-profile/catalogue.mts','writer-profile/catalogue-value-bridge.ts','writer-profile/catalogue-projection.ts','writer-profile/catalogue-leaves.ts',
'writer-profile/application-writer-profile-catalogue.ts','writer-profile/application-writer-profile-catalogue-values.ts',
'writer-profile/application-writer-profile-catalogue-parent-policy.ts','writer-profile/application-writer-profile-canonical.ts',
'writer-profile/fixed44-build-sql.ts','writer-profile/fixed44-build-definition.ts','writer-profile/expanded44-data-policy.ts',
])
@@ -9,6 +9,7 @@ import {deploymentRequest} from './database-writer-bootstrap-operator.mjs'
const hex=(v:unknown)=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
const parse=(b:Buffer)=>JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(b))
export async function main(args:string[]){
if(['maintenance-inspect','maintenance-resolve'].includes(args[0]))return (await import('./writer-profile/maintenance-resolution.mjs')).maintenanceMain(args)
const flags=['bootstrap','bootstrap-sha256','approval','approval-sha256','definition','definition-sha256','transport','transport-sha256','ca','source-registry','source-spki-sha256','journal']
assert(['publish','reconcile','preflight'].includes(args[0])&&args.length===1+2*flags.length)
assertJsDriverEnvironment(process.env)
@@ -38,7 +39,12 @@ export async function main(args:string[]){
assertJsDriverEnvironment(process.env)
assert.equal(approvedDefinition(definition),pinned.definitionSha256)
const installation=Object.freeze({...subject,roles:Object.fromEntries(roleKeys.map(k=>[k,request.roles[k].signedOid])),roleBindings:request.roles,expectedSource:request.source})
const verifier=createDatabaseSourceBindingVerifier({registryDirectory:get('source-registry'),installationId:subject.installationId,publicKeySpkiSha256:get('source-spki-sha256'),currentHeadSha256:request.sourceHeadSha256})
const sourcePin={registryDirectory:get('source-registry'),installationId:subject.installationId,publicKeySpkiSha256:get('source-spki-sha256'),currentHeadSha256:request.sourceHeadSha256}
// Preflight is the read-only lifecycle entry for protected registry evidence.
// A refused protection check never falls back to the signature-only verifier.
const verifier=args[0]==='preflight'
?(await import('./writer-profile/protected-source-registry.mjs')).createProtectedDatabaseSourceBindingVerifier(sourcePin)
:createDatabaseSourceBindingVerifier(sourcePin)
const publisher=createConnectedPublisher(installation,(s:Parameters<typeof collectSource44Catalogue>[0])=>collectSource44Catalogue(s,installation),verifier,pinned)
const pool=new pg.Pool(options)
// A pool-level async error never becomes raw stdout or a retry signal.
@@ -47,7 +53,7 @@ export async function main(args:string[]){
try{result=args[0]==='preflight'?await publisher.preflightCurrentProfile(pool,{deadlineAt:Date.now()+120000}):args[0]==='reconcile'?await publisher.reconcilePendingPublication(pool,{journalDirectory:get('journal'),deadlineAt:Date.now()+120000}):await publisher.publishCurrentProfile(pool,{privateKeyFile,expectedHeadSha256:pinned.expectedHeadSha256,journalDirectory:get('journal'),validityMs:pinned.validityMs,deadlineAt:Date.now()+120000})}
catch(error){primary=error;failed=true}
try{await pool.end()}catch(error){primary??=error;failed=true}
if(args[0]==='preflight'&&result?.state==='matched'&&!failed&&!asyncFailure)return {event:'application-writer-profile-preflight',outcome:'matched',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,sourceMatched:true,rolesMatched:true,catalogueMatched:true,writerAuthorized:false,cleanupUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(args[0]==='preflight'&&result?.state==='matched'&&result.platformProtectionVerified===true&&!failed&&!asyncFailure)return {event:'application-writer-profile-preflight',outcome:'matched',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,sourceMatched:true,rolesMatched:true,catalogueMatched:true,platformProtectionVerified:true,writerAuthorized:false,cleanupUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(result?.outcome==='committed')return {event:'application-writer-profile-publication',outcome:'committed',epoch:result.epoch,headSha256:result.headSha256,notAfter:result.notAfter,
cleanupUnconfirmed:result.cleanupUnconfirmed||asyncFailure||failed,journalUnconfirmed:!!result.journalUnconfirmed,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
if(args[0]==='reconcile'&&result&&!failed&&!asyncFailure&&['idle','confirmed','not-committed'].includes(result.state))return {event:'application-writer-profile-reconciliation',outcome:result.state,headSha256:result.headSha256??null,cleanupUnconfirmed:false,journalUnconfirmed:false,automaticReplay:false,generationAuthenticated:false,readyAuthorized:false}
@@ -55,5 +61,5 @@ export async function main(args:string[]){
}
if(process.argv[1]&&import.meta.url===pathToFileURL(path.resolve(process.argv[1])).href){
try{const result=await main(process.argv.slice(2));console.log(JSON.stringify(result));if(result.cleanupUnconfirmed||result.journalUnconfirmed)process.exitCode=2}
catch{console.error(JSON.stringify({event:'application-writer-profile-publication-refused',automaticReplay:false,inspectPendingBeforeRetry:true,readyAuthorized:false}));process.exitCode=1}
catch{console.error(JSON.stringify({event:['maintenance-inspect','maintenance-resolve'].includes(process.argv[2])?'database-maintenance-command-refused':'application-writer-profile-publication-refused',automaticReplay:false,inspectPendingBeforeRetry:true,readyAuthorized:false}));process.exitCode=1}
}
@@ -11,9 +11,9 @@ import {readCurrentApplicationWriterProfile} from '../src/db/application-writer-
import {syntheticFixture,stagedModelClient} from '../src/db/application-writer-profile-registry-read-model.test.ts'
const require=createRequire(new URL('../package.json',import.meta.url)),ts=require('typescript'),pg=require('pg')
const candidate=await fs.readFile(new URL('./writer-profile/publisher.mjs',import.meta.url),'utf8')
interface PreflightDiagnostics {state:string;headSha256:string;sourceMatched:boolean;rolesMatched:boolean;catalogueMatched:boolean;writerAuthorized:boolean;generationAuthenticated:boolean;readyAuthorized:boolean}
interface PublisherModule {createConnectedPublisher(installation:unknown,collect:()=>Promise<unknown>,sourceVerifier:{verify:()=>Promise<void>},approval:unknown):{preflightCurrentProfile(pool:unknown,options:{deadlineAt:number}):Promise<PreflightDiagnostics>}}
interface OperatorModule {main(args:string[]):Promise<{event:string;outcome:string;writerAuthorized:boolean;readyAuthorized:boolean}>}
interface PreflightDiagnostics {state:string;headSha256:string;sourceMatched:boolean;rolesMatched:boolean;catalogueMatched:boolean;platformProtectionVerified:boolean;writerAuthorized:boolean;generationAuthenticated:boolean;readyAuthorized:boolean}
interface PublisherModule {createConnectedPublisher(installation:unknown,collect:()=>Promise<unknown>,sourceVerifier:{verify:()=>Promise<{platformProtectionVerified:boolean}>},approval:unknown):{preflightCurrentProfile(pool:unknown,options:{deadlineAt:number}):Promise<PreflightDiagnostics>}}
interface OperatorModule {main(args:string[]):Promise<{event:string;outcome:string;platformProtectionVerified:boolean;writerAuthorized:boolean;readyAuthorized:boolean}>}
function compile<T>(source:string,modules:Record<string,unknown>):T{
const exports:Record<string,unknown>={};const compiled=ts.transpileModule(source.split('\nif(process.argv[1]')[0].replaceAll('import.meta.url',JSON.stringify('file:///candidate-not-cli.mts')),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(compiled,{exports,require:(name:string)=>{assert(name in modules,'Unapproved import '+name);return modules[name]},Buffer,URL,TextDecoder,Date,setTimeout,clearTimeout,Object,Error,AggregateError,Promise,Map,Set,process:{env:{},argv:[]}}, {filename:'candidate.mjs'})
@@ -56,17 +56,23 @@ function fixture(fault=''){
'../../src/db/application-writer-profile-format.js':format,
'./context.mjs':{ownedContext:async()=>{contexts++;if(fault==='late-actor'&&contexts===2)throw Error('Actor changed');return {pid:1,source}},sameOwned:(a:unknown,b:unknown)=>assert.deepEqual(a,b),sourceCoordinates:(v:{source:typeof source})=>v.source,protectedStructure:async()=>{events.push('roles');if(fault==='role-structure')throw Error('Unsafe role');return security},readCurrent:readCurrentApplicationWriterProfile,HISTORY:'history',CURRENT:'current'},
}
const api=compile<PublisherModule>(candidate,modules).createConnectedPublisher(installation,async()=>{events.push('catalogue');if(fault==='catalogue-eof')throw Error('Incomplete EOF');return catalogue},{verify:async()=>{verifications++;events.push('verify-source');if(fault==='source-verifier')throw Error('Unsigned source');if(fault==='late-expiry'&&verifications===2)approval.expiresAt=1}},approval)
const api=compile<PublisherModule>(candidate,modules).createConnectedPublisher(installation,async()=>{events.push('catalogue');if(fault==='catalogue-eof')throw Error('Incomplete EOF');return catalogue},{verify:async()=>{verifications++;events.push('verify-source');if(fault==='source-verifier')throw Error('Unsigned source');if(fault==='late-expiry'&&verifications===2)approval.expiresAt=1;return {platformProtectionVerified:!(fault==='initial-unprotected'&&verifications===1||fault==='terminal-unprotected'&&verifications===2)}}},approval)
return {run:()=>api.preflightCurrentProfile(pool,{deadlineAt:Date.now()+30000}),queries,events,f}
}
test('current44 preflight authenticates signed current and actual component inputs on one read-only lease',async()=>{
const f=fixture(),result=await f.run();assert.equal(result.state,'matched');assert.equal(result.headSha256,f.f.state.current[0].head_sha256)
assert.equal(result.sourceMatched,true);assert.equal(result.rolesMatched,true);assert.equal(result.catalogueMatched,true)
assert.equal(result.platformProtectionVerified,true)
assert.equal(result.writerAuthorized,false);assert.equal(result.generationAuthenticated,false);assert.equal(result.readyAuthorized,false)
assert.equal(f.queries.filter(q=>q.startsWith('BEGIN')).length,1);assert(f.queries.includes('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'));assert.equal(f.queries.filter(q=>q==='ROLLBACK').length,1)
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)));assert.equal(f.events.at(-1),'release:false')
assert.deepEqual(f.events.slice(0,6),['capture-source','verify-source','roles','catalogue','verify-source','fresh-source'])
})
for(const fault of ['initial-unprotected','terminal-unprotected'])test('preflight cannot promote '+fault+' into protected evidence',async()=>{
const f=fixture(fault),result=await f.run();assert.equal(result.platformProtectionVerified,false)
assert.equal(result.writerAuthorized,false);assert.equal(result.readyAuthorized,false)
assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)))
})
for(const fault of ['tamper','empty','expired','signed-source','signed-role','signed-catalogue','signed-definition','signed-manifest','role-structure','catalogue-eof','source-verifier','late-source','late-actor','late-expiry','late-clock','rollback-ack'])test('preflight refuses '+fault+' without any write or replay',async()=>{
const f=fixture(fault);await assert.rejects(f.run());assert(!f.queries.some(q=>/^(?:INSERT|UPDATE|DELETE|COMMIT|CREATE|ALTER|DROP)\b/.test(q)));assert.equal(f.queries.filter(q=>q.startsWith('BEGIN')).length,1)
})
@@ -74,23 +80,28 @@ test('existing operator main dispatches preflight only after protected input val
const cli=await fs.readFile(new URL('./database-writer-profile-operator.mts',import.meta.url),'utf8'),f=syntheticFixture('1'),reads:string[]=[],events:string[]=[]
const request={installationId:f.subject.installationId,roles:Object.fromEntries(['runtime','operator','capture','publisher','control'].map((k,i)=>[k,{name:k,signedOid:String(i+11)}])),source:f.policy.source,sourceHeadSha256:'a'.repeat(64),tls:{caSha256:'a'.repeat(64)}}
const files:Record<string,unknown>={bootstrap:{format:'application-writer-bootstrap-v1',publicationAuthorized:false,readyAuthorized:false,deploymentRequest:request,verificationSubject:f.subject},approval:{expiresAt:Date.now()+60000,definitionSha256:'b'.repeat(64)},definition:{},transport:{},ca:'fixture'}
let failEnd=false,asyncError=false
let failEnd=false,asyncError=false,protectedRefused=false
const retainedPools:Pool[]=[]
class Pool extends EventEmitter{constructor(){super();retainedPools.push(this)}async end(){events.push('pool-end');if(failEnd)throw Error('Pool cleanup unconfirmed')}}
const result={state:'matched',epoch:'1',headSha256:'c'.repeat(64),notAfter:Date.now()+60000}
const result={state:'matched',epoch:'1',headSha256:'c'.repeat(64),notAfter:Date.now()+60000,platformProtectionVerified:true}
const protectedVerifier={verify:async()=>({platformProtectionVerified:true})}
const modules={
'node:assert/strict':assert,'node:path':require('node:path'),'node:url':require('node:url'),'node:crypto':require('node:crypto'),
'./writer-profile/operator-files.mjs':{readProtected:async(file:string)=>{reads.push(file);assert(file in files);return Buffer.from(JSON.stringify(files[file]))},protectedDirectory:async()=>{},sha:format.sha},
'./writer-profile/operator-contract.mjs':{approval:(value:unknown)=>{events.push('approval');return value},reconciliationApproval:()=>{throw Error('No expired reconciliation bypass')},poolOptions:()=>{events.push('transport');return {}},roleKeys:['runtime','operator','capture','publisher','control'],assertJsDriverEnvironment:()=>{}},
'./database-writer-bootstrap-operator.mjs':{deploymentRequest:(value:unknown)=>value},'../src/db/database-source-binding.js':{createDatabaseSourceBindingVerifier:()=>({})},
pg:{Pool},'./writer-profile/publisher.mjs':{createConnectedPublisher:()=>({preflightCurrentProfile:async()=>{events.push('preflight');if(asyncError){const activePool=retainedPools.at(-1);assert(activePool);activePool.emit('error',Error('Async pool fault'))}return result},publishCurrentProfile:()=>{throw Error('No publish')},reconcilePendingPublication:()=>{throw Error('No reconcile')}})},
'./database-writer-bootstrap-operator.mjs':{deploymentRequest:(value:unknown)=>value},'../src/db/database-source-binding.js':{createDatabaseSourceBindingVerifier:()=>{throw Error('Preflight must never fall back to unprotected source verification')}},
'./writer-profile/protected-source-registry.mjs':{createProtectedDatabaseSourceBindingVerifier:(pin:{registryDirectory:string;currentHeadSha256:string})=>{events.push('protected-source');assert.equal(pin.registryDirectory,'source-registry');assert.equal(pin.currentHeadSha256,request.sourceHeadSha256);if(protectedRefused)throw Error('Protected source refused');return protectedVerifier}},
pg:{Pool},'./writer-profile/publisher.mjs':{createConnectedPublisher:(_installation:unknown,_collect:unknown,verifier:unknown)=>{assert.equal(verifier,protectedVerifier);return {preflightCurrentProfile:async()=>{events.push('preflight');if(asyncError){const activePool=retainedPools.at(-1);assert(activePool);activePool.emit('error',Error('Async pool fault'))}return result},publishCurrentProfile:()=>{throw Error('No publish')},reconcilePendingPublication:()=>{throw Error('No reconcile')}}}},
'./writer-profile/catalogue.mjs':{collectSource44Catalogue:()=>{},approvedDefinition:()=> 'b'.repeat(64)},
}
const flags=['bootstrap','bootstrap-sha256','approval','approval-sha256','definition','definition-sha256','transport','transport-sha256','ca','source-registry','source-spki-sha256','journal'],args=['preflight',...flags.flatMap(flag=>['--'+flag,flag.endsWith('sha256')?'a'.repeat(64):flag])]
const output=await compile<OperatorModule>(cli,modules).main(args)
assert.equal(output.event,'application-writer-profile-preflight');assert.equal(output.outcome,'matched');assert.equal(output.writerAuthorized,false);assert.equal(output.readyAuthorized,false)
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca']);assert.deepEqual(events,['approval','transport','preflight','pool-end'])
assert.equal(output.event,'application-writer-profile-preflight');assert.equal(output.outcome,'matched');assert.equal(output.platformProtectionVerified,true);assert.equal(output.writerAuthorized,false);assert.equal(output.readyAuthorized,false)
assert.deepEqual(reads,['bootstrap','approval','definition','transport','ca']);assert.deepEqual(events,['approval','transport','protected-source','preflight','pool-end'])
failEnd=true;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Pool cleanup unconfirmed/)
failEnd=false;asyncError=true;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Publisher terminal unconfirmed/)
asyncError=false;result.platformProtectionVerified=false;await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Publisher terminal unconfirmed/)
result.platformProtectionVerified=true;protectedRefused=true;const before=retainedPools.length
await assert.rejects(compile<OperatorModule>(cli,modules).main(args),/Protected source refused/);assert.equal(retainedPools.length,before,'Protection failure must not open a Pool or fall back')
})
@@ -0,0 +1,29 @@
/** Build-time only: copy the exact operator inputs into a scoped Docker context. */
import assert from 'node:assert/strict'
import {constants} from 'node:fs'
import {copyFile,lstat,mkdir} from 'node:fs/promises'
import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {OPERATOR_SOURCES} from './database-operator-sources.mjs'
const scriptsDirectory=path.dirname(fileURLToPath(import.meta.url))
const buildInputs=Object.freeze(['build-database-operator.mjs','database-operator-sources.mjs',...OPERATOR_SOURCES])
export async function stageDatabaseOperatorScripts(destination,source=scriptsDirectory){
const from=path.resolve(source),to=path.resolve(destination)
assert(from!==to,'A separate operator staging directory is required')
// Refuse an incomplete source tree before writing any staged files.
for(const file of buildInputs)assert((await lstat(path.join(from,file))).isFile(),'Regular operator build input required: '+file)
for(const file of buildInputs){
const target=path.join(to,file)
await mkdir(path.dirname(target),{recursive:true})
await copyFile(path.join(from,file),target,constants.COPYFILE_EXCL)
}
return buildInputs.length
}
if(process.argv[1]&&import.meta.url===pathToFileURL(path.resolve(process.argv[1])).href){
assert(process.argv.length===3&&process.argv[2],'A single Docker scripts destination is required')
const files=await stageDatabaseOperatorScripts(process.argv[2])
console.log(JSON.stringify({event:'database-operator-inputs-staged',files}))
}
+15 -6
View File
@@ -65,8 +65,9 @@ const telemetrySettingsConfiguration = process.argv.includes('--telemetry-settin
if (telemetrySettingsConfiguration && process.argv.slice(2).some(arg => arg !== '--telemetry-settings-configuration')) throw new Error('Telemetry settings configuration requires its own disposable cluster')
const routingReservationSchema = process.argv.includes('--routing-reservation-schema')
if (routingReservationSchema && process.argv.slice(2).some(arg => arg !== '--routing-reservation-schema')) throw new Error('Routing reservation schema requires its own disposable cluster')
const routingForwardRounds = process.argv.includes('--routing-network-forward-rounds')
if (routingForwardRounds && process.argv.slice(2).some(arg => arg !== '--routing-network-forward-rounds')) throw new Error('Routing forward rounds requires its own disposable cluster')
const routingOriginalEffects = process.argv.includes('--routing-original-effects')
const routingForwardRounds = routingOriginalEffects || process.argv.includes('--routing-network-forward-rounds')
if (routingForwardRounds && process.argv.slice(2).some(arg => arg !== (routingOriginalEffects ? '--routing-original-effects' : '--routing-network-forward-rounds'))) throw new Error('Routing forward rounds requires its own disposable cluster')
const flowActiveServices = process.argv.includes('--flow-active-services')
if (flowActiveServices && process.argv.slice(2).some(arg => arg !== '--flow-active-services')) throw new Error('Flow active services requires its own disposable cluster')
const flowActiveRebuild = process.argv.includes('--flow-active-rebuild')
@@ -93,8 +94,11 @@ const operatorPeerMetadata = process.argv.includes('--operator-peer-metadata')
if (operatorPeerMetadata && process.argv.slice(2).some(arg => arg !== '--operator-peer-metadata')) throw new Error('Operator peer metadata requires a separate disposable cluster without restore')
const externalPeerMetadata = process.argv.includes('--external-peer-metadata')
if (externalPeerMetadata && process.argv.slice(2).some(arg => arg !== '--external-peer-metadata')) throw new Error('Guarded external peer metadata requires a separate disposable cluster without restore')
const mcpOperationOrigin = process.argv.includes('--mcp-operation-origin')
if (mcpOperationOrigin && process.argv.slice(2).some(arg => arg !== '--mcp-operation-origin')) throw new Error('MCP operation root requires a separate disposable cluster without restore')
const pollOperationRetirement = process.argv.includes('--poll-operation-retirement')
const ordinaryOperationRetirement = process.argv.includes('--ordinary-operation-retirement')
const probeOperationRetirement = process.argv.includes('--probe-operation-retirement')
const mcpOperationOrigin = process.argv.includes('--mcp-operation-origin') || pollOperationRetirement || ordinaryOperationRetirement || probeOperationRetirement
if (mcpOperationOrigin && process.argv.slice(2).some(arg => arg !== (probeOperationRetirement ? '--probe-operation-retirement' : ordinaryOperationRetirement ? '--ordinary-operation-retirement' : pollOperationRetirement ? '--poll-operation-retirement' : '--mcp-operation-origin'))) throw new Error('MCP operation root requires a separate disposable cluster without restore')
const mcpSpeedOriginal = process.argv.includes('--mcp-speed-original')
if (mcpSpeedOriginal && process.argv.slice(2).some(arg => arg !== '--mcp-speed-original')) throw new Error('MCP speed component requires a separate disposable cluster without restore')
const routerConfigurationGuard = process.argv.includes('--router-configuration-guard')
@@ -242,12 +246,13 @@ try {
if (regressionOnly) console.log('Selected regression only: primary integration and terminal restore are not run')
if (profilesOnly) console.log('Profiles-only regression matrix: primary integration NOT RUN; terminal restore NOT RUN; full acceptance is not claimed')
const result = regressionOnly || profilesOnly ? 0 : await new Promise((resolve, reject) => {
const child = spawn(process.execPath, pollMemory ? ['--max-old-space-size=192', '--max-semi-space-size=16', fileURLToPath(new URL('../dist-mcp-memory/services/server-poll-memory.owned.test.js', import.meta.url))] : ['--import', 'tsx', principalSecuritySource ? 'src/modules/mcp/principal-security-source.owned.test.ts' : pollJournalSnapshot ? 'src/services/server-poll-journal-snapshot.owned.test.ts' : pollTerminalArchive ? 'src/services/server-poll-terminal-archive.owned.test.ts' : pollArchiveSchema ? 'src/db/server-poll-terminal-archives.sql.test.ts' : telemetrySettingsConfiguration ? 'src/modules/mcp/telemetry-settings-configuration.owned.test.ts' : routingReservationSchema ? 'src/db/routing-reservation-schema.owned.test.ts' : routingForwardRounds ? 'src/modules/mcp/routing-network-forward-rounds.owned.test.ts' : flowActiveServices ? 'src/services/traffic-flow-active-services.owned.test.ts' : flowActiveRebuild ? 'src/services/traffic-flow-active-rebuild.owned.test.ts' : flowQuality ? 'src/services/traffic-flow-quality.owned.test.ts' : flowWorkerIpc ? 'src/services/traffic-flow-worker-ipc.owned.test.ts' : manualUptime ? 'src/modules/mcp/manual-uptime-http.test.ts' : manualPingGroup ? 'src/modules/mcp/manual-ping-group-http.test.ts' : manualTraffic ? 'src/modules/mcp/manual-traffic-http.test.ts' : manualApiPing ? 'src/modules/mcp/manual-api-ping-http.test.ts' : simpleSpeedChr ? 'src/modules/mcp/simple-speed-chr.test.ts' : simpleSpeed ? 'src/modules/mcp/simple-speed-http.test.ts' : externalPeerMetadata ? 'src/services/bandwidth-external-peer-metadata.sql.test.ts' : operatorPeerMetadata ? 'src/services/bandwidth-operator-metadata.sql.test.ts' : publicRegistryMetadata ? 'src/services/bandwidth-public-registry.sql.test.ts' : operatorDockerSql ? 'src/services/bandwidth-operator-docker-stop.sql.test.ts' : mcpOperationOrigin ? 'src/modules/mcp/operation-execution-origin.sql.test.ts' : mcpSpeedOriginal ? 'src/modules/mcp/speed-bandwidth-original.sql.test.ts' : routerConfigurationGuard ? 'src/services/router-configuration-guard.sql.test.ts' : scheduledSpeedOriginal ? 'src/services/bandwidth-scheduled-speed-original.sql.test.ts' : bandwidthSpeedWorkflow ? 'src/services/bandwidth-speed-workflow.sql.test.ts' : restSpeedOriginal ? 'src/services/bandwidth-rest-speed-original.sql.test.ts' : operationLocks ? 'src/services/operation-config-lock-lease.sql.test.ts' : serviceExecutionOrigin ? 'src/modules/mcp/service-execution-origin.sql.test.ts' : bandwidthNodeSource ? 'src/services/bandwidth-node-source.sql.test.ts' : bandwidthExternalRegistry ? 'src/services/bandwidth-external-node-registry.sql.test.ts' : bandwidthNodeRegistry ? 'src/services/bandwidth-node-registry.sql.test.ts' : bandwidthControlSource ? 'src/services/bandwidth-control-source.sql.test.ts' : bandwidthAdmission ? 'src/services/bandwidth-admission.sql.test.ts' : serverPollStorageFresh ? 'src/services/server-poll-storage-fresh.sql.test.ts' : serverPollStorage ? 'src/services/server-poll-storage.sql.test.ts' : probeStorageFresh ? 'src/services/server-probe-storage-fresh.sql.test.ts' : probeLegacyOrphans ? 'src/services/server-probe-legacy-orphans.sql.test.ts' : diagnosticCancelHost ? 'src/modules/mcp/diagnostic-cancellation-storage.sql.test.ts' : ipsecExportHttp ? 'src/modules/mcp/ipsec-existing-certificate-export-http.sql.test.ts' : ipsecExportJournal ? 'src/services/ipsec-existing-certificate-export-rest-journal.sql.test.ts' : migrationDataInventory ? 'src/db/migration-data-inventory.sql.test.ts' : probeStorageHistory ? 'src/services/server-probe-storage-history.sql.test.ts' : probeStorageJournal ? 'src/services/server-probe-storage-journal.sql.test.ts' : controlledSchemaReference ? 'src/db/controlled-schema-reference.sql.test.ts' : controlledMigrations ? 'src/db/controlled-migrations.sql.test.ts' : databaseSchemaControl ? 'src/db/database-schema-control.sql.test.ts' : migrationSchemaInventory ? 'src/db/migration-schema-inventory.sql.test.ts' : routingNetworkNative ? 'src/modules/mcp/routing-network-native.sql.test.ts' : routingReservationRuntime ? 'src/modules/mcp/routing-reservation-runtime.sql.test.ts' : routingReservations ? 'src/modules/mcp/routing-reservation.sql.test.ts' : collectorPartitionWriters ? 'src/services/collector-partition-writes.sql.test.ts' : serverPollDurable ? 'src/services/server-poll-durable-session.sql.test.ts' : routingRecoveryAuthority ? 'src/modules/mcp/routing-recovery-authority.sql.test.ts' : legacyFlowRebuild ? 'src/test/legacy-flow-rebuild-sql.ts' : probeJournal ? 'src/services/server-probe-journal.sql.test.ts' : flowHostKeys ? 'src/modules/mcp/flow-host-keys-http.test.ts' : flowRebuildMcp ? 'src/modules/mcp/flow-facts-rebuild-http.test.ts' : flowRebuild ? 'src/test/flow-facts-rebuild-sql.ts' : flowWrites ? 'src/services/traffic-flow-write-transaction.pg.test.ts' : nativeConservation ? 'src/modules/mcp/native-conservation-sql.test.ts' : nativeCreateId ? 'src/modules/mcp/native-create-id-http.test.ts' : ipsecAdapters ? "src/mLine truncated
const child = spawn(process.execPath, pollMemory ? ['--max-old-space-size=192', '--max-semi-space-size=16', fileURLToPath(new URL('../dist-mcp-memory/services/server-poll-memory.owned.test.js', import.meta.url))] : ['--import', 'tsx', principalSecuritySource ? 'src/modules/mcp/principal-security-source.owned.test.ts' : pollJournalSnapshot ? 'src/services/server-poll-journal-snapshot.owned.test.ts' : pollTerminalArchive ? 'src/services/server-poll-terminal-archive.owned.test.ts' : pollArchiveSchema ? 'src/db/server-poll-terminal-archives.sql.test.ts' : telemetrySettingsConfiguration ? 'src/modules/mcp/telemetry-settings-configuration.owned.test.ts' : routingReservationSchema ? 'src/db/routing-reservation-schema.owned.test.ts' : routingForwardRounds ? 'src/modules/mcp/routing-network-forward-rounds.owned.test.ts' : flowActiveServices ? 'src/services/traffic-flow-active-services.owned.test.ts' : flowActiveRebuild ? 'src/services/traffic-flow-active-rebuild.owned.test.ts' : flowQuality ? 'src/services/traffic-flow-quality.owned.test.ts' : flowWorkerIpc ? 'src/services/traffic-flow-worker-ipc.owned.test.ts' : manualUptime ? 'src/modules/mcp/manual-uptime-http.test.ts' : manualPingGroup ? 'src/modules/mcp/manual-ping-group-http.test.ts' : manualTraffic ? 'src/modules/mcp/manual-traffic-http.test.ts' : manualApiPing ? 'src/modules/mcp/manual-api-ping-http.test.ts' : simpleSpeedChr ? 'src/modules/mcp/simple-speed-chr.test.ts' : simpleSpeed ? 'src/modules/mcp/simple-speed-http.test.ts' : externalPeerMetadata ? 'src/services/bandwidth-external-peer-metadata.sql.test.ts' : operatorPeerMetadata ? 'src/services/bandwidth-operator-metadata.sql.test.ts' : publicRegistryMetadata ? 'src/services/bandwidth-public-registry.sql.test.ts' : operatorDockerSql ? 'src/services/bandwidth-operator-docker-stop.sql.test.ts' : probeOperationRetirement ? 'src/services/server-probe-operation-retirement.owned.test.ts' : ordinaryOperationRetirement ? 'src/modules/mcp/ordinary-operation-retirement.owned.test.ts' : pollOperationRetirement ? 'src/services/server-poll-operation-retirement.owned.test.ts' : mcpOperationOrigin ? 'src/modules/mcp/operation-execution-origin.sql.test.ts' : mcpSpeedOriginal ? 'src/modules/mcp/speed-bandwidth-original.sql.test.ts' : routerConfigurationGuard ? 'src/services/router-configuration-guard.sql.test.ts' : scheduledSpeedOriginal ? 'src/services/bandwidth-scheduled-speed-original.sql.test.ts' : bandwidthSpeedWorkflow ? 'src/services/bandwidth-speed-workflow.sql.test.ts' : restSpeedOriginal ? 'src/services/bandwidth-rest-speed-original.sql.test.ts' : operationLocks ? 'src/services/operation-config-lock-lease.sql.test.ts' : serviceExecutionOrigin ? 'src/modules/mcp/service-execution-origin.sql.test.ts' : bandwidthNodeSource ? 'src/services/bandwidth-node-source.sql.test.ts' : bandwidthExternalRegistry ? 'src/services/bandwidth-external-node-registry.sql.test.ts' : bandwidthNodeRegistry ? 'src/services/bandwidth-node-registry.sql.test.ts' : bandwidthControlSource ? 'src/services/bandwidth-control-source.sql.test.ts' : bandwidthAdmission ? 'src/services/bandwidth-admission.sql.test.ts' : serverPollStorageFresh ? 'src/services/server-poll-storage-fresh.sql.test.ts' : serverPollStorage ? 'src/services/server-poll-storage.sql.test.ts' : probeStorageFresh ? 'src/services/server-probe-storage-fresh.sql.test.ts' : probeLegacyOrphans ? 'src/services/server-probe-legacy-orphans.sql.test.ts' : diagnosticCancelHost ? 'src/modules/mcp/diagnostic-cancellation-storage.sql.test.ts' : ipsecExportHttp ? 'src/modules/mcp/ipsec-existing-certificate-export-http.sql.test.ts' : ipsecExportJournal ? 'src/services/ipsec-existing-certificate-export-rest-journal.sql.test.ts' : migrationDataInventory ? 'src/db/migration-data-inventory.sql.test.ts' : probeStorageHistory ? 'src/services/server-probe-storage-history.sql.test.ts' : probeStorageJournal ? 'src/services/server-probe-storage-journal.sql.test.ts' : controlledSchemaReference ? 'src/db/controlled-schema-reference.sql.test.ts' : controlledMigrations ? 'src/db/controlled-migrations.sql.test.ts' : databaseSchemaControl ? 'src/db/database-schema-control.sql.test.ts' : migrationSchemaInventory ? 'src/db/migration-schema-inventory.sql.test.ts' : routingNetworkNative ? 'src/modules/mcp/routing-network-native.sql.test.ts' : routingReservationRuntime ? 'src/modules/mcp/routing-reservation-runtime.sql.test.ts' : routingReservations ? 'src/modules/mcp/routing-reservation.sql.test.ts' : collectorPartitionWriters ? 'src/services/collector-partition-writes.sql.test.ts' : serverPollDurable ? 'src/services/server-poll-durable-session.sql.test.ts' : routingRecoveryAuthority ? 'src/modules/mcp/routing-recovery-authority.sql.test.ts' : legacyFlowRebuild ? 'src/test/legacy-flow-rebuild-sql.ts' : probeJournal ? 'src/services/server-probe-journal.sql.test.ts' : flowHostKeys ? 'src/modules/mcp/flow-host-keys-http.test.ts' : flowRebuildMcp ? 'src/modules/mcp/flow-facts-rebuild-http.test.ts' : flowRebuildLine truncated
cwd: pollMemory ? directory : new URL('../', import.meta.url), stdio: 'inherit', env: { ...(pollMemory ? pollMemoryEnvironment : process.env),
...(routingOriginalEffects ? { MMAPP_OWN_ROUTING_ORIGINAL_EFFECTS: 'true' } : {}),
DATABASE_URL: `postgresql://mcp_test:${password}@127.0.0.1:${port}/postgres`,
DATABASE_PATH: join(directory, 'no-legacy-database.sqlite'), SQLITE_IMPORT_FULL_HISTORY: 'false',
MCP_STATE_DIR: join(directory, 'state'), AUTH_REQUIRED: 'true', AUTH_JWT_SECRET: 'mcp-integration-test-only', MMAPP_TEST_WG_EXPORT_ONLY: String(wireguardExport),
MCP_PUBLIC_URL: 'http://localhost:8000/mcp', NODE_ENV: 'test', PATH: testPath, NODE_OPTIONS: pollMemory ? '' : process.env.NODE_OPTIONS, MMAPP_OWN_PRINCIPAL_SECURITY_SOURCE: String(principalSecuritySource), MMAPP_OWN_POLL_JOURNAL_SNAPSHOT: String(pollJournalSnapshot), MMAPP_OWN_POLL_MEMORY: String(pollMemory), MMAPP_OWN_POLL_BOOTSTRAP: String(pollMemoryBootstrap), MMAPP_OWN_POLL_COST_DIAGNOSTICS: String(pollMemoryCost), MMAPP_OWN_POLL_ARCHIVE_CLAIM_LOSS: String(pollArchiveClaimLoss), MMAPP_OWN_POLL_ARCHIVE_HISTORY: String(pollArchiveHistory), MMAPP_OWN_POLL_ARCHIVE_COLD: String(pollArchiveCold), MMAPP_OWN_POLL_ARCHIVE_LOST_ACK: String(pollArchiveLostAck), MMAPP_OWN_POLL_TERMINAL_ARCHIVE: String(pollTerminalArchive), MMAPP_OWN_POLL_ARCHIVE_SCHEMA: String(pollArchiveSchema), MMAPP_OWN_TELEMETRY_SETTINGS_CONFIGURATION: String(telemetrySettingsConfiguration), MMAPP_OWN_ROUTING_RESERVATION_SCHEMA: String(routingReservationSchema), MMAPP_OWN_ROUTING_FORWARD_ROUNDS: String(routingForwardRounds), MMAPP_OWN_FLOW_ACTIVE_SERVICES: String(flowActiveServices), MMAPP_OWN_FLOW_ACTIVE_REBUILD: String(flowActiveRebuild), MMAPP_OWN_FLOW_QUALITY: String(flowQuality), MMAPP_OWN_FLOW_WORKER_IPC: String(flowWorkerIpc), MMAPP_OWN_MANUAL_UPTIME: String(manualUptime), MMAPP_OWN_MANUAL_PING_GROUP: String(manualPingGroup), MMAPP_OWN_MANUAL_TRAFFIC: String(manualTraffic), MMAPP_OWN_MANUAL_API_PING: String(manualApiPing), MMAPP_OWN_SIMPLE_SPEED: String(simpleSpeed), MMAPP_OWN_SIMPLE_SPEED_CHR: String(simpleSpeedChr), MMAPP_OWN_DIAGNOSTIC_CANCEL_HOST: String(diagnosticCancelHost), MMAPP_OWN_OPERATOR_DOCKER_SQL: String(operatorDockerSql), MMAPP_OWN_PUBLIC_REGISTRY_METADATA: String(publicRegistryMetadata), MMAPP_OWN_OPERATOR_PEER_METADATA: String(operatorPeerMetadata), MMAPP_OWN_EXTERNAL_PEER_METADATA: String(externalPeerMetadata), MMAPP_OWN_BANDWIDTH_EXTERNAL_REGISTRY: String(externalPeerMetadata || operatorPeerMetadata || publicRegistryMetadata || operatorDockerSql || mcpSpeedOriginal || bandwidthSpeedWorkflow || bandwidthExternalRegistry), MMAPP_OWN_BANDWIDTH_SPEED_WORKFLOW: String(bandwidthSpeedWorkflow), MMAPP_OWN_REST_SPEED_ORIGINAL: String(restSpeedOriginal), MMAPP_OWN_MCP_OPERATION_ORIGIN: String(mcpOperationOrigin), MMAPP_OWN_MCP_SPEED_ORIGINAL: String(mcpSpeedOriginal), MMAPP_OWN_SCHEDULED_SPEED_ORIGINAL: String(scheduledSpeedOriginal), MMAPP_TEST_ROUTER_GUARD_OWNED: String(routerConfigurationGuard), MMAPP_TEST_ROUTER_GUARD_CLUSTER_DISPOSABLE: String(routerConfigurationGuard), MMAPP_TEST_ROUTER_GUARD_ADMIN_URL: routerConfigurationGuard ? `postgresql://mcp_test:${password}@127.0.0.1:${port}/postgres` : '', MMAPP_OWN_BANDWIDTH_ADMISSION: String(externalPeerMetadata || operatorPeerMetadata || publicRegistryMetadata || operatorDockerSql || mcpOperationOrigin || mcpSpeedOriginal || scheduledSpeedOriginal || bandwidthSpeedWorkflow || restSpeedOriginal || bandwidthExternalRegistry || operationLocks || serviceExecutionOrigin || bandwidthAdmission || bandwidthControlSource || bandwidthNodeRegistry || bandwidthNodeSource), MMAPP_OWN_BANDWIDTH_RESTORE: String((bandwidthAdmission || bandwidthControlSource || bandwidthNodeRegistry || bandwidthNodeSource) && process.argv.includes('--restore')), MMAPP_OWN_IPSEC_EXPORT_HTTP: String(ipsecExportHttp), MMAPP_OWN_IPSEC_EXPORT_JOURNAL: String(ipsecExportJournal), MMAPP_OWN_SQL_DB: 'postgres', MMAPP_OWN_PROBE_LEGACY_ORPHANS: String(probeLegacyOrphans), MMAPP_OWN_PROBE_STORAGE_HISTORY: String(probeStorageHistory), MMAPP_OWN_PROBE_STORAGE_FRESH: String(probeStorageFresh), MMAPP_OWN_PROBE_STORAGE_JOURNAL: String(probeStorageJournal || probeStorageFresh), MMAPP_OWN_CONTROLLED_SCHEMA_REFERENCE: String(controlledSchemaReference), MMAPP_OWN_CONTROLLED_MIGRATIONS: String(controlledMigrations), MMAPP_OWN_DATABASE_SCHEMA_CONTROL: String(databaseSchemaControl), MMAPP_OWN_MIGRATION_DATA_INVENTORY: String(migrationDataInventory), MMAPP_OWN_MIGRATION_SCHEMA_INVENTORY: String(migrationSchemaInventory), MMAPP_OWN_ROUTING_NETWORK_NATIVE: String(routingNetworkNative), MMAPP_OWN_ROUTING_RESERVATIONS: String(routingReservations || routingReservationRuntime), MMAPP_OWN_COLLECTOR_PARTITION_WRITERS: String(collectorPartitionWriters), MMAPP_OWN_SERVER_POLL_STORAGE_FRESH: String(serverPollStorageFresh), MMAPP_OWN_SERVER_POLL_STORAGE: String(serverPollStorage), MMAPP_OWN_SERVER_POLL_DURABLE: String(serverPollDurable), MMAPP_OWN_ROUTING_RECOVERY_AUTHORITY: String(routingRecoveryAuthority), MMAPP_OWN_LEGACY_FLOW_REBUILD: String(legacyFlowRebuild), MMAPP_OWN_PROBE_JOURNAL: String(probeJournal), MMAPP_MODULE_COMPLETION_ONLY: String(moduleCompletion), MMAPP_MODULE_COMPLETION_TARGET: completionTarget, MMAPP_OWN_FLOW_HOST_KEYS: String(flowHostKeys), MMAPP_OWN_MCP_FLOW_REBUILD: String(flowRebuildMcp), MMAPP_OWN_FLOW_WRITES: String(flowWrites), MMAPP_OWN_FLOW_REBUILD: String(flowRebuild) },
MCP_PUBLIC_URL: 'http://localhost:8000/mcp', NODE_ENV: 'test', PATH: testPath, NODE_OPTIONS: pollMemory ? '' : process.env.NODE_OPTIONS, MMAPP_OWN_PRINCIPAL_SECURITY_SOURCE: String(principalSecuritySource), MMAPP_OWN_POLL_JOURNAL_SNAPSHOT: String(pollJournalSnapshot), MMAPP_OWN_POLL_MEMORY: String(pollMemory), MMAPP_OWN_POLL_BOOTSTRAP: String(pollMemoryBootstrap), MMAPP_OWN_POLL_COST_DIAGNOSTICS: String(pollMemoryCost), MMAPP_OWN_POLL_ARCHIVE_CLAIM_LOSS: String(pollArchiveClaimLoss), MMAPP_OWN_POLL_ARCHIVE_HISTORY: String(pollArchiveHistory), MMAPP_OWN_POLL_ARCHIVE_COLD: String(pollArchiveCold), MMAPP_OWN_POLL_ARCHIVE_LOST_ACK: String(pollArchiveLostAck), MMAPP_OWN_POLL_TERMINAL_ARCHIVE: String(pollTerminalArchive), MMAPP_OWN_POLL_ARCHIVE_SCHEMA: String(pollArchiveSchema), MMAPP_OWN_TELEMETRY_SETTINGS_CONFIGURATION: String(telemetrySettingsConfiguration), MMAPP_OWN_ROUTING_RESERVATION_SCHEMA: String(routingReservationSchema), MMAPP_OWN_ROUTING_FORWARD_ROUNDS: String(routingForwardRounds), MMAPP_OWN_FLOW_ACTIVE_SERVICES: String(flowActiveServices), MMAPP_OWN_FLOW_ACTIVE_REBUILD: String(flowActiveRebuild), MMAPP_OWN_FLOW_QUALITY: String(flowQuality), MMAPP_OWN_FLOW_WORKER_IPC: String(flowWorkerIpc), MMAPP_OWN_MANUAL_UPTIME: String(manualUptime), MMAPP_OWN_MANUAL_PING_GROUP: String(manualPingGroup), MMAPP_OWN_MANUAL_TRAFFIC: String(manualTraffic), MMAPP_OWN_MANUAL_API_PING: String(manualApiPing), MMAPP_OWN_SIMPLE_SPEED: String(simpleSpeed), MMAPP_OWN_SIMPLE_SPEED_CHR: String(simpleSpeedChr), MMAPP_OWN_DIAGNOSTIC_CANCEL_HOST: String(diagnosticCancelHost), MMAPP_OWN_OPERATOR_DOCKER_SQL: String(operatorDockerSql), MMAPP_OWN_PUBLIC_REGISTRY_METADATA: String(publicRegistryMetadata), MMAPP_OWN_OPERATOR_PEER_METADATA: String(operatorPeerMetadata), MMAPP_OWN_EXTERNAL_PEER_METADATA: String(externalPeerMetadata), MMAPP_OWN_BANDWIDTH_EXTERNAL_REGISTRY: String(externalPeerMetadata || operatorPeerMetadata || publicRegistryMetadata || operatorDockerSql || mcpSpeedOriginal || bandwidthSpeedWorkflow || bandwidthExternalRegistry), MMAPP_OWN_BANDWIDTH_SPEED_WORKFLOW: String(bandwidthSpeedWorkflow), MMAPP_OWN_REST_SPEED_ORIGINAL: String(restSpeedOriginal), MMAPP_OWN_MCP_OPERATION_ORIGIN: String(mcpOperationOrigin), MMAPP_OWN_PROBE_OPERATION_RETIREMENT: String(probeOperationRetirement), MMAPP_OWN_MCP_SPEED_ORIGINAL: String(mcpSpeedOriginal), MMAPP_OWN_SCHEDULED_SPEED_ORIGINAL: String(scheduledSpeedOriginal), MMAPP_TEST_ROUTER_GUARD_OWNED: String(routerConfigurationGuard), MMAPP_TEST_ROUTER_GUARD_CLUSTER_DISPOSABLE: String(routerConfigurationGuard), MMAPP_TEST_ROUTER_GUARD_ADMIN_URL: routerConfigurationGuard ? `postgresql://mcp_test:${password}@127.0.0.1:${port}/postgres` : '', MMAPP_OWN_BANDWIDTH_ADMISSION: String(externalPeerMetadata || operatorPeerMetadata || publicRegistryMetadata || operatorDockerSql || mcpOperationOrigin || mcpSpeedOriginal || scheduledSpeedOriginal || bandwidthSpeedWorkflow || restSpeedOriginal || bandwidthExternalRegistry || operationLocks || serviceExecutionOrigin || bandwidthAdmission || bandwidthControlSource || bandwidthNodeRegistry || bandwidthNodeSource), MMAPP_OWN_BANDWIDTH_RESTORE: String((bandwidthAdmission || bandwidthControlSource || bandwidthNodeRegistry || bandwidthNodeSource) && process.argv.includes('--restore')), MMAPP_OWN_IPSEC_EXPORT_HTTP: String(ipsecExportHttp), MMAPP_OWN_IPSEC_EXPORT_JOURNAL: String(ipsecExportJournal), MMAPP_OWN_SQL_DB: 'postgres', MMAPP_OWN_PROBE_LEGACY_ORPHANS: String(probeLegacyOrphans), MMAPP_OWN_PROBE_STORAGE_HISTORY: String(probeStorageHistory), MMAPP_OWN_PROBE_STORAGE_FRESH: String(probeStorageFresh), MMAPP_OWN_PROBE_STORAGE_JOURNAL: String(probeStorageJournal || probeStorageFresh), MMAPP_OWN_CONTROLLED_SCHEMA_REFERENCE: String(controlledSchemaReference), MMAPP_OWN_CONTROLLED_MIGRATIONS: String(controlledMigrations), MMAPP_OWN_DATABASE_SCHEMA_CONTROL: String(databaseSchemaControl), MMAPP_OWN_MIGRATION_DATA_INVENTORY: String(migrationDataInventory), MMAPP_OWN_MIGRATION_SCHEMA_INVENTORY: String(migrationSchemaInventory), MMAPP_OWN_ROUTING_NETWORK_NATIVE: String(routingNetworkNative), MMAPP_OWN_ROUTING_RESERVATIONS: String(routingReservations || routingReservationRuntime), MMAPP_OWN_COLLECTOR_PARTITION_WRITERS: String(collectorPartitionWriters), MMAPP_OWN_SERVER_POLL_STORAGE_FRESH: String(serverPollStorageFresh), MMAPP_OWN_SERVER_POLL_STORAGE: String(serverPollStorage), MMAPP_OWN_SERVER_POLL_DURABLE: String(serverPollDurable), MMAPP_OWN_ROUTING_RECOVERY_AUTHORITY: String(routingRecoveryAuthority), MMAPP_OWN_LEGACY_FLOW_REBUILD: String(legacyFlowRebuild), MMAPP_OWN_PROBE_JOURNAL: String(probeJournal), MMAPP_MODULE_COMPLETION_ONLY: String(moduleCompletion), MMAPP_MODULE_COMPLETION_TARGET: completionTarget, MMAPP_OWN_FLOW_HOST_KEYS: String(flowHostKeys), MMAPP_OWN_MCP_FLOW_REBUILD: String(flowRebuildMcp), MMAPP_OWN_FLOW_WRITES: String(flowWrites), MMAPP_OWN_FLOW_REBUILD: String(flowRebuild) },
})
child.once('error', reject); child.once('exit', code => resolve(code ?? 1))
})
@@ -268,6 +273,7 @@ try {
else if (result === 0 && pollArchiveSchema) console.log('Poll archive schema: owned nonempty40 preservation, numbered41 and structural refusal guards; no positive MCP archive claim')
else if (result === 0 && telemetrySettingsConfiguration) console.log('Telemetry settings: genuine MCP/SQL configuration binding, collector metadata tolerated; no native or external sources')
else if (result === 0 && routingReservationSchema) console.log('Routing reservation numbered0040: ownSQL fresh/legacy/manual preservation; no READY, backfill, capabilities or public native activation')
else if (result === 0 && routingOriginalEffects) console.log('Routing original effects: approved manual revision 2 and original effects checked against disposable PostgreSQL and synthetic RouterOS HTTP')
else if (result === 0 && routingForwardRounds) console.log('Routing forward rounds: genuine approved running CONFIG_APPLIED, shared cumulative native budget; logs/security/autoRecovery remain unaccepted')
else if (result === 0 && flowActiveServices) console.log('Flow active settings/purge: production services and main Worker; disposable SQL/localhost only')
else if (result === 0 && flowActiveRebuild) console.log('Flow active rebuild: production shared service and genuine Worker drain; disposable SQL/localhost only')
@@ -283,6 +289,9 @@ try {
else if (result === 0 && operatorPeerMetadata) console.log('Operator peer metadata: actual managed endpoint SQL and genuine localhost license origin; synthetic Q, external metadata and full operator window remain open')
else if (result === 0 && publicRegistryMetadata) console.log('Public registry metadata: actual owned PostgreSQL and same physical lease; no secret/Q/native/write authority or backend-window acceptance')
else if (result === 0 && operatorDockerSql) console.log('Operator Docker SQL: actual owned PostgreSQL with mocked Docker HTTP only; not a real Docker/backend-window acceptance')
else if (result === 0 && ordinaryOperationRetirement) console.log('Ordinary operation retirement: owned actual logs-settings apply and worker')
else if (result === 0 && pollOperationRetirement) console.log('Poll operation retirement: owned authenticated historical fixtures and current reader/GC')
else if (result === 0 && probeOperationRetirement) console.log('Diagnostic retirement: actual owned worker tick, PostgreSQL and product read/replay; no live scheduling')
else if (result === 0 && mcpOperationOrigin) console.log('MCP operation root: owned PostgreSQL ordinary runner provenance; no restore/recovery, global writer activation, CHR or deployment')
else if (result === 0 && mcpSpeedOriginal) console.log('MCP speed: genuine trusted service context with real connection/grant/SQL, host receipt and localhost native; synthetic Q/mapping; not a public MCP runner/action acceptance')
else if (result === 0 && routerConfigurationGuard) console.log('Router configuration guard: dedicated owned databases/roles and localhost native fixture; no application migration, production activation or real CHR')
@@ -1,7 +1,7 @@
import EmbeddedPostgres from 'embedded-postgres'
import { createRequire } from 'node:module'
import { mkdir, mkdtemp, readdir, readFile, writeFile, cp, rm } from 'node:fs/promises'
import { resolve, join, relative, dirname } from 'node:path'
import { resolve, join, relative, dirname, basename } from 'node:path'
import { randomBytes, createHash } from 'node:crypto'
import { createServer } from 'node:net'
import { spawn } from 'node:child_process'
@@ -16,7 +16,8 @@ await readFile(join(root, 'backend/src/modules/mcp/routing-main-transition.ts'))
await readFile(join(root, 'backend/src/modules/mcp/routing-controller-baseline-acceptance.ts'))
if (process.argv.includes('--check-only')) { console.log(JSON.stringify({ mode, sourcePresent: true, acceptanceExecuted: false, productionApplied: false })); process.exit(0) }
await mkdir(join(root, 'tmp'), { recursive: true })
const directory = await mkdtemp(join(root, 'tmp/routing-controller-baseline-owned-')), output = join(directory, 'dist'), sourcePins = []
const ownedPrefix = 'mm-mcp-test-routing-controller-baseline-'
const directory = await mkdtemp(join(root, 'tmp', ownedPrefix)), output = join(directory, 'dist'), sourcePins = []
const hash = buffer => createHash('sha256').update(buffer).digest('hex')
async function compile(sourceDirectory) {
for (const entry of await readdir(sourceDirectory, { withFileTypes: true })) {
@@ -59,7 +60,7 @@ try {
// embedded-postgres waits for a new exit event; avoid waiting again if its
// owned child has already exited. This never targets another database.
if (postgres.process?.exitCode === null) await postgres.stop()
if (dirname(resolve(directory)) !== resolve(root, 'tmp') || !directory.includes('routing-controller-baseline-owned-')) throw new Error('Owned routing cleanup boundary changed')
if (dirname(resolve(directory)) !== resolve(root, 'tmp') || !basename(directory).startsWith(ownedPrefix)) throw new Error('Owned routing cleanup boundary changed')
await rm(join(directory, 'pg'), { recursive: true, force: true, maxRetries: 20, retryDelay: 100 })
console.log('Owned routing PostgreSQL stopped and its data removed; snapshot/log evidence retained')
}
@@ -0,0 +1,129 @@
/** Explicit host-operator inspection/acceptance. No restore, service restart, role
* enrollment or source/READY issuer. Populated data is accepted, not certified. */
import assert from 'node:assert/strict'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import {mkdir,readdir} from 'node:fs/promises'
import {readProtected,protectedDirectory,writeFile,withJournalLease,sha} from './operator-files.mjs'
import {poolOptions,STARTUP_OPTIONS,assertJsDriverEnvironment} from './operator-contract.mjs'
import {readMaintenanceRecoveryFacts,readMaintenanceAcceptanceRecords} from '../../src/services/database-maintenance-startup.js'
import {parseMaintenanceJSON,runtimeDatabaseTargetSha256,evaluateMaintenanceAcceptances,validateMaintenanceKnownCurrent,validateMaintenanceAcceptance,MAINTENANCE_ACCEPTANCE_FORMAT,type MaintenanceKnownCurrent} from '../../src/services/database-maintenance-resolution.js'
import {captureMigrationSchema} from '../../src/db/migration-schema-inventory.js'
import {MIGRATION_MANIFEST,CURRENT_REQUIRED_TABLES,migrationManifestDigest,inventoryMigrationDirectory} from '../../src/db/migration-manifest.js'
import type {Pool,PoolClient} from 'pg'
const hex=(v:unknown):v is string=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
function object(v:unknown):asserts v is Record<string,unknown>{assert(v&&typeof v==='object'&&!Array.isArray(v))}
function exact(v:unknown,keys:string[]):asserts v is Record<string,unknown>{object(v);assert.equal(Object.keys(v).sort().join('\0'),keys.sort().join('\0'))}
const stamp=(v:unknown):v is string=>typeof v==='string'&&Number.isFinite(Date.parse(v))&&new Date(v).toISOString()===v
const stable=(v:unknown)=>JSON.stringify(v)
const flags=['state-dir','transport','transport-sha256','runtime-target','runtime-target-sha256']
const validityMs=900000
/** Explicit local-container transport never falls back from TLS or environment.
* Caller launch review supplies --network container:<exact reviewed PG id>;
* this module has no Docker socket/API and claims only loopback + physical SQL proof. */
export function maintenancePoolConfiguration(value:unknown,ca?:Buffer,caSha256?:string){
object(value);const mode=value.mode===undefined?'tls':value.mode;assert(mode==='tls'||mode==='local-loopback')
exact(value,mode==='local-loopback'?['format','mode','connection']:value.mode===undefined?['format','connection','tls']:['format','mode','connection','tls'])
assert.equal(value.format,'database-maintenance-transport-v1');exact(value.connection,['host','port','user','password','database']);const connection=value.connection
assert(typeof connection.host==='string'&&typeof connection.user==='string'&&/^[^\x00-\x1f\x7f]{1,63}$/.test(connection.user)&&typeof connection.password==='string'&&connection.password.length>0&&connection.password.length<=4096&&!connection.password.includes('\0')&&typeof connection.database==='string'&&/^[^\x00-\x1f\x7f]{1,63}$/.test(connection.database)&&typeof connection.port==='number'&&Number.isSafeInteger(connection.port)&&Number(connection.port)>0&&Number(connection.port)<=65535)
let options,identity:unknown
if(mode==='tls'){
exact(value.tls,['serverName','spkiSha256']);assert(typeof value.tls.serverName==='string'&&/^[a-zA-Z0-9][a-zA-Z0-9.-]{0,252}$/.test(value.tls.serverName)&&hex(value.tls.spkiSha256));assert(ca&&hex(caSha256))
options=poolOptions(connection,{roles:{publisher:{name:connection.user}},source:{databaseName:connection.database},tls:{...value.tls,caSha256}},ca)
identity={mode,host:connection.host,port:connection.port,database:connection.database,tls:{...value.tls,caSha256}}
}else{
assert(connection.host==='127.0.0.1'||connection.host==='::1','Plaintext maintenance requires exact loopback and reviewed PostgreSQL container namespace');assert(ca===undefined&&caSha256===undefined)
options={host:connection.host,port:connection.port,user:connection.user,password:connection.password,database:connection.database,max:1,connectionTimeoutMillis:5000,idleTimeoutMillis:5000,statement_timeout:10000,lock_timeout:5000,options:STARTUP_OPTIONS,application_name:'mmapp-maintenance-acceptance',client_encoding:'UTF8',ssl:false}
identity={mode,host:connection.host,port:connection.port,database:connection.database}
}
return {options,transportBinding:{mode,endpointSha256:sha(stable(identity))} as MaintenanceKnownCurrent['transport']}
}
export function maintenanceApproval(value:unknown,inspectionSha256:string,inspectionCreatedAt:string){
exact(value,['format','resolutionId','decision','actorId','inspectionSha256','issuedAt','expiresAt','managedBackendStopped','historicOutcomesRemainUnknown','automaticRestore'])
assert(value.format==='database-maintenance-current-state-approval-v1'&&value.decision==='accept-observed-current-state'&&typeof value.resolutionId==='string'&&/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value.resolutionId))
assert(typeof value.actorId==='string'&&/^[^\x00-\x1f\x7f]{1,160}$/.test(value.actorId)&&value.inspectionSha256===inspectionSha256&&stamp(value.issuedAt)&&stamp(value.expiresAt))
const now=Date.now();assert(Date.parse(value.issuedAt)>=Date.parse(inspectionCreatedAt)&&Date.parse(value.issuedAt)<=now&&now<Date.parse(value.expiresAt)&&Date.parse(value.expiresAt)-Date.parse(value.issuedAt)<=validityMs)
assert(value.managedBackendStopped===true&&value.historicOutcomesRemainUnknown===true&&value.automaticRestore===false)
return value as unknown as {resolutionId:string;actorId:string;issuedAt:string;expiresAt:string}
}
/** Genuine observation, not a supplied snapshot factory. Owner/transaction ends
* must be confirmed before a caller can publish an acceptance. Exclusive key
* protects cooperative observations; stopped-backend assumption spans publication. */
export async function observeMaintenanceCurrent(pool:Pick<Pool,'connect'>,transport:MaintenanceKnownCurrent['transport']){
let client:PoolClient|undefined,locked=false,begun=false,bad=false,faulted=false
const fault=()=>{faulted=true},live=()=>{assert(!faulted)}
try{
client=await pool.connect();client.on('error',fault);client.on('end',fault)
const query=async(text:string,values:unknown[]=[])=>{live();const result=await client!.query(text,values);live();return result}
await query("SET statement_timeout='10s'");await query("SET lock_timeout='5s'")
const lease=await query('SELECT pg_catalog.pg_try_advisory_lock(8723110::bigint) AS locked');assert(lease.rows.length===1&&lease.rows[0].locked===true);locked=true
await query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');begun=true
for(const sql of ["SET LOCAL search_path=pg_catalog","SET LOCAL TimeZone='UTC'","SET LOCAL DateStyle='ISO, YMD'","SET LOCAL standard_conforming_strings=on","SET LOCAL statement_timeout='10s'"])await query(sql)
const identity=await query(`SELECT c.system_identifier::text AS "systemIdentifier",d.oid::text AS "databaseOid",d.datname AS "databaseName",pg_catalog.current_setting('server_version_num')::int AS "serverVersionNum",pg_catalog.pg_is_in_recovery() AS "inRecovery" FROM pg_catalog.pg_database d CROSS JOIN pg_catalog.pg_control_system() c WHERE d.datname=pg_catalog.current_database()`)
assert.equal(identity.rows.length,1)
// The optional writer-profile control schema is not installed by ordinary
// application migrations. Observe its presence in this same transaction;
// detailed capture still refuses every other unexpected namespace.
const control=await query("SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname='mmapp_control'")
assert(control.rows.length<=1&&control.rows.every(row=>row.nspname==='mmapp_control'))
const schema=await captureMigrationSchema({query:async(text,values)=>query(text,values)},{scope:{applicationNamespaces:['public'],excludedControlNamespaces:control.rows.length?['mmapp_control']:[]},deadlineAt:Date.now()+60000})
const marker=schema.sections.relations.find(row=>row.namespace==='public'&&row.name==='schema_migrations'),column=schema.sections.columns.find(row=>row.namespace==='public'&&row.relation==='schema_migrations'&&row.name==='id'&&row.dropped===false)
assert(marker?.kind==='r'&&marker.persistence==='p'&&marker.row_security===false&&marker.force_row_security===false&&column?.type==='text')
for(const table of CURRENT_REQUIRED_TABLES)assert(schema.sections.relations.some(row=>row.namespace==='public'&&row.name===table&&['r','p'].includes(String(row.kind))&&row.row_security===false&&row.force_row_security===false))
const markers=(await query('SELECT id FROM public.schema_migrations ORDER BY id LIMIT $1',[MIGRATION_MANIFEST.length+1])).rows
assert.equal(stable(markers),stable(MIGRATION_MANIFEST.map(entry=>({id:entry.file.slice(0,-4)}))))
const knownCurrent={transport,source:identity.rows[0],schemaSha256:schema.sha256,rawSchemaSha256:schema.observedRaw.sha256,physicalSchemaSha256:schema.observedPhysical.sha256,manifestSha256:migrationManifestDigest(),markersSha256:sha(stable(markers))}
validateMaintenanceKnownCurrent(knownCurrent)
const end=await query('ROLLBACK');assert.equal(end.command,'ROLLBACK');begun=false
const unlock=await query('SELECT pg_catalog.pg_advisory_unlock(8723110::bigint) AS unlocked');assert(unlock.rows.length===1&&unlock.rows[0].unlocked===true);locked=false
return {knownCurrent,schema}
}catch(error){bad=true;throw error}finally{
if(client){let cleanupFailed=false
try{if(begun&&!faulted){const end=await client.query('ROLLBACK');assert.equal(end.command,'ROLLBACK')}if(locked&&!faulted){const end=await client.query('SELECT pg_catalog.pg_advisory_unlock(8723110::bigint) AS unlocked');assert(end.rows[0]?.unlocked===true)}}catch{cleanupFailed=true}
try{client.off('error',fault);client.off('end',fault)}catch{cleanupFailed=true}
try{client.release(bad||faulted||cleanupFailed)}catch{cleanupFailed=true}
assert(!cleanupFailed&&!faulted,'Read-only maintenance owner cleanup unconfirmed')
}
}
}
async function snapshot(stateDirectory:string,target:string,records:readonly Buffer[]){
const state=await readMaintenanceRecoveryFacts(stateDirectory);assert(state.kind==='journal'&&state.generation)
const accepted=evaluateMaintenanceAcceptances(records,state.journal,state.vault.evidence,target)
const unresolvedOperations=state.operations.filter(op=>op.state!=='applied'),vaultCoverage=state.vault.evidence.filter(marker=>state.vault.unresolvedFiles.includes(marker.file))
const newUnresolved=unresolvedOperations.some(op=>!accepted.acceptedCoreIds.has(op.operationId))||state.vault.unresolvedFiles.some(file=>!accepted.acceptedVaultFiles.has(file))
return {newUnresolved,external:{journalCoverage:{prefixBytes:state.journal.length,sha256:sha(state.journal),unresolvedOperations},vaultCoverage,generationObservedSha256:sha(state.generation),previousResolutionSha256:accepted.headSha256}}
}
export async function maintenanceMain(args:string[]){
assertJsDriverEnvironment(process.env);const resolve=args[0]==='maintenance-resolve';assert(resolve||args[0]==='maintenance-inspect')
const required=[...flags,...(resolve?['inspection','inspection-sha256','approval','approval-sha256']:['output'])],names=[...required,'ca','ca-sha256'],values=new Map<string,string>()
assert(args.length===1+required.length*2||args.length===1+(required.length+2)*2)
for(let i=1;i<args.length;i+=2){const name=args[i].slice(2);assert(args[i].startsWith('--')&&names.includes(name)&&!values.has(name)&&args[i+1]);values.set(name,args[i+1])}
for(const name of required)assert(values.has(name));assert(values.has('ca')===values.has('ca-sha256'));const get=(name:string)=>values.get(name)!
for(const name of [...values.keys()].filter(name=>name.endsWith('sha256')))assert(hex(get(name)))
const stateDirectory=get('state-dir');await protectedDirectory(stateDirectory)
const transport=parseMaintenanceJSON(await readProtected(get('transport'),16384,get('transport-sha256')));object(transport);const mode=transport.mode===undefined?'tls':transport.mode;assert(mode==='tls'||mode==='local-loopback');assert(values.has('ca')===(mode==='tls'))
const ca=mode==='tls'?await readProtected(get('ca'),65536,get('ca-sha256')):undefined
const runtime=parseMaintenanceJSON(await readProtected(get('runtime-target'),16384,get('runtime-target-sha256')));exact(runtime,['format','databaseUrl']);assert(runtime.format==='database-maintenance-runtime-target-v1'&&typeof runtime.databaseUrl==='string');const runtimeUrl=new URL(runtime.databaseUrl);assert(!runtimeUrl.username&&!runtimeUrl.password)
const target=runtimeDatabaseTargetSha256(runtime.databaseUrl),{options,transportBinding}=maintenancePoolConfiguration(transport,ca,mode==='tls'?get('ca-sha256'):undefined)
exact(transport.connection,['host','port','user','password','database']);assert.equal(decodeURIComponent(runtimeUrl.pathname.slice(1)),transport.connection.database)
const records=await readMaintenanceAcceptanceRecords(stateDirectory);if(resolve)assert(records.length<64,'Maintenance acceptance history bound reached');const currentState=await snapshot(stateDirectory,target,records),before=currentState.external;if(resolve)assert(currentState.newUnresolved,'No new unresolved history to accept')
await inventoryMigrationDirectory(path.join(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url)))),'drizzle'))
let inspection:Record<string,unknown>|undefined,approved:ReturnType<typeof maintenanceApproval>|undefined
if(resolve){inspection=parseMaintenanceJSON(await readProtected(get('inspection'),67108864,get('inspection-sha256'))) as Record<string,unknown>;exact(inspection,['format','createdAt','expiresAt','runtimeTargetSha256','acceptanceRequired','external','knownCurrent','schema']);assert(inspection.format==='database-maintenance-current-state-inspection-v1'&&stamp(inspection.createdAt)&&stamp(inspection.expiresAt)&&Date.parse(inspection.createdAt)<=Date.now()&&Date.now()<Date.parse(inspection.expiresAt)&&Date.parse(inspection.expiresAt)-Date.parse(inspection.createdAt)<=validityMs&&inspection.runtimeTargetSha256===target&&inspection.acceptanceRequired===true);validateMaintenanceKnownCurrent(inspection.knownCurrent);assert.equal(stable(inspection.external),stable(before));approved=maintenanceApproval(parseMaintenanceJSON(await readProtected(get('approval'),16384,get('approval-sha256'))),get('inspection-sha256'),inspection.createdAt)}
const {default:pg}=await import('pg');assertJsDriverEnvironment(process.env)
const runProof=async()=>{const pool=new pg.Pool(options);let failed=false;pool.on('error',()=>{failed=true});try{const result=await observeMaintenanceCurrent(pool,transportBinding);await pool.end();assert(!failed);return result}catch(error){try{await pool.end()}catch{/* preserve refusal */}throw error}}
if(!resolve){const result=await runProof();assert.equal(stable((await snapshot(stateDirectory,target,await readMaintenanceAcceptanceRecords(stateDirectory))).external),stable(before));const nowMs=Date.now(),now=new Date(nowMs).toISOString(),out={format:'database-maintenance-current-state-inspection-v1',createdAt:now,expiresAt:new Date(nowMs+validityMs).toISOString(),runtimeTargetSha256:target,acceptanceRequired:currentState.newUnresolved,external:before,...result};const raw=Buffer.from(stable(out));assert(raw.length<=67108864);await writeFile(get('output'),raw,{flag:'wx',mode:0o600});return {event:'database-maintenance-inspection',inspectionSha256:sha(raw),unresolvedCoreOperations:before.journalCoverage.unresolvedOperations.length,unresolvedVaultFiles:before.vaultCoverage.length,state:currentState.newUnresolved?'inspection-required':before.previousResolutionSha256?'historically-accepted':'no-unresolved-history',historicalAcceptanceHeadSha256:before.previousResolutionSha256,humanApprovalRequired:currentState.newUnresolved,knownCurrentObserved:true,archiveDataEquivalenceProved:false,historicOutcomesChanged:false,automaticRestore:false}}
const resolutionDirectory=path.join(stateDirectory,'maintenance-acceptances');await mkdir(resolutionDirectory,{recursive:true,mode:0o700});await protectedDirectory(resolutionDirectory)
return withJournalLease(resolutionDirectory,async()=>{
// Existing immutable records are reread while our proven O_EXCL lease is held.
// Runtime reader refuses this lease; never infer stale ownership from PID/time.
const expected=records.map(raw=>{const v=parseMaintenanceJSON(raw) as {resolutionId:string};return v.resolutionId+'.json'}).sort()
assert.equal((await readdir(resolutionDirectory)).sort().join('\0'),[...expected,'publisher-lease.lock'].sort().join('\0'))
for(let i=0;i<records.length;i++){const value=parseMaintenanceJSON(records[i]) as {resolutionId:string};assert.equal(sha(await readProtected(path.join(resolutionDirectory,value.resolutionId+'.json'),1048576)),sha(records[i]))}
const observed=await runProof();assert.equal(stable(observed.knownCurrent),stable(inspection!.knownCurrent));assert.equal(stable(observed.schema),stable(inspection!.schema))
assert.equal(stable((await snapshot(stateDirectory,target,records)).external),stable(before));assert(Date.now()<Date.parse(approved!.expiresAt)&&Date.now()<Date.parse(inspection!.expiresAt as string))
const record={format:MAINTENANCE_ACCEPTANCE_FORMAT,resolutionId:approved!.resolutionId,decision:'accept-observed-current-state',historicOutcomeChanged:false,automaticRestore:false,actor:{kind:'host-operator',id:approved!.actorId},approvalSha256:get('approval-sha256'),inspectionSha256:get('inspection-sha256'),approvedAt:approved!.issuedAt,approvalExpiresAt:approved!.expiresAt,acceptedAt:new Date().toISOString(),runtimeTargetSha256:target,...before,knownCurrent:observed.knownCurrent}
validateMaintenanceAcceptance(record);const raw=Buffer.from(stable(record));assert(raw.length<=1048576,'Maintenance acceptance byte bound exceeded');await writeFile(path.join(resolutionDirectory,record.resolutionId+'.json'),raw,{flag:'wx',mode:0o600})
return {event:'database-maintenance-current-state-accepted',outcome:'committed',resolutionId:record.resolutionId,acceptanceSha256:sha(raw),archiveDataEquivalenceProved:false,historicOutcomesChanged:false,automaticRestore:false}
})
}
@@ -0,0 +1,84 @@
/** Explicit operator/lifecycle read component. No startup, grants or READY. */
import assert from 'node:assert/strict'
import {lstat} from 'node:fs/promises'
import path from 'node:path'
import {protectedDirectory,readProtected} from './operator-files.mjs'
import {createDatabaseSourceBindingVerifier,readDatabaseSourceBindingRegistry,
type DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.js'
import type {DatabaseSourceObservation,DatabaseSourceObservationOptions} from '../../src/db/database-source-observation.js'
import type {ProbeStorageSql} from '../../src/services/server-probe-journal-maintenance.js'
const hex=(value:unknown):value is string=>typeof value==='string'&&/^[a-f0-9]{64}$/.test(value)
function fence(options:DatabaseSourceObservationOptions){
assert(Number.isSafeInteger(options.deadlineAt)&&Date.now()<options.deadlineAt&&!options.signal?.aborted,'Protected source lifetime refused')
}
async function identity(filename:string,content=false){
const s=await lstat(filename,{bigint:true})
// BigInt identity is never narrowed to Number. Content changes/permission
// changes are checked around the whole verified read, not just open().
return [s.dev,s.ino,s.mode,s.uid,s.gid,s.nlink,...(content?[s.size,s.mtimeNs,s.ctimeNs]:[])].map(String).join(':')
}
async function protectedState(pin:DatabaseSourceDeploymentPin){
// Proven POSIX protection refuses unsupported Windows ACL BEFORE file reads.
await protectedDirectory(pin.registryDirectory)
const directories:Record<string,string>={}
for(let p=pin.registryDirectory;;p=path.dirname(p)){
directories[p]=await identity(p,p===pin.registryDirectory)
if(p===path.dirname(p))break
}
const names=['operator-public.pem','head-'+pin.currentHeadSha256+'.json']
const content:Record<string,Buffer>={},identities:Record<string,string>={}
const collect=async(name:string,maximum:number,expected?:string)=>{
const filename=path.join(pin.registryDirectory,name)
const before=await identity(filename,true)
content[name]=await readProtected(filename,maximum,expected)
assert.equal(await identity(filename,true),before,'Protected source file changed during read')
identities[name]=before
}
await collect(names[0],4096)
await collect(names[1],8192,pin.currentHeadSha256)
const head=JSON.parse(new TextDecoder('utf8',{fatal:true}).decode(content[names[1]]))
assert(hex(head?.body?.bindingSha256),'Pinned source binding digest required')
const bindingName='binding-'+head.body.bindingSha256+'.json'
await collect(bindingName,8192,head.body.bindingSha256)
await protectedDirectory(pin.registryDirectory)
for(const [directory,wanted]of Object.entries(directories))assert.equal(await identity(directory,directory===pin.registryDirectory),wanted,'Protected source directory changed')
return {directories,identities,content}
}
function same(before:Awaited<ReturnType<typeof protectedState>>,after:Awaited<ReturnType<typeof protectedState>>){
assert.deepEqual(after.directories,before.directories,'Protected source directory drift')
assert.deepEqual(after.identities,before.identities,'Protected source identity/mode drift')
assert.deepEqual(after.content,before.content,'Protected source content drift')
}
function captured(raw:DatabaseSourceDeploymentPin){
const pin=Object.freeze({...raw})
// Existing strict deployment schema rejects unknown/malformed pin fields.
const verifier=createDatabaseSourceBindingVerifier(pin)
return {pin,verifier}
}
/** Signature/head/source DATA plus actual POSIX protection of the pinned files.
* The supplied pin still needs independent deployment approval; protection
* cannot distinguish a restored clone/generation or authenticate all writers. */
export async function readProtectedDatabaseSourceBindingRegistry(raw:DatabaseSourceDeploymentPin,options:DatabaseSourceObservationOptions){
const {pin}=captured(raw);const lifetime={...options};fence(lifetime)
const before=await protectedState(pin);fence(lifetime)
const result=await readDatabaseSourceBindingRegistry(pin);fence(lifetime)
const after=await protectedState(pin);fence(lifetime);same(before,after)
return Object.freeze({...result,platformProtectionVerified:true as const,
generationAuthenticated:false as const,allWriterDrainAuthenticated:false as const,readyAuthorized:false as const})
}
/** Same-facade source verification is retained; filesystem JSON is never a
* source observation, writer capability or independent generation authority. */
export function createProtectedDatabaseSourceBindingVerifier(raw:DatabaseSourceDeploymentPin){
const {pin,verifier}=captured(raw)
return Object.freeze({async verify(sql:ProbeStorageSql,observation:DatabaseSourceObservation,options:DatabaseSourceObservationOptions){
const lifetime={...options};fence(lifetime)
const before=await protectedState(pin);fence(lifetime)
const result=await verifier.verify(sql,observation,lifetime);fence(lifetime)
const after=await protectedState(pin);fence(lifetime);same(before,after)
return Object.freeze({...result,platformProtectionVerified:true as const,
generationAuthenticated:false as const,allWriterDrainAuthenticated:false as const,readyAuthorized:false as const})
}})
}
@@ -0,0 +1,85 @@
/** Genuine Ed25519/files; POSIX permission metadata is modeled on Windows.
* No SQL, service, deployment or real POSIX acceptance is claimed. */
import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import path from 'node:path'
import os from 'node:os'
import crypto from 'node:crypto'
import {createRequire} from 'node:module'
import type {Stats,BigIntStats} from 'node:fs'
import type {DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.ts'
import type * as ProtectedApi from './protected-source-registry.mts'
import {runInNewContext} from 'node:vm'
import {readProtectedDatabaseSourceBindingRegistry} from './protected-source-registry.mts'
import {readDatabaseSourceBindingRegistry,sourceBindingBytes,sourceBindingCanonical,sourceBindingSha256,DATABASE_SOURCE_SIGNATURE_DOMAINS} from '../../src/db/database-source-binding.ts'
const require=createRequire(import.meta.url),ts=require('typescript')
const helperSource=await fs.readFile(new URL('./operator-files.mjs',import.meta.url),'utf8')
const source=await fs.readFile(new URL('./protected-source-registry.mts',import.meta.url),'utf8')
const deadline=()=>({deadlineAt:Date.now()+10000})
function compile(text:string){return ts.transpileModule(text,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText}
function evaluate<T=Record<string,unknown>>(text:string,imports:Record<string,unknown>,processModel:unknown=process):T{
const exports:Record<string,unknown>={}
runInNewContext(compile(text),{exports,require:(name:string)=>imports[name]??require(name),process:processModel,Buffer,TextDecoder,console})
return exports as T
}
interface Fixture {pin:DatabaseSourceDeploymentPin;faults:Record<string,boolean>;api:typeof ProtectedApi;verificationReads:()=>number;root:string}
async function own(work:(f:Fixture)=>Promise<void>){
const root=await fs.mkdtemp(path.join(os.tmpdir(),'mm-protected-source-'))
try{
const key=crypto.generateKeyPairSync('ed25519'),installationId=crypto.randomUUID()
const signed=(body:unknown,domain:string)=>sourceBindingBytes({body,signature:crypto.sign(null,Buffer.from(domain+sourceBindingCanonical(body)),key.privateKey).toString('base64url')})
const body={format:'database-source-binding-v1',installationId,sourceIdentityId:crypto.randomUUID(),generation:crypto.randomUUID(),ordinal:1,
source:{systemIdentifier:'123',databaseOid:16384,databaseName:'synthetic',sessionUser:'capture',currentUser:'capture',inRecovery:false,serverVersionNum:180004}}
const binding=signed(body,DATABASE_SOURCE_SIGNATURE_DOMAINS.binding),bindingSha256=sourceBindingSha256(binding)
const head=signed({format:'database-source-registry-head-v1',installationId,sourceIdentityId:body.sourceIdentityId,generation:body.generation,ordinal:1,bindingSha256,previousHeadSha256:null},DATABASE_SOURCE_SIGNATURE_DOMAINS.head)
const pin={registryDirectory:root,installationId,publicKeySpkiSha256:sourceBindingSha256(key.publicKey.export({format:'der',type:'spki'})),currentHeadSha256:sourceBindingSha256(head)}
await fs.writeFile(path.join(root,'operator-public.pem'),key.publicKey.export({format:'pem',type:'spki'}),{mode:0o600})
await fs.writeFile(path.join(root,'head-'+pin.currentHeadSha256+'.json'),head,{mode:0o600})
await fs.writeFile(path.join(root,'binding-'+bindingSha256+'.json'),binding,{mode:0o600})
const faults={modeFile:false,modeDirectory:false,symlink:false,replacement:false,lateMode:false,changed:false};let verificationReads=0
const metadata=(s:Stats|BigIntStats,filename:string,big:boolean)=>{
const n=(value:number)=>big?BigInt(value):value,isRoot=filename===root,isFile=s.isFile()
const mode=(isFile?0o100600:0o40700)|((isFile&&faults.modeFile||isRoot&&faults.modeDirectory||isFile&&faults.lateMode&&faults.changed)?0o022:0)
return {...s,mode:n(mode),uid:n(1000),gid:n(1000),ino:faults.replacement&&faults.changed&&isFile?(typeof s.ino==='bigint'?s.ino+1n:s.ino+1):s.ino,
isFile:()=>isFile,isDirectory:()=>s.isDirectory(),isSymbolicLink:()=>faults.symlink&&isRoot}
}
const modelFs={...fs,lstat:async(filename:string,options?:{bigint?:boolean})=>metadata(await fs.lstat(filename,{bigint:options?.bigint===true}),filename,options?.bigint===true),open:async(filename:string,flags:number)=>{
const file=await fs.open(filename,flags)
return {stat:async()=>metadata(await file.stat(),filename,false),read:file.read.bind(file),close:file.close.bind(file)}
}}
const processModel={platform:'linux',getuid:()=>1000}
const proven=evaluate(helperSource,{'node:fs/promises':modelFs},processModel)
const bindingModule={readDatabaseSourceBindingRegistry:async(p:DatabaseSourceDeploymentPin)=>{
verificationReads++;const result=await readDatabaseSourceBindingRegistry(p);faults.changed=true;return result
},createDatabaseSourceBindingVerifier:()=>({verify:async()=>{verificationReads++;faults.changed=true;return {genuineSourceFactsMatched:true,generationAuthenticated:false,readyAuthorized:false}}})}
const api=evaluate<typeof ProtectedApi>(source,{'node:fs/promises':modelFs,'./operator-files.mjs':proven,'../../src/db/database-source-binding.js':bindingModule},processModel)
await work({pin,faults,api,verificationReads:()=>verificationReads,root})
}finally{const rel=path.relative(os.tmpdir(),root);assert(rel&&!rel.startsWith('..')&&!path.isAbsolute(rel));await fs.rm(root,{recursive:true,force:true})}
}
test('unsupported Windows refuses before registry signature reads',{skip:process.platform!=='win32'},async()=>{
await assert.rejects(readProtectedDatabaseSourceBindingRegistry({registryDirectory:path.resolve('not-created-protected-source'),installationId:crypto.randomUUID(),publicKeySpkiSha256:'a'.repeat(64),currentHeadSha256:'b'.repeat(64)},deadline()),/Windows ACL/)
})
test('proven POSIX helpers plus genuine signature/head bytes retain false generation/READY',()=>own(async f=>{
const value=await f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline())
assert.equal(value.platformProtectionVerified,true);assert.equal(value.generationAuthenticated,false);assert.equal(value.allWriterDrainAuthenticated,false);assert.equal(value.readyAuthorized,false)
assert.equal(value.headSha256,f.pin.currentHeadSha256);assert.equal(f.verificationReads(),1)
}))
for(const fault of ['modeFile','modeDirectory','symlink'])test(fault+' refuses before cryptographic registry read',()=>own(async f=>{
f.faults[fault]=true;await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()));assert.equal(f.verificationReads(),0)
}))
for(const fault of ['replacement','lateMode'])test(fault+' during genuine signature verification refuses protected output',()=>own(async f=>{
f.faults[fault]=true;await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()));assert.equal(f.verificationReads(),1)
}))
test('expired/aborted lifetime refuses before filesystem or source access',()=>own(async f=>{
const abort=new AbortController();abort.abort()
for(const options of [{deadlineAt:Date.now()-1},{deadlineAt:Date.now()+1000,signal:abort.signal}])await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,options))
assert.equal(f.verificationReads(),0)
}))
test('source verifier drift cannot grant protected output',()=>own(async f=>{
f.faults.lateMode=true
const verify=f.api.createProtectedDatabaseSourceBindingVerifier(f.pin).verify
await assert.rejects(verify({} as Parameters<typeof verify>[0],{} as Parameters<typeof verify>[1],deadline()))
assert.equal(f.verificationReads(),1)
}))
+3 -3
View File
@@ -141,14 +141,14 @@ async function preflightCurrentProfile(pool,{deadlineAt=Date.now()+120000}={}){
const first=await ownedContext(s,installation,PUBLISHER,'publisher-read')
if(canonical(sourceCoordinates(first))!==canonical(installation.expectedSource))throw Error('Pinned preflight source mismatch')
const facade=Object.freeze({query:(sql,values)=>s.query(sql,values)}),options={deadlineAt}
const observation=await captureDatabaseSourceObservation(facade,options);await sourceVerifier.verify(facade,observation,options)
const observation=await captureDatabaseSourceObservation(facade,options),initialSource=await sourceVerifier.verify(facade,observation,options)
const security=await protectedStructure(s,installation),catalogue=await collectFixedCatalogue(s)
const current=await readCurrent(s,installation)
if(!current)throw Error('Current signed policy required for preflight')
const catalogSha256=sha(canonical({base:catalogue.catalogSha256,security:security.sha256})),manifestSha256=migrationManifestDigest()
if(approval.expiresAt<=Date.now()||approval.manifestSha256!==manifestSha256||approval.definitionSha256!==catalogue.definitionSha256||approval.catalogSha256!==catalogSha256)throw Error('Independent preflight approval differs from actual source44 catalogue')
if(canonical(current.policy.source)!==canonical(sourceCoordinates(first))||canonical(current.policy.roles)!==canonical(security.roles)||current.policy.manifestSha256!==manifestSha256||current.policy.definitionSha256!==catalogue.definitionSha256||current.policy.catalogSha256!==catalogSha256)throw Error('Current signed policy differs from actual source/roles/catalogue')
await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options)
const terminalSource=await sourceVerifier.verify(facade,observation,options);await assertDatabaseSourceObservationCurrent(facade,observation,options)
sameOwned(first,await ownedContext(s,installation,PUBLISHER,'publisher-read'))
// Recheck the live interval after the potentially lengthy catalogue walk.
const clock=(await s.query('SELECT (EXTRACT(EPOCH FROM pg_catalog.clock_timestamp())*1000)::bigint::text AS now')).rows
@@ -157,7 +157,7 @@ async function preflightCurrentProfile(pool,{deadlineAt=Date.now()+120000}={}){
if(approval.expiresAt<=Date.now())throw Error('Preflight approval expired before terminal')
const terminal=await s.query('ROLLBACK');if(terminal.command!=='ROLLBACK')throw Error('Preflight terminal unconfirmed');s.begun(false)
return Object.freeze({state:'matched',epoch:current.epoch,headSha256:current.headSha256,notAfter:current.policy.notAfter,
sourceMatched:true,rolesMatched:true,catalogueMatched:true,writerAuthorized:false,generationAuthenticated:false,readyAuthorized:false})
sourceMatched:true,rolesMatched:true,catalogueMatched:true,platformProtectionVerified:initialSource?.platformProtectionVerified===true&&terminalSource?.platformProtectionVerified===true,writerAuthorized:false,generationAuthenticated:false,readyAuthorized:false})
})
}
async function boundedPending(filename){
+5
View File
@@ -3,10 +3,14 @@ import { applySqlMigrations } from "./migrate.js"
import { dropExpiredPartitions, ensurePartitionsAround } from "./partitions.js"
import { importSqliteToPostgres, shouldImportSqlite, sqliteFileLooksPresent } from "./sqlite-import.js"
import { env } from "../config.js"
import { mcpStateDirectory } from "../modules/mcp/security.js"
import { assertDatabaseMaintenanceStartup } from "../services/database-maintenance-startup.js"
import { reconcileDatabaseRestoreAudits } from "../services/database-restore-audit.js"
const ETL_LOCK = 8723101
export async function initDatabase(): Promise<void> {
await assertDatabaseMaintenanceStartup(mcpStateDirectory(), env.DATABASE_URL)
await pool.query("SELECT 1")
await applySqlMigrations(pool)
await ensurePartitionsAround(pool)
@@ -39,4 +43,5 @@ export async function initDatabase(): Promise<void> {
}
await dropExpiredPartitions(pool)
await ensurePartitionsAround(pool)
await reconcileDatabaseRestoreAudits(pool, mcpStateDirectory())
}
+7 -6
View File
@@ -12,10 +12,10 @@ const reviewed = (source: string, extra: Partial<MigrationReview> = {}) => {
const bytes = Buffer.from(source), file = "9000_fixture.sql"
return () => analyzeMigration(file, bytes, { file, sha256: createHash("sha256").update(bytes).digest("hex"), transaction: "owner-transaction", selfMarker: false, opaqueBodies: 0, bodyReview: [], effects: ["Explicit inert fixture review"], ...extra })
}
await test("all44 exact file pins and every top-level schema operation are inventoried", () => {
assert.equal(inventory.length, 44); assert.equal(MIGRATION_MANIFEST.length, 44)
await test("all 55 exact file pins and every top-level schema operation are inventoried", () => {
assert.equal(inventory.length, 55); assert.equal(MIGRATION_MANIFEST.length, 55)
assert.equal(inventory.filter(file => file.transaction === "owner-transaction").length, 17)
assert.equal(inventory.filter(file => file.transaction === "self-managed-single-transaction").length, 27)
assert.equal(inventory.filter(file => file.transaction === "self-managed-single-transaction").length, 38)
assert(inventory.every(file => file.operations.length === file.statementCount && file.effects.length > 0))
let tables = new Set<string>()
for (const file of inventory) {
@@ -32,8 +32,8 @@ await test("DO BEGIN and stored function BEGIN are never transaction boundaries"
assert.equal(endpoint.topLevelTransactionControls.length, 2); assert.equal(endpoint.opaqueBodies.length, 2)
assert.deepEqual(endpoint.opaqueBodies.map(body => body.kind), ["stored-function-body", "executed-do-body"])
})
await test("0035 through0044 have exact atomic file-owned terminal markers", () => {
assert.deepEqual(inventory.filter(file => file.selfMarker).map(file => file.id), ["0035_server_probe_journal", "0036_diagnostic_cancellations", "0037_server_probe_storage_v2", "0038_server_poll_journal_and_writer_guards", "0039_server_poll_storage", "0040_mcp_routing_reservations", "0041_server_poll_terminal_archives", "0042_server_poll_settings_epoch", "0043_mcp_routing_forward_charges", "0044_mcp_routing_manual_inverse_charges"])
await test("0035 through 0055 have exact atomic file-owned terminal markers", () => {
assert.deepEqual(inventory.filter(file => file.selfMarker).map(file => file.id), ["0035_server_probe_journal", "0036_diagnostic_cancellations", "0037_server_probe_storage_v2", "0038_server_poll_journal_and_writer_guards", "0039_server_poll_storage", "0040_mcp_routing_reservations", "0041_server_poll_terminal_archives", "0042_server_poll_settings_epoch", "0043_mcp_routing_forward_charges", "0044_mcp_routing_manual_inverse_charges", "0045_mcp_operation_replay_tombstones", "0046_server_poll_operation_retirement", "0047_server_poll_retirement_claims", "0048_mcp_ordinary_retirement_claims", "0049_mcp_ordinary_telemetry_retirement", "0050_server_probe_retired_sources", "0051_server_probe_retirement_claims", "0052_server_probe_retired_gc", "0053_server_probe_terminal_proofs", "0054_mcp_geoip_retirement", "0055_mcp_backup_schedule_retirement"])
for (const file of inventory.filter(file => file.selfMarker)) { assert.equal(file.selfMarker!.atomicWithAllFileEffects, true); assert.equal(file.selfMarker!.statement, file.statementCount - 2); assert.equal(file.currentOuterMarkerGap, false) }
assert.equal(inventory.filter(file => file.currentOuterMarkerGap).length, 34)
})
@@ -134,7 +134,8 @@ await test("poll storage enrollment refuses history before any change and preser
for(const name of ["server_poll_executions","server_poll_intents","server_poll_acks","server_poll_receipts","server_poll_quarantines"])assert(guard.includes(`EXISTS(SELECT 1 FROM ${name})`))
for(const name of ["server_poll_storage_admissions","server_poll_storage_usage"])assert(guard.includes("'"+name+"'")&&CURRENT_REQUIRED_TABLES.includes(name))
const bodies=(list:ReturnType<typeof parseMigrationStatements>)=>list.filter(statement=>statement.tokens[0].value==="CREATE").flatMap(statement=>statement.tokens.filter(token=>token.kind==="dollar").map(token=>token.value))
assert.deepEqual(bodies(statements),bodies(historical));assert.equal(bodies(statements).length,5)
const comparableBodies=(list:ReturnType<typeof parseMigrationStatements>)=>bodies(list).map(body=>body.replace(/\r\n/g,"\n"))
assert.deepEqual(comparableBodies(statements),comparableBodies(historical));assert.equal(bodies(statements).length,5)
assert(bodies(statements)[0].includes("mcp_settings.id=1 FOR SHARE"),"Predicate itself fences current retention, including direct guarded deletes")
assert.deepEqual(migration.operations.filter(operation=>operation.command==="INSERT").map(operation=>operation.target),["server_poll_storage_usage","schema_migrations"])
assert.equal(migration.operations.some(operation=>["UPDATE","DELETE","DROP"].includes(operation.command)),false)
+153 -1
View File
@@ -702,6 +702,158 @@ const classifiedMigrationPins: readonly MigrationReview[] = [
"Adds one one-unit immutable manual inverse charge table, two charge indexes, one manual-history page index, and four triggers. Existing43 bytes, reserved2 and generic debit ownership remain unchanged.",
"No inferred/backfilled identity, legacy timestamp debit, permission change, source enrollment, public controller activation or READY. Full RT3/all-writer/family-deletion proof remains open."
]
},
{
"file": "0045_mcp_operation_replay_tombstones.sql",
"sha256": "8edf5b074c7eef49dad7835c44ac2539df4347f19eb9dd9b2aa7005bafaadbc0",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 0,
"bodyReview": [],
"effects": [
"Adds one empty replay identity table with plan/operation uniqueness and connection-scoped idempotency digest uniqueness.",
"Table creation refuses an unmarked collision; the exact marker commits with the schema. No backfill, operation deletion, or worker activation."
]
},
{
"file": "0046_server_poll_operation_retirement.sql",
"sha256": "75d7956ea8134b01bcc88da083b77eb52f6e1ad70ad854918ed3380cb0e8cf88",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 4,
"bodyReview": [
"Archive guard permits only fixed receipt-scoped V1 to V2 updates with matching approved terminal operation, tombstone and SQL 180-day cutoff.",
"Deferred guard independently requires the tombstone, physically absent operation and plan, and exact ledger accounting.",
"Admission guard retains existing paths and applies exact V2 archive-byte delta to both owners without changing row charges or lifetime fields.",
"Existing GC retains its authority/activity cutoff and admits retired identity only with exact poll tombstone and compatible V2 archive when present."
],
"effects": [
"Extends the same archive table to a bounded version 2, changes three guards and adds one deferred retirement guard.",
"No automatic conversion, worker activation, operation deletion or retention-window adjustment in migration."
]
},
{
"file": "0047_server_poll_retirement_claims.sql",
"sha256": "8c01ab14d241f715af021e8a7d7f5bb273b450b32f566140359260282524e01d",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 2,
"bodyReview": [
"Protects exact current-transaction V1 claim insertion and permits deletion only as the owning admission is cascaded by GC.",
"Requires exact receipt, V1 archive proof and claim token already committed in another transaction before V2 update."
],
"effects": [
"Adds one durable one-shot poll retirement claim per admission, cascading only with that admission's existing guarded GC.",
"No migration-time conversion, backfill, dispatch, or quota change."
]
},
{
"file": "0048_mcp_ordinary_retirement_claims.sql",
"sha256": "5bc3598c9f5d09611e98d05345accda6d39b199a583bba789b97a24140fafbfd",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 1,
"bodyReview": [
"Makes ordinary claims immutable and allows insert only for the exact linked settled and aged logs.settings.update v1 operation."
],
"effects": [
"Adds one durable ordinary-operation claim with an authenticated source digest. No migration-time data purge or backfill."
]
},
{
"file": "0049_mcp_ordinary_telemetry_retirement.sql",
"sha256": "68993e924d20db6b08973846a0da367fe89fe2948c414c810e9b171dbbae6761",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 1,
"bodyReview": [
"Extends the same immutable ordinary claim guard to four explicitly versioned settled telemetry settings families."
],
"effects": [
"Replaces only the ordinary claim guard function. No backfill, data purge, or changes to poll or diagnostic storage."
]
},
{
"file": "0050_server_probe_retired_sources.sql",
"sha256": "0468ac7e53638b532557273f55d065ff4cde092d8b08073f5860bab35263aead",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 1,
"bodyReview": [
"Refuses update, delete, and truncate of a signed retired diagnostic source pending a separately reviewed lifecycle."
],
"effects": [
"Creates empty bounded storage linking exact diagnostic admission and replay tombstone IDs. No backfill, retirement, or GC activation."
]
},
{
"file": "0051_server_probe_retirement_claims.sql",
"sha256": "b38876b5da44cf14e20a9df0baaa233ea4de81e471b38dddd370e149c8116243",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 2,
"bodyReview": [
"Restricts diagnostic claims to their original settled 180-day source and makes each claim permanent.",
"Restricts archival insertion to the exact committed claim and current original source and tombstone identity."
],
"effects": [
"Creates empty diagnostic retirement claims and guards the archive insert. No conversion, backfill, or GC activation."
]
},
{
"file": "0052_server_probe_retired_gc.sql",
"sha256": "ea56a1f1bdabfce606bdfec56b6a7b31d45b0187fbea4c900bc656ae320f2fdd",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 3,
"bodyReview": [
"Validates the exact retired diagnostic identity and the later of current and persisted retention plus the original safety window.",
"Only permits archive removal during an exact guarded admission cascade after journal removal.",
"Adds the exact retired GC branch while retaining the live admission eligibility predicate."
],
"effects": [
"Changes only the retired source admission FK to guarded cascade and adds an explicit exact GC path. No collection runs during migration."
]
},
{
"file": "0053_server_probe_terminal_proofs.sql",
"sha256": "759ec8c349fcb24c43af410ad6ade714432678b3a021c94bd982080d6a8986be",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 2,
"bodyReview": [
"Pins terminal proof to the current retained original on insert and an exact committed retirement claim on delete.",
"Extends claims only to the identical signed terminal proof when the original diagnostic admission is gone."
],
"effects": [
"Creates empty bounded private terminal proof storage. Existing payload retention, scheduler, and archive GC remain unchanged."
]
},
{
"file": "0054_mcp_geoip_retirement.sql",
"sha256": "680390f98076e3047dbb5a34eab417b253853703932d16c17e4fa3a11060accc",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 1,
"bodyReview": [
"Adds only exact settled GeoIP settings version 1 to the existing immutable ordinary retirement claim guard."
],
"effects": [
"Replaces the ordinary claim guard function without changing settings or retiring existing rows during migration."
]
},
{
"file": "0055_mcp_backup_schedule_retirement.sql",
"sha256": "07e63c1245ea547176d613933a81ad354e734fa64218dadced67ee8f965c01b6",
"transaction": "self-managed-single-transaction",
"selfMarker": true,
"opaqueBodies": 1,
"bodyReview": [
"Adds only exact settled backup schedule settings version 1 to the existing immutable ordinary retirement claim guard."
],
"effects": [
"Replaces the ordinary claim guard function without changing backup settings, metadata or entries during migration."
]
}
]
@@ -724,7 +876,7 @@ export const migrationManifestDigest = () => hash(JSON.stringify(MIGRATION_MANIF
// resetting the accumulated public table set at the reviewed destructive0002.
// This is a necessary presence check, not a substitute for exact live column,
// constraint/index/function/trigger comparison by the control protocol.
export const CURRENT_REQUIRED_TABLES: readonly string[] = Object.freeze(["acme_settings","alert_engine_confirm_pending","alert_engine_cursor","alert_engine_prev_live","alert_engine_state","alert_groups","alert_history","alert_outbox","alert_rule_conditions","alert_rule_targets","alert_rules","alert_telegram_settings","app_users","backup_entries","backup_schedule_settings","backup_storage_settings","certificate_issue_jobs","certificate_renew_settings","config_revisions","data_migration","events","evobgp_settings","filter_rules","flow_asn_meta","flow_buckets","flow_daily_dims","flow_daily_facts","flow_hour_facts","flow_ip_meta","flow_minute_dims","flow_minute_stats","flow_settings_intents","flow_write_receipts","geoip_settings","internet_path_settings","internet_path_snapshots","mcp_artifacts","mcp_audit","mcp_budget_buckets","mcp_connections","mcp_credentials","mcp_diagnostic_cancellations","mcp_drift","mcp_grants","mcp_management_channels","mcp_oauth_clients","mcp_oauth_requests","mcp_operation_checkpoints","mcp_operation_steps","mcp_operations","mcp_owner_revocations","mcp_path_checks","mcp_plans","mcp_portal_events","mcp_protections","mcp_read_leases","mcp_recovery_intents","mcp_requests","mcp_routing_forward_charges","mcp_routing_manual_inverse_charges","mcp_routing_observations","mcp_routing_profiles","mcp_routing_reservation_slots","mcp_routing_reservations","mcp_scoped_recovery_intents","mcp_secrets","mcp_settings","mcp_unauthorized_buckets","recursive_routes","router_log_collection_state","router_log_entries","router_log_settings","scheduler_runs","schema_migrations","server_poll_acks","server_poll_archives","server_poll_executions","server_poll_intents","server_poll_quarantines","server_poll_receipts","server_poll_storage_admissions","server_poll_storage_usage","server_probe_execution_payloads","server_probe_execution_quarantines","server_probe_execution_stages","server_probe_executions","server_probe_storage_admissions","server_probe_storage_usage","server_snapshots","servers","servers_api_ping_settings","servers_rest_ping_samples","traffic_collection_state","traffic_flow_settings","traffic_samples","traffic_settings","uptime_probe_samples","uptime_probes","uptime_resource_samples","uptime_settings","uptime_speed_probes","uptime_speed_test_runs","user_interface_bindings","vrf_production_tickets"])
export const CURRENT_REQUIRED_TABLES: readonly string[] = Object.freeze(["acme_settings","alert_engine_confirm_pending","alert_engine_cursor","alert_engine_prev_live","alert_engine_state","alert_groups","alert_history","alert_outbox","alert_rule_conditions","alert_rule_targets","alert_rules","alert_telegram_settings","app_users","backup_entries","backup_schedule_settings","backup_storage_settings","certificate_issue_jobs","certificate_renew_settings","config_revisions","data_migration","events","evobgp_settings","filter_rules","flow_asn_meta","flow_buckets","flow_daily_dims","flow_daily_facts","flow_hour_facts","flow_ip_meta","flow_minute_dims","flow_minute_stats","flow_settings_intents","flow_write_receipts","geoip_settings","internet_path_settings","internet_path_snapshots","mcp_artifacts","mcp_audit","mcp_budget_buckets","mcp_connections","mcp_credentials","mcp_diagnostic_cancellations","mcp_drift","mcp_grants","mcp_management_channels","mcp_oauth_clients","mcp_oauth_requests","mcp_operation_checkpoints","mcp_operation_replay_tombstones","mcp_operation_steps","mcp_operations","mcp_ordinary_retirement_claims","mcp_owner_revocations","mcp_path_checks","mcp_plans","mcp_portal_events","mcp_protections","mcp_read_leases","mcp_recovery_intents","mcp_requests","mcp_routing_forward_charges","mcp_routing_manual_inverse_charges","mcp_routing_observations","mcp_routing_profiles","mcp_routing_reservation_slots","mcp_routing_reservations","mcp_scoped_recovery_intents","mcp_secrets","mcp_settings","mcp_unauthorized_buckets","recursive_routes","router_log_collection_state","router_log_entries","router_log_settings","scheduler_runs","schema_migrations","server_poll_acks","server_poll_archives","server_poll_executions","server_poll_intents","server_poll_quarantines","server_poll_receipts","server_poll_retirement_claims","server_poll_storage_admissions","server_poll_storage_usage","server_probe_execution_payloads","server_probe_execution_quarantines","server_probe_execution_stages","server_probe_executions","server_probe_retired_sources","server_probe_retirement_claims","server_probe_storage_admissions","server_probe_storage_usage","server_probe_terminal_proofs","server_snapshots","servers","servers_api_ping_settings","servers_rest_ping_samples","traffic_collection_state","traffic_flow_settings","traffic_samples","traffic_settings","uptime_probe_samples","uptime_probes","uptime_resource_samples","uptime_settings","uptime_speed_probes","uptime_speed_test_runs","user_interface_bindings","vrf_production_tickets"])
export interface LegacyBaselineEvidence {
inventoryComplete: boolean; databaseGeneration: string; unresolvedAttemptIds: readonly string[]
+4
View File
@@ -4,6 +4,8 @@ import { serializerCompiler, validatorCompiler } from "@fastify/type-provider-zo
import { monitorEventLoopDelay } from "node:perf_hooks"
import { env } from "./config.js"
import { initDatabase } from "./db/bootstrap.js"
import { mcpStateDirectory } from "./modules/mcp/security.js"
import { assertDatabaseMaintenanceStartup } from "./services/database-maintenance-startup.js"
import { closePool } from "./db/index.js"
import authPlugin, { requireApiAuth } from "./plugins/auth.js"
import { installDatabaseRequestGate } from "./plugins/database-request-gate.js"
@@ -186,6 +188,8 @@ const isMain =
if (isMain) {
try {
// Resolve external maintenance before init/migrations or consumer startup.
await assertDatabaseMaintenanceStartup(mcpStateDirectory(), env.DATABASE_URL)
await initDatabase()
const app = await buildApp()
let shuttingDown = false
+7 -1
View File
@@ -14,6 +14,7 @@ import { logFiltersSchema, logSettingsPatchSchema } from "@mmapp/contracts"
import { appUserCreateSchema, appUserUpdateSchema, appUserRoleSchema, sectionPermSchema, serverPermSchema, userBindingCreateSchema } from "@mmapp/contracts/users"
import type { McpOperationDescriptor } from "@mmapp/contracts/mcp"
import { hasPermission } from "../../lib/permissions.js"
import { assertOperationReplayScope, findOperationReplayTombstone, operationHistoryExpired } from "./operation-replay-tombstones.js"
import { pool } from "../../db/index.js"
import { getServerReadById } from "../servers/service/servers-service.js"
import { listUsers, getUserById, listInterfaceCatalog, getInterfaceCatalogState } from "../users/service/users-service.js"
@@ -473,7 +474,12 @@ add("changes.cancel", "mm_change_cancel", "Отменить свой незап
add("operations.read", "mm_operation_get", "Результат своей операции", "mcp", null, "read", "connection", z.object({ operationId: z.string().uuid() }).strict(), {
read: async (input, context) => {
const { rows: [operation] } = await pool.query("SELECT * FROM mcp_operations WHERE id=$1 AND connection_id=$2", [input.operationId, context.connection.id])
if (!operation) throw new McpError("NOT_FOUND", "Операция не найдена", 404)
if (!operation) {
const tombstone = await findOperationReplayTombstone(pool, context.connection.id, String(input.operationId))
if (!tombstone) throw new McpError("NOT_FOUND", "Операция не найдена", 404)
assertOperationReplayScope(tombstone, context.connection)
operationHistoryExpired()
}
await assertPlanReadScope(await (await import("./runtime.js")).loadPlan(operation.plan_id), context)
const { rows: steps } = await pool.query("SELECT * FROM mcp_operation_steps WHERE operation_id=$1 ORDER BY step_index", [input.operationId])
return { ...operation, steps }
@@ -1,3 +1,4 @@
import { isConfirmedRoutingFibTable } from "../../services/routing-table-fib.js"
import type { RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { nativeBgpRows, type NativeBgpRow } from "../../services/bgp-export-model.js"
import { stableBgpPolicyRow } from "../../services/bgp-filter-binding-model.js"
@@ -56,7 +57,7 @@ export async function readControlPathContext(profile: RouteAiProfile, candidateI
return matching[0]
}
const sourceId = profile.serverId, table = unique(await get(sourceId, "/routing/table"), profile.probeVrf)
if (![true, "true", "yes"].includes(table.fib as string | boolean) || [true, "true", "yes"].includes(table.disabled as string | boolean) || [true, "true", "yes"].includes(table.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB probe VRF контрольного пути не подтверждён", 409)
if (!isConfirmedRoutingFibTable(table)) throw new McpError("STATE_UNAVAILABLE", "FIB probe VRF контрольного пути не подтверждён", 409)
const vrfs = await get(sourceId, "/ip/vrf"), effectiveVrfs: NativeBgpRow[] = []
await enabled(sourceId, "/ip/vrf", unique(vrfs, profile.probeVrf))
for (const row of vrfs) effectiveVrfs.push([true, "true", "yes"].includes(row.disabled as string | boolean) ? row : await enabled(sourceId, "/ip/vrf", row))
@@ -31,6 +31,15 @@ const verifyData = process.env.MMAPP_OWN_TERMINAL_DATA_EQUIVALENCE === "true"
let dataExpectation: Awaited<ReturnType<typeof import("./database-restore-data-equivalence.sql-helper.js")["prepareOwnedRestoreDataExpectation"]>> | undefined
const originalConnect = pool.connect
let migrationCaptures = 0
async function waitForDatabaseRetirement() {
let timer: ReturnType<typeof setTimeout> | undefined
try {
await Promise.race([sharedDatabaseGate.whenOpen(), new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(Error("Terminal restore did not retire its database admission")), 10000)
})])
} finally { clearTimeout(timer) }
}
try {
// Every owned restore profile archives inert domain collectors, not only the
// data-equivalence profile. Otherwise restart can launch unrelated downloads
@@ -138,6 +147,10 @@ try {
await assert.rejects(assertPortalClaimsCurrent(revokedOwner))
await assert.rejects(authenticateMcpToken(issued.token))
await assert.rejects(readRestoreReceipt(operation.id, { ...actor, permissions: ["mm:mcp:read"] }))
// The applied receipt is durable before the terminal callback releases its
// last SQL lease/exclusive admission. Await that existing owner, rather than
// racing its cleanup with the stale-callback/no-replay assertion below.
await waitForDatabaseRetirement()
// A stale callback or process replay cannot invoke pg_restore a second time.
await pool.query("UPDATE mcp_restore_acceptance_marker SET value='after-receipt'")
await executeMcpDatabaseRestore(operation.id, principal)
@@ -165,6 +178,7 @@ try {
await delay(100)
}
assert.equal(receipt.state, "blocked", JSON.stringify(receipt))
await waitForDatabaseRetirement()
assert.equal(await accessGeneration(), generationBeforeCorruption)
assert.equal((await pool.query("SELECT value FROM mcp_restore_acceptance_marker")).rows[0].value, "after-receipt")
console.log("✓ Tampered archive is blocked before data replacement")
+24 -12
View File
@@ -5,7 +5,7 @@ import { z } from "zod"
import { pool } from "../../db/index.js"
import { hasPermission, type AuthUser } from "../../lib/permissions.js"
import { validateSystemDatabaseRestoreArchive } from "../../services/system-database-restore-execution.js"
import { restoreSystemDatabaseBackup, assertSystemDatabaseRestoreTools } from "../../services/system-database-backup.js"
import { restoreSystemDatabaseBackup, assertSystemDatabaseRestoreTools, withSystemDatabaseRestoreAdmission } from "../../services/system-database-backup.js"
import { withVerifiedConfigurationChange } from "../../services/config-mutation-policy.js"
import { accessGeneration, decryptBinary, encryptBinary, decryptSecret, McpError, mcpStateDirectory } from "./security.js"
import { audit, validatePrincipal, type McpPrincipal } from "./store.js"
@@ -14,6 +14,7 @@ import type { add } from "./actions.js"
import type { ChangeEvidence } from "./action-types.js"
import { withApplicationMutationLock } from "../../services/config-mutation-lock.js"
import {assertOriginalMcpTemporaryGrant,type OriginalServiceGrant} from "./temporary-grant-origin.js"
import {reconcileRestoreReceiptAudit,inspectRestoreReceiptAudit} from "../../services/database-restore-audit.js"
export const MAX_RESTORE_ARCHIVE_BYTES = 64 * 1024 * 1024
const metadataSchema = z.object({ id: z.string().uuid(), ownerId: z.string().min(1), sha256: z.string().regex(/^[a-f0-9]{64}$/), sizeBytes: z.number().int().positive().max(MAX_RESTORE_ARCHIVE_BYTES), generation: z.string(), expiresAt: z.string().datetime(), createdAt: z.string().datetime() }).strict()
@@ -76,6 +77,10 @@ export async function readRestoreReceipt(id: string, actor: AuthUser) {
try { return JSON.parse(await readFile(filename(id, ".receipt.json"), "utf8")) as Record<string, unknown> }
catch { throw new McpError("NOT_FOUND", "Внешняя квитанция восстановления не найдена", 404) }
}
export async function readRestoreReceiptAuditStatus(id:string,actor:AuthUser){
await readRestoreReceipt(id,actor)
return inspectRestoreReceiptAudit(pool,mcpStateDirectory(),id)
}
export function registerDatabaseRestore(register: typeof add) {
register("database.restore", "mm_database_restore_plan", "Восстановить БД из загруженного человеком архива", "settings", "mm:settings:admin", "write", "global", z.object({ archiveRef: z.string().uuid(), expectedSha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict(), {
prepare: async (input, context) => {
@@ -91,15 +96,17 @@ export function registerDatabaseRestore(register: typeof add) {
}
/** This terminal operation must never hold a database-request admission while asking for an exclusive restore. */
export async function executeMcpDatabaseRestore(operationId: string, principal: McpPrincipal,originalGrant?:OriginalServiceGrant) {
const lease = await pool.connect()
let locked = false, unsafe = false
try {
locked = (await lease.query<{ locked: boolean }>("SELECT pg_try_advisory_lock(hashtextextended($1,0)) AS locked", ["mcp-operation:" + operationId])).rows[0].locked
if (locked) await executeRestore(operationId, principal,originalGrant)
} finally {
if (locked) try { await lease.query("SELECT pg_advisory_unlock(hashtextextended($1,0))", ["mcp-operation:" + operationId]) } catch { unsafe = true }
lease.release(unsafe)
}
return withSystemDatabaseRestoreAdmission(async () => {
const lease = await pool.connect()
let locked = false, unsafe = false
try {
locked = (await lease.query<{ locked: boolean }>("SELECT pg_try_advisory_lock(hashtextextended($1,0)) AS locked", ["mcp-operation:" + operationId])).rows[0].locked
if (locked) await executeRestore(operationId, principal,originalGrant)
} finally {
if (locked) try { await lease.query("SELECT pg_advisory_unlock(hashtextextended($1,0))", ["mcp-operation:" + operationId]) } catch { unsafe = true }
lease.release(unsafe)
}
})
}
async function executeRestore(operationId: string, principal: McpPrincipal,originalGrant?:OriginalServiceGrant) {
const { loadPlan } = await import("./runtime.js")
@@ -110,7 +117,7 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
let receipt: Record<string, unknown> = { operationId, archiveRef: payload.archiveRef, state: "running", startedAt: new Date().toISOString(), automaticRetry: false }
try { await durableWrite(filename(operationId, ".receipt.json"), JSON.stringify(receipt), true) }
catch (error) { if (error instanceof Error && "code" in error && error.code === "EEXIST") return; throw error }
let attempted = false, stage = "authority"
let attempted = false, stage = "authority", restoreActorId:string|undefined
const assertAuthority = async () => {
await assertOriginalMcpTemporaryGrant(pool,principal.connectionId,principal.credentialId,originalGrant)
const connection = await validatePrincipal(principal)
@@ -124,6 +131,7 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
}
try {
const { connection, ids } = await assertAuthority(), source = await archive(payload.archiveRef, connection.owner_id)
restoreActorId=connection.owner_id
stage = "archive-read"
if (source.sha256 !== payload.expectedSha256) throw new McpError("STATE_CONFLICT", "Архив подменён", 409)
const buffer = await decryptBinary(await readFile(filename(source.id, ".archive.bin")))
@@ -139,7 +147,6 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
receipt = { ...receipt, state: "applied", finishedAt: new Date().toISOString(), accessRevoked: true, warnings: Object.keys(outcome).filter(key => key.endsWith("Error") && outcome[key as keyof typeof outcome]) }
stage = "receipt-publish"
await durableWrite(filename(operationId, ".receipt.json"), JSON.stringify(receipt))
await audit(pool, connection.owner_id, "database.restore.receipt", null, receipt).catch(() => {})
} catch (error) {
// The isolated destructive acceptance runner can print bounded diagnostics
// without archive SQL, server credentials, or arbitrary error text.
@@ -154,4 +161,9 @@ async function executeRestore(operationId: string, principal: McpPrincipal,origi
await durableWrite(filename(operationId, ".receipt.json"), JSON.stringify(receipt))
if (!attempted) await pool.query("UPDATE mcp_operations SET state='rejected',error_code=$2,finished_at=now() WHERE id=$1", [operationId, receipt.errorCode]).catch(() => {})
}
// Audit failure is a separate pending/unconfirmed import AFTER durable
// applied receipt publication. It must never relabel confirmed restore.
if(receipt.state==="applied"){
try{await reconcileRestoreReceiptAudit(pool,mcpStateDirectory(),operationId,restoreActorId)}catch{/* Startup reconciles the existing external receipt; never replay restore. */}
}
}
@@ -1,6 +1,9 @@
import assert from "node:assert/strict"
import { createHash, randomUUID } from "node:crypto"
import { createDiagnosticCancellationSourceForTests, createReadServerProbeExecutionSourceForTests } from "./diagnostic-cancellation-source.js"
import { mkdtemp, readdir, rm, rmdir } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { createDiagnosticCancellationSourceForTests, createReadServerProbeExecutionSourceForTests, readServerProbeExecutionSource } from "./diagnostic-cancellation-source.js"
import { fingerprint, redact } from "./security.js"
const proof = async (value: unknown) => createHash("sha256").update(fingerprint(value)).digest("hex")
@@ -25,6 +28,7 @@ function fixture() {
return { rows }
} }
return { load: () => load(client, operationId, owner), read: () => read(snapshot, operationId, owner), readSql,
readWithExistingKey: () => readServerProbeExecutionSource(snapshot, operationId, owner),
refresh, binding, input, evidence, row, sealed, setRows: (value: typeof rows) => { rows=value }, calls: () => calls, operationId, credentialId }
}
{
@@ -100,4 +104,22 @@ for (const mutate of mutations) {
assert.equal(source.originalContext.tool, "bandwidth"); assert.equal(source.stageBudget, 1)
assert.equal(source.originalContext.identity.credentialId, f.credentialId)
}
console.log(`diagnostic-cancellation-source: locked/read-only source, expired original contexts and ${mutations.length} shared tamper checks passed`)
{
const state = await mkdtemp(join(tmpdir(), "mm-legacy-source-test-"))
const previousState = process.env.MCP_STATE_DIR
process.env.MCP_STATE_DIR = state
try {
const f = fixture()
await f.refresh()
f.row.evidence = { sealed: Buffer.alloc(32).toString("base64") }
await assert.rejects(f.readWithExistingKey, { code: "STATE_CONFLICT" })
assert.equal(f.readSql.length, 1)
assert.deepEqual(await readdir(state), [], "Historical source reads must leave missing encryption state untouched")
} finally {
if (previousState === undefined) delete process.env.MCP_STATE_DIR
else process.env.MCP_STATE_DIR = previousState
await rm(join(state, "encryption-key"), { force: true })
await rmdir(state)
}
}
console.log(`diagnostic-cancellation-source: locked/read-only source, missing key, expired original contexts and ${mutations.length} shared tamper checks passed`)
@@ -3,7 +3,7 @@ import type { ProbeStorageScope, ProbeStorageSql } from "../../services/server-p
import type { ServerProbeJournalContext } from "../../services/server-probe-journal.js"
import { SERVER_PROBE_OPERATION } from "../../services/server-probe-authority.js"
import { openServerProbeMcpBinding } from "./server-probe-actions.js"
import { decryptSecret, fingerprint, privateSecretFingerprint, redact, McpError } from "./security.js"
import { decryptSecret, decryptSecretExisting, fingerprint, privateFingerprinterExisting, privateSecretFingerprint, redact, McpError } from "./security.js"
const MAX_BYTES = 2097152
const uuid = z.string().uuid()
@@ -113,5 +113,14 @@ export const loadDiagnosticCancellationSource = sourceLoader(productionCrypto)
/** Test-only crypto seam. Never accept these dependencies from tool JSON. */
export const createDiagnosticCancellationSourceForTests = sourceLoader
/** Historical authentication only; confers no execution/cancellation grant. */
export const readServerProbeExecutionSource = readSourceLoader(productionCrypto)
export const readServerProbeExecutionSource = readSourceLoader({
open: decryptSecretExisting,
proof: async value => (await privateFingerprinterExisting("server-probe-mcp-binding-v1"))(fingerprint(value)),
})
export const createReadServerProbeExecutionSourceForTests = readSourceLoader
/** Exact previously locked source projection for private history conversion. */
export function authenticateServerProbeExecutionSourceRow(row: Record<string, unknown>, operationId: string,
owner: Readonly<DiagnosticCancellationOwner>) {
checkLookup(operationId, owner)
return validateSourceRow(row, operationId, owner, productionCrypto)
}
@@ -0,0 +1,73 @@
import assert from "node:assert/strict"
import { randomUUID } from "node:crypto"
import { test } from "node:test"
import type { McpAction, ActionContext } from "./action-types.js"
import { registerGlobalReconciliationActionsForTests } from "./global-reconciliation-actions.js"
type Register = Parameters<typeof registerGlobalReconciliationActionsForTests>[0]
type Dependencies = Parameters<typeof registerGlobalReconciliationActionsForTests>[1]
function fixture() {
const connectionId = randomUUID(), operationId = randomUUID(), ownerId = "current-retired-owner"
const context = { principal: { connectionId }, connection: { id: connectionId, owner_id: ownerId, owner_permissions: ["mm:servers:write"],
policy: { permissions: ["mm:servers:write"], scope: { allowGlobal: true, operationIds: ["servers.poll", "operations.global.reconcile"], serverIds: [1, 2] } } } } as ActionContext
let action!: Pick<McpAction, "target" | "read">
let history: { affectedServerIds: number[] } | undefined = { affectedServerIds: [1, 2] }
let revoke = false, retiredCalls = 0, live = false
const deps = {
connection: async () => {
if (revoke) throw Error("revoked-current-grant")
return structuredClone(context.connection)
},
query: async (sql: string) => {
if (sql.startsWith("SELECT id FROM servers")) return [{ id: 1 }]
if (live) return [{ id: operationId, state: "applied", operation_id: "servers.poll", evidence: { sealed: "live" } }]
return []
},
operation: (id: string) => ({ id, scope: "global", requiredPermissions: ["mm:servers:write"] }),
service: () => ({ reconcile: async () => ({ matchesDesiredState: true, matchesOriginalState: false }) }),
decrypt: async () => JSON.stringify({ target: { scope: "global" }, objects: [{ serverId: 1 }], steps: [{ kind: "service", service: "servers.poll", input: {} }] }),
retired: async (identity, authorize) => {
retiredCalls++
assert.deepEqual(identity, { operationId, connectionId, ownerId })
await authorize()
return history
},
} satisfies Omit<Dependencies, "operation" | "service"> & Partial<Record<"operation" | "service", unknown>>
registerGlobalReconciliationActionsForTests(((...args: Parameters<Register>) => { action = args[8] }) as Register, deps as unknown as Dependencies)
return { context, operationId, history: (value: typeof history) => { history = value }, live: () => { live = true },
revoke: () => { revoke = true }, calls: () => retiredCalls, read: () => action.read!({ operationId }, context),
target: () => action.target({ operationId }, context) }
}
test("global action reconciles retired poll only under current ownership and returns no archived material", async () => {
const f = fixture()
assert.deepEqual(await f.target(), { scope: "global" })
const result = await f.read() as { observedAt: string }
const { observedAt, ...rest } = result
assert(Number.isFinite(Date.parse(observedAt)))
assert.deepEqual(rest, { operationId: f.operationId, state: "applied", automaticRetry: false,
results: [{ stepIndex: 0, service: "servers.poll", matchesDesiredState: true, matchesOriginalState: false }] })
assert.equal(f.calls(), 2)
})
test("global action refuses missing or inaccessible retired history", async () => {
const f = fixture()
f.history(undefined)
await assert.rejects(f.read(), { code: "NOT_FOUND" })
f.history({ affectedServerIds: [1, 2] })
f.context.connection.policy.scope.serverIds = [1]
await assert.rejects(f.read(), { code: "SERVER_DENIED" })
f.context.connection.policy.scope.serverIds = [1, 2]
f.context.connection.policy.scope.operationIds = ["operations.global.reconcile"]
await assert.rejects(f.read(), { code: "PERMISSION_DENIED" })
f.revoke()
await assert.rejects(f.read(), /revoked-current-grant/)
})
test("global action uses the live reconciliation path while original source exists", async () => {
const f = fixture()
f.live()
await f.read()
assert.equal(f.calls(), 0)
})
@@ -1,5 +1,6 @@
import { z } from "zod"
import { pool } from "../../db/index.js"
import { readRetiredServerPollHistory } from "../../services/server-poll-history-reader.js"
import { hasPermission } from "../../lib/permissions.js"
import { effectiveMcpConnection } from "./store.js"
import { decryptSecret, fingerprint, McpError } from "./security.js"
@@ -7,40 +8,84 @@ import { findMcpOperation } from "./registry.js"
import { findMcpService } from "./service-registry.js"
import type { add } from "./actions.js"
import type { ActionContext, ChangeEvidence } from "./action-types.js"
import type { ConnectionRow } from "./store.js"
async function authorizedSource(operationId: string, context: ActionContext) {
const production = {
connection: effectiveMcpConnection,
query: async (sql: string, values: unknown[] = []) => (await pool.query(sql, values)).rows,
operation: findMcpOperation,
service: findMcpService,
decrypt: decryptSecret,
retired: readRetiredServerPollHistory,
}
type Dependencies = typeof production
const notFound = () => new McpError("NOT_FOUND", "Операция не найдена", 404)
async function currentAccess(context: ActionContext, deps: Dependencies) {
context.signal?.throwIfAborted()
const current = await effectiveMcpConnection(context.principal)
const current = await deps.connection(context.principal)
if (!current.policy.scope.allowGlobal || fingerprint(current.policy) !== fingerprint(context.connection.policy) || fingerprint(current.owner_permissions) !== fingerprint(context.connection.owner_permissions)) throw new McpError("PERMISSION_DENIED", "Глобальный допуск сверки изменился", 403)
const ids = (await pool.query("SELECT id FROM servers ORDER BY id LIMIT 1001")).rows.map(row => Number(row.id))
const ids = (await deps.query("SELECT id FROM servers ORDER BY id LIMIT 1001")).map(row => Number(row.id))
if (ids.length > 1000) throw new McpError("LIMIT_EXCEEDED", "Каталог глобальной сверки превышает лимит", 409)
if (ids.some(id => !current.policy.scope.serverIds.includes(id))) throw new McpError("SERVER_DENIED", "Глобальная сверка требует доступа ко всему каталогу серверов", 403)
const row = (await pool.query("SELECT o.id,o.state,p.operation_id,p.evidence FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id WHERE o.id=$1 AND o.connection_id=$2", [operationId, current.id])).rows[0]
if (!row) throw new McpError("NOT_FOUND", "Операция не найдена", 404)
const original = findMcpOperation(row.operation_id)
return current
}
function requireOriginal(actionId: string, current: ConnectionRow, deps: Dependencies) {
const original = deps.operation(actionId)
if (!original || original.scope !== "global" || !current.policy.scope.operationIds.includes(original.id) || original.requiredPermissions.some(permission => !hasPermission(current.policy.permissions, permission) || !hasPermission(current.owner_permissions, permission))) throw new McpError("PERMISSION_DENIED", "Утрачено право на исходную глобальную операцию", 403)
const evidence = JSON.parse(await decryptSecret(row.evidence.sealed)) as ChangeEvidence
if (evidence.target.scope !== "global" || !evidence.steps.length || evidence.steps.some(step => step.kind !== "service" || !findMcpService(step.service)?.reconcile)) throw new McpError("UNSUPPORTED_CAPABILITY", "Для глобальной операции нет безопасной read-only сверки", 409)
const declared = evidence.objects.flatMap(object => object.serverId === undefined ? [] : [object.serverId])
}
function requireServerScope(declared: number[], current: ConnectionRow) {
if (declared.some(id => !current.policy.scope.serverIds.includes(id))) throw new McpError("SERVER_DENIED", "Зависимость операции вне текущего глобального допуска", 403)
}
async function authorizedSource(operationId: string, context: ActionContext, deps: Dependencies) {
const current = await currentAccess(context, deps)
const row = (await deps.query("SELECT o.id,o.state,p.operation_id,p.evidence FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id WHERE o.id=$1 AND o.connection_id=$2", [operationId, current.id]))[0]
if (!row) {
const authorize = async () => {
const fresh = await currentAccess(context, deps)
if (fresh.id !== current.id || fresh.owner_id !== current.owner_id) throw notFound()
}
const archived = await deps.retired({ operationId, connectionId: current.id, ownerId: current.owner_id }, authorize,
{ deadlineAt: Date.now() + 10000, signal: context.signal })
context.signal?.throwIfAborted()
if (!archived) throw notFound()
requireOriginal("servers.poll", current, deps)
requireServerScope(archived.affectedServerIds, current)
return { kind: "retired-poll" as const }
}
requireOriginal(row.operation_id, current, deps)
const evidence = JSON.parse(await deps.decrypt(row.evidence.sealed)) as ChangeEvidence
if (evidence.target.scope !== "global" || !evidence.steps.length || evidence.steps.some(step => step.kind !== "service" || !deps.service(step.service)?.reconcile)) throw new McpError("UNSUPPORTED_CAPABILITY", "Для глобальной операции нет безопасной read-only сверки", 409)
requireServerScope(evidence.objects.flatMap(object => object.serverId === undefined ? [] : [object.serverId]), current)
if (["pending", "running"].includes(row.state)) throw new McpError("STATE_CONFLICT", "Операция ещё выполняется", 409)
context.signal?.throwIfAborted()
return { row, evidence }
return { kind: "live" as const, row, evidence }
}
export function registerGlobalReconciliationActions(register: typeof add) {
function registerActions(register: typeof add, deps: Dependencies) {
register("operations.global.reconcile", "mm_global_operation_reconcile", "Сверить глобальную сервисную операцию без повторной записи", "mcp", null, "read", "global", z.object({ operationId: z.string().uuid() }).strict(), {
target: async (input, context) => { await authorizedSource(String(input.operationId), context); return { scope: "global" } },
target: async (input, context) => { await authorizedSource(String(input.operationId), context, deps); return { scope: "global" } },
read: async (input, context) => {
const { row, evidence } = await authorizedSource(String(input.operationId), context)
const authorize = async () => { await authorizedSource(String(input.operationId), context) }
const operationId = String(input.operationId)
const source = await authorizedSource(operationId, context, deps)
if (source.kind === "retired-poll") return { operationId, state: "applied", observedAt: new Date().toISOString(),
results: [{ stepIndex: 0, service: "servers.poll", matchesDesiredState: true, matchesOriginalState: false }], automaticRetry: false }
const { row, evidence } = source
const authorize = async () => { if ((await authorizedSource(operationId, context, deps)).kind !== "live") throw notFound() }
const results = []
for (const [index, step] of evidence.steps.entries()) {
if (step.kind !== "service") throw new McpError("UNSUPPORTED_CAPABILITY", "Нативная глобальная сверка недоступна", 409)
await authorize()
results.push({ stepIndex: index, service: step.service, ...await findMcpService(step.service)!.reconcile!(step.input, authorize) })
results.push({ stepIndex: index, service: step.service, ...await deps.service(step.service)!.reconcile!(step.input, authorize) })
}
await authorize()
return { operationId: row.id, state: row.state, observedAt: new Date().toISOString(), results, automaticRetry: false }
},
})
}
export const registerGlobalReconciliationActions = (register: typeof add) => registerActions(register, production)
export const registerGlobalReconciliationActionsForTests = registerActions
@@ -5,6 +5,8 @@ await prepareOwnedBandwidthState()
const { pool, closePool } = await import("../../db/index.js")
try {
const { applySqlMigrations } = await import("../../db/migrate.js"); await applySqlMigrations(pool)
const { acceptOperationReplayTombstones } = await import("./operation-replay-tombstones.sql-helper.js")
await acceptOperationReplayTombstones()
const { acceptOwnedMcpOperationRoot } = await import("./operation-execution-origin.sql-helper.js")
console.log("Owned actual MCP operation root:", JSON.stringify(await acceptOwnedMcpOperationRoot()))
} finally { await closePool() }
@@ -9,6 +9,7 @@ import { performance } from "node:perf_hooks"
import type { QueryConfig, PoolClient } from "pg"
import { mcpTemporaryGrantSchema, type McpObjectRef } from "@mmapp/contracts/mcp"
import { pool } from "../../db/index.js"
import { sharedDatabaseGate } from "../../services/shared-database-gate.js"
import { assertNoConfigurationLockContext } from "../../services/config-mutation-lock.js"
import { assertNoOperationConfigurationLockContext } from "../../services/operation-config-lock-lease.js"
import { validatePrincipal, audit, type McpPrincipal, type StoredOperation } from "./store.js"
@@ -200,7 +201,10 @@ async function interrupt(entry: Admission) {
const stopped = new Promise<never>((_yes, no) => { timeout = setTimeout(() => { retired = true; client?.release(true); client = undefined; no(Error("Operation root cleanup deadline")) }, 2500) })
const work = async () => {
const owned = await connect
const query = (text: string, values: unknown[] = []) => owned.query({ text, values, query_timeout: Math.max(1, Math.min(1000, deadline - Date.now())) } as QueryConfig)
const query = (text: string, values: unknown[] = []) => {
if (retired || Date.now() >= deadline || !sharedDatabaseGate.hasActiveAdmission()) throw Error("Late cleanup SQL")
return owned.query({ text, values, query_timeout: Math.max(1, Math.min(1000, deadline - Date.now())) } as QueryConfig)
}
if ((await query("BEGIN")).command !== "BEGIN") throw Error("No cleanup BEGIN ACK")
const result = await query(`UPDATE public.mcp_operations SET state='unknown',verification='inconclusive',error_code='PROCESS_INTERRUPTED',
result=jsonb_build_object('needsReview',true,'automaticRetry',false),finished_at=now() WHERE id=$1 AND plan_id=$2 AND connection_id=$3
@@ -217,11 +221,21 @@ async function interrupt(entry: Admission) {
try { await Promise.race([work(), stopped]) } catch { console.error("MCP operation root cleanup deferred", { operationId: entry.operationId, retryAllowed: false }) }
finally { retired = true; clearTimeout(timeout); if (client) { client.release(!safe); client = undefined } }
}
/** Cleanup SQL owns a live request/exclusive admission. Waiting for repair is
* detached from runner slots; only admission is retried, never native work. */
async function interruptWithinDatabaseAdmission(entry: Admission): Promise<void> {
if (sharedDatabaseGate.hasActiveAdmission()) return sharedDatabaseGate.withinAdmission(() => interrupt(entry))
if (!sharedDatabaseGate.isPaused()) return sharedDatabaseGate.request(() => interrupt(entry))
void (async () => {
while (sharedDatabaseGate.isPaused()) await sharedDatabaseGate.whenOpen()
await sharedDatabaseGate.request(() => interrupt(entry))
})().catch(() => console.error("MCP operation root cleanup deferred", { operationId: entry.operationId, retryAllowed: false }))
}
/** Aborted admission/queue ownership only. A serialized/foreign cap is inert. */
export async function retireMcpOperationAdmission(admission: unknown): Promise<void> {
const entry = admission && typeof admission === "object" ? admissions.get(admission) : undefined
if (!entry) return
admissions.delete(admission as object); await interrupt(entry)
admissions.delete(admission as object); await interruptWithinDatabaseAdmission(entry)
}
/** Called exactly by the ordinary runtime before its first pool checkout/lock.
* The callback is fixed runner code; callers supply no metadata/authorizer. */
@@ -270,7 +284,7 @@ export async function withMcpOperationExecutionOrigin(admission: unknown, work:
await roots.run(entry, work)
} finally {
if (entry) { entry.active = false; clearTimeout(entry.timer); entry.controller.abort() }
if (interruptOwn) await interrupt(admitted)
if (interruptOwn) await interruptWithinDatabaseAdmission(admitted)
}
}
export async function captureMcpOperationExecutionOrigin(): Promise<McpOperationExecutionOrigin> {
@@ -0,0 +1,74 @@
import assert from "node:assert/strict"
import { randomUUID } from "node:crypto"
import { assertOwnedBandwidthSql } from "../../services/bandwidth-admission-process.sql-helper.js"
export async function acceptOperationReplayTombstones() {
assertOwnedBandwidthSql()
assert.equal(process.env.MMAPP_OWN_MCP_OPERATION_ORIGIN, "true")
const { pool } = await import("../../db/index.js")
const { mcpConnectionCreateSchema } = await import("@mmapp/contracts/mcp")
const { createMcpConnection, authenticateMcpToken } = await import("./store.js")
const { applyPlan, invokeMcpTool } = await import("./runtime.js")
const { operationIdempotencyDigest } = await import("./operation-replay-tombstones.js")
const { encryptSecret, McpError } = await import("./security.js")
const { default: Fastify } = await import("fastify")
const { default: authPlugin } = await import("../../plugins/auth.js")
const { createMcpRoutes } = await import("../../routes/mcp.js")
const { env } = await import("../../config.js")
const { SignJWT } = await import("jose")
const makeConnection = async () => {
const actor = { id: randomUUID(), email: "owned-tombstone@example.test", name: "Owned replay fixture", apps: ["mm"],
permissions: ["mm:users:write", "mm:mcp:read"], issuedAt: Date.now()-1000, expiresAt: Date.now()+300000 }
const issued = await createMcpConnection(mcpConnectionCreateSchema.parse({ name: actor.name, mode: "approval",
permissions: actor.permissions, expiresAt: new Date(actor.expiresAt).toISOString(),
scope: { serverIds: [], operationIds: ["users.create", "operations.read", "changes.apply"], allowGlobal: true },
limits: { callsPerMinute: 100, mutationsPerHour: 10 } }), actor)
return { actor, principal: await authenticateMcpToken(issued.token) }
}
const app = Fastify()
const ids: string[] = []
const matches = (code: string, status: number) => (error: unknown) =>
error instanceof McpError && error.code === code && error.statusCode === status
await pool.query("UPDATE public.mcp_settings SET enabled=true WHERE id=1")
try {
const owner = await makeConnection(), foreign = await makeConnection()
// A real completed plan has already initialized the owned encryption state.
// The fixture seeds its tombstone directly, so exercise the same initializer.
await encryptSecret("owned replay fixture state")
const connectionId = owner.principal.connectionId
ids.push(connectionId, foreign.principal.connectionId)
const operationId = randomUUID(), planId = randomUUID(), key = "owned-replay-" + randomUUID()
await pool.query(`INSERT INTO public.mcp_operation_replay_tombstones
(operation_id,plan_id,connection_id,idempotency_digest,action_id,target_server_ids,target_global)
VALUES($1,$2,$3,$4,'users.create','{}',true)`, [operationId, planId, connectionId, operationIdempotencyDigest(connectionId,key)])
await assert.rejects(applyPlan(owner.principal, planId, key), matches("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(applyPlan(owner.principal, planId, randomUUID()), matches("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(applyPlan(owner.principal, randomUUID(), key), matches("STATE_CONFLICT",409))
await assert.rejects(applyPlan(foreign.principal, planId, key), matches("NOT_FOUND",404))
await assert.rejects(invokeMcpTool(owner.principal,"mm_change_apply",{ planId,idempotencyKey:key }), matches("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(invokeMcpTool(owner.principal,"mm_operation_get",{ operationId }), matches("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(invokeMcpTool(foreign.principal,"mm_operation_get",{ operationId }), matches("NOT_FOUND",404))
await app.register(authPlugin)
await app.register(createMcpRoutes(async () => {}))
for (const subject of [owner, foreign]) {
const token = await new SignJWT({ sub: subject.actor.id, email: subject.actor.email, name: subject.actor.name,
apps: subject.actor.apps, permissions: subject.actor.permissions })
.setProtectedHeader({ alg:"HS256" }).setIssuer(env.authIssuer).setIssuedAt().setExpirationTime("4m")
.sign(new TextEncoder().encode(env.jwtSecret))
const reply = await app.inject({ method: "POST", url: "/mcp/plans/"+planId+"/apply", headers: { authorization:"Bearer "+token } })
assert.equal(reply.statusCode, subject === owner ? 410 : 404)
assert.equal(reply.json().code, subject === owner ? "OPERATION_HISTORY_EXPIRED" : "NOT_FOUND")
}
await pool.query("UPDATE public.mcp_connections SET policy=jsonb_set(policy,'{scope,allowGlobal}','false') WHERE id=$1", [connectionId])
await assert.rejects(applyPlan(owner.principal, planId, key), matches("NOT_FOUND",404))
await assert.rejects(invokeMcpTool(owner.principal,"mm_operation_get",{ operationId }), matches("NOT_FOUND",404))
const { rows:[proof] } = await pool.query(`SELECT
NOT EXISTS(SELECT 1 FROM public.mcp_operations WHERE connection_id=ANY($1::uuid[])) AS no_operation,
NOT EXISTS(SELECT 1 FROM public.mcp_budget_buckets WHERE connection_id=ANY($1::uuid[]) AND kind='mutations') AS no_write_budget`, [ids])
assert.deepEqual(proof,{no_operation:true,no_write_budget:true})
console.log("Owned operation tombstones: scoped MCP and Web 410, replay conflict and no dispatch")
} finally {
await app.close()
await pool.query("DELETE FROM public.mcp_operation_replay_tombstones WHERE connection_id=ANY($1::uuid[])", [ids])
}
}
@@ -0,0 +1,64 @@
import { createHash } from "node:crypto"
import type { PoolClient } from "pg"
import { hasPermission } from "../../lib/permissions.js"
import { listActions } from "./actions.js"
import { McpError } from "./security.js"
import { effectiveMcpConnection, type ConnectionRow, type McpPrincipal } from "./store.js"
type Query = Pick<PoolClient, "query">
export interface OperationReplayTombstone {
operation_id: string
plan_id: string
connection_id: string
action_id: string
target_server_ids: string[]
target_global: boolean
}
// Idempotency tokens are caller input and already stored in plaintext on live
// operations. This domain-separated digest avoids copying that plaintext and
// survives DB restore and access-key rotation. It is not a keyed secret proof.
export function operationIdempotencyDigest(connectionId: string, key: string): string {
return createHash("sha256").update("mcp-operation-replay-v1\0").update(connectionId).update("\0").update(key).digest("hex")
}
export function operationHistoryExpired(): never {
throw new McpError("OPERATION_HISTORY_EXPIRED", "Срок хранения завершённой операции истёк. Повторное применение запрещено.", 410)
}
export function assertOperationReplayScope(row: OperationReplayTombstone, connection: ConnectionRow): void {
const scope = connection.policy.scope
if (row.connection_id !== connection.id || !scope.operationIds.includes(row.action_id)) throw operationReplayNotFound()
const action = listActions().find(candidate => candidate.operation.id === row.action_id)
if (!action || action.operation.requiredPermissions.some(permission =>
!hasPermission(connection.policy.permissions, permission) || !hasPermission(connection.owner_permissions, permission))) throw operationReplayNotFound()
if (row.target_global && !scope.allowGlobal || row.target_server_ids.some(id => !scope.serverIds.some(server => String(server) === id))) throw operationReplayNotFound()
}
function operationReplayNotFound(): McpError {
return new McpError("NOT_FOUND", "Операция не найдена", 404)
}
const projection = "t.operation_id,t.plan_id,t.connection_id,t.action_id,t.target_server_ids::text[] AS target_server_ids,t.target_global"
export async function findOperationReplayTombstone(db: Query, connectionId: string, operationId: string) {
return (await db.query<OperationReplayTombstone>(`SELECT ${projection} FROM mcp_operation_replay_tombstones t
WHERE t.connection_id=$1 AND t.operation_id=$2`, [connectionId, operationId])).rows[0]
}
export async function findOwnerPlanReplayTombstone(db: Query, ownerId: string, planId: string) {
return (await db.query<OperationReplayTombstone>(`SELECT ${projection} FROM mcp_operation_replay_tombstones t
JOIN mcp_connections c ON c.id=t.connection_id WHERE c.owner_id=$1 AND t.plan_id=$2`, [ownerId, planId])).rows[0]
}
export async function refuseExpiredOperationReplay(db: Query, principal: McpPrincipal, connectionId: string, planId: string, key: string): Promise<void> {
const rows = (await db.query<OperationReplayTombstone>(`SELECT ${projection} FROM mcp_operation_replay_tombstones t
WHERE t.connection_id=$1 AND (t.plan_id=$2 OR t.idempotency_digest=$3)`,
[connectionId, planId, operationIdempotencyDigest(connectionId, key)])).rows
if (!rows.length) return
const conflict = rows.find(row => row.plan_id !== planId.toLowerCase())
const current = await effectiveMcpConnection(principal, db)
assertOperationReplayScope(conflict ?? rows[0], current)
if (conflict) throw new McpError("STATE_CONFLICT", "Ключ уже используется другим планом", 409)
operationHistoryExpired()
}
@@ -0,0 +1,226 @@
import assert from "node:assert/strict"
import { randomUUID } from "node:crypto"
import { setTimeout as delay } from "node:timers/promises"
import { assertOwnedBandwidthSql,prepareOwnedBandwidthState } from "../../services/bandwidth-admission-process.sql-helper.js"
assertOwnedBandwidthSql()
assert.equal(process.env.MMAPP_OWN_MCP_OPERATION_ORIGIN,"true")
await prepareOwnedBandwidthState()
const {pool,closePool}=await import("../../db/index.js")
const {applySqlMigrations}=await import("../../db/migrate.js")
const {mcpConnectionCreateSchema}=await import("@mmapp/contracts/mcp")
const {createMcpConnection,authenticateMcpToken}=await import("./store.js")
const {invokeMcpTool,approvePlan,applyPlan}=await import("./runtime.js")
const {OrdinaryRetirementCommitUnknown,retireOrdinaryMcpOperations}=await import("./ordinary-operation-retirement.js")
const {tickMcpWorkers}=await import("./workers.js")
const {pauseSchedulerJobsAndWait,resumeSchedulerJobs}=await import("../../services/scheduler-running.js")
const {Client}=await import("pg")
const {McpError}=await import("./security.js")
const {touchGeoipRunMeta}=await import("../../services/geoip-settings.js")
const {touchBackupScheduleRunMeta}=await import("../../services/backup-service.js")
const errorIs=(code:string,status:number)=>(error:unknown)=>error instanceof McpError&&error.code===code&&error.statusCode===status
try {
await applySqlMigrations(pool)
await pauseSchedulerJobsAndWait()
await pool.query("UPDATE public.mcp_settings SET enabled=true WHERE id=1")
const serverId=Number((await pool.query("INSERT INTO servers(name,host,enabled) VALUES('owned logs retention','127.0.0.1',false) RETURNING id")).rows[0].id)
async function access(){
const actor={id:randomUUID(),email:"owned-logs-retirement@example.test",name:"Owned settings retention",apps:["mm"],
permissions:["mm:settings:admin","mm:mcp:approve","mm:traffic:write","mm:uptime:write","mm:servers:write","mm:network:write","mm:backups:write"],
issuedAt:Date.now()-1000,expiresAt:Date.now()+300000}
const issued=await createMcpConnection(mcpConnectionCreateSchema.parse({name:actor.name,mode:"approval",
permissions:actor.permissions,expiresAt:new Date(actor.expiresAt).toISOString(),
scope:{serverIds:[serverId],operationIds:["logs.settings.update","traffic.settings.update","uptime.settings.update",
"servers.api-ping.settings.update","internet-path.settings.update","geoip.settings.update","backups.schedule.update","operations.read","changes.apply"],allowGlobal:true},
limits:{callsPerMinute:100,mutationsPerHour:20}}),actor)
return {actor,principal:await authenticateMcpToken(issued.token)}
}
const {actor,principal}=await access(),foreign=(await access()).principal
async function operation(retentionDays:number,ageMs:number,tool="mm_logs_settings_update_plan",
settings:Record<string,unknown>={retentionDays}){
const plan=await invokeMcpTool(principal,tool,{settings}) as {planId:string;state:string}
if(tool==="mm_geoip_settings_update_plan"||tool==="mm_backup_schedule_update_plan")assert.equal(plan.state,"awaiting-approval")
if(plan.state==="awaiting-approval")await approvePlan(plan.planId,actor,true)
const key=randomUUID(),applied=await applyPlan(principal,plan.planId,key)
let terminal
for(const deadline=Date.now()+15000;Date.now()<deadline;){
terminal=(await pool.query("SELECT state,verification,error_code FROM mcp_operations WHERE id=$1",[applied.id])).rows[0]
if(terminal&&!["pending","running"].includes(terminal.state))break
await delay(50)
}
assert.deepEqual(terminal,{state:"applied",verification:"passed",error_code:null})
// Only the disposable fixture ages rows, after real authenticated apply.
// No cutoff override or production encryption/approval bypass.
const finished=new Date(Date.now()-ageMs),created=new Date(finished.getTime()-1000)
await pool.query("UPDATE mcp_plans SET created_at=$2,approved_at=$2,expires_at=$3 WHERE id=$1",
[plan.planId,created,new Date(finished.getTime()+1000)])
await pool.query("UPDATE mcp_operations SET created_at=$2,finished_at=$3 WHERE id=$1",[applied.id,created,finished])
await pool.query("UPDATE mcp_operation_steps SET created_at=$2,finished_at=$3 WHERE operation_id=$1",[applied.id,created,finished])
return {id:applied.id,planId:plan.planId,key}
}
const age=180*86400000
const held=await operation(51,age+180000),spent=await operation(52,age+120000)
const due=await operation(53,age+60000),young=await operation(54,age-60000)
const boundary=await pool.connect()
try{
await boundary.query("BEGIN")
await boundary.query("UPDATE mcp_operations SET finished_at=transaction_timestamp()-interval '180 days' WHERE id=$1",[young.id])
await assert.rejects(boundary.query(`INSERT INTO mcp_ordinary_retirement_claims
(operation_id,plan_id,connection_id,source_proof,claim_token) VALUES($1,$2,$3,$4,$5)`,
[young.id,young.planId,principal.connectionId,"a".repeat(64),randomUUID()]),/cannot be replaced or reopened/)
}finally{await boundary.query("ROLLBACK");boundary.release()}
await pool.query("INSERT INTO mcp_drift(id,operation_id,expected,observed) VALUES($1,$2,'{}','{}')",[randomUUID(),held.id])
const ids=[held.id,spent.id,due.id,young.id]
const projection=async()=>(await pool.query(`SELECT i.id,o.state,p.state AS plan_state,s.state AS step_state,
k.operation_id AS checkpoint,t.operation_id AS tombstone,c.claim_token
FROM unnest($1::uuid[]) i(id)
LEFT JOIN mcp_operations o ON o.id=i.id LEFT JOIN mcp_plans p ON p.id=o.plan_id
LEFT JOIN mcp_operation_steps s ON s.operation_id=i.id LEFT JOIN mcp_operation_checkpoints k ON k.operation_id=i.id
LEFT JOIN mcp_operation_replay_tombstones t ON t.operation_id=i.id
LEFT JOIN mcp_ordinary_retirement_claims c ON c.operation_id=i.id ORDER BY i.id`,[ids])).rows
const originalQuery=Client.prototype.query
let commits=0
try{
Client.prototype.query=function(this:InstanceType<typeof Client>,...args:unknown[]){
if((args[0] as {text?:string})?.text==="COMMIT"&&++commits===3)
return Promise.reject(Error("owned ordinary destructive COMMIT transport loss"))
return Reflect.apply(originalQuery,this,args)
} as typeof Client.prototype.query
await assert.rejects(retireOrdinaryMcpOperations(),
(error:unknown)=>error instanceof OrdinaryRetirementCommitUnknown&&error.automaticReplay===false)
}finally{Client.prototype.query=originalQuery}
assert.equal(commits,3)
const before=await projection(),spentBefore=before.find(row=>row.id===spent.id)
assert.equal(spentBefore?.state,"applied");assert(spentBefore?.claim_token);assert.equal(spentBefore?.tombstone,null)
await tickMcpWorkers()
const after=await projection()
assert.deepEqual(after.filter(row=>row.id!==due.id),before.filter(row=>row.id!==due.id))
const retired=after.find(row=>row.id===due.id)
assert(retired?.claim_token);assert.equal(retired?.tombstone,due.id)
for(const field of ["state","plan_state","step_state","checkpoint"])assert.equal(retired[field],null)
assert.equal((await pool.query("SELECT 1 FROM mcp_plans WHERE id=$1",[due.planId])).rows.length,0)
assert.equal((await pool.query("SELECT retention_days FROM router_log_settings WHERE id=1")).rows[0].retention_days,54)
await tickMcpWorkers()
assert.deepEqual(await projection(),after)
await assert.rejects(applyPlan(principal,due.planId,due.key),errorIs("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(applyPlan(principal,randomUUID(),due.key),errorIs("STATE_CONFLICT",409))
await assert.rejects(invokeMcpTool(foreign,"mm_operation_get",{operationId:due.id}),errorIs("NOT_FOUND",404))
const telemetry=[
{action:"traffic.settings.update",version:2,record:await operation(55,age+60000,"mm_traffic_settings_update_plan")},
{action:"uptime.settings.update",version:2,record:await operation(56,age+60000,"mm_uptime_settings_update_plan",{enabled:false,retentionDays:56})},
{action:"servers.api-ping.settings.update",version:2,record:await operation(0,age+60000,"mm_servers_api_ping_settings_update_plan",{intervalSec:123})},
{action:"internet-path.settings.update",version:1,record:await operation(57,age+60000,"mm_internet_path_settings_update_plan")},
]
const telemetryRows=async()=>(await pool.query(`SELECT p.operation_id,p.operation_version,o.id
FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id WHERE o.id=ANY($1::uuid[]) ORDER BY p.operation_id`,
[telemetry.map(item=>item.record.id)])).rows
assert.deepEqual((await telemetryRows()).map(row=>[row.operation_id,row.operation_version]),
telemetry.map(item=>[item.action,item.version]).sort((a,b)=>String(a[0]).localeCompare(String(b[0]))))
const telemetrySettings=async()=>(await pool.query(`SELECT to_jsonb(t) AS traffic,to_jsonb(u) AS uptime,
to_jsonb(a) AS api_ping,to_jsonb(i) AS internet FROM traffic_settings t,uptime_settings u,
servers_api_ping_settings a,internet_path_settings i WHERE t.id=1 AND u.id=1 AND a.id=1 AND i.id=1`)).rows[0]
const settingsBefore=await telemetrySettings()
const traffic=telemetry[0].record
const {rows:[originalStep]}=await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1",[traffic.id])
await pool.query("UPDATE mcp_operation_steps SET result=jsonb_set(result,'{retentionDays}','999') WHERE operation_id=$1",[traffic.id])
await tickMcpWorkers()
assert.equal((await pool.query("SELECT 1 FROM mcp_ordinary_retirement_claims WHERE operation_id=$1",[traffic.id])).rows.length,0)
assert.equal((await pool.query("SELECT 1 FROM mcp_operations WHERE id=$1",[traffic.id])).rows.length,1)
await pool.query("UPDATE mcp_operation_steps SET result=$2 WHERE operation_id=$1",[traffic.id,originalStep.result])
await tickMcpWorkers()
await tickMcpWorkers()
// The keyset owner may need one tick to wrap after the rejected oldest row.
await tickMcpWorkers()
assert.deepEqual(await telemetryRows(),[])
assert.deepEqual(await telemetrySettings(),settingsBefore)
for(const {action,record} of telemetry){
const {rows:[proof]}=await pool.query(`SELECT t.action_id,t.target_global,t.target_server_ids::text[] AS server_ids,
NOT EXISTS(SELECT 1 FROM mcp_plans WHERE id=$2) AS plan_gone,
NOT EXISTS(SELECT 1 FROM mcp_operation_steps WHERE operation_id=$1) AS step_gone,
NOT EXISTS(SELECT 1 FROM mcp_operation_checkpoints WHERE operation_id=$1) AS checkpoint_gone
FROM mcp_operation_replay_tombstones t WHERE t.operation_id=$1 AND t.plan_id=$2`,[record.id,record.planId])
assert.deepEqual(proof,{action_id:action,target_global:true,server_ids:[String(serverId)],plan_gone:true,step_gone:true,checkpoint_gone:true})
await assert.rejects(applyPlan(principal,record.planId,record.key),errorIs("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(invokeMcpTool(foreign,"mm_operation_get",{operationId:record.id}),errorIs("NOT_FOUND",404))
}
// Keep the owned fixture offline even if a product scheduler refresh occurs.
await pool.query("UPDATE geoip_settings SET enabled=false WHERE id=1")
await touchGeoipRunMeta({lastCheckAt:new Date().toISOString(),lastError:"owned GeoIP metadata",etags:{"owned-test":"unchanged"}})
const geoip=await operation(0,age+60000,"mm_geoip_settings_update_plan",{enabled:false,updateIntervalSec:86400})
const geoipState=async()=>(await pool.query("SELECT to_jsonb(g) AS row FROM geoip_settings g WHERE id=1")).rows[0].row
const geoipBefore=await geoipState()
assert.equal(geoipBefore.enabled,false);assert.equal(geoipBefore.update_interval_sec,86400)
assert.equal(geoipBefore.last_error,"owned GeoIP metadata");assert.deepEqual(geoipBefore.etags_json,{"owned-test":"unchanged"})
const {rows:[geoipStep]}=await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1",[geoip.id])
const assertGeoipUnclaimed=async()=>{
assert.equal((await pool.query("SELECT 1 FROM mcp_ordinary_retirement_claims WHERE operation_id=$1",[geoip.id])).rows.length,0)
assert.equal((await pool.query("SELECT 1 FROM mcp_operations WHERE id=$1",[geoip.id])).rows.length,1)
assert.deepEqual(await geoipState(),geoipBefore)
}
await pool.query("UPDATE mcp_operation_steps SET result=jsonb_set(result,'{settings,updateIntervalSec}','172800') WHERE operation_id=$1",[geoip.id])
await tickMcpWorkers();await tickMcpWorkers();await assertGeoipUnclaimed()
await pool.query("UPDATE mcp_operation_steps SET result=$2 WHERE operation_id=$1",[geoip.id,geoipStep.result])
const geoipHold=randomUUID()
await pool.query("INSERT INTO mcp_drift(id,operation_id,expected,observed) VALUES($1,$2,'{}','{}')",[geoipHold,geoip.id])
await tickMcpWorkers();await assertGeoipUnclaimed()
await pool.query("DELETE FROM mcp_drift WHERE id=$1",[geoipHold])
await tickMcpWorkers();await tickMcpWorkers()
const {rows:[geoipTombstone]}=await pool.query(`SELECT t.action_id,t.target_global,t.target_server_ids::text[] AS server_ids,
NOT EXISTS(SELECT 1 FROM mcp_operations WHERE id=$1) AS operation_gone,
NOT EXISTS(SELECT 1 FROM mcp_plans WHERE id=$2) AS plan_gone,
NOT EXISTS(SELECT 1 FROM mcp_operation_steps WHERE operation_id=$1) AS step_gone,
NOT EXISTS(SELECT 1 FROM mcp_operation_checkpoints WHERE operation_id=$1) AS checkpoint_gone
FROM mcp_operation_replay_tombstones t WHERE t.operation_id=$1 AND t.plan_id=$2`,[geoip.id,geoip.planId])
assert.deepEqual(geoipTombstone,{action_id:"geoip.settings.update",target_global:true,server_ids:[String(serverId)],
operation_gone:true,plan_gone:true,step_gone:true,checkpoint_gone:true})
await assert.rejects(applyPlan(principal,geoip.planId,geoip.key),errorIs("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(applyPlan(principal,randomUUID(),geoip.key),errorIs("STATE_CONFLICT",409))
await assert.rejects(invokeMcpTool(foreign,"mm_operation_get",{operationId:geoip.id}),errorIs("NOT_FOUND",404))
assert.deepEqual(await geoipState(),geoipBefore)
await pool.query("UPDATE backup_schedule_settings SET enabled=false,server_ids_json='[]' WHERE id=1")
await touchBackupScheduleRunMeta({lastRunAt:new Date().toISOString(),lastDurationMs:123,lastError:"owned backup scheduler metadata"})
await pool.query(`INSERT INTO backup_entries(id,server_id,server_name,filename,size_bytes,kind,notes,created_at)
VALUES($1,$2,'owned fixture','owned-retention.rsc',123,'auto','keep',clock_timestamp())`,[randomUUID(),serverId])
const backupState=async()=>(await pool.query(`SELECT to_jsonb(s) AS schedule,
(SELECT jsonb_agg(to_jsonb(b) ORDER BY id) FROM backup_entries b) AS entries
FROM backup_schedule_settings s WHERE id=1`)).rows[0]
const backupOutside=await operation(0,age+120000,"mm_backup_schedule_update_plan",{enabled:false,serverIds:[String(serverId+1000)]})
const outsideState=await backupState()
await tickMcpWorkers();await tickMcpWorkers()
assert.equal((await pool.query("SELECT 1 FROM mcp_ordinary_retirement_claims WHERE operation_id=$1",[backupOutside.id])).rows.length,0)
assert.equal((await pool.query("SELECT 1 FROM mcp_operations WHERE id=$1",[backupOutside.id])).rows.length,1)
assert.deepEqual(await backupState(),outsideState)
await pool.query("UPDATE backup_schedule_settings SET server_ids_json='[]' WHERE id=1")
const backup=await operation(0,age+60000,"mm_backup_schedule_update_plan",{enabled:false,keepCount:9,serverIds:[String(serverId)]})
const backupBefore=await backupState()
assert.equal(backupBefore.schedule.enabled,false);assert.equal(backupBefore.schedule.keep_count,9)
assert.deepEqual(backupBefore.schedule.server_ids_json,[String(serverId)])
assert.equal(backupBefore.schedule.last_duration_ms,123);assert.equal(backupBefore.schedule.last_error,"owned backup scheduler metadata")
assert.deepEqual(backupBefore.entries,outsideState.entries)
const {rows:[backupStep]}=await pool.query("SELECT result FROM mcp_operation_steps WHERE operation_id=$1",[backup.id])
await pool.query("UPDATE mcp_operation_steps SET result=jsonb_set(result,'{keepCount}','10') WHERE operation_id=$1",[backup.id])
await tickMcpWorkers();await tickMcpWorkers()
assert.equal((await pool.query("SELECT 1 FROM mcp_ordinary_retirement_claims WHERE operation_id=$1",[backup.id])).rows.length,0)
assert.deepEqual(await backupState(),backupBefore)
await pool.query("UPDATE mcp_operation_steps SET result=$2 WHERE operation_id=$1",[backup.id,backupStep.result])
await tickMcpWorkers();await tickMcpWorkers()
const {rows:[backupTombstone]}=await pool.query(`SELECT t.action_id,t.target_global,t.target_server_ids::text[] AS server_ids,
NOT EXISTS(SELECT 1 FROM mcp_operations WHERE id=$1) AS operation_gone,
NOT EXISTS(SELECT 1 FROM mcp_plans WHERE id=$2) AS plan_gone,
NOT EXISTS(SELECT 1 FROM mcp_operation_steps WHERE operation_id=$1) AS step_gone,
NOT EXISTS(SELECT 1 FROM mcp_operation_checkpoints WHERE operation_id=$1) AS checkpoint_gone
FROM mcp_operation_replay_tombstones t WHERE t.operation_id=$1 AND t.plan_id=$2`,[backup.id,backup.planId])
assert.deepEqual(backupTombstone,{action_id:"backups.schedule.update",target_global:true,server_ids:[String(serverId)],
operation_gone:true,plan_gone:true,step_gone:true,checkpoint_gone:true})
await assert.rejects(applyPlan(principal,backup.planId,backup.key),errorIs("OPERATION_HISTORY_EXPIRED",410))
await assert.rejects(applyPlan(principal,randomUUID(),backup.key),errorIs("STATE_CONFLICT",409))
await assert.rejects(invokeMcpTool(foreign,"mm_operation_get",{operationId:backup.id}),errorIs("NOT_FOUND",404))
assert.deepEqual(await backupState(),backupBefore)
await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{scope,serverIds}','[]') WHERE id=$1",[principal.connectionId])
await assert.rejects(applyPlan(principal,due.planId,due.key),errorIs("NOT_FOUND",404))
for(const {record} of telemetry)await assert.rejects(applyPlan(principal,record.planId,record.key),errorIs("NOT_FOUND",404))
await assert.rejects(applyPlan(principal,geoip.planId,geoip.key),errorIs("NOT_FOUND",404))
await assert.rejects(applyPlan(principal,backup.planId,backup.key),errorIs("NOT_FOUND",404))
console.log("Owned settings operations: seven exact families, authenticated apply, physical 180-day purge, preserved settings and scoped replay")
}finally{resumeSchedulerJobs();await closePool()}
@@ -0,0 +1,221 @@
import { randomUUID } from "node:crypto"
import { logSettingsPatchSchema, logSettingsSchema } from "@mmapp/contracts"
import { geoipSettingsDtoSchema, geoipSettingsPatchSchema } from "@mmapp/contracts/geoip"
import { backupScheduleSettingsDtoSchema, putBackupScheduleSettingsSchema } from "@mmapp/contracts/backups"
import { z } from "zod"
import { connectProbeStorage } from "../../services/server-probe-storage-connection.js"
import type { ProbeStorageSql } from "../../services/server-probe-journal-maintenance.js"
import { sharedDatabaseGate } from "../../services/shared-database-gate.js"
import { operationIdempotencyDigest } from "./operation-replay-tombstones.js"
import { decryptSecretExisting, fingerprint, privateFingerprinterExisting, redact } from "./security.js"
const uuid = z.string().uuid()
const positive = z.number().int().positive().safe()
const object = z.record(z.string(),z.unknown())
const sealed = z.object({sealed:z.string().min(1).max(262144)}).strict()
const ids = z.array(positive).max(1000)
const retention = z.number().int().min(1).max(365)
const bool = z.boolean()
const interval = (min:number,max:number)=>z.number().int().min(min).max(max)
const backupServerIds=z.array(z.string().regex(/^[1-9]\d{0,15}$/).refine(value=>Number.isSafeInteger(Number(value))))
.max(1000).refine(values=>new Set(values).size===values.length)
const backupSchedule=backupScheduleSettingsDtoSchema.extend({serverIds:backupServerIds}).strict()
const backupBefore=z.object({value:backupSchedule,serverIds:ids}).strict()
// Drizzle's string-mode timestamptz keeps PostgreSQL's space and hour-only
// offset. Accept that exact wire form as well as an ISO timestamptz.
const timestamp = z.string().regex(/^\d{4}-\d\d-\d\d[ T]\d\d:\d\d:\d\d(?:\.\d{1,6})?(?:Z|[+-]\d\d(?::\d\d)?)$/)
.refine(value=>Number.isFinite(Date.parse(value.replace(" ","T").replace(/([+-]\d\d)$/,"$1:00"))))
const metadata = {
id:z.literal(1),createdAt:timestamp,updatedAt:timestamp,
lastCollectedAt:timestamp.nullable(),lastDurationMs:z.number().int().safe().nullable(),
lastError:z.string().max(16384).nullable(),
}
function telemetryFamily(version:number,domain:string,settings:z.ZodObject,hasEpoch:boolean,hasConfigurationProof:boolean){
const originalSettings=hasConfigurationProof?settings.extend({configurationProof:z.string().regex(/^[0-9a-f]{64}$/)}):settings
const effect=settings.extend({...metadata,...(hasEpoch?{pollConfigurationEpoch:uuid}:{})}).strict()
return {version,domain,patch:settings.partial().strict().refine(value=>Object.keys(value).length>0),
before:z.object({value:originalSettings.strict(),serverIds:ids}).strict(),effect}
}
const families={
"logs.settings.update":{version:1,domain:null,patch:logSettingsPatchSchema,before:logSettingsSchema.strict(),effect:logSettingsSchema.strict()},
"traffic.settings.update":telemetryFamily(2,"traffic-settings",z.object({enabled:bool,intervalSec:interval(5,3600),retentionDays:retention}),true,true),
"uptime.settings.update":telemetryFamily(2,"uptime-settings",z.object({enabled:bool,resourcesEnabled:bool,pingEnabled:bool,
speedEnabled:bool,intervalSec:interval(5,86400),probeIntervalSec:interval(5,86400),speedIntervalSec:interval(10,86400),retentionDays:retention}),true,true),
"servers.api-ping.settings.update":telemetryFamily(2,"servers-api-ping-settings",z.object({enabled:bool,intervalSec:interval(10,86400)}),false,true),
"internet-path.settings.update":telemetryFamily(1,"internet-path-settings",z.object({enabled:bool,intervalSec:interval(30,86400),retentionDays:retention}),true,false),
"geoip.settings.update":{
version:1,domain:"geoip-settings",
patch:geoipSettingsPatchSchema.strict().refine(value=>Object.keys(value).length>0),
before:z.object({value:z.object({enabled:bool,updateIntervalSec:positive}).strict(),serverIds:ids}).strict(),
effect:z.object({settings:geoipSettingsDtoSchema.strict(),verificationScope:z.literal("configuration-persistence")})
.strict().transform(result=>result.settings),
},
"backups.schedule.update":{
version:1,domain:"backup-schedule",
patch:putBackupScheduleSettingsSchema.extend({serverIds:backupServerIds.optional()}).strict().refine(value=>Object.keys(value).length>0),
before:backupBefore,effect:backupSchedule,
},
} as const
const planEvidence = z.object({
target:z.object({scope:z.literal("global")}).strict(),
objects:z.array(z.object({kind:z.string(),id:z.string(),serverId:positive.optional()}).strict()).max(1001),
steps:z.tuple([z.object({kind:z.literal("service"),service:z.string(),input:z.object({settings:object}).strict()}).strict()]),
snapshots:z.tuple([]),before:object,warnings:z.array(z.string()).max(8),
verificationScope:z.literal("configuration").optional(),
requiresHuman:z.literal(true).optional(),
}).strict()
class RetirementRefused extends Error {}
export class OrdinaryRetirementCommitUnknown extends Error {
readonly automaticReplay = false
constructor() { super("Ordinary operation retirement outcome unknown; automatic replay forbidden") }
}
const requireThat: (value: unknown) => asserts value = value => {
if (!value) throw new RetirementRefused()
}
// Fixed reviewed settings families without artifact, historic receipt or
// inverse-plan consumers. Other action families need separate review.
const ELIGIBLE = `FROM public.mcp_operations o
JOIN public.mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
JOIN public.mcp_operation_steps s ON s.operation_id=o.id AND s.step_index=0
JOIN public.mcp_operation_checkpoints k ON k.operation_id=o.id AND k.step_index=0
WHERE (p.operation_id,p.operation_version) IN (
('logs.settings.update',1),('traffic.settings.update',2),('uptime.settings.update',2),
('servers.api-ping.settings.update',2),('internet-path.settings.update',1),('geoip.settings.update',1),('backups.schedule.update',1)
) AND p.state='completed'
AND o.state='applied' AND o.verification='passed' AND o.error_code IS NULL
AND isfinite(o.finished_at) AND o.finished_at<transaction_timestamp()-interval '180 days'
AND isfinite(p.expires_at) AND p.expires_at<transaction_timestamp()
AND s.state='verified' AND s.server_id IS NULL AND isfinite(s.finished_at)
AND k.after_sealed IS NOT NULL
AND (SELECT count(*) FROM public.mcp_operation_steps WHERE operation_id=o.id)=1
AND (SELECT count(*) FROM public.mcp_operation_checkpoints WHERE operation_id=o.id)=1
AND NOT EXISTS(SELECT 1 FROM public.mcp_drift WHERE operation_id=o.id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_recovery_intents WHERE operation_id=o.id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_scoped_recovery_intents WHERE source_operation_id=o.id OR recovery_operation_id=o.id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_routing_reservations WHERE source_operation_id=o.id OR source_plan_id=p.id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_routing_forward_charges WHERE source_operation_id=o.id OR source_plan_id=p.id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_routing_manual_inverse_charges WHERE source_operation_id=o.id OR recovery_operation_id=o.id OR source_plan_id=p.id OR recovery_plan_id=p.id)
AND NOT EXISTS(SELECT 1 FROM public.mcp_diagnostic_cancellations WHERE operation_id=o.id)
AND NOT EXISTS(SELECT 1 FROM public.server_probe_storage_admissions WHERE identity_kind='mcp' AND execution_id=o.id)
AND NOT EXISTS(SELECT 1 FROM public.server_poll_storage_admissions WHERE identity_kind='mcp' AND execution_id=o.id)
AND NOT EXISTS(SELECT 1 FROM public.vrf_production_tickets WHERE plan_id=p.id)`
const CANDIDATES = `SELECT o.id,o.connection_id,o.finished_at::text AS finished_at ${ELIGIBLE}
AND NOT EXISTS(SELECT 1 FROM public.mcp_ordinary_retirement_claims WHERE operation_id=o.id)
AND ($1::timestamptz IS NULL OR (o.finished_at,o.id)>($1::timestamptz,$2::uuid))
ORDER BY o.finished_at,o.id LIMIT 8`
const SOURCE = `SELECT to_jsonb(o) AS operation,to_jsonb(p) AS plan,to_jsonb(s) AS step,to_jsonb(k) AS checkpoint ${ELIGIBLE}
AND o.id=$1 AND o.connection_id=$2
AND octet_length(to_jsonb(o)::text)+octet_length(to_jsonb(p)::text)+octet_length(to_jsonb(s)::text)+octet_length(to_jsonb(k)::text)<=1048576
FOR UPDATE OF o,p,s,k NOWAIT`
async function transaction<T>(deadlineAt:number, work:(sql:ProbeStorageSql)=>Promise<T>):Promise<T> {
const sql = await connectProbeStorage({deadlineAt})
let committing=false,committed=false
try {
await sql.query("BEGIN")
await sql.query("SELECT set_config('TimeZone','UTC',true),set_config('statement_timeout','5000ms',true),set_config('lock_timeout','1000ms',true)")
const result=await work(sql)
committing=true
await sql.query("COMMIT")
committed=true
return result
} catch(error) {
if(committing)throw new OrdinaryRetirementCommitUnknown()
try{await sql.query("ROLLBACK")}catch{/* Destroy this checkout. */}
throw error
} finally {
sql.release(!committed)
}
}
async function prepare(sql:ProbeStorageSql, operationId:string, connectionId:string) {
requireThat((await sql.query("SELECT id FROM public.mcp_connections WHERE id=$1 FOR UPDATE NOWAIT",[connectionId])).rows.length===1)
requireThat((await sql.query("SELECT pg_try_advisory_xact_lock(hashtextextended($1,0)) AS locked",["mcp-operation:"+operationId])).rows[0]?.locked===true)
const rows=(await sql.query(SOURCE,[operationId,connectionId])).rows
requireThat(rows.length===1)
const raw=rows[0], operation=object.parse(raw.operation), plan=object.parse(raw.plan)
const step=object.parse(raw.step),checkpoint=object.parse(raw.checkpoint)
const open=async(value:unknown)=>JSON.parse(await decryptSecretExisting(sealed.parse(value).sealed))
const original=planEvidence.parse(await open(plan.evidence))
const action=z.enum(Object.keys(families) as [keyof typeof families,...(keyof typeof families)[]]).parse(plan.operation_id)
const family=families[action],service=original.steps[0]
requireThat(plan.operation_version===family.version&&service.service===action)
requireThat(fingerprint(await open(plan.payload))===fingerprint(service.input))
requireThat(fingerprint(step.intended_state)===fingerprint(redact(service)))
const patch=object.parse(family.patch.parse(service.input.settings))
const effect=object.parse(family.effect.parse(step.result))
requireThat(Object.entries(patch).every(([key,value])=>fingerprint(effect[key])===fingerprint(value)))
requireThat(fingerprint(operation.result)===fingerprint({confirmedWrites:1,verificationScope:"configuration"}))
for(const value of [checkpoint.before_sealed,checkpoint.after_sealed]){
requireThat(typeof value==="string"&&value.length<=1024&&fingerprint(await open({sealed:value}))===fingerprint([]))
}
const serverObjects=original.objects.filter(item=>item.kind==="server")
const serverIds=ids.parse(serverObjects.map(item=>item.serverId))
requireThat(new Set(serverIds).size===serverIds.length&&serverObjects.every(item=>item.id===String(item.serverId)))
const domainObjects=original.objects.filter(item=>item.kind!=="server")
const before=family.before.parse(original.before)
if(family.domain===null){
requireThat(domainObjects.length===0&&original.requiresHuman===undefined)
}else{
requireThat(original.requiresHuman===true&&original.verificationScope==="configuration"
&&domainObjects.length===1&&domainObjects[0].kind===family.domain&&domainObjects[0].id==="1"&&domainObjects[0].serverId===undefined)
requireThat("serverIds" in before&&fingerprint(before.serverIds)===fingerprint(serverIds))
}
if(action==="backups.schedule.update"){
const scope=new Set(serverIds.map(String))
requireThat([...backupBefore.parse(before).value.serverIds,...backupSchedule.parse(effect).serverIds].every(id=>scope.has(id)))
}
const signer=await privateFingerprinterExisting("mcp-ordinary-retirement-source-v1")
return {planId:uuid.parse(plan.id),action,serverIds,key:z.string().min(1).max(200).parse(operation.idempotency_key),proof:signer(fingerprint(raw))}
}
async function retire(operationId:string,connectionId:string,deadlineAt:number) {
const token=randomUUID()
await transaction(deadlineAt,async sql=>{
const source=await prepare(sql,operationId,connectionId)
const claim=await sql.query(`INSERT INTO public.mcp_ordinary_retirement_claims(operation_id,plan_id,connection_id,source_proof,claim_token)
VALUES($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING RETURNING operation_id`,[operationId,source.planId,connectionId,source.proof,token])
requireThat(claim.rows.length===1&&claim.rows[0].operation_id===operationId)
})
await transaction(deadlineAt,async sql=>{
const claim=(await sql.query(`SELECT source_proof,plan_id FROM public.mcp_ordinary_retirement_claims
WHERE operation_id=$1 AND connection_id=$2 AND claim_token=$3 AND claim_xid<>pg_current_xact_id()`,[operationId,connectionId,token])).rows
requireThat(claim.length===1)
const source=await prepare(sql,operationId,connectionId)
requireThat(claim[0].source_proof===source.proof&&claim[0].plan_id===source.planId)
const tombstone=await sql.query(`INSERT INTO public.mcp_operation_replay_tombstones
(operation_id,plan_id,connection_id,idempotency_digest,action_id,target_server_ids,target_global)
VALUES($1,$2,$3,$4,$5,$6,true) RETURNING operation_id`,
[operationId,source.planId,connectionId,operationIdempotencyDigest(connectionId,source.key),source.action,source.serverIds])
requireThat(tombstone.rows.length===1&&tombstone.rows[0].operation_id===operationId)
const checkpoint=await sql.query("DELETE FROM public.mcp_operation_checkpoints WHERE operation_id=$1 AND step_index=0 RETURNING operation_id",[operationId])
const step=await sql.query("DELETE FROM public.mcp_operation_steps WHERE operation_id=$1 AND step_index=0 RETURNING operation_id",[operationId])
const operation=await sql.query("DELETE FROM public.mcp_operations WHERE id=$1 AND connection_id=$2 AND plan_id=$3 RETURNING id AS operation_id",[operationId,connectionId,source.planId])
const plan=await sql.query("DELETE FROM public.mcp_plans WHERE id=$1 AND connection_id=$2 RETURNING id AS plan_id",[source.planId,connectionId])
requireThat([checkpoint,step,operation].every(result=>result.rows.length===1&&result.rows[0].operation_id===operationId)
&&plan.rows.length===1&&plan.rows[0].plan_id===source.planId)
})
}
let cursor:{finishedAt:string;operationId:string}|undefined
export function retireOrdinaryMcpOperations() {
return sharedDatabaseGate.request(async()=>{
const deadlineAt=Date.now()+15000
const rows=await transaction(deadlineAt,async sql=>(await sql.query(CANDIDATES,[cursor?.finishedAt??null,cursor?.operationId??null])).rows)
requireThat(rows.length<=8)
let scanned=0,retired=0
for(const row of rows){
if(Date.now()>=deadlineAt-2000||retired>=2)break
const id=uuid.parse(row.id),connectionId=uuid.parse(row.connection_id)
requireThat(typeof row.finished_at==="string"&&Number.isFinite(Date.parse(row.finished_at)))
cursor={finishedAt:row.finished_at,operationId:id}
scanned++
try{await retire(id,connectionId,deadlineAt);retired++}
catch(error){if(error instanceof OrdinaryRetirementCommitUnknown)throw error}
}
if(scanned===rows.length&&rows.length<8)cursor=undefined
return {scanned,retired,automaticReplay:false as const}
})
}
@@ -1,3 +1,5 @@
import { isConfirmedRoutingFibTable } from "../../services/routing-table-fib.js"
import { prepareOriginalEffectSelection } from "./routing-original-effects.js"
import type { RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { pool } from "../../db/index.js"
import { nativeBgpRows, bgpTemplateReferences, type NativeBgpRow } from "../../services/bgp-export-model.js"
@@ -96,7 +98,7 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
}
const sourceId = profile.serverId, tables = await get(sourceId, "/routing/table"), vrfs = await get(sourceId, "/ip/vrf")
const table = unique(tables, profile.table)
if (![true, "true", "yes"].includes(table.fib as string | boolean) || [true, "true", "yes"].includes(table.disabled as string | boolean) || [true, "true", "yes"].includes(table.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB таблицы назначения не подтверждён", 409)
if (!isConfirmedRoutingFibTable(table)) throw new McpError("STATE_UNAVAILABLE", "FIB таблицы назначения не подтверждён", 409)
await enabled(sourceId, "/ip/vrf", unique(vrfs, profile.probeVrf))
// Keep ordered VRF membership (first matching row wins), never equate an
// arbitrary policy table with the diagnostic VRF's same-name table.
@@ -140,7 +142,7 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
for (const property of ["vrf", "routing-table"]) if (!values[property] && instance) values[property] = bgpFilterName(await post(sourceId, "/routing/bgp/instance/get", { ".id": instance[".id"], "value-name": property }))
if (!values.vrf || !values["routing-table"] || !values.afi || values["input.filter"] === null) throw new McpError("STATE_UNAVAILABLE", "Не подтверждены effective BGP table/VRF/AFI/input binding", 409)
const boundTable = unique(tables, values["routing-table"])
if (![true, "true", "yes"].includes(boundTable.fib as string | boolean) || [true, "true", "yes"].includes(boundTable.disabled as string | boolean) || [true, "true", "yes"].includes(boundTable.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB BGP таблицы не подтверждён", 409)
if (!isConfirmedRoutingFibTable(boundTable)) throw new McpError("STATE_UNAVAILABLE", "FIB BGP таблицы не подтверждён", 409)
await enabled(sourceId, "/ip/vrf", unique(vrfs, values.vrf))
bindings.push({ connectionId: connection[".id"], instance: instance ? String(instance.name) : null, vrf: values.vrf, table: values["routing-table"]!, afi: values.afi, inputChain: values["input.filter"]!, outputChain: values["output.filter-chain"] ?? "" })
}
@@ -177,7 +179,7 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
let gateway: ReturnType<typeof routeGatewayContext>
try { gateway = routeGatewayContext(candidate.gateway) } catch { throw new McpError("STATE_UNAVAILABLE", "Gateway кандидата не подтверждён", 409) }
const resolver = unique(tables, gateway.resolverTable)
if (![true, "true", "yes"].includes(resolver.fib as string | boolean) || [true, "true", "yes"].includes(resolver.disabled as string | boolean) || [true, "true", "yes"].includes(resolver.invalid as string | boolean)) throw new McpError("STATE_UNAVAILABLE", "FIB resolver таблицы не подтверждён", 409)
if (!isConfirmedRoutingFibTable(resolver)) throw new McpError("STATE_UNAVAILABLE", "FIB resolver таблицы не подтверждён", 409)
if (unavailableIds.includes(candidate.jumpHostId) || unavailableIds.includes(candidate.exitNodeId)) throw new McpError("DEVICE_UNAVAILABLE", "Источник кандидата отсутствует или отключён", 409)
} catch (error) { unavailableCandidate(candidate.id, error) }
}
@@ -250,6 +252,14 @@ async function readContext(profile: RouteAiProfile, options: RouteContextOptions
nativeRuleId: selected && current ? filters.find(row => row.chain === selected.inputChain)![".id"] : null, ownedCompilerRecognized: !!current,
sharedConnectionIds, bindings, disabledConnectionIds: disabledBindings.map(binding => binding.connectionId), externalReferences, executionAvailable: reason === null, executionReason: reason, scopedExecutionAvailable: scopedReason === null, scopedExecutionReason: scopedReason, targetRuleProof, dependencyProof, sourceProofs: sources.expected }
return { before, clients, fence, probeEndpoints, ownedTarget,
captureOriginalEffectSelection: async (candidateId: string) => {
try {
const routes = await get(sourceId, "/routing/route"), sessions = await get(sourceId, "/routing/bgp/session")
await fence()
return prepareOriginalEffectSelection(profile, candidateId, selected, { routes, connections, sessions,
candidate: candidateRows.get(candidateId), probe: probeEndpoints.get(candidateId) })
} catch { await fence(); return { status: "unknown" as const, reason: "prepared-effect-catalogue-unavailable" } }
},
signEvidence: (value: unknown) => sign(pathProofTag("community") + fingerprint({ dependencyProof, targetRuleProof, value })),
verifyEvidence: (value: unknown, proof: unknown) => typeof proof === "string" && constantEqual(sign(pathProofTag("community") + fingerprint({ dependencyProof, targetRuleProof, value })), proof),
}
@@ -1,5 +1,8 @@
import assert from "node:assert/strict"
import { readFile } from "node:fs/promises"
import { test } from "node:test"
import { runInNewContext } from "node:vm"
import ts from "typescript"
import type { ActionContext } from "./action-types.js"
import { loadReservedScopedRecoverySource } from "./routing-community-scoped-recovery-source.js"
import { executeReservedScopedRecovery, scopedRecoveryIntentFromCheckpoint, validateReservedInverseVerified } from "./routing-community-scoped-recovery.js"
@@ -16,6 +19,67 @@ test("a serialized allocation cannot enter the shared inverse engine", async ()
await assert.rejects(executeReservedScopedRecovery(forged, {} as Parameters<typeof executeReservedScopedRecovery>[1]), /capability/)
})
test("confirmed inverse spend quarantines the exact slot on post-spend refusal without replay", async () => {
// Execute the actual boundary body with only its I/O dependencies substituted.
// This does not mint a production capability or claim native/SQL acceptance.
const source = ts.createSourceFile("routing-community-scoped-recovery.ts",
await readFile(new URL("./routing-community-scoped-recovery.ts", import.meta.url), "utf8"), ts.ScriptTarget.ES2022)
const declaration = source.statements.find(node => ts.isFunctionDeclaration(node) && node.name?.text === "executeReservedScopedRecovery")
assert(declaration)
const compiled = ts.transpileModule(declaration.getText(source), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText
const allocation = Object.freeze({ kind: "routing-reserved-inverse-allocation" })
const execution = Object.freeze({ kind: "routing-reserved-inverse-execution" })
const principal = { connectionId: "owned", credentialId: "original" }
const original = new Error("original authority failure"), result = Object.freeze({ outcome: "recovered" })
const scenarios = {
spend: ["spend"],
inspect: ["spend", "inspect", "quarantine"],
authority: ["spend", "inspect", "authority", "quarantine"],
source: ["spend", "inspect", "authority", "source", "quarantine"],
proof: ["spend", "inspect", "authority", "source", "proof", "quarantine"],
engine: ["spend", "inspect", "authority", "source", "proof", "engine", "quarantine"],
success: ["spend", "inspect", "authority", "source", "proof", "engine"],
} as const
for (const [scenario, expected] of Object.entries(scenarios)) {
const events: string[] = [], exports: Record<string, unknown> = {}, capabilities = new WeakMap()
let request: object | undefined
const at = (phase: string) => { events.push(phase); if (phase === scenario) throw original }
runInNewContext(compiled, {
exports, reservedExecutions: capabilities, fingerprint: JSON.stringify,
createRoutingReservationStore: () => ({
async beginInverseAttempt(value: unknown) {
assert.equal(value, allocation); at("spend"); return { reservationId: "spent-slot", execution }
},
async inspectInverseExecution(value: unknown) {
assert.equal(value, execution); at("inspect")
return { principal, sourceOperationId: "source", deadline: "2026-10-29T01:00:00.000Z" }
},
async markUnknown(id: string, lane: string) {
assert.equal(id, "spent-slot"); assert.equal(lane, "inverse"); at("quarantine")
throw new Error("quarantine persistence unavailable")
},
}),
effectiveMcpConnection: async () => { at("authority"); return { owner_id: "owner", policy: {}, owner_permissions: [] } },
loadReservedScopedRecoverySource: async () => {
at("source"); return { profile: { serverId: 1 }, input: {}, configuration: { packet: {} } }
},
accessGeneration: async () => "generation",
deadline: () => Date.parse("2026-10-29T01:00:00.000Z"),
sealedProof: async () => { at("proof"); return "proof" },
executeScopedRecovery: async (value: object) => {
request = value; assert.equal(capabilities.get(value), execution); at("engine"); return result
},
})
const execute = exports.executeReservedScopedRecovery as
(allocation: unknown, expected: { sourceOperationId: string; principal: typeof principal }) => Promise<unknown>
const outcome = execute(allocation, { sourceOperationId: "source", principal })
if (scenario === "success") assert.equal(await outcome, result)
else await assert.rejects(outcome, error => error === original)
assert.deepEqual(events, expected, scenario)
if (request) assert.equal(capabilities.has(request), false)
}
})
test("configuration equality or generic VERIFIED cannot replace reserved signed receipts", async () => {
const expected = {} as Parameters<typeof validateReservedInverseVerified>[1]
for (const row of [{}, { state: "verified", phase: "RECOVERY_VERIFIED", configurationVerified: true }, { inverse_intent_sealed: "x".repeat(524289) }]) {
@@ -1,6 +1,10 @@
import { createRoutingReservationStore } from "./routing-reservation-store.js"
import { observeCommittedScopedCheckpoint } from "../../test/committed-scoped-checkpoint-observer.js"
import { prepareRoutingControllerBaseline } from "./routing-controller-baseline.js"
import { prepareRoutingControllerBaseline, advanceRoutingControllerBaseline } from "./routing-controller-baseline.js"
import { loadScopedConfigurationPacket, loadScopedNetworkCheckpoint } from "./routing-community-scoped-checkpoint.js"
import { assertScopedCommunityRequest } from "./routing-community-scoped-request.js"
import { originalEffectReportSchema, originalEffectSelectionSchema } from "./routing-original-effects.js"
import { tickRoutingProfiles } from "./routing-profiles.js"
import { saveControllerProfileCas } from "./routing-controller-fence.js"
import { withApplicationMutationLock, withConfigMutationLock } from "../../services/config-mutation-lock.js"
import { performance } from "node:perf_hooks"
@@ -31,8 +35,9 @@ import { registerManagementChannel } from "./management.js"
import { isSchedulerJobsPaused, pauseSchedulerJobsAndWait, resumeSchedulerJobs } from "../../services/scheduler-running.js"
/** Disposable SQL + loopback HTTP only. Does not access or configure CHR. */
export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options: { originalCredentialRevocationOnly?: boolean; forwardRoundsOnly?: boolean; manualInverseChargeOnly?: boolean; manualInverseChargeCase?: "positive" | "quota" | "concurrent" | "unknown" | "revoked" | "nonpersistent" } = {}) {
const sid = 131, writer: AuthUser = { ...actor, permissions: [...new Set([...actor.permissions, "mm:filters:write"])] }
export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options: { originalEffectsOnly?: boolean; originalCredentialRevocationOnly?: boolean; forwardRoundsOnly?: boolean; manualInverseChargeOnly?: boolean; manualInverseChargeCase?: "positive" | "quota" | "concurrent" | "unknown" | "revoked" | "nonpersistent" | "mixed-forward"; lateInverseAuthorityChange?: "edit" | "disable" | "grant" } = {}) {
const sid = options.lateInverseAuthorityChange ? { edit: 131, disable: 132, grant: 133 }[options.lateInverseAuthorityChange] : 131
const writer: AuthUser = { ...actor, permissions: [...new Set([...actor.permissions, "mm:filters:write"])] }
const initial = compileManagedFilterRules([{ community: "65000:10", gateway: "192.0.2.1", outIface: "", action: "route", description: "" }, { community: "65000:20", gateway: "192.0.2.5", outIface: "", action: "route", description: "" }])
const filters = ["a", "b"].map(key => ({ ".id": "*f" + key, chain: "custom-" + key, rule: initial, disabled: "false", comment: managedComment("NEVER_SCOPED_COMMENT_" + key), opaque: "NEVER_SCOPED_OPAQUE" }))
let mode: "normal" | "ignored" | "lost" | "collateral" | "post-failed" | "post-unknown" = "normal", writes = 0, requests = 0
@@ -42,6 +47,22 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
let forwardRoundBulk = 0, afterRoundLogRead: (() => Promise<void>) | undefined
let primaryDown = false, primaryControlRequests = 0, pendingInversePost = false, inversePostChecks = 0
const observedVrfs = new Set<string>()
// Only this separately selected SQL/localhost phase changes its wire data.
// These are declared synthetic RouterOS responses, never actual CHR facts.
let originalEffectCase: "transition" | "no-op" | "ambiguous" = "transition"
const originalEffectWire: Array<{ method: string; path: string; afterPatch: boolean }> = []
const originalEffectChanged = () => originalEffectCase === "no-op" || filters[0].rule !== initial
const effectRoutes = () => {
const changed = originalEffectChanged()
const row = { ".id": "*effect1", afi: "ip4", "routing-table": "tenant-a", "dst-address": "203.0.113.0/24",
bgp: "true", connect: "false", static: "false", active: "true", contribution: "active", ecmp: "false",
disabled: "false", filtered: "false", unreachable: "false", blackhole: "false", "bgp.communities": "65000:10",
gateway: changed ? "192.0.2.9" : "192.0.2.1", "immediate-gw": (changed ? "203.0.113.9" : "203.0.113.1") + "%wan-a" }
return originalEffectCase === "ambiguous" ? [row, { ...row, ".id": "*effect2" }] : [row]
}
const effectConnection = { ".id": "*ca", name: "peer-a", instance: "default", vrf: "main", "routing-table": "tenant-a", afi: "ip",
"input.filter": "custom-a", "output.filter-chain": "", templates: "", disabled: "false", "use-bfd": "false",
"local.address": "198.18.0.1", "remote.address": "198.18.0.2", "remote.as": "65001" }
const collections = (path: string): Record<string, unknown>[] => ({
"/system/resource": [{ version: "7.20.1" }],
...Object.fromEntries(["/routing/route", "/routing/bgp/session", "/routing/bfd/session", "/log"].map(resource => [resource,
@@ -54,12 +75,25 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
"/ip/route": [1, 9].map(value => ({ ".id": "*r" + value, "dst-address": "192.0.2." + value + "/32", "routing-table": "main", disabled: "false", comment: managedComment("recursive"), opaque: "NEVER_SCOPED_ROUTE" })),
"/interface": ["wan-a", "wan-b", "hop", "target"].map((name, index) => ({ ".id": "*if" + index, name, disabled: "false", running: "true" })),
"/ip/address": ["wan-a", "wan-b", "hop", "target"].map((name, index) => ({ ".id": "*ad" + index, interface: name, address: "198.51.100." + (index + 1) + "/32", disabled: "false", invalid: "false" })),
...(options.originalEffectsOnly ? {
"/routing/table": ["main", "tenant-a", "tenant-b", "probe-a", "probe-b"].map((name, index) => ({ ".id": "*t" + index, name, fib: "", disabled: "false", invalid: "false" })),
"/routing/route": effectRoutes(), "/routing/bgp/connection": [effectConnection],
"/routing/bgp/session": [{ ...effectConnection, ".id": "*sa", "local.afi": "ip", "remote.afi": "ip", established: "true" }],
"/routing/bfd/session": [], "/log": [{ ".id": "*l1", topics: "bgp,route,info", message: "Declared synthetic localhost effect observation; interval coverage unknown" }],
"/ip/route": [1, 9].map(value => ({ ".id": "*r" + value, "dst-address": "192.0.2." + value + "/32", "routing-table": "main", disabled: "false",
"immediate-gw": "203.0.113." + value + "%wan-a", comment: managedComment("recursive"), opaque: "NEVER_SCOPED_ROUTE" })),
} : {}),
} as Record<string, Record<string, unknown>[]>)[path] ?? []
let requestFailure: { error: unknown } | undefined
const handler: RequestListener = async (req, res) => {
requests++; res.setHeader("content-type", "application/json")
if (options.originalEffectsOnly) {
assert.equal(req.socket.localAddress, "127.0.0.1")
assert.equal(req.headers.authorization, "Basic " + Buffer.from("admin:NEVER_SCOPED_PASSWORD").toString("base64"))
}
let raw = ""; for await (const chunk of req) raw += chunk
const args = raw ? JSON.parse(raw) : {}, path = decodeURIComponent((req.url ?? "").replace(/^\/rest/, ""))
if (options.originalEffectsOnly) originalEffectWire.push({ method: req.method ?? "", path, afterPatch: filters[0].rule !== initial })
if (req.socket.localAddress === "127.0.0.1") {
if (/^\/(routing|tool)\//.test(path) || path === "/system/license") primaryControlRequests++
if (primaryDown) { req.socket.destroy(); return }
@@ -155,7 +189,7 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
}
await pool.query("INSERT INTO filter_rules(server_id,sort_order,community,community_name,action,gateway,gateway_tunnel_id,description) VALUES($1,77,'65000:10','NEVER_SCOPED_SQL_NAME','route','203.0.113.99','NEVER_SCOPED_SQL_TUNNEL','NEVER_SCOPED_SQL_DESCRIPTION')", [sid])
const dbBefore = (await pool.query("SELECT * FROM filter_rules WHERE server_id=$1 ORDER BY id", [sid])).rows
const prepare = async (key = "a", persistent?: { id: string; version: number }, exactProfile?: ReturnType<typeof profile>) => {
const prepare = async (key = "a", persistent?: { id: string; version: number }, exactProfile?: ReturnType<typeof profile>, assertApprovalRequired = false) => {
const current = exactProfile === undefined ? profile(key) : routeAiProfileSchema.parse(exactProfile)
assert.equal((await invokeMcpTool(principal, "mm_route_path_verify", { profile: current, candidateId: "b" }) as { status: string }).status, "passed")
const plan = await invokeMcpTool(principal, "mm_route_optimizer_plan", { profile: current, candidateId: "b" }, undefined,
@@ -171,6 +205,11 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
assert(!JSON.stringify(projection).includes("persistentProfileAuthority"), "Persistent authority remains private")
}
assert.equal(evidence.requiresHuman, true); assert.equal(evidence.mutationCost, 2); assert.ok(!JSON.stringify(input).includes("NEVER_")); assert.equal(input.compiledBody, undefined)
if (assertApprovalRequired) {
const beforeApproval = { writes, requests }
await assert.rejects(applyPlan(principal, plan.planId, randomUUID()), (error: unknown) => error instanceof McpError && error.code === "APPROVAL_REQUIRED")
assert.deepEqual({ writes, requests }, beforeApproval)
}
await approvePlan(plan.planId, writer, true); return { plan, input }
}
const execute = async (planId: string, executingPrincipal: McpPrincipal = principal) => {
@@ -203,6 +242,139 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
return { id, version: 1 }
}
const disablePersistent = (id: string) => pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [id])
if (options.originalEffectsOnly) {
const cases: Array<Record<string, unknown>> = []
let publicManualRevision2: Record<string, unknown> | undefined
for (const scenario of ["transition", "no-op", "ambiguous", "public-manual-rev2"] as const) {
const manual = scenario === "public-manual-rev2"
const effectCase = manual ? "transition" : scenario
reset(); await refresh(); originalEffectCase = effectCase
const config = profile("a")
for (const candidate of config.candidates) candidate.controlProbe!.sourceAddress = "198.51.100.1"
assert.equal(config.autoRecovery, false)
// The baseline and persistent authority are issued by the SAME actual
// fixed readers/CAS used by existing tests, not hand-written proofs.
const persistent = manual ? undefined : await persistentFixture(config)
try {
const { plan, input } = await prepare("a", persistent, config, manual)
assert.equal(input.manualEffectPolicy, manual ? "observational-v1" : undefined)
if (manual) assert.equal(input.persistentProfileAuthority, undefined)
const sealedSelection = originalEffectSelectionSchema.parse(input.originalEffectSelection)
assert.equal(sealedSelection.status, effectCase === "ambiguous" ? "unknown" : "prepared")
const storedPlan = await loadPlan(plan.planId)
assert.equal(storedPlan.approved_by, writer.id)
const publicProjection = JSON.stringify(await publicPlan(storedPlan))
assert(!publicProjection.includes("originalEffectSelection"))
assert(!publicProjection.includes("manualEffectPolicy"))
const beforeWrites = writes, wireStart = originalEffectWire.length
const operation = await execute(plan.planId)
assert.equal(operation.state, "applied", "Optional effect unknown must preserve genuine manual configuration/probe acceptance")
assert.equal(writes - beforeWrites, 1)
const rows: Array<{ created_at: Date; state: string; operation_id: string; operation_version: number; step_state: string;
result: { configurationVerified: boolean; controlProbeVerified: boolean; originalEffects: unknown } }> = (await pool.query(`SELECT o.created_at,o.state,p.operation_id,p.operation_version,s.state AS step_state,s.result
FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id JOIN mcp_operation_steps s ON s.operation_id=o.id
WHERE o.id=$1 AND o.connection_id=$2 AND o.plan_id=$3 AND s.step_index=0`, [operation.id, connectionId, plan.planId])).rows
assert.equal(rows.length, 1); assert.equal(rows[0].operation_id, "routing.community.switch")
assert.equal(rows[0].state, "applied"); assert.equal(rows[0].step_state, "verified")
assert.equal(rows[0].result.configurationVerified, true); assert.equal(rows[0].result.controlProbeVerified, true)
const report = originalEffectReportSchema.parse(rows[0].result.originalEffects)
const requestSeal = await assertScopedCommunityRequest(input)
if (manual) {
const changedInput = { ...input, manualEffectPolicy: "unapproved-policy" }
await assert.rejects(assertScopedCommunityRequest(changedInput), (error: unknown) => error instanceof McpError && error.code === "STATE_CONFLICT")
}
const configuration = await loadScopedConfigurationPacket(input, requestSeal, { operationId: operation.id, stepIndex: 0 })
assert(configuration); assert.equal(configuration.packet.operationId, operation.id)
assert.equal(configuration.packet.revision, manual ? 1 : 2); assert.equal(configuration.packet.phase, "CONFIG_APPLIED")
const network = await loadScopedNetworkCheckpoint(input, requestSeal)
assert(network); assert.equal(network.status, "passed"); assert.deepEqual(network.originalEffects, report)
const runningWire = originalEffectWire.slice(wireStart)
const ribBefore = runningWire.filter(row => row.method === "GET" && row.path === "/routing/route" && !row.afterPatch).length
const ribAfter = runningWire.filter(row => row.method === "GET" && row.path === "/routing/route" && row.afterPatch).length
console.log("Owned original effect diagnostic:", JSON.stringify({ case: scenario, state: operation.state,
status: report.status, reason: report.reason, rounds: report.rounds.length, budget: report.budget ?? null,
ribBefore, ribAfter, runtimeNativeRequests: runningWire.length, lastNativePaths: runningWire.slice(-8).map(row => row.path) }))
assert.equal(ribBefore, effectCase === "ambiguous" ? 0 : 1)
assert.equal(ribAfter, effectCase === "ambiguous" ? 0 : 2)
assert.equal(report.status, effectCase === "transition" ? "observed-expected" : "unknown")
assert.equal(report.switchEffectObserved, effectCase === "transition")
assert.equal(report.logCoverage, "unknown"); assert.equal(report.routeOriginVerified, false)
assert.equal(report.forwardingVerified, false); assert.equal(report.semanticProof, false); assert.equal(report.autoRecoveryAllowed, false)
if (sealedSelection.status === "prepared") {
assert.equal(report.selectionIdentity, fingerprint(sealedSelection)); assert.equal(report.rounds.length, 2)
assert(report.budget && report.budget.calls > 18 && report.budget.calls <= 256)
assert(report.budget.bytes > 0 && report.budget.bytes <= 8 * 1048576)
assert(report.budget.rows > 0 && report.budget.rows <= 10000)
assert(report.budget.deadline <= rows[0].created_at.getTime() + 105000, "Original120s horizon is narrowed by15s publication reserve")
assert(report.rounds.every(round => Date.parse(round.observedAt) < report.budget!.deadline && round.controlProbe === "passed" && round.bgp === "observed-expected" && round.bfd === "not-configured"))
assert.deepEqual(report.transition?.changedPrefixes, effectCase === "transition" ? ["203.0.113.0/24"] : [])
assert.equal(report.transitionAttribution, "unproven")
}
if (manual) {
const repeatReads = requests
const repeated = await applyPlan(principal, plan.planId, randomUUID())
assert.equal(repeated.id, operation.id)
assert.equal(repeated.state, "applied")
assert.equal(requests, repeatReads)
assert.equal(writes - beforeWrites, 1)
assert.equal(Number((await pool.query("SELECT count(*) AS n FROM mcp_routing_profiles WHERE enabled")).rows[0].n), 0)
assert.equal(Number((await pool.query("SELECT count(*) AS n FROM mcp_scoped_recovery_intents WHERE source_operation_id=$1", [operation.id])).rows[0].n), 0)
publicManualRevision2 = { accepted: true, humanApprovalRequired: true, proofRevision: input.proofRevision,
configurationCheckpointRevision: configuration.packet.revision, effectStatus: report.status,
rounds: report.rounds.length, nativeRibBefore: ribBefore, nativeRibAfter: ribAfter, sharedBudget: report.budget,
autoRecoveryAllowed: report.autoRecoveryAllowed, scopedControllerEnabled: false, nativeWrites: 1 }
continue
}
assert(persistent)
const row = (await pool.query("SELECT state,grant_expires_at FROM mcp_routing_profiles WHERE id=$1 AND version=1", [persistent.id])).rows[0]
assert(row?.state.baseline)
const advance = () => advanceRoutingControllerBaseline(row.state.baseline, persistent.id, 1, config, row.grant_expires_at.toISOString(),
{ principal, connection: principalConnection }, plan.planId, operation.id)
const principalConnection = await effectiveMcpConnection(principal), beforeAdvanceReads = requests
if (effectCase === "transition") {
const advanced = await advance(); assert.equal(advanced.packet.candidateId, "b")
assert.equal(advanced.packet.transition?.operationId, operation.id); assert.equal(advanced.packet.transition?.checkpointProof, configuration.checkpointProof)
} else {
await assert.rejects(advance, (error: unknown) => error instanceof McpError && error.code === "STATE_CONFLICT")
assert.equal(requests, beforeAdvanceReads, "Unknown/no-transition effect gate refuses before another native context read")
}
// Check actual HMAC loading, not only a caller-visible result object.
if (effectCase === "transition") {
const audit: Array<{ id: string; payload: Record<string, unknown> }> = (await pool.query("SELECT id,payload FROM mcp_audit WHERE connection_id=$1 AND action='routing.scoped.network-check' AND payload->>'receiptId'=$2", [connectionId, input.receiptId])).rows
assert.equal(audit.length, 1)
try {
await pool.query("UPDATE mcp_audit SET payload=jsonb_set(payload,'{originalEffects,switchEffectObserved}','false'::jsonb) WHERE id=$1", [audit[0].id])
assert.equal(await loadScopedNetworkCheckpoint(input, requestSeal), null, "Modified effect vector cannot reuse its original network HMAC")
} finally { await pool.query("UPDATE mcp_audit SET payload=$2::jsonb WHERE id=$1", [audit[0].id, JSON.stringify(audit[0].payload)]) }
assert.deepEqual((await loadScopedNetworkCheckpoint(input, requestSeal))?.originalEffects, report)
}
const repeatReads = requests
const repeated = await applyPlan(principal, plan.planId, randomUUID())
assert.equal(repeated.id, operation.id, "Completed plan returns its original operation without replay")
assert.equal(repeated.state, "applied")
assert.equal(Number((await pool.query("SELECT count(*) AS n FROM mcp_operations WHERE plan_id=$1", [plan.planId])).rows[0].n), 1)
assert.equal(writes - beforeWrites, 1); assert.equal(requests, repeatReads, "Completed approved plan cannot repeat native work")
// Preserve the existing scoped-controller activation guard. A real
// tick pauses HERE before effect dispatch; never call this an
// effect-triggered scheduler pause or enable that unsupported lane.
await tickRoutingProfiles()
const paused = (await pool.query("SELECT enabled,state FROM mcp_routing_profiles WHERE id=$1", [persistent.id])).rows[0]
assert.equal(paused.enabled, false); assert.equal(typeof paused.state.pausedReason, "string")
assert.deepEqual(paused.state.baseline, row.state.baseline)
cases.push({ case: effectCase, manualState: operation.state, configurationCheckpoint: true, actualPrivateExecutionOrigin: true,
effectStatus: report.status, observedFibTransition: report.switchEffectObserved, nativeRibBefore: ribBefore, nativeRibAfter: ribAfter,
sharedBudget: report.budget ?? null, effectBaselineAdvanceAccepted: effectCase === "transition", effectGateNativeReads: effectCase === "transition" ? "fresh-native-checked" : 0,
actualTickPausedByExistingScopedGuard: true, tickEffectPauseAccepted: false, publicManualRevision2EffectOwnerAccepted: false })
} finally { if (persistent) await disablePersistent(persistent.id) }
}
assert(publicManualRevision2)
assert.deepEqual((await pool.query("SELECT * FROM filter_rules WHERE server_id=$1 ORDER BY id", [sid])).rows, dbBefore)
const result = { accepted: true, cases, publicManualRevision2, persistentAuthorityCases: 0, realMcpDispatcher: true, realSql: true, routerOsTransport: "declared-synthetic-localhost-http",
genuineApprovedRunningOrigin: true, actualConfigurationCheckpoint: true, signedEffectTamperRejected: true,
originalAutoRecoveryEnabled: false, fullRoutingAccepted: false, scopedControllerEnabled: false, nativeChrAccepted: false, forwardingVerified: false }
console.log("Original effect SQL/runtime component:", JSON.stringify(result))
return result
}
const assertOriginalCredentialRevocation = async () => {
console.log(JSON.stringify({stage:'original-credential-revocation:init'}))
reset(); await refresh()
@@ -252,14 +424,16 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
await assertOriginalCredentialRevocation()
return {accepted:true,originalCredentialRevocation:true,unauthorized401:true,fullAdmissionRollback:true,nativeReads:0,nativeWrites:0,realSql:true,allWriterLedgerComplete:false}
}
if (options.manualInverseChargeOnly) {
if (options.manualInverseChargeOnly || options.lateInverseAuthorityChange) {
const chargeCase = options.manualInverseChargeCase ?? "positive"
if (options.lateInverseAuthorityChange) assert.equal(chargeCase, "positive")
const persistent = chargeCase !== "nonpersistent"
// Same genuine fixed-reader baseline/CAS used by the existing persistent
// acceptance below. No hand-written baseline, sealed plan or authority.
const id = randomUUID(), current = await effectiveMcpConnection(principal), config = profile("a")
assert.equal(config.autoRecovery, false)
if (chargeCase === "quota") config.maxSwitchesPerHour = 1
if (chargeCase === "mixed-forward") config.maxSwitchesPerHour = 2
const expiry = new Date(Math.min(Date.now() + 600000, current.owner_valid_until.getTime(), Date.parse(principal.credentialExpiresAt), Date.parse(current.policy.expiresAt)))
await withApplicationMutationLock(() => withConfigMutationLock([sid], async () => {
const baseline = await prepareRoutingControllerBaseline(id, 1, config, expiry.toISOString(), { principal, connection: current })
@@ -292,6 +466,10 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
}
console.log(JSON.stringify({stage:"manual-inverse:forward-prepare",chargeCase}))
const { plan, input } = await prepare("a", persistent ? { id, version: 1 } : undefined, config)
// Prepare the competing forward while the ORIGINAL native baseline is
// current. Admission must charge it even if execution later refuses the
// changed network state. No invented request/authority or reserved lane.
const competingForward = chargeCase === "mixed-forward" ? await prepare("a", { id, version: 1 }, config) : undefined
// Existing known partial configuration path: genuine committed CONFIG_APPLIED,
// then failed control probe, then genuine manual inverse positive to A.
mode = "post-failed"
@@ -348,7 +526,97 @@ export async function acceptScopedRouteCommunitySwitch(actor: AuthUser, options:
await assert.rejects(applyPlan(principal, recovery.planId, randomUUID()))
assert.deepEqual(await snapshot(), afterForward, "No approval means no debit/charge/native write")
await approvePlan(recovery.planId, writer, true)
if (chargeCase === "mixed-forward") {
assert(competingForward)
const attempts = [
{ lane: "forward", planId: competingForward.plan.planId, key: randomUUID(), cost: 2 },
{ lane: "manual-inverse", planId: recovery.planId, key: randomUUID(), cost: 1 },
] as const
const initialPlans = await Promise.all(attempts.map(attempt => loadPlan(attempt.planId)))
assert(initialPlans.every(value => value.state === "approved"))
const admitted = await Promise.allSettled(attempts.map(attempt => applyPlan(principal, attempt.planId, attempt.key)))
const winners = admitted.flatMap((value, index) => value.status === "fulfilled" ? [index] : [])
assert.equal(winners.length, 1, "Two writer lanes must compete for the same last profile slot")
const winnerIndex = winners[0], loserIndex = 1 - winnerIndex
const winner = admitted[winnerIndex], loser = admitted[loserIndex]
assert(winner.status === "fulfilled" && loser.status === "rejected")
assert(loser.reason instanceof McpError && loser.reason.code === "STATE_CONFLICT", "The losing lane must fail profile admission")
assert.equal(fingerprint(await loadPlan(attempts[loserIndex].planId)), fingerprint(initialPlans[loserIndex]), "Losing plan is completely unchanged")
assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_operations WHERE plan_id=$1", [attempts[loserIndex].planId])).rows[0].n), 0)
assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_routing_forward_charges WHERE source_plan_id=$1", [attempts[loserIndex].planId])).rows[0].n), 0)
assert.equal(Number((await pool.query("SELECT count(*)::int AS n FROM mcp_routing_manual_inverse_charges WHERE recovery_plan_id=$1", [attempts[loserIndex].planId])).rows[0].n), 0)
const deadline = Date.now() + 140000
let terminalState = "pending"
while (["pending", "running"].includes(terminalState) && Date.now() < deadline) {
terminalState = (await pool.query("SELECT state FROM mcp_operations WHERE id=$1", [winner.value.id])).rows[0].state
if (["pending", "running"].includes(terminalState)) await delay(20)
}
assert(!["pending", "running"].includes(terminalState), "Winner must retire before fixture cleanup")
if (requestFailure) throw requestFailure.error
snapshotStage = "mixed-writer-terminal"
const afterMixed = await snapshot()
assert.equal(afterMixed.total, 2)
assert.equal(afterMixed.forward, winnerIndex === 0 ? 2 : 1)
assert.equal(afterMixed.manualInverse, winnerIndex === 1 ? 1 : 0)
assert.equal(afterMixed.reserved, 0); assert.equal(afterMixed.legacy, 0)
assert.equal(afterMixed.generic, afterForward.generic + attempts[winnerIndex].cost, "Losing writer cannot debit generic quota")
if (winnerIndex === 1) {
const charges = (await pool.query("SELECT source_operation_id,source_step_index,recovery_operation_id FROM mcp_routing_manual_inverse_charges WHERE recovery_operation_id=$1", [winner.value.id])).rows
assert.equal(charges.length, 1); assert.equal(charges[0].source_operation_id, source.id)
assert.equal(charges[0].source_step_index, 0); assert.equal(charges[0].recovery_operation_id, winner.value.id)
assert.equal(terminalState, "applied"); assert.equal(afterMixed.writes, 2)
}
const beforeReplay = fingerprint(await snapshot())
assert.equal((await applyPlan(principal, attempts[winnerIndex].planId, attempts[winnerIndex].key)).id, winner.value.id)
await delay(30)
assert.equal(fingerprint(await snapshot()), beforeReplay, "Same-key observation cannot recharge or replay either lane")
return { accepted: true, mixedWriterAdmission: true, winnerLane: attempts[winnerIndex].lane, before, afterForward, afterMixed,
exactlyOneAdmission: true, losingPlanOperationChargeAndGenericRollback: true, sameKeyReplayUnchanged: true,
realSql: true, allWriterLedgerComplete: false, reservedDispatchEnabled: false, autoRecoveryEnabled: false }
}
console.log(JSON.stringify({ stage: "manual-inverse:human-recovery-apply" }))
const lateChange = options.lateInverseAuthorityChange
if (lateChange) {
const priorIntent = (await pool.query("SELECT coalesce(max(id),0)::text AS id FROM mcp_audit WHERE connection_id=$1 AND action='configuration.intent'", [connectionId])).rows[0].id
let writeId: string | undefined
afterIntent = async () => {
const intents = (await pool.query(`SELECT payload FROM mcp_audit WHERE connection_id=$1 AND id>$2::bigint
AND action='configuration.intent' AND payload->>'path'=$3`,
[connectionId, priorIntent, "/routing/filter/rule/" + loaded.configuration.packet.nativeId])).rows
assert.equal(intents.length, 1)
assert.equal(intents[0].payload.method, "PATCH")
assert.equal(intents[0].payload.serverId, sid)
writeId = intents[0].payload.writeId
assert.match(writeId ?? "", /^[0-9a-f-]{36}$/)
const sql = {
edit: "UPDATE mcp_routing_profiles SET version=version+1,config=jsonb_set(config,'{name}','\"late inverse edit\"') WHERE id=$1",
disable: "UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1",
grant: "UPDATE mcp_routing_profiles SET grant_expires_at=clock_timestamp()-interval '1 second' WHERE id=$1",
}[lateChange]
assert.equal((await pool.query(sql, [id])).rowCount, 1)
}
const inverse = await execute(recovery.planId)
assert.equal(afterIntent, undefined)
assert.ok(writeId)
assert.notEqual(inverse.state, "applied")
assert.equal(writes, afterForward.writes)
assert.equal(inversePostChecks, 0)
const auditRows = (await pool.query(`SELECT action FROM mcp_audit WHERE connection_id=$1 AND payload->>'writeId'=$2
ORDER BY id`, [connectionId, writeId])).rows
assert.deepEqual(auditRows.map(row => row.action), ["configuration.intent", "configuration.unconfirmed"])
const refusal = (await pool.query(`SELECT payload->>'phase' AS phase, payload->>'code' AS code
FROM mcp_audit WHERE connection_id=$1 AND action='routing.scoped.recovery-unconfirmed'`, [connectionId])).rows
assert.deepEqual(refusal, [{ phase: "before-dispatch-context", code: "STATE_CONFLICT" }])
assert.deepEqual((await pool.query(`SELECT state,verified_sealed FROM mcp_scoped_recovery_intents
WHERE source_operation_id=$1 AND recovery_operation_id=$2`, [source.id, inverse.id])).rows,
[{ state: "unknown", verified_sealed: null }])
assert.equal((await pool.query(`SELECT count(*)::int AS n FROM mcp_operation_checkpoints
WHERE operation_id=$1 AND configuration_sealed IS NOT NULL`, [inverse.id])).rows[0].n, 0)
assert.equal((await pool.query("SELECT error_code FROM mcp_operations WHERE id=$1", [inverse.id])).rows[0].error_code,
"MUTATION_OUTCOME_UNKNOWN")
return { accepted: true, lateInverseAuthorityChange: lateChange, durableIntents: 1, inverseNativeWrites: 0,
realSql: true, realMcpDispatcher: true, fullInverseLifecycleRepeated: false }
}
if (chargeCase === "quota" || chargeCase === "revoked") {
if (chargeCase === "revoked") await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [id])
const beforeOperations = (await pool.query("SELECT count(*)::int AS n FROM mcp_operations WHERE connection_id=$1", [connectionId])).rows[0].n
@@ -1,3 +1,4 @@
import { originalEffectReportSchema, type OriginalEffectReport } from "./routing-original-effects.js"
import { z } from "zod"
import type { PoolClient } from "pg"
import { pool } from "../../db/index.js"
@@ -19,7 +20,7 @@ const checkpointSchema = z.union([manualCheckpointSchema, manualCheckpointSchema
export type ScopedConfigurationPacket = z.infer<typeof checkpointSchema>
type Checkpoint = ScopedConfigurationPacket
const networkSchema = z.object({ receiptId: z.string().uuid(), requestSeal: proof, configurationCheckpointProof: proof,
check: z.object({ id: z.string().uuid(), candidateId: z.string(), status: z.enum(["passed", "failed"]), observedAt: z.string().datetime() }).strict(), networkSeal: proof }).strict()
check: z.object({ id: z.string().uuid(), candidateId: z.string(), status: z.enum(["passed", "failed"]), observedAt: z.string().datetime() }).strict(), originalEffects: originalEffectReportSchema.optional(), networkSeal: proof }).strict()
const intended = (input: Record<string, unknown>) => JSON.stringify(redact({ kind: "service", service: "routing.community.scoped", input }))
const connection = (input: Record<string, unknown>) => z.object({ connectionId: z.string().uuid() }).parse(input.principal).connectionId
const seal = (value: Checkpoint) => privateSecretFingerprint(fingerprint(value), `route-ai-scoped-config-checkpoint-v${value.revision}`)
@@ -72,12 +73,13 @@ export async function loadScopedConfigurationCheckpoint(input: Record<string, un
return value ? { checkpointProof: value.checkpointProof, observedAt: value.observedAt } : null
}
export async function saveScopedNetworkCheckpoint(input: Record<string, unknown>, requestSeal: string, check: { id: string; candidateId: string; status: "passed" | "failed"; observedAt: string }) {
export async function saveScopedNetworkCheckpoint(input: Record<string, unknown>, requestSeal: string, check: { id: string; candidateId: string; status: "passed" | "failed"; observedAt: string }, originalEffects?: OriginalEffectReport) {
await assertPersistentScopedProfileAuthority(input)
const configuration = await loadScopedConfigurationCheckpoint(input, requestSeal)
if (!configuration) throw new McpError("STATE_CONFLICT", "Нет подтверждённого CONFIG_APPLIED", 409)
const packet = { receiptId: input.receiptId, requestSeal, configurationCheckpointProof: configuration.checkpointProof,
check: { id: check.id, candidateId: check.candidateId, status: check.status, observedAt: check.observedAt } }
check: { id: check.id, candidateId: check.candidateId, status: check.status, observedAt: check.observedAt },
...(originalEffects === undefined ? {} : { originalEffects: originalEffectReportSchema.parse(originalEffects) }) }
const networkSeal = await privateSecretFingerprint(fingerprint(packet), "route-ai-scoped-network-checkpoint-v1")
await assertPersistentScopedProfileAuthority(input)
await audit(pool, String(input.ownerId), "routing.scoped.network-check", connection(input), { ...packet, networkSeal })
@@ -92,5 +94,5 @@ export async function loadScopedNetworkCheckpoint(input: Record<string, unknown>
if (!parsed.success) return null
const { networkSeal, ...packet } = parsed.data
if (packet.configurationCheckpointProof !== configuration.checkpointProof || packet.check.candidateId !== input.candidateId || Date.parse(packet.check.observedAt) < Date.parse(configuration.observedAt) || Date.parse(packet.check.observedAt) > Date.now() || networkSeal !== await privateSecretFingerprint(fingerprint(packet), "route-ai-scoped-network-checkpoint-v1")) return null
return { status: packet.check.status, networkSeal }
return { status: packet.check.status, networkSeal, ...(packet.originalEffects === undefined ? {} : { originalEffects: packet.originalEffects }) }
}
@@ -257,24 +257,30 @@ async function checkpoint(value: Session, intent: Intent, phase: Checkpoint["pha
export async function executeReservedScopedRecovery(allocation: RoutingReservedInverseAllocation, expected: { sourceOperationId: string; principal: McpPrincipal }) {
const store = createRoutingReservationStore(), spent = await store.beginInverseAttempt(allocation)
const execution = spent.execution
const binding = await store.inspectInverseExecution(execution)
if (binding.sourceOperationId !== expected.sourceOperationId || fingerprint(binding.principal) !== fingerprint(expected.principal)) throw fail("Reserved runner identity differs")
const current = await effectiveMcpConnection(binding.principal)
const context: ActionContext = { principal: binding.principal, connection: current }
const source = await loadReservedScopedRecoverySource(execution, context)
const channel: ManagementTransportBinding | undefined = source.input.managementBindingCiphertext == null ? undefined
: JSON.parse(await decryptSecret(String(source.input.managementBindingCiphertext)))
const input: Record<string, unknown> = { mode: "reserved", serverId: source.profile.serverId, sourceOperationId: binding.sourceOperationId,
sourceProof: source.sourceProof, configurationProof: binding.configurationProof, sourceReceiptId: binding.sourceReceiptId,
profile: source.profile, candidateId: source.configuration.packet.originalCandidateId, rules: source.input.beforeRules, beforeRules: source.input.rules,
principal: binding.principal, ownerId: current.owner_id, ownerProof: fingerprint(current.owner_permissions), policyProof: fingerprint(current.policy), connectionGeneration: current.generation,
generation: await accessGeneration(), receiptId: binding.inverseReceiptId, proofRevision: 1,
validUntil: new Date(Math.min(Date.parse(binding.deadline), deadline(source, context, channel?.expiresAt))).toISOString(), managementBindingCiphertext: source.input.managementBindingCiphertext ?? null }
input.requestProof = await sealedProof(input, "request")
reservedExecutions.set(input, execution)
try { return await executeScopedRecovery(input) }
catch (error) { await store.markUnknown(binding.reservationId, "inverse").catch(() => {}); throw error }
finally { reservedExecutions.delete(input) }
try {
const binding = await store.inspectInverseExecution(execution)
if (binding.sourceOperationId !== expected.sourceOperationId || fingerprint(binding.principal) !== fingerprint(expected.principal)) throw fail("Reserved runner identity differs")
const current = await effectiveMcpConnection(binding.principal)
const context: ActionContext = { principal: binding.principal, connection: current }
const source = await loadReservedScopedRecoverySource(execution, context)
const channel: ManagementTransportBinding | undefined = source.input.managementBindingCiphertext == null ? undefined
: JSON.parse(await decryptSecret(String(source.input.managementBindingCiphertext)))
const input: Record<string, unknown> = { mode: "reserved", serverId: source.profile.serverId, sourceOperationId: binding.sourceOperationId,
sourceProof: source.sourceProof, configurationProof: binding.configurationProof, sourceReceiptId: binding.sourceReceiptId,
profile: source.profile, candidateId: source.configuration.packet.originalCandidateId, rules: source.input.beforeRules, beforeRules: source.input.rules,
principal: binding.principal, ownerId: current.owner_id, ownerProof: fingerprint(current.owner_permissions), policyProof: fingerprint(current.policy), connectionGeneration: current.generation,
generation: await accessGeneration(), receiptId: binding.inverseReceiptId, proofRevision: 1,
validUntil: new Date(Math.min(Date.parse(binding.deadline), deadline(source, context, channel?.expiresAt))).toISOString(), managementBindingCiphertext: source.input.managementBindingCiphertext ?? null }
input.requestProof = await sealedProof(input, "request")
reservedExecutions.set(input, execution)
try { return await executeScopedRecovery(input) }
finally { reservedExecutions.delete(input) }
} catch (error) {
// Acknowledged spend owns this exact slot even when its authority can no
// longer be inspected. A refused or uncertain spend never enters here.
await store.markUnknown(spent.reservationId, "inverse").catch(() => {})
throw error
}
}
/** Fixed-domain receipt verifier used by the reservation store. It recognizes
@@ -1,3 +1,7 @@
import { createRoutingNetworkSessionForCurrentExecution } from "./routing-network-execution-session.js"
import { readRoutingNetworkScopedCommunityContext } from "./routing-network-scoped-context.js"
import { resolveRoutingNetworkForwardRoundConfigurationContext, closeRoutingNetworkForwardRoundConfigurationContext } from "./routing-network-configuration-bridge.js"
import { originalEffectSelectionSchema, unknownOriginalEffects, originalEffectBaselineMatches, inspectOriginalEffectRounds, type OriginalEffectReport } from "./routing-original-effects.js"
import { randomUUID } from "node:crypto"
import { z } from "zod"
import { routeAiProfileSchema } from "@mmapp/contracts/mcp-routing"
@@ -25,6 +29,11 @@ import { createRoutingReservationStore } from "./routing-reservation-store.js"
const principalSchema = z.object({ connectionId: z.string().uuid(), credentialId: z.string().uuid(), credentialExpiresAt: z.string() }).strict()
const fail = (message: string, code = "STATE_CONFLICT") => new McpError(code, message, 409)
const effectReadFailure = (stage: "original-effect-rounds-unavailable" | "effect-publication-revalidation-unavailable", error: unknown) => {
const code = error instanceof McpError && ["READ_CANCELLED", "PLAN_EXPIRED", "LIMIT_EXCEEDED", "STATE_CONFLICT", "RESET_REQUIRED", "STATE_UNAVAILABLE"].includes(error.code)
? error.code : "STATE_UNAVAILABLE"
return unknownOriginalEffects(stage + ":" + code)
}
export const scopedProfileIds = (profile: z.infer<typeof routeAiProfileSchema>) => [...new Set([profile.serverId, ...profile.candidates.flatMap(candidate => [candidate.jumpHostId, candidate.exitNodeId])])].sort((a, b) => a - b)
const publicRule = (rule: { community: string; gateway: string; outIface: string }) => ({ community: rule.community, action: "route", gateway: rule.gateway, gatewayTunnelId: rule.outIface })
type ObservationPhase = "admission" | "configuration"
@@ -80,7 +89,7 @@ async function receipt(input: Record<string, unknown>, action: string) {
await assertPersistentScopedProfileAuthority(input)
await audit(pool, String(input.ownerId), action, principalSchema.parse(input.principal).connectionId, { receiptId: input.receiptId, requestSeal, revision: input.proofRevision })
}
const matches = (input: Record<string, unknown>, context: Awaited<ReturnType<typeof readRouteCommunityContext>>) => scopedConfigurationMatches({ dependencyProof: input.dependencyProof, originalTargetProof: input.originalTargetProof, desiredTargetProof: input.desiredTargetProof, candidateId: input.candidateId }, context.before)
const matches = (input: Record<string, unknown>, context: { before: Parameters<typeof scopedConfigurationMatches>[1] }) => scopedConfigurationMatches({ dependencyProof: input.dependencyProof, originalTargetProof: input.originalTargetProof, desiredTargetProof: input.desiredTargetProof, candidateId: input.candidateId }, context.before)
async function original(input: Record<string, unknown>, verified: boolean) {
await sealedInput(input)
const value = await observe(input, undefined, verified)
@@ -128,7 +137,10 @@ export async function prepareScopedCommunitySwitch(input: Record<string, unknown
try { managementBindingCiphertext = await encryptSecret(JSON.stringify(await prepareManagementTransportBinding(profile.serverId, { authorize: value.authorize, signal: action.signal, deadline: Math.min(Date.now() + 120000, Date.parse(action.principal.credentialExpiresAt), action.connection.owner_valid_until.getTime(), Date.parse(action.connection.policy.expiresAt)) }))) }
catch (error) { if (!(error instanceof McpError) || error.code !== "APPROVAL_REQUIRED") throw error }
}
const sealed: Record<string, unknown> = { ...withPath, nativeId: owned.nativeId, outIface, contextSources: value.context.before.sourceProofs, dependencyProof: value.context.before.dependencyProof,
const originalEffectSelection = await value.context.captureOriginalEffectSelection(String(input.candidateId))
const sealed: Record<string, unknown> = { ...withPath, originalEffectSelection,
...(!persistent && !profile.autoRecovery && originalEffectSelection.status === "prepared" ? { manualEffectPolicy: "observational-v1" } : {}),
nativeId: owned.nativeId, outIface, contextSources: value.context.before.sourceProofs, dependencyProof: value.context.before.dependencyProof,
originalTargetProof: value.context.before.targetRuleProof, desiredTargetProof: owned.desiredTargetProof(compiled.compiledBody), receiptId: randomUUID(), proofRevision: persistent ? 3 : 2,
originalCandidateId: value.context.before.currentCandidateId, originalRuleCiphertext: await encryptSecret(String(owned.rawRow.rule)), desiredRuleCiphertext: await encryptSecret(compiled.compiledBody), managementBindingCiphertext,
rules: [publicRule(compiled.proposed)], beforeRules: [publicRule(compiled.before)] }
@@ -156,7 +168,26 @@ export function registerScopedCommunityService() {
if (await receiptExists(input, "routing.scoped.attempt")) throw fail("Повтор PATCH запрещён", "MUTATION_OUTCOME_UNKNOWN")
await receipt(input, "routing.scoped.attempt")
const before = await original(input, true), owned = before.context.ownedTarget!
let effectSession: Awaited<ReturnType<typeof createRoutingNetworkSessionForCurrentExecution>> | undefined
let effectBaseline: Parameters<typeof originalEffectBaselineMatches>[1] | undefined
let originalEffects: OriginalEffectReport | undefined
const effectSelection = originalEffectSelectionSchema.safeParse(input.originalEffectSelection)
if (effectSelection.success) originalEffects = unknownOriginalEffects(effectSelection.data.status === "unknown" ? effectSelection.data.reason : "effect-acquisition-unavailable")
try {
// Optional original-effect vector is never an extra write/recovery grant.
// Old manual configuration+probe acceptance remains authoritative.
if (context?.phase === "forward" && (input.proofRevision === 3 || input.proofRevision === 2 && input.manualEffectPolicy === "observational-v1") && effectSelection.success && effectSelection.data.status === "prepared") {
try {
const selected = effectSelection.data
effectSession = await createRoutingNetworkSessionForCurrentExecution({ servers: scopedProfileIds(before.profile),
probes: [{ key: selected.probe.key, serverId: selected.probe.serverId, destination: selected.probe.destination,
source: selected.probe.source, interface: selected.probe.interface, probeVrf: selected.probe.probeVrf }] })
effectBaseline = await effectSession.readSnapshot("before")
if (!originalEffectBaselineMatches(selected, effectBaseline)) {
effectSession.close(); effectSession = undefined; originalEffects = unknownOriginalEffects("prepared-effect-baseline-drift")
}
} catch { effectSession?.close(); effectSession = undefined }
}
await executeGuardedConfigurationWrite(before.profile.serverId, "PATCH", "/routing/filter/rule/" + owned.nativeId, async () => {
// This runs AFTER the durable configuration.intent, immediately before transport.
const fresh = await original(input, true); await before.context.fence()
@@ -170,13 +201,47 @@ export function registerScopedCommunityService() {
await saveScopedConfigurationCheckpoint(input, await sealedInput(input))
const check = await checkCandidatePath(after.profile, after.candidate.id, principalSchema.parse(input.principal).connectionId, { authorize: after.authorize })
if (check.status === "unknown") throw fail("Control probe неизвестен", "MUTATION_OUTCOME_UNKNOWN")
// A probe can be long: prove the whole configuration again before a durable VERIFIED receipt.
const final = await observe(input, undefined, true, undefined, "configuration")
if (!matches(input, final.context).configurationMatchesDesired) throw fail("Состояние изменилось после probe")
await final.context.fence()
await saveScopedNetworkCheckpoint(input, await sealedInput(input), { ...check, status: check.status })
if (effectSession && effectBaseline && effectSelection.success && effectSelection.data.status === "prepared") {
let roundContext: Awaited<ReturnType<typeof resolveRoutingNetworkForwardRoundConfigurationContext>> | undefined
try {
const principal = principalSchema.parse(input.principal), action = { principal, connection: await effectiveMcpConnection(principal) }
roundContext = await resolveRoutingNetworkForwardRoundConfigurationContext(effectSession.sharedContext, action)
const first = await effectSession.readConfigurationBoundForwardSnapshot(roundContext)
const second = await effectSession.readConfigurationBoundForwardSnapshot(roundContext)
const observed = await inspectOriginalEffectRounds(effectSelection.data, [first, second], effectBaseline), budget = effectSession.readBudget()
originalEffects = { ...observed, budget: { deadline: budget.deadline, calls: budget.calls, bytes: budget.bytes, rows: budget.rows } }
} catch (error) { originalEffects = effectReadFailure("original-effect-rounds-unavailable", error) }
finally { if (roundContext) closeRoutingNetworkForwardRoundConfigurationContext(roundContext) }
}
// Publication revalidation uses the existing native owner's fixed
// context reader and therefore its SAME counters, without a snapshot.
let sharedPublication = false
if (effectSession && !effectSession.readBudget().closed) {
try {
const published = await readRoutingNetworkScopedCommunityContext(effectSession.sharedContext)
if (!matches(input, published).configurationMatchesDesired) throw fail("Состояние изменилось после effect-check")
await published.fence()
const budget = effectSession.readBudget()
originalEffects = { ...originalEffects!, budget: { deadline: budget.deadline, calls: budget.calls, bytes: budget.bytes, rows: budget.rows } }
sharedPublication = true
} catch (error) { originalEffects = effectReadFailure("effect-publication-revalidation-unavailable", error) }
}
if (!sharedPublication) {
// This is the OLD manual final read, relocated after optional work,
// never duplicated or counted as a completed effect-owner read.
// Closed/exhausted effect owners cannot waive this fresh guard.
const final = await observe(input, undefined, true, undefined, "configuration")
if (!matches(input, final.context).configurationMatchesDesired) throw fail("Состояние изменилось после probe")
await final.context.fence()
}
if (effectSession && originalEffects) {
const budget = effectSession.readBudget()
originalEffects = { ...originalEffects, budget: { deadline: budget.deadline, calls: budget.calls, bytes: budget.bytes, rows: budget.rows } }
}
await saveScopedNetworkCheckpoint(input, await sealedInput(input), { ...check, status: check.status }, originalEffects)
return verifiedResult(input)
} catch { throw fail("Результат переключения не подтверждён; повтор запрещён, требуется сверка", "MUTATION_OUTCOME_UNKNOWN") }
finally { effectSession?.close() }
})),
verify: async (input, result) => {
const expected = await verifiedResult(input)
@@ -200,6 +265,7 @@ async function verifiedResult(input: Record<string, unknown>) {
const network = await loadScopedNetworkCheckpoint(input, await sealedInput(input))
const result = { outcome: network?.status === "passed" ? "applied" : "configuration-applied-network-unverified", nativeWrites: 1, forwardingVerified: false, configurationVerified: true, receiptId: input.receiptId, proofRevision: input.proofRevision,
configurationCheckpointAvailable: !!checkpoint, configurationCheckpointProof: checkpoint?.checkpointProof ?? null, controlProbeVerified: network?.status === "passed", postProbeStatus: network?.status ?? "unknown", networkCheckpointProof: network?.networkSeal ?? null,
...(network?.originalEffects === undefined ? {} : { originalEffects: network.originalEffects }),
nativeVerificationSeal: await privateSecretFingerprint(fingerprint({ request: await sealedInput(input), dependency: input.dependencyProof, desired: input.desiredTargetProof, candidate: input.candidateId, configuration: checkpoint?.checkpointProof ?? null, network: network?.networkSeal ?? null }), `route-ai-scoped-verified-v${input.proofRevision}`) }
await assertPersistentScopedProfileAuthority(input)
return result
@@ -4,6 +4,7 @@ import { findMcpOperation } from "./registry.js"
import { assertScopedCommunityRequest } from "./routing-community-scoped-request.js"
import { assertPersistentScopedProfileAuthority, routingProfileAuthorityBindingSchema } from "./routing-profile-authority.js"
import { loadScopedConfigurationPacket, loadScopedNetworkCheckpoint } from "./routing-community-scoped-checkpoint.js"
import { originalEffectSelectionSchema } from "./routing-original-effects.js"
import { z } from "zod"
import { routeAiProfileSchema, type RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { hasPermission } from "../../lib/permissions.js"
@@ -166,6 +167,13 @@ export async function advanceRoutingControllerBaseline(rawBaseline: unknown, pro
const configuration = await loadScopedConfigurationPacket(input, requestSeal, { operationId, stepIndex: 0 })
const network = await loadScopedNetworkCheckpoint(input, requestSeal)
if (!configuration || configuration.packet.revision !== 2 || !network || network.status !== "passed") throw fail()
// A genuinely prepared effect check may pause controller advancement without
// erasing the separate successful manual configuration/control-probe receipt.
if (Object.hasOwn(input, "originalEffectSelection")) {
const effects = originalEffectSelectionSchema.safeParse(input.originalEffectSelection)
if (!effects.success || effects.data.status !== "prepared" || network.originalEffects?.status !== "observed-expected" ||
network.originalEffects.selectionIdentity !== fingerprint(effects.data) || network.originalEffects.switchEffectObserved !== true || !network.originalEffects.transition?.changedPrefixes.length) throw fail()
}
const packet = configuration.packet, result = row.result
if (packet.operationId !== operationId || packet.stepIndex !== 0 || packet.nativeId !== original.nativeId ||
packet.originalCandidateId !== original.candidateId || packet.originalTargetProof !== original.targetProof ||
+22 -8
View File
@@ -1,3 +1,5 @@
import { readRoutingTopology } from "../../services/routing-topology-observation.js"
import { createRoutingSourceContext, readRoutingEndpoints } from "./routing-observation-source.js"
import { z } from "zod"
import { dedupeVrfAliasSnapshots } from "../../services/vrf-production-action-binding.js"
import { pool } from "../../db/index.js"
@@ -177,14 +179,26 @@ export function registerRoutingControls(register: typeof add) {
},
})
register("routing.topology.read", "mm_routing_topology_get", "Наблюдаемая конфигурация маршрутизации", "network", "mm:network:read", "read", "servers", z.object({ serverId }).strict(), {
read: async input => {
const { client } = await routerClient(Number(input.serverId))
const paths = { interfaces: "/interface", ipv4: "/ip/route", ipv6: "/ipv6/route", tables: "/routing/table", rules: "/routing/rule", vrfs: "/ip/vrf", ospfNeighbors: "/routing/ospf/neighbor", bgpSessions: "/routing/bgp/session" }
const entries = await Promise.all(Object.entries(paths).map(async ([key, path]) => {
try { const values = await client.get<unknown>(path); const rows = Array.isArray(values) ? values : [values]; return [key, { items: rows.slice(0, 500), truncated: rows.length > 500, observed: true }] }
catch { return [key, { items: [], observed: false, reason: "Устройство не подтвердило возможность чтения" }] }
}))
return { serverId: input.serverId, observedAt: new Date().toISOString(), resources: Object.fromEntries(entries), observedPath: false }
read: async (input, context) => {
const sid = Number(input.serverId)
if (!context.connection.policy.scope.serverIds.includes(sid)) throw new McpError("SERVER_DENIED", "Источник топологии вне разрешённой области", 403)
const signal = AbortSignal.any([AbortSignal.timeout(30000), ...(context.signal ? [context.signal] : [])])
const authorize = async () => {
if (signal.aborted) throw new McpError("READ_CANCELLED", "Чтение топологии отменено или превысило срок", 409)
const current = await effectiveMcpConnection(context.principal)
if (!hasPermission(current.owner_permissions, "mm:network:read") || !hasPermission(current.policy.permissions, "mm:network:read")) throw new McpError("PERMISSION_DENIED", "Право чтения сети отозвано", 403)
if (fingerprint(current.policy) !== fingerprint(context.connection.policy) || fingerprint(current.owner_permissions) !== fingerprint(context.connection.owner_permissions)) throw new McpError("RESET_REQUIRED", "Допуск изменился; повторите чтение топологии", 409)
}
await authorize()
const source = await createRoutingSourceContext(await readRoutingEndpoints([sid]))
if (source.expected.length !== 1) throw new McpError("DEVICE_UNAVAILABLE", "Источник топологии отсутствует или выключен", 409)
const beforeRead = async () => {
await authorize(); await source.assertCurrent()
if (signal.aborted) throw new McpError("READ_CANCELLED", "Чтение топологии отменено или превысило срок", 409)
}
await beforeRead()
const { client } = await routerClient(sid)
return { serverId: sid, ...await readRoutingTopology(client, beforeRead, signal) }
},
})
register("routing.ecmp.update", "mm_ecmp_plan", "Равная стоимость маршрутов ECMP и hash policy", "network", "mm:network:write", "write", "servers", z.object({ serverId, family, routeIds: z.array(objectId).min(2).max(8).refine(ids => new Set(ids).size === ids.length), distance: z.number().int().min(1).max(255).default(1), hashPolicy: z.enum(["l3", "l4", "l3-inner"]).optional() }).strict(), {
+95 -6
View File
@@ -43,14 +43,43 @@ test("new unknown attempts block older successful data independently for each me
test("failover requires three distinct failures and a freshly verified eligible standby", () => {
const ranking = rankRoutingCandidates(profile, [...a, sample("b1", 1, 4, "wan-b"), sample("b2", 4, 3, "exit-b")], now)
let state: ControllerState = {}
const health = (id: string, status: PathHealth["status"] = "failed"): PathHealth[] => [{ id, candidateId: "a", status, observedAt: at }, { id: "reserve", candidateId: "b", status: "passed", observedAt: at }]
const health = (id: string, time = now, status: PathHealth["status"] = "failed"): PathHealth[] => {
const observedAt = new Date(time).toISOString()
return [{ id, candidateId: "a", status, observedAt }, { id: "reserve", candidateId: "b", status: "passed", observedAt }]
}
for (let i = 0; i < 5; i++) { const next = decideRoutingSwitch(profile, state, ranking, "a", now, health("one")); state = next.state; assert.equal(next.switchTo, null) }
assert.equal(state.failureCount, 1)
const second = decideRoutingSwitch(profile, state, ranking, "a", now, health("two")); assert.equal(second.switchTo, null)
assert.equal(decideRoutingSwitch(profile, second.state, ranking, "a", now, health("three")).switchTo, "b")
assert.equal(decideRoutingSwitch(profile, second.state, ranking, "a", now, health("three", "unknown")).switchTo, null)
assert.equal(decideRoutingSwitch(profile, { ...second.state, switches: [at, at, at] }, ranking, "a", now, health("three")).switchTo, null)
assert.equal(decideRoutingSwitch(profile, second.state, ranking, "a", now + 100000, health("three")).switchTo, null)
const secondAt = now + 60000, thirdAt = now + 120000
const second = decideRoutingSwitch(profile, state, ranking, "a", secondAt, health("two", secondAt)); assert.equal(second.switchTo, null)
assert.equal(decideRoutingSwitch(profile, second.state, ranking, "a", thirdAt, health("three", thirdAt)).switchTo, "b")
const unknown = decideRoutingSwitch(profile, second.state, ranking, "a", thirdAt, health("three", thirdAt, "unknown"))
assert.equal(unknown.switchTo, null)
assert.equal(unknown.state.failureObservedAt, undefined)
assert.equal(decideRoutingSwitch(profile, { ...second.state, switches: [at, at, at] }, ranking, "a", thirdAt, health("three", thirdAt)).switchTo, null)
assert.equal(decideRoutingSwitch(profile, second.state, ranking, "a", thirdAt + 100000, health("three", thirdAt)).switchTo, null)
})
test("failover starts a fresh series after downtime or legacy state and ignores reordered failures", () => {
const ranking = rankRoutingCandidates(profile, [...a, sample("b1", 1, 4, "wan-b"), sample("b2", 4, 3, "exit-b")], now)
const last = now + 60000
const persisted: ControllerState = JSON.parse(JSON.stringify({ failureCount: 2, failureKey: "two",
failureObservedAt: new Date(last).toISOString(), failingCandidateId: "a" }))
const check = (state: ControllerState, id: string, time: number, clock = time) => {
const observedAt = new Date(time).toISOString()
return decideRoutingSwitch(profile, state, ranking, "a", clock,
[{ id, candidateId: "a", status: "failed", observedAt }, { id: "reserve", candidateId: "b", status: "passed", observedAt }])
}
for (const result of [check(persisted, "two", last + 60000), check(persisted, "older", last - 1000, last),
check(persisted, "clock-rollback", last - 1000), check(persisted, "equal-time", last)]) {
assert.equal(result.switchTo, null)
assert.equal(result.state.failureCount, 2)
assert.equal(result.state.failureObservedAt, persisted.failureObservedAt)
}
for (const [state, time] of [[persisted, now + 4 * 3600000], [{ ...persisted, failureObservedAt: undefined }, last + 60000]] as const) {
const result = check(state, "next", time)
assert.equal(result.switchTo, null)
assert.equal(result.state.failureCount, 1)
assert.equal(result.state.failureObservedAt, new Date(time).toISOString())
}
})
test("routing combines RTT, independent losses and bottleneck speed, never claims an observed path", () => {
const ranked = rankRoutingCandidates(profile, a, now).find(row => row.candidate.id === "a")!
@@ -92,3 +121,63 @@ test("controller does not count polling the same sample as consecutive confirmat
assert.equal(decideRoutingSwitch(profile, { ...state, switches: [at, at, at] }, ranks, "a", now).switchTo, null)
assert.equal(decideRoutingSwitch(profile, state, ranks, null, now).switchTo, null)
})
test("hysteresis retains pending confirmation without counting sub-threshold observations or releasing cooldown", () => {
const ranked = rankRoutingCandidates(profile, [...a, sample("b1", 1, 4, "wan-b"), sample("b2", 4, 3, "exit-b")], now)
const compare = (state: ControllerState, gain: number, observation: string, time = now) => {
const ranks = ["b", "a"].map(id => ({ ...ranked.find(row => row.candidate.id === id)!,
score: id === "a" ? 50 : 50 + gain, observationIds: [id + observation] }))
return decideRoutingSwitch(profile, state, ranks, "a", time)
}
const initial = compare({}, 15, "1").state
assert.equal(initial.consecutive, 1)
for (const [gain, observation] of [[6, "2"], [5, "3"]] as const) {
const result = compare(initial, gain, observation)
assert.equal(result.switchTo, null)
assert.deepEqual(result.state, initial)
}
const reset = compare(initial, 4, "4")
assert.equal(reset.state.pendingCandidateId, undefined)
assert.equal(reset.state.consecutive, 0)
const second = compare(initial, 15, "5")
assert.equal(second.switchTo, null)
const third = compare({ ...second.state, lastSwitchAt: at }, 15, "6")
assert.equal(third.state.consecutive, 3)
assert.equal(third.switchTo, null)
const afterCooldown = now + profile.cooldownSec * 1000
assert.equal(compare(third.state, 6, "7", afterCooldown).switchTo, null)
assert.equal(compare(third.state, 15, "8", afterCooldown).switchTo, "b")
})
test("a different pending candidate cannot borrow confirmations from a shared observation key", () => {
const ranks = rankRoutingCandidates(profile, [...a, sample("b1", 1, 4, "wan-b"), sample("b2", 4, 3, "exit-b")], now)
.map(row => ({ ...row, score: row.candidate.id === "a" ? 50 : 75 }))
.sort((left, right) => right.score - left.score)
const observationKey = ranks.flatMap(row => row.observationIds).sort().join(":")
const persisted: ControllerState = { pendingCandidateId: "previous-candidate", consecutive: 3, observationKey }
const selected = decideRoutingSwitch(profile, persisted, ranks, "a", now)
assert.equal(selected.switchTo, null)
assert.equal(selected.state.pendingCandidateId, "b")
assert.equal(selected.state.consecutive, 0)
assert.equal(selected.state.observationKey, observationKey)
const replay = decideRoutingSwitch(profile, selected.state, ranks, "a", now)
assert.equal(replay.switchTo, null)
assert.equal(replay.state.consecutive, 0)
const freshRanks = [{ ...ranks[0], observationIds: ["new-b-1", "new-b-2"] }, ranks[1]]
const fresh = decideRoutingSwitch(profile, replay.state, freshRanks, "a", now)
assert.equal(fresh.state.consecutive, 1)
assert.equal(fresh.switchTo, null)
const belowFloor = [{ ...freshRanks[0], score: 54 }, freshRanks[1]]
const reset = decideRoutingSwitch(profile, fresh.state, belowFloor, "a", now)
assert.equal(reset.state.pendingCandidateId, undefined)
assert.equal(reset.state.consecutive, 0)
const replayAfterReset = decideRoutingSwitch(profile, reset.state, freshRanks, "a", now)
assert.equal(replayAfterReset.switchTo, null)
assert.equal(replayAfterReset.state.consecutive, 0)
})
+20 -9
View File
@@ -51,36 +51,47 @@ export function rankRoutingCandidates(profile: RouteAiProfile, observations: Rou
}
export interface PathHealth { id: string; candidateId: string; status: "passed" | "failed" | "unknown"; observedAt: string }
export interface ControllerState { pendingCandidateId?: string; consecutive?: number; observationKey?: string; lastSwitchAt?: string; switches?: string[]; operationId?: string; pausedReason?: string; failureCount?: number; failureKey?: string; failingCandidateId?: string }
export interface ControllerState { pendingCandidateId?: string; consecutive?: number; observationKey?: string; lastSwitchAt?: string; switches?: string[]; operationId?: string; pausedReason?: string; failureCount?: number; failureKey?: string; failureObservedAt?: string; failingCandidateId?: string }
const pathHealthFreshnessSec = 90
export function decideRoutingSwitch(profile: RouteAiProfile, state: ControllerState, ranking: ReturnType<typeof rankRoutingCandidates>, currentCandidateId: string | null, now = Date.now(), health: PathHealth[] = [], chargedSwitchesPerHour?: number) {
const next: ControllerState = { ...state, switches: (state.switches ?? []).filter(at => now - Date.parse(at) < 3600000) }
if (chargedSwitchesPerHour !== undefined && (!Number.isSafeInteger(chargedSwitchesPerHour) || chargedSwitchesPerHour < next.switches!.length || chargedSwitchesPerHour > 4096)) throw Error("Invalid common routing charge count")
const switchCount = chargedSwitchesPerHour ?? next.switches!.length
const best = ranking.find(candidate => candidate.eligible), current = ranking.find(candidate => candidate.candidate.id === currentCandidateId)
const currentHealth = health.find(value => value.candidateId === currentCandidateId && fresh(value.observedAt, 90, now))
const currentHealth = health.find(value => value.candidateId === currentCandidateId && fresh(value.observedAt, pathHealthFreshnessSec, now))
if (currentHealth?.status === "failed" && currentCandidateId && !state.pausedReason) {
if (state.failureKey !== currentHealth.id) {
next.failureCount = state.failingCandidateId === currentCandidateId ? (state.failureCount ?? 0) + 1 : 1
next.failureKey = currentHealth.id; next.failingCandidateId = currentCandidateId
const observed = Date.parse(currentHealth.observedAt), previous = Date.parse(state.failureObservedAt ?? "")
const sameCandidate = state.failingCandidateId === currentCandidateId
if (state.failureKey === currentHealth.id) {
if (!sameCandidate || observed !== previous) return { state: next, switchTo: null, reason: "Ожидание новой проверки отказа" }
} else {
if (observed <= previous) return { state: next, switchTo: null, reason: "Ожидание новой проверки отказа" }
const continues = sameCandidate && fresh(state.failureObservedAt ?? null, pathHealthFreshnessSec, observed)
next.failureCount = continues ? (state.failureCount ?? 0) + 1 : 1
next.failureKey = currentHealth.id; next.failureObservedAt = currentHealth.observedAt; next.failingCandidateId = currentCandidateId
}
const reserve = ranking.find(value => value.eligible && value.candidate.id !== currentCandidateId && health.some(check => check.candidateId === value.candidate.id && check.status === "passed" && fresh(check.observedAt, 90, now)))
const reserve = ranking.find(value => value.eligible && value.candidate.id !== currentCandidateId && health.some(check => check.candidateId === value.candidate.id && check.status === "passed" && fresh(check.observedAt, pathHealthFreshnessSec, now)))
if ((next.failureCount ?? 0) < 3 || !reserve) return { state: next, switchTo: null, reason: "Failover ожидает три отдельных отказа и подтверждённый резерв" }
if (switchCount >= profile.maxSwitchesPerHour) return { state: { ...next, pausedReason: "Лимит переключений" }, switchTo: null, reason: "Исчерпан часовой бюджет, включая failover" }
if (next.lastSwitchAt && now - Date.parse(next.lastSwitchAt) < profile.cooldownSec * 1000) return { state: next, switchTo: null, reason: "Failover ожидает окончания cooldown" }
return { state: { ...next, failureCount: 0 }, switchTo: reserve.candidate.id, reason: "Failover: три подтверждённых отказа и проверенный резерв" }
}
next.failureCount = 0; next.failureKey = undefined; next.failingCandidateId = undefined
next.failureCount = 0; next.failureKey = undefined; next.failureObservedAt = undefined; next.failingCandidateId = undefined
if (state.pausedReason || !currentCandidateId || !best || best.candidate.id === currentCandidateId) return { state: { ...next, pendingCandidateId: undefined, consecutive: 0 }, switchTo: null, reason: "Нет подтверждённого улучшения или текущего пути" }
if (!current?.eligible || current.score === null) return { state: next, switchTo: null, reason: "Состояние текущего пути неизвестно; failover требует отдельных подтверждённых проверок отказа" }
const gain = best.score! - current.score
if (gain < Math.max(0, profile.switchThreshold - profile.hysteresis)) return { state: { ...next, consecutive: 0, pendingCandidateId: undefined }, switchTo: null, reason: "Сработал гистерезис" }
if (gain < profile.switchThreshold && next.pendingCandidateId !== best.candidate.id) return { state: next, switchTo: null, reason: "Порог улучшения не достигнут" }
if (gain < profile.switchThreshold) return { state: next, switchTo: null, reason: "Порог улучшения не достигнут" }
const observationKey = [...best.observationIds, ...current.observationIds].sort().join(":")
if (next.pendingCandidateId !== undefined && next.pendingCandidateId !== best.candidate.id) {
next.pendingCandidateId = best.candidate.id
next.consecutive = 0
}
if (observationKey !== next.observationKey) {
next.consecutive = next.pendingCandidateId === best.candidate.id ? (next.consecutive ?? 0) + 1 : 1
next.pendingCandidateId = best.candidate.id; next.observationKey = observationKey
}
if ((next.consecutive ?? 0) < profile.confirmationSamples) return { state: next, switchTo: null, reason: "Ожидание новых измерений" }
if (next.pendingCandidateId !== best.candidate.id || (next.consecutive ?? 0) < profile.confirmationSamples) return { state: next, switchTo: null, reason: "Ожидание новых измерений" }
if (next.lastSwitchAt && now - Date.parse(next.lastSwitchAt) < profile.cooldownSec * 1000) return { state: next, switchTo: null, reason: "Cooldown" }
if (switchCount >= profile.maxSwitchesPerHour) return { state: { ...next, pausedReason: "Лимит переключений" }, switchTo: null, reason: "Исчерпан часовой бюджет переключений" }
return { state: next, switchTo: best.candidate.id, reason: "Улучшение подтверждено свежими наблюдениями" }
@@ -14,7 +14,7 @@ async function fixture() {
let nativeReads = 0, profileChecks = 0, sourceReads = 0, configChecks = 0
let nativeBody = "desired", nativeDependency = "dependency", nativeTarget = "desired-target", nativeCandidate = "b", nativeId = "*1", generation = "generation"
let afterNative: (() => void) | undefined, afterCheckpoint: (() => void) | undefined, afterProfile: (() => void) | undefined
let sharedReads = 0, sharedClosed = false
let sharedReads = 0, sharedClosed = false, manualOwner = false
class ClockDate extends Date { static now() { return now } }
class BridgeError extends Error { constructor(readonly code: string, message: string) { super(message) } }
const fingerprint = (value: unknown) => JSON.stringify(value)
@@ -37,6 +37,7 @@ async function fixture() {
const sharedToken = Object.freeze({}), acquisition = Object.freeze({}), sharedBinding = { sourceOperationId: row.id, profile: structuredClone(profile), principal: structuredClone(principal),
deadline: now + 10000, contextSources: structuredClone(input.contextSources), candidateId: "b" }
const providers: Record<string, unknown> = {
"./routing-network-execution-session.js": { isVerifiedManualEffectOwner: (owner: unknown) => owner === sharedToken && manualOwner },
zod: { z }, "@mmapp/contracts/mcp-routing": { routeAiProfileSchema: { parse: (value: unknown) => value } },
"../../db/index.js": { pool: { query: async () => { sourceReads++; return { rows: [{ ...row, plan_id: sourceChanged ? "changed" : row.plan_id }] } } } },
"../../lib/permissions.js": { hasPermission: (values: string[], permission: string) => values.includes(permission) },
@@ -93,7 +94,7 @@ async function fixture() {
const reseal = () => { row.evidence.sealed = JSON.stringify(evidence) }
return { resolve: () => resolve(row.id, action), resolveShared: (token = sharedToken) => resolveShared(token, action), sharedToken, sharedBinding,
consume, close, resolveRound: () => resolveRound(sharedToken, action), readRound: (token: object, read = acquisition) => readRound(token, read), closeRound, acquisition,
row, input, profile, checkpoint, packet, action, authority, reseal,
row, input, profile, checkpoint, packet, action, authority, reseal, brandManual: () => { manualOwner = true },
stats: () => ({ nativeReads, profileChecks, sourceReads, configChecks, sharedReads, sharedClosed }), advance: (ms: number) => { now += ms },
revoke: () => { revoked = true }, sourceChanged: () => { sourceChanged = true }, checkpointMissing: () => { checkpointMissing = true },
checkpointChanged: () => { checkpointChanged = true }, guard: () => { guard = true },
@@ -110,6 +111,25 @@ test("fixed resolver captures opaque context and consume rechecks configuration
assert.equal(f.stats().nativeReads, 2); assert(f.stats().profileChecks > 5)
await assert.rejects(f.consume(token), /Unknown or consumed/); await assert.rejects(f.resolve(), /already issued/)
})
for (const mode of ["positive", "unbranded", "wrong-checkpoint", "terminal-source", "standalone"] as const) {
test(`manual revision-2 round ${mode}`, async () => {
const f = await fixture()
f.input.proofRevision = 2; f.input.manualEffectPolicy = "observational-v1"; delete f.input.persistentProfileAuthority
f.row.state = mode === "terminal-source" ? "applied" : "running"; f.row.step_state = "intent"
f.packet.revision = mode === "wrong-checkpoint" ? 2 : 1
if (mode !== "unbranded") f.brandManual()
f.reseal()
if (mode !== "positive") {
await assert.rejects(mode === "standalone" ? f.resolve() : f.resolveRound())
assert.equal(f.stats().nativeReads, 0)
return
}
const round = await f.resolveRound(), observation = await f.readRound(round)
assert.equal(observation.sharedNetworkBudgetVerified, true)
assert.equal(observation.forwardingVerified, false)
})
}
test("caller objects, JSON, another resolver and closed contexts cannot be consumed", async () => {
const f = await fixture(), other = await fixture(), token = await f.resolve()
await assert.rejects(f.consume({}), /Unknown or consumed/); await assert.rejects(f.consume(JSON.parse(JSON.stringify(token))), /Unknown or consumed/)
@@ -201,12 +221,13 @@ test("missing genuine checkpoint closes shared owner without accepting equality"
test("forward round context reuses one admitted source only through its exact ephemeral acquisition", async () => {
const f = await fixture(), token = await f.resolveRound()
assert.equal(f.stats().sharedReads, 0, "Round admission grants no native-success observation")
for (let round = 0; round < 2; round++) {
const before = await f.readRound(token), after = await f.readRound(token)
assert.equal(before.operationId, f.row.id); assert.equal(after.securityProof, false)
assert.equal(after.forwardingVerified, false); assert.equal(after.sharedNetworkBudgetVerified, true)
}
assert.equal(f.stats().sharedReads, 5)
assert.equal(f.stats().sharedReads, 4)
await assert.rejects(f.resolveShared(), /already issued/)
})
@@ -221,3 +242,16 @@ for (const fault of ["clone", "owner", "body", "dependency", "checkpoint", "revo
await assert.rejects(f.readRound(fault === "clone" ? JSON.parse(JSON.stringify(token)) : token, fault === "owner" ? {} : f.acquisition))
assert.equal(f.stats().sharedClosed, !["clone", "close"].includes(fault))
})
for (const drift of ["body", "dependency", "checkpoint", "revoke"] as const)
test("first forward-round read detects " + drift + " after source-only admission", async () => {
const f = await fixture(), token = await f.resolveRound()
assert.equal(f.stats().nativeReads, 0)
if (drift === "body" || drift === "dependency") f.drift(drift)
if (drift === "checkpoint") f.checkpointChanged()
if (drift === "revoke") f.revoke()
await assert.rejects(f.readRound(token))
assert.equal(f.stats().sharedClosed, true)
await assert.rejects(f.readRound(token))
})
@@ -8,6 +8,7 @@ import { findMcpOperation } from "./registry.js"
import { accessGeneration, decryptSecret, fingerprint, McpError, redact } from "./security.js"
import { assertScopedCommunityRequest } from "./routing-community-scoped-request.js"
import { loadScopedConfigurationPacket } from "./routing-community-scoped-checkpoint.js"
import { isVerifiedManualEffectOwner } from "./routing-network-execution-session.js"
import { assertPersistentScopedProfileAuthority, routingProfileAuthorityBindingSchema } from "./routing-profile-authority.js"
import { readRouteCommunityContext, readRouteCommunityContextWithSharedNativeSession } from "./routing-community-context.js"
import { readRoutingNetworkScopedCommunityContext } from "./routing-network-scoped-context.js"
@@ -68,7 +69,9 @@ export async function resolveRoutingNetworkConfigurationContextFromSharedContext
* as the existing bridge. Only its owning session can use it inside a queued
* round acquisition; JSON copies and caller-supplied observations cannot. */
export async function resolveRoutingNetworkForwardRoundConfigurationContext(sharedContext: RoutingNetworkSharedContext, action: ActionContext): Promise<RoutingNetworkForwardRoundConfigurationContext> {
const initial = await resolveRoutingNetworkConfigurationContextFromSharedContext(sharedContext, action)
const binding = inspectRoutingNetworkSharedContext(sharedContext)
if (fingerprint(binding.principal) !== fingerprint(action.principal)) throw fail("Shared configuration principal changed")
const initial = await resolveConfigurationContext(binding.sourceOperationId, action, sharedContext, true)
const entry = contexts.get(initial)
if (!entry) throw fail("Configuration bridge admission disappeared")
contexts.delete(initial)
@@ -96,7 +99,8 @@ export function closeRoutingNetworkForwardRoundConfigurationContext(context: Rou
roundContexts.delete(context); entry?.close()
}
async function resolveConfigurationContext(sourceOperationId: string, action: ActionContext, sharedContext?: RoutingNetworkSharedContext): Promise<RoutingNetworkConfigurationContext> {
async function resolveConfigurationContext(sourceOperationId: string, action: ActionContext, sharedContext?: RoutingNetworkSharedContext, deferredRoundReadback = false): Promise<RoutingNetworkConfigurationContext> {
if (deferredRoundReadback && !sharedContext) throw fail("Forward round requires its original shared owner")
z.string().uuid().parse(sourceOperationId)
const principal = freeze(structuredClone(action.principal)), connection = freeze(structuredClone(action.connection))
const abort = new AbortController()
@@ -134,9 +138,16 @@ async function resolveConfigurationContext(sourceOperationId: string, action: Ac
if (binding.sourceOperationId !== sourceOperationId || fingerprint(binding.profile) !== fingerprint(profile) ||
fingerprint(binding.contextSources) !== fingerprint(input.contextSources) || binding.candidateId !== input.candidateId) throw fail("Shared configuration source binding changed")
}
if (input.proofRevision !== 3 || input.persistentProfileAuthority === undefined || profile.autoRecovery) throw fail("Persistent manual revision-3 source required; recovery guard remains closed")
const authority = routingProfileAuthorityBindingSchema.parse(input.persistentProfileAuthority)
deadline = Math.min(deadline, Date.parse(authority.packet.validUntil))
const manual = !!sharedContext && deferredRoundReadback && isVerifiedManualEffectOwner(sharedContext)
if (profile.autoRecovery) throw fail("Recovery guard remains closed")
if (manual) {
if (row.state !== "running" || row.step_state !== "intent" || input.proofRevision !== 2 ||
input.persistentProfileAuthority !== undefined || input.manualEffectPolicy !== "observational-v1") throw fail("Exact running manual effect source required")
} else {
if (input.proofRevision !== 3 || input.persistentProfileAuthority === undefined) throw fail("Persistent manual revision-3 source required")
const authority = routingProfileAuthorityBindingSchema.parse(input.persistentProfileAuthority)
deadline = Math.min(deadline, Date.parse(authority.packet.validUntil))
}
signal = AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, deadline - Date.now()))])
active()
const ids = [...new Set([profile.serverId, ...profile.candidates.flatMap(candidate => [candidate.jumpHostId, candidate.exitNodeId])])].sort((a,b) => a-b)
@@ -157,7 +168,7 @@ async function resolveConfigurationContext(sourceOperationId: string, action: Ac
const checkpoint = async () => {
await authorize()
const result = await guarded(() => loadScopedConfigurationPacket(input, requestSeal, { operationId: sourceOperationId, stepIndex: 0 }))
if (!result || result.packet.revision !== 2 || result.packet.operationId !== sourceOperationId || result.packet.stepIndex !== 0 ||
if (!result || result.packet.revision !== (manual ? 1 : 2) || result.packet.operationId !== sourceOperationId || result.packet.stepIndex !== 0 ||
result.packet.phase !== "CONFIG_APPLIED" || Date.parse(result.observedAt) < row.created_at.getTime() || Date.parse(result.observedAt) > Date.now() ||
Date.parse(result.observedAt) >= deadline) throw fail("Genuine persistent CONFIG_APPLIED unavailable")
await authorize()
@@ -190,7 +201,11 @@ async function resolveConfigurationContext(sourceOperationId: string, action: Ac
bodyIdentity: fingerprint(original.packet.desiredRule), dependencyIdentity: original.packet.dependencyProof },
securityProof: false as const, forwardingVerified: false as const, sharedNetworkBudgetVerified: !!sharedContext })
}
await readback()
// A forward-round token carries no native success observation. Its fixed
// owner must perform a fresh read immediately BEFORE every snapshot and
// another immediately AFTER. Avoid an unused eager native read here;
// independent/shared one-shot bridge semantics remain unchanged.
if (!deferredRoundReadback) await readback()
const token = Object.freeze({}) as RoutingNetworkConfigurationContext
contexts.set(token, { consumed: false, close, read: readback, consume: async () => {
try { return await readback() }
@@ -0,0 +1,91 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import test from 'node:test';
import { createHash } from 'node:crypto';
const require = createRequire(new URL('../../../package.json', import.meta.url));
const ts = require('typescript');
const code = ts.transpileModule(await readFile(new URL('./routing-network-execution-session.ts', import.meta.url), 'utf8'), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }
}).outputText;
function fixture() {
const now = Date.now();
const metadata = { approved: true, phase: 'forward', stepIndex: 0, operationName: 'routing.community.switch', operationVersion: 7,
serviceName: 'routing.community.scoped', operationId: 'real-operation', planId: 'real-plan', principal: { connectionId: 'real-connection', credentialExpiresAt: new Date(now + 90000).toISOString() },
serviceInput: { persistentProfileAuthority: { packet: { validUntil: new Date(now + 70000).toISOString() } } } };
metadata.sealedEvidenceProof = createHash('sha256').update('evidence-ciphertext').digest('hex');
metadata.sealedPayloadProof = createHash('sha256').update('payload-ciphertext').digest('hex');
const original = structuredClone(metadata);
const row = { created_at: new Date(now - 40000), expires_at: new Date(now + 100000), evidence: { sealed: 'evidence-ciphertext' }, payload: { sealed: 'payload-ciphertext' },
intended_state: { kind: 'service', service: metadata.serviceName, input: structuredClone(metadata.serviceInput) } };
const lifetime = { expiresAt: now + 60000, monotonicDeadline: performance.now() + 60000, signal: new AbortController().signal };
let calls = 0, inspections = 0, closed = 0, options, query, originAvailable = true, afterCreate = () => {};
const connection = { owner_valid_until: new Date(now + 110000), policy: { expiresAt: new Date(now + 120000).toISOString() } };
const token = Object.freeze({}), sharedContext = Object.freeze({});
const providers = {
'node:crypto': { createHash },
'../../db/index.js': { pool: { query: async value => { query = value; return { rows: [row] }; } } },
'../../services/config-mutation-policy.js': {
captureVerifiedMcpServiceExecution: async () => { if (!originAvailable) throw Error('no-origin'); return token; },
inspectVerifiedMcpServiceExecution: async value => { assert.equal(value, token); inspections++; return structuredClone(metadata); },
inspectVerifiedMcpServiceExecutionLifetime: async value => { assert.equal(value, token); return lifetime; }
},
'./store.js': { effectiveMcpConnection: async principal => { assert.deepEqual(principal, original.principal); return connection; } },
'./registry.js': { findMcpOperation: () => ({ version: 7 }) },
'./security.js': { fingerprint: JSON.stringify, redact: value => value, McpError: Error },
'./routing-profile-authority.js': { routingProfileAuthorityBindingSchema: { parse: value => value } },
'./routing-network-native-session.js': { createRoutingNetworkNativeSession: async value => { calls++; options = value; afterCreate(); return { sharedContext, close() { closed++; } }; } }
};
const exports = {};
new Function('exports', 'require', code)(exports, name => { assert.ok(providers[name], name); return providers[name]; });
const manifest = { servers: [1, 2], probes: [] };
return { metadata, row, lifetime, connection, manifest, now, sharedContext, isManual: exports.isVerifiedManualEffectOwner, run: () => exports.createRoutingNetworkSessionForCurrentExecution(manifest),
revokeOrigin: () => { originAvailable = false; }, afterCreate: fn => { afterCreate = fn; },
stats: () => ({ calls, closed, inspections, options, query }) };
}
for (const mode of ['positive', 'missing-policy', 'missing-selection', 'recovery', 'late-revocation']) {
test(`manual effect owner origin ${mode}`, async () => {
const f = fixture();
f.metadata.serviceInput = { proofRevision: 2, manualEffectPolicy: 'observational-v1', profile: { autoRecovery: mode === 'recovery' }, originalEffectSelection: { status: 'prepared' } };
if (mode === 'missing-policy') delete f.metadata.serviceInput.manualEffectPolicy;
if (mode === 'missing-selection') delete f.metadata.serviceInput.originalEffectSelection;
if (mode === 'late-revocation') f.afterCreate(() => { f.metadata.approved = false; });
f.row.intended_state.input = structuredClone(f.metadata.serviceInput);
if (mode === 'positive') {
await f.run();
assert.equal(f.isManual(f.sharedContext), true);
assert.equal(f.isManual(JSON.parse(JSON.stringify(f.sharedContext))), false);
} else {
await assert.rejects(f.run());
assert.equal(f.isManual(f.sharedContext), false);
assert.equal(f.stats().calls, mode === 'late-revocation' ? 1 : 0);
assert.equal(f.stats().closed, mode === 'late-revocation' ? 1 : 0);
}
});
}
test('real origin supplies operation, principal, signal and minimum immutable horizon', async () => {
const f = fixture(); await f.run(); const { options, query } = f.stats();
assert.equal(options.sourceOperationId, f.metadata.operationId); assert.equal(options.context.signal, f.lifetime.signal);
assert.equal(options.deadline, f.lifetime.expiresAt - 15000); assert.equal(options.manifest, f.manifest);
assert.deepEqual(query.values, [f.metadata.operationId, f.metadata.principal.connectionId, f.metadata.planId]);
assert.match(query.text, /o.state='running'/); assert.match(query.text, /s.state='intent'/);
});
test('elapsed original 120 seconds cannot be renewed from current time', async () => {
const f = fixture(); f.row.created_at = new Date(f.now - 120001); await assert.rejects(f.run); assert.equal(f.stats().calls, 0);
});
for (const [name, change] of [
['plan', f => { f.row.expires_at = new Date(f.now + 30000); }],
['owner', f => { f.connection.owner_valid_until = new Date(f.now + 30000); }],
['policy', f => { f.connection.policy.expiresAt = new Date(f.now + 30000).toISOString(); }],
['profile', f => { f.metadata.serviceInput.persistentProfileAuthority.packet.validUntil = new Date(f.now + 30000).toISOString(); f.row.intended_state.input = structuredClone(f.metadata.serviceInput); }],
['operation', f => { f.row.created_at = new Date(f.now - 90000); }]
]) test(`minimum ${name} expiry is conserved with publication reserve`, async () => { const f = fixture(); change(f); await f.run(); assert.equal(f.stats().options.deadline, f.now + 15000); });
for (const [key, value] of [['approved', false], ['phase', 'recovery'], ['stepIndex', 1], ['operationName', 'operations.recover'], ['operationVersion', 6], ['serviceName', 'routing.community']]) {
test(`wrong live origin ${key} is denied before native factory`, async () => { const f = fixture(); f.metadata[key] = value; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
}
test('outside verified origin cannot supply authority', async () => { const f = fixture(); f.revokeOrigin(); await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
test('persisted intended state must match the live service input', async () => { const f = fixture(); f.row.intended_state.input = {}; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
for (const field of ['evidence', 'payload']) test(`changed randomized sealed ${field} is denied before native`, async () => { const f = fixture(); f.row[field].sealed = 'changed'; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
test('monotonic expiry denies open even with unexpired wall horizon', async () => { const f = fixture(); f.lifetime.monotonicDeadline = performance.now() - 1; await assert.rejects(f.run); assert.equal(f.stats().calls, 0); });
test('late origin change closes the newly opened native owner', async () => { const f = fixture(); f.afterCreate(() => { f.metadata.planId = 'changed'; }); await assert.rejects(f.run); assert.equal(f.stats().closed, 1); });
test('returned native authorize callback remains tied to original metadata', async () => { const f = fixture(); await f.run(); f.metadata.operationId = 'changed'; await assert.rejects(f.stats().options.authorize); });
@@ -0,0 +1,77 @@
import { createHash } from "node:crypto"
import type { QueryConfig } from "pg"
import { pool } from "../../db/index.js"
import { captureVerifiedMcpServiceExecution, inspectVerifiedMcpServiceExecution, inspectVerifiedMcpServiceExecutionLifetime } from "../../services/config-mutation-policy.js"
import { effectiveMcpConnection } from "./store.js"
import { findMcpOperation } from "./registry.js"
import { fingerprint, McpError, redact } from "./security.js"
import { routingProfileAuthorityBindingSchema } from "./routing-profile-authority.js"
import { createRoutingNetworkNativeSession, type RoutingNetworkNativeManifest, type RoutingNetworkSharedContext } from "./routing-network-native-session.js"
const manualEffectOwners = new WeakSet<RoutingNetworkSharedContext>()
/** Only the verified execution factory can mark a genuine shared owner. */
export function isVerifiedManualEffectOwner(owner: RoutingNetworkSharedContext): boolean {
return manualEffectOwners.has(owner)
}
/** Private producer for the existing native/configuration-round reader. The
* manifest selects reads, never identity or authority. Its exact scope, probe
* ownership and whole-session budget are still checked by the native factory.
* Only the original live verified service step can open or use this session.
* No operation/principal/deadline/authorization callback is caller supplied.
* This grants no write, recovery or authenticated log-interval coverage. */
export async function createRoutingNetworkSessionForCurrentExecution(manifest: RoutingNetworkNativeManifest): Promise<Awaited<ReturnType<typeof createRoutingNetworkNativeSession>>> {
const fail = () => new McpError("STATE_CONFLICT", "Нет исходного running scoped execution для native acquisition", 409)
const origin = await captureVerifiedMcpServiceExecution()
const initial = await inspectVerifiedMcpServiceExecution(origin)
if (!initial.approved || initial.phase !== "forward" || initial.stepIndex !== 0 ||
initial.operationName !== "routing.community.switch" || initial.operationVersion !== findMcpOperation(initial.operationName)?.version ||
initial.serviceName !== "routing.community.scoped") throw fail()
const input = initial.serviceInput, manual = input.proofRevision === 2
if (manual && (input.manualEffectPolicy !== "observational-v1" || input.persistentProfileAuthority !== undefined ||
(input.profile as { autoRecovery?: unknown } | undefined)?.autoRecovery !== false ||
(input.originalEffectSelection as { status?: unknown } | undefined)?.status !== "prepared")) throw fail()
const lifetime = await inspectVerifiedMcpServiceExecutionLifetime(origin)
const authorize = async () => {
const current = await inspectVerifiedMcpServiceExecution(origin)
if (fingerprint(current) !== fingerprint(initial)) throw fail()
}
await authorize()
const connection = await effectiveMcpConnection(initial.principal)
await authorize()
// The service lifetime may be narrower than plan expiry, but it does not
// replace the operation-created-at +120s native horizon. Never renew at now.
const query: QueryConfig & { query_timeout: number } = { text: `SELECT o.created_at,p.expires_at,
CASE WHEN octet_length(p.evidence::text)<=1048576 THEN p.evidence END AS evidence,
CASE WHEN octet_length(p.payload::text)<=1048576 THEN p.payload END AS payload,
CASE WHEN octet_length(s.intended_state::text)<=1048576 THEN s.intended_state END AS intended_state FROM mcp_operations o
JOIN mcp_plans p ON p.id=o.plan_id JOIN mcp_operation_steps s ON s.operation_id=o.id
WHERE o.id=$1 AND o.connection_id=$2 AND o.plan_id=$3 AND o.state='running'
AND s.step_index=0 AND s.state='intent' LIMIT 2`,
values: [initial.operationId, initial.principal.connectionId, initial.planId],
query_timeout: Math.max(1, Math.min(10000, lifetime.expiresAt - Date.now())) }
const source = await pool.query(query)
await authorize()
if (source.rows.length !== 1 || !(source.rows[0].created_at instanceof Date) || !(source.rows[0].expires_at instanceof Date)) throw fail()
const sha = (value: string) => createHash("sha256").update(value).digest("hex")
if (typeof source.rows[0].evidence?.sealed !== "string" || typeof source.rows[0].payload?.sealed !== "string" ||
sha(source.rows[0].evidence.sealed) !== initial.sealedEvidenceProof || sha(source.rows[0].payload.sealed) !== initial.sealedPayloadProof) throw fail()
if (fingerprint(source.rows[0].intended_state) !== fingerprint(redact({ kind: "service", service: initial.serviceName, input: initial.serviceInput }))) throw fail()
const authorityExpiry = initial.serviceInput.persistentProfileAuthority === undefined ? Infinity :
Date.parse(routingProfileAuthorityBindingSchema.parse(initial.serviceInput.persistentProfileAuthority).packet.validUntil)
const deadline = Math.min(source.rows[0].created_at.getTime() + 120000, source.rows[0].expires_at.getTime(), lifetime.expiresAt,
Date.parse(initial.principal.credentialExpiresAt), connection.owner_valid_until.getTime(), Date.parse(connection.policy.expiresAt), authorityExpiry)
// Reserve publication/current-authority time for the existing manual result.
// This only narrows the original horizon; it never creates a renewed grant.
const readDeadline = deadline - 15000
if (!Number.isFinite(readDeadline) || Date.now() >= readDeadline || lifetime.signal.aborted || performance.now() >= lifetime.monotonicDeadline) throw fail()
const session = await createRoutingNetworkNativeSession({ sourceOperationId: initial.operationId,
context: { principal: initial.principal, connection, signal: lifetime.signal }, authorize, deadline: readDeadline, manifest })
try {
await authorize()
if (manual) manualEffectOwners.add(session.sharedContext)
return session
}
catch (error) { session.close(); throw error }
}
@@ -15,6 +15,8 @@ assert.equal(target.search, ""); assert.equal(target.hash, "")
assert(process.env.MCP_STATE_DIR && isAbsolute(process.env.MCP_STATE_DIR))
assert.equal(basename(resolve(process.env.MCP_STATE_DIR)), "state")
assert(basename(dirname(resolve(process.env.MCP_STATE_DIR))).startsWith("mm-mcp-test-"))
const originalEffects = process.env.MMAPP_OWN_ROUTING_ORIGINAL_EFFECTS === "true"
assert(!(originalEffects && process.env.MMAPP_OWN_ROUTING_WORKER_REPRO === "true"), "Owned effect acceptance cannot mix with the worker repro profile")
process.env.AUTH_REQUIRED = "true"
process.env.AUTH_JWT_SECRET = "owned-routing-forward-rounds-acceptance-secret"
process.env.AUTH_ISSUER = "https://owned-routing-forward-rounds.test"
@@ -32,7 +34,8 @@ try {
if(process.env.MMAPP_OWN_ROUTING_WORKER_REPRO==="true") {
console.log("Worker fixture repro:",await (await import("./routing-worker-fixture-isolation.sql-helper.js")).acceptRoutingWorkerFixtureIsolation(actor))
}
const result = await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor, process.env.MMAPP_OWN_ROUTING_WORKER_REPRO==="true"?{}:{ forwardRoundsOnly: true })
const result = await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor,
originalEffects ? { originalEffectsOnly: true } : process.env.MMAPP_OWN_ROUTING_WORKER_REPRO==="true"?{}:{ forwardRoundsOnly: true })
assert.equal(result.accepted, true)
} finally {
await (await import("../../services/scheduler.js")).stopSchedulerForDatabaseOperation()
@@ -353,3 +353,21 @@ test("DNS cancellation retains uncancellable lookup capacity and a late answer c
authorize: async () => { if (++checks > 1) throw new SessionError("RESET_REQUIRED", "revoked during DNS") } }), (error: { code?: string }) => error.code === "RESET_REQUIRED")
assert.equal(lookups, 1)
})
test("complete authority fences bound repeated runtime authorization while preserving every native boundary", async () => {
const f = await fixture(), session = await f.create(f.options)
const before = f.stats()
await session.readSnapshot("before")
const after = f.stats(), native = after.nativeCalls - before.nativeCalls, callbacks = after.callbacks - before.callbacks
assert.equal(native, 6)
// Two full fences and the direct dispatch guard stay fresh for every call.
// SQL/file leaves inside those fences must not recursively multiply them.
assert(callbacks >= native * 4)
assert(callbacks <= 50, "Six native reads must fit a bounded number of complete runtime revalidations")
f.afterNative(f.revoke)
await assert.rejects(session.readSnapshot("after-forward"))
assert.equal(f.stats().nativeCalls, after.nativeCalls + 1)
await assert.rejects(session.readSnapshot("after-forward"))
assert.equal(f.stats().nativeCalls, after.nativeCalls + 1)
})
@@ -119,8 +119,16 @@ export async function createRoutingNetworkNativeSession(options: {
try { return await work() }
finally { active(); await trustedAuthorize(); active() }
}
// Primitive source reads keep the immutable lifetime checks. The complete
// authority fence surrounds their batch; native dispatch retains its own
// fresh before/after authorization. Avoid recursively re-running the full
// runtime authorization around every SQL/file leaf inside that same fence.
const bounded = async <T>(work: () => Promise<T>) => {
active()
try { return await work() } finally { active() }
}
const currentAuthority = async () => {
const current = await guarded(() => effectiveMcpConnection(principal))
const current = await bounded(() => effectiveMcpConnection(principal))
if (current.id !== connection.id || current.owner_id !== connection.owner_id || current.generation !== connection.generation ||
fingerprint(current.policy) !== fingerprint(connection.policy) || fingerprint(current.owner_permissions) !== fingerprint(connection.owner_permissions) ||
!hasPermission(current.owner_permissions, "mm:network:read") || !hasPermission(current.policy.permissions, "mm:network:read") ||
@@ -129,13 +137,14 @@ export async function createRoutingNetworkNativeSession(options: {
const sourceRow = async () => {
const query: QueryConfig & { query_timeout: number } = { text: "SELECT o.id,o.state,o.connection_id,o.created_at,p.id AS plan_id,p.operation_id,p.operation_version,p.approved_by,p.expires_at,s.state AS step_state,CASE WHEN octet_length(s.intended_state::text)<=1048576 THEN s.intended_state END AS intended_state,CASE WHEN octet_length(p.evidence::text)<=1048576 THEN p.evidence END AS evidence FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id JOIN mcp_operation_steps s ON s.operation_id=o.id AND s.step_index=0 WHERE o.id=$1 AND o.connection_id=$2 LIMIT 2",
values: [sourceOperationId, principal.connectionId], query_timeout: Math.max(1, Math.min(10000, deadline - Date.now())) }
const result = await guarded(() => pool.query(query))
const result = await bounded(() => pool.query(query))
if (result.rows.length !== 1) throw fail("NOT_FOUND", "Exact source native session недоступен")
const row = result.rows[0]
if (!["running", "applied", "partial"].includes(row.state) || !["intent", "applied", "verified"].includes(row.step_state) || row.operation_id !== "routing.community.switch" || row.operation_version !== findMcpOperation(row.operation_id)?.version || !row.approved_by || !row.evidence?.sealed || !(row.created_at instanceof Date) || !(row.expires_at instanceof Date)) throw fail("STATE_CONFLICT", "Нет совместимого подтверждённого source intent")
return row
}
try {
await guarded(async () => {})
await currentAuthority()
const row = await sourceRow()
if (row.state !== "running" || row.step_state !== "intent") throw fail("STATE_CONFLICT", "Native session требует исходный running intent")
@@ -176,14 +185,14 @@ export async function createRoutingNetworkNativeSession(options: {
if (endpoints.length !== fullScope.length || endpoints.some(endpoint => !endpoint.enabled) || fingerprint(endpoints.map(endpoint => endpoint.id).sort((a,b) => a-b)) !== fingerprint(fullScope)) throw fail("DEVICE_UNAVAILABLE", "Полный источник native manifest недоступен")
const sources = await guarded(() => createRoutingSourceContext(endpoints))
if (fingerprint(sources.expected) !== fingerprint(input.contextSources)) throw fail("STATE_CONFLICT", "Исходный endpoint manifest изменился")
const fence = async () => {
const fence = async () => guarded(async () => {
await currentAuthority()
if (await guarded(() => accessGeneration()) !== generation) throw fail("RESET_REQUIRED", "Access generation native session изменён")
await guarded(() => assertPersistentScopedProfileAuthority(input, signal))
if (await bounded(() => accessGeneration()) !== generation) throw fail("RESET_REQUIRED", "Access generation native session изменён")
await bounded(() => assertPersistentScopedProfileAuthority(input, signal))
const current = await sourceRow()
if (fingerprint({ id: current.id, createdAt: current.created_at, planId: current.plan_id, evidence: current.evidence, expiresAt: current.expires_at, intendedState: current.intended_state }) !== sourceIdentity) throw fail("STATE_CONFLICT", "Исходный native intent изменён")
await guarded(() => sources.assertCurrent())
}
await bounded(() => sources.assertCurrent())
})
const pins = await guarded(() => resolveRoutingNetworkDestinationPins(endpoints, { deadline, signal, authorize: fence }))
await fence()
const clients = routeContextClients(freeze(structuredClone(endpoints)), signal, { deadline, maxCalls: 256, maxBytes: 8 * 1048576, pins })
@@ -17,7 +17,8 @@ const { installLoopbackFixtureAgent } = await import("../../test/loopback-http-a
const { mcpConnectionPolicySchema } = await import("@mmapp/contracts/mcp")
const { routeAiProfileSchema } = await import("@mmapp/contracts/mcp-routing")
const security = await import("./security.js")
const { effectiveMcpConnection } = await import("./store.js")
const { effectiveMcpConnection, authenticateMcpToken } = await import("./store.js")
const { invokeMcpTool } = await import("./runtime.js")
const { createRoutingSourceContext, readRoutingEndpoints } = await import("./routing-observation-source.js")
const { resolveRoutingProfileAuthority } = await import("./routing-profile-authority.js")
await import("./actions.js")
@@ -28,7 +29,7 @@ const { captureRoutingNetworkObservationAssembly } = await import("./routing-net
const { readRoutingNetworkScopedCommunityContext } = await import("./routing-network-scoped-context.js")
type Options = Parameters<typeof createRoutingNetworkNativeSession>[0]
type Mode = "normal" | "zero-ping" | "redirect" | "auth" | "malformed" | "oversized" | "rows" | "total-bytes" | "total-rows"
type Mode = "topology-unknown" | "normal" | "zero-ping" | "redirect" | "auth" | "malformed" | "oversized" | "rows" | "total-bytes" | "total-rows"
let mode: Mode = "normal", onRequest: (() => Promise<void> | void) | undefined
const requests: { method: string; path: string; body: Record<string, string> }[] = []
let responseBytes = 0, writes = 0, sinks = 0
@@ -82,6 +83,7 @@ const router = http.createServer(async (req, res) => {
assert.equal(body.address, "203.0.113.9"); assert.equal(body.interface, "wan1"); assert.equal(body.vrf, "probe"); assert.equal(body["max-hops"], "16")
payload = [{ ".section": "0", address: "203.0.113.9", sent: "3", loss: "0", last: "1ms" }]
} else { writes++; throw Error("Unexpected native mutation") }
if (mode === "topology-unknown" && path === "/routing/route") payload = [{ ...collections(path)[0], "immediate-gw": "17", "bgp.peer-cache-id": "*s1" }]
if (mode === "rows") payload = Array.from({ length: 1001 }, () => ({ version: "7.26" }))
if (mode === "total-rows" && path !== "/system/resource") payload = Array.from({ length: 1000 }, () => ({ value: "fixture" }))
if (mode === "total-bytes") payload = [{ version: "7.26", padding: "x".repeat(600000) }]
@@ -133,6 +135,12 @@ async function source(withProbes = true) {
try {
await applySqlMigrations(pool)
// Other genuine phases may have already created authenticated checkpoints in
// this owned database. Read-only acceptance must preserve every existing row
// and add none; raw SQL JSON also preserves timestamp microseconds that
// conversion to a JavaScript Date would truncate.
const checkpointBaselineRows = (await pool.query("SELECT to_jsonb(c)::text AS inventory_row FROM mcp_operation_checkpoints c ORDER BY operation_id,step_index")).rows
const checkpointBaseline = security.fingerprint(checkpointBaselineRows)
assert.equal((await pool.query("SELECT count(*)::int AS n FROM servers")).rows[0].n, 0)
await pool.query("UPDATE mcp_settings SET enabled=true WHERE id=1")
serverId = Number((await pool.query("INSERT INTO servers(name,host,port,enabled,username,password,use_ssl,verify_ssl) VALUES('owned-native-reader','127.0.0.1',$1,true,'fixture','inert-only',false,true) RETURNING id", [bound.port])).rows[0].id)
@@ -140,9 +148,11 @@ try {
const f = await source(), originalAuthorize = f.options.authorize, session = await createRoutingNetworkNativeSession(f.options), before = responseBytes
const first = await session.readSnapshot("before")
assert.equal(first.securityProof, false); assert.equal(first.logCoverage, "unknown"); assert.equal(first.probes.length, 1)
const stamp = Date.now(), server = first.servers[0]
const server = first.servers[0]
const manifest = { revision: 1, phase: "forward", identity: { operationId: f.operationId, stepIndex: 0, profileId: f.profileId, profileVersion: 1, sourceEpoch: "owned-fixture-epoch", authorityIdentity: "owned-fixture-authority" },
sourceCreatedAt: stamp - 1000, deadline: f.options.deadline, observationStartAt: stamp - 500, maximumSampleAgeMs: 30000, serverIds: [serverId],
// Bind the diagnostic fixture to the actual acquisition interval. A guessed
// lookback after SQL/HTTP completes can put its source after the first read.
sourceCreatedAt: Date.parse(first.startedAt), deadline: f.options.deadline, observationStartAt: Date.parse(first.observedAt), maximumSampleAgeMs: 30000, serverIds: [serverId],
configuration: { serverId, nativeId: "*r1", table: "tenant", bodyIdentity: "not-a-configuration-checkpoint", dependencyIdentity: "not-a-publication-proof" },
routes: [{ key: "route1", serverId, family: "ipv4", prefix: "203.0.113.0/24", table: "tenant", sessionKey: "peer1", nextHop: "192.0.2.1", interface: "wan1", resolverTable: "main", resolutionChain: [{ address: "192.0.2.1", table: "main", interface: "wan1" }] }],
bgp: [{ key: "peer1", serverId, family: "ipv4", connectionId: "*c1", instance: "default", localAddress: "192.0.2.2", remoteAddress: "192.0.2.1", remoteAsn: 65001 }],
@@ -233,9 +243,59 @@ try {
const f = await source(false), session = await createRoutingNetworkNativeSession(f.options); mode = "total-rows"
await session.readSnapshot("before"); await assert.rejects(session.readSnapshot("after-forward")); assert(session.readBudget().rows > 10000)
})
const topologyObserver = async () => {
const f = await source(false), { connectionId, credentialId } = f.options.context.principal
const policy = mcpConnectionPolicySchema.parse({ ...f.options.context.connection.policy, mode: "observer", permissions: ["mm:network:read"],
scope: { serverIds: [serverId!], operationIds: ["routing.topology.read"], allowGlobal: false } })
await pool.query("UPDATE mcp_connections SET policy=$2::jsonb,owner_permissions=$3::jsonb WHERE id=$1", [connectionId, JSON.stringify(policy), JSON.stringify(["mm:network:read"])])
const token = security.randomToken("bearer")
await pool.query("UPDATE mcp_credentials SET digest=$2 WHERE id=$1", [credentialId, security.tokenDigest(token)])
return { principal: await authenticateMcpToken(token), connectionId }
}
await run("actual public topology bearer reader observes final gateway without operation binding or namespace joins", async () => {
const f = await topologyObserver(), count = requests.length, plans = Number((await pool.query("SELECT count(*) AS n FROM mcp_plans")).rows[0].n)
const result = await invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }) as { routeResolution: { status: string; items: Array<{ status: string; immediateGateway: { address: string; interface: string }; semanticProof: boolean; routeOriginVerified: boolean; recursiveChainVerified: boolean }> } }
assert.equal(requests.length - count, 11)
assert.equal(result.routeResolution.status, "observed")
const route = result.routeResolution.items[0]
assert.equal(route.status, "observed"); assert.deepEqual(route.immediateGateway, { address: "192.0.2.1", interface: "wan1" })
assert.equal(route.semanticProof, false); assert.equal(route.routeOriginVerified, false); assert.equal(route.recursiveChainVerified, false)
assert.equal(Number((await pool.query("SELECT count(*) AS n FROM mcp_plans")).rows[0].n), plans)
assert(!JSON.stringify(result).includes("peer-cache-id")); assert(!JSON.stringify(result).includes("bgp.session"))
})
await run("actual public topology preserves unknown numeric gateway despite matching BGP cache/session strings", async () => {
const f = await topologyObserver(); mode = "topology-unknown"
const result = await invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }) as { routeResolution: { items: Array<{ status: string; reason: string }> } }
assert.equal(result.routeResolution.items[0].status, "unknown")
assert.equal(result.routeResolution.items[0].reason, "unconfirmed-immediate-endpoint")
})
await run("actual public topology bearer and server scope/disabled checks refuse before native I/O", async () => {
const f = await topologyObserver(), count = requests.length
await assert.rejects(authenticateMcpToken("not-a-token"))
await assert.rejects(invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId: serverId! + 100000 }))
await pool.query("UPDATE servers SET enabled=false WHERE id=$1", [serverId])
await assert.rejects(invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }))
await pool.query("UPDATE servers SET enabled=true WHERE id=$1", [serverId])
assert.equal(requests.length, count)
})
await run("actual public topology pause endpoint ABA and cancellation retire at first response without publication", async () => {
for (const fault of ["pause", "endpoint-aba", "cancel"] as const) {
const f = await topologyObserver(), count = requests.length, cancel = new AbortController()
onRequest = async () => {
if (fault === "pause") await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}','\"paused\"') WHERE id=$1", [f.connectionId])
else if (fault === "endpoint-aba") {
await pool.query("UPDATE servers SET verify_ssl=NOT verify_ssl WHERE id=$1", [serverId])
await pool.query("UPDATE servers SET verify_ssl=NOT verify_ssl WHERE id=$1", [serverId])
} else cancel.abort()
}
await assert.rejects(invokeMcpTool(f.principal, "mm_routing_topology_get", { serverId }, cancel.signal))
assert.equal(requests.length, count + 1)
}
})
assert.equal(writes, 0)
assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_operation_checkpoints")).rows[0].n, 0, "Read-only acceptance must not manufacture CONFIG_APPLIED or network checkpoints")
console.log("Owned routing native reader SQL/HTTP:", JSON.stringify({ cases: cases.length, names: cases, requests: requests.length, nativeWrites: writes, actualSql: true, actualHttp: true, syntheticEncryptedSourcePlans: true, genuinePrepareAccepted: false, configurationCheckpointFabricated: false, signedNetworkOutcomePublished: false, logCoverage: "unknown", reservedDispatchEnabled: false, realChr: false }))
assert.equal(security.fingerprint((await pool.query("SELECT to_jsonb(c)::text AS inventory_row FROM mcp_operation_checkpoints c ORDER BY operation_id,step_index")).rows), checkpointBaseline,
"Read-only acceptance must preserve existing checkpoints exactly and create none")
console.log("Owned routing native reader SQL/HTTP:", JSON.stringify({ cases: cases.length, names: cases, requests: requests.length, nativeWrites: writes, actualSql: true, actualHttp: true, syntheticEncryptedSourcePlans: true, genuinePrepareAccepted: false, configurationCheckpointFabricated: false, priorCheckpointsPreserved: checkpointBaselineRows.length, checkpointInventoryUnchanged: true, signedNetworkOutcomePublished: false, logCoverage: "unknown", reservedDispatchEnabled: false, realChr: false }))
} finally {
if (profiles.length) await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=ANY($1::uuid[])", [profiles]).catch(() => {})
if (connections.length) await pool.query("UPDATE mcp_connections SET policy=jsonb_set(policy,'{status}','\"paused\"') WHERE id=ANY($1::uuid[])", [connections]).catch(() => {})
@@ -65,6 +65,7 @@ for (const ipv6 of [false, true]) for (const inverse of [false, true]) test(`fix
})
const invalid = {
"source-after-first-read": (f: ReturnType<typeof captured>) => { f.manifest.sourceCreatedAt = Date.parse(f.snapshots[0].startedAt) + 1 },
"foreign-operation": (f: ReturnType<typeof captured>) => { f.snapshots[1].sourceOperationId = "00000000-0000-4000-8000-000000000099" },
"duplicate-server": (f: ReturnType<typeof captured>) => { f.snapshots[1].servers.push(f.snapshots[1].servers[0]) },
"missing-probe": (f: ReturnType<typeof captured>) => { f.snapshots[1].probes = [] },
@@ -14,7 +14,9 @@ import { nativeBgpRows, bgpTemplateReferences } from "../../services/bgp-export-
import { bgpFilterName, effectiveBgpFilter, stableBgpPolicyRow } from "../../services/bgp-filter-binding-model.js"
import { canonicalVrfMembers } from "../../services/vrf-members.js"
import { readOspfEffectiveDisabled } from "../../services/ospf-effective-disabled.js"
import { isConfirmedRoutingFibTable } from "../../services/routing-table-fib.js"
import * as contextModel from "./routing-community-context-model.js"
import { prepareOriginalEffectSelection } from "./routing-original-effects.js"
import { readMeasurementAddress } from "./routing-measurement-address.js"
import { routeContextClients } from "./routing-community-context-native.js"
import { pathProofTag } from "./path-proof-purpose.js"
@@ -65,9 +67,8 @@ async function fixture(autoRecovery = false) {
afterHttp?.(); afterHttp = undefined
if (afterPath?.path === path) { const run = afterPath.run; afterPath = undefined; run() }
})
await new Promise<void>(resolve => router.listen(0, "127.0.0.1", resolve))
const now = Date.now(), fingerprint = (value: unknown) => createHash("sha256").update(JSON.stringify(value)).digest("hex")
const endpoint = { id: 1, host: "127.0.0.1", port: (router.address() as { port: number }).port, username: "fixture", password: "private", useSsl: false, verifySsl: true, enabled: true, routingEndpointEpoch: randomUUID() }
const endpoint = { id: 1, host: "127.0.0.1", port: 0, username: "fixture", password: "private", useSsl: false, verifySsl: true, enabled: true, routingEndpointEpoch: randomUUID() }
const principal = { connectionId: randomUUID(), credentialId: randomUUID(), credentialExpiresAt: new Date(now + 600000).toISOString() }
const connection = { id: principal.connectionId, owner_id: "owner", generation: "generation", owner_permissions: ["mm:network:read"],
policy: { permissions: ["mm:network:read"], scope: { serverIds: [1] }, expiresAt: new Date(now + 600000).toISOString() }, owner_valid_until: new Date(now + 600000) }
@@ -92,6 +93,7 @@ async function fixture(autoRecovery = false) {
"./routing-community-context-native.js": { routeContextClients }, "../../services/vrf-members.js": { canonicalVrfMembers }, "../../services/ospf-effective-disabled.js": { readOspfEffectiveDisabled },
"./routing-measurement-address.js": { readMeasurementAddress } }
await loadSource("./routing-community-context.ts", { ...common, "./routing-network-native-session.js": native,
"../../services/routing-table-fib.js": { isConfirmedRoutingFibTable }, "./routing-original-effects.js": { prepareOriginalEffectSelection },
"../../services/bgp-export-model.js": { nativeBgpRows, bgpTemplateReferences }, "../../services/bgp-filter-binding-model.js": { bgpFilterName, effectiveBgpFilter, stableBgpPolicyRow },
"./routing-community-context-model.js": contextModel, "./path-proof-purpose.js": { pathProofTag },
"./management-transport.js": { assertManagementTransport: async () => { throw Error("No alternate management fixture") } } }, context)
@@ -103,14 +105,23 @@ async function fixture(autoRecovery = false) {
await loadSource("./routing-network-scoped-context.ts", { "./routing-network-native-session.js": native }, privateApi)
const create = native.createRoutingNetworkNativeSession as (options: unknown) => Promise<Session>
const abort = new AbortController()
const session = await create({ sourceOperationId: row.id, context: { principal, connection, signal: abort.signal }, authorize: authority, deadline: now + 119000, manifest: { servers: [1], probes: [] } })
await new Promise<void>(resolve => router.listen(0, "127.0.0.1", resolve))
endpoint.port = (router.address() as { port: number }).port
const closeRouter = () => {
router.closeAllConnections()
return new Promise<void>(resolve => router.close(() => resolve()))
}
const session = await create({ sourceOperationId: row.id, context: { principal, connection, signal: abort.signal }, authorize: authority, deadline: now + 119000, manifest: { servers: [1], probes: [] } }).catch(async error => {
await closeRouter()
throw error
})
const readContext = privateApi.readRoutingNetworkScopedCommunityContext as (token: Token) => Promise<Context>
return { session, readContext: () => readContext(session.sharedContext), forged: () => readContext(JSON.parse(JSON.stringify(session.sharedContext))),
readResource: native.readRoutingNetworkSharedResource as (token: Token, server: number, path: string, body?: Record<string, string>) => Promise<unknown>,
bypassTail: context.readRouteCommunityContextWithSharedNativeSession as (token: Token) => Promise<unknown>,
counters: () => ({ requests, wireBytes, wireRows, rawGetObjects }), bulk: (value: number) => { bulk = value }, padding: (value: number) => { padding = value },
revoke: () => { revoked = true }, abort: () => abort.abort(), afterHttp: (hook: () => void) => { afterHttp = hook }, afterPath: (path: string, hook: () => void) => { afterPath = { path, run: hook } },
cleanup: async () => { session.close(); router.closeAllConnections(); await new Promise<void>(resolve => router.close(() => resolve())) } }
cleanup: async () => { session.close(); await closeRouter() } }
}
test("actual source context and snapshots share one pinned HTTP request/byte/row budget including object /get", async () => {
@@ -0,0 +1,55 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
import test from 'node:test';
const require=createRequire(new URL('../../../package.json',import.meta.url)),ts=require('typescript'),{z}=require('zod');
const code=ts.transpileModule(await readFile(new URL('./routing-community-scoped-service.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText;
async function fixture(mode='prepared',failure){
const calls=[],principal={connectionId:randomUUID(),credentialId:randomUUID(),credentialExpiresAt:new Date(Date.now()+120000).toISOString()},connection={id:principal.connectionId,owner_id:'owner',generation:'generation',owner_permissions:['mm:filters:write','mm:network:read'],policy:{mode:'copilot',permissions:['mm:filters:write','mm:network:read'],scope:{serverIds:[1]},expiresAt:new Date(Date.now()+120000).toISOString()},owner_valid_until:new Date(Date.now()+120000)};
const profile={serverId:1,family:'ipv4',table:'tenant',probeVrf:'probe',community:'65000:10',autoRecovery:false,candidates:[{id:'a',jumpHostId:1,exitNodeId:1,gateway:'192.0.2.8',gatewayRef:{resource:'ipv4_route',id:'*r0'},controlProbe:{}},{id:'b',jumpHostId:1,exitNodeId:1,gateway:'192.0.2.9',gatewayRef:{resource:'ipv4_route',id:'*r'},controlProbe:{}}],excludedCandidateIds:[],pinnedCandidateId:null};
const input={serverId:1,profile,candidateId:'b',principal,ownerId:'owner',policyProof:JSON.stringify(connection.policy),connectionGeneration:'generation',generation:'access',proofRevision:3,originalCandidateId:'a',nativeId:'*1',outIface:'',originalRuleCiphertext:'original',desiredRuleCiphertext:'desired',desiredTargetProof:'desired-proof',beforeRules:[],receiptId:randomUUID()};
if(mode!=='legacy')input.originalEffectSelection=mode==='prepared'?{status:'prepared',probe:{key:'b',serverId:1,destination:'198.51.100.20',source:'198.51.100.10',interface:'wan',probeVrf:'probe'}}:{status:'unknown',reason:'unsupported-source'};
let wrote=false,handler,network,foreign=false,reads=0,rounds=0,closed=0,ownerClosed=false,nativeCalls=100;
const unknown=reason=>({scope:'prepared-route-fib-adjacency-control-effects',status:'unknown',reason,rounds:[],logCoverage:'unknown',semanticProof:false,forwardingVerified:false,autoRecoveryAllowed:false});
const report={...unknown(null),status:'observed-expected',rounds:[{controlProbe:'passed'},{controlProbe:'passed'}]};
const session={sharedContext:{},readSnapshot:async stage=>{calls.push('baseline:'+stage);return {};},readConfigurationBoundForwardSnapshot:async()=>{calls.push('round:'+(++rounds));if(failure==='round'||failure==='round-closed'){ownerClosed=failure==='round-closed';throw Error('unknown');}return {};},readBudget:()=>({deadline:Date.now()+10000,calls:nativeCalls,bytes:1000,rows:50,closed:ownerClosed}),close:()=>{closed++;ownerClosed=true;calls.push('close-session');}};
const providers={
'node:crypto':{randomUUID},zod:{z},'@mmapp/contracts/mcp-routing':{routeAiProfileSchema:{parse:x=>x}},'../../db/index.js':{pool:{query:async sql=>({rows:String(sql).includes('FROM mcp_path_checks')?[{id:randomUUID(),connection_id:principal.connectionId,status:'passed',created_at:new Date(),evidence:{}}]:[]})}},
'../../lib/permissions.js':{hasPermission:(list,value)=>list.includes(value)},
'../../services/config-mutation-policy.js':{assertVerifiedConfigurationAccess:async()=>{},executeGuardedConfigurationWrite:async(_sid,_method,_path,work)=>{calls.push('guarded-intent');return work();}},
'../../services/config-mutation-lock.js':{withApplicationMutationLock:work=>work(),withConfigMutationLock:(_ids,work)=>work()},
'./store.js':{effectiveMcpConnection:async()=>connection,audit:async()=>{}},
'./security.js':{accessGeneration:async()=>'access',decryptSecret:async x=>x,encryptSecret:async x=>x,fingerprint:JSON.stringify,McpError:Error,privateSecretFingerprint:async()=>'seal',redact:x=>x},
'./routing-community-scoped-checkpoint.js':{saveScopedConfigurationCheckpoint:async()=>{calls.push('CONFIG_APPLIED');},loadScopedConfigurationCheckpoint:async()=>({checkpointProof:'configuration'}),saveScopedNetworkCheckpoint:async(_input,_seal,check,originalEffects)=>{calls.push('save-network');network={status:check.status,networkSeal:'network',...(originalEffects?{originalEffects}:{})};},loadScopedNetworkCheckpoint:async()=>network},
'./routing-community-scoped-request.js':{assertScopedCommunityRequest:async()=>'request'},'./management-transport.js':{prepareManagementTransportBinding:async()=>({})},
'./routing-community-context.js':{readRouteCommunityContext:async()=>{reads++;return {before:{scopedExecutionAvailable:true,currentCandidateId:wrote?'b':'a',dependencyProof:'dep',sourceProofs:[{serverId:1,proof:'source'}]},ownedTarget:{nativeId:'*1',rawRow:{rule:wrote?'desired':'original'},endpoint:{},desiredTargetProof:()=> 'desired-proof'},fence:async()=>{},clients:new Map([[1,{get:async()=>[{'.id':'*r'}]}]]),captureOriginalEffectSelection:async()=>{calls.push('capture-prepared-selection');return input.originalEffectSelection;}};}},
'./routing-community-context-model.js':{candidateMatchesGateway:()=>true},'./routing-model.js':{rankRoutingCandidates:()=>profile.candidates.map(candidate=>({candidate,eligible:true}))},'./routing-observations.js':{readRoutingObservations:async()=>[]},
'./path-verification.js':{requireRecentPath:async()=>'path-check',checkCandidatePath:async()=>{calls.push('legacy-probe');return {id:randomUUID(),candidateId:'b',status:'passed',observedAt:new Date().toISOString()};}},
'./routing-community-scoped-model.js':{compileScopedCommunitySwitch:()=>({compiledBody:'desired',proposed:{community:'65000:10',gateway:'192.0.2.9',outIface:''},before:{community:'65000:10',gateway:'192.0.2.8',outIface:''}}),scopedConfigurationMatches:()=>({configurationMatchesOriginal:!wrote,configurationMatchesDesired:wrote&&!foreign})},
'./routing-community-scoped-native.js':{patchScopedCommunity:async()=>{calls.push('PATCH');wrote=true;}},'./service-registry.js':{registerMcpService:(_name,value)=>{handler=value;}},
'./routing-profile-authority.js':{assertPersistentScopedProfileAuthority:async()=>{},resolveRoutingProfileAuthority:async()=>{},routingProfileAuthorityBindingSchema:{parse:x=>x},scopedProfileAuthorityTransport:async()=>({})},
'./routing-observation-source.js':{createRoutingSourceContext:async()=>({}),readRoutingEndpoints:async()=>[]},'./routing-reservation-store.js':{createRoutingReservationStore:()=>({})},
'./routing-network-execution-session.js':{createRoutingNetworkSessionForCurrentExecution:async options=>{calls.push('open-session');assert.deepEqual(Object.keys(options).sort(),['probes','servers']);if(failure==='open')throw Error('unknown');return session;}},
'./routing-network-configuration-bridge.js':{resolveRoutingNetworkForwardRoundConfigurationContext:async()=>{calls.push('configuration-round-owner');return {};},closeRoutingNetworkForwardRoundConfigurationContext:()=>{calls.push('close-round-owner');}},
'./routing-network-scoped-context.js':{readRoutingNetworkScopedCommunityContext:async()=>{calls.push('shared-publication');if(failure==='publication')throw Error('DO_NOT_EXPOSE_TEST_DETAIL');nativeCalls+=10;return{before:{dependencyProof:'dep',targetRuleProof:'desired-proof',currentCandidateId:'b'},fence:async()=>{}};}},
'./routing-original-effects.js':{originalEffectSelectionSchema:{safeParse:x=>x?{success:true,data:x}:{success:false}},unknownOriginalEffects:unknown,originalEffectBaselineMatches:()=>failure!=='baseline',inspectOriginalEffectRounds:async()=>{calls.push('inspect-effects');if(failure==='foreign')foreign=true;return report;}}
};
const exports={};new Function('exports','require',code)(exports,name=>{assert.ok(providers[name],name);return providers[name];});exports.registerScopedCommunityService();
return {calls,input,run:()=>handler.execute(input,{operationId:randomUUID(),phase:'forward',stepIndex:0}),prepare:()=>exports.prepareScopedCommunitySwitch({profile,candidateId:'b'},{principal,connection}),stats:()=>({wrote,closed,reads}),network:()=>network};
}
test('actual service consumer baselines before the single PATCH and persists separate effects after genuine CONFIG_APPLIED and manual probe',async()=>{const f=await fixture(),result=await f.run();const c=f.calls;assert(c.indexOf('baseline:before')<c.indexOf('PATCH'));assert(c.indexOf('CONFIG_APPLIED')<c.indexOf('configuration-round-owner'));assert(c.indexOf('legacy-probe')<c.indexOf('round:1'));assert(c.indexOf('round:2')<c.indexOf('shared-publication'));assert(c.indexOf('shared-publication')<c.indexOf('save-network'));assert.equal(c.filter(x=>x==='PATCH').length,1);assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);assert.equal(result.forwardingVerified,false);assert.equal(result.originalEffects.status,'observed-expected');assert.equal(result.originalEffects.budget.calls,110);assert.equal(f.stats().closed,1);assert.deepEqual(result.originalEffects,f.network().originalEffects);});
for(const mode of ['legacy','unknown'])test(`existing ${mode} manual path does not open native effect session`,async()=>{const f=await fixture(mode),result=await f.run();assert(!f.calls.includes('open-session'));assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);assert.equal(f.calls.filter(x=>x==='PATCH').length,1);});
for(const failure of ['open','baseline','round','round-closed','publication'])test(`optional ${failure} uncertainty preserves actual manual config/control receipt with effects unknown`,async()=>{const f=await fixture('prepared',failure),result=await f.run();assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);assert.equal(result.originalEffects.status,'unknown');assert.equal(f.calls.filter(x=>x==='PATCH').length,1);assert(f.calls.includes('save-network'));});
test('foreign configuration drift after optional effect observations still refuses publication, never repeats PATCH',async()=>{const f=await fixture('prepared','foreign');await assert.rejects(f.run);assert.equal(f.calls.filter(x=>x==='PATCH').length,1);assert(!f.calls.includes('save-network'));assert.equal(f.stats().closed,1);});
test('revision2 remains on existing manual path even with a prepared-like field',async()=>{const f=await fixture();f.input.proofRevision=2;await f.run();assert(!f.calls.includes('open-session'));});
test('sealed manual policy runs read-only rounds through the real scoped consumer',async()=>{const f=await fixture();f.input.proofRevision=2;f.input.manualEffectPolicy='observational-v1';const result=await f.run();assert.equal(f.calls.filter(x=>x==='PATCH').length,1);assert(f.calls.includes('round:2'));assert.equal(result.originalEffects.status,'observed-expected');});
test('actual preparation consumes the required real-context selection method and includes it in sealed service input',async()=>{const f=await fixture(),evidence=await f.prepare();assert(f.calls.includes('capture-prepared-selection'));assert.deepEqual(evidence.steps[0].input.originalEffectSelection,f.input.originalEffectSelection);assert.equal(evidence.steps[0].input.requestProof,'seal');assert(!f.calls.includes('PATCH'));});
test('closed effect owner falls back to exactly the old final context read without bypassing publication guard',async()=>{const legacy=await fixture('legacy');await legacy.run();const f=await fixture('prepared','round-closed');await f.run();assert(!f.calls.includes('shared-publication'));assert.equal(f.stats().reads,legacy.stats().reads);});
test('optional publication error exposes only a bounded failure category', async()=>{
const f=await fixture('prepared','publication'), result=await f.run();
assert.equal(result.originalEffects.reason,'effect-publication-revalidation-unavailable:STATE_UNAVAILABLE');
assert(!JSON.stringify(result).includes('DO_NOT_EXPOSE_TEST_DETAIL'));
assert.equal(result.configurationVerified,true);assert.equal(result.controlProbeVerified,true);
});
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import { randomUUID, createHash } from 'node:crypto';
import test from 'node:test';
const require=createRequire(new URL('../../../package.json',import.meta.url)),ts=require('typescript'),{z}=require('zod');
const hash=value=>createHash('sha256').update(JSON.stringify(value)).digest('hex'),sign=async(value,purpose)=>hash([purpose,value]);
const code=ts.transpileModule(await readFile(new URL('./routing-controller-baseline.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText;
const effectCode=ts.transpileModule(await readFile(new URL('./routing-original-effects.ts',import.meta.url),'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,effectExports={};
new Function('exports','require',effectCode)(effectExports,name=>{if(name==='node:net')return require('node:net');if(name==='zod')return{z};if(name==='./security.js')return{fingerprint:hash};if(name==='./routing-community-context-model.js')return{};throw Error(name);});
const preparedValue={status:'prepared',revision:1,serverId:1,routeSelectionScope:'table-afi-community-observation',candidateId:'b',family:'ipv4',table:'tenant',community:'65000:10',expectedGateway:'192.0.2.9',expectedImmediate:{address:'198.51.100.2',interface:'ether2'},binding:{connectionId:'*c',sessionId:'*s',instance:'default',table:'tenant',vrf:'main',localAddress:'192.0.2.1',remoteAddress:'192.0.2.2',remoteAsn:65001,afi:'ip'},bfd:{applicability:'not-configured',evidence:'explicit-use-bfd-false'},routes:[{nativeId:'*r',prefix:'203.0.113.0/24',originalGateway:'192.0.2.8'}],probe:{key:'b',serverId:1,destination:'198.51.100.20',source:'198.51.100.10',interface:'wan',probeVrf:'probe',expectedHops:['198.51.100.20']}};
async function fixture(selection='absent',effectStatus='observed-expected',switched=true){
const principal={connectionId:randomUUID(),credentialId:randomUUID(),credentialExpiresAt:new Date(Date.now()+120000).toISOString()},profileId=randomUUID(),planId=randomUUID(),operationId=randomUUID(),grant=new Date(Date.now()+60000).toISOString();
const profile={serverId:1,table:'tenant',probeVrf:'probe',candidates:[{id:'a',jumpHostId:1,exitNodeId:1},{id:'b',jumpHostId:1,exitNodeId:1}]},perms=['mm:network:read','mm:network:write','mm:filters:write'];
const connection={id:principal.connectionId,owner_id:'owner',owner_permissions:perms,owner_valid_until:new Date(Date.now()+120000),owner_claims_issued_at:new Date(Date.now()-1000),generation:'access',policy:{status:'active',mode:'copilot',permissions:perms,scope:{serverIds:[1],operationIds:['routing.analyze','routing.community.switch','routing.path.verify']},expiresAt:new Date(Date.now()+120000).toISOString()}};
const context={principal,connection},sourceProofs=[{serverId:1,proof:'c'.repeat(64)}],dependency='d'.repeat(64),originalTarget='a'.repeat(64),desiredTarget='b'.repeat(64),checkpointProof='e'.repeat(64),networkSeal='f'.repeat(64),requestSeal='1'.repeat(64);
let advancing=false,nativeReads=0,row;
const input={proofRevision:3,profile,principal,ownerId:'owner',nativeId:'*1',originalCandidateId:'a',originalTargetProof:originalTarget,desiredTargetProof:desiredTarget,dependencyProof:dependency,contextSources:sourceProofs,candidateId:'b',receiptId:randomUUID(),persistentProfileAuthority:{packet:{profileId,profileVersion:1}}};
if(selection!=='absent')input.originalEffectSelection=selection?.status==='prepared'&&Object.keys(selection).length===1?structuredClone(preparedValue):selection;
const packet={revision:2,operationId,stepIndex:0,nativeId:'*1',originalCandidateId:'a',originalTargetProof:originalTarget,dependencyProof:dependency,contextSources:sourceProofs,desiredCandidateId:'b',desiredTargetProof:desiredTarget,desiredRule:'desired'};
const network={status:'passed',networkSeal,...(effectStatus==='missing'?{}:{originalEffects:{status:effectStatus,switchEffectObserved:switched,selectionIdentity:hash(input.originalEffectSelection??null),transition:{changedPrefixes:switched?['203.0.113.0/24']:[]}}})};
const providers={
'./routing-main-transition.js':{},'../../db/index.js':{pool:{query:async()=>({rows:[row]})}},'./registry.js':{findMcpOperation:()=>({version:7})},
'./routing-community-scoped-request.js':{assertScopedCommunityRequest:async()=>requestSeal},'./routing-profile-authority.js':{assertPersistentScopedProfileAuthority:async()=>{},routingProfileAuthorityBindingSchema:{parse:x=>x}},
'./routing-community-scoped-checkpoint.js':{loadScopedConfigurationPacket:async()=>({packet,checkpointProof,observedAt:new Date(Date.now()-500).toISOString()}),loadScopedNetworkCheckpoint:async()=>network},
zod:{z},'@mmapp/contracts/mcp-routing':{routeAiProfileSchema:{parse:x=>x}},'../../lib/permissions.js':{hasPermission:(list,value)=>list.includes(value)},'./store.js':{effectiveMcpConnection:async()=>connection},
'./security.js':{accessGeneration:async()=>'access',constantEqual:(a,b)=>a===b,decryptSecret:async x=>x,fingerprint:hash,redact:x=>x,McpError:Error,privateSecretFingerprint:sign},
'./routing-community-context.js':{readRouteCommunityContext:async()=>{nativeReads++;return {before:{scopedExecutionAvailable:true,currentCandidateId:advancing?'b':'a',targetRuleProof:advancing?desiredTarget:originalTarget,dependencyProof:dependency,sourceProofs,nativeRuleId:'*1'},ownedTarget:{nativeId:'*1',rawRow:{rule:advancing?'desired':'original'}},fence:async()=>{}};}},
'./routing-original-effects.js':{originalEffectSelectionSchema:effectExports.originalEffectSelectionSchema}
};
const exports={};new Function('exports','require',code)(exports,name=>{assert.ok(providers[name],name);return providers[name];});
const baseline=await exports.prepareRoutingControllerBaseline(profileId,1,profile,grant,context);advancing=true;
const step={kind:'service',service:'routing.community.scoped',input};
row={id:operationId,plan_id:planId,state:'applied',step_state:'verified',operation_id:'routing.community.switch',operation_version:7,approved_by:'owner',created_at:new Date(Date.now()-1000),evidence:{sealed:JSON.stringify({steps:[step],target:{scope:'servers',serverIds:[1]}})},intended_state:step};
row.result={nativeVerificationSeal:await sign(hash({request:requestSeal,dependency,desired:desiredTarget,candidate:'b',configuration:checkpointProof,network:networkSeal}),'route-ai-scoped-verified-v3'),configurationCheckpointProof:checkpointProof,networkCheckpointProof:networkSeal,receiptId:input.receiptId,proofRevision:3,nativeWrites:1,configurationVerified:true,postProbeStatus:'passed',controlProbeVerified:true};
return {advance:()=>exports.advanceRoutingControllerBaseline(baseline,profileId,1,profile,grant,context,planId,operationId),nativeReads:()=>nativeReads,row,network};
}
test('actual controller preserves old advancement only when new selection field is absent',async()=>{const f=await fixture();assert.equal((await f.advance()).packet.candidateId,'b');assert.equal(f.nativeReads(),2);});
for(const [name,selection]of[['unknown',{status:'unknown',reason:'unsupported'}],['malformed',{}],['malformed prepared',{status:'prepared',revision:2}],['null',null],['array',[]]])test(`actual controller rejects present ${name} before desired native read`,async()=>{const f=await fixture(selection);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);assert.equal(f.row.result.configurationVerified,true);assert.equal(f.row.result.controlProbeVerified,true);});
for(const status of ['unknown','observed-other','missing'])test(`actual controller rejects prepared selection with ${status} durable effect result`,async()=>{const f=await fixture({status:'prepared'},status);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
test('actual controller rejects matched poststate without an observed selected FIB transition',async()=>{const f=await fixture({status:'prepared'},'observed-expected',false);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
test('actual controller advances after durable expected selected FIB transition',async()=>{const f=await fixture({status:'prepared'});assert.equal((await f.advance()).packet.candidateId,'b');assert.equal(f.nativeReads(),2);});
test('actual controller rejects effect vector belonging to a different prepared selection',async()=>{const f=await fixture({status:'prepared'});f.network.originalEffects.selectionIdentity='0'.repeat(64);await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
test('actual controller rejects transition flag without an actual changed selected prefix',async()=>{const f=await fixture({status:'prepared'});f.network.originalEffects.transition.changedPrefixes=[];await assert.rejects(f.advance);assert.equal(f.nativeReads(),1);});
@@ -0,0 +1,51 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import { isIP } from 'node:net';
import { randomUUID, createHash } from 'node:crypto';
const require=createRequire(new URL('../../../package.json',import.meta.url)), ts=require('typescript'),{z}=require('zod');
const source=await readFile(new URL('./routing-original-effects.ts',import.meta.url),'utf8');
const exports={};
const gateway=value=>{const match=/^([^@%\s,]+)(?:%([^@%\s,]+))?(?:@([^@%\s,]+))?$/.exec(value);if(!match||!isIP(match[1]))throw Error();return {address:match[1],family:isIP(match[1]),interface:match[2]??null,resolverTable:match[3]??'main'};};
const providers={ 'node:net':{isIP},'node:crypto':{randomUUID},zod:{z},'./routing-community-context-model.js':{routeGatewayContext:gateway},'./security.js':{fingerprint:value=>createHash('sha256').update(JSON.stringify(value)).digest('hex')},
'./path-verification.js':{inspectControlPing:rows=>{const row=rows.at(-1),sent=Number(row.sent),received=Number(row.received);return {confirmedFailure:sent===3&&received===0,ping:{measured:sent===3&&Number.isInteger(received)&&received>=0&&received<=3,loss:(sent-received)/sent*100}};},
inspectTrace:(rows,destination,expected)=>({verified:Array.isArray(rows)&&rows.at(-1)?.address===destination&&expected.every(x=>rows.some(r=>r.address===x))})}};
new Function('exports','require',ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText)(exports,name=>{assert.ok(providers[name],name);return providers[name];});
function fixture(){
const profile={serverId:1,family:'ipv4',table:'tenant',probeVrf:'probe',community:'65000:10',candidates:[{id:'b',wanInterface:'wan',gateway:'192.0.2.9',gatewayRef:{resource:'ipv4_route',id:'*9'},controlProbe:{targetInterface:'target'}}]};
const binding={connectionId:'*c',instance:'default',table:'tenant',vrf:'main'};
const connection={'.id':'*c',instance:'default','routing-table':'tenant',vrf:'main','local.address':'192.0.2.1','remote.address':'192.0.2.2','remote.as':'65001',afi:'ip',templates:'',disabled:'false','use-bfd':'false'};
const session={...connection,'.id':'*s','local.afi':'ip','remote.afi':'ip',established:'true'};
const route={'.id':'*r',afi:'ip4','routing-table':'tenant','dst-address':'203.0.113.0/24',bgp:'true','bgp.communities':'65000:10',gateway:'192.0.2.8',active:'true',contribution:'active',ecmp:'false','immediate-gw':'198.51.100.1%ether1'};
const raw={connections:[connection],sessions:[session],routes:[route],candidate:{'.id':'*9','immediate-gw':'198.51.100.2%ether2'},probe:{source:'198.51.100.10',destination:'198.51.100.20',expected:['198.51.100.20']}};
const selection=exports.prepareOriginalEffectSelection(profile,'b',binding,raw);
const sample=()=>({observedAt:new Date().toISOString(),servers:[{serverId:1,routes:{payload:[{...route,gateway:'192.0.2.9','immediate-gw':'198.51.100.2%ether2'}]},bgp:{payload:[{...session}],connectionsPayload:[{...connection}]},logs:{payload:[{topics:'route,info',message:'bounded diagnostic'}]}}],probes:[{key:'b',pingPayload:[{sent:'3',received:'3','packet-loss':'0'}],tracePayload:[{address:'198.51.100.20'}]}]});
const baseline=sample();baseline.servers[0].routes.payload=structuredClone(raw.routes);
return {profile,binding,raw,selection,sample,baseline,prepare:()=>exports.prepareOriginalEffectSelection(profile,'b',binding,raw),inspect:samples=>exports.inspectOriginalEffectRounds(selection,samples,baseline)};
}
test('actual captured predicate seals BGP destinations separately from static resolver ID',()=>{const f=fixture();assert.equal(f.selection.status,'prepared');assert.equal(f.selection.routes[0].nativeId,'*r');assert.notEqual(f.selection.routes[0].nativeId,f.raw.candidate['.id']);assert.equal(f.selection.binding.sessionId,'*s');assert.equal('sessionKey'in f.selection.routes[0],false);});
test('two real effect captures can observe expected FIB/adjacency/probe while logcoverage and stronger claims stay false',async()=>{const f=fixture(),result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'observed-expected');assert.equal(result.logCoverage,'unknown');assert.equal(result.routeOriginVerified,false);assert.equal(result.recursiveChainVerified,false);assert.equal(result.forwardingVerified,false);assert.equal(result.autoRecoveryAllowed,false);assert.equal(result.rounds[0].relatedTopicRows,1);});
for(const [name,change]of[
['missing affected BGP route',f=>{f.raw.routes=[];}],['ambiguous prefix',f=>{f.raw.routes.push({...f.raw.routes[0],'.id':'*r2'});}],
['connected resolver is not affected BGP route',f=>{f.raw.routes[0].bgp='false';f.raw.routes[0].connect='true';}],
['unresolved inheritance',f=>{f.raw.connections[0].templates='template';}],['BFD not explicitly known',f=>{delete f.raw.connections[0]['use-bfd'];}],
['router-selected source retained as unsupported effect subset',f=>{delete f.raw.probe.source;}],['mixed session identity',f=>{f.raw.sessions.push({...f.raw.sessions[0],'.id':'*s2'});}],
['noncanonical AFI prefix',f=>{f.raw.routes[0]['dst-address']='2001:db8::/64';}]
])test(`prepared ambiguity/unsupported remains unknown: ${name}`,()=>{const f=fixture();change(f);assert.equal(f.prepare().status,'unknown');});
test('baseline binds saved route IDs/prefix/gateway and does not infer operation UUID',()=>{const f=fixture(),base=f.sample();base.servers[0].routes.payload=structuredClone(f.raw.routes);assert.equal(exports.originalEffectBaselineMatches(f.selection,base),true);base.servers[0].routes.payload[0]['.id']='*different';assert.equal(exports.originalEffectBaselineMatches(f.selection,base),false);});
for(const [name,change,expected]of[
['unexpected immediate next-hop',s=>{s.servers[0].routes.payload[0]['immediate-gw']='198.51.100.3%ether3';},'observed-other'],
['unchanged connected route',s=>{s.servers[0].routes.payload[0].bgp='false';s.servers[0].routes.payload[0].connect='true';},'unknown'],
['same prefix competition',s=>{s.servers[0].routes.payload.push({...s.servers[0].routes.payload[0],'.id':'*other'});},'unknown'],
['new community target scope',s=>{s.servers[0].routes.payload.push({...s.servers[0].routes.payload[0],'.id':'*other','dst-address':'203.0.114.0/24'});},'unknown'],
['reconnected session ID',s=>{s.servers[0].bgp.payload[0]['.id']='*new';},'unknown'],
['observed adjacency down',s=>{s.servers[0].bgp.payload[0].established='false';},'observed-other'],
['BFD applicability changed',s=>{s.servers[0].bgp.connectionsPayload[0]['use-bfd']='true';},'unknown'],
['unreachable route',s=>{s.servers[0].routes.payload[0].unreachable='true';},'unknown'],
['terminal control loss',s=>{s.probes[0].pingPayload=[{sent:'3',received:'0','packet-loss':'100'}];},'observed-other']
])test(`effect observation: ${name}`,async()=>{const f=fixture(),s=f.sample();change(s);const result=await f.inspect([s,structuredClone(s)]);assert.equal(result.status,expected);assert.equal(result.forwardingVerified,false);});
test('one round is not upgraded to two',async()=>{const f=fixture();assert.equal((await f.inspect([f.sample()])).status,'unknown');});
test('unchanged expected poststate is not labelled a switch effect',async()=>{const f=fixture();f.baseline.servers[0].routes.payload[0].gateway=f.selection.expectedGateway;f.baseline.servers[0].routes.payload[0]['immediate-gw']='198.51.100.2%ether2';f.selection.routes[0].originalGateway=f.selection.expectedGateway;const result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'unknown');assert.equal(result.switchEffectObserved,false);assert.deepEqual(result.transition.changedPrefixes,[]);assert.equal(result.reason,'poststate-matched-without-selected-fib-transition');});
test('actual selected destination gateway/final endpoint transition is recorded separately from origin',async()=>{const f=fixture(),result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'observed-expected');assert.equal(result.switchEffectObserved,true);assert.deepEqual(result.transition.changedPrefixes,['203.0.113.0/24']);assert.equal(result.transition.baselineObservedAt,f.baseline.observedAt);assert.equal(result.routeOriginVerified,false);});
test('logical gateway change with unchanged final forwarding endpoint is not a FIB switch',async()=>{const f=fixture();f.baseline.servers[0].routes.payload[0]['immediate-gw']='198.51.100.2%ether2';const result=await f.inspect([f.sample(),f.sample()]);assert.equal(result.status,'unknown');assert.equal(result.switchEffectObserved,false);assert.equal(result.routeSelectionScope,'table-afi-community-observation');assert.equal(result.transitionAttribution,'unproven');assert.deepEqual(result.transition.changedPrefixes,[]);});
@@ -0,0 +1,174 @@
import { isIP } from "node:net"
import { z } from "zod"
import type { RouteAiProfile } from "@mmapp/contracts/mcp-routing"
import { routeGatewayContext } from "./routing-community-context-model.js"
import { fingerprint } from "./security.js"
type Row = Record<string, unknown>
const id = z.string().regex(/^\*[a-z0-9]+$/i).max(100)
const text = z.string().min(1).max(200)
const ip = text.refine(value => isIP(value) !== 0)
const flag = (value: unknown): boolean | undefined => [true, "true", "yes"].includes(value as string | boolean) ? true : [false, "false", "no"].includes(value as string | boolean) ? false : undefined
const communities = (row: Row) => Array.isArray(row["bgp.communities"]) ? row["bgp.communities"] as unknown[] : typeof row["bgp.communities"] === "string" ? row["bgp.communities"].split(",") : []
const family = (row: Row) => ["ip", "ip4"].includes(String(row.afi)) ? "ipv4" : ["ipv6", "ip6"].includes(String(row.afi)) ? "ipv6" : null
const unique = (rows: Row[], nativeId: string) => rows.filter(row => row[".id"] === nativeId)
const tupleSchema = z.object({ connectionId: id, sessionId: id, instance: text, table: text, vrf: text,
localAddress: ip, remoteAddress: ip, remoteAsn: z.number().int().positive().max(4294967295), afi: z.enum(["ip", "ipv6"]) }).strict()
const prepared = z.object({ status: z.literal("prepared"), revision: z.literal(1), serverId: z.number().int().positive(),
routeSelectionScope: z.literal("table-afi-community-observation"),
candidateId: text, family: z.enum(["ipv4", "ipv6"]), table: text, community: text, expectedGateway: text,
expectedImmediate: z.object({ address: ip, interface: text }).strict(), binding: tupleSchema,
bfd: z.object({ applicability: z.literal("not-configured"), evidence: z.literal("explicit-use-bfd-false") }).strict(),
routes: z.array(z.object({ nativeId: id, prefix: text, originalGateway: text }).strict()).min(1).max(32),
probe: z.object({ key: text, serverId: z.number().int().positive(), destination: ip, source: ip, interface: text, probeVrf: text,
expectedHops: z.array(ip).min(1).max(8) }).strict(),
}).strict()
export const originalEffectSelectionSchema = z.union([prepared, z.object({ status: z.literal("unknown"), reason: text }).strict()])
export type OriginalEffectSelection = z.infer<typeof originalEffectSelectionSchema>
type Prepared = z.infer<typeof prepared>
const state = z.enum(["observed-expected", "observed-other", "unknown"])
const roundSchema = z.object({ observedAt: z.string().datetime(), routes: z.array(z.object({ prefix: text, status: state }).strict()).max(32),
bgp: state, bfd: z.enum(["not-configured", "unknown"]), controlProbe: z.enum(["passed", "failed", "unknown"]),
relatedTopicRows: z.number().int().nonnegative().max(1000) }).strict()
export const originalEffectReportSchema = z.object({ scope: z.literal("prepared-route-fib-adjacency-control-effects"), status: state,
reason: text.nullable(), rounds: z.array(roundSchema).max(3), logSource: z.literal("routeros-rest-ring"), logCoverage: z.literal("unknown"),
selectionIdentity: z.string().regex(/^[a-f0-9]{64}$/).nullable(),
routeSelectionScope: z.literal("table-afi-community-observation"), transitionAttribution: z.literal("unproven"),
switchEffectObserved: z.boolean(),
transition: z.object({ baselineObservedAt: z.string().datetime(), changedPrefixes: z.array(text).max(32) }).strict().optional(),
budget: z.object({ deadline: z.number().int().positive(), calls: z.number().int().nonnegative().max(256), bytes: z.number().int().nonnegative().max(8 * 1048576),
rows: z.number().int().nonnegative().max(10000) }).strict().optional(),
semanticProof: z.literal(false), routeOriginVerified: z.literal(false), recursiveChainVerified: z.literal(false), forwardingVerified: z.literal(false), autoRecoveryAllowed: z.literal(false) }).strict()
export type OriginalEffectReport = z.infer<typeof originalEffectReportSchema>
export const unknownOriginalEffects = (reason: string): OriginalEffectReport => ({ scope: "prepared-route-fib-adjacency-control-effects", status: "unknown", reason,
rounds: [], logSource: "routeros-rest-ring", logCoverage: "unknown", selectionIdentity: null, routeSelectionScope: "table-afi-community-observation", transitionAttribution: "unproven", switchEffectObserved: false, semanticProof: false, routeOriginVerified: false, recursiveChainVerified: false, forwardingVerified: false, autoRecoveryAllowed: false })
function tuple(row: Row, expected: Prepared["binding"], session: boolean) {
return row.instance === expected.instance && row["routing-table"] === expected.table && row.vrf === expected.vrf &&
row["local.address"] === expected.localAddress && row["remote.address"] === expected.remoteAddress && Number(row["remote.as"]) === expected.remoteAsn &&
(session ? row["local.afi"] === expected.afi && row["remote.afi"] === expected.afi : row.afi === expected.afi)
}
function selectedRows(rows: Row[], selection: Pick<Prepared, "table" | "family" | "community">) {
return rows.filter(row => row["routing-table"] === selection.table && family(row) === selection.family && flag(row.bgp) === true && communities(row).includes(selection.community))
}
/** Called only by the real private context reader. This selects observed route
* effects by table/AFI/Community, never a route→session provenance relationship.
* The initial bounded subset requires explicit flat peer identity, explicit
* BFD-off and an already configured explicit probe source. Other manual paths
* keep their existing configuration/probe behavior, with effects unknown. */
export function prepareOriginalEffectSelection(profile: RouteAiProfile, candidateId: string, binding: { connectionId: string; instance: string | null; vrf: string; table: string } | null,
raw: { routes: Row[]; connections: Row[]; sessions: Row[]; candidate: Row | undefined; probe: { destination: string; expected: string[]; source?: string } | undefined }): OriginalEffectSelection {
try {
if (!binding || !binding.instance) throw Error()
const candidate = profile.candidates.find(value => value.id === candidateId), probe = raw.probe
if (!candidate?.controlProbe || !probe?.source || !raw.candidate) throw Error()
const connection = unique(raw.connections, binding.connectionId)
if (connection.length !== 1 || !(connection[0].templates === "" || Array.isArray(connection[0].templates) && connection[0].templates.length === 0) ||
flag(connection[0].disabled) !== false || flag(connection[0]["use-bfd"]) !== false) throw Error()
const peer = connection[0], afi = profile.family === "ipv4" ? "ip" : "ipv6"
const wanted = tupleSchema.parse({ connectionId: peer[".id"], sessionId: "*0", instance: binding.instance, table: binding.table, vrf: binding.vrf,
localAddress: peer["local.address"], remoteAddress: peer["remote.address"], remoteAsn: Number(peer["remote.as"]), afi })
if (!tuple(peer, wanted, false)) throw Error()
const sessions = raw.sessions.filter(row => tuple(row, wanted, true))
if (sessions.length !== 1 || flag(sessions[0].established) === undefined) throw Error()
wanted.sessionId = id.parse(sessions[0][".id"])
const affected = selectedRows(raw.routes, profile)
if (!affected.length || affected.length > 32 || new Set(affected.map(row => row["dst-address"])).size !== affected.length || new Set(affected.map(row => row[".id"])).size !== affected.length) throw Error()
for (const row of affected) {
const [address, mask, extra] = String(row["dst-address"]).split("/")
if (extra !== undefined || isIP(address) !== (profile.family === "ipv4" ? 4 : 6) || !/^\d+$/.test(mask ?? "") || Number(mask) > (profile.family === "ipv4" ? 32 : 128)) throw Error()
routeGatewayContext(String(row.gateway))
}
// A selected destination cannot silently choose among competing origins.
if (affected.some(row => raw.routes.filter(other => other["routing-table"] === profile.table && other["dst-address"] === row["dst-address"]).length !== 1)) throw Error()
const endpoint = candidate.gatewayRef.resource === "gre" ? { address: routeGatewayContext(candidate.gateway).address, interface: String(raw.candidate.name) } : routeGatewayContext(String(raw.candidate["immediate-gw"]))
if (!endpoint.interface) throw Error()
return prepared.parse({ status: "prepared", revision: 1, serverId: profile.serverId, candidateId, family: profile.family, table: profile.table,
routeSelectionScope: "table-afi-community-observation",
community: profile.community, expectedGateway: candidate.gateway, expectedImmediate: { address: endpoint.address, interface: endpoint.interface }, binding: wanted,
bfd: { applicability: "not-configured", evidence: "explicit-use-bfd-false" },
routes: affected.map(row => ({ nativeId: row[".id"], prefix: row["dst-address"], originalGateway: row.gateway })).sort((a, b) => String(a.prefix).localeCompare(String(b.prefix))),
probe: { key: candidateId, serverId: profile.serverId, destination: probe.destination, source: probe.source, interface: candidate.wanInterface,
probeVrf: profile.probeVrf, expectedHops: probe.expected } })
} catch { return { status: "unknown", reason: "prepared-effect-selection-unsupported-or-ambiguous" } }
}
type Snapshot = { observedAt: string; servers: Array<{ serverId: number; routes: { payload: Row[] }; bgp: { payload: Row[]; connectionsPayload: Row[] }; logs: { payload: Row[] } }>;
probes: Array<{ key: string; pingPayload: Row[]; tracePayload?: Row[] }> }
export function originalEffectBaselineMatches(selection: Prepared, sample: Snapshot) {
const source = sample.servers.find(row => row.serverId === selection.serverId)
if (!source) return false
const actual = selectedRows(source.routes.payload, selection)
return actual.length === selection.routes.length && selection.routes.every(wanted => actual.filter(row => row[".id"] === wanted.nativeId && row["dst-address"] === wanted.prefix && row.gateway === wanted.originalGateway).length === 1)
}
/** Actual bounded native captures only; all stronger causal/whole-path claims
* remain false. Related log inspection is reported without completeness. */
export async function inspectOriginalEffectRounds(rawSelection: unknown, samples: Snapshot[], baseline: Snapshot) {
const selection = prepared.parse(rawSelection), rounds: z.infer<typeof roundSchema>[] = []
if (!originalEffectBaselineMatches(selection, baseline)) return unknownOriginalEffects("prepared-effect-baseline-drift")
const { inspectControlPing, inspectTrace } = await import("./path-verification.js")
for (const sample of samples) {
const source = sample.servers.find(row => row.serverId === selection.serverId)
if (!source) return unknownOriginalEffects("effect-source-missing")
const scope = selectedRows(source.routes.payload, selection)
const scopeChanged = fingerprint(scope.map(row => row["dst-address"]).sort()) !== fingerprint(selection.routes.map(row => row.prefix).sort())
const routes = selection.routes.map(wanted => {
const matching = source.routes.payload.filter(row => row["routing-table"] === selection.table && row["dst-address"] === wanted.prefix && family(row) === selection.family)
let status: z.infer<typeof state> = "unknown"
if (!scopeChanged && matching.length === 1) {
const row = matching[0]
if (flag(row.bgp) === true && flag(row.connect) !== true && flag(row.static) !== true && flag(row.active) !== undefined && row.ecmp !== undefined && flag(row.ecmp) === false &&
["disabled", "filtered", "unreachable", "blackhole", "invalid"].every(key => row[key] === undefined || flag(row[key]) === false)) {
try {
const final = routeGatewayContext(String(row["immediate-gw"]))
status = flag(row.active) === true && row.contribution === "active" && row.gateway === selection.expectedGateway && final.address === selection.expectedImmediate.address && final.interface === selection.expectedImmediate.interface ? "observed-expected" : "observed-other"
} catch { /* no final resolution evidence */ }
}
}
return { prefix: wanted.prefix, status }
})
const connections = unique(source.bgp.connectionsPayload, selection.binding.connectionId)
const sessions = source.bgp.payload.filter(row => tuple(row, selection.binding, true))
let bgp: z.infer<typeof state> = "unknown"
if (connections.length === 1 && tuple(connections[0], selection.binding, false) && flag(connections[0].disabled) === false && flag(connections[0]["use-bfd"]) === false &&
sessions.length === 1 && sessions[0][".id"] === selection.binding.sessionId && flag(sessions[0].established) !== undefined) bgp = flag(sessions[0].established) ? "observed-expected" : "observed-other"
const probe = sample.probes.find(row => row.key === selection.probe.key)
let controlProbe: "passed" | "failed" | "unknown" = "unknown"
if (probe) {
const ping = inspectControlPing(z.array(z.record(z.string(), z.string())).max(256).parse(probe.pingPayload))
if (ping.confirmedFailure) controlProbe = "failed"
else if (ping.ping.measured && ping.ping.loss === 0 && inspectTrace(probe.tracePayload, selection.probe.destination, selection.probe.expectedHops).verified) controlProbe = "passed"
}
rounds.push(roundSchema.parse({ observedAt: sample.observedAt, routes, bgp,
bfd: connections.length === 1 && flag(connections[0]["use-bfd"]) === false ? "not-configured" : "unknown", controlProbe,
relatedTopicRows: source.logs.payload.filter(row => typeof row.topics === "string" && row.topics.split(",").some(topic => ["bgp", "bfd", "route", "interface"].includes(topic))).length }))
}
if (rounds.length !== 2) return unknownOriginalEffects("two-effect-rounds-required")
const all = rounds.flatMap(round => [...round.routes.map(route => route.status), round.bgp, round.bfd === "unknown" ? "unknown" : "observed-expected", round.controlProbe === "passed" ? "observed-expected" : round.controlProbe === "failed" ? "observed-other" : "unknown"])
const before = baseline.servers.find(row => row.serverId === selection.serverId)!
// Native IDs alone may churn; they never count as a forwarding transition.
// Require an actual selected active destination's final forwarding endpoint
// to differ from the expected post-state seen consistently in both rounds.
// A changed logical gateway resolving to the SAME endpoint is not a FIB switch.
const changedPrefixes = selection.routes.filter(wanted => {
if (!rounds.every(round => round.routes.find(row => row.prefix === wanted.prefix)?.status === "observed-expected")) return false
const row = before.routes.payload.find(row => row[".id"] === wanted.nativeId && row["dst-address"] === wanted.prefix)!
if (flag(row.bgp) !== true || flag(row.active) !== true || row.contribution !== "active" || flag(row.connect) === true || flag(row.static) === true || flag(row.ecmp) !== false ||
["disabled", "filtered", "unreachable", "blackhole", "invalid"].some(key => row[key] !== undefined && flag(row[key]) !== false)) return false
try {
const endpoint = routeGatewayContext(String(row["immediate-gw"]))
routeGatewayContext(String(row.gateway))
if (!endpoint.interface) return false
return endpoint.address !== selection.expectedImmediate.address || endpoint.interface !== selection.expectedImmediate.interface
} catch { return false }
}).map(row => row.prefix)
const postState = all.includes("unknown") ? "unknown" : all.includes("observed-other") ? "observed-other" : "observed-expected"
return originalEffectReportSchema.parse({ ...unknownOriginalEffects(""), rounds, reason: null,
selectionIdentity: fingerprint(selection),
transition: { baselineObservedAt: baseline.observedAt, changedPrefixes }, switchEffectObserved: postState === "observed-expected" && changedPrefixes.length > 0,
status: postState === "observed-expected" && !changedPrefixes.length ? "unknown" : postState,
...(postState === "observed-expected" && !changedPrefixes.length ? { reason: "poststate-matched-without-selected-fib-transition" } : {}) })
}
@@ -19,7 +19,7 @@ export async function acceptRoutingRecoveryAuthoritySql() {
const { pool } = await import("../../db/index.js")
const { mcpConnectionPolicySchema } = await import("@mmapp/contracts/mcp")
const { routeAiProfileSchema } = await import("@mmapp/contracts/mcp-routing")
const { accessGeneration } = await import("./security.js")
const { accessGeneration, McpError } = await import("./security.js")
const { readRoutingEndpoints, createRoutingSourceContext } = await import("./routing-observation-source.js")
const { resolveRoutingProfileAuthority } = await import("./routing-profile-authority.js")
const { captureScopedRecoveryAuthority, scopedRecoveryDeadlineCeiling } = await import("./routing-community-scoped-recovery.js")
@@ -27,7 +27,7 @@ export async function acceptRoutingRecoveryAuthoritySql() {
assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name, "postgres")
const previousEnabled = (await pool.query("SELECT enabled FROM mcp_settings WHERE id=1")).rows[0].enabled as boolean
const profileIds: string[] = [], ids: number[] = []
let nativeHttpRequests = 0, callbackDispatches = 0, durableIntents = 0
let nativeHttpRequests = 0, callbackDispatches = 0, dispatchFences = 0
const listener = createServer((req, res) => { nativeHttpRequests++; req.resume(); res.setHeader("content-type", "application/json"); res.end("{}") })
try {
await new Promise<void>((resolve, reject) => { listener.once("error", reject); listener.listen(0, "127.0.0.1", () => { listener.off("error", reject); resolve() }) })
@@ -65,12 +65,7 @@ export async function acceptRoutingRecoveryAuthoritySql() {
const path = "/routing/filter/rule/*1"
await assert.rejects(withVerifiedConfigurationChange({ actorId: ownerId, connectionId, approved: true, serverIds: ids, serverWide: true, objects: [],
authorize: async () => { await captured.assert() }, beforeDispatch: async () => {
assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE connection_id=$1 AND action='configuration.intent' AND payload->>'path'=$2", [connectionId, path])).rows[0].n, 1)
durableIntents++
if (change === "edit") await pool.query("UPDATE mcp_routing_profiles SET version=version+1,config=jsonb_set(config,'{name}','\"late edit\"') WHERE id=$1", [profileId])
if (change === "disable") await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [profileId])
if (change === "grant") await pool.query("UPDATE mcp_routing_profiles SET grant_expires_at=clock_timestamp()-interval '1 second' WHERE id=$1", [profileId])
await captured.assert()
dispatchFences++
},
}, () => executeGuardedConfigurationWrite(ids[0], "PATCH", path, async () => {
callbackDispatches++
@@ -78,12 +73,17 @@ export async function acceptRoutingRecoveryAuthoritySql() {
const req = request({ hostname: "127.0.0.1", port, path: "/rest" + path, method: "PATCH", timeout: 1000 }, res => { res.resume(); res.once("end", resolve) })
req.once("error", reject); req.once("timeout", () => req.destroy(new Error("Synthetic loopback timeout"))); req.end("{}")
})
})), /Постоянный допуск/)
assert.equal(callbackDispatches, 0); assert.equal(nativeHttpRequests, 0)
assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE connection_id=$1 AND action='configuration.unconfirmed'", [connectionId])).rows[0].n, 1)
})), (error: unknown) => error instanceof McpError && error.code === "STATE_CONFLICT" && /Активный routing profile/.test(error.message))
assert.equal(callbackDispatches, 0); assert.equal(nativeHttpRequests, 0); assert.equal(dispatchFences, 0)
assert.equal((await pool.query("SELECT count(*)::int AS n FROM mcp_audit WHERE connection_id=$1 AND action IN ('configuration.intent','configuration.unconfirmed','configuration.acknowledged')", [connectionId])).rows[0].n, 0)
if (change === "edit") await pool.query("UPDATE mcp_routing_profiles SET version=version+1,config=jsonb_set(config,'{name}','\"late edit\"') WHERE id=$1", [profileId])
if (change === "disable") await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=$1", [profileId])
if (change === "grant") await pool.query("UPDATE mcp_routing_profiles SET grant_expires_at=clock_timestamp()-interval '1 second' WHERE id=$1", [profileId])
await assert.rejects(captured.assert(), /Постоянный допуск/)
}
return { cases: 3, lateMutations: ["edit", "disable", "grant"], deepCaptureCases: 3, durableIntents, callbackDispatches, nativeHttpRequests,
productionSessionAuthorityFence: true, realSql: true, productionHmac: true, fullInverseLifecycleRepeated: false, controllerEnabled: false, externalNetwork: false }
return { cases: 3, deepCaptureCases: 3, directGuardBeforeIntent: true, dispatchFences, callbackDispatches, nativeHttpRequests,
capturedAuthorityChanges: ["edit", "disable", "grant"], realSql: true, productionHmac: true, controllerEnabled: false, externalNetwork: false }
} finally {
if (profileIds.length) await pool.query("UPDATE mcp_routing_profiles SET enabled=false WHERE id=ANY($1::uuid[])", [profileIds])
if (ids.length) await pool.query("UPDATE servers SET enabled=false WHERE id=ANY($1::int[])", [ids])
@@ -2,9 +2,26 @@ import { assertOwnedRoutingRecoveryAuthoritySql, acceptRoutingRecoveryAuthorityS
// Guard precedes imports that configure a pool or load private MCP state.
assertOwnedRoutingRecoveryAuthoritySql()
process.env.AUTH_REQUIRED = "true"
process.env.AUTH_JWT_SECRET = "owned-routing-recovery-authority-acceptance-secret"
process.env.AUTH_ISSUER = "https://owned-routing-recovery-authority.test"
const { pool, closePool } = await import("../../db/index.js")
try {
const { applySqlMigrations } = await import("../../db/migrate.js")
await applySqlMigrations(pool)
console.log("captured recovery authority SQL acceptance:", JSON.stringify(await acceptRoutingRecoveryAuthoritySql()))
} finally { await closePool() }
const { updateMcpSettings } = await import("./store.js")
await import("./actions.js")
const { acceptScopedRouteCommunitySwitch } = await import("./routing-community-scoped-acceptance.js")
const actor = { id: "owned-routing-recovery-authority", email: "routing-recovery@example.invalid", name: "Owned recovery authority",
apps: ["mm"], permissions: ["mm:settings:admin", "mm:mcp:admin", "mm:mcp:approve", "mm:network:read", "mm:network:write", "mm:filters:write", "mm:servers:admin"],
issuedAt: Date.now() - 1000, expiresAt: Date.now() + 900000 }
await updateMcpSettings({ enabled: true }, actor)
for (const lateInverseAuthorityChange of ["edit", "disable", "grant"] as const) {
const evidence = await acceptScopedRouteCommunitySwitch(actor, { lateInverseAuthorityChange })
console.log("genuine late inverse authority SQL acceptance:", JSON.stringify(evidence))
}
} finally {
await (await import("../../services/scheduler.js")).stopSchedulerForDatabaseOperation()
await closePool()
}
@@ -1,9 +1,23 @@
import { assertOwnedRoutingReservationSql, acceptRoutingReservationSql } from "./routing-reservation.sql-helper.js"
assertOwnedRoutingReservationSql()
process.env.AUTH_REQUIRED = "true"
process.env.AUTH_JWT_SECRET = "owned-routing-mixed-writer-acceptance-secret"
process.env.AUTH_ISSUER = "https://owned-routing-mixed-writer.test"
const { pool, closePool } = await import("../../db/index.js")
try {
const { applySqlMigrations } = await import("../../db/migrate.js")
await applySqlMigrations(pool)
console.log("private routing reservation runtime admission SQL acceptance:", JSON.stringify(await acceptRoutingReservationSql({ runtimeAdmission: true })))
} finally { await closePool() }
const actor = { id: "owned-routing-mixed-writer", email: "routing-mixed@example.invalid", name: "Owned mixed writer",
apps: ["mm"], permissions: ["mm:settings:admin", "mm:mcp:admin", "mm:mcp:approve", "mm:network:read", "mm:network:write", "mm:filters:write", "mm:servers:admin"],
issuedAt: Date.now() - 1000, expiresAt: Date.now() + 900000 }
await (await import("./store.js")).updateMcpSettings({ enabled: true }, actor)
await import("./actions.js")
const mixed = await (await import("./routing-community-scoped-acceptance.js")).acceptScopedRouteCommunitySwitch(actor,
{ manualInverseChargeOnly: true, manualInverseChargeCase: "mixed-forward" })
console.log("private routing mixed forward/manual inverse admission SQL acceptance:", JSON.stringify(mixed))
} finally {
await (await import("../../services/scheduler.js")).stopSchedulerForDatabaseOperation()
await closePool()
}
@@ -352,6 +352,47 @@ export async function acceptRoutingReservationSql(options: { runtimeAdmission?:
await assert.rejects(store.beginForwardAttempt(reservation.forwardAllocation), /capability/)
names.push("spent forward/no genuine checkpoint and preterminal unused-proof refuse; unknown never replays or refunds")
}
{
const f = await fixture(), source = await f.makeSource(), reservation = await store.reserve(source.operationId)
const { saveScopedConfigurationCheckpoint, saveScopedNetworkCheckpoint } = await import("./routing-community-scoped-checkpoint.js")
const { executeReservedScopedRecovery } = await import("./routing-community-scoped-recovery.js")
await pool.query("UPDATE mcp_operations SET state='running' WHERE id=$1", [source.operationId])
const step = { kind: "service", service: "routing.community.scoped", input: source.input }
await pool.query("INSERT INTO mcp_operation_steps(operation_id,step_index,state,intended_state) VALUES($1,0,'intent',$2::jsonb)",
[source.operationId, JSON.stringify(security.redact(step))])
await pool.query("INSERT INTO mcp_operation_checkpoints(operation_id,step_index,before_sealed) VALUES($1,0,$2)",
[source.operationId, await security.encryptSecret(JSON.stringify(source.input.beforeRules))])
assert(reservation.forwardAllocation)
await store.beginForwardAttempt(reservation.forwardAllocation)
// Use the actual checkpoint writers for production crypto and SQL, but
// deliberately seed a synthetic failed-forward observation. No PATCH
// or genuine network observation is part of this owned component case.
const requestSeal = String(source.input.requestProof)
const configuration = await saveScopedConfigurationCheckpoint(source.input, requestSeal)
await saveScopedNetworkCheckpoint(source.input, requestSeal, { id: randomUUID(), candidateId: "b",
status: "failed", observedAt: configuration.observedAt })
const published = await store.recordForwardOutcome(reservation.reservationId)
assert(published.allocation)
assert.equal(published.receipt.state.inverse, "held")
const refused = { sourceOperationId: randomUUID(), principal: f.principal }
await assert.rejects(executeReservedScopedRecovery(published.allocation, refused), /Reserved runner identity differs/)
const unknown = await store.read(reservation.reservationId, f.principal)
assert.deepEqual(unknown.state, { ...published.receipt.state, revision: published.receipt.state.revision + 2, inverse: "unknown" })
assert.equal(unknown.chargedMutationUnits, 2)
assert.equal(unknown.chargedProfileSlots, 2)
assert.equal(unknown.refundsAvailable, false)
const shared = (await pool.query(`SELECT state,slot_phase,inverse_intent_sealed,inverse_configuration_sealed,verified_sealed
FROM mcp_scoped_recovery_intents WHERE reservation_id=$1`, [reservation.reservationId])).rows
assert.deepEqual(shared, [{ state: "unknown", slot_phase: "reserved-attempt", inverse_intent_sealed: null,
inverse_configuration_sealed: null, verified_sealed: null }])
await assert.rejects(executeReservedScopedRecovery(published.allocation, refused), /capability/)
await assert.rejects(store.recordForwardOutcome(reservation.reservationId), /republished/)
await assert.rejects(store.proveUnusedInverse(reservation.reservationId), /terminal applied/)
assert.equal(await used(f.connectionId), 2)
assert.equal((await store.readHourlyCharges(f.profileId, f.principal)).reservedCharges, 2)
assert.deepEqual((await store.read(reservation.reservationId, f.principal)).state, unknown.state)
names.push("genuine inverse capability on synthetic failed-forward checkpoints: acknowledged spend, late identity refusal, signed same-slot unknown and both charges conserved without replay or refund")
}
{
const f = await fixture(), source = await f.makeSource(), reservation = await store.reserve(source.operationId)
const saved = (await pool.query("SELECT state_proof FROM mcp_routing_reservations WHERE id=$1", [reservation.reservationId])).rows[0].state_proof
+19 -4
View File
@@ -1,4 +1,6 @@
import { pollErrorLog } from "../../services/server-poll-error-provenance.js"
import { capturePublishedServerProbeTerminalProof } from "../../services/server-probe-operation-retirement.js"
import { SERVER_PROBE_OPERATION } from "../../services/server-probe-authority.js"
import { preparedPlanExpiry } from "./plan-expiry.js"
import { acknowledgedCreateId, assertNoCallerCreateId } from "./native-create-identity.js"
import { randomUUID, createHash } from "node:crypto"
@@ -23,6 +25,7 @@ import { beginMcpOperationAdmission, insertMcpOperationAdmission, retireMcpOpera
import { createOspfOwnerSourceGuard, verifyOspfOwnerGraph, verifyOspfOwnerGraphAtPhase } from "./ospf-owner-graph.js"
import { evaluateMcpPolicy } from "./policy.js"
import { findAction, findActionByOperation } from "./actions.js"
import { refuseExpiredOperationReplay } from "./operation-replay-tombstones.js"
import { accessGeneration, decryptSecret, encryptSecret, fingerprint, privateSecretFingerprint, McpError, redact } from "./security.js"
import { audit, getConnection, transaction, validatePrincipal, effectiveMcpConnection, assertConnectionVisible, type McpPrincipal, type ConnectionRow, type StoredPlan, type StoredOperation } from "./store.js"
import { executeRosStep, readRosSnapshot, resourceById, routerClient, type RosSnapshot } from "./resources.js"
@@ -291,6 +294,7 @@ export async function applyPlan(principal: McpPrincipal, planId: string, idempot
reservedRouting = !!await routingReservationEvidence(original, originalEvidence)
return existing
}
await refuseExpiredOperationReplay(client, principal, connection.id, planId, idempotencyKey)
await client.query("SELECT pg_advisory_xact_lock(hashtextextended('mcp-runner-admission',0))")
const pending = await client.query<{ count: number }>("SELECT count(*)::int AS count FROM mcp_operations WHERE state IN ('pending','running')")
if (pending.rows[0].count >= 100) throw new McpError("BUDGET_EXCEEDED", "Очередь исполнителя заполнена; повторите подготовленное применение позже", 429)
@@ -359,6 +363,7 @@ export async function applyPlan(principal: McpPrincipal, planId: string, idempot
async function executeOperation(operationId: string, principal: McpPrincipal, routingReservationForward?: RoutingReservedForwardAllocation, routingReservationId?: string,originalServiceGrant?:OriginalServiceGrant, operationAdmission?: McpOperationAdmission): Promise<void> {
return withMcpOperationExecutionOrigin(operationAdmission, async () => {
const lease = await pool.connect()
let terminalProbe: Awaited<ReturnType<typeof executeOperationWithLease>>
let locked = false, released = false
const ownerLifetime = new AbortController()
// This closure is the SINGLE owner of physical destruction/release.
@@ -380,7 +385,7 @@ async function executeOperation(operationId: string, principal: McpPrincipal, ro
throw error
}
if (released || !locked) return
await withOperationConfigurationLockClient(lease, retire, ownerLifetime.signal,
terminalProbe = await withOperationConfigurationLockClient(lease, retire, ownerLifetime.signal,
() => executeOperationWithLease(operationId, principal, routingReservationForward, routingReservationId, originalServiceGrant))
} finally {
if (!released && locked) {
@@ -395,9 +400,14 @@ async function executeOperation(operationId: string, principal: McpPrincipal, ro
lease.release()
}
}
// Capture has its own bounded checkout and may fail without rewriting the
// acknowledged terminal outcome or redispatching the diagnostic.
if (terminalProbe) {
try { await capturePublishedServerProbeTerminalProof(terminalProbe) } catch { /* Existing retention remains authoritative. */ }
}
})
}
async function executeOperationWithLease(operationId: string, principal: McpPrincipal, routingReservationForward?: RoutingReservedForwardAllocation, routingReservationId?: string,originalServiceGrant?:OriginalServiceGrant): Promise<void> {
async function executeOperationWithLease(operationId: string, principal: McpPrincipal, routingReservationForward?: RoutingReservedForwardAllocation, routingReservationId?: string,originalServiceGrant?:OriginalServiceGrant) {
const { rows: [operation] } = await pool.query<StoredOperation>("SELECT * FROM mcp_operations WHERE id=$1 AND state='running'", [operationId])
if (!operation) return
const plan = await loadPlan(operation.plan_id), evidence = JSON.parse(await decryptSecret(plan.evidence.sealed)) as ChangeEvidence
@@ -576,14 +586,18 @@ async function executeOperationWithLease(operationId: string, principal: McpPrin
})
await (evidence.steps.some(step => step.kind === "service") ? withApplicationMutationLock(applyWithLocks) : applyWithLocks())
assertOperationConfigurationLockCheckpoint()
await transaction(async client => {
const receiptId = plan.operation_id === SERVER_PROBE_OPERATION ? diagnosticReceipt?.receiptId : undefined
const finishedAt = await transaction(async client => {
assertOperationConfigurationLockCheckpoint()
await client.query("UPDATE mcp_operations SET state='applied',verification=$3,result=$2::jsonb,finished_at=now() WHERE id=$1", [operationId, JSON.stringify({ confirmedWrites: diagnosticOnly ? 0 : confirmed, verificationScope: evidence.verificationScope ?? "configuration", ...(diagnosticOnly ? { completedServiceSteps: confirmed, diagnosticReceipt } : {}) }), evidence.verificationScope === "acknowledgement" ? "inconclusive" : "passed"])
const terminal = await client.query<{ finished_at: string }>("UPDATE mcp_operations SET state='applied',verification=$3,result=$2::jsonb,finished_at=now() WHERE id=$1"
+ (receiptId ? ` RETURNING to_char(finished_at AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS finished_at` : ""),
[operationId, JSON.stringify({ confirmedWrites: diagnosticOnly ? 0 : confirmed, verificationScope: evidence.verificationScope ?? "configuration", ...(diagnosticOnly ? { completedServiceSteps: confirmed, diagnosticReceipt } : {}) }), evidence.verificationScope === "acknowledgement" ? "inconclusive" : "passed"])
assertOperationConfigurationLockCheckpoint()
await client.query("UPDATE mcp_plans SET state='completed' WHERE id=$1", [plan.id])
assertOperationConfigurationLockCheckpoint()
await audit(client, "mcp-runner", "operation.complete", operation.connection_id, { operationId, confirmedWrites: diagnosticOnly ? 0 : confirmed, ...(diagnosticOnly ? { completedServiceSteps: confirmed, diagnosticReceipt } : {}) })
assertOperationConfigurationLockCheckpoint()
return receiptId ? terminal.rows[0]?.finished_at : undefined
})
assertOperationConfigurationLockCheckpoint()
if (routingReservationId && reservedForwardPassed) {
@@ -592,6 +606,7 @@ async function executeOperationWithLease(operationId: string, principal: McpPrin
await createRoutingReservationStore().proveUnusedInverse(routingReservationId).catch(() => {})
}
assertOperationConfigurationLockCheckpoint()
if (receiptId && finishedAt) return { receiptId, operationId, connectionId:operation.connection_id, finishedAt }
} catch (error) {
if (routingReservationId && hasConfirmedReservedRoutingForward(error)) confirmed = Math.max(confirmed, 1)
if (error instanceof VrfProductionAcknowledgedFailure) confirmed = Math.max(confirmed, error.acknowledgedWrites)
@@ -12,9 +12,9 @@ const principal = { connectionId, credentialId, credentialExpiresAt: new Date(no
const proof = async (value: unknown) => createHash("sha256").update(JSON.stringify(value)).digest("hex")
const context = { principal, connection: { id: connectionId, owner_id: "owner", owner_claims_issued_at: new Date(now - 1000) } } as ActionContext
let assertions = 0
function fixture() {
function fixture(retiredSource?: ServerProbeMcpDependencies["retiredSource"]) {
const events: string[] = [], receipts = new Map<string, ServerProbeReceipt>(), contexts: ServerProbeJournalContext[] = [], sealed = new Map<string, string>()
let evidence: ChangeEvidence, result: unknown = null, stepState = "intent", operationState = "running", scope = [2, 9], revoked = false, sqlTamper: string | undefined, dispatchFailure: unknown, preparedTool: PreparedServerProbe["tool"] = "ping", mismatchDestination = false, nextCipher = 0, revokeOnQuery = false, expiredPrepared = false, clock = now
let evidence: ChangeEvidence, result: unknown = null, stepState = "intent", operationState = "running", scope = [2, 9], revoked = false, sqlTamper: string | undefined, dispatchFailure: unknown, preparedTool: PreparedServerProbe["tool"] = "ping", mismatchDestination = false, nextCipher = 0, revokeOnQuery = false, expiredPrepared = false, clock = now, liveHistory = true
const access: ServerProbeAuthority = { transport: "mcp", ownerId: "owner", connectionId, credentialId, expiresAt: now + 600000, generation: "generation", policyProof: "1".repeat(64), ownerProof: "2".repeat(64), active: true, operationAllowed: true, scopeServerIds: scope, ownerPermissions: ["mm:servers:write"], policyPermissions: ["mm:servers:write"] }
const readAuthority = async () => { events.push("authority"); if (revoked) throw new ServerProbeExecutionError("PERMISSION_DENIED", "PRIVATE_PROVIDER_SECRET", 403); return { ...access, expiresAt: clock + 600000, scopeServerIds: [...scope] } }
const assertProtections = async (ids: number[]) => { events.push("protections"); if (ids.some(id => !scope.includes(id))) throw new Error("out of scope") }
@@ -28,6 +28,7 @@ function fixture() {
read: async id => { events.push("journal.read"); return receipts.get(id) },
}
const deps: ServerProbeMcpDependencies = {
retiredSource,
authority: (() => ({ readAuthority, assertProtections })) as ServerProbeMcpDependencies["authority"],
historicalAuthority: ((currentPrincipal: typeof principal) => ({ readAuthority: async () => { events.push("read-authority"); return { ...await readAuthority(), connectionId: currentPrincipal.connectionId, credentialId: currentPrincipal.credentialId } }, assertProtections })) as ServerProbeMcpDependencies["historicalAuthority"],
history: dependencies => createServerProbeJournalHistoryReader({ ...dependencies, loadArchive: async binding => {
@@ -51,7 +52,7 @@ function fixture() {
journal: (ctx, dependencies, execution) => { assert.equal(execution?.deadlineAt, Date.parse(prepared().expiresAt)); contexts.push(ctx); return { ...journal, read: async (...args) => { await dependencies.assertAuthority(ctx, args[1]); return journal.read(...args) }, claim: async binding => { await dependencies.assertAuthority(ctx, binding.bindingProof); return journal.claim(binding) } } },
query: async (sql, values) => {
events.push("sql"); if (revokeOnQuery) revoked = true
if (sql.includes("probe-history:scope")) { assert.equal(values[2], connectionId); assert.equal(values[3], "owner"); return [{ source_server_id: 9, destination_server_id: preparedTool === "bandwidth" ? 2 : null }] }
if (sql.includes("probe-history:scope")) { assert.equal(values[2], connectionId); assert.equal(values[3], "owner"); return liveHistory ? [{ source_server_id: 9, destination_server_id: preparedTool === "bandwidth" ? 2 : null }] : [] }
if (sql.includes("FROM servers")) { assert.ok(sql.includes("ANY($1::int[])")); assert.deepEqual(values[0], scope); return sql.includes("AND id=$2") ? [] : [{ id: 2, host: "peer", enabled: true }] }
if (!sql.includes("mcp_operation_steps")) return [{ evidence: { sealed: await seal(JSON.stringify(evidence)) } }]
assert.equal(values[0], connectionId); assert.equal(values[1], "owner"); assert.equal(values[2], "servers.probes.run")
@@ -68,8 +69,9 @@ function fixture() {
}, seal, open, proof, now: () => clock,
}
const adapter = createServerProbeMcpAdapter(deps)
const retireHistory = () => { liveHistory = false }
const prepare = async (tool: PreparedServerProbe["tool"] = "ping") => { evidence = await adapter.prepare({ serverId: 9, request: tool === "bandwidth" ? { tool, bwRemoteAddress: "peer", dstServerId: 8 } : { tool, target: "192.0.2.1" } }, context); const step = evidence.steps[0]; assert.equal(step.kind, "service"); return step.kind === "service" ? step.input : assert.fail() }
return { adapter, events, receipts, contexts, prepare, setResult: (value: unknown) => { result = value; stepState = "applied" }, finishOperation: () => { operationState = "applied"; stepState = "acknowledged" }, setTamper: (value?: string) => { sqlTamper = value }, revoke: () => { revoked = true }, setScope: (value: number[]) => { scope = value }, fail: (value: unknown) => { dispatchFailure = value }, revokeOnQuery: () => { revokeOnQuery = true }, evidence: () => evidence, mismatchDestination: () => { mismatchDestination = true }, expirePrepared: () => { expiredPrepared = true; clock = now + 3600000 } }
return { adapter, events, receipts, contexts, prepare, retireHistory, setResult: (value: unknown) => { result = value; stepState = "applied" }, finishOperation: () => { operationState = "applied"; stepState = "acknowledged" }, setTamper: (value?: string) => { sqlTamper = value }, revoke: () => { revoked = true }, setScope: (value: number[]) => { scope = value }, fail: (value: unknown) => { dispatchFailure = value }, revokeOnQuery: () => { revokeOnQuery = true }, evidence: () => evidence, mismatchDestination: () => { mismatchDestination = true }, expirePrepared: () => { expiredPrepared = true; clock = now + 3600000 } }
}
for (const tool of ["ping", "traceroute", "bandwidth", "dns", "route", "mtu"] as const) {
@@ -128,6 +130,38 @@ for (const input of [{ serverId: 9, request: { tool: "ping", target: "x", creden
await assert.rejects(f.adapter.service.execute(input, { operationId, phase: "forward", stepIndex: 0, signal: controller.signal })); assert.equal(f.events.filter(event => event === "execute").length, attempted); assertions += 3
}
assert.ok(!("diagnosticProgress" in serverProbeSafeMcpError(new ServerProbeExecutionError("X", "secret", 500, "unknown", 99), { receiptId, operationId, stageBudget: 1 }))); assertions++
{
const hash = "3".repeat(64), date = (value: number) => new Date(value).toISOString().replace("Z", "000Z")
const retired = { source: { version: 1 as const, operation: "servers.probes.run" as const, operationVersion: 1 as const, tool: "ping" as const,
receiptId, executionId: operationId, connectionId, ownerId: "owner", planId: randomUUID(), originalCredentialId: credentialId,
sourceServerId: 9, serverIds: [9], issuedAt: now - 1000, authorityExpiresAt: date(now + 600000), terminalAt: date(now + 600000),
activityAt: date(now + 600000), gcNotBefore: date(now + 87060000), retentionDays: 1, safetyMs: 60000,
bindingProof: hash, sourceDigest: hash, terminalDigest: hash, stageSnapshotDigest: hash, receiptProof: hash, ciphertextSha256: hash,
}, payloadPurged: false }
let lookups = 0, changeOnRecheck = false
const f = fixture(async lookup => {
lookups++
assert.deepEqual(lookup, { receiptId, operationId, connectionId, ownerId: "owner", scopeServerIds: [2, 9] })
return changeOnRecheck && lookups % 2 === 0 ? undefined : retired
})
const input = await f.prepare(), output = await f.adapter.service.execute(input, { operationId, phase: "forward", stepIndex: 0 })
f.setResult(output); f.finishOperation()
const request = { operationId, receiptId, page: 0 }
const live = await f.adapter.readPage(request, context)
assert.equal(lookups, 0)
f.retireHistory()
const rotated = { ...context, principal: { ...principal, credentialId: randomUUID() } }
assert.deepEqual(await f.adapter.readPage(request, rotated), live)
assert.deepEqual(await f.adapter.receiptTarget(request, rotated), { scope: "servers", serverIds: [9] })
const beforeForeign = lookups
await assert.rejects(f.adapter.readPage(request, { ...rotated, connection: { ...context.connection, owner_id: "foreign" } }))
assert.equal(lookups, beforeForeign)
lookups = 0; changeOnRecheck = true
const privateReads = f.events.filter(event => event === "history.read").length
await assert.rejects(f.adapter.readPage(request, rotated))
assert.equal(f.events.filter(event => event === "history.read").length, privateReads)
assertions += 7
}
{
const f = fixture(), input = await f.prepare("bandwidth"), output = await f.adapter.service.execute(input, { operationId, phase: "forward", stepIndex: 0 }); f.setResult(output); f.finishOperation(); f.expirePrepared()
await assert.rejects(f.adapter.service.current(input))
@@ -10,6 +10,7 @@ import { serverProbeReceiptPage } from "../../services/server-probe-public-resul
import { decryptSecret, encryptSecret, fingerprint, privateSecretFingerprint, McpError, redact } from "./security.js"
import type { ActionContext, ChangeEvidence } from "./action-types.js"
import type { McpServiceHandler } from "./service-registry.js"
import type { findRetiredServerProbeHistory, ServerProbeRetiredLookup } from "../../services/server-probe-retired-history.js"
/** Foundations only: importing this module registers no tools or services. */
export const serverProbeActionSchema = z.object({ serverId: z.number().int().positive().safe(), request: serverProbeExecutionRequest }).strict()
@@ -32,6 +33,7 @@ export interface ServerProbeMcpDependencies {
executor: typeof createProductionServerProbeExecutor
journal: (context: ServerProbeJournalContext, dependencies: ServerProbeJournalDependencies, execution?: { signal?: AbortSignal; deadlineAt?: number }) => ServerProbeJournal
query: Query
retiredSource?: (lookup: ServerProbeRetiredLookup) => ReturnType<typeof findRetiredServerProbeHistory>
seal: (value: string) => Promise<string>
open: (value: string) => Promise<string>
proof: (value: unknown) => Promise<string>
@@ -52,7 +54,7 @@ export async function openServerProbeMcpBinding(raw: Record<string, unknown>, cr
return { input, binding }
}
const ids = (prepared: PreparedServerProbe) => [...new Set([prepared.serverId, ...(prepared.destinationServerId ? [prepared.destinationServerId] : [])])].sort((a, b) => a - b)
function metadata(receipt: ServerProbeReceipt, operationId: string, stageBudget: number) {
export function serverProbeReceiptMetadata(receipt: ServerProbeReceipt, operationId: string, stageBudget: number) {
const page = serverProbeReceiptPage(receipt, 0)
if (!Number.isSafeInteger(receipt.attemptedStages) || !Number.isSafeInteger(receipt.completedStages) || receipt.completedStages < 0 || receipt.completedStages > receipt.attemptedStages || receipt.attemptedStages > stageBudget) return denied()
return { operationId, receiptId: receipt.receiptId, tool: receipt.tool, status: receipt.state, httpStatus: receipt.httpStatus, stageBudget, attemptedStages: receipt.attemptedStages, completedStages: receipt.completedStages, sha256: page.sha256, totalPages: page.totalPages, bytes: page.bytes, automaticReplay: false, forwardingVerified: false }
@@ -119,7 +121,7 @@ export function createServerProbeMcpAdapter(overrides: Partial<ServerProbeMcpDep
await owned(input, binding, row.operation_id)
await preflight(binding, signal)
if (!receipt || receipt.receiptId !== input.receiptId || receipt.serverId !== binding.prepared.serverId || receipt.destinationServerId !== binding.prepared.destinationServerId || receipt.tool !== binding.prepared.tool) return denied()
return { row, receipt, result: metadata(receipt, row.operation_id, binding.stageBudget) }
return { row, receipt, result: serverProbeReceiptMetadata(receipt, row.operation_id, binding.stageBudget) }
}
const target = async (raw: Record<string, unknown>, context: ActionContext): Promise<ChangeEvidence["target"]> => {
const input = serverProbeActionSchema.parse(raw), access = deps.authority(context.principal, { signal: context.signal })
@@ -177,7 +179,7 @@ export function createServerProbeMcpAdapter(overrides: Partial<ServerProbeMcpDep
const { executor } = execution(input, binding, row, true, context.signal)
const receipt = await executor.execute(binding.prepared, context.signal)
await preflight(binding, context.signal); await owned(input, binding, context.operationId, true); await preflight(binding, context.signal)
return metadata(receipt, context.operationId, binding.stageBudget)
return serverProbeReceiptMetadata(receipt, context.operationId, binding.stageBudget)
} catch (error) { throw serverProbeSafeMcpError(error, progress) }
},
verify: async (raw, result) => {
@@ -200,19 +202,47 @@ export function createServerProbeMcpAdapter(overrides: Partial<ServerProbeMcpDep
AND j.source_server_id=ANY($6::int[]) AND (j.destination_server_id IS NULL OR j.destination_server_id=ANY($6::int[])) LIMIT 2`,
[request.receiptId, request.operationId, context.principal.connectionId, initial.ownerId, SERVER_PROBE_OPERATION, initial.scopeServerIds])
await fresh()
if (rows.length !== 1) return denied()
const row = rows[0], serverIds = [...new Set([z.number().int().positive().safe().parse(row.source_server_id), ...(row.destination_server_id === null ? [] : [z.number().int().positive().safe().parse(row.destination_server_id)])])].sort((a, b) => a - b)
const loadRetired = deps.retiredSource
const readRetired = rows.length === 0 && loadRetired ? () => loadRetired({
receiptId: request.receiptId, operationId: request.operationId, connectionId: context.principal.connectionId,
ownerId: initial.ownerId, scopeServerIds: initial.scopeServerIds,
}) : undefined
const retired = readRetired ? await readRetired() : undefined
if (readRetired) await fresh()
if (rows.length !== 1 && !retired) return denied()
const row = rows[0]
const serverIds = retired ? retired.source.serverIds : [...new Set([z.number().int().positive().safe().parse(row.source_server_id), ...(row.destination_server_id === null ? [] : [z.number().int().positive().safe().parse(row.destination_server_id)])])].sort((a, b) => a - b)
const fence = async () => { const current = await fresh(); if (serverIds.some(id => !current.scopeServerIds.includes(id))) return denied(); await access.assertProtections(serverIds, current); await fresh() }
await fence()
return { initial, serverIds, fence }
const retiredFence = retired && readRetired ? async () => {
await fence()
const current = await readRetired()
await fence()
if (!current || fingerprint(current) !== fingerprint(retired)) return denied()
} : undefined
return { initial, serverIds, fence, retired, retiredFence }
}
const receiptTarget = async (raw: Record<string, unknown>, context: ActionContext): Promise<ChangeEvidence["target"]> => {
const request = serverProbeReceiptReadSchema.parse(raw), { serverIds, fence } = await historicalAccess(request, context)
await fence(); return { scope: "servers", serverIds }
const request = serverProbeReceiptReadSchema.parse(raw), { serverIds, fence, retiredFence } = await historicalAccess(request, context)
await (retiredFence ?? fence)(); return { scope: "servers", serverIds }
}
const readPage = async (raw: Record<string, unknown>, context: ActionContext) => {
try {
const request = serverProbeReceiptReadSchema.parse(raw), { initial, serverIds, fence } = await historicalAccess(request, context)
const request = serverProbeReceiptReadSchema.parse(raw), { initial, serverIds, fence, retired, retiredFence } = await historicalAccess(request, context)
if (retired && retiredFence) {
const source = retired.source
const originalContext: ServerProbeJournalContext = {
identity: { kind: "mcp", ownerId: source.ownerId, executionId: source.executionId, connectionId: source.connectionId, credentialId: source.originalCredentialId,
issuedAt: source.issuedAt, expiresAt: Date.parse(source.authorityExpiresAt), operationName: SERVER_PROBE_OPERATION },
sourceServerId: source.sourceServerId, ...(source.destinationServerId !== undefined ? { destinationServerId: source.destinationServerId } : {}), tool: source.tool,
}
await retiredFence()
const reader = deps.history({ assertReadAuthority: retiredFence }, { signal: context.signal })
const receipt = await reader.read({ context: originalContext, receiptId: source.receiptId, bindingProof: source.bindingProof })
const page = serverProbeReceiptPage(receipt, request.page)
await retiredFence()
return page
}
await fence()
const rows = await deps.query(`SELECT p.evidence FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id JOIN mcp_connections c ON c.id=o.connection_id WHERE o.id=$1 AND o.connection_id=$2 AND c.owner_id=$3 AND p.operation_id=$4 AND p.operation_version=1 AND octet_length(p.evidence::text)<=2097152 LIMIT 2`, [request.operationId, context.principal.connectionId, initial.ownerId, SERVER_PROBE_OPERATION])
await fence(); if (rows.length !== 1) return denied()
@@ -171,6 +171,16 @@ export async function acceptServerProbeAllToolsSql() {
assert.equal(done.result.confirmedWrites, 0); assert.equal(done.result.completedServiceSteps, 1); assert.equal(done.result.verificationScope, "acknowledgement")
const receipt = done.result.diagnosticReceipt as { receiptId: string; stageBudget: number; attemptedStages: number; completedStages: number; totalPages: number; sha256: string }
assert(receipt); assert.equal(receipt.attemptedStages, receipt.completedStages); assert(receipt.attemptedStages > 0 && receipt.attemptedStages <= receipt.stageBudget)
let captured = false
for (const until = Date.now()+2000; Date.now()<until;) {
const proof = (await pool.query(`SELECT f.source->>'terminalAt'=to_char(GREATEST(o.finished_at,p.expires_at) AT TIME ZONE 'UTC',
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') AS terminal_matches FROM server_probe_terminal_proofs f
JOIN mcp_operations o ON o.id=f.operation_id JOIN mcp_plans p ON p.id=o.plan_id
WHERE f.operation_id=$1 AND f.receipt_id=$2`, [done.id,receipt.receiptId])).rows[0]
if (proof) { assert.equal(proof.terminal_matches,true); captured = true; break }
await delay(20)
}
assert.equal(captured,true,"Owned successful diagnostic did not publish its terminal proof")
const step = (await pool.query("SELECT state,result FROM mcp_operation_steps WHERE operation_id=$1", [done.id])).rows
assert.equal(step.length, 1); assert.equal(step[0].state, "acknowledged")
for (const field of ["output", "data", "contentB64", "bindingCiphertext"]) assert(!Object.hasOwn(step[0].result, field))
@@ -201,6 +211,49 @@ export async function acceptServerProbeAllToolsSql() {
cases.push(tool + ": actual public plan/approval/apply, receipt metadata, full stage ledger and checksum pages without redispatch")
}
// Force a genuine capture SQL failure after the real diagnostic has
// committed. A sequence records the rejected attempt despite rollback.
const captureFault = await fixture()
const faultPlan = await prepare(captureFault, { tool: "route", target: "127.0.0.1" })
await pool.query("CREATE SEQUENCE public.owned_probe_capture_failure")
try {
await pool.query(`CREATE FUNCTION public.owned_probe_capture_failure() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.source->>'connectionId'=TG_ARGV[0] THEN
PERFORM nextval('public.owned_probe_capture_failure');
RAISE EXCEPTION 'owned terminal proof insertion refused';
END IF;
RETURN NEW;
END $$`)
try {
assert.match(captureFault.principal.connectionId,/^[a-f0-9-]{36}$/)
await pool.query(`CREATE TRIGGER owned_probe_capture_failure BEFORE INSERT ON public.server_probe_terminal_proofs
FOR EACH ROW EXECUTE FUNCTION public.owned_probe_capture_failure('${captureFault.principal.connectionId}')`)
try {
const beforeFault = requests+dnsUdp+dnsTcp, faultKey=randomUUID()
const applied = await applyPlan(captureFault.principal,faultPlan,faultKey), done=await terminal(applied.id)
let attempted = false
for (const until=Date.now()+2500;Date.now()<until;) {
attempted = (await pool.query("SELECT is_called FROM public.owned_probe_capture_failure")).rows[0].is_called
if (attempted) break
await delay(20)
}
assert.equal(attempted,true,"Owned post-COMMIT capture insert must have reached the injected SQL failure")
assert.equal(done.state,"applied"); assert.equal(done.error_code,null)
assert.equal((await pool.query("SELECT 1 FROM server_probe_terminal_proofs WHERE operation_id=$1",[applied.id])).rows.length,0)
const originalReceipt = done.result.diagnosticReceipt as { receiptId:string; sha256:string }
const page = await invokeMcpTool(captureFault.principal,"mm_server_probe_receipt_get",
{ operationId:applied.id,receiptId:originalReceipt.receiptId,page:0 }) as {sha256:string}
assert.equal(page.sha256,originalReceipt.sha256)
assert.equal((await applyPlan(captureFault.principal,faultPlan,faultKey)).id,applied.id)
assert.equal((await terminal(applied.id)).state,"applied")
assert.equal(requests+dnsUdp+dnsTcp,beforeFault+1)
assert.equal((await pool.query("SELECT last_value::int AS attempts FROM public.owned_probe_capture_failure")).rows[0].attempts,1)
} finally { await pool.query("DROP TRIGGER owned_probe_capture_failure ON public.server_probe_terminal_proofs") }
} finally { await pool.query("DROP FUNCTION public.owned_probe_capture_failure()") }
} finally { await pool.query("DROP SEQUENCE public.owned_probe_capture_failure") }
cases.push("terminal proof SQL refusal: acknowledged result remains applied and readable, exactly one loopback measurement and no retry")
// Pause only the one runner's pending->running SQL BEFORE sending it. This
// leaves a real pending approved source, allowing genuine durable cancel.
// A server-table lock after 'running' would have no intent step yet and the
@@ -209,8 +262,9 @@ export async function acceptServerProbeAllToolsSql() {
const entered = gate(), resume = gate(); releasePending = resume.resolve
let heldOperation = "", held = false
pool.query = (async function (...args: unknown[]) {
if (!held && typeof args[0] === "string" && args[0].startsWith("UPDATE mcp_operations SET state='running'")) {
held = true; heldOperation = String((args[1] as unknown[])[0]); entered.resolve(); await resume.promise
const query = args[0] as { text?: string; values?: unknown[] } | undefined
if (!held && query?.text?.startsWith("/* mcp-operation-root:claim */") && typeof query.values?.[0] === "string") {
held = true; heldOperation = query.values[0]; entered.resolve(); await resume.promise
}
return Reflect.apply(originalQuery, pool, args)
}) as typeof pool.query
@@ -2,6 +2,7 @@
import { createMcpServerProbeAuthority, createMcpServerProbeReadAuthority } from "../../services/server-probe-authority.js"
import { createProductionServerProbeExecutor } from "../../services/server-probe-execution.js"
import { createServerProbeStorageHistoryReader } from "../../services/server-probe-storage-history.js"
import { findRetiredServerProbeHistory } from "../../services/server-probe-retired-history.js"
import { createSqlServerProbeSourceReaders } from "../../services/server-probe-storage-source.js"
import { createProbeStorageHostIo } from "../../services/server-probe-storage-host-io.js"
import { assertVerifiedConfigurationAccess } from "../../services/config-mutation-policy.js"
@@ -28,6 +29,7 @@ export function createStorageServerProbeMcpAdapter(limits: ProbeStorageLimits =
},
}
const adapter = createServerProbeMcpAdapter({ query: io.rows, journal: io.journal,
retiredSource: lookup => findRetiredServerProbeHistory(io.rows, lookup),
authority: (principal, options) => createMcpServerProbeAuthority(principal, options, authorityDependencies),
historicalAuthority: (principal, options) => createMcpServerProbeReadAuthority(principal, options, authorityDependencies),
history: (dependencies, execution) => createServerProbeStorageHistoryReader({ assertReadAuthority: dependencies.assertReadAuthority,
+20 -3
View File
@@ -1,10 +1,13 @@
import { randomUUID } from "node:crypto"
import { retainServerProbeStorage } from "../../services/server-probe-retention.js"
import { maintainServerProbeOperationRetirement } from "../../services/server-probe-operation-retirement.js"
import { collectExpiredServerPollStorage } from "../../services/server-poll-storage-retention.js"
import { compactCompletedServerPollStorage } from "../../services/server-poll-terminal-compaction.js"
import { retireCompletedServerPollOperations } from "../../services/server-poll-operation-retirement.js"
import { pool } from "../../db/index.js"
import { sharedDatabaseGate } from "../../services/shared-database-gate.js"
import { recoverMcpOperations, verifyStep } from "./runtime.js"
import { retireOrdinaryMcpOperations } from "./ordinary-operation-retirement.js"
import { audit, getMcpSettings } from "./store.js"
import { accessGeneration, decryptSecret, redact } from "./security.js"
import { tickRoutingProfiles } from "./routing-profiles.js"
@@ -48,11 +51,22 @@ export async function detectMcpDrift() {
}
}
const mcpRetentionBatchSize = 1000
async function retainMcpData() {
const settings = await getMcpSettings()
await pool.query("DELETE FROM mcp_requests WHERE created_at<now()-make_interval(days=>$1) AND status NOT IN ('running')", [settings.requestsRetentionDays])
await pool.query("DELETE FROM mcp_audit WHERE created_at<now()-make_interval(days=>$1)", [settings.auditRetentionDays])
await pool.query("DELETE FROM mcp_artifacts WHERE expires_at<now()")
await pool.query(`WITH expired AS (
SELECT id FROM mcp_requests WHERE created_at<now()-make_interval(days=>$1) AND status NOT IN ('running')
ORDER BY created_at,id LIMIT $2 FOR UPDATE
) DELETE FROM mcp_requests USING expired WHERE mcp_requests.id=expired.id`, [settings.requestsRetentionDays, mcpRetentionBatchSize])
await pool.query(`WITH expired AS (
SELECT id FROM mcp_audit WHERE created_at<now()-make_interval(days=>$1)
ORDER BY created_at,id LIMIT $2 FOR UPDATE
) DELETE FROM mcp_audit USING expired WHERE mcp_audit.id=expired.id`, [settings.auditRetentionDays, mcpRetentionBatchSize])
await pool.query(`WITH expired AS (
SELECT id FROM mcp_artifacts WHERE expires_at<now()
ORDER BY expires_at,id LIMIT $1 FOR UPDATE
) DELETE FROM mcp_artifacts USING expired WHERE mcp_artifacts.id=expired.id`, [mcpRetentionBatchSize])
await pool.query("DELETE FROM mcp_oauth_requests WHERE expires_at<now()-interval '1 day'")
await pool.query("DELETE FROM mcp_budget_buckets WHERE bucket<now()-interval '2 days'")
await routingReservationTransaction(db => retainManualInverseChargesWithinOwner(db, settings.requestsRetentionDays))
@@ -79,8 +93,11 @@ export async function tickMcpWorkers() {
await retainMcpData()
await retainServerProbeStorage()
await compactCompletedServerPollStorage()
await retireCompletedServerPollOperations()
await collectExpiredServerPollStorage()
await retireOrdinaryMcpOperations()
if ((await getMcpSettings()).enabled) { await detectMcpDrift(); await tickRoutingProfiles() }
await maintainServerProbeOperationRetirement()
} finally {
if (locked) try { await lease.query("SELECT pg_advisory_unlock(hashtextextended('mcp-workers',0))") } catch { unsafe = true }
lease.release(unsafe)
@@ -0,0 +1,144 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import Fastify, { type FastifyInstance, type FastifyPluginAsync } from "fastify"
import { readFile } from "node:fs/promises"
import { runInNewContext } from "node:vm"
import * as crypto from "node:crypto"
import { z } from "zod"
import ts from "typescript"
import { DatabaseRequestGate } from "../services/database-request-gate.js"
import * as operation from "../services/system-database-operation.js"
// Real Fastify + plugin and shared gate/readiness wiring; SQL is inert and
// counted. No configured pool, credentials, native process or network is opened.
async function fixture() {
async function load<T extends object>(file: URL, modules: Record<string, unknown>): Promise<T> {
const exports: Record<string, unknown> = {}
const compiled = ts.transpileModule(await readFile(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true } }).outputText
runInNewContext(compiled, { exports, require: (name: string) => { assert(name in modules, `Unapproved import ${name}`); return modules[name] }, URL, URLSearchParams, Buffer, Date, Promise, Error, process: { env: {} } })
return exports as T
}
const shared = await load<{ sharedDatabaseGate: DatabaseRequestGate; databaseSchemaReadiness: ReturnType<typeof operation.createDatabaseSchemaReadiness> }>(new URL("../services/shared-database-gate.ts", import.meta.url), {
"./database-request-gate.js": { DatabaseRequestGate }, "./system-database-operation.js": operation,
})
const gate = shared.sharedDatabaseGate as DatabaseRequestGate
const readiness = shared.databaseSchemaReadiness as ReturnType<typeof operation.createDatabaseSchemaReadiness>
let admissions = 0, sqlCalls = 0, transactions = 0, audits = 0
let holdClientRead: (() => Promise<void>) | undefined
const events: string[] = [], resource = "http://localhost:8000/mcp", generation = "current-owned-generation"
const query = async (sql: string) => {
sqlCalls++; events.push(sql.startsWith("INSERT INTO mcp_credentials") ? "credential-write" : "sql")
if (sql.startsWith("SELECT id FROM mcp_oauth_clients")) {
await holdClientRead?.(); return { rows: [{ id: "owned-client" }] }
}
if (sql.startsWith("SELECT * FROM mcp_oauth_clients")) return { rows: [{ id: "owned-client", redirect_uris: ["http://localhost/callback"] }] }
if (sql.startsWith("SELECT * FROM mcp_credentials")) return { rows: [{ id: "refresh-id", client_id: "owned-client", audience: resource, expires_at: new Date(Date.now() + 600000), revoked_at: null, connection_id: "connection-id", family_id: "family-id" }] }
if (sql.startsWith("SELECT * FROM mcp_oauth_clients ORDER")) return { rows: [] }
return { rows: [], rowCount: 1 }
}
const request = gate.request.bind(gate)
Object.defineProperty(gate, "request", { value: function<T>(run: () => Promise<T>, cancel?: () => void) { admissions++; return request(run, cancel) } })
class McpError extends Error { constructor(readonly code: string, message: string, readonly statusCode = 403) { super(message) } }
const oauth = await load<{ mcpOauth: FastifyPluginAsync }>(new URL("./mcp-oauth.ts", import.meta.url), {
"node:crypto": crypto, zod: { z }, "../db/index.js": { pool: { query } },
"../services/shared-database-gate.js": shared,
"../config.js": { env: { authRequired: true, CORS_ORIGIN: "http://localhost:3000" } },
"../plugins/auth.js": { requireAuth: async (req: { authUser?: { id: string } }) => { req.authUser = { id: "owned-admin" } } },
"../modules/mcp/store.js": {
audit: async () => { audits++ }, getConnection: async () => ({ generation, policy: { status: "active", expiresAt: new Date(Date.now() + 600000).toISOString() }, owner_valid_until: new Date(Date.now() + 600000) }),
transaction: async (work: (db: { query: typeof query }) => Promise<unknown>) => { transactions++; events.push("transaction-start"); const result = await work({ query }); events.push("transaction-settled"); return result },
},
"../modules/mcp/security.js": { McpError, accessGeneration: async () => generation, constantEqual: (a: string, b: string) => a === b, randomToken: (kind: string) => kind + "-owned-inert", tokenDigest: (token: string) => crypto.createHash("sha256").update(token).digest("hex") },
"./mcp-transport.js": { mcpPublicUrl: () => new URL(resource) },
"../modules/mcp/portal-revocation.js": { assertPortalClaimsCurrent: async () => { await query("SELECT invalid_before FROM mcp_owner_revocations") } },
})
const api = await load<{ installDatabaseRequestGate(app: FastifyInstance): void }>(new URL("../plugins/database-request-gate.ts", import.meta.url), {
"../services/shared-database-gate.js": shared,
"../services/config-mutation-policy.js": { withConfigurationActor: (_actor: string, work: () => unknown) => work() },
})
const app = Fastify()
api.installDatabaseRequestGate(app)
await app.register(oauth.mcpOauth)
const publicRequests = () => [
{ method: "GET" as const, url: "/oauth/authorize?" + new URLSearchParams({ client_id: "owned-client", redirect_uri: "http://localhost/callback", response_type: "code", code_challenge: "a".repeat(43), code_challenge_method: "S256", resource }).toString() },
{ method: "POST" as const, url: "/oauth/token", payload: { grant_type: "refresh_token", client_id: "owned-client", resource, refresh_token: "owned-refresh" } },
{ method: "POST" as const, url: "/oauth/revoke", payload: { token: "owned-refresh", client_id: "owned-client" } },
]
return { app, gate, readiness, events, publicRequests, counts: () => ({ admissions, sqlCalls, transactions, audits }), hold: (work?: () => Promise<void>) => { holdClientRead = work } }
}
function deferred() { let resolve!: () => void; const promise = new Promise<void>(done => { resolve = done }); return { promise, resolve } }
async function unavailable(f: Awaited<ReturnType<typeof fixture>>) {
const before = f.counts()
for (const options of f.publicRequests()) {
const response = await f.app.inject(options)
assert.equal(response.statusCode, 503); assert.deepEqual(response.json(), { error: "temporarily_unavailable" }); assert.equal(response.headers["cache-control"], "no-store")
}
assert.equal(f.counts().sqlCalls, before.sqlCalls); assert.equal(f.counts().transactions, before.transactions); assert.equal(f.counts().audits, before.audits)
}
test("public OAuth authorize/token/revoke refuse before SQL while exclusive maintenance runs", async () => {
const f = await fixture(), entered = deferred(), release = deferred()
const maintenance = f.gate.exclusive(async () => { entered.resolve(); await release.promise })
try { await entered.promise; await unavailable(f) }
finally { release.resolve(); await maintenance; await f.app.close() }
})
test("in-flight actual token transaction settles before exclusive replacement begins", async () => {
const f = await fixture(), entered = deferred(), release = deferred()
f.hold(async () => { entered.resolve(); await release.promise })
const token = f.app.inject(f.publicRequests()[1]).then(response => response)
await entered.promise
let replacements = 0
const maintenance = f.gate.exclusive(async () => { replacements++; f.events.push("replacement"); f.readiness.beginReplacement() })
try {
await unavailable(f); assert.equal(replacements, 0)
release.resolve(); const response = await token; assert.equal(response.statusCode, 200); assert.equal(response.json().access_token, "access-owned-inert")
await maintenance; assert.equal(replacements, 1)
assert(f.events.indexOf("transaction-settled") < f.events.indexOf("replacement")); assert.equal(f.events.filter(event => event === "credential-write").length, 2)
await unavailable(f)
} finally { release.resolve(); await token; await maintenance; await f.app.close() }
})
test("sticky replacement refusal survives exclusive finally and only explicit repair reopens OAuth SQL", async () => {
const f = await fixture()
try {
await f.gate.exclusive(async () => { f.readiness.beginReplacement() })
await unavailable(f)
await f.gate.exclusive(async () => {})
await unavailable(f)
await f.gate.exclusive(async () => { f.readiness.confirmSchema() })
const responses = []
for (const options of f.publicRequests()) responses.push(await f.app.inject(options))
assert.deepEqual(responses.map(response => response.statusCode), [302, 200, 200]); assert(f.counts().sqlCalls > 0)
} finally { await f.app.close() }
})
test("both pure discovery endpoints remain200 with no SQL or admission while maintenance is sticky-closed", async () => {
const f = await fixture()
try {
f.readiness.beginReplacement(); const before = f.counts()
for (const url of ["/.well-known/oauth-protected-resource", "/.well-known/oauth-authorization-server"]) assert.equal((await f.app.inject(url)).statusCode, 200)
assert.deepEqual(f.counts(), before)
} finally { await f.app.close() }
})
test("OAuth admits public SQL once and protects both API admin SQL stages", async () => {
const f = await fixture()
try {
const before = f.counts().admissions
assert.equal((await f.app.inject(f.publicRequests()[0])).statusCode, 302)
assert.equal(f.counts().admissions - before, 1)
const middle = f.counts().admissions
assert.equal((await f.app.inject("/api/mcp/oauth/clients")).statusCode, 200)
assert.equal(f.counts().admissions - middle, 2)
await f.gate.exclusive(async () => {
const sqlBefore = f.counts().sqlCalls
const response = await f.app.inject("/api/mcp/oauth/clients")
assert.equal(response.statusCode, 503)
assert.equal(f.counts().sqlCalls, sqlBefore)
})
} finally { await f.app.close() }
})
test("ordinary invalid OAuth input preserves its existing protocol error and no-store behavior", async () => {
const f = await fixture()
try {
const response = await f.app.inject({ method: "POST", url: "/oauth/token", payload: {} })
assert.equal(response.statusCode, 400); assert.deepEqual(response.json(), { error: "invalid_request" }); assert.equal(response.headers["cache-control"], "no-store"); assert.equal(f.counts().sqlCalls, 0)
} finally { await f.app.close() }
})
+12 -1
View File
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto"
import type { FastifyPluginAsync } from "fastify"
import { z } from "zod"
import { pool } from "../db/index.js"
import { sharedDatabaseGate } from "../services/shared-database-gate.js"
import { env } from "../config.js"
import { requireAuth } from "../plugins/auth.js"
import { audit, getConnection, transaction } from "../modules/mcp/store.js"
@@ -18,6 +19,15 @@ export function verifyPkce(verifier: string, challenge: string): boolean {
return /^[A-Za-z0-9._~-]{43,128}$/.test(verifier) && constantEqual(createHash("sha256").update(verifier).digest("base64url"), challenge)
}
export const mcpOauth: FastifyPluginAsync = async app => {
// Public OAuth SQL is outside /api: own its full handler admission here.
// Pure discovery stays available; /api consent/admin use the API gate.
app.addHook("onRoute", options => {
if (!options.url.startsWith("/oauth/")) return
const original = options.handler
options.handler = async function (request, reply) {
return sharedDatabaseGate.request(async () => await original.call(this, request, reply))
}
})
app.setErrorHandler((error, _request, reply) => {
reply.header("Cache-Control", "no-store")
if (error instanceof z.ZodError) return reply.code(400).send({ error: "invalid_request" })
@@ -99,7 +109,8 @@ export const mcpOauth: FastifyPluginAsync = async app => {
if (!env.authRequired) return reply.code(503).send({ error: "Требуется авторизация портала" })
await requireAuth(request, reply)
if (reply.sent) return
await assertPortalClaimsCurrent(request.authUser!)
// The generic API gate admits the handler, not this SQL-bearing hook.
await sharedDatabaseGate.request(() => assertPortalClaimsCurrent(request.authUser!))
if (request.headers.origin && request.headers.origin !== env.CORS_ORIGIN) return reply.code(403).send({ error: "Недопустимый Origin" })
})
admin.get("/api/mcp/oauth/clients", async () => ({ clients: (await pool.query("SELECT * FROM mcp_oauth_clients ORDER BY created_at DESC LIMIT 100")).rows }))
+7
View File
@@ -10,6 +10,7 @@ const { default: authPlugin } = await import("../plugins/auth.js")
const { createMcpRoutes, mcpDevicePlanBodySchema, mcpDevicePlanArguments } = await import("./mcp.js")
const { findAction } = await import("../modules/mcp/actions.js")
const { pool } = await import("../db/index.js")
const { sharedDatabaseGate } = await import("../services/shared-database-gate.js")
const { env } = await import("../config.js")
const { mcpPolicyPreviewRequestSchema, mcpCatalogResponseSchema, mcpPolicyPreviewResponseSchema } = await import("@mmapp/contracts/mcp")
const { listMcpOperations } = await import("../modules/mcp/registry.js")
@@ -37,6 +38,12 @@ const payload = mcpPolicyPreviewRequestSchema.parse({
target: { scope: "servers", serverIds: [1] },
})
try {
await sharedDatabaseGate.exclusive(async () => {
const before = claimChecks
const response = await app.inject({ url: "/api/mcp/catalog", headers: admin })
assert.equal(response.statusCode, 503)
assert.equal(claimChecks, before, "Maintenance stops the claims hook before SQL")
})
for (const request of [{ method: "GET" as const, url: "/api/mcp/catalog" }, { method: "POST" as const, url: "/api/mcp/policy/preview", payload }]) {
assert.equal((await app.inject(request)).statusCode, 401)
assert.equal((await app.inject({ ...request, headers: { authorization: "Bearer arbitrary-agent-token" } })).statusCode, 401)
+16 -3
View File
@@ -18,12 +18,14 @@ import { accessGeneration, decryptSecret, encryptSecret, McpError, randomToken,
import { createMcpConnection, listMcpConnections, getMcpSettings, updateMcpSettings, getConnection, assertConnectionVisible, rotateConnectionToken, setConnectionStatus, updateMcpConnection, transaction, audit } from "../modules/mcp/store.js"
import { approvePlan, loadPlan, publicPlan } from "../modules/mcp/runtime.js"
import { applyPlan, invokeMcpTool } from "../modules/mcp/runtime.js"
import { findOwnerPlanReplayTombstone } from "../modules/mcp/operation-replay-tombstones.js"
import { routeAiProfileSchema } from "@mmapp/contracts/mcp-routing"
import { revalidateMcpOwner } from "../modules/mcp/store.js"
import { resourceById } from "../modules/mcp/resources.js"
import { registerManagementChannel } from "../modules/mcp/management.js"
import { MAX_RESTORE_ARCHIVE_BYTES, uploadRestoreArchive, readRestoreReceipt } from "../modules/mcp/database-restore.js"
import { MAX_RESTORE_ARCHIVE_BYTES, uploadRestoreArchive, readRestoreReceipt, readRestoreReceiptAuditStatus } from "../modules/mcp/database-restore.js"
import { assertPortalClaimsCurrent } from "../modules/mcp/portal-revocation.js"
import { sharedDatabaseGate } from "../services/shared-database-gate.js"
import { readFile } from "node:fs/promises"
import { checkMcpProtocol, type ProtocolProbe } from "../modules/mcp/readiness.js"
import { humanBackupDownloadMetadata, readHumanBackupDownloadArtifact } from "../modules/mcp/backup-download-artifact.js"
@@ -96,12 +98,14 @@ export function createMcpRoutes(checkClaims = assertPortalClaimsCurrent, options
// Explicit guard: these routes must stay protected if the global API hook changes.
await requireAuth(request, reply)
if (reply.sent) return
await checkClaims(actorOf(request.authUser))
// This hook runs before the API handler's database admission.
await sharedDatabaseGate.request(() => checkClaims(actorOf(request.authUser)))
if (request.headers.origin && request.headers.origin !== env.CORS_ORIGIN) return reply.code(403).send({ error: "Недопустимый Origin" })
})
app.post("/mcp/database/archives", { bodyLimit: MAX_RESTORE_ARCHIVE_BYTES }, async request => uploadRestoreArchive(request.body as Buffer, actorOf(request.authUser)))
app.get("/mcp/database/receipts/:id", async request => readRestoreReceipt(idOf(request.params), actorOf(request.authUser)))
app.get("/mcp/database/receipts/:id/audit-status", async request => readRestoreReceiptAuditStatus(idOf(request.params), actorOf(request.authUser)))
app.post("/mcp/database/plan", async request => {
const { connectionId, archiveRef, expectedSha256 } = z.object({ connectionId: uuid, archiveRef: uuid, expectedSha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict().parse(request.body)
const actor = actorOf(request.authUser)
@@ -240,7 +244,16 @@ export function createMcpRoutes(checkClaims = assertPortalClaimsCurrent, options
return ownerDelegate(async () => invokeMcpTool(principal, action === "recover" ? "mm_operation_recovery_plan" : globalReconcile ? "mm_global_operation_reconcile" : "mm_operation_reconcile", { operationId: id }))
})
app.post("/mcp/plans/:id/apply", async request => {
const plan = await loadPlan(idOf(request.params)), actor = actorOf(request.authUser)
const id = idOf(request.params), actor = actorOf(request.authUser)
let plan: { id: string; connection_id: string; operation_id: string }
try { plan = await loadPlan(id) }
catch (error) {
if (!(error instanceof McpError) || error.code !== "NOT_FOUND") throw error
const tombstone = await findOwnerPlanReplayTombstone(pool, actor.id, id)
if (!tombstone) throw error
const principal = await ownerPrincipal(tombstone.connection_id, actor, tombstone.action_id)
return ownerDelegate(async () => applyPlan(principal, tombstone.plan_id, `web:${tombstone.plan_id}`))
}
const principal = await ownerPrincipal(plan.connection_id, actor, plan.operation_id)
await audit(pool, actor.id, "plan.web.apply", plan.connection_id, { planId: plan.id })
return ownerDelegate(async () => applyPlan(principal, plan.id, `web:${plan.id}`))
@@ -0,0 +1,107 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import path from 'node:path'
import {mkdtemp,mkdir,writeFile,readFile,appendFile,rm,lstat,chmod,symlink} from 'node:fs/promises'
import * as filesystem from 'node:fs/promises'
import {fileURLToPath} from 'node:url'
import {createRequire} from 'node:module'
import {randomUUID,createHash} from 'node:crypto'
import {runInNewContext} from 'node:vm'
import {parseMaintenanceJournal,evaluateMaintenanceAcceptances,runtimeDatabaseTargetSha256,MAINTENANCE_ACCEPTANCE_FORMAT,type MaintenanceAcceptance} from './database-maintenance-resolution.js'
import {inspectDatabaseMaintenanceStartup,readMaintenanceRecoveryFacts} from './database-maintenance-startup.js'
const here=path.dirname(fileURLToPath(import.meta.url)),backend=path.resolve(here,'../..'),temp=path.resolve(backend,'../tmp'),require=createRequire(import.meta.url),ts=require('typescript'),digest=(v:Buffer|string)=>createHash('sha256').update(v).digest('hex')
const target='postgresql://service:old-password@db.example.invalid:5432/managed?sslmode=require'
const record=(operationId:string,state:string)=>JSON.stringify({operationId,state,at:'2026-11-01T00:00:00.000Z'})+'\n'
const source={systemIdentifier:'1234567890',databaseOid:'12345',databaseName:'managed',serverVersionNum:180004,inRecovery:false as const}
const knownCurrent={transport:{mode:'tls' as const,endpointSha256:'f'.repeat(64)},source,schemaSha256:'a'.repeat(64),rawSchemaSha256:'b'.repeat(64),physicalSchemaSha256:'c'.repeat(64),manifestSha256:'d'.repeat(64),markersSha256:'e'.repeat(64)}
async function own(work:(root:string,uncertain:string,applied:string)=>Promise<void>){await mkdir(temp,{recursive:true});const root=await mkdtemp(path.join(temp,'owned-maintenance-resolution-'));try{
await mkdir(path.join(root,'restore-vault'));await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,7));const uncertain=randomUUID(),applied=randomUUID()
await writeFile(path.join(root,'database-maintenance.jsonl'),record(uncertain,'intent')+record(uncertain,'unconfirmed')+record(applied,'intent')+record(applied,'applied'))
for(const [id,state]of [[uncertain,'unknown'],[applied,'applied']]){const archiveRef=randomUUID(),at='2026-11-01T00:00:00.000Z';await writeFile(path.join(root,'restore-vault',archiveRef+'.used'),JSON.stringify({operationId:id,at}));await writeFile(path.join(root,'restore-vault',id+'.receipt.json'),JSON.stringify({operationId:id,archiveRef,state,startedAt:at,automaticRetry:false,...(state==='applied'?{finishedAt:at,accessRevoked:true,warnings:[]}:{errorCode:'RESTORE_UNCONFIRMED',finishedAt:at})}))}
await work(root,uncertain,applied)
}finally{assert(path.dirname(root)===temp&&path.basename(root).startsWith('owned-maintenance-resolution-'));await rm(root,{recursive:true,force:true})}}
export async function modeledStartup(){
// This is deliberately a MODELED POSIX ownership adapter over actual owned
// files on Windows, not evidence that Windows/Linux protection was accepted.
const sourceText=await readFile(path.join(backend,'src/services/database-maintenance-startup.ts'),'utf8'),exports:Record<string,unknown>={}
const modeledStat=(stat:Awaited<ReturnType<typeof lstat>>)=>new Proxy(stat,{get(object,key){if(key==='uid')return typeof object.uid==='bigint'?0n:0;if(key==='mode')return typeof object.mode==='bigint'?(object.mode&~0o777n)|(object.isDirectory()?0o700n:0o600n):(object.mode&~0o777)|(object.isDirectory()?0o700:0o600);const value=Reflect.get(object,key);return typeof value==='function'?value.bind(object):value}})
const fs={...filesystem,lstat:async(...args:Parameters<typeof filesystem.lstat>)=>modeledStat(await Reflect.apply(filesystem.lstat,filesystem,args)),open:async(...args:Parameters<typeof filesystem.open>)=>{const handle=await Reflect.apply(filesystem.open,filesystem,args);return new Proxy(handle,{get(object,key){if(key==='stat')return async(...args:Parameters<typeof handle.stat>)=>modeledStat(await Reflect.apply(handle.stat,handle,args));const value=Reflect.get(object,key);return typeof value==='function'?value.bind(object):value}})}}
const code=ts.transpileModule(sourceText,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name==='node:fs/promises'?fs:name==='node:process'?{platform:'linux',getuid:()=>0,env:{DATABASE_URL:target}}:require(name),Buffer,TextDecoder,Date,Map,Set,Object,Error})
return exports as unknown as {inspectDatabaseMaintenanceStartup:typeof inspectDatabaseMaintenanceStartup;readAppliedRestoreAuditSources:(root:string)=>Promise<readonly {operationId:string}[]>}
}
async function acceptance(root:string,previousResolutionSha256:string|null=null):Promise<MaintenanceAcceptance>{const facts=await readMaintenanceRecoveryFacts(root);assert(facts.generation);return {format:MAINTENANCE_ACCEPTANCE_FORMAT,resolutionId:randomUUID(),decision:'accept-observed-current-state',historicOutcomeChanged:false,automaticRestore:false,actor:{kind:'host-operator',id:'explicit-test-operator'},approvalSha256:'1'.repeat(64),inspectionSha256:'2'.repeat(64),approvedAt:'2026-11-02T00:00:00.000Z',approvalExpiresAt:'2026-11-02T00:15:00.000Z',acceptedAt:'2026-11-02T00:01:00.000Z',runtimeTargetSha256:runtimeDatabaseTargetSha256(target),previousResolutionSha256,journalCoverage:{prefixBytes:facts.journal.length,sha256:digest(facts.journal),unresolvedOperations:facts.operations.filter(op=>op.state!=='applied')},vaultCoverage:facts.vault.evidence.filter(marker=>facts.vault.unresolvedFiles.includes(marker.file)),generationObservedSha256:digest(facts.generation),knownCurrent}}
async function publish(root:string,value:MaintenanceAcceptance){await mkdir(path.join(root,'maintenance-acceptances'),{recursive:true});const raw=Buffer.from(JSON.stringify(value));await writeFile(path.join(root,'maintenance-acceptances',value.resolutionId+'.json'),raw);return raw}
test('explicit historical acceptance allows restart after approval expiry, preserves unknown and imports only actual applied',()=>own(async(root,uncertain,applied)=>{
const modeled=await modeledStartup(),before=await readFile(path.join(root,'restore-vault',uncertain+'.receipt.json'))
assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await publish(root,await acceptance(root));const result=await modeled.inspectDatabaseMaintenanceStartup(root)
assert.equal(result.classification,'accepted-current-state');assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root,target.replace('old-password','rotated-password'))).classification,'accepted-current-state');assert.equal(result.acceptedHistoricResolutions,1);assert.equal(result.historicOutcomesChanged,false)
assert.equal(JSON.stringify((await modeled.readAppliedRestoreAuditSources(root)).map(value=>value.operationId)),JSON.stringify([applied]));assert.deepEqual(await readFile(path.join(root,'restore-vault',uncertain+'.receipt.json')),before)
}))
test('completed suffix and legitimate later schema/partition changes do not require a perpetual SQL fingerprint gate',()=>own(async root=>{
const modeled=await modeledStartup();await publish(root,await acceptance(root));const later=randomUUID();await appendFile(path.join(root,'database-maintenance.jsonl'),record(later,'intent')+record(later,'applied'))
// Reader has no SQL pool/schema query at all. External generation can rotate
// without reviving old credentials or invalidating historical acceptance.
await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,9));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'accepted-current-state')
await appendFile(path.join(root,'database-maintenance.jsonl'),record(randomUUID(),'intent'));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('new explicit acceptance can cover a later uncertainty without altering old records',()=>own(async root=>{
const modeled=await modeledStartup(),first=await publish(root,await acceptance(root));await appendFile(path.join(root,'database-maintenance.jsonl'),record(randomUUID(),'intent'))
assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required');const second=await acceptance(root,digest(first));second.acceptedAt='2026-11-02T00:02:00.000Z';await publish(root,second)
assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'accepted-current-state');assert.deepEqual(await readFile(path.join(root,'maintenance-acceptances',JSON.parse(first.toString()).resolutionId+'.json')),first)
}))
test('covered bytes drift, missing proof, forks and malformed tail never unlock startup',()=>own(async(root,uncertain)=>{
const modeled=await modeledStartup(),value=await acceptance(root),raw=await publish(root,value),filename=path.join(root,'maintenance-acceptances',value.resolutionId+'.json'),journal=path.join(root,'database-maintenance.jsonl'),original=await readFile(journal)
for(const mutation of ['receipt','tail','record','oversized-record','fork','target','generation']){
const receipt=path.join(root,'restore-vault',uncertain+'.receipt.json'),receiptBefore=await readFile(receipt)
if(mutation==='receipt')await writeFile(receipt,receiptBefore.toString().replace('RESTORE_UNCONFIRMED','OTHER_UNCONFIRMED'))
if(mutation==='tail')await appendFile(journal,'{"operationId":')
if(mutation==='record')await writeFile(filename,raw.subarray(0,raw.length-1));if(mutation==='oversized-record')await writeFile(filename,Buffer.alloc(1048577))
let forkFile='';if(mutation==='fork'){const fork={...value,resolutionId:randomUUID()};forkFile=path.join(root,'maintenance-acceptances',fork.resolutionId+'.json');await writeFile(forkFile,JSON.stringify(fork))}
if(mutation==='generation')await writeFile(path.join(root,'access-generation'),Buffer.alloc(31))
const result=await modeled.inspectDatabaseMaintenanceStartup(root,mutation==='target'?target.replace('/managed','/other'):target);assert.equal(result.classification,'inspection-required',mutation)
await writeFile(journal,original);await writeFile(filename,raw);await writeFile(receipt,receiptBefore);await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,7));if(forkFile)await rm(forkFile)
}
}))
test('credential rotation is excluded; host/database/TLS identity changes are bound',()=>{
const expected=runtimeDatabaseTargetSha256(target);assert.equal(runtimeDatabaseTargetSha256(target.replace('old-password','new-password')),expected)
for(const changed of [target.replace('db.example','other.example'),target.replace('/managed','/different'),target.replace('sslmode=require','sslmode=disable')])assert.notEqual(runtimeDatabaseTargetSha256(changed),expected)
assert.throws(()=>runtimeDatabaseTargetSha256(target+'&unreviewed_target_option=true'))
})
test('native Windows never treats modeled POSIX acceptance as real protected authorization',()=>own(async root=>{
if(process.platform!=='win32')return
await publish(root,await acceptance(root));assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'inspection-required')
}))
test('acceptance never accepts torn/duplicate transition grammar or fabricated current proof',()=>own(async root=>{
const facts=await readMaintenanceRecoveryFacts(root),recordValue=await acceptance(root),raw=Buffer.from(JSON.stringify(recordValue))
assert.throws(()=>parseMaintenanceJournal(Buffer.from(record(randomUUID(),'applied'))));assert.throws(()=>parseMaintenanceJournal(Buffer.from('{bad}')))
const wrong={...recordValue,knownCurrent:{...knownCurrent,source:{...source,inRecovery:true}}};assert.throws(()=>evaluateMaintenanceAcceptances([Buffer.from(JSON.stringify(wrong))],facts.journal,facts.vault.evidence,recordValue.runtimeTargetSha256))
const empty={...recordValue,journalCoverage:{...recordValue.journalCoverage,unresolvedOperations:[]},vaultCoverage:[]};assert.throws(()=>evaluateMaintenanceAcceptances([Buffer.from(JSON.stringify(empty))],facts.journal,facts.vault.evidence,recordValue.runtimeTargetSha256));assert(evaluateMaintenanceAcceptances([raw],facts.journal,facts.vault.evidence,recordValue.runtimeTargetSha256).acceptedCoreIds.size)
}))
test('without an acceptance file existing completed-history startup gains no new target-parser prerequisite',()=>own(async(root,uncertain,applied)=>{
await writeFile(path.join(root,'database-maintenance.jsonl'),record(applied,'intent')+record(applied,'applied'))
const receipt=JSON.parse((await readFile(path.join(root,'restore-vault',uncertain+'.receipt.json'))).toString());await rm(path.join(root,'restore-vault',uncertain+'.receipt.json'));await rm(path.join(root,'restore-vault',receipt.archiveRef+'.used'))
assert.equal((await inspectDatabaseMaintenanceStartup(root,'postgres:///managed?host=%2Ftmp&options=existing')).classification,'completed-core-maintenance')
}))
test('new unresolved vault marker blocks even without a new core journal record',()=>own(async root=>{
const modeled=await modeledStartup();await publish(root,await acceptance(root));const operationId=randomUUID(),archiveRef=randomUUID(),startedAt='2026-11-03T00:00:00.000Z',file=path.join(root,'restore-vault',operationId+'.receipt.json')
await writeFile(file,JSON.stringify({operationId,archiveRef,state:'running',startedAt,automaticRetry:false}));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await writeFile(file,JSON.stringify({operationId,archiveRef,state:'blocked',startedAt,automaticRetry:false,errorCode:'PERMISSION_DENIED',finishedAt:startedAt}));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'accepted-current-state')
const dangling=randomUUID();await writeFile(path.join(root,'restore-vault',dangling+'.used'),JSON.stringify({operationId:randomUUID(),at:startedAt}));assert.equal((await modeled.inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('native POSIX immutable fsynced acceptance is consumed by actual startup; mode and symlink drift refuse',async context=>{
if(process.platform==='win32'){context.skip('Native POSIX filesystem acceptance must run directly on Linux; Windows model is not proof');return}
await own(async root=>{
const {writeFile:writeProtected}=await import('../../scripts/writer-profile/operator-files.mjs'),value=await acceptance(root),directory=path.join(root,'maintenance-acceptances'),filename=path.join(directory,value.resolutionId+'.json')
await mkdir(directory,{mode:0o700});await writeProtected(filename,Buffer.from(JSON.stringify(value)),{flag:'wx',mode:0o600})
assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'accepted-current-state')
await chmod(filename,0o644);assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'inspection-required');await chmod(filename,0o600)
const alias=path.join(directory,randomUUID()+'.json');await symlink(filename,alias);assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'inspection-required');await rm(alias)
const later=randomUUID();await appendFile(path.join(root,'database-maintenance.jsonl'),record(later,'intent')+record(later,'applied'));assert.equal((await inspectDatabaseMaintenanceStartup(root,target)).classification,'accepted-current-state')
})
})
@@ -0,0 +1,68 @@
/** Explicit historical acceptance, not restore success or permanent schema/data admission. */
import {createHash} from 'node:crypto'
export const MAINTENANCE_ACCEPTANCE_FORMAT='database-maintenance-current-state-acceptance-v1'
const sha=(value:Buffer|string)=>createHash('sha256').update(value).digest('hex')
const hex=(v:unknown):v is string=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
const uuid=(v:unknown):v is string=>typeof v==='string'&&/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(v)
function fail():never{throw Error('Explicit maintenance acceptance unavailable or conflicting')}
function exact(value:unknown,keys:string[]):asserts value is Record<string,unknown>{if(!value||typeof value!=='object'||Array.isArray(value)||Object.keys(value).sort().join('\0')!==keys.sort().join('\0'))fail()}
const instant=(v:unknown):v is string=>typeof v==='string'&&Number.isFinite(Date.parse(v))&&new Date(v).toISOString()===v
export function parseMaintenanceJSON(raw:Buffer):unknown{const text=new TextDecoder('utf8',{fatal:true}).decode(raw),value:unknown=JSON.parse(text);if(JSON.stringify(value)!==text.trim())fail();return value}
export interface MaintenanceOperation {operationId:string;state:'intent'|'applied'|'unconfirmed'}
export function parseMaintenanceJournal(raw:Buffer):MaintenanceOperation[]{
if(!raw.length||raw.length>8388608)fail();const text=new TextDecoder('utf8',{fatal:true}).decode(raw);if(!text.endsWith('\n'))fail()
const lines=text.slice(0,-1).split('\n');if(lines.length>65536)fail();const operations=new Map<string,MaintenanceOperation['state']>()
for(const line of lines){if(!line||Buffer.byteLength(line)>256)fail();const value=parseMaintenanceJSON(Buffer.from(line));exact(value,['operationId','state','at']);if(!uuid(value.operationId)||!instant(value.at))fail()
if(value.state==='intent'){if(operations.has(value.operationId))fail();operations.set(value.operationId,'intent')}
else if(value.state==='applied'||value.state==='unconfirmed'){if(operations.get(value.operationId)!=='intent')fail();operations.set(value.operationId,value.state)}else fail()
}
return [...operations].map(([operationId,state])=>({operationId,state}))
}
export function runtimeDatabaseTargetSha256(raw:string):string{
const url=new URL(raw);if(!['postgres:','postgresql:'].includes(url.protocol)||!url.hostname||url.hash)fail()
const port=Number(url.port||5432);if(!Number.isInteger(port)||port<1||port>65535)fail();const database=decodeURIComponent(url.pathname.slice(1));if(!database||/[\x00-\x1f\x7f/]/.test(database))fail()
const relevant=new Set(['sslmode','ssl','sslrootcert','sslcert','sslkey','channel_binding','gssencmode','target_session_attrs']),ignored=new Set(['user','password','application_name','connect_timeout','statement_timeout','lock_timeout'])
const tls:Record<string,string>={};for(const [key,value]of url.searchParams){if(relevant.has(key)){if(Object.hasOwn(tls,key))fail();tls[key]=value}else if(!ignored.has(key))fail()}
return sha(JSON.stringify({format:'database-target-selector-v1',host:url.hostname.toLowerCase(),port,database,tls:Object.fromEntries(Object.entries(tls).sort())}))
}
export interface MaintenanceMarker {file:string;bytes:number;sha256:string}
export interface MaintenanceKnownCurrent {transport:{mode:'tls'|'local-loopback';endpointSha256:string};source:{systemIdentifier:string;databaseOid:string;databaseName:string;serverVersionNum:number;inRecovery:false};schemaSha256:string;rawSchemaSha256:string;physicalSchemaSha256:string;manifestSha256:string;markersSha256:string}
export interface MaintenanceAcceptance {
format:typeof MAINTENANCE_ACCEPTANCE_FORMAT;resolutionId:string;decision:'accept-observed-current-state';historicOutcomeChanged:false;automaticRestore:false;
actor:{kind:'host-operator';id:string};approvalSha256:string;inspectionSha256:string;approvedAt:string;approvalExpiresAt:string;acceptedAt:string;
runtimeTargetSha256:string;previousResolutionSha256:string|null;journalCoverage:{prefixBytes:number;sha256:string;unresolvedOperations:MaintenanceOperation[]};vaultCoverage:MaintenanceMarker[];generationObservedSha256:string;knownCurrent:MaintenanceKnownCurrent
}
export function validateMaintenanceKnownCurrent(value:unknown):asserts value is MaintenanceKnownCurrent{
exact(value,['transport','source','schemaSha256','rawSchemaSha256','physicalSchemaSha256','manifestSha256','markersSha256']);if(!['schemaSha256','rawSchemaSha256','physicalSchemaSha256','manifestSha256','markersSha256'].every(key=>hex(value[key])))fail()
exact(value.transport,['mode','endpointSha256']);if(!['tls','local-loopback'].includes(String(value.transport.mode))||!hex(value.transport.endpointSha256))fail()
exact(value.source,['systemIdentifier','databaseOid','databaseName','serverVersionNum','inRecovery']);const s=value.source
if(typeof s.systemIdentifier!=='string'||!/^[1-9][0-9]{0,19}$/.test(s.systemIdentifier)||BigInt(s.systemIdentifier)>18446744073709551615n||typeof s.databaseOid!=='string'||!/^[1-9][0-9]{0,9}$/.test(s.databaseOid)||BigInt(s.databaseOid)>4294967295n||typeof s.databaseName!=='string'||!/^[^\x00-\x1f\x7f]{1,63}$/.test(s.databaseName)||!Number.isInteger(s.serverVersionNum)||Number(s.serverVersionNum)<180000||Number(s.serverVersionNum)>=190000||s.inRecovery!==false)fail()
}
export function validateMaintenanceAcceptance(value:unknown):asserts value is MaintenanceAcceptance{
exact(value,['format','resolutionId','decision','historicOutcomeChanged','automaticRestore','actor','approvalSha256','inspectionSha256','approvedAt','approvalExpiresAt','acceptedAt','runtimeTargetSha256','previousResolutionSha256','journalCoverage','vaultCoverage','generationObservedSha256','knownCurrent'])
if(value.format!==MAINTENANCE_ACCEPTANCE_FORMAT||!uuid(value.resolutionId)||value.decision!=='accept-observed-current-state'||value.historicOutcomeChanged!==false||value.automaticRestore!==false)fail()
exact(value.actor,['kind','id']);if(value.actor.kind!=='host-operator'||typeof value.actor.id!=='string'||!/^[^\x00-\x1f\x7f]{1,160}$/.test(value.actor.id))fail()
if(!['approvalSha256','inspectionSha256','runtimeTargetSha256','generationObservedSha256'].every(key=>hex(value[key]))||(value.previousResolutionSha256!==null&&!hex(value.previousResolutionSha256)))fail()
if(!instant(value.approvedAt)||!instant(value.approvalExpiresAt)||!instant(value.acceptedAt)||Date.parse(value.approvedAt)>Date.parse(value.acceptedAt)||Date.parse(value.acceptedAt)>=Date.parse(value.approvalExpiresAt))fail()
exact(value.journalCoverage,['prefixBytes','sha256','unresolvedOperations']);const coverage=value.journalCoverage
if(!Number.isSafeInteger(coverage.prefixBytes)||Number(coverage.prefixBytes)<1||Number(coverage.prefixBytes)>8388608||!hex(coverage.sha256)||!Array.isArray(coverage.unresolvedOperations)||coverage.unresolvedOperations.length>65536)fail()
const ids=new Set<string>();for(const operation of coverage.unresolvedOperations){exact(operation,['operationId','state']);if(!uuid(operation.operationId)||!['intent','unconfirmed'].includes(String(operation.state))||ids.has(operation.operationId))fail();ids.add(operation.operationId)}
if(!Array.isArray(value.vaultCoverage)||value.vaultCoverage.length>4096)fail();const files=new Set<string>();for(const marker of value.vaultCoverage){exact(marker,['file','bytes','sha256']);if(typeof marker.file!=='string'||!/^([a-f0-9-]{36})\.(?:used|receipt\.json)$/.test(marker.file)||!uuid(marker.file.slice(0,36))||!Number.isSafeInteger(marker.bytes)||Number(marker.bytes)<1||Number(marker.bytes)>65536||!hex(marker.sha256)||files.has(marker.file))fail();files.add(marker.file)}
if(!ids.size&&!files.size)fail();validateMaintenanceKnownCurrent(value.knownCurrent)
}
export function evaluateMaintenanceAcceptances(rawRecords:readonly Buffer[],journal:Buffer,markers:readonly MaintenanceMarker[],targetSha256:string){
if(rawRecords.length>64)fail();const records=new Map<string,MaintenanceAcceptance>(),ids=new Set<string>()
for(const raw of rawRecords){if(!raw.length||raw.length>1048576)fail();const value=parseMaintenanceJSON(raw);validateMaintenanceAcceptance(value);const digest=sha(raw);if(records.has(digest)||ids.has(value.resolutionId)||value.runtimeTargetSha256!==targetSha256)fail();records.set(digest,value);ids.add(value.resolutionId)
const prefix=journal.subarray(0,value.journalCoverage.prefixBytes);if(prefix.length!==value.journalCoverage.prefixBytes||sha(prefix)!==value.journalCoverage.sha256)fail()
const unresolved=parseMaintenanceJournal(prefix).filter(op=>op.state!=='applied');if(JSON.stringify(unresolved)!==JSON.stringify(value.journalCoverage.unresolvedOperations))fail()
for(const marker of value.vaultCoverage){if(!markers.some(actual=>JSON.stringify(actual)===JSON.stringify(marker)))fail()}
}
if(!records.size)return {headSha256:null as string|null,acceptedCoreIds:new Set<string>(),acceptedVaultFiles:new Set<string>(),count:0}
const parents=new Set([...records.values()].map(value=>value.previousResolutionSha256).filter((v):v is string=>v!==null)),heads=[...records.keys()].filter(key=>!parents.has(key));if(heads.length!==1)fail()
let current:string|null=heads[0],child:MaintenanceAcceptance|undefined;const visited=new Set<string>(),acceptedCoreIds=new Set<string>(),acceptedVaultFiles=new Set<string>()
while(current!==null){if(visited.has(current))fail();const value=records.get(current);if(!value)fail();visited.add(current)
if(child){if(child.journalCoverage.prefixBytes<value.journalCoverage.prefixBytes||Date.parse(child.acceptedAt)<Date.parse(value.acceptedAt)||value.vaultCoverage.some(marker=>!child!.vaultCoverage.some(later=>JSON.stringify(later)===JSON.stringify(marker))))fail()}
value.journalCoverage.unresolvedOperations.forEach(op=>acceptedCoreIds.add(op.operationId));value.vaultCoverage.forEach(marker=>acceptedVaultFiles.add(marker.file));child=value;current=value.previousResolutionSha256
}
if(visited.size!==records.size)fail();return {headSha256:heads[0],acceptedCoreIds,acceptedVaultFiles,count:records.size}
}
@@ -0,0 +1,168 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {mkdtemp,readFile,writeFile,appendFile,mkdir,rm,readdir,link} from 'node:fs/promises'
import * as filesystem from 'node:fs/promises'
import {randomUUID} from 'node:crypto'
import path from 'node:path'
import {fileURLToPath,pathToFileURL} from 'node:url'
import {spawnSync} from 'node:child_process'
import {createRequire} from 'node:module'
import {runInNewContext} from 'node:vm'
import {inspectDatabaseMaintenanceStartup,assertDatabaseMaintenanceStartup,DatabaseMaintenanceStartupError} from './database-maintenance-startup.js'
const testFile=fileURLToPath(import.meta.url),here=path.dirname(testFile),backend=path.resolve(here,'../..'),area=path.resolve(backend,'../tmp'),require=createRequire(import.meta.url),ts=require('typescript')
const stamp='2026-11-03T00:00:00.000Z'
// Same installed loader as the backend's owned test conventions, resolved to
// an absolute file URL rather than assuming a child's package resolution.
if(process.argv[2]==='--inspect-owned-maintenance'){
const value=await inspectDatabaseMaintenanceStartup(process.argv[3])
process.stdout.write(JSON.stringify(value)+'\n');process.exit(value.classification==='inspection-required'?1:0)
}
const record=(operationId:string,state:string)=>JSON.stringify({operationId,state,at:stamp})+'\n'
async function own(work:(root:string)=>Promise<void>){
await mkdir(area,{recursive:true});const root=await mkdtemp(path.join(area,'owned-maintenance-startup-'))
try{await work(root)}finally{assert(path.dirname(root)===area&&path.basename(root).startsWith('owned-maintenance-startup-'));await rm(root,{recursive:true,force:true})}
}
async function generation(root:string){await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,7))}
async function completed(root:string){const id=randomUUID();await generation(root);await writeFile(path.join(root,'database-maintenance.jsonl'),record(id,'intent')+record(id,'applied'));return id}
async function outer(root:string,state:string,used=true,wrongClaim=false){
const vault=path.join(root,'restore-vault');await mkdir(vault,{recursive:true})
const operationId=randomUUID(),archiveRef=randomUUID()
const value:Record<string,unknown>={operationId,archiveRef,state,startedAt:stamp,automaticRetry:false}
if(state==='applied')Object.assign(value,{finishedAt:stamp,accessRevoked:true,warnings:['migrationError']})
else if(state!=='running')Object.assign(value,{errorCode:'RESTORE_UNCONFIRMED',finishedAt:stamp})
await writeFile(path.join(vault,operationId+'.receipt.json'),JSON.stringify(value))
if(used)await writeFile(path.join(vault,archiveRef+'.used'),JSON.stringify({operationId:wrongClaim?randomUUID():operationId,at:stamp}))
return {vault,operationId,archiveRef}
}
function child(root:string){
const value=spawnSync(process.execPath,['--import',pathToFileURL(require.resolve('tsx')).href,testFile,'--inspect-owned-maintenance',root],{cwd:backend,encoding:'utf8',timeout:10000})
assert.equal(value.error,undefined);assert.equal(value.signal,null)
return {code:value.status,inspection:JSON.parse(value.stdout)}
}
test('missing external state is read-only fresh legacy; empty existing state does not create authority',()=>own(async root=>{
const absent=path.join(root,'not-created'),before=await readdir(root)
assert.equal((await inspectDatabaseMaintenanceStartup(absent)).classification,'fresh-legacy')
assert.deepEqual(await readdir(root),before)
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'fresh-legacy')
assert.deepEqual(await readdir(root),before)
}))
test('empty journal is only before-intent when an existing external access generation is readable',()=>own(async root=>{
await writeFile(path.join(root,'database-maintenance.jsonl'),'')
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await generation(root)
const result=await inspectDatabaseMaintenanceStartup(root);assert.equal(result.classification,'empty-before-intent');assert.equal(result.accessGenerationObserved,true)
}))
test('actual fresh process refuses dangling intent and unconfirmed without replay or rewriting',()=>own(async root=>{
await generation(root);const file=path.join(root,'database-maintenance.jsonl'),id=randomUUID()
await writeFile(file,record(id,'intent'))
const before=await readFile(file)
const first=child(root);assert.equal(first.code,1);assert.equal(first.inspection.reason,'UNFINISHED_MAINTENANCE')
assert.deepEqual(await readFile(file),before)
await appendFile(file,record(id,'unconfirmed'))
const unknown=child(root);assert.equal(unknown.code,1);assert.equal(unknown.inspection.reason,'UNCONFIRMED_MAINTENANCE')
assert.equal(unknown.inspection.automaticRestore,false)
}))
test('core applied allows ordinary bootstrap but never infers outer success, audit import or repaired schema',()=>own(async root=>{
await completed(root)
const result=child(root);assert.equal(result.code,0);assert.equal(result.inspection.classification,'completed-core-maintenance')
assert.equal(result.inspection.outerOperationInferred,false);assert.equal(result.inspection.auditImportStatus,'not-verified');assert.equal(result.inspection.postRepairSchemaStatus,'not-inspected')
}))
for(const state of ['running','unknown'])test('core applied cannot promote outer '+state+' receipt to completed',()=>own(async root=>{
await completed(root);await outer(root,state)
const result=await inspectDatabaseMaintenanceStartup(root);assert.equal(result.classification,'inspection-required');assert.equal(result.reason,'EXTERNAL_OPERATION_UNRESOLVED')
}))
test('outer applied receipt requires exact matching persisted used claim',()=>own(async root=>{
await completed(root);const f=await outer(root,'applied',true)
assert.equal((await inspectDatabaseMaintenanceStartup(root)).outerAppliedReceipts,1)
await writeFile(path.join(f.vault,f.archiveRef+'.used'),JSON.stringify({operationId:randomUUID(),at:stamp}))
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('blocked predispatch operation may share an archive later successfully consumed by a different operation',()=>own(async root=>{
await completed(root);const a=await outer(root,'blocked',false),operationB=randomUUID()
await writeFile(path.join(a.vault,operationB+'.receipt.json'),JSON.stringify({operationId:operationB,archiveRef:a.archiveRef,state:'applied',startedAt:stamp,automaticRetry:false,finishedAt:stamp,accessRevoked:true,warnings:[]}))
await writeFile(path.join(a.vault,a.archiveRef+'.used'),JSON.stringify({operationId:operationB,at:stamp}))
const result=await inspectDatabaseMaintenanceStartup(root)
assert.equal(result.classification,'completed-core-maintenance');assert.equal(result.blockedReceipts,1);assert.equal(result.outerAppliedReceipts,1)
await writeFile(path.join(a.vault,a.archiveRef+'.used'),JSON.stringify({operationId:a.operationId,at:stamp}))
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('missing journal with attempt markers is not a new clean install',()=>own(async root=>{
await outer(root,'applied',true)
const value=await inspectDatabaseMaintenanceStartup(root);assert.equal(value.classification,'inspection-required');assert.equal(value.reason,'JOURNAL_ABSENT_WITH_ATTEMPT_MARKERS')
}))
test('orphan used claim, unknown publication temporary file and blocked-with-used refuse',()=>own(async root=>{
await completed(root);const f=await outer(root,'blocked',true)
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
await rm(path.join(f.vault,f.operationId+'.receipt.json'))
assert.equal((await inspectDatabaseMaintenanceStartup(root)).reason,'EXTERNAL_OPERATION_UNRESOLVED')
await rm(path.join(f.vault,f.archiveRef+'.used'));await writeFile(path.join(f.vault,'receipt-publication.tmp'),'partial')
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('malformed tails, duplicates, unknown fields/states and impossible transitions never admit startup',()=>own(async root=>{
await generation(root);const id=randomUUID(),good=record(id,'intent')
const bad=[good.slice(0,-1),good+'{',good+record(id,'intent'),good+record(id,'applied')+record(id,'applied'),record(id,'applied'),good+record(id,'future'),JSON.stringify({operationId:id,state:'intent',at:stamp,ready:true})+'\n',`{"operationId":"${id}","state":"intent","state":"applied","at":"${stamp}"}\n`]
for(const content of bad){await writeFile(path.join(root,'database-maintenance.jsonl'),content);assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')}
}))
test('journal byte bound refuses without accepting a truncated history',()=>own(async root=>{
await generation(root);const file=path.join(root,'database-maintenance.jsonl')
await writeFile(file,Buffer.alloc(8*1024*1024+1,32));assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('hardlinked external files are not accepted as complete history',async t=>own(async root=>{
await generation(root);const file=path.join(root,'database-maintenance.jsonl')
await writeFile(file,record(randomUUID(),'intent'))
try{await link(file,path.join(root,'other-link'))}catch(error){if((error as NodeJS.ErrnoException).code==='EPERM'){t.skip('Host filesystem policy refuses creating an owned hardlink');return}throw error}
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'inspection-required')
}))
test('actual patched bootstrap issues no SQL, migration or consumer work before recovery guard',()=>own(async root=>{
await generation(root);await writeFile(path.join(root,'database-maintenance.jsonl'),record(randomUUID(),'intent'))
const source=await readFile(path.join(backend,'src/db/bootstrap.ts'),'utf8'),events:string[]=[],exports:Record<string,unknown>={}
const query=async()=>{events.push('sql');return {rows:[{sqlite_imported_at:'done'}]}},client={query,release:()=>{}}
const modules:Record<string,unknown>={
'./index.js':{pool:{query,connect:async()=>{events.push('connect');return client}}},
'./migrate.js':{applySqlMigrations:async()=>{events.push('migrations')}},
'./partitions.js':{dropExpiredPartitions:async()=>{events.push('drop')},ensurePartitionsAround:async()=>{events.push('partitions')}},
'./sqlite-import.js':{shouldImportSqlite:async()=>false,sqliteFileLooksPresent:()=>false,importSqliteToPostgres:async()=>{events.push('import')}},
'../config.js':{env:{DATABASE_PATH:'synthetic-only'}},'../modules/mcp/security.js':{mcpStateDirectory:()=>root},
'../services/database-maintenance-startup.js':{assertDatabaseMaintenanceStartup:async(directory:string)=>{events.push('guard');return assertDatabaseMaintenanceStartup(directory)}},
'../services/database-restore-audit.js':{reconcileDatabaseRestoreAudits:async()=>{events.push('audit-reconcile')}},
}
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText
runInNewContext(code,{exports,require:(name:string)=>{assert(name in modules);return modules[name]},console})
const initialize=exports.initDatabase as ()=>Promise<void>
await assert.rejects(initialize(),error=>error instanceof DatabaseMaintenanceStartupError)
assert.deepEqual(events,['guard'])
await writeFile(path.join(root,'database-maintenance.jsonl'),'');events.length=0
await initialize();assert.equal(events[0],'guard');assert(events.includes('sql')&&events.includes('migrations'))
const main=await readFile(path.join(backend,'src/index.ts'),'utf8')
const start=main.indexOf('if (isMain)');assert(start>=0)
const body=main.slice(start);assert(body.indexOf('await assertDatabaseMaintenanceStartup')<body.indexOf('await initDatabase()'));assert(body.indexOf('await initDatabase()')<body.indexOf('await app.listen'))
}))
test('actual journal append between metadata capture and open refuses an unstable snapshot',()=>own(async root=>{
await generation(root);const operation=randomUUID(),file=path.join(root,'database-maintenance.jsonl')
await writeFile(file,record(operation,'intent'))
const source=await readFile(path.join(backend,'src/services/database-maintenance-startup.ts'),'utf8'),exports:Record<string,unknown>={};let changed=false
const model={...filesystem,lstat:async(...args:Parameters<typeof filesystem.lstat>)=>{
const value=await Reflect.apply(filesystem.lstat,filesystem,args)
if(!changed&&args[0]===file){changed=true;await appendFile(file,record(operation,'applied'))}
return value
}}
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name==='node:fs/promises'?model:require(name),Buffer,TextDecoder,Date,Map,Set,Object,Error})
const inspect=exports.inspectDatabaseMaintenanceStartup as typeof inspectDatabaseMaintenanceStartup
const value=await inspect(root);assert.equal(changed,true);assert.equal(value.classification,'inspection-required')
assert.equal(value.reason,'EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED')
assert.equal((await inspectDatabaseMaintenanceStartup(root)).classification,'completed-core-maintenance','a later distinct stable inspection may observe actual terminal bytes without rewriting them')
}))
test('existing non-directory or unverifiable ancestor never becomes a missing clean state',()=>own(async root=>{
await writeFile(path.join(root,'ordinary-file'),'not-directory')
assert.equal((await inspectDatabaseMaintenanceStartup(path.join(root,'ordinary-file','state'))).classification,'inspection-required')
const source=await readFile(path.join(backend,'src/services/database-maintenance-startup.ts'),'utf8'),exports:Record<string,unknown>={}
const model={...filesystem,realpath:async(file:Parameters<typeof filesystem.realpath>[0])=>{
if(file===root)throw Object.assign(Error('Synthetic ancestor became unverifiable'),{code:'ENOENT'})
return filesystem.realpath(file)
}}
const code=ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText
runInNewContext(code,{exports,require:(name:string)=>name==='node:fs/promises'?model:require(name),Buffer,TextDecoder,Date,Map,Set,Object,Error})
const inspect=exports.inspectDatabaseMaintenanceStartup as typeof inspectDatabaseMaintenanceStartup
assert.equal((await inspect(path.join(root,'not-created-state'))).classification,'inspection-required')
}))
@@ -0,0 +1,181 @@
/** Read-only classification of the EXISTING external maintenance grammar.
* No restore, journal rewrite, generation rotation, SQL, role issuer or READY. */
import process from 'node:process'
import {constants,type BigIntStats} from 'node:fs'
import {lstat,open,readdir,realpath} from 'node:fs/promises'
import path from 'node:path'
import {createHash} from 'node:crypto'
import {parseMaintenanceJournal,evaluateMaintenanceAcceptances,runtimeDatabaseTargetSha256,type MaintenanceMarker} from './database-maintenance-resolution.js'
const MAX_JOURNAL_BYTES=8*1024*1024,MAX_VAULT_ENTRIES=4096,MAX_RECEIPT_BYTES=65536
const uuid=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
type Reason='NONE'|'UNFINISHED_MAINTENANCE'|'UNCONFIRMED_MAINTENANCE'|'EXTERNAL_OPERATION_UNRESOLVED'|'JOURNAL_ABSENT_WITH_ATTEMPT_MARKERS'|'EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED'
export interface MaintenanceStartupInspection {
readonly classification:'fresh-legacy'|'empty-before-intent'|'completed-core-maintenance'|'accepted-current-state'|'inspection-required'
readonly reason:Reason
readonly journalOperations:number
readonly coreApplied:number
readonly outerAppliedReceipts:number
readonly blockedReceipts:number
readonly accessGenerationObserved:boolean
readonly outerOperationInferred:false
readonly auditImportStatus:'not-verified'
readonly postRepairSchemaStatus:'not-inspected'
readonly acceptedHistoricResolutions:number
readonly historicOutcomesChanged:false
readonly automaticRestore:false
}
class Refusal extends Error {constructor(readonly reason:Reason='EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED'){super('External maintenance state requires inspection')}}
function refuse(reason?:Reason):never{throw new Refusal(reason)}
function plain(value:unknown):value is Record<string,unknown>{return value!==null&&typeof value==='object'&&!Array.isArray(value)}
function exact(value:unknown,keys:readonly string[]):asserts value is Record<string,unknown>{if(!plain(value)||Object.keys(value).sort().join('\0')!==[...keys].sort().join('\0'))refuse()}
function id(value:unknown):asserts value is string{if(typeof value!=='string'||!uuid.test(value))refuse()}
function instant(value:unknown):asserts value is string{if(typeof value!=='string'||!Number.isFinite(Date.parse(value))||new Date(value).toISOString()!==value)refuse()}
function json(raw:Buffer):unknown{
const text=new TextDecoder('utf8',{fatal:true}).decode(raw)
// All existing writers produce JSON.stringify output. Exact roundtrip also
// refuses duplicate keys and ambiguous/trailing non-JSON representations.
const value:unknown=JSON.parse(text)
if(JSON.stringify(value)!==text.trim())refuse()
return value
}
function identity(s:BigIntStats){return [s.dev,s.ino,s.size,s.mode,s.nlink,s.mtimeNs,s.ctimeNs].map(String).join(':')}
async function directory(file:string,optional=false){
let s
try{s=await lstat(file,{bigint:true})}
catch(error){if(optional&&(error as NodeJS.ErrnoException).code==='ENOENT')return null;throw error}
if(!s.isDirectory()||s.isSymbolicLink())refuse()
// Only initial lstat absence is a new/legacy absence. An existing ancestor
// whose realpath disappears or cannot be verified is ambiguous, not fresh.
try{if(await realpath(file)!==file)refuse()}catch{refuse()}
return identity(s)
}
async function existingAncestors(file:string){
for(let current=path.dirname(file);;current=path.dirname(current)){
await directory(current,true)
if(current===path.dirname(current))break
}
}
async function bounded(file:string,maximum:number,optional=false){
let before
try{before=await lstat(file,{bigint:true})}catch(error){if(optional&&(error as NodeJS.ErrnoException).code==='ENOENT')return null;throw error}
if(!before.isFile()||before.isSymbolicLink()||before.nlink!==1n||before.size>BigInt(maximum)||await realpath(file)!==file)refuse()
const signature=(s:typeof before)=>[s.dev,s.ino,s.size,s.mode,s.nlink,s.mtimeNs,s.ctimeNs].map(String).join(':')
const handle=await open(file,constants.O_RDONLY|(constants.O_NOFOLLOW??0))
try{
const current=await handle.stat({bigint:true});if(signature(current)!==signature(before))refuse()
const data=Buffer.alloc(Number(current.size)+1);let size=0
while(size<data.length){const got=await handle.read(data,size,data.length-size,size);if(!got.bytesRead)break;size+=got.bytesRead}
if(BigInt(size)!==current.size||signature(await handle.stat({bigint:true}))!==signature(before)||signature(await lstat(file,{bigint:true}))!==signature(before))refuse()
return data.subarray(0,size)
}finally{await handle.close()}
}
export interface AppliedRestoreAuditSource {readonly operationId:string;readonly archiveRef:string;readonly finishedAt:string;readonly receiptSha256:string;readonly receipt:Readonly<Record<string,unknown>>}
interface VaultFacts {markers:number;applied:number;blocked:number;unresolved:boolean;auditSources:readonly AppliedRestoreAuditSource[];evidence:readonly MaintenanceMarker[];unresolvedFiles:readonly string[]}
async function vaultFacts(stateDirectory:string):Promise<VaultFacts>{
const root=path.join(stateDirectory,'restore-vault'),before=await directory(root,true)
if(before===null)return {markers:0,applied:0,blocked:0,unresolved:false,auditSources:[],evidence:[],unresolvedFiles:[]}
const auditSources:AppliedRestoreAuditSource[]=[],evidence:MaintenanceMarker[]=[],unresolvedFiles=new Set<string>()
const names=(await readdir(root)).sort();if(names.length>MAX_VAULT_ENTRIES)refuse()
const signatures=new Map<string,string>(),used=new Map<string,{operationId:string}>(),receipts=new Map<string,{state:string;archiveRef:string}>()
for(const name of names){
const match=/^([a-f0-9-]{36})\.(used|receipt\.json|archive\.json|archive\.bin)$/.exec(name)
if(!match)refuse();id(match[1])
const file=path.join(root,name),stat=await lstat(file,{bigint:true});if(!stat.isFile()||stat.isSymbolicLink()||stat.nlink!==1n||await realpath(file)!==file)refuse()
signatures.set(name,identity(stat))
if(match[2]==='used'){
const raw=(await bounded(file,4096))!;evidence.push({file:name,bytes:raw.length,sha256:createHash('sha256').update(raw).digest('hex')});const value=json(raw);exact(value,['operationId','at']);id(value.operationId);instant(value.at)
used.set(match[1],{operationId:value.operationId})
}else if(match[2]==='receipt.json'){
const raw=(await bounded(file,MAX_RECEIPT_BYTES))!,value=json(raw);evidence.push({file:name,bytes:raw.length,sha256:createHash('sha256').update(raw).digest('hex')})
if(!plain(value)||!['running','applied','unknown','blocked'].includes(String(value.state)))refuse()
const fields=['operationId','archiveRef','state','startedAt','automaticRetry']
if(value.state==='applied')fields.push('finishedAt','accessRevoked','warnings')
else if(value.state!=='running')fields.push('errorCode','finishedAt')
exact(value,fields);id(value.operationId);id(value.archiveRef);instant(value.startedAt)
if(value.operationId!==match[1]||value.automaticRetry!==false)refuse()
if(value.state!=='running')instant(value.finishedAt)
if(value.state==='applied'){
const allowed=new Set(['listenerError','schedulerError','cleanupError','migrationError','maintenanceError'])
if(value.accessRevoked!==true||!Array.isArray(value.warnings)||value.warnings.length>5||value.warnings.some(w=>typeof w!=='string'||!allowed.has(w))||new Set(value.warnings).size!==value.warnings.length)refuse()
auditSources.push(Object.freeze({operationId:value.operationId,archiveRef:value.archiveRef,finishedAt:value.finishedAt as string,receiptSha256:createHash('sha256').update(raw).digest('hex'),receipt:Object.freeze({...value})}))
}else if(value.state!=='running'&&(typeof value.errorCode!=='string'||!/^[A-Z][A-Z0-9_]{0,79}$/.test(value.errorCode)))refuse()
receipts.set(match[1],{state:String(value.state),archiveRef:value.archiveRef})
}
}
let applied=0,blocked=0,unresolved=false
for(const [operationId,receipt]of receipts){
const claim=used.get(receipt.archiveRef)
if(receipt.state==='applied'){if(!claim||claim.operationId!==operationId)refuse();applied++}
else if(receipt.state==='blocked'){if(claim?.operationId===operationId)refuse();blocked++}
else{unresolved=true;unresolvedFiles.add(operationId+'.receipt.json')}
}
for(const [archiveRef,claim]of used){const receipt=receipts.get(claim.operationId);if(!receipt||receipt.archiveRef!==archiveRef||receipt.state!=='applied'){unresolved=true;unresolvedFiles.add(archiveRef+'.used')}}
if(await directory(root)!==before||(await readdir(root)).sort().join('\0')!==[...names].sort().join('\0'))refuse()
for(const [name,signature]of signatures)if(identity(await lstat(path.join(root,name),{bigint:true}))!==signature)refuse()
return {markers:used.size+receipts.size,applied,blocked,unresolved,auditSources:Object.freeze(auditSources),evidence:Object.freeze(evidence),unresolvedFiles:Object.freeze([...unresolvedFiles].sort())}
}
/** Strict inspection facts; no acceptance decision and no SQL. Operator commands
* use the same grammar, not a looser recovery parser. Existing single managed
* host/operator assumption applies; bounded scans are not a concurrent fence. */
export async function readMaintenanceRecoveryFacts(rawDirectory:string){
if(typeof rawDirectory!=='string'||!path.isAbsolute(rawDirectory)||path.resolve(rawDirectory)!==rawDirectory)refuse()
await existingAncestors(rawDirectory);const stateBefore=await directory(rawDirectory,true)
if(stateBefore===null)return {kind:'fresh-legacy' as const,journal:Buffer.alloc(0),operations:[],vault:{markers:0,applied:0,blocked:0,unresolved:false,auditSources:[],evidence:[],unresolvedFiles:[]} as VaultFacts,generation:null}
const journalFile=path.join(rawDirectory,'database-maintenance.jsonl'),raw=await bounded(journalFile,MAX_JOURNAL_BYTES,true),vault=await vaultFacts(rawDirectory)
if(await directory(rawDirectory)!==stateBefore)refuse()
if(raw===null||raw.length===0){if(vault.markers)refuse('JOURNAL_ABSENT_WITH_ATTEMPT_MARKERS');if(raw===null)return {kind:'fresh-legacy' as const,journal:Buffer.alloc(0),operations:[],vault,generation:null}
const generation=await bounded(path.join(rawDirectory,'access-generation'),32);if(generation?.length!==32)refuse();return {kind:'empty-before-intent' as const,journal:raw,operations:[],vault,generation}
}
const generation=await bounded(path.join(rawDirectory,'access-generation'),32);if(generation?.length!==32)refuse();const operations=parseMaintenanceJournal(raw)
if(!(await bounded(journalFile,MAX_JOURNAL_BYTES))!.equals(raw)||await directory(rawDirectory)!==stateBefore)refuse()
return {kind:'journal' as const,journal:raw,operations,vault,generation}
}
/** Acceptance grants startup admission, so POSIX ownership/modes are verified.
* Native Windows protection has no implementation and refuses, never modeled. */
export async function readMaintenanceAcceptanceRecords(stateDirectory:string):Promise<readonly Buffer[]>{
const root=path.join(stateDirectory,'maintenance-acceptances'),before=await directory(root,true);if(before===null)return []
if(process.platform==='win32'||typeof process.getuid!=='function')refuse()
const uid=BigInt(process.getuid()),protectedStat=async(file:string,isDirectory:boolean)=>{const s=await lstat(file,{bigint:true});if(s.uid!==uid||(s.mode&0o077n)!==0n||(isDirectory?!s.isDirectory():!s.isFile()))refuse()}
await protectedStat(root,true)
for(let current=path.dirname(root);;current=path.dirname(current)){const stat=await lstat(current,{bigint:true});if(stat.isSymbolicLink()||!stat.isDirectory()||(stat.uid!==uid&&stat.uid!==0n)||((stat.mode&0o022n)!==0n&&!(stat.uid===0n&&(stat.mode&0o1000n)!==0n)))refuse();if(current===path.dirname(current))break}
const names=(await readdir(root)).sort();if(names.length>64)refuse();const records:Buffer[]=[]
for(const name of names){if(!/^[a-f0-9-]{36}\.json$/.test(name))refuse();id(name.slice(0,36));const file=path.join(root,name);await protectedStat(file,false);const raw=(await bounded(file,1048576))!,value=json(raw);if(!plain(value)||value.resolutionId!==name.slice(0,36))refuse();records.push(raw)}
if(await directory(root)!==before||(await readdir(root)).sort().join('\0')!==names.join('\0'))refuse();return records
}
const configuredTarget=()=>process.env.DATABASE_URL??'postgres://mmapp:mmapp@127.0.0.1:5432/mmapp'
export async function inspectDatabaseMaintenanceStartup(rawDirectory:string,runtimeTarget=configuredTarget()):Promise<MaintenanceStartupInspection>{
const facts={journalOperations:0,coreApplied:0,outerAppliedReceipts:0,blockedReceipts:0,accessGenerationObserved:false,outerOperationInferred:false as const,auditImportStatus:'not-verified' as const,postRepairSchemaStatus:'not-inspected' as const,acceptedHistoricResolutions:0,historicOutcomesChanged:false as const,automaticRestore:false as const}
const result=(classification:MaintenanceStartupInspection['classification'],reason:Reason):MaintenanceStartupInspection=>Object.freeze({classification,reason,...facts})
try{
const state=await readMaintenanceRecoveryFacts(rawDirectory),records=await readMaintenanceAcceptanceRecords(rawDirectory)
facts.outerAppliedReceipts=state.vault.applied;facts.blockedReceipts=state.vault.blocked;facts.accessGenerationObserved=state.generation!==null
if(state.kind!=='journal'){if(records.length)refuse();return result(state.kind,'NONE')}
facts.journalOperations=state.operations.length;facts.coreApplied=state.operations.filter(op=>op.state==='applied').length
const accepted=evaluateMaintenanceAcceptances(records,state.journal,state.vault.evidence,records.length?runtimeDatabaseTargetSha256(runtimeTarget):'0'.repeat(64));facts.acceptedHistoricResolutions=accepted.count
const unresolved=state.operations.filter(op=>op.state!=='applied'&&!accepted.acceptedCoreIds.has(op.operationId))
if(unresolved.some(op=>op.state==='intent'))return result('inspection-required','UNFINISHED_MAINTENANCE')
if(unresolved.some(op=>op.state==='unconfirmed'))return result('inspection-required','UNCONFIRMED_MAINTENANCE')
if(state.vault.unresolvedFiles.some(file=>!accepted.acceptedVaultFiles.has(file)))return result('inspection-required','EXTERNAL_OPERATION_UNRESOLVED')
return result(accepted.count?'accepted-current-state':'completed-core-maintenance','NONE')
}catch(error){return result('inspection-required',error instanceof Refusal?error.reason:'EXTERNAL_STATE_UNAVAILABLE_OR_MALFORMED')}
}
export class DatabaseMaintenanceStartupError extends Error {
readonly code='DATABASE_MAINTENANCE_INSPECTION_REQUIRED'
constructor(readonly inspection:MaintenanceStartupInspection){super('External database maintenance requires read-only inspection before managed startup')}
}
export async function assertDatabaseMaintenanceStartup(directory:string,runtimeTarget=configuredTarget()):Promise<MaintenanceStartupInspection>{
const result=await inspectDatabaseMaintenanceStartup(directory,runtimeTarget)
if(result.classification==='inspection-required')throw new DatabaseMaintenanceStartupError(result)
return result
}
/** Audit recovery consumes the same strict grammar/claims as startup. No core
* applied record can manufacture an applied outer receipt or original actor. */
export async function readAppliedRestoreAuditSources(directory:string,runtimeTarget=configuredTarget()):Promise<readonly AppliedRestoreAuditSource[]>{
await assertDatabaseMaintenanceStartup(directory,runtimeTarget)
const before=await vaultFacts(directory)
await assertDatabaseMaintenanceStartup(directory,runtimeTarget)
const after=await vaultFacts(directory)
if(JSON.stringify(before)!==JSON.stringify(after))throw new DatabaseMaintenanceStartupError(await inspectDatabaseMaintenanceStartup(directory))
return after.auditSources
}
+44 -8
View File
@@ -3,14 +3,47 @@ function conflict(message: string): Error & { statusCode: number } {
return Object.assign(new Error(message), { statusCode: 409 })
}
type DatabaseAdmission = { active: boolean; kind: "request" | "exclusive"; requestAncestry: boolean; children: Set<Promise<void>> }
/** Handler lifetime, not socket lifetime: disconnect does not finish an active write. */
export class DatabaseRequestGate {
private readonly requestContext = new AsyncLocalStorage<boolean>()
assertNoRequestContext(): void { if (this.requestContext.getStore()) throw conflict("Scheduler bootstrap inside request context") }
private readonly requestContext = new AsyncLocalStorage<DatabaseAdmission>()
assertNoRequestContext(): void { if (this.requestContext.getStore()?.requestAncestry) throw conflict("Scheduler bootstrap inside request context") }
/** Live private scope only; detached children cannot retain a settled admission. */
hasActiveAdmission(): boolean { return this.requestContext.getStore()?.active === true }
/** Registration is synchronous; the parent drains this continuation and its
* descendants even if the caller deliberately does not await its result. */
async withinAdmission<T>(run: () => Promise<T>): Promise<T> {
const parent = this.requestContext.getStore()
if (!parent?.active) throw conflict("Database admission is no longer active")
const admission: DatabaseAdmission = { active: true, kind: parent.kind, requestAncestry: parent.requestAncestry, children: new Set() }
let release!: () => void
const settled = new Promise<void>(resolve => { release = resolve })
parent.children.add(settled)
try { return await this.requestContext.run(admission, run) }
finally {
await this.drainAdmission(admission)
parent.children.delete(settled)
release()
}
}
private async drainAdmission(admission: DatabaseAdmission): Promise<void> {
admission.active = false
await Promise.all(admission.children)
}
private openWaiters = new Set<() => void>()
isPaused(): boolean { return this.paused }
// Replacement uncertainty survives the temporary exclusive operation.
private admissionClosed = false
closeAdmission(): void { this.admissionClosed = true }
openAdmission(): void { this.admissionClosed = false; this.notifyOpen() }
private notifyOpen(): void {
if (this.isPaused()) return
const waiting = [...this.openWaiters]; this.openWaiters.clear()
for (const resolve of waiting) resolve()
}
isPaused(): boolean { return this.paused || this.admissionClosed }
whenOpen(): Promise<void> {
if (!this.paused) return Promise.resolve()
if (!this.isPaused()) return Promise.resolve()
return new Promise(resolve => { this.openWaiters.add(resolve) })
}
private paused = false
@@ -19,10 +52,13 @@ export class DatabaseRequestGate {
async request<T>(run: () => Promise<T>, cancelStream?: () => void): Promise<T> {
if (this.paused) throw conflict("Выполняется операция с базой приложения; повторите запрос позже")
if (this.admissionClosed) throw Object.assign(new Error("Схема базы после восстановления не подтверждена; требуется проверка или явное восстановление"), { statusCode: 503 })
const lease = Symbol()
this.active.set(lease, cancelStream)
try { return await this.requestContext.run(true, run) }
const admission: DatabaseAdmission = { active: true, kind: "request", requestAncestry: true, children: new Set() }
try { return await this.requestContext.run(admission, run) }
finally {
await this.drainAdmission(admission)
this.active.delete(lease)
if (!this.active.size) this.idle?.()
}
@@ -38,13 +74,13 @@ export class DatabaseRequestGate {
this.idle = resolve
timer = setTimeout(() => reject(conflict("Не удалось дождаться завершения API-запросов; операция с базой отменена")), timeoutMs)
})
return await run()
const admission: DatabaseAdmission = { active: true, kind: "exclusive", requestAncestry: this.requestContext.getStore()?.requestAncestry === true, children: new Set() }
try { return await this.requestContext.run(admission, run) } finally { await this.drainAdmission(admission) }
} finally {
if (timer) clearTimeout(timer)
this.idle = undefined
this.paused = false
const waiting = [...this.openWaiters]; this.openWaiters.clear()
for (const resolve of waiting) resolve()
this.notifyOpen()
}
}
}
@@ -0,0 +1,110 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {mkdtemp,mkdir,writeFile,readFile,rm} from 'node:fs/promises'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import {randomUUID} from 'node:crypto'
import {EventEmitter} from 'node:events'
import type {Pool} from 'pg'
import {createRequire} from 'node:module'
import {runInNewContext} from 'node:vm'
import {reconcileRestoreReceiptAudit,inspectRestoreReceiptAudit,reconcileDatabaseRestoreAudits} from './database-restore-audit.js'
const here=path.dirname(fileURLToPath(import.meta.url)),backend=path.resolve(here,'../..'),temp=path.resolve(backend,'../tmp'),require=createRequire(import.meta.url),ts=require('typescript')
async function own(work:(root:string,operation:string,receipt:Record<string,unknown>)=>Promise<void>,old=false){
await mkdir(temp,{recursive:true});const root=await mkdtemp(path.join(temp,'owned-restore-audit-'))
try{
const operation=randomUUID(),archive=randomUUID(),core=randomUUID(),stamp=new Date(Date.now()-(old?181*86400000:1000)).toISOString()
const receipt={operationId:operation,archiveRef:archive,state:'applied',startedAt:stamp,automaticRetry:false,finishedAt:stamp,accessRevoked:true,warnings:[]}
await writeFile(path.join(root,'access-generation'),Buffer.alloc(32,11));await mkdir(path.join(root,'restore-vault'))
await writeFile(path.join(root,'database-maintenance.jsonl'),['intent','applied'].map(state=>JSON.stringify({operationId:core,state,at:stamp})).join('\n')+'\n')
await writeFile(path.join(root,'restore-vault',archive+'.used'),JSON.stringify({operationId:operation,at:stamp}))
await writeFile(path.join(root,'restore-vault',operation+'.receipt.json'),JSON.stringify(receipt))
await work(root,operation,receipt)
}finally{assert(path.dirname(root)===temp&&path.basename(root).startsWith('owned-restore-audit-'));await rm(root,{recursive:true,force:true})}
}
type Row={actor_id:string;connection_id:null;action:string;payload:unknown;created_at?:string}
function model(){
const rows:Row[]=[],queries:string[]=[],releases:boolean[]=[];let lostCommit=false,insertFailure=false,releaseFailure=false,offFailure=false,inserts=0
const pool={connect:async()=>{
let pending:Row[]=[]
const client=new EventEmitter(),originalOff=client.off.bind(client)
return Object.assign(client,{off:(name:string,listener:()=>void)=>{if(offFailure)throw Error('Synthetic listener cleanup failure');return originalOff(name,listener)},release:(bad:boolean)=>{releases.push(bad);if(releaseFailure)throw Error('Synthetic release failure')},query:async(config:{text:string;values?:unknown[]})=>{
const text=config.text,values=config.values??[];queries.push(text)
if(text.startsWith('SELECT audit_retention'))return {rows:[{days:180,now:String(Date.now())}],command:'SELECT'}
if(text.startsWith('SELECT actor_id'))return {rows:rows.filter(row=>(row.payload as {operationId:string}).operationId===values[1]).slice(0,2),command:'SELECT'}
if(text.startsWith('INSERT')){if(insertFailure)throw Error('Synthetic audit unavailable');inserts++;pending.push({actor_id:String(values[0]),action:String(values[1]),connection_id:null,payload:JSON.parse(String(values[2])),created_at:String(values[3])});return {rows:[],command:'INSERT'}}
if(text==='COMMIT'){rows.push(...pending);pending=[];if(lostCommit){lostCommit=false;throw Error('Synthetic COMMIT delivery loss')}return {rows:[],command:'COMMIT'}}
if(text==='ROLLBACK')pending=[]
return {rows:[],command:text.split(' ')[0]}
}})
}} as unknown as Pick<Pool,'connect'>
return {pool,rows,queries,releases,inserts:()=>inserts,loseCommit:()=>{lostCommit=true},failInsert:()=>{insertFailure=true},failRelease:()=>{releaseFailure=true},failOff:()=>{offFailure=true}}
}
test('live and startup audit writers converge through read-first without changing external receipt',()=>own(async(root,id,receipt)=>{
const m=model(),file=path.join(root,'restore-vault',id+'.receipt.json'),before=await readFile(file)
const live=await reconcileRestoreReceiptAudit(m.pool,root,id,'actual-owner');assert.equal(live.status,'imported');assert.equal(live.originalActorKnown,true)
assert.equal(m.rows[0].action,'database.restore.receipt');assert.deepEqual(m.rows[0].payload,receipt);assert.equal(m.rows[0].actor_id,'actual-owner')
await reconcileDatabaseRestoreAudits(m.pool,root);assert.equal(m.inserts(),1)
const status=await inspectRestoreReceiptAudit(m.pool,root,id);assert.equal(status.status,'already-recorded');assert.equal(m.inserts(),1)
assert(m.queries.some(q=>q==='BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY'));assert(m.queries.every(q=>!/^CREATE|^ALTER|^DROP|pg_restore|psql/.test(q)))
assert.deepEqual(await readFile(file),before)
}))
test('legacy missing audit imports exact receipt evidence under system importer without inventing human',()=>own(async(root,id)=>{
const m=model();await reconcileDatabaseRestoreAudits(m.pool,root)
assert.equal(m.rows.length,1);assert.equal(m.rows[0].actor_id,'database-maintenance-reconciler');assert.equal(m.rows[0].action,'database.restore.receipt.recovered')
assert.equal((m.rows[0].payload as {originalActorKnown:boolean}).originalActorKnown,false)
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id,'actual-owner')).status,'already-recorded');assert.equal(m.inserts(),1)
}))
test('lost audit COMMIT destroys owner and reconciles observed existing row without another insert',()=>own(async(root,id)=>{
const m=model();m.loseCommit()
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'unconfirmed');assert.equal(m.releases[0],true)
assert.equal(m.queries.filter(value=>value==='ROLLBACK').length,0,'Uncertain COMMIT owner must be destroyed, not rolled back or reused')
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'already-recorded');assert.equal(m.inserts(),1)
}))
test('conflicting or duplicate audit evidence is never treated as imported',()=>own(async(root,id)=>{
const m=model();m.rows.push({actor_id:'foreign',connection_id:null,action:'database.restore.receipt',payload:{operationId:id,foreign:true}})
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'conflict');assert.equal(m.inserts(),0)
await assert.rejects(reconcileDatabaseRestoreAudits(m.pool,root),error=>error instanceof Error&&'code'in error&&error.code==='DATABASE_RESTORE_AUDIT_PENDING')
}))
test('audit retention is not reset by receipt reimport',()=>own(async(root,id)=>{
const m=model();assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'outside-retention');assert.equal(m.inserts(),0)
},true))
test('audit outage remains pending and never overwrites fsynced applied receipt',()=>own(async(root,id)=>{
const m=model();m.failInsert();const file=path.join(root,'restore-vault',id+'.receipt.json'),before=await readFile(file)
assert.equal((await reconcileRestoreReceiptAudit(m.pool,root,id)).status,'pending');assert.deepEqual(await readFile(file),before)
}))
for(const fault of ['release','listener'])test('confirmed audit COMMIT survives '+fault+' cleanup failure without relabeling restore',()=>own(async(root,id)=>{
const m=model();if(fault==='release')m.failRelease();else m.failOff()
const file=path.join(root,'restore-vault',id+'.receipt.json'),before=await readFile(file)
const result=await reconcileRestoreReceiptAudit(m.pool,root,id,'actual-owner')
assert.equal(result.status,'imported');assert.equal(result.cleanupUnconfirmed,true);assert.equal(m.rows.length,1)
assert.equal(m.releases.length,1,'Listener cleanup failure must not bypass actual owner release')
assert.deepEqual(await readFile(file),before)
}))
for(const legacyImport of [false,true])test('actual bootstrap audit follows migrations and legacy import='+legacyImport+' before business startup',()=>own(async(root)=>{
const source=await readFile(path.join(backend,'src/db/bootstrap.ts'),'utf8'),events:string[]=[],exports:Record<string,unknown>={}
const query=async()=>{events.push('sql');return {rows:[{sqlite_imported_at:'done'}]}},client={query,release:()=>{}}
const modules:Record<string,unknown>={
'./index.js':{pool:{query,connect:async()=>client}},'./migrate.js':{applySqlMigrations:async()=>events.push('migrations')},
'./partitions.js':{dropExpiredPartitions:async()=>events.push('drop'),ensurePartitionsAround:async()=>events.push('partitions')},
'./sqlite-import.js':{shouldImportSqlite:async()=>legacyImport,sqliteFileLooksPresent:()=>false,importSqliteToPostgres:async()=>{events.push('import');return {durationMs:0,tables:{}}}},
'../config.js':{env:{DATABASE_PATH:'synthetic'}},'../modules/mcp/security.js':{mcpStateDirectory:()=>root},
'../services/database-maintenance-startup.js':{assertDatabaseMaintenanceStartup:async()=>events.push('guard')},
'../services/database-restore-audit.js':{reconcileDatabaseRestoreAudits:async()=>{events.push('audit');throw Error('Pending audit import')}},
}
runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,{exports,require:(name:string)=>{assert(name in modules);return modules[name]},console})
await assert.rejects((exports.initDatabase as ()=>Promise<void>)(),/Pending audit import/)
assert.equal(events[0],'guard');assert.equal(events.at(-1),'audit');assert(events.indexOf('migrations')<events.indexOf('audit'));if(legacyImport)assert(events.indexOf('import')<events.indexOf('audit'))
}))
test('live audit recovery call is outside the catch that can relabel restore',async()=>{
const source=await readFile(path.join(backend,'src/modules/mcp/database-restore.ts'),'utf8'),tree=ts.createSourceFile('restore.ts',source,ts.ScriptTarget.ES2022,true)
let callAncestors:unknown[]=[]
function visit(node:{kind:number;expression?:{getText:()=>string};getChildren:()=>unknown[]},ancestors:unknown[]=[]){
if(node.kind===ts.SyntaxKind.CallExpression&&node.expression?.getText()==='reconcileRestoreReceiptAudit')callAncestors=ancestors
for(const child of node.getChildren())visit(child as typeof node,[...ancestors,node])
}
visit(tree)
const tries=callAncestors.filter((node:unknown)=>(node as {kind:number}).kind===ts.SyntaxKind.TryStatement) as {getText:()=>string}[]
assert.equal(tries.length,1);assert(!tries[0].getText().includes('state: attempted ? "unknown"'))
assert(source.includes('restoreActorId=connection.owner_id'))
})
@@ -0,0 +1,80 @@
/** Specific external receipt -> audit import. Never dispatches restore or
* changes journal/generation/receipt. Both live and startup writers use it. */
import type {Pool,PoolClient,QueryConfig,QueryResultRow} from 'pg'
import {isDeepStrictEqual} from 'node:util'
import {env} from '../config.js'
import {readAppliedRestoreAuditSources,type AppliedRestoreAuditSource} from './database-maintenance-startup.js'
const LIVE='database.restore.receipt',RECOVERED='database.restore.receipt.recovered',IMPORTER='database-maintenance-reconciler'
export type RestoreAuditStatus='imported'|'already-recorded'|'missing'|'outside-retention'|'pending'|'unconfirmed'|'conflict'
export interface RestoreAuditInspection {readonly operationId:string;readonly receiptSha256:string;readonly status:RestoreAuditStatus;
/** Recorded audit actor or live validated owner; not cryptographic provenance. */
readonly originalActorKnown:boolean;readonly cleanupUnconfirmed:boolean;readonly automaticRestore:false}
const boundedQuery=(text:string,values:unknown[]=[]):QueryConfig&{query_timeout:number}=>({text,values,query_timeout:10000})
type AuditRow=QueryResultRow&{actor_id:string;connection_id:string|null;action:string;payload:unknown}
function recoveryPayload(source:AppliedRestoreAuditSource){return {operationId:source.operationId,externalReceiptSha256:source.receiptSha256,originalActorKnown:false,receipt:source.receipt}}
function status(source:AppliedRestoreAuditSource,value:RestoreAuditStatus,known=false):RestoreAuditInspection{return Object.freeze({operationId:source.operationId,receiptSha256:source.receiptSha256,status:value,originalActorKnown:known,cleanupUnconfirmed:false,automaticRestore:false})}
async function one(pool:Pick<Pool,'connect'>,source:AppliedRestoreAuditSource,write:boolean,actorId?:string):Promise<RestoreAuditInspection>{
let client:PoolClient|undefined,begun=false,commitSent=false,committed=false,bad=false,faulted=false,cleanupUnconfirmed=false,result:RestoreAuditInspection
const fault=()=>{faulted=true}
const check=()=>{if(faulted)throw Error('Audit owner unavailable')}
try{
result=await(async()=>{
client=await pool.connect();client.on('error',fault);client.on('end',fault)
const query=async<R extends QueryResultRow=QueryResultRow>(text:string,values:unknown[]=[])=>{check();const result=await client!.query<R>(boundedQuery(text,values));check();return result}
await query(write?'BEGIN ISOLATION LEVEL READ COMMITTED READ WRITE':'BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY');begun=true
await query("SET LOCAL statement_timeout='10s'");await query("SET LOCAL lock_timeout='5s'")
await query('SELECT pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended($1,0))',['mcp-restore-audit:'+source.operationId])
const settings=await query<{days:number;now:string}>('SELECT audit_retention_days AS days,(EXTRACT(EPOCH FROM pg_catalog.clock_timestamp())*1000)::bigint::text AS now FROM public.mcp_settings WHERE id=1 LIMIT 2')
const config=settings.rows[0],finished=Date.parse(source.finishedAt)
if(settings.rows.length!==1||!config||!Number.isInteger(config.days)||config.days<30||config.days>3650||!/^\d{10,16}$/.test(config.now)||!Number.isSafeInteger(Number(config.now))||!Number.isFinite(finished)||finished>Number(config.now)+5000)throw Error('Audit retention/clock unavailable')
if(finished<Number(config.now)-config.days*86400000){const end=await query('ROLLBACK');if(end.command!=='ROLLBACK')throw Error('Audit rollback unconfirmed');begun=false;return status(source,'outside-retention')}
const existing=await query<AuditRow>("SELECT actor_id,connection_id,action,payload FROM public.mcp_audit WHERE action=ANY($1::text[]) AND payload->>'operationId'=$2 ORDER BY id LIMIT 2",[[LIVE,RECOVERED],source.operationId])
if(existing.rows.length>1)throw Object.assign(Error('Audit receipt conflict'),{auditConflict:true})
if(existing.rows.length){
const row=existing.rows[0]
const live=row.action===LIVE&&typeof row.actor_id==='string'&&row.actor_id.length>0&&row.connection_id===null&&isDeepStrictEqual(row.payload,source.receipt)&&(!actorId||row.actor_id===actorId)
const recovered=row.action===RECOVERED&&row.actor_id===IMPORTER&&row.connection_id===null&&isDeepStrictEqual(row.payload,recoveryPayload(source))
if(!live&&!recovered)throw Object.assign(Error('Audit receipt conflict'),{auditConflict:true})
const end=await query('ROLLBACK');if(end.command!=='ROLLBACK')throw Error('Audit rollback unconfirmed');begun=false
return status(source,'already-recorded',live)
}
if(!write){const end=await query('ROLLBACK');if(end.command!=='ROLLBACK')throw Error('Audit rollback unconfirmed');begun=false;return status(source,'missing')}
if(actorId!==undefined&&(typeof actorId!=='string'||!actorId))throw Error('Audit actor unavailable')
const actor=actorId??IMPORTER,action=actorId?LIVE:RECOVERED,payload=actorId?source.receipt:recoveryPayload(source)
await query('INSERT INTO public.mcp_audit(actor_id,action,connection_id,payload,created_at) VALUES($1,$2,NULL,$3::jsonb,$4::timestamptz)',[actor,action,JSON.stringify(payload),source.finishedAt])
commitSent=true;const end=await client!.query(boundedQuery('COMMIT'));if(end.command!=='COMMIT')throw Error('Audit COMMIT acknowledgement unavailable');committed=true;begun=false
if(faulted){bad=true;cleanupUnconfirmed=true}
return status(source,'imported',!!actorId)
})()
}catch(error){
bad=true
result=status(source,commitSent&&!committed?'unconfirmed':error&&typeof error==='object'&&'auditConflict'in error?'conflict':'pending')
}finally{
if(client){
if(begun&&!commitSent&&!faulted)try{const end=await client.query(boundedQuery('ROLLBACK'));if(end.command!=='ROLLBACK'){bad=true;cleanupUnconfirmed=true}}catch{bad=true;cleanupUnconfirmed=true}
try{client.off('error',fault)}catch{bad=true;cleanupUnconfirmed=true}
try{client.off('end',fault)}catch{bad=true;cleanupUnconfirmed=true}
try{client.release(bad||faulted||commitSent&&!committed)}catch{cleanupUnconfirmed=true}
}
}
return cleanupUnconfirmed?Object.freeze({...result,cleanupUnconfirmed:true}):result
}
export async function reconcileRestoreReceiptAudit(pool:Pick<Pool,'connect'>,directory:string,operationId:string,actorId?:string):Promise<RestoreAuditInspection>{
const sources=await readAppliedRestoreAuditSources(directory,env.DATABASE_URL),source=sources.find(value=>value.operationId===operationId)
if(!source)throw Error('Confirmed external receipt unavailable')
return one(pool,source,true,actorId)
}
export async function inspectRestoreReceiptAudit(pool:Pick<Pool,'connect'>,directory:string,operationId:string):Promise<RestoreAuditInspection>{
const sources=await readAppliedRestoreAuditSources(directory,env.DATABASE_URL),source=sources.find(value=>value.operationId===operationId)
if(!source)throw Error('Confirmed external receipt unavailable')
return one(pool,source,false)
}
export async function reconcileDatabaseRestoreAudits(pool:Pick<Pool,'connect'>,directory:string):Promise<void>{
for(const source of await readAppliedRestoreAuditSources(directory,env.DATABASE_URL)){
const result=await one(pool,source,true)
if(result.cleanupUnconfirmed||!['imported','already-recorded','outside-retention'].includes(result.status))throw Object.assign(Error('External restore audit requires reconciliation before managed startup'),{code:'DATABASE_RESTORE_AUDIT_PENDING',auditStatus:result.cleanupUnconfirmed?'cleanup-unconfirmed':result.status})
}
}
@@ -0,0 +1,212 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { readFile } from "node:fs/promises"
import { runInNewContext } from "node:vm"
import { AsyncLocalStorage } from "node:async_hooks"
import { EventEmitter } from "node:events"
import * as crypto from "node:crypto"
import { performance } from "node:perf_hooks"
import path from "node:path"
import os from "node:os"
import { z } from "zod"
import ts from "typescript"
import type { PoolClient } from "pg"
import type { McpPrincipal } from "../modules/mcp/store.js"
import { DatabaseRequestGate } from "./database-request-gate.js"
import * as operation from "./system-database-operation.js"
import * as maintenance from "../modules/mcp/database-maintenance.js"
async function load<T extends object>(file: URL, modules: Record<string, unknown>): Promise<T> {
const exports = {}
const source = ts.transpileModule(await readFile(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true } }).outputText
runInNewContext(source, { exports, require: (name: string) => { assert(name in modules, `Unapproved import ${name}`); return modules[name] }, Buffer, Date, Error, Promise, structuredClone, AbortController, setTimeout, clearTimeout, queueMicrotask, console, process: { env: {} } })
return exports as T
}
function deferred() { let resolve!: () => void; const promise = new Promise<void>(done => { resolve = done }); return { promise, resolve } }
async function bounded<T>(work: Promise<T>): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined
try { return await Promise.race([work, new Promise<never>((_, reject) => { timer = setTimeout(() => reject(Error("Owned pure check deadline")), 1500) })]) }
finally { clearTimeout(timer) }
}
const principal: McpPrincipal = { connectionId: "00000000-0000-4000-8000-000000000001", credentialId: "00000000-0000-4000-8000-000000000002", credentialExpiresAt: new Date(Date.now() + 600000).toISOString() }
class McpError extends Error { constructor(readonly code: string, message: string, readonly statusCode = 403) { super(message) } }
async function originFixture(gate: DatabaseRequestGate) {
let checkouts = 0, sqlCalls = 0, commits = 0
let hold: (() => Promise<void>) | undefined
const committed = deferred(), events: string[] = [], generation = Buffer.alloc(32, 71)
const cleanupQuery = async (raw: string | { text: string }) => {
assert(gate.hasActiveAdmission(), "Cleanup SQL must own live admission")
const text = typeof raw === "string" ? raw : raw.text; sqlCalls++; events.push(text)
if (text === "COMMIT") { commits++; if (commits === 2) committed.resolve() }
return { command: text === "BEGIN" || text === "COMMIT" ? text : "UPDATE", rows: text.startsWith("UPDATE public.mcp_operations") ? [{ plan_id: "owned-plan" }] : [] }
}
const pool = { connect: async () => { assert(gate.hasActiveAdmission()); checkouts++; await hold?.(); return { query: cleanupQuery, release() {} } } }
const connection = { generation: generation.toString("hex"), policy: { version: 1, expiresAt: new Date(Date.now() + 600000).toISOString() }, owner_valid_until: new Date(Date.now() + 600000) }
const origin = await load<typeof import("../modules/mcp/operation-execution-origin.js")>(new URL("../modules/mcp/operation-execution-origin.ts", import.meta.url), {
"node:async_hooks": { AsyncLocalStorage }, "node:crypto": crypto, "node:path": path, "node:perf_hooks": { performance },
"node:fs": { readFileSync: (file: string) => Buffer.alloc(32, file.endsWith("access-generation") ? 71 : 72) },
"node:fs/promises": { readFile: async () => Buffer.from(generation) },
"@mmapp/contracts/mcp": { mcpTemporaryGrantSchema: { parse: (value: unknown) => value } },
"../../db/index.js": { pool }, "../../services/shared-database-gate.js": { sharedDatabaseGate: gate },
"../../services/config-mutation-lock.js": { assertNoConfigurationLockContext() {} }, "../../services/operation-config-lock-lease.js": { assertNoOperationConfigurationLockContext() {} },
"./store.js": { validatePrincipal: async () => connection, audit: async (db: { query: typeof cleanupQuery }) => db.query("INSERT INTO inert_audit") },
"./security.js": { McpError, mcpStateDirectory: () => "/inert-owned-state", fingerprint: () => "inert", decryptSecret: async (value: string) => value, privateFingerprinterExisting: async () => () => "inert-key" },
"./actions.js": { findActionByOperation: () => ({ operation: { version: 1 } }) }, "./policy.js": { evaluateMcpPolicy: () => ({ decision: "allowed" }) }, "./change-cost.js": { changeMutationCost: () => 1 },
"./temporary-grant-origin.js": { assertOriginalMcpTemporaryGrant: async () => {} },
})
const mint = async () => {
let id = "", planId = ""
const result = (command: string, rows: unknown[]) => ({ command, rows, rowCount: rows.length })
const client = { query: async (sql: string, args: string[] = []) => {
if (sql.startsWith("SELECT pg_current_xact_id")) return result("SELECT", [{ creating_xid: "123" }])
if (sql.startsWith("INSERT INTO mcp_operations")) { [id, planId] = args; return result("INSERT", [{ id, plan_id: planId, connection_id: principal.connectionId, state: "pending" }]) }
assert(sql.includes("AS creating_transaction")); return result("SELECT", [{ id, plan_id: planId, connection_id: principal.connectionId, creating_transaction: true, operation_id: "users.create", operation_version: 1, policy_version: 1, generation: connection.generation, expires_at: new Date(Date.now() + 600000), evidence: { sealed: "owned-evidence" }, payload: { sealed: "owned-payload" } }])
} }
const start = origin.beginMcpOperationAdmission(principal)
return (await origin.insertMcpOperationAdmission(client as unknown as PoolClient, start, crypto.randomUUID(), crypto.randomUUID(), { proof: "inert", expiresAt: null, monotonicEnd: Infinity, admissionExpiresAt: Date.now() + 600000, admissionMonotonicEnd: performance.now() + 600000 })).admission
}
return { origin, mint, events, committed, counts: () => ({ checkouts, sqlCalls, commits }), hold: (work?: () => Promise<void>) => { hold = work } }
}
async function actualBoundedRunner() {
const runtime = await readFile(new URL("../modules/mcp/runtime.ts", import.meta.url), "utf8")
const begin = runtime.indexOf("let activeRunners = 0"), end = runtime.indexOf("async function policyInput", begin)
assert(begin > 0 && end > begin)
// Execute the existing private slot owner directly, not a mirrored semaphore.
const code = ts.transpileModule(runtime.slice(begin, end) + "\nexports.runBounded = runBounded", { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText
const exports = {} as { runBounded(work: () => Promise<void>): Promise<void> }
runInNewContext(code, { exports, Promise }); return exports.runBounded
}
test("deferred one-shot cleanup owns no bounded runner slot and emits no SQL until repair admission", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), runBounded = await actualBoundedRunner(), first = await f.mint(), second = await f.mint()
gate.closeAdmission()
await bounded(Promise.all([runBounded(() => f.origin.retireMcpOperationAdmission(first)), runBounded(() => f.origin.retireMcpOperationAdmission(second))]))
assert.deepEqual(f.counts(), { checkouts: 0, sqlCalls: 0, commits: 0 })
await bounded(runBounded(() => gate.exclusive(async () => { assert.equal(f.counts().sqlCalls, 0); gate.openAdmission() })))
await bounded(f.committed.promise); assert.equal(f.counts().commits, 2)
const before = f.counts(); await f.origin.retireMcpOperationAdmission(first); await f.origin.retireMcpOperationAdmission(second); assert.deepEqual(f.counts(), before)
})
test("cleanup already inside admitted work completes while exclusive drain waits, without inversion", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), cap = await f.mint(), entered = deferred(), release = deferred()
f.hold(async () => { entered.resolve(); await release.promise })
const request = gate.request(() => f.origin.retireMcpOperationAdmission(cap)); await entered.promise
let replaced = false; const exclusive = gate.exclusive(async () => { assert.equal(f.counts().commits, 1); replaced = true })
assert.equal(replaced, false); release.resolve(); await bounded(Promise.all([request, exclusive])); assert.equal(replaced, true)
})
test("temporary exclusive also defers outside cleanup until its callback has completed", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), cap = await f.mint(), entered = deferred(), release = deferred()
const exclusive = gate.exclusive(async () => { entered.resolve(); await release.promise }); await entered.promise
await bounded(f.origin.retireMcpOperationAdmission(cap)); assert.equal(f.counts().checkouts, 0)
release.resolve(); await exclusive
await bounded(gate.request(async () => { while (f.counts().commits !== 1) await new Promise<void>(resolve => setTimeout(resolve, 0)) }))
})
test("settled request ancestry cannot grant cleanup admission or be laundered into scheduler bootstrap", async () => {
const gate = new DatabaseRequestGate(), entered = deferred(), release = deferred()
let stale: Promise<void> | undefined
await gate.request(async () => { stale = (async () => { entered.resolve(); await release.promise; assert.equal(gate.hasActiveAdmission(), false); assert.throws(() => gate.assertNoRequestContext(), /request context/); await gate.exclusive(async () => { assert(gate.hasActiveAdmission()); assert.throws(() => gate.assertNoRequestContext(), /request context/) }) })() })
await entered.promise; release.resolve(); await stale
await gate.exclusive(async () => { assert(gate.hasActiveAdmission()); assert.doesNotThrow(() => gate.assertNoRequestContext()) })
})
async function serviceFixture(gate: DatabaseRequestGate) {
const calls: string[] = [], readiness = operation.createDatabaseSchemaReadiness()
const spawn = (command: string) => {
calls.push(command); const child = new EventEmitter() as EventEmitter & { stdout: EventEmitter; stderr: EventEmitter; stdin: { end(): void } }
child.stdout = new EventEmitter(); child.stderr = new EventEmitter(); child.stdin = { end: () => queueMicrotask(() => child.emit("close", 0)) }; return child
}
const service = await load<typeof import("./system-database-backup.js")>(new URL("./system-database-backup.ts", import.meta.url), {
"node:crypto": crypto, "node:async_hooks": { AsyncLocalStorage }, "node:fs/promises": { open: async () => ({ writeFile: async () => {}, sync: async () => {}, close: async () => {} }), chmod: async () => {}, mkdir: async () => {}, rm: async () => {}, writeFile: async () => {} }, "node:os": os, "node:path": path, "node:child_process": { spawn },
"../modules/events/service/events-service.js": { appendEvent: async () => {} }, "./database-dump-connection.js": { runPrivateDatabaseDump: async () => Buffer.alloc(0) }, "./database-control-archive-boundary.js": { DATABASE_CONTROL_DUMP_EXCLUDE_SCHEMA_ARG: "--exclude-schema=mmapp_control" },
"./shared-database-gate.js": { sharedDatabaseGate: gate, databaseSchemaReadiness: readiness }, "./config-mutation-policy.js": { assertDatabaseRestoreAllowed: async () => {} }, "../modules/mcp/security.js": { accessGeneration: async () => "inert", invalidateMcpAccess: async () => {}, mcpStateDirectory: () => "/inert-owned-state" }, "../modules/mcp/database-maintenance.js": maintenance,
"../db/migrate.js": { applySqlMigrations: async () => { assert(gate.hasActiveAdmission()); calls.push("migrations") } }, "./system-database-restore-execution.js": { validateSystemDatabaseRestoreArchive() {}, executeSystemDatabaseRestore: async (_buffer: Buffer, _url: string, deps: { run(args: string[]): Promise<void> }) => { await deps.run(["owned.dump"]); return {} } },
"../config.js": { env: { DATABASE_URL: "inert-owned-database" } }, "../db/index.js": { pool: { connect: () => assert.fail("Native SQL forbidden") } }, "../db/operation-lock.js": { withOperationLock: async (_connect: unknown, _key: number, work: () => Promise<unknown>) => { assert(gate.hasActiveAdmission()); calls.push("operation-lock"); return work() } }, "./system-database-operation.js": operation,
"./scheduler.js": { stopSchedulerForDatabaseOperation: async () => {}, restartSchedulerAfterDatabaseOperation: async () => { calls.push("scheduler-restart") } }, "./traffic-flow-ripe.js": { pauseRipeBackgroundWork: async () => {}, resumeRipeBackgroundWork() {} }, "./system-database-cache.js": { invalidateSystemDatabaseCaches() {} },
"./system-database-partition-restore.js": { restorePartitionedDatabase: async (_file: string, _url: string, deps: { beforeTargetReplacement(): void }) => { assert(gate.hasActiveAdmission()); assert(gate.isPaused()); deps.beforeTargetReplacement(); calls.push("replacement") } }, "./traffic-flow-ingest.js": { stopTrafficFlowListenerForDatabaseOperation: async () => {}, restartTrafficFlowListenerAfterDatabaseOperation: async () => {} },
})
return { service, calls }
}
test("terminal helper grants matching inner restore exactly one exclusive admission and revokes stale handoff", async () => {
const gate = new DatabaseRequestGate(), f = await serviceFixture(gate), exclusive = gate.exclusive.bind(gate); let entries = 0
Object.defineProperty(gate, "exclusive", { value: <T>(work: () => Promise<T>) => { entries++; return exclusive(work) } })
let late: Promise<unknown> | undefined; const release = deferred()
await f.service.withSystemDatabaseRestoreAdmission(async () => { await f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP")); late = release.promise.then(() => f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP"))) })
assert.equal(entries, 1); assert(f.calls.includes("replacement")); release.resolve(); await late; assert.equal(entries, 2)
await f.service.withSystemDatabaseBackupAdmission(async () => { await assert.rejects(f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP")), /уже выполняется/) })
})
test("actual terminal first pool checkout and claim are inside exclusive, never during another restore", async () => {
const gate = new DatabaseRequestGate(), f = await serviceFixture(gate); let checkouts = 0, claims = 0
const terminal = await load<typeof import("../modules/mcp/database-restore.js")>(new URL("../modules/mcp/database-restore.ts", import.meta.url), {
"node:crypto": crypto, "node:fs/promises": {}, "node:path": path, zod: { z }, "../../db/index.js": { pool: { connect: async () => { assert(gate.hasActiveAdmission()); assert(gate.isPaused()); checkouts++; return { query: async () => { assert(gate.hasActiveAdmission()); claims++; return { rows: [{ locked: false }] } }, release: () => assert(gate.hasActiveAdmission()) } } } },
"../../lib/permissions.js": {}, "../../services/system-database-restore-execution.js": {}, "../../services/system-database-backup.js": f.service, "../../services/config-mutation-policy.js": {}, "./security.js": {}, "./store.js": {}, "./state-file-publish.js": {}, "../../services/config-mutation-lock.js": {}, "./temporary-grant-origin.js": {}, "../../services/database-restore-audit.js": {},
})
const entered = deferred(), release = deferred(), other = gate.exclusive(async () => { entered.resolve(); await release.promise }); await entered.promise
await assert.rejects(terminal.executeMcpDatabaseRestore(crypto.randomUUID(), principal), /уже выполняется/); assert.equal(checkouts, 0)
release.resolve(); await other
await terminal.executeMcpDatabaseRestore(crypto.randomUUID(), principal); assert.equal(checkouts, 1); assert.equal(claims, 1)
})
test("detached cleanup begun in a live parent pins admission after that parent callback returns", async () => {
const gate = new DatabaseRequestGate(), f = await originFixture(gate), cap = await f.mint()
const checkout = deferred(), releaseCheckout = deferred(), parentReturned = deferred()
f.hold(async () => { checkout.resolve(); await releaseCheckout.promise })
let cleanup!: Promise<void>
const request = gate.request(async () => {
cleanup = f.origin.retireMcpOperationAdmission(cap)
await checkout.promise
parentReturned.resolve()
// Deliberately do not await the detached cleanup.
})
await parentReturned.promise
let replaced = false
const exclusive = gate.exclusive(async () => { assert.equal(f.counts().commits, 1); replaced = true })
await new Promise<void>(resolve => setTimeout(resolve, 10))
assert.equal(replaced, false); assert.equal(f.counts().sqlCalls, 0)
releaseCheckout.resolve()
await bounded(Promise.all([request, cleanup, exclusive]))
assert.equal(replaced, true)
})
test("detached matching restore handoff pins exclusive through an already-started inner restore", async () => {
const gate = new DatabaseRequestGate(), f = await serviceFixture(gate), entered = deferred(), release = deferred()
const scoped = gate.withinAdmission.bind(gate)
Object.defineProperty(gate, "withinAdmission", { value: <T>(work: () => Promise<T>) => scoped(async () => {
entered.resolve(); await release.promise; return work()
}) })
let inner!: Promise<unknown>
const outer = f.service.withSystemDatabaseRestoreAdmission(async () => {
inner = f.service.restoreSystemDatabaseBackup(Buffer.from("PGDMP"))
await entered.promise
})
await entered.promise
await new Promise<void>(resolve => setTimeout(resolve, 10))
assert(gate.isPaused())
await assert.rejects(gate.exclusive(async () => {}), /уже выполняется/)
assert(!f.calls.includes("operation-lock")); assert(!f.calls.includes("replacement"))
release.resolve(); await bounded(Promise.all([outer, inner]))
assert(f.calls.includes("replacement")); assert.equal(gate.isPaused(), false)
})
test("retained child rejection preserves drain and never grants a stale child admission", async () => {
const gate = new DatabaseRequestGate(), started = deferred(), release = deferred()
let child!: Promise<void>, late!: () => Promise<unknown>
const request = gate.request(async () => {
child = gate.withinAdmission(async () => {
const inherited = AsyncLocalStorage.snapshot()
late = () => inherited(() => gate.withinAdmission(async () => assert.fail("Stale work cannot run")))
started.resolve(); await release.promise; throw Error("Expected child rejection")
})
void child.catch(() => {})
await started.promise
})
await started.promise
let replaced = false; const exclusive = gate.exclusive(async () => { replaced = true })
assert.equal(replaced, false); release.resolve()
await assert.rejects(child, /Expected child rejection/)
await bounded(Promise.all([request, exclusive]))
assert.equal(replaced, true)
// Invoke in the inherited child context, not this outside test context.
await assert.rejects(late(), /no longer active/)
})
+2 -1
View File
@@ -6,10 +6,11 @@ import { observedBoolean, observedDurationMs, observedInteger } from "./ospf-obs
import { readGreNativeRows } from "./gre-observed-state.js"
import { greAddressKey } from "./entity-snapshots.js"
import type { GreMutation } from "./gre-mutation-input.js"
import { greDependencyPaths } from "./routeros-native-paths.js"
export { greDependencyPaths } from "./routeros-native-paths.js"
export const greMutationProblem = (message: string) => Object.assign(new Error(message), { statusCode: 409 })
export type GreNativeRow = Record<string, string>
export const greDependencyPaths = ["/interface", "/interface/bridge/port", "/interface/vlan", "/interface/list/member", "/ip/vrf", "/ip/route", "/ipv6/route", "/routing/filter/rule", "/routing/ospf/interface-template", "/routing/bfd/configuration", "/routing/bgp/connection", "/routing/rule", ...["ip","ipv6"].flatMap(family=>["filter","nat","mangle","raw"].map(table=>`/${family}/firewall/${table}`))] as const
export interface GreMutationState {
serverConfiguration: string; gre: GreNativeRow[]; ipv4: GreNativeRow[]; ipv6: GreNativeRow[];
restoreProtectionProof?:string;
+1 -4
View File
@@ -617,10 +617,7 @@ export function firewallRestPath(
return `${root}/${table}`
}
export function encodeRosId(rosId: string): string {
const id = rosId.startsWith("*") ? rosId : `*${rosId.replace(/^\*/, "")}`
return encodeURIComponent(id)
}
export { encodeRosId } from "./routeros-native-paths.js"
// ── MikrotikClient ─────────────────────────────────────────────────────────────
@@ -0,0 +1,17 @@
export function encodeRosId(rosId: string): string {
const id = rosId.startsWith("*") ? rosId : `*${rosId.replace(/^\*/, "")}`
return encodeURIComponent(id)
}
export const greDependencyPaths = ["/interface", "/interface/bridge/port", "/interface/vlan",
"/interface/list/member", "/ip/vrf", "/ip/route", "/ipv6/route", "/routing/filter/rule",
"/routing/ospf/interface-template", "/routing/bfd/configuration", "/routing/bgp/connection", "/routing/rule",
...["ip", "ipv6"].flatMap(family => ["filter", "nat", "mangle", "raw"].map(table => `/${family}/firewall/${table}`))] as const
export const wireguardInterfaceRuntimeDependencyPaths = ["/routing/bgp/session", "/routing/ospf/neighbor",
"/routing/ospf/interface", "/routing/bfd/session"] as const
export const wireguardInterfaceDependencyPaths = [...new Set<string>([...greDependencyPaths, "/interface/vxlan",
"/interface/vrrp", "/interface/bonding", "/interface/eoip", "/interface/gre", "/interface/ipip", "/ip/dhcp-server",
"/ip/dhcp-client", "/ipv6/dhcp-server", "/ipv6/dhcp-client", "/ip/ipsec/peer", "/ip/ipsec/policy",
...wireguardInterfaceRuntimeDependencyPaths])]
@@ -0,0 +1,13 @@
import assert from "node:assert/strict"
import test from "node:test"
import { isConfirmedRoutingFibTable } from "./routing-table-fib.js"
test("native FIB switch and explicit enabled representations preserve the table", () => {
for (const fib of ["", true, "true", "yes"]) assert.equal(isConfirmedRoutingFibTable({ name: "main", fib, invalid: "false" }), true)
})
test("absent or false FIB never confirms a forwarding table", () => {
for (const fib of [undefined, null, false, "false", "no", "unknown", "0", 1]) assert.equal(isConfirmedRoutingFibTable({ fib }), false)
})
test("disabled, invalid and ambiguous status cannot be promoted by the FIB switch", () => {
for (const key of ["disabled", "invalid"]) for (const value of [true, "true", "yes", "", null, "unknown"]) assert.equal(isConfirmedRoutingFibTable({ fib: "", [key]: value }), false)
})
@@ -0,0 +1,8 @@
/** RouterOS fib is a CLI switch: its enabled REST representation may be "".
* Do not interpret empty strings as true for general boolean properties.
* https://manual.mikrotik.com/docs/cli-reference/routing/table/ */
export function isConfirmedRoutingFibTable(row: Readonly<Record<string, unknown>>): boolean {
const fib = row.fib
if (![true, "true", "yes", ""].includes(fib as string | boolean)) return false
return ["disabled", "invalid"].every(key => row[key] === undefined || [false, "false", "no"].includes(row[key] as string | boolean))
}
@@ -0,0 +1,56 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { projectRoutingImmediateGateways, readRoutingTopology } from "./routing-topology-observation.js"
const route = { ".id": "*r", afi: "ip", "dst-address": "192.0.2.0/24", "routing-table": "main", active: "true", contribution: "active", gateway: "198.51.100.1", "immediate-gw": "198.51.100.1%wan", comment: "NEVER_PRIVATE", "bgp.peer-cache-id": "*opaque", "bgp.session": "*opaque" }
const ports = [{ name: "wan", running: "true" }], tables = [{ name: "main", fib: "true" }]
const project = (row = route, version = "7.24.5 (stable)", interfaces: unknown = ports, catalogue: unknown = tables) => projectRoutingImmediateGateways(version, [row], interfaces, catalogue)
test("documented IPv4 final gateway is reachable without operation/BGP namespace assertions", () => {
const result = project(); assert.equal(result.status, "observed"); assert.equal(result.items[0].status, "observed")
const item = result.items[0]; if (item.status !== "observed") throw Error("Missing observation")
assert.deepEqual(item.immediateGateway, { address: "198.51.100.1", interface: "wan" })
assert.equal(item.routeOriginVerified, false); assert.equal(item.recursiveChainVerified, false); assert.equal(item.semanticProof, false)
assert(!JSON.stringify(result).includes("NEVER_PRIVATE")); assert(!JSON.stringify(result).includes("opaque"))
})
test("IPv6 endpoint and directly connected interface are observed separately from full recursion", () => {
const ipv6 = project({ ...route, afi: "ipv6", "dst-address": "2001:db8::/64", "immediate-gw": "2001:db8::1%wan" })
assert.equal(ipv6.items[0].status, "observed")
const direct = project({ ...route, connect: "true", "immediate-gw": "wan" } as typeof route)
assert.equal(direct.items[0].status, "observed")
})
for (const [name, changed] of Object.entries({ multipath: { ecmp: "true" }, absentActive: { active: undefined }, disabled: { disabled: "true" }, unavailable: { "immediate-gw": "" }, numericOnly: { "immediate-gw": "17" }, wrongFamily: { "immediate-gw": "2001:db8::1%wan" }, wrongVrf: { "immediate-gw": "198.51.100.1%wan@other" } })) test(name + " stays unknown", () => {
assert.equal(project({ ...route, ...changed } as typeof route).items[0].status, "unknown")
})
test("unsupported version, missing/duplicate/down interface and missing/nonfib table stay unknown", () => {
assert.equal(project(route, "6.49").status, "unknown")
for (const ports of [[], [{ name: "wan", running: "false" }], [{ name: "wan", running: "true" }, { name: "wan", running: "true" }]]) assert.equal(project(route, "7.24.5", ports).items[0].status, "unknown")
for (const tables of [[], [{ name: "main", fib: "false" }]]) assert.equal(project(route, "7.24.5", ports, tables).items[0].status, "unknown")
assert.equal(projectRoutingImmediateGateways("7.24.5", [route, route], ports, tables).status, "unknown")
})
test("actual topology reader uses fixed GETs/fences/signal/wire bound and publishes no raw RIB", async () => {
const calls: string[] = []; let fences = 0
const client = { async get<T>(path: string, timeout?: number, signal?: AbortSignal, bytes?: number, beforeDispatch?: () => Promise<unknown>) {
assert(timeout! <= 10000); assert.equal(bytes, 1048576); assert(signal); await beforeDispatch?.(); calls.push(path)
return structuredClone(path === "/system/resource" ? [{ version: "7.24.5" }] : path === "/interface" ? ports : path === "/routing/table" ? tables : path === "/routing/route" ? [route] : []) as T
} }
const result = await readRoutingTopology(client, async () => { fences++ }, new AbortController().signal)
assert.equal(calls.length, 11); assert.equal(result.routeResolution.items[0].status, "observed"); assert(fences >= calls.length * 3)
assert(!JSON.stringify(result).includes("NEVER_PRIVATE")); assert.equal(result.observedPath, false); assert.equal(result.routeResolution.captureAtomic, false)
})
test("post-read access failure cannot be swallowed as provider unknown", async () => {
let denied = false
const client = { async get<T>() { denied = true; return [] as T } }
await assert.rejects(readRoutingTopology(client, async () => { if (denied) throw Error("revoked") }), /revoked/)
})
test("cancellation during the last asynchronous publication fence refuses the result", async () => {
const cancel = new AbortController(); let fences = 0
const client = { async get<T>(path: string) { return (path === "/system/resource" ? [{ version: "7.24.5" }] : path === "/interface" ? ports : path === "/routing/table" ? tables : path === "/routing/route" ? [route] : []) as T } }
await assert.rejects(readRoutingTopology(client, async () => { if (++fences === 23) { await Promise.resolve(); cancel.abort() } }, cancel.signal), /Topology read cancelled/)
assert.equal(fences, 23)
})
test("actual RouterOS FIB switch encoding is distinct from an absent or invalid table", () => {
const native = [{ ".id": "*0", name: "main", dynamic: "true", fib: "", invalid: "false" }]
assert.equal(project(route, "7.24.5 (stable)", ports, native).items[0].status, "observed")
for (const table of [{ name: "main" }, { name: "main", fib: "false" }, { name: "main", fib: "", invalid: "true" }, { name: "main", fib: "", disabled: "true" }]) assert.equal(project(route, "7.24.5", ports, [table]).items[0].status, "unknown")
})
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff. Show more