Skip to content

🤖 fix: enforce canonical organization and owner names - #106

Merged
ThomasK33 merged 2 commits into
mainfrom
fix/aggregated-canonical-identity
Sep 22, 2026
Merged

ThomasK33 merged 2 commits into
mainfrom
fix/aggregated-canonical-identity

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Sep 22, 2026

Copy link
Copy Markdown
Member

Summary

Require canonical organization and owner names for aggregated CoderTemplate and CoderWorkspace objects. Requests whose organization or owner segment resolves to another name now fail with an actionable 400 BadRequest before backend mutation, rather than returning a different metadata.name and breaking repeated kubectl apply.

Part of #105. Import readiness is unchanged; issue #105 remains open. Alias-based manifests must migrate to canonical names. This does not restore quickstart-bundle support.

Implementation and review focus

  • Resolve names through the namespace-bound Coder client. Do not blacklist literal default or me: both remain valid when they are the resolved canonical names.
  • Preserve opaque NotFound for cross-organization workspaces. A denied organization-membership lookup also returns opaque NotFound on existing-workspace paths and create-on-update, so it does not distinguish an existing workspace from a missing one. Direct Create and ordinary authentication/outage errors keep their normal mapping.
  • Existing workspace paths reuse fetched owner information. Create adds an owner lookup before mutation; create-on-update preflights organization membership before falling back to Create.
  • Test Get/Create/Update/Delete and create-on-update, zero backend mutations on rejection, canonical literal names, LIST→GET identity, and repeated apply. Parsers, API types, dependencies, permissions, and import behavior are unchanged.
  • Document pre-creation discovery and migration. Use the verified bootstrap organization coder in the E2E fixture and CI lookup.

Validation

Current candidate: ef29d3a598e60e55330ece26c48d641f0c978309.

  • Real storage-entrypoint regressions fail before their fixes and pass afterward. The review correction covers all eight existing/missing × Get/Update/Delete/create-on-update denied-organization combinations, with no backend mutation. Direct Create retains Forbidden; a 503 lookup outage retains its ordinary mapping.
  • Exact candidate passed make verify-vendor, make test (including envtest), make build, make lint, and make docs-check, including an independent desk rerun. The docs target uses an isolated MkDocs executable because Nix is unavailable on the validation host. Actionlint, govulncheck, CI-configured Trivy filesystem scanning, Markdown lint, and spelling checks also passed.
  • Owned KIND + CNPG run: 35/35 steps and the process passed, with no retries. The binary is stamped with this SHA and bound to the running pod image. Canonical template and workspace applied twice; GET/LIST names round-tripped; eight alias requests failed with canonical guidance; backend object counts and workspace state remained unchanged.
  • Template-import waiting is harness-only, not an import-readiness fix. One transient bootstrap deployment-update conflict was logged; the control plane subsequently reached Ready. Owned cluster, image tag, kubeconfig, port-forward, and terminal session were removed.

Denied-organization behavior is verified with mock backend 403/503 responses. Live testing covers the owner-role operator principal used by the namespace-bound provider, not lower-privileged credentials. External link checking and full admitted merge-group E2E remain CI gates.

Current recorded evidence

Screenshot: terminal end state of the ef29d3a5 execution. Video: replay rendered from that run's raw terminal recording with idle gaps compressed, not another test run. The replay was rendered separately after the evidence-path incident described below.

Final state of the ef29d3a5 identity verification

ef29-stepwise-replay.mp4
Evidence provenance and historical recording

During the corrected run, a sourced harness helper reassigned its output-directory variable. This overwrote 94 files in the earlier ad1c090f E2E packet after that packet had been verified. Of the 376 original manifest entries, 282 remain byte-for-byte intact; the 94 original files are no longer retained. The original manifest was not rewritten and no original receipts were reconstructed.

The original complete terminal recording, screenshot/video, report, and handoff survive unchanged. The publicly uploaded original screenshot/video still match their verified hashes. They are historical ad1c090f evidence, not validation of the current head. That original run passed 37/38 steps; the final command had invalid multi-resource kubectl get syntax, and a corrected final-step retry passed in the same session.

For the current ef29d3a5 run, output files were relocated into its own packet. All 165 finalized artifact hashes were verified. Their source/image identity and all 35 native step exits were reconciled against the independent terminal transcript and raw recording, which were written outside the overwritten paths. The collision affected output destinations; source, image, cluster, and kubeconfig bindings remained those of the current run, and scenario inputs were regenerated before use. The actual as-run harness is retained; a separate corrected harness has not been represented as executed.

Historical ad1c090f terminal state and harness retry

Historical ad1c090f replay, with idle gaps compressed:

e2e-replay-compressed-gaps.mp4

Risks and follow-up

Existing alias manifests receive an explicit error and need the documented migration. Workspace creation now includes identity preflight reads. No broader credential-compatibility claim is made beyond the documented coverage.

This increment enforces organization and owner segments only, not the final template/workspace-name segment. Review identified two unchanged baseline limitations:

The maintainer owns both immediate API-correctness follow-ups after this increment's disposition, before quickstart support is restored. The separate import-readiness contract remains in #105. These are not claims of complete canonical-name enforcement or atomic conditional deletion.


📋 Implementation Plan

Canonical identity for aggregated API objects

Goal and boundary

Require CoderTemplate and CoderWorkspace request names to match the backend's canonical organization and owner names. Reject aliases with an actionable BadRequest instead of returning a differently named object. Canonical names must still support repeated apply.

This increment does not fix template-import readiness or make alias-based quickstart manifests converge. Keep issue #105 open for the remaining readiness contract. Exclude API/schema changes, alias persistence, SSA changes, quickstart restoration, dependency changes, and unrelated cleanup. Final template/workspace-name casing and DELETE preconditions retain baseline behavior; follow-ups #107 and #108 cover those separate contracts.

Contract

  • Keep ParseTemplateName and ParseWorkspaceName syntactic. Do not blacklist literal default or me.
  • Resolve identity through the namespace-bound Coder client. Reject a segment only when authoritative resolution returns a different canonical name. Literal default/me remains valid when resolution returns that exact name.
  • Reuse existing organization lookups and fetched workspace owner information where possible. Do not add unconditional user lookups to reads or expand permissions. Creation needs authoritative owner validation before mutation.
  • On existing workspace paths, establish requested organization membership before returning alias guidance. Prefer stable organization IDs when available. A genuine cross-organization mismatch remains opaque NotFound and must not disclose another object's canonical organization or owner.
  • Ordinary lookup/authentication failures retain their normal mapped errors; they are not alias errors. A denied organization-membership lookup on existing-workspace paths or create-on-update returns opaque NotFound, so denied-org requests cannot distinguish existing from missing workspaces. Direct Create and 401/5xx failures retain their normal mapping. Finish identity validation before upload, template/version creation, workspace build, update, or deletion.
  • Apply the contract to Get/Create/Update/Delete, including any create-on-update branch. Preserve fail-fast assertions for impossible internal states.

Steps and acceptance gates

  1. Add focused red tests using the real storage entrypoints. Verify alias rejection across verbs, canonical literal names, genuine cross-organization NotFound, alias plus wrong organization, unchanged lookup failures, and zero backend mutation calls on rejected requests. Prove canonical repeated apply and LIST-to-GET identity controls. Gate: retain native failing-test receipts on the unchanged implementation.
  2. Implement the smallest storage-layer checks and update naming/migration documentation. Discovery commands must work before a template or workspace exists. Change E2E manifest/workflow names only after verifying the actual bootstrap organization. Gate: focused tests and existing package tests pass; parsers remain compatible and no excluded behavior is added.
  3. Validate the exact candidate with make test, make build, make verify-vendor, make lint, and make docs-check. Run actionlint if workflows change, plus relevant integration/security checks. Gate: native exit receipts for the final candidate and a bounded reviewable diff; report blockers rather than enlarging scope.
  4. Dogfood on an owned isolated KIND cluster with a dedicated kubeconfig and exact candidate image. Follow the existing CNPG/control-plane setup, discover canonical names, create/apply a canonical template, explicitly wait for its existing import process, and create/apply a canonical workspace twice. Check alias rejection and unchanged backend counts/state after rejected operations. Gate: canonical identity round-trips, repeated apply succeeds, aliases fail before mutation, and only owned resources are cleaned up.

Dogfood evidence

Capture commands, exact source SHA/image binding, native exits, screenshots, and video of the actual CLI verification. Inspect the screenshot and recording for the claimed steps. Any playback copy must be labeled as replayed evidence. Harness waits document the existing import requirement and are not evidence of an import-readiness fix. Preserve a hash manifest, final handoff, and cleanup receipts outside the source tree.


Generated with xum • Model: coder:openai/gpt-6-astra • Thinking: xhigh

…objects

Coder resolves aliases such as the "default" organization, the "me" user and raw IDs on the
server side, so a CoderTemplate or CoderWorkspace requested under an alias name came back with a
different metadata.name. kubectl apply then failed on repeated applies (name precondition) and
turned a NotFound GET into an AlreadyExists POST (#105).

The storage layer now keeps name parsing syntactic and validates identity through the
namespace-bound Coder client before any mutation:

- templates: the organization segment must equal the resolved organization name on
  Get/Create/Update/Delete (create-on-update included); otherwise a BadRequest names the
  canonical form and no upload, template version or template is created.
- workspaces: existing-object paths reuse the fetched workspace's organization and owner; a
  workspace from another organization stays an opaque NotFound (membership is established by
  organization ID before any alias guidance), while alias segments are rejected with the
  canonical name. Create resolves the owner before the workspace is created.
- literal organizations or users really named "default"/"me" remain valid; lookup and
  authorization failures keep their normal mapped errors.

The E2E fixture and CI reference use the bootstrap organization's canonical name ("coder"),
and the aggregated API how-to documents the naming rules, pre-creation discovery and migration.

Template-import readiness (workspace creation while a version is importing) is out of scope
and stays tracked in #105.

Part of #105

Signed-off-by: Thomas Kosiewski <tk@coder.com>

---
_Generated with [`xum`](https://github.com/coder/xum) • Model: `coder:anthropic/claude-fable-5-1` • Thinking: `xhigh`_

Change-Id: I95bd8cda4b8e0a8274ef03a44095baec4f4663ca
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-22T11:44:56.937194Z ef29d3a Manual request
🔒 Security Review Completed 2026-09-22T11:20:10.339521Z ef29d3a Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad1c090fea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/aggregated/storage/identity.go Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: ad1c090fea

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Review finding on identity.go: when a workspace fetched by owner belongs to another
organization and the requested organization's lookup is denied (403), the aggregated API
returned Forbidden, while a missing workspace under the same denied organization returned
NotFound before any lookup. A caller without access to the organization could therefore tell
whether a cross-organization workspace exists, contrary to the documented opaque NotFound.

Map a denied membership-verification lookup to the same opaque NotFound as a genuine
cross-organization mismatch, on the existing-workspace path (Get/Update/Delete) and on the
create-on-update branch, where the fallback Create would otherwise surface the denial as
Forbidden for a missing workspace only. Direct Create keeps its ordinary mapped errors, and
401/404/5xx lookup failures keep their normal mapping.

The regression test drives Get/Update/Delete/create-on-update for an existing and a missing
workspace under a denied organization through the real storage entrypoints (all opaque
NotFound, nothing disclosed, zero backend mutations), keeps direct Create at Forbidden and an
organization lookup outage at its normal mapping.

Part of #105

Signed-off-by: Thomas Kosiewski <tk@coder.com>

---
_Generated with [`xum`](https://github.com/coder/xum) • Model: `coder:anthropic/claude-fable-5-1` • Thinking: `xhigh`_

Change-Id: I43bae79e936b55e7af2261c34d053d6c86c9ef17
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: ef29d3a598

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef29d3a598

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/aggregated/storage/template.go
@ThomasK33 ThomasK33 changed the title 🤖 fix: preserve canonical identity in aggregated objects 🤖 fix: enforce canonical organization and owner names Sep 22, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: ef29d3a598

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33

Copy link
Copy Markdown
Member Author

Maintainer readiness decision

Ready for the required merge queue at ef29d3a598e60e55330ece26c48d641f0c978309, with tracked follow-ups.

  1. Exact-head local test/build/vendor/lint/docs gates passed. Current-head normal review and security review are explicitly clean. Both review threads are resolved; the native readiness helper exits 0.
  2. The fresh independent final assessor recommends ready with tracked follow-ups. Six of six lifetime assessments are complete, none pending. Review stops because the scoped candidate is approved and the final evidence assessment is complete, not because deferred defects are fixed.
  3. Organization/owner validation is this increment's contract. Leaf-name casing (🤖 fix: enforce canonical template and workspace leaf names #107) and ignored DELETE preconditions (🤖 fix: honor aggregated API delete preconditions #108) remain unfixed, owned by the maintainer before quickstart restoration. Import readiness remains in 🤖 fix: make aggregated quickstart creation and repeated apply converge #105. Workspace casing and restricted-role consequences are not claimed as live verified.
  4. Current isolated runtime evidence passed 35/35 steps. The predecessor evidence-loss incident remains disclosed above. PR E2E is a skip path; full admitted merge-group E2E must still run and pass. Queue admission is authorization for normal automatic squash landing after protected checks, not a completed-delivery claim.

After landing I will verify the admitted run, squash tree/parent/attribution, main checks, and published image revision/digest.


Generated with xum • Model: coder:openai/gpt-6-astra • Thinking: xhigh

@ThomasK33
ThomasK33 added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 3f65df0 Sep 22, 2026
13 checks passed
@ThomasK33
ThomasK33 deleted the fix/aggregated-canonical-identity branch September 22, 2026 12:59
@ThomasK33

Copy link
Copy Markdown
Member Author

Delivery verified

  1. Merged through the required squash queue as 3f65df0ca09205ca84e112e576060aac8ad9b73b. Its tree matches reviewed source ef29d3a598e60e55330ece26c48d641f0c978309. Linear parent, main ancestry, and attribution were verified.
  2. Merge-group CI passed all required checks, including the full KIND + CNPG + Templates E2E execution, not the PR skip notice. Post-merge CI/publishing and Docs also passed.
  3. The publisher log and registry agree on immutable image index ghcr.io/coder/coder-k8s@sha256:ca89b440375887add3abedfd0b311c21ad00c8949a04269445e8ebcd67c3fcad. Both linux/amd64 and linux/arm64 manifests/configs hash correctly and carry the exact landed revision. The publishing image scan reported zero HIGH/CRITICAL vulnerabilities for its OS and Go-binary targets. No production/shared-cluster deployment was performed.
  4. Review stopped at six completed assessments, with current normal/security verdicts clean and the independent final recommendation ready with tracked follow-ups. The source validation and recorded dogfood remain in the PR description, including the disclosed predecessor-evidence loss and coverage limits.

Unfixed follow-ups remain owned: #108 (DELETE preconditions) is next, then #107 (canonical leaf-name checks and live RBAC evidence). Issue #105 stays open for import readiness. Quickstart restoration is not included in this delivery.


Generated with xum • Model: coder:openai/gpt-6-astra • Thinking: xhigh

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