canopycms 0.0.67-int.90 → 0.0.67
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/README.md +30 -3
- package/dist/ai/generate.js +1 -27
- package/dist/ai/handler.d.ts +0 -4
- package/dist/ai/handler.js +0 -11
- package/dist/ai/index.d.ts +1 -8
- package/dist/ai/index.js +1 -8
- package/dist/ai/json-to-markdown.js +15 -58
- package/dist/ai/resolve-branch.d.ts +2 -7
- package/dist/ai/resolve-branch.js +2 -8
- package/dist/ai/strip-mdx.d.ts +5 -13
- package/dist/ai/strip-mdx.js +5 -17
- package/dist/ai/to-plain-text.d.ts +10 -28
- package/dist/ai/to-plain-text.js +10 -28
- package/dist/ai/transform-components.d.ts +2 -4
- package/dist/ai/transform-components.js +8 -19
- package/dist/ai/types.d.ts +5 -10
- package/dist/api/admin-branch-health.d.ts +4 -12
- package/dist/api/admin-branch-health.js +26 -66
- package/dist/api/admin.d.ts +4 -12
- package/dist/api/admin.js +10 -26
- package/dist/api/assets.d.ts +0 -25
- package/dist/api/assets.js +50 -91
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +0 -2
- package/dist/api/branch-review.d.ts +0 -8
- package/dist/api/branch-review.js +0 -13
- package/dist/api/branch-status.d.ts +0 -3
- package/dist/api/branch-status.js +8 -33
- package/dist/api/branch-withdraw.d.ts +0 -1
- package/dist/api/branch-withdraw.js +6 -9
- package/dist/api/branch.d.ts +7 -12
- package/dist/api/branch.js +7 -44
- package/dist/api/client.d.ts +71 -224
- package/dist/api/client.js +82 -233
- package/dist/api/comments.d.ts +2 -4
- package/dist/api/comments.js +0 -17
- package/dist/api/content.d.ts +0 -6
- package/dist/api/content.js +66 -142
- package/dist/api/entries-constants.d.ts +5 -9
- package/dist/api/entries-constants.js +5 -9
- package/dist/api/entries.d.ts +8 -13
- package/dist/api/entries.js +20 -51
- package/dist/api/github-sync.d.ts +2 -4
- package/dist/api/github-sync.js +4 -8
- package/dist/api/groups.d.ts +3 -0
- package/dist/api/groups.js +8 -21
- package/dist/api/guards.d.ts +4 -16
- package/dist/api/guards.js +3 -22
- package/dist/api/index.d.ts +4 -4
- package/dist/api/permissions.d.ts +2 -0
- package/dist/api/permissions.js +18 -40
- package/dist/api/reference-options.d.ts +0 -1
- package/dist/api/reference-options.js +3 -11
- package/dist/api/request-body-hash.d.ts +12 -23
- package/dist/api/request-body-hash.js +12 -23
- package/dist/api/resolve-references.d.ts +0 -1
- package/dist/api/resolve-references.js +13 -28
- package/dist/api/route-builder.d.ts +3 -7
- package/dist/api/route-builder.js +1 -1
- package/dist/api/routes.d.ts +12 -0
- package/dist/api/routes.js +47 -0
- package/dist/api/schema.d.ts +23 -33
- package/dist/api/schema.js +20 -74
- package/dist/api/settings-helpers.js +4 -5
- package/dist/api/types.d.ts +0 -5
- package/dist/api/user.js +0 -5
- package/dist/api/validators.d.ts +14 -52
- package/dist/api/validators.js +14 -52
- package/dist/assets/asset-prefixes.d.ts +4 -8
- package/dist/assets/asset-prefixes.js +4 -8
- package/dist/assets/asset-src.d.ts +5 -22
- package/dist/assets/asset-src.js +5 -21
- package/dist/assets/factory.d.ts +1 -2
- package/dist/assets/factory.js +1 -2
- package/dist/assets/finalize.d.ts +3 -1
- package/dist/assets/finalize.js +3 -1
- package/dist/assets/index.d.ts +3 -3
- package/dist/assets/index.js +1 -1
- package/dist/assets/keys.d.ts +5 -3
- package/dist/assets/keys.js +6 -3
- package/dist/assets/pipeline.d.ts +3 -2
- package/dist/assets/pipeline.js +22 -56
- package/dist/assets/sharp-loader.d.ts +2 -10
- package/dist/assets/sharp-loader.js +2 -10
- package/dist/assets/store-local.d.ts +1 -2
- package/dist/assets/store-local.js +1 -2
- package/dist/assets/store-s3.d.ts +3 -4
- package/dist/assets/svg-sanitizer.d.ts +6 -14
- package/dist/assets/svg-sanitizer.js +6 -14
- package/dist/assets/transform-directives.d.ts +3 -4
- package/dist/assets/transform-directives.js +1 -1
- package/dist/assets/transform.d.ts +19 -30
- package/dist/assets/transform.js +18 -31
- package/dist/auth/cache.d.ts +2 -3
- package/dist/auth/cache.js +2 -3
- package/dist/auth/caching-auth-plugin.d.ts +11 -22
- package/dist/auth/caching-auth-plugin.js +12 -14
- package/dist/auth/context-helpers.d.ts +6 -20
- package/dist/auth/context-helpers.js +5 -17
- package/dist/auth/file-based-auth-cache.d.ts +10 -21
- package/dist/auth/file-based-auth-cache.js +23 -51
- package/dist/auth/plugin.d.ts +29 -61
- package/dist/auth/plugin.js +9 -13
- package/dist/auth/types.d.ts +11 -20
- package/dist/authorization/branch.d.ts +30 -49
- package/dist/authorization/branch.js +16 -42
- package/dist/authorization/content.d.ts +11 -40
- package/dist/authorization/content.js +13 -45
- package/dist/authorization/groups/index.d.ts +0 -3
- package/dist/authorization/groups/index.js +0 -3
- package/dist/authorization/groups/loader.d.ts +14 -28
- package/dist/authorization/groups/loader.js +14 -38
- package/dist/authorization/groups/schema.d.ts +0 -6
- package/dist/authorization/groups/schema.js +4 -7
- package/dist/authorization/helpers.d.ts +9 -28
- package/dist/authorization/helpers.js +9 -28
- package/dist/authorization/index.d.ts +3 -25
- package/dist/authorization/index.js +3 -35
- package/dist/authorization/path.d.ts +6 -13
- package/dist/authorization/path.js +8 -25
- package/dist/authorization/permissions/index.d.ts +0 -3
- package/dist/authorization/permissions/index.js +0 -3
- package/dist/authorization/permissions/loader.d.ts +14 -27
- package/dist/authorization/permissions/loader.js +14 -32
- package/dist/authorization/permissions/schema.d.ts +1 -6
- package/dist/authorization/permissions/schema.js +6 -13
- package/dist/authorization/protected-branch.d.ts +36 -65
- package/dist/authorization/protected-branch.js +29 -41
- package/dist/authorization/settings-file-store.d.ts +38 -81
- package/dist/authorization/settings-file-store.js +38 -79
- package/dist/authorization/types.d.ts +4 -26
- package/dist/authorization/types.js +0 -6
- package/dist/authorization/validation.d.ts +1 -23
- package/dist/authorization/validation.js +1 -24
- package/dist/branch-health.d.ts +39 -67
- package/dist/branch-health.js +27 -43
- package/dist/branch-metadata-file.d.ts +16 -32
- package/dist/branch-metadata-file.js +13 -26
- package/dist/branch-metadata.d.ts +49 -80
- package/dist/branch-metadata.js +57 -87
- package/dist/branch-registry.d.ts +26 -64
- package/dist/branch-registry.js +44 -93
- package/dist/branch-schema-cache.d.ts +34 -124
- package/dist/branch-schema-cache.js +49 -155
- package/dist/branch-workspace.d.ts +4 -4
- package/dist/branch-workspace.js +15 -19
- package/dist/build/generate-ai-content.d.ts +1 -0
- package/dist/build/generate-ai-content.js +12 -25
- package/dist/build-canopy.d.ts +17 -27
- package/dist/build-canopy.js +16 -23
- package/dist/build-mode.d.ts +25 -27
- package/dist/build-mode.js +25 -29
- package/dist/cli/cli.d.ts +15 -26
- package/dist/cli/cli.js +601 -978
- package/dist/cli/generate-ai-content.js +351 -628
- package/dist/cli/github-app-manifest.d.ts +84 -0
- package/dist/cli/github-app-manifest.js +161 -0
- package/dist/cli/init-github-app.d.ts +110 -444
- package/dist/cli/init-github-app.js +236 -773
- package/dist/cli/init.js +39 -80
- package/dist/cli/migrate.d.ts +13 -10
- package/dist/cli/migrate.js +22 -30
- package/dist/cli/project-detect.d.ts +17 -31
- package/dist/cli/project-detect.js +31 -42
- package/dist/cli/project-root.d.ts +4 -11
- package/dist/cli/project-root.js +4 -11
- package/dist/cli/prompt.d.ts +10 -0
- package/dist/cli/prompt.js +67 -0
- package/dist/cli/sync.d.ts +0 -8
- package/dist/cli/sync.js +8 -28
- package/dist/cli/template-files/middleware.ts.template +15 -29
- package/dist/cli/templates.d.ts +2 -12
- package/dist/cli/templates.js +4 -4
- package/dist/client.d.ts +1 -1
- package/dist/comment-store.d.ts +32 -53
- package/dist/comment-store.js +32 -52
- package/dist/config/flatten.d.ts +0 -3
- package/dist/config/flatten.js +2 -12
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -4
- package/dist/config/schemas/collection.d.ts +0 -8
- package/dist/config/schemas/collection.js +0 -10
- package/dist/config/schemas/config.d.ts +6 -78
- package/dist/config/schemas/config.js +38 -54
- package/dist/config/schemas/field.d.ts +2 -242
- package/dist/config/schemas/field.js +9 -20
- package/dist/config/schemas/media.js +1 -1
- package/dist/config/schemas/permissions.js +0 -2
- package/dist/config/schemas/url.d.ts +5 -6
- package/dist/config/schemas/url.js +5 -6
- package/dist/config/types.d.ts +45 -33
- package/dist/config/types.js +0 -1
- package/dist/config.d.ts +1 -8
- package/dist/config.js +1 -9
- package/dist/content-id-index.d.ts +76 -188
- package/dist/content-id-index.js +79 -230
- package/dist/content-index-generation.d.ts +27 -48
- package/dist/content-index-generation.js +35 -84
- package/dist/content-index-registry.d.ts +15 -24
- package/dist/content-index-registry.js +5 -9
- package/dist/content-listing.d.ts +64 -107
- package/dist/content-listing.js +68 -118
- package/dist/content-reader.d.ts +20 -29
- package/dist/content-reader.js +10 -18
- package/dist/content-store.d.ts +210 -336
- package/dist/content-store.js +413 -657
- package/dist/content-tree.d.ts +44 -86
- package/dist/content-tree.js +26 -54
- package/dist/context.d.ts +67 -94
- package/dist/context.js +53 -72
- package/dist/dev-content-watcher.d.ts +18 -18
- package/dist/dev-content-watcher.js +41 -44
- package/dist/editor/BranchManager.d.ts +3 -6
- package/dist/editor/BranchManager.js +24 -44
- package/dist/editor/CanopyEditor.js +5 -2
- package/dist/editor/CommentsPanel.js +0 -2
- package/dist/editor/Editor.js +19 -76
- package/dist/editor/EditorAuthGate.d.ts +27 -0
- package/dist/editor/EditorAuthGate.js +140 -0
- package/dist/editor/EntryNavigator.js +0 -20
- package/dist/editor/FormRenderer.d.ts +1 -2
- package/dist/editor/FormRenderer.js +8 -23
- package/dist/editor/admin/SystemHealthPanel.js +20 -37
- package/dist/editor/admin/useSystemHealth.d.ts +2 -3
- package/dist/editor/admin/useSystemHealth.js +10 -12
- package/dist/editor/client-reference-resolver.d.ts +2 -5
- package/dist/editor/client-reference-resolver.js +2 -19
- package/dist/editor/comments/BranchComments.js +0 -4
- package/dist/editor/comments/EntryComments.js +0 -4
- package/dist/editor/comments/FieldWrapper.js +0 -4
- package/dist/editor/comments/InlineCommentThread.js +0 -2
- package/dist/editor/comments/ThreadCarousel.d.ts +0 -58
- package/dist/editor/comments/ThreadCarousel.js +0 -12
- package/dist/editor/components/EditorFooter.d.ts +0 -8
- package/dist/editor/components/EditorFooter.js +0 -5
- package/dist/editor/components/EditorHeader.d.ts +0 -31
- package/dist/editor/components/EditorHeader.js +21 -71
- package/dist/editor/components/EditorSidebar.d.ts +0 -19
- package/dist/editor/components/EditorSidebar.js +0 -16
- package/dist/editor/components/EntryCreateModal.d.ts +4 -4
- package/dist/editor/components/EntryCreateModal.js +7 -22
- package/dist/editor/components/RenameEntryModal.js +0 -4
- package/dist/editor/components/UserBadge.js +0 -10
- package/dist/editor/components/index.js +0 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -7
- package/dist/editor/context/ApiClientContext.js +39 -11
- package/dist/editor/context/AssetContext.d.ts +7 -17
- package/dist/editor/context/AssetContext.js +7 -17
- package/dist/editor/context/EditorIdentityContext.d.ts +13 -0
- package/dist/editor/context/EditorIdentityContext.js +7 -0
- package/dist/editor/context/EditorStateContext.d.ts +10 -41
- package/dist/editor/context/EditorStateContext.js +7 -23
- package/dist/editor/context/SWRProvider.d.ts +3 -6
- package/dist/editor/context/index.d.ts +1 -20
- package/dist/editor/context/index.js +1 -20
- package/dist/editor/editor-utils.d.ts +8 -47
- package/dist/editor/editor-utils.js +19 -72
- package/dist/editor/fields/BlockField.d.ts +2 -2
- package/dist/editor/fields/BlockField.js +1 -2
- package/dist/editor/fields/CodeField.d.ts +0 -1
- package/dist/editor/fields/CodeField.js +0 -1
- package/dist/editor/fields/DateTimeField.d.ts +15 -22
- package/dist/editor/fields/DateTimeField.js +17 -26
- package/dist/editor/fields/ImageField.d.ts +2 -2
- package/dist/editor/fields/ImageField.js +8 -14
- package/dist/editor/fields/InlineGroupField.d.ts +0 -1
- package/dist/editor/fields/InlineGroupField.js +0 -1
- package/dist/editor/fields/MarkdownField.d.ts +0 -1
- package/dist/editor/fields/MarkdownField.js +6 -11
- package/dist/editor/fields/MdxImageDialog.d.ts +1 -1
- package/dist/editor/fields/MdxImageDialog.js +0 -1
- package/dist/editor/fields/NumberField.d.ts +9 -19
- package/dist/editor/fields/NumberField.js +11 -22
- package/dist/editor/fields/NumberListField.d.ts +7 -10
- package/dist/editor/fields/NumberListField.js +7 -10
- package/dist/editor/fields/ObjectField.d.ts +5 -8
- package/dist/editor/fields/ObjectField.js +0 -1
- package/dist/editor/fields/ReferenceField.d.ts +1 -1
- package/dist/editor/fields/ReferenceField.js +11 -16
- package/dist/editor/fields/SelectField.d.ts +2 -2
- package/dist/editor/fields/SelectField.js +0 -1
- package/dist/editor/fields/StringListField.d.ts +0 -4
- package/dist/editor/fields/StringListField.js +4 -9
- package/dist/editor/fields/TextField.d.ts +0 -1
- package/dist/editor/fields/TextField.js +0 -1
- package/dist/editor/fields/ToggleField.d.ts +0 -1
- package/dist/editor/fields/ToggleField.js +0 -1
- package/dist/editor/fields/entry-link/EntryLinkContext.js +2 -4
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +0 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.js +0 -4
- package/dist/editor/group-manager/ExternalGroupsTab.d.ts +0 -3
- package/dist/editor/group-manager/ExternalGroupsTab.js +0 -1
- package/dist/editor/group-manager/GroupCard.d.ts +0 -3
- package/dist/editor/group-manager/GroupForm.d.ts +0 -3
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +0 -3
- package/dist/editor/group-manager/InternalGroupsTab.js +0 -2
- package/dist/editor/group-manager/MemberList.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useExternalGroupSearch.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useExternalGroupSearch.js +0 -3
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useGroupState.js +0 -5
- package/dist/editor/group-manager/hooks/useUserSearch.d.ts +0 -3
- package/dist/editor/group-manager/hooks/useUserSearch.js +0 -3
- package/dist/editor/group-manager/index.d.ts +0 -6
- package/dist/editor/group-manager/index.js +0 -10
- package/dist/editor/group-manager/types.d.ts +2 -5
- package/dist/editor/group-manager/types.js +0 -3
- package/dist/editor/hooks/index.d.ts +6 -11
- package/dist/editor/hooks/index.js +6 -12
- package/dist/editor/hooks/useBranchActions.d.ts +0 -14
- package/dist/editor/hooks/useBranchActions.js +0 -22
- package/dist/editor/hooks/useBranchManager.d.ts +2 -48
- package/dist/editor/hooks/useBranchManager.js +4 -64
- package/dist/editor/hooks/useBranchesData.d.ts +7 -6
- package/dist/editor/hooks/useBranchesData.js +7 -6
- package/dist/editor/hooks/useCommentSystem.d.ts +0 -49
- package/dist/editor/hooks/useCommentSystem.js +1 -36
- package/dist/editor/hooks/useDraftManager.d.ts +2 -35
- package/dist/editor/hooks/useDraftManager.js +23 -64
- package/dist/editor/hooks/useEditorLayout.d.ts +0 -20
- package/dist/editor/hooks/useEditorLayout.js +0 -23
- package/dist/editor/hooks/useEntriesData.d.ts +1 -0
- package/dist/editor/hooks/useEntriesData.js +2 -3
- package/dist/editor/hooks/useEntryLinkResolution.d.ts +2 -2
- package/dist/editor/hooks/useEntryLinkResolution.js +2 -2
- package/dist/editor/hooks/useEntryManager.d.ts +2 -27
- package/dist/editor/hooks/useEntryManager.js +29 -78
- package/dist/editor/hooks/useGroupManager.d.ts +0 -21
- package/dist/editor/hooks/useGroupManager.js +0 -21
- package/dist/editor/hooks/usePermissionManager.d.ts +0 -20
- package/dist/editor/hooks/usePermissionManager.js +0 -20
- package/dist/editor/hooks/useReferenceResolution.d.ts +2 -33
- package/dist/editor/hooks/useReferenceResolution.js +1 -48
- package/dist/editor/hooks/useSchemaManager.d.ts +8 -27
- package/dist/editor/hooks/useSchemaManager.js +0 -19
- package/dist/editor/hooks/useUserContext.d.ts +2 -9
- package/dist/editor/hooks/useUserContext.js +10 -10
- package/dist/editor/hooks/useUserMetadata.d.ts +0 -2
- package/dist/editor/hooks/useUserMetadata.js +0 -3
- package/dist/editor/media/AssetCard.d.ts +0 -1
- package/dist/editor/media/AssetCard.js +1 -2
- package/dist/editor/media/CropStep.d.ts +0 -1
- package/dist/editor/media/CropStep.js +0 -1
- package/dist/editor/media/MediaLibrary.d.ts +1 -2
- package/dist/editor/media/MediaLibrary.js +1 -2
- package/dist/editor/media/MediaLibraryBody.d.ts +0 -1
- package/dist/editor/media/MediaLibraryBody.js +0 -1
- package/dist/editor/media/crop-math.d.ts +1 -1
- package/dist/editor/media/crop-math.js +1 -1
- package/dist/editor/permission-manager/GroupSelector.d.ts +0 -3
- package/dist/editor/permission-manager/PermissionEditor.d.ts +0 -4
- package/dist/editor/permission-manager/PermissionEditor.js +0 -1
- package/dist/editor/permission-manager/PermissionLevelBadge.d.ts +0 -7
- package/dist/editor/permission-manager/PermissionLevelBadge.js +0 -4
- package/dist/editor/permission-manager/PermissionTree.d.ts +0 -4
- package/dist/editor/permission-manager/UserSelector.d.ts +0 -3
- package/dist/editor/permission-manager/constants.d.ts +0 -3
- package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts +0 -3
- package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js +0 -10
- package/dist/editor/permission-manager/hooks/usePermissionTree.d.ts +0 -14
- package/dist/editor/permission-manager/hooks/usePermissionTree.js +0 -11
- package/dist/editor/permission-manager/index.d.ts +0 -7
- package/dist/editor/permission-manager/index.js +0 -16
- package/dist/editor/permission-manager/types.d.ts +2 -12
- package/dist/editor/permission-manager/types.js +0 -3
- package/dist/editor/permission-manager/utils.d.ts +0 -28
- package/dist/editor/permission-manager/utils.js +9 -23
- package/dist/editor/preview-bridge.d.ts +5 -1
- package/dist/editor/preview-bridge.js +4 -4
- package/dist/editor/schema-editor/CollectionEditor.d.ts +0 -5
- package/dist/editor/schema-editor/CollectionEditor.js +9 -31
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +0 -6
- package/dist/editor/schema-editor/EntryTypeEditor.js +3 -24
- package/dist/editor/schema-editor/index.d.ts +0 -7
- package/dist/editor/schema-editor/index.js +0 -7
- package/dist/editor/utils/env.d.ts +3 -18
- package/dist/editor/utils/env.js +6 -25
- package/dist/entry-link-resolver.d.ts +13 -35
- package/dist/entry-link-resolver.js +20 -53
- package/dist/entry-schema-registry.d.ts +20 -60
- package/dist/entry-schema-registry.js +20 -69
- package/dist/entry-schema.d.ts +118 -223
- package/dist/entry-schema.js +46 -91
- package/dist/git-manager.d.ts +101 -200
- package/dist/git-manager.js +211 -384
- package/dist/github-service.d.ts +34 -75
- package/dist/github-service.js +37 -91
- package/dist/http/handler.d.ts +6 -37
- package/dist/http/handler.js +65 -120
- package/dist/http/router.d.ts +15 -32
- package/dist/http/router.js +34 -99
- package/dist/http/types.d.ts +15 -40
- package/dist/http/types.js +3 -7
- package/dist/id.d.ts +4 -10
- package/dist/id.js +4 -10
- package/dist/index.js +6 -9
- package/dist/operating-mode/client-safe-strategy.d.ts +7 -14
- package/dist/operating-mode/client-safe-strategy.js +8 -28
- package/dist/operating-mode/client-unsafe-strategy.d.ts +6 -16
- package/dist/operating-mode/client-unsafe-strategy.js +9 -34
- package/dist/operating-mode/client.d.ts +3 -10
- package/dist/operating-mode/client.js +3 -8
- package/dist/operating-mode/deployment-name-fixtures.d.ts +14 -18
- package/dist/operating-mode/deployment-name-fixtures.js +14 -18
- package/dist/operating-mode/deployment-name.d.ts +19 -29
- package/dist/operating-mode/deployment-name.js +29 -47
- package/dist/operating-mode/index.d.ts +4 -25
- package/dist/operating-mode/index.js +9 -31
- package/dist/operating-mode/mode-env.d.ts +32 -54
- package/dist/operating-mode/mode-env.js +36 -58
- package/dist/operating-mode/types.d.ts +24 -72
- package/dist/operating-mode/types.js +1 -7
- package/dist/paths/branch-name.d.ts +30 -40
- package/dist/paths/branch-name.js +30 -40
- package/dist/paths/branch.d.ts +4 -18
- package/dist/paths/branch.js +6 -22
- package/dist/paths/index.d.ts +2 -13
- package/dist/paths/index.js +3 -23
- package/dist/paths/normalize-server.d.ts +3 -15
- package/dist/paths/normalize-server.js +3 -15
- package/dist/paths/normalize.d.ts +13 -33
- package/dist/paths/normalize.js +13 -33
- package/dist/paths/resolve.d.ts +4 -17
- package/dist/paths/resolve.js +4 -22
- package/dist/paths/types.d.ts +12 -34
- package/dist/paths/types.js +5 -7
- package/dist/paths/validation.d.ts +22 -119
- package/dist/paths/validation.js +30 -148
- package/dist/reference-resolver.d.ts +0 -13
- package/dist/reference-resolver.js +0 -18
- package/dist/resolve-canopy-user.d.ts +16 -18
- package/dist/resolve-canopy-user.js +17 -26
- package/dist/resource-generation.d.ts +16 -25
- package/dist/resource-generation.js +34 -97
- package/dist/schema/meta-loader.d.ts +2 -14
- package/dist/schema/meta-loader.js +15 -64
- package/dist/schema/resolver.d.ts +1 -4
- package/dist/schema/resolver.js +1 -6
- package/dist/schema/schema-store-types.d.ts +0 -12
- package/dist/schema/schema-store.d.ts +61 -138
- package/dist/schema/schema-store.js +85 -282
- package/dist/schema/types.d.ts +0 -4
- package/dist/server.d.ts +62 -93
- package/dist/server.js +63 -94
- package/dist/services.d.ts +23 -47
- package/dist/services.js +55 -98
- package/dist/settings-workspace.d.ts +17 -30
- package/dist/settings-workspace.js +51 -81
- package/dist/static/index.d.ts +26 -52
- package/dist/static/index.js +33 -67
- package/dist/{worker/task-queue.d.ts → task-queue/cms-task-queue.d.ts} +7 -9
- package/dist/task-queue/cms-task-queue.js +13 -0
- package/dist/task-queue/task-queue-config.d.ts +3 -0
- package/dist/{worker → task-queue}/task-queue-config.js +1 -6
- package/dist/task-queue/task-queue.d.ts +16 -22
- package/dist/task-queue/task-queue.js +20 -44
- package/dist/task-queue/types.d.ts +0 -1
- package/dist/task-queue/worker-status.d.ts +35 -0
- package/dist/task-queue/worker-status.js +40 -0
- package/dist/types.d.ts +40 -51
- package/dist/url-collision.d.ts +26 -40
- package/dist/url-collision.js +47 -63
- package/dist/url-exclusivity-fixtures.d.ts +15 -20
- package/dist/url-exclusivity-fixtures.js +14 -19
- package/dist/url-path-resolver.d.ts +8 -16
- package/dist/url-path-resolver.js +19 -29
- package/dist/user.d.ts +8 -21
- package/dist/user.js +10 -19
- package/dist/utils/async-mutex.js +4 -6
- package/dist/utils/atomic-write.d.ts +2 -11
- package/dist/utils/atomic-write.js +2 -11
- package/dist/utils/body-field.d.ts +8 -15
- package/dist/utils/body-field.js +8 -15
- package/dist/utils/content-serialize.d.ts +18 -24
- package/dist/utils/content-serialize.js +109 -160
- package/dist/utils/content-write-lock.d.ts +51 -106
- package/dist/utils/content-write-lock.js +66 -111
- package/dist/utils/debug.d.ts +7 -23
- package/dist/utils/debug.js +7 -23
- package/dist/utils/entry-url.d.ts +11 -23
- package/dist/utils/entry-url.js +11 -26
- package/dist/utils/error.d.ts +25 -90
- package/dist/utils/error.js +60 -147
- package/dist/utils/flatten-group-fields.d.ts +5 -7
- package/dist/utils/flatten-group-fields.js +5 -7
- package/dist/utils/format.d.ts +1 -5
- package/dist/utils/format.js +1 -5
- package/dist/utils/fs.d.ts +3 -4
- package/dist/utils/fs.js +3 -4
- package/dist/utils/git.d.ts +52 -83
- package/dist/utils/git.js +79 -125
- package/dist/utils/logger.d.ts +35 -50
- package/dist/utils/logger.js +35 -52
- package/dist/utils/provisioning-lock.d.ts +23 -35
- package/dist/utils/provisioning-lock.js +49 -69
- package/dist/utils/sanitize-href.d.ts +5 -10
- package/dist/utils/sanitize-href.js +8 -18
- package/dist/utils/title-field.d.ts +17 -20
- package/dist/utils/title-field.js +18 -27
- package/dist/utils/typed-filename.d.ts +20 -48
- package/dist/utils/typed-filename.js +25 -61
- package/dist/utils/url-prefix.d.ts +3 -3
- package/dist/utils/url-prefix.js +3 -3
- package/dist/validation/block-structural-keys.d.ts +16 -31
- package/dist/validation/block-structural-keys.js +19 -29
- package/dist/validation/deletion-checker.d.ts +0 -34
- package/dist/validation/deletion-checker.js +1 -43
- package/dist/validation/entry-link-validator.d.ts +6 -13
- package/dist/validation/entry-link-validator.js +4 -13
- package/dist/validation/entry-type-reference-validator.d.ts +13 -18
- package/dist/validation/entry-type-reference-validator.js +13 -18
- package/dist/validation/entry-validator.d.ts +13 -25
- package/dist/validation/entry-validator.js +15 -32
- package/dist/validation/field-traversal.d.ts +3 -30
- package/dist/validation/field-traversal.js +1 -32
- package/dist/validation/reference-validator.d.ts +5 -24
- package/dist/validation/reference-validator.js +5 -30
- package/dist/worker/cms-worker.d.ts +172 -232
- package/dist/worker/cms-worker.js +241 -333
- package/dist/worker/git-sync.d.ts +32 -133
- package/dist/worker/git-sync.js +129 -191
- package/dist/worker/github-auth.d.ts +13 -19
- package/dist/worker/github-auth.js +13 -19
- package/dist/worker/history-rewrite.d.ts +53 -66
- package/dist/worker/history-rewrite.js +54 -60
- package/dist/worker/log.d.ts +24 -41
- package/dist/worker/log.js +25 -42
- package/dist/worker/rebase.d.ts +20 -110
- package/dist/worker/rebase.js +279 -354
- package/dist/worker/task-runner.d.ts +32 -66
- package/dist/worker/task-runner.js +126 -162
- package/dist/worker/worker-context.d.ts +41 -81
- package/package.json +4 -4
- package/dist/cli/template-files/middleware-clerk.ts.template +0 -37
- package/dist/worker/task-queue-config.d.ts +0 -8
- package/dist/worker/task-queue.js +0 -19
- package/dist/worker/worker-status.d.ts +0 -47
- package/dist/worker/worker-status.js +0 -52
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Protected base branch predicate
|
|
2
|
+
* Protected base branch predicate: the single source of truth other modules
|
|
3
|
+
* key off of — do not re-derive the comparison elsewhere.
|
|
3
4
|
*
|
|
4
|
-
* The base branch (the PR base
|
|
5
|
-
* review
|
|
6
|
-
* review
|
|
7
|
-
* editable
|
|
8
|
-
* "Protected Base Branch"). This is the single source of truth other modules
|
|
9
|
-
* key off of; do not re-derive the comparison elsewhere.
|
|
5
|
+
* The base branch (the PR base, usually `main`) can never be submitted for
|
|
6
|
+
* review in either mode, since submitting it would push straight to itself and
|
|
7
|
+
* bypass review, and is read-only in the editor in prod only — dev needs it
|
|
8
|
+
* editable (see ARCHITECTURE.md "Protected Base Branch").
|
|
10
9
|
*/
|
|
11
10
|
import type { CanopyConfig } from '../config/index.js';
|
|
12
11
|
import type { BranchStatus } from '../types.js';
|
|
@@ -28,7 +27,7 @@ export interface BranchWriteProtection extends BranchProtection {
|
|
|
28
27
|
/**
|
|
29
28
|
* True when content writes must be rejected, for ANY of three reasons: the
|
|
30
29
|
* branch is the read-only protected base branch, its workflow status has
|
|
31
|
-
* moved past `'editing'` (locked while
|
|
30
|
+
* moved past `'editing'` (locked while its PR is under review), or its
|
|
32
31
|
* status could not be read at all.
|
|
33
32
|
*/
|
|
34
33
|
writeBlocked: boolean;
|
|
@@ -40,74 +39,46 @@ export interface BranchWriteProtection extends BranchProtection {
|
|
|
40
39
|
* `writeBlocked` does: `status !== 'editing'` is true when `status` is
|
|
41
40
|
* `undefined`).
|
|
42
41
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* `
|
|
46
|
-
*
|
|
47
|
-
* guard reads exactly that, narrow, meaning, and must keep reading it: the
|
|
48
|
-
* guard's whole point is to refuse the base branch regardless of status. If
|
|
49
|
-
* this wider, compound answer had reused the same field name on the
|
|
50
|
-
* subtype, the two meanings would be one property access apart and
|
|
51
|
-
* indistinguishable at every call site -- a future edit anywhere near the
|
|
52
|
-
* guard could silently start reading the wide answer where the narrow one
|
|
53
|
-
* is required (or vice versa) and no type error would catch it, because
|
|
54
|
-
* both are `boolean`. The verbose name is the guard against that: nobody
|
|
55
|
-
* writes `submitBlockedIncludingStatus` by accident.
|
|
42
|
+
* NOT named `submitBlocked`: `BranchProtection.submitBlocked`, the field
|
|
43
|
+
* this type inherits, means ONLY "this is the base branch", and
|
|
44
|
+
* `api/guards.ts`'s `submittableBranch` guard depends on that narrow
|
|
45
|
+
* meaning, so the compound answer gets a distinct name.
|
|
56
46
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* ONLY) plus the status clause, while this is built from `isProtected` /
|
|
60
|
-
* `submitBlocked` (protected base branch, BOTH MODES) plus the same status
|
|
61
|
-
* clause. They are not two spellings of one rule -- in dev, the base branch
|
|
62
|
-
* is writable (`readOnly` is false there, so `writeBlocked` can be false)
|
|
63
|
-
* but still never submittable (`isProtected` is true regardless of mode, so
|
|
64
|
-
* this stays true). A branch can be `writeBlocked: false,
|
|
65
|
-
* submitBlockedIncludingStatus: true` in dev's base branch specifically;
|
|
66
|
-
* collapsing the two fields into one would lose that state.
|
|
47
|
+
* It also differs from `writeBlocked`: in dev the base branch is writable
|
|
48
|
+
* but never submittable, so the two fields disagree there.
|
|
67
49
|
*/
|
|
68
50
|
submitBlockedIncludingStatus: boolean;
|
|
69
51
|
}
|
|
70
52
|
/**
|
|
71
|
-
*
|
|
53
|
+
* Whether `branchName` is the protected base branch for `config`.
|
|
72
54
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
55
|
+
* The comparison is sanitization-aware: branch metadata names are sanitized but
|
|
56
|
+
* `config.defaultBaseBranch` holds the raw git name, so both sides go through
|
|
57
|
+
* `sanitizeBranchName` first.
|
|
76
58
|
*
|
|
77
|
-
* `recordedBaseBranch` (a branch's own
|
|
78
|
-
* fork point
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* base workspace was actually forked from. The clause is purely additive: it
|
|
85
|
-
* only ever adds protection the config clause didn't already grant, so a
|
|
86
|
-
* normal editing branch (`baseBranch !== name`) is never falsely protected.
|
|
59
|
+
* `recordedBaseBranch` (a branch's own recorded fork point) is a second, purely
|
|
60
|
+
* additive clause: a branch whose fork point equals its own name IS a base
|
|
61
|
+
* workspace whatever `config.defaultBaseBranch` says now. In dev that config
|
|
62
|
+
* value tracks live git HEAD (`refreshActiveBranch`) and can drift, and without
|
|
63
|
+
* this clause the drift would silently un-protect the branch the base workspace
|
|
64
|
+
* was forked from; a normal branch (`baseBranch !== name`) is never falsely
|
|
65
|
+
* protected.
|
|
87
66
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
67
|
+
* Answers base-branch questions only (submit/delete/ACL rails). To authorize a
|
|
68
|
+
* content write or render a lock, use {@link getBranchWriteProtection}, which
|
|
69
|
+
* also accounts for workflow status.
|
|
91
70
|
*/
|
|
92
71
|
export declare function getBranchProtection(config: Pick<CanopyConfig, 'mode' | 'defaultBaseBranch'>, branchName: string, recordedBaseBranch?: string): BranchProtection;
|
|
93
72
|
/**
|
|
94
|
-
* {@link getBranchProtection} plus the write decision: writes are blocked on
|
|
95
|
-
* read-only base branch
|
|
96
|
-
*
|
|
97
|
-
*
|
|
73
|
+
* {@link getBranchProtection} plus the write decision: writes are blocked on
|
|
74
|
+
* the read-only base branch and on any branch whose status has left
|
|
75
|
+
* `'editing'` -- the single expression of that rule, read by the API guard, the
|
|
76
|
+
* branches-list wire flag and the editor.
|
|
98
77
|
*
|
|
99
|
-
* `status` is REQUIRED
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* malformed branch metadata is a real, handled condition here (see the
|
|
105
|
-
* corrupt-metadata quarantine in branch-registry/branch-health). A branch whose
|
|
106
|
-
* review state cannot be determined must not be writable.
|
|
107
|
-
*
|
|
108
|
-
* Requiring the parameter is the point: an optional one would make "caller
|
|
109
|
-
* omitted it" and "the file had no status" indistinguishable, and the safe
|
|
110
|
-
* answer differs between them. Callers that genuinely don't care about status
|
|
111
|
-
* call {@link getBranchProtection} instead and get no `writeBlocked` at all.
|
|
78
|
+
* `status` is REQUIRED and admits `undefined` because a missing status must
|
|
79
|
+
* FAIL CLOSED: `branch.json` is read with a bare `JSON.parse(...) as
|
|
80
|
+
* BranchMetadataFile` (branch-metadata.ts), so it can be absent at runtime.
|
|
81
|
+
* Required, not optional, so "argument omitted" and "file had no status" stay
|
|
82
|
+
* distinguishable -- the safe answer differs.
|
|
112
83
|
*/
|
|
113
84
|
export declare function getBranchWriteProtection(config: Pick<CanopyConfig, 'mode' | 'defaultBaseBranch'>, branchName: string, recordedBaseBranch: string | undefined, status: BranchStatus | undefined): BranchWriteProtection;
|
|
@@ -1,38 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Protected base branch predicate
|
|
2
|
+
* Protected base branch predicate: the single source of truth other modules
|
|
3
|
+
* key off of — do not re-derive the comparison elsewhere.
|
|
3
4
|
*
|
|
4
|
-
* The base branch (the PR base
|
|
5
|
-
* review
|
|
6
|
-
* review
|
|
7
|
-
* editable
|
|
8
|
-
* "Protected Base Branch"). This is the single source of truth other modules
|
|
9
|
-
* key off of; do not re-derive the comparison elsewhere.
|
|
5
|
+
* The base branch (the PR base, usually `main`) can never be submitted for
|
|
6
|
+
* review in either mode, since submitting it would push straight to itself and
|
|
7
|
+
* bypass review, and is read-only in the editor in prod only — dev needs it
|
|
8
|
+
* editable (see ARCHITECTURE.md "Protected Base Branch").
|
|
10
9
|
*/
|
|
11
10
|
// branch-name, NOT branch: this module is client-reachable (editor bundle →
|
|
12
11
|
// api/guards.ts → here), and paths/branch.ts drags node:fs into the graph,
|
|
13
12
|
// which breaks adopters' production `next build`.
|
|
14
13
|
import { sanitizeBranchName } from '../paths/branch-name.js';
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
15
|
+
* Whether `branchName` is the protected base branch for `config`.
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* The comparison is sanitization-aware: branch metadata names are sanitized but
|
|
18
|
+
* `config.defaultBaseBranch` holds the raw git name, so both sides go through
|
|
19
|
+
* `sanitizeBranchName` first.
|
|
21
20
|
*
|
|
22
|
-
* `recordedBaseBranch` (a branch's own
|
|
23
|
-
* fork point
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* base workspace was actually forked from. The clause is purely additive: it
|
|
30
|
-
* only ever adds protection the config clause didn't already grant, so a
|
|
31
|
-
* normal editing branch (`baseBranch !== name`) is never falsely protected.
|
|
21
|
+
* `recordedBaseBranch` (a branch's own recorded fork point) is a second, purely
|
|
22
|
+
* additive clause: a branch whose fork point equals its own name IS a base
|
|
23
|
+
* workspace whatever `config.defaultBaseBranch` says now. In dev that config
|
|
24
|
+
* value tracks live git HEAD (`refreshActiveBranch`) and can drift, and without
|
|
25
|
+
* this clause the drift would silently un-protect the branch the base workspace
|
|
26
|
+
* was forked from; a normal branch (`baseBranch !== name`) is never falsely
|
|
27
|
+
* protected.
|
|
32
28
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
29
|
+
* Answers base-branch questions only (submit/delete/ACL rails). To authorize a
|
|
30
|
+
* content write or render a lock, use {@link getBranchWriteProtection}, which
|
|
31
|
+
* also accounts for workflow status.
|
|
36
32
|
*/
|
|
37
33
|
export function getBranchProtection(config, branchName, recordedBaseBranch) {
|
|
38
34
|
const sanitizedName = sanitizeBranchName(branchName);
|
|
@@ -45,24 +41,16 @@ export function getBranchProtection(config, branchName, recordedBaseBranch) {
|
|
|
45
41
|
};
|
|
46
42
|
}
|
|
47
43
|
/**
|
|
48
|
-
* {@link getBranchProtection} plus the write decision: writes are blocked on
|
|
49
|
-
* read-only base branch
|
|
50
|
-
*
|
|
51
|
-
*
|
|
44
|
+
* {@link getBranchProtection} plus the write decision: writes are blocked on
|
|
45
|
+
* the read-only base branch and on any branch whose status has left
|
|
46
|
+
* `'editing'` -- the single expression of that rule, read by the API guard, the
|
|
47
|
+
* branches-list wire flag and the editor.
|
|
52
48
|
*
|
|
53
|
-
* `status` is REQUIRED
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* malformed branch metadata is a real, handled condition here (see the
|
|
59
|
-
* corrupt-metadata quarantine in branch-registry/branch-health). A branch whose
|
|
60
|
-
* review state cannot be determined must not be writable.
|
|
61
|
-
*
|
|
62
|
-
* Requiring the parameter is the point: an optional one would make "caller
|
|
63
|
-
* omitted it" and "the file had no status" indistinguishable, and the safe
|
|
64
|
-
* answer differs between them. Callers that genuinely don't care about status
|
|
65
|
-
* call {@link getBranchProtection} instead and get no `writeBlocked` at all.
|
|
49
|
+
* `status` is REQUIRED and admits `undefined` because a missing status must
|
|
50
|
+
* FAIL CLOSED: `branch.json` is read with a bare `JSON.parse(...) as
|
|
51
|
+
* BranchMetadataFile` (branch-metadata.ts), so it can be absent at runtime.
|
|
52
|
+
* Required, not optional, so "argument omitted" and "file had no status" stay
|
|
53
|
+
* distinguishable -- the safe answer differs.
|
|
66
54
|
*/
|
|
67
55
|
export function getBranchWriteProtection(config, branchName, recordedBaseBranch, status) {
|
|
68
56
|
const protection = getBranchProtection(config, branchName, recordedBaseBranch);
|
|
@@ -1,64 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-host layered locking for settings JSON files (permissions.json,
|
|
3
|
-
* groups.json) in the settings workspace —
|
|
2
|
+
* Cross-host layered locking for the settings JSON files (permissions.json,
|
|
3
|
+
* groups.json) in the settings workspace — one global orphan-git-branch
|
|
4
4
|
* checkout at `{settingsRoot}` shared by every branch (see
|
|
5
5
|
* `api/settings-helpers.ts`'s `getSettingsBranchContext`).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (see branch-metadata.ts):
|
|
7
|
+
* `mutateSettingsJsonFile` composes the three layers docs/concurrency.md owns
|
|
8
|
+
* ("The four layers"; the settings-files row of "Who uses what"): `withLock`
|
|
9
|
+
* on the resolved path, then `withOccFileLock`, then `withOccRetry` around
|
|
10
|
+
* `writeOccJsonFile`, which reloads the file on every attempt. Without all
|
|
11
|
+
* three the write path is an unprotected TOCTOU: two warm Lambda containers
|
|
12
|
+
* are separate NFS clients on EFS, so both can read the same pre-mutation file
|
|
13
|
+
* and the second write silently wins.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
* path. Deterministic same-process serialization.
|
|
18
|
-
* 2. {@link withOccFileLock} - a server-enforced, cross-process/cross-host
|
|
19
|
-
* lock (proper-lockfile, mkdir-based), immune to NFS client dentry/
|
|
20
|
-
* attribute caching. This is the actual fix for a lost permission/group
|
|
21
|
-
* edit across two warm Lambda containers on EFS.
|
|
22
|
-
* 3. {@link withOccRetry} around {@link writeOccJsonFile} - version/writeId
|
|
23
|
-
* based optimistic concurrency control, reloading the file fresh on
|
|
24
|
-
* EVERY retry attempt. With layers 1-2 in place this is defense-in-depth
|
|
25
|
-
* (e.g. a stale process from a rolling deploy writing without the lock),
|
|
26
|
-
* not the primary safety mechanism.
|
|
15
|
+
* What that doc and `utils/occ-json-write.ts` do not cover:
|
|
27
16
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* workspace), permissions.json/groups.json are git-committed on the
|
|
40
|
-
* settings orphan branch. `commitSettings()` (api/settings-helpers.ts)
|
|
41
|
-
* calls `commitToSettingsBranch`, whose `pullCurrentBranch()` merge runs
|
|
42
|
-
* AFTER this helper has released its lock, and that merge can rewrite
|
|
43
|
-
* the file's `version` from upstream. So, unlike branch.json, `version`
|
|
44
|
-
* here is NOT guaranteed monotonic — it remains a correctness aid (it
|
|
45
|
-
* still catches the common same-host and short-window cross-host races)
|
|
46
|
-
* but is advisory/defense-in-depth, not a hard guarantee. The LOCKFILE
|
|
47
|
-
* (layer 2) is the actual cross-host correctness mechanism.
|
|
48
|
-
* (b) The git commit+push deliberately happens OUTSIDE this lock (mirrors
|
|
49
|
-
* branch-metadata.ts keeping registry invalidation outside the lock it
|
|
50
|
-
* protects): committing/pushing is comparatively slow network/process
|
|
51
|
-
* I/O, and holding a mkdir-based lock across it would serialize
|
|
52
|
-
* unrelated requests behind that I/O for no correctness gain — only the
|
|
53
|
-
* write to the working tree needs the lock.
|
|
54
|
-
* (c) Because commit+push is outside the lock, two writers' save-then-commit
|
|
55
|
-
* sequences can interleave (A saves+commits+pushes, B's save lands and
|
|
56
|
-
* commits before A's push is visible, or similar), and the second
|
|
57
|
-
* `git commit` can run against an already-clean tree. Verified benign
|
|
58
|
-
* with simple-git 3.36: its task-error detection depends on stderr
|
|
59
|
-
* output, and a clean-tree `git commit` exits 1 with "nothing to
|
|
60
|
-
* commit" on STDOUT only, so `git.commit()` resolves rather than
|
|
61
|
-
* throwing. Re-verify this on any simple-git upgrade.
|
|
17
|
+
* (a) These files are git-committed, so `version` is NOT monotonic here --
|
|
18
|
+
* `commitSettings()` (api/settings-helpers.ts) calls
|
|
19
|
+
* `commitToSettingsBranch`, whose `pullCurrentBranch()` merge runs after
|
|
20
|
+
* this helper releases its lock and can rewrite `version` from upstream.
|
|
21
|
+
* The lockfile (layer 2) is the cross-host guarantee; OCC is defense.
|
|
22
|
+
* (b) The git commit+push deliberately runs OUTSIDE this lock: it is slow
|
|
23
|
+
* network I/O, and only the working-tree write needs the lock.
|
|
24
|
+
* (c) So two writers' save-then-commit sequences can interleave and the second
|
|
25
|
+
* `git commit` can hit an already-clean tree. Benign with simple-git 3.36,
|
|
26
|
+
* whose task-error detection reads stderr while a clean-tree commit exits
|
|
27
|
+
* 1 with "nothing to commit" on stdout only. Re-verify on upgrade.
|
|
62
28
|
*/
|
|
63
29
|
import { type OccWriteResult } from '../utils/occ-json-write.js';
|
|
64
30
|
/**
|
|
@@ -70,23 +36,19 @@ export declare class SettingsFileConflictError extends Error {
|
|
|
70
36
|
constructor(message?: string);
|
|
71
37
|
}
|
|
72
38
|
/**
|
|
73
|
-
* Thrown by a caller's `mutate`
|
|
74
|
-
* `expectedContentVersion`
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* {@link OccWriteConflictError} as retryable, so this propagates on the
|
|
80
|
-
* very first attempt.
|
|
39
|
+
* Thrown by a caller's `mutate` when a client-supplied
|
|
40
|
+
* `expectedContentVersion` doesn't match the file's current `version`: a real
|
|
41
|
+
* edit conflict the user resolves by reloading, not the transient contention
|
|
42
|
+
* behind {@link SettingsFileConflictError}. It is never retried —
|
|
43
|
+
* {@link withOccRetry} only retries {@link OccWriteConflictError} — so it
|
|
44
|
+
* propagates on the first attempt.
|
|
81
45
|
*/
|
|
82
46
|
export declare class SettingsVersionConflictError extends Error {
|
|
83
47
|
constructor(message?: string);
|
|
84
48
|
}
|
|
85
49
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* `any`. Concrete file types (e.g. `PermissionsFile`, `GroupsFile`) satisfy
|
|
89
|
-
* this automatically since `version` is optional on both.
|
|
50
|
+
* Just enough of a parsed settings file to read its OCC version without `any`.
|
|
51
|
+
* `PermissionsFile` and `GroupsFile` satisfy it — `version` is optional on both.
|
|
90
52
|
*/
|
|
91
53
|
interface VersionedSettingsFile {
|
|
92
54
|
version?: number;
|
|
@@ -97,13 +59,11 @@ export interface MutateSettingsFileOptions<TFile extends VersionedSettingsFile>
|
|
|
97
59
|
/** JSON.parse + zod-parse the raw file contents. Throws propagate untouched (never retried). */
|
|
98
60
|
parse: (raw: string) => TFile;
|
|
99
61
|
/**
|
|
100
|
-
* Compute the next payload from the current parsed file (`null` on
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* `OccWriteConflictError`, which it should never throw itself) propagates
|
|
106
|
-
* out of `mutateSettingsJsonFile` untouched.
|
|
62
|
+
* Compute the next payload from the current parsed file (`null` on ENOENT)
|
|
63
|
+
* and the version to write it under; return `null` for a deliberate no-op.
|
|
64
|
+
* Called once per retry attempt against freshly reloaded state, so it must be
|
|
65
|
+
* safe to call more than once. Anything it throws propagates out untouched,
|
|
66
|
+
* and it must never throw `OccWriteConflictError` itself.
|
|
107
67
|
*/
|
|
108
68
|
mutate: (current: TFile | null, version: number) => Record<string, unknown> | null | Promise<Record<string, unknown> | null>;
|
|
109
69
|
/** Forwarded to writeOccJsonFile. Pass 0 in tests. */
|
|
@@ -112,15 +72,12 @@ export interface MutateSettingsFileOptions<TFile extends VersionedSettingsFile>
|
|
|
112
72
|
maxAttempts?: number;
|
|
113
73
|
}
|
|
114
74
|
/**
|
|
115
|
-
* Run
|
|
116
|
-
*
|
|
117
|
-
* conflict that survives every retry surfaces as
|
|
75
|
+
* Run one load -> mutate -> write cycle under the full lock + OCC-retry stack
|
|
76
|
+
* described in the module doc. A conflict surviving every retry surfaces as
|
|
118
77
|
* {@link SettingsFileConflictError}; everything else — including
|
|
119
78
|
* {@link SettingsVersionConflictError} thrown by `mutate`, and any
|
|
120
|
-
* parse/validation error — propagates untouched.
|
|
121
|
-
*
|
|
122
|
-
* Returns the `writeOccJsonFile` result, or `null` if `mutate` chose a
|
|
123
|
-
* no-op (no write happened).
|
|
79
|
+
* parse/validation error — propagates untouched. Returns the
|
|
80
|
+
* `writeOccJsonFile` result, or `null` if `mutate` chose a no-op.
|
|
124
81
|
*/
|
|
125
82
|
export declare function mutateSettingsJsonFile<TFile extends VersionedSettingsFile>(opts: MutateSettingsFileOptions<TFile>): Promise<OccWriteResult | null>;
|
|
126
83
|
export {};
|
|
@@ -1,64 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-host layered locking for settings JSON files (permissions.json,
|
|
3
|
-
* groups.json) in the settings workspace —
|
|
2
|
+
* Cross-host layered locking for the settings JSON files (permissions.json,
|
|
3
|
+
* groups.json) in the settings workspace — one global orphan-git-branch
|
|
4
4
|
* checkout at `{settingsRoot}` shared by every branch (see
|
|
5
5
|
* `api/settings-helpers.ts`'s `getSettingsBranchContext`).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (see branch-metadata.ts):
|
|
7
|
+
* `mutateSettingsJsonFile` composes the three layers docs/concurrency.md owns
|
|
8
|
+
* ("The four layers"; the settings-files row of "Who uses what"): `withLock`
|
|
9
|
+
* on the resolved path, then `withOccFileLock`, then `withOccRetry` around
|
|
10
|
+
* `writeOccJsonFile`, which reloads the file on every attempt. Without all
|
|
11
|
+
* three the write path is an unprotected TOCTOU: two warm Lambda containers
|
|
12
|
+
* are separate NFS clients on EFS, so both can read the same pre-mutation file
|
|
13
|
+
* and the second write silently wins.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
* path. Deterministic same-process serialization.
|
|
18
|
-
* 2. {@link withOccFileLock} - a server-enforced, cross-process/cross-host
|
|
19
|
-
* lock (proper-lockfile, mkdir-based), immune to NFS client dentry/
|
|
20
|
-
* attribute caching. This is the actual fix for a lost permission/group
|
|
21
|
-
* edit across two warm Lambda containers on EFS.
|
|
22
|
-
* 3. {@link withOccRetry} around {@link writeOccJsonFile} - version/writeId
|
|
23
|
-
* based optimistic concurrency control, reloading the file fresh on
|
|
24
|
-
* EVERY retry attempt. With layers 1-2 in place this is defense-in-depth
|
|
25
|
-
* (e.g. a stale process from a rolling deploy writing without the lock),
|
|
26
|
-
* not the primary safety mechanism.
|
|
15
|
+
* What that doc and `utils/occ-json-write.ts` do not cover:
|
|
27
16
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* workspace), permissions.json/groups.json are git-committed on the
|
|
40
|
-
* settings orphan branch. `commitSettings()` (api/settings-helpers.ts)
|
|
41
|
-
* calls `commitToSettingsBranch`, whose `pullCurrentBranch()` merge runs
|
|
42
|
-
* AFTER this helper has released its lock, and that merge can rewrite
|
|
43
|
-
* the file's `version` from upstream. So, unlike branch.json, `version`
|
|
44
|
-
* here is NOT guaranteed monotonic — it remains a correctness aid (it
|
|
45
|
-
* still catches the common same-host and short-window cross-host races)
|
|
46
|
-
* but is advisory/defense-in-depth, not a hard guarantee. The LOCKFILE
|
|
47
|
-
* (layer 2) is the actual cross-host correctness mechanism.
|
|
48
|
-
* (b) The git commit+push deliberately happens OUTSIDE this lock (mirrors
|
|
49
|
-
* branch-metadata.ts keeping registry invalidation outside the lock it
|
|
50
|
-
* protects): committing/pushing is comparatively slow network/process
|
|
51
|
-
* I/O, and holding a mkdir-based lock across it would serialize
|
|
52
|
-
* unrelated requests behind that I/O for no correctness gain — only the
|
|
53
|
-
* write to the working tree needs the lock.
|
|
54
|
-
* (c) Because commit+push is outside the lock, two writers' save-then-commit
|
|
55
|
-
* sequences can interleave (A saves+commits+pushes, B's save lands and
|
|
56
|
-
* commits before A's push is visible, or similar), and the second
|
|
57
|
-
* `git commit` can run against an already-clean tree. Verified benign
|
|
58
|
-
* with simple-git 3.36: its task-error detection depends on stderr
|
|
59
|
-
* output, and a clean-tree `git commit` exits 1 with "nothing to
|
|
60
|
-
* commit" on STDOUT only, so `git.commit()` resolves rather than
|
|
61
|
-
* throwing. Re-verify this on any simple-git upgrade.
|
|
17
|
+
* (a) These files are git-committed, so `version` is NOT monotonic here --
|
|
18
|
+
* `commitSettings()` (api/settings-helpers.ts) calls
|
|
19
|
+
* `commitToSettingsBranch`, whose `pullCurrentBranch()` merge runs after
|
|
20
|
+
* this helper releases its lock and can rewrite `version` from upstream.
|
|
21
|
+
* The lockfile (layer 2) is the cross-host guarantee; OCC is defense.
|
|
22
|
+
* (b) The git commit+push deliberately runs OUTSIDE this lock: it is slow
|
|
23
|
+
* network I/O, and only the working-tree write needs the lock.
|
|
24
|
+
* (c) So two writers' save-then-commit sequences can interleave and the second
|
|
25
|
+
* `git commit` can hit an already-clean tree. Benign with simple-git 3.36,
|
|
26
|
+
* whose task-error detection reads stderr while a clean-tree commit exits
|
|
27
|
+
* 1 with "nothing to commit" on stdout only. Re-verify on upgrade.
|
|
62
28
|
*/
|
|
63
29
|
import fs from 'node:fs/promises';
|
|
64
30
|
import path from 'node:path';
|
|
@@ -77,14 +43,12 @@ export class SettingsFileConflictError extends Error {
|
|
|
77
43
|
}
|
|
78
44
|
}
|
|
79
45
|
/**
|
|
80
|
-
* Thrown by a caller's `mutate`
|
|
81
|
-
* `expectedContentVersion`
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* {@link OccWriteConflictError} as retryable, so this propagates on the
|
|
87
|
-
* very first attempt.
|
|
46
|
+
* Thrown by a caller's `mutate` when a client-supplied
|
|
47
|
+
* `expectedContentVersion` doesn't match the file's current `version`: a real
|
|
48
|
+
* edit conflict the user resolves by reloading, not the transient contention
|
|
49
|
+
* behind {@link SettingsFileConflictError}. It is never retried —
|
|
50
|
+
* {@link withOccRetry} only retries {@link OccWriteConflictError} — so it
|
|
51
|
+
* propagates on the first attempt.
|
|
88
52
|
*/
|
|
89
53
|
export class SettingsVersionConflictError extends Error {
|
|
90
54
|
constructor(message = 'Settings were modified by another user. Please reload and try again.') {
|
|
@@ -93,17 +57,15 @@ export class SettingsVersionConflictError extends Error {
|
|
|
93
57
|
}
|
|
94
58
|
}
|
|
95
59
|
/**
|
|
96
|
-
* Reload the file fresh and report the version to
|
|
60
|
+
* Reload the file fresh and report the version fed to both `mutate()` and
|
|
97
61
|
* `writeOccJsonFile`'s `expectedVersion`.
|
|
98
62
|
*
|
|
99
|
-
* ENOENT is the ONLY case
|
|
100
|
-
* create-via-link path in {@link writeOccJsonFile})
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* versioned update. (Same contract as `CommentStore`'s `loadWithVersion` in
|
|
106
|
-
* comment-store.ts.)
|
|
63
|
+
* ENOENT is the ONLY case mapping to a `null` `occExpectedVersion` (the
|
|
64
|
+
* create-via-link path in {@link writeOccJsonFile}); an existing file, even one
|
|
65
|
+
* hand-written with no `version` field, maps to `0` and takes the rename-based
|
|
66
|
+
* update path. Conflating them makes `writeOccJsonFile` attempt a `link()`
|
|
67
|
+
* create over a file that exists and fail with EEXIST. (Same contract as
|
|
68
|
+
* `CommentStore`'s `loadWithVersion` in comment-store.ts.)
|
|
107
69
|
*/
|
|
108
70
|
async function loadCurrent(filePath, parse) {
|
|
109
71
|
let raw;
|
|
@@ -120,15 +82,12 @@ async function loadCurrent(filePath, parse) {
|
|
|
120
82
|
return { current: parsed, occExpectedVersion: parsed.version ?? 0 };
|
|
121
83
|
}
|
|
122
84
|
/**
|
|
123
|
-
* Run
|
|
124
|
-
*
|
|
125
|
-
* conflict that survives every retry surfaces as
|
|
85
|
+
* Run one load -> mutate -> write cycle under the full lock + OCC-retry stack
|
|
86
|
+
* described in the module doc. A conflict surviving every retry surfaces as
|
|
126
87
|
* {@link SettingsFileConflictError}; everything else — including
|
|
127
88
|
* {@link SettingsVersionConflictError} thrown by `mutate`, and any
|
|
128
|
-
* parse/validation error — propagates untouched.
|
|
129
|
-
*
|
|
130
|
-
* Returns the `writeOccJsonFile` result, or `null` if `mutate` chose a
|
|
131
|
-
* no-op (no write happened).
|
|
89
|
+
* parse/validation error — propagates untouched. Returns the
|
|
90
|
+
* `writeOccJsonFile` result, or `null` if `mutate` chose a no-op.
|
|
132
91
|
*/
|
|
133
92
|
export async function mutateSettingsJsonFile(opts) {
|
|
134
93
|
const resolved = path.resolve(opts.filePath);
|
|
@@ -1,54 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This module exports all types related to authorization including
|
|
5
|
-
* branch access, path permissions, and content access results.
|
|
6
|
-
*/
|
|
7
|
-
import type { CanopyUserId, CanopyGroupId } from '../types.js';
|
|
8
|
-
export type { CanopyUserId, CanopyGroupId };
|
|
9
|
-
/**
|
|
10
|
-
* A path used in permission rules.
|
|
11
|
-
* SECURITY CRITICAL: Always validated to prevent path traversal.
|
|
12
|
-
* Example: "content/posts" or "content/settings/config"
|
|
2
|
+
* A path used in permission rules, e.g. "content/posts".
|
|
3
|
+
* SECURITY CRITICAL: always validated to prevent path traversal.
|
|
13
4
|
*/
|
|
14
5
|
export type PermissionPath = string & {
|
|
15
6
|
readonly __brand: 'PermissionPath';
|
|
16
7
|
};
|
|
17
|
-
/**
|
|
18
|
-
* Result of checking branch-level access
|
|
19
|
-
*/
|
|
20
8
|
export interface BranchAccessResult {
|
|
21
9
|
allowed: boolean;
|
|
22
10
|
reason: 'privileged' | 'base_branch' | 'creator' | 'allowed_by_acl' | 'denied_by_acl' | 'no_acl';
|
|
23
11
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Result of checking path-level permissions
|
|
26
|
-
*/
|
|
27
12
|
export interface PathPermissionResult {
|
|
28
13
|
allowed: boolean;
|
|
29
14
|
matchedRule?: import('../config/index.js').PathPermission;
|
|
30
15
|
reason?: string;
|
|
31
16
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Combined result of checking both branch and path access
|
|
34
|
-
*/
|
|
35
17
|
export interface ContentAccessResult {
|
|
36
18
|
allowed: boolean;
|
|
37
19
|
branch: BranchAccessResult;
|
|
38
20
|
path: PathPermissionResult;
|
|
39
21
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Dependencies for content access checking
|
|
42
|
-
*/
|
|
43
22
|
export interface ContentAccessDeps {
|
|
44
23
|
checkBranchAccess: (context: import('../types.js').BranchContext, user: import('../user.js').CanopyUser) => BranchAccessResult;
|
|
45
24
|
loadPathPermissions: (branchRoot: string, mode: import('../operating-mode/index.js').OperatingMode) => Promise<import('../config/index.js').PathPermission[]>;
|
|
46
25
|
defaultPathAccess: import('../config/index.js').DefaultPathAccess;
|
|
47
26
|
mode: import('../operating-mode/index.js').OperatingMode;
|
|
48
27
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* Must throw if settings branch cannot be loaded.
|
|
28
|
+
* Settings branch root for loading centralized permissions, in modes with a
|
|
29
|
+
* separate settings branch. Must throw if it cannot be loaded.
|
|
52
30
|
*/
|
|
53
31
|
getSettingsBranchRoot?: () => Promise<string>;
|
|
54
32
|
}
|