Commit Graph

41 Commits

Author SHA1 Message Date
Jakub Zych
a13a1214cb test(bouncer,cabana): cover admin refresh subject checks without a database
Quick 260927-q23 (CR-01), unit coverage that runs under -short.

- bouncer: TestRefreshAudienceForSubject covers active, pre/post cutoff,
  missing, nil provider, non-numeric sub, provider error, and proves
  token-only refusals never reach the provider
- bouncer: TestJWTGuardTokensValidAfter pins the unchanged "User not found"
  message and errors.Is(err, ErrSubjectRejected)
- cabana: TestPhase10Coverage subtest pins cookie expiry on subject
  refusals, no cookies over Bearer or on a provider error, and the
  post-cutoff success path
2026-09-27 19:00:12 +02:00
Jakub Zych
be4a923f36 fix(cabana): enforce tokens_valid_after and is_activated on admin refresh
Fixes review finding CR-01 (quick 260927-q23): POST {prefix}/api/v1/auth/refresh
minted a new token without loading the admin, so a session kept alive by the
SPA's refresh-on-401 survived admin:reset-password, deactivation and deletion.
This broke Phase 9 truth T-09-04.

- bouncer: extract the JWT guard's subject lookup into subjectPrincipal and
  issuedBeforeCutoff (same order and messages), add ErrSubjectRejected
- bouncer: add RefreshAudienceFor, which runs the guard's subject checks
  after the token-only checks and before minting; Refresh and
  RefreshAudience are unchanged (nil hook)
- cabana: share one lazyBackendUsers provider between the backend guard and
  refresh; a cookie refresh refused for its subject expires summer_admin
- test: TestAdminRefreshRevocation (Postgres, real admin:reset-password)
2026-09-27 18:58:15 +02:00
Jakub Zych
ef448da1cc test(10-05): cover every Phase 10 Go change with branch-level tests
- bouncer TestPhase10CookieGuard: cookie read without Bearer, Bearer wins,
  empty cookie, frontend audience and blacklisted jti rejected
- boardwalk TestPhase10BoardwalkServing: HEAD, query strings, encoded
  traversal, index by name, nested prefix, MIME fallback, constructor errors
- cabana TestPhase10Coverage: mounted unsafe routes vs the CSRF walk, option
  and filter edges, read-only labels, relation message defaults, bundle
  fallback locale, cookie refresh of an expired token in the refresh window
- phrasebook override precedence, new locale, Bundle merge order, Forms shapes
- surf prefix collision for deeper paths and the default /backend prefix
- swagger2openapi TestUnionRewrite and converter branch tests
- framework tests no longer name the application (acme fixtures instead)
2026-09-27 18:05:28 +02:00
Jakub Zych
f4e97cccad feat(10-04): search, link and unlink related records through the relation manager
- relation-manager registered in the field registry; renders only on an
  existing record, never on create, and is never part of the save body
- RelationManager: relation schema label and comment, debounced search,
  selectable linked list (DataTable relation variant), toolbar buttons in
  declared order, confirmed unlink with plural messages and toasts
- RelationPickerModal: Reka Dialog (aria-modal, focus trap, Esc) over the
  candidates endpoint five per page, selection kept across pages, Dodaj (N)
  POSTs link, focus returns to the opener
- admin OpenAPI documents search, sort, dir, page and per_page on the linked
  and candidate relation routes so the SPA sends them typed
- neutral acme.demo.widgets members fixtures and relation smoke tests
2026-09-27 17:18:14 +02:00
Jakub Zych
126ca5b8ed feat(10-03): open, edit and save a record with toast and 422 feedback
- The SPA loads the backend::lang bundle before /auth/me, sets the
  document language from meta.locale and renders plural messages with
  Intl.PluralRules; interpolate mirrors phrasebook for :name/:Name/:NAME
- Create and record routes; mapWinterUrl maps recordUrl and redirects
  onto the controller's list, create and record routes only
- List rows open their record; FormView loads the form schema and the
  record, shows context-allowed fields in the span grid, saves values
  keyed by field name and toasts the resolved saved message
- A 422 puts each message under its field (aria-invalid,
  aria-describedby), shows the plural banner, focuses the first invalid
  field in schema order and clears a field's error on change
- The D-05 registry maps text, textarea, number and dropdown; any other
  type renders the unsupported-field box with the type in DM Mono
- The admin OpenAPI document declares the write request bodies
  (AdminRecord, AdminIDsRequest) and the list filter query as a
  deepObject, so the typed client can send them
- New backend::lang form.load_failed key; boardwalk/dist rebuilt
2026-09-27 16:43:47 +02:00
Jakub Zych
9f296b0484 feat(10-02): filter choices and a fully typed admin API proven on the wire
- pact.FilterOptions on the model serves a scope filter's choices; a scope
  filter whose model lacks it fails activation (D-27)
- GET /{vendor}/{plugin}/{controller}/filters/{scope}/options answers a
  declared scope filter behind the controller permission with localized
  {value, label} choices, 404 otherwise
- Every admin route documents a typed success schema, and protected routes
  document 401, 403 and 404 (422 on writes); SuccessEnvelope is gone and
  logout writes a typed AdminLogoutData
- jsonScalar and fieldContext decode their served shapes
- TestPhase10OpenAPIConformance calls every inventoried route through the
  assembled router on PostgreSQL and decodes each body into its documented
  type with unknown fields disallowed, checking admin.json's schema ref
- The SPA aliases every new schema type; Tailwind no longer scans the
  generated API files, so API changes do not churn boardwalk/dist
2026-09-27 16:27:42 +02:00
Jakub Zych
c87148a34f feat(10-02): backend strings, controller messages and declarative toolbar
- phrasebook ships the backend::lang admin strings (pl, en) with CLDR
  plural maps, loads them as namespace backend, applies
  pact.HasLangOverrides trees (lang/<locale>/<namespace>/<group>.yaml)
  after every namespace, and fails activation when a backend key cannot
  convert to plural forms
- Translator.Forms, Bundle, Resolved and Has serve keys as CLDR form maps
- Public GET /lang returns every backend::lang key for the request
  locale over the fallback locale, Cache-Control no-cache
- config_list, config_form and config_relation accept a strict messages
  block; omitted keys take framework defaults, schemas serve every message
  as CLDR forms, and activation fails on a missing phrase key
- toolbar.buttons is an ordered [create, delete] list; the Winter string
  form, duplicates, unknown actions and delete without showCheckboxes fail
  at boot, and create is dropped when the controller has no form
- Form schema serves the raw Winter redirects; scaffold emits the list
  syntax; form and relation schema routes are typed in the admin OpenAPI
2026-09-27 16:16:32 +02:00
Jakub Zych
fe04dbc89e feat(10-02): relation field options and relation saves with labels
- FieldRelationContract/FieldRelationProvider bind every type: relation
  field to a belongsTo foreign key or a belongsToMany pivot; activation
  fails naming plugin, controller and field on a missing or broken contract
- GET /{vendor}/{plugin}/{controller}/fields/{field}/options serves
  {value, label} pages scoped by pact.RelationExtendOptionsQuery, behind
  the controller permission; read-only and non-relation fields are 404
- Saves apply present relation keys after the Before hook: ids are
  revalidated through the same scoped query (422 and full rollback
  otherwise), belongsTo sets the foreign key, belongsToMany replaces pivot
  rows in submitted order with the order column set to the index
- Show, create and update return relation values in data and meta.labels
- A belongsTo on a protected fill key is read-only (D-26)
- One six-segment GET pattern dispatches relation lists and field options,
  which ServeMux cannot register side by side
- Admin OpenAPI documents the options route and RecordEnvelope
2026-09-27 16:00:52 +02:00
Jakub Zych
dafdb18234 feat(10-01): harden the admin cookie session and prefix boot guards
- refresh and logout read the Bearer header first, then the summer_admin
  cookie; a cookie refresh rotates the cookie without a token in the body and
  logout always expires the cookie
- backend.cookie_secure (default true) may drop Secure outside production only
- activation rejects controller vendor segments api, assets, login, settings
- BuildRouter rejects non-cabana routes at or under the admin prefix
- SPA single-flights refresh on 401, replays once, and refreshes proactively
  at 80 percent of expires_in; dist rebuilt
- scripts/check-admin-dist.sh rebuilds the SPA and fails on dist drift
- tests: TestPhase10CookieAuth, TestPhase10CSRF, TestPhase10Prefix,
  TestPhase10AdminPrefixCollision, boardwalk serving and header tests
2026-09-27 15:34:19 +02:00
Jakub Zych
5f9353841b feat(10-01): serve the embedded admin SPA at backend.uri with cookie login
- backend.uri prefix (default /backend) mounts the admin API at {prefix}/api/v1
  and the embedded SPA shell at {prefix} with an api/ JSON 404 fallback
- cookie transport: an X-Requested-With login sets the HttpOnly summer_admin
  cookie and returns no token; the backend guard reads the cookie after Bearer
- CSRF wrapper refuses cookie-only POST/PUT/DELETE without X-Requested-With
- boardwalk package embeds boardwalk/dist, rewrites index.html once per prefix
  and sets cache and security headers
- framework admin OpenAPI pipeline (swag, swagger2openapi, openapi-typescript)
  with prefix-relative paths and typed envelopes for the tracer routes
- admin/ Vite SPA: login, plugin rail, section panel and read-only list
  through the openapi-fetch client typed by the generated schema
2026-09-27 15:21:48 +02:00
Jakub Zych
4392550e23 feat(09-12): add the phase 9 acceptance gate
- Document every D-09 admin route for the OpenAPI contract.
- Fail the gate on skipped tests, zero-test runs, and a missing admin path.
2026-09-27 03:02:00 +02:00
Jakub Zych
30bfd2d8d5 test(09-12): add the phase 9 security matrix
- Guard isolation covers audience, secret, refresh, blacklist, and reset cutoff.
- The mounted admin route table must carry the backend guard.
- Fresh PostgreSQL migrate and rollback keep framework and plugin histories apart.
2026-09-27 03:01:54 +02:00
Jakub Zych
10ca7a02e3 feat(09-11): add permissioned admin metadata and settings 2026-09-26 23:06:22 +02:00
Jakub Zych
12081c18d1 feat(09-10): add typed relation manager 2026-09-26 21:33:43 +02:00
Jakub Zych
0caa86ec0b feat(09-06): keep relation required flags off unbound columns
- Schema JSON still reports required relations for the client
- Save validation only adds required for scalar writable fields
2026-09-24 20:19:29 +02:00
Jakub Zych
c63146accb feat(09-06): serve admin form schemas and match Winter relations
- GET schema/form localizes the compiled form after the permission check
- relation: genre resolves to the exported Go field without changing the YAML key
2026-09-24 20:10:48 +02:00
Jakub Zych
50754808f6 feat(09-05): make bulk delete atomic, ordered, and retry-safe
- Reject an empty selection and dedupe ids before locking rows in pk order
- Return deleted 0 when every requested row is already gone, without hooks
- Roll back mixed, hook, and cancelled batches so no partial delete commits
2026-09-24 19:45:15 +02:00
Jakub Zych
247c3235f6 test(09-05): add failing tests for deterministic bulk delete
- Empty selections are 422 and duplicates run once in primary-key order
- A completed retry and an all-absent selection delete nothing and skip hooks
- Mixed, hook, cancel, and concurrent requests keep the batch atomic
2026-09-24 19:43:15 +02:00
Jakub Zych
e3e1c2546e feat(09-05): enforce scoped record lifecycle on admin routes
- Mount show, create, update, and delete behind the backend permission check
- Run controller and model hooks once per operation and roll back on failure
- Treat missing and out-of-scope records the same, including idempotent delete
2026-09-24 19:39:08 +02:00
Jakub Zych
94814d980b test(09-05): add failing tests for scoped record lifecycle
- Record routes must enforce permission before ids or bodies and return D-10 envelopes
- Create, update, and delete run Before and After hooks once inside the transaction
- Out-of-scope and missing records are indistinguishable, and hook failure rolls back
2026-09-24 19:38:23 +02:00
Jakub Zych
578bdc8d5d feat(09-05): project writable fields through Fill and Validate
- Bind schema fields to model columns at activation and drop protected keys
- Create and update Fill, run BeforeValidate, then Validate before persistence
- Missing Fill or Validate capability and provider errors fail closed
2026-09-24 19:28:36 +02:00
Jakub Zych
1e14da7bb5 test(09-05): add failing tests for writable fill and validate
- Create and update must Fill then Validate and return D-10 422 field errors
- Schema bindings exclude protected, cased, nested, and unknown keys
- Missing Fill or Validate capability fails closed with controller context
2026-09-24 19:25:18 +02:00
Jakub Zych
3efdcc1c59 fix(09-04): keep relation columns out of the default sort set
- A relation column is not sortable unless columns.yaml says so
- An explicit sortable relation orders the joined select column, then the primary key
2026-09-24 19:04:42 +02:00
Jakub Zych
6a57f63e81 feat(09-04): execute allowlisted deterministic list queries
- Search, sort, filters, and pagination use compiled selectors and bound values
- Equal sort keys break ties on the primary key so adjacent pages do not overlap
- Unknown identifiers return validation_failed before SQL
2026-09-24 19:03:44 +02:00
Jakub Zych
7f451c3572 test(09-04): add failing tests for deterministic list queries
- Search, sort, filters, and adjacent pages must return the D-11 envelope
- Empty and single results keep an array and the requested page size
- Unknown identifiers and injected values fail closed before unsafe SQL
2026-09-24 18:58:56 +02:00
Jakub Zych
d3a93073c9 feat(09-04): compile switch, date-range, and scope filters
- Filters keep typed values and only registered scope names
- Raw conditions and arbitrary methods fail activation
- Request localization copies labels and leaves identifiers unchanged
2026-09-24 18:53:52 +02:00
Jakub Zych
cb832eb70c test(09-04): add failing tests for typed list filters
- Switch, date-range, and model-scope filters must keep typed values
- Option labels localize without changing identifiers or cached keys
- Raw conditions, unknown scopes, and arbitrary methods fail activation
2026-09-24 18:50:17 +02:00
Jakub Zych
aab4398ce4 feat(09-04): compile ordered Winter list schemas
- Typed columns, actions, default sort, search term, and page sizes
- Omitted sortable defaults to true and empty collections marshal as arrays
- Unknown keys, bad defaults, and path escape fail before routes are served
2026-09-24 18:46:53 +02:00
Jakub Zych
fd591ed3a7 test(09-04): add failing tests for ordered list schemas
- Columns, actions, default sort, and page sizes must compile to typed JSON
- Empty and single declarations stay arrays and keep source order
- Unsupported keys, actions, defaults, and path escape fail activation
2026-09-24 18:43:26 +02:00
Jakub Zych
af8e58a038 test(09-03): add failing tests for Winter admin controller scaffolding
- make:admin-controller must emit config_form and config_list beside model fields and columns
- A pre-existing model asset must fail before any controller file is written
2026-09-24 18:24:06 +02:00
Jakub Zych
da8828ef38 feat(09-03): localize form schemas and resolve dropdown options
- Cached schemas stay source-key IR and each response carries its own meta.locale
- YAML option maps keep declaration order and scalar type; method options call DropdownOptions and fail boot without a provider
2026-09-24 18:21:59 +02:00
Jakub Zych
ad1b76085a test(09-03): add failing tests for form locale and dropdown options
- The same cached schema must localize pl and en independently, including Accept-Language parent fallback and raw keys
- YAML option maps keep order and scalar type, and a method provider is required at boot
2026-09-24 18:20:21 +02:00
Jakub Zych
4c814e5f63 feat(09-03): compile ordered Winter form schemas
- Strict config_form and fields documents keep source order and JSON scalar types
- Unknown keys, partials, path escape, and a mismatched modelClass fail activation with plugin context
- List-only controllers still activate when config_form.yaml is absent
2026-09-24 18:17:45 +02:00
Jakub Zych
ea3f0705f4 test(09-03): add failing test for strict form schema compilation
- All locked field kinds, empty and single documents, and source order fail closed
- Unknown keys, types, duplicates, path escape, modelClass, partials, and missing assets must name the plugin, controller, and file
2026-09-24 18:12:53 +02:00
Jakub Zych
5f218977e4 feat(09-02): add admin create and reset-password commands
- Commands hash with bcrypt, validate role codes, and revoke tokens on reset
- Generated app main appends cabana.RuntimeCommands exactly once
2026-09-24 17:56:34 +02:00
Jakub Zych
d27f442c49 test(09-02): add failing tests for admin create and reset commands
- admin:create and admin:reset-password are not registered yet
- Generated app main does not append cabana runtime commands
2026-09-24 17:54:42 +02:00
Jakub Zych
9740c3dcdb feat(09-02): implement backend JWT lifecycle, throttle, and auth logs
- Refresh, logout, and me use a separate PostgreSQL jti blacklist and safe profile
- Login stamps last_login only after a successful check and throttles repeated attempts
2026-09-24 17:50:41 +02:00
Jakub Zych
0953308e26 test(09-02): add failing tests for the backend auth lifecycle
- Login does not stamp last_login and logout, refresh, and me are unmounted
- Repeated logins are not throttled and auth events are not logged
2026-09-24 17:47:15 +02:00
Jakub Zych
06a7292dea feat(09-02): implement exact backend identity migrations
- Add the admin jti table, reset cutoff, and Winter indexes without AutoMigrate
- Reapply the developer and publisher seed idempotently and allow repeated role codes
2026-09-24 17:40:34 +02:00
Jakub Zych
8c1de83f01 test(09-01): add failing audience crossover and authorization-order tests
- Same-secret backend token is still accepted by the frontend guard
- Permission denial must not invoke the schema or database callback
- Admin error bodies must not echo secrets or raw tokens
2026-09-24 17:19:57 +02:00
Jakub Zych
dfa00f7e3a feat(09-01): implement separate-admin genre list tracer
- Audience-aware mint, verify, refresh, and backend guard keep frontend tokens compatible
- Cabana mounts raw admin login, list schema, and record list behind admin.jwt.secret
- Framework migration seeds Winter backend users and developer/publisher roles
2026-09-24 17:17:19 +02:00