Addressed both request_changes findings on the human-gated userinput intake (impl-znqy7sftgvuxqmqsuji32okd).
Foreign strongrefs now honour the version they pin #
packages/ui/src/lib/userinput.ts gained one documented policy, split by what a pin buys:
- Authority needs an exact
uri#cidpin.moderators()honours an owner'sapp.userinput.membergrant only against the board version this tab fetched;effectiveStatus()andliveDiscussion()honour a moderator status and an author edit only against the discussion version fetched. A superseded pin falls back — to the owner alone (the board URI's DID, which cannot be rewritten), toopen, to the original text. The status case is the one with teeth: aplannedset against text its author has since replaced would otherwise be carried onto the new text, which is exactly how a member gets walked into importing something else. - Presentation still resolves by URI. A discussion's
spaceref grants nothing — it names the board it was posted to, and every discussion older than the owner's last board edit legitimately pins a superseded version, so an exact match there would empty the section on a rename with no safety gain.goalsByOriginstays URI-keyed for the same reason. This is a deliberate deviation from the finding's "board or discussion" wording; the rest of the finding is implemented as written. - Dropped pins are reported, not swallowed.
discoverUserinputreturnsstaleGrantsand each row carriesstale; the Userinput page states both, because "no moderator has looked at this" and "a moderator looked at an earlier draft" are different sentences.
New pure tests cover the dropped grant (owner-only fallback, unauthorized status ignored), the dropped status and edit, and the stale counters.
Permutation coverage for the new record type and index rules #
goldenScenario() now carries setFeedbackSource and origin-bearing goals, so the 200-permutation whole-index deepEqual covers them:
- four
setFeedbackSourcerecords — one board toggled off→on (enabled, and the later record is the one the view names), one toggled on→off (associated, absent from the view), a non-admin's, and one naming a project this space does not hold (both ignored as unresolved); - two goals imported from one discussion at two different CIDs — two members importing the same feedback — proving
goalsByOrigingroups by URI so a feedback edit cannot un-badge an import.
Assertions live in a new feedback sources and imported goals block in materializer.test.mjs, including that a project with no source lists none and a goal nobody imported is in no group.
Also updated docs/adr-userinput-intake.md with the pin policy and added the missing src/lib/userinput.ts row to packages/ui/README.md's module map.
Checks #
pnpm lint (typecheck + type-strip + generated-file check), pnpm test (all packages; ui 565 passed), and pnpm codegen with a clean git diff — all pass. No packages/daemon changes.
Remaining risks #
An owner who rewrites their board record in place silently loses every moderator grant until they reissue them; the section says so and degrades to owner-only authority rather than failing open. Likewise a discussion rewritten in place (rather than revised via app.userinput.edit) reads as open until its status is re-set. Both are fail-closed by choice. The earlier operational risks stand: a Constellation or foreign PDS outage still hides feedback, reported separately from an empty board, and two members can still import the same discussion twice.