@mj-biz-apps/contracts-core-entities-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +183 -0
  2. package/dist/ContractEntityServer.d.ts +352 -0
  3. package/dist/ContractEntityServer.d.ts.map +1 -0
  4. package/dist/ContractEntityServer.js +729 -0
  5. package/dist/ContractEntityServer.js.map +1 -0
  6. package/dist/ContractTemplateEntityServer.d.ts +39 -0
  7. package/dist/ContractTemplateEntityServer.d.ts.map +1 -0
  8. package/dist/ContractTemplateEntityServer.js +99 -0
  9. package/dist/ContractTemplateEntityServer.js.map +1 -0
  10. package/dist/ContractTemplateModificationEntityServer.d.ts +119 -0
  11. package/dist/ContractTemplateModificationEntityServer.d.ts.map +1 -0
  12. package/dist/ContractTemplateModificationEntityServer.js +242 -0
  13. package/dist/ContractTemplateModificationEntityServer.js.map +1 -0
  14. package/dist/ContractTemplateProvisionEntityServer.d.ts +74 -0
  15. package/dist/ContractTemplateProvisionEntityServer.d.ts.map +1 -0
  16. package/dist/ContractTemplateProvisionEntityServer.js +171 -0
  17. package/dist/ContractTemplateProvisionEntityServer.js.map +1 -0
  18. package/dist/ContractTypeEntityServers.d.ts +28 -0
  19. package/dist/ContractTypeEntityServers.d.ts.map +1 -0
  20. package/dist/ContractTypeEntityServers.js +65 -0
  21. package/dist/ContractTypeEntityServers.js.map +1 -0
  22. package/dist/SupersedeOperation.d.ts +53 -0
  23. package/dist/SupersedeOperation.d.ts.map +1 -0
  24. package/dist/SupersedeOperation.js +112 -0
  25. package/dist/SupersedeOperation.js.map +1 -0
  26. package/dist/delete-guard.d.ts +89 -0
  27. package/dist/delete-guard.d.ts.map +1 -0
  28. package/dist/delete-guard.js +107 -0
  29. package/dist/delete-guard.js.map +1 -0
  30. package/dist/index.d.ts +15 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +49 -0
  33. package/dist/index.js.map +1 -0
  34. package/package.json +36 -0
package/LICENSE ADDED
@@ -0,0 +1,183 @@
1
+ Business Source License 1.1
2
+
3
+ License text copyright (c) 2024 MariaDB plc, All Rights Reserved.
4
+ "Business Source License" is a trademark of MariaDB plc.
5
+
6
+ -----------------------------------------------------------------------------
7
+
8
+ Parameters
9
+
10
+ Licensor: Blue Cypress, Inc.
11
+
12
+ Licensed Work: MemberJunction.
13
+ The Licensed Work is (c) 2023-2026 Blue Cypress, Inc.
14
+
15
+ Additional Use Grant: Subject to the terms of this License, Licensor grants
16
+ you the following additional rights to make Production
17
+ Use of the Licensed Work.
18
+
19
+ 1. Internal Use
20
+
21
+ You may make production use of the Licensed Work for
22
+ your own internal business or organizational operations.
23
+
24
+ 2. Nonprofit Use
25
+
26
+ If you are a Nonprofit, you may make production use of
27
+ the Licensed Work for the operations and activities of
28
+ your Organizational Family.
29
+
30
+ 3. MemberJunction Certified Program Use
31
+
32
+ If you are authorized by Licensor under the
33
+ MemberJunction Certified Program to provide professional
34
+ services using the Licensed Work, you may make
35
+ production use of the Licensed Work in providing such
36
+ professional services to a client, provided that:
37
+
38
+ (a) the Licensed Work is deployed in, and the applicable
39
+ production use occurs within, an environment owned,
40
+ leased, licensed, subscribed to, or otherwise controlled
41
+ by that client; and
42
+
43
+ (b) the production use is for that client's own internal
44
+ business or organizational operations or is otherwise
45
+ independently permitted to that client under this
46
+ Additional Use Grant.
47
+
48
+ 4. Definitions Applicable to the Additional Use Grant
49
+
50
+ "Affiliate" means, with respect to a specified Person,
51
+ any other Person that directly or indirectly Controls,
52
+ is Controlled by, or is under common Control with such
53
+ specified Person.
54
+
55
+ "Control" (including the terms "Controls," "Controlled
56
+ by," and "under common Control with") means the direct
57
+ or indirect possession of the power to direct or cause
58
+ the direction of the management and policies of a
59
+ Person, whether through ownership of voting interests,
60
+ by contract, or otherwise.
61
+
62
+ "Organizational Family" means, with respect to a Person,
63
+ (a) such Person and its Affiliates, and (b) any
64
+ nonprofit organization, governmental entity, chapter,
65
+ division, local affiliate, regional affiliate, state
66
+ affiliate, national affiliate, or other entity that is
67
+ formally affiliated with such Person through governing
68
+ documents, a charter, bylaws, a membership agreement, or
69
+ another written organizational instrument, and is
70
+ recognized under such documents as part of the same
71
+ organizational structure.
72
+
73
+ "Nonprofit" means a Person recognized by the Internal
74
+ Revenue Service as exempt from federal income taxation
75
+ under Section 501(c)(3), 501(c)(4), 501(c)(5), or
76
+ 501(c)(6) of the Internal Revenue Code, or a foreign
77
+ organization recognized under substantially equivalent
78
+ laws.
79
+
80
+ A Person claiming eligibility as a Nonprofit shall, upon
81
+ Licensor's reasonable request, provide documentation
82
+ reasonably sufficient to demonstrate that it qualifies
83
+ as a Nonprofit. If such Person materially misrepresents,
84
+ or is unable to demonstrate, its qualification as a
85
+ Nonprofit, the rights granted to such Person under
86
+ Section 2 of this Additional Use Grant shall terminate.
87
+
88
+ "MemberJunction Certified Program" means Licensor's
89
+ then-current program for certifying and authorizing a
90
+ Person to provide professional services using the
91
+ Licensed Work.
92
+
93
+ "Person" means any individual, corporation, limited
94
+ liability company, partnership, association, nonprofit
95
+ organization, governmental entity, or other legal or
96
+ organizational entity.
97
+
98
+ Change Date: Four (4) years from the date the Licensed Work is first
99
+ made available.
100
+
101
+ Change License: MIT License.
102
+
103
+ For information about alternative licensing arrangements for the Licensed
104
+ Work, please contact Blue Cypress, Inc.
105
+
106
+ -----------------------------------------------------------------------------
107
+
108
+ Terms
109
+
110
+ The Licensor hereby grants you the right to copy, modify, create derivative
111
+ works, redistribute, and make non-production use of the Licensed Work. The
112
+ Licensor may make an Additional Use Grant, above, permitting limited
113
+ production use.
114
+
115
+ Effective on the Change Date, or the fourth anniversary of the first publicly
116
+ available distribution of a specific version of the Licensed Work under this
117
+ License, whichever comes first, the Licensor hereby grants you rights under
118
+ the terms of the Change License, and the rights granted in the paragraph
119
+ above terminate.
120
+
121
+ If your use of the Licensed Work does not comply with the requirements
122
+ currently in effect as described in this License, you must purchase a
123
+ commercial license from the Licensor, its affiliated entities, or authorized
124
+ resellers, or you must refrain from using the Licensed Work.
125
+
126
+ All copies of the original and modified Licensed Work, and derivative works
127
+ of the Licensed Work, are subject to this License. This License applies
128
+ separately for each version of the Licensed Work and the Change Date may vary
129
+ for each version of the Licensed Work released by Licensor.
130
+
131
+ You must conspicuously display this License on each original or modified copy
132
+ of the Licensed Work. If you receive the Licensed Work in original or
133
+ modified form from a third party, the terms and conditions set forth in this
134
+ License apply to your use of that work.
135
+
136
+ Any use of the Licensed Work in violation of this License will automatically
137
+ terminate your rights under this License for the current and all other
138
+ versions of the Licensed Work.
139
+
140
+ This License does not grant you any right in any trademark or logo of
141
+ Licensor or its affiliates (provided that you may use a trademark or logo of
142
+ Licensor as expressly required by this License).
143
+
144
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
145
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
146
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
147
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
148
+ TITLE.
149
+
150
+ MariaDB hereby grants you permission to use this License's text to license
151
+ your works, and to refer to it using the trademark "Business Source License",
152
+ as long as you comply with the Covenants of Licensor below.
153
+
154
+ -----------------------------------------------------------------------------
155
+
156
+ Covenants of Licensor
157
+
158
+ In consideration of the right to use this License's text and the "Business
159
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
160
+ other recipients of the licensed work to be provided by Licensor:
161
+
162
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
163
+ or a license that is compatible with GPL Version 2.0 or a later version,
164
+ where "compatible" means that software provided under the Change License
165
+ can be included in a program with software provided under GPL Version 2.0
166
+ or a later version. Licensor may specify additional Change Licenses without
167
+ limitation.
168
+
169
+ 2. To either: (a) specify an additional grant of rights to use that does not
170
+ impose any additional restriction on the right granted in this License, as
171
+ the Additional Use Grant; or (b) insert the text "None".
172
+
173
+ 3. To specify a Change Date.
174
+
175
+ 4. Not to modify this License in any other way.
176
+
177
+ -----------------------------------------------------------------------------
178
+
179
+ Notice
180
+
181
+ The Business Source License (this document, or the "License") is not an Open
182
+ Source license. However, the Licensed Work will eventually be made available
183
+ under an Open Source License, as stated in this License.
@@ -0,0 +1,352 @@
1
+ /**
2
+ * @fileoverview `ContractEntityServer` — the rules that CANNOT run in a browser.
3
+ *
4
+ * Registered later than the shared `ContractEntity`, so MJ's ClassFactory resolves this one on the
5
+ * server while the browser keeps the shared class (plan §6.3). Everything a client should be able to
6
+ * preflight lives on the shared class instead; what is here needs a lock or a cross-entity read.
7
+ *
8
+ * @module @mj-biz-apps/contracts-core-entities-server
9
+ */
10
+ import { BaseEntity, type EntityDeleteOptions, ValidationResult } from '@memberjunction/core';
11
+ import { ContractEntity } from '@mj-biz-apps/contracts-entities';
12
+ /**
13
+ * Whether a foreign key is being CHOSEN right now, as opposed to merely being present.
14
+ *
15
+ * A free function so it can be unit-tested against a duck-typed record without a provider — the same
16
+ * reason `ValidateValueLists` and `ExplainMissingRequiredFields` are free functions. The semantics
17
+ * are the whole point and they are easy to get subtly wrong, so they are pinned by tests rather than
18
+ * by this comment:
19
+ *
20
+ * · NEW record -> any value is a new selection. (accounting's identity lock skips this
21
+ * case, correctly for its purpose and wrongly for ours)
22
+ * · SAVED, key changed -> a new selection.
23
+ * · SAVED, key the same -> NOT a new selection, whatever the referenced row now says.
24
+ *
25
+ * The comparison is case-insensitive and string-wise because these are UUIDs, which MJ can hand back
26
+ * in either casing depending on how the record was loaded (MJ's UUID_COMPARISON_GUIDE). A `!==` here
27
+ * would report a change on a record nobody touched.
28
+ */
29
+ export declare function IsNewlySelected(entity: BaseEntity, fieldName: string): boolean;
30
+ export declare class ContractEntityServer extends ContractEntity {
31
+ /**
32
+ * Mint `ContractNumber` on first save, then persist.
33
+ *
34
+ * WHY THE NUMBER IS MINTED HERE and not defaulted in SQL. It comes from a singleton counter that
35
+ * has to be taken under a lock inside the save's own transaction, so a rollback releases the
36
+ * number instead of burning it. A column default cannot do that, and a client cannot be trusted
37
+ * with it.
38
+ *
39
+ * Contracts needs no `SkipRelatedCollections` dance, unlike orders: nothing here prepares child
40
+ * rows before the graph executor runs, so the plain save carries `Modifications` with it.
41
+ */
42
+ Save(): Promise<boolean>;
43
+ /**
44
+ * Server-authoritative rules that need to read another entity.
45
+ *
46
+ * SETTLE THE `HasModifications` UNKNOWN CASE. The shared `ContractEntity.Validate()` only rejects
47
+ * a false flag when it can PROVE rows exist — `Modifications.Count > 0`. On an ordinary form load
48
+ * the collection is not loaded, so `Count === 0` means *unknown*, and the shared guard stays
49
+ * silent by design rather than refusing a legitimate save.
50
+ *
51
+ * That design is only honest if something settles the unknown case, and until now nothing did:
52
+ * load a saved contract without its modifications, set `HasModifications = false`, save, and it
53
+ * succeeded — flag false with rows in the table, which ERD §4.4 explicitly rejects and which the
54
+ * modification subclass's own comment calls the single most misleading state this app can produce.
55
+ * The shared class's comment PROMISED this check existed. Caught on review of PR #9.
56
+ *
57
+ * One `EXISTS`, and only when it can matter (`IsSaved && HasModifications === false`), so the
58
+ * ordinary save pays nothing. The message is the shared class's, deliberately — the user should
59
+ * not be able to tell which tier refused them.
60
+ *
61
+ * THE TYPE DECIDES WHERE IN THE TREE A CONTRACT MAY SIT, and whether it needs its own paper.
62
+ * `ContractType.MustBeRoot` / `MustBeChild` / `TemplateRequired` — three booleans, all read from the
63
+ * one memoised type row. The rules need that row, which is a join the browser has no business
64
+ * doing, so the shared class cannot carry them (ERD §7.1).
65
+ *
66
+ * ⚠ TWO GENERATIONS OF DEFECT ARE BURIED HERE, and both are worth remembering.
67
+ *
68
+ * FIRST, this compared the type's NAME to the string 'Change Order'. A display name is not a rule:
69
+ * renaming that lookup row — an ordinary thing to do — silently stopped the check from ever firing,
70
+ * with nothing failing and no error to notice (fixed 2026-08-19).
71
+ *
72
+ * SECOND, the column that replaced the name match was a THREE-STATE STRING,
73
+ * `ParentStatusRequirement IN ('Required','Prohibited')` or NULL. Ruled out by Marcelo on
74
+ * 2026-08-20 as confusing and overly complex, with the transposition argument as the proof: a
75
+ * column whose values INVERT the rule when read in the wrong order is a column that will
76
+ * eventually be read in the wrong order. Now two booleans, mutually exclusive by
77
+ * `CK_ContractType_RootOrChild`, and "both false" means unrestricted — a real and in fact majority
78
+ * state (V202608200300).
79
+ *
80
+ * Both placement directions are enforced, because a rule that only ever checks one of them is half
81
+ * decoration: `MustBeChild` refuses a missing parent (a change order that amends nothing would
82
+ * never appear in the original agreement's lineage), and `MustBeRoot` refuses a present one (a root
83
+ * agreement sitting under another contract is a lineage ring waiting to happen — see R-3).
84
+ */
85
+ ValidateAsync(): Promise<ValidationResult>;
86
+ /**
87
+ * R-8 — a contract that other records point at cannot be deleted, and the refusal says who.
88
+ *
89
+ * THREE DEPENDENCIES, and the two lineage ones are the reason this is not obvious. A contract is
90
+ * referenced not only by its modifications but by any contract naming it as `ParentContractID`
91
+ * (its change orders) or as `SupersededByContractID` (the agreement it replaced). Both of those are
92
+ * self-references on `Contract`, so the rows blocking the delete are *other contracts* — and the
93
+ * user is looking at the record they wanted gone, not at the ones pointing to it. Naming them is
94
+ * the whole value.
95
+ *
96
+ * Deleting a superseded contract is the case worth spelling out: it is the *predecessor* in a
97
+ * re-papering chain, so removing it destroys the history the successor exists to continue.
98
+ */
99
+ Delete(options?: EntityDeleteOptions): Promise<boolean>;
100
+ /**
101
+ * R-5 — A RETIRED TYPE MUST NOT BE *NEWLY* SELECTED, and the word doing the work is "newly".
102
+ *
103
+ * A contract that already references a type keeps working. We assume the type was Active when it
104
+ * was chosen, and retiring a type is not a statement about the contracts already signed under it —
105
+ * refusing to save an existing contract because a lookup row was retired years later would make
106
+ * the retirement itself a destructive act. So this is a rule about the EDIT, not about the row.
107
+ *
108
+ * "Newly selected" means one of exactly two things, and both must be covered:
109
+ * · the record is NEW and names the type at all, or
110
+ * · the record is SAVED and the foreign key CHANGED.
111
+ *
112
+ * That first clause is why this does not reuse `GLAccountEntityServer`'s shape verbatim.
113
+ * Accounting's identity lock returns early when `OldValue` is null — correct there, because it is
114
+ * asking "did an existing value change". Here the same early return would skip every CREATE, which
115
+ * is the primary case the rule exists for: the Configuration page promises a retired type "stops
116
+ * being offered for new contracts", and a create is the newest selection there is.
117
+ *
118
+ * TIER: code only, no trigger. `Status` lives on another table so a CHECK cannot see it, and the
119
+ * plan ruled a trigger not worth it — the failure mode is a person picking a retired value in our
120
+ * own UI, not silent corruption by an outside writer.
121
+ *
122
+ * COST: the `ContractTypeID` half is FREE — `typeRule()` already reads that row for the
123
+ * parent-status rule, and `Status` now rides along on the same RunView. The `ContractTemplateID`
124
+ * half costs two reads (template -> its type -> that type's `Status`) and only runs when the
125
+ * template FK is newly selected, which is rare. There is no single view exposing the template's
126
+ * type's status; `vwContractTemplates` carries the type's NAME, not its `Status`.
127
+ */
128
+ private refuseRetiredSelections;
129
+ /**
130
+ * Whether this foreign key is being chosen right now, as opposed to merely being present.
131
+ *
132
+ * On a NEW record any value is a new selection. On a SAVED one, only a change counts — and the
133
+ * comparison is case-insensitively string-wise because these are UUIDs, which MJ can hand back in
134
+ * either casing depending on the path that loaded them (see MJ's UUID_COMPARISON_GUIDE). Comparing
135
+ * them with `!==` would report a change on a record nobody touched, which would refuse saves of
136
+ * contracts whose type is perfectly legal.
137
+ */
138
+ private isNewlySelected;
139
+ /**
140
+ * The NAME of the template's type when that type is retired, or null.
141
+ *
142
+ * Two hops, because `Status` lives on `ContractTemplateType` and the contract points at a
143
+ * `ContractTemplate`. Returns the type's name rather than a boolean so the refusal can say WHICH
144
+ * kind of template was retired — "that template is an Order Form, which we no longer issue" is
145
+ * actionable where "invalid template" sends the user back to the same picker.
146
+ */
147
+ private retiredTemplateType;
148
+ /**
149
+ * R-3 — a contract may not end up in its own lineage, on either axis.
150
+ *
151
+ * THE HOLE. `CK_Contract_ParentNotSelf` and `CK_Contract_SupersededNotSelf` stop only `A -> A`.
152
+ * Nothing stopped `A -> B -> A`, or a longer ring, because every individual save is legal in
153
+ * isolation: setting B's parent to A is fine, and later setting A's parent to B is fine — the row
154
+ * being written references a different row, which is all a CHECK can see.
155
+ *
156
+ * A cycle can only be created by THE EDGE BEING ADDED, which is what makes the check cheap: the
157
+ * question is not "is the graph acyclic" but "starting from the parent I am proposing, can I reach
158
+ * myself?" One upward walk along an indexed FK, one row per level, and change-order chains are 1–3
159
+ * deep in practice. `Depth < 50` caps it and — more importantly — means an ALREADY-corrupt ring
160
+ * terminates instead of spinning.
161
+ *
162
+ * ⚠ THE CONSEQUENCE OF A CYCLE IS WORSE THAN THE PLAN SAYS, and this is worth recording because it
163
+ * changes the tier. R-3 argued a cycle "corrupts nothing silently — it makes the Lineage panel walk
164
+ * forever, which is loud." That was true when written. It is not true now: `vwContracts` computes
165
+ * `RootParentContractID` and `RootSupersededByContractID` through
166
+ * `fnContract*_GetRootID`, whose CTE walks to `Depth < 100` and then selects the row
167
+ * `WHERE ParentContractID IS NULL`. In a ring **no row satisfies that**, so the function returns no
168
+ * row and the column comes back **NULL** — silently wrong, on the app's primary view, for every
169
+ * contract in the ring, while also costing 100 levels of recursion per row on every read. So a
170
+ * cycle is quiet after all, which is exactly the shape of defect this app keeps designing away.
171
+ *
172
+ * SKIPPED ON A CREATE. An unsaved contract has an ID but no row, so nothing can point at it and no
173
+ * walk can reach it. Skipped when the proposed target IS this record, too — that is `A -> A`, which
174
+ * the generated CHECK validator already reports, and two errors for one mistake is its own defect.
175
+ */
176
+ private refuseLineageCycles;
177
+ /**
178
+ * A contract may not be its own parent, nor its own successor. Reported HERE, unconditionally.
179
+ *
180
+ * WHY THIS EXISTS when `CK_Contract_ParentNotSelf` / `CK_Contract_SupersededNotSelf` and their
181
+ * generated validators all cover it: because the generated validators DO NOT actually cover it.
182
+ * They are LLM-authored from the constraint text (`generateValidatorFunctionFromCheckConstraint`
183
+ * → the `CodeGen: Check Constraint Parser` prompt), and both emitted `===` on a `uniqueidentifier`:
184
+ *
185
+ * if (this.SupersededByContractID != null && this.SupersededByContractID === this.ID) { ... }
186
+ *
187
+ * MJ returns UUIDs in either casing depending on how a row was loaded, so a lowercase value against
188
+ * an uppercase `ID` is the SAME contract and that check silently misses it. Filed in MJ-UPSTREAM.md.
189
+ *
190
+ * AND THE RULE IS CHECKED WHOLE, not only the part the generated code misses. Covering just the
191
+ * casing-differs case and deferring the rest would bless a genuinely invalid state on an assumption
192
+ * about code this class does not own — and the moment that code changes, an invalid state starts
193
+ * reading as success. The cost of overlap is one duplicated message; the cost of the gap is a wrong
194
+ * save. This is also why the two lineage guards below delegate here rather than each deciding: one
195
+ * owner, checked fully, in this file, where the skip can be verified by reading it.
196
+ */
197
+ private refuseSelfReferences;
198
+ /**
199
+ * A contract may only supersede a contract at the SAME LEVEL of the tree — same `ParentContractID`.
200
+ *
201
+ * WHY THIS IS NOT "ROOT ONLY" (ruled by Marcelo, 2026-08-20). The obvious-looking rule is that only
202
+ * a top-level agreement may be re-papered. It is wrong in both directions. A change order genuinely
203
+ * does get renegotiated and replaced by a second change order under the same order form — forbidding
204
+ * that would force somebody to DELETE the first one, destroying the history re-papering exists to
205
+ * preserve. And "root only" would still permit the incoherent case: a change order claiming to
206
+ * replace a whole order form, which asserts that a subordinate document replaced the agreement it
207
+ * hangs off.
208
+ *
209
+ * So the axis that matters is not depth, it is SIBLINGHOOD. `ParentContractID` says what a document
210
+ * sits under; `SupersededByContractID` says what replaced it. A supersession that crosses levels is
211
+ * the only genuinely meaningless combination, and same-parent is exactly the predicate that excludes
212
+ * it while allowing everything real: two roots (both NULL) or two siblings (same parent).
213
+ *
214
+ * TYPE IS DELIBERATELY NOT CONSTRAINED. An Order Form replaced by an Order Form is the common case,
215
+ * but a Payment Link customer graduating to signed paper is a real cross-type supersession — and
216
+ * `Payment Link -> Order Form` is precisely the upgrade path the two types exist to distinguish.
217
+ * Constraining `ContractTypeID` here would forbid it for no benefit.
218
+ *
219
+ * WHY THIS IS NOT A CHECK CONSTRAINT. The rule compares THIS row's `ParentContractID` with ANOTHER
220
+ * row's, and a CHECK sees only the row being written — the same reason `refuseLineageCycles` lives
221
+ * here. One indexed read of the target contract is the whole cost.
222
+ *
223
+ * RUNS ON CREATE TOO, unlike `refuseLineageCycles`. A cycle is impossible before the row exists
224
+ * (nothing can point at it yet), but a cross-level supersession is entirely possible on a brand-new
225
+ * contract — a successor composed in the browser names its predecessor before either is saved, which
226
+ * is the primary flow this rule exists to police. Gated on `isNewlySelected` instead, so an ordinary
227
+ * re-save of an existing chain is not re-policed and legacy rows are not retro-refused.
228
+ */
229
+ private refuseCrossLevelSupersession;
230
+ /**
231
+ * Walk up `<column>` from `startID`; return the chain as text if it reaches this record, else null.
232
+ *
233
+ * Returns the CHAIN rather than a boolean so the refusal can show the ring — "A -> B -> A" is
234
+ * something a person can act on, where "cycle detected" sends them to guess which edge to remove.
235
+ *
236
+ * The column name comes from a fixed internal list, never from input, and both IDs are checked
237
+ * against a UUID shape before interpolation — `ExecuteSQL`'s object-parameter form needs the
238
+ * concrete `SQLServerDataProvider` type rather than the `DatabaseProviderBase` this class holds
239
+ * (see the note on `assignContractNumber`), so a guard plus interpolation is the honest option here
240
+ * rather than a cast that switches off checking.
241
+ */
242
+ private lineageReachesSelf;
243
+ /**
244
+ * A shrotcut to get the datbaseProvider.
245
+ *
246
+ * `ExecuteSQL<T>(query, parameters?, options?, contextUser?)` is a public member of
247
+ * `DatabaseProviderBase`, which `@memberjunction/core` exports — so the four
248
+ * `as unknown as { ExecuteSQL: … }` casts this replaces were never necessary. Each of them
249
+ * re-declared a narrower signature than the real one: no generic (forcing a second cast on every
250
+ * result), and no `contextUser` parameter, which made the API look as though it could not carry a
251
+ * context user at all. Worse, `as unknown as` switches off checking, so a signature change
252
+ * upstream would have compiled here and failed at runtime.
253
+ *
254
+ * `ProviderToUse` is typed as the metadata-provider interface, which does not declare
255
+ * `ExecuteSQL`; this is a narrowing cast to the concrete base class, which is what a server-side
256
+ * entity subclass always has. It is one cast in one place instead of four scattered ones, and it
257
+ * keeps the real generic signature.
258
+ */
259
+ private get db();
260
+ /**
261
+ * Whether any modification row exists for this contract, read from the DATABASE rather than the
262
+ * collection — which is the whole point: the collection is exactly what the browser could not
263
+ * know about. `TOP 1` because existence is the question, not the count.
264
+ */
265
+ private modificationRowsExist;
266
+ /**
267
+ * What this contract's TYPE says about naming a parent: `'Required'`, `'Prohibited'` or null.
268
+ *
269
+ * One scalar read, no entity load, and memoised for the call — `ValidateAsync` asks for the
270
+ * requirement and then for the type's name to build the message, and a validation pass should not
271
+ * make two round trips to say one thing.
272
+ */
273
+ private typeRule;
274
+ /**
275
+ * Cleared on nothing, and that is safe: a contract's type can change between saves, but this cache
276
+ * lives only for the duration of one `ValidateAsync` call tree — the field is re-read from the
277
+ * database on the next validation because a new entity instance is what a new save uses.
278
+ */
279
+ private cachedTypeRule;
280
+ /**
281
+ * R-12 — a contract may not reference a template nobody can read.
282
+ *
283
+ * `IsUsable` is derived in `vwContractTemplates`: 1 when the template records a `SourceURL` **or**
284
+ * has a file attached, 0 when it has neither. The flag and this refusal do different jobs and both
285
+ * are needed (ruled 2026-08-20):
286
+ *
287
+ * · **the flag is the affordance** — someone authoring a template sees the red "Unusable" chip and
288
+ * fixes it, rather than being blocked by a condition they cannot see;
289
+ * · **this refusal is the floor** — a contract citing standard terms nobody can read is the actual
290
+ * harm, and it is worth stopping at the moment it would happen.
291
+ *
292
+ * Shipping only the flag leaves the harm unprevented; shipping only the refusal is the confusing
293
+ * version this item started as, where a person is refused for something invisible.
294
+ *
295
+ * ONLY ON A NEW REFERENCE. Whether an EXISTING contract should be affected when its template later
296
+ * becomes unusable is a lifecycle question, and it is R-15's — not this one. So this fires on the
297
+ * same `isNewlySelected` gate as R-5, which also means an ordinary re-save of an existing contract
298
+ * costs nothing.
299
+ */
300
+ private refuseUnusableTemplate;
301
+ /**
302
+ * R-4 (certain half) — do not clear `ContractTemplateID` while modifications on THIS contract point
303
+ * at provisions of THAT template.
304
+ *
305
+ * Clearing it would leave every one of those modifications comparing negotiated language against
306
+ * standard language the contract no longer claims to incorporate — the modification rows survive
307
+ * and quietly stop meaning anything, which is the app's worst failure shape.
308
+ *
309
+ * ⚠ THIS IS THE NARROW HALF ON PURPOSE. The full rule is *"reject clearing when a modification
310
+ * ANYWHERE IN THE TREE references that template and it is not reachable elsewhere in the tree"*,
311
+ * which the plan DEFERS to ride with R-14 — not on cost (it is the same walk, and chains are 1–3
312
+ * deep) but on sequencing, so the ancestor walk is written once against a settled tree model. This
313
+ * half covers modifications on this contract only. A modification on a CHILD contract pointing at
314
+ * the template this contract is clearing is **not yet caught here**; the modification-side rule
315
+ * below refuses to SAVE such a row, so the gap is the pre-existing row, not a new one.
316
+ */
317
+ private refuseClearingAReferencedTemplate;
318
+ /** How many of THIS contract's modifications cite a provision of the given template. */
319
+ private modificationsAgainstTemplate;
320
+ /**
321
+ * Mint the next contract number by calling the database's own numbering procedure.
322
+ *
323
+ * WHY A SPROC AND NOT `RunView` / `RunQuery` / `BaseEntity`. This is an atomic
324
+ * read-modify-write, and none of those can express one: `RunView` is read-only, `RunQuery` is a
325
+ * read surface (hiding DML inside a "query" would be worse than being honest about it), and the
326
+ * entity path removes the lock — two concurrent creates would both read N, both mint the same
327
+ * number, and the collision would surface later as a unique-index violation on the insert rather
328
+ * than as a serialised wait.
329
+ *
330
+ * So the lock stays in the database, in a DATABASE OBJECT rather than a TypeScript string.
331
+ * `spAssignNextContractNumber` (V202608192200) holds the `HOLDLOCK, UPDLOCK` and the format; this
332
+ * method is one dialect-free `EXEC`. That is accounting's pattern, adopted rather than invented —
333
+ * `SequenceService.ts` there keeps `spAssignNextJournalEntryNumber` at DB level for the same
334
+ * stated reason. Orders still inlines the same UPDATE in four places, which is what this stops
335
+ * doing.
336
+ *
337
+ * It also makes the PostgreSQL port a database exercise: write a PG function with this name and
338
+ * signature and no application code changes.
339
+ *
340
+ * `isMutation: true` matters and was missing while this was inline: MJ's SQL-statement logging
341
+ * uses it to know a statement CHANGED data, which is what lets a logging session capture the
342
+ * mutation. `description` names the operation in that log.
343
+ *
344
+ * ⚠ NOT gap-free. On a standalone save the number is consumed before the row's own transaction
345
+ * opens, so a later failure has already spent it; inside a graph save a rollback releases it. The
346
+ * unique index is the real guard.
347
+ */
348
+ private assignContractNumber;
349
+ }
350
+ /** Anti-tree-shake anchor — see the note in index.ts. */
351
+ export declare function LoadContractEntityServer(): void;
352
+ //# sourceMappingURL=ContractEntityServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContractEntityServer.d.ts","sourceRoot":"","sources":["../src/ContractEntityServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EACH,UAAU,EAEV,KAAK,mBAAmB,EAIxB,gBAAgB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,cAAc,EAAuB,MAAM,iCAAiC,CAAC;AAGtF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAK9E;AAqBD,qBACa,oBAAqB,SAAQ,cAAc;IACpD;;;;;;;;;;OAUG;IACmB,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IA0B9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACmB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA+EhE;;;;;;;;;;;;OAYG;IACmB,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiC7E;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;YACW,uBAAuB;IAmCrC;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAIvB;;;;;;;OAOG;YACW,mBAAmB;IA6BjC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;YACW,mBAAmB;IA4CjC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;YACW,4BAA4B;IA6D1C;;;;;;;;;;;OAWG;YACW,kBAAkB;IA0BhC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,KAAK,EAAE,GAEb;IAED;;;;OAIG;YACW,qBAAqB;IAkBnC;;;;;;OAMG;YACW,QAAQ;IAkBtB;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAkD;IAGxE;;;;;;;;;;;;;;;;;;;OAmBG;YACW,sBAAsB;IAuDpC;;;;;;;;;;;;;;;OAeG;YACW,iCAAiC;IA0B/C,wFAAwF;YAC1E,4BAA4B;IAgB1C;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;YACW,oBAAoB;CAyBrC;AAED,yDAAyD;AACzD,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}