Repository navigation
feat(site): flag and vouch for guestbook entries (lifecycle layer 2) - #84
Merged
Merged
Conversation
Units U1-U6 for the visitor-only Visible/Flagged/Hidden lifecycle on the systemfsoftware/xstate fork. U1 is the fork spike that R3 depends on; if it fails, A stops with no fallback to upstream.
Adds the systemfsoftware-xstate input (locked at 84e602e) to sfs-deps and the catalog, so the package resolves as a file: tarball and never from npm. The README's removal steps now take the dependency and the flake input out too. flake.nix and flake.lock are judgment surfaces.
moderateGuestbookEntry resolves the entry's decoded state on a module-private machine, applies its transition, and refuses an unhandled event with IllegalTransition. Seven property laws carry R8 and AE3's refusal.
…line infer alias
createMachine's result intersects StateMachine with identity members, and
the fork types transition's effects as ExecutableActionObjectFromLogic<this>
(StateMachine.ts:701), so the method returns any on it. provide({}) is
declared to return the plain StateMachine, which restores the types. Delete
it once the fork types that return from its own parameters (conductor
ruling A-4).
Migration 0002 adds the state column. The store reads an entry's state and writes a move only if the row still holds the state it read. The moderate RPC runs the lifecycle decision in a Sandwich cell and types all four refusals. list omits Hidden entries. The page gets Flag and Vouch buttons. The journeys cover R10, AE1 and the AE2 race. The README removal steps cover migration 0002, and the check-sfs-sources message stops naming flakes.
…code lands The doc returns at its 365f3e7 content and path, in the layer that makes area A true (ruling A-5).
The devshell builds its pnpm store with a frozen install, so entering it after the flake edit fails until the lockfile no longer lists xstate.
systemfsoftware-maker
added this pull request to stack #85
October 9, 2026 05:25
Step 8 now says the install rewrites pnpm-lock.yaml without its xstate entries and uses --no-frozen-lockfile, because a frozen install (pnpm's default under CI) refuses that rewrite. R12's ':!*.lock' covers only flake.lock, so this step is what clears the lockfile (ruling A-7, F3).
ryanleecode
removed this pull request from stack #85
October 9, 2026 09:28
ryanleecode
changed the base branch from
feat/guestbook-lifecycle-decision
to
main
October 9, 2026 09:28
main's tree equals layer 1's head 9392a3a, which this branch already contains, so the merge keeps this branch's tree unchanged.
ryanleecode
approved these changes
Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Layer 2 of the guestbook moderation lifecycle (area A, plan
docs/plans/2026-10-09-0313-feat-guestbook-moderation-lifecycle-plan.md, units U4-U6), stacked on #82. Any visitor can now flag or vouch for an entry from the page. Two flags with no vouch between them hide it, andlistnever returns a hidden entry. The two PRs land together (ruling A-6).What changes for someone using the starter
moderateRPC. It takes{ id, event }and answers with the entry's new state, or with one of four typed refusals:IllegalTransition,TransitionConflict,EntryNotFound,StoredStateInvalid. ASandwichcell runs it: it reads the stored state, runs layer 1's decision, then writes. The write isUPDATE … WHERE id = ? AND state = ?from, so a move commits only if the row still holds the state that was read. Zero changed rows is aTransitionConflict(R5). No identity, role or token is checked (R11).statefield is nowStoredEntryState: text that decodes toVisible | Flagged | Hidden. A storedBanishedbecomes the cell'sCommandRejectedand thenStoredStateInvalid, and it never reaches the machine. Layer 1's AE3 property draws this same schema.0002_add_guestbook_entry_state.sqladdsstate TEXT NOT NULL DEFAULT 'Visible'.pnpm devapplies it on start: the journeys sign and read rows through the new column.Flag <name>'s entryandVouch for <name>'s entrybuttons and showsFlaggedwhen flagged. While a moderation request is in flight, the Flag, Vouch and Sign buttons are all disabled, so a double-click can't send two Flags. Signing itself is unchanged and doesn't disable anything. Each refusal's message goes into therole=alertnotice.pnpm install --no-frozen-lockfilebefore the flake edit, and it namespnpm-lock.yamlas the file it rewrites (see U6).scripts/check-sfs-sources.ts(feat(site): decide guestbook moderation on our xstate fork (lifecycle layer 1) #82 reviewer's P3). The message no longer lists flakes by name. It says the packages resolve "from the .sfs-deps tarballs our flake inputs build". A list of names went stale with xstate and would go stale again when an adopter removes the guestbook.docs/brainstorms/2026-10-08-0340-feat-starter-state-of-the-art-plan.mdreturns at its365f3e7content and path (rulings A-5, A-6).git diff 365f3e7 HEAD -- docs/brainstorms/is empty.Evidence
U4: the cell fits the RPC.
moderateEntry.run(request)typechecks as themoderatehandler against the four-refusal error schema (pnpm typecheckexit 0). The store's two infrastructure failures become defects, the waysignandlistalready handle them.U5: journeys against
pnpm dev(pnpm journeysexit 0, 8 of 8):Flagged), Vouch (unlabelled), Flag, Flag. B gets one Flag. A fresh browser context then lists B asFlaggedand C as signed, and A not at all. Every step is its own page load and real RPC.An entry that is Visible can't take Vouch., and the entry's listed line is identical before and after.Flaggedentries each get a concurrent Vouch and Flag from two browser contexts. Every pair must end asok/conflict/Visible,conflict/ok/Hidden,illegal/ok/Hiddenorok/ok/Flagged.AE2 admission probe (plan U5; the scenario is admitted only if the probe shows the race test can fail). The
AND state = ?3guard was removed frommove, then 8 concurrent Vouch+Flag pairs ran on freshFlaggedentries, 3 times against onepnpm dev. The harness ran each scenario twice per run, so each run printed two result rows (16 pairs per run). Every run had pairs where both moves succeeded and the final state wasn'tFlagged, which is a lost update. Tallies per run, asvouch/flag/final:ok/ok/Visible3,ok/ok/Hidden6,ok/ok/Flagged6,illegal/ok/Hidden1;ok/ok/Visible10,ok/ok/Hidden3,ok/ok/Flagged2,illegal/ok/Hidden1;ok/ok/Visible6,ok/ok/Hidden5,ok/ok/Flagged5.The criterion held in 3 of 3 runs, so the race scenario is admitted. The probe file is deleted.
Sabotage (CONST-T10). Each break got a full
pnpm journeysrun and was reverted (each file byte-identical afterwards):latestdropsWHERE state IN ('Visible', 'Flagged')IllegalTransitionhandlerEffect.fail→Effect.diemovedropsAND state = ?3U6: removal procedure in a scratch clone of
f7a9a8a(ruling A-7, F1). The full transcript is.cache/u6-removal.login the worktree; it isn't committed. Before any edit, I opened the clone's devshell to stand in for "the shell you already have open". Then I followed steps 1-10 literally. Decisive lines:R12's
':!*.lock'excludes onlyflake.lock.pnpm-lock.yamlstops matching because step 8 rewrites it, and the README now says so (F3). Step 8 passes--no-frozen-lockfilebecause pnpm freezes the lockfile by default whenCIis set, which would refuse that rewrite. An earlier run, on85e3d27with the install after the flake edit, failed the devshell build withERR_PNPM_LOCKFILE_CONFIG_MISMATCH. That run's output wasn't kept, so the step order rests on the passing run above, not on that failure.Gates.
pnpm check:cipassed on the committed tree (exit 0 onf7a9a8a; site tests 13/13): format,check:sfs-sources, lint,lint:suppressions, typecheck, test, dist and the sandbox proofs. There are no inline suppressions and noanyor cast. No Stryker run locally; the release gate scores the moderation decision after merge.Judgment surfaces
scripts/check-sfs-sources.tsis a check script. Only its two messages changed. What it accepts and refuses is untouched, and the change was ruled in A-6.Stack upkeep
Main moved to
368b1d2(#83) under the stack. It was merged intofeat/guestbook-lifecycle-decision(9392a3a), and that branch was merged into this one. There was no rebase and no force-push.