-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathobjectstack.config.ts
More file actions
155 lines (141 loc) · 7 KB
/
Copy pathobjectstack.config.ts
File metadata and controls
155 lines (141 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects/index.js';
import { data, AtsDemoSeedGatePlugin } from './src/data/index.js';
import * as views from './src/views/index.js';
import { allHooks } from './src/hooks/index.js';
import * as flows from './src/flows/index.js';
import * as actions from './src/actions/index.js';
import { AtsApp } from './src/apps/index.js';
import * as dashboards from './src/dashboards/index.js';
import * as datasets from './src/datasets/index.js';
import { AtsTranslations } from './src/translations/index.js';
import {
PlatformAdminPosition,
PlatformOpsPosition,
EmployerAdminPosition,
EmployerRecruiterPosition,
JobSeekerPosition,
PlatformAdminSet,
PlatformOpsSet,
EmployerAdminSet,
EmployerRecruiterSet,
JobSeekerSet,
registerAtsPositionBindings,
AtsRlsMembershipResolverPlugin,
AtsAuthMembershipPolicyPlugin,
} from './src/security/index.js';
/**
* ATS — an open-source recruiting marketplace on ObjectStack.
*
* Multi-employer job posting, candidate pipeline, interviews, offers and
* platform governance, all as typed metadata. See DESIGN.md for the model.
*/
export default defineStack({
manifest: {
id: 'ats',
namespace: 'ats',
version: '0.1.0',
type: 'app',
name: 'ATS',
description: 'Open-source recruiting marketplace — employers post, candidates apply, the platform governs.',
// Protocol major this app is authored against. The runtime checks the
// range at load time and refuses a major-incompatible runtime with a
// structured diagnostic instead of failing deep in a schema parse.
engines: { protocol: '^17' },
},
// `ui` serves the Console so the app can be browsed as soon as it boots.
// The approval flows (F1–F3, src/flows/) need four tokens, not one:
// `automation` is the flow engine; `triggers` arms their `record_change`
// start nodes (defineStack refuses a triggered flow without it);
// `approvals` contributes the `approval` node executor; `messaging` delivers
// the `notify` nodes to the inbox (absent, notify reports success and
// delivers nothing). All four providers ship with the CLI — no new package.
requires: ['ui', 'automation', 'triggers', 'approvals', 'messaging'],
// Locales (card 14). `en` is the language of every inline label in src/ and
// the fallback; `zh-CN` is the second locale, authored in
// src/translations/zh-CN.ts with DESIGN.md's vocabulary. The list is a
// contract `pnpm lint` enforces: `objectstack lint --i18n-strict` fails on
// any translatable key that lacks one of these locales, so a new field or
// option lands with its Chinese label or not at all. Add a locale here and
// lint tells you every string it needs.
i18n: { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'], fallbackLocale: 'en' },
translations: [AtsTranslations],
objects: Object.values(objects),
// Data — the demo seed; `OS_SEED_LOCALE` selects demo-en (default) or demo-zh.
// Scoped to dev/test: a production boot loads none of it, logins included
// (src/data/demo-seed-gate.ts, #42).
data,
// UI — the views each app navigates to (card 07 employer, card 08 platform/seeker).
views: Object.values(views),
// Logic — the stamps that make row-level scoping resolvable (see stamp.hook.ts).
hooks: allHooks,
// Automation — the approval chains F1–F3, the stage notification F4 and the
// scheduled reminders F5–F6 (DESIGN.md §05). F5/F6 are `schedule`-type flows:
// `triggers` binds them onto the job service, which `approvals` above already
// pulls in (`serve` prepends `job` + `queue` for it) — so nothing is added to
// `requires`, and removing `approvals` would silently unschedule them.
flows: Object.values(flows),
// Actions — inquiry triage (convert / reject / spam, declarative single-row
// updates written AS THE CALLER) and the job page's public apply link.
actions: Object.values(actions),
// Analytics — the semantic layer (ADR-0021) and the three dashboards over it
// (card 13, DESIGN.md §04). Widgets bind datasets and measures BY NAME;
// `validate` refuses a binding that resolves to nothing, because at runtime
// it would render an empty tile indistinguishable from "no data yet".
datasets: Object.values(datasets),
dashboards: Object.values(dashboards),
// The one app: three audience groups (Platform · Hiring · Job Seeker), each
// gated by a capability the permission sets grant (DESIGN.md §04). An
// 'app' package may define at most one app (ADR-0019 D3).
apps: [AtsApp],
// Security — capability containers plus the row- and field-level scopes.
positions: [
PlatformAdminPosition,
PlatformOpsPosition,
EmployerAdminPosition,
EmployerRecruiterPosition,
JobSeekerPosition,
],
permissions: [
PlatformAdminSet,
PlatformOpsSet,
EmployerAdminSet,
EmployerRecruiterSet,
JobSeekerSet,
// No guest set: the anonymous public form is authorised by the
// route-derived `publicFormGrant`, not by a permission set (#32; see the
// header of permission-sets.ts).
],
// Runtime — the app-owned RLS membership resolver that makes the employer
// policies above resolvable (`current_user.employer_org_ids`, DESIGN.md §03).
// A kernel plugin rather than an `onEnable` call on purpose: plugin-security
// reads the resolver once in its `start()`, before the app's `onEnable`
// runs, so only a plugin's `init()` (Phase 1) lands the service in time.
// In-repo code, no new package (see rls-membership-resolver.ts).
//
// The membership-policy plugin declares `membershipPolicy: 'invite-only'` on
// the live AuthManager, also in `init()` and for the same phase reason: the
// ADR-0093 D6 backfill runs at `kernel:ready`/`app:seeded`, and on the
// platform default (`auto`) it binds every member-less user — the 80 seekers
// and 2 platform staff — to the Default Organization, which DESIGN.md §03
// forbids (#38; see auth-membership-policy.ts).
//
// The demo-seed gate plugin only logs: one line per boot saying whether the
// demo seed (and its 7 logins) loads under this NODE_ENV, and what toggles
// it — warn when skipped, because the CLI's default log level hides info.
plugins: [AtsRlsMembershipResolverPlugin, AtsAuthMembershipPolicyPlugin, AtsDemoSeedGatePlugin],
});
/**
* Declaring a position and a permission set grants nobody anything until a
* `sys_position_permission_set` row joins them, and that row cannot be a seed
* (the seed loader runs before the security bootstrap creates the rows it
* would reference). Bind them on `kernel:bootstrapped` instead.
*
* Not the place for the RLS membership resolver: `onEnable` runs inside
* AppPlugin's `start()`, after plugin-security has already looked the resolver
* up (measured on cli 17.3.0 — registered here, never seen). It is declared
* under `plugins` above so it registers in Phase 1.
*/
export const onEnable = async (ctx: unknown): Promise<void> => {
registerAtsPositionBindings(ctx as Parameters<typeof registerAtsPositionBindings>[0]);
};