@lssm/lib.contracts 0.0.0-canary-20251217063201 → 0.0.0-canary-20251217073102
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/dist/app-config/app-config.feature.js +53 -1
- package/dist/app-config/contracts.d.ts +50 -50
- package/dist/app-config/contracts.js +396 -1
- package/dist/app-config/docs/app-config.docblock.js +22 -220
- package/dist/app-config/events.d.ts +27 -27
- package/dist/app-config/events.js +168 -1
- package/dist/app-config/index.js +8 -1
- package/dist/app-config/lifecycle-contracts.d.ts +80 -80
- package/dist/app-config/lifecycle-contracts.js +441 -1
- package/dist/app-config/runtime.js +617 -1
- package/dist/app-config/spec.js +36 -1
- package/dist/app-config/validation.js +538 -1
- package/dist/capabilities/docs/capabilities.docblock.js +22 -1
- package/dist/capabilities/openbanking.js +92 -1
- package/dist/capabilities.js +50 -1
- package/dist/client/index.js +9 -1
- package/dist/client/react/drivers/rn-reusables.js +21 -1
- package/dist/client/react/drivers/shadcn.js +11 -1
- package/dist/client/react/feature-render.js +43 -1
- package/dist/client/react/form-render.js +298 -1
- package/dist/client/react/index.js +8 -1
- package/dist/contract-registry/index.js +3 -1
- package/dist/contract-registry/schemas.js +61 -1
- package/dist/contracts-adapter-hydration.js +41 -1
- package/dist/contracts-adapter-input.js +77 -1
- package/dist/data-views/docs/data-views.docblock.js +22 -1
- package/dist/data-views/query-generator.js +48 -1
- package/dist/data-views/runtime.js +39 -1
- package/dist/data-views.js +35 -1
- package/dist/docs/PUBLISHING.docblock.js +17 -76
- package/dist/docs/accessibility_wcag_compliance_specs.docblock.js +17 -350
- package/dist/docs/index.js +33 -1
- package/dist/docs/meta.docs.js +15 -2
- package/dist/docs/presentations.js +77 -1
- package/dist/docs/registry.js +51 -1
- package/dist/docs/tech/PHASE_1_QUICKSTART.docblock.js +17 -383
- package/dist/docs/tech/PHASE_2_AI_NATIVE_OPERATIONS.docblock.js +17 -68
- package/dist/docs/tech/PHASE_3_AUTO_EVOLUTION.docblock.js +17 -140
- package/dist/docs/tech/PHASE_4_PERSONALIZATION_ENGINE.docblock.js +17 -86
- package/dist/docs/tech/PHASE_5_ZERO_TOUCH_OPERATIONS.docblock.js +17 -1
- package/dist/docs/tech/auth/better-auth-nextjs.docblock.js +25 -2
- package/dist/docs/tech/contracts/README.docblock.js +21 -1
- package/dist/docs/tech/contracts/create-subscription.docblock.js +21 -1
- package/dist/docs/tech/contracts/graphql-typed-outputs.docblock.js +21 -180
- package/dist/docs/tech/contracts/migrations.docblock.js +21 -1
- package/dist/docs/tech/contracts/openapi-export.docblock.js +22 -2
- package/dist/docs/tech/contracts/ops-to-presentation-linking.docblock.js +19 -60
- package/dist/docs/tech/contracts/overlays.docblock.js +21 -68
- package/dist/docs/tech/contracts/tests.docblock.js +21 -132
- package/dist/docs/tech/contracts/themes.docblock.js +21 -1
- package/dist/docs/tech/contracts/vertical-pocket-family-office.docblock.js +21 -106
- package/dist/docs/tech/lifecycle-stage-system.docblock.js +17 -213
- package/dist/docs/tech/llm/llm-integration.docblock.js +74 -5
- package/dist/docs/tech/mcp-endpoints.docblock.js +38 -1
- package/dist/docs/tech/presentation-runtime.docblock.js +17 -1
- package/dist/docs/tech/schema/README.docblock.js +21 -262
- package/dist/docs/tech/studio/learning-events.docblock.js +49 -1
- package/dist/docs/tech/studio/learning-journeys.docblock.js +25 -2
- package/dist/docs/tech/studio/platform-admin-panel.docblock.js +24 -2
- package/dist/docs/tech/studio/project-access-teams.docblock.js +26 -16
- package/dist/docs/tech/studio/project-routing.docblock.js +68 -1
- package/dist/docs/tech/studio/sandbox-unlogged.docblock.js +23 -2
- package/dist/docs/tech/studio/team-invitations.docblock.js +41 -36
- package/dist/docs/tech/studio/workspace-ops.docblock.js +48 -1
- package/dist/docs/tech/studio/workspaces.docblock.js +24 -2
- package/dist/docs/tech/telemetry-ingest.docblock.js +37 -3
- package/dist/docs/tech/templates/runtime.docblock.js +21 -1
- package/dist/docs/tech/vscode-extension.docblock.js +37 -3
- package/dist/docs/tech/workflows/overview.docblock.js +21 -1
- package/dist/docs/tech-contracts.docs.js +19 -2
- package/dist/events.js +12 -1
- package/dist/experiments/docs/experiments.docblock.js +22 -128
- package/dist/experiments/evaluator.js +101 -1
- package/dist/experiments/spec.js +33 -1
- package/dist/features.js +68 -1
- package/dist/forms/docs/forms.docblock.js +22 -1
- package/dist/forms.js +119 -1
- package/dist/index.js +107 -1
- package/dist/install.js +40 -1
- package/dist/integrations/contracts.d.ts +102 -102
- package/dist/integrations/contracts.js +388 -1
- package/dist/integrations/docs/integrations.docblock.js +95 -1
- package/dist/integrations/health.js +69 -1
- package/dist/integrations/index.js +23 -1
- package/dist/integrations/openbanking/contracts/accounts.d.ts +66 -66
- package/dist/integrations/openbanking/contracts/accounts.js +237 -1
- package/dist/integrations/openbanking/contracts/balances.d.ts +34 -34
- package/dist/integrations/openbanking/contracts/balances.js +167 -1
- package/dist/integrations/openbanking/contracts/index.js +12 -1
- package/dist/integrations/openbanking/contracts/transactions.d.ts +48 -48
- package/dist/integrations/openbanking/contracts/transactions.js +218 -1
- package/dist/integrations/openbanking/guards.js +32 -1
- package/dist/integrations/openbanking/models.d.ts +55 -55
- package/dist/integrations/openbanking/models.js +242 -1
- package/dist/integrations/openbanking/openbanking.feature.js +68 -1
- package/dist/integrations/openbanking/telemetry.js +39 -1
- package/dist/integrations/providers/elevenlabs.js +56 -1
- package/dist/integrations/providers/gcs-storage.js +79 -1
- package/dist/integrations/providers/gmail.js +91 -1
- package/dist/integrations/providers/google-calendar.js +70 -1
- package/dist/integrations/providers/impls/elevenlabs-voice.js +95 -1
- package/dist/integrations/providers/impls/gcs-storage.js +88 -1
- package/dist/integrations/providers/impls/gmail-inbound.js +200 -1
- package/dist/integrations/providers/impls/gmail-outbound.js +104 -5
- package/dist/integrations/providers/impls/google-calendar.js +154 -1
- package/dist/integrations/providers/impls/index.js +16 -1
- package/dist/integrations/providers/impls/mistral-embedding.js +41 -1
- package/dist/integrations/providers/impls/mistral-llm.js +247 -1
- package/dist/integrations/providers/impls/postmark-email.js +55 -1
- package/dist/integrations/providers/impls/powens-client.js +171 -1
- package/dist/integrations/providers/impls/powens-openbanking.js +218 -1
- package/dist/integrations/providers/impls/provider-factory.js +142 -1
- package/dist/integrations/providers/impls/qdrant-vector.js +69 -1
- package/dist/integrations/providers/impls/stripe-payments.js +202 -1
- package/dist/integrations/providers/impls/twilio-sms.js +58 -1
- package/dist/integrations/providers/index.js +13 -1
- package/dist/integrations/providers/mistral.js +72 -1
- package/dist/integrations/providers/postmark.js +72 -1
- package/dist/integrations/providers/powens.js +120 -1
- package/dist/integrations/providers/qdrant.js +77 -1
- package/dist/integrations/providers/registry.js +34 -1
- package/dist/integrations/providers/stripe.js +87 -1
- package/dist/integrations/providers/twilio-sms.js +65 -1
- package/dist/integrations/runtime.js +186 -1
- package/dist/integrations/secrets/aws-secret-manager.js +231 -1
- package/dist/integrations/secrets/env-secret-provider.js +81 -1
- package/dist/integrations/secrets/gcp-secret-manager.js +229 -1
- package/dist/integrations/secrets/index.js +8 -1
- package/dist/integrations/secrets/manager.js +103 -1
- package/dist/integrations/secrets/provider.js +58 -1
- package/dist/integrations/secrets/scaleway-secret-manager.js +247 -1
- package/dist/integrations/spec.js +39 -1
- package/dist/jobs/define-job.js +16 -1
- package/dist/jobs/gcp-cloud-tasks.js +53 -1
- package/dist/jobs/gcp-pubsub.js +39 -1
- package/dist/jobs/handlers/gmail-sync-handler.js +9 -1
- package/dist/jobs/handlers/index.js +12 -1
- package/dist/jobs/handlers/ping-handler.js +15 -1
- package/dist/jobs/handlers/storage-document-handler.js +14 -1
- package/dist/jobs/index.js +4 -1
- package/dist/jobs/memory-queue.js +71 -1
- package/dist/jobs/queue.js +33 -1
- package/dist/jobs/scaleway-sqs-queue.js +153 -1
- package/dist/jsonschema.d.ts +3 -3
- package/dist/jsonschema.js +32 -1
- package/dist/knowledge/contracts.d.ts +66 -66
- package/dist/knowledge/contracts.js +317 -1
- package/dist/knowledge/docs/knowledge.docblock.js +22 -138
- package/dist/knowledge/index.js +10 -1
- package/dist/knowledge/ingestion/document-processor.js +54 -1
- package/dist/knowledge/ingestion/embedding-service.js +25 -1
- package/dist/knowledge/ingestion/gmail-adapter.js +50 -5
- package/dist/knowledge/ingestion/index.js +7 -1
- package/dist/knowledge/ingestion/storage-adapter.js +26 -1
- package/dist/knowledge/ingestion/vector-indexer.js +32 -1
- package/dist/knowledge/query/index.js +3 -1
- package/dist/knowledge/query/service.js +64 -2
- package/dist/knowledge/runtime.js +49 -1
- package/dist/knowledge/spaces/email-threads.js +38 -1
- package/dist/knowledge/spaces/financial-docs.js +38 -1
- package/dist/knowledge/spaces/financial-overview.js +42 -1
- package/dist/knowledge/spaces/index.js +8 -1
- package/dist/knowledge/spaces/product-canon.js +38 -1
- package/dist/knowledge/spaces/support-faq.js +41 -1
- package/dist/knowledge/spaces/uploaded-docs.js +38 -1
- package/dist/knowledge/spec.js +39 -1
- package/dist/llm/exporters.js +541 -8
- package/dist/llm/index.js +4 -1
- package/dist/llm/prompts.js +246 -56
- package/dist/markdown.js +116 -3
- package/dist/migrations.js +33 -1
- package/dist/onboarding-base.d.ts +29 -29
- package/dist/onboarding-base.js +196 -1
- package/dist/openapi.js +75 -1
- package/dist/openbanking/docs/openbanking.docblock.js +22 -109
- package/dist/ownership.js +40 -1
- package/dist/policy/docs/policy.docblock.js +22 -1
- package/dist/policy/engine.js +223 -1
- package/dist/policy/opa-adapter.js +71 -1
- package/dist/policy/spec.js +33 -1
- package/dist/presentations/docs/presentations-conventions.docblock.js +21 -7
- package/dist/presentations.backcompat.js +47 -1
- package/dist/presentations.d.ts +3 -3
- package/dist/presentations.js +66 -1
- package/dist/presentations.v2.js +278 -6
- package/dist/prompt.js +10 -1
- package/dist/promptRegistry.js +34 -1
- package/dist/regenerator/docs/regenerator.docblock.js +22 -184
- package/dist/regenerator/executor.js +86 -1
- package/dist/regenerator/index.js +6 -1
- package/dist/regenerator/service.js +92 -1
- package/dist/regenerator/sinks.js +32 -1
- package/dist/regenerator/utils.js +51 -1
- package/dist/registry.js +208 -1
- package/dist/resources.js +47 -1
- package/dist/schema/dist/EnumType.js +2 -1
- package/dist/schema/dist/FieldType.js +49 -1
- package/dist/schema/dist/ScalarTypeEnum.js +236 -1
- package/dist/schema/dist/SchemaModel.js +39 -1
- package/dist/schema/dist/entity/defineEntity.js +1 -1
- package/dist/schema/dist/entity/index.js +2 -1
- package/dist/schema/dist/entity/types.js +1 -1
- package/dist/schema/dist/index.js +6 -1
- package/dist/schema-to-markdown.js +214 -10
- package/dist/server/graphql-pothos.js +128 -1
- package/dist/server/index.js +10 -1
- package/dist/server/mcp/createMcpServer.js +28 -1
- package/dist/server/mcp/registerPresentations.js +151 -1
- package/dist/server/mcp/registerPrompts.js +36 -2
- package/dist/server/mcp/registerResources.js +35 -1
- package/dist/server/mcp/registerTools.js +22 -1
- package/dist/server/provider-mcp.js +3 -1
- package/dist/server/rest-elysia.js +20 -1
- package/dist/server/rest-express.js +39 -1
- package/dist/server/rest-generic.js +125 -1
- package/dist/server/rest-next-app.js +38 -1
- package/dist/server/rest-next-mcp.js +45 -1
- package/dist/server/rest-next-pages.js +25 -1
- package/dist/spec.js +35 -1
- package/dist/telemetry/anomaly.js +48 -1
- package/dist/telemetry/docs/telemetry.docblock.js +22 -139
- package/dist/telemetry/index.js +5 -1
- package/dist/telemetry/spec.js +69 -1
- package/dist/telemetry/tracker.js +76 -1
- package/dist/tests/index.js +4 -1
- package/dist/tests/runner.js +150 -1
- package/dist/tests/spec.js +33 -1
- package/dist/themes.js +39 -1
- package/dist/workflow/adapters/db-adapter.js +83 -1
- package/dist/workflow/adapters/file-adapter.js +11 -1
- package/dist/workflow/adapters/index.js +5 -1
- package/dist/workflow/adapters/memory-store.js +58 -1
- package/dist/workflow/expression.js +98 -1
- package/dist/workflow/index.js +9 -1
- package/dist/workflow/runner.js +337 -1
- package/dist/workflow/sla-monitor.js +47 -1
- package/dist/workflow/spec.js +32 -1
- package/dist/workflow/validation.js +175 -1
- package/package.json +11 -4
|
@@ -1 +1,22 @@
|
|
|
1
|
-
import{registerDocBlocks
|
|
1
|
+
import { registerDocBlocks } from "../../docs/registry.js";
|
|
2
|
+
import "../../registry.js";
|
|
3
|
+
|
|
4
|
+
//#region src/policy/docs/policy.docblock.ts
|
|
5
|
+
const tech_contracts_policy_DocBlocks = [{
|
|
6
|
+
id: "docs.tech.contracts.policy",
|
|
7
|
+
title: "PolicySpec & PolicyEngine",
|
|
8
|
+
summary: "`PolicySpec` gives a declarative, typed home for access-control logic covering:",
|
|
9
|
+
kind: "reference",
|
|
10
|
+
visibility: "public",
|
|
11
|
+
route: "/docs/tech/contracts/policy",
|
|
12
|
+
tags: [
|
|
13
|
+
"tech",
|
|
14
|
+
"contracts",
|
|
15
|
+
"policy"
|
|
16
|
+
],
|
|
17
|
+
body: "# PolicySpec & PolicyEngine\n\n## Purpose\n\n`PolicySpec` gives a declarative, typed home for access-control logic covering:\n- **Who** can perform an action (ABAC/ReBAC style rules)\n- **What** they can access (resources + optional field-level overrides)\n- **When** special conditions apply (contextual expressions)\n- **How** PII should be handled (consent/retention hints)\n\n`PolicyEngine` evaluates one or more policies and returns an `allow`/`deny` decision, field-level outcomes, and PII metadata suitable for downstream enforcement (`SpecRegistry` → `ctx.decide`).\n\n## Location\n\n- Types & registry: `packages/libs/contracts/src/policy/spec.ts`\n- Runtime evaluation: `packages/libs/contracts/src/policy/engine.ts`\n- Tests: `packages/.../policy/engine.test.ts`\n\n## `PolicySpec`\n\n```ts\nexport interface PolicySpec {\n meta: PolicyMeta; // ownership metadata + { name, version, scope? }\n rules: PolicyRule[]; // allow/deny rules for actions\n fieldPolicies?: FieldPolicyRule[];\n pii?: { fields: string[]; consentRequired?: boolean; retentionDays?: number };\n relationships?: RelationshipDefinition[];\n consents?: ConsentDefinition[];\n rateLimits?: RateLimitDefinition[];\n opa?: { package: string; decision?: string };\n}\n```\n\n- `PolicyRule`\n - `effect`: `'allow' | 'deny'`\n - `actions`: e.g., `['read', 'write', 'delete']` (string namespace is flexible)\n - `subject`: `{ roles?: string[]; attributes?: { attr: matcher } }`\n - `resource`: `{ type: string; fields?: string[]; attributes?: {...} }`\n - `relationships`: `{ relation, objectId?, objectType? }[]` → ReBAC checks (use `objectId: '$resource'` to target the current resource)\n - `requiresConsent`: `['consent_id']` → references spec-level consent definitions\n - `flags`: feature flags that must be enabled (`DecisionContext.flags`)\n - `rateLimit`: string reference to `rateLimits` entry or inline object `{ rpm, key?, windowSeconds?, burst? }`\n - `escalate`: `'human_review' | null` to indicate manual approval\n - `conditions`: optional expression snippets evaluated against `{ subject, resource, context }`\n- `FieldPolicyRule`\n - `field`: dot-path string (e.g., `contact.email`)\n - `actions`: subset of `['read', 'write']`\n - Same `subject` / `resource` / `conditions` shape\n - Useful for redacting specific fields, even when the global action is allowed\n- `RelationshipDefinition`\n - Canonical tuples for relationship graph (`subjectType`, `relation`, `objectType`, `transitive?`)\n- `ConsentDefinition`\n - `{ id, scope, purpose, lawfulBasis?, expiresInDays?, required? }`\n- `RateLimitDefinition`\n - `{ id, rpm, key?, windowSeconds?, burst? }`\n- `PolicyRef`\n - `{ name: string; version: number }` → attach to contract specs / workflows\n\n## Registry\n\n```ts\nconst registry = new PolicyRegistry();\nregistry.register(CorePolicySpec);\nconst spec = registry.get('core.default', 1);\n```\n\nGuarantees uniqueness per `(name, version)` and exposes helpers to resolve highest versions.\n\n## Engine\n\n```ts\nconst engine = new PolicyEngine(policyRegistry);\n\nconst decision = engine.decide({\n action: 'read',\n subject: { roles: ['admin'] },\n resource: { type: 'resident', fields: ['contact.email'] },\n policies: [{ name: 'core.default', version: 1 }],\n});\n/*\n{\n effect: 'allow',\n reason: 'core.default',\n fieldDecisions: [{ field: 'contact.email', effect: 'allow' }],\n pii: { fields: ['contact.email'], consentRequired: true }\n}\n*/\n```\n\n- First matching **deny** wins; otherwise the first **allow** is returned.\n- Field policies are aggregated across referenced policies:\n - Later denies override earlier allows for a given field.\n - Returned as `fieldDecisions` to simplify downstream masking.\n- PII metadata is surfaced when defined to help adapt logging/telemetry.\n\n### Expression Support\n\nConditions accept small JS snippets (e.g., `subject.attributes.orgId === context.orgId`). The engine runs them in a constrained scope (`subject`, `resource`, `context`) without access to global state.\n\n### ReBAC & Relationships\n\n- Provide relationship tuples via `PolicySpec.relationships` for documentation/validation.\n- Reference them inside rules with `relationships: [{ relation: 'manager_of', objectType: 'resident', objectId: '$resource' }]`.\n- The execution context must populate `subject.relationships` (`[{ relation, object, objectType }]`) for the engine to evaluate ReBAC guards.\n\n### Consent & Rate Limits\n\n- Declare reusable consent definitions under `consents`. Rules list the IDs they require; if a user session lacks the consent (`DecisionContext.consents`), the engine returns `effect: 'deny'` with `reason: 'consent_required'` and enumerates missing consents.\n- Attach rate limits either inline or via `rateLimits` references. When a rule matches, the engine surfaces `{ rpm, key, windowSeconds?, burst? }` so callers can feed it to shared limiters.\n\n### OPA Adapter\n\n- `OPAPolicyAdapter` bridges engine decisions to Open Policy Agent (OPA). It forwards the evaluation context + policies to OPA and merges any override result (`effect`, `reason`, `fieldDecisions`, `requiredConsents`).\n- Use when migrating to OPA policies or running defense-in-depth: call `engine.decide()`, then pass the preliminary decision to `adapter.evaluate(...)`. The adapter marks merged decisions with `evaluatedBy: 'opa'`.\n- OPA inputs include meta, rules, relationships, rate limits, and consent catalogs to simplify policy authoring on the OPA side.\n\n## Contract Integration\n\n`ContractSpec.policy` now supports:\n\n```ts\npolicy: {\n auth: 'anonymous' | 'user' | 'admin';\n ...\n policies?: PolicyRef[]; // policies evaluated before execution\n fieldPolicies?: { // field hints (read/write) per policy\n field: string;\n actions: ('read' | 'write')[];\n policy?: PolicyRef;\n }[];\n}\n```\n\nAdapters can resolve refs through a shared `PolicyEngine` and populate `ctx.decide` so `SpecRegistry.execute` benefits from centralized enforcement.\n\n## Authoring Guidelines\n\n1. Prefer **allow-by-default** policies but explicitly deny sensitive flows (defense-in-depth).\n2. Keep rule scopes narrow (per feature/operation) and compose multiple `PolicyRef`s when necessary.\n3. Store PII field lists here to avoid duplication across logs/telemetry.\n4. Use explicit rule reasons for auditability and better developer feedback.\n5. Treat versioning seriously; bump `meta.version` whenever behavior changes.\n\n## Future Enhancements\n\n- Richer expression language (composable predicates, time-based conditions).\n- Multi-tenant relationship graph services (store/resolve relationships at scale).\n- Tooling that auto-generates docs/tests for policies referenced in specs.\n\n"
|
|
18
|
+
}];
|
|
19
|
+
registerDocBlocks(tech_contracts_policy_DocBlocks);
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { tech_contracts_policy_DocBlocks };
|
package/dist/policy/engine.js
CHANGED
|
@@ -1 +1,223 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/policy/engine.ts
|
|
2
|
+
var PolicyEngine = class {
|
|
3
|
+
constructor(registry) {
|
|
4
|
+
this.registry = registry;
|
|
5
|
+
}
|
|
6
|
+
decide(input) {
|
|
7
|
+
const policies = this.resolvePolicies(input.policies);
|
|
8
|
+
let allowReason;
|
|
9
|
+
let appliedRateLimit;
|
|
10
|
+
let escalate;
|
|
11
|
+
for (const policy of policies) {
|
|
12
|
+
const match = this.matchRuleSet(policy, input);
|
|
13
|
+
if (!match) continue;
|
|
14
|
+
if (match.rule.effect === "deny") return {
|
|
15
|
+
effect: "deny",
|
|
16
|
+
reason: match.rule.reason ?? policy.meta.name,
|
|
17
|
+
requiredConsents: match.missingConsents.length ? match.missingConsents : void 0,
|
|
18
|
+
evaluatedBy: "engine"
|
|
19
|
+
};
|
|
20
|
+
if (match.rule.effect === "allow") {
|
|
21
|
+
if (match.missingConsents.length) return {
|
|
22
|
+
effect: "deny",
|
|
23
|
+
reason: "consent_required",
|
|
24
|
+
requiredConsents: match.missingConsents,
|
|
25
|
+
evaluatedBy: "engine"
|
|
26
|
+
};
|
|
27
|
+
if (!allowReason) allowReason = match.rule.reason ?? policy.meta.name;
|
|
28
|
+
if (!appliedRateLimit && match.rateLimit) appliedRateLimit = match.rateLimit;
|
|
29
|
+
if (!escalate && match.rule.escalate) escalate = match.rule.escalate;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const fieldDecisions = this.evaluateFields(policies, input);
|
|
33
|
+
const pii = policies.find((p) => p.pii)?.pii;
|
|
34
|
+
return {
|
|
35
|
+
effect: allowReason ? "allow" : "deny",
|
|
36
|
+
reason: allowReason,
|
|
37
|
+
rateLimit: appliedRateLimit,
|
|
38
|
+
escalate: typeof escalate === "undefined" ? void 0 : escalate,
|
|
39
|
+
fieldDecisions: fieldDecisions.length ? fieldDecisions : void 0,
|
|
40
|
+
pii,
|
|
41
|
+
evaluatedBy: "engine"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
resolvePolicies(refs) {
|
|
45
|
+
const specs = [];
|
|
46
|
+
for (const ref of refs) {
|
|
47
|
+
const spec = this.registry.get(ref.name, ref.version);
|
|
48
|
+
if (!spec) throw new Error(`PolicyEngine: policy not found ${ref.name}.v${ref.version}`);
|
|
49
|
+
specs.push(spec);
|
|
50
|
+
}
|
|
51
|
+
return specs;
|
|
52
|
+
}
|
|
53
|
+
matchRuleSet(policy, input) {
|
|
54
|
+
let allowMatch;
|
|
55
|
+
for (const rule of policy.rules) {
|
|
56
|
+
if (!rule.actions.includes(input.action)) continue;
|
|
57
|
+
if (!matchesSubject(rule, input.subject)) continue;
|
|
58
|
+
if (!matchesResource(rule, input.resource)) continue;
|
|
59
|
+
if (!matchesFlags(rule, input)) continue;
|
|
60
|
+
if (!matchesRelationships(rule.relationships, input)) continue;
|
|
61
|
+
if (!matchesConditions(rule, input)) continue;
|
|
62
|
+
const evaluation = {
|
|
63
|
+
rule,
|
|
64
|
+
missingConsents: collectMissingConsents(rule, policy, input),
|
|
65
|
+
rateLimit: resolveRateLimit(rule, policy, input)
|
|
66
|
+
};
|
|
67
|
+
if (rule.effect === "deny") return evaluation;
|
|
68
|
+
if (rule.effect === "allow" && !allowMatch) allowMatch = evaluation;
|
|
69
|
+
}
|
|
70
|
+
return allowMatch;
|
|
71
|
+
}
|
|
72
|
+
evaluateFields(policies, input) {
|
|
73
|
+
const out = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const policy of policies) {
|
|
75
|
+
if (!policy.fieldPolicies) continue;
|
|
76
|
+
for (const rule of policy.fieldPolicies) {
|
|
77
|
+
if (!rule.actions.includes(mapActionToFieldAction(input.action))) continue;
|
|
78
|
+
if (!matchesSubject(rule, input.subject)) continue;
|
|
79
|
+
if (!matchesResource(rule, input.resource)) continue;
|
|
80
|
+
if (!matchesConditions(rule, input)) continue;
|
|
81
|
+
const existing = out.get(rule.field);
|
|
82
|
+
if (rule.effect === "deny") out.set(rule.field, {
|
|
83
|
+
field: rule.field,
|
|
84
|
+
effect: "deny",
|
|
85
|
+
reason: rule.reason ?? policy.meta.name
|
|
86
|
+
});
|
|
87
|
+
else if (!existing) out.set(rule.field, {
|
|
88
|
+
field: rule.field,
|
|
89
|
+
effect: "allow",
|
|
90
|
+
reason: rule.reason ?? policy.meta.name
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return [...out.values()];
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
function mapActionToFieldAction(action) {
|
|
98
|
+
if (action.startsWith("write")) return "write";
|
|
99
|
+
return "read";
|
|
100
|
+
}
|
|
101
|
+
function matchesSubject(rule, subject) {
|
|
102
|
+
const matcher = rule.subject;
|
|
103
|
+
if (!matcher) return true;
|
|
104
|
+
if (matcher.roles?.length) {
|
|
105
|
+
const roles = subject.roles ?? [];
|
|
106
|
+
if (!matcher.roles.some((role) => roles.includes(role))) return false;
|
|
107
|
+
}
|
|
108
|
+
if (matcher.attributes) {
|
|
109
|
+
const attributes = subject.attributes ?? {};
|
|
110
|
+
if (!matchAttributes(matcher.attributes, attributes)) return false;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function matchesResource(rule, resource) {
|
|
115
|
+
const matcher = rule.resource;
|
|
116
|
+
if (!matcher) return true;
|
|
117
|
+
if (matcher.type && matcher.type !== resource.type) return false;
|
|
118
|
+
if (matcher.fields?.length) {
|
|
119
|
+
const targetFields = resource.fields ?? [];
|
|
120
|
+
if (!matcher.fields.some((field) => targetFields.includes(field))) return false;
|
|
121
|
+
}
|
|
122
|
+
if (matcher.attributes) {
|
|
123
|
+
const attributes = resource.attributes ?? {};
|
|
124
|
+
if (!matchAttributes(matcher.attributes, attributes)) return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
function matchesFlags(rule, input) {
|
|
129
|
+
if (!rule.flags?.length) return true;
|
|
130
|
+
const available = /* @__PURE__ */ new Set();
|
|
131
|
+
if (input.flags) for (const flag of input.flags) available.add(flag);
|
|
132
|
+
const attributeFlags = input.subject.attributes?.featureFlags;
|
|
133
|
+
if (Array.isArray(attributeFlags)) for (const flag of attributeFlags) available.add(flag);
|
|
134
|
+
return rule.flags.every((flag) => available.has(flag));
|
|
135
|
+
}
|
|
136
|
+
function matchesRelationships(matchers, input) {
|
|
137
|
+
if (!matchers || matchers.length === 0) return true;
|
|
138
|
+
const relationships = input.subject.relationships ?? [];
|
|
139
|
+
const resourceId = getResourceId(input.resource);
|
|
140
|
+
const resourceType = input.resource.type;
|
|
141
|
+
return matchers.every((matcher) => relationships.some((relation) => {
|
|
142
|
+
if (relation.relation !== matcher.relation) return false;
|
|
143
|
+
if (!(!matcher.objectType || relation.objectType === matcher.objectType || matcher.objectType === resourceType)) return false;
|
|
144
|
+
if (!matcher.objectId) return true;
|
|
145
|
+
if (matcher.objectId === "$resource") {
|
|
146
|
+
if (resourceId) return relation.object === resourceId;
|
|
147
|
+
return relation.object === resourceType || relation.objectType === resourceType;
|
|
148
|
+
}
|
|
149
|
+
return relation.object === matcher.objectId;
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
function matchesConditions(rule, input) {
|
|
153
|
+
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
154
|
+
return rule.conditions.every((condition) => evaluateCondition(condition.expression, input));
|
|
155
|
+
}
|
|
156
|
+
function matchAttributes(matcher, actual) {
|
|
157
|
+
for (const [key, attrMatcher] of Object.entries(matcher)) {
|
|
158
|
+
const value = actual[key];
|
|
159
|
+
if (attrMatcher.exists && typeof value === "undefined") return false;
|
|
160
|
+
if (typeof attrMatcher.equals !== "undefined") {
|
|
161
|
+
if (value !== attrMatcher.equals) return false;
|
|
162
|
+
}
|
|
163
|
+
if (attrMatcher.oneOf && !attrMatcher.oneOf.includes(value)) return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
function collectMissingConsents(rule, policy, input) {
|
|
168
|
+
if (!rule.requiresConsent?.length) return [];
|
|
169
|
+
const granted = new Set(input.consents ?? []);
|
|
170
|
+
const missingIds = rule.requiresConsent.filter((id) => !granted.has(id));
|
|
171
|
+
if (missingIds.length === 0) return [];
|
|
172
|
+
return resolveConsentDefinitions(policy, missingIds);
|
|
173
|
+
}
|
|
174
|
+
function resolveConsentDefinitions(policy, ids) {
|
|
175
|
+
const catalog = policy.consents ?? [];
|
|
176
|
+
return ids.map((id) => {
|
|
177
|
+
return catalog.find((consent) => consent.id === id) ?? {
|
|
178
|
+
id,
|
|
179
|
+
scope: "unspecified",
|
|
180
|
+
purpose: "unspecified",
|
|
181
|
+
description: `Consent "${id}" required by ${policy.meta.name}`,
|
|
182
|
+
required: true
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function resolveRateLimit(rule, policy, input) {
|
|
187
|
+
if (!rule.rateLimit) return void 0;
|
|
188
|
+
const definition = typeof rule.rateLimit === "string" ? (policy.rateLimits ?? []).find((item) => item.id === rule.rateLimit) : rule.rateLimit;
|
|
189
|
+
if (!definition) throw new Error(`PolicyEngine: rate limit "${String(rule.rateLimit)}" not declared in ${policy.meta.name}`);
|
|
190
|
+
return {
|
|
191
|
+
rpm: definition.rpm,
|
|
192
|
+
key: definition.key ?? input.resource.type,
|
|
193
|
+
windowSeconds: definition.windowSeconds,
|
|
194
|
+
burst: definition.burst
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function evaluateCondition(expression, input) {
|
|
198
|
+
const trimmed = expression.trim();
|
|
199
|
+
if (!trimmed) return true;
|
|
200
|
+
const context = {
|
|
201
|
+
subject: input.subject,
|
|
202
|
+
resource: input.resource,
|
|
203
|
+
context: input.context ?? {}
|
|
204
|
+
};
|
|
205
|
+
try {
|
|
206
|
+
const result = new Function("subject", "resource", "context", `return (${transformExpression(trimmed)});`)(context.subject, context.resource, context.context);
|
|
207
|
+
return Boolean(result);
|
|
208
|
+
} catch (_error) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function transformExpression(expression) {
|
|
213
|
+
return expression.replace(/&&/g, "&&").replace(/\|\|/g, "||");
|
|
214
|
+
}
|
|
215
|
+
function getResourceId(resource) {
|
|
216
|
+
if (resource.id) return resource.id;
|
|
217
|
+
const candidate = resource.attributes?.id;
|
|
218
|
+
if (typeof candidate === "string") return candidate;
|
|
219
|
+
if (typeof candidate === "number") return String(candidate);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
export { PolicyEngine };
|
|
@@ -1 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/policy/opa-adapter.ts
|
|
2
|
+
var OPAPolicyAdapter = class {
|
|
3
|
+
constructor(client, options) {
|
|
4
|
+
this.client = client;
|
|
5
|
+
this.options = options;
|
|
6
|
+
}
|
|
7
|
+
async evaluate(context, policies, engineDecision) {
|
|
8
|
+
const input = buildOPAInput(context, policies, engineDecision);
|
|
9
|
+
const raw = await this.client.evaluate(this.options.decisionPath, input);
|
|
10
|
+
const resolved = this.options.mapResult ? this.options.mapResult(raw) : raw;
|
|
11
|
+
if (!resolved) return {
|
|
12
|
+
...engineDecision,
|
|
13
|
+
evaluatedBy: engineDecision.evaluatedBy ?? "engine"
|
|
14
|
+
};
|
|
15
|
+
const opaResult = resolved;
|
|
16
|
+
const mergedRequiredConsents = mergeRequiredConsents(policies, engineDecision.requiredConsents ?? [], opaResult.requiredConsents ?? []);
|
|
17
|
+
return {
|
|
18
|
+
...engineDecision,
|
|
19
|
+
effect: opaResult.effect ?? engineDecision.effect,
|
|
20
|
+
reason: opaResult.reason ?? engineDecision.reason,
|
|
21
|
+
fieldDecisions: opaResult.fieldDecisions ?? engineDecision.fieldDecisions,
|
|
22
|
+
requiredConsents: mergedRequiredConsents.length ? mergedRequiredConsents : void 0,
|
|
23
|
+
evaluatedBy: "opa"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function buildOPAInput(context, policies, engineDecision) {
|
|
28
|
+
return {
|
|
29
|
+
context,
|
|
30
|
+
decision: engineDecision,
|
|
31
|
+
policies: policies.map((policy) => ({
|
|
32
|
+
meta: policy.meta,
|
|
33
|
+
rules: policy.rules,
|
|
34
|
+
fieldPolicies: policy.fieldPolicies,
|
|
35
|
+
pii: policy.pii,
|
|
36
|
+
relationships: policy.relationships,
|
|
37
|
+
consents: policy.consents,
|
|
38
|
+
rateLimits: policy.rateLimits
|
|
39
|
+
}))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function mergeRequiredConsents(policies, existing, incomingIds) {
|
|
43
|
+
if (incomingIds.length === 0) return existing;
|
|
44
|
+
const existingIds = new Set(existing.map((consent) => consent.id));
|
|
45
|
+
const merged = [...existing];
|
|
46
|
+
for (const id of incomingIds) {
|
|
47
|
+
if (existingIds.has(id)) continue;
|
|
48
|
+
const resolved = resolveConsentAcrossPolicies(policies, id);
|
|
49
|
+
if (resolved) {
|
|
50
|
+
merged.push(resolved);
|
|
51
|
+
existingIds.add(resolved.id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return merged;
|
|
55
|
+
}
|
|
56
|
+
function resolveConsentAcrossPolicies(policies, id) {
|
|
57
|
+
for (const policy of policies) {
|
|
58
|
+
const match = policy.consents?.find((consent) => consent.id === id);
|
|
59
|
+
if (match) return match;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
scope: "unspecified",
|
|
64
|
+
purpose: "unspecified",
|
|
65
|
+
description: `Consent "${id}" returned by OPA`,
|
|
66
|
+
required: true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { OPAPolicyAdapter, buildOPAInput };
|
package/dist/policy/spec.js
CHANGED
|
@@ -1 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/policy/spec.ts
|
|
2
|
+
const policyKey = (name, version) => `${name}.v${version}`;
|
|
3
|
+
var PolicyRegistry = class {
|
|
4
|
+
items = /* @__PURE__ */ new Map();
|
|
5
|
+
register(spec) {
|
|
6
|
+
const key = policyKey(spec.meta.name, spec.meta.version);
|
|
7
|
+
if (this.items.has(key)) throw new Error(`Duplicate policy ${key}`);
|
|
8
|
+
this.items.set(key, spec);
|
|
9
|
+
return this;
|
|
10
|
+
}
|
|
11
|
+
list() {
|
|
12
|
+
return [...this.items.values()];
|
|
13
|
+
}
|
|
14
|
+
get(name, version) {
|
|
15
|
+
if (version != null) return this.items.get(policyKey(name, version));
|
|
16
|
+
let candidate;
|
|
17
|
+
let max = -Infinity;
|
|
18
|
+
for (const spec of this.items.values()) {
|
|
19
|
+
if (spec.meta.name !== name) continue;
|
|
20
|
+
if (spec.meta.version > max) {
|
|
21
|
+
max = spec.meta.version;
|
|
22
|
+
candidate = spec;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function makePolicyKey(ref) {
|
|
29
|
+
return policyKey(ref.name, ref.version);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PolicyRegistry, makePolicyKey };
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import{registerDocBlocks
|
|
1
|
+
import { registerDocBlocks } from "../../docs/registry.js";
|
|
2
|
+
import "../../registry.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
//#region src/presentations/docs/presentations-conventions.docblock.ts
|
|
5
|
+
const tech_contracts_presentations_conventions_DocBlocks = [{
|
|
6
|
+
id: "docs.tech.contracts.presentations-conventions",
|
|
7
|
+
title: "Presentations Conventions (A11y & i18n)",
|
|
8
|
+
summary: "- Always provide `meta.description` (≥ 3 chars) — used by a11y/docs/agents.",
|
|
9
|
+
kind: "reference",
|
|
10
|
+
visibility: "public",
|
|
11
|
+
route: "/docs/tech/contracts/presentations-conventions",
|
|
12
|
+
tags: [
|
|
13
|
+
"tech",
|
|
14
|
+
"contracts",
|
|
15
|
+
"presentations-conventions"
|
|
16
|
+
],
|
|
17
|
+
body: "## Presentations Conventions (A11y & i18n)\n\n- Always provide `meta.description` (≥ 3 chars) — used by a11y/docs/agents.\n- Prefer source = BlockNote for rich guides; use component key for interactive flows.\n- i18n strings belong in host apps; descriptors carry keys/defaults only.\n- Target selection: include only what you intend to support to avoid drift.\n- PII: declare JSON-like paths under `policy.pii`; engine redacts in outputs.\n"
|
|
18
|
+
}];
|
|
19
|
+
registerDocBlocks(tech_contracts_presentations_conventions_DocBlocks);
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { tech_contracts_presentations_conventions_DocBlocks };
|
|
@@ -1 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/presentations.backcompat.ts
|
|
2
|
+
function toV2FromV1(v1) {
|
|
3
|
+
if (v1.content.kind === "web_component") return {
|
|
4
|
+
meta: { ...v1.meta },
|
|
5
|
+
policy: v1.policy,
|
|
6
|
+
source: {
|
|
7
|
+
type: "component",
|
|
8
|
+
framework: v1.content.framework,
|
|
9
|
+
componentKey: v1.content.componentKey,
|
|
10
|
+
props: v1.content.props
|
|
11
|
+
},
|
|
12
|
+
targets: [
|
|
13
|
+
"react",
|
|
14
|
+
"application/json",
|
|
15
|
+
"application/xml"
|
|
16
|
+
]
|
|
17
|
+
};
|
|
18
|
+
if (v1.content.kind === "markdown") return {
|
|
19
|
+
meta: { ...v1.meta },
|
|
20
|
+
policy: v1.policy,
|
|
21
|
+
source: {
|
|
22
|
+
type: "blocknotejs",
|
|
23
|
+
docJson: v1.content.content ?? v1.content.resourceUri ?? ""
|
|
24
|
+
},
|
|
25
|
+
targets: [
|
|
26
|
+
"markdown",
|
|
27
|
+
"application/json",
|
|
28
|
+
"application/xml"
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
meta: { ...v1.meta },
|
|
33
|
+
policy: v1.policy,
|
|
34
|
+
source: {
|
|
35
|
+
type: "blocknotejs",
|
|
36
|
+
docJson: {
|
|
37
|
+
kind: "data",
|
|
38
|
+
mimeType: v1.content.mimeType,
|
|
39
|
+
model: "schema"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
targets: ["application/json", "application/xml"]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { toV2FromV1 };
|
package/dist/presentations.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Stability } from "./ownership.js";
|
|
2
2
|
import z from "zod";
|
|
3
|
-
import * as
|
|
3
|
+
import * as _lssm_lib_schema136 from "@lssm/lib.schema";
|
|
4
4
|
import { AnySchemaModel } from "@lssm/lib.schema";
|
|
5
5
|
|
|
6
6
|
//#region src/presentations.d.ts
|
|
@@ -62,7 +62,7 @@ declare class PresentationRegistry {
|
|
|
62
62
|
declare function jsonSchemaForPresentation(p: PresentationSpec): {
|
|
63
63
|
framework: "react";
|
|
64
64
|
componentKey: string;
|
|
65
|
-
props: z.core.ZodStandardJSONSchemaPayload<
|
|
65
|
+
props: z.core.ZodStandardJSONSchemaPayload<_lssm_lib_schema136.TopLevelZodFromModel<_lssm_lib_schema136.SchemaModelFieldsAnyConfig<AnySchemaModel | _lssm_lib_schema136.AnyFieldType | _lssm_lib_schema136.AnyEnumType>>>;
|
|
66
66
|
meta: {
|
|
67
67
|
readonly name: string;
|
|
68
68
|
readonly version: number;
|
|
@@ -84,7 +84,7 @@ declare function jsonSchemaForPresentation(p: PresentationSpec): {
|
|
|
84
84
|
kind: PresentationKind;
|
|
85
85
|
} | {
|
|
86
86
|
mimeType: string;
|
|
87
|
-
model: z.core.ZodStandardJSONSchemaPayload<
|
|
87
|
+
model: z.core.ZodStandardJSONSchemaPayload<_lssm_lib_schema136.TopLevelZodFromModel<_lssm_lib_schema136.SchemaModelFieldsAnyConfig<AnySchemaModel | _lssm_lib_schema136.AnyFieldType | _lssm_lib_schema136.AnyEnumType>>>;
|
|
88
88
|
meta: {
|
|
89
89
|
readonly name: string;
|
|
90
90
|
readonly version: number;
|
package/dist/presentations.js
CHANGED
|
@@ -1 +1,66 @@
|
|
|
1
|
-
import
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/presentations.ts
|
|
4
|
+
function keyOf(p) {
|
|
5
|
+
return `${p.meta.name}.v${p.meta.version}`;
|
|
6
|
+
}
|
|
7
|
+
/** In-memory registry for V1 PresentationSpec. */
|
|
8
|
+
var PresentationRegistry = class {
|
|
9
|
+
items = /* @__PURE__ */ new Map();
|
|
10
|
+
constructor(items) {
|
|
11
|
+
if (items) items.forEach((p) => this.register(p));
|
|
12
|
+
}
|
|
13
|
+
register(p) {
|
|
14
|
+
const key = keyOf(p);
|
|
15
|
+
if (this.items.has(key)) throw new Error(`Duplicate presentation ${key}`);
|
|
16
|
+
this.items.set(key, p);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
list() {
|
|
20
|
+
return [...this.items.values()];
|
|
21
|
+
}
|
|
22
|
+
get(name, version) {
|
|
23
|
+
if (version != null) return this.items.get(`${name}.v${version}`);
|
|
24
|
+
let candidate;
|
|
25
|
+
let max = -Infinity;
|
|
26
|
+
for (const [k, p] of this.items.entries()) {
|
|
27
|
+
if (!k.startsWith(`${name}.v`)) continue;
|
|
28
|
+
if (p.meta.version > max) {
|
|
29
|
+
max = p.meta.version;
|
|
30
|
+
candidate = p;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function jsonSchemaForPresentation(p) {
|
|
37
|
+
const base = {
|
|
38
|
+
meta: {
|
|
39
|
+
name: p.meta.name,
|
|
40
|
+
version: p.meta.version,
|
|
41
|
+
stability: p.meta.stability ?? "stable",
|
|
42
|
+
tags: p.meta.tags ?? [],
|
|
43
|
+
description: p.meta.description ?? ""
|
|
44
|
+
},
|
|
45
|
+
kind: p.content.kind
|
|
46
|
+
};
|
|
47
|
+
if (p.content.kind === "web_component") return {
|
|
48
|
+
...base,
|
|
49
|
+
framework: p.content.framework,
|
|
50
|
+
componentKey: p.content.componentKey,
|
|
51
|
+
props: z.toJSONSchema(p.content.props.getZod())
|
|
52
|
+
};
|
|
53
|
+
if (p.content.kind === "markdown") return {
|
|
54
|
+
...base,
|
|
55
|
+
content: p.content.content,
|
|
56
|
+
resourceUri: p.content.resourceUri
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
...base,
|
|
60
|
+
mimeType: p.content.mimeType,
|
|
61
|
+
model: z.toJSONSchema(p.content.model.getZod())
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { PresentationRegistry, jsonSchemaForPresentation };
|