rcf-lite 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/blueprints/application-account-settings/CHANGELOG.md +12 -0
- package/blueprints/application-account-settings/README.md +114 -0
- package/blueprints/application-account-settings/blueprint.json +234 -0
- package/blueprints/application-account-settings/contributions/adrs/adr-2601-application-account-settings-capability-vocabulary.json +20 -0
- package/blueprints/application-account-settings/contributions/adrs/adr-2602-application-account-settings-security-surface-shape.json +20 -0
- package/blueprints/application-account-settings/contributions/adrs/adr-2603-application-account-settings-theme-persistence.json +20 -0
- package/blueprints/application-account-settings/contributions/adrs/adr-2604-application-account-settings-reauth-window.json +20 -0
- package/blueprints/application-account-settings/contributions/requirements/application-account-settings-req-001.json +18 -0
- package/blueprints/application-account-settings/contributions/requirements/application-account-settings-req-002.json +18 -0
- package/blueprints/application-account-settings/contributions/requirements/application-account-settings-req-003.json +18 -0
- package/blueprints/application-account-settings/contributions/requirements/application-account-settings-req-004.json +18 -0
- package/blueprints/application-account-settings/contributions/requirements/application-account-settings-req-005.json +18 -0
- package/blueprints/application-account-settings/contributions/requirements/application-account-settings-req-006.json +18 -0
- package/blueprints/application-account-settings/contributions/tacs/tac-2601-application-account-settings-shell.json +41 -0
- package/blueprints/application-account-settings/contributions/tacs/tac-2602-application-account-settings-capability-discovery.json +28 -0
- package/blueprints/application-account-settings/contributions/tacs/tac-2603-application-account-settings-hosted-ui-bridge.json +35 -0
- package/blueprints/application-account-settings/contributions/tacs/tac-2604-application-account-settings-session-store.json +40 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25101.json +34 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25102.json +27 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25103.json +27 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25104.json +27 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25105.json +33 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25106.json +27 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25107.json +27 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25108.json +27 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25109.json +33 -0
- package/blueprints/application-account-settings/contributions/user-stories/application-account-settings-us-25110.json +27 -0
- package/blueprints/application-account-settings/docs/topics.md +48 -0
- package/blueprints/application-account-settings/guide/application-account-settings.md +30 -0
- package/blueprints/application-account-settings/probe-packs/application-account-settings.pack.mjs +246 -0
- package/blueprints/application-admin-console/docs/topics.md +7 -0
- package/blueprints/application-api-rest/docs/topics.md +8 -1
- package/blueprints/application-charts/docs/topics.md +7 -0
- package/blueprints/application-dashboard/docs/topics.md +7 -0
- package/blueprints/application-datatable/docs/topics.md +7 -0
- package/blueprints/application-empty-error-states/CHANGELOG.md +9 -0
- package/blueprints/application-empty-error-states/README.md +88 -0
- package/blueprints/application-empty-error-states/blueprint.json +119 -0
- package/blueprints/application-empty-error-states/contributions/adrs/adr-2301-application-empty-error-states-status-contract.json +25 -0
- package/blueprints/application-empty-error-states/contributions/adrs/adr-2302-application-empty-error-states-stack-trace-visibility.json +25 -0
- package/blueprints/application-empty-error-states/contributions/adrs/adr-2303-application-empty-error-states-offline-strategy.json +25 -0
- package/blueprints/application-empty-error-states/contributions/requirements/application-empty-error-states-req-001.json +18 -0
- package/blueprints/application-empty-error-states/contributions/requirements/application-empty-error-states-req-002.json +18 -0
- package/blueprints/application-empty-error-states/contributions/requirements/application-empty-error-states-req-003.json +18 -0
- package/blueprints/application-empty-error-states/contributions/requirements/application-empty-error-states-req-004.json +18 -0
- package/blueprints/application-empty-error-states/contributions/requirements/application-empty-error-states-req-005.json +18 -0
- package/blueprints/application-empty-error-states/contributions/tacs/tac-2301-application-empty-error-states-state-machine.json +46 -0
- package/blueprints/application-empty-error-states/contributions/tacs/tac-2302-application-empty-error-states-recovery-router.json +36 -0
- package/blueprints/application-empty-error-states/contributions/tacs/tac-2303-application-empty-error-states-offline-buffer.json +40 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22101.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22102.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22103.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22104.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22105.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22106.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22107.json +29 -0
- package/blueprints/application-empty-error-states/contributions/user-stories/application-empty-error-states-us-22108.json +29 -0
- package/blueprints/application-empty-error-states/docs/topics.md +53 -0
- package/blueprints/application-empty-error-states/guide/application-empty-error-states.md +50 -0
- package/blueprints/application-empty-error-states/probe-packs/application-empty-error-states.pack.mjs +284 -0
- package/blueprints/application-error-handling/docs/topics.md +7 -0
- package/blueprints/application-file-upload/CHANGELOG.md +9 -0
- package/blueprints/application-file-upload/README.md +85 -0
- package/blueprints/application-file-upload/blueprint.json +114 -0
- package/blueprints/application-file-upload/contributions/adrs/adr-2401-application-file-upload-transport.json +25 -0
- package/blueprints/application-file-upload/contributions/adrs/adr-2402-application-file-upload-accepted-set.json +25 -0
- package/blueprints/application-file-upload/contributions/adrs/adr-2403-application-file-upload-virus-scan.json +25 -0
- package/blueprints/application-file-upload/contributions/requirements/application-file-upload-req-001.json +18 -0
- package/blueprints/application-file-upload/contributions/requirements/application-file-upload-req-002.json +18 -0
- package/blueprints/application-file-upload/contributions/requirements/application-file-upload-req-003.json +18 -0
- package/blueprints/application-file-upload/contributions/requirements/application-file-upload-req-004.json +18 -0
- package/blueprints/application-file-upload/contributions/requirements/application-file-upload-req-005.json +18 -0
- package/blueprints/application-file-upload/contributions/tacs/tac-2401-application-file-upload-input.json +46 -0
- package/blueprints/application-file-upload/contributions/tacs/tac-2402-application-file-upload-transport.json +47 -0
- package/blueprints/application-file-upload/contributions/tacs/tac-2403-application-file-upload-progress-announcer.json +46 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23101.json +28 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23102.json +28 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23103.json +29 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23104.json +28 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23105.json +29 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23106.json +28 -0
- package/blueprints/application-file-upload/contributions/user-stories/application-file-upload-us-23107.json +28 -0
- package/blueprints/application-file-upload/docs/topics.md +58 -0
- package/blueprints/application-file-upload/guide/application-file-upload.md +48 -0
- package/blueprints/application-file-upload/probe-packs/application-file-upload.pack.mjs +238 -0
- package/blueprints/application-forms-wizard/CHANGELOG.md +9 -0
- package/blueprints/application-forms-wizard/README.md +102 -0
- package/blueprints/application-forms-wizard/blueprint.json +120 -0
- package/blueprints/application-forms-wizard/contributions/adrs/adr-2501-application-forms-wizard-navigation.json +25 -0
- package/blueprints/application-forms-wizard/contributions/adrs/adr-2502-application-forms-wizard-save-and-return.json +25 -0
- package/blueprints/application-forms-wizard/contributions/adrs/adr-2503-application-forms-wizard-step-indicator.json +25 -0
- package/blueprints/application-forms-wizard/contributions/requirements/application-forms-wizard-req-001.json +18 -0
- package/blueprints/application-forms-wizard/contributions/requirements/application-forms-wizard-req-002.json +18 -0
- package/blueprints/application-forms-wizard/contributions/requirements/application-forms-wizard-req-003.json +18 -0
- package/blueprints/application-forms-wizard/contributions/requirements/application-forms-wizard-req-004.json +18 -0
- package/blueprints/application-forms-wizard/contributions/requirements/application-forms-wizard-req-005.json +18 -0
- package/blueprints/application-forms-wizard/contributions/tacs/tac-2501-application-forms-wizard-task-list.json +46 -0
- package/blueprints/application-forms-wizard/contributions/tacs/tac-2502-application-forms-wizard-error-summary.json +40 -0
- package/blueprints/application-forms-wizard/contributions/tacs/tac-2503-application-forms-wizard-draft-store.json +40 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24101.json +28 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24102.json +29 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24103.json +28 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24104.json +29 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24105.json +28 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24106.json +28 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24107.json +28 -0
- package/blueprints/application-forms-wizard/contributions/user-stories/application-forms-wizard-us-24108.json +29 -0
- package/blueprints/application-forms-wizard/docs/topics.md +49 -0
- package/blueprints/application-forms-wizard/guide/application-forms-wizard.md +48 -0
- package/blueprints/application-forms-wizard/probe-packs/application-forms-wizard.pack.mjs +233 -0
- package/blueprints/application-notifications-in-app/docs/topics.md +7 -0
- package/blueprints/application-onboarding-tour/CHANGELOG.md +12 -0
- package/blueprints/application-onboarding-tour/README.md +77 -0
- package/blueprints/application-onboarding-tour/blueprint.json +164 -0
- package/blueprints/application-onboarding-tour/contributions/adrs/adr-2701-application-onboarding-tour-dismissal-policy.json +20 -0
- package/blueprints/application-onboarding-tour/contributions/adrs/adr-2702-application-onboarding-tour-completion-store-shape.json +20 -0
- package/blueprints/application-onboarding-tour/contributions/adrs/adr-2703-application-onboarding-tour-checklist-anchor.json +20 -0
- package/blueprints/application-onboarding-tour/contributions/requirements/application-onboarding-tour-req-001.json +18 -0
- package/blueprints/application-onboarding-tour/contributions/requirements/application-onboarding-tour-req-002.json +18 -0
- package/blueprints/application-onboarding-tour/contributions/requirements/application-onboarding-tour-req-003.json +18 -0
- package/blueprints/application-onboarding-tour/contributions/requirements/application-onboarding-tour-req-004.json +18 -0
- package/blueprints/application-onboarding-tour/contributions/tacs/tac-2701-application-onboarding-tour-step-runner.json +41 -0
- package/blueprints/application-onboarding-tour/contributions/tacs/tac-2702-application-onboarding-tour-checklist-slot.json +35 -0
- package/blueprints/application-onboarding-tour/contributions/tacs/tac-2703-application-onboarding-tour-completion-store.json +34 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26101.json +27 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26102.json +27 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26103.json +27 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26104.json +27 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26105.json +27 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26106.json +27 -0
- package/blueprints/application-onboarding-tour/contributions/user-stories/application-onboarding-tour-us-26107.json +27 -0
- package/blueprints/application-onboarding-tour/docs/topics.md +48 -0
- package/blueprints/application-onboarding-tour/guide/application-onboarding-tour.md +59 -0
- package/blueprints/application-onboarding-tour/probe-packs/application-onboarding-tour.pack.mjs +231 -0
- package/blueprints/application-spa/docs/topics.md +8 -1
- package/blueprints/delivery-ci-workflows/docs/topics.md +8 -1
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +8 -1
- package/blueprints/email-smtp-resend/docs/topics.md +8 -1
- package/blueprints/object-storage-s3/CHANGELOG.md +13 -0
- package/blueprints/object-storage-s3/README.md +108 -0
- package/blueprints/object-storage-s3/blueprint.json +72 -0
- package/blueprints/object-storage-s3/contributions/adrs/adr-2901-object-storage-s3-adapter.json +30 -0
- package/blueprints/object-storage-s3/contributions/adrs/adr-2902-object-storage-s3-presigned-ttl-floor.json +25 -0
- package/blueprints/object-storage-s3/contributions/adrs/adr-2903-object-storage-s3-multipart-threshold.json +25 -0
- package/blueprints/object-storage-s3/contributions/adrs/adr-2904-object-storage-s3-contract.json +25 -0
- package/blueprints/object-storage-s3/contributions/probes/event-secrecy.mjs +103 -0
- package/blueprints/object-storage-s3/contributions/probes/facade-round-trip.mjs +43 -0
- package/blueprints/object-storage-s3/contributions/probes/multipart-upload.mjs +84 -0
- package/blueprints/object-storage-s3/contributions/probes/presigned-url.mjs +101 -0
- package/blueprints/object-storage-s3/contributions/probes/probe-utils.mjs +85 -0
- package/blueprints/object-storage-s3/contributions/probes/put-get-round-trip.mjs +84 -0
- package/blueprints/object-storage-s3/contributions/probes/r2-real-account-smoke.mjs +64 -0
- package/blueprints/object-storage-s3/contributions/probes/run-event-secrecy.mjs +4 -0
- package/blueprints/object-storage-s3/contributions/probes/run-facade-round-trip.mjs +10 -0
- package/blueprints/object-storage-s3/contributions/probes/run-multipart-upload.mjs +4 -0
- package/blueprints/object-storage-s3/contributions/probes/run-presigned-url.mjs +4 -0
- package/blueprints/object-storage-s3/contributions/probes/run-put-get-round-trip.mjs +7 -0
- package/blueprints/object-storage-s3/contributions/probes/run-r2-real-account-smoke.mjs +4 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-001.json +18 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-002.json +18 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-003.json +18 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-004.json +18 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-005.json +18 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-006.json +18 -0
- package/blueprints/object-storage-s3/contributions/tacs/tac-2901-object-storage-s3-facade.json +57 -0
- package/blueprints/object-storage-s3/contributions/tacs/tac-2902-object-storage-s3-multipart-uploader.json +36 -0
- package/blueprints/object-storage-s3/contributions/tacs/tac-2903-object-storage-s3-event-sink.json +36 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28101.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28102.json +43 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28103.json +25 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28104.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28105.json +25 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28106.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28107.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28108.json +34 -0
- package/blueprints/object-storage-s3/docs/topics.md +62 -0
- package/blueprints/object-storage-s3/guide/object-storage-s3.md +207 -0
- package/blueprints/observability-essentials/docs/topics.md +8 -1
- package/blueprints/observability-logging/CHANGELOG.md +4 -0
- package/blueprints/observability-logging/README.md +3 -0
- package/blueprints/observability-logging/blueprint.json +3 -2
- package/blueprints/observability-logging/docs/topics.md +7 -0
- package/blueprints/observability-probe-endpoints/docs/topics.md +8 -1
- package/blueprints/persistence-data-d1/docs/topics.md +8 -1
- package/blueprints/persistence-data-postgres/CHANGELOG.md +12 -0
- package/blueprints/persistence-data-postgres/README.md +101 -0
- package/blueprints/persistence-data-postgres/blueprint.json +80 -0
- package/blueprints/persistence-data-postgres/contributions/adrs/adr-2801-persistence-data-postgres-driver.json +30 -0
- package/blueprints/persistence-data-postgres/contributions/adrs/adr-2802-persistence-data-postgres-migration-shape.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/adrs/adr-2803-persistence-data-postgres-runner-mode.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/adrs/adr-2804-persistence-data-postgres-recovery.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/adrs/adr-2805-persistence-data-postgres-connection-pool-posture.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/probes/facade-round-trip.mjs +56 -0
- package/blueprints/persistence-data-postgres/contributions/probes/migration-apply.mjs +75 -0
- package/blueprints/persistence-data-postgres/contributions/probes/pool-posture-smoke.mjs +56 -0
- package/blueprints/persistence-data-postgres/contributions/probes/prepared-statement-scan.mjs +186 -0
- package/blueprints/persistence-data-postgres/contributions/probes/probe-utils.mjs +76 -0
- package/blueprints/persistence-data-postgres/contributions/probes/recovery-restore-round-trip.mjs +173 -0
- package/blueprints/persistence-data-postgres/contributions/probes/run-facade-round-trip.mjs +10 -0
- package/blueprints/persistence-data-postgres/contributions/probes/run-migration-apply.mjs +10 -0
- package/blueprints/persistence-data-postgres/contributions/probes/run-pool-posture-smoke.mjs +9 -0
- package/blueprints/persistence-data-postgres/contributions/probes/run-prepared-statement-scan.mjs +10 -0
- package/blueprints/persistence-data-postgres/contributions/probes/run-recovery-restore-round-trip.mjs +9 -0
- package/blueprints/persistence-data-postgres/contributions/probes/run-transaction-atomicity.mjs +10 -0
- package/blueprints/persistence-data-postgres/contributions/probes/transaction-atomicity.mjs +79 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-001.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-002.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-003.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-004.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-005.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-006.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/requirements/persistence-data-postgres-req-007.json +18 -0
- package/blueprints/persistence-data-postgres/contributions/tacs/tac-2801-persistence-data-postgres-facade.json +58 -0
- package/blueprints/persistence-data-postgres/contributions/tacs/tac-2802-persistence-data-postgres-migration-runner.json +40 -0
- package/blueprints/persistence-data-postgres/contributions/tacs/tac-2803-persistence-data-postgres-transaction-helper.json +35 -0
- package/blueprints/persistence-data-postgres/contributions/tacs/tac-2804-persistence-data-postgres-recovery-runner.json +40 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27101.json +34 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27102.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27103.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27104.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27105.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27106.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27107.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27108.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27109.json +25 -0
- package/blueprints/persistence-data-postgres/contributions/user-stories/persistence-data-postgres-us-27110.json +25 -0
- package/blueprints/persistence-data-postgres/docs/topics.md +64 -0
- package/blueprints/persistence-data-postgres/guide/persistence-data-postgres.md +172 -0
- package/blueprints/persistence-data-sqlite/docs/topics.md +8 -1
- package/blueprints/security-auth-clerk/CHANGELOG.md +4 -0
- package/blueprints/security-auth-clerk/README.md +3 -0
- package/blueprints/security-auth-clerk/blueprint.json +147 -30
- package/blueprints/security-auth-clerk/docs/topics.md +8 -1
- package/blueprints/security-auth-keycloak/CHANGELOG.md +4 -0
- package/blueprints/security-auth-keycloak/README.md +3 -0
- package/blueprints/security-auth-keycloak/blueprint.json +182 -37
- package/blueprints/security-auth-keycloak/docs/topics.md +8 -1
- package/blueprints/security-auth-magic-link/CHANGELOG.md +4 -0
- package/blueprints/security-auth-magic-link/README.md +3 -0
- package/blueprints/security-auth-magic-link/blueprint.json +159 -33
- package/blueprints/security-auth-magic-link/docs/topics.md +8 -1
- package/blueprints/security-auth-oauth2/CHANGELOG.md +4 -0
- package/blueprints/security-auth-oauth2/README.md +3 -0
- package/blueprints/security-auth-oauth2/blueprint.json +158 -32
- package/blueprints/security-auth-oauth2/docs/topics.md +8 -1
- package/blueprints/security-secrets-management/CHANGELOG.md +11 -0
- package/blueprints/security-secrets-management/blueprint.json +2 -1
- package/blueprints/security-secrets-management/docs/topics.md +8 -1
- package/fixtures/canary-manifest.json +9 -9
- package/package.json +1 -1
- package/rcf/code-nodes/cn-134.json +13 -0
- package/rcf/code-nodes/cn-135.json +12 -0
- package/rcf/code-nodes/cn-136.json +13 -0
- package/rcf/code-nodes/cn-137.json +13 -0
- package/rcf/code-nodes/cn-138.json +12 -0
- package/rcf/code-nodes/cn-139.json +12 -0
- package/rcf/code-nodes/cn-140.json +13 -0
- package/rcf/code-nodes/cn-141.json +13 -0
- package/rcf/code-nodes/cn-142.json +12 -0
- package/rcf/code-nodes/cn-143.json +13 -0
- package/rcf/code-nodes/cn-144.json +13 -0
- package/rcf/code-nodes/cn-145.json +12 -0
- package/rcf/code-nodes/cn-146.json +12 -0
- package/rcf/code-nodes/cn-147.json +13 -0
- package/rcf/code-nodes/cn-148.json +13 -0
- package/rcf/code-nodes/cn-149.json +12 -0
- package/rcf/code-nodes/cn-150.json +13 -0
- package/rcf/code-nodes/cn-151.json +13 -0
- package/rcf/code-nodes/cn-152.json +12 -0
- package/rcf/code-nodes/cn-153.json +12 -0
- package/rcf/code-nodes/cn-154.json +13 -0
- package/rcf/code-nodes/cn-155.json +14 -0
- package/rcf/code-nodes/cn-156.json +13 -0
- package/rcf/code-nodes/cn-157.json +13 -0
- package/rcf/code-nodes/cn-158.json +12 -0
- package/rcf/code-nodes/cn-159.json +13 -0
- package/rcf/code-nodes/cn-160.json +12 -0
- package/rcf/code-nodes/cn-161.json +12 -0
- package/rcf/code-nodes/cn-162.json +13 -0
- package/rcf/code-nodes/cn-163.json +12 -0
- package/rcf/code-nodes/cn-164.json +13 -0
- package/rcf/code-nodes/cn-165.json +12 -0
- package/rcf/code-nodes/cn-166.json +12 -0
- package/rcf/code-nodes/cn-167.json +12 -0
- package/rcf/code-nodes/cn-168.json +12 -0
- package/rcf/code-nodes/cn-169.json +12 -0
- package/rcf/code-nodes/cn-170.json +12 -0
- package/rcf/code-nodes/cn-171.json +12 -0
- package/rcf/code-nodes/cn-172.json +12 -0
- package/rcf/code-nodes/cn-173.json +12 -0
- package/rcf/code-nodes/cn-174.json +12 -0
- package/rcf/code-nodes/cn-175.json +13 -0
- package/rcf/code-nodes/cn-176.json +12 -0
- package/rcf/code-nodes/cn-177.json +12 -0
- package/rcf/code-nodes/cn-178.json +12 -0
- package/rcf/code-nodes/cn-179.json +12 -0
- package/rcf/code-nodes/cn-180.json +13 -0
- package/rcf/code-nodes/cn-181.json +12 -0
- package/rcf/code-nodes/cn-182.json +13 -0
- package/rcf/code-nodes/cn-183.json +12 -0
- package/rcf/code-nodes/cn-184.json +13 -0
- package/rcf/code-nodes/cn-185.json +12 -0
- package/rcf/code-nodes/cn-186.json +12 -0
- package/rcf/code-nodes/cn-200.json +14 -0
- package/rcf/code-nodes/cn-201.json +13 -0
- package/rcf/code-nodes/cn-202.json +14 -0
- package/rcf/code-nodes/cn-203.json +14 -0
- package/rcf/code-nodes/cn-204.json +13 -0
- package/rcf/code-nodes/cn-205.json +14 -0
- package/rcf/fbs/fbs-043.json +23 -0
- package/rcf/fbs/fbs-044.json +23 -0
- package/rcf/fbs/fbs-045.json +23 -0
- package/rcf/fbs/fbs-046.json +24 -0
- package/rcf/fbs/fbs-047.json +24 -0
- package/rcf/fbs/fbs-060.json +22 -0
- package/rcf/fbs/fbs-061.json +20 -0
- package/rcf/requirements/req-024.json +69 -0
- package/rcf/requirements/req-025.json +59 -0
- package/rcf/requirements/req-026.json +154 -0
- package/rcf/requirements/req-027.json +33 -0
- package/rcf/requirements/req-028.json +13 -0
- package/rcf/requirements/req-040.json +13 -0
- package/rcf/requirements/req-041.json +125 -0
- package/rcf/test-suites/ts-053.json +70 -0
- package/rcf/test-suites/ts-054.json +70 -0
- package/rcf/test-suites/ts-055.json +77 -0
- package/rcf/test-suites/ts-056.json +78 -0
- package/rcf/test-suites/ts-057.json +78 -0
- package/rcf/test-suites/ts-070.json +62 -0
- package/rcf/test-suites/ts-071.json +62 -0
- package/rcf/user-stories/us-2401.json +62 -0
- package/rcf/user-stories/us-2501.json +58 -0
- package/rcf/user-stories/us-2601.json +51 -0
- package/rcf/user-stories/us-2701.json +55 -0
- package/rcf/user-stories/us-2801.json +55 -0
- package/rcf/user-stories/us-4001.json +69 -0
- package/rcf/user-stories/us-4101.json +69 -0
- package/releases/releases.yaml +11 -1
- package/src/blueprint/apply.js +10 -1
- package/src/blueprint/capabilities.js +41 -2
- package/src/cli/blueprint.js +8 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// application-empty-error-states probe pack (v1.0.0).
|
|
2
|
+
//
|
|
3
|
+
// Eight surface-observable checks, one per named state, anchored to
|
|
4
|
+
// the blueprint's contributed AC ids:
|
|
5
|
+
// AC-22101-1 not-found (heading, parent-surface link, search entry, tab title)
|
|
6
|
+
// AC-22102-1 forbidden (request-access control, no resource-id leak)
|
|
7
|
+
// AC-22103-1 server-error (retry, no stack trace, no path shape, no env-key shape)
|
|
8
|
+
// AC-22104-1 permission-denied (class-level cause, no resource id)
|
|
9
|
+
// AC-22105-1 offline (banner, buffered synthetic write, polite reconnection announcement)
|
|
10
|
+
// AC-22106-1 empty-list (distinct visual wrapper, creation-affordance recovery)
|
|
11
|
+
// AC-22107-1 no-search-results (distinct visual wrapper, echoed query, clear-filters recovery)
|
|
12
|
+
// AC-22108-1 error-boundary (role=alert, retry control, no stack detail)
|
|
13
|
+
//
|
|
14
|
+
// Every check drives the real Playwright browser the T-0 runner
|
|
15
|
+
// injects. URLs are composed by the withUrl(runtimeUrl, path) helper
|
|
16
|
+
// per the round 3 T-3 fix train; no bare string concatenation.
|
|
17
|
+
//
|
|
18
|
+
// Sample-app fixture:
|
|
19
|
+
// packages/rcf-lite/test/fixtures/probe-pack-application-empty-error-states/
|
|
20
|
+
// Fixture ships one route per state under /probe/<state> plus break
|
|
21
|
+
// switches (?break=stack-trace, ?break=leak-id, ?break=no-recovery,
|
|
22
|
+
// ?break=no-live-region) so the negative runs can be driven from a
|
|
23
|
+
// single boot.
|
|
24
|
+
|
|
25
|
+
function withUrl(runtimeUrl, path) {
|
|
26
|
+
return new URL(path, runtimeUrl).toString();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Sensitive-pattern list used by AC-22103-1 (server-error surface)
|
|
30
|
+
// and by AC-22108-1 (error-boundary surface). Words on this list must
|
|
31
|
+
// not appear on the rendered document text in production.
|
|
32
|
+
//
|
|
33
|
+
// Encoded as an array of {name, re} entries so a failure message can
|
|
34
|
+
// cite which pattern matched. Every regexp is anchored on token
|
|
35
|
+
// boundaries to reduce false positives on legitimate copy.
|
|
36
|
+
const SENSITIVE_PATTERNS = [
|
|
37
|
+
{ name: 'backtrace-frame', re: /\bat\s+[A-Za-z_$][\w$.]*\s*\([^)]*:\d+:\d+\)/ },
|
|
38
|
+
{ name: 'source-path-absolute', re: /(?:^|[\s"'>])\/(?:usr|opt|home|var|etc|Users|private)\/[\w\-./]+/ },
|
|
39
|
+
{ name: 'source-path-relative', re: /(?:^|[\s"'>])\.{1,2}\/[\w\-./]+\.(?:js|mjs|cjs|ts|tsx|jsx)/ },
|
|
40
|
+
{ name: 'env-key-assignment', re: /\b[A-Z][A-Z0-9_]{2,}=(?!=)[^\s"']+/ },
|
|
41
|
+
{ name: 'process-env-reference', re: /\bprocess\.env\.[A-Z_][A-Z0-9_]*/ },
|
|
42
|
+
{ name: 'framework-internal-frame', re: /\b(?:node_modules|webpack-internal|nextjs-internal)\b/ },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Resource-id pattern list for AC-22104-1 (permission-denied cause
|
|
46
|
+
// must be class-level, not per-resource).
|
|
47
|
+
const RESOURCE_ID_PATTERNS = [
|
|
48
|
+
{ name: 'four-plus-digit-run', re: /\b\d{4,}\b/ },
|
|
49
|
+
{ name: 'uuid-shape', re: /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/ },
|
|
50
|
+
{ name: 'resource-slug-hint', re: /\b(?:resource|id|slug|handle)=[A-Za-z0-9._-]+/i },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function firstSensitiveMatch(text, patterns) {
|
|
54
|
+
if (typeof text !== 'string') return null;
|
|
55
|
+
for (const { name, re } of patterns) {
|
|
56
|
+
const m = text.match(re);
|
|
57
|
+
if (m) return { name, match: m[0] };
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default {
|
|
63
|
+
packName: 'application-empty-error-states',
|
|
64
|
+
version: '1.0.0',
|
|
65
|
+
blueprintSlug: 'application-empty-error-states',
|
|
66
|
+
// Applies to any FBS that realises the state-machine TAC or whose
|
|
67
|
+
// navModel routes name the operator-configured error-shell path.
|
|
68
|
+
// References BOTH `tacIds` AND `route` per the loader source-scan
|
|
69
|
+
// rule (T-0 AC-1701-3, cross-check with authoring standard 8c).
|
|
70
|
+
appliesTo: ({ fbs }) => {
|
|
71
|
+
const routes = fbs?.designStage?.navModel?.routes ?? [];
|
|
72
|
+
const tacIds = fbs?.contextRequirements?.tacIds ?? [];
|
|
73
|
+
if (tacIds.includes('TAC-2301-application-empty-error-states-state-machine')) return true;
|
|
74
|
+
return routes.some((r) => typeof r?.path === 'string' && /\/probe\/(?:not-found|forbidden|server-error|permission-denied|offline|empty-list|search|error-boundary)(\/|$|\?)/.test(r.path));
|
|
75
|
+
},
|
|
76
|
+
boot: { bootCommand: null, waitForUrl: null, waitForSelector: null },
|
|
77
|
+
checks: [
|
|
78
|
+
{
|
|
79
|
+
id: 'AC-22101-1',
|
|
80
|
+
severity: 'block',
|
|
81
|
+
description: 'not-found state renders inside a region with a naming heading, a parent-surface recovery link, a search entry, and a browser tab title matching /^Not found - .+/',
|
|
82
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
83
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
84
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/not-found'));
|
|
85
|
+
const dom = await browser.evaluate(() => {
|
|
86
|
+
const region = document.querySelector('[data-surface="not-found"]');
|
|
87
|
+
if (!region) return { present: false };
|
|
88
|
+
const heading = region.querySelector('h1, h2, [role="heading"]');
|
|
89
|
+
const parent = region.querySelector('[data-recovery="parent-surface"]');
|
|
90
|
+
const search = region.querySelector('[data-recovery="search"]');
|
|
91
|
+
return {
|
|
92
|
+
present: true,
|
|
93
|
+
headingText: heading ? heading.textContent.trim() : null,
|
|
94
|
+
hasParent: !!parent,
|
|
95
|
+
hasSearch: !!search,
|
|
96
|
+
title: document.title,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (!dom.present) return { verdict: 'fail', detail: 'not-found region [data-surface="not-found"] not found' };
|
|
100
|
+
if (!dom.headingText) return { verdict: 'fail', detail: 'not-found region missing heading' };
|
|
101
|
+
if (!dom.hasParent) return { verdict: 'fail', detail: 'not-found region missing [data-recovery="parent-surface"]' };
|
|
102
|
+
if (!dom.hasSearch) return { verdict: 'fail', detail: 'not-found region missing [data-recovery="search"]' };
|
|
103
|
+
if (!/^Not found - .+/.test(dom.title)) return { verdict: 'fail', detail: `not-found tab title did not match /^Not found - .+/: ${JSON.stringify(dom.title)}` };
|
|
104
|
+
return { verdict: 'pass', detail: `heading=${JSON.stringify(dom.headingText)} title=${JSON.stringify(dom.title)}` };
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'AC-22102-1',
|
|
109
|
+
severity: 'block',
|
|
110
|
+
description: 'forbidden state renders inside a region with a request-access control and the rendered surface refuses to include a resource id, resource name or path-shape token',
|
|
111
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
112
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
113
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/forbidden'));
|
|
114
|
+
const dom = await browser.evaluate(() => {
|
|
115
|
+
const region = document.querySelector('[data-surface="forbidden"]');
|
|
116
|
+
if (!region) return { present: false };
|
|
117
|
+
const control = region.querySelector('[data-action="request-access"]');
|
|
118
|
+
return { present: true, hasControl: !!control, text: document.body.innerText };
|
|
119
|
+
});
|
|
120
|
+
if (!dom.present) return { verdict: 'fail', detail: 'forbidden region [data-surface="forbidden"] not found' };
|
|
121
|
+
if (!dom.hasControl) return { verdict: 'fail', detail: 'forbidden region missing [data-action="request-access"] control' };
|
|
122
|
+
// Least-privilege posture: no resource-id shape, no source path leaking through.
|
|
123
|
+
const leak = firstSensitiveMatch(dom.text, RESOURCE_ID_PATTERNS);
|
|
124
|
+
if (leak) return { verdict: 'fail', detail: `forbidden surface leaked ${leak.name}: ${leak.match}` };
|
|
125
|
+
const path = firstSensitiveMatch(dom.text, SENSITIVE_PATTERNS.filter((p) => p.name.startsWith('source-path')));
|
|
126
|
+
if (path) return { verdict: 'fail', detail: `forbidden surface leaked ${path.name}: ${path.match}` };
|
|
127
|
+
return { verdict: 'pass', detail: 'forbidden region clean; request-access control present' };
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'AC-22103-1',
|
|
132
|
+
severity: 'block',
|
|
133
|
+
description: 'server-error state renders in production without a stack trace, without a source path, without an env-key shape and without a framework-internal frame prefix',
|
|
134
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
135
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
136
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/server-error'));
|
|
137
|
+
const dom = await browser.evaluate(() => {
|
|
138
|
+
const region = document.querySelector('[data-surface="server-error"]');
|
|
139
|
+
if (!region) return { present: false };
|
|
140
|
+
const retry = region.querySelector('[data-recovery="retry"]');
|
|
141
|
+
return { present: true, hasRetry: !!retry, text: document.body.innerText };
|
|
142
|
+
});
|
|
143
|
+
if (!dom.present) return { verdict: 'fail', detail: 'server-error region [data-surface="server-error"] not found' };
|
|
144
|
+
if (!dom.hasRetry) return { verdict: 'fail', detail: 'server-error region missing [data-recovery="retry"] control' };
|
|
145
|
+
const leak = firstSensitiveMatch(dom.text, SENSITIVE_PATTERNS);
|
|
146
|
+
if (leak) return { verdict: 'fail', detail: `server-error surface leaked ${leak.name}: ${leak.match}` };
|
|
147
|
+
return { verdict: 'pass', detail: 'server-error region clean; retry control present' };
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'AC-22104-1',
|
|
152
|
+
severity: 'block',
|
|
153
|
+
description: 'permission-denied state renders a class-level cause string and never a resource id',
|
|
154
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
155
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
156
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/permission-denied'));
|
|
157
|
+
const dom = await browser.evaluate(() => {
|
|
158
|
+
const region = document.querySelector('[data-surface="permission-denied"]');
|
|
159
|
+
if (!region) return { present: false };
|
|
160
|
+
const cause = region.querySelector('[data-cause]');
|
|
161
|
+
const control = region.querySelector('[data-action="request-access"]');
|
|
162
|
+
return {
|
|
163
|
+
present: true,
|
|
164
|
+
causeText: cause ? cause.textContent.trim() : null,
|
|
165
|
+
hasControl: !!control,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
if (!dom.present) return { verdict: 'fail', detail: 'permission-denied region [data-surface="permission-denied"] not found' };
|
|
169
|
+
if (!dom.causeText) return { verdict: 'fail', detail: 'permission-denied region missing [data-cause] class-level cause' };
|
|
170
|
+
if (!dom.hasControl) return { verdict: 'fail', detail: 'permission-denied region missing [data-action="request-access"] control' };
|
|
171
|
+
const leak = firstSensitiveMatch(dom.causeText, RESOURCE_ID_PATTERNS);
|
|
172
|
+
if (leak) return { verdict: 'fail', detail: `permission-denied cause leaked ${leak.name}: ${leak.match}` };
|
|
173
|
+
return { verdict: 'pass', detail: `cause=${JSON.stringify(dom.causeText)}` };
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'AC-22105-1',
|
|
178
|
+
severity: 'block',
|
|
179
|
+
description: 'offline state renders with a read-only banner, a synthetic write buffers to window.__offlineBuffer with an idempotency token, and the polite live-region wrapper carries the reconnection announcement',
|
|
180
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
181
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
182
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/offline'));
|
|
183
|
+
const offline = await browser.evaluate(() => {
|
|
184
|
+
const region = document.querySelector('[data-surface="offline"]');
|
|
185
|
+
if (!region) return { present: false };
|
|
186
|
+
const banner = region.querySelector('[data-banner="offline"]');
|
|
187
|
+
// Fixture pre-seeds a synthetic write on page load so the
|
|
188
|
+
// pack can observe the buffer without driving Playwright
|
|
189
|
+
// page.setOffline directly.
|
|
190
|
+
const buf = window.__offlineBuffer;
|
|
191
|
+
const hasBufferEntry = Array.isArray(buf) && buf.length > 0
|
|
192
|
+
&& typeof buf[0].idempotencyToken === 'string' && buf[0].idempotencyToken.length > 0
|
|
193
|
+
&& typeof buf[0].sequence === 'number';
|
|
194
|
+
return { present: true, hasBanner: !!banner, hasBufferEntry, bufferSample: buf ? buf.slice(0, 1) : null };
|
|
195
|
+
});
|
|
196
|
+
if (!offline.present) return { verdict: 'fail', detail: 'offline region [data-surface="offline"] not found' };
|
|
197
|
+
if (!offline.hasBanner) return { verdict: 'fail', detail: 'offline region missing [data-banner="offline"]' };
|
|
198
|
+
if (!offline.hasBufferEntry) return { verdict: 'fail', detail: `window.__offlineBuffer not populated with a token-and-sequence entry: ${JSON.stringify(offline.bufferSample)}` };
|
|
199
|
+
// Reconnection: drive the fixture's reconnect route, then
|
|
200
|
+
// read the polite live-region wrapper's text.
|
|
201
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/offline?reconnect=1'));
|
|
202
|
+
const reconnect = await browser.evaluate(() => {
|
|
203
|
+
const live = document.querySelector('[data-live-region="polite"]');
|
|
204
|
+
return { text: live ? live.textContent.trim() : null };
|
|
205
|
+
});
|
|
206
|
+
if (!reconnect.text || reconnect.text.length === 0) {
|
|
207
|
+
return { verdict: 'fail', detail: 'polite live-region wrapper empty on reconnect; expected a reconnection announcement' };
|
|
208
|
+
}
|
|
209
|
+
return { verdict: 'pass', detail: `bufferSample=${JSON.stringify(offline.bufferSample)} reconnect=${JSON.stringify(reconnect.text)}` };
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'AC-22106-1',
|
|
214
|
+
severity: 'block',
|
|
215
|
+
description: 'empty-list state renders with a distinct visual wrapper and a creation-affordance recovery control',
|
|
216
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
217
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
218
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/empty-list'));
|
|
219
|
+
const dom = await browser.evaluate(() => {
|
|
220
|
+
const region = document.querySelector('[data-surface="empty-list"]');
|
|
221
|
+
if (!region) return { present: false };
|
|
222
|
+
const wrapper = region.querySelector('[data-visual="empty-list"]');
|
|
223
|
+
const create = region.querySelector('[data-recovery="create"]');
|
|
224
|
+
return { present: true, hasWrapper: !!wrapper, hasCreate: !!create };
|
|
225
|
+
});
|
|
226
|
+
if (!dom.present) return { verdict: 'fail', detail: 'empty-list region [data-surface="empty-list"] not found' };
|
|
227
|
+
if (!dom.hasWrapper) return { verdict: 'fail', detail: 'empty-list region missing [data-visual="empty-list"] wrapper' };
|
|
228
|
+
if (!dom.hasCreate) return { verdict: 'fail', detail: 'empty-list region missing [data-recovery="create"] control' };
|
|
229
|
+
return { verdict: 'pass', detail: 'empty-list wrapper and create control present' };
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'AC-22107-1',
|
|
234
|
+
severity: 'block',
|
|
235
|
+
description: 'no-search-results state renders with a distinct visual wrapper, an echoed query and a clear-filters recovery control',
|
|
236
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
237
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
238
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/search?q=needle'));
|
|
239
|
+
const dom = await browser.evaluate(() => {
|
|
240
|
+
const region = document.querySelector('[data-surface="no-search-results"]');
|
|
241
|
+
if (!region) return { present: false };
|
|
242
|
+
const wrapper = region.querySelector('[data-visual="no-search-results"]');
|
|
243
|
+
const query = region.querySelector('[data-query]');
|
|
244
|
+
const clear = region.querySelector('[data-recovery="clear-filters"]');
|
|
245
|
+
return {
|
|
246
|
+
present: true,
|
|
247
|
+
hasWrapper: !!wrapper,
|
|
248
|
+
queryText: query ? query.textContent.trim() : null,
|
|
249
|
+
hasClear: !!clear,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
if (!dom.present) return { verdict: 'fail', detail: 'no-search-results region [data-surface="no-search-results"] not found' };
|
|
253
|
+
if (!dom.hasWrapper) return { verdict: 'fail', detail: 'no-search-results region missing [data-visual="no-search-results"] wrapper' };
|
|
254
|
+
if (!dom.hasClear) return { verdict: 'fail', detail: 'no-search-results region missing [data-recovery="clear-filters"] control' };
|
|
255
|
+
if (!dom.queryText || !dom.queryText.includes('needle')) {
|
|
256
|
+
return { verdict: 'fail', detail: `no-search-results region did not echo the query verbatim: ${JSON.stringify(dom.queryText)}` };
|
|
257
|
+
}
|
|
258
|
+
return { verdict: 'pass', detail: `queryEcho=${JSON.stringify(dom.queryText)}` };
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: 'AC-22108-1',
|
|
263
|
+
severity: 'block',
|
|
264
|
+
description: 'error-boundary state renders inside role=alert with a retry recovery hook and no stack detail on the rendered surface',
|
|
265
|
+
run: async ({ browser, runtimeUrl }) => {
|
|
266
|
+
if (!browser) return { verdict: 'fail', detail: 'no packBrowser wired' };
|
|
267
|
+
await browser.goto(withUrl(runtimeUrl, '/probe/error-boundary?crash=1'));
|
|
268
|
+
const dom = await browser.evaluate(() => {
|
|
269
|
+
const region = document.querySelector('[data-surface="error-boundary"]');
|
|
270
|
+
if (!region) return { present: false };
|
|
271
|
+
const role = region.getAttribute('role');
|
|
272
|
+
const retry = region.querySelector('[data-recovery="retry"]');
|
|
273
|
+
return { present: true, role, hasRetry: !!retry, text: document.body.innerText };
|
|
274
|
+
});
|
|
275
|
+
if (!dom.present) return { verdict: 'fail', detail: 'error-boundary region [data-surface="error-boundary"] not found' };
|
|
276
|
+
if (dom.role !== 'alert') return { verdict: 'fail', detail: `error-boundary region role expected "alert", got ${JSON.stringify(dom.role)}` };
|
|
277
|
+
if (!dom.hasRetry) return { verdict: 'fail', detail: 'error-boundary region missing [data-recovery="retry"] control' };
|
|
278
|
+
const leak = firstSensitiveMatch(dom.text, SENSITIVE_PATTERNS);
|
|
279
|
+
if (leak) return { verdict: 'fail', detail: `error-boundary surface leaked ${leak.name}: ${leak.match}` };
|
|
280
|
+
return { verdict: 'pass', detail: 'error-boundary role=alert, retry control present, no stack detail' };
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
};
|
|
@@ -25,5 +25,12 @@
|
|
|
25
25
|
| application-dashboard | 19101-19899 | 20xx | shipped v1.0.0 | none |
|
|
26
26
|
| application-notifications-in-app | 20101-20899 | 21xx | shipped v1.0.0 | none |
|
|
27
27
|
| application-admin-console | 21101-21899 | 22xx | shipped v1.0.0 | none |
|
|
28
|
+
| application-empty-error-states | 22101-22899 | 23xx | shipped v1.0.0 | none |
|
|
29
|
+
| application-file-upload | 23101-23899 | 24xx | shipped v1.0.0 | none |
|
|
30
|
+
| application-forms-wizard | 24101-24899 | 25xx | shipped v1.0.0 | none |
|
|
31
|
+
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
32
|
+
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
33
|
+
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
34
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
|
|
28
35
|
|
|
29
36
|
The shelf-wide band registry lives in `packages/rcf-lite/docs/blueprint-authoring.md` section 5.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# application-file-upload CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 1.0.0 (visual round T-2, spec 2026-09-06)
|
|
4
|
+
|
|
5
|
+
- First ratified version of the shelf's file-upload blueprint. Five REQs (input contract with a labelled file input plus drop-zone plus keyboard alternative per WCAG 2.5.7, progress contract with per-file percent and aggregate percent and polite live-region tick, refusal contract with MIME and size and virus-scan gates and aria-describedby error binding and focus-to-retry, transport contract chunked-and-resumable with multipart POST default and tus.io elicited alternative, completion contract with assertive-slot success announcement per WCAG 4.1.3 and per-file remove and retry), seven USs 23101-23107 binding one runtime-observable AC per REQ plus two cross-cutting cases (network-interruption resumption AC-23106-1, background-tab progress AC-23107-1), three TACs 2401-2403 (upload input surface, upload transport contract, upload progress announcer), three ADRs 2401-2403 (transport branch with multipart recommendedDefault true and tus and vendor-sdk elicited, accepted MIME set and per-file size cap elicited, virus-scan integration via a capability-plus-elicit). No new global topics.
|
|
6
|
+
- Ships `probe-packs/application-file-upload.pack.mjs`: four surface-observable browser-verify checks anchored one per contract (`AC-23101-1` input surface, `AC-23102-1` progress announcer, `AC-23103-1` refusal contract, `AC-23104-1` chunked transport). Every URL is composed by the `withUrl(runtimeUrl, path)` helper per the round 3 T-3 fix train; no bare string concatenation. The pack-level `appliesTo` predicate references BOTH `tacIds` (TAC-2401) AND `route` (the operator-configured upload path glob).
|
|
7
|
+
- Ships a dependency-free sample-app fixture at `packages/rcf-lite/test/fixtures/probe-pack-application-file-upload/` with a Node HTTP server realising both transport branches honestly (`?transport=multipart` default, `?transport=tus` elicited alternative) and break switches (`?break=no-input`, `?break=no-live-region`, `?break=send-refused`, `?break=no-chunks`) driving the negative runs from a single boot. The fixture ships its own README naming PORT, every `?transport=`, every `?seed=`, every `?break=`, the manual boot line and the two-line gate-reviewer boot.
|
|
8
|
+
- Declares `suggestedCompanions` `logging` (every refused upload writes one request-scoped log line with the closed refusal reason and the correlation identifier) and `errorHandling` (refusals and transport-layer failures construct an internal error record through the applied error-handling factory). Does NOT declare `providesRoles` (leaf blueprint per spec; loader refuses an empty `providesRoles` array when set, so the leaf-blueprint intent is expressed by omitting the field). Does NOT declare `capabilities` and does NOT declare `requiresAppliedCapabilities` (no auth required).
|
|
9
|
+
- Seventh shipped consumer of T-0's probe-pack runner extension.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# File upload blueprint (v1.0.0)
|
|
2
|
+
|
|
3
|
+
Vendor-neutral file-upload contract for an rcf-lite application. Ships one upload surface with three input affordances (a labelled `<input type="file">`, a `[data-drop-zone]` region and a keyboard-focusable `[data-open-picker]` button per WCAG 2.5.7), a chunked-and-resumable transport with a multipart POST default and a tus.io elicited alternative, a refusal contract (MIME, size cap, virus-scan) that binds errors via `aria-describedby` and refuses to send refused bytes to the server, a progress announcer that emits a polite live-region tick in the closed format `"<uploaded> of <total> files, <PP> percent"` and an assertive-slot completion announcement per WCAG 4.1.3, and per-file remove and retry controls. Ships a Playwright probe pack under `probe-packs/application-file-upload.pack.mjs` whose four checks each anchor one contract and refuse on the corresponding break switch on the shipped sample-app fixture.
|
|
4
|
+
|
|
5
|
+
## Apply
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
rcf define blueprint add <path-to>/blueprints/application-file-upload
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Applies 18 contributions (5 REQs, 7 USs, 3 TACs, 3 ADRs) cleanly on any fresh init project. No `requiresAppliedCapabilities`, no auth prerequisite. The three ADR elicits (`ADR-2401` transport branch, `ADR-2402` accepted MIME set and per-file size cap, `ADR-2403` virus-scan verifier) carry the multipart default with `recommendedDefault: true` on the transport branch; the other two elicits (accepted set and virus-scan) have no shelf default (see the elicits below).
|
|
12
|
+
|
|
13
|
+
## Anatomy
|
|
14
|
+
|
|
15
|
+
| Piece | Where | What |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Metadata | `blueprint.json` | Slug, version 1.0.0, category `application`, `suggestedCompanions: [logging, errorHandling]`, 18 contributions in the 24xx / 23xxx band |
|
|
18
|
+
| Doc set | `contributions/` | 5 REQs, 7 USs (7 runtime-observable ACs), 3 TACs, 3 ADRs |
|
|
19
|
+
| Probe pack | `probe-packs/application-file-upload.pack.mjs` | Four surface-observable browser-verify checks, one per contract |
|
|
20
|
+
| Guide | `guide/application-file-upload.md` | Operator-facing: when to reach, when not, mechanism-reach gaps |
|
|
21
|
+
| Coordination vocabulary | `docs/topics.md` | Shelf id band registry update; no new global topics claimed |
|
|
22
|
+
| Sample-app fixture | `packages/rcf-lite/test/fixtures/probe-pack-application-file-upload/` | Dependency-free Node HTTP server the pack is probed against on the shelf gate |
|
|
23
|
+
|
|
24
|
+
## The four contracts
|
|
25
|
+
|
|
26
|
+
| Contract | REQ | AC on the pack | Region wrapper | Announcement |
|
|
27
|
+
|---|---|---|---|---|
|
|
28
|
+
| Input surface | REQ-001 | `AC-23101-1` | `[data-surface="file-upload"]` | (n/a; keyboard reach + accessible name) |
|
|
29
|
+
| Progress announcer | REQ-002 | `AC-23102-1` | `[data-live-region="polite"]` inside the region | polite `aria-live="polite"` |
|
|
30
|
+
| Refusal contract | REQ-003 | `AC-23103-1` | `[data-file-row][data-refused="true"]` | `aria-describedby` error binding |
|
|
31
|
+
| Chunked transport | REQ-004 | `AC-23104-1` | `[data-transport]` on the region; `[data-chunks-uploaded]` on each row | (n/a; observed on the wire) |
|
|
32
|
+
| Completion | REQ-005 | asserted by AC-23105-1 (see below) | `[data-assertive-slot]` | `aria-live="assertive"` |
|
|
33
|
+
|
|
34
|
+
The pack ships four checks (`AC-23101-1`, `AC-23102-1`, `AC-23103-1`, `AC-23104-1`) which is the minimum honest set per spec section 5.2; the completion contract (`AC-23105-1`) and the cross-cutting cases (`AC-23106-1` network interruption resumption, `AC-23107-1` background-tab progress) are runtime-observable ACs bound only through the fixture and the anatomy test. Every gap is enumerated below and is a candidate for a v1.1.0 minor bump extending the pack.
|
|
35
|
+
|
|
36
|
+
## The four fixture states
|
|
37
|
+
|
|
38
|
+
Every file row on the fixture cycles through the closed state set `idle` -> `uploading` -> `complete` (honest branch) or `idle` -> `refused` (refusal branch). The state slug rides `[data-file-state]` on the row. The four slugs appear identically across the fixture README, this README, the guide, the pack `checks[]` descriptions and `docs/topics.md`.
|
|
39
|
+
|
|
40
|
+
## The two transport slugs
|
|
41
|
+
|
|
42
|
+
`multipart` is the recommended default per ADR-2401; `tus` is the elicited alternative (tus.io 1.0.0). The two slugs appear identically across the fixture README, this README, the guide, the pack `checks[]` descriptions and `docs/topics.md`. The third elicited alternative (`vendor-sdk` for S3, R2, Cloudinary) is documented in ADR-2401 but is not exercised by the shipped fixture; a project on a vendor SDK adapts the pack to run against a fixture that mocks the vendor's completion callback.
|
|
43
|
+
|
|
44
|
+
## Elicited parameters
|
|
45
|
+
|
|
46
|
+
- **Transport branch (ADR-2401).** Default: `multipart` (recommendedDefault true). Elicited alternatives: `tus`, `vendor-sdk`.
|
|
47
|
+
- **Accepted MIME set (ADR-2402).** No default. Every project elicits its own list of IANA media types. The input TAC refuses every file until this is elicited (the safe floor).
|
|
48
|
+
- **Per-file size cap in bytes (ADR-2402).** No default. Every project elicits its own cap. The input TAC refuses every file until this is elicited.
|
|
49
|
+
- **Concurrency cap** for the transport worker pool. Recommended default: 3. Projects with a slow-link posture elicit a lower cap.
|
|
50
|
+
- **Announcer polling interval in milliseconds.** Recommended default: 250. Backgrounded tabs continue to tick at the same interval per REQ-002 and AC-23107-1.
|
|
51
|
+
- **Virus-scan verifier (ADR-2403).** Capability-plus-elicit. Every project elicits the verifier or the explicit noop; there is no shelf default.
|
|
52
|
+
|
|
53
|
+
## The one runtime gate
|
|
54
|
+
|
|
55
|
+
`probe-packs/application-file-upload.pack.mjs` ships four checks the `rcf verify browser` runner invokes on any FBS whose surface binds `TAC-2401-application-file-upload-input` or whose nav model routes name an operator-configured upload path. Each check drives the real Playwright browser the runner provisions, reads the accessibility tree and the DOM (and the request log for the refusal check and the tus branch), and returns a verdict. URLs are composed by the `withUrl(runtimeUrl, path)` helper per the round 3 T-3 fix train; no bare string concatenation.
|
|
56
|
+
|
|
57
|
+
## Quality bar
|
|
58
|
+
|
|
59
|
+
WCAG 2.2 AA on every upload surface: 2.5.7 Dragging Movements (the drop-zone is never the only input path; the keyboard opener satisfies the alternative), 4.1.3 Status Messages (the polite live-region tick and the assertive-slot completion announcement fire through preseeded wrappers), 3.3.1 Error Identification (every refusal names the closed reason from `mime-refused`, `size-refused`, `virus-refused`), 1.3.1 Info and Relationships (every refusal binds the error message to its file row via `aria-describedby`), 3.3.3 Error Suggestion (every refused row exposes a keyboard-reachable retry control and the guide names the correction). ARIA APG button pattern on the retry control; ARIA APG status pattern on the polite tick. `standardsTrace` on the blueprint is empty (this is a general-enterprise-practice blueprint); each ADR contribution carries `standardsTraceClause` naming the clause or vendor spec it implements.
|
|
60
|
+
|
|
61
|
+
The refusal contract is a real refusal: the pack asserts the refused filename never appears on `window.__uploadRequestLog`, and the `?break=send-refused` fixture switch is the only path that lets refused bytes reach the server.
|
|
62
|
+
|
|
63
|
+
## Known mechanism-reach gaps
|
|
64
|
+
|
|
65
|
+
Runtime-observable ACs the pack does NOT bind directly (checklist section 6.g), listed individually per AC id:
|
|
66
|
+
|
|
67
|
+
- **AC-23101-1 drop-zone drop event round-trip.** The pack asserts the `[data-drop-zone]` region exists and that the keyboard opener focuses the file input on Enter; it does NOT additionally synthesise a drag event and drop a File onto the drop-zone (Playwright's DataTransfer synthesis is fragile across browsers). A v1.1.0 minor bump can drive a drop via `page.evaluate` and read that the file-list picked up the drop.
|
|
68
|
+
- **AC-23102-1 screen-reader adoption of the polite region.** The pack asserts the polite wrapper's textContent matches the closed format and advances monotonically; it does NOT additionally probe screen-reader adoption (that requires a real assistive tech in the loop). A v1.1.0 minor bump can attach an accessibility-tree observer via the browser's a11y snapshot.
|
|
69
|
+
- **AC-23103-1 aria-describedby resolves to a non-empty error message.** The pack asserts `aria-describedby` resolves and that the refused row exposes the closed `data-refusal-reason`; the error-text content check is a substring assertion (present and non-empty), not a full-message copy check. A v1.1.0 minor bump can pin the error message content per refusal reason.
|
|
70
|
+
- **AC-23104-1 network-interruption resume on the multipart branch.** The pack asserts `[data-chunks-uploaded]` exceeds one on both branches and that the tus branch records numeric `Upload-Offset` values on `window.__tusPatchOffsets`; it does NOT additionally probe a mid-upload interruption on the multipart branch (that requires a request-blocking driver). A v1.1.0 minor bump can drive the `?interrupt=1` + `?resume=1` seams the fixture already exposes via AC-23106-1.
|
|
71
|
+
- **AC-23105-1 assertive-slot exclusive fire.** The pack does NOT bind this AC directly; the anatomy test drives the fixture and reads the assertive slot's textContent. A v1.1.0 minor bump can promote the check into the pack, driving an upload set to completion and reading the assertive wrapper.
|
|
72
|
+
- **AC-23106-1 network interruption resumption.** The pack does NOT bind this AC directly. The fixture exposes `?interrupt=1` and `?resume=1` and the anatomy test walks the resume path; a v1.1.0 minor bump promotes the check.
|
|
73
|
+
- **AC-23107-1 background-tab tick continuity.** The pack does NOT bind this AC directly. The fixture exposes `?background=1` and the anatomy test synthesises a `visibilitychange` event; a v1.1.0 minor bump promotes the check.
|
|
74
|
+
|
|
75
|
+
## Companions
|
|
76
|
+
|
|
77
|
+
`suggestedCompanions` declares `logging` (every refused upload writes one request-scoped log line with the closed refusal reason and the correlation identifier the applied logger factory returns) and `errorHandling` (refusals and transport-layer failures construct an internal error record through the applied error-handling factory so the aria-describedby error message renders a consistent internal representation). Neither is required to apply the blueprint; a project without a logging companion still ships the refusal contract, it just does not emit a per-refusal log line.
|
|
78
|
+
|
|
79
|
+
## Consumers
|
|
80
|
+
|
|
81
|
+
The round-4 spec's CSV-import candidate would consume this blueprint's transport and refusal contracts; that candidate is not in this round. A downstream `application-forms-wizard` may wire a per-step file-upload field to this blueprint's input TAC.
|
|
82
|
+
|
|
83
|
+
## Composition with application-spa
|
|
84
|
+
|
|
85
|
+
`application-spa` REQ-006 ships upload, progress and error components as part of its component library. This blueprint composes with SPA; the topic-ownership boundary lives in `docs/topics.md`. SPA owns the visual tokens for the upload region; this sibling owns the input affordances, the transport contract, the refusal contract and the runtime enforcement pack.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "application-file-upload",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"category": "application",
|
|
5
|
+
"suggestedCompanions": [
|
|
6
|
+
{
|
|
7
|
+
"role": "logging",
|
|
8
|
+
"reason": "Every refused upload writes one request-scoped log line with the refusal reason (mime, size, virus-scan) and the correlation identifier the applied logger factory returns."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"role": "errorHandling",
|
|
12
|
+
"reason": "Refusals and transport-layer failures construct an internal error record through the applied error-handling factory so the aria-describedby error binding renders a consistent message."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"contributions": [
|
|
16
|
+
{
|
|
17
|
+
"id": "application-file-upload-REQ-001",
|
|
18
|
+
"kind": "req",
|
|
19
|
+
"path": "requirements/application-file-upload-req-001.json"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "application-file-upload-REQ-002",
|
|
23
|
+
"kind": "req",
|
|
24
|
+
"path": "requirements/application-file-upload-req-002.json"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "application-file-upload-REQ-003",
|
|
28
|
+
"kind": "req",
|
|
29
|
+
"path": "requirements/application-file-upload-req-003.json"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "application-file-upload-REQ-004",
|
|
33
|
+
"kind": "req",
|
|
34
|
+
"path": "requirements/application-file-upload-req-004.json"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "application-file-upload-REQ-005",
|
|
38
|
+
"kind": "req",
|
|
39
|
+
"path": "requirements/application-file-upload-req-005.json"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "application-file-upload-US-23101",
|
|
43
|
+
"kind": "us",
|
|
44
|
+
"path": "user-stories/application-file-upload-us-23101.json"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "application-file-upload-US-23102",
|
|
48
|
+
"kind": "us",
|
|
49
|
+
"path": "user-stories/application-file-upload-us-23102.json"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "application-file-upload-US-23103",
|
|
53
|
+
"kind": "us",
|
|
54
|
+
"path": "user-stories/application-file-upload-us-23103.json"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "application-file-upload-US-23104",
|
|
58
|
+
"kind": "us",
|
|
59
|
+
"path": "user-stories/application-file-upload-us-23104.json"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "application-file-upload-US-23105",
|
|
63
|
+
"kind": "us",
|
|
64
|
+
"path": "user-stories/application-file-upload-us-23105.json"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "application-file-upload-US-23106",
|
|
68
|
+
"kind": "us",
|
|
69
|
+
"path": "user-stories/application-file-upload-us-23106.json"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "application-file-upload-US-23107",
|
|
73
|
+
"kind": "us",
|
|
74
|
+
"path": "user-stories/application-file-upload-us-23107.json"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "TAC-2401-application-file-upload-input",
|
|
78
|
+
"kind": "tac",
|
|
79
|
+
"path": "tacs/tac-2401-application-file-upload-input.json"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "TAC-2402-application-file-upload-transport",
|
|
83
|
+
"kind": "tac",
|
|
84
|
+
"path": "tacs/tac-2402-application-file-upload-transport.json"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "TAC-2403-application-file-upload-progress-announcer",
|
|
88
|
+
"kind": "tac",
|
|
89
|
+
"path": "tacs/tac-2403-application-file-upload-progress-announcer.json"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "ADR-2401-application-file-upload-transport",
|
|
93
|
+
"kind": "adr",
|
|
94
|
+
"path": "adrs/adr-2401-application-file-upload-transport.json",
|
|
95
|
+
"recommendedDefault": true,
|
|
96
|
+
"elicited": true,
|
|
97
|
+
"standardsTraceClause": "WHATWG Fetch multipart/form-data; tus.io resumable upload protocol 1.0.0"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "ADR-2402-application-file-upload-accepted-set",
|
|
101
|
+
"kind": "adr",
|
|
102
|
+
"path": "adrs/adr-2402-application-file-upload-accepted-set.json",
|
|
103
|
+
"elicited": true,
|
|
104
|
+
"standardsTraceClause": "IANA media types; generic enterprise practice"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "ADR-2403-application-file-upload-virus-scan",
|
|
108
|
+
"kind": "adr",
|
|
109
|
+
"path": "adrs/adr-2403-application-file-upload-virus-scan.json",
|
|
110
|
+
"elicited": true,
|
|
111
|
+
"standardsTraceClause": "OWASP ASVS V12 file uploads"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-2401-application-file-upload-transport",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Transport branch: multipart POST is the recommended default; tus.io 1.0.0 and per-vendor SDK are the elicited alternatives",
|
|
8
|
+
"context": "There are three plausible upload transports for a shelf blueprint whose consumers range from a hobby SPA hitting a Node server to an enterprise SPA hitting Cloudflare R2. Native multipart POST is the least-surprising choice: every server toolkit and every fetch client can send it, the client side of the code is a Blob split into fixed-size chunks, and the server assembles the chunks server-side. The tus.io resumable upload protocol 1.0.0 (RFC-shaped spec) is the strongest choice for large files over spotty connections because it standardises Upload-Offset and Upload-Length headers and a PATCH-per-chunk shape that lets the client resume by re-reading the offset from the server; it needs a tus-server or the third-party tus client library. A per-vendor SDK (S3 multipart, Cloudflare R2 client-direct upload, Cloudinary widget, Uppy with a specific backend) buys the vendor's operational benefits (signed URLs, resumable multipart, CDN write-through) at the price of pinning the client to the vendor and the vendor's SDK release cycle.",
|
|
9
|
+
"decision": "Adopt multipart POST as the recommended default. Every applied blueprint gets multipart out of the box with no configuration. Projects with tus servers or the tus client library elicit the tus branch through this ADR and the transport TAC instantiates the tus adapter. Projects committed to a vendor (S3, R2, Cloudinary) elicit the vendor-sdk branch and supply the SDK client through the applied endpoint config; the transport TAC delegates chunking to the SDK. recommendedDefault is true; elicited is true because the branch is a per-project answer.",
|
|
10
|
+
"consequences": "Projects on the default get a working chunked-and-resumable upload against a plain server with no extra dependency. Projects that need tus wire it in via a small adapter and gain the protocol's cross-vendor portability. Projects on vendor SDKs delegate the wire-level details to the vendor and accept the vendor lock-in as an explicit choice. The pack fires on both the multipart branch and the tus branch of the sample fixture; a vendor-sdk project runs the pack against a fixture that mocks the vendor's completion callback.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "tus-only as the shelf default",
|
|
14
|
+
"summary": "Ship tus.io as the sole transport, on the theory that resumable-by-default is the correct posture.",
|
|
15
|
+
"reasonNotChosen": "Every applied blueprint would need a tus server, which is a hard prerequisite for a shelf blueprint; a hobby SPA hitting a Node server would refuse to apply without stand-up work. The default has to be reachable by every consumer."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Vendor-SDK-only",
|
|
19
|
+
"summary": "Ship an SDK-agnostic vendor adapter as the only transport, on the theory that most enterprise uploads eventually pin a vendor.",
|
|
20
|
+
"reasonNotChosen": "A vendor-first shelf pins the shelf to a vendor's SDK release cycle and breaks the vendor-neutral posture of every other application blueprint on the shelf."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T16:55:00Z",
|
|
24
|
+
"updatedAt": "2026-09-06T16:55:00Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-2402-application-file-upload-accepted-set",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Accepted MIME set and per-file size cap are elicited per project; no shelf default beyond \"nothing accepted\"",
|
|
8
|
+
"context": "A shelf blueprint cannot know what a downstream project's upload endpoint accepts. A photo-gallery product accepts a narrow image set (jpeg, png, webp) with a 20 MB per-file cap; a document workspace accepts pdf, docx, txt with a 100 MB cap; a code-review product accepts a broad set with a 5 MB cap. Picking a default that fits all projects picks one that fits none. Shipping without any elicited values leaves the input TAC in a state where every file is refused (the safe default), which is the correct floor: a project that forgets to elicit the accepted set discovers this immediately on the first upload.",
|
|
9
|
+
"decision": "The accepted MIME set and the per-file size cap are elicited per project through this ADR. The blueprint contributes the two parameters as elicits on apply; the input TAC's MIME gate refuses every file until both are elicited. The elicited MIME set is a list of IANA media types; wildcards (image/*, application/*) are permitted for projects that want them but discouraged in the guide. The per-file size cap is a positive integer in bytes. A concurrency cap and the polling interval for the announcer are secondary elicits with recommended defaults (concurrency 3, interval 250 ms).",
|
|
10
|
+
"consequences": "Projects that apply the blueprint without elicitizing discover the refusal on the first upload attempt and go through the elicit flow to unblock it; the refusal is legible (\"MIME gate has no accepted set\") rather than silently accepting everything. Projects with a mature accepted-set list paste it in once and never revisit. The pack asserts the input TAC refuses an obvious wrong-MIME file (a text/plain payload with a jpeg extension) and refuses a payload exceeding the size cap.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Ship a broad default set (image/*, application/pdf, text/plain)",
|
|
14
|
+
"summary": "Adopt a shelf default that covers the most common cases so most projects can apply without elicitizing.",
|
|
15
|
+
"reasonNotChosen": "Any default that is broad enough to be useful is broad enough to be wrong for a project whose backend cannot accept the same set; the discovery cost of a mismatched accepted set at the server is higher than the discovery cost of the refuse-all floor at the client."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Ship no accepted-set gate at all",
|
|
19
|
+
"summary": "Rely on the server to refuse a wrong-MIME upload and skip the client-side gate.",
|
|
20
|
+
"reasonNotChosen": "The server refusal happens after the bytes are on the wire; a client that never gates wastes the operator's upload budget on payloads the server will refuse and loses the aria-describedby error binding the input TAC ships."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T16:55:00Z",
|
|
24
|
+
"updatedAt": "2026-09-06T16:55:00Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-2403-application-file-upload-virus-scan",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Virus scan integration is a capability-plus-elicit; the operator supplies a verifier or the transport refuses the branch",
|
|
8
|
+
"context": "A file-upload surface that skips virus scanning ships a well-known attack path: a workspace with unmoderated attachments hands a payload from one user to any other user that opens the file. The right answer is server-side scanning against a vendor (ClamAV, VirusTotal, S3 GuardDuty malware protection) plus a client-side hook that refuses to hand a scan-hit file to the user for retry. But the vendor and the wire shape are project decisions: a healthcare product with a HIPAA data path uses a different scanner than a games product with an S3 bucket. The shelf cannot choose one.",
|
|
9
|
+
"decision": "Virus scan integration is a capability-plus-elicit: the blueprint contributes a virusScan capability the input TAC calls (an async predicate over a File that resolves to {clean: true} or {clean: false, reason: <string>}), and the applied capability is elicited per project. Projects with a virus scanner supply the verifier. Projects that explicitly opt out of virus scanning elicit the noop verifier and accept the risk (the guide names the risk in plain language). The pack asserts the input TAC's refusal path fires on a virus-scan hit and that the refused file never leaves the browser.",
|
|
10
|
+
"consequences": "Projects with a scanner apply the blueprint and paste in the verifier. Projects without one see the elicit and either wire in ClamAV/VirusTotal or accept the noop with the guide's warning in hand. The shelf posture is opinionated on the mechanism (a virus-scan hook exists and is called) but neutral on the vendor. A project that ignores the elicit and never supplies a verifier hits an apply-time refusal.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Skip virus scanning at the shelf layer entirely",
|
|
14
|
+
"summary": "Leave scanning to the server and ship no client-side hook.",
|
|
15
|
+
"reasonNotChosen": "The server-side scanner cannot refuse the client's retry loop on a hit without an aria-describedby error binding; the client-side hook is what surfaces the refusal legibly."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Bundle ClamAV as the shelf default",
|
|
19
|
+
"summary": "Ship the ClamAV client bindings as the sole verifier.",
|
|
20
|
+
"reasonNotChosen": "Adds a vendor dependency to every applied project and pins the shelf to a scanner that many projects will not use; capability-plus-elicit keeps the shelf vendor-neutral."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T16:55:00Z",
|
|
24
|
+
"updatedAt": "2026-09-06T16:55:00Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "application-file-upload-REQ-001",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Input contract: a labelled file input plus a drop-zone plus a keyboard alternative per WCAG 2.5.7",
|
|
5
|
+
"description": "The upload surface renders a labelled native file input, a labelled drop-zone region for pointer users, and a keyboard-focusable alternative control that opens the picker on Enter or Space. Every input carries an accessible name via label association or aria-label. The three affordances share the same underlying file collector so a keyboard-only user, a pointer user with drag-drop and a screen-reader user reach the same state. Per WCAG 2.5.7 Dragging Movements, the drop-zone is never the only way to add a file; a click and keyboard path both satisfy the same acceptance criterion.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "application",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Per visual round 4 spec section 5.2, REQ 1.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:application-file-upload",
|
|
12
|
+
"category:01-input-contract"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T16:55:00Z",
|
|
17
|
+
"updatedAt": "2026-09-06T16:55:00Z"
|
|
18
|
+
}
|