Repository navigation
feat(site): decide guestbook moderation on our xstate fork (lifecycle layer 1) - #82
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).
systemfsoftware-maker
added this pull request to stack #85
October 9, 2026 05:25
ryanleecode
approved these changes
Oct 9, 2026
ryanleecode
removed this pull request from stack #85
October 9, 2026 09:28
ryanleecode
added a commit
that referenced
this pull request
Oct 9, 2026
main's tree equals layer 1's head 9392a3a, which this branch already contains, so the merge keeps this branch's tree unchanged.
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 1 of the guestbook moderation lifecycle (area A), per
docs/plans/2026-10-09-0313-feat-guestbook-moderation-lifecycle-plan.md(approved in conductor ruling A-3, recorded in the plan's first two commits). This layer adds the fork as a dependency (U2), the pure decision and its properties (U3), and the README's xstate removal steps. Nothing calls the decision yet; layer 2 wires it.Judgment surfaces
flake.nixandflake.lockchange (a newsystemfsoftware-xstateinput, locked at84e602e, withinputs.nixpkgs.follows = "nixpkgs"). Both are on the origin's R24 list.U1 spike (fork main
84e602e)Tarball:
nix build github:systemfsoftware/xstate/84e602e#workspace-tarballs→xstate-6.0.0-alpha.64.tgz, extracted to.cache/xstate-spike/(gitignored). The runtime script ran under Bun 1.4.2 (the harness blocks spawningnode); the property file below runs the same calls under Vitest on Node 24.resolveState({ value })per nameVisible,Flagged,Hiddeneach give that value, statusactive.transitionand methodmachine.transitionisUnhandledis true for exactly the illegal three.type: 'final'HiddenFlagged/Flag returns statusdoneand effects["@xstate.terminate"](both forms). KTD1 holds:Hiddenis a plain state with noon.resolveStateonBanishedError: State 'Banished' does not exist on 'guestbook-entry'. KTD2 holds.tsc7.0.2)resolveState's snapshot value is exactly'Visible' | 'Flagged' | 'Hidden'. The freetransitionreturns that value type, with effects typedTransitionExecutableActionObject<never>. The methodmachine.transitioncalled on acreateMachineresult returnsany(hover:…): any). The same method on a binding typed as the plainStateMachine<…>returns the declared types. v6 also needs{ target: '…' }objects for typed transitions; string targets fail to type-check.pnpm lint)transitioninsidedecide:dmmf-workflow(make-body-purity)reports the unsealed import (KTD3's reading holds). Method on thecreateMachineresult: purity passes, but theanyreturn tripstypescript(no-unsafe-assignment)×3 andno-unsafe-member-access×2. Method oncreateMachine(…).provide({}):pnpm lintexits 0.EntryMovedcarriesactions: S.Int, the length of the transition's effect list, and∀p_MoveActions_=Emptypins it at 0. No branch is added.Raw runtime output:
No stop condition hit: items 1, 2 and 4 support R3, and one form reaching the fork passes the full lint (item 6).
Declared deviations from the plan (CONST-W3)
createMachine(…).provide({}), not the barecreateMachine(…). Fork gap:StateMachine.ts:701typestransition's effects asExecutableActionObjectFromLogic<this>, andthisiscreateMachine's intersection (createMachine.ts:221-224), so the whole result degrades toany.provideis declared to return the plainStateMachine<…>(StateMachine.ts:590), which types it.Pick,OmitandAnyStateMachineannotations all still giveanyor lose the value type. Proposed fork fix, one line atStateMachine.ts:701:ExecutableActionObjectFromLogic<this>→ExecutableActionObjectFromLogic<StateMachine<TContext, TEvent, TChildren, TStateValue, TTag, TInput, TOutput, TEmitted, TMeta, TConfig, TActionMap, TActorMap, TGuardMap, TDelayMap, TInternalEvent, TTransitionMeta>>. Applied to a scratch copy of the fork's.d.ts, it makes the directmachine.transitionfully typed. After it lands,.provide({})is deleted.[Workflow.InstrumentationBrand]is{}, not a (state, event) map. A mapped object literal can be mutated to{}and still compiles (checked withtsc), andstryker-ignorer-effect-schema-declarationshas no rule for it. That mutant would survive and break R9's 100. The sign workflow uses the same empty map.∀e_WalkMoves_≡HandWrittenTableWalk: generated walks must take each legal pair, at a minimum share of 0.1 each. AE3's hand-written refusal is now∀s_CommandStateDecode_≡ThreeNameMembership, drawn fromEntryState ∪ String, with the three names written by hand as the oracle. The house property-file rule refusesitexamples and hand-picked lists.What changed
flake.nix,flake.lock: thesystemfsoftware-xstateinput, and itsworkspace-tarballsin thesfs-depscopy loop andjq -s addmerge.pnpm-workspace.yaml: thecatalog:linefile:.sfs-deps/xstate-6.0.0-alpha.64.tgzand itsoverrides:mirror.apps/site/package.json:"@systemfsoftware/xstate": "catalog:".pnpm-lock.yaml: frompnpm install.guestbook.schema.ts:EntryStateandLifecycleEventliteral schemas.moderate-guestbook-entry.workflow.ts:moderateGuestbookEntry(Workflow.make,EntryMoved|IllegalTransition) over a module-private machine.__tests__/moderate-guestbook-entry.workflow.property.test.ts: seven laws, covering R8's six and AE3's refusal.README.md: removal steps 7-10 (the xstate lines, the flake input,pnpm install, and the README's own guestbook text, so R12's grep can reach exit 1).Verification (run locally, in the devshell)
nix build .#sfs-depscontainsxstate-6.0.0-alpha.64.tgz;nix build .#pnpm-storeandnix develop -c truesucceed;pnpm install --frozen-lockfilepasses; the lockfile key is@systemfsoftware/xstate@file:.sfs-deps/xstate-6.0.0-alpha.64.tgz;pnpm check:sfs-sourcesreports 26 packages, all fromfile:.pnpm --filter @endgame/site testpasses, with the constant-impostor gate and the coverage classes satisfied. The decision matchesstryker.mutate(src/**/*.workflow.ts) unchanged. Mutation was not run locally; it runs at the release gate.Flagged/Flag target toVisibleturns∀p_LegalPair_≡HandWrittenTableand∀e_WalkMoves_≡HandWrittenTableWalkred.∀e_RefusedEvent_=UnchangedStatered.pnpm check:ci: exit 0 on the committed tree (3612204), covering format,check:sfs-sources, lint, lint:suppressions, typecheck, test, dist and the sandbox proofs.368b1d2(docs(repo): take the unbuilt brainstorm off main until its code lands #83, which takes the brainstorm doc off main). It was merged in as9392a3awith a plain merge, no rebase. The merge only deletes the brainstorm doc; layer 2 (feat(site): flag and vouch for guestbook entries (lifecycle layer 2) #84) brings it back.Notes for the owner of the instruments (not changed here)
scripts/check-sfs-sources.tsstill says "our flakes (systemfsoftware, stryker-js-effect)". Its check is scope-based, so it covers the fork; only the wording is stale. Ruling A-6 fixes it in layer 2 (feat(site): flag and vouch for guestbook entries (lifecycle layer 2) #84).effecttsgooxlint plugin exists only afterpnpm run prepare(effect-tsgo patch --oxlint).ignoreScripts: trueskips it on install, sopnpm lintfails withUnknown plugin: 'effecttsgo'in a fresh worktree.