-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathobjectstack.config.ts
More file actions
221 lines (211 loc) · 12.2 KB
/
Copy pathobjectstack.config.ts
File metadata and controls
221 lines (211 loc) · 12.2 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects/index.js';
import { allHooks } from './src/objects/hooks.js';
import * as actions from './src/actions/index.js';
import { allFlows, flowFunctions } from './src/flows/index.js';
import * as profiles from './src/profiles/index.js';
import { ClmPositions, ClmSharingRules } from './src/sharing/index.js';
import { registerClmPositionBindings, type BindHostContext } from './src/security/index.js';
import { clmSeeds } from './src/data/index.js';
import * as apps from './src/apps/index.js';
import * as datasets from './src/datasets/index.js';
import * as dashboards from './src/dashboards/index.js';
import * as pages from './src/pages/index.js';
import * as views from './src/views/index.js';
import * as translations from './src/translations/index.js';
/**
* HotCLM — contract lifecycle management on ObjectStack.
*
* Intake, clause playbook, approval matrix, sealing and e-signature,
* obligations and payment schedules, renewals and archive — all as typed
* metadata. See DESIGN.md for the model; docs/backlog/ for what lands next.
*/
export default defineStack({
manifest: {
id: 'app.objectstack.hotclm',
namespace: 'clm',
version: '0.1.0',
type: 'app',
name: 'HotCLM',
description: 'Contract lifecycle management — intake, review, approval, execution, obligations and archive. Global by default, AI-assisted under governance.',
// 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; `auth` mounts the login surface and the platform
// bootstrap that creates `sys_organization`. Both are required for the app to
// be *browsable at all* — measured, not assumed: with `ui` alone the boot
// logs "System started with degraded capabilities. Missing core services:
// auth", the sharing seeder cannot enumerate organizations ("no such table:
// sys_organization"), and the Console has no way to sign in. DESIGN.md §11
// already prescribed `auth`; this is the implementation catching up, not a
// capability expansion.
//
// `sharing` seeds the §04 sharing rules into `sys_sharing_rule` and
// materialises their grants (card 04).
//
// `hierarchy-security` is the one enterprise-edition capability this app
// declares. `clm_requester` authors `readScope` / `writeScope:
// 'own_and_reports'` on `clm_contract` — the `contract_manager_reports` row
// of DESIGN.md §04 and §13 Q2 — which is an ADR-0057 HIERARCHY scope
// resolved by a service that ships only in `@objectstack/security-enterprise`.
// Measured on `@objectstack/spec` 17.3.0 (card 04): `defineStack` refuses the
// grant outright without the token ("uses readScope='own_and_reports', a
// HIERARCHY scope. Declare `requires: ['hierarchy-security']`"), so the pair
// is one declaration — move them together or not at all.
//
// Safe on an OPEN-edition boot: the token is in the platform's known
// vocabulary, so the capability resolver takes its deliberate "stay quiet"
// branch (no warning, no abort — HotCRM's config note measured the same
// against the CLI's serve command). What the open edition then delivers is
// the EDITION BOUNDARY: with no resolver present the scope fails CLOSED to
// owner-only, so a requester reads exactly their own contracts and a manager
// does not reach their reports' — the M1 acceptance measurement is taken on
// that edition. On the enterprise edition the same metadata widens to the
// manager chain. The app states what it MEANS; the edition supplies it.
//
// `automation` is the flow engine: it registers `flows`, runs the
// `contract_intake` screen flow (F1, card 05) and resolves `functions`.
// Measured as required on 17.3.0: without the token the flow-typed
// `launch_contract` action is refused at dispatch (503, "flow action
// unavailable"), because the automation service is never resolved.
//
// `approvals` is the Approval NODE (ADR-0019): `@objectstack/plugin-approvals`
// registers the executor that opens `sys_approval_request`, holds the record
// lock, mirrors the decision onto `approval_status` and resumes the run down
// the matching branch label. MEASURED on 17.3.0, both directions, each boot
// from a freshly built artifact (a stale `dist/objectstack.json` carries the
// OLD `requires` and answers this question wrong): declared ⇒
// `ApprovalsServicePlugin` is in the boot roster (36 plugins); omitted ⇒ it
// is absent (35), and the ladder's first rung dies with the flow-run error
// `No executor registered for node type 'approval'`. That failure is
// invisible to the caller — the submitting PATCH still answers 200 and the
// contract sits in `in_approval` with `approval_status: not_required`, no
// `sys_approval_request` row, no lock, no approver; only the run history
// and the server log carry it — which is exactly why the token is declared
// rather than discovered.
//
// `messaging` backs the `notify` node (ADR-0012) — F5 tells the contract
// owner about both terminal outcomes, F7 names the missing execution
// formality to the legal owner. Measured the same way, and the answer cuts
// the other way: with `messaging` REMOVED from this list,
// `MessagingServicePlugin` still loads and the notify nodes still deliver.
// It is in `PLATFORM_ALWAYS_ON_CAPABILITIES`, which `serve` appends for
// every non-`minimal` preset, so no gate here has teeth. It is declared
// because the app must state what it MEANS — a runtime that does not carry
// that always-on slate degrades the notify node to a logged no-op — not
// because anything caught its absence.
//
// `triggers` was the token card 05's note deferred to "the first card that
// authors a flow a trigger launches (F5 in card 06)" — and this is that
// card. It installs the record-change PROVIDER that binds a `record_change`
// flow to the ObjectQL lifecycle hooks. Not optional and not silent:
// `defineStack` REFUSES the stack without it, once per flow — "flow
// 'contract_approval' declares a 'record_change' trigger but `requires` does
// not include 'triggers' — no 'record_change' trigger would be registered,
// so the flow would never auto-launch" (measured: `pnpm validate` exits 1
// with three such issues). `approvals` additionally pulls `job` + `queue`
// in ahead of itself, so its SLA escalation has durable scheduling.
//
// `analytics` is the ADR-0021 semantic layer DESIGN.md §09's four datasets
// and three dashboards run on. MEASURED on 17.3.0 before it was declared,
// because the question the card asked first was whether it is
// enterprise-gated the way `hierarchy-security` is. It is not, on either
// count:
//
// - `PLATFORM_CAPABILITY_PROVIDERS.analytics` is
// `{ package: '@objectstack/service-analytics', edition: 'open' }` — the
// open edition, where `hierarchy-security` reads
// `{ package: '@objectstack/security-enterprise', edition: 'enterprise' }`.
// - It needs no declaration to load. `analytics` is a member of
// `PLATFORM_ALWAYS_ON_CAPABILITIES`, so `serve` appends it for every
// non-`minimal` preset: `AnalyticsServicePlugin` was already in the boot
// roster (36 plugins) on `main` with this list unchanged, and
// `POST /api/v1/analytics/dataset/query` already answered.
//
// So this token has no gate behind it, exactly like `messaging` above, and
// it is declared for the same reason: the app must state what it MEANS. A
// runtime that does not carry the always-on slate serves no dataset at all,
// and every widget on all three dashboards would render its error state.
//
// The rest arrive with the cards that need them. Capability expansion stays
// tight — a card names the token it adds (AGENTS.md).
requires: ['ui', 'auth', 'sharing', 'hierarchy-security', 'automation', 'triggers', 'approvals', 'messaging', 'analytics'],
// Internationalization (DESIGN.md §01 全球优先, card 11). English is the
// DEFAULT and the SOURCE language — every label is authored in English inline
// on the metadata and `en` is the locale the inline strings ARE. `zh-CN` is a
// complete second bundle, not a partial one.
//
// ⚠️ `fallbackLocale` is what makes this block dangerous as well as useful: a
// key missing from `zh-CN` renders its English source string SILENTLY — no
// error, no warning, no log line, and nothing on screen that distinguishes a
// gap from a translation. `objectstack lint` will not fail on it either
// (a non-default-locale miss is a WARNING there; measured — see
// `scripts/check-lint-i18n-gate.mjs`). `pnpm lint:i18n-gate` is the only
// instrument that fails the build on one, which is why removing that script
// silently unpicks this whole block.
//
// The three keys below are the whole of `TranslationConfigSchema`. Four more
// were removed at protocol 17 (`fileOrganization`, `messageFormat`,
// `lazyLoad`, `cache`) because no runtime read them; the schema now rejects
// them by name rather than dropping them, so this block cannot silently grow
// a setting that does nothing.
i18n: {
defaultLocale: 'en',
supportedLocales: ['en', 'zh-CN'],
fallbackLocale: 'en',
},
// One bundle carrying both locales — `translations` is an array of bundles,
// and a bundle is `Record<locale, TranslationData>`.
translations: Object.values(translations),
objects: Object.values(objects),
apps: Object.values(apps),
views: Object.values(views),
pages: Object.values(pages),
// Analytics (DESIGN.md §09, card 10). `datasets` is the ONE semantic layer
// (ADR-0021): a dashboard widget names a dataset and picks its dimensions
// and measures by name, so "contract value" is defined once and means the
// same number on every surface. There are no hand-written cubes.
datasets: Object.values(datasets),
dashboards: Object.values(dashboards),
// Lifecycle hooks (numbering, type-derived stamps, the state machines and
// the display_name mirrors). A metadata `Hook` is only registered from
// here — `hooks` is a top-level stack key, not an object key.
hooks: allHooks,
// Automation (DESIGN.md §06, cards 05 + 06): the intake screen flow (F1) and
// the action that launches it, the approval ladder (F5), and the
// signature/execution check (F7) with its insert-time twin. The routing (F2)
// and the deviation gate (F6) are hooks above — F5 READS the `route_*` flags
// F2 stamps and never re-derives them. `functions` holds the callables a
// `script` node names by string, each pure: the intake refusal that fails a
// run with a message, the routing-flag normaliser, and the
// execution-formalities comparison.
flows: allFlows,
actions: Object.values(actions),
functions: flowFunctions,
// Security (DESIGN.md §04, card 04): seven positions, five permission sets,
// the sharing rules. Positions and sets are containers — the join rows that
// make a position grant anything are written by `onEnable` below.
positions: ClmPositions,
permissions: Object.values(profiles),
sharingRules: ClmSharingRules,
// The demo dataset (DESIGN.md §10, card 08). EMPTY unless `CLM_DEMO_SEED`
// asked for it at compile time — `pnpm demo` sets it, `pnpm dev` does not,
// and `src/data/index.ts` states why a sellable standard product does not
// install somebody else's contract book into every fresh deployment.
// `OS_SEED_LOCALE` chooses which language the fixture is written in.
data: clmSeeds,
});
/**
* 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 — the shape
* ATS ships. A module-level named export, which the CLI preserves beside the
* default `defineStack()` export on the config-load path and grafts back onto
* the app bundle on the artifact-boot path.
*/
export const onEnable = async (ctx: unknown): Promise<void> => {
registerClmPositionBindings(ctx as BindHostContext);
};