@orthacms/segments-domain 0.4.3 → 0.5.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.
- package/package.json +1 -1
- package/dist/lib/access-rule.d.ts +0 -120
- package/dist/lib/access-rule.d.ts.map +0 -1
- package/dist/lib/adopt-relation-sets.d.ts +0 -103
- package/dist/lib/adopt-relation-sets.d.ts.map +0 -1
- package/dist/lib/evaluate.d.ts +0 -78
- package/dist/lib/evaluate.d.ts.map +0 -1
- package/dist/lib/inheritance.d.ts +0 -43
- package/dist/lib/inheritance.d.ts.map +0 -1
- package/dist/lib/limits.d.ts +0 -27
- package/dist/lib/limits.d.ts.map +0 -1
- package/dist/lib/segment-type.d.ts +0 -87
- package/dist/lib/segment-type.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The **access rule** — what an entry, a type or a workspace declares about who
|
|
3
|
-
* may read it.
|
|
4
|
-
*
|
|
5
|
-
* The shape is deliberately fixed rather than an expression language, and the
|
|
6
|
-
* three constraints below are what buy the whole design its legibility:
|
|
7
|
-
*
|
|
8
|
-
* 1. **Groups are OR-ed, types inside a group are AND-ed.** Disjunctive normal
|
|
9
|
-
* form, and nothing else. `(Acme) OR (EU AND Pro)` is expressible;
|
|
10
|
-
* arbitrary nesting is not.
|
|
11
|
-
* 2. **No nesting.** A group holds conditions, never other groups.
|
|
12
|
-
* 3. **One negation.** Rule-level {@link AccessRule.exclusions} are absolute and
|
|
13
|
-
* sit above every group; a group's own `all-except` narrows only that group.
|
|
14
|
-
*
|
|
15
|
-
* Lift any of them and "why can this reader see this?" stops being a flat list
|
|
16
|
-
* and becomes a proof tree — which is exactly the failure mode this kernel
|
|
17
|
-
* exists to avoid.
|
|
18
|
-
*/
|
|
19
|
-
import type { SegmentTypeKey } from './segment-type';
|
|
20
|
-
/** How one segment type constrains one group. */
|
|
21
|
-
export declare const CONDITION_MODE: {
|
|
22
|
-
/** Does not constrain — the type is open in this group. */
|
|
23
|
-
readonly All: "all";
|
|
24
|
-
/** Everyone in the type except the named segments. */
|
|
25
|
-
readonly AllExcept: "all-except";
|
|
26
|
-
/** Only the named segments. */
|
|
27
|
-
readonly Only: "only";
|
|
28
|
-
};
|
|
29
|
-
/** @see CONDITION_MODE */
|
|
30
|
-
export type ConditionMode = (typeof CONDITION_MODE)[keyof typeof CONDITION_MODE];
|
|
31
|
-
/** The authored-only mode: take this level's value from the level above. */
|
|
32
|
-
export declare const INHERIT: "inherit";
|
|
33
|
-
/** A condition as **authored**, which may still defer to the level above. */
|
|
34
|
-
export interface AuthoredCondition {
|
|
35
|
-
readonly mode: ConditionMode | typeof INHERIT;
|
|
36
|
-
/** Segment ids the mode refers to. Empty for `all` and `inherit`. */
|
|
37
|
-
readonly segmentIds: readonly string[];
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* A condition after inheritance has run: every `inherit` is gone.
|
|
41
|
-
*
|
|
42
|
-
* The two types are kept apart on purpose. {@link evaluate} takes only resolved
|
|
43
|
-
* conditions, so "did anyone forget to run inheritance?" is a compile error
|
|
44
|
-
* rather than a silently permissive decision at runtime.
|
|
45
|
-
*/
|
|
46
|
-
export interface ResolvedCondition {
|
|
47
|
-
readonly mode: ConditionMode;
|
|
48
|
-
readonly segmentIds: readonly string[];
|
|
49
|
-
}
|
|
50
|
-
/** One AND-group, as authored: segment type key → condition. */
|
|
51
|
-
export type AuthoredConditionGroup = Readonly<Record<SegmentTypeKey, AuthoredCondition>>;
|
|
52
|
-
/** One AND-group, after inheritance. */
|
|
53
|
-
export interface ResolvedConditionGroup {
|
|
54
|
-
/** Segment type key → condition. A type absent here does not constrain. */
|
|
55
|
-
readonly conditions: Readonly<Record<SegmentTypeKey, ResolvedCondition>>;
|
|
56
|
-
/**
|
|
57
|
-
* Which level contributed this group — what the editor is shown as
|
|
58
|
-
* "inherited from type article". Absent for a group authored on the entry
|
|
59
|
-
* itself.
|
|
60
|
-
*/
|
|
61
|
-
readonly source?: AccessLevelName;
|
|
62
|
-
}
|
|
63
|
-
/** What a reader who matched no group is served. */
|
|
64
|
-
export declare const ACCESS_FALLBACK: {
|
|
65
|
-
/** Absent from listings, 404 on a direct read — existence stays secret. */
|
|
66
|
-
readonly Hidden: "hidden";
|
|
67
|
-
/** Returned with a teaser and `requires`, so the client can upsell. */
|
|
68
|
-
readonly Teaser: "teaser";
|
|
69
|
-
/** As `teaser`, plus whatever paywall treatment the client renders. */
|
|
70
|
-
readonly Paywall: "paywall";
|
|
71
|
-
};
|
|
72
|
-
/** @see ACCESS_FALLBACK */
|
|
73
|
-
export type AccessFallback = (typeof ACCESS_FALLBACK)[keyof typeof ACCESS_FALLBACK];
|
|
74
|
-
/** The levels a rule can be attached to, outermost first. */
|
|
75
|
-
export declare const ACCESS_LEVEL: {
|
|
76
|
-
readonly Installation: "installation";
|
|
77
|
-
readonly Workspace: "workspace";
|
|
78
|
-
readonly Type: "type";
|
|
79
|
-
readonly Slice: "slice";
|
|
80
|
-
readonly Entry: "entry";
|
|
81
|
-
};
|
|
82
|
-
/** @see ACCESS_LEVEL */
|
|
83
|
-
export type AccessLevelName = (typeof ACCESS_LEVEL)[keyof typeof ACCESS_LEVEL];
|
|
84
|
-
/** Exclusions, by segment type key. Absolute: they outrank every group. */
|
|
85
|
-
export type Exclusions = Readonly<Record<SegmentTypeKey, readonly string[]>>;
|
|
86
|
-
/** A rule as authored at one level. */
|
|
87
|
-
export interface AuthoredAccessRule {
|
|
88
|
-
/** Segments that never see the content, whatever any group says. */
|
|
89
|
-
readonly exclusions?: Exclusions;
|
|
90
|
-
/** Condition groups, OR-ed. Empty means this level adds no condition. */
|
|
91
|
-
readonly groups?: readonly AuthoredConditionGroup[];
|
|
92
|
-
/**
|
|
93
|
-
* Drop everything inherited from the levels above instead of adding to it.
|
|
94
|
-
*
|
|
95
|
-
* Merging is the default because replacing silently loses a type's own
|
|
96
|
-
* restriction the moment someone edits one entry — but "this one article is
|
|
97
|
-
* genuinely public" has to remain expressible, and this is how.
|
|
98
|
-
*/
|
|
99
|
-
readonly detachInherited?: boolean;
|
|
100
|
-
/** Start of the visibility window. `null`/absent = no lower bound. */
|
|
101
|
-
readonly startsAt?: Date | null;
|
|
102
|
-
/** End of the visibility window. `null`/absent = no upper bound. */
|
|
103
|
-
readonly endsAt?: Date | null;
|
|
104
|
-
/** What a reader who matched no group gets. */
|
|
105
|
-
readonly fallback?: AccessFallback;
|
|
106
|
-
}
|
|
107
|
-
/** A rule after inheritance: what {@link evaluate} actually reads. */
|
|
108
|
-
export interface ResolvedAccessRule {
|
|
109
|
-
readonly exclusions: Exclusions;
|
|
110
|
-
/** OR-ed groups. **Empty means unrestricted** — see {@link evaluate}. */
|
|
111
|
-
readonly groups: readonly ResolvedConditionGroup[];
|
|
112
|
-
readonly startsAt: Date | null;
|
|
113
|
-
readonly endsAt: Date | null;
|
|
114
|
-
readonly fallback: AccessFallback;
|
|
115
|
-
}
|
|
116
|
-
/** A rule that restricts nothing — the installation default. */
|
|
117
|
-
export declare const OPEN_ACCESS: ResolvedAccessRule;
|
|
118
|
-
/** Whether a resolved rule constrains anything at all. */
|
|
119
|
-
export declare function isUnrestricted(rule: ResolvedAccessRule): boolean;
|
|
120
|
-
//# sourceMappingURL=access-rule.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"access-rule.d.ts","sourceRoot":"","sources":["../../src/lib/access-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,iDAAiD;AACjD,eAAO,MAAM,cAAc;IACvB,2DAA2D;;IAE3D,sDAAsD;;IAEtD,+BAA+B;;CAEzB,CAAC;AAEX,0BAA0B;AAC1B,MAAM,MAAM,aAAa,GACrB,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEzD,4EAA4E;AAC5E,eAAO,MAAM,OAAO,EAAG,SAAkB,CAAC;AAE1C,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,OAAO,CAAC;IAC9C,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,gEAAgE;AAChE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CACzC,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAC5C,CAAC;AAEF,wCAAwC;AACxC,MAAM,WAAW,sBAAsB;IACnC,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;CACrC;AAED,oDAAoD;AACpD,eAAO,MAAM,eAAe;IACxB,2EAA2E;;IAE3E,uEAAuE;;IAEvE,uEAAuE;;CAEjE,CAAC;AAEX,2BAA2B;AAC3B,MAAM,MAAM,cAAc,GACtB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAE3D,6DAA6D;AAC7D,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,wBAAwB;AACxB,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE/E,2EAA2E;AAC3E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAE7E,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACpD;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;CACtC;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;CACrC;AAED,gEAAgE;AAChE,eAAO,MAAM,WAAW,EAAE,kBAMzB,CAAC;AAEF,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAOhE"}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { type AuthoredAccessRule } from './access-rule';
|
|
2
|
-
import type { SegmentTypeKey } from './segment-type';
|
|
3
|
-
/**
|
|
4
|
-
* How an entry's access is expressed **before** segmentation, in the scheme
|
|
5
|
-
* almost every CMS reaches for first: two relation fields on the entry, one
|
|
6
|
-
* listing who may read it and one listing who may not.
|
|
7
|
-
*
|
|
8
|
-
* The names are the shape's, not this CMS's — an adopter's fields are called
|
|
9
|
-
* `subscriptions` and `excludedSubscriptions`, or `plans` and `blockedPlans`.
|
|
10
|
-
* What matters is that both sides name the *same kind of thing*, which is what
|
|
11
|
-
* makes them one axis.
|
|
12
|
-
*/
|
|
13
|
-
export interface RelationSets {
|
|
14
|
-
/**
|
|
15
|
-
* Segment ids that may read the entry. **Empty means everyone**, which is
|
|
16
|
-
* the convention every instance of this scheme uses — an entry nobody
|
|
17
|
-
* bothered to restrict is an open entry, not a closed one.
|
|
18
|
-
*/
|
|
19
|
-
readonly included: readonly string[];
|
|
20
|
-
/** Segment ids that may not read it, whatever `included` says. */
|
|
21
|
-
readonly excluded: readonly string[];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Translates one entry's relation sets into the rule that means the same thing.
|
|
25
|
-
*
|
|
26
|
-
* ## Why this is a function and not a migration script
|
|
27
|
-
*
|
|
28
|
-
* There is no old scheme *in this repository* to migrate from — the scheme
|
|
29
|
-
* lives in the app adopting segmentation, in fields this CMS has never seen,
|
|
30
|
-
* under names only that app knows. A script here would have to invent the input
|
|
31
|
-
* format it claims to read. What is genuinely shared is the **mapping**, and
|
|
32
|
-
* getting it wrong is what produces content that silently opens: so the mapping
|
|
33
|
-
* is the part that ships, with a test, and the adopter's script is a loop that
|
|
34
|
-
* reads their fields, calls this, and posts the result to `/api/access`.
|
|
35
|
-
* `docs/segments-adoption.md` is that loop, written out.
|
|
36
|
-
*
|
|
37
|
-
* ## The three rules of the translation
|
|
38
|
-
*
|
|
39
|
-
* **An empty `included` is not an empty `only`.** This is the whole trap. In
|
|
40
|
-
* the relation scheme an empty include list means *everyone*; in this model an
|
|
41
|
-
* `only` naming nobody admits *nobody*. Translating one to the other literally
|
|
42
|
-
* turns every unrestricted entry in a library dark on the day of the migration
|
|
43
|
-
* — so an empty `included` produces the `all` mode, and the entry stays open.
|
|
44
|
-
*
|
|
45
|
-
* **`excluded` becomes an exclusion, never the complement.** Listing the other
|
|
46
|
-
* three hundred and ninety-seven segments would be storing the complement,
|
|
47
|
-
* which is the one thing the projection invariant forbids: onboarding a new
|
|
48
|
-
* organisation would then have to rewrite every entry in the library.
|
|
49
|
-
*
|
|
50
|
-
* **The result is one group.** The old scheme has no disjunction to preserve —
|
|
51
|
-
* it is a single include list AND-ed with a single exclude list — so a
|
|
52
|
-
* translation that produced several groups would be inventing structure the
|
|
53
|
-
* source never carried.
|
|
54
|
-
*/
|
|
55
|
-
export declare function ruleFromRelationSets(typeKey: SegmentTypeKey, sets: RelationSets): AuthoredAccessRule;
|
|
56
|
-
/**
|
|
57
|
-
* Groups entries by the rule they translate to, so an adopter creates a handful
|
|
58
|
-
* of rules rather than one per entry.
|
|
59
|
-
*
|
|
60
|
-
* This is the difference between a usable migration and an unusable one. A
|
|
61
|
-
* library of forty thousand articles carrying the same two subscription tiers
|
|
62
|
-
* has, in practice, a few dozen distinct combinations — and one rule per entry
|
|
63
|
-
* would produce forty thousand rows nobody can read, edit, or reason about,
|
|
64
|
-
* defeating the reuse that made a rule worth having. Entries sharing a
|
|
65
|
-
* combination share a rule and get an assignment each.
|
|
66
|
-
*
|
|
67
|
-
* The signature key is the two **sorted** id lists, because the relation scheme
|
|
68
|
-
* has no order and two entries listing the same segments differently are the
|
|
69
|
-
* same rule. Entries whose sets restrict nobody are returned under
|
|
70
|
-
* {@link RelationSetGrouping.unrestricted} rather than given an empty rule:
|
|
71
|
-
* they need no assignment at all, and the count of them is the first number an
|
|
72
|
-
* adopter should sanity-check against their own.
|
|
73
|
-
*/
|
|
74
|
-
export declare function groupByRelationSets<T>(entries: readonly {
|
|
75
|
-
entry: T;
|
|
76
|
-
sets: RelationSets;
|
|
77
|
-
}[]): RelationSetGrouping<T>;
|
|
78
|
-
/** One distinct combination, and the entries carrying it. */
|
|
79
|
-
export interface RelationSetGroup<T> {
|
|
80
|
-
/** The sorted-id signature the grouping keyed on. */
|
|
81
|
-
signature: string;
|
|
82
|
-
/** The normalised sets — deduplicated and sorted. */
|
|
83
|
-
sets: RelationSets;
|
|
84
|
-
/** Every entry with this combination. */
|
|
85
|
-
entries: readonly T[];
|
|
86
|
-
}
|
|
87
|
-
/** What {@link groupByRelationSets} returns. */
|
|
88
|
-
export interface RelationSetGrouping<T> {
|
|
89
|
-
/** One entry per distinct combination — the rules to create. */
|
|
90
|
-
groups: readonly RelationSetGroup<T>[];
|
|
91
|
-
/** Entries whose sets restrict nobody. They need no rule and no assignment. */
|
|
92
|
-
unrestricted: readonly T[];
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Whether a translation would fit the model, and what to do when it does not.
|
|
96
|
-
*
|
|
97
|
-
* Called before writing anything. The one bound worth checking up front is the
|
|
98
|
-
* group ceiling — an adopter whose scheme carries a genuine disjunction (two
|
|
99
|
-
* independent axes OR-ed) will hit it, and finding out entry by entry through
|
|
100
|
-
* failed writes is a migration that stops half done.
|
|
101
|
-
*/
|
|
102
|
-
export declare function relationSetsFit(count: number): boolean;
|
|
103
|
-
//# sourceMappingURL=adopt-relation-sets.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adopt-relation-sets.d.ts","sourceRoot":"","sources":["../../src/lib/adopt-relation-sets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IACzB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,YAAY,GACnB,kBAAkB,CA8BpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACjC,OAAO,EAAE,SAAS;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,EAAE,GACrD,mBAAmB,CAAC,CAAC,CAAC,CA+BxB;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAC/B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,IAAI,EAAE,YAAY,CAAC;IACnB,yCAAyC;IACzC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;CACzB;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB,CAAC,CAAC;IAClC,gEAAgE;IAChE,MAAM,EAAE,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,+EAA+E;IAC/E,YAAY,EAAE,SAAS,CAAC,EAAE,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD"}
|
package/dist/lib/evaluate.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The access decision — the single place that answers "may this reader see
|
|
3
|
-
* this?".
|
|
4
|
-
*
|
|
5
|
-
* A domain service in the strict sense: no database, no framework, no clock of
|
|
6
|
-
* its own. That is what lets the same function run in the SQL compiler's tests,
|
|
7
|
-
* in the admin's "who sees this" panel, and on a client rendering a paywall,
|
|
8
|
-
* and give all three the same answer.
|
|
9
|
-
*
|
|
10
|
-
* The order of the checks is not interchangeable:
|
|
11
|
-
*
|
|
12
|
-
* 1. **Exclusions**, absolutely and first. "Everyone except Globex" must not be
|
|
13
|
-
* reopened by a group that happens to match — a denied reader is denied
|
|
14
|
-
* however good their plan is.
|
|
15
|
-
* 2. **The window**, before any group. An embargoed entry is invisible to
|
|
16
|
-
* everyone, so evaluating groups first would only produce a match nobody is
|
|
17
|
-
* allowed to act on.
|
|
18
|
-
* 3. **The groups**, OR-ed. One match is enough, and the rest are not even
|
|
19
|
-
* reported as failures — which is what keeps the explanation short.
|
|
20
|
-
*/
|
|
21
|
-
import { type ResolvedAccessRule } from './access-rule';
|
|
22
|
-
import type { SegmentTypeKey } from './segment-type';
|
|
23
|
-
/** Why a reader was refused. */
|
|
24
|
-
export declare const DENIAL_REASON: {
|
|
25
|
-
/** Matched a rule-level exclusion. */
|
|
26
|
-
readonly Excluded: "excluded";
|
|
27
|
-
/** The visibility window is not open. */
|
|
28
|
-
readonly Window: "window";
|
|
29
|
-
/** No condition group matched. */
|
|
30
|
-
readonly NoGroup: "no-group";
|
|
31
|
-
};
|
|
32
|
-
/** @see DENIAL_REASON */
|
|
33
|
-
export type DenialReason = (typeof DENIAL_REASON)[keyof typeof DENIAL_REASON];
|
|
34
|
-
/** The group a refused reader came closest to, and what stopped them. */
|
|
35
|
-
export interface ClosestGroup {
|
|
36
|
-
/** Index into {@link ResolvedAccessRule.groups}. */
|
|
37
|
-
readonly index: number;
|
|
38
|
-
/** Segment type keys whose condition failed, in declaration order. */
|
|
39
|
-
readonly failed: readonly SegmentTypeKey[];
|
|
40
|
-
/** Segment ids that would have satisfied those failing conditions. */
|
|
41
|
-
readonly requires: Readonly<Record<SegmentTypeKey, readonly string[]>>;
|
|
42
|
-
}
|
|
43
|
-
/** The verdict for one reader on one entry. */
|
|
44
|
-
export type AccessDecision = {
|
|
45
|
-
readonly visible: true;
|
|
46
|
-
/**
|
|
47
|
-
* Which group let them through — `-1` when the rule restricts
|
|
48
|
-
* nothing, so "unrestricted" and "matched group 0" stay
|
|
49
|
-
* distinguishable in an explanation.
|
|
50
|
-
*/
|
|
51
|
-
readonly matchedGroup: number;
|
|
52
|
-
} | {
|
|
53
|
-
readonly visible: false;
|
|
54
|
-
readonly reason: DenialReason;
|
|
55
|
-
readonly fallback: ResolvedAccessRule['fallback'];
|
|
56
|
-
/** Absent when the refusal was an exclusion or the window. */
|
|
57
|
-
readonly closest?: ClosestGroup;
|
|
58
|
-
};
|
|
59
|
-
/** What {@link evaluate} is asked about. */
|
|
60
|
-
export interface EvaluationInput {
|
|
61
|
-
/** The entry's rule, after inheritance. */
|
|
62
|
-
readonly rule: ResolvedAccessRule;
|
|
63
|
-
/** The reader's segment ids, already resolved from their tags. */
|
|
64
|
-
readonly callerSegmentIds: ReadonlySet<string>;
|
|
65
|
-
/** The instant the decision is made at — passed in, never read here. */
|
|
66
|
-
readonly now: Date;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Decide whether one reader may see one entry.
|
|
70
|
-
*
|
|
71
|
-
* **An empty `groups` means unrestricted**, not "matches nothing". A rule with
|
|
72
|
-
* no conditions is what every entry starts life with, and reading it as a
|
|
73
|
-
* closed door would black out an installation the moment the plugin is
|
|
74
|
-
* enabled. Exclusions and the window still apply on their own, so
|
|
75
|
-
* "everyone except Globex" needs no group at all.
|
|
76
|
-
*/
|
|
77
|
-
export declare function evaluate(input: EvaluationInput): AccessDecision;
|
|
78
|
-
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/lib/evaluate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAGH,KAAK,kBAAkB,EAG1B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,gCAAgC;AAChC,eAAO,MAAM,aAAa;IACtB,sCAAsC;;IAEtC,yCAAyC;;IAEzC,kCAAkC;;CAE5B,CAAC;AAEX,yBAAyB;AACzB,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE9E,yEAAyE;AACzE,MAAM,WAAW,YAAY;IACzB,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CAC1E;AAED,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GACpB;IACI,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CACjC,GACD;IACI,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAClD,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;CACnC,CAAC;AAER,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/C,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;CACtB;AAgGD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,CAsC/D"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inheritance — turning the chain of levels an entry sits under into the one
|
|
3
|
-
* rule {@link evaluate} reads.
|
|
4
|
-
*
|
|
5
|
-
* The chain runs installation → workspace → type → slice → entry, and the rule
|
|
6
|
-
* for merging it is the answer to a question the plan leaves open on purpose:
|
|
7
|
-
* a lower level **adds** its groups to what it inherited rather than replacing
|
|
8
|
-
* them. Replacement as the default loses a type's restriction the moment
|
|
9
|
-
* somebody edits one entry — the failure is silent and the content is already
|
|
10
|
-
* public by the time anyone notices. Detaching stays possible, but it has to be
|
|
11
|
-
* asked for ({@link AuthoredAccessRule.detachInherited}).
|
|
12
|
-
*
|
|
13
|
-
* Exclusions never merge downward the other way: they are unioned across the
|
|
14
|
-
* whole chain and a lower level cannot lift one. That is the same "deny wins"
|
|
15
|
-
* rule {@link evaluate} enforces within a single rule, applied across levels.
|
|
16
|
-
*/
|
|
17
|
-
import { type AccessLevelName, type AuthoredAccessRule, type ResolvedAccessRule } from './access-rule';
|
|
18
|
-
/** One link in the chain: where the rule was authored, and what it says. */
|
|
19
|
-
export interface AccessLevel {
|
|
20
|
-
/** Which level this is. Used to label an inherited group in the editor. */
|
|
21
|
-
readonly name: AccessLevelName;
|
|
22
|
-
/** The rule authored there, or `null`/absent when the level says nothing. */
|
|
23
|
-
readonly rule?: AuthoredAccessRule | null;
|
|
24
|
-
}
|
|
25
|
-
/** A resolved rule plus what it was assembled from. */
|
|
26
|
-
export interface ResolvedAccess {
|
|
27
|
-
/** What {@link evaluate} takes. */
|
|
28
|
-
readonly rule: ResolvedAccessRule;
|
|
29
|
-
/** Levels that contributed anything, outermost first. */
|
|
30
|
-
readonly contributors: readonly AccessLevelName[];
|
|
31
|
-
/** The level whose `detachInherited` dropped the chain above it, if any. */
|
|
32
|
-
readonly detachedAt?: AccessLevelName;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Collapse the chain into a single rule.
|
|
36
|
-
*
|
|
37
|
-
* Levels are read outermost-first and each may add groups, add exclusions, set
|
|
38
|
-
* the window, set the fallback, or detach. The **nearest** level that states a
|
|
39
|
-
* window or a fallback wins, because those are single-valued — unlike groups,
|
|
40
|
-
* which accumulate.
|
|
41
|
-
*/
|
|
42
|
-
export declare function resolveAccess(levels: readonly AccessLevel[]): ResolvedAccess;
|
|
43
|
-
//# sourceMappingURL=inheritance.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inheritance.d.ts","sourceRoot":"","sources":["../../src/lib/inheritance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAKH,KAAK,eAAe,EACpB,KAAK,kBAAkB,EAGvB,KAAK,kBAAkB,EAG1B,MAAM,eAAe,CAAC;AAGvB,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAC7C;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC3B,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;IAClD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC;CACzC;AA6CD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,cAAc,CA0D5E"}
|
package/dist/lib/limits.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The ceilings segmentation runs under.
|
|
3
|
-
*
|
|
4
|
-
* Both are cost limits with a visible failure mode, not arbitrary round
|
|
5
|
-
* numbers, and both are stated here so the server's validation and the admin's
|
|
6
|
-
* "2 of 8" counter read the same constant.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Condition groups per rule.
|
|
10
|
-
*
|
|
11
|
-
* Each group is one row of the entry's projection, so a group added at type
|
|
12
|
-
* level multiplies the projection for every entry of that type. Eight is the
|
|
13
|
-
* point past which "add another OR" stops being a cheap edit; the number is a
|
|
14
|
-
* starting position to be revisited against a real write benchmark, not a
|
|
15
|
-
* measured optimum.
|
|
16
|
-
*/
|
|
17
|
-
export declare const MAX_CONDITION_GROUPS = 8;
|
|
18
|
-
/**
|
|
19
|
-
* Segment types active at once.
|
|
20
|
-
*
|
|
21
|
-
* Bounded by the projection's slot columns — a type claims one `allow`/`deny`
|
|
22
|
-
* pair, and the pairs are created by migration rather than by runtime DDL, so
|
|
23
|
-
* the schema stays reproducible from a checkout. Raising it is one migration
|
|
24
|
-
* that adds the next batch.
|
|
25
|
-
*/
|
|
26
|
-
export declare const MAX_SEGMENT_TYPES = 8;
|
|
27
|
-
//# sourceMappingURL=limits.d.ts.map
|
package/dist/lib/limits.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/lib/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC"}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A **segment type** — one axis of the access decision, and the unit of AND
|
|
3
|
-
* logic. "Organisation", "Plan", "Region" and "Role" are all segment types; the
|
|
4
|
-
* kernel knows none of them by name, only that each owns a tag namespace and
|
|
5
|
-
* that types are AND-ed with one another.
|
|
6
|
-
*/
|
|
7
|
-
/** A reader's raw entitlement tag, namespaced by its type key: `org:acme`. */
|
|
8
|
-
export type SegmentTag = string;
|
|
9
|
-
/** The namespace half of a tag — and the key of the type that owns it. */
|
|
10
|
-
export type SegmentTypeKey = string;
|
|
11
|
-
/**
|
|
12
|
-
* How many segments a type is expected to hold, and therefore how the admin
|
|
13
|
-
* renders it: `low` earns matrix columns, `high` gets a searchable picker. A
|
|
14
|
-
* hint for the UI, never consulted by {@link evaluate}.
|
|
15
|
-
*/
|
|
16
|
-
export declare const SEGMENT_CARDINALITY: {
|
|
17
|
-
readonly Low: "low";
|
|
18
|
-
readonly High: "high";
|
|
19
|
-
};
|
|
20
|
-
/** @see SEGMENT_CARDINALITY */
|
|
21
|
-
export type SegmentCardinality = (typeof SEGMENT_CARDINALITY)[keyof typeof SEGMENT_CARDINALITY];
|
|
22
|
-
/**
|
|
23
|
-
* Where a type's segments come from. The kernel does not read any of these —
|
|
24
|
-
* it is the server package that populates segments from the named source — but
|
|
25
|
-
* the vocabulary lives here so both runtimes name the four the same way.
|
|
26
|
-
*/
|
|
27
|
-
export declare const SEGMENT_SOURCE_KIND: {
|
|
28
|
-
/** Maintained by hand in the admin (plans, tiers). */
|
|
29
|
-
readonly Manual: "manual";
|
|
30
|
-
/** A fixed list declared in configuration (regions). */
|
|
31
|
-
readonly Static: "static";
|
|
32
|
-
/** Mirrors a content collection — one segment per record (organisations). */
|
|
33
|
-
readonly ContentType: "contentType";
|
|
34
|
-
/** Mirrors an external directory, read through the resolver's adapter. */
|
|
35
|
-
readonly External: "external";
|
|
36
|
-
};
|
|
37
|
-
/** @see SEGMENT_SOURCE_KIND */
|
|
38
|
-
export type SegmentSourceKind = (typeof SEGMENT_SOURCE_KIND)[keyof typeof SEGMENT_SOURCE_KIND];
|
|
39
|
-
/**
|
|
40
|
-
* A type's lifecycle. `draining` is the state that makes slot reuse safe: the
|
|
41
|
-
* type has left the predicate and the UI, but its slot columns still hold the
|
|
42
|
-
* old ids and must be zeroed before another type may claim it. Skipping it is
|
|
43
|
-
* how a new type would silently inherit a stranger's segments.
|
|
44
|
-
*/
|
|
45
|
-
export declare const SEGMENT_TYPE_STATE: {
|
|
46
|
-
readonly Active: "active";
|
|
47
|
-
readonly Draining: "draining";
|
|
48
|
-
readonly Free: "free";
|
|
49
|
-
};
|
|
50
|
-
/** @see SEGMENT_TYPE_STATE */
|
|
51
|
-
export type SegmentTypeState = (typeof SEGMENT_TYPE_STATE)[keyof typeof SEGMENT_TYPE_STATE];
|
|
52
|
-
/** Who owns a type's definition — and therefore whether the admin may edit it. */
|
|
53
|
-
export declare const SEGMENT_TYPE_MANAGED_BY: {
|
|
54
|
-
/** Declared in `ortha.config.ts`; read-only in the admin. */
|
|
55
|
-
readonly Config: "config";
|
|
56
|
-
/** Created in the admin; editable there. */
|
|
57
|
-
readonly Ui: "ui";
|
|
58
|
-
};
|
|
59
|
-
/** @see SEGMENT_TYPE_MANAGED_BY */
|
|
60
|
-
export type SegmentTypeManagedBy = (typeof SEGMENT_TYPE_MANAGED_BY)[keyof typeof SEGMENT_TYPE_MANAGED_BY];
|
|
61
|
-
/** One declared axis of the access decision. */
|
|
62
|
-
export interface SegmentType {
|
|
63
|
-
/** Stable id. */
|
|
64
|
-
readonly id: string;
|
|
65
|
-
/** Tag namespace this type owns, e.g. `org` for `org:acme`. */
|
|
66
|
-
readonly key: SegmentTypeKey;
|
|
67
|
-
/** Human-readable name, shown in the editor. */
|
|
68
|
-
readonly label: string;
|
|
69
|
-
/** Rendering hint. @see SEGMENT_CARDINALITY */
|
|
70
|
-
readonly cardinality: SegmentCardinality;
|
|
71
|
-
/** Which projection slot pair this type reads and writes. */
|
|
72
|
-
readonly slot: number;
|
|
73
|
-
/** @see SEGMENT_TYPE_STATE */
|
|
74
|
-
readonly state: SegmentTypeState;
|
|
75
|
-
/** @see SEGMENT_TYPE_MANAGED_BY */
|
|
76
|
-
readonly managedBy: SegmentTypeManagedBy;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* The namespace a tag belongs to — everything before the first `:`.
|
|
80
|
-
*
|
|
81
|
-
* A tag with no separator has **no** namespace rather than being its own: a
|
|
82
|
-
* bare `pro` must not be swept up by a mask over some type that happens to be
|
|
83
|
-
* called `pro`, and returning `undefined` is what keeps
|
|
84
|
-
* {@link segmentMatchesTag} from guessing.
|
|
85
|
-
*/
|
|
86
|
-
export declare function tagNamespace(tag: SegmentTag): SegmentTypeKey | undefined;
|
|
87
|
-
//# sourceMappingURL=segment-type.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"segment-type.d.ts","sourceRoot":"","sources":["../../src/lib/segment-type.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;CAGtB,CAAC;AAEX,+BAA+B;AAC/B,MAAM,MAAM,kBAAkB,GAC1B,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;IAC5B,sDAAsD;;IAEtD,wDAAwD;;IAExD,6EAA6E;;IAE7E,0EAA0E;;CAEpE,CAAC;AAEX,+BAA+B;AAC/B,MAAM,MAAM,iBAAiB,GACzB,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAEX,8BAA8B;AAC9B,MAAM,MAAM,gBAAgB,GACxB,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAEjE,kFAAkF;AAClF,eAAO,MAAM,uBAAuB;IAChC,6DAA6D;;IAE7D,4CAA4C;;CAEtC,CAAC;AAEX,mCAAmC;AACnC,MAAM,MAAM,oBAAoB,GAC5B,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAE3E,gDAAgD;AAChD,MAAM,WAAW,WAAW;IACxB,iBAAiB;IACjB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,cAAc,GAAG,SAAS,CAGxE"}
|