Skip to content

moduleauthority: mint through the real host gateway in CI, type the 412 delegation-missing refusal, document organization refusals #35

Closed

Description

@antoinetoussaint-byte

What we are trying to do

The SDK's tests should go red when the host's contract moves, not when a module first mints in production. They also shouldn't let a caller misread a refusal it can act on.

Why now

#30 regenerated gen/ from host module-saas-starter v0.0.94. That regeneration surfaced a break that every test in this repo had missed.

  • moduleauthority still sent X-Codefly-Module-Secret on all three mints.
  • The v0.0.94 gateway reads only Authorization: Bearer <projected ServiceAccount token> (module/services/auth-gateway/code/gateway_modules.go:99-122). Its own comment: "No header carrying a secret is read any more."
  • The suite stayed green because moduleauthority/host_fake_test.go is a hand-written fake that still accepted the old header.

#30 fixed the break. The proof came from a one-off throwaway test that served the real v0.0.94 gateway handlers (newGatewayHarness, gw.ServeHTTP) to the SDK's real client. Nothing in this repo runs that check, so the next drift will be invisible again.

Asks

1. Real-gateway conformance in CI

Mint through the host's real gateway handlers at the tag recorded in SOURCE.txt, not only through the fake.

  • CI checks out the host at that tag (gen-drift already does).
  • It serves the real auth-gateway handlers over httptest, with the accounts leg faked the way the host's own gateway tests fake it.
  • It runs the three moduleauthority mints (work context, operation context, source operation context) through the SDK's real client.

Assert:

  • the request is accepted;
  • the fields accounts receives (service_account_token, organization_id, binding, source_id);
  • the refusal mapping, from accounts code → gateway status → SDK error;
  • that the legacy header, a missing bearer, a duplicate bearer, or a non-Bearer scheme is refused.

The throwaway version of this test, real_gateway_review_test.go.txt, is in the #30 review packet. Keep the fake for edge cases the real handlers cannot be driven into. Where the fake still diverges from the real gateway, either align it or document why. The review listed these divergences:

  • D1: secret plus bearer: the fake returns 401, the real gateway 200.
  • D3: the prefix pattern differs.
  • D4: check order differs.
  • D5: the fake returns a 403 on work-context, which the real gateway never sends.

2. Type the source-mint 412 (review finding F1, minor, inherited from #34)

  • moduleauthority/source_context.go:11-17,104-120 turns a source-mint 412 into a bare BrokerError. ErrDelegationMissing is deprecated, and errors.Is(err, ErrDelegationMissing) is false.
  • The host's module/WORK_CONTEXTS.md at v0.0.94 tells consumers to treat 412 as "a person must reconnect this source".
  • The gateway sends 412 on this path only for DELEGATION_MISSING (any other FailedPrecondition becomes 502). So map on the status alone, exactly as 403 → ErrDelegationInvalid does today. That needs no body read and keeps fix(moduleauthority): a refusal stays a refusal, and a broker error never echoes the bearer #34's rule.
  • Un-deprecate ErrDelegationMissing and update README.md and CHANGELOG.md.

3. Say what an organization refusal looks like (review finding F2, minor)

  • A shared-principal module that omits OrganizationID, or a pinned module that names another organization, is refused by accounts (module_identity.go:266-275). The gateway turns that into 401 on the work mint (403 on the operation mint), so the SDK reports ErrInvalidCredentials.
  • The ErrInvalidCredentials doc comment (client.go:43-49) lists only token, workload and build causes. State the organization cause there and in the README's OrganizationID paragraph.
  • Optionally, refuse a non-canonical UUID in New (client.go:252-276). Today ACME passes construction and fails at the first mint as an untyped 400.

Not verified

  • Accounts' real mint handlers were not run in the feat(datasource)!: invoke declared operations with host receipts #30 review; they need a Kubernetes TokenReview and a database. The accounts leg was faked the way the host's gateway tests fake it.
  • F2's 401 mapping was exercised against the real gateway. The accounts-side refusal cause was read from the code, not run.

Activity

  1. antoinetoussaint-byte commented on Oct 11, 2026

    @antoinetoussaint-byte
    ContributorAuthor

    This is out of scope for #36 and I have not filed anything; I'm recording it here as asked.

    The real-gateway conformance test is coupled to host internals. module-saas-starter's auth-gateway is package main, and its test harness is an unexported helper. So #36's conformance/hostgateway test is compiled into that package by go test -overlay and binds to four unexported names:

    • newGatewayHarness
    • (*Gateway).authz
    • (*ExtAuthz).backendConn
    • (*ExtAuthz).internalToken

    A host rename turns the job red at compile time, so it fails loudly, never silently. The fix that removes the coupling belongs in the host: an importable, exported gateway test harness (for example a non-main package exposing the handler with an injectable accounts connection). Once that exists, this SDK could depend on it instead of overlaying a file.

    Two smaller observations from the same work:

    • Requiring the SDK in the host's module graph raises sdk-go/workcontext (b91ce91 → b3fb40d) and several golang.org/x/* modules for the conformance build only. run.sh prints this list on every run.
    • From the feat(datasource)!: invoke declared operations with host receipts #30 review, already known and still unchanged: NewGatewayAuthority targets /modules/_authority, which the v0.0.94 gateway does not serve.
  2. antoinetoussaint-byte commented on Oct 13, 2026

    @antoinetoussaint-byte
    ContributorAuthor

    lazybox has finished working on this. claude (Opus 5.5) held it from 2026-10-11 23:42 UTC until 2026-10-20 01:11 UTC. The working label has been removed; this comment is the record.

    claim detail
    Lazybox-Claim-Device: effacd542b611010656e
    Lazybox-Claim-Session: d845510cae
    Lazybox-Claim-Workspace: github-codefly-dev-saas-sdk-go-35
    Lazybox-Claim-Agent: claude
    Lazybox-Claim-Model: Opus 5.5
    Lazybox-Claim-Started: 2026-10-11T23:42:03Z
    Lazybox-Claim-Heartbeat: 2026-10-20T01:11:12Z
    Lazybox-Claim-Expires: 2026-10-19T16:49:29Z
    Lazybox-Claim-Released: 2026-10-20T01:11:12Z
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions