docs(14-05): complete sm-feedback-plugin plan
This commit is contained in:
@@ -0,0 +1,304 @@
|
|||||||
|
---
|
||||||
|
phase: 14-domain-jobs-and-external-integrations
|
||||||
|
plan: 05
|
||||||
|
subsystem: feedback
|
||||||
|
tags: [sm-feedback-plugin, golem15.feedback, feedback-widget, g15office, cors, preflight, river, fetchguard, cabana, parity, upstream-sidecar, submodule]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 14-domain-jobs-and-external-integrations
|
||||||
|
provides: "14-01 fetchguard.Client (AllowHostsMode, PostMultipart, WithTransport), tide upstream sidecars and summer parity:upstream; 14-04 the shared-plugin submodule workflow"
|
||||||
|
- phase: 07-user-plugin
|
||||||
|
provides: "golem15.user jwt.auth, GetApiArrayEvent and the user payload's feedback_widget_hidden default"
|
||||||
|
provides:
|
||||||
|
- "New shared core plugin repo git.golem15.com/golem15/sm-feedback-plugin (master pushed, b4b4135..af5d77c), mounted in fonoteka.go at plugins/golem15/feedback: plugin golem15.feedback, package feedback, requires golem15.user"
|
||||||
|
- "Widget API: GET {key}/config and POST {key}/submit behind a constant-time key check and a fail-closed Origin allow-list, buckets feedback-config (60/min) and feedback-submit (10/min); JWT PUT me/hidden; feedback_widget_hidden on every golem15.user payload"
|
||||||
|
- "Tables golem15_feedback_submissions and golem15_feedback_user_preferences (PHP's columns, strings as TEXT) and the typed singleton golem15_feedback_settings with a generated widget key"
|
||||||
|
- "G15Office River job (kind golem15.feedback.sync_g15office, queue feedback, 3 attempts) on a fetchguard client limited to the configured host, replaying PHP's recorded exchange"
|
||||||
|
- "Settings screen (code feedback), read-only submissions list, golem15.feedback.manage_settings, embed.js byte for byte at /plugins/golem15/feedback/assets/js/embed.js, feedback:import-settings"
|
||||||
|
- "surf (framework): OPTIONS on a CORS path answers with Laravel HandleCors headers (summercms.go b5d20b3)"
|
||||||
|
- "Parity: feedback section (feedbackRouteIDs, 4 routes, 28 PHP-recorded cases), job sidecar fixtures/jobs/feedback-g15office; corpus 175 routes, 172 ported, 3 pending (D-09)"
|
||||||
|
affects: [14-06 unit tests and gate (API-08 marked there), 15 cutover (feedback:import-settings, SUMMER_GOLEM15__FEEDBACK__G15_OFFICE__*)]
|
||||||
|
|
||||||
|
actuals:
|
||||||
|
tokens: 98000 # chars/4 over the added lines of all three repos (embed.js included)
|
||||||
|
tasks: 3
|
||||||
|
commits: 1 # MEASURED in summercms.go: plan_head_before..plan_head_after (code only)
|
||||||
|
app_repo_commits: 7 # MEASURED in fonoteka.go: app_repo_head_before..app_repo_head_after
|
||||||
|
plugin_repo_commits: 3 # MEASURED in sm-feedback-plugin: the whole new repo (pushed)
|
||||||
|
plan_head_before: 7eb0174612855067a156d34baf4dcb31af408b05
|
||||||
|
plan_head_after: b5d20b3bfdd712429e76e17cbb22e85db427e051
|
||||||
|
app_repo_head_before: eea0b1607ef9b5d14b3a5186bf322e6eb45a4b9b
|
||||||
|
app_repo_head_after: 9fc38d9
|
||||||
|
plugin_repo_head_after: af5d77c
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "A shared plugin's G15Office/vendor sidecar is replayed from the plugin's own testdata copy; the application keeps the recorded original under fixtures/jobs with a rows golden and a test that the two copies are byte-identical"
|
||||||
|
- "Origin-gated routes record the Origin and Access-Control-Request-* request headers (capture-rules keep them)"
|
||||||
|
- "A spec's query string goes in query:, never in path:"
|
||||||
|
- "Recordings of routes that queue jobs run PHP with QUEUE_CONNECTION=database; the job itself is recorded separately under sync with the upstream proxy"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- ../fonoteka.go/plugins/golem15/feedback/ (whole repo: plugin.go, routes.go, admin.go, jobs.go, assets.go, assets/js/embed.js, README.md, config, lang/{en,pl}, models, updates, controllers/{api,submissions}, classes, console, internal/pgtest)
|
||||||
|
- ../fonoteka.go/parity/feedback_seed_test.go
|
||||||
|
- ../fonoteka.go/parity/fixtures/routes/{GET,POST,OPTIONS,PUT}___feedback_api_v1_* (28 fixtures)
|
||||||
|
- ../fonoteka.go/parity/fixtures/jobs/feedback-g15office.{upstream.yaml,rows.json}
|
||||||
|
- ../fonoteka.go/parity/upstream/scripts/g15office-task.yaml
|
||||||
|
modified:
|
||||||
|
- modules/surf/cors.go
|
||||||
|
- modules/surf/cors_coverage_test.go
|
||||||
|
- modules/surf/README.md
|
||||||
|
- docs/services/routing.md
|
||||||
|
- ../fonoteka.go/{.gitmodules,go.work,go.mod,summer.yaml,plugins.gen.go,app/app.go,README.md}
|
||||||
|
- ../fonoteka.go/parity/{check_corpus.go,routes.snapshot,manifest.yaml,parity_test.go,parity_contract_test.go,schema_diff_test.go,migrate_test.go,rollback_isolation_full_test.go,fonoteka_seed_test.go,fonoteka_reset.php,php_parity.sh,capture-rules.yaml,README.md}
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "surf answers OPTIONS on CORS paths with Laravel HandleCors headers: Cache-Control no-cache, private always; on a preflight the requested method (upper-cased) and headers echoed when * allows any, Vary, and PHP's default Content-Type text/html; charset=UTF-8"
|
||||||
|
- "The settings screen's code is feedback, not WinterCMS's settings: cabana keys settings screens by code across all plugins"
|
||||||
|
- "The G15Office job is dispatched through conga with a summer_jobs row labelled Golem15\\Feedback\\Jobs\\SyncFeedbackToG15Office and completed on success (PHP used a plain Laravel queue job)"
|
||||||
|
- "The G15Office client runs in fetchguard AllowHostsMode for the base URL's host, so it refuses an http base URL (PHP's curl would send over http)"
|
||||||
|
- "Submission string columns are TEXT: PHP's user_agent column is 255 characters while its validation allows 500"
|
||||||
|
- "The submissions list is read-only and has no form, so models/submission/fields.yaml was not created"
|
||||||
|
- "API-08 is not marked complete: 14-06 also declares it (as 14-04 did with INTG-02)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "feedback, feedback-closed and feedback-off are additive seed extras on both sides; without one the reset removes the feedback settings and the parity users' preferences"
|
||||||
|
|
||||||
|
requirements-completed: []
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "sm-feedback-plugin exists, is pushed and mounted; golem15.feedback loads after golem15.user and before golem15.fonoteka"
|
||||||
|
requirement: API-08
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "plugins/golem15/feedback#TestFeedbackPluginBoot; parity#TestMigrate* (activateAppPlugins order)"
|
||||||
|
status: pass
|
||||||
|
- kind: other
|
||||||
|
ref: "git -C plugins/golem15/feedback status --porcelain --branch → '## master...origin/master'; .gitmodules, go.mod, summer.yaml, plugins.gen.go"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "Widget config with the constant-time key check and the fail-closed Origin gate"
|
||||||
|
requirement: API-08
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "plugins/golem15/feedback#TestFeedbackConfig, TestKeyMatches, TestURLHost, TestAllowedOriginHosts"
|
||||||
|
status: pass
|
||||||
|
- kind: parity
|
||||||
|
ref: "parity#TestParityCorpus/GET___feedback_api_v1_{key}_config_feedback (10 cases)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D3
|
||||||
|
description: "Submit with PHP's validation, the image guard, the public screenshot and the job in one transaction; me/hidden; feedback_widget_hidden; the preflight"
|
||||||
|
requirement: API-08
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "plugins/golem15/feedback#TestFeedbackSubmit, TestMeHidden, TestFeedbackApiArrayHook, TestFeedbackOptionsPreflight; classes#TestImageGuardCopy; summercms modules/surf#TestCORSOptionsMatchesLaravel"
|
||||||
|
status: pass
|
||||||
|
- kind: parity
|
||||||
|
ref: "parity#TestParityCorpus/POST___feedback_api_v1_{key}_submit_feedback (9), OPTIONS___feedback_api_v1_{any}_feedback (2), PUT___feedback_api_v1_me_hidden_jwt (7, two with the user fetch); coverage 172 ported, 3 pending"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D4
|
||||||
|
description: "G15Office job matches PHP's recorded requests; failures stored and retried; nothing sent unconfigured"
|
||||||
|
requirement: API-08
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "plugins/golem15/feedback/classes#TestSyncG15Office (php-recorded sidecar + 9 failure paths), TestTaskText"
|
||||||
|
status: pass
|
||||||
|
- kind: other
|
||||||
|
ref: "parity#TestFeedbackJobSidecarMatchesPlugin, TestUpstreamSidecarsAreReplayed"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D5
|
||||||
|
description: "Settings screen, submissions list, embed.js, settings import"
|
||||||
|
requirement: API-08
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "plugins/golem15/feedback#TestFeedbackAdminSchemas, TestEmbedJSServed, TestFeedbackImportSettings"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
|
||||||
|
duration: 100min
|
||||||
|
completed: 2026-10-04
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 14 Plan 05: sm-feedback-plugin Summary
|
||||||
|
|
||||||
|
**A new shared plugin, sm-feedback-plugin, ports the WinterCMS feedback widget to Go. The embedded widget loads its configuration, a visitor sends a report with a screenshot, and a signed-in collector hides the widget, all as PHP answers, behind the same key and Origin gate. Each report reaches G15Office as a task with its screenshot, in the requests PHP sent. Operators configure the widget and read the reports in the admin.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** about 100 min
|
||||||
|
- **Started:** 2026-10-03T21:44:47Z
|
||||||
|
- **Completed:** 2026-10-04T00:25Z
|
||||||
|
- **Tasks:** 3
|
||||||
|
- **Commits:** 3 in sm-feedback-plugin (pushed), 7 in fonoteka.go, 1 code commit in summercms.go (plus this summary)
|
||||||
|
- **Files:** 37 in the plugin (+5430), 52 in fonoteka.go (+1178/-27), 4 in summercms.go (+106/-2)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- **sm-feedback-plugin (D-12).** The empty remote was cloned at `plugins/golem15/feedback`, built, committed and pushed, then registered with `git submodule add`. `master` is at `af5d77c` and in sync with origin. fonoteka.go loads `golem15.feedback` after `golem15.user` and before `golem15.fonoteka`: `go.work`, `go.mod` (require and replace), `summer.yaml`, the regenerated `plugins.gen.go` and `app.PluginIDs` all name it.
|
||||||
|
- **Widget config (D-13).** `GET /_feedback/api/v1/{key}/config` checks the key in constant time and answers 404 `Not found` when the widget is disabled, has no key or gets the wrong one. The Origin gate lets a request with no Origin through. A request whose Origin host, lower-cased, is not on the list gets 403 `Origin not allowed`, and an empty list refuses every Origin. `models.URLHost` follows PHP's `parse_url`, including `localhost:3000` and IPv6 brackets. `?lang=en` selects the English labels.
|
||||||
|
- **Submit (D-13).**
|
||||||
|
- `POST {key}/submit` validates with PHP's rules and returns the 422 envelope with Laravel's messages in rule order.
|
||||||
|
- The screenshot must pass a copy of ImageContentGuard, or the answer is 422 `The file is not a valid image.`.
|
||||||
|
- One transaction stores the submission, queues the G15Office job and stores the public screenshot (attachOne `screenshot`). A failed write keeps no row and no job. The answer is 202 `{"success":true}`.
|
||||||
|
- **Hiding the widget.** JWT `PUT me/hidden` accepts Laravel booleans, writes only the caller's row and answers `{"hidden":bool}`. A `GetApiArrayEvent` listener puts `feedback_widget_hidden` on every golem15.user payload. The recorded me/hidden cases include a following `GET /_user/api/v1/fetch`, so the payload key is asserted against PHP.
|
||||||
|
- **Preflight.** surf's CORS layer answers `OPTIONS` on `_feedback/api/*`, and no route is registered for it. surf now sends the headers PHP sends (see Deviation 1).
|
||||||
|
- **G15Office (D-13, D-15).**
|
||||||
|
- `classes.SyncG15Office` builds PHP's title. `Str::limit` is ported with `mb_strwidth` widths, using a table generated from PHP. The whitespace collapse is PCRE `\s`, so it includes VT.
|
||||||
|
- It builds PHP's Markdown description, the type map and the settings' status and priority.
|
||||||
|
- It posts the task JSON, then the screenshot as the multipart part `file`. Both requests go through `fetchguard` in AllowHostsMode for the base URL's host, with `Accept: application/json`, the Bearer token and no User-Agent.
|
||||||
|
- Success stores `sent` and the task id. Failure stores `failed` and PHP's message, and returns the error so River retries, up to `conga.MaxAttempts(3)`. When unconfigured, nothing is sent.
|
||||||
|
- PHP ran the job under the sync queue through the recording proxy. Its exchange is replayed offline. A one-word mutation of the title limit failed the replay.
|
||||||
|
- **Admin and assets.**
|
||||||
|
- The settings screen (code `feedback`) uses cabana's field types only. The colors are text fields, the widget key is read-only through `attributes`, and the defaults are `initSettingsData`. A row saved without a key gets a new one.
|
||||||
|
- The submissions list is read-only: no form and no buttons, every column plain text.
|
||||||
|
- The plugin registers the `golem15.feedback.manage_settings` permission (developer role) and a Feedback menu.
|
||||||
|
- `embed.js` is served byte for byte, with an ETag.
|
||||||
|
- `feedback:import-settings` copies the WinterCMS row once, the widget key included. Without `--force` it refuses settings that were already changed.
|
||||||
|
- **Parity.**
|
||||||
|
- 28 cases were recorded against PHP: 10 config, 9 submit, 2 OPTIONS and 7 me/hidden.
|
||||||
|
- The submit cases include each limit at the boundary and one past it (5000-character multibyte message, page_url, user_agent, console_log), the rule-order 422, a non-image, a spoofed JPEG, a foreign Origin and a wrong key.
|
||||||
|
- The config cases include an uppercase Origin, `localhost:3000`, `null`, an empty list, a disabled widget and an unconfigured widget.
|
||||||
|
- The corpus has 175 routes: 172 ported and passing, and 3 pending (D-09). `check_corpus --require-recorded --check-secrets` is green.
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
sm-feedback-plugin (`git@git.golem15.com:golem15/sm-feedback-plugin.git`, master, pushed):
|
||||||
|
1. `b4b4135` feat: golem15.feedback plugin with the widget config route (Task 1)
|
||||||
|
2. `92e1b4e` feat: feedback submissions with screenshots, the hide-widget preference and its user payload key (Task 2)
|
||||||
|
3. `af5d77c` feat: G15Office job, widget settings and submissions admin, embed.js and the settings importer (Task 3)
|
||||||
|
|
||||||
|
fonoteka.go (not pushed):
|
||||||
|
1. `a29c809` chore(14-05): mount sm-feedback-plugin at plugins/golem15/feedback (`.gitmodules` + gitlink only)
|
||||||
|
2. `a845178` feat(14-05): the application loads golem15.feedback after golem15.user (Task 1)
|
||||||
|
3. `2028880` feat(14-05): the embedded widget loads its configuration from golem15.feedback exactly as from PHP (Task 1)
|
||||||
|
4. `e497e94` chore(14-05): bump sm-feedback-plugin (gitlink only, Task 2)
|
||||||
|
5. `8d5d451` feat(14-05): a visitor submits feedback, a collector hides the widget and the user payload reports it, as in PHP (Task 2)
|
||||||
|
6. `86afa59` chore(14-05): bump sm-feedback-plugin (gitlink only, Task 3)
|
||||||
|
7. `9fc38d9` feat(14-05): the team receives each submission as a G15Office task with its screenshot, as PHP sent it (Task 3)
|
||||||
|
|
||||||
|
summercms.go:
|
||||||
|
- `b5d20b3` fix(14-05): surf answers OPTIONS on CORS paths with Laravel's HandleCors headers (README and routing docs updated; TestDocsTree and docs:build --check pass)
|
||||||
|
|
||||||
|
Tracer gate: after Task 1 the `<verify>` was re-run end to end (vet, the named tests with `-race`, the corpus, check_corpus, submodule status) and passed. The expansion tasks then went ahead.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed issues
|
||||||
|
|
||||||
|
**1. [Rule 1 - Bug, framework] surf's OPTIONS answer did not match PHP**
|
||||||
|
- **Found during:** Task 1 recording
|
||||||
|
- **Issue:** PHP's preflight answers 204 with `Access-Control-Allow-Methods: POST` and `Access-Control-Allow-Headers: content-type` (php-cors echoes the request when `*` allows any), `Cache-Control: no-cache, private` and `Content-Type: text/html; charset=UTF-8`. A plain OPTIONS answers 204 with the Cache-Control. surf sent `*` for both Allow headers and no Cache-Control or Content-Type, so the plan's assumption that "the recorded preflight cases must replay green" failed.
|
||||||
|
- **Fix:** `surf.writeOptions` sends what HandleCors sends. A configured method or header list is still sent as configured. Added `TestCORSOptionsMatchesLaravel` and updated the surf README and `docs/services/routing.md`.
|
||||||
|
- **Commit:** b5d20b3 (summercms.go)
|
||||||
|
|
||||||
|
**2. [Rule 3 - Blocking] The Origin gate could not be replayed**
|
||||||
|
- **Issue:** `capture-rules.yaml` keeps only Authorization, Content-Type, Accept and Accept-Language, so a recorded Origin would be dropped and a 403 case would replay as 200.
|
||||||
|
- **Fix:** The rules also keep `Origin`, `Access-Control-Request-Method` and `Access-Control-Request-Headers`. Existing fixtures are unaffected.
|
||||||
|
- **Commit:** 2028880
|
||||||
|
|
||||||
|
**3. [Rule 2 - Privacy] The PHP checkout's `.env` could point the job at a real G15Office**
|
||||||
|
- **Fix:** `php_parity.sh` exports `G15_OFFICE_BASE_URL`, `G15_OFFICE_TOKEN` and `G15_OFFICE_PROJECT` empty unless the caller sets them. The job recording sets them to `https://office.parity.test` and a fake token, behind the scripted proxy.
|
||||||
|
- **Commit:** 2028880
|
||||||
|
|
||||||
|
**4. [Rule 1 - Bug] The fonoteka seed left alice's user flags to chance**
|
||||||
|
- **Issue:** The me/hidden user-payload step expected `has_self_set_password: true`. The shared replay database left the flag at whatever an earlier user-api replay had set.
|
||||||
|
- **Fix:** `seedFonotekaCase` now sets alice's `has_self_set_password`, `must_change_password`, `marketing_consent` and `is_onboarded` to PHP's values. No other fonoteka-seeded fixture shows them.
|
||||||
|
- **Commit:** 8d5d451
|
||||||
|
|
||||||
|
**5. [Rule 3 - Blocking] `TestParityContract` rejected the new ported routes**
|
||||||
|
- **Fix:** Added the `feedbackSeedRoutes` set.
|
||||||
|
- **Commit:** 2028880
|
||||||
|
|
||||||
|
### Plan details refined
|
||||||
|
|
||||||
|
**6.** All 28 route cases were recorded in one session and committed with Task 1. The submit, OPTIONS and me/hidden routes stayed `pending` until Task 2 flipped them. The routes were recorded with `QUEUE_CONNECTION=database`: under `sync`, PHP runs the job inside the request, and an unconfigured G15Office would make the submit a 500.
|
||||||
|
|
||||||
|
**7.** The settings screen's code is `feedback`, not WinterCMS's `settings`. cabana keys settings screens by code across all plugins, so a generic code would clash with the next shared plugin.
|
||||||
|
|
||||||
|
**8.** The job goes through `conga.Dispatch`, with a `summer_jobs` row labelled with the PHP job class, and is completed on success. Inside the one transaction it is queued before the screenshot is written, so `TestFeedbackSubmit/rolled-back-write-queues-nothing` proves that a failed screenshot write leaves no job behind. The order is not visible outside the transaction.
|
||||||
|
|
||||||
|
**9.** No `models/submission/fields.yaml` was created. The list is read-only and has no form, which `TestFeedbackAdminSchemas` asserts.
|
||||||
|
|
||||||
|
**10.** `TestImageGuardCopy` compares the copy with the framework guard (`attach.IsAllowedImage`) and with PHP's verdicts on the recorded fixtures. A plugin in its own repository cannot import the application's fonoteka guard.
|
||||||
|
|
||||||
|
**11.** The plugin replays the G15Office sidecar from its own `classes/testdata` copy. The application keeps the recorded original under `fixtures/jobs`, with a rows golden (required by `TestUpstreamSidecarsAreReplayed`), and `TestFeedbackJobSidecarMatchesPlugin` keeps the two copies byte-identical.
|
||||||
|
|
||||||
|
**12.** The G15Office client uses AllowHostsMode, so it accepts an https base URL only. PHP's curl would also post over http.
|
||||||
|
|
||||||
|
**13.** All string columns are TEXT. PHP's `user_agent` is `string(255)`, but its own rule allows 500 characters, which Postgres would refuse.
|
||||||
|
|
||||||
|
**14.** The submit route has nine cases, because the Go replay keeps one app per route and the bucket allows ten. `TestFeedbackSubmit/throttle` asserts that the 11th post gets a 429.
|
||||||
|
|
||||||
|
**15.** Extra tests beyond the plan: `TestKeyMatches`, `TestURLHost` (PHP `parse_url` vectors), `TestAllowedOriginHosts` and `TestTaskText` (Str::limit and buildTitle/buildDescription against PHP outputs).
|
||||||
|
|
||||||
|
**16.** The fonoteka.go README layout table now lists the golem and feedback submodules. The golem row was missing since 14-04.
|
||||||
|
|
||||||
|
**17.** Commits land on `master` in all three repos, as in the earlier Phase 14 plans (`branching_strategy: none`, sequential executor). gsd-tools reports `master` as protected and `allow_default_branch_commits` is unset; the orchestrator's sequential-execution instructions were followed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 17. Five are auto-fixes and twelve are refinements. **Impact:** the only framework change is surf's OPTIONS answer. It is documented and limited to OPTIONS requests on CORS paths. There is no scope creep.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
- `/tmp` hit its disk quota. The parity root moved to `~/.cache/summercms-parity/p1405`, and Go and PHP temporary files to `~/.cache/gotest-tmp`. Older caches in `/tmp` left by earlier sessions were not touched.
|
||||||
|
- A spec with `?lang=en` inside `path:` was sent with an escaped path and got Winter's 404 page. It was re-recorded with `query:`.
|
||||||
|
- `artisan serve` keeps a child `php -S` alive after the parent is killed. The child was stopped by its PID.
|
||||||
|
- The submit recordings wrote two screenshots into the PHP checkout's `storage/app/uploads/public/6ac`, and that directory was removed. `git status` in the PHP checkout shows only the user's own pre-existing files.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- sm-feedback-plugin: `go vet ./...` and `go test ./...` pass both standalone (`GOWORK=off`) and in the workspace. Every named test passes with `-race`.
|
||||||
|
- fonoteka.go: `go vet ./...` passes, `go test ./... -count=1` passes (the app and parity packages), and `go test ./... -short` passes. The fonoteka, golem and user plugin suites pass with `-count=1`.
|
||||||
|
- Parity: `TestParityCorpus` reports `recorded 175/175 passing 172 failing 0 unrecorded 0 pending 3`. `TestCheckCorpusPortedCaseStatus`, `TestParityContract`, `TestSchemaMatchesPHPSnapshot`, `TestUpstreamSidecarsAreReplayed` and `TestFeedbackJobSidecarMatchesPlugin` pass. `check_corpus --routes … --require-recorded --check-secrets` passes with 175/175.
|
||||||
|
- summercms.go: `go vet ./...` and `go test ./... -count=1` pass. `TestDocsTree` and `summer docs:build --check` pass.
|
||||||
|
- Acceptance greps for all three tasks pass:
|
||||||
|
- `.gitmodules` has 1 feedback path, and the plugin's `origin/master` resolves.
|
||||||
|
- `feedbackRouteIDs` appears 3 times, and `routes.snapshot` has 4 `_feedback/api/v1` lines.
|
||||||
|
- `expectedRouteCount` and `expectedPHPRoutes` are 175, and `expectedPortedRoutes` is 172.
|
||||||
|
- `ConstantTimeCompare` appears once, and `feedback_widget_hidden` is in plugin.go.
|
||||||
|
- `routes.go` has no `Options(`.
|
||||||
|
- The embed.js sha256 values are identical, and fields.yaml has no `colorpicker`.
|
||||||
|
- The job sidecar's Authorization is `Bearer {{secret:g15office-token}}`, and jobs.go has `conga.MaxAttempts(3)`.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None. The new surfaces are in the plan's threat register:
|
||||||
|
- The public widget routes (T-14-30, T-14-31), with the constant-time key check, the fail-closed Origin list and the per-IP buckets.
|
||||||
|
- The screenshot (T-14-32), with the rules and the sniff-and-decode guard.
|
||||||
|
- The G15Office token and forwarding (T-14-33, T-14-34). The token comes from config only, the sidecars mask it, the client is limited to the configured host, and the job logs only the submission id.
|
||||||
|
- me/hidden (T-14-35), which writes only the principal's row.
|
||||||
|
- The admin list (T-14-36), which renders text columns only.
|
||||||
|
|
||||||
|
The `embed.js` route serves an embedded static file and reads no input. No key, token or DSN literal was pushed: the diff was scanned before each push, and the tests use only short fake secrets.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None for development. At cutover (Phase 15) the operator:
|
||||||
|
- sets `SUMMER_GOLEM15__FEEDBACK__G15_OFFICE__BASE_URL` (https), `__TOKEN` and `__PROJECT`;
|
||||||
|
- runs `feedback:import-settings` once, so embed snippets keep their widget key;
|
||||||
|
- runs a queue worker that serves the `feedback` queue.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- 14-06 picks up the unit-test gate. It should add `./plugins/golem15/feedback/...` to `check-phase14.sh`'s plugin list and mark API-08 (left Pending here because 14-06 also declares it).
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 14-domain-jobs-and-external-integrations*
|
||||||
|
*Completed: 2026-10-04*
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
Every listed file exists; commits b5d20b3 (summercms.go), a29c809, a845178, 2028880, e497e94, 8d5d451, 86afa59 and 9fc38d9 (fonoteka.go) and b4b4135, 92e1b4e and af5d77c (sm-feedback-plugin, on origin/master) are present. The final `go test ./... -count=1` in fonoteka.go passed after the last commit.
|
||||||
Reference in New Issue
Block a user