Skip to content

Refactor/rpc only - #25

Open
phibkro wants to merge 177 commits into
mainfrom
refactor/rpc-only
Open

phibkro wants to merge 177 commits into
mainfrom
refactor/rpc-only

Conversation

@phibkro

@phibkro phibkro commented Sep 28, 2026

Copy link
Copy Markdown
Member

What changed

Related issue

Checklist

  • Self-reviewed my code
  • Verified it works as intended
  • CI passes

…mands

createDepartment, createTeam, and createFieldOfStudy take
OrganizationAdministratorEvidence instead of the OrganizationActor union.
requireOrganizationAdministrator is its only constructor, so a forged or
member actor no longer type checks, and the adapter's runtime re-check
(decodeOrganizationActor, authorizeOrganizationActor) is deleted. The
unused resolveActor option and organizationActorFrom are removed.

The stored actor_json keeps its shape, and the authorization-rules proof
observes the same tags. Pilot of docs/specs/authz-evidence.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Flatten the evidence tests: Stryker 10 with Vitest 5 misses failures of
tests nested in describe, and killed 18 of 18 mutants once they were
top level. The authorization evidence spec records the falsifier
results, and STATE.md records the helpers still trusted by convention.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…ands

Add requireDepartmentReach, the only constructor of DepartmentReach<C>,
which proves a capability over one department. Three commands whose
only barrier was a check at their HTTP caller now take it:

- commandOnboarding takes the coordinator's admissions.outcomes reach and
  reads its department and audited actor from it.
- recordAdmissionOutcome takes the decider's reach and denies evidence for
  another department on the locked application, a check it lacked.
- Placements.execute takes a coordinator's placements.coordinate reach for
  board and coverage-board changes; own affiliation and coverage still act
  as the person.

Property tests show the evidence agrees with reaches, with department
administration, with canManagePlacements, and with the outcome Decide
permission, so the replaced handler checks keep their answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Four commands authorized before replay and then authorized again inside
the command, each resolving current authority twice in one transaction.
Their authorize step now returns branded evidence, and the command runs
from it without resolving authority again:

- Schools: SchoolCommandAuthorization carries the decoded command and
  the locked school and departments; executeCommand takes only it.
- Recruitment maintenance: RecruitmentMaintenanceAuthorization carries
  the decoded command; maintainRecruitment takes only it.
- Certificates: authorizeCertificateCommand and CertificateCommandTarget
  give way to DaysServedConfirmationAuthorization and
  CertificateIssueAuthorization; the scope, the assistant, the department
  row, and the issuer's seat come from the evidence.
- Team applications: authorize returns TeamApplicationAuthorization for
  the action; delete and revise take their application or team from it.

Schools and maintenance resolved authority at DateTime.now twice; they now
resolve it once. Each evidence is valid only in the transaction that
resolved it, which is the transaction the command runs in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
listTeamInterestRegistrations took a filter of authorized departments and
teams that its HTTP caller computed, so it read whatever scope a caller
named. requireTeamInterestScope now computes that scope from the resolved
authority, as the handler did, and is the only constructor of
TeamInterestReadScope, which the listing requires. The unused
departmentId filter is gone; the semester stays an argument.

A property test checks that the scope holds only departments and teams
that team-interest.read reaches, inside the requested department, and
that it denies exactly where nothing is readable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
SocialEvents.create took the creator's person id and trusted its caller
to have evaluated the create AccessSpec. requireSocialEventCreation now
builds SocialEventCreation from the same candidate grants, and create
takes it: the audited actor comes from the evidence, and a request for
another department than the evidence names is a defect.

A property test states the rule independently of the grants: an active
global administrator creates anywhere, an active appointment creates in
its department.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Bun's node:http destroy() emits close but no error, so a probe attempt that
timed out never resumed, and waitForHttp outlived its deadline. The golden
team-application journey hung for 25 minutes before its first page when the
homepage's first request was slow. A probe against a silent listener with a
one-second deadline hung past 8 s before this change and fails at 4.1 s after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…ect RPC

Delete the HttpApi contract, its backend handlers and endpoint tests, the
generated SDK, and OpenAPI generation. Better Auth, the OAuth2 routes, and
/health stay plain HTTP behind the same ingress.

Add packages/rpc: the RPC contract with one group per context, the
credential middlewares, the defect boundary, typed problem errors, the
AccessSpec annotation, the client, and a script client for journeys. The
backend serves NativeRpcs at /api/rpc; each context exports one handler
layer that the router lists. Social events is ported end to end as the
precedent; every other group is an empty stub that its worker fills from
the base commit, following docs/specs/rpc-only.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…board session check, and the request recorder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Enable all five @effect/tsgo presets (correctness, antipattern, style,
effectNative, recommended) as errors everywhere. apps/homepage and
apps/dashboard keep their previous set: recommended and correctness without
effectNative. The guard test now enumerates every rule the package ships,
so a rule or category added by an upgrade is an error at once, and it
accepts the React applications' override as the only relaxation.

Measured on 6979637, the newly enabled rules report 3,475 findings outside
the React applications, 323 of them in files the RPC migration deletes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
The RPC server lays each message's headers over the HTTP request headers,
so a caller could forge the source address, user agent, or request
correlation that session revocation audit and the contact quota read. The
ingress now keeps only the headers a caller may supply inside a message
(cookie, authorization, and the two contact headers the contact RPC reads
behind its secret) on both the external and the internal RPC paths.

A test at the ingress failed before the change (cf-connecting-ip reached
the handler) and passes after it. The RPC path also answers preflight with
a trailing slash, and both router builders take NativeRpcOptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
strict-effect-provide cannot recognise an entry point; its documentation
asks to disable it there. The exemption derives from effectConfig: files
declared as composition roots or tests, and the backend test harness. The
guard pins it as the only relaxation outside the React applications.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
# Conflicts:
#	oxlint.config.ts
# Conflicts:
#	docs/effect-exceptions.json
…ct program

It runs its commands through ChildProcess, reads ambient configuration through Config, owns its
temporary directory, cluster, and pool as scoped resources, and runs at a Bun composition root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…ffect services

The conventions CLI is a Bun composition root. Reading a repository loads its
files through FileSystem once, so the checks keep their synchronous reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…rogram

Its loopback receiver is a Bun HttpServer in the proof scope, it reads its database URL through Config,
and it queries through SqlClient. The Bun server types conflict with the Node request types of the other
verification programs, so the proof type-checks in its own tsconfig.bun.json, as tools/e2e does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
The runner and its evidence module become TypeScript. Processes run through
ChildProcess in owned scopes, the provider and browser callbacks run through a
FiberSet runtime, and time, environment, files, and JSON go through Clock,
DateTime, Config, FileSystem, Path, and Schema. A declaration file types the
browser driver's hooks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…itest admission as Effect programs

The lock keeps its file protocol and flock(1) on the descriptor that it
holds (EX-0013). Vitest admission writes the holder into the environment
of its main process for its workers (EX-0014). A measured job runs through
node spawn so that the ledger and the exit keep its terminating signal
(EX-0015).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
Two journeys failed at random on color-contrast for a button: the golden
reimbursement owner page and the onboarding spec. The shared button has
transition-all and disabled:opacity-50, so right after a submission it is
enabled but still half transparent, and axe reads the midpoint colours.

auditSettledPage in apps/dashboard/e2e/settled-axe.ts waits in the page for
every animation with a finite end, then runs axe with the caller's tags and
regions and returns each violation's rule, impact, targets, and failure
summary, without markup. It lives beside the dashboard journeys because the
dashboard owns @axe-core/playwright and Playwright; the homepage journeys
import it by path as the homepage Vite config already imports dashboard
source, and the tools import it as they import native-operations.ts.

All 24 modules that ran axe now audit through it, each with its own tags,
regions, and assertion. The construct joins the new browser-audit category.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
anti-slop/no-unsettled-axe rejects new AxeBuilder(...) and any load of
@axe-core/playwright (import, dynamic import, or a require call such as
requireDashboard) outside apps/dashboard/e2e/settled-axe.ts, so no audit
can run on a page whose transitions still move. The RuleTester test keeps
negative controls beside the invalid cases. AGENTS.md records the defect
class in Construction over trust with the flaky runs of 2026-09-28.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…ervations Effect programs

The rehearsal owns its cluster, pool, and backend processes in its scope, runs commands through
ChildProcess, reads configuration through Config, and serves the 0097 delivery sink on the Bun
HttpServer that its entry provides. The delivery and reopening observations return Effects that
require platform services; Playwright, pg, and the script client stay behind journey steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…ct program

The runner becomes TypeScript: children run through ChildProcess in owned
scopes, the recording proxy serves through Bun.serve and forwards with
HttpClient from a FiberSet runtime, ports and executables come from Config,
and the psql and browser evidence decode through Schema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
… sink stops

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…eview deployment as Effect programs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…as Effect programs

The artifact checks and the subprocess runner become TypeScript modules
that return Effects; the CI job runs the renamed test file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
The recommendation probe and its helpers (returning assistant, report observer,
co-interviewer correction, correction boundaries) become Effect programs on the
Bun platform: ChildProcess through startOwnedProcess and commandOutput,
HttpClient for fetch, FileSystem and Path, Config, DateTime, Effect.sleep and
timeouts, Console through one ordered output queue, and typed failures.
owned-process.ts and its test go; EX-0009 retires, and EX-0013 registers the
page fetch of the returning options probe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…n Effect program

The proof owns its native processes through ChildProcess and signals them as before, serves its
loopback provider on the Bun HttpServer of its entry, waits through Clock and Effect.sleep, and
writes its evidence through FileSystem. It joins the Bun composition-root group and tsconfig.bun.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
# Conflicts:
#	tools/verification/receipt-reopen-observation.ts
# Conflicts:
#	docs/effect-exceptions.json
#	tools/acceptance/recommendation-check.ts
…ect programs

The Promise API of tools/postgres now bridges Effect programs over FileSystem,
Path, and ChildProcessSpawner in one module runtime of the shared Node
implementations, which Bun programs and Node suites both run. initdb,
createdb, pg_isready, PgBouncer, and the sentinels run through ChildProcess;
the postmaster and the synchronous installation resolution stay on Node
under EX-0017. The exported functions of two parameters gain dual forms.
The package tests become @effect/vitest programs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
…as Effect programs

placement-check.ts becomes one Effect program at a Bun composition root: owned children
through ChildProcess, the loopback receiver through Bun.serve with Effect handlers, readiness
and sign-in through HttpClient, the artifacts through FileSystem, and the variables through
Config. The golden school-service and recruitment observers and the evidence reader become
typed TypeScript modules whose checkpoints are Effects; the evidence reader keeps its .mjs
path and Promise forms for the CI wrapper and the browser drivers (EX-0017). The journey
clock reads instants through DateTime, and the projection file lock through Semaphore
(EX-0018, EX-0019).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmXVLTGJH6D28NK4jkdy3e
# Conflicts:
#	docs/effect-exceptions.json
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-09-28T18:15:36.908363Z 7de87a5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown

Worker Previews

Surface Latest pull-request revision Exact deployment
Public homepage Open Preview Open immutable deployment
Staff dashboard Open Preview Open immutable deployment

Previews are frontend-only: pages that read the API show their unavailable state.

Source: 7de87a5cdb123b28f56b88da279a9c5394f2e541

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants