Spec 026 — Hosts + agent tokens scaffolding#79
Merged
Conversation
Lay the data + auth foundation for Phase 6 (Docker Host Agent MVP). - Migrations: hosts, agent_tokens, containers, host_metric_snapshots, container_metric_snapshots. - Models, factories, HostStatus + HostConnectionType enums. - Docker domain actions: Create/Update/Archive Host, Issue/Rotate/Revoke AgentToken. Plaintext token returned once via AgentTokenIssueResult and flashed to the session — never persisted, never logged. - HostPolicy + AgentTokenPolicy mirror WebsitePolicy (project-owner gates). - Routes + Inertia controllers under /monitoring/hosts/* (sibling to /monitoring/websites/*). - Vue pages Index / Create / Edit / Show + AgentTokenPanel component surfaces the one-time plaintext via Inertia flash. - Tests: 20 new feature tests covering CRUD, policy gating, token issuance / rotation / revocation, and mismatched host/token pairs. Telemetry ingestion lands in spec 027; metric rendering in 028.
- ArchiveHostAction is now idempotent: re-archive leaves archived_at pinned to the original timestamp instead of overwriting. - Replace ad-hoc mb_substr name truncation with StoreAgentTokenRequest form-request validation (name nullable, max:80) so overlong labels surface a real validation error. - Add rotate-mismatch 404 test (mirrors the existing destroy-mismatch case) and assert no token state mutation on either side. - Add 4 sibling-isolation feature tests covering show/edit/update/ destroy (the policy is unit-tested, but a controller-level regression would slip past those — these lock the contract end-to-end). - Add archive-idempotency feature test asserting archived_at is pinned across a second destroy call. - Update spec Files-touched list to reflect the lib/hostStyles.ts + StoreAgentTokenRequest additions and the dropped HostStatusBadge.vue. Tests: 27 → 30 in this file family, full suite 434 green. Pint clean, build green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #78
Spec:
specs/phase-6-docker-hosts/026-hosts-and-agent-tokens.mdSummary
hosts,agent_tokens,containers,host_metric_snapshots,container_metric_snapshots), models, factories, andHostStatus/HostConnectionTypeenums.Create/Update/Archive Host,Issue/Rotate/Revoke AgentToken) plus a smallAgentTokenIssueResultDTO. Plaintext bearer tokens are generated byIssueAgentTokenAction, returned exactly once via the DTO, flashed to the session for one-shot reveal in the UI, and never persisted, never logged. The DB only ever seessha256(plaintext).HostPolicy+AgentTokenPolicy(project-owner gates mirroringWebsitePolicy), Inertia controllers + routes under/monitoring/hosts/*, and the matching Vue pages (Index / Create / Edit / Show) plus anAgentTokenPanelcomponent that handles the one-shot plaintext reveal + copy + rotate / revoke confirmations.Test plan
migrate:fresh+migrate:rollback --step=5).php artisan tinker.flash.agentTokenPlaintext); subsequent navigation drops it.Hoststab with an empty state (existing phase-pending placeholder; spec 028 wires the real list).vendor/bin/pint --dirtyclean.php artisan test— 434 passing (+30 vs main).npm run buildsucceeds.Self-review notes
Self-review pass via
superpowers:code-reviewerflagged four should-fix items, all addressed in commit f4ee30a:ArchiveHostActionis now idempotent onarchived_at— re-archive leaves the original timestamp pinned (with a feature test).StoreAgentTokenRequestform request (max:80) instead of being silentlymb_substr'd in the controller.rotatemismatched-pair 404 test to mirror the existingdestroycase (both endpoints shareensureBelongs).show/edit/update/destroy. The policy is unit-tested, but a controller-level regression (e.g., dropping$this->authorize(...)) would have slipped past those — these lock the contract end-to-end.Spec deviation:
resources/js/lib/hostStyles.ts(mirrorswebsiteStyles.ts) replaced the plannedHostStatusBadge.vue. The spec's "Files touched" list was updated to reflect the actual approach.Two nice-to-haves deferred (in scope for a future hardening pass, not blocking):
sessions.payload(encrypted withAPP_KEY) for the redirect target's read. Known characteristic of any Laravelredirect()->with(...). A future polish could return the plaintext viauseForm'sonSuccesscallback to skip the session round-trip.window.history.state, so a back-nav after issuing a token will re-render the plaintext from history cache. Not a cross-user leak (same browser session, same authenticated user), but a tighter UX couldrouter.replace({ preserveState: false })after the first paint.