State
Lifecycle: build
This file records current state only. Remove an item when it is resolved. Git keeps the history.
Current
Production uses the legacy PHP application. Production replacement is not authorized or rehearsed.
The legacy source is in the vektorprogrammet repository; the former apps/server continues there as branch modernize/mono-web-server (908368a8), with the same tree (023b85bd) as mono-web b6438cb2. The removal commit is ea124143; its parent 1277be8b still contains apps/server.
Local implementation and acceptance do not authorize production, provider, or source-data actions.
The stable Effect upgrade follows its frozen contract. Local acceptance remains pending. The canonical worktree excludes unpublished dashboard Root and Worker feature sources. Historical RC proof results do not establish stable acceptance. The HTTP union patch and native queue extensions remain necessary. Their stable ports are not retirements. The approved native SQL change uses separate scoped Database and Better Auth pools. SQL migration, cancellation, and cleanup proofs remain gates. The compiler artifact manifest records the actual consumed source and archive hashes. A package build does not establish installed-consumer acceptance. Fresh committed-source checks, generated contracts, native UI/API/PostgreSQL journeys, and the immutable lift snapshot remain required.
Recruitment migration 0029 has operator approval reported by the director; exact words not
preserved. This is not verified direct operator approval. Effx commit b88523d2a8ffcbde5c19008d649da653c26ee475 records the report under docs/specs/0029-recruitment-migration.md.
The current phase is LOCAL implemented, not accepted. The source descends from the accepted Admissions integration 815a28f8.
Canonical declarations cover sixteen Recruitment operations and two Recruitment-owned onboarding operations.
The project IR contains 74 operations across eleven generated groups. The complete external native contract retains 111 operations.
These eleven groups do not represent every external API group.
Identity owns the handwritten onboarding claim.
One mixed onboarding root requires typed claim completion after the generated handlers register readBoard and command.
The four Foldkit programs retain handwritten Command bodies over the generated SDK, directly or through the existing bridges.
The AOT contracts and raw-handler types do not replace domain authority, transaction locks, receipts, or delivery ownership.
The system and architecture documents describe the implemented validator, correction, frozen-outcome, and dialog policies.
The third and final original full R2 diagnostic failed with ProfileContactNotFound during ReadBoard.
The unchanged mapper returned recruitment.unavailable/503. The original 30-second button assertion remained red after eighteen correction stages.
The diagnostic did not record the missing runtime Person identity. Source establishes an incomplete Returning fixture, not runtime Person attribution.
The local Returning fixture repair seeds the declared primary interviewer's synthetic contact before the appointment in one transaction. Fixture completeness grants no business authority and permits no production fallback or change to the missing-contact refusal. The third diagnostic remains separate, red evidence; it does not identify a missing runtime Person.
The original full R2 regression on 66d2baee91d515d6c0787d6ec4aef85ba2c23e57 reached seven outer gates and nineteen completed stages, then failed.
The unchanged tools/acceptance/recommendation-check.ts:3600 assertion required focus on the current Reopen action within 5,000 ms.
Close returned 200 and staffing refreshed to Closed, but Reopen remained inactive. This is not migration acceptance or observed Chrome causality.
The operator approved a permanent native workflow-focus repair for this next-action transition. It does not reuse the Close opener as Reopen.
Ordinary Cancel/Escape retain native opener restoration. The system and architecture documents define the approved ownership and correlation policy.
On source 66d2baee, the native 39-case suite, canonical/parity checks, full check (16/16 tasks), and full test (21/21 tasks) passed.
These results and every original 28-row result on that source are historical, source-specific evidence only.
Collector omissions and repeats remain explicit external evidence incidents, not product failures.
The original full R2 remains a required regression confirmation, not a fourth diagnostic or a target-only substitute. Its original actions, filters, selectors, 5/30-second click waits, and 5,000-ms focus assertions remain gates. Window privacy, 403 denials, exact no-write snapshots, corrections, mail, and frozen outcome citations remain unchanged.
Acceptance requires a new clean final candidate after workflow-focus, permanent tests, and documentation integration:
- New executions of all 28 original acceptance rows, including the complete original R2 journey.
- The full 39-case HTTP regression suite, canonical contract/handler parity, and serial
just check --concurrency=1andjust test --concurrency=1. - The mounted production workflow and its full permanent focused tests: failure, release, ordinary cancellation, interruption, stale identity, replacement, real navigation, and disposal.
- A held native Close must block replacement open/focus until its actual completion and any pending focus-cancellation completion. Late old-generation completions and duplicate save responses must not arm focus or change a replacement dialog.
- Before/after custody of the exact consumed source, served bundles/runtime, and each job's own environment.
- Source conditions, runners, and build manifests decide which emitted or materialized artifacts each gate consumes.
- A missing SDK dist, OpenAPI binary, or environment inventory is a gap only when evidence proves that the gate consumes it. Labels alone prove neither consumption nor nonconsumption.
- The homepage's existing build stamp is consumed evidence only. The dashboard embeds no Git HEAD stamp; a new dashboard stamp is not a gate.
- All 195 completed outcomes from the existing persistent authority-model run and byte equality with its model source.
All final-candidate gates run after integration and the new clean source freeze. Earlier focused or historical results cannot replace them. The two baseline and seven prototype HappyDOM tests establish a native mechanism only, not Chrome acceptance.
The single proc://recruitment-authority-model-195 run remains active. Whole-model acceptance and its completed outcomes remain pending.
The actual Authority and contexts bytes match source 66d2baee; final-candidate byte equality remains a gate.
The model source digest is ca4460362b618320f0c6a200e98be8dc0692ee37f0b1113d94cf0d5c7fc8d4e7.
/srv/share/projects/effx-recruitment-0029-evidence/final66-model195-input-and-expected-obligations.json records both actual input hashes and expected obligations.
Partial progress does not establish SAT instance counts or completed obligations. The existing run must not receive signals or restart.
Migrations 0083–0085 remain registered. Migration 0085 neither rounds nor rewrites historical instants.
Historical repaired-baseline and prerequisite results remain external evidence, not final-source acceptance.
Evidence stays in /srv/share/projects/effx-recruitment-0029-evidence/.
The final manifest and operator approval receipt stay outside the repository. Acceptance requires no evidence-only commit after the source freeze.
No generated Recruitment/onboarding cutover is accepted.
The operator’s direct instruction was: “Forgoe the heavy job slot in place of checking the machine status for
what resources are taken” (2026-10-04, history://Main:253-255). The director supplied the
admission procedure: two readings 10 seconds apart, load below nproc, MemAvailable at least
25% of RAM, and no swap growth. Retain top RSS/CPU processes and heavy-job attribution beside
each job’s evidence. If admission fails, wait 60 seconds and repeat. After 15 minutes, report
the resource holders. Admit each heavy job from its retained resource observations. Measured lead execution
skips obsolete exclusive-flock admission under that quoted instruction, without claiming lock ownership.
The director dropped the provisional IO-PSI gate after the operator reported a
monitoring bug. Admission uses only load, available memory and swap growth.
For a suite timeout, retain NVMe io_ticks and elapsed time around the run to
distinguish actual disk saturation from a code failure. Staged-only formatting/lint
hooks are light work. The existing owned Alloy run stays running.
The director accepted the nine-group re-vendor prerequisite at ac51f9aa for LOCAL implementation only.
The installed compiler supports typed completion of the mixed onboarding group. A missing claim remains a compile error.
The native recipe generates contracts before handlers, without a root shim or generated-output edit.
Prior-group byte identity, the repaired OpenAPI semantics, and SDK parity remain final-source gates.
Delivery tests own their migrated database and native queue scope. An Idle result does not establish fixture isolation.
Earlier prerequisite receipts and repair chronology remain outside the product tree. They do not establish final acceptance.
The retained source-66d2baee aggregate receipt records historical canonical/parity, HTTP39, maintenance, check, and test results.
It remains separate from the new final candidate and its required original acceptance rows.
The record is /srv/share/projects/effx-recruitment-0029-evidence/final66-aggregate-BeIa6C-receipt.json.
Unmerged worktrees remain retained. Cleanup requires a proven merge or explicit disposal authority.
The credential-exposure incident is held separately. Raw installer evidence remains private and unpublished;
token revocation is recommended but awaits the operator’s decision. No credential or transcript changes are authorized.
Stopping verbose installs is the director’s containment order, not a quoted operator instruction.
No mono-web main merge, push, publication, provider, or production-cutover authority is granted.
Retirement trigger: after Identity migrates claim, the complete generated group replaces
the transitional handwritten completion. The retained mixed-onboarding probe is outside
the product tree in /srv/share/projects/effx-recruitment-0029-evidence/compiler/mixed-onboarding/.
Attribution correction (2026-10-04)
Earlier Recruitment state and evidence labels credited director decisions to the operator.
The continuation, re-vendor conditions and later R2 confirmation came from the director through
agent://Main. Rulings 1–7 retain the distinct label: “operator approval reported by the
director; exact words not preserved”. Their direct approval is not verified. The operator’s
resource instruction is quoted in this record, while its numeric admission thresholds came
from the director. Earlier commits and raw evidence remain unchanged. The dated evidence
correction at /srv/share/projects/effx-recruitment-0029-evidence/attribution-correction-2026-10-04.md
records those distinctions without altering test results or product behavior.
Operator decisions:
- The backend is a portable Bun process with PostgreSQL, not a Cloudflare Worker.
- DigitalOcean is the selected host (2026-09-25). Provisioning and deployment stay deferred until cutover preparation.
- No cloud provisioning or source-data upload is authorized.
- The private 2024-08-22 legacy backup is the working source for import rehearsals. Its schema shape is expected to match current production. Its contents are historical, not current.
- The native target is PostgreSQL 17 or 18 (default 18); hosted Supabase runs 17 (operator decision, 2026-09-25).
The root manifest declares the set once as
engines.postgresql;VEKTOR_POSTGRES_MAJORselects a major per environment. mainis pushed toorigin(2026-09-25), so hosted CI runs. The SDK is not published (operator decision, 2026-09-25).@vektorprogrammet/sdkis private; the Release SDK workflow and Changesets are removed.- Migrations are frozen (2026-09-27). Nothing runs in production; every database is disposable; the
legacy database is reached only through import commands. The numbered migrations, the checksum
registry,
just migration-hashesand upgrade proofs will be replaced by one schema declaration we edit directly; the design awaits the operator's approval of its mechanism. No new numbered migration is written and no branch renumbers one. - The architecture direction is FCIS with DDD (2026-09-27): bounded contexts are a modelling and code-organisation boundary, not a runtime one; the core domain is a stateless library of pure algebraic APIs; the shell is one generic imperative runner; Layer requirements are declared as service interfaces and provided incrementally at composition roots; coalgebraic state only where state bound to operations is a real requirement. See docs/specs/architecture-consolidation.md (draft).
- Interview corrections: the interviewer and the co-interviewer may correct a completed assessment
until the admission decision is recorded; after that the department board leader, or a current
holder of the delegable staffing delegation, may reopen one window per interview with a reason
and an end instant no later than the semester's end; whoever reopens cannot correct under it; a
post-decision correction never changes what the recorded decision acts on. In progress on
fix/recruitment-review-0926; the specification is not onmainyet, so it has no page here. - The deployment image and hosting are undecided. No image exists. Podman is the operator's preference for building and running one; Podman is not installed on this machine.
Development stays local. just dev starts both frontends and the native Bun backend; PostgreSQL is a separate prerequisite.
See local development. External delivery is disabled in local development.
Evidence boundary
Each row is the latest local acceptance of its area. All runs used synthetic or historical data, disposable PostgreSQL, and local or loopback delivery.
No row proves production data, a real provider, a deployment, or cutover. Evidence lives outside the repository; temporary storage is not an archive.
"Probes" means disposable checks; no maintained root command replays them.
The evidence directories under /tmp cited below were deleted on 2026-09-27 during a lead cleanup; the rows record what was observed then. Re-run the named command, or use the latest green hosted Tests run (36268554218 or later), which runs every journey in the just sets.
| Area | Revision | Command | Evidence | Unverified boundary |
|---|---|---|---|---|
| Local development and native sign-in | 47305540 | bun dev and probes | (deleted 2026-09-27) | Provider services |
| Organization lifecycle and account access | b08d87e3 | Probes | (deleted 2026-09-27) | Production migration, providers |
| School administration and capacity | 34d9109a | Probes | (deleted 2026-09-27) | Production migration, providers |
| Recruitment maintenance | 79fd4e28 to ed0246cb | Probes | (deleted 2026-09-27) | External mail delivery |
| Interview rebooking | 8fe59f3e | Probes | (deleted 2026-09-27) | External mail delivery; full accessibility compliance |
| Scoped mailing recipients | b082e628 | Probes | (deleted 2026-09-27) | Subscription administration, delivery, provider sync |
| Coordinator identity cards | de57e160 | Probes | (deleted 2026-09-27) | External delivery |
| Economy query and settlement | e3177b24 (snapshot) | Probes | (deleted 2026-09-27) | Real payment, providers |
| Golden school service, with coverage (26 steps) | 1a583b88 | just golden school-service | (deleted 2026-09-27) | Real providers |
| Golden recruitment (19 steps) | 1a583b88 | just golden recruitment | (deleted 2026-09-27) | Real providers |
| Admission outcomes and substitutes on call | 1a583b88 | bun run --cwd apps/dashboard e2e:real-substitutes (12 API and 7 browser gates) | (deleted 2026-09-27) | Outcome messages, real providers |
| Coverage record upgrade (migrations 72 to 75) | 1a583b88 | Throwaway proof: 1961c14b seeds offers, then PGlite 18.3, PostgreSQL 18.6 and 17.11 upgrade | (deleted 2026-09-27) | Production data |
| Unattended delivery recovery (10 observations) | 9965f0c6 | bun run verify:delivery-recovery | (deleted 2026-09-27) | Real providers |
| Golden reimbursement (12 steps, 2 fault modes) | 7af9e51d | bun run test:golden-reimbursement, GOLDEN_REIMBURSEMENT_FAULT=after-submitted|interrupt-after-submitted | (deleted 2026-09-27) | Real payment, remote provider after abort |
| Golden team application (17 steps, 2 fault modes) | c9303fab | bun run test:golden-team-application, GOLDEN_TEAM_APPLICATION_FAULT=after-submitted|interrupt-after-submitted | (deleted 2026-09-27) | Real providers |
| Placements documentation CI | 49f38ccc (hosted CI) | bun run --cwd packages/placements docs:ci <dir> | GitHub Actions run 36118163912 | Source URL availability, repository protection |
| Reviewed current-assignment import | 5d4001e8 (migration 65) | bun run rehearsal:legacy-current-assignment | (deleted 2026-09-27) | Current source data, human review |
| Reviewed Organization import | bd2fcbc1 (migration 66) | bun run rehearsal:legacy-organization | (deleted 2026-09-27) | Current source data, human review |
| Reviewed receipt import | 05f05974 (migration 67) | bun run rehearsal:legacy-receipt | (deleted 2026-09-27) | Historical receipt files; power loss during restore |
| Combined candidate and historical backup | 6b8b1c4b (migration 67) | bun run rehearsal:legacy-candidate and the private backup run | (deleted 2026-09-27) | Current authority, assignments, receipt bytes, mailbox owners |
e3177b24 is a disposable snapshot commit, not a commit on main.
The golden fault modes are expected failures that prove cleanup, not successful journeys.
Other implemented journeys have local observations without a retained record: identity, OAuth, password recovery, profile, directory, team interest,
admission periods, public applications, interview scheduling and conduct, onboarding, placement rosters, content, contact, and social events.
Content repair-first acceptance (2026-10-03)
At clean e87a1c2b18300453fa01595d0fd901cf0b96cae6, three independent real-PostgreSQL/backend regressions went red before their separate source fixes. They preceded every effx Content declaration. The integrated focused suites passed: Content database workspace/slug/public reads, four files and 19 tests; backend public ETags/shared conditional response plus Profile/Schools/SocialEvents, six files and 25 tests; domain slug projection, one test. All three jobs ran under just measure and exited 0.
- Workspace visibility: committed red test
58aae63eobserved a foreign department's unpublished working title in a scoped editor workspace. Fix60b429afapplies the samecanReviseDraftrule as detail/revision reads; authorized publisher and global administrator retain their rows. Enforced bypackages/database/src/content/workspace-visibility-postgres.test.tswith disposable PostgreSQL (postgres.ts:225-239). - Public validators: committed red test
2aa9f2e8saw the old304and ETag after department links changed. Fixe87a1c2bhashes the strictly encoded public listing/detail bytes and passes them to the one conditional-response helper; its original body path retains lazy 304, 200 and 412 behavior. Real-PGapps/backend/src/content/http-news-etag.postgres.test.tsand deterministicapps/backend/src/http-api/conditional-json.test.tsenforce this. Shared ETag derivation and Profile/Schools/SocialEvents HTTP handler source SHA-256s stayed unchanged; full browser regressions remain a later gate. - Slug bound: committed red test
c396f76dobserved a failed insert for a title whose transliteration exceeded 255 characters. Fix9c92e455bounds the first slug and reserves room for collision suffixes while preserving short slugs and atomic domain receipt/audit writes. Enforced bypackages/database/src/content/slug-postgres.test.tson disposable PostgreSQL andpackages/domain/src/content/projection.test.ts.
These earlier repair checks alone did not certify Content; the later generated browser result is recorded below. Full serial just check/just test and separate Profile, Schools, and SocialEvents browser regressions remain gates at the final Content revision. The operator's main, production, and providers remain unchanged. The journal and homepage error mapping in fix/content-teams-review-0926 remain excluded (docs/specs/0014-content-migration.md in effx).
Content public-news midnight hydration (2026-10-04)
The original just e2e content-publication on clean generated Content commit ef826549 reached its final browser assertion but exited 1 after 40 seconds (peak RSS 4.5 GiB): four React hydration #418 text mismatches on anonymous news pages. Original mono-web main at f433ea90 already rendered all three news dates with new Date(publishedAt).toLocaleDateString("nb-NO"), without a zone. This behavior predates and is independent of effx.
Commit 52242f8b extracted the unchanged route formatter and committed the focused apps/homepage/test/news.test.ts regression red: 1 failed/7 passed; at 2026-10-03T22:30Z, controlled UTC rendering returned 3.10.2026, while Europe/Oslo returned 4.10.2026. Fix 3d6e37a3 explicitly formats Norwegian dates in Europe/Oslo at all three public route callsites and fixes four other implicit dashboard formatters. The new anti-slop/no-implicit-date-time-zone rule guards both applications; all 15 rule cases pass, including zone and non-date negative controls. The focused homepage regression passed 8/8 and scoped lint passed. On clean 3d6e37a3, the original measured Chromium/Better Auth/disposable PostgreSQL just e2e content-publication passed (exit 0, 79 seconds, peak RSS 4.6 GiB). This repairs a pre-existing UI defect; it does not prove provider or production cutover.
TeamApplications generated parity work (2026-10-04)
The approved local cutover replaces the seven handwritten TeamApplications HTTP declarations and raw bindings with one generated group; original domain authority, PostgreSQL transactions, Foldkit, mail and PersistedQueue/outbox remain application-owned. Vendored effx f3c6bf8 strict five-group contract/handler emits exited 0 with only EFFX0001. Complete OpenAPI 81e7b872178803a3c8bf6ccf86b96b5185ba560e9661b47e98d1d0d4e827b749 and SDK index c4abcca1878171c84fd3c531c3fe5c93f92329818c59d4b73a8bf796bca8aad3 matched the accepted fcc2f3e0 bytes exactly: 107 external operations, the same seven TeamApplications IDs and one excluded internal receipt. The generated DELETE response is the real Effect NoContent 204 with no body and the original no-store/Vary headers. The unmerged service-drain/operation-grant pilot is excluded (effx/docs/specs/0023-team-applications-migration.md).
- Copied intake limit: the source-based declaration test was committed before a clean focused run observed one failure: metadata said 250 active teams while the domain constant required 500. A one-line literal correction first turned 5/5 green;
36158786removed the copy entirely. The application-owned operation annotator now derives OpenAPI description fromTEAM_APPLICATION_INTAKE_LIST_LIMIT. A regenerated whole OpenAPI document stayed byte-identical. The original test now checks rendered OpenAPI against that domain value. No other copied numeric intake limit was found in application, package, tool or documentation source. - Real migration proof lifetime: at the seven-handler cutover source, the existing queue-migration suite failed 1/22 only because full prior-schema setup inside the PGlite case exceeded Vitest’s default 5-second test timer. Commit
75d8deb8gives that case a bounded 30-second timer; the PostgreSQL/PGlite historical-row, claim-fence and queue-state assertions are unchanged. The focused real proof then passed 2/2. This is not a delivery retry or a weaker assertion.
The original just golden team-application, both fault modes, four adjacent browser journeys and full just check/just test remain unverified at one final clean TeamApplications commit. Source-focused HTTP/backend/homepage/Foldkit/SDK checks passed during integration; they do not substitute for those gates. No production, credentials, provider, migration schema or operator-owned main changed.
Historical backup rehearsal
The rehearsal used the private 2024-08-22 backup through a SELECT-only MariaDB account and a separate PostgreSQL target. The backup reader reads users, departments, semesters, schools, school-department links, and assistant history. It left Organization and current assignments unimported. Its census facts drive the import decisions:
- People: 2,893 of 2,923 reconciled; 30 quarantined.
- References: 5 departments, 28 semesters, 44 schools, 43 school-department links.
- Assistant service: 1,690 of 1,815 rows imported; 125 quarantined (105 invalid, 10 unmapped, 10 duplicate targets). 1,681 distinct historical affiliations.
- Accounts: 1,482 supported hashes imported unchanged; 1,410 passwordless identities without credentials, sessions, or mail; 31 quarantined (13 inactive, 5 invalid, 13 without an accepted Person).
- One accepted Person has an account email without a domain dot. The login schema rejects it, so that Person has no login identity. Correct the address with verified evidence before cutover. Do not relax login validation or invent an address.
- Assignments: 0 for 2024 Høst; 92 for 2024 Vår, historical when the backup was made. The backup cannot prove current placements.
- Receipts: 2,206 rows (2,169 refunded, 32 rejected, 5 pending). 2,142 owners resolve to accepted People; 64 owners are inactive. The backup has SQL only, not the 2,206 files. Account numbers are plaintext; native receipts require encrypted account custody. A legacy refunded status proves neither payment nor native settlement. Do not import file-less receipts or mark them settled.
- Messages: 702 admission-opening notices and 18 accept-interview reminders were sent in 2024 by a scheduler outside the repository.
Legacy usernames and company emails are not supported as login aliases. No import infers current affiliation, placement, or authority from history.
Known gaps
Fix an instance when a change touches it (see AGENTS.md).
- Flat copies of the outcome problem lists, under "Construction over trust":
packages/http-api/src/admission-outcomes-effx-adapters.tsrepeats the three spread-composed lists ofpackages/http-api/src/admission-outcomes.ts(17, 24 and 18 codes) as flat literal tuples, because the effx frontend resolves a literal tuple but not a spread of a const tuple inHttp.problemscodes.packages/http-api/test/admission-outcomes-effx-adapters.test.tscompares each copy with its source element for element and at the type level, so neither side can drift. Retire the copies when the effx frontend resolves spreads of const tuples. - Typed endpoint problems: every backend group fails with declared
Problemvalues; the raw problem path is gone. Defects found and left open:packages/database/src/content/postgres.tsnever recognizes a department foreign-key violation (503, not 422);Schema.Struct({})request bodies accept excess members; a sanitizer rejection of article HTML answers 500; a lost serialization race inside the content, school, or recruitment-maintenance domain answers 503, not 409 (content errors keep only the cause's text); the dashboard maps a command'stransaction.conflictto its unknown-error branch (receipt-view.ts). apps/homepage/src/lib/public-application.tslists its problem codes by hand and omitsheader.malformed. The homepage problem mappers (mapPublicApplicationError,publicTeamApplicationPageFailure,failedPublicTeamApplication) still accept a plain problem-shaped object besides the SDK'sProblem; the dashboard reads problems only throughnativeProblemFrom.- Instants: domain fields still use
Rfc3339InstantSchema, notInstant(packages/domain/src/time.ts). The authority instant (M2), per-slice cutovers (M3), and deletion of the string helpers such ascompareRfc3339Instants(M4) remain. - Receipt keyset cursors carry microsecond text (
packages/database/src/receipt/cursor.ts). They stay exact because storage is millisecond; M3 moves them toInstant. packages/database/runtime/schema-boundary-postgres-proof-main.ts(proof:schema-boundary-postgres) is run by nojustset or hosted job, so it stayed red unnoticed: migration 0037 and the migrations after it broke its fresh seed and its comparison of the moved tables at the head. Host it as ajust proofname and in the Tests workflow.- Parameters bound through raw
pgquerycalls,sql.in, orsql.unsafeare not typed. Only theDatabasetemplate rejects aDateTimeargument. Raw calls include the identity and OAuth adapters,packages/database/src/service-principal-grants-live.ts, the cohort importers inpackages/database/src, andpackages/database/src/placements/current-assignment-cohort.ts. - Raw
pgfiles still write advisory-lock SQL and copy lock keys by hand. The exceptions ofanti-slop/no-raw-advisory-lock-sqlinoxlint.config.tslist them; each leaves that list when it moves to Effect SQL andlockAdvisory. The rule does not covertools/, wheretools/e2e/legacy-cutover-references.tsandtools/verification/organization-import-rehearsal.test.tsalso write advisory-lock SQL. - The raw
pgidentity adapterspackages/database/src/auth-engine.tsandpackages/database/src/password-recovery.tsreadNOT access_disabledby hand instead ofaccountAccessEnabledinpackages/database/src/identity-access.ts. Team.email(packages/domain/src/organization/schema.ts) accepts text that is not a mailbox. Open team intake requires a deliverable mailbox; the write boundary does not check it.- Hand-written operation ids outside content, hand-written dashboard navigation paths, a fixed admissions
retry-after, and fixed ports in older browser runners. - Twenty test files of the dashboard and homepage answer API and provider calls with a stubbed global
fetch(vi.stubGlobal("fetch", …)) or a recording fetch, not a contract-served boundary: the*.test.tsfiles that stubfetchunderapps/dashboard/app,apps/dashboard/test, andapps/homepage/test, andapps/dashboard/app/foldkit/team-applications/update.test.ts.packages/sdk/src/__tests__/generated-native-client.test.tsshows the replacement for native API calls: it serves the contract's endpoints withHttpApiBuilderand records what the server decoded. - PR previews (operator decision, 2026-09-25): Cloudflare Worker Previews of the homepage and dashboard only, as
vektor-preview-homepageandvektor-preview-dashboard, whichwrangler previewcreates on first use (contract). Their Cloudflare token (Workers Editor on the two preview Workers only) and account id live in Bitwarden Secrets Manager;secretspec.tomlprofilepreviewgives them to the deploy and delete steps, and GitHub holds only the read-onlyBWS_ACCESS_TOKEN. The parent Workers were created on 2026-09-26 and hold no version. Draft PR #24 proved the path on 2026-09-26: run36242120400read both secrets from Bitwarden (US cloud) through secretspec, deployed both previews, passed the document and asset probes, and posted the review comment; closing the PR ran the cleanup, and the Cloudflare API then listed no previews or versions on either Worker. The homepage preview hostname answered 404 at once, butpr-24-vektor-preview-dashboardstill served the app 7 minutes later.wrangler preview deleteis an open beta; recheck a closed PR's dashboard hostname and report upstream if it keeps serving. They have no backend, so pages that read the API show the unavailable state. A native backend preview host is the planned follow-up: full-stack per-PR previews on DigitalOcean App Platform (digitalocean/app_actionwithdeploy_pr_preview) so previews rehearse the production platform. - The
dev-mainstage was torn down on 2026-09-26: its Workers, custom domain, route, tunnel, tunnel DNS records, and workstation units are gone.vektor.phibkro.orgis free for a future staging deployment ofmain. The retired Workers (p20 homepage, dashboard and preview worker; p001 homepage and dashboard; the superseded development backend;vektor-migration-docs), their routesvektor.phibkro.org/api/*and/health, and the custom domainsp001,p001-dashboard,p20.vektor.phibkro.organdvector-docs.phibkro.orgwere deleted on 2026-09-26 (operator decision). - Hosted
Testsrun36191536836passed every job at6c812703, including the PostgreSQL 17 lane. TheBrowser journeysmatrix runs everyjust golden,just e2e,just proof, andjust rehearsalname that no other hosted job runs;just layout writegenerates its legs from the justfile, andjust layoutfails on a Playwright spec or acceptance probe that no name runs and no exclusion lists (hosted journeys). Hosted run36219552428passed 16 of its first 17 legs;e2e contactfailed because it served a homepage build it did not make, and its runner now builds one. Hosted run36233895862at74e165efpassed 37 of 39 jobs, including every leg added since. The two hosted-only failures are fixed at their cause:rehearsal organization-importno longer reads Git history (anti-slop/no-git-history), ande2e recommendation-returningasserts the revision that won the person lock. Journey ports come fromreserveLoopbackPorts(anti-slop/no-port-probe).just proof authorization-rules,just e2e recommendation, andjust e2e recommendation-reportare back in the hosted set and pass locally; no hosted run has run them yet. The proof asserts that a replay after rule removal is denied (InactiveActor), because a replay rechecks current authority. Onlye2e recommendation-returningruns the returning assistant journey and its report population. No name hostsrecommendation-check.ts --returning-login-probe(it records a login diagnostic and asserts nothing) orbun run --cwd tools/e2e rehearsal:legacy-person(it reads a private backup).apps/dashboard/app/foldkit/organizationkeeps Team catalog view and load branches that no element mounts since 62a7b94c. - The golden CI gate once failed at
ae5928feafter a dashboard GET returned HTTP 503; a later diagnostic run passed and the cause is unproven. Evidence:/tmp/golden-ci-success-ae5928fe. devenv shellis the toolchain entry: Bun, Node, PostgreSQL, openssl, Chromium, and prek Git hooks;--profile legacy-dataadds MariaDB and the PHP 8.4 CLI for the legacy data rehearsals. CI runs in the same shell; its hosted cost is unmeasured.- Placement drafts take a new applicant's application availability once the interview is conducted, as the legacy scheduler took every interviewed applicant (
packages/database/src/placements/draft.ts). Admission outcomes are now recorded. Decide whether supply follows the Admitted outcome; the system document announces outcomes after allocation. - Drafts show school wishes but do not use them to choose a school (follow-up: use wishes in scoring). Scoring needs one teaching-language registry first:
applications and returning registrations use
AssistantLanguageSchema(packages/domain/src/application/availability.ts), and schoolsSchoolLanguageSchema(packages/domain/src/schools/schema.ts). - The application asks for no school level, and a new applicant names no preferred school: the legacy application asked neither, and the school directory records no level. A level wish needs a school level in Schools first; the operator decides whether to add one.
- Admission outcomes: an interview's
Jaderives Admitted andNeiderives Rejected; Rekruttering decides aKanskjeor returning application in/dashboard/opptaksutfallthrough anadmissions.outcomesdelegation (O8-17), and the department's delegated school coordinator announces each outcome once, which queues one immutable mail from the editable templates. The mail is delivered through the configured HTTP mail transport; no deployed provider has accepted its HTML. - Receipt approval and settlement still also accept the person-keyed Economy grants (
economy_receipt_approval_grants,economy_receipt_settlement_grants, read bypackages/database/src/receipt/authority-postgres.ts). The model makes them delegations, not grants (O8-15). Retire the grants after the receipt seeds and the golden reimbursement journey issue the Økonomi delegations instead. - Membership still gives department-wide reach where the model names no action: content editing (
ContentEditorinpackages/domain/src/content/actor.ts), social-event creation (packages/domain/src/social-events/authority.ts), the schools directory (packages/domain/src/schools/authority.ts), the receipt approver relationship requirement (packages/domain/src/receipt/approval-list.ts), and the recruitmentMemberactor. Decide each case; none gives administration. - Derived board seats (a team leader's seat on Styret or Hovedstyret) are specified, not stored or listed; the certificates slice adds the seat list. A seat confers no administration (Q2) and no global-administrator grant (K3).
- Routes without a department (admission periods, the recruitment board, the interview report) act in the single department of the person's active memberships. Hovedstyret's leader and an organization-wide delegation must name a department there; the requests fail closed.
- The dashboard profile role is a coarse projection with four values;
ROLE_DEPARTMENT_ADMINISTRATORmeans reach beyond one team for some capability, not for every page it shows.
Next
Full migration needs implementation, operational decisions, current-data reconciliation, provider acceptance, and an authorized cutover. The testing roadmap owns journey sequencing: generated sequences and measured execution scaling come next. PGlite performance and full native composition are unmeasured.
Lead handoff
Updated 2026-09-26 at d9f6afca. A new lead resumes from this section, AGENTS.md, and docs/specs/, not from chat or session files.
Agents follow the effect-house overlay (.agents/skills/effect-house/SKILL.md); the homelab projects-tier extension injects it and the instruction chain on first touch.
spike/persisted-queue-outbox-0925 (worktree mono-web-pq-spike-0925) is the starting point of the team-application PersistedQueue pilot the operator chose on 2026-09-26; its landing conditions are in infrastructure ports.
How work runs: one writer per worktree and branch; heavy commands go through just measure, whose machine-wide lock serialises every agent;
the lead lands with just land <branch> and pushes main; hosted CI runs every journey named in the just sets.
Next, in this order:
- Keep hosted CI green; fix any red leg at its cause. Hosted Tests run
36268554218at165aa3eapassed all 42 jobs, including the returnedproof authorization-rules,e2e recommendation, ande2e recommendation-reportlegs; the Effect lint wiring passed Checks run36267629067atcdfd81b4. - Certificates for days served (O8-16), frozen in certificates and days served. It adds the derived Styret and Hovedstyret seat list that the delegation slice specified.
- Retire the receipt person grants: the receipt seeds and the golden reimbursement journey issue the Økonomi delegations instead (O8-15).
- Formatter scope for the frontends; per-domain typed problems and instants; the remaining phases of repository conventions: boundary and reuse rules and the migration onto them, the duplication check after the public-surface slice, then agent docs.
- A reusable migration upgrade-proof recipe. The 72 to 75 proof was a throwaway harness.
The legacy-data devenv profile needs the home binary cache; since the homelab rebuild of 2026-09-26 a dead cache is non-fatal. Changing a root file such as oxlint.config.ts runs no package test in the pre-commit hook (Turbo maps changed files to packages); just check and CI still run them.
Operator steps pending
- Staging deploys run
docker compose downwithout--remove-orphans. After the PostgreSQL 18 change reaches thestagingbranch, rundocker compose down --remove-orphansonce on the staging host to remove the orphanedreceipt-postgrescontainer. BWS_ACCESS_TOKEN(the only preview secret in GitHub) holds the access token of the machine accountvektorprogrammet-ci, which reads Bitwarden project2ddfeed1-59d8-4139-b6b0-b4d1001edcfcin the US cloud; the Cloudflare token is its keyWORKERS_EDIT_CLOUDFLARE_API_TOKEN. Rotate both before they expire.- Before any production use of reach and delegation: classify the Styret and national teams, recognize independent departments, and issue the Økonomi delegations, each by explicit command (see Production gates).
Remaining migration work
| Workstream | Remaining deliverable | Completion gate | Authority |
|---|---|---|---|
| Operational scope | Implement the scope decisions or define an explicit transition process. | Each active core case has a supported journey and a responsible owner. Unresolved policy is not silently waived. | Product decisions for policy; local implementation for defined contracts. |
| Real-source import coverage | Rehearse authorized current data through the combined import boundaries. Define import or handover for pending operational work. | Each occurrence has an evidenced mapping and disposition. Current assignments and authority need current evidence. Receipts need verified private bytes and ownership evidence. | Local implementation. Current production reads need authorization. |
| Current-data reconciliation | Obtain a consistent current snapshot, private file archive, and external-work inventory. Resolve identity, authority, quarantine, and payment evidence. | Rehearse the complete reconciled candidate, not only the historical backup. Verify identity-level accounting and retained private bytes. | Authorized source access and human decisions for ambiguous facts. |
| Portable deployment preparation | On DigitalOcean, select the PostgreSQL service, private storage, mail, and integrations. Configure ingress, secrets, migrations, workers, backups, and alerts. | The exact candidate preserves PostgreSQL locking and private-file custody. Required delivery work has an explicit runner and recovery path. | Local preparation now; provisioning at cutover preparation. |
| Provider acceptance | Exercise real authentication, scoped access, private-file writes and reads, required delivery, restart, retry, revocation, and restore. | Observe the selected providers. Verify deployment limits and recovery. Local capture adapters and frontend previews are not substitutes. | Explicit provider and credential authorization. |
| Cutover and retirement | Reconcile the final delta, fence legacy writers, verify rollback after native writes, and transfer ownership. Retain required archives and retire legacy code. | One authoritative writer; no unexplained delta, lost pending effect, missing required file, or unresolved active case. Required readers and writers no longer depend on PHP. | Separate production authority for transfer, rollback, and retirement. |
The recruitment, password-reset, and receipt delivery workers run under Bun supervision. The delivery guide defines their configuration and recovery. The Bun composition applies migrations and starts configured workers. Deployment acceptance must verify that composition and every required delivery path.
Operational scope decisions
The operator decided these on 2026-09-25. Each becomes one design specification under docs/specs/ before implementation. A decision is scope, not acceptance.
| Area | Decision | Size |
|---|---|---|
| Semesters (P7) | Derive future semesters from the calendar. No manual step. | S |
| Accounts for non-applicants (P8) | Staff invitation that is not bound to an application. | M |
| Global administrators (P9) | Grant and end commands with audit, plus a one-time bootstrap grant at cutover. | S-M |
| Organization upkeep (P10) | Revise and deactivate commands with dashboard screens for departments, teams, positions, and fields of study. Evaluate NTNU or DBH (HK-dir) as the source for fields of study. | M |
| Open recruitment cycle (P11) | Cut over in a quiet window between cycles; create new periods natively. Openings happen in January and August. | constrains date |
| Legacy data without an import path (P12) | Default to a retained, access-controlled read-only archive. Import only where an active workflow needs it. | per cohort |
| Messages (P3) | Implement admission-opening notices with subscription consent and unsubscribe, accept-interview reminders, and staff interview digests. Retire info-meeting notices. Email only; no SMS. | M |
| Team applications (P2) | Add a privacy notice to the form and purge applications after a fixed period (period to be set). Import applications that are open at cutover. | S-M |
| Interview no-show (P4) | A distinct no-show outcome with evidence, then rebook or close. Staff corrections through an admin-only audited command. | S-M |
| Service corrections (P5) | Name the correction cases and authority, then add supersession commands that keep history. | M |
| Mailing and Google Workspace (P1) | Native list administration and automated Workspace synchronization. | M-L |
| Coordinator reports (P6) | A read-only archive export at cutover. Build each report when a named consumer exists. | S-M each |
| Public page text and sponsors (P13) | An editor UI in the dashboard. | M |
| Legacy features (P14) | Keep the team-interest form, in-app feedback, and party/stand screens. Retire Slack notices, the shared file browser, and the changelog. Replace server-side IP geolocation (ipinfo.io) with an optional browser-side location choice; no third-party IP lookup. | varies |
| Parity registers (P15) | Retire the external parity registers. The implementation-agnostic specification and its conformance checks become the parity authority. Done 2026-09-25: tools/parity is deleted; its source-safety rules run as tools/source-safety. | S |
| Team intake owner (D3) | TeamApplications owns intake (open/closed, deadline) with its own revision; it moves off Organization's team row. | M |
| Year of study owner (AD7) | Admissions owns the application's year of study. Done 2026-09-26: the substitute pool, its other writer, is removed. | S |
| Positions (D10) | Positions become managed reference data; appointment free-text titles are mapped once at import. | M |
| Onboarding (D7) | Two aggregates: Recruitment owns the onboarding invitation; Identity owns the one-time account claim. | M |
| Domain event payloads | Identifiers plus closed non-personal values (booleans, instants, revisions, enumerated states). No names, contact details, or free text. | — |
| Credentials (O8-1, O8-2) | Exactly one credential per request; a session cookie with a bearer is rejected. The onboarding claim token is a single-use requirement bound to its target, not a second principal. | S |
| Grants and roles (O8-3) | Ending a global-administrator grant never changes role authority, uniformly. | S |
| Boards (O8-4) | Styret (department scope) and Hovedstyret (national scope) are units like teams; board positions carry capabilities with that reach. A seat implies no global-administrator grant. | M |
| Authority model (O8-5 to O8-10) | Add RosterMember and Applicant role types; scheduling membership is a named requirement; payment destinations become Economy data; contact stays anonymous with its quota; school contacts stay data. | M |
| Reach and delegation (O8-11 to O8-14) | Only Styret positions reach their department; ordinary team leaders act within their team. Department work for a team is an explicit, time-bounded delegation, managed by Styret leadership in its department and by Hovedstyret or a global administrator for national teams. The global-administrator grant keeps its organisational actions for now. Done 2026-09-26: one reach interpreter (packages/domain/src/authz/reach.ts), explicit board, national-team and independence facts (migration reach-and-delegation), the delegation aggregate and the Delegeringer page. | M |
| Package layout | Layer-first packages with CML context folders in every layer; a context's service owns complete commands, locks included. Done 2026-09-26 for Placements: packages/placements folded into packages/domain/src/placements and packages/database/src/placements. | M |
| Substitutes scope (interview) | Substitute is an admission outcome: admitted but unplaced applicants are on call. Record absences and who covered each lesson date; coordination stays in Slack. Remove the offer and dispatch flow. Done 2026-09-26: Admissions records the outcome; Placements records coverage and derives attendance; the pool and offers are removed. | M |
| Placement scheduling (interview) | Port the legacy automated scheduler (weekday availability, bolk, school capacity) as a pure domain function that drafts placements; Skolekoordinering adjusts the draft manually. | M |
| Certificates (operations scan) | In scope. Skolekoordinering records days served per assistant at semester end; Styret generates certificates for its department. | M |
| Surveys (operations scan) | Stay in Google Forms. The system supplies the data Evaluering needs (assistants per bolk, schools) instead of hosting surveys. Done 2026-09-26: the module and its tables are gone (migration retire-native-school-surveys); the placement board read (placements.manage) supplies the data until a delegation gives Evaluering its own read. | S |
| Receipt approval (O8-15) | Any current member of the national Økonomi team approves or rejects receipt claims through a national delegation; only its leader, the finance lead, records settlement through a leaders-only delegation. Done 2026-09-26: both delegations reach the Economy authority projection. | S |
| Certificates by Hovedstyret (O8-16) | Hovedstyret positions issue the certificates of a department that is not independent. | S |
| Admission outcomes (O8-17) | Rekruttering decides admission outcomes through an admissions.outcomes delegation. Done 2026-09-26: the delegation is available; issuing it is an operator step per department. | S |
| Department recognition (2026-09-26) | Recognising a department as independent requires an active Styret board now; a board of an independent department cannot be demoted. Later: a team-function fact on ClassifyTeam (Rekruttering, Skolekoordinering), both teams and 3 appointed Styret seats (derived seats excluded) required; losing one is rejected, not silently withdrawn. | |
| Appointment candidates (2026-09-26) | A manager covering the target unit may appoint any existing Person. Candidates without people.read: the department's team applicants and active affiliations; anyone else only by exact email lookup, which reveals nothing on no match. | |
| Existing-account claim (O8-18) | The existing-account claim is session-only; a bearer cannot make it. Done before 2026-09-26. | S |
The parity map is at (deleted 2026-09-27) (84 capability rows: 27 evidenced, 16 native without evidence, 9 missing, 17 policy). Changelogs, articles, generic events and surveys, certificates, and nonessential statistics are not default cutover gates. An active core obligation cannot disappear under that exclusion. Retained data still needs an explicit archive or migration disposition. Recruitment keeps recommendation, invitation, account claim, affiliation, and placement separate. The admission outcome is a separate recorded decision.
Reconciliation prerequisites
- Obtain an authorized consistent current snapshot and its watermark. Browser observations do not substitute for it.
- Reconcile current identity and mailbox ownership. Correct the known invalid login email with verified evidence.
- Resolve unsupported credentials and legacy aliases. Preserve post-import credentials on replay.
- Reconcile active authority separately from Accounts, historical membership, and volunteer affiliation.
- Obtain receipt file bytes and digests. Apply encrypted account custody with reviewed owner, department, and account evidence.
- Reconcile settlement references separately. A legacy refunded flag is not evidence of payment.
- Inventory external schedules, open cases, active chapters, and pending notifications. Empty screens do not prove an empty workload.
- Reconcile combined teaching blocks and ambiguous membership groups before imposing legacy uniqueness assumptions.
- Define a final-delta strategy. Exact replay refuses changed source; rerunning the initial importer is not incremental migration.
Architecture work
Close remaining service boundaries through a concrete operational journey, not a repository-wide rewrite.
The Economy boundary is the precedent for a complete command and a schema-derived query.
For each new journey, create one active contract under docs/specs/. Remove it after acceptance.
Production gates
Before cutover:
- classify the legacy Styret teams and the national teams, and recognize independent departments, by explicit command (O8-11). Until then no imported team leader reaches a department. Compared with legacy, a leader of an ordinary team in department D without a board leadership or delegation loses: admission periods and outcome decisions of D; interview assignment, staffing, the full board, and the report of D; placement coordination and applicant account invitations of D; school administration of D; appointments and team-interest registrations of D's other teams; D's people directory and mailing recipients; publishing, unpublishing, pinning, and revising others' articles in D; and the pages "Fullførte intervjuer", "Intervjubemanning", "Søkerkontoer", "Attester", "Linjer", and the applicants board. Tell department leaders before cutover;
- close required operational outcomes or obtain an approved, owned transition process;
- reconcile identities, credentials, authority, recruitment, placements, claims, private files, and pending effects against the current source;
- resolve or explicitly disposition each quarantine, unsupported credential, alias, and missing evidence item;
- verify the Bun deployment, PostgreSQL locks, required delivery providers, private storage, and separately authorized settlement evidence;
- qualify sustained operation, worker supervision, failure alerts, backup, restore, and credential hashing limits on the selected host;
- choose and rehearse a final-delta method;
- fence legacy writers and external schedules before native ownership starts;
- verify rollback after native writes, including native-only facts and pending external effects;
- obtain explicit operator authority for production transfer and each external or destructive action.
After transfer, verify that all required readers and writers use the native system. Retire PHP and obsolete provider composition only after those dependencies and required archives have an accepted disposition. Team membership never stands in for volunteer affiliation. A recommendation never stands in for an explicit coordinator outcome.