@flowdular/sdk 0.2.4 → 0.3.1
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/.ai/agents/sandbox/business-manager.md +5 -1
- package/.ai/blueprints/author-spec/README.md +1 -1
- package/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
- package/.ai/blueprints/author-spec/steps.yaml +5 -5
- package/.ai/blueprints/author-spec/templates/module.yaml +99 -3
- package/.ai/blueprints/edit-module/gates.yaml +4 -0
- package/.ai/blueprints/edit-module/required-files.yaml +9 -0
- package/.ai/blueprints/new-module/gates.yaml +4 -0
- package/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
- package/.ai/blueprints/release/gates.yaml +4 -0
- package/.ai/platform-capabilities.md +128 -0
- package/.ai/policies/capabilities.yaml +130 -3
- package/.ai/policies/path-ownership.yaml +5 -2
- package/.ai/policies/task-budgets.yaml +5 -3
- package/.ai/references/catalog/module.json +4 -4
- package/.ai/references/catalog/package.json +2 -2
- package/.ai/references/catalog/spec/module.yaml +5 -3
- package/.ai/references/catalog/src/platform.ts +2 -0
- package/.ai/references/catalog/src/services/catalog-service.ts +89 -1
- package/.ai/references/catalog/src/services/data-classes.ts +47 -0
- package/.ai/references/catalog/src/services/database-repository.ts +98 -1
- package/.ai/references/catalog/src/services/repository.ts +22 -1
- package/.ai/references/catalog/tests/data-classes.test.ts +157 -0
- package/.ai/references/catalog.provenance.json +12 -10
- package/.ai/skills/README.md +10 -0
- package/.ai/skills/agent-tool-design/SKILL.md +1 -2
- package/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/.ai/skills/business-agent-design/SKILL.md +0 -1
- package/.ai/skills/cli-extension/SKILL.md +1 -1
- package/.ai/skills/deploy-operate/SKILL.md +119 -0
- package/.ai/skills/module-new/SKILL.md +29 -3
- package/.ai/skills/module-update/SKILL.md +9 -3
- package/.ai/skills/perf-audit/SKILL.md +0 -1
- package/.ai/skills/release-eject-pr/SKILL.md +0 -1
- package/.ai/skills/spec-interview/SKILL.md +120 -0
- package/.ai/skills/test-hardening/SKILL.md +1 -0
- package/.ai/skills/ux-design/SKILL.md +34 -3
- package/.ai/skills/variables/SKILL.md +0 -2
- package/.ai/skills/workflow-development/SKILL.md +0 -1
- package/AGENTS.md +4 -0
- package/docs/agent-contract.md +2 -2
- package/docs/design-system.md +185 -31
- package/modules/access/migrations/0001_access_core.down.sql +1 -0
- package/modules/access/migrations/0001_access_core.up.sql +28 -0
- package/modules/access/migrations/README.md +3 -0
- package/modules/access/module.json +33 -0
- package/modules/access/package.json +43 -0
- package/modules/access/spec/module.yaml +225 -0
- package/modules/access/src/acl/permissions.ts +6 -0
- package/modules/access/src/api/endpoints.ts +244 -0
- package/modules/access/src/client/ActivityView.tsrx +245 -0
- package/modules/access/src/client/AttestationsView.tsrx +176 -0
- package/modules/access/src/client/ReviewView.tsrx +586 -0
- package/modules/access/src/client/api.ts +132 -0
- package/modules/access/src/client/contribution.tsrx +41 -0
- package/modules/access/src/client/index.ts +41 -0
- package/modules/access/src/client/navigation.ts +57 -0
- package/modules/access/src/client/presentation.ts +69 -0
- package/modules/access/src/client/state.ts +72 -0
- package/modules/access/src/domain/types.ts +189 -0
- package/modules/access/src/index.ts +38 -0
- package/modules/access/src/platform.ts +60 -0
- package/modules/access/src/server/index.ts +42 -0
- package/modules/access/src/server/runtime.ts +105 -0
- package/modules/access/src/services/access-service.ts +298 -0
- package/modules/access/src/services/audit-window.ts +78 -0
- package/modules/access/src/services/auth-directory.ts +72 -0
- package/modules/access/src/services/changes.ts +91 -0
- package/modules/access/src/services/data-classes.ts +28 -0
- package/modules/access/src/services/database-repository.ts +185 -0
- package/modules/access/src/services/directory.ts +102 -0
- package/modules/access/src/services/index.ts +33 -0
- package/modules/access/src/services/list-exports.ts +150 -0
- package/modules/access/src/services/migration.ts +54 -0
- package/modules/access/src/services/repository.ts +30 -0
- package/modules/access/src/services/review.ts +178 -0
- package/modules/access/translations/en.json +155 -0
- package/modules/access/translations/pl.json +155 -0
- package/modules/agents/migrations/0022_credential_rotation_inventory.down.sql +2 -0
- package/modules/agents/migrations/0022_credential_rotation_inventory.up.sql +7 -0
- package/modules/agents/migrations/0023_agents_retention_indexes.down.sql +2 -0
- package/modules/agents/migrations/0023_agents_retention_indexes.up.sql +9 -0
- package/modules/agents/migrations/0024_agent_meter_refusals.down.sql +3 -0
- package/modules/agents/migrations/0024_agent_meter_refusals.up.sql +22 -0
- package/modules/agents/migrations/0025_agent_list_indexes.down.sql +3 -0
- package/modules/agents/migrations/0025_agent_list_indexes.up.sql +11 -0
- package/modules/agents/module.json +30 -4
- package/modules/agents/package.json +2 -1
- package/modules/agents/spec/module.yaml +63 -5
- package/modules/agents/src/api/endpoints.ts +291 -33
- package/modules/agents/src/cli/commands.json +11 -0
- package/modules/agents/src/cli/index.ts +45 -0
- package/modules/agents/src/client/AgentDashboardWidget.tsrx +19 -11
- package/modules/agents/src/client/AgentDefinitionList.tsrx +53 -10
- package/modules/agents/src/client/AgentPlaygroundView.tsrx +4 -4
- package/modules/agents/src/client/AgentProceduresView.tsrx +2 -2
- package/modules/agents/src/client/AgentRunsView.tsrx +143 -40
- package/modules/agents/src/client/AgentsView.tsrx +109 -33
- package/modules/agents/src/client/api.ts +99 -21
- package/modules/agents/src/client/state.ts +97 -0
- package/modules/agents/src/domain/types.ts +44 -4
- package/modules/agents/src/platform.ts +42 -0
- package/modules/agents/src/server/action-execution.ts +201 -106
- package/modules/agents/src/server/runtime.ts +32 -0
- package/modules/agents/src/services/agent-service.ts +158 -30
- package/modules/agents/src/services/credential-rotation.ts +185 -0
- package/modules/agents/src/services/credential-vault.ts +71 -46
- package/modules/agents/src/services/data-classes.ts +176 -0
- package/modules/agents/src/services/database-repository.ts +393 -101
- package/modules/agents/src/services/metering.ts +111 -0
- package/modules/agents/src/services/migration.ts +109 -0
- package/modules/agents/src/services/module-agent-preflight.ts +17 -9
- package/modules/agents/src/services/notifications.ts +98 -0
- package/modules/agents/src/services/provider-repository.ts +9 -18
- package/modules/agents/src/services/provider-service.ts +1 -9
- package/modules/agents/src/services/reports.ts +107 -0
- package/modules/agents/src/services/repository.ts +86 -6
- package/modules/agents/src/services/usage-service.ts +2 -0
- package/modules/agents/src/services/worker.ts +114 -4
- package/modules/agents/src/settings.ts +14 -3
- package/modules/agents/translations/en.json +14 -4
- package/modules/agents/translations/pl.json +14 -4
- package/modules/approvals/migrations/0001_approvals_core.down.sql +3 -0
- package/modules/approvals/migrations/0001_approvals_core.up.sql +78 -0
- package/modules/approvals/migrations/0002_approvals_expiry_routing_role.down.sql +2 -0
- package/modules/approvals/migrations/0002_approvals_expiry_routing_role.up.sql +19 -0
- package/modules/approvals/migrations/0003_approvals_retention_indexes.down.sql +6 -0
- package/modules/approvals/migrations/0003_approvals_retention_indexes.up.sql +14 -0
- package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.down.sql +5 -0
- package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.up.sql +12 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.down.sql +4 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.up.sql +20 -0
- package/modules/approvals/migrations/README.md +3 -0
- package/modules/approvals/module.json +34 -0
- package/modules/approvals/package.json +43 -0
- package/modules/approvals/spec/module.yaml +327 -0
- package/modules/approvals/src/acl/permissions.ts +7 -0
- package/modules/approvals/src/api/endpoints.ts +457 -0
- package/modules/approvals/src/client/ApprovalRequestDrawer.tsrx +138 -0
- package/modules/approvals/src/client/ApprovalsView.tsrx +562 -0
- package/modules/approvals/src/client/PendingCountWidget.tsrx +52 -0
- package/modules/approvals/src/client/api.ts +203 -0
- package/modules/approvals/src/client/approvals.css +51 -0
- package/modules/approvals/src/client/contribution.tsrx +44 -0
- package/modules/approvals/src/client/index.ts +26 -0
- package/modules/approvals/src/client/navigation.ts +39 -0
- package/modules/approvals/src/client/pending-count.ts +26 -0
- package/modules/approvals/src/client/presentation.ts +52 -0
- package/modules/approvals/src/client/state.ts +141 -0
- package/modules/approvals/src/domain/capability.ts +107 -0
- package/modules/approvals/src/domain/grant.ts +69 -0
- package/modules/approvals/src/domain/types.ts +168 -0
- package/modules/approvals/src/index.ts +61 -0
- package/modules/approvals/src/platform.ts +136 -0
- package/modules/approvals/src/server/index.ts +42 -0
- package/modules/approvals/src/server/runtime.ts +185 -0
- package/modules/approvals/src/services/approvals-service.ts +637 -0
- package/modules/approvals/src/services/callbacks.ts +75 -0
- package/modules/approvals/src/services/data-classes.ts +167 -0
- package/modules/approvals/src/services/database-repository.ts +783 -0
- package/modules/approvals/src/services/eligibility.ts +121 -0
- package/modules/approvals/src/services/expiry-runner.ts +81 -0
- package/modules/approvals/src/services/index.ts +31 -0
- package/modules/approvals/src/services/migration.ts +256 -0
- package/modules/approvals/src/services/notifications.ts +95 -0
- package/modules/approvals/src/services/repository.ts +169 -0
- package/modules/approvals/src/services/service-error.ts +70 -0
- package/modules/approvals/src/settings.ts +102 -0
- package/modules/approvals/translations/en.json +104 -0
- package/modules/approvals/translations/pl.json +104 -0
- package/modules/audit/migrations/0001_audit_core.down.sql +8 -0
- package/modules/audit/migrations/0001_audit_core.up.sql +107 -0
- package/modules/audit/migrations/0002_audit_sweep_routing_role.down.sql +2 -0
- package/modules/audit/migrations/0002_audit_sweep_routing_role.up.sql +21 -0
- package/modules/audit/migrations/0003_audit_export_request.down.sql +12 -0
- package/modules/audit/migrations/0003_audit_export_request.up.sql +40 -0
- package/modules/audit/migrations/0004_audit_hold_seal_erasure.down.sql +17 -0
- package/modules/audit/migrations/0004_audit_hold_seal_erasure.up.sql +192 -0
- package/modules/audit/migrations/0005_audit_event_format.down.sql +17 -0
- package/modules/audit/migrations/0005_audit_event_format.up.sql +60 -0
- package/modules/audit/migrations/0006_audit_own_class_walks.down.sql +3 -0
- package/modules/audit/migrations/0006_audit_own_class_walks.up.sql +14 -0
- package/modules/audit/migrations/README.md +3 -0
- package/modules/audit/module.json +32 -0
- package/modules/audit/package.json +49 -0
- package/modules/audit/spec/module.yaml +809 -0
- package/modules/audit/src/acl/permissions.ts +7 -0
- package/modules/audit/src/api/endpoints.ts +315 -0
- package/modules/audit/src/cli/commands.json +95 -0
- package/modules/audit/src/cli/index.ts +658 -0
- package/modules/audit/src/client/DataClassesView.tsrx +420 -0
- package/modules/audit/src/client/ExportsView.tsrx +256 -0
- package/modules/audit/src/client/HoldsView.tsrx +604 -0
- package/modules/audit/src/client/SweepsView.tsrx +251 -0
- package/modules/audit/src/client/api.ts +185 -0
- package/modules/audit/src/client/contribution.tsrx +52 -0
- package/modules/audit/src/client/index.ts +23 -0
- package/modules/audit/src/client/navigation.ts +74 -0
- package/modules/audit/src/client/presentation.ts +105 -0
- package/modules/audit/src/client/state.ts +86 -0
- package/modules/audit/src/domain/data-classes.ts +16 -0
- package/modules/audit/src/domain/types.ts +339 -0
- package/modules/audit/src/index.ts +76 -0
- package/modules/audit/src/platform.ts +44 -0
- package/modules/audit/src/server/index.ts +177 -0
- package/modules/audit/src/server/runtime.ts +303 -0
- package/modules/audit/src/services/anchor-key.ts +143 -0
- package/modules/audit/src/services/anchor-rotation.ts +147 -0
- package/modules/audit/src/services/audit-runners.ts +278 -0
- package/modules/audit/src/services/backup-guard.ts +95 -0
- package/modules/audit/src/services/database-repository.ts +1946 -0
- package/modules/audit/src/services/declared-classes.ts +64 -0
- package/modules/audit/src/services/erasure-port.ts +126 -0
- package/modules/audit/src/services/erasure-service.ts +770 -0
- package/modules/audit/src/services/export-directory.ts +106 -0
- package/modules/audit/src/services/export-service.ts +714 -0
- package/modules/audit/src/services/hold-service.ts +295 -0
- package/modules/audit/src/services/index.ts +17 -0
- package/modules/audit/src/services/migration.ts +658 -0
- package/modules/audit/src/services/own-classes.ts +318 -0
- package/modules/audit/src/services/platform-version.ts +23 -0
- package/modules/audit/src/services/repository.ts +495 -0
- package/modules/audit/src/services/retention-service.ts +191 -0
- package/modules/audit/src/services/seal-service.ts +769 -0
- package/modules/audit/src/services/service-error.ts +61 -0
- package/modules/audit/src/services/subject-keys.ts +90 -0
- package/modules/audit/src/services/sweep-service.ts +298 -0
- package/modules/audit/src/services/zip.ts +217 -0
- package/modules/audit/src/settings.ts +69 -0
- package/modules/audit/translations/en.json +210 -0
- package/modules/audit/translations/pl.json +210 -0
- package/modules/auth/README.md +77 -29
- package/modules/auth/migrations/0016_external_identities.up.sql +16 -0
- package/modules/auth/migrations/0017_mfa_key_id.up.sql +8 -0
- package/modules/auth/migrations/0018_retire_bundled_module_scopes.up.sql +29 -0
- package/modules/auth/migrations/0019_notifications_member_scopes.up.sql +44 -0
- package/modules/auth/migrations/0020_identity_providers.up.sql +61 -0
- package/modules/auth/migrations/0021_membership_status.up.sql +19 -0
- package/modules/auth/migrations/0022_auth_provider_scopes.up.sql +45 -0
- package/modules/auth/migrations/0023_enterprise_module_scopes.down.sql +19 -0
- package/modules/auth/migrations/0023_enterprise_module_scopes.up.sql +71 -0
- package/modules/auth/migrations/0024_approvals_member_scopes.down.sql +22 -0
- package/modules/auth/migrations/0024_approvals_member_scopes.up.sql +48 -0
- package/modules/auth/migrations/0025_audit_holds_owner_scope.down.sql +21 -0
- package/modules/auth/migrations/0025_audit_holds_owner_scope.up.sql +47 -0
- package/modules/auth/migrations/0026_auth_export_keyset_indexes.down.sql +6 -0
- package/modules/auth/migrations/0026_auth_export_keyset_indexes.up.sql +15 -0
- package/modules/auth/migrations/0027_workflow_automation_profile_scopes.down.sql +34 -0
- package/modules/auth/migrations/0027_workflow_automation_profile_scopes.up.sql +72 -0
- package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.down.sql +6 -0
- package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.up.sql +21 -0
- package/modules/auth/migrations/0029_reports_exports_access_scopes.down.sql +22 -0
- package/modules/auth/migrations/0029_reports_exports_access_scopes.up.sql +56 -0
- package/modules/auth/migrations/0030_auth_membership_keyset_index.down.sql +4 -0
- package/modules/auth/migrations/0030_auth_membership_keyset_index.up.sql +10 -0
- package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.down.sql +4 -0
- package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.up.sql +31 -0
- package/modules/auth/migrations/0032_audit_actor_service.down.sql +9 -0
- package/modules/auth/migrations/0032_audit_actor_service.up.sql +10 -0
- package/modules/auth/migrations/0033_auth_account_keyset_indexes.down.sql +5 -0
- package/modules/auth/migrations/0033_auth_account_keyset_indexes.up.sql +23 -0
- package/modules/auth/module.json +3 -3
- package/modules/auth/package.json +2 -1
- package/modules/auth/spec/module.yaml +372 -3
- package/modules/auth/src/acl/scopes.ts +50 -6
- package/modules/auth/src/cli/commands.json +11 -0
- package/modules/auth/src/cli/greenfield.ts +2 -2
- package/modules/auth/src/cli/index.ts +14 -0
- package/modules/auth/src/cli/secrets.ts +114 -0
- package/modules/auth/src/client/AuthenticationCore.tsrx +61 -8
- package/modules/auth/src/client/api.ts +72 -6
- package/modules/auth/src/client/contribution.tsrx +23 -0
- package/modules/auth/src/client/index.ts +6 -0
- package/modules/auth/src/client/providers/IdentityProviderForm.tsrx +191 -0
- package/modules/auth/src/client/providers/IdentityProvidersView.tsrx +358 -0
- package/modules/auth/src/client/providers/api.ts +128 -0
- package/modules/auth/src/client/providers/provider-columns.tsrx +60 -0
- package/modules/auth/src/client/providers/state.ts +35 -0
- package/modules/auth/src/client/security/SecurityView.tsrx +9 -1
- package/modules/auth/src/client/state.ts +15 -1
- package/modules/auth/src/domain/types.ts +55 -0
- package/modules/auth/src/index.ts +11 -1
- package/modules/auth/src/middleware/authentication.ts +4 -0
- package/modules/auth/src/server/endpoints.ts +467 -171
- package/modules/auth/src/server/http.ts +3 -2
- package/modules/auth/src/server/index.ts +22 -1
- package/modules/auth/src/server/membership-endpoints.ts +62 -0
- package/modules/auth/src/server/mfa-enforcement.ts +156 -0
- package/modules/auth/src/server/oidc.ts +389 -0
- package/modules/auth/src/server/provider-endpoints.ts +246 -0
- package/modules/auth/src/server/provider-host.ts +77 -0
- package/modules/auth/src/server/runtime.ts +231 -55
- package/modules/auth/src/services/auth-service-error.ts +7 -2
- package/modules/auth/src/services/auth-service.ts +948 -51
- package/modules/auth/src/services/data-classes.ts +236 -0
- package/modules/auth/src/services/database-repository.ts +853 -71
- package/modules/auth/src/services/identity-provider-service.ts +571 -0
- package/modules/auth/src/services/mail-delivery.ts +16 -16
- package/modules/auth/src/services/mail-message.ts +54 -0
- package/modules/auth/src/services/mail-port.ts +24 -0
- package/modules/auth/src/services/mail-smtp.ts +85 -0
- package/modules/auth/src/services/mfa-rotation.ts +144 -0
- package/modules/auth/src/services/migration.ts +810 -0
- package/modules/auth/src/services/password.ts +14 -0
- package/modules/auth/src/services/provider-secret-rotation.ts +169 -0
- package/modules/auth/src/services/provider-secrets.ts +136 -0
- package/modules/auth/src/services/repository.ts +321 -5
- package/modules/auth/src/services/session-sweep-runner.ts +73 -0
- package/modules/auth/src/services/settings-store.ts +10 -83
- package/modules/auth/src/services/totp.ts +88 -62
- package/modules/auth/src/services/validation.ts +194 -7
- package/modules/auth/src/settings.ts +18 -0
- package/modules/auth/translations/en.json +84 -1
- package/modules/auth/translations/pl.json +84 -1
- package/modules/automations/migrations/0005_secret_rotation_inventory.down.sql +1 -0
- package/modules/automations/migrations/0005_secret_rotation_inventory.up.sql +6 -0
- package/modules/automations/migrations/0006_automations_list_sort_indexes.down.sql +4 -0
- package/modules/automations/migrations/0006_automations_list_sort_indexes.up.sql +11 -0
- package/modules/automations/module.json +24 -6
- package/modules/automations/package.json +4 -1
- package/modules/automations/spec/module.yaml +81 -7
- package/modules/automations/src/api/endpoints.ts +202 -28
- package/modules/automations/src/cli/commands.json +17 -0
- package/modules/automations/src/cli/index.ts +114 -0
- package/modules/automations/src/client/AutomationScheduleForm.tsrx +77 -19
- package/modules/automations/src/client/AutomationSchedulesView.tsrx +144 -40
- package/modules/automations/src/client/AutomationTriggersView.tsrx +131 -32
- package/modules/automations/src/client/api.ts +62 -19
- package/modules/automations/src/client/presentation.ts +20 -2
- package/modules/automations/src/client/state.ts +84 -0
- package/modules/automations/src/domain/cadence.ts +99 -3
- package/modules/automations/src/domain/cron.ts +366 -0
- package/modules/automations/src/domain/time-zone.ts +61 -0
- package/modules/automations/src/platform.ts +38 -4
- package/modules/automations/src/server/index.ts +11 -0
- package/modules/automations/src/server/runtime.ts +46 -22
- package/modules/{automations-workflows-integration/src/server/adapter.ts → automations/src/server/workflow-target.ts} +13 -14
- package/modules/{automations-workflows-integration/src/server/tools.ts → automations/src/server/workflow-tools.ts} +0 -0
- package/modules/automations/src/services/data-classes.ts +62 -0
- package/modules/automations/src/services/database-repository.ts +152 -38
- package/modules/automations/src/services/migration.ts +119 -9
- package/modules/automations/src/services/repository.ts +52 -1
- package/modules/automations/src/services/schedule-runner.ts +84 -0
- package/modules/automations/src/services/schedule-service.ts +152 -26
- package/modules/automations/src/services/secret-rotation.ts +176 -0
- package/modules/automations/src/services/secret-vault.ts +68 -41
- package/modules/automations/src/services/trigger-service.ts +13 -10
- package/modules/automations/translations/en.json +15 -2
- package/modules/automations/translations/pl.json +15 -2
- package/modules/connectors/migrations/0001_connectors_core.down.sql +3 -0
- package/modules/connectors/migrations/0001_connectors_core.up.sql +72 -0
- package/modules/connectors/migrations/0002_connectors_call_keys.down.sql +3 -0
- package/modules/connectors/migrations/0002_connectors_call_keys.up.sql +18 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.down.sql +2 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.up.sql +19 -0
- package/modules/connectors/migrations/README.md +3 -0
- package/modules/connectors/module.json +32 -0
- package/modules/{automations-workflows-integration → connectors}/package.json +28 -26
- package/modules/connectors/spec/module.yaml +448 -0
- package/modules/connectors/src/acl/permissions.ts +6 -0
- package/modules/connectors/src/agent/tools.ts +180 -0
- package/modules/connectors/src/api/endpoints.ts +572 -0
- package/modules/connectors/src/cli/commands.json +17 -0
- package/modules/connectors/src/cli/index.ts +126 -0
- package/modules/connectors/src/client/ConnectorCallsView.tsrx +358 -0
- package/modules/connectors/src/client/ConnectorForm.tsrx +424 -0
- package/modules/connectors/src/client/ConnectorsView.tsrx +711 -0
- package/modules/connectors/src/client/api.ts +281 -0
- package/modules/connectors/src/client/consent.ts +48 -0
- package/modules/connectors/src/client/contribution.tsrx +37 -0
- package/modules/connectors/src/client/index.ts +22 -0
- package/modules/connectors/src/client/navigation.ts +42 -0
- package/modules/connectors/src/client/presentation.ts +38 -0
- package/modules/connectors/src/client/state.ts +175 -0
- package/modules/connectors/src/domain/calls.ts +70 -0
- package/modules/connectors/src/domain/definitions.ts +190 -0
- package/modules/connectors/src/domain/http-json.ts +68 -0
- package/modules/connectors/src/domain/types.ts +185 -0
- package/modules/connectors/src/index.ts +64 -0
- package/modules/connectors/src/platform.ts +56 -0
- package/modules/connectors/src/server/index.ts +66 -0
- package/modules/connectors/src/server/runtime.ts +163 -0
- package/modules/connectors/src/services/call-service.ts +903 -0
- package/modules/connectors/src/services/connectors-service.ts +653 -0
- package/modules/connectors/src/services/credential-rotation.ts +221 -0
- package/modules/connectors/src/services/credential-vault.ts +194 -0
- package/modules/connectors/src/services/data-classes.ts +158 -0
- package/modules/connectors/src/services/database-repository.ts +897 -0
- package/modules/connectors/src/services/egress.ts +261 -0
- package/modules/connectors/src/services/index.ts +19 -0
- package/modules/connectors/src/services/migration.ts +202 -0
- package/modules/connectors/src/services/repository.ts +175 -0
- package/modules/connectors/src/services/service-error.ts +46 -0
- package/modules/connectors/src/settings.ts +69 -0
- package/modules/connectors/translations/en.json +191 -0
- package/modules/connectors/translations/pl.json +191 -0
- package/modules/directory/migrations/0001_directory_core.down.sql +5 -0
- package/modules/directory/migrations/0001_directory_core.up.sql +111 -0
- package/modules/directory/migrations/README.md +3 -0
- package/modules/directory/module.json +27 -0
- package/modules/directory/package.json +46 -0
- package/modules/directory/spec/module.yaml +351 -0
- package/modules/directory/src/acl/permissions.ts +7 -0
- package/modules/directory/src/api/endpoints.ts +478 -0
- package/modules/directory/src/api/scim-endpoints.ts +516 -0
- package/modules/directory/src/client/GroupMappingsView.tsrx +430 -0
- package/modules/directory/src/client/ProvisioningLogView.tsrx +248 -0
- package/modules/directory/src/client/ScimTokensView.tsrx +587 -0
- package/modules/directory/src/client/api.ts +222 -0
- package/modules/directory/src/client/contribution.tsrx +46 -0
- package/modules/directory/src/client/index.ts +22 -0
- package/modules/directory/src/client/navigation.ts +57 -0
- package/modules/directory/src/client/presentation.ts +143 -0
- package/modules/directory/src/client/state.ts +173 -0
- package/modules/directory/src/domain/scim.ts +346 -0
- package/modules/directory/src/domain/types.ts +166 -0
- package/modules/directory/src/index.ts +44 -0
- package/modules/directory/src/platform.ts +43 -0
- package/modules/directory/src/server/index.ts +17 -0
- package/modules/directory/src/server/runtime.ts +124 -0
- package/modules/directory/src/services/auth-port.ts +127 -0
- package/modules/directory/src/services/data-classes.ts +41 -0
- package/modules/directory/src/services/database-repository.ts +1067 -0
- package/modules/directory/src/services/directory-service.ts +311 -0
- package/modules/directory/src/services/index.ts +43 -0
- package/modules/directory/src/services/migration.ts +146 -0
- package/modules/directory/src/services/provisioning-service.ts +1420 -0
- package/modules/directory/src/services/rate-limiter.ts +103 -0
- package/modules/directory/src/services/repository.ts +208 -0
- package/modules/directory/src/services/service-error.ts +42 -0
- package/modules/directory/src/services/token-service.ts +215 -0
- package/modules/directory/src/settings.ts +86 -0
- package/modules/directory/translations/en.json +185 -0
- package/modules/directory/translations/pl.json +185 -0
- package/modules/documents/migrations/0001_documents_core.down.sql +1 -0
- package/modules/documents/migrations/0001_documents_core.up.sql +35 -0
- package/modules/documents/migrations/0002_documents_files_page.down.sql +1 -0
- package/modules/documents/migrations/0002_documents_files_page.up.sql +6 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.down.sql +2 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.up.sql +18 -0
- package/modules/documents/migrations/README.md +3 -0
- package/modules/documents/module.json +32 -0
- package/modules/documents/package.json +50 -0
- package/modules/documents/spec/module.yaml +248 -0
- package/modules/documents/src/acl/permissions.ts +6 -0
- package/modules/documents/src/api/endpoints.ts +401 -0
- package/modules/documents/src/cli/commands.json +17 -0
- package/modules/documents/src/cli/index.ts +145 -0
- package/modules/documents/src/client/DocumentUploader.tsrx +148 -0
- package/modules/documents/src/client/DocumentsView.tsrx +540 -0
- package/modules/documents/src/client/api.ts +209 -0
- package/modules/documents/src/client/contribution.tsrx +29 -0
- package/modules/documents/src/client/download.ts +70 -0
- package/modules/documents/src/client/index.ts +22 -0
- package/modules/documents/src/client/navigation.ts +25 -0
- package/modules/documents/src/client/presentation.ts +110 -0
- package/modules/documents/src/client/state.ts +117 -0
- package/modules/documents/src/domain/attachments.ts +84 -0
- package/modules/documents/src/domain/types.ts +80 -0
- package/modules/documents/src/index.ts +46 -0
- package/modules/documents/src/platform.ts +61 -0
- package/modules/documents/src/server/index.ts +30 -0
- package/modules/documents/src/server/runtime.ts +107 -0
- package/modules/documents/src/services/attachments.ts +39 -0
- package/modules/documents/src/services/data-classes.ts +25 -0
- package/modules/documents/src/services/database-repository.ts +270 -0
- package/modules/documents/src/services/documents-service.ts +590 -0
- package/modules/documents/src/services/index.ts +18 -0
- package/modules/documents/src/services/migration.ts +117 -0
- package/modules/documents/src/services/repository.ts +58 -0
- package/modules/documents/src/services/storage-rotation.ts +157 -0
- package/modules/documents/src/settings.ts +107 -0
- package/modules/documents/translations/en.json +111 -0
- package/modules/documents/translations/pl.json +111 -0
- package/modules/exports/migrations/0001_exports_core.down.sql +2 -0
- package/modules/exports/migrations/0001_exports_core.up.sql +28 -0
- package/modules/exports/migrations/0002_exports_job_routing_role.down.sql +4 -0
- package/modules/exports/migrations/0002_exports_job_routing_role.up.sql +21 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.down.sql +1 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.up.sql +9 -0
- package/modules/exports/migrations/README.md +3 -0
- package/modules/exports/module.json +32 -0
- package/modules/exports/package.json +45 -0
- package/modules/exports/spec/module.yaml +230 -0
- package/modules/exports/src/acl/permissions.ts +6 -0
- package/modules/exports/src/api/endpoints.ts +211 -0
- package/modules/exports/src/cli/commands.json +17 -0
- package/modules/exports/src/cli/index.ts +145 -0
- package/modules/exports/src/client/ExportsView.tsrx +327 -0
- package/modules/exports/src/client/api.ts +133 -0
- package/modules/exports/src/client/contribution.tsrx +31 -0
- package/modules/exports/src/client/index.ts +20 -0
- package/modules/exports/src/client/navigation.ts +25 -0
- package/modules/exports/src/client/presentation.ts +53 -0
- package/modules/exports/src/client/state.ts +56 -0
- package/modules/exports/src/domain/lists.ts +29 -0
- package/modules/exports/src/domain/types.ts +120 -0
- package/modules/exports/src/index.ts +41 -0
- package/modules/exports/src/platform.ts +80 -0
- package/modules/exports/src/server/index.ts +50 -0
- package/modules/exports/src/server/runtime.ts +160 -0
- package/modules/exports/src/services/data-classes.ts +59 -0
- package/modules/exports/src/services/database-repository.ts +403 -0
- package/modules/exports/src/services/export-runner.ts +123 -0
- package/modules/exports/src/services/export-service.ts +356 -0
- package/modules/exports/src/services/index.ts +6 -0
- package/modules/exports/src/services/list-registry.ts +105 -0
- package/modules/exports/src/services/migration.ts +116 -0
- package/modules/exports/src/services/repository.ts +96 -0
- package/modules/exports/src/services/storage-rotation.ts +138 -0
- package/modules/exports/src/settings.ts +70 -0
- package/modules/exports/translations/en.json +69 -0
- package/modules/exports/translations/pl.json +69 -0
- package/modules/import/migrations/0001_import_core.down.sql +6 -0
- package/modules/import/migrations/0001_import_core.up.sql +61 -0
- package/modules/import/migrations/0002_import_job_routing_role.down.sql +4 -0
- package/modules/import/migrations/0002_import_job_routing_role.up.sql +20 -0
- package/modules/import/migrations/0003_import_jobs_export_index.down.sql +1 -0
- package/modules/import/migrations/0003_import_jobs_export_index.up.sql +7 -0
- package/modules/import/migrations/0004_import_jobs_traceparent.down.sql +1 -0
- package/modules/import/migrations/0004_import_jobs_traceparent.up.sql +7 -0
- package/modules/import/migrations/README.md +3 -0
- package/modules/import/module.json +37 -0
- package/modules/import/package.json +50 -0
- package/modules/import/spec/module.yaml +335 -0
- package/modules/import/src/acl/permissions.ts +6 -0
- package/modules/import/src/api/endpoints.ts +400 -0
- package/modules/import/src/client/ImportJobDrawer.tsrx +229 -0
- package/modules/import/src/client/ImportsView.tsrx +455 -0
- package/modules/import/src/client/NewImportForm.tsrx +274 -0
- package/modules/import/src/client/api.ts +243 -0
- package/modules/import/src/client/contribution.tsrx +29 -0
- package/modules/import/src/client/index.ts +23 -0
- package/modules/import/src/client/navigation.ts +25 -0
- package/modules/import/src/client/presentation.ts +59 -0
- package/modules/import/src/client/state.ts +43 -0
- package/modules/import/src/domain/csv.ts +258 -0
- package/modules/import/src/domain/ports.ts +157 -0
- package/modules/import/src/domain/types.ts +179 -0
- package/modules/import/src/index.ts +58 -0
- package/modules/import/src/platform.ts +60 -0
- package/modules/import/src/server/index.ts +49 -0
- package/modules/import/src/server/runtime.ts +158 -0
- package/modules/import/src/services/csv-source.ts +121 -0
- package/modules/import/src/services/data-classes.ts +63 -0
- package/modules/import/src/services/database-repository.ts +780 -0
- package/modules/import/src/services/import-runner.ts +127 -0
- package/modules/import/src/services/import-service.ts +872 -0
- package/modules/import/src/services/index.ts +6 -0
- package/modules/import/src/services/migration.ts +156 -0
- package/modules/import/src/services/port-registry.ts +158 -0
- package/modules/import/src/services/repository.ts +147 -0
- package/modules/import/src/settings.ts +70 -0
- package/modules/import/translations/en.json +175 -0
- package/modules/import/translations/pl.json +175 -0
- package/modules/metering/migrations/0001_metering_core.down.sql +6 -0
- package/modules/metering/migrations/0001_metering_core.up.sql +119 -0
- package/modules/metering/migrations/0002_metering_bucket_export.down.sql +3 -0
- package/modules/metering/migrations/0002_metering_bucket_export.up.sql +5 -0
- package/modules/metering/migrations/README.md +3 -0
- package/modules/metering/module.json +46 -0
- package/modules/metering/package.json +50 -0
- package/modules/metering/spec/module.yaml +269 -0
- package/modules/metering/src/acl/permissions.ts +5 -0
- package/modules/metering/src/api/endpoints.ts +104 -0
- package/modules/metering/src/cli/commands.json +28 -0
- package/modules/metering/src/cli/index.ts +246 -0
- package/modules/metering/src/client/LimitsView.tsrx +170 -0
- package/modules/metering/src/client/UsageSummaryWidget.tsrx +63 -0
- package/modules/metering/src/client/UsageView.tsrx +205 -0
- package/modules/metering/src/client/api.ts +91 -0
- package/modules/metering/src/client/contribution.tsrx +42 -0
- package/modules/metering/src/client/index.ts +26 -0
- package/modules/metering/src/client/navigation.ts +54 -0
- package/modules/metering/src/client/presentation.ts +63 -0
- package/modules/metering/src/client/state.ts +31 -0
- package/modules/metering/src/domain/meters.ts +100 -0
- package/modules/metering/src/domain/types.ts +71 -0
- package/modules/metering/src/index.ts +51 -0
- package/modules/metering/src/platform.ts +84 -0
- package/modules/metering/src/server/index.ts +40 -0
- package/modules/metering/src/server/runtime.ts +165 -0
- package/modules/metering/src/services/data-classes.ts +34 -0
- package/modules/metering/src/services/database-repository.ts +574 -0
- package/modules/metering/src/services/index.ts +9 -0
- package/modules/metering/src/services/meter-registry.ts +145 -0
- package/modules/metering/src/services/metering-service.ts +441 -0
- package/modules/metering/src/services/migration.ts +183 -0
- package/modules/metering/src/services/notifications.ts +75 -0
- package/modules/metering/src/services/reports.ts +105 -0
- package/modules/metering/src/services/repository.ts +132 -0
- package/modules/metering/src/services/service-error.ts +46 -0
- package/modules/metering/src/settings.ts +47 -0
- package/modules/metering/translations/en.json +68 -0
- package/modules/metering/translations/pl.json +68 -0
- package/modules/notifications/migrations/0001_notifications_core.down.sql +1 -0
- package/modules/notifications/migrations/0001_notifications_core.up.sql +26 -0
- package/modules/notifications/migrations/0002_notifications_preferences.down.sql +1 -0
- package/modules/notifications/migrations/0002_notifications_preferences.up.sql +17 -0
- package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.down.sql +1 -0
- package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.up.sql +30 -0
- package/modules/notifications/migrations/0004_notifications_deliveries.down.sql +1 -0
- package/modules/notifications/migrations/0004_notifications_deliveries.up.sql +35 -0
- package/modules/notifications/migrations/0005_notifications_delivery_routing_role.down.sql +2 -0
- package/modules/notifications/migrations/0005_notifications_delivery_routing_role.up.sql +17 -0
- package/modules/notifications/migrations/0006_notifications_delivery_title.down.sql +1 -0
- package/modules/notifications/migrations/0006_notifications_delivery_title.up.sql +11 -0
- package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.down.sql +2 -0
- package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.up.sql +19 -0
- package/modules/notifications/migrations/0008_notifications_delivery_claim.down.sql +8 -0
- package/modules/notifications/migrations/0008_notifications_delivery_claim.up.sql +19 -0
- package/modules/notifications/migrations/0009_notifications_kind_approvals.down.sql +16 -0
- package/modules/notifications/migrations/0009_notifications_kind_approvals.up.sql +18 -0
- package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.down.sql +16 -0
- package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.up.sql +18 -0
- package/modules/notifications/migrations/0011_notifications_retention_keyset.down.sql +5 -0
- package/modules/notifications/migrations/0011_notifications_retention_keyset.up.sql +15 -0
- package/modules/notifications/migrations/0012_notifications_email_channel.down.sql +12 -0
- package/modules/notifications/migrations/0012_notifications_email_channel.up.sql +38 -0
- package/modules/notifications/migrations/0013_notifications_list_pages.down.sql +5 -0
- package/modules/notifications/migrations/0013_notifications_list_pages.up.sql +13 -0
- package/modules/notifications/migrations/README.md +3 -0
- package/modules/notifications/module.json +32 -0
- package/modules/notifications/package.json +49 -0
- package/modules/notifications/spec/module.yaml +718 -0
- package/modules/notifications/src/acl/permissions.ts +11 -0
- package/modules/notifications/src/api/endpoints.ts +814 -0
- package/modules/notifications/src/cli/commands.json +17 -0
- package/modules/notifications/src/cli/index.ts +126 -0
- package/modules/notifications/src/client/DeliveriesView.tsrx +483 -0
- package/modules/notifications/src/client/FactList.tsrx +24 -0
- package/modules/notifications/src/client/NotificationPreferencesView.tsrx +240 -0
- package/modules/notifications/src/client/NotificationsInboxView.tsrx +592 -0
- package/modules/notifications/src/client/UnreadCountWidget.tsrx +62 -0
- package/modules/notifications/src/client/WebhookForm.tsrx +297 -0
- package/modules/notifications/src/client/WebhooksView.tsrx +637 -0
- package/modules/notifications/src/client/api.ts +382 -0
- package/modules/notifications/src/client/contribution.tsrx +83 -0
- package/modules/notifications/src/client/inbox.ts +41 -0
- package/modules/notifications/src/client/index.ts +29 -0
- package/modules/notifications/src/client/navigation.ts +92 -0
- package/modules/notifications/src/client/notifications.css +70 -0
- package/modules/notifications/src/client/presentation.ts +81 -0
- package/modules/notifications/src/client/state.ts +246 -0
- package/modules/notifications/src/client/unread-count.ts +26 -0
- package/modules/notifications/src/domain/locale.ts +47 -0
- package/modules/notifications/src/domain/publish.ts +46 -0
- package/modules/notifications/src/domain/types.ts +186 -0
- package/modules/notifications/src/index.ts +62 -0
- package/modules/notifications/src/platform.ts +85 -0
- package/modules/notifications/src/server/index.ts +82 -0
- package/modules/notifications/src/server/runtime.ts +251 -0
- package/modules/notifications/src/services/data-classes.ts +139 -0
- package/modules/notifications/src/services/database-repository.ts +1334 -0
- package/modules/notifications/src/services/delivery-payload.ts +93 -0
- package/modules/notifications/src/services/delivery-runner.ts +170 -0
- package/modules/notifications/src/services/delivery-service.ts +811 -0
- package/modules/notifications/src/services/egress.ts +239 -0
- package/modules/notifications/src/services/email-channel.ts +93 -0
- package/modules/notifications/src/services/index.ts +23 -0
- package/modules/notifications/src/services/migration.ts +572 -0
- package/modules/notifications/src/services/notifications-service.ts +297 -0
- package/modules/notifications/src/services/paging.ts +60 -0
- package/modules/notifications/src/services/publish-service.ts +133 -0
- package/modules/notifications/src/services/repository.ts +342 -0
- package/modules/notifications/src/services/secret-rotation.ts +177 -0
- package/modules/notifications/src/services/secret-vault.ts +168 -0
- package/modules/notifications/src/services/service-error.ts +67 -0
- package/modules/notifications/src/services/signature.ts +81 -0
- package/modules/notifications/src/services/webhook-service.ts +307 -0
- package/modules/notifications/src/settings.ts +130 -0
- package/modules/notifications/translations/en.json +255 -0
- package/modules/notifications/translations/pl.json +255 -0
- package/modules/profile/module.json +3 -3
- package/modules/profile/package.json +1 -1
- package/modules/profile/spec/module.yaml +3 -3
- package/modules/profile/src/services/database-repository.ts +3 -13
- package/modules/reports/module.json +28 -0
- package/modules/reports/package.json +42 -0
- package/modules/reports/spec/module.yaml +231 -0
- package/modules/reports/src/acl/permissions.ts +5 -0
- package/modules/reports/src/api/endpoints.ts +72 -0
- package/modules/reports/src/client/ReportsSummaryWidget.tsrx +52 -0
- package/modules/reports/src/client/ReportsView.tsrx +246 -0
- package/modules/reports/src/client/api.ts +65 -0
- package/modules/reports/src/client/contribution.tsrx +36 -0
- package/modules/reports/src/client/index.ts +18 -0
- package/modules/reports/src/client/navigation.ts +39 -0
- package/modules/reports/src/client/presentation.ts +123 -0
- package/modules/reports/src/client/state.ts +28 -0
- package/modules/reports/src/domain/providers.ts +115 -0
- package/modules/reports/src/domain/types.ts +44 -0
- package/modules/reports/src/index.ts +47 -0
- package/modules/reports/src/platform.ts +37 -0
- package/modules/reports/src/server/index.ts +21 -0
- package/modules/reports/src/server/runtime.ts +40 -0
- package/modules/reports/src/services/index.ts +17 -0
- package/modules/reports/src/services/provider-registry.ts +144 -0
- package/modules/reports/src/services/range.ts +65 -0
- package/modules/reports/src/services/reports-service.ts +316 -0
- package/modules/reports/src/services/service-error.ts +26 -0
- package/modules/reports/src/settings.ts +49 -0
- package/modules/reports/translations/en.json +35 -0
- package/modules/reports/translations/pl.json +35 -0
- package/modules/sandbox/module.json +4 -4
- package/modules/sandbox/package.json +1 -1
- package/modules/sandbox/spec/module.yaml +3 -3
- package/modules/sandbox/src/services/database-repository.ts +14 -23
- package/modules/sandbox/src/services/directory.ts +16 -0
- package/modules/sandbox/src/services/sandbox-service.ts +13 -11
- package/modules/search/migrations/0001_search_core.down.sql +1 -0
- package/modules/search/migrations/0001_search_core.up.sql +22 -0
- package/modules/search/migrations/0002_search_recent_export.down.sql +3 -0
- package/modules/search/migrations/0002_search_recent_export.up.sql +5 -0
- package/modules/search/migrations/README.md +3 -0
- package/modules/search/module.json +28 -0
- package/modules/search/package.json +43 -0
- package/modules/search/spec/module.yaml +177 -0
- package/modules/search/src/acl/permissions.ts +5 -0
- package/modules/search/src/api/endpoints.ts +221 -0
- package/modules/search/src/client/SearchView.tsrx +346 -0
- package/modules/search/src/client/api.ts +157 -0
- package/modules/search/src/client/contribution.tsrx +31 -0
- package/modules/search/src/client/index.ts +17 -0
- package/modules/search/src/client/navigation.ts +97 -0
- package/modules/search/src/client/search.css +56 -0
- package/modules/search/src/client/state.ts +113 -0
- package/modules/search/src/domain/data-classes.ts +73 -0
- package/modules/search/src/domain/providers.ts +88 -0
- package/modules/search/src/domain/types.ts +56 -0
- package/modules/search/src/index.ts +46 -0
- package/modules/search/src/platform.ts +42 -0
- package/modules/search/src/server/index.ts +25 -0
- package/modules/search/src/server/runtime.ts +123 -0
- package/modules/search/src/services/database-repository.ts +163 -0
- package/modules/search/src/services/index.ts +21 -0
- package/modules/search/src/services/migration.ts +65 -0
- package/modules/search/src/services/provider-registry.ts +137 -0
- package/modules/search/src/services/repository.ts +43 -0
- package/modules/search/src/services/search-service.ts +442 -0
- package/modules/search/src/services/service-error.ts +35 -0
- package/modules/search/src/settings.ts +68 -0
- package/modules/search/translations/en.json +45 -0
- package/modules/search/translations/pl.json +45 -0
- package/modules/system/module.json +2 -2
- package/modules/system/package.json +1 -1
- package/modules/system/spec/module.yaml +30 -3
- package/modules/system/src/client/FlagsPanel.tsrx +127 -0
- package/modules/system/src/client/ModuleSettingRow.tsrx +17 -7
- package/modules/system/src/client/ModuleSettingsSection.tsrx +10 -71
- package/modules/system/src/client/ModulesView.tsrx +91 -65
- package/modules/system/src/client/flags.ts +35 -0
- package/modules/system/src/client/index.ts +8 -0
- package/modules/system/src/client/navigation.ts +8 -0
- package/modules/system/src/client/settings-state.ts +159 -0
- package/modules/system/src/client/state.ts +2 -0
- package/modules/system/src/domain/time-zone.ts +105 -0
- package/modules/system/src/index.ts +12 -0
- package/modules/system/src/platform.ts +5 -1
- package/modules/system/src/server/endpoints.ts +72 -21
- package/modules/system/src/settings.ts +28 -0
- package/modules/system/translations/en.json +17 -0
- package/modules/system/translations/pl.json +17 -0
- package/modules/users/module.json +30 -4
- package/modules/users/package.json +4 -1
- package/modules/users/spec/module.yaml +290 -7
- package/modules/users/src/api/endpoints.ts +228 -15
- package/modules/users/src/client/InviteMemberForm.tsrx +2 -2
- package/modules/users/src/client/MemberDrawer.tsrx +142 -26
- package/modules/users/src/client/UserAccountForm.tsrx +2 -2
- package/modules/users/src/client/UsersView.tsrx +432 -71
- package/modules/users/src/client/api.ts +106 -12
- package/modules/users/src/client/contribution.tsrx +5 -0
- package/modules/users/src/client/member-columns.tsrx +22 -9
- package/modules/users/src/client/state.ts +172 -7
- package/modules/users/src/domain/lists.ts +9 -0
- package/modules/users/src/index.ts +11 -0
- package/modules/users/src/platform.ts +50 -1
- package/modules/users/src/services/member-export.ts +51 -0
- package/modules/users/src/services/member-import.ts +249 -0
- package/modules/users/src/services/member-search.ts +145 -0
- package/modules/users/src/services/users-service.ts +117 -4
- package/modules/users/translations/en.json +57 -8
- package/modules/users/translations/pl.json +57 -8
- package/modules/workflows/migrations/0004_payload_rotation_inventory.up.sql +17 -0
- package/modules/workflows/migrations/0005_workflows_human_approval.up.sql +28 -0
- package/modules/workflows/migrations/0006_workflows_retention_indexes.up.sql +11 -0
- package/modules/workflows/migrations/0007_workflows_erasure_subject_indexes.up.sql +17 -0
- package/modules/workflows/migrations/0008_workflows_subject_account.up.sql +24 -0
- package/modules/workflows/migrations/0009_workflows_definition_list_indexes.up.sql +9 -0
- package/modules/workflows/module.json +34 -6
- package/modules/workflows/package.json +2 -1
- package/modules/workflows/spec/module.yaml +26 -9
- package/modules/workflows/src/api/endpoints.ts +131 -25
- package/modules/workflows/src/cli/commands.json +17 -0
- package/modules/workflows/src/cli/index.ts +114 -0
- package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +36 -27
- package/modules/workflows/src/client/WorkflowInspector.tsrx +116 -0
- package/modules/workflows/src/client/WorkflowNodePalette.tsrx +1 -0
- package/modules/workflows/src/client/WorkflowsView.tsrx +236 -99
- package/modules/workflows/src/client/api.ts +54 -34
- package/modules/workflows/src/client/canvas-model.ts +12 -0
- package/modules/workflows/src/client/state.ts +95 -0
- package/modules/workflows/src/domain/events.ts +25 -11
- package/modules/workflows/src/domain/graph.ts +135 -1
- package/modules/workflows/src/domain/types.ts +91 -4
- package/modules/workflows/src/platform.ts +12 -0
- package/modules/workflows/src/server/runtime.ts +78 -5
- package/modules/workflows/src/services/approvals.ts +62 -0
- package/modules/workflows/src/services/cursors.ts +37 -0
- package/modules/workflows/src/services/data-classes.ts +232 -0
- package/modules/workflows/src/services/database-repository.ts +660 -171
- package/modules/workflows/src/services/migration.ts +240 -0
- package/modules/workflows/src/services/notifications.ts +98 -0
- package/modules/workflows/src/services/payload-codec.ts +51 -28
- package/modules/workflows/src/services/payload-rotation.ts +171 -0
- package/modules/workflows/src/services/repository.ts +123 -8
- package/modules/workflows/src/services/simulation.ts +7 -0
- package/modules/workflows/src/services/worker.ts +446 -25
- package/modules/workflows/src/services/workflows-service.ts +264 -103
- package/modules/workflows/translations/en.json +12 -4
- package/modules/workflows/translations/pl.json +12 -4
- package/modules.json +48 -4
- package/package.json +64 -6
- package/packages/client/package.json +1 -0
- package/packages/client/src/ApplicationShell.tsrx +43 -0
- package/packages/client/src/contributions.ts +55 -0
- package/packages/client/src/i18n/locales/en.json +5 -0
- package/packages/client/src/i18n/locales/pl.json +5 -0
- package/packages/client/src/index.ts +13 -1
- package/packages/client/src/routing.ts +22 -0
- package/packages/client/src/shell/CommandPalette.tsrx +223 -11
- package/packages/client/src/shell/command-search.ts +126 -0
- package/packages/client/src/shell/command.ts +30 -0
- package/packages/client/src/shell/navigation.ts +12 -0
- package/packages/client/src/shell/palette-keys.ts +27 -0
- package/packages/client/src/shell/shell.css +7 -0
- package/packages/contracts/schemas/module-spec.schema.json +287 -1
- package/packages/contracts/schemas/module.schema.json +25 -0
- package/packages/contracts/src/index.ts +144 -1
- package/packages/database/src/backup.ts +180 -0
- package/packages/database/src/decoders.ts +25 -0
- package/packages/database/src/index.ts +17 -0
- package/packages/database/src/migrations.ts +7 -0
- package/packages/database/src/record-history.ts +5 -14
- package/packages/harness/src/index.ts +2 -0
- package/packages/harness/src/runtime.ts +378 -2
- package/packages/harness/src/tool-adapters.ts +20 -9
- package/packages/kernel/src/acl.ts +179 -0
- package/packages/kernel/src/approval-grant.ts +310 -0
- package/packages/kernel/src/capability-registry.ts +69 -2
- package/packages/kernel/src/data-class-registry.ts +354 -0
- package/packages/kernel/src/index.ts +85 -4
- package/packages/kernel/src/keyring.ts +208 -0
- package/packages/kernel/src/module-compatibility.ts +35 -4
- package/packages/kernel/src/module-registry.ts +49 -2
- package/packages/kernel/src/module-settings.ts +189 -27
- package/packages/server/package.json +2 -1
- package/packages/server/src/composition.ts +38 -0
- package/packages/server/src/endpoint.ts +104 -47
- package/packages/server/src/export/csv.ts +34 -0
- package/packages/server/src/export/definition.ts +346 -0
- package/packages/server/src/export/index.ts +61 -0
- package/packages/server/src/export/run.ts +116 -0
- package/packages/server/src/index.ts +155 -0
- package/packages/server/src/jobs/index.ts +93 -0
- package/packages/server/src/jobs/runner.ts +486 -0
- package/packages/server/src/log.ts +291 -0
- package/packages/server/src/mail/config.ts +184 -0
- package/packages/server/src/mail/contracts.ts +251 -0
- package/packages/server/src/mail/index.ts +31 -0
- package/packages/server/src/mail/port.ts +101 -0
- package/packages/server/src/mail/smtp.ts +191 -0
- package/packages/server/src/mail/template.ts +105 -0
- package/packages/server/src/metrics.ts +447 -0
- package/packages/server/src/pagination.ts +246 -0
- package/packages/server/src/trace/context.ts +103 -0
- package/packages/server/src/trace/egress.ts +66 -0
- package/packages/server/src/trace/error-sink.ts +296 -0
- package/packages/server/src/trace/exporter.ts +295 -0
- package/packages/server/src/trace/index.ts +60 -0
- package/packages/server/src/trace/job-sink.ts +172 -0
- package/packages/server/src/trace/tracer.ts +338 -0
- package/packages/server/src/web.ts +5 -1
- package/packages/storage/package.json +40 -0
- package/packages/storage/src/config.ts +189 -0
- package/packages/storage/src/content-type.ts +124 -0
- package/packages/storage/src/contracts.ts +112 -0
- package/packages/storage/src/envelope.ts +238 -0
- package/packages/storage/src/index.ts +49 -0
- package/packages/storage/src/local.ts +75 -0
- package/packages/storage/src/port.ts +262 -0
- package/packages/storage/src/read-token.ts +130 -0
- package/packages/storage/src/reseal.ts +128 -0
- package/packages/storage/src/s3.ts +144 -0
- package/packages/storage/src/scanner.ts +22 -0
- package/packages/storage/src/sigv4.ts +104 -0
- package/packages/storage/src/store.ts +13 -0
- package/packages/ui/package.json +1 -0
- package/packages/ui/src/components/Button.tsrx +3 -0
- package/packages/ui/src/components/ConfirmDialog.tsrx +13 -2
- package/packages/ui/src/components/DateField.tsrx +92 -0
- package/packages/ui/src/components/DatePicker.tsrx +525 -0
- package/packages/ui/src/components/DateRangeField.tsrx +93 -0
- package/packages/ui/src/components/Drawer.tsrx +10 -1
- package/packages/ui/src/components/FileUpload.tsrx +158 -0
- package/packages/ui/src/components/Pagination.tsrx +130 -0
- package/packages/ui/src/components/Select.tsrx +78 -0
- package/packages/ui/src/components/Table.tsrx +498 -81
- package/packages/ui/src/components/Tabs.tsrx +53 -0
- package/packages/ui/src/components/ToastHost.tsrx +48 -0
- package/packages/ui/src/components/calendar.ts +243 -0
- package/packages/ui/src/components/date-field.ts +62 -0
- package/packages/ui/src/components/file-upload.ts +49 -0
- package/packages/ui/src/components/focus-trap.ts +62 -0
- package/packages/ui/src/components/pagination.ts +68 -0
- package/packages/ui/src/components/table-sorting.ts +49 -0
- package/packages/ui/src/components/tabs.ts +61 -0
- package/packages/ui/src/components/toast-store.ts +116 -0
- package/packages/ui/src/icons/Icon.tsrx +5 -0
- package/packages/ui/src/index.ts +45 -0
- package/packages/ui/src/styles/components.css +598 -0
- package/modules/automations-workflows-integration/module.json +0 -27
- package/modules/automations-workflows-integration/spec/module.yaml +0 -93
- package/modules/automations-workflows-integration/src/index.ts +0 -14
- package/modules/automations-workflows-integration/src/platform.ts +0 -24
- package/modules/automations-workflows-integration/src/server/index.ts +0 -11
- package/modules/automations-workflows-integration/translations/en.json +0 -21
- package/modules/automations-workflows-integration/translations/pl.json +0 -21
- package/modules/workflows/src/services/cursor-codec.ts +0 -62
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
schemaVersion: 2
|
|
2
|
+
id: documents.core
|
|
3
|
+
specVersion: 0.1.9
|
|
4
|
+
status: approved
|
|
5
|
+
name: Documents Core
|
|
6
|
+
description: Lets a workspace attach files to its records through the platform storage port, owning only the document metadata and the reference to the record, with tenant-scoped keys, encrypted bytes, size and type limits, and a scan state, so a module attaches by reference and never holds bytes.
|
|
7
|
+
profile: full
|
|
8
|
+
capabilities:
|
|
9
|
+
- api
|
|
10
|
+
- database
|
|
11
|
+
- client
|
|
12
|
+
- translations
|
|
13
|
+
- cli
|
|
14
|
+
dependencies:
|
|
15
|
+
- id: system.core
|
|
16
|
+
range: ^0.7.0
|
|
17
|
+
- id: auth.core
|
|
18
|
+
range: ^0.13.0
|
|
19
|
+
tenancy: required
|
|
20
|
+
locales:
|
|
21
|
+
- en
|
|
22
|
+
- pl
|
|
23
|
+
invariants:
|
|
24
|
+
- Persistence runs on the shared asynchronous database contract with explicit PostgreSQL SQL; every tenant table enables and forces row-level security with USING and WITH CHECK policies bound to the transaction-local tenant id, and the runtime role holds neither SUPERUSER nor BYPASSRLS.
|
|
25
|
+
- Bytes live only in the platform storage port under a key whose first segment is the tenant id taken from the principal; documents.core stores metadata and never a byte, and a module attaches a document to its record by reference through the public capability documents.attachments.v1, never by reading the store. The capability offers list, open and delete on that reference; open streams the decrypted bytes through documents.core's own storage access and answers nothing for a reference with no readable object behind it, an infected or deleted document included, so the storage key never leaves this module.
|
|
26
|
+
- An upload is one request whose body is the file, bounded by the platform object limit, with the declared content type checked against the platform allowlist and the magic bytes before the object is stored; a refusal leaves no metadata row and no object.
|
|
27
|
+
- A download is a short-lived signed read URL from the storage port; documents.core serves no route that streams bytes, and the URL is issued only to a principal allowed to read the owning record's documents.
|
|
28
|
+
- The scan state follows the storage port's verdict; an infected object is deleted from the store immediately, its row keeps the infected state for the audit trail, and it can never be downloaded; an unscanned object is downloadable and labelled as such.
|
|
29
|
+
- Deleting a document deletes the object first and the row after; a document referenced by a record is deleted only through the owning module's capability call or by a member holding documents.files.manage.
|
|
30
|
+
- Every workspace has a storage quota setting; an upload that would exceed it is refused with a stable error before any byte is stored.
|
|
31
|
+
- The document list is filtered, searched and paged by the server. The owner module, the record reference, the scan verdict and the screen's search term narrow the query; the term travels as the bounded query parameter q, matches the filename or the description, and reaches SQL as a parameter whose LIKE wildcards are escaped, so a wildcard a reader types searches for that character. The page is a keyset cursor the module signs with a secret of its own process, so a cursor issued by one replica is refused by another with CURSOR_INVALID and the client starts over. The screen shows either a real total or that more rows remain, and never narrows or pages the rows of one answer as if they were the workspace.
|
|
32
|
+
- Upload metadata travels in headers, which carry ASCII only, so a value holding a code point above 0x7F is refused and a client percent-encodes it; a name is stored as it was sent or not at all.
|
|
33
|
+
- The object ceiling and the accepted content types are served to the screen, which states them and refuses a file past the ceiling before it sends one. The storage port stays the authority on both and refuses again on the bytes it receives.
|
|
34
|
+
- Documents are a data class declared through the platform data class registry, exportable as metadata rows that name the storage key while the bytes stay in the object store until the export sink gains an attachment channel, and swept only by explicit deletion, never by retention.
|
|
35
|
+
permissions:
|
|
36
|
+
- id: documents.files.read
|
|
37
|
+
description: List documents of the active workspace and obtain read URLs for them.
|
|
38
|
+
- id: documents.files.manage
|
|
39
|
+
description: Upload, replace and delete documents in the active workspace.
|
|
40
|
+
dataOwnership:
|
|
41
|
+
- documents.core owns document metadata, the storage key, the checksum, the scan state and the attachment reference.
|
|
42
|
+
- The platform storage port owns the bytes and their encryption; the owning module owns the record a document is attached to and decides through its own permissions who may see the record.
|
|
43
|
+
entities:
|
|
44
|
+
- id: document
|
|
45
|
+
name: Document
|
|
46
|
+
description: One stored file and the record it is attached to.
|
|
47
|
+
fields:
|
|
48
|
+
- id: ownerModule
|
|
49
|
+
type: string
|
|
50
|
+
required: true
|
|
51
|
+
maxLength: 64
|
|
52
|
+
- id: recordRef
|
|
53
|
+
type: string
|
|
54
|
+
required: true
|
|
55
|
+
maxLength: 200
|
|
56
|
+
- id: filename
|
|
57
|
+
type: string
|
|
58
|
+
required: true
|
|
59
|
+
maxLength: 255
|
|
60
|
+
- id: contentType
|
|
61
|
+
type: string
|
|
62
|
+
required: true
|
|
63
|
+
maxLength: 128
|
|
64
|
+
- id: bytes
|
|
65
|
+
type: integer
|
|
66
|
+
required: true
|
|
67
|
+
- id: checksum
|
|
68
|
+
type: string
|
|
69
|
+
required: false
|
|
70
|
+
maxLength: 80
|
|
71
|
+
description: The port's algorithm-prefixed digest; absent for a refused infected upload that left no object.
|
|
72
|
+
- id: storageKey
|
|
73
|
+
type: string
|
|
74
|
+
required: true
|
|
75
|
+
maxLength: 512
|
|
76
|
+
- id: uploaderAccountId
|
|
77
|
+
type: string
|
|
78
|
+
required: true
|
|
79
|
+
maxLength: 128
|
|
80
|
+
- id: scan
|
|
81
|
+
type: enum
|
|
82
|
+
required: true
|
|
83
|
+
values:
|
|
84
|
+
- unscanned
|
|
85
|
+
- clean
|
|
86
|
+
- infected
|
|
87
|
+
- id: status
|
|
88
|
+
type: enum
|
|
89
|
+
required: true
|
|
90
|
+
values:
|
|
91
|
+
- stored
|
|
92
|
+
- deleted
|
|
93
|
+
- id: description
|
|
94
|
+
type: text
|
|
95
|
+
required: false
|
|
96
|
+
maxLength: 1000
|
|
97
|
+
states:
|
|
98
|
+
field: status
|
|
99
|
+
values:
|
|
100
|
+
- stored
|
|
101
|
+
- deleted
|
|
102
|
+
transitions:
|
|
103
|
+
- from: stored
|
|
104
|
+
to: deleted
|
|
105
|
+
permission: documents.files.manage
|
|
106
|
+
screens:
|
|
107
|
+
- id: documents
|
|
108
|
+
kind: list
|
|
109
|
+
entity: document
|
|
110
|
+
title: Documents
|
|
111
|
+
columns:
|
|
112
|
+
- filename
|
|
113
|
+
- ownerModule
|
|
114
|
+
- recordRef
|
|
115
|
+
- contentType
|
|
116
|
+
- bytes
|
|
117
|
+
- scan
|
|
118
|
+
- uploaderAccountId
|
|
119
|
+
filters:
|
|
120
|
+
- ownerModule
|
|
121
|
+
- scan
|
|
122
|
+
- filename
|
|
123
|
+
- description
|
|
124
|
+
navigationGroup: Workspace
|
|
125
|
+
actions:
|
|
126
|
+
- id: upload
|
|
127
|
+
entity: document
|
|
128
|
+
permission: documents.files.manage
|
|
129
|
+
kind: create
|
|
130
|
+
risk: workspace-write
|
|
131
|
+
idempotent: false
|
|
132
|
+
description: Store one file for a record reference; refused over the object limit, outside the allowlist, on a magic-byte mismatch or over the workspace quota.
|
|
133
|
+
- id: read-limits
|
|
134
|
+
entity: document
|
|
135
|
+
permission: documents.files.read
|
|
136
|
+
kind: custom
|
|
137
|
+
risk: read
|
|
138
|
+
idempotent: true
|
|
139
|
+
description: Answer the platform object ceiling and the accepted content types, so the uploader states them and refuses a file before it is sent.
|
|
140
|
+
- id: read-url
|
|
141
|
+
entity: document
|
|
142
|
+
permission: documents.files.read
|
|
143
|
+
kind: custom
|
|
144
|
+
risk: read
|
|
145
|
+
idempotent: true
|
|
146
|
+
description: Issue a signed read URL valid for a bounded number of seconds; refused for an infected document.
|
|
147
|
+
- id: delete
|
|
148
|
+
entity: document
|
|
149
|
+
permission: documents.files.manage
|
|
150
|
+
kind: delete
|
|
151
|
+
risk: destructive
|
|
152
|
+
idempotent: true
|
|
153
|
+
description: Delete the object, then mark the row deleted.
|
|
154
|
+
- id: delete-many
|
|
155
|
+
entity: document
|
|
156
|
+
permission: documents.files.manage
|
|
157
|
+
kind: delete
|
|
158
|
+
risk: destructive
|
|
159
|
+
idempotent: true
|
|
160
|
+
description: Delete the documents the screen selected, between 1 and 100 unique ids, each through the single delete path, answering one outcome per id (deleted, not-found or refused) and never failing the whole call for a missing id.
|
|
161
|
+
widgets: []
|
|
162
|
+
settings:
|
|
163
|
+
- key: quotaBytes
|
|
164
|
+
type: integer
|
|
165
|
+
scope: tenant
|
|
166
|
+
default: 10737418240
|
|
167
|
+
description: Storage quota of the workspace in bytes; 0 means no uploads.
|
|
168
|
+
- key: readUrlSeconds
|
|
169
|
+
type: integer
|
|
170
|
+
scope: platform
|
|
171
|
+
default: 300
|
|
172
|
+
description: Lifetime of a read URL; 30 to 3600.
|
|
173
|
+
agentTools: []
|
|
174
|
+
outOfScope:
|
|
175
|
+
- Previews, thumbnails and in-browser rendering are not offered; a document is downloaded.
|
|
176
|
+
- Versioning of a document is not offered; replacing is an upload plus a delete by the owning module.
|
|
177
|
+
- A shared FileUpload component does not exist yet; the Documents screen builds its uploader locally and flags it as a promotion candidate.
|
|
178
|
+
- Malware scanning runs only when the deployment configures a scanner for the storage port; otherwise documents are labelled unscanned.
|
|
179
|
+
decisions:
|
|
180
|
+
- id: D-DOCUMENTS-STORAGE
|
|
181
|
+
question: Where do bytes live?
|
|
182
|
+
answer: In the platform storage port with a local adapter for development and an S3-compatible adapter for deployments, encrypted under the storage keyring.
|
|
183
|
+
decidedBy: user
|
|
184
|
+
- id: D-DOCUMENTS-LIMITS
|
|
185
|
+
question: What limits apply?
|
|
186
|
+
answer: 25 MB per object by default, a content type allowlist with magic-byte verification, a scanner hook, and a 10 GB workspace quota by default.
|
|
187
|
+
decidedBy: user
|
|
188
|
+
- id: D-DOCUMENTS-ATTACH
|
|
189
|
+
question: How does a module attach a file?
|
|
190
|
+
answer: By reference through documents.attachments.v1, naming its module id and record reference; the module never holds bytes.
|
|
191
|
+
decidedBy: default
|
|
192
|
+
- id: D-DOCUMENTS-INFECTED
|
|
193
|
+
question: What happens to an infected file?
|
|
194
|
+
answer: The object is deleted at once, the row stays infected for the trail, and a download is refused.
|
|
195
|
+
decidedBy: default
|
|
196
|
+
- id: D-DOCUMENTS-PERMISSIONS
|
|
197
|
+
question: Who reads and manages documents?
|
|
198
|
+
answer: Members hold documents.files.read and documents.files.manage by default; the owning module may narrow through its own record permissions.
|
|
199
|
+
decidedBy: default
|
|
200
|
+
- id: D-DOCUMENTS-ATTACH-AUTHORITY
|
|
201
|
+
question: Who may attach a file to another module's record?
|
|
202
|
+
answer: Attaching is workspace-wide under documents.files.manage. documents.core holds no per-module attach predicate and does not ask the owning module whether the reference it was given is a record at all, so any member who may upload may name any owner module and record reference. An owning module narrows on read alone, through the capability, where its own record permission decides who sees an attachment. A per-module attach predicate stays out of scope until a module needs to refuse a reference at upload time.
|
|
203
|
+
decidedBy: default
|
|
204
|
+
acceptanceScenarios:
|
|
205
|
+
- id: DOCUMENTS-UPLOAD
|
|
206
|
+
given: A principal holding documents.files.manage uploads a PDF under the object limit for a record reference.
|
|
207
|
+
when: The upload completes.
|
|
208
|
+
then: The object is stored encrypted under a key whose first segment is the principal's tenant, a metadata row records filename, content type, bytes, checksum, uploader and scan state, and the response carries the document id.
|
|
209
|
+
- id: DOCUMENTS-REFUSE
|
|
210
|
+
given: A principal uploads a file over the object limit, an executable, a file whose magic bytes do not match its declared type, or a file that would exceed the workspace quota.
|
|
211
|
+
when: The upload is attempted.
|
|
212
|
+
then: Each is refused with a stable error before any byte is stored and no row is written.
|
|
213
|
+
- id: DOCUMENTS-READ-URL
|
|
214
|
+
given: A stored document.
|
|
215
|
+
when: A principal holding documents.files.read requests a read URL, waits past its lifetime, and requests one for an infected document.
|
|
216
|
+
then: The first URL streams the decrypted bytes with the stored content type, the expired URL answers not found, and the infected document is refused.
|
|
217
|
+
- id: DOCUMENTS-ATTACH-BY-REFERENCE
|
|
218
|
+
given: A module holds a record and a document id.
|
|
219
|
+
when: The module lists documents for its record reference through documents.attachments.v1 and deletes one.
|
|
220
|
+
then: Only documents of that module and reference in the caller's tenant are returned, the delete removes the object first and marks the row, and a document of another module or tenant is not visible.
|
|
221
|
+
- id: DOCUMENTS-OPEN
|
|
222
|
+
given: A module owns a record with a stored document, another module names the same document id under its own reference pair, and the record also holds an infected document.
|
|
223
|
+
when: Each of them is opened through documents.attachments.v1.
|
|
224
|
+
then: The owning module's own reference streams the decrypted bytes with the stored content type, byte count and filename and no storage key, the other module's reference pair answers nothing, and the infected document answers nothing as well.
|
|
225
|
+
- id: DOCUMENTS-PAGE
|
|
226
|
+
given: A workspace holding more documents than one page carries, some of them another module's.
|
|
227
|
+
when: A screen lists them with a page limit, follows the cursor it receives to the end, filters on the owner module, searches a term the filename or the description carries, and presents a cursor this server did not sign.
|
|
228
|
+
then: Every row is answered exactly once across the pages, the cursor is null on the last one, the owner module filter and the search term are applied by the query rather than by the screen, the term pages with the cursor, a term that is only a LIKE wildcard matches nothing instead of the workspace, a term past its bound is refused, and the forged cursor is refused with CURSOR_INVALID.
|
|
229
|
+
- id: DOCUMENTS-HEADER-ASCII
|
|
230
|
+
given: A client uploading a file whose name is not ASCII.
|
|
231
|
+
when: The name travels raw in the header and, on a second attempt, percent-encoded.
|
|
232
|
+
then: The raw value is refused before any byte is stored, and the percent-encoded one is stored and read back as the name that was sent.
|
|
233
|
+
- id: DOCUMENTS-LIMITS
|
|
234
|
+
given: A principal holding documents.files.read.
|
|
235
|
+
when: The screen asks for the upload limits.
|
|
236
|
+
then: The platform object ceiling and the accepted content types are answered, an unauthenticated request is refused, and the uploader refuses a larger file before it sends one.
|
|
237
|
+
- id: DOCUMENTS-DELETE-MANY
|
|
238
|
+
given: A principal holding documents.files.manage selects several stored documents, and the request also names an id that does not exist and one that belongs to another workspace.
|
|
239
|
+
when: The principal deletes them in one call naming the selected ids.
|
|
240
|
+
then: A list of no ids, of more than 100, or one repeating an id is refused with INVALID_INPUT before any row is touched; otherwise each named row of the workspace is deleted through the single delete path, so its object is removed first and its row is kept as the deleted trail, the missing and the foreign id are answered not-found with nothing else deleted, the response carries one outcome per id in the order sent, and a call without the permission or the CSRF proof is refused first.
|
|
241
|
+
- id: DOCUMENTS-DENY
|
|
242
|
+
given: A principal is unauthenticated or lacks the required permission.
|
|
243
|
+
when: The principal uploads, lists, requests a read URL or deletes.
|
|
244
|
+
then: The server denies the operation before repository access, and a mutation without a CSRF proof is refused first.
|
|
245
|
+
- id: DOCUMENTS-TENANT-BOUNDARY
|
|
246
|
+
given: Two workspaces with documents.
|
|
247
|
+
when: A runtime transaction bound to one workspace reads or writes rows, or a read URL of one workspace is presented for the other's object.
|
|
248
|
+
then: Only that workspace's rows are visible or mutable, the storage key prefix keeps the objects apart, and the foreign URL answers not found.
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
decodeCursor,
|
|
4
|
+
defineEndpoint,
|
|
5
|
+
encodeCursor,
|
|
6
|
+
HttpProblem,
|
|
7
|
+
jsonResponse,
|
|
8
|
+
pageResponse,
|
|
9
|
+
problemResponse,
|
|
10
|
+
readJsonObject,
|
|
11
|
+
readPageQuery,
|
|
12
|
+
requiredString,
|
|
13
|
+
} from '@flowdular/sdk/server';
|
|
14
|
+
import { STORAGE_CONTENT_TYPES } from '@flowdular/sdk/storage';
|
|
15
|
+
import type { AuthRuntime } from '@flowdular/sdk/modules/auth/server';
|
|
16
|
+
import {
|
|
17
|
+
endpointIdentityFromContext,
|
|
18
|
+
principalFromContext,
|
|
19
|
+
sessionMutationDenial,
|
|
20
|
+
} from '@flowdular/sdk/modules/auth/server';
|
|
21
|
+
import { DOCUMENTS_PERMISSIONS } from '../acl/permissions.ts';
|
|
22
|
+
import {
|
|
23
|
+
DOCUMENT_LIMITS,
|
|
24
|
+
DOCUMENT_SCANS,
|
|
25
|
+
type DocumentScan,
|
|
26
|
+
} from '../domain/types.ts';
|
|
27
|
+
import type { DocumentsRuntime } from '../server/runtime.ts';
|
|
28
|
+
import {
|
|
29
|
+
documentAttachment,
|
|
30
|
+
DocumentsServiceError,
|
|
31
|
+
} from '../services/documents-service.ts';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The upload carries the file as the whole body, so everything about it travels
|
|
35
|
+
* in headers: `content-type` is the declared type the storage port verifies
|
|
36
|
+
* against the bytes, and these three name the record it belongs to.
|
|
37
|
+
*/
|
|
38
|
+
export const UPLOAD_HEADERS = {
|
|
39
|
+
filename: 'x-document-filename',
|
|
40
|
+
ownerModule: 'x-document-owner-module',
|
|
41
|
+
recordRef: 'x-document-record-ref',
|
|
42
|
+
description: 'x-document-description',
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
/* A header travels as ASCII, so anything else is percent encoded. A plain value
|
|
46
|
+
decodes to itself; a malformed escape is a rejection rather than a guess. */
|
|
47
|
+
const HEADER_RAW_LIMIT = 4_096;
|
|
48
|
+
|
|
49
|
+
/** The default page of the documents screen, and the ceiling it may ask for. */
|
|
50
|
+
const LIST_PAGE_LIMIT = 50;
|
|
51
|
+
|
|
52
|
+
function failure(error: unknown): Response {
|
|
53
|
+
if (error instanceof DocumentsServiceError) {
|
|
54
|
+
return jsonResponse(
|
|
55
|
+
{ error: { code: error.code, message: error.message } },
|
|
56
|
+
error.status,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return problemResponse(error, 'The documents operation failed.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function invalid(message: string): HttpProblem {
|
|
63
|
+
return new HttpProblem('INVALID_INPUT', message, 400);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function headerValue(
|
|
67
|
+
request: Request,
|
|
68
|
+
name: string,
|
|
69
|
+
max: number,
|
|
70
|
+
): string | null {
|
|
71
|
+
const raw = request.headers.get(name);
|
|
72
|
+
if (raw === null || raw.trim() === '') return null;
|
|
73
|
+
if (raw.length > HEADER_RAW_LIMIT) throw invalid(`${name} is too long.`);
|
|
74
|
+
/* Only a client that percent-encodes reaches this module with a name the
|
|
75
|
+
whole way intact: a byte above 0x7F in a header value is read as Latin-1
|
|
76
|
+
by one hop and as UTF-8 by the next, so the value that arrives is not the
|
|
77
|
+
value that was sent. It is refused rather than stored mangled. */
|
|
78
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
79
|
+
if (raw.charCodeAt(index) > 0x7f) {
|
|
80
|
+
throw invalid(`${name} must be percent-encoded ASCII.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
let decoded: string;
|
|
84
|
+
try {
|
|
85
|
+
decoded = decodeURIComponent(raw);
|
|
86
|
+
} catch {
|
|
87
|
+
throw invalid(`${name} is not percent-encoded text.`);
|
|
88
|
+
}
|
|
89
|
+
const normalized = decoded.trim();
|
|
90
|
+
if (normalized.length === 0) return null;
|
|
91
|
+
if (normalized.length > max) throw invalid(`${name} is too long.`);
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requiredHeader(request: Request, name: string, max: number): string {
|
|
96
|
+
const value = headerValue(request, name, max);
|
|
97
|
+
if (value === null) throw invalid(`${name} is required.`);
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* The declared length refuses an oversized or over-quota body before it is
|
|
102
|
+
read; the port and the quota check measure what actually arrived. */
|
|
103
|
+
function declaredBytes(request: Request): number | undefined {
|
|
104
|
+
const raw = request.headers.get('content-length');
|
|
105
|
+
if (raw === null || raw.trim() === '') return undefined;
|
|
106
|
+
const value = Number(raw);
|
|
107
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
108
|
+
throw invalid('content-length must be a byte count.');
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* One outcome answers one row, so an id is named once; the count and each id
|
|
114
|
+
are bounded like the single delete's. */
|
|
115
|
+
function requiredIds(
|
|
116
|
+
value: Record<string, unknown>,
|
|
117
|
+
key: string,
|
|
118
|
+
): readonly string[] {
|
|
119
|
+
const raw = value[key];
|
|
120
|
+
if (!Array.isArray(raw)) throw invalid(`${key} must be an array.`);
|
|
121
|
+
if (raw.length < 1 || raw.length > DOCUMENT_LIMITS.deleteMany) {
|
|
122
|
+
throw invalid(
|
|
123
|
+
`${key} must name between 1 and ${DOCUMENT_LIMITS.deleteMany} documents.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const ids = raw.map((entry) =>
|
|
127
|
+
requiredString({ id: entry }, 'id', { max: DOCUMENT_LIMITS.id }),
|
|
128
|
+
);
|
|
129
|
+
if (new Set(ids).size !== ids.length) {
|
|
130
|
+
throw invalid(`${key} must not repeat an id.`);
|
|
131
|
+
}
|
|
132
|
+
return ids;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function queryValue(
|
|
136
|
+
request: Request,
|
|
137
|
+
key: string,
|
|
138
|
+
max: number,
|
|
139
|
+
): string | undefined {
|
|
140
|
+
const raw = new URL(request.url).searchParams.get(key);
|
|
141
|
+
if (raw === null || raw === '') return undefined;
|
|
142
|
+
if (raw.length > max) throw invalid(`${key} is too long.`);
|
|
143
|
+
return raw;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function queryScan(request: Request): DocumentScan | undefined {
|
|
147
|
+
const raw = queryValue(request, 'scan', 32);
|
|
148
|
+
if (raw === undefined) return undefined;
|
|
149
|
+
if (!(DOCUMENT_SCANS as readonly string[]).includes(raw)) {
|
|
150
|
+
throw invalid(`scan must be one of: ${DOCUMENT_SCANS.join(', ')}.`);
|
|
151
|
+
}
|
|
152
|
+
return raw as DocumentScan;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface DocumentsRoutesOptions {
|
|
156
|
+
/**
|
|
157
|
+
* The platform object ceiling, so the uploader can refuse a file before it
|
|
158
|
+
* sends it. The storage port stays the authority; this is what the screen is
|
|
159
|
+
* told about it.
|
|
160
|
+
*/
|
|
161
|
+
readonly maxObjectBytes: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createDocumentsRoutes(
|
|
165
|
+
auth: AuthRuntime,
|
|
166
|
+
runtime: DocumentsRuntime,
|
|
167
|
+
options: DocumentsRoutesOptions,
|
|
168
|
+
) {
|
|
169
|
+
/* Module-owned and never stored: a cursor names a position in one
|
|
170
|
+
workspace's own list, so a restart invalidating one costs a client the
|
|
171
|
+
first page. */
|
|
172
|
+
const cursorSecret = randomBytes(32);
|
|
173
|
+
const upload = defineEndpoint({
|
|
174
|
+
id: 'documents.files.upload',
|
|
175
|
+
path: '/api/documents/upload',
|
|
176
|
+
methods: ['POST'],
|
|
177
|
+
access: { kind: 'permission', permission: DOCUMENTS_PERMISSIONS.manage },
|
|
178
|
+
resolveIdentity: endpointIdentityFromContext,
|
|
179
|
+
handler: async ({ octane }) => {
|
|
180
|
+
const denial = sessionMutationDenial(octane, auth);
|
|
181
|
+
if (denial) return denial;
|
|
182
|
+
try {
|
|
183
|
+
const request = octane.request;
|
|
184
|
+
const contentType = request.headers.get('content-type');
|
|
185
|
+
if (!contentType) {
|
|
186
|
+
throw new HttpProblem(
|
|
187
|
+
'CONTENT_TYPE_REQUIRED',
|
|
188
|
+
'The declared content type is required.',
|
|
189
|
+
415,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const body = request.body;
|
|
193
|
+
if (!body) throw invalid('The request body is required.');
|
|
194
|
+
const principal = principalFromContext(octane)!;
|
|
195
|
+
const service = await runtime.service();
|
|
196
|
+
const record = await service.upload(
|
|
197
|
+
principal.tenantId,
|
|
198
|
+
principal.accountId,
|
|
199
|
+
{
|
|
200
|
+
ownerModule: requiredHeader(
|
|
201
|
+
request,
|
|
202
|
+
UPLOAD_HEADERS.ownerModule,
|
|
203
|
+
DOCUMENT_LIMITS.ownerModule,
|
|
204
|
+
),
|
|
205
|
+
recordRef: requiredHeader(
|
|
206
|
+
request,
|
|
207
|
+
UPLOAD_HEADERS.recordRef,
|
|
208
|
+
DOCUMENT_LIMITS.recordRef,
|
|
209
|
+
),
|
|
210
|
+
filename: requiredHeader(
|
|
211
|
+
request,
|
|
212
|
+
UPLOAD_HEADERS.filename,
|
|
213
|
+
DOCUMENT_LIMITS.filename,
|
|
214
|
+
),
|
|
215
|
+
description: headerValue(
|
|
216
|
+
request,
|
|
217
|
+
UPLOAD_HEADERS.description,
|
|
218
|
+
DOCUMENT_LIMITS.description,
|
|
219
|
+
),
|
|
220
|
+
contentType,
|
|
221
|
+
declaredBytes: declaredBytes(request),
|
|
222
|
+
body,
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
return jsonResponse({ document: documentAttachment(record) }, 201);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
return failure(error);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const list = defineEndpoint({
|
|
233
|
+
id: 'documents.files.list',
|
|
234
|
+
path: '/api/documents',
|
|
235
|
+
methods: ['GET'],
|
|
236
|
+
access: { kind: 'permission', permission: DOCUMENTS_PERMISSIONS.read },
|
|
237
|
+
resolveIdentity: endpointIdentityFromContext,
|
|
238
|
+
handler: async ({ octane }) => {
|
|
239
|
+
try {
|
|
240
|
+
const service = await runtime.service();
|
|
241
|
+
const page = readPageQuery(new URL(octane.request.url), {
|
|
242
|
+
maxLimit: DOCUMENT_LIMITS.page,
|
|
243
|
+
defaultLimit: LIST_PAGE_LIMIT,
|
|
244
|
+
});
|
|
245
|
+
const cursor = page.cursor
|
|
246
|
+
? decodeCursor(page.cursor, cursorSecret)
|
|
247
|
+
: null;
|
|
248
|
+
const records = await service.list(
|
|
249
|
+
principalFromContext(octane)!.tenantId,
|
|
250
|
+
{
|
|
251
|
+
ownerModule: queryValue(
|
|
252
|
+
octane.request,
|
|
253
|
+
'ownerModule',
|
|
254
|
+
DOCUMENT_LIMITS.ownerModule,
|
|
255
|
+
),
|
|
256
|
+
recordRef: queryValue(
|
|
257
|
+
octane.request,
|
|
258
|
+
'recordRef',
|
|
259
|
+
DOCUMENT_LIMITS.recordRef,
|
|
260
|
+
),
|
|
261
|
+
scan: queryScan(octane.request),
|
|
262
|
+
/* The screen's search box. It narrows the query, so a term and
|
|
263
|
+
a cursor answer one result set rather than a page the screen
|
|
264
|
+
would have to search again. */
|
|
265
|
+
search: queryValue(octane.request, 'q', DOCUMENT_LIMITS.search),
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
limit: page.limit,
|
|
269
|
+
after:
|
|
270
|
+
cursor === null
|
|
271
|
+
? null
|
|
272
|
+
: {
|
|
273
|
+
createdAt: Number(cursor.createdAt),
|
|
274
|
+
id: String(cursor.id),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
const last = records.at(-1);
|
|
279
|
+
return pageResponse({
|
|
280
|
+
items: records.map(documentAttachment),
|
|
281
|
+
limit: page.limit,
|
|
282
|
+
/* A full page may still be the last one; the client stops when the
|
|
283
|
+
cursor stops, which costs one empty page at most. */
|
|
284
|
+
nextCursor:
|
|
285
|
+
last && records.length === page.limit
|
|
286
|
+
? encodeCursor(
|
|
287
|
+
{ createdAt: last.createdAt, id: last.id },
|
|
288
|
+
cursorSecret,
|
|
289
|
+
)
|
|
290
|
+
: null,
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return failure(error);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
/* What the uploader has to know before it sends a byte. The port applies
|
|
299
|
+
both rules itself; a screen that knows them refuses a file the reader
|
|
300
|
+
picked instead of spending the upload to find out. */
|
|
301
|
+
const limits = defineEndpoint({
|
|
302
|
+
id: 'documents.files.limits',
|
|
303
|
+
path: '/api/documents/limits',
|
|
304
|
+
methods: ['GET'],
|
|
305
|
+
access: { kind: 'permission', permission: DOCUMENTS_PERMISSIONS.read },
|
|
306
|
+
resolveIdentity: endpointIdentityFromContext,
|
|
307
|
+
handler: async () =>
|
|
308
|
+
jsonResponse({
|
|
309
|
+
maxObjectBytes: options.maxObjectBytes,
|
|
310
|
+
contentTypes: STORAGE_CONTENT_TYPES,
|
|
311
|
+
}),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const readUrl = defineEndpoint({
|
|
315
|
+
id: 'documents.files.read-url',
|
|
316
|
+
path: '/api/documents/read-url',
|
|
317
|
+
methods: ['POST'],
|
|
318
|
+
access: { kind: 'permission', permission: DOCUMENTS_PERMISSIONS.read },
|
|
319
|
+
resolveIdentity: endpointIdentityFromContext,
|
|
320
|
+
handler: async ({ octane }) => {
|
|
321
|
+
const denial = sessionMutationDenial(octane, auth);
|
|
322
|
+
if (denial) return denial;
|
|
323
|
+
try {
|
|
324
|
+
const value = await readJsonObject(octane.request);
|
|
325
|
+
const service = await runtime.service();
|
|
326
|
+
return jsonResponse(
|
|
327
|
+
await service.readUrl(
|
|
328
|
+
principalFromContext(octane)!.tenantId,
|
|
329
|
+
requiredString(value, 'id', { max: DOCUMENT_LIMITS.id }),
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return failure(error);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const remove = defineEndpoint({
|
|
339
|
+
id: 'documents.files.delete',
|
|
340
|
+
path: '/api/documents/delete',
|
|
341
|
+
methods: ['POST'],
|
|
342
|
+
access: { kind: 'permission', permission: DOCUMENTS_PERMISSIONS.manage },
|
|
343
|
+
resolveIdentity: endpointIdentityFromContext,
|
|
344
|
+
handler: async ({ octane }) => {
|
|
345
|
+
const denial = sessionMutationDenial(octane, auth);
|
|
346
|
+
if (denial) return denial;
|
|
347
|
+
try {
|
|
348
|
+
const value = await readJsonObject(octane.request);
|
|
349
|
+
const service = await runtime.service();
|
|
350
|
+
const record = await service.remove(
|
|
351
|
+
principalFromContext(octane)!.tenantId,
|
|
352
|
+
requiredString(value, 'id', { max: DOCUMENT_LIMITS.id }),
|
|
353
|
+
);
|
|
354
|
+
return jsonResponse({ document: documentAttachment(record) });
|
|
355
|
+
} catch (error) {
|
|
356
|
+
return failure(error);
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const removeMany = defineEndpoint({
|
|
362
|
+
id: 'documents.files.delete-many',
|
|
363
|
+
path: '/api/documents/delete-many',
|
|
364
|
+
methods: ['POST'],
|
|
365
|
+
access: { kind: 'permission', permission: DOCUMENTS_PERMISSIONS.manage },
|
|
366
|
+
resolveIdentity: endpointIdentityFromContext,
|
|
367
|
+
handler: async ({ octane }) => {
|
|
368
|
+
const denial = sessionMutationDenial(octane, auth);
|
|
369
|
+
if (denial) return denial;
|
|
370
|
+
try {
|
|
371
|
+
const value = await readJsonObject(octane.request);
|
|
372
|
+
const service = await runtime.service();
|
|
373
|
+
const outcomes = await service.removeMany(
|
|
374
|
+
principalFromContext(octane)!.tenantId,
|
|
375
|
+
requiredIds(value, 'ids'),
|
|
376
|
+
);
|
|
377
|
+
return jsonResponse({ outcomes });
|
|
378
|
+
} catch (error) {
|
|
379
|
+
return failure(error);
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return [
|
|
385
|
+
upload.serverRoute,
|
|
386
|
+
list.serverRoute,
|
|
387
|
+
limits.serverRoute,
|
|
388
|
+
readUrl.serverRoute,
|
|
389
|
+
remove.serverRoute,
|
|
390
|
+
removeMany.serverRoute,
|
|
391
|
+
] as const;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export const endpoints = [
|
|
395
|
+
'documents.files.upload',
|
|
396
|
+
'documents.files.list',
|
|
397
|
+
'documents.files.limits',
|
|
398
|
+
'documents.files.read-url',
|
|
399
|
+
'documents.files.delete',
|
|
400
|
+
'documents.files.delete-many',
|
|
401
|
+
] as const;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocolVersion": 1,
|
|
3
|
+
"moduleId": "documents.core",
|
|
4
|
+
"commands": [
|
|
5
|
+
{
|
|
6
|
+
"path": ["documents", "secrets-rotate"],
|
|
7
|
+
"capability": {
|
|
8
|
+
"id": "documents.storage.rotate",
|
|
9
|
+
"version": 1,
|
|
10
|
+
"summary": "Re-seal stored document objects with the current storage encryption key.",
|
|
11
|
+
"risk": "process",
|
|
12
|
+
"requiresApprovedSpec": false,
|
|
13
|
+
"supportsDryRun": true
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|