Skip to content

Enterprise page: zero trust for AI agents; home line; post on zero trust in the model - #241

Merged
matt-dean-git merged 3 commits into
mainfrom
site/zero-trust-model-1016
Oct 16, 2026
Merged

matt-dean-git merged 3 commits into
mainfrom
site/zero-trust-model-1016

Conversation

@matt-dean-git

Copy link
Copy Markdown
Member

What changed

/govern

  • H1 before: Enterprise AI agent governance, from Policy to Proof.
  • H1 after: Zero trust for AI agents
  • Hero paragraph before: SatGate checks each request before it goes through: Observe agent activity, Control scopes, budgets, delegation, and revocation before the agent acts, then Prove every decision with signed receipts (Evidence Packs) across APIs, MCP tools, and payment methods.
  • Hero paragraph after: Treat every model like a capable insider that can make mistakes or be tricked. The model provider supplies the intelligence. You keep the authority.
  • Eyebrow ("Enterprise agent governance") and both hero buttons unchanged.
  • New section after the hero: Your limits, your evidence, your off switch. Five cards (limits the model can't change, evidence the model didn't write, an off switch that works mid-task, no single model in charge, run it yourself) and the link "Why this matters: zero trust in the model →" to /blog/zero-trust-in-the-model.
  • WebPage JSON-LD dateModified: 2026-06-01 → 2026-10-16. Page metadata and FAQ JSON-LD unchanged.

Home

Added in the "Agents only get what you allow" card, next to "Revoke works on the next request":

  • Your rules sit outside the model. Limits, receipts and the off switch live in SatGate, where no prompt or model provider can change them.
  • Link: Zero trust for AI agents → /govern

Nothing else on the home page changed.

Blog

New post at /blog/zero-trust-in-the-model, listed first on /blog and in app/sitemap.ts. Sitemap lastModified for /govern and /blog set to 2026-10-16 so it is not older than the page dateModified.

The post quotes Satya Nadella's Oct 10 essay with exact wording checked against the original (https://x.com/satyanadella/status/2108931348857827686, article text from api.fxtwitter.com). Straight apostrophes stand in for the original curly apostrophes. The visible H1 and TechArticle headline are the approved title. Metadata, Open Graph, and Twitter titles use the 53-character prefix "Zero trust in the model: Nadella's insider-risk rules" because the full title is 76 characters and the site audit rejects titles over 70.

Guards

Every scripts/check_*.py log matched the origin/main baseline. Pre-existing failures are unchanged (buyer narrative missing strings on app/govern/page.tsx, design-partners Policy-to-Proof layer, acceptance story, authority follow-ups, reputation substrate).

check_policy_to_proof_page.py: the pin "Observe agent activity, Control scopes, budgets, delegation, and revocation before the agent acts" was removed from the hero, so it was repointed to "Every request is checked before it reaches the service, so a tricked agent gets refused." The assertion was not deleted. That guard still exits on the earlier design-partners failure, so its log is unchanged. The new pin is in GovernClient.tsx.

internal_link_audit.py: no new failure lines. Scan counts rose with the new page (internal links 514 to 519, sitemap entries 76 to 77, blog TechArticle pages 29 to 30). Exit remains 1 from pre-existing issues.

Checks

  • npx eslint on changed files: 0 errors. HomeClient.tsx still has its pre-existing unused-var warnings.
  • NEXT_TELEMETRY_DISABLED=1 npx next build: exit 0. Local node_modules was the APFS clone from the main checkout (Next 16.1.6). package.json pins 16.3.8.
  • curl 200 for /, /govern, /blog, /blog/zero-trust-in-the-model.
  • Mobile 390x844 scrollWidth: / 390, /govern 390, /blog 390, /blog/zero-trust-in-the-model 390. The post URL was 446 until the link got break-all.
  • Jargon regex on new text: "authority" in the approved hero sentence ("You keep the authority.") and twice in the approved post. Not rewritten. The new home line has no hits.

Not merged. No reviewer requested.

Replace the /govern hero, add the limits, evidence, and off-switch cards, add the home line, and publish the post on zero trust in the model.
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
satgate Ready Ready Preview Oct 16, 2026 4:10pm UTC

@matt-dean-git

Copy link
Copy Markdown
Member Author

Independent review (profile satgate-security-reviewer, xai-oauth grok-4.7), site copy
Verdict: NO-GO
Head SHA: eec336a
Blockers:

  • satgate-landing/app/components/GovernClient.tsx:96 claims every checked request refuses a tricked agent. That is not what ships. The same page still says start in Observe (GovernClient.tsx:84) and Observe does not block on budget: enterprise origin/main e3598a99 gateway/internal/proxy/proxy.go:1504-1515 forces observe, disables hard-budget enforcement, and says Observe never issues a 402 or other billing denial; public pkg/mcpserver/proxy.go:846-849 allows observe overspend and still forwards at :964. Scope and expiry are refused before upstream (pkg/mcpserver/proxy.go:686 and pkg/macaroon/macaroon.go:426; HTTP handleCapability verifies the macaroon before proxyRequest). A tricked agent inside those limits is not refused. Minimum fix: replace the second sentence with "SatGate checks each request against them before it reaches the service. In Control, a request outside those limits is refused. Observe records it and does not block on budget." Point scripts/check_policy_to_proof_page.py:146 at that new sentence.
  • satgate-landing/app/components/GovernClient.tsx:100 and app/blog/zero-trust-in-the-model/page.tsx:112 claim each allowed request gets a signed receipt, and that anyone can check it against SatGate's published keys. Signed receipts do exist for a budgeted or paid allow, and for a refusal over budget, payment, or permission (enterprise capability_budget.go writes budget_authorized / budget_exhausted; proxy.go payment_required and writeVerifiedScopeDenial; MCP recordMCPDecision on control budget deny and on a successful spend when cost > 0, pkg/mcpserver/proxy.go:792-929 and :1082-1089). They are not written for every allow: a capability route with no budget and no cost returns true and proxies with no receipt (capability_budget.go, enforceCapabilityBudget, the no-budget branch); an MCP allow with no budget id or zero cost skips recordMCPDecision; an MCP observe overspend allow falls through without one. Cloud receipts verify against the published JWKS (live GET https://api.satgate.io/.well-known/jwks.json returned 200, kid satgate-gateway-ed25519-2026-05; https://satgate.io/.well-known/jwks.json redirects there; OSS verifier tools/verify_evidence_pack.py). A self-hosted gateway signs with its own SATGATE_RECEIPT_SIGNING_KEY_B64, not those keys. Minimum fix, both sentences: "A budgeted or paid request that SatGate allows, and a refusal over a budget, a payment or a permission, gets a receipt signed by SatGate. A Cloud receipt can be checked with the open-source verifier against our published keys. A request with no budget and no payment does not get one."
  • app/blog/zero-trust-in-the-model/page.tsx:112 says the receipt says which agent asked and what it asked for. The signed receipt stores authority.token_id_hash and raw_token_id "redacted" (enterprise gateway/internal/evidence/receipt.go:709 and :718), plus decision, decision_reason (:767), and route_or_tool (:774). It does not store the agent name. Call arguments are kept out of the receipt (pkg/mcpserver/approval_gate.go:15-17). Minimum fix: "The receipt names the tool or route, says whether SatGate allowed or refused it and why, and carries a hash of the token so you can match it to the agent. It does not name the agent or include the call's arguments."

Checks:

  • 1a Limits card, partial. Budgets, tool scope, argument rules, and timestamp end dates are checked before the upstream call on governed MCP tools/call and HTTP capability/pay routes (pkg/mcpserver/proxy.go:458-493, :686-703, :806 then forward at :964; mint writes expires as unix ms, gateway/internal/mint/mint.go:1044, verified at pkg/macaroon/macaroon.go:426). The refusal half of GovernClient.tsx:96 is the blocker above. A duration-string expires caveat is not enforced (pkg/macaroon/macaroon.go:407-411); shipped end dates are timestamps, so that is not a separate blocker.
  • 1b Receipt card, blocker. Which decisions are signed: budget allow and budget_exhausted, payment_required and payment_verified, scope/policy_denied. Not signed: route and MCP rate-limit refusals, bare capability allows, zero-cost or no-budget MCP allows, MCP observe overspend allows. OSS verifier tools/verify_evidence_pack.py against the published JWKS above. "Each allowed request" and "our published keys" for every deployment overclaim.
  • 1c Revoke card, holds. Next request is refused: MCP checks the Redis revoked set before the call (pkg/mcpserver/proxy.go:477-479); HTTP capability checks governance bans before proxyRequest (enterprise gateway/internal/proxy/proxy.go handleCapability). Parent revoke cascades the subtree and publishes it, or answers 500 REVOCATION_NOT_PUBLISHED (delegation_v2.go:966-1096, enterprise #499 merged 2026-10-15 as eb1af22f, on origin/main e3598a99). Dashboard shows "Revoked" only on 200 with revoked:true; otherwise TOKEN_REVOKE_FAILED_TEXT "SatGate couldn't confirm this token was revoked. It may still work. Try again." (dashboard/app/lib/token-revoke-text.ts:4-16 and verified-revoke.ts). Helpers: public A zero-credit helper carries an id of its own, so revoking it reaches holder-extended copies #236 (merged e5a73d3) stamps a zero-credit helper with its own id so a revoke can name it (pkg/mcpserver/delegation.go). Heading "works mid-task" (GovernClient.tsx:103) is looser than the body; the body correctly says the next request. Not a separate blocker.
  • 1d Model/vendor card, holds. Limits and receipts are on the token and the route, not on which model called. GovernClient.tsx:108 does not claim a model API call that bypasses SatGate is governed. The blog already says an agent with its own keys can go around the gateway.
  • 1e Self-host card, holds. Enterprise origin/main rejects hybrid mode and the hybrid gateway URL (gateway/internal/cloud/cloud.go:2337 and :4627). With no tenant routing config the proxy uses the local global config (gateway/internal/proxy/proxy.go:1138). Docs at docs/SELF_HOST.md and docs/QUICK_START.md match: own Postgres and Redis, no policy pull from cloud.satgate.io. Not a code path I re-executed live.
  • 1f Home line, holds. HomeClient.tsx:383 says the limits, receipts, and off switch live in SatGate and that a prompt or model provider cannot change them. It does not say every tricked agent is refused. Caveats are in the token; revoke is the dashboard/gateway path above.
  • 2a Blog tool rules, holds as examples. "buy only" and "one account only" are argument-rule presets (dashboard/app/lib/mcp-arg-presets.ts side one_of buy, account one_of) enforced by pkg/argrules/check.go one_of before any upstream tools/call (pkg/mcpserver/proxy.go:701-703). They are not a rule on every tool. The parentheses read as examples. page.tsx:98 does not repeat the false "tricked agent gets refused" sentence.
  • 2b Blog receipt identity, blocker. See the third blocker. page.tsx:112.
  • 2c Rate-limit carve-out, holds. HTTP route rate limit writes 429 and returns with no receipt (enterprise gateway/internal/proxy/proxy.go:1155-1175; golden headers have no receipt). MCP rate limit returns at pkg/mcpserver/proxy.go:482-492 before recordMCPDecision. page.tsx:116 is true. The same paragraph's "each allowed request" is the second blocker.
  • 2d "Since this week" revoke failure, holds. #499 merged 2026-10-15. The dashboard does not show done: it shows the failure sentence above, not the word "failed". Substance matches. Not a wording-taste blocker.
  • 2e Email approval, holds with a narrower reading. An operator can put an "ask me" threshold on an MCP tool. A call above it is not forwarded; the owner is emailed when Resend and a dashboard URL are set; the agent retries after approval (pkg/mcpserver/approval_gate.go:36-48 and :164-175; enterprise gateway/internal/mcpgateway/approval_gate_wiring.go). It is not an automatic risk score, and the HTTP connection does not stay open waiting for the email. "You can also make" matches that. If email is unset, the hold is on the approvals page only. Not a blocker.
  • 2f In-flight request finishes, holds. Revoke is an admission check on the next request (pkg/mcpserver/proxy.go:477 before forward at :964; HTTP ban check before proxyRequest). I found no cancel of a call already dispatched. page.tsx:131 matches that. I did not run a live in-flight probe.
  • 3 Quotations, holds. All 8 blockquotes match nadella-1010-models-as-insider-risks.txt word for word after straight-versus-curly apostrophes. Each is introduced as Nadella, not a paraphrase inside quotation marks. The X link is the status in the brief.
  • 4 Build and guards, holds. node_modules copied with cp -Rc from /Users/mattdean/code/satgate/satgate/satgate-landing (not npm ci). From ../pr-241/satgate-landing on eec336a: npx tsc --noEmit exit 0; NEXT_TELEMETRY_DISABLED=1 npx next build exit 0, including /govern, /blog/zero-trust-in-the-model, and /blog/zero-trust-for-ai-agents. Guard exit codes, identical on public main 79ddfa5 and on this head, output does not differ: check_acceptance_story.py 1; check_authority_followups.py 1; check_build_page.py 0; check_buyer_narrative.py 1; check_cache_protocol.py 0; check_demo_ia.py 0; check_economic_firewall_copy.py 0; check_fable_burn_down.py 0; check_policy_to_proof_page.py 1; check_proof_spine.py 0; check_receipt_schema.py 0; check_reputation_substrate.py 1; check_well_known_satgate.py 0. The shared check_policy_to_proof_page.py failure is the pre-existing missing design-partners phrase "Policy-to-Proof layer", which the script reaches only after the govern phrase check (script lines 756-758, then 785-790). The repointed pin at line 146 was therefore asserted: a missing new sentence would have exited as "missing govern policy-to-proof phrases" instead. Internal links /govern, /blog/zero-trust-in-the-model, and /blog/zero-trust-for-ai-agents are real routes. The X link at page.tsx:164 has rel="noopener noreferrer".
  • 5 CI, holds on this SHA. gh pr checks 241 and gh run view 38920049433: headSha eec336a, conclusion success. Go job 117037344108, Node.js SDK job 117037344204, Python SDK job 117037343619, all success. Vercel check success (https://vercel.com/waynes-projects-50c6eb51/satgate/EePjARkavsuBofLZdmuat7sCESjH). Vercel Preview Comments success.

Enterprise code read from origin/main e3598a99d6ece0c2cd0d742c139afceeea00b71f, not the local enterprise branch. Public pkg lines match the module enterprise pins (78dd26f); those files have no diff to public main. No files in the PR were modified by this review. Recommended next action: replace the three sentences and the pin, then re-request review. Do not merge this copy as written.

@matt-dean-git

Copy link
Copy Markdown
Member Author

Independent review (profile satgate-security-reviewer, xai-oauth grok-4.7), site copy, round 2
Verdict: NO-GO
Head SHA: 1bc5708
Blockers:

  • satgate-landing/app/components/GovernClient.tsx:100 and app/blog/zero-trust-in-the-model/page.tsx:112 say a budgeted request that SatGate allows gets a signed receipt. An Observe allow that goes over budget does not. MCP Observe with a budget id and a positive cost still forwards that call and does not record a receipt (pkg/mcpserver/proxy.go:846-849 falls through; recordMCPDecision is only the Control branch at :857 and the successful spend at :922; forward is :958). Enterprise origin/main fcd34df3 pins that file at 78dd26f, the same blob as public main. Minimum fix: replace the GovernClient.tsx:100 paragraph with "In Control, a budgeted request that SatGate allows, and a refusal over a budget, a missing payment or a permission, gets a receipt signed by SatGate. A paid request that SatGate allows gets one too. A Cloud receipt can be checked with the open-source verifier against our published keys. An allowed request with no budget and no payment does not get one, except an Observe projection when that recording is on. An Observe allow that goes over budget is not refused; on MCP it does not get a receipt."
  • The same sentences say a request with no budget and no payment does not get a receipt (GovernClient.tsx:100, page.tsx:116). A permission refusal on that request still gets a signed policy_denied receipt (enterprise origin/main gateway/internal/proxy/proxy.go:2334 calls writeVerifiedScopeDenial at :3589; MCP pkg/mcpserver/proxy.go:1141). HTTP Observe can also sign an observe_projected receipt with no budget debit (gateway/internal/proxy/observe_evidence.go:157, called from proxy.go:1515 before proxyRequest). The GovernClient replacement above covers that card. For page.tsx:116 replace the paragraph with "There are limits to this. An allowed request with no budget and no payment does not get a receipt, except an Observe projection when that recording is on. An Observe allow that goes over budget is not refused; on MCP it does not get a receipt. Neither does a request that never goes through SatGate, or a refusal for going over a rate limit."
  • app/blog/zero-trust-in-the-model/page.tsx:112 says the receipt carries a hash of the token so you can match it to the agent. A payment refusal does not: AuthDenial replaces authority with no_verified_capability and drops token_id_hash (enterprise gateway/internal/proxy/receipt.go:786-787 and :808; the payment_required input sets no TokenID at proxy.go:3091-3098). A paid allow stores a hash of the payment hash, not the agent token (proxy.go:3145, then receipt.go:726). An x402 allow replaces authority with settled_x402_authorization and no token hash (receipt.go:780-784). A budgeted receipt hashes the token id when the token has one (receipt.go:726 and :768) and redacts the raw id (receipt.go:777). It does not name the agent and does not include call arguments (pkg/mcpserver/evidence.go:49; pkg/mcpserver/approval_gate.go:15-17). Minimum fix: replace page.tsx:112 with "In Control, a budgeted request that SatGate allows, and a refusal over a budget, a missing payment or a permission, gets a receipt signed by SatGate. A paid request that SatGate allows gets one too. The receipt names the tool or route and says whether SatGate allowed or refused it and why. When the token has an id, a budgeted receipt carries a hash of that id so you can match it to the agent. It does not name the agent or include the call's arguments. A Cloud receipt can be checked with the open-source verifier against SatGate's published keys. The agent never writes its own record, so the record doesn't depend on the model's account of itself."

Checks:

  • 1 Diff scope, holds. git log eec336a..1bc5708 is one commit. git diff --stat is three files, 5 insertions and 5 deletions: GovernClient.tsx, the blog page, and the guard pin. No change outside those sentences and scripts/check_policy_to_proof_page.py:146.
  • 2 Limits card, holds. GovernClient.tsx:96. In Control, a request outside budget is refused before upstream (enterprise origin/main gateway/internal/proxy/proxy.go:1672-1688 returns before proxyRequest at :1744; MCP control returns at pkg/mcpserver/proxy.go:881 before forward at :958). Outside tools: MCP :686 before forward; HTTP verifyDefaultProtection scope denial at proxy.go:2638 returns before the Control proxy. End date: a timestamp expires caveat fails macaroon verify before upstream (pkg/macaroon/macaroon.go:426, from VerifyRouted at proxy.go:2596 and MCP authenticate at pkg/mcpserver/proxy.go:524). Observe does not block on budget (proxy.go:1503-1515 forces observe and disables hard budget; MCP :846-849 allows overspend and still forwards). Observe records the request (HTTP usage at :1527 and an optional projection at observe_evidence.go:157; MCP publishes the exhaust event at :828). Not a blocker.
  • 3 Receipt issuance, blocker. See the first two blockers. Control budgeted allows and paid allows are signed (proxy.go:1707 budget_authorized; proxy.go:3140 payment_verified). Budget, missing-payment, and permission refusals are signed (proxy.go:1673; paid_uses.go:314 writeSignedPaidDenial; proxy.go:3589). The new sentences claim more than that.
  • 4 Receipt contents, blocker on the token-hash sentence. Route, decision, and reason are present (gateway/internal/proxy/receipt.go:856-857 and :864). No agent name. No call arguments. A token hash is not on every receipt that paragraph describes.
  • 5 Cloud verification, holds for a Cloud receipt. tools/verify_evidence_pack.py documents --jwks-url https://api.satgate.io/.well-known/jwks.json. Live GET of that URL returned 200, one OKP Ed25519 key, kid satgate-gateway-ed25519-2026-05. https://satgate.io/.well-known/jwks.json returned 308 to that URL. Receipt jwks_url is the issuer plus /.well-known/jwks.json (gateway/internal/evidence/receipt.go:894-899); the default issuer is https://api.satgate.io. The sentence says Cloud, so the self-hosted key is not this claim.
  • 6 Rate-limit carve-out, holds. HTTP route rate limit returns 429 at proxy.go:1155-1175 with no receipt write. MCP rate limit returns at pkg/mcpserver/proxy.go:490-492 before recordMCPDecision. A request that never goes through SatGate is outside the gateway. The no-budget half of page.tsx:116 is the second blocker, not this half.
  • 7 Build, holds. node_modules cloned with cp -Rc from /Users/mattdean/code/satgate/satgate/satgate-landing into ../pr-241/satgate-landing. From that tree on 1bc5708: npx tsc --noEmit exit 0; NEXT_TELEMETRY_DISABLED=1 npx next build exit 0, including /govern and /blog/zero-trust-in-the-model.
  • 8 Guard, not a new failure. python3 scripts/check_policy_to_proof_page.py exit 1 on this head and exit 1 on public main 79ddfa5. Both print only "missing design-partners authority/proof phrases: Policy-to-Proof layer". The new pin at line 146 was asserted: a missing sentence exits at lines 756-758 as "missing govern policy-to-proof phrases" before the design-partners check at 785-790.
  • 9 CI, holds on this SHA. gh pr checks 241 and gh run view 38923776054: headSha 1bc5708, conclusion success. Go job 117050006058, Node.js SDK job 117050006142, Python SDK job 117050006226, all success. Vercel check success (https://vercel.com/waynes-projects-50c6eb51/satgate/2xmYYTEWA5bX8eqquqYcyhxEN93D). Vercel Preview Comments success.

Enterprise code read from origin/main fcd34df3e6fc05e077793115235f2f6bdf699603. proxy.go, capability_budget.go, evidence/receipt.go, mint.go, and approval_gate_wiring.go are the same blobs as round-1 enterprise e3598a99. The commits since then do not touch those files. No files in the PR were modified by this review. Recommended next action: replace the receipt sentences as above. The limits card and the guard pin do not need another change. Do not merge this copy as written.

…med (refusals over budget, payment or permission; spending in Control); no exhaustive coverage claims
@matt-dean-git

Copy link
Copy Markdown
Member Author

Independent review (profile satgate-security-reviewer, xai-oauth grok-4.7), site copy, round 3
Verdict: PASS
Head SHA: ac67cab
Blockers: none

Checks:

  • 1 Diff scope, holds. git log --oneline 1bc5708..ac67cab is one commit. git diff --stat is two files, 3 insertions and 3 deletions: satgate-landing/app/components/GovernClient.tsx and satgate-landing/app/blog/zero-trust-in-the-model/page.tsx. The diff is only the two receipt paragraphs. The paid-allow sentence, the token-hash sentence, and "a request with no budget and no payment does not get one" are gone.
  • 2 Receipt sentences, hold on the gateway that ships today, HTTP and MCP. Enterprise origin/main fcd34df3e6fc05e077793115235f2f6bdf699603. Cloud MCP pins github.com/satgate-io/satgate 78dd26f; pkg/mcpserver/proxy.go blob 5a61452f3ef6a84ae8ac20d98941d3ed4c2ab14c is the same blob as public main 79ddfa5. A budget refusal is signed: HTTP Control gateway/internal/proxy/proxy.go:1672 calls writeBudgetDecisionResponse at :1673; the pay-policy budget refusal does the same at :1394; a capability-route budget refusal does the same in capability_budget.go:126. MCP Control records the denial at pkg/mcpserver/proxy.go:857 before it returns at :881. Observe does not refuse over budget (HTTP proxy.go:1503-1515 forces observe and disables hard budget; MCP :846-849 forwards), so the refusal sentence does not cover that allow. A missing-payment refusal is signed on HTTP: writeChargeChallenge calls writeSignedPaidDenial at paid_uses.go:163 (the receipt is built at :314), and the pay-backed challenge builds one at proxy.go:3091. MCP charge mode does not refuse for a missing payment (pkg/mcpserver/proxy.go:784 and :788-792 skip the budget block and forward), so there is no unsigned MCP payment refusal. A permission refusal is signed: HTTP scope mismatch calls writeVerifiedScopeDenial at proxy.go:2334 (helper at :3589) before the body is written; MCP scope and argument refusals go through policyDeniedResponse at pkg/mcpserver/proxy.go:1141, and a gate refusal through call_gate_response.go:41. A Control spend is signed: HTTP writeBudgetAllowedReceiptHeaders at proxy.go:1707, and if that fails the debit is compensated and the request is not proxied (:1721-1735); MCP recordMCPDecision at pkg/mcpserver/proxy.go:922, and a record failure in Control compensates and does not forward (:930-937). Cloud MCP installs that recorder or does not start (gateway/internal/mcpgateway/entrypoint.go:202-208 and :490-496). The receipt names the route or tool (gateway/internal/proxy/receipt.go:864 route_or_tool; MCP sets RouteOrTool to mcp:tools/call: plus the tool name at pkg/mcpserver/proxy.go:1060), and it has decision and decision_reason (receipt.go:856-857). It does not name the agent: the token id is hashed and the raw id is redacted (receipt.go:726 and :777). It does not include call arguments: MCP argument values are not receipt fields (pkg/mcpserver/evidence.go:49); the HTTP receipt has method and path, not the body or query. A SatGate Cloud receipt verifies against the published keys: tools/verify_evidence_pack.py:6 documents --jwks-url https://api.satgate.io/.well-known/jwks.json; live GET of that URL returned 200 with one OKP Ed25519 key, kid satgate-gateway-ed25519-2026-05; https://satgate.io/.well-known/jwks.json returned 308 to that URL; the receipt jwks_url is the issuer plus /.well-known/jwks.json (gateway/internal/evidence/receipt.go:894-899) and the default issuer is https://api.satgate.io (receipt.go:30). The agent does not write or edit that record: no evidence mutation route, and POST /receipts is an operator-gated Fiat402 payment token (gateway/api/server.go:518), not the decision record. A rate-limit refusal gets no receipt: HTTP proxy.go:1164-1175 returns 429 with no receipt write; MCP returns at pkg/mcpserver/proxy.go:490-492 before recordMCPDecision; the MCP HTTP limiter returns 429 at gateway/internal/mcpgateway/ratelimit.go:122. A request that never goes through SatGate is outside the gateway. Not every request gets a receipt, and the sentences no longer say which other requests do not.
  • 3 Build, holds. Direct cp -Rc of node_modules into the worktree was blocked, so node_modules was cloned with cp -Rc from /Users/mattdean/code/satgate/satgate/satgate-landing into the scratch directory and the PR landing tree was copied beside that clone. On that tree, same bytes as ac67cab: npx tsc --noEmit exit 0; NEXT_TELEMETRY_DISABLED=1 npx next build exit 0, including /govern and /blog/zero-trust-in-the-model.
  • 4 Guard, not a new failure. python3 satgate-landing/scripts/check_policy_to_proof_page.py exit 1 on this head and exit 1 on public main 79ddfa5. Both print only "missing design-partners authority/proof phrases: Policy-to-Proof layer".
  • 5 CI, holds on this SHA. gh pr checks 241 and gh run view 38929782001: headSha ac67cab, conclusion success. Go job 117070504395, Node.js SDK job 117070504682, Python SDK job 117070504014, all success. Vercel check success (https://vercel.com/waynes-projects-50c6eb51/satgate/G3qejvdXkhnwC5uHerbZu3JvnxTn). Vercel Preview Comments success.

No files in the PR were modified by this review. Recommended next action: this copy can merge. The limits card and the guard pin were not reopened.

@matt-dean-git
matt-dean-git merged commit d7d9684 into main Oct 16, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — ac67caba Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant