Commit Graph

551 Commits

Author SHA1 Message Date
Jakub Zych
5382947ef8 fix(11-06): send Cache-Control: no-cache, private on the jwt.auth 401
The recorded PHP 401 for a missing bearer (realtime token no-bearer
case) carries Laravel's default Cache-Control header; the Go guard's
401 omitted it, so the replay failed on header.Cache-Control.
2026-09-30 13:31:55 +02:00
Jakub Zych
9ecbf74a22 feat(11-06): add the tide fake Centrifugo recorder, broadcast goldens and parity:broadcasts
- CentrifugoRecorder records publish/broadcast requests (method, path,
  whether the API key matched, JSON body) and binds loopback only
- BroadcastGolden load/write, NormalizePublications (timestamps, actor,
  captured ids only) and DiffPublications (structural, key order ignored)
- RecordBroadcasts runs a flow or one step against a loopback backend
- summer parity:broadcasts wraps it; README documents format and rules
2026-09-30 13:21:23 +02:00
Jakub Zych
fb4aed176a docs(11-05): record plan 11-05 progress and decisions 2026-09-30 13:07:37 +02:00
Jakub Zych
0a1fdb5aa0 docs(11-05): complete beachcomber search sync plan 2026-09-30 13:06:46 +02:00
Jakub Zych
9543e6508c fix(11-05): sync single-statement and plain-transaction writes, type engine status errors
- pin the sync callbacks before gorm:commit_or_rollback_transaction: an
  After-only anchor is appended past the commit and lagoon's after-commit
  flush, so single-statement writes never synced
- give the gate and the document builder a clean session: Session with NewDB
  and a Context clones the write's statement, and a later WithContext queried
  through the written model's table
- typesense.StatusError carries method, path and status, never the body
- README: sync semantics, the three gates, delete on soft delete, and the
  SQL re-gate required of SearchIDs callers
2026-09-30 13:05:10 +02:00
Jakub Zych
3e1f3e6a1b feat(11-05): add the beachcomber search sync package and its Typesense engine
- Searchable, Engine, Gate and an init-time engine registry with the null engine
- GORM callbacks installed through lagoon.OnDatabase register an after-commit
  sync that reloads the row and upserts or deletes its document
- Gates run before any request: engine configured, database published, app Gate
- hand-rolled net/http Typesense engine following the Scout wire contract
- module README and root modules row
2026-09-30 12:50:54 +02:00
Jakub Zych
0d45698ad7 docs(11-03): record plan 11-03 progress and decisions 2026-09-30 12:38:42 +02:00
Jakub Zych
499177a242 docs(11-03): complete lighthouse realtime plan 2026-09-30 12:37:58 +02:00
Jakub Zych
eab2b007f5 docs(11-03): record the websockets plugin dissolution (D-16) and the RT-01 client note
- PROJECT.md: websockets is not a separate app plugin; Key Decisions row
- REQUIREMENTS.md: RT-01 names a hand-rolled Centrifugo client (D-12)
2026-09-30 12:36:16 +02:00
Jakub Zych
211c413273 feat(11-03): broadcast model writes through River jobs enqueued in the write transaction
- Broadcastable contract and Bind[T] bindings; event {action}.{alias},
  default {model, actor, timestamp, ttl} payload, delete snapshot taken
  before the row goes
- GORM callbacks installed via lagoon.OnDatabase enqueue a summer.broadcast
  job on the write's *sql.Tx inside a savepoint; failures are logged and
  never abort the write; zero-key batch writes are skipped
- WithoutBroadcasting[T] (ctx-scoped, per type) and Service.Emit for one
  summary event; the one-attempt job namespaces channels and publishes or
  broadcasts; the payload travels as a JSON string so JSONB keeps its order
- no jobs for the null driver or Centrifugo without an API key
2026-09-30 12:36:07 +02:00
Jakub Zych
79fd705680 feat(11-03): re-authorize every Centrifugo subscribe through a namespace registry
- lighthouse: Registry of namespace authorizers (Result, Allowed, Denied),
  ParseChannel, ChannelID with PHP (int)-cast semantics (PHPInt, pinned by
  a php -r table test), FormatChannels, WithClientID/ClientID
- centrifugo: ProxyHandler (constant-time X-Centrifugo-Secret, HTTP 200
  generic deny, info [] on allow, presence allow/override merge, 64 KiB
  body cap) mounted as the ServerToServer subscribe route
- README: proxy contract, registry and channel rules
2026-09-30 12:29:09 +02:00
Jakub Zych
cada7a4442 feat(11-03): add the lighthouse realtime package and its Centrifugo driver
- lighthouse: Service/From with realtime.driver selection, RegisterDriver
  registry, null/log/memory drivers, Route/Surface/Mount, users and actors
- centrifugo: HTTP API client (apikey header, 2xx success, no request
  without a key), five-generator HS256 TokenIssuer, TokenHandler with the
  WinterCMS 401/503 bodies
- module README and root modules table row
2026-09-30 12:18:11 +02:00
Jakub Zych
f1077382f5 docs(11-02): complete scheduler plan 2026-09-29 22:54:28 +02:00
Jakub Zych
2237a640d2 feat(11-02): add schedule:run as a scheduler process and a cron --once mode
- schedule:run runs a worker on the scheduled queue with every plugin's periodic jobs
- schedule:run --once runs entries due in the current app.timezone minute without River,
  warns and skips unregistered commands, and returns the first command error
- summer schedule:run delegate forwards --once
- tests for --once minute matching, forged-entry skipping (T-11-09) and ByPeriod dedupe
2026-09-29 22:34:21 +02:00
Jakub Zych
d9f939a1ea feat(11-02): run plugin schedules as River periodic jobs through bonfire.Call
- pact.HasSchedule with ScheduledCommand and Daily/DailyAt/Every cadences (no River import)
- bonfire.Call, Catalog and ErrUnknownCommand for in-process command runs
- conga Daily/Every wall-clock schedules in app.timezone, periodic jobs on every worker,
  scheduled queue (MaxAttempts 1, unique by args within the cadence period)
- scheduled worker runs only entries matching the compiled table; unregistered
  commands are skipped with a Warn log
- generated app main publishes bonfire.NewCatalog(commands); hello main regenerated
2026-09-29 19:47:51 +02:00
Jakub Zych
77b8ff177a docs(11-01): record plan 11-01 progress and decisions 2026-09-29 15:27:56 +02:00
Jakub Zych
144c54bb2c docs(11-01): complete conga job framework plan 2026-09-29 15:27:29 +02:00
Jakub Zych
6c1f94e57c feat(11-01): add lagoon.OnDatabase and after-commit transactions
- OnDatabase runs a callback once the database is published (now, or when
  lagoon.Publish runs), so GORM callbacks registered at Boot also install
  under serve, where Boot runs before the database is opened
- Transaction runs AfterCommit callbacks in order after a successful commit;
  nested calls are savepoints whose callbacks drop with them
- the lagoon:after_commit GORM callback flushes single-statement AfterCommit
  work after GORM's own commit; outside a transaction it runs immediately
2026-09-29 15:25:51 +02:00
Jakub Zych
b319e7cc61 feat(11-01): run job workers in serve and queue:work, add queue:clear
- Manager gains the apparatus JobManager surface: StartJob, UpdateJobState,
  UpdateMetadata, FailJob, CancelJob (is_canceled + STOPPED + River JobCancel),
  StopJob (STOPPED only), CheckIfCanceled and GetMetadata, all raw column
  writes so updated_at is untouched
- serve starts the in-process worker unless queue.work_in_serve is false and
  stops it on shutdown; an app without jobs gets an idle worker
- queue:work runs a foreground worker with repeatable --queue filters;
  queue:clear deletes available, scheduled and retryable jobs of one queue
- the generated main appends conga.RuntimeCommands; summer delegates
  queue:work and queue:clear; make:job scaffolds a conga.Job
2026-09-29 15:20:14 +02:00
Jakub Zych
05ba88a54a fix(11-01): restore the toolchain line that go mod tidy dropped
The scaffolder's ensureToolchain keeps 'toolchain go1.27.0' in every
module; the River tidy in the previous commit removed it from the example
app and its plugins.
2026-09-29 15:10:19 +02:00
Jakub Zych
0bc5c77097 feat(11-01): add the conga job framework on River with transactional dispatch
- Pin River v0.47.0 (riverdatabasesql, rivertype) and tidy the example modules
- lagoon.Migrate runs the summercms.conga set: River schema v7 and summer_jobs
  with the apparatus columns plus an internal river_job_id link
- conga.Manager.Dispatch writes the summer_jobs row (status IN_PROGRESS) and the
  River job on the caller's *sql.Tx; a rollback leaves neither
- conga.Job wraps typed job functions so plugins never import River
- conga.StartWorker runs one client on riverdatabasesql.NewWithPgxListener with
  a single-connection LISTEN pool; the final failed attempt sets ERROR
- TestListenPickupLatency: 30s poll, pickup under 1s; poll-only control 2s miss
2026-09-29 15:02:04 +02:00
Jakub Zych
718a35caba docs(11): create phase plan 2026-09-29 14:34:06 +02:00
Jakub Zych
d9b951fe29 docs(11): map phase patterns 2026-09-29 13:40:53 +02:00
Jakub Zych
a1ed5b3539 docs(11): reword SC-1 for River's single-client pgx listener split 2026-09-29 13:38:46 +02:00
Jakub Zych
9dabc6c5a1 docs(phase-11): add validation strategy 2026-09-29 12:32:56 +02:00
Jakub Zych
c82950c2a6 docs(11): research phase domain 2026-09-29 12:32:02 +02:00
Jakub Zych
0bbbce6d27 docs(10.1): record code review disposition 2026-09-29 10:10:45 +02:00
Jakub Zych
c0d16eccc1 docs(10.1): add code review fix report 2026-09-29 10:10:44 +02:00
Jakub Zych
719ed719b4 fix(10.1): WR-06 honour the widget field's context on the action route
widgetAction derives the form from the request (create without record_id,
update with one) and answers 404 when the field's context hides the
widget on that form, reusing contextAllows as the save path does. An
update-only action can no longer run with a nil record through a direct
POST.
2026-09-29 10:00:08 +02:00
Jakub Zych
7b72bf4be4 fix(10.1): WR-05 drop widgets the admin may not run from the form schema
formSchema now filters type: widget fields by the action's permissions,
the same D-12 filtering listSchema applies to toolbarActions, so an admin
without the action permission no longer gets a button that always
answers 403, and the action name is not revealed. The filtered fields are
a new slice, so the cached schema is never modified.
2026-09-29 09:58:27 +02:00
Jakub Zych
0bdb6ebcac fix(10.1): WR-04 judge action fill values by their JSON encoding
isJSONScalar now encodes each value and keeps it only when the encoding
is a string, number, boolean or null, so a named scalar whose MarshalJSON
writes an array or object, NaN and the infinities are dropped. writeJSON
encodes into a buffer before the status line, so an encode failure is a
logged 500 with the generic envelope instead of a 200 with a truncated
body.
2026-09-29 09:56:44 +02:00
Jakub Zych
7333f450ad fix(10.1): WR-03 walk the whole partial view model before rendering
refusedViewModel compared only the top-level type with the controller's
model. It now walks the type through pointers, slices, arrays, maps,
struct fields and the results of exported methods, and the values held
in interface-typed members, refusing the controller's model, any other
GORM model (TableName, a gorm tag, gorm.Model, gorm.DeletedAt) and
html/template's trusted content types anywhere in that structure.
2026-09-29 09:54:35 +02:00
Jakub Zych
5bbb0ada05 fix(10.1): WR-01 keep a late schema response from restyling the current view
loadControllerAssets no longer activates stylesheets. activateStyles,
called only by the router, records the controller on screen, and new
links are created disabled unless they belong to it, so a list or form
schema that resolves after its view was left adds its links disabled
instead of switching plugin CSS to the wrong controller. Rebuilt the
embedded admin dist.
2026-09-29 09:50:05 +02:00
Jakub Zych
849a9ffe3f fix(10.1): WR-02 disable plugin stylesheets on screens that are not controller views
The router now drives stylesheet activation after every confirmed
navigation: the list, create and record routes enable their controller's
links, and settings, login, not-found and a controller whose schema has
not arrived yet enable none. Rebuilt the embedded admin dist.
2026-09-29 09:47:52 +02:00
Jakub Zych
e60e69745e fix(10.1): CR-01 answer a value that does not fit its column with a 422
lagoon.Fill now returns a *lagoon.FillTypeError naming the key when a
requested value cannot be stored in its column (a fraction, exponent or
overflow for an integer field, or a value of the wrong type). The admin
save path maps it to a validation_failed 422 on that field instead of a
500 CapabilityError; genuine capability failures keep the 500.
2026-09-29 09:44:14 +02:00
Jakub Zych
74e30c9878 test(10.1): persist human verification items as UAT 2026-09-29 03:37:38 +02:00
Jakub Zych
29433f3c40 docs(10.1): record code review disposition 2026-09-29 03:29:52 +02:00
Jakub Zych
9f6b5a5706 docs(10.1): add code review report 2026-09-29 03:29:41 +02:00
Jakub Zych
613491f674 docs(10.1-04): record plan progress, decisions and ADMIN-07 in state 2026-09-29 03:15:16 +02:00
Jakub Zych
0ddf7f8f68 docs(10.1-04): complete unit tests, gate and security evidence plan 2026-09-29 03:14:47 +02:00
Jakub Zych
bbceeb957f docs(10.1-04): record the Phase 10.1 security review and validation map
- 10.1-SECURITY-REVIEW.md: T-10.1-01 to T-10.1-22 and T-10.1-SC with
  mitigation, test or gate stage, observed result and 23 removal checks
- 10.1-VALIDATION.md: every plan task mapped to its command, all green
  under check-phase10.1.sh --all; nyquist_compliant and wave 0 complete
- Phase 10 deferred item for the parity failures marked resolved
2026-09-29 03:13:18 +02:00
Jakub Zych
02df0a8a15 feat(10.1-04): add the fail-closed Phase 10.1 gate
scripts/check-phase10.1.sh: --self-test, --go, --security, --postgres,
--spa, --openapi, --dist, --hygiene, --evidence and --all.

- phase101_detect refuses failed, skipped, zero-test, non-JSON and
  build-failed runs and required tests that did not pass
- hygiene_101 refuses HTML-string parsers in admin/src, network, cookie
  or storage access in application plugin asset JS, and script, style or
  inline handler markup in application partial templates; each rule is
  proven by its own self-test plant
- --evidence requires a review row and, for every high threat, a named
  test and a removal check row
2026-09-29 03:13:08 +02:00
Jakub Zych
9aeb0e156b fix(10.1-04): drop the stale parity allow-list from the Phase 10 gate
TestMigrateSeedsCanonicalGenres and TestSchemaMatchesPHPSnapshot pass
since fonoteka.go 21c0f12, and the detector refuses an allow-listed
failure that passes, so check-phase10.sh --go failed. The gate now
allow-lists nothing.
2026-09-29 03:13:08 +02:00
Jakub Zych
11c4e5466b test(10.1-04): bring the SPA extension point under Vitest
- WidgetField: skeleton and aria-busy, the 5000 ms whenDefined timeout,
  script failure, attributes only, fill-values and locale sync, one POST
  while busy, fill-key-only patching, danger toasts, unmount
- PartialHost and partialNodes: exhaustive tag, attribute and URL
  allowlist, depth and node caps, skeleton sizes, empty, failure,
  busy refetch and ?id= only with a record
- PartialField, ListToolbar, ListView, registry, formState, FormField and
  FormView: group labels, registered toolbar actions, header refetch
  rules, form context provision and asset loading
2026-09-29 02:59:13 +02:00
Jakub Zych
6b0ac15086 fix(10.1-04): refuse percent-encoded dot segments in plugin asset URLs
The URL parser resolves %2e%2e like .., so /{base}/assets/%2e%2e/api/...
passed assetAllowed and would load from outside the asset prefix. A
segment is now a dot segment after decoding %2e, in any case.

- tests/app/pluginAssets.test.ts covers the URL check, loadScript,
  loadStyles, activateStyles and loadControllerAssets
- modules/boardwalk/dist rebuilt
2026-09-29 02:52:14 +02:00
Jakub Zych
7eed4acd87 test(10.1-04): cover the Phase 10.1 extension point Go code
- acme fixture plugin under modules/cabana/testdata/extension (gadgets
  controller, header and form partials, lookup widget, JS and CSS)
- TestPhase101FormExtensionSchema, TestPhase101PartialSchema and
  TestPhase101Toolbar: every widget, partial and toolbar boot rule
- TestPhase101PartialSanitizer: tag, attribute and URL allowlist, escaping,
  per-request trans, size/node/depth caps and the view-model guard
- TestPhase101Assets: exact-key asset hits, revalidation, SPA fall-through,
  boot path checks and ?v= schema URLs
- TestPhase101Actions (PostgreSQL): scoping, fill filter, strict body,
  action permission, error mapping, CSRF header, toolbar and partial routes
- TestPhase101BoardwalkExports: ContentType and SetSecurityHeaders
2026-09-29 02:48:12 +02:00
Jakub Zych
c3c547c394 docs(10.1-03): record plan decisions in state 2026-09-29 02:29:12 +02:00
Jakub Zych
0f6c10c4e3 docs(10.1-03): record plan progress in state and roadmap 2026-09-29 02:28:56 +02:00
Jakub Zych
29336a58ce docs(10.1-03): complete Albums extension proof plan 2026-09-29 02:28:40 +02:00
Jakub Zych
c3efbc3428 fix(10.1-03): fill numeric model fields from JSON numbers
- lagoon.Fill converts a json.Number (from a UseNumber decoder, as cabana's
  save path uses) into integer, unsigned and float fields; a fraction or an
  overflow is an error
- before this, saving a type: number field into an *int column was a 500
- README documents the conversion
2026-09-29 02:24:25 +02:00