Skip to content

feat: complete brokered authenticated reference status - #15

Merged
kanushka merged 5 commits into
wso2:mainfrom
kanushka:feat/brokered-reference-status
Jul 28, 2026
Merged

feat: complete brokered authenticated reference status#15
kanushka merged 5 commits into
wso2:mainfrom
kanushka:feat/brokered-reference-status

Conversation

@kanushka

Copy link
Copy Markdown
Contributor

Closes #7. Parent: #3. Increment 4 of
the first CLI vertical slice plan.

What this proves

The architecture proof's last unproved boundary was authentication. The shell
now owns it end to end:

wso2 reference status
  -> isolated context (name, organization, endpoint, method, credential source)
  -> module AcquireAccess (audience, scopes)
  -> broker policy: receipt ∩ context ∩ organization ∩ invocation
  -> short-lived fixture token, or a typed AccessDenied
  -> local read-only status service
  -> typed Result or Problem, rendered by the shell

The reference module receives a token and nothing else: not the credential the
shell holds, not a reference to it, and no way to renew what it was given. It
is launched with an empty environment, so there is no ambient credential for it
to find.

Contract

Field numbers 13, 14, and 15 were reserved for this exchange and are now
AcquireAccess, AccessGranted, and AccessDenied. InvocationContext gains
an endpoint: it tells a module where to call, never that it may.
sdk/protocol/contractv1 is regenerated with the pinned buf.

Policy

The module receipt is the ceiling. Refused, without reaching the service:

Denial Code
Audience the installation does not declare auth.audience_not_declared
Permission the installation does not declare auth.scope_not_declared
No context selected auth.context_not_selected
Context names no organization auth.organization_not_selected
Method this shell does not implement auth.method_unsupported
Credential source not set auth.credential_unavailable
A second request in one command auth.already_granted

A broker denial exits 77 and a product-service failure exits 75, so automation
can tell "you may not" from "it is broken" without reading text.

Non-production seams

internal/auth/devtoken (the issuer) and internal/statusservice (the service)
are test infrastructure. Both are internal packages with no command behind them,
neither is linked into the shell binary, and every token it mints begins with
wso2-development-token.. The production replacements are a real token exchange
and a real product service.

Tests

47 new tests across unit, contract, and black-box layers, covering denied
access, successful access, claim mismatch, expiry, service failure, a sanitized
module environment, and a canary scan proving no run discloses the source
credential. CI now also builds, vets, and tests the reference module, whose own
tests previously never ran.

Known limit

The status service compares the token's invocation claim with a header from the
same caller. That proves the token belongs to the invocation the caller is
acting as; it is not replay detection, since a replayer sends both. Closing it
needs an identity provider the audience trusts, which is what the production
replacement brings. The limit is documented at the check.

The architecture proof's last unproved boundary was authentication. The
shell now owns it end to end: it selects an isolated context, reads a
development credential the module never sees, and answers the module's
AcquireAccess with a fixture token bound to the declared audience and
scope, the context's organization, this invocation, and a near-term
expiry. A denial is the shell's own typed problem.

The module receipt is the ceiling. An undeclared audience, a permission
the installation does not declare, a context with no credential source,
and a second request in one command are all refused, and none of them
reaches the service. The module is launched with an empty environment,
so there is no ambient credential for it to find.

The local status service accepts only what it serves: the wrong
audience, scope, organization, invocation, or an expired token is
refused. A broker denial and a service failure end in different exit
classes, so automation can tell "you may not" from "it is broken".

The development issuer and the status service are test infrastructure.
Both are internal packages, neither is linked into the shell binary, and
every token says so on the wire.

Closes wso2#7
@kanushka
kanushka requested a review from hevayo as a code owner July 27, 2026 15:59
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary

Completes brokered authentication for wso2 reference status using isolated contexts and a local read-only status service.

Key changes

  • Added shell-mediated access requests, grants, and typed denials.
  • Added context selection, endpoint propagation, credential handling, and short-lived fixture tokens.
  • Implemented status-service calls with claim and expiry validation.
  • Added distinct handling for access denials and service failures.
  • Sanitized the module environment and added output-safety checks.
  • Expanded SDK, RPC, protocol, context, authentication, and status-service support.
  • Added 47 unit, integration, and acceptance tests covering success, denial, expiry, claim validation, failures, and environment behavior.
  • Updated CI and contributor documentation to build, vet, and test the reference module.

Walkthrough

This PR implements brokered authentication for the reference status module. It adds validated context documents, isolated fixture installation, invocation-bound development tokens, shell and module access-broker protocol messages, and RPC enforcement. The reference module now requests access and calls a local authenticated status service. Tests cover token claims, access denials, service failures, expiry, claim mismatches, credential isolation, and rendered status output. CI and contributor documentation now include reference-module validation.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Shell
  participant ReferenceModule
  participant StatusService
  CLI->>Shell: Run reference status
  Shell->>ReferenceModule: Launch with selected context
  ReferenceModule->>Shell: Request declared audience and scope
  Shell-->>ReferenceModule: Return invocation-bound token
  ReferenceModule->>StatusService: GET /status with token
  StatusService-->>ReferenceModule: Return status JSON
  ReferenceModule-->>Shell: Return semantic status result
  Shell-->>CLI: Render status output
Loading

Suggested reviewers: hevayo, axewilledge, sachinisam

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: brokered authenticated reference status.
Description check ✅ Passed The description matches the implemented brokered status flow and supporting tests.
Linked Issues check ✅ Passed The changes align with #7: context, brokered access, token checks, service validation, sanitized environment, and tests are covered.
Out of Scope Changes check ✅ Passed No obvious out-of-scope code changes appear; the docs, fixtures, protocol, and tests support the authenticated status flow.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

A missing-credential denial could only tell a user what to do by naming
the variable to set, and that whole problem was encoded into
AccessDenied. The module was therefore handed the credential's source
reference, which is one of the three things the proof promises it never
receives.

A denial is now two statements of one refusal. The module is sent a
refusal that names no credential source; the shell keeps the version
that does, and reports it when the module returns the refusal as its
outcome. Only a refusal this invocation actually issued is restored, so
nothing a module invents can make the shell speak for it.

Three smaller refusals join it. The broker serves the reserved reference
namespace alone, so a product module cannot reach the development issuer
behind it. An access request that names no exchange is refused rather
than answered into the void. An endpoint that embeds credentials in its
URL is refused, and no rejected endpoint is echoed into a problem the
shell renders.

The contract said an expired token could be reacquired, which the broker
has never allowed. It now says what the code does.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
CONTRIBUTING.md (1)

29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep contributor commands aligned with CI.

The new command runs only go test, while CI also requires formatting validation and go vet. Document those checks as well, or provide one shared validation command so local and CI results stay consistent.

As per path instructions, this feedback is concise and focused on correctness and best practices.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONTRIBUTING.md` at line 29, Update the contributor validation command near
the reference module entry to include the same formatting validation and go vet
checks required by CI, or replace it with the shared validation command used by
CI so local verification remains consistent.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/module/broker.go`:
- Around line 88-121: Update streamBroker.Acquire so the response wait through
b.reader.ReadEnvelope is bounded by ctx cancellation or deadline. Ensure
cancellation unblocks or stops the read and returns the context error, while
preserving the existing envelope validation and error handling for successful or
failed reads.

---

Nitpick comments:
In `@CONTRIBUTING.md`:
- Line 29: Update the contributor validation command near the reference module
entry to include the same formatting validation and go vet checks required by
CI, or replace it with the shared validation command used by CI so local
verification remains consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e3581e50-78ac-4faf-90b2-d5cf34ee4895

📥 Commits

Reviewing files that changed from the base of the PR and between e57aeee and 5d6e0de.

⛔ Files ignored due to path filters (1)
  • sdk/protocol/contractv1/contract.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (36)
  • .github/workflows/pr-checks.yml
  • CONTRIBUTING.md
  • docs/examples/authentication-contexts.md
  • docs/plans/first-cli-vertical-slice.md
  • examples/reference-module/cmd/wso2-module-reference/main.go
  • examples/reference-module/cmd/wso2-module-reference/status.go
  • examples/reference-module/cmd/wso2-module-reference/status_test.go
  • internal/app/app.go
  • internal/app/invoke.go
  • internal/auth/auth.go
  • internal/auth/auth_test.go
  • internal/auth/devtoken/devtoken.go
  • internal/auth/devtoken/devtoken_test.go
  • internal/contexts/contexts.go
  • internal/contexts/contexts_test.go
  • internal/contexts/fixture/fixture.go
  • internal/modules/fixture/fixture.go
  • internal/rpc/broker_test.go
  • internal/rpc/invocation_id.go
  • internal/rpc/launch.go
  • internal/rpc/rpc.go
  • internal/rpc/session.go
  • internal/state/state.go
  • internal/state/state_test.go
  • internal/statusservice/statusservice.go
  • internal/statusservice/statusservice_test.go
  • sdk/module/broker.go
  • sdk/module/broker_test.go
  • sdk/module/serve.go
  • sdk/proto/wso2/cli/module/v1/contract.proto
  • sdk/protocol/codec.go
  • sdk/testkit/testkit.go
  • test/acceptance/acceptance_test.go
  • test/acceptance/broker_test.go
  • test/acceptance/status_test.go
  • test/acceptance/testdata/noisymodule/main.go

Comment thread sdk/module/broker.go
kanushka added 3 commits July 28, 2026 14:20
The fail-closed work landed on main while this branch was open, and the
two disagree in one place each. Its shadowing test runs the reference
module and expects an answer, which now requires brokered access to the
local status service, so it deploys one. Its control-file helper was
renamed, so the environment-reporting test calls it by its new name.
A reviewer asked whether a module waiting for the shell's answer can
hang, because the wait carries no deadline. It cannot: the shell closes
the module's protocol input on every path it can end an invocation by,
and that closure ends the wait. Bounding the read on the context instead
would leave a framed stream at an unknown position, so the only safe
response to a cancelled read is the one already in place.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/acceptance/failclosed_test.go (1)

269-421: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the repeated fault-test setup.

Nearly every test in this range repeats the same three lines (buildShell, isolatedStateRoot, installFaultyModule) before calling tryFailingShell. A small helper (e.g. runWithFault(t, fault string, args ...string) failedRun) would remove this duplication across the ~8 call sites without changing behavior.

♻️ Proposed helper
+// runWithFault installs the fault fixture selecting the given fault and runs
+// the shell against it, returning the run for assertion.
+func runWithFault(t *testing.T, fault string, args ...string) failedRun {
+	t.Helper()
+	shell := buildShell(t)
+	stateRoot := isolatedStateRoot(t)
+	installFaultyModule(t, stateRoot, fault)
+	return tryFailingShell(t, shell, stateRoot, args...)
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/acceptance/failclosed_test.go` around lines 269 - 421, Extract the
repeated buildShell, isolatedStateRoot, installFaultyModule, and tryFailingShell
setup into a helper such as runWithFault(t, fault string, args ...string)
failedRun. Update the affected fault-based tests, including the table-driven
cases, to use this helper while preserving each test’s arguments, assertions,
and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/acceptance/failclosed_test.go`:
- Around line 269-421: Extract the repeated buildShell, isolatedStateRoot,
installFaultyModule, and tryFailingShell setup into a helper such as
runWithFault(t, fault string, args ...string) failedRun. Update the affected
fault-based tests, including the table-driven cases, to use this helper while
preserving each test’s arguments, assertions, and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e8052cb0-d1e9-4195-9491-27cf00da18f9

📥 Commits

Reviewing files that changed from the base of the PR and between 5d6e0de and 1de08f4.

📒 Files selected for processing (4)
  • sdk/module/broker.go
  • test/acceptance/broker_test.go
  • test/acceptance/failclosed_test.go
  • test/acceptance/status_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • sdk/module/broker.go
  • test/acceptance/status_test.go
  • test/acceptance/broker_test.go

@kanushka
kanushka merged commit ae5242d into wso2:main Jul 28, 2026
5 checks passed
@kanushka
kanushka deleted the feat/brokered-reference-status branch August 5, 2026 06:38
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.

Complete brokered authenticated reference status

2 participants