Repository navigation
feat(moduleauthority): mint through the real host gateway in CI, type the 412, refuse a non-canonical organization - #36
Conversation
… the 412, refuse a non-canonical organization The SDK's broker contract was held only by a hand-written fake, which still accepted X-Codefly-Module-Secret after the v0.0.94 gateway stopped reading it. - conformance/hostgateway: run.sh compiles a test into module-saas-starter's auth-gateway package (go test -overlay, -modfile) at the SOURCE.txt ref and drives the real moduleauthority client through the real gateway handlers; only accounts' gRPC mints are faked, as the host's own tests fake them. The host checkout is verified at the ref and unmodified before and after. - CI: host-gateway-conformance job; the host checkout moves into a host-source composite action shared with gen-drift. SOURCE.txt records the auth gateway path. - A source mint's 412 is ErrDelegationMissing (un-deprecated), by status alone, as 403 is ErrDelegationInvalid. - New refuses a non-canonical UUID OrganizationID with ErrInvalidCredentials, accounts' own rule; the organization refusal (401 work mint, 403 operation mint) is documented. - The fake follows the real gateway on D1, D3, D4; D2, D5-D8 are documented. Closes #35 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…; say who the organization check changes Review findings on #36 at a02c9b6: - F1: run.sh lists the conformance tests first and requires exactly the expected eight, then requires each to PASS and none to SKIP. Renaming the tests used to exit 0 with "[no tests to run]". - F3: after one `go get` of the SDK, everything runs -mod=readonly, so an import no required module provides is an error, never an @latest lookup. The build fails if grpc, protobuf or sdk-go/workcontext would differ from the host's pin, unless the two module trees are byte-identical (checked each run; today only workcontext b91ce91 -> b3fb40d). Other moves warn. - F2: conformance/hostgateway/AGENTS.md and the refresh skill tell a compile red (re-bind the host-facing calls) from an assertion red (fix the client); the clone recipe handles a 40-hex SHA as well as a tag. - F4: the canonical-UUID check stays in New. CHANGELOG, the OrganizationID doc and README now say it refuses source-only clients too, which used to work; the test covers refusal before any request and both mint paths. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
CI on head b29baf9 (run 38190972193) passed all three jobs. |
…go get Review N1 at b29baf9: `go get` resolved an import of moduleauthority that the SDK's go.mod did not declare at @latest (rsc.io/quote v1.5.2) and the run passed. run.sh now requires the SDK's own `go list -mod=readonly -deps ./moduleauthority` to succeed first, so the claim that nothing is resolved at @latest holds; run.sh, AGENTS.md and CHANGELOG say what is checked where. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Fixes N1 from the review at b29baf9. Proof, against the sparse v0.0.94 checkout with
CI on head be431d1 (run 38191842390) passed all three jobs: test, gen-drift and host-gateway-conformance (8/8). |
Closes #35
What this does
moduleauthority's broker contract was held only byhost_fake_test.go, a hand-written fake that still acceptedX-Codefly-Module-Secretafter the v0.0.94 gateway stopped reading it. Now CI also mints through the host's real auth-gateway handlers at the refSOURCE.txtrecords (v0.0.94). The PR also types the source-mint 412 and documents (and partly pre-checks) the organization refusal.1. Real-gateway conformance (
conformance/hostgateway/, new CI jobhost-gateway-conformance)testdata/sdk_conformance_test.gois this repo's code, written aspackage mainof the host's gateway.run.sh <host checkout>compiles it intomodule/services/auth-gateway/codewithgo test -overlay. The SDK is required (via areplacepointing at this working tree) in a temporary copy of the gateway'sgo.mod, passed with-modfile.SOURCE.txtref, and checks that the checkout is unmodified both before and after the run. The host stays a read-only checkout, and nothing of it is copied or vendored here.Gateway.ServeHTTP(fromnewGatewayHarness) overhttptest. It fakes only accounts' threeMint*RPCs, on a real gRPC listener, with a handgrpc.ServiceDesc, exactly asgateway_modules_test.go,gateway_operation_context_test.goandgateway_source_operation_context_test.godo. The SDK's realmoduleauthority.Newclient runs all three mints against it.Authorization: Bearer, the internal token, and no secret header reach the gateway.prefix,service_account_token,organization_id,bindingandsource_id(the source arm, withdelegation_idempty).bearerand an empty bearer..github/actions/host-source, which both jobs use.SOURCE.txtgains an(auth gateway: module/services/auth-gateway/code)line beside the proto path, so the gateway path is recorded once and not hardcoded in CI or the script.Mutation proof. I broke
moduleauthority/broker.gofour ways, ranrun.sh, and reverted:organization_id→organizationIdTestSDKConformanceThreeMints—accounts received work mint … organization_id="". The real gateway drops the misnamed key silently, so only the forwarded-field assertion catches this one.X-Codefly-Module-Secretsent instead of the bearerwork-context mint: … HTTP 401: request refusedsource_id→sourceIdsource-operation-context mint: … HTTP 400binding→binding_idoperation-context mint: … HTTP 400Removing the new 412 mapping turns
TestSDKConformanceRefusals/source_delegation_missingred, along with two unit tests. Removing the canonical-UUID check turnsTestNewRefusesANonCanonicalOrganizationred.The runner fails rather than passing vacuously (review F1, F3):
--- PASSand none to--- SKIP.go get, then runs everything-mod=readonly, so an import no required module provides is an error, never an@latestlookup. This replaces-mod=mod.google.golang.org/grpc,google.golang.org/protobuforsdk-go/workcontextother than the host's pin. The one exception is a version whose module tree is byte-identical, whichdiff -rchecks on every run instead of a hand-kept allow-list.workcontextb91ce91 → b3fb40d. Both trees are identical, includinggo.mod.replacewould instead compile the SDK against versions it does not pin.golang.org/x/*moves are printed as warnings.Proofs, run locally with
run.sh <v0.0.94> -raceexactly as CI runs it, then reverted:TestSdkConformanceTokenBound)t.Skipin one testrsc.io/quote, which no required module providesKeeping the test in step with the host (review F2).
conformance/hostgateway/AGENTS.md("When it goes red") and the refresh skill tell the two kinds of red apart:newGatewayHarness's results, or anaccountsv1field. Re-bindnewRealGatewayand the accessors, and change no assertion.The clone recipe now fetches a 40-hex SHA as well as a tag. I tested both forms; they resolve to the same commit, 9f6a4aa.
Seams the host does not export. The gateway is a
mainpackage and its harness is a test helper, so nothing of it can be imported. The test therefore binds to four unexported host names:newGatewayHarness,(*Gateway).authz,(*ExtAuthz).backendConnand(*ExtAuthz).internalToken. It also uses the host'sauth-gateway/pkg/gen/saas/accounts/v1types. If the host renames any of these, the compile fails red; it never passes silently. Removing that coupling needs an exported conformance harness in module-saas-starter. Per instructions, I have not filed that; I'll note it on #35.Fake (
host_fake_test.go). It stays as the edge-case double.assertProjectedBearerstill asserts the client never sends it, and the conformance edge check does too.validCatalogIdentity's exact pattern, at most 63 characters.TestHostFakeFollowsTheRealGatewayOrderpins all three answers on the fake. The conformance job pins the same three on the real gateway.brokerMintstable drives those statuses through a raw transport and says they are unreachable on the real gateway.2. Source-mint 412 is typed
delegationRefusalmaps 412 →ErrDelegationMissingon status alone, exactly as 403 →ErrDelegationInvalid; it reads no body. The v0.0.94 gateway sends 412 on that path only forDELEGATION_MISSING; any other FailedPrecondition becomes 502, which the conformance table pins.ErrDelegationMissingis un-deprecated.ErrDelegationRevokedstays deprecated, because telling it apart would need the body.3. Organization refusals
The
ErrInvalidCredentialsandCredentials.OrganizationIDdoc comments, and the README'sOrganizationIDparagraph, now say that a missing or wrong organization surfaces asErrInvalidCredentials(401) on the work mint andErrPermissionDenied(403) on the operation mint.Behaviour change:
Newrefuses a non-canonical UUIDOrganizationIDwithErrInvalidCredentials. It uses accounts' own rule (uuid.ParseplusString() == org, frommodule_identity.gomoduleMintOrganization), so it is no stricter than the host. The test coversACME, uppercase, braced,urn:uuid:, unhyphenated and space-padded forms.Who changes behaviour (review F4). I kept the check in
New, even for a client that only makes source mints, which never send the field.Why construction, not mint time:
OrganizationIDis part of the credentials the client is built with, and the README'sNewexample already fails closed at startup onErrInvalidCredentials.CHANGELOG, the
OrganizationIDdoc comment and the README now say this precisely.TestNewRefusesANonCanonicalOrganizationcovers three things: refusal before any request, a canonical organization sent on both the work and operation mints, and a source-only client that sends none.Fix or hack
ErrDelegationMissing, the organization check inNew, and the doc comments. Each lives at the place that owns the behaviour, the SDK's error mapping and constructor.SOURCE.txt.-overlay,-modfile), and nothing of the host is edited or copied. The coupling is the four unexported host names above. That is not a hack around a host defect, but the real fix for the coupling is a host-exported harness.GOLANG_PROTOBUF_REGISTRATION_CONFLICT=ignoreinrun.sh.gen/saas/accounts/v1register the same proto files, and the conformance binary links both.warninstead, a failure log would open with about 120 warning lines.Reused, and deliberately not
newGatewayHarnessand its accounts-fake pattern.replacedirection (the host requires the SDK), the protobuf conflict handling, and the case list.go.modedit. It is replaced by-modfile, so the host checkout is never modified.testing).BodyStrictnessprobe and itst.Logf-only divergence probes. Each probe is now either an assertion or documented.Docs updated
README.md: theOrganizationIDparagraph, the broker-error paragraph (412), and the regeneration section (the ref also moves the conformance contract).CHANGELOG.md: a new Unreleased section, including the behaviour change.AGENTS.md: the CI description, the "Where things live" row, and the "what CI runs" line.conformance/hostgateway/AGENTS.md..claude/skills/refresh-generated-api/SKILL.md: SOURCE.txt's gateway line, and running conformance when the ref moves.client.go,source_context.goandbroker_errors.go.Verification (local, at the head of this branch)
GOWORK=off go build ./... && go vet ./... && go test -race -count=1 ./...: all packages ok.gofmt -l . | grep -v '^gen/': empty.TestPublicAPINamesEveryTypeItExposes,TestGateCatchesKnownBypassesandTestGateRejectsAVacuousPassall pass.buf generate(buf 1.73.0) from v0.0.94'smodule/services/accounts/protoreproducesgen/byte-for-byte. This PR does not changegen/.conformance/hostgateway/run.sh <v0.0.94 checkout> -race: ok, on both a full clone and a sparse checkout of exactly the two paths CI checks out (2.1 MB). The host'sgit statusis clean afterwards.accounts/,datasource/).datasource/parity_test.gopasses unchanged.Not verified
PermissionDenied, was read frommodule_identity.go, not executed. The conformance test only proves how the real gateway relays each code.golang.org/x/*modules the SDK raises are compiled into the gateway in this job and only warned about. grpc, protobuf and workcontext are gated as described above.go getreports that the SDK's own pinnedsdk-go/workcontextb3fb40d is retracted by its author. This predates this PR and I left it alone; it is out of scope here.b29baf9: see the comment on this PR.🤖 Generated with Claude Code