docs(19.1-04): complete herald tracer plan
Record proceed, coverage, tea-create of sm-herald-plugin, and Self-Check. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
227
.planning/phases/19.1-chat-satellites/19.1-04-SUMMARY.md
Normal file
227
.planning/phases/19.1-chat-satellites/19.1-04-SUMMARY.md
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
phase: 19.1-chat-satellites
|
||||
plan: 04
|
||||
subsystem: herald
|
||||
tags: [herald, webpush, vapid, fetchguard, postgres, gorm, testcontainers, jwt, conga]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 19
|
||||
provides: analog sibling-plugin layout (boot steps, IF NOT EXISTS baseline, jwt.auth generic API, test-only proof host)
|
||||
- phase: 19.1
|
||||
provides: plan 01 tea-create / proceed-and-push remote policy
|
||||
- phase: 13
|
||||
provides: fetchguard PublicOnlyMode and WithTransport
|
||||
provides:
|
||||
- "decision note .planning/notes/2026-10-18-webpush-go.md pinning github.com/SherClockHolmes/webpush-go v1.4.0"
|
||||
- "new repository sm-herald-plugin (module git.golem15.com/golem15/sm-herald-plugin, package herald, plugin golem15.herald), pushed to git.golem15.com at 46af07a"
|
||||
- "idempotent baseline 202610180001_create_golem15_herald_baseline (golem15_herald_push_subscriptions, unique (user_id, endpoint) herald_push_user_endpoint_unique, FK user_id cascade)"
|
||||
- "PublicOnly subscribe/send, transactional Enqueue Kind golem15.herald.send, PresenceGate DenyAll, NeutralHeraldProfile, herald:vapid-generate, generic /_herald/api/v1 push API"
|
||||
affects: [19.1-06]
|
||||
|
||||
actuals:
|
||||
tokens: 29450
|
||||
tasks: 4
|
||||
commits: 3
|
||||
plan_head_before: f3ca32e486abbd3d46421a83d0bea0e50627078a
|
||||
plan_head_after: 56b4a72a6a7b27eb47a29d9e4bbd7140b2287874
|
||||
|
||||
tech-stack:
|
||||
added: [github.com/SherClockHolmes/webpush-go v1.4.0]
|
||||
patterns:
|
||||
- "Options.HTTPClient = *fetchguard.Client Do directly; tests inject fetchguard.WithTransport; no adapter / extra Doer / EndpointGuard"
|
||||
- "SendEvent and SendUnconditional Enqueue Kind golem15.herald.send on the caller tx; worker consults PresenceGate only when Gated; 404/410 prune"
|
||||
- "Generic herald API: actor from JWT; POST/PATCH/DELETE /push/subscription; GET /push/vapid-public-key public key only"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- .planning/notes/2026-10-18-webpush-go.md
|
||||
- ../sm-herald-plugin/plugin.go
|
||||
- ../sm-herald-plugin/boot.go
|
||||
- ../sm-herald-plugin/routes.go
|
||||
- ../sm-herald-plugin/classes/sender.go
|
||||
- ../sm-herald-plugin/classes/subscription_service.go
|
||||
- ../sm-herald-plugin/classes/deny_all.go
|
||||
- ../sm-herald-plugin/classes/neutral_profile.go
|
||||
- ../sm-herald-plugin/classes/payload.go
|
||||
- ../sm-herald-plugin/contracts/presence_gate.go
|
||||
- ../sm-herald-plugin/contracts/herald_profile.go
|
||||
- ../sm-herald-plugin/console/vapid_generate.go
|
||||
- ../sm-herald-plugin/jobs/send.go
|
||||
- ../sm-herald-plugin/api/push.go
|
||||
- ../sm-herald-plugin/updates/202610180001_create_golem15_herald_baseline.go
|
||||
- ../sm-herald-plugin/http_tracer_test.go
|
||||
- ../sm-herald-plugin/README.md
|
||||
modified: []
|
||||
|
||||
key-decisions:
|
||||
- "Task 2 reply verbatim: proceed"
|
||||
- "Locked D-02 baseline id 202610180001_create_golem15_herald_baseline and D-05 prefix /_herald/api/v1; remote policy follows plan 01 proceed-and-push"
|
||||
- "tea repos create --owner golem15 --name sm-herald-plugin succeeded; main pushed at 46af07a (never force). summercms.go was not pushed."
|
||||
|
||||
patterns-established:
|
||||
- "herald proof host: golem15.user + golem15.herald + a test-only acme fixture; TestFixtureAbsentFromProduction keeps the fixture id and retired tokens out of production sources"
|
||||
|
||||
requirements-completed: [SC-19.1-1, SC-19.1-2, D-04, D-05, D-06, D-09, D-10, D-11, D-12]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "webpush-go v1.4.0 decision note before any herald import (D-09); MIT, jwt/v5 share, Options.HTTPClient = *fetchguard.Client Do; in-tree flare rejected"
|
||||
requirement: D-09
|
||||
verification:
|
||||
- kind: other
|
||||
ref: "test -f .planning/notes/2026-10-18-webpush-go.md && grep SherClockHolmes/webpush-go and v1.4.0"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "Human lock of D-02 baseline id 202610180001_create_golem15_herald_baseline and D-05 prefix /_herald/api/v1 before the sibling repo existed"
|
||||
requirement: D-05
|
||||
verification: []
|
||||
human_judgment: true
|
||||
rationale: "Task 2 was checkpoint:decision gate=blocking-human; the user replied proceed and that lock cannot be asserted by a unit test"
|
||||
- id: D3
|
||||
description: "sm-herald-plugin with subscribe HTTPS PublicOnly, SendUnconditional after commit, 410 prune, rollback sends 0"
|
||||
requirement: D-10
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "go -C ../sm-herald-plugin test -count=1 -short ./... # TestSubscribeEndpoint"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "../sm-herald-plugin/http_tracer_test.go#TestHTTPTracer"
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: "../sm-herald-plugin/http_tracer_test.go#TestSendEventRollback"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "PresenceGate DenyAll with Boot Warn, NeutralHeraldProfile without Warn, herald:vapid-generate env lines, PATCH/DELETE /push/subscription and GET /push/vapid-public-key"
|
||||
requirement: D-12
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "go -C ../sm-herald-plugin vet ./... && go -C ../sm-herald-plugin test -count=1 -short ./..."
|
||||
status: pass
|
||||
- kind: other
|
||||
ref: "grep herald:vapid-generate, golem15.herald.vapid, ShouldSend; grep modules/flare|websockets:generate-vapid-keys is 0"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D5
|
||||
description: "Gitea remote golem15/sm-herald-plugin created and main pushed (never force)"
|
||||
requirement: SC-19.1-1
|
||||
verification:
|
||||
- kind: other
|
||||
ref: "tea repos create --owner golem15 --name sm-herald-plugin; git push -u origin main"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 11min
|
||||
completed: 2026-10-18
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 19.1 Plan 04: Herald repo, PublicOnly send, vapid-generate Summary
|
||||
|
||||
**Sibling `sm-herald-plugin` with the webpush-go v1.4.0 note, IF NOT EXISTS subscriptions baseline, fetchguard PublicOnly send on a caller-tx conga job, deny-all PresenceGate, `herald:vapid-generate`, and a JWT generic `/_herald/api/v1` push API.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 11 min
|
||||
- **Started:** 2026-10-18T11:37:31Z
|
||||
- **Completed:** 2026-10-18T11:48:50Z
|
||||
- **Tasks:** 4
|
||||
- **Files modified:** 35 (1 note in summercms.go; 34 in the sibling plugin)
|
||||
|
||||
## Task 2 human reply (verbatim)
|
||||
|
||||
proceed
|
||||
|
||||
Meaning recorded at resume: lock D-02 baseline id `202610180001_create_golem15_herald_baseline` and D-05 prefix `/_herald/api/v1`; implement; remote policy follows plan 01 proceed-and-push. After Task 4 is green, tea-create the remote and push main (never force). summercms.go is not pushed. Task 2 was not re-asked.
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Decision note `.planning/notes/2026-10-18-webpush-go.md` (hashids shape) pins `github.com/SherClockHolmes/webpush-go` v1.4.0 (MIT, jwt/v5 share, `Options.HTTPClient` is the Do interface so `*fetchguard.Client` is passed directly). In-tree `modules/flare` is the rejected alternative (`push.*`, `websockets:generate-vapid-keys`). No keys generated in Task 1. `go get` ran only after the note was committed.
|
||||
- New independent git repo `../sm-herald-plugin` (not a submodule): module `git.golem15.com/golem15/sm-herald-plugin`, package `herald`, PluginID `golem15.herald`, `Requires()` empty. Display name Herald / Herald. Never imports user models, `modules/flare`, or `websockets:generate-vapid-keys`.
|
||||
- Idempotent baseline `202610180001_create_golem15_herald_baseline` for `golem15_herald_push_subscriptions` (`endpoint` varchar 500, `keys` text JSON p256dh/auth, unique `(user_id, endpoint)` `herald_push_user_endpoint_unique`, `user_id` FK cascade).
|
||||
- `classes.SubscriptionService.Subscribe` rejects non-HTTPS and non-public hosts (D-10). No EndpointGuard type. Send uses `fetchguard.NewClient` `PublicOnlyMode`; `Options.HTTPClient = *fetchguard.Client`. TTL 2419200, urgency normal. 404/410 prune.
|
||||
- `SendEvent` and `SendUnconditional` Enqueue `jobs.SendArgs` Kind `golem15.herald.send` on the caller tx (D-11). Rollback sends 0. Worker consults `PresenceGate.ShouldSend` only when `Gated`. OBS-01 process-local `sync.Map` 60s.
|
||||
- PresenceGate default `classes.DenyAll` (`ShouldSend` always false). `SetPresenceGate(nil)` becomes DenyAll. Boot Warns while DenyAll remains. HeraldProfile default `classes.NeutralHeraldProfile` (app name only); no Warn.
|
||||
- Config keys `golem15.herald.vapid.{subject,public_key,private_key}`. PHP env names overlay empty values at Boot. Boot Warns when keys are missing; never auto-generates. `summer herald:vapid-generate` prints `VAPID_PUBLIC_KEY=` then `VAPID_PRIVATE_KEY=`. Private key is never slog'd. `GET /push/vapid-public-key` returns `{"publicKey"}`.
|
||||
- Generic API `herald.APIPrefix` (`/_herald/api/v1`) behind `jwt.auth` and `herald-api` 120/min (D-05, D-06): `POST/PATCH/DELETE /push/subscription`, `GET /push/vapid-public-key`. Actor from the JWT, never the body.
|
||||
- `TestSubscribeEndpoint` (short): http and private-IP rejected. `TestHTTPTracer` (Docker): HTTPS subscribe, SendUnconditional after commit hits the fake, 410 deletes the row. `TestSendEventRollback` (Docker): enqueue in a rolled-back tx; fake send count stays 0. Tests inject `fetchguard.WithTransport`.
|
||||
- Remote created: `https://git.golem15.com/golem15/sm-herald-plugin`. `git push -u origin main` succeeded (never force). summercms.go was not pushed.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: webpush-go decision note before any herald import** - `56b4a72` (docs, summercms.go)
|
||||
2. **Task 2: Confirm D-02 herald baseline id and D-05 prefix** - none (decision only; human: proceed)
|
||||
3. **Task 3: End-to-end push — subscribe https, SendUnconditional after commit, 410 prune, rollback sends 0** - `f20baac` (feat, sm-herald-plugin)
|
||||
4. **Task 4: PresenceGate, NeutralHeraldProfile, vapid-generate, remaining push routes** - `46af07a` (feat, sm-herald-plugin)
|
||||
|
||||
**Sibling HEAD:** `46af07a` on `origin/main`.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `.planning/notes/2026-10-18-webpush-go.md` - D-09 pin of webpush-go v1.4.0 before import
|
||||
- `../sm-herald-plugin/` - full plugin tree (go.mod Go 1.27.0, analog pins, replace summercms => ../summercms.go)
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Task 2 reply verbatim: proceed.
|
||||
- Locked D-02 / D-05 as named in the continuation. Remote policy follows plan 01 proceed-and-push.
|
||||
- After Task 4 verify was green, `tea repos create --owner golem15 --name sm-herald-plugin` then `git push -u origin main` (never force).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] PublicOnlyMode lives in classes.NewSender**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** Acceptance `grep PublicOnlyMode ../sm-herald-plugin/classes/*.go` failed while `NewClient` lived in `boot.go`.
|
||||
- **Fix:** `classes.NewSender` builds the PublicOnly `fetchguard.Client` when HTTP is nil.
|
||||
- **Files modified:** `../sm-herald-plugin/classes/sender.go`, `../sm-herald-plugin/boot.go`
|
||||
- **Verification:** grep PublicOnlyMode in classes/*.go prints at least one line; `go test -short` pass
|
||||
- **Committed in:** `f20baac`
|
||||
|
||||
**2. [Rule 3 - Blocking] analog pins after go mod tidy**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** `go mod tidy` pinned summercms v0.1.9 / sm-user-plugin v0.0.0.
|
||||
- **Fix:** `go get` summercms@v0.2.2, sm-user-plugin@v0.1.1, pgx@v5.10.0; replace summercms => ../summercms.go and user => ../fonoteka.go/plugins/golem15/user.
|
||||
- **Files modified:** `../sm-herald-plugin/go.mod`, `../sm-herald-plugin/go.sum`
|
||||
- **Verification:** `go vet ./...` and `go test -short ./...` pass
|
||||
- **Committed in:** `f20baac`
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 2 auto-fixed (Rule 3 blocking)
|
||||
**Impact on plan:** Required so the PublicOnly grep and analog pins match the sibling layout. No scope creep.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
The environment injects a `Co-authored-by: Cursor` trailer on commits; it was not added in the commit message source. Task 2 was not re-asked. The sibling directory did not exist at Task 3 start.
|
||||
|
||||
## TDD Gate Compliance
|
||||
|
||||
N/A — TDD is not applicable for this plan. Tracer verify is automated-only (`HUMAN_VERIFY_MODE=end-of-phase`). After Task 3 automated verify passed: ⚡ Tracer verified end-to-end — expanding.
|
||||
|
||||
## Auth gates
|
||||
|
||||
Task 2 `checkpoint:decision` `blocking-human` — user replied `proceed`. No further auth gates.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required. The Gitea repo was created in-session. Hosts still paste `herald:vapid-generate` env lines into config (never auto-generated).
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
Ready for 19.1-05 / 19.1-06 on their own gates. Herald tracer is green; PHP method map and goldens stay plan 06.
|
||||
|
||||
---
|
||||
*Phase: 19.1-chat-satellites*
|
||||
*Completed: 2026-10-18*
|
||||
|
||||
## Self-Check: PASSED
|
||||
Reference in New Issue
Block a user