Seed data: demo-en and demo-zh - #19
Draft
os-sam wants to merge 1 commit into
Draft
Conversation
Seed data is the demo: the kanban funnel, the interview calendar, the review queues and the isolation model are all driven from these rows. One locale-independent skeleton (src/data/shared) is rendered through two locale packs, so demo-zh mirrors demo-en row for row by construction: 60 skills, 15 credential types, 12 employers (2 pending, 1 suspended), 40 jobs (6 pending_review, 4 featured), 80 candidates, 200 applications distributed exactly applied 88 / screening 46 / interview 28 / offer 14 / hired 9 / rejected 15, 40 interviews over the next 14 days, 14 offers (3 pending_approval), 30 credentials (5 expiring within 90 days), 6 reports, 1 admin + 1-2 recruiters per employer. Why the platform identity rows are seeded too: every employer-side policy is `employer_org IN (current_user.accessible_org_ids)`, which is derived from sys_member, so each employer gets a sys_organization, its staff get sys_user and sys_member rows, and the persona is granted through sys_user_position (employer_admin / employer_recruiter / job_seeker only). Without them an employer shows zero rows to its own staff. Why every date is CEL: `daysAgo(n)` / `daysFromNow(n)` (+ `duration` for a wall-clock hour) are evaluated against the loader's pinned `now`, so the calendar still shows next week a month after the build and two builds stay byte-identical. The denormalised fields (display_name mirrors, employer, employer_org, candidate_user) are written explicitly rather than left to the stamp hooks, as the card requires. avatar stays empty: no hot-linked placeholders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG
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.
Fixes #5
What changed
Two demo seed sets, one schema, selected by
OS_SEED_LOCALE(defaulten):src/data/shared/— the locale-independent half.skeleton.ts(hand-authored structure: employers, jobs, credential types, credentials, reports),pipeline.ts(the generated, literal 200-row pipeline plus candidate attributes; produced once by a seeded generator and pasted so the source carries no randomness and no clock),build.ts(pure builders: skeleton × locale pack → typeddefineSeed()records),sys-objects.ts(minimal typed shapes for the four platform identity tables the seed writes),pack.ts(the locale pack contract).src/data/demo-en/andsrc/data/demo-zh/— one*.seed.tsper object (generated from a single template so the two locales cannot drift) plus the localepack.tswith every human string.demo-zhmirrorsdemo-enrow for row: the compiled artifacts differ only in strings and in the candidate phone prefix (+1vs+86); every enum value, number, index and CEL date is byte-identical between the two.src/data/index.ts— readsOS_SEED_LOCALEthrough a typedglobalThiscast (the app has no Node typings) and exportsdata.objectstack.config.ts— exactly two lines: the./src/data/index.jsimport and thedata,key. Nothing else in the file was touched (Employer views: pipeline kanban, inbox, interview calendar, talent pool #2 addsviews:, Approval flows F1–F3 + theautomationcapability #6 addsflows:/requires:).Counts, measured on the compiled artifact of both locales and again through REST on a booted server
ats_skillats_credential_typevalidity_months(12–60), 8 with levelsats_employerats_employer_memberdisplay_namemirroredats_jobats_candidateavataremptyats_candidate_credentialats_application(job, candidate)unique; only on published/paused/closed jobs; closed jobs carry only terminal stagesats_interviewscheduled,daysFromNow(1..14) + duration("9h".."16h30m"); measured 1.31–14.62 days out at bootats_offerats_reportsys_organizationats-SLUG, idorg_ats_SLUG(whatemployer_orgpoints at)sys_usersys_memberadmin/membergrade)sys_user_positionemployer_admin×12 ·employer_recruiter×18 ·job_seeker×80; no platform positions grantedEvery date is dynamic CEL (
daysAgo(n),daysFromNow(n),daysFromNow(n) + duration(...)); an audit over both artifacts asserts that no date-typed field carries a fixed value, that every natural-key reference resolves inside the seed, and thatdisplay_name/employer/employer_org/candidate_userare consistent with the rows they denormalise.Gates (commit a59f716)
Boot verification — read this carefully
A clean
pnpm devon this branch does NOT load the ATS rows onorigin/main, for two reasons that are outside this card:ats_*objects are tenant-scoped by default: system-context seed writes are refused once more than one organization exists — declaretenancy: { enabled: false }(ADR-0066) #16 (new) — everyats_*object is tenant-scoped by default; once the seed's 12 employer organizations exist, a system-context write cannot derive an organization and is refused (Insert on 'ats_employer' was REFUSED ... declare tenancy: { enabled: false } (ADR-0066)). Measured: dictionaries andsys_*rows load (339), all 452ats_*rows are refused.skipTriggersdoes not cover app hooks): 31ats_candidate_credential_stampthrows were logged during the seed load.Verified with a local, uncommitted override artifact (compiled artifact with
tenancy: { enabled: false }on the 11ats_*objects andhooks: [], booted viapnpm dev --fresh --artifact dist/objectstack.json), for both locales:Seeds: ats 791 rows, zero refusals, zero hook throws;pending_approvaloffers, 40 interviews all in the future window,is_expiring === trueon exactly 5 credentials,employer_org/candidate_userpersisted,can_publishtrue on the 9 verified employers;OS_SEED_LOCALE=zhboots the Chinese set with identical numbers (张建国 → 生产主管,杭州办公室 3 号会议室,药剂技师 · 成都).Isolation acceptance ("two employer accounts see disjoint pipelines") — blocked by #18 (new). Passwords were set for the seeded
admin@quillstone.exampleandadmin@harborline.examplevia the auth admin API and both signed in; better-auth selected their employer organization as the active one and the request context carriedpositions: [..., "employer_admin"]andpermissions: [..., "ats_employer_admin"]— i.e. the seeded membership → position → permission-set chain resolves end to end. The reads still return zero because the M1 policies are spelled in the deprecated SQL-styleIN (current_user.accessible_org_ids)and the bridge cannot bind the variable (DENY (fail closed) ... variable "current_user.accessible_org_ids" is undefined). With the canonical CEL spelling the expected numbers are Quillstone 5 jobs / 27 applications / 2 offers and Harborline 5 / 31 / 2, disjoint.Recipe to reproduce the override boot: compile (
pnpm exec objectstack compile), sethooksto[]andobjects[i].tenancy = { enabled: false }forats_*indist/objectstack.json, thenpnpm dev --fresh --port 4106 --artifact dist/objectstack.json.Decisions taken inside the card (strike any you disagree with)
sys_organization,sys_user,sys_member,sys_user_position), as the issue body asks:accessible_org_idsis derived fromsys_member, so an employer without an organization shows zero rows to its own staff. The write path is legitimate — the seed loader writes as the system, the same path the platform's default-organization bootstrap uses; the identity write guard only refuses user-context writes. The shapes are typed through local{ name, fields }literals rather than a new dependency on@objectstack/platform-objects.access_level admin → employer_admin,recruiter → employer_recruiter, candidates →job_seeker. The two platform staff stay directory rows (they exist soats_report.handled_byresolves); the dev admin remains the platform persona.offerbecause an offer exists). The 9 hired applications carry no offer row because the count is pinned at 14; if the dashboards (Three dashboards and their datasets #8) would rather see accepted offers on hired rows, that is a five-line change inpipeline.ts.ats_report.target_refstores the target's natural key (title / name / display name): a seed cannot know engine-minted ids. Seeats_report.target_refis free text: the polymorphic pointer cannot be resolved by the seed loader or linked in the UI #17.avataris empty, per the card and the issue body. DESIGN.md §06 says candidates come 「带头像」; the card overrides it and there is no asset pipeline to seed an image without a hot link — flagged in the report as a §06 amendment.src/data/index.ts; switch on a fresh database.no_sign_in_account_at_bootERROR line seen on every--freshboot is a platform ordering artifact (it fires before the dev admin is provisioned) and is not caused by the seed.Out-of-scope findings (filed, not fixed here)
ats_*objects are tenant-scoped by default: system-context seed writes are refused once more than one organization exists — declaretenancy: { enabled: false }(ADR-0066) #16 —ats_*objects needtenancy: { enabled: false }(blocks the boot acceptance of this card)IN (current_user.accessible_org_ids)is the deprecated SQL-style spelling and the bridge cannot bind the variable #18 — employer-side RLS policies fail closed for everyone: deprecatedIN (...)spelling (blocks the isolation acceptance)ats_report.target_refis free text: the polymorphic pointer cannot be resolved by the seed loader or linked in the UI #17 —ats_report.target_refcannot be resolved as a pointerats_employer_memberhas noemployer_orgfield, yet the member stamp hook writes it and two row-level policies read it #14 (already filed by a sibling) —ats_employer_memberhas noemployer_orgfield the policy and hook reference; the seed cannot fill a field the object does not declarehooks: []the identical seed loads cleanSuggested landing order: #16 and #10 first, then this PR boots clean on
main; the RLS fix completes the isolation acceptance.🤖 Generated with Claude Code
https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG
Generated by Claude Code