@cullet/erp-core 1.0.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/KIT_CONTEXT.md +39 -0
- package/README.md +110 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/exceptions/validation-field.d.ts +2 -0
- package/dist/exceptions/validation-field.js +2 -0
- package/dist/gate-engine-registry.js +260 -0
- package/dist/gate-engine-registry.js.map +1 -0
- package/dist/gate-types.d.ts +307 -0
- package/dist/gate-v1-payload.schema.js +677 -0
- package/dist/gate-v1-payload.schema.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/dist/outcome.js +92 -0
- package/dist/outcome.js.map +1 -0
- package/dist/parse-gate-payload.d.ts +136 -0
- package/dist/policies/engines/index.d.ts +3 -0
- package/dist/policies/engines/index.js +2 -0
- package/dist/policies/engines/v1/gate/index.d.ts +115 -0
- package/dist/policies/engines/v1/gate/index.js +84 -0
- package/dist/policies/engines/v1/gate/index.js.map +1 -0
- package/dist/policies/index.d.ts +4 -0
- package/dist/policies/index.js +5 -0
- package/dist/policy-service.d.ts +541 -0
- package/dist/policy-service.js +992 -0
- package/dist/policy-service.js.map +1 -0
- package/dist/validation-code.js +210 -0
- package/dist/validation-code.js.map +1 -0
- package/dist/validation-error.d.ts +686 -0
- package/dist/validation-error.js +757 -0
- package/dist/validation-error.js.map +1 -0
- package/dist/validation-field.d.ts +12 -0
- package/dist/validation-field.js +23 -0
- package/dist/validation-field.js.map +1 -0
- package/meta.json +66 -0
- package/package.json +59 -0
- package/src/core/application/commands/command.ts +28 -0
- package/src/core/application/commands/index.ts +2 -0
- package/src/core/application/commands/requested-by.ts +97 -0
- package/src/core/application/index.ts +31 -0
- package/src/core/application/policy-error-mapper.ts +60 -0
- package/src/core/application/ports/index.ts +14 -0
- package/src/core/application/ports/logger.port.ts +10 -0
- package/src/core/application/ports/metrics.port.ts +9 -0
- package/src/core/application/ports/policy-port.ts +20 -0
- package/src/core/application/ports/repository.port.ts +7 -0
- package/src/core/application/ports/temporal-repository.port.ts +14 -0
- package/src/core/application/ports/tracer.port.ts +16 -0
- package/src/core/application/queries/index.ts +2 -0
- package/src/core/application/queries/query.ts +53 -0
- package/src/core/application/temporal/index.ts +11 -0
- package/src/core/application/temporal/temporal-context.ts +41 -0
- package/src/core/application/temporal/temporal-use-case.ts +41 -0
- package/src/core/application/use-case.ts +24 -0
- package/src/core/config/core-config.instance.ts +14 -0
- package/src/core/config/core-config.ts +87 -0
- package/src/core/config/index.ts +12 -0
- package/src/core/config/policy-reporter.ts +60 -0
- package/src/core/config/silent-policy-reporter.ts +5 -0
- package/src/core/domain/entity.ts +69 -0
- package/src/core/domain/rulesets/entity-ruleset.contracts.ts +16 -0
- package/src/core/domain/rulesets/ruleset-registry.ts +71 -0
- package/src/core/domain/rulesets/ruleset.contracts.ts +15 -0
- package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +13 -0
- package/src/core/domain/temporal/index.ts +18 -0
- package/src/core/domain/temporal/temporal-range.ts +53 -0
- package/src/core/domain/temporal/temporal-snapshot.ts +47 -0
- package/src/core/domain/temporal/transaction-time.ts +60 -0
- package/src/core/domain/temporal/valid-time.ts +57 -0
- package/src/core/domain/value-object.ts +30 -0
- package/src/core/errors/app-error.ts +85 -0
- package/src/core/errors/authentication-error.ts +171 -0
- package/src/core/errors/authorization-error.ts +204 -0
- package/src/core/errors/business-rule-violation-error.ts +28 -0
- package/src/core/errors/conflict-error.ts +246 -0
- package/src/core/errors/error-codes.ts +60 -0
- package/src/core/errors/idempotency-error.ts +372 -0
- package/src/core/errors/index.ts +99 -0
- package/src/core/errors/integration-error.ts +157 -0
- package/src/core/errors/legacy-incompatible-error.ts +26 -0
- package/src/core/errors/not-found-error.ts +27 -0
- package/src/core/errors/serialization-error.ts +233 -0
- package/src/core/errors/temporal-error.ts +145 -0
- package/src/core/errors/types.ts +74 -0
- package/src/core/errors/unexpected-error.ts +22 -0
- package/src/core/errors/utils/index.ts +4 -0
- package/src/core/errors/utils/json-safe.ts +93 -0
- package/src/core/errors/validation-error.ts +34 -0
- package/src/core/exceptions/business-rule-violation-exception.ts +12 -0
- package/src/core/exceptions/domain-exception.ts +9 -0
- package/src/core/exceptions/entity-not-found-exception.ts +12 -0
- package/src/core/exceptions/invalid-state-transition-exception.ts +15 -0
- package/src/core/exceptions/invariant-violation-exception.ts +9 -0
- package/src/core/exceptions/validation-code.ts +39 -0
- package/src/core/exceptions/validation-exception.ts +39 -0
- package/src/core/exceptions/validation-field.ts +32 -0
- package/src/core/index.ts +15 -0
- package/src/core/policies/asof/asof.ts +68 -0
- package/src/core/policies/asof/index.ts +2 -0
- package/src/core/policies/catalog/catalog-types.ts +12 -0
- package/src/core/policies/catalog/catalog.instance.ts +21 -0
- package/src/core/policies/catalog/index.ts +11 -0
- package/src/core/policies/catalog/policy-catalog-entry.ts +220 -0
- package/src/core/policies/catalog/policy-catalog.ts +124 -0
- package/src/core/policies/catalog/policy-key.ts +78 -0
- package/src/core/policies/context/context-builder.ts +391 -0
- package/src/core/policies/context/context-registry.instance.ts +26 -0
- package/src/core/policies/context/context-registry.ts +95 -0
- package/src/core/policies/context/context-resolver.ts +31 -0
- package/src/core/policies/context/context-seed.ts +32 -0
- package/src/core/policies/context/index.ts +21 -0
- package/src/core/policies/context/path.ts +152 -0
- package/src/core/policies/defs/in-memory-policy-definition-repo.ts +73 -0
- package/src/core/policies/defs/index.ts +20 -0
- package/src/core/policies/defs/policy-definition-repository.ts +8 -0
- package/src/core/policies/defs/policy-definition.ts +192 -0
- package/src/core/policies/defs/policy-payload.contracts.ts +13 -0
- package/src/core/policies/defs/policy-scope.ts +53 -0
- package/src/core/policies/defs/policy-semver.ts +83 -0
- package/src/core/policies/engines/compute-engine-registry.ts +57 -0
- package/src/core/policies/engines/compute-evaluator-registry.ts +28 -0
- package/src/core/policies/engines/compute-payload.ts +5 -0
- package/src/core/policies/engines/compute-registry.ts +67 -0
- package/src/core/policies/engines/compute-types.ts +51 -0
- package/src/core/policies/engines/condition-evaluator-reporter.ts +34 -0
- package/src/core/policies/engines/gate-engine-registry.ts +38 -0
- package/src/core/policies/engines/gate-payload.ts +5 -0
- package/src/core/policies/engines/gate-types.ts +63 -0
- package/src/core/policies/engines/index.ts +48 -0
- package/src/core/policies/engines/parse-compute-payload.ts +71 -0
- package/src/core/policies/engines/parse-gate-payload.ts +68 -0
- package/src/core/policies/engines/v1/compute/compute-engine-v1.ts +55 -0
- package/src/core/policies/engines/v1/compute/compute-payload.schema.ts +75 -0
- package/src/core/policies/engines/v1/compute/index.ts +12 -0
- package/src/core/policies/engines/v1/compute/resolve-decision-table-v1.ts +54 -0
- package/src/core/policies/engines/v1/condition-evaluator.ts +592 -0
- package/src/core/policies/engines/v1/condition-schema.ts +50 -0
- package/src/core/policies/engines/v1/condition-types.ts +41 -0
- package/src/core/policies/engines/v1/gate/gate-engine-v1.ts +157 -0
- package/src/core/policies/engines/v1/gate/gate-types-v1.ts +12 -0
- package/src/core/policies/engines/v1/gate/gate-v1-payload.schema.ts +46 -0
- package/src/core/policies/engines/v1/gate/index.ts +9 -0
- package/src/core/policies/engines/v1/index.ts +13 -0
- package/src/core/policies/index.ts +132 -0
- package/src/core/policies/package/policy-package.ts +17 -0
- package/src/core/policies/policy-ids.ts +60 -0
- package/src/core/policies/resolver/index.ts +1 -0
- package/src/core/policies/resolver/policy-resolver.ts +57 -0
- package/src/core/policies/service/index.ts +10 -0
- package/src/core/policies/service/policy-evaluation-error.ts +148 -0
- package/src/core/policies/service/policy-service.ts +493 -0
- package/src/core/policies/utils/date.ts +7 -0
- package/src/core/policies/utils/hash.ts +90 -0
- package/src/core/policies/utils/index.ts +3 -0
- package/src/core/policies/utils/result.ts +4 -0
- package/src/core/result/outcome.ts +158 -0
- package/src/core/result/result.ts +153 -0
- package/src/core/shared/aggregate-version.ts +11 -0
- package/src/core/shared/hashing.ts +38 -0
- package/src/core/shared/immutable.ts +46 -0
- package/src/core/shared/temporal-guards.ts +22 -0
- package/src/core/versioning/domain-event-contracts.ts +73 -0
- package/src/core/versioning/version.ts +34 -0
- package/src/errors/index.ts +1 -0
- package/src/examples/rulesets/entity/order-creation-rules-v1.ts +31 -0
- package/src/examples/rulesets/entity/order-invariants-v1.ts +48 -0
- package/src/examples/rulesets/entity/order-invariants-v2.ts +28 -0
- package/src/examples/rulesets/entity/order.ts +162 -0
- package/src/examples/rulesets/value-object/cpf-rules-v1.ts +52 -0
- package/src/examples/rulesets/value-object/cpf-rules-v2.ts +27 -0
- package/src/examples/rulesets/value-object/cpf.ts +37 -0
- package/src/examples/rulesets/value-object/customer.ts +67 -0
- package/src/examples/rulesets/value-object/person-name-rules-v1.ts +37 -0
- package/src/examples/rulesets/value-object/person-name-rules-v2.ts +27 -0
- package/src/examples/rulesets/value-object/person-name.ts +39 -0
- package/src/exceptions/validation-field.ts +1 -0
- package/src/index.ts +11 -0
- package/src/policies/engines/index.ts +1 -0
- package/src/policies/engines/v1/gate/index.ts +1 -0
- package/src/policies/index.ts +1 -0
- package/src/version.ts +7 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { UnexpectedError } from "../../errors";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AsOfSource,
|
|
5
|
+
PolicyKind,
|
|
6
|
+
PolicyOwner,
|
|
7
|
+
PolicyScopeLevel,
|
|
8
|
+
} from "./catalog-types";
|
|
9
|
+
import type { PolicyKey } from "./policy-key";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Properties for creating a PolicyCatalogEntry.
|
|
13
|
+
*/
|
|
14
|
+
export interface PolicyCatalogEntryProps {
|
|
15
|
+
readonly key: PolicyKey;
|
|
16
|
+
readonly kind: PolicyKind;
|
|
17
|
+
readonly gateEngineVersion?: number;
|
|
18
|
+
readonly computeEngineVersion?: number;
|
|
19
|
+
readonly payloadSchemaVersion?: number;
|
|
20
|
+
readonly owner: PolicyOwner;
|
|
21
|
+
readonly allowedScopes: readonly PolicyScopeLevel[];
|
|
22
|
+
readonly asOfSource: AsOfSource;
|
|
23
|
+
/** Paths the context must contain for this policy to be evaluable. */
|
|
24
|
+
readonly contextRequirements: readonly string[];
|
|
25
|
+
readonly tags?: readonly string[];
|
|
26
|
+
readonly description: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Represents a single entry in the Policy Catalog.
|
|
31
|
+
* Encapsulates metadata and validation logic for a policy.
|
|
32
|
+
*/
|
|
33
|
+
export class PolicyCatalogEntry {
|
|
34
|
+
public readonly key: PolicyKey;
|
|
35
|
+
public readonly kind: PolicyKind;
|
|
36
|
+
|
|
37
|
+
public readonly gateEngineVersion?: number;
|
|
38
|
+
public readonly computeEngineVersion?: number;
|
|
39
|
+
public readonly payloadSchemaVersion?: number;
|
|
40
|
+
|
|
41
|
+
public readonly owner: PolicyOwner;
|
|
42
|
+
public readonly allowedScopes: readonly PolicyScopeLevel[];
|
|
43
|
+
public readonly asOfSource: AsOfSource;
|
|
44
|
+
public readonly contextRequirements: readonly string[];
|
|
45
|
+
public readonly tags: readonly string[];
|
|
46
|
+
public readonly description: string;
|
|
47
|
+
|
|
48
|
+
constructor(props: PolicyCatalogEntryProps) {
|
|
49
|
+
this.key = props.key;
|
|
50
|
+
this.kind = props.kind;
|
|
51
|
+
this.gateEngineVersion = props.gateEngineVersion;
|
|
52
|
+
this.computeEngineVersion = props.computeEngineVersion;
|
|
53
|
+
this.payloadSchemaVersion = props.payloadSchemaVersion;
|
|
54
|
+
this.owner = props.owner;
|
|
55
|
+
this.allowedScopes = [...props.allowedScopes];
|
|
56
|
+
this.asOfSource = props.asOfSource;
|
|
57
|
+
this.contextRequirements = [...props.contextRequirements];
|
|
58
|
+
this.tags = [...(props.tags ?? [])];
|
|
59
|
+
this.description = props.description;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static assertFamilyConsistency(entries: readonly PolicyCatalogEntry[]): void {
|
|
63
|
+
if (entries.length === 0) {
|
|
64
|
+
throw new UnexpectedError(
|
|
65
|
+
"Cannot validate an empty PolicyCatalogEntry family",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const [first, ...rest] = entries;
|
|
70
|
+
for (const entry of rest) {
|
|
71
|
+
if (!first.key.equals(entry.key)) {
|
|
72
|
+
throw new UnexpectedError(
|
|
73
|
+
"Cannot merge PolicyCatalog entries with different keys",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (first.kind !== entry.kind) {
|
|
78
|
+
throw new UnexpectedError(
|
|
79
|
+
`PolicyCatalog entries for key "${first.key.toString()}" must share the same kind`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (first.owner !== entry.owner) {
|
|
84
|
+
throw new UnexpectedError(
|
|
85
|
+
`PolicyCatalog entries for key "${first.key.toString()}" must share the same owner`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (first.asOfSource !== entry.asOfSource) {
|
|
90
|
+
throw new UnexpectedError(
|
|
91
|
+
`PolicyCatalog entries for key "${first.key.toString()}" must share the same asOfSource`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (first.description !== entry.description) {
|
|
96
|
+
throw new UnexpectedError(
|
|
97
|
+
`PolicyCatalog entries for key "${first.key.toString()}" must share the same description`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static from(
|
|
104
|
+
entry: PolicyCatalogEntry | PolicyCatalogEntryProps,
|
|
105
|
+
): PolicyCatalogEntry {
|
|
106
|
+
return entry instanceof PolicyCatalogEntry
|
|
107
|
+
? entry
|
|
108
|
+
: new PolicyCatalogEntry(entry);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
isGate(): boolean {
|
|
112
|
+
return this.kind === "GATE";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
isCompute(): boolean {
|
|
116
|
+
return this.kind === "COMPUTE";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
allowsScope(scope: PolicyScopeLevel): boolean {
|
|
120
|
+
return this.allowedScopes.includes(scope);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
hasExplicitVersionSelector(): boolean {
|
|
124
|
+
return (
|
|
125
|
+
this.gateEngineVersion !== undefined ||
|
|
126
|
+
this.computeEngineVersion !== undefined ||
|
|
127
|
+
this.payloadSchemaVersion !== undefined
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
matchesVersion(params: {
|
|
132
|
+
readonly kind: PolicyKind;
|
|
133
|
+
readonly gateEngineVersion?: number;
|
|
134
|
+
readonly computeEngineVersion?: number;
|
|
135
|
+
readonly payloadSchemaVersion?: number;
|
|
136
|
+
}): boolean {
|
|
137
|
+
if (this.kind !== params.kind) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
this.gateEngineVersion !== undefined &&
|
|
143
|
+
this.gateEngineVersion !== params.gateEngineVersion
|
|
144
|
+
) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
this.computeEngineVersion !== undefined &&
|
|
150
|
+
this.computeEngineVersion !== params.computeEngineVersion
|
|
151
|
+
) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
this.payloadSchemaVersion !== undefined &&
|
|
157
|
+
this.payloadSchemaVersion !== params.payloadSchemaVersion
|
|
158
|
+
) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
toVariantKey(): string {
|
|
166
|
+
return [
|
|
167
|
+
this.key.toString(),
|
|
168
|
+
this.kind,
|
|
169
|
+
this.gateEngineVersion ?? "gate:any",
|
|
170
|
+
this.computeEngineVersion ?? "compute:any",
|
|
171
|
+
this.payloadSchemaVersion ?? "schema:any",
|
|
172
|
+
].join("::");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
usesNowAsOf(): boolean {
|
|
176
|
+
return this.asOfSource === "NOW";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
usesCallerProvidedAsOf(): boolean {
|
|
180
|
+
return this.asOfSource === "CALLER_PROVIDED";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
requiresContext(path: string): boolean {
|
|
184
|
+
return this.contextRequirements.includes(path);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
requiresAllContexts(paths: readonly string[]): boolean {
|
|
188
|
+
return paths.every((path) => this.requiresContext(path));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
hasTag(tag: string): boolean {
|
|
192
|
+
return this.tags.includes(tag);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
equals(other: PolicyCatalogEntry): boolean {
|
|
196
|
+
return this.key.equals(other.key);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
toJSON(): PolicyCatalogEntryProps {
|
|
200
|
+
return {
|
|
201
|
+
key: this.key,
|
|
202
|
+
kind: this.kind,
|
|
203
|
+
...(this.gateEngineVersion !== undefined
|
|
204
|
+
? { gateEngineVersion: this.gateEngineVersion }
|
|
205
|
+
: {}),
|
|
206
|
+
...(this.computeEngineVersion !== undefined
|
|
207
|
+
? { computeEngineVersion: this.computeEngineVersion }
|
|
208
|
+
: {}),
|
|
209
|
+
...(this.payloadSchemaVersion !== undefined
|
|
210
|
+
? { payloadSchemaVersion: this.payloadSchemaVersion }
|
|
211
|
+
: {}),
|
|
212
|
+
owner: this.owner,
|
|
213
|
+
allowedScopes: [...this.allowedScopes],
|
|
214
|
+
asOfSource: this.asOfSource,
|
|
215
|
+
contextRequirements: [...this.contextRequirements],
|
|
216
|
+
tags: this.tags.length > 0 ? [...this.tags] : undefined,
|
|
217
|
+
description: this.description,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { UnexpectedError } from "../../errors";
|
|
2
|
+
import { Result } from "../../result/result";
|
|
3
|
+
|
|
4
|
+
import { PolicyCatalogEntry } from "./policy-catalog-entry";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* In-code catalog of all known policies.
|
|
8
|
+
* The catalog defines the universe of policies; the database cannot invent new ones.
|
|
9
|
+
*/
|
|
10
|
+
export class PolicyCatalog {
|
|
11
|
+
private readonly versionedEntries: ReadonlyMap<string, PolicyCatalogEntry>;
|
|
12
|
+
private readonly entriesByKey: ReadonlyMap<
|
|
13
|
+
string,
|
|
14
|
+
readonly PolicyCatalogEntry[]
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
constructor(entries: readonly PolicyCatalogEntry[]) {
|
|
18
|
+
const families = new Map<string, PolicyCatalogEntry[]>();
|
|
19
|
+
const versionedMap = new Map<string, PolicyCatalogEntry>();
|
|
20
|
+
|
|
21
|
+
for (const rawEntry of entries) {
|
|
22
|
+
const entry = PolicyCatalogEntry.from(rawEntry);
|
|
23
|
+
const key = entry.key.toString();
|
|
24
|
+
const variantKey = entry.toVariantKey();
|
|
25
|
+
|
|
26
|
+
if (versionedMap.has(variantKey)) {
|
|
27
|
+
throw new UnexpectedError(
|
|
28
|
+
`Duplicate PolicyCatalog entry for key "${key}" and variant "${variantKey}"`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const family = families.get(key) ?? [];
|
|
33
|
+
family.push(entry);
|
|
34
|
+
families.set(key, family);
|
|
35
|
+
versionedMap.set(variantKey, entry);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const family of families.values()) {
|
|
39
|
+
PolicyCatalogEntry.assertFamilyConsistency(family);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.versionedEntries = versionedMap;
|
|
43
|
+
this.entriesByKey = families;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get(key: string): Result<PolicyCatalogEntry, string> {
|
|
47
|
+
const familyResult = this.getFamily(key);
|
|
48
|
+
if (familyResult.isErr()) {
|
|
49
|
+
return Result.err(familyResult.errorOrNull()!);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const family = familyResult.getOrNull()!;
|
|
53
|
+
if (family.length > 1) {
|
|
54
|
+
return Result.err(
|
|
55
|
+
`Policy key "${key}" has multiple catalog variants; use getVersioned(...) or getFamily(...)`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const [entry] = family;
|
|
60
|
+
if (!entry) {
|
|
61
|
+
return Result.err(`Policy not found in catalog: "${key}"`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Result.ok(entry);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getFamily(key: string): Result<readonly PolicyCatalogEntry[], string> {
|
|
68
|
+
const family = this.entriesByKey.get(key);
|
|
69
|
+
if (!family) {
|
|
70
|
+
return Result.err(`Policy not found in catalog: "${key}"`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Result.ok(family);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getVersioned(params: {
|
|
77
|
+
readonly key: string;
|
|
78
|
+
readonly kind: "GATE" | "COMPUTE";
|
|
79
|
+
readonly gateEngineVersion?: number;
|
|
80
|
+
readonly computeEngineVersion?: number;
|
|
81
|
+
readonly payloadSchemaVersion?: number;
|
|
82
|
+
}): Result<PolicyCatalogEntry, string> {
|
|
83
|
+
const family = this.entriesByKey.get(params.key);
|
|
84
|
+
if (!family || family.length === 0) {
|
|
85
|
+
return Result.err(`Policy not found in catalog: "${params.key}"`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const exactMatch = family.find((entry) => entry.matchesVersion(params));
|
|
89
|
+
if (exactMatch) {
|
|
90
|
+
return Result.ok(exactMatch);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (family.length === 1 && !family[0].hasExplicitVersionSelector()) {
|
|
94
|
+
return Result.ok(family[0]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const versionDetails = [
|
|
98
|
+
params.kind,
|
|
99
|
+
params.gateEngineVersion !== undefined
|
|
100
|
+
? `gateEngineVersion=${params.gateEngineVersion}`
|
|
101
|
+
: null,
|
|
102
|
+
params.computeEngineVersion !== undefined
|
|
103
|
+
? `computeEngineVersion=${params.computeEngineVersion}`
|
|
104
|
+
: null,
|
|
105
|
+
params.payloadSchemaVersion !== undefined
|
|
106
|
+
? `payloadSchemaVersion=${params.payloadSchemaVersion}`
|
|
107
|
+
: null,
|
|
108
|
+
]
|
|
109
|
+
.filter((part): part is string => part !== null)
|
|
110
|
+
.join(", ");
|
|
111
|
+
|
|
112
|
+
return Result.err(
|
|
113
|
+
`Policy variant not found in catalog: "${params.key}" (${versionDetails})`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
list(): readonly PolicyCatalogEntry[] {
|
|
118
|
+
return Array.from(this.versionedEntries.values());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
has(key: string): boolean {
|
|
122
|
+
return this.entriesByKey.has(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Result } from "../../result/result";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PolicyKey value object.
|
|
5
|
+
*
|
|
6
|
+
* Format: `module.area.policy_name` (exactly 3 dot-separated segments).
|
|
7
|
+
* - module: structural segment (lowercase alphanumeric + underscore, starting with a letter)
|
|
8
|
+
* - area: structural segment (lowercase alphanumeric + underscore, starting with a letter)
|
|
9
|
+
* - name: specific policy identifier (lowercase alphanumeric + underscore, starting with a letter)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const SEGMENT_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
13
|
+
const MAX_POLICY_KEY_LENGTH = 255;
|
|
14
|
+
|
|
15
|
+
export class PolicyKey {
|
|
16
|
+
public readonly module: string;
|
|
17
|
+
public readonly area: string;
|
|
18
|
+
public readonly name: string;
|
|
19
|
+
|
|
20
|
+
private constructor(module: string, area: string, name: string) {
|
|
21
|
+
this.module = module;
|
|
22
|
+
this.area = area;
|
|
23
|
+
this.name = name;
|
|
24
|
+
Object.freeze(this);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static parse(raw: string): Result<PolicyKey, string> {
|
|
28
|
+
if (!raw || typeof raw !== "string") {
|
|
29
|
+
return Result.err("PolicyKey cannot be empty");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (raw.length > MAX_POLICY_KEY_LENGTH) {
|
|
33
|
+
return Result.err(
|
|
34
|
+
`PolicyKey cannot exceed ${MAX_POLICY_KEY_LENGTH} characters, got ${raw.length}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const segments = raw.split(".");
|
|
39
|
+
if (segments.length !== 3) {
|
|
40
|
+
return Result.err(
|
|
41
|
+
`PolicyKey must have exactly 3 segments (module.area.name), got ${segments.length}: "${raw}"`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const [rawModule, rawArea, name] = segments;
|
|
46
|
+
|
|
47
|
+
if (!SEGMENT_PATTERN.test(rawModule)) {
|
|
48
|
+
return Result.err(
|
|
49
|
+
`PolicyKey segment "module" is invalid: "${rawModule}". ` +
|
|
50
|
+
"Must start with a lowercase letter and contain only [a-z0-9_].",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!SEGMENT_PATTERN.test(rawArea)) {
|
|
55
|
+
return Result.err(
|
|
56
|
+
`PolicyKey segment "area" is invalid: "${rawArea}". ` +
|
|
57
|
+
"Must start with a lowercase letter and contain only [a-z0-9_].",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!SEGMENT_PATTERN.test(name)) {
|
|
62
|
+
return Result.err(
|
|
63
|
+
`PolicyKey segment "name" is invalid: "${name}". ` +
|
|
64
|
+
"Must start with a lowercase letter and contain only [a-z0-9_].",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Result.ok(new PolicyKey(rawModule, rawArea, name));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
toString(): string {
|
|
72
|
+
return `${this.module}.${this.area}.${this.name}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
equals(other: PolicyKey): boolean {
|
|
76
|
+
return this.toString() === other.toString();
|
|
77
|
+
}
|
|
78
|
+
}
|