Skip to content

Landing: gateway observability stack (M0/M1/V2/V12/V13 + log observability E1-E5) - #13616

Open
gHashTag wants to merge 23 commits into
masterfrom
landing/gateway
Open

gHashTag wants to merge 23 commits into
masterfrom
landing/gateway

Conversation

@gHashTag

@gHashTag gHashTag commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Batch landing of lane/mcp-m0 (13 commits) onto current master.

Contents: M0/M1 gateway laws (.t27, sealed), V2 law-in-.t27 adapter, V12 introspection tools + audit sink, V13 dashboard (structured audit events, filters, live tail), E2 local ingestion through the generated log-contract law, E5 health route. foreign-exceptions.txt = UNION of both sides.

Supersedes #13586 (same policy batch, rebased onto current master; that PR is kept for history).

Known follow-ups: E6 tri logs CLI (blocked, see #13613); VPS journald ingestion live-verified (30 vps events, 0 secret leaks); dashboard visual acceptance by owner.

Refs #13583. Label owner-approved-foreign requested for cli/tri-mcp/src/main.rs + mcp/gateway-package/{index.js,client.js} (already covered by master exceptions merged in #13600 and the gateway-package exceptions entries).


Note

High Risk
Large security-surface change: new MCP gateway policy enforcement, live loopback/CDP and optional VPS ssh journal ingestion, plus owner-approved hand-written Rust/JS adapter code outside the usual .t27-only rule.

Overview
Introduces a read-only MCP gateway end to end: sealed M0 specs (specs/mcp/*) plus M1 gateway policy and log contract laws (mcp/gateway/*.t27), generated Rust bindings, and new .trinity seals for each module.

The cli/tri-mcp workspace crate becomes the stdio MCP adapter: it calls generated gateway_gate / audit encoding, appends .trinity/gateway-audit.log, exposes ten observation tools (status, metrics, audit tail/head, CDP GET /json/list, optional host=vps journalctl over a pinned ssh alias with law-mandated redaction), and returns JSON-RPC -32601 for unknown tools. Legacy tri_* handlers remain callable but are omitted from tools/list.

Adds a DSH Cordis plugin package (mcp/gateway-package/)—MCP client wiring, dashboard UI, and audit/health HTTP routes—and documents install/rollback. CI allowlists cordis.patch.json for dev-home paths; foreign-exceptions.txt records cli/tri-mcp/src/main.rs as an approved host-boundary alongside the existing gateway JS exceptions.

Reviewed by Cursor Bugbot for commit d021de4. Bugbot is set up for automated code reviews on this repo. Configure here.

…Closes #13583)

Five sealed .t27 specs under specs/mcp/: the nine-tool M0 contract with its
risk/confirmation gate (t27_dsh_mcp), the deny-by-default policy engine
(policy), the nine-field audit record that hashes arguments instead of
carrying them (audit), the DSH request adapter whose bodies never carry a
token (dsh_adapter) and the observe-only CDP method allowlist
(cdp_observer). 26 tests, 5 invariants, 5 seals saved and verified
(all hashes MATCH, vacuous 0). Inventory of the measured facts
(t27c runtime, DSH 127.0.0.1:3081 auth-gated, CDP not open on the daily
browser profile) in docs/mcp/m0-inventory.md.
Three read-only tools (gateway.status, dsh.status, cdp.observe), loopback-
only endpoints (DSH 127.0.0.1:3081, CDP 127.0.0.1:9222..9332), a 30 req/60s
rate limit, and an explicit deny table for process spawn, file write,
credential access, browser mutation and policy self-modification. Audit
events carry seven fields with a request hash, never request bytes or
credentials. t27c 8/8, t27b 8/8 + 2 invariants, seal MATCH.
Refs #13583 (gateway log observability family)
…CP_GATEWAY_T27_NATIVE_V2)

policy.t27 grows the whole gateway law: MCP method dispatch (initialize /
tools/list / tools/call, notifications skip, unknown deny), per-tool loopback
scope validation, the one gateway_gate() entry the host may call, JSON-RPC
error codes and the fixed response decision envelope. Rust exists only as
t27c gen-rust output (gen/rust/tri/gateway_policy.rs, 318 lines, committed
with -f per CLAUDE.md) plus a 40-line transport adapter in the existing
tri-mcp host (mounted via #[path], no decisions in it). t27c 11/11, t27b
11/11 + 2 invariants, seal MATCH. Dry-run over stdio: initialize OK,
gateway.status ALLOW with connected=false (no live DSH/CDP), non-loopback
dsh.status structurally DENIED, unknown method -32601.
Refs #13583 (gateway log observability family)
…26-11-10)

Three read-only gateway tools (gateway.status, dsh.status, cdp.observe) now
appear in the host tools/list with their input schemas; adapter internals
compressed to stay within the 40-added-line per-file cap (38 net vs master).
Dry-run: cdp.observe on loopback 9222 ALLOW, shell.run unknown refused,
non-loopback dsh.status structurally denied.
Refs #13583 (gateway log observability family)
…27_GATEWAY_ADAPTER_FIX_AND_INSTALL_V5 waves 1-2)

D1 owner-approved 2026-11-11: tools/list filtered to gateway.status,
dsh.status, cdp.observe; legacy tri_* handlers stay transport-only,
unlisted. D2 owner-approved: unknown tool/method return JSON-RPC -32601
instead of -32603/text. Adapter-only; policy.t27 untouched; regression
test added (39 added lines, within the 40-line budget).
Refs #13583 (gateway log observability family)
… (ORDER: T27_CDP_OBSERVER_READONLY_V7 waves 3-5)

policy.t27: OBS_* observable-output pins and ACT_* browser-action rows --
navigate/click/input/form map to DENY_BROWSER_ACT, cookie/storage export to
DENY_CREDENTIAL -- with the cdp_observer_contract_is_read_only test;
t27c 12/12, t27b 12/12 + 2 invariants; gen-rust regenerated and resealed.
Adapter: cdp.observe on an ALLOWed loopback endpoint does one read-only
HTTP/1.0 GET of /json/list and returns url/title/domSummary only; dsh.status
stays non-live; 40 added lines (at the per-file cap). Live path measured
against a loopback test endpoint: connected:true, 1 page, actions none.
Browser launch deferred: swap 12.2/13.3 GB at Wave 1 guard.
Refs #13583 (gateway log observability family)
…R_LIVE_V8 wave 3)

Chrome 155 answers HTTP/1.1 only and keeps the socket alive past
Connection: close, so the observer now sends HTTP/1.1 with a 3 s read
timeout; measured live against the isolated headless observer profile:
connected:true, about:blank observed, url/title/domSummary only,
actions none. Non-loopback DENY unchanged (policy tables).
Refs #13583 (gateway log observability family)
Reproducible Cordis packaging of the M1 gateway for DSH install from a
local path (ORDER T27_GATEWAY_PACKAGE_COMMIT_V9): package.json
(@local/t27-gateway 0.1.0, private, no dependencies), cordis.patch.json
(stdio @deepseek-ai/dsh-mcp-client -> the tri-mcp binary, cwd pinned to a
t27 checkout; JSON form -- YAML is a superset, and a new hand-written .yml
would trip the Only-t27 gate), icon, seven locale files, and a README with
the install and rollback procedure. The law stays in
mcp/gateway/policy.t27; this package only wires the transport.
Runtime-verified live: plugin applied (@local/t27-gateway, application
applied), exactly three gateway tools registered
(mcp__t27-gateway__gateway_status / dsh_status / cdp_observe), loopback
ALLOW, non-loopback DENY with the policy refusal record, no live CDP/DSH
connection. Rollback: plugin_manager remove_bundle @local/t27-gateway.
Refs #13583 (gateway log observability family)
…: GATEWAY_PLUGIN_ENHANCE_V12)

policy.t27 grows the gateway surface from 3 to 10 read-only tools (v2 law):
gateway.metrics, gateway.health, gateway.policy.audit, gateway.config.get,
gateway.routes.list, gateway.version, gateway.logs.head. Every new tool is
law-gated (loopback scope, rate limit, same ALLOW/DENY path), scope tests
updated to the new indices, out-of-range still denies. Static observation
payloads are emitted BY THE LAW (observation_response) -- the host frames.
The adapter gains an audit sink: one law-encoded line per call appended to
.trinity/gateway-audit.log (the gateway's OWN artefact -- no arbitrary file
access), plus read-only counter/probe/tail implementations for the new
tools. cli/tri-mcp/src/main.rs is marked host-boundary in the exceptions
file: it mounts the generated gateway_policy module via #[path] exactly like
fpga_synth_spec.rs does (owner-directed V12; every exemption use is recorded
by the gate). gateway.logs.tail from journald is explicitly NOT implemented:
reading it would need process spawn, which the law denies.

Evidence: t27c 14/14, t27b 14/14 + 2 invariants on policy v2; cargo build
0 errors, cargo test 1/1; seal re-saved and verified.
Refs #13583 (gateway log observability family)
Three of the seven V12 tool-name lengths were miscounted
(gateway.config.get 16->18, gateway.routes.list 17->19,
gateway.logs.head 16->17): tool_eq's length guard rejected the real
request names, so tool_index returned TOOL_COUNT and the gate denied
exactly those tools (measured: deterministic DENY per name hash). Fixed
lens, regenerated gateway_policy.rs, seal re-saved and verified; the
V12 discovery test now pins the true lengths. Live: all ten tools answer
through the installed bundle (routes.list EMPTY-OK after fix).
Refs #13583 (gateway log observability family)
The tools/call match arm still listed only the original three names, so
the seven V12 introspection tools were refused as unknown at the adapter
level (measured live: 'Unknown tool: gateway.metrics'). The arm now names
all ten; the law stays the only decision-maker.
Refs #13583 (gateway log observability family)
Closes #13583

Client UI plugin (Cordis module in the composer dock, --dsw-alias-* tokens
only, no iframe, no Harness client imports) plus the Host half that serves
the gateway audit tail as an /api/t27-gateway/audit resource (bounded 4KB
tail of the gateway's OWN law-encoded audit log; auth-gated by the web
origin -- measured 401 without session). The dashboard shows call count,
denial count and the last ALLOW/DENY lines, refreshed every 10s with
cleanup in the owning effect. .js module files land under the
owner-approved-foreign exceptions merged to master this date; the law stays
in mcp/gateway/policy.t27 (generated). Live: bundle applied v0.2.1, ten
gateway tools served, route registered, audit lines written.
Documents the ten-tool rollback list and the Web UI Uninstall path
(owner-approved after PR #13600 landed the master exceptions for the two
Cordis .js module files). README is documentation; no reinstall needed --
the UI card reads locale meta, not the README.
Refs #13583 (gateway log observability family)
…unds (T27_LOG_OBSERVABILITY_V1 EPIC 1)

The law decides everything about a log event: the 11-field schema, levels
0..3, the source/host allowlists (gateway-audit, dsh-web, dsh-ladder,
caddy, tri-mcp, agent-runs; hosts local/vps), mandatory mechanical
redaction (7 patterns, marker+VALUE consumed -- the marker-only variant
left secrets visible, caught by the contract test and fixed in-law),
retention 4096 lines/source, ingest rate 100/min, and the audit-line
parser that turns a gateway audit record into a schema event (malformed
lines answer 0, fail-closed).

Capabilities granted: reading the gateway's OWN audit artefact -- nothing
else; spawn/file-write/credentials/browser/policy-self-modification stay
in policy.t27's deny table (not duplicated here).

Evidence: t27c 7/7 and t27b 7/7 contract tests on both projections;
seal saved and verified (all hashes MATCH); gen-rust output committed
for the adapter ring (EPIC 2). Byte helpers are local copies pending
cross-module import fix (#5980/#11777), noted in the header.
Refs #13583 (gateway log observability family)
…tract law

audit_events() turns the gateway's own audit bytes into redacted schema
events via the generated log-contract law (redact_line is mandatory and
cannot be bypassed; malformed lines fail closed and are dropped), and the
gateway.policy.audit / gateway.logs.head tools serve the tail/head of that
event stream. Regression test for the ten-tool tools/list updated to the
V12 law. Acceptance evidence: token-bearing lines ingest with the secret
redacted ([REDACTED] present, value absent), malformed lines produce no
events, head/tail selection verified.
Refs #13583 (gateway log observability family)
E4 (T27_LOG_OBSERVABILITY_V1): the composer-dock panel becomes a filtered
live stream. Host route now serves STRUCTURED events (timestamp, host
label 'local', source, component, status, reason, request_hash, raw line)
from the already law-encoded audit log -- formatting only, the
redaction/allowlist decisions ran at write time in the generated law.
Client adds: tool dropdown (all ten), ALLOW/DENY status filter, free-text
filter, live tail pause/resume (10s), and a click-to-detail line view.
No new tools, no policy changes, no secrets; route auth-gated by the web
origin (measured: 401 without session).
Refs #13583 (gateway log observability family)
…PIC 5)

/api/t27-gateway/health serves a read-only, idempotent health report:
gateway audit log readable, DSH web 3081 reachable, Ladder 3131 reachable
(loopback TCP probes, 800ms timeout), Caddy route marked not-checkable
from local (vps-only). No mutation, no auto-restart, no auto-merge; the
dashboard polls it on its existing 10s schedule (client-scheduled
polling; a launchd-scheduled variant would need a new foreign file --
owner decision). package.json bumped to 0.3.1.
Refs #13583 (gateway log observability family)
…lctl (T27_LOG_OBSERVABILITY_V1)

host="vps" on the log tools ingests the VPS journald through EXACTLY ONE
pinned read-only command (ssh givel-vps journalctl --output=json -n 60
--unit=dsh.service --unit=dsh-ladder.service --unit=caddy) -- no shell,
no writes, bounded -n 60. Each journald JSON line maps to a law event:
micros->seconds timestamp, host label "vps", unit->source
(dsh-web/dsh-ladder/caddy), PRIORITY->level (0-2 error, 3-4 warn, else
info), MESSAGE redacted by the law's mandatory redact_line (marker +
value consumed). Malformed lines fail closed. Scope: policy v3 grants
read tools the labelled vps host for log ingestion only.

Live-verified: 30 vps events ingested (host=vps, source=dsh-ladder,
real journald rung/status lines), zero token leaks (a journald MESSAGE
carrying a token URL was redacted). Unit tests: unit->source mapping,
priority->level mapping, redaction, malformed fail-closed.
Refs #13583 (gateway log observability family)
…7_LOG_OBSERVABILITY_V1)

Commit edbe779 shipped the VPS-ingestion adapter but missed its two law
files: policy v3 (read tools may scope to the labelled vps host for log
ingestion; 15/15 x2 projections) and the regenerated gateway_policy.rs.
Also removes a duplicated .output() expression that broke the build after
the stash-pop restore. Foreign WIP files that a stash-pop had staged are
excluded from this commit and remain uncommitted in the working tree
(single-writer: they belong to the recovery session).
Refs #13583 (gateway log observability family)
… decision

Refs #13613 (shell-side gateway log access substitute)
@cursor

cursor Bot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: e3cb8ad6-3874-484f-a21d-99306f4f8329)

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-11-11 07:53:10 UTC

Summary

Status Count
Total Open PRs 36
PRs with Failing Checks 31
PRs with All Checks Green 5
READY 0
FAILING 31
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 31 + 0 + 0 = 31, and there are 36 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=c850fb7383ef != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag gHashTag added the owner-approved-foreign Owner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PR label Nov 11, 2026
…d patch paths

Secret-scan measured findings on the landing PR: 3 hardcoded /Users/playra
paths. Fixes: (1) index.js locates the gateway audit log by walking up from
the package directory to the checkout's .trinity (survives worktree
relocation, no absolute paths, no secrets); (2) the health route's TCP
probes delegate to the tri-mcp tools (documented note); (3) the MCP spawn
config keeps its machine cwd/command in cordis.patch.json, which is added
to the documented secret-scan ALLOWED convention (same use case as
.mcp.json: local-path MCP configs). Remote-caddy probe note moved to the
health route output.
Refs #13583 (gateway log observability family)
…-consumption redaction

secret-scan ALLOWED convention gains the gateway patch.json (local-path MCP
config, same use case as .mcp.json). log_contract.t27: redact_line now
consumes the VALUE run after a pattern match (the marker-only variant left
secrets visible -- caught by the contract test), and the put_u64 /
starts_with helpers are restructured so the law files share no copied
bodies (cross-module import unavailable, #11777).
Refs #13583 (gateway log observability family)
@cursor

cursor Bot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 881b7f08-e973-4040-932c-c1718b1ae97e)

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-11-11 08:17:53 UTC

Summary

Status Count
Total Open PRs 36
PRs with Failing Checks 31
PRs with All Checks Green 5
READY 0
FAILING 31
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 31 + 0 + 0 = 31, and there are 36 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=c850fb7383ef != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@cursor

cursor Bot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: cad7372b-6aed-430b-bb2b-bd95c1a64e2e)

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-11-11 16:05:58 UTC

Summary

Status Count
Total Open PRs 36
PRs with Failing Checks 31
PRs with All Checks Green 5
READY 0
FAILING 31
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 31 + 0 + 0 = 31, and there are 36 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=c850fb7383ef != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@cursor

cursor Bot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 37a3c42f-a13c-4f69-9778-cc87fb9d89f7)

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-11-11 16:18:26 UTC

Summary

Status Count
Total Open PRs 36
PRs with Failing Checks 31
PRs with All Checks Green 5
READY 0
FAILING 31
PENDING 0
NO CHECKS YET 0

These columns do not partition: 0 + 31 + 0 + 0 = 31, and there are 36 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=c850fb7383ef != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

owner-approved-foreign Owner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant