Skip to content

feat: governed callers - MSP becomes one implementation of the caller role - #28

Merged
Freshair129 merged 1 commit into
mainfrom
feat/governed-callers
Sep 27, 2026
Merged

Freshair129 merged 1 commit into
mainfrom
feat/governed-callers

Conversation

@Freshair129

Copy link
Copy Markdown
Owner

Summary

This PR implements ADR-GKS-GOVERNED-CALLERS. The owner accepted it on 2026-09-27, including all four open questions as proposed.

Why. A customer that already has its own auth and memory should not have to deploy MSP just to write to GKS. GKS doesn't need the MSP product. It needs someone to fill three roles: authenticate the user, choose the scope, and attest provenance. This PR turns MSP into the first implementation of that role, a governed caller. Any other system can be provisioned for its own portfolios.

How a governed caller works

{ "schemaVersion": "gks-client-grants/v2",
  "clients": [{ "profile": "governed", "clientId": "acme-backend", "credentialSha256": "…",
                "provenanceNamespace": "acme", "portfolioIds": ["acme-portfolio"],
                "allowedTools": ["gks_knowledge_promote", "gks_search", "…"] }] }
  • Transport: HTTP only. The credential is sent as Authorization: Bearer gksc_…, and each call carries _meta.gksCallerAuth with { version: "gks-caller-auth/v1", callerId, scopeDigest }.
  • Boundary: the caller may act only in the portfolios listed in its grant, and each portfolio has exactly one governed caller. Because idempotency keys and refs are keyed by scope, two callers can never collide. The exact tenant wall (C0 D1) still applies inside a caller's own portfolio.
  • Provenance: provenance_ref, provenanceRef and evidenceRef must use <own namespace>:proof/…. A caller cannot write under another caller's namespace, and msp is reserved.
  • Attribution: every write records the authenticated caller_id (migration 0008).
  • Out of scope: pipeline tools, OIDC/JWT, verifying proofs (GKS-IDN-006), and portfolios shared between callers.

What did not change for MSP

  • The GKS_MSP_RELAY_CREDENTIAL / gks-msp-auth/v1 envelope over stdio and HTTP, API-010 payloads, and response shapes are all unchanged.
  • MSP keeps msp:proof/ with the same error messages.
  • MSP is denied only the portfolios that a governed grant owns (gks_scope_denied, "This portfolio is governed by another caller."). With no governed grants configured, behaviour is identical to before.
  • The tool schema provenance pattern widens from ^msp:proof/ to ^[a-z][a-z0-9-]{0,30}:proof/. Every value accepted before is still accepted.

Changes

Area Change
gks-contracts/client-auth.mjs Grants v2 parsing (governed profile; v1 still parses as read); authorizeGovernedCallerRequest; governedPortfolios; GKS_MSP_CALLER
gks-contracts/validation.mjs, tool-definitions.mjs Provenance checked against the caller's namespace (default msp); schema pattern widened
gks-core promoteCandidate, applyHumanResolution and linkArtifact take a caller and pass callerId to persistence
gks-persistence, migrations/0008_caller_attribution.sql caller_id NOT NULL DEFAULT 'msp-runtime' on promotions, stage_evidence, human_resolutions and artifact_links. The port accepts an optional callerId. The value is not returned in responses.
apps/gks-server A bearer credential resolves to a read or governed grant. MSP is denied on governed portfolios. directClientGrant is renamed clientGrant.
docs ADR-GKS-BOUNDARY 0.7.0b, ADR-GKS-CLIENT-ACCESS 0.3.0b, GKS-PORT-CONTRACT 0.11.0b, README, production runbook (provisioning and revoking a governed caller), and the call-direction rule in CLAUDE.md

Evidence

  • tests/contract/governed-callers.test.mjs (27 tests):
    • grants parsing, plus 9 invalid documents that must fail startup;
    • 10 authorization denials;
    • over real HTTP: a governed caller writes and reads in its own portfolio, attributed to itself; it is denied outside its boundary, under MSP's namespace, without an envelope, and on pipeline tools;
    • MSP is denied the governed portfolio but keeps the others;
    • migration 0008 backfills existing rows as msp-runtime, including a stage-evidence row created by the 0005 hook.
  • C0.4 golden corpus: 23 of 24 cases replay unchanged. The one that changes is TOOL-001, where tools/list now shows the widened pattern.
  • check:baseline: caught exactly the tool registry hash and migration 0008; re-locked.
  • Real MSP suites (MSP_REPO_ROOT pointing at the local Memory-and-Soul-Passport checkout):
    • msp-provider-compatibility passes.
    • msp-service-chain fails, but inside MSP's own client before any request reaches GKS. MSP's latest commit (fd6c24b) requires a workspace ID that this test's fixture doesn't send. This is unrelated to this PR and should be fixed separately.

Follow-up

The ADR's acceptance criteria include golden corpus cases for a governed caller. The corpus runner currently speaks only stdio, so the next PR adds an HTTP mode to the runner and a governed-caller golden case.

Test plan

  • npm test: vitest 327 passed, 2 skipped (the MSP suites need MSP_REPO_ROOT); security 12/12; unit 9/9
  • npm run check:c0 (PASS 24 / NOT_RUN 1), check:baseline, check:corpus
  • msp-provider-compatibility against the real MSP checkout
  • CI on this PR

🤖 Generated with Claude Code

… role

ADR-GKS-GOVERNED-CALLERS was accepted by the owner on 2026-09-27, with all
four open questions settled as proposed. A system that already has its own
auth and memory can now write to GKS without deploying MSP.

- Grants v2 adds a "governed" profile to GKS_CLIENT_GRANTS_PATH. Such a
  grant carries clientId, a hash-only credential, the legacy knowledge
  tools it may call, an exclusive list of portfolioIds, and a unique
  provenanceNamespace. v1 documents still parse as read grants. Startup
  fails on overlapping portfolios, a namespace claimed twice, the reserved
  "msp" namespace or msp-runtime id, and pipeline tools.
- Governed calls arrive over HTTP with a Bearer credential and a
  _meta.gksCallerAuth envelope (version, callerId, scopeDigest).
  authorizeGovernedCallerRequest enforces the tool list, the portfolio
  boundary, the scope digest and the caller's own <namespace>:proof/ refs.
- Provenance checks are caller-aware. The tool schemas widen to
  ^[a-z][a-z0-9-]{0,30}:proof/, which still accepts every value accepted
  before. MSP stays msp:proof/, and its messages are unchanged.
- MSP remains the built-in governed caller, and GKS_MSP_CALLER now lives
  in contracts. MSP is denied the portfolios a governed grant owns. Pipeline
  tools stay MSP-relay only.
- Migration 0008 records the authenticated caller_id on promotions,
  stage_evidence, human_resolutions and artifact_links. Existing rows read
  as msp-runtime. The persistence port takes an optional callerId, and
  caller_id is not returned in responses.

The C0.4 corpus replays unchanged except for TOOL-001, whose tools/list
pattern widened. The baseline is re-locked for the tool registry hash and
migration 0008. Docs amended: ADR-GKS-BOUNDARY, ADR-GKS-CLIENT-ACCESS,
GKS-PORT-CONTRACT, README, the production runbook (provisioning a governed
caller), and the call-direction rule in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Freshair129
Freshair129 merged commit 36ae491 into main Sep 27, 2026
14 checks passed
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