@aventara/core 1.0.0-rc.1 → 1.0.0-rc.10
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/CHANGELOG.md +943 -16
- package/ERRORS.md +142 -0
- package/README.md +274 -16
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +22 -0
- package/dist/adapters/adapter-identifier.d.ts +9 -0
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-model.d.ts +9 -0
- package/dist/adapters/adapter-operation.d.ts +18 -3
- package/dist/adapters/adapter-resource.d.ts +5 -0
- package/dist/adapters/adapter.interface.d.ts +10 -0
- package/dist/avz/avz.d.ts +1 -1
- package/dist/avz/json-shape.registry.d.ts +2 -1
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.d.ts +6 -0
- package/dist/compiler/contract.compiler.js +227 -24
- package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
- package/dist/compiler/managed-elsewhere.projector.js +179 -0
- package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
- package/dist/compiler/managed-elsewhere.validator.js +74 -0
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +73 -0
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/config.resolver.d.ts +24 -0
- package/dist/config/config.resolver.js +65 -0
- package/dist/config/config.validator.js +222 -8
- package/dist/config/define-resource.d.ts +1 -5
- package/dist/config/entrypoint.normalizer.d.ts +3 -2
- package/dist/config/exact-framework-config.d.ts +67 -22
- package/dist/config/field-compute.resolver.d.ts +2 -0
- package/dist/config/field-compute.resolver.js +8 -0
- package/dist/config/framework-config.d.ts +53 -15
- package/dist/config/framework-resources-config.d.ts +25 -14
- package/dist/config/pipeline-config.d.ts +4 -4
- package/dist/config/pipeline-tree.resolver.d.ts +2 -2
- package/dist/contracts/access-route.rules.d.ts +10 -0
- package/dist/contracts/access-route.rules.js +45 -0
- package/dist/contracts/capability.d.ts +26 -1
- package/dist/contracts/capability.js +20 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +301 -89
- package/dist/contracts/client-contract-structure.validator.js +37 -2
- package/dist/contracts/contract-access.d.ts +29 -0
- package/dist/contracts/contract-access.js +1 -0
- package/dist/contracts/contract-canonicalizer.d.ts +9 -1
- package/dist/contracts/contract-canonicalizer.js +30 -4
- package/dist/contracts/contract-hash.d.ts +0 -1
- package/dist/contracts/contract-hash.js +1 -1
- package/dist/contracts/contract.d.ts +27 -1
- package/dist/contracts/field.d.ts +58 -1
- package/dist/contracts/json-schema.projector.d.ts +11 -0
- package/dist/contracts/json-schema.projector.js +78 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/access-facade.d.ts +116 -0
- package/dist/framework/access-facade.js +120 -0
- package/dist/framework/contract-view.d.ts +120 -16
- package/dist/framework/framework-construction.error.d.ts +14 -4
- package/dist/framework/framework-construction.error.js +18 -2
- package/dist/framework/framework.d.ts +26 -5
- package/dist/framework/framework.factory.d.ts +1 -1
- package/dist/framework/framework.factory.js +115 -23
- package/dist/framework/operation-facade.d.ts +80 -13
- package/dist/framework/operation-facade.js +2 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1 -0
- package/dist/internal.d.ts +7 -2
- package/dist/internal.js +6 -0
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +148 -60
- package/dist/operations/operation-call.d.ts +9 -5
- package/dist/operations/operation-result.d.ts +6 -4
- package/dist/operations/operation-validator-core.js +159 -36
- package/dist/operations/operation-validator.d.ts +1 -1
- package/dist/operations/structural-limits.walker.d.ts +1 -1
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/write-scope.resolver.d.ts +4 -0
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/policy/access-provider.interface.d.ts +91 -0
- package/dist/policy/access-provider.interface.js +1 -0
- package/dist/policy/access-provider.registry.d.ts +17 -0
- package/dist/policy/access-provider.registry.js +15 -0
- package/dist/policy/access-question.d.ts +20 -0
- package/dist/policy/access-question.js +1 -0
- package/dist/policy/access-question.registry.d.ts +5 -0
- package/dist/policy/access-question.registry.js +19 -0
- package/dist/policy/access-question.runtime.d.ts +36 -0
- package/dist/policy/access-question.runtime.js +297 -0
- package/dist/policy/access-verb.table.d.ts +56 -0
- package/dist/policy/access-verb.table.js +21 -0
- package/dist/policy/access-verbs.d.ts +46 -0
- package/dist/policy/access-verbs.js +1 -0
- package/dist/policy/policy-admission.d.ts +20 -0
- package/dist/policy/policy-admission.js +409 -0
- package/dist/policy/policy-after-update.runtime.d.ts +79 -0
- package/dist/policy/policy-after-update.runtime.js +208 -0
- package/dist/policy/policy-condition.d.ts +15 -0
- package/dist/policy/policy-condition.js +36 -0
- package/dist/policy/policy-engine.d.ts +205 -0
- package/dist/policy/policy-engine.js +1 -0
- package/dist/policy/policy-named-rows.d.ts +18 -0
- package/dist/policy/policy-named-rows.js +51 -0
- package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
- package/dist/policy/policy-nested-write.runtime.js +545 -0
- package/dist/policy/policy-read.runtime.d.ts +81 -0
- package/dist/policy/policy-read.runtime.js +503 -0
- package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
- package/dist/policy/policy-reference-write.runtime.js +345 -0
- package/dist/policy/policy-request.d.ts +41 -0
- package/dist/policy/policy-request.js +63 -0
- package/dist/policy/policy-session.d.ts +106 -0
- package/dist/policy/policy-session.js +295 -0
- package/dist/policy/policy-startup.validator.d.ts +10 -0
- package/dist/policy/policy-startup.validator.js +112 -0
- package/dist/policy/policy-write.runtime.d.ts +39 -0
- package/dist/policy/policy-write.runtime.js +532 -0
- package/dist/prebuilt/core-version.generated.d.ts +2 -0
- package/dist/prebuilt/core-version.generated.js +1 -0
- package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
- package/dist/prebuilt/prebuilt-contracts.js +1 -0
- package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +229 -0
- package/dist/protocol/absent-route.responder.d.ts +9 -27
- package/dist/protocol/absent-route.responder.js +36 -3
- package/dist/protocol/contract-document.responder.d.ts +2 -1
- package/dist/protocol/http-request.decoder.d.ts +19 -3
- package/dist/protocol/http-request.decoder.js +24 -1
- package/dist/protocol/http-request.reader.d.ts +6 -3
- package/dist/protocol/http-request.reader.js +1 -0
- package/dist/protocol/http-response.encoder.js +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/protocol/protocol-surface.d.ts +6 -1
- package/dist/protocol/protocol-surface.js +7 -0
- package/dist/protocol/protocol.binder.d.ts +16 -1
- package/dist/protocol/protocol.binder.js +4 -1
- package/dist/protocol/protocol.handler.d.ts +12 -1
- package/dist/protocol/protocol.handler.js +86 -17
- package/dist/runtime/caller-data.copier.d.ts +18 -3
- package/dist/runtime/caller-data.copier.js +21 -8
- package/dist/runtime/caller-data.door.d.ts +13 -6
- package/dist/runtime/computed-read-runtime.d.ts +6 -1
- package/dist/runtime/computed-read-runtime.js +232 -45
- package/dist/runtime/computed-value.error.d.ts +3 -2
- package/dist/runtime/computed-value.error.js +3 -2
- package/dist/runtime/context-executor.runtime.d.ts +4 -3
- package/dist/runtime/context-executor.runtime.js +4 -2
- package/dist/runtime/core-class.brand.d.ts +10 -0
- package/dist/runtime/core-class.brand.js +21 -0
- package/dist/runtime/create-requirement.d.ts +31 -5
- package/dist/runtime/create-requirement.js +129 -21
- package/dist/runtime/decimal.d.ts +14 -0
- package/dist/runtime/decimal.js +21 -0
- package/dist/runtime/diagnostics-reporter.d.ts +44 -9
- package/dist/runtime/diagnostics-reporter.js +13 -1
- package/dist/runtime/error-boundary.d.ts +9 -2
- package/dist/runtime/error-boundary.js +12 -0
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/execution-options.d.ts +8 -0
- package/dist/runtime/field-check.evaluator.d.ts +0 -2
- package/dist/runtime/field-check.evaluator.js +1 -1
- package/dist/runtime/field-compute.d.ts +11 -10
- package/dist/runtime/field-stage.runtime.d.ts +3 -1
- package/dist/runtime/field-stage.runtime.js +8 -4
- package/dist/runtime/field-validator.runtime.d.ts +19 -1
- package/dist/runtime/field-validator.runtime.js +47 -8
- package/dist/runtime/framework-error.js +9 -0
- package/dist/runtime/framework-warning.emitter.d.ts +6 -0
- package/dist/runtime/framework-warning.emitter.js +8 -0
- package/dist/runtime/json-value.d.ts +10 -5
- package/dist/runtime/level-lifecycle.runtime.d.ts +4 -3
- package/dist/runtime/level-lifecycle.runtime.js +3 -5
- package/dist/runtime/mutation-planner.d.ts +1 -6
- package/dist/runtime/mutation-planner.js +3 -14
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-request.d.ts +16 -9
- package/dist/runtime/operation-runtime.d.ts +35 -17
- package/dist/runtime/operation-runtime.js +220 -49
- package/dist/runtime/outcome-mapper.d.ts +2 -1
- package/dist/runtime/output-validator.d.ts +1 -1
- package/dist/runtime/owned-execution.registry.d.ts +13 -3
- package/dist/runtime/pipe-output.validator.d.ts +10 -0
- package/dist/runtime/pipe-output.validator.js +4 -2
- package/dist/runtime/pipeline.d.ts +47 -22
- package/dist/runtime/readonly-value.d.ts +20 -5
- package/dist/runtime/readonly-value.js +74 -26
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/runtime/scalar-runtime.d.ts +4 -11
- package/dist/runtime/scalar-runtime.js +91 -22
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/operation-arguments-encoder.js +8 -3
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +10 -2
- package/dist/transactions/sql-statement.validator.d.ts +2 -2
- package/dist/transactions/sql-statement.validator.js +38 -5
- package/dist/transactions/transaction-envelope.d.ts +5 -4
- package/dist/transactions/transaction-integrity-error.js +2 -0
- package/dist/transactions/transaction-plan-assembler.d.ts +12 -5
- package/dist/transactions/transaction-plan-assembler.js +48 -10
- package/dist/transactions/transaction-plan-validator.d.ts +23 -7
- package/dist/transactions/transaction-plan-validator.js +27 -1
- package/dist/transactions/transaction-reference-resolver.d.ts +3 -2
- package/dist/transactions/transaction-reference-validator.d.ts +43 -3
- package/dist/transactions/transaction-reference-validator.js +83 -57
- package/dist/transactions/transaction-reference.d.ts +1 -1
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-request.d.ts +8 -8
- package/dist/transactions/transaction-runtime.d.ts +5 -2
- package/dist/transactions/transaction-runtime.js +79 -27
- package/dist/transactions/transaction-settlement.runtime.d.ts +4 -3
- package/dist/transactions/transaction-slot-path-error.js +2 -0
- package/dist/transactions/transaction-stage.runtime.d.ts +5 -3
- package/dist/transactions/transaction-stage.runtime.js +7 -4
- package/package.json +6 -5
- package/src/runtime/field-check.evaluator.ts +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AvAccessProvider } from "./access-provider.interface.js";
|
|
2
|
+
/** Records the provider `framework` was configured with; called once, at construction. */
|
|
3
|
+
export declare function recordAccessProvider(framework: object, access: AvAccessProvider | undefined): void;
|
|
4
|
+
/** What a host reads of the provider a Framework was configured with. */
|
|
5
|
+
type AccessProviderView = {
|
|
6
|
+
/** The provider's `state`, called on the provider. */
|
|
7
|
+
readonly state: AvAccessProvider["state"];
|
|
8
|
+
/** The provider's `client`, when it has one. */
|
|
9
|
+
readonly client: AvAccessProvider["client"];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The provider `framework` was configured with (`access`), as a view;
|
|
13
|
+
* `undefined` for a Framework without `access`, or a value that is no
|
|
14
|
+
* constructed Framework.
|
|
15
|
+
*/
|
|
16
|
+
export declare function accessProviderOf(framework: object): AccessProviderView | undefined;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const ACCESS_PROVIDERS = new WeakMap();
|
|
2
|
+
export function recordAccessProvider(framework, access) {
|
|
3
|
+
if (access !== undefined) {
|
|
4
|
+
ACCESS_PROVIDERS.set(framework, access);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function accessProviderOf(framework) {
|
|
8
|
+
const access = ACCESS_PROVIDERS.get(framework);
|
|
9
|
+
return access === undefined
|
|
10
|
+
? undefined
|
|
11
|
+
: Object.freeze({
|
|
12
|
+
state: (input) => access.state(input),
|
|
13
|
+
client: access.client,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AvPolicyWhere } from "./policy-engine.js";
|
|
2
|
+
/**
|
|
3
|
+
* One question to the policy, as a client asks it: may the signed-in principal
|
|
4
|
+
* run `action` on `resource`, or on the one row `where` names. The answer is
|
|
5
|
+
* what the real call would do before any data is sent, decided by the same
|
|
6
|
+
* engine on the server; a row the principal cannot reach answers `false`
|
|
7
|
+
* exactly as a missing one does. It only shapes the UI, and the real call is
|
|
8
|
+
* still checked.
|
|
9
|
+
*
|
|
10
|
+
* The answer is about the row and the action, not about what a page reads of
|
|
11
|
+
* the row: it is the call selecting the Resource's identifier only. A field the
|
|
12
|
+
* principal reads on some rows only is checked by the page's own call that
|
|
13
|
+
* selects it, so `true` here and a refusal of that call can both hold.
|
|
14
|
+
*/
|
|
15
|
+
export type AvAccessQuestion<R extends string = string, UniqueWhere = AvPolicyWhere> = {
|
|
16
|
+
readonly resource: R;
|
|
17
|
+
readonly action: "read" | "create" | "update" | "delete";
|
|
18
|
+
/** One row, by its unique where. Omitted: may the principal run `action` on `resource` at all. */
|
|
19
|
+
readonly where?: UniqueWhere;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AccessQuestionsAnswerer } from "./access-question.runtime.js";
|
|
2
|
+
/** Records `framework`'s `can` answerer; called once, at construction. */
|
|
3
|
+
export declare function recordAccessQuestions(framework: object, answer: AccessQuestionsAnswerer): void;
|
|
4
|
+
/** `framework`'s `can` answerer, or `undefined` for anything `createFramework` did not return. */
|
|
5
|
+
export declare function accessQuestionsOf(framework: object): AccessQuestionsAnswerer | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { coreBrandOf } from "../runtime/core-class.brand.js";
|
|
2
|
+
const ANSWERERS = sharedAnswererRegistry();
|
|
3
|
+
function sharedAnswererRegistry() {
|
|
4
|
+
const key = coreBrandOf("access-questions");
|
|
5
|
+
const holder = globalThis;
|
|
6
|
+
const existing = holder[key];
|
|
7
|
+
if (existing instanceof WeakMap) {
|
|
8
|
+
return existing;
|
|
9
|
+
}
|
|
10
|
+
const registry = new WeakMap();
|
|
11
|
+
Object.defineProperty(holder, key, { value: registry });
|
|
12
|
+
return registry;
|
|
13
|
+
}
|
|
14
|
+
export function recordAccessQuestions(framework, answer) {
|
|
15
|
+
ANSWERERS.set(framework, answer);
|
|
16
|
+
}
|
|
17
|
+
export function accessQuestionsOf(framework) {
|
|
18
|
+
return ANSWERERS.get(framework);
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
2
|
+
import type { AvAdapterOperationExecutor } from "../adapters/adapter-operation-executor.interface.js";
|
|
3
|
+
import type { AvContracts } from "../compiler/contract.compiler.js";
|
|
4
|
+
import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
|
|
5
|
+
import type { RemoteRequestOrigin } from "../config/config-layer.js";
|
|
6
|
+
import { type AvDiagnosticsSink } from "../runtime/diagnostics-reporter.js";
|
|
7
|
+
import type { MisuseReporter } from "../runtime/misuse-reporter.js";
|
|
8
|
+
import { type AvOperationResponse } from "../runtime/operation-response.js";
|
|
9
|
+
import type { AvState } from "../runtime/request-state.js";
|
|
10
|
+
/** A `can` request as core answers it: the body the protocol read, under the request's origin, id and state. */
|
|
11
|
+
export type AccessQuestionsRequest = {
|
|
12
|
+
readonly origin: RemoteRequestOrigin;
|
|
13
|
+
readonly requestId: string;
|
|
14
|
+
/** The request's `ctx.$state` as its host seeded it; absent, `{}`. */
|
|
15
|
+
readonly state?: AvState;
|
|
16
|
+
/** The JSON object the request carried: `{ questions }`. */
|
|
17
|
+
readonly body: unknown;
|
|
18
|
+
};
|
|
19
|
+
/** Answers one `can` request: `A1000` with one boolean per question, or the refusal. */
|
|
20
|
+
export type AccessQuestionsAnswerer = (request: AccessQuestionsRequest) => Promise<AvOperationResponse<readonly boolean[]>>;
|
|
21
|
+
/** What answering reads off the Framework. */
|
|
22
|
+
type AccessQuestionsDependencies = {
|
|
23
|
+
readonly contracts: AvContracts;
|
|
24
|
+
readonly policy: ResolvedPolicyConfig | undefined;
|
|
25
|
+
readonly adapter: AvAdapterOperationExecutor & {
|
|
26
|
+
readonly model: AvAdapterModel;
|
|
27
|
+
};
|
|
28
|
+
readonly diagnostics: AvDiagnosticsSink | undefined;
|
|
29
|
+
readonly misuse: MisuseReporter;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Builds the `can` answerer of one Framework. Total: every request resolves to
|
|
33
|
+
* an answer, and no throw of the engine or the adapter escapes it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createAccessQuestionAnswerer(dependencies: AccessQuestionsDependencies): AccessQuestionsAnswerer;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
2
|
+
import { lookupOwn, lookupResource } from "../contracts/contract-lookup.js";
|
|
3
|
+
import { isOperationVariantAvailable } from "../contracts/resource-operations.js";
|
|
4
|
+
import { resourceUnavailableIssue, validateOperationArguments, } from "../operations/operation-validator-core.js";
|
|
5
|
+
import { validationIssue, } from "../operations/validation-code.js";
|
|
6
|
+
import { copyAtDoor, refuseCallerData } from "../runtime/caller-data.door.js";
|
|
7
|
+
import { createReporter, } from "../runtime/diagnostics-reporter.js";
|
|
8
|
+
import { ADAPTER_FAILURE_MESSAGE, deliberateRefusalCode, } from "../runtime/error-boundary.js";
|
|
9
|
+
import { internalFailure, operationFailure, operationSuccess, } from "../runtime/operation-response.js";
|
|
10
|
+
import { VALIDATION_OUTCOME_MESSAGES } from "../runtime/outcome-mapper.js";
|
|
11
|
+
import { requestStateOf } from "../runtime/request-state.resolver.js";
|
|
12
|
+
import { normalizeOperationScalarValues } from "../runtime/scalar-runtime.js";
|
|
13
|
+
import { admitByPolicy } from "./policy-admission.js";
|
|
14
|
+
import { allOf, everyOf, whereOf } from "./policy-condition.js";
|
|
15
|
+
import { openRequestPolicy } from "./policy-request.js";
|
|
16
|
+
import { fieldAccess, } from "./policy-session.js";
|
|
17
|
+
const ACTIONS = ["read", "create", "update", "delete"];
|
|
18
|
+
const BODY_KEYS = new Set(["questions"]);
|
|
19
|
+
const QUESTION_KEYS = new Set([
|
|
20
|
+
"resource",
|
|
21
|
+
"action",
|
|
22
|
+
"where",
|
|
23
|
+
]);
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
const LIMIT_CODES = new Set([
|
|
28
|
+
"V1013",
|
|
29
|
+
"V1014",
|
|
30
|
+
"V1015",
|
|
31
|
+
"V1021",
|
|
32
|
+
]);
|
|
33
|
+
function refusal(issues) {
|
|
34
|
+
const code = issues.some((issue) => issue.code === "V1005" && issue.path?.[2] === "resource")
|
|
35
|
+
? "A2001"
|
|
36
|
+
: issues.some((issue) => LIMIT_CODES.has(issue.code))
|
|
37
|
+
? "A2009"
|
|
38
|
+
: "A2004";
|
|
39
|
+
return operationFailure(code, {
|
|
40
|
+
message: VALIDATION_OUTCOME_MESSAGES[code],
|
|
41
|
+
issues,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function unknownKeyIssues(record, allowed, path) {
|
|
45
|
+
return Object.keys(record)
|
|
46
|
+
.filter((key) => !allowed.has(key))
|
|
47
|
+
.map((key) => validationIssue("V1001", [...path, key], `Property "${key}" is not valid here.`));
|
|
48
|
+
}
|
|
49
|
+
function checkQuestion(active, origin, question, index) {
|
|
50
|
+
const at = ["questions", index];
|
|
51
|
+
if (!isRecord(question)) {
|
|
52
|
+
return [validationIssue("V1001", at, "Each question must be an object.")];
|
|
53
|
+
}
|
|
54
|
+
const issues = unknownKeyIssues(question, QUESTION_KEYS, at);
|
|
55
|
+
const { resource, action } = question;
|
|
56
|
+
let knownResource;
|
|
57
|
+
if (resource === undefined) {
|
|
58
|
+
issues.push(validationIssue("V1000", [...at, "resource"], 'Required property "resource" is missing.'));
|
|
59
|
+
}
|
|
60
|
+
else if (typeof resource !== "string") {
|
|
61
|
+
issues.push(validationIssue("V1001", [...at, "resource"], "resource must be a string."));
|
|
62
|
+
}
|
|
63
|
+
else if (lookupResource(active, resource) === undefined) {
|
|
64
|
+
const unavailable = resourceUnavailableIssue(resource);
|
|
65
|
+
issues.push({ ...unavailable, path: [...at, "resource"] });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
knownResource = resource;
|
|
69
|
+
}
|
|
70
|
+
if (action === undefined) {
|
|
71
|
+
issues.push(validationIssue("V1000", [...at, "action"], 'Required property "action" is missing.'));
|
|
72
|
+
}
|
|
73
|
+
else if (typeof action !== "string") {
|
|
74
|
+
issues.push(validationIssue("V1001", [...at, "action"], "action must be a string."));
|
|
75
|
+
}
|
|
76
|
+
else if (!ACTIONS.includes(action)) {
|
|
77
|
+
issues.push(validationIssue("V1002", [...at, "action"], 'action must be one of "read", "create", "update" or "delete".'));
|
|
78
|
+
}
|
|
79
|
+
const where = !Object.hasOwn(question, "where")
|
|
80
|
+
? undefined
|
|
81
|
+
: action === "create"
|
|
82
|
+
? [
|
|
83
|
+
validationIssue("V1001", [...at, "where"], "A create question names no row: it takes no where."),
|
|
84
|
+
]
|
|
85
|
+
: checkedWhere(active, origin, knownResource, question.where, at);
|
|
86
|
+
if (Array.isArray(where)) {
|
|
87
|
+
issues.push(...where);
|
|
88
|
+
}
|
|
89
|
+
if (issues.length > 0 || knownResource === undefined) {
|
|
90
|
+
return issues;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
resource: knownResource,
|
|
94
|
+
action: action,
|
|
95
|
+
...(where === undefined ? {} : { where: where }),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function checkedWhere(active, origin, resource, where, at) {
|
|
99
|
+
if (resource === undefined) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const request = {
|
|
103
|
+
resource,
|
|
104
|
+
family: "find",
|
|
105
|
+
variant: "unique",
|
|
106
|
+
arguments: { where },
|
|
107
|
+
};
|
|
108
|
+
const validation = validateOperationArguments(active, request);
|
|
109
|
+
const normalized = validation.ok
|
|
110
|
+
? normalizeOperationScalarValues(active, {
|
|
111
|
+
...request,
|
|
112
|
+
arguments: validation.arguments,
|
|
113
|
+
}, origin)
|
|
114
|
+
: validation;
|
|
115
|
+
if (!normalized.ok) {
|
|
116
|
+
return normalized.issues.map((issue) => ({
|
|
117
|
+
...issue,
|
|
118
|
+
path: [...at, ...(issue.path ?? ["arguments", "where"]).slice(1)],
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
return normalized.arguments.where;
|
|
122
|
+
}
|
|
123
|
+
function checkQuestions(active, origin, body) {
|
|
124
|
+
if (!isRecord(body)) {
|
|
125
|
+
return refusal([
|
|
126
|
+
validationIssue("V1001", [], "The request body must be an object."),
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
const shape = unknownKeyIssues(body, BODY_KEYS, []);
|
|
130
|
+
if (!Object.hasOwn(body, "questions")) {
|
|
131
|
+
shape.push(validationIssue("V1000", ["questions"], 'Required property "questions" is missing.'));
|
|
132
|
+
}
|
|
133
|
+
else if (!Array.isArray(body.questions)) {
|
|
134
|
+
shape.push(validationIssue("V1001", ["questions"], "questions must be an array."));
|
|
135
|
+
}
|
|
136
|
+
if (shape.length > 0) {
|
|
137
|
+
return refusal(shape);
|
|
138
|
+
}
|
|
139
|
+
const questions = body.questions;
|
|
140
|
+
const limit = active.limits.maxListLimit;
|
|
141
|
+
if (questions.length > limit) {
|
|
142
|
+
return refusal([
|
|
143
|
+
validationIssue("V1014", ["questions"], `Question count exceeds maxListLimit (${limit}).`),
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
const checked = [];
|
|
147
|
+
const issues = [];
|
|
148
|
+
questions.forEach((question, index) => {
|
|
149
|
+
const result = checkQuestion(active, origin, question, index);
|
|
150
|
+
if (Array.isArray(result)) {
|
|
151
|
+
issues.push(...result);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
checked.push(result);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return issues.length > 0 ? refusal(issues) : checked;
|
|
158
|
+
}
|
|
159
|
+
function familyOf(action) {
|
|
160
|
+
return action === "read" ? "find" : action;
|
|
161
|
+
}
|
|
162
|
+
function advertised(active, question) {
|
|
163
|
+
const operations = lookupOwn(lookupResource(active, question.resource)?.operations, familyOf(question.action));
|
|
164
|
+
if (operations === undefined) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return question.where === undefined
|
|
168
|
+
? Object.keys(operations).some((variant) => isOperationVariantAvailable(lookupOwn(operations, variant)))
|
|
169
|
+
: isOperationVariantAvailable(lookupOwn(operations, "unique"));
|
|
170
|
+
}
|
|
171
|
+
async function answerQuestion(session, dependencies, active, question) {
|
|
172
|
+
if (!advertised(active, question)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const { resource, action, where } = question;
|
|
176
|
+
const identifier = adapterPrimaryIdentifier(dependencies.adapter.model, resource);
|
|
177
|
+
const interactive = active.transactions === "interactive";
|
|
178
|
+
if (action === "update") {
|
|
179
|
+
const checkedAfter = session.checksAfter(resource, "update");
|
|
180
|
+
if ((!interactive && checkedAfter) ||
|
|
181
|
+
((interactive || checkedAfter) && identifier.length === 0)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (where === undefined) {
|
|
186
|
+
return session.can(resource, action);
|
|
187
|
+
}
|
|
188
|
+
if (action === "create") {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const { core } = dependencies.contracts;
|
|
192
|
+
const refused = await admitByPolicy(session, core, active, {
|
|
193
|
+
resource,
|
|
194
|
+
family: action === "read" ? "find" : action,
|
|
195
|
+
variant: "unique",
|
|
196
|
+
}, { where, select: [...identifier] });
|
|
197
|
+
if (refused !== undefined) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
const read = await session.scope(resource, "read");
|
|
201
|
+
const scope = action === "read"
|
|
202
|
+
? read
|
|
203
|
+
: allOf(await session.scope(resource, action), read);
|
|
204
|
+
const fields = await session.fields(resource, { access: "read" });
|
|
205
|
+
const grants = identifier.flatMap((field) => {
|
|
206
|
+
const access = fieldAccess(core, resource, fields, field);
|
|
207
|
+
return typeof access === "object" ? [access] : [];
|
|
208
|
+
});
|
|
209
|
+
if (action === "update" && !interactive && grants.length > 0) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const reach = everyOf([where, scope, ...grants]);
|
|
213
|
+
return reach !== false && session.exists(resource, whereOf(reach));
|
|
214
|
+
}
|
|
215
|
+
const CONCURRENT_QUESTIONS = 8;
|
|
216
|
+
async function answerAll(session, dependencies, active, questions) {
|
|
217
|
+
const settled = new Array(questions.length);
|
|
218
|
+
let next = 0;
|
|
219
|
+
const worker = async () => {
|
|
220
|
+
while (next < questions.length) {
|
|
221
|
+
const index = next;
|
|
222
|
+
next += 1;
|
|
223
|
+
try {
|
|
224
|
+
settled[index] = {
|
|
225
|
+
status: "fulfilled",
|
|
226
|
+
value: await answerQuestion(session, dependencies, active, questions[index]),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch (reason) {
|
|
230
|
+
settled[index] = { status: "rejected", reason };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENT_QUESTIONS, questions.length) }, worker));
|
|
235
|
+
return settled;
|
|
236
|
+
}
|
|
237
|
+
function isDeliberateRefusal(error) {
|
|
238
|
+
return deliberateRefusalCode(error) !== undefined;
|
|
239
|
+
}
|
|
240
|
+
function defectAnswer(dependencies, request, error, fromAdapter, blamed) {
|
|
241
|
+
createReporter(dependencies.diagnostics, {
|
|
242
|
+
kind: "can",
|
|
243
|
+
origin: "client",
|
|
244
|
+
requestId: request.requestId,
|
|
245
|
+
...(blamed === undefined
|
|
246
|
+
? {}
|
|
247
|
+
: {
|
|
248
|
+
question: blamed.index,
|
|
249
|
+
resource: blamed.question.resource,
|
|
250
|
+
action: blamed.question.action,
|
|
251
|
+
}),
|
|
252
|
+
})(error, fromAdapter ? "A3001" : "A3000");
|
|
253
|
+
return fromAdapter
|
|
254
|
+
? operationFailure("A3001", { message: ADAPTER_FAILURE_MESSAGE })
|
|
255
|
+
: internalFailure();
|
|
256
|
+
}
|
|
257
|
+
export function createAccessQuestionAnswerer(dependencies) {
|
|
258
|
+
return async (request) => {
|
|
259
|
+
const active = dependencies.contracts[request.origin];
|
|
260
|
+
const copy = copyAtDoor(request.body);
|
|
261
|
+
if (!copy.ok) {
|
|
262
|
+
return refusal([
|
|
263
|
+
refuseCallerData(copy.error, [], { origin: request.origin }, dependencies.misuse),
|
|
264
|
+
]);
|
|
265
|
+
}
|
|
266
|
+
const questions = checkQuestions(active, request.origin, copy.value);
|
|
267
|
+
if (!Array.isArray(questions)) {
|
|
268
|
+
return questions;
|
|
269
|
+
}
|
|
270
|
+
if (questions.length === 0) {
|
|
271
|
+
return operationSuccess([], "A1000");
|
|
272
|
+
}
|
|
273
|
+
let session;
|
|
274
|
+
try {
|
|
275
|
+
session = await openRequestPolicy(dependencies.policy, dependencies.contracts.core, { origin: request.origin, state: requestStateOf(request.state) }, undefined, dependencies.adapter);
|
|
276
|
+
if (session === undefined) {
|
|
277
|
+
throw new Error("The can route was reached on a Framework whose policy does not check its origin.");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return defectAnswer(dependencies, request, error, false);
|
|
282
|
+
}
|
|
283
|
+
const settled = await answerAll(session, dependencies, active, questions);
|
|
284
|
+
const answers = [];
|
|
285
|
+
for (const [index, result] of settled.entries()) {
|
|
286
|
+
if (result.status === "rejected" && isDeliberateRefusal(result.reason)) {
|
|
287
|
+
answers.push(false);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (result.status === "rejected") {
|
|
291
|
+
return defectAnswer(dependencies, request, result.reason, session.execution.boundary.originOf(result.reason) === "adapter", { question: questions[index], index });
|
|
292
|
+
}
|
|
293
|
+
answers.push(result.value);
|
|
294
|
+
}
|
|
295
|
+
return operationSuccess(answers, "A1000");
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Each `$access` verb beside `can`, in `$access`'s order: whether it takes arguments, and what it resolves. */
|
|
2
|
+
export declare const ACCESS_VERBS: Readonly<{
|
|
3
|
+
readonly me: {
|
|
4
|
+
readonly args: false;
|
|
5
|
+
readonly resolves: "identity";
|
|
6
|
+
};
|
|
7
|
+
readonly signIn: {
|
|
8
|
+
readonly args: true;
|
|
9
|
+
readonly resolves: "answer";
|
|
10
|
+
};
|
|
11
|
+
readonly signUp: {
|
|
12
|
+
readonly args: true;
|
|
13
|
+
readonly resolves: "answer";
|
|
14
|
+
};
|
|
15
|
+
readonly signOut: {
|
|
16
|
+
readonly args: false;
|
|
17
|
+
readonly resolves: "nothing";
|
|
18
|
+
};
|
|
19
|
+
readonly resetPassword: {
|
|
20
|
+
readonly args: true;
|
|
21
|
+
readonly resolves: "nothing";
|
|
22
|
+
};
|
|
23
|
+
readonly switchUser: {
|
|
24
|
+
readonly args: true;
|
|
25
|
+
readonly resolves: "answer";
|
|
26
|
+
};
|
|
27
|
+
readonly lock: {
|
|
28
|
+
readonly args: false;
|
|
29
|
+
readonly resolves: "nothing";
|
|
30
|
+
};
|
|
31
|
+
readonly setPin: {
|
|
32
|
+
readonly args: true;
|
|
33
|
+
readonly resolves: "nothing";
|
|
34
|
+
};
|
|
35
|
+
readonly enrollDevice: {
|
|
36
|
+
readonly args: true;
|
|
37
|
+
readonly resolves: "answer";
|
|
38
|
+
};
|
|
39
|
+
readonly revokeDevice: {
|
|
40
|
+
readonly args: true;
|
|
41
|
+
readonly resolves: "nothing";
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
/** One verb's row of {@link ACCESS_VERBS}. */
|
|
45
|
+
export type AccessVerbShape = {
|
|
46
|
+
readonly args: boolean;
|
|
47
|
+
readonly resolves: "identity" | "answer" | "nothing";
|
|
48
|
+
};
|
|
49
|
+
/** A verb `$access` offers beside `can`. */
|
|
50
|
+
export type AccessVerbName = keyof typeof ACCESS_VERBS;
|
|
51
|
+
/** The verbs of {@link ACCESS_VERBS}, in `$access`'s order. */
|
|
52
|
+
export declare const ACCESS_VERB_NAMES: readonly AccessVerbName[];
|
|
53
|
+
/** The provider's `name` verb: its `verbs`' own enumerable member, else `undefined`. */
|
|
54
|
+
export declare function providerVerb<Verbs extends object, Name extends string>(verbs: Verbs, name: Name): (Name extends keyof Verbs ? Verbs[Name] : unknown) | undefined;
|
|
55
|
+
/** Whether `name` is one of `$access`'s verbs (an own key of the table, never `toString`). */
|
|
56
|
+
export declare function isAccessVerbName(name: string): name is AccessVerbName;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const ACCESS_VERBS = Object.freeze({
|
|
2
|
+
me: { args: false, resolves: "identity" },
|
|
3
|
+
signIn: { args: true, resolves: "answer" },
|
|
4
|
+
signUp: { args: true, resolves: "answer" },
|
|
5
|
+
signOut: { args: false, resolves: "nothing" },
|
|
6
|
+
resetPassword: { args: true, resolves: "nothing" },
|
|
7
|
+
switchUser: { args: true, resolves: "answer" },
|
|
8
|
+
lock: { args: false, resolves: "nothing" },
|
|
9
|
+
setPin: { args: true, resolves: "nothing" },
|
|
10
|
+
enrollDevice: { args: true, resolves: "answer" },
|
|
11
|
+
revokeDevice: { args: true, resolves: "nothing" },
|
|
12
|
+
});
|
|
13
|
+
export const ACCESS_VERB_NAMES = Object.freeze(Object.keys(ACCESS_VERBS));
|
|
14
|
+
export function providerVerb(verbs, name) {
|
|
15
|
+
return Object.prototype.propertyIsEnumerable.call(verbs, name)
|
|
16
|
+
? verbs[name]
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
export function isAccessVerbName(name) {
|
|
20
|
+
return Object.hasOwn(ACCESS_VERBS, name);
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `$access`: the policy's answers for the UI (`can`), and the Aventara Access
|
|
3
|
+
* verbs (`me`, `signIn`, `signUp`, `signOut`, `resetPassword`, and the `kiosk`
|
|
4
|
+
* profile's `switchUser`, `lock`, `setPin`, `enrollDevice`, `revokeDevice`).
|
|
5
|
+
*/
|
|
6
|
+
export type AvAccessVerbs<Question, Me, Args extends {
|
|
7
|
+
readonly signIn: unknown;
|
|
8
|
+
readonly signUp: unknown;
|
|
9
|
+
readonly resetPassword: unknown;
|
|
10
|
+
readonly switchUser: unknown;
|
|
11
|
+
readonly setPin: unknown;
|
|
12
|
+
readonly enrollDevice: unknown;
|
|
13
|
+
readonly revokeDevice: unknown;
|
|
14
|
+
}, Options> = {
|
|
15
|
+
/**
|
|
16
|
+
* Asks the policy every question at once, in one request, and answers one
|
|
17
|
+
* boolean each, in order: what each call would do before any data is sent.
|
|
18
|
+
* It only shapes the UI; the real call is still checked.
|
|
19
|
+
*/
|
|
20
|
+
can<const Q extends readonly Question[]>(questions: Q, options?: Options): Promise<{
|
|
21
|
+
readonly [K in keyof Q]: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
/** Who is signed in: the token identity, or `null` when no one is. */
|
|
24
|
+
me(options?: Options): Promise<Me | null>;
|
|
25
|
+
/** Signs in through Aventara Access; resolves what Access answers. */
|
|
26
|
+
signIn(args: Args["signIn"], options?: Options): Promise<unknown>;
|
|
27
|
+
/** Signs up through Aventara Access; resolves what Access answers. */
|
|
28
|
+
signUp(args: Args["signUp"], options?: Options): Promise<unknown>;
|
|
29
|
+
/** Signs the caller out through Aventara Access. */
|
|
30
|
+
signOut(options?: Options): Promise<void>;
|
|
31
|
+
/** Resets a password through Aventara Access. */
|
|
32
|
+
resetPassword(args: Args["resetPassword"], options?: Options): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Switches this enrolled device to another staff member by their PIN
|
|
35
|
+
* (Aventara Access, `kiosk` profile); resolves what Access answers.
|
|
36
|
+
*/
|
|
37
|
+
switchUser(args: Args["switchUser"], options?: Options): Promise<unknown>;
|
|
38
|
+
/** Locks this enrolled device: ends its session, keeps it enrolled (`kiosk` profile). */
|
|
39
|
+
lock(options?: Options): Promise<void>;
|
|
40
|
+
/** Sets the signed-in staff member's PIN (`kiosk` profile). */
|
|
41
|
+
setPin(args: Args["setPin"], options?: Options): Promise<void>;
|
|
42
|
+
/** Enrolls this device for PIN switching (`kiosk` profile); resolves what Access answers. */
|
|
43
|
+
enrollDevice(args: Args["enrollDevice"], options?: Options): Promise<unknown>;
|
|
44
|
+
/** Revokes an enrolled device (`kiosk` profile). */
|
|
45
|
+
revokeDevice(args: Args["revokeDevice"], options?: Options): Promise<void>;
|
|
46
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AvContract, AvContractCore } from "../contracts/contract.js";
|
|
2
|
+
import type { AvOperationIdentity } from "../operations/operation-identity.js";
|
|
3
|
+
import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
|
|
4
|
+
import { type PolicySession } from "./policy-session.js";
|
|
5
|
+
/** The refusal every policy decision answers: `A4002`. */
|
|
6
|
+
export declare function policyRefusal(message: string): AvOperationFailureResponse;
|
|
7
|
+
/**
|
|
8
|
+
* Policy admission for one request (see the module comment): the refusal, or
|
|
9
|
+
* `undefined` when the request may go on to its guards.
|
|
10
|
+
*/
|
|
11
|
+
export declare function admitByPolicy(session: PolicySession, core: AvContractCore, active: AvContract, request: AvOperationIdentity & {
|
|
12
|
+
readonly resource: string;
|
|
13
|
+
}, args: unknown): Promise<AvOperationFailureResponse | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* The write half of policy admission: every field the write sends against
|
|
16
|
+
* `fieldsOf(write)`, nested records included, the refusal or `undefined`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function admitWriteByPolicy(session: PolicySession, core: AvContractCore, request: AvOperationIdentity & {
|
|
19
|
+
readonly resource: string;
|
|
20
|
+
}, args: unknown): Promise<AvOperationFailureResponse | undefined>;
|