canopycms 0.0.68-int.98 → 0.1.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 +2131 -0
- package/README.md +47 -63
- package/dist/ai/generate.d.ts +5 -0
- package/dist/ai/generate.js +244 -115
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/json-to-markdown.d.ts +21 -1
- package/dist/ai/json-to-markdown.js +120 -57
- package/dist/ai/references.d.ts +72 -0
- package/dist/ai/references.js +223 -0
- package/dist/ai/resolve-branch.js +9 -2
- package/dist/ai/to-plain-text.d.ts +17 -8
- package/dist/ai/to-plain-text.js +18 -15
- package/dist/ai/types.d.ts +84 -3
- package/dist/api/admin-branch-health.d.ts +24 -1
- package/dist/api/admin-branch-health.js +52 -20
- package/dist/api/admin.d.ts +23 -5
- package/dist/api/admin.js +36 -1
- package/dist/api/assets.js +88 -78
- package/dist/api/branch-create-window.d.ts +12 -0
- package/dist/api/branch-create-window.js +13 -0
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch-merge.js +1 -1
- package/dist/api/branch-review.d.ts +3 -3
- package/dist/api/branch-review.js +16 -7
- package/dist/api/branch-status.d.ts +16 -6
- package/dist/api/branch-status.js +57 -12
- package/dist/api/branch-withdraw.d.ts +2 -2
- package/dist/api/branch-withdraw.js +8 -3
- package/dist/api/branch.d.ts +25 -12
- package/dist/api/branch.js +164 -103
- package/dist/api/client.d.ts +78 -58
- package/dist/api/client.js +150 -117
- package/dist/api/comments.d.ts +12 -3
- package/dist/api/comments.js +118 -9
- package/dist/api/content.d.ts +9 -9
- package/dist/api/content.js +131 -22
- package/dist/api/entries-constants.d.ts +14 -3
- package/dist/api/entries-constants.js +14 -3
- package/dist/api/entries.d.ts +40 -2
- package/dist/api/entries.js +102 -6
- package/dist/api/github-sync.d.ts +19 -0
- package/dist/api/github-sync.js +50 -3
- package/dist/api/groups.d.ts +4 -4
- package/dist/api/groups.js +2 -1
- package/dist/api/guards.js +23 -3
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +1 -0
- package/dist/api/permissions.d.ts +21 -2
- package/dist/api/permissions.js +50 -3
- package/dist/api/request-deadline.d.ts +59 -0
- package/dist/api/request-deadline.js +73 -0
- package/dist/api/resolve-references.js +8 -7
- package/dist/api/route-builder.d.ts +14 -0
- package/dist/api/route-builder.js +14 -1
- package/dist/api/schema.d.ts +11 -9
- package/dist/api/schema.js +2 -0
- package/dist/api/settings-helpers.d.ts +5 -1
- package/dist/api/settings-helpers.js +10 -2
- package/dist/api/types.d.ts +18 -0
- package/dist/api/users-constants.d.ts +6 -0
- package/dist/api/users-constants.js +6 -0
- package/dist/assets/asset-url.d.ts +28 -16
- package/dist/assets/asset-url.js +51 -25
- package/dist/assets/finalize.js +3 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +153 -0
- package/dist/assets/materialize.js +402 -0
- package/dist/assets/store-local.d.ts +19 -5
- package/dist/assets/store-local.js +69 -10
- package/dist/assets/store-s3.d.ts +38 -7
- package/dist/assets/store-s3.js +128 -22
- package/dist/assets/transform-directives.d.ts +52 -13
- package/dist/assets/transform-directives.js +87 -27
- package/dist/assets/transform.d.ts +3 -3
- package/dist/assets/transform.js +13 -8
- package/dist/assets/types.d.ts +31 -4
- package/dist/auth/cache.d.ts +1 -1
- package/dist/auth/cache.js +1 -1
- package/dist/auth/caching-auth-plugin.d.ts +2 -0
- package/dist/auth/caching-auth-plugin.js +12 -2
- package/dist/auth/file-based-auth-cache.d.ts +8 -0
- package/dist/auth/file-based-auth-cache.js +18 -8
- package/dist/auth/person-name.d.ts +5 -0
- package/dist/auth/person-name.js +8 -0
- package/dist/auth/plugin.d.ts +10 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/auth/user-metadata-lookup.d.ts +21 -0
- package/dist/auth/user-metadata-lookup.js +116 -0
- package/dist/authorization/branch.d.ts +1 -1
- package/dist/authorization/branch.js +12 -13
- package/dist/authorization/groups/schema.d.ts +4 -4
- package/dist/authorization/helpers.d.ts +8 -0
- package/dist/authorization/helpers.js +8 -0
- package/dist/authorization/index.d.ts +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/authorization/protected-branch.d.ts +5 -4
- package/dist/authorization/protected-branch.js +7 -5
- package/dist/branch-health.d.ts +35 -8
- package/dist/branch-health.js +54 -18
- package/dist/branch-metadata-error.d.ts +27 -0
- package/dist/branch-metadata-error.js +23 -0
- package/dist/branch-metadata-file.d.ts +205 -24
- package/dist/branch-metadata-file.js +126 -25
- package/dist/branch-metadata.d.ts +45 -6
- package/dist/branch-metadata.js +142 -50
- package/dist/branch-provisioning.d.ts +167 -0
- package/dist/branch-provisioning.js +501 -0
- package/dist/branch-registry.d.ts +8 -0
- package/dist/branch-registry.js +61 -9
- package/dist/branch-schema-cache.d.ts +29 -12
- package/dist/branch-schema-cache.js +64 -21
- package/dist/branch-sparse.d.ts +23 -0
- package/dist/branch-sparse.js +118 -0
- package/dist/branch-workspace.d.ts +42 -3
- package/dist/branch-workspace.js +239 -100
- package/dist/build/asset-refs.d.ts +94 -0
- package/dist/build/asset-refs.js +267 -0
- package/dist/build/generate-ai-content.js +5 -0
- package/dist/build/inline-flight.d.ts +37 -0
- package/dist/build/inline-flight.js +142 -0
- package/dist/cli/asset-refs.d.ts +52 -0
- package/dist/cli/asset-refs.js +194 -0
- package/dist/cli/cli.d.ts +15 -1
- package/dist/cli/cli.js +8543 -3193
- package/dist/cli/configured-asset-store.d.ts +12 -0
- package/dist/cli/configured-asset-store.js +46 -0
- package/dist/cli/generate-ai-content.js +4244 -1072
- package/dist/cli/init.js +217 -126
- package/dist/cli/project-detect.d.ts +2 -0
- package/dist/cli/project-detect.js +2 -0
- package/dist/cli/template-files/Dockerfile.cms.template +13 -5
- package/dist/cli/template-files/cms-stack.ts.template +16 -13
- package/dist/client.d.ts +1 -2
- package/dist/client.js +1 -2
- package/dist/comment-store.d.ts +25 -0
- package/dist/comment-store.js +43 -8
- package/dist/config/flatten.js +2 -0
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schemas/config.d.ts +12 -29
- package/dist/config/schemas/config.js +9 -12
- package/dist/config/schemas/field.d.ts +3 -0
- package/dist/config/schemas/field.js +58 -4
- package/dist/config/schemas/media.d.ts +4 -13
- package/dist/config/schemas/media.js +5 -8
- package/dist/config/schemas/url.d.ts +2 -20
- package/dist/config/schemas/url.js +2 -25
- package/dist/config/types.d.ts +116 -22
- package/dist/config/types.js +3 -9
- package/dist/config/validation.d.ts +25 -4
- package/dist/config/validation.js +116 -3
- package/dist/content-id-index.js +2 -2
- package/dist/content-index-generation.d.ts +1 -1
- package/dist/content-index-generation.js +1 -1
- package/dist/content-listing.d.ts +10 -1
- package/dist/content-listing.js +22 -5
- package/dist/content-reader.d.ts +9 -0
- package/dist/content-reader.js +16 -11
- package/dist/content-store.d.ts +75 -12
- package/dist/content-store.js +249 -86
- package/dist/content-tree.js +2 -2
- package/dist/context.js +5 -2
- package/dist/dangling-reference-log.d.ts +17 -0
- package/dist/dangling-reference-log.js +29 -0
- package/dist/dev-content-watcher.d.ts +1 -1
- package/dist/dev-content-watcher.js +1 -1
- package/dist/editor/BranchManager.d.ts +23 -7
- package/dist/editor/BranchManager.js +119 -54
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +1 -2
- package/dist/editor/CommentsPanel.d.ts +14 -0
- package/dist/editor/CommentsPanel.js +68 -43
- package/dist/editor/Editor.d.ts +11 -5
- package/dist/editor/Editor.js +891 -218
- package/dist/editor/EditorAuthGate.d.ts +7 -1
- package/dist/editor/EditorAuthGate.js +41 -9
- package/dist/editor/EntryNavigator.d.ts +22 -0
- package/dist/editor/EntryNavigator.js +353 -105
- package/dist/editor/FormHeader.d.ts +25 -0
- package/dist/editor/FormHeader.js +82 -0
- package/dist/editor/FormRenderer.d.ts +27 -3
- package/dist/editor/FormRenderer.js +171 -76
- package/dist/editor/PreviewFrame.d.ts +40 -0
- package/dist/editor/PreviewFrame.js +215 -0
- package/dist/editor/PreviewPane.d.ts +47 -0
- package/dist/editor/PreviewPane.js +147 -0
- package/dist/editor/PreviewToolbarTools.d.ts +22 -0
- package/dist/editor/PreviewToolbarTools.js +73 -0
- package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
- package/dist/editor/admin/SystemHealthPanel.js +163 -33
- package/dist/editor/admin/useSystemHealth.d.ts +18 -0
- package/dist/editor/admin/useSystemHealth.js +70 -1
- package/dist/editor/branch-status.d.ts +17 -0
- package/dist/editor/branch-status.js +29 -0
- package/dist/editor/canopy-path.d.ts +9 -6
- package/dist/editor/canopy-path.js +8 -11
- package/dist/editor/client-reference-resolver.d.ts +43 -16
- package/dist/editor/client-reference-resolver.js +155 -64
- package/dist/editor/comments/BranchComments.d.ts +1 -1
- package/dist/editor/comments/BranchComments.js +2 -2
- package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
- package/dist/editor/comments/CloseRefusedNotice.js +7 -0
- package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
- package/dist/editor/comments/CommentThreadsPopover.js +274 -0
- package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
- package/dist/editor/comments/CommentWriteAlert.js +13 -0
- package/dist/editor/comments/EntryComments.d.ts +10 -4
- package/dist/editor/comments/EntryComments.js +14 -17
- package/dist/editor/comments/FieldWrapper.d.ts +18 -4
- package/dist/editor/comments/FieldWrapper.js +62 -28
- package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
- package/dist/editor/comments/InlineCommentThread.js +11 -47
- package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
- package/dist/editor/comments/ThreadCarousel.js +18 -21
- package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
- package/dist/editor/comments/ThreadReplyControls.js +92 -0
- package/dist/editor/comments/field-comment-context.d.ts +14 -0
- package/dist/editor/comments/field-comment-context.js +14 -0
- package/dist/editor/comments/form-comments.d.ts +48 -0
- package/dist/editor/comments/form-comments.js +178 -0
- package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
- package/dist/editor/comments/item-anchor-copy.js +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
- package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
- package/dist/editor/comments/popover-placement.d.ts +18 -0
- package/dist/editor/comments/popover-placement.js +58 -0
- package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
- package/dist/editor/comments/use-tracked-writes.js +53 -0
- package/dist/editor/components/BranchAccessList.d.ts +15 -0
- package/dist/editor/components/BranchAccessList.js +52 -0
- package/dist/editor/components/BranchLockBanner.d.ts +22 -0
- package/dist/editor/components/BranchLockBanner.js +39 -0
- package/dist/editor/components/BranchPill.d.ts +32 -0
- package/dist/editor/components/BranchPill.js +33 -0
- package/dist/editor/components/BranchesDrawer.d.ts +11 -0
- package/dist/editor/components/BranchesDrawer.js +5 -0
- package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
- package/dist/editor/components/ConfirmDeleteModal.js +3 -2
- package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
- package/dist/editor/components/DestructiveMenuItem.js +8 -0
- package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
- package/dist/editor/components/EditorCrashScreen.js +22 -0
- package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
- package/dist/editor/components/EditorErrorBoundary.js +40 -0
- package/dist/editor/components/EditorHeader.d.ts +56 -106
- package/dist/editor/components/EditorHeader.js +130 -131
- package/dist/editor/components/EditorRail.d.ts +49 -0
- package/dist/editor/components/EditorRail.js +65 -0
- package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
- package/dist/editor/components/EntryBreadcrumb.js +56 -0
- package/dist/editor/components/EntryCreateModal.d.ts +12 -7
- package/dist/editor/components/EntryCreateModal.js +51 -57
- package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
- package/dist/editor/components/NoEditPermissionNotice.js +5 -4
- package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
- package/dist/editor/components/QuickOpenPalette.js +66 -0
- package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
- package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
- package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
- package/dist/editor/components/ReasonedMenuItem.js +15 -0
- package/dist/editor/components/ReferencedByList.d.ts +11 -0
- package/dist/editor/components/ReferencedByList.js +28 -0
- package/dist/editor/components/RelativeTime.d.ts +10 -0
- package/dist/editor/components/RelativeTime.js +10 -0
- package/dist/editor/components/RenameEntryModal.d.ts +9 -1
- package/dist/editor/components/RenameEntryModal.js +19 -31
- package/dist/editor/components/RetryToastMessage.d.ts +9 -0
- package/dist/editor/components/RetryToastMessage.js +6 -0
- package/dist/editor/components/SaveAllResult.d.ts +25 -0
- package/dist/editor/components/SaveAllResult.js +86 -0
- package/dist/editor/components/SaveSplitButton.d.ts +30 -0
- package/dist/editor/components/SaveSplitButton.js +108 -0
- package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
- package/dist/editor/components/StagedChangesDrawer.js +92 -0
- package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
- package/dist/editor/components/UnavailableEntryNotice.js +9 -0
- package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
- package/dist/editor/components/UnavailableTypeMessage.js +9 -0
- package/dist/editor/components/UndoToastMessage.d.ts +18 -0
- package/dist/editor/components/UndoToastMessage.js +10 -0
- package/dist/editor/components/UserBadge.d.ts +2 -5
- package/dist/editor/components/UserBadge.js +62 -33
- package/dist/editor/components/ValidationSummary.d.ts +14 -0
- package/dist/editor/components/ValidationSummary.js +24 -0
- package/dist/editor/components/index.d.ts +2 -2
- package/dist/editor/components/index.js +2 -2
- package/dist/editor/context/ApiClientContext.d.ts +8 -2
- package/dist/editor/context/ApiClientContext.js +19 -8
- package/dist/editor/context/AssetContext.d.ts +13 -35
- package/dist/editor/context/AssetContext.js +12 -31
- package/dist/editor/context/EditorStateContext.d.ts +4 -4
- package/dist/editor/context/EditorStateContext.js +4 -4
- package/dist/editor/context/SWRProvider.d.ts +3 -3
- package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
- package/dist/editor/context/SiteMdxAllowContext.js +7 -0
- package/dist/editor/context/index.d.ts +2 -1
- package/dist/editor/context/index.js +2 -1
- package/dist/editor/contrast.d.ts +19 -0
- package/dist/editor/contrast.js +41 -0
- package/dist/editor/copy.d.ts +18 -0
- package/dist/editor/copy.js +18 -0
- package/dist/editor/editor-config.js +16 -0
- package/dist/editor/editor-utils.d.ts +34 -8
- package/dist/editor/editor-utils.js +42 -20
- package/dist/editor/entry-search.d.ts +20 -0
- package/dist/editor/entry-search.js +34 -0
- package/dist/editor/field-breadcrumb.d.ts +12 -0
- package/dist/editor/field-breadcrumb.js +85 -0
- package/dist/editor/field-props.d.ts +86 -0
- package/dist/editor/field-props.js +27 -0
- package/dist/editor/field-target.d.ts +30 -0
- package/dist/editor/field-target.js +77 -0
- package/dist/editor/fields/BlockField.d.ts +12 -0
- package/dist/editor/fields/BlockField.js +89 -73
- package/dist/editor/fields/CodeField.d.ts +2 -0
- package/dist/editor/fields/CodeField.js +6 -2
- package/dist/editor/fields/DateTimeField.d.ts +4 -29
- package/dist/editor/fields/DateTimeField.js +79 -41
- package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
- package/dist/editor/fields/ExpandAllButton.js +4 -0
- package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
- package/dist/editor/fields/FieldCrashFallback.js +30 -0
- package/dist/editor/fields/FieldLabel.d.ts +37 -0
- package/dist/editor/fields/FieldLabel.js +19 -0
- package/dist/editor/fields/ImageField.d.ts +4 -0
- package/dist/editor/fields/ImageField.js +29 -14
- package/dist/editor/fields/InlineGroupField.js +4 -3
- package/dist/editor/fields/ItemList.d.ts +42 -0
- package/dist/editor/fields/ItemList.js +130 -0
- package/dist/editor/fields/MarkdownField.d.ts +32 -0
- package/dist/editor/fields/MarkdownField.js +223 -46
- package/dist/editor/fields/MdxImageDialog.js +1 -1
- package/dist/editor/fields/NumberField.d.ts +2 -0
- package/dist/editor/fields/NumberField.js +8 -5
- package/dist/editor/fields/NumberListField.d.ts +2 -0
- package/dist/editor/fields/NumberListField.js +19 -16
- package/dist/editor/fields/ObjectField.d.ts +2 -7
- package/dist/editor/fields/ObjectField.js +15 -5
- package/dist/editor/fields/ObjectListField.d.ts +29 -0
- package/dist/editor/fields/ObjectListField.js +57 -0
- package/dist/editor/fields/ReferenceField.d.ts +2 -0
- package/dist/editor/fields/ReferenceField.js +34 -9
- package/dist/editor/fields/SelectField.d.ts +2 -0
- package/dist/editor/fields/SelectField.js +11 -2
- package/dist/editor/fields/StringListField.d.ts +4 -1
- package/dist/editor/fields/StringListField.js +22 -8
- package/dist/editor/fields/TextField.d.ts +2 -0
- package/dist/editor/fields/TextField.js +6 -2
- package/dist/editor/fields/ToggleField.d.ts +2 -0
- package/dist/editor/fields/ToggleField.js +8 -3
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
- package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
- package/dist/editor/fields/form-expansion.d.ts +9 -0
- package/dist/editor/fields/form-expansion.js +2 -0
- package/dist/editor/fields/item-expansion.d.ts +15 -0
- package/dist/editor/fields/item-expansion.js +60 -0
- package/dist/editor/fields/list-item-keys.d.ts +8 -0
- package/dist/editor/fields/list-item-keys.js +50 -0
- package/dist/editor/fields/list-item-title.d.ts +20 -0
- package/dist/editor/fields/list-item-title.js +85 -0
- package/dist/editor/fields/markdown-export-options.d.ts +2 -0
- package/dist/editor/fields/markdown-export-options.js +8 -0
- package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
- package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
- package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
- package/dist/editor/fields/mdx-jsx-support.js +157 -69
- package/dist/editor/fields/rich-text-failures.d.ts +5 -0
- package/dist/editor/fields/rich-text-failures.js +26 -0
- package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
- package/dist/editor/fields/toolbar-wrap.js +35 -0
- package/dist/editor/form-outline.d.ts +13 -0
- package/dist/editor/form-outline.js +70 -0
- package/dist/editor/group-manager/GroupCard.d.ts +4 -1
- package/dist/editor/group-manager/GroupCard.js +12 -3
- package/dist/editor/group-manager/GroupForm.js +1 -1
- package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
- package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
- package/dist/editor/group-manager/MemberList.d.ts +2 -1
- package/dist/editor/group-manager/MemberList.js +15 -4
- package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
- package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
- package/dist/editor/group-manager/index.js +34 -5
- package/dist/editor/group-manager/types.d.ts +5 -2
- package/dist/editor/hooks/create-branch-request.d.ts +20 -0
- package/dist/editor/hooks/create-branch-request.js +54 -0
- package/dist/editor/hooks/save-all.d.ts +68 -0
- package/dist/editor/hooks/save-all.js +61 -0
- package/dist/editor/hooks/useBranchActions.d.ts +11 -1
- package/dist/editor/hooks/useBranchActions.js +42 -19
- package/dist/editor/hooks/useBranchManager.d.ts +21 -0
- package/dist/editor/hooks/useBranchManager.js +321 -125
- package/dist/editor/hooks/useBranchesData.d.ts +8 -4
- package/dist/editor/hooks/useBranchesData.js +9 -5
- package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
- package/dist/editor/hooks/useCommentSystem.js +79 -55
- package/dist/editor/hooks/useCommentsData.d.ts +11 -7
- package/dist/editor/hooks/useCommentsData.js +20 -19
- package/dist/editor/hooks/useDraftManager.d.ts +77 -3
- package/dist/editor/hooks/useDraftManager.js +887 -94
- package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
- package/dist/editor/hooks/useEditorLayout.js +98 -20
- package/dist/editor/hooks/useEntriesData.d.ts +2 -1
- package/dist/editor/hooks/useEntriesData.js +18 -4
- package/dist/editor/hooks/useEntryManager.d.ts +41 -2
- package/dist/editor/hooks/useEntryManager.js +227 -41
- package/dist/editor/hooks/useGroupManager.js +13 -16
- package/dist/editor/hooks/usePermissionManager.js +14 -6
- package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
- package/dist/editor/hooks/usePreviewMarks.js +33 -0
- package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
- package/dist/editor/hooks/useReferenceResolution.js +34 -177
- package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
- package/dist/editor/hooks/useSchemaManager.js +97 -39
- package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
- package/dist/editor/hooks/useUserMetadata.js +76 -47
- package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
- package/dist/editor/hooks/user-metadata-batcher.js +33 -0
- package/dist/editor/media/AssetCard.d.ts +6 -7
- package/dist/editor/media/AssetCard.js +24 -31
- package/dist/editor/media/AssetListRow.d.ts +16 -0
- package/dist/editor/media/AssetListRow.js +41 -0
- package/dist/editor/media/CropStep.js +6 -1
- package/dist/editor/media/MediaLibrary.d.ts +4 -3
- package/dist/editor/media/MediaLibrary.js +11 -4
- package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
- package/dist/editor/media/MediaLibraryBody.js +195 -80
- package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
- package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
- package/dist/editor/media/crop-math.d.ts +3 -4
- package/dist/editor/media/crop-math.js +11 -22
- package/dist/editor/media/editor-image-src.d.ts +8 -0
- package/dist/editor/media/editor-image-src.js +10 -0
- package/dist/editor/media/format-bytes.d.ts +2 -0
- package/dist/editor/media/format-bytes.js +23 -0
- package/dist/editor/media/media-library-prefs.d.ts +23 -0
- package/dist/editor/media/media-library-prefs.js +49 -0
- package/dist/editor/media/media-library-view.d.ts +34 -0
- package/dist/editor/media/media-library-view.js +40 -0
- package/dist/editor/media/useAssetLibrary.d.ts +30 -0
- package/dist/editor/media/useAssetLibrary.js +126 -0
- package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
- package/dist/editor/media/useStableAssetOrder.js +37 -0
- package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
- package/dist/editor/media/useThumbnailFailure.js +16 -0
- package/dist/editor/permission-manager/GroupSelector.js +2 -2
- package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
- package/dist/editor/permission-manager/PermissionEditor.js +15 -4
- package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
- package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
- package/dist/editor/permission-manager/UserSelector.js +3 -3
- package/dist/editor/permission-manager/index.js +12 -3
- package/dist/editor/permission-manager/types.d.ts +5 -2
- package/dist/editor/platform.d.ts +3 -0
- package/dist/editor/platform.js +9 -0
- package/dist/editor/preview-asset-base.d.ts +9 -0
- package/dist/editor/preview-asset-base.js +17 -0
- package/dist/editor/preview-bridge.d.ts +51 -27
- package/dist/editor/preview-bridge.js +103 -144
- package/dist/editor/preview-marks.d.ts +15 -0
- package/dist/editor/preview-marks.js +74 -0
- package/dist/editor/preview-path.d.ts +12 -0
- package/dist/editor/preview-path.js +45 -1
- package/dist/editor/preview-scale.d.ts +27 -0
- package/dist/editor/preview-scale.js +34 -0
- package/dist/editor/raw-asset-base.d.ts +5 -0
- package/dist/editor/raw-asset-base.js +14 -0
- package/dist/editor/relative-time.d.ts +4 -4
- package/dist/editor/relative-time.js +16 -8
- package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
- package/dist/editor/schema-editor/CollectionEditor.js +361 -62
- package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
- package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
- package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
- package/dist/editor/schema-editor/collection-draft.js +40 -0
- package/dist/editor/schema-editor/index.d.ts +1 -1
- package/dist/editor/schema-label.d.ts +10 -0
- package/dist/editor/schema-label.js +17 -0
- package/dist/editor/settings-conflict.d.ts +5 -0
- package/dist/editor/settings-conflict.js +8 -0
- package/dist/editor/shell/ActivityRail.d.ts +37 -0
- package/dist/editor/shell/ActivityRail.js +91 -0
- package/dist/editor/shell/EditorShell.d.ts +17 -0
- package/dist/editor/shell/EditorShell.js +31 -0
- package/dist/editor/shell/PaneLayout.d.ts +28 -0
- package/dist/editor/shell/PaneLayout.js +116 -0
- package/dist/editor/shell/SidePanelHost.d.ts +35 -0
- package/dist/editor/shell/SidePanelHost.js +184 -0
- package/dist/editor/shell/TopBar.d.ts +12 -0
- package/dist/editor/shell/TopBar.js +14 -0
- package/dist/editor/shell/host-chrome.d.ts +16 -0
- package/dist/editor/shell/host-chrome.js +69 -0
- package/dist/editor/slug.d.ts +16 -0
- package/dist/editor/slug.js +43 -0
- package/dist/editor/theme.d.ts +2 -1
- package/dist/editor/theme.js +314 -13
- package/dist/editor/unanswered.d.ts +17 -0
- package/dist/editor/unanswered.js +26 -0
- package/dist/editor/unavailable-entry-type.d.ts +20 -0
- package/dist/editor/unavailable-entry-type.js +28 -0
- package/dist/editor/undo-removal.d.ts +57 -0
- package/dist/editor/undo-removal.js +163 -0
- package/dist/editor/user-display.d.ts +4 -0
- package/dist/editor/user-display.js +5 -0
- package/dist/editor/utils/confirm-modal.d.ts +8 -0
- package/dist/editor/utils/confirm-modal.js +12 -0
- package/dist/editor/utils/editor-errors.d.ts +19 -0
- package/dist/editor/utils/editor-errors.js +32 -0
- package/dist/editor/utils/focus-return.d.ts +56 -0
- package/dist/editor/utils/focus-return.js +106 -0
- package/dist/entry-link-resolver.d.ts +11 -1
- package/dist/entry-link-resolver.js +65 -5
- package/dist/entry-schema-registry.d.ts +3 -3
- package/dist/entry-schema-registry.js +7 -4
- package/dist/entry-schema.d.ts +49 -3
- package/dist/entry-schema.js +18 -0
- package/dist/git-manager.d.ts +172 -10
- package/dist/git-manager.js +449 -107
- package/dist/github-service.d.ts +39 -2
- package/dist/github-service.js +101 -7
- package/dist/http/handler.js +74 -9
- package/dist/http/types.d.ts +2 -0
- package/dist/http/worker-not-ready.d.ts +9 -4
- package/dist/http/worker-not-ready.js +59 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/item-keys.d.ts +46 -0
- package/dist/item-keys.js +235 -0
- package/dist/operating-mode/editor-mode-check.d.ts +15 -0
- package/dist/operating-mode/editor-mode-check.js +24 -0
- package/dist/operating-mode/mode-env.d.ts +13 -14
- package/dist/operating-mode/mode-env.js +13 -14
- package/dist/operating-mode/types.d.ts +2 -2
- package/dist/paths/branch.d.ts +0 -1
- package/dist/paths/branch.js +1 -8
- package/dist/paths/index.d.ts +1 -1
- package/dist/paths/index.js +1 -1
- package/dist/paths/validation.js +0 -2
- package/dist/preview.d.ts +8 -0
- package/dist/preview.js +7 -0
- package/dist/reference-resolver.js +1 -0
- package/dist/resolve-canopy-user.d.ts +1 -1
- package/dist/resolve-canopy-user.js +1 -1
- package/dist/schema/meta-loader.d.ts +18 -8
- package/dist/schema/meta-loader.js +42 -9
- package/dist/schema/registry-fingerprint.d.ts +14 -0
- package/dist/schema/registry-fingerprint.js +63 -0
- package/dist/schema/resolver.d.ts +7 -4
- package/dist/schema/resolver.js +7 -6
- package/dist/schema/schema-store.d.ts +7 -10
- package/dist/schema/schema-store.js +13 -11
- package/dist/schema/schema-unavailable-error.d.ts +11 -0
- package/dist/schema/schema-unavailable-error.js +15 -0
- package/dist/schema/types.d.ts +22 -0
- package/dist/schema-registry-record.d.ts +35 -0
- package/dist/schema-registry-record.js +134 -0
- package/dist/server.d.ts +79 -9
- package/dist/server.js +62 -8
- package/dist/services.d.ts +33 -5
- package/dist/services.js +155 -37
- package/dist/settings-workspace.d.ts +23 -3
- package/dist/settings-workspace.js +159 -45
- package/dist/stamp-on-publish.d.ts +36 -0
- package/dist/stamp-on-publish.js +71 -0
- package/dist/static/index.d.ts +58 -5
- package/dist/static/index.js +133 -3
- package/dist/static/seo.d.ts +5 -5
- package/dist/static/seo.js +5 -5
- package/dist/submission-attribution.d.ts +8 -0
- package/dist/submission-attribution.js +22 -0
- package/dist/system-users.d.ts +12 -0
- package/dist/system-users.js +12 -0
- package/dist/task-queue/cms-task-queue.d.ts +1 -1
- package/dist/task-queue/cms-task-queue.js +1 -1
- package/dist/task-queue/index.d.ts +1 -1
- package/dist/task-queue/index.js +1 -1
- package/dist/task-queue/task-queue.d.ts +6 -0
- package/dist/task-queue/task-queue.js +29 -0
- package/dist/task-queue/worker-status.d.ts +35 -9
- package/dist/task-queue/worker-status.js +84 -17
- package/dist/types.d.ts +90 -9
- package/dist/url-collision.js +3 -3
- package/dist/url-exclusivity-fixtures.d.ts +8 -1
- package/dist/url-exclusivity-fixtures.js +11 -1
- package/dist/user.js +2 -1
- package/dist/utils/atomic-write.js +2 -1
- package/dist/utils/base-branch.d.ts +13 -0
- package/dist/utils/base-branch.js +23 -0
- package/dist/utils/content-serialize.d.ts +15 -7
- package/dist/utils/content-serialize.js +353 -51
- package/dist/utils/content-write-lock.d.ts +6 -1
- package/dist/utils/content-write-lock.js +21 -6
- package/dist/utils/debug.d.ts +1 -1
- package/dist/utils/debug.js +5 -3
- package/dist/utils/error.js +13 -2
- package/dist/utils/escape-text.d.ts +7 -0
- package/dist/utils/escape-text.js +27 -0
- package/dist/utils/fs.d.ts +12 -4
- package/dist/utils/fs.js +18 -5
- package/dist/utils/git.d.ts +21 -1
- package/dist/utils/git.js +63 -8
- package/dist/utils/json-source-splice.d.ts +14 -0
- package/dist/utils/json-source-splice.js +120 -0
- package/dist/utils/markdown-body-splice.d.ts +39 -0
- package/dist/utils/markdown-body-splice.js +421 -0
- package/dist/utils/occ-json-write.js +18 -12
- package/dist/utils/provision-log.d.ts +25 -0
- package/dist/utils/provision-log.js +49 -0
- package/dist/utils/provisioning-lock.d.ts +32 -1
- package/dist/utils/provisioning-lock.js +78 -32
- package/dist/utils/sanitize-href.d.ts +9 -23
- package/dist/utils/sanitize-href.js +14 -28
- package/dist/utils/title-field.d.ts +10 -0
- package/dist/utils/title-field.js +47 -0
- package/dist/utils/yaml-source-splice.d.ts +58 -0
- package/dist/utils/yaml-source-splice.js +515 -0
- package/dist/validation/block-structural-keys.d.ts +1 -1
- package/dist/validation/block-structural-keys.js +1 -1
- package/dist/validation/deletion-checker.d.ts +21 -29
- package/dist/validation/deletion-checker.js +41 -117
- package/dist/validation/entry-validator.d.ts +6 -10
- package/dist/validation/entry-validator.js +19 -26
- package/dist/validation/field-traversal.d.ts +22 -0
- package/dist/validation/field-traversal.js +39 -8
- package/dist/validation/markdown-safety.d.ts +68 -0
- package/dist/validation/markdown-safety.js +596 -0
- package/dist/validation/mdx-allowlist.d.ts +55 -0
- package/dist/validation/mdx-allowlist.js +183 -0
- package/dist/validation/reference-validator.d.ts +2 -0
- package/dist/validation/reference-validator.js +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-policy.d.ts +124 -0
- package/dist/worker/branch-policy.js +243 -0
- package/dist/worker/bundle-header.d.ts +117 -0
- package/dist/worker/bundle-header.js +253 -0
- package/dist/worker/cms-worker.d.ts +236 -142
- package/dist/worker/cms-worker.js +772 -285
- package/dist/worker/gateway-egress.d.ts +43 -0
- package/dist/worker/gateway-egress.js +209 -0
- package/dist/worker/gateway-instance.d.ts +42 -0
- package/dist/worker/gateway-instance.js +129 -0
- package/dist/worker/git-process.d.ts +81 -0
- package/dist/worker/git-process.js +234 -0
- package/dist/worker/git-sync.d.ts +34 -17
- package/dist/worker/git-sync.js +394 -131
- package/dist/worker/github-auth.d.ts +9 -9
- package/dist/worker/github-auth.js +13 -15
- package/dist/worker/github-gateway-http.d.ts +62 -0
- package/dist/worker/github-gateway-http.js +977 -0
- package/dist/worker/github-gateway-wire.d.ts +590 -0
- package/dist/worker/github-gateway-wire.js +488 -0
- package/dist/worker/github-gateway.d.ts +238 -0
- package/dist/worker/github-gateway.js +726 -0
- package/dist/worker/github-mirror-upkeep.d.ts +75 -0
- package/dist/worker/github-mirror-upkeep.js +266 -0
- package/dist/worker/github-mirror.d.ts +364 -0
- package/dist/worker/github-mirror.js +1030 -0
- package/dist/worker/github-transfer.d.ts +56 -0
- package/dist/worker/github-transfer.js +444 -0
- package/dist/worker/history-rewrite.d.ts +4 -5
- package/dist/worker/history-rewrite.js +12 -17
- package/dist/worker/provisioned-workspace.d.ts +5 -5
- package/dist/worker/provisioned-workspace.js +5 -5
- package/dist/worker/rebase.d.ts +1 -1
- package/dist/worker/rebase.js +58 -54
- package/dist/worker/remote-git-maintenance.d.ts +3 -0
- package/dist/worker/remote-git-maintenance.js +12 -0
- package/dist/worker/schema-gate.d.ts +49 -0
- package/dist/worker/schema-gate.js +226 -0
- package/dist/worker/self-checks.d.ts +107 -0
- package/dist/worker/self-checks.js +358 -0
- package/dist/worker/shared-repo-git.d.ts +96 -0
- package/dist/worker/shared-repo-git.js +459 -0
- package/dist/worker/sparse-cone.d.ts +20 -0
- package/dist/worker/sparse-cone.js +136 -0
- package/dist/worker/stale-mirror.d.ts +82 -0
- package/dist/worker/stale-mirror.js +130 -0
- package/dist/worker/task-runner.d.ts +5 -3
- package/dist/worker/task-runner.js +287 -186
- package/dist/worker/worker-context.d.ts +36 -37
- package/package.json +35 -7
- package/dist/editor/EditorPanes.d.ts +0 -10
- package/dist/editor/EditorPanes.js +0 -98
- package/dist/editor/components/EditorFooter.d.ts +0 -11
- package/dist/editor/components/EditorFooter.js +0 -9
- package/dist/editor/components/EditorSidebar.d.ts +0 -67
- package/dist/editor/components/EditorSidebar.js +0 -22
package/dist/worker/rebase.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { simpleGit } from 'simple-git';
|
|
4
3
|
import { BranchMetadataFileManager, buildMergedBranchUpdate, getBranchMetadataFileManager, } from '../branch-metadata.js';
|
|
5
4
|
import { extractIdFromFilename } from '../content-id-index.js';
|
|
6
5
|
import { invalidateBranchContentCaches } from '../content-index-generation.js';
|
|
@@ -14,6 +13,7 @@ import { isCanopyInternalPath, isRebaseInProgress } from '../utils/git.js';
|
|
|
14
13
|
import { TRACKED_CANOPY_STATE_FIX, restoreRetiredSchemaCache, splitByUpstreamTracking, trackedCanopyStateChanges, } from './canopy-state.js';
|
|
15
14
|
import { enqueueGitHubPush, forcePublishToLocalRemote, markHistoryRewritten, readPublishedSha, reconcilePendingRewrite, } from './history-rewrite.js';
|
|
16
15
|
import { workerLog, workerLogWarn } from './log.js';
|
|
16
|
+
import { SHARED_REPO_STATUS_ARGS, assertNoIncomingSubmodules, assertSharedRepoConfig, fetchFromRemoteGit, sharedRepoGit, } from './shared-repo-git.js';
|
|
17
17
|
/**
|
|
18
18
|
* Poll GitHub for a submitted/approved branch's PR resolution. Those branches
|
|
19
19
|
* sit outside the rebase loop and get no other signal that their PR resolved --
|
|
@@ -32,12 +32,7 @@ export async function pollMergeState(ctx, branchDir, branchPath, metaFile) {
|
|
|
32
32
|
const controller = new AbortController();
|
|
33
33
|
const timer = setTimeout(() => controller.abort(), ctx.taskTimeoutMs);
|
|
34
34
|
try {
|
|
35
|
-
const
|
|
36
|
-
owner: ctx.githubOwner,
|
|
37
|
-
repo: ctx.githubRepo,
|
|
38
|
-
pull_number: prNumber,
|
|
39
|
-
request: { signal: controller.signal },
|
|
40
|
-
});
|
|
35
|
+
const data = await ctx.github().getPullRequest(prNumber, controller.signal);
|
|
41
36
|
if (data.merged) {
|
|
42
37
|
const meta = getBranchMetadataFileManager(branchPath, ctx.contentBranchesPath);
|
|
43
38
|
// GitHub's actual merge time when available; buildMergedBranchUpdate
|
|
@@ -176,7 +171,7 @@ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLock
|
|
|
176
171
|
}
|
|
177
172
|
catch (rebaseErr) {
|
|
178
173
|
nextAction = 'continue';
|
|
179
|
-
const st = await branchGit.status();
|
|
174
|
+
const st = await branchGit.status([...SHARED_REPO_STATUS_ARGS]);
|
|
180
175
|
if (st.conflicted.length > 0) {
|
|
181
176
|
await ctx.afterConflictDetectedForTesting();
|
|
182
177
|
// During rebase, --theirs = the branch being replayed (the editor's
|
|
@@ -199,20 +194,23 @@ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLock
|
|
|
199
194
|
//
|
|
200
195
|
// A per-file resolution that STILL fails routes into the `!completed`
|
|
201
196
|
// path below, which aborts and records, rather than escaping.
|
|
197
|
+
//
|
|
198
|
+
// `--sparse` on add/rm: a conflicted path outside a sparse clone's cone
|
|
199
|
+
// is materialised for the conflict, but git refuses to stage it without.
|
|
202
200
|
const conflictKind = new Map(st.files.map((f) => [f.path, `${f.index}${f.working_dir}`]));
|
|
203
201
|
let resolutionFailure;
|
|
204
202
|
for (const file of st.conflicted) {
|
|
205
203
|
const kind = conflictKind.get(file);
|
|
206
204
|
try {
|
|
207
205
|
if (kind === 'UD') {
|
|
208
|
-
await branchGit.raw(['rm', '-f', '--', file]);
|
|
206
|
+
await branchGit.raw(['rm', '-f', '--sparse', '--', file]);
|
|
209
207
|
}
|
|
210
208
|
else if (kind === 'DU') {
|
|
211
|
-
await branchGit.add
|
|
209
|
+
await branchGit.raw(['add', '--sparse', '--', file]);
|
|
212
210
|
}
|
|
213
211
|
else {
|
|
214
|
-
await branchGit.raw(['checkout', '--theirs', file]);
|
|
215
|
-
await branchGit.add
|
|
212
|
+
await branchGit.raw(['checkout', '--theirs', '--', file]);
|
|
213
|
+
await branchGit.raw(['add', '--sparse', '--', file]);
|
|
216
214
|
}
|
|
217
215
|
}
|
|
218
216
|
catch (resolveErr) {
|
|
@@ -242,9 +240,7 @@ async function runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, isLock
|
|
|
242
240
|
else {
|
|
243
241
|
// Unexpected error — abort and leave the branch behind.
|
|
244
242
|
// conflictStatus/conflictFiles are deliberately not updated: the
|
|
245
|
-
// rebase did not complete, so
|
|
246
|
-
// the existing (possibly stale) metadata is preserved until the next
|
|
247
|
-
// successful rebase cycle corrects it.
|
|
243
|
+
// rebase did not complete, so it replaced nothing to record.
|
|
248
244
|
workerLogWarn(` Unexpected rebase error in ${branchDir}: ${msg || 'Unknown error'}`);
|
|
249
245
|
failureReason = msg || 'Unknown error';
|
|
250
246
|
await branchGit.rebase(['--abort']).catch(() => { });
|
|
@@ -304,6 +300,21 @@ function conflictFilesToContentIds(conflictedFiles, contentRoot) {
|
|
|
304
300
|
.filter((id) => id !== null);
|
|
305
301
|
return [...new Set(conflictIds)];
|
|
306
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* The conflict record after a rebase: the IDs already recorded plus this
|
|
305
|
+
* rebase's. A conflict means this branch's version replaced base's change to
|
|
306
|
+
* that entry, which a later clean rebase does not undo, so the record only
|
|
307
|
+
* grows until the branch merges (buildMergedBranchUpdate clears it).
|
|
308
|
+
*
|
|
309
|
+
* Pure.
|
|
310
|
+
*/
|
|
311
|
+
function recordConflicts(recorded, found) {
|
|
312
|
+
const conflictFiles = [...new Set([...(recorded ?? []), ...found])];
|
|
313
|
+
return {
|
|
314
|
+
conflictStatus: conflictFiles.length > 0 ? 'conflicts-detected' : 'clean',
|
|
315
|
+
conflictFiles,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
307
318
|
/**
|
|
308
319
|
* [SYNC-H1] Carry a just-rebased history forward into `remote.git`, and queue
|
|
309
320
|
* the GitHub hop.
|
|
@@ -378,6 +389,12 @@ export async function runRebaseCycle(ctx) {
|
|
|
378
389
|
return { rebased, skippedDirty, skippedLocked, failed };
|
|
379
390
|
}
|
|
380
391
|
for (const branchDir of branchDirs) {
|
|
392
|
+
// A draining worker finishes the branch it is rebasing and starts no other:
|
|
393
|
+
// a rebase killed mid-branch is recovered lossily on the next boot.
|
|
394
|
+
if (ctx.isDraining()) {
|
|
395
|
+
workerLog(' Rebase cycle stopped: the worker is draining');
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
381
398
|
// Known structural entries under content-branches/, not branch workspaces:
|
|
382
399
|
// branches.json (registry snapshot) and dot-prefixed entries
|
|
383
400
|
// (.canopy-meta/, transient lock dirs). Skipped SILENTLY so the no-.git
|
|
@@ -436,7 +453,7 @@ export async function runRebaseCycle(ctx) {
|
|
|
436
453
|
/**
|
|
437
454
|
* {@link rebaseOneBranch} under the branch's provisioning lock, so no git step races a clone of
|
|
438
455
|
* this directory. A branch whose lock is held elsewhere is `skippedLocked` and retried next cycle;
|
|
439
|
-
* one with no `branch.json`
|
|
456
|
+
* one with no `branch.json` is skipped. Lock order: provisioning, then the
|
|
440
457
|
* content-write lock inside it; see `provisioned-workspace.ts` for why that order cannot deadlock.
|
|
441
458
|
* Never throws, like rebaseOneBranch.
|
|
442
459
|
*/
|
|
@@ -455,10 +472,10 @@ async function holdAndRebaseOneBranch(ctx, branchDir, branchPath) {
|
|
|
455
472
|
workerLog(` Skipping ${branchDir}: provisioning lock held elsewhere (retrying next cycle)`);
|
|
456
473
|
return { kind: 'skippedLocked' };
|
|
457
474
|
}
|
|
458
|
-
|
|
459
|
-
|
|
475
|
+
// Silent: a directory without branch.json is a staging-era leftover, which
|
|
476
|
+
// repairBranchDirResidue (git-sync.ts) quarantines and logs at the cycle's start.
|
|
477
|
+
if (hold.kind === 'not-provisioned')
|
|
460
478
|
return { kind: 'none' };
|
|
461
|
-
}
|
|
462
479
|
try {
|
|
463
480
|
return await rebaseOneBranch(ctx, branchDir, branchPath, hold.isCompromised);
|
|
464
481
|
}
|
|
@@ -499,15 +516,11 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
499
516
|
workerLog(` Skipping ${branchDir} (${branchStatus})`);
|
|
500
517
|
return { kind: 'none' };
|
|
501
518
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// so allowUnsafeEditor carries no injection risk here.
|
|
508
|
-
config: ['core.editor=true'],
|
|
509
|
-
unsafe: { allowUnsafeEditor: true },
|
|
510
|
-
});
|
|
519
|
+
// Throws into the catch below, which records the refusal on the branch: a planted key here
|
|
520
|
+
// would run in this worker's rebase, merge and checkout.
|
|
521
|
+
await assertSharedRepoConfig(branchPath, 'worktree');
|
|
522
|
+
// The pins also keep git non-interactive (core.editor=true): a rebase never waits on an editor.
|
|
523
|
+
const branchGit = sharedRepoGit(branchPath, 'worktree');
|
|
511
524
|
// [SYNC-C1] Take the branch's cross-host content-write lock BEFORE the
|
|
512
525
|
// dirty check, and hold it for the whole rebase. The dirty check alone is
|
|
513
526
|
// check-then-act, and its residual window spans fetch, replay and N
|
|
@@ -574,7 +587,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
574
587
|
// happen SILENTLY, so anything modified beyond the rebase's own conflict
|
|
575
588
|
// state is logged by path first. That log is the operator's only record.
|
|
576
589
|
if (await isRebaseInProgress(branchPath)) {
|
|
577
|
-
const preAbort = await branchGit.status().catch(() => null);
|
|
590
|
+
const preAbort = await branchGit.status([...SHARED_REPO_STATUS_ARGS]).catch(() => null);
|
|
578
591
|
// Keyed on the WORKING-TREE column ONLY. The two porcelain columns mean
|
|
579
592
|
// different things here, and conflating them reports false data loss on
|
|
580
593
|
// essentially every conflict-wedged recovery (verified against real
|
|
@@ -621,10 +634,10 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
621
634
|
// Skip dirty branches — the editor has changes that cannot be rebased.
|
|
622
635
|
// Inside the lock, so no write can land between this check and the rebase
|
|
623
636
|
// below. canopycms's own untracked state is not dirt.
|
|
624
|
-
let dirtyCheck = await branchGit.status();
|
|
637
|
+
let dirtyCheck = await branchGit.status([...SHARED_REPO_STATUS_ARGS]);
|
|
625
638
|
if (await restoreRetiredSchemaCache(branchGit, dirtyCheck)) {
|
|
626
639
|
workerLog(` ${branchDir}: restored the retired in-tree schema cache`);
|
|
627
|
-
dirtyCheck = await branchGit.status();
|
|
640
|
+
dirtyCheck = await branchGit.status([...SHARED_REPO_STATUS_ARGS]);
|
|
628
641
|
}
|
|
629
642
|
const editorDirt = dirtyCheck.files.filter((f) => !isCanopyInternalPath(f.path));
|
|
630
643
|
if (editorDirt.length > 0) {
|
|
@@ -638,7 +651,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
638
651
|
// the bytes stay untouched, and the wedge is recorded for an operator.
|
|
639
652
|
const trackedState = trackedCanopyStateChanges(dirtyCheck);
|
|
640
653
|
if (trackedState.length > 0) {
|
|
641
|
-
await branchGit
|
|
654
|
+
await fetchFromRemoteGit(branchGit, ctx.remoteGitPath, ctx.baseBranch);
|
|
642
655
|
const baseTip = (await branchGit.revparse(['FETCH_HEAD'])).trim();
|
|
643
656
|
const { stillTracked } = await splitByUpstreamTracking(branchGit, trackedState, baseTip);
|
|
644
657
|
const reason = stillTracked.length > 0
|
|
@@ -672,7 +685,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
672
685
|
marker: metaFile.branch.historyRewrittenFrom,
|
|
673
686
|
});
|
|
674
687
|
}
|
|
675
|
-
await branchGit
|
|
688
|
+
await fetchFromRemoteGit(branchGit, ctx.remoteGitPath, ctx.baseBranch);
|
|
676
689
|
// rev-list, not status.behind, which needs an upstream tracking branch
|
|
677
690
|
// that checkoutBranch's fallback paths do not always configure. Against
|
|
678
691
|
// the just-fetched tip: a fetch by path updates no remote-tracking ref.
|
|
@@ -691,26 +704,17 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
691
704
|
// `metaFile` snapshot from before the fetch/rev-list above, so a
|
|
692
705
|
// concurrent editor-driven metadata change in that window is not
|
|
693
706
|
// clobbered by a stale skip decision.
|
|
707
|
+
//
|
|
708
|
+
// Catching up leaves the conflict record alone: it names entries whose
|
|
709
|
+
// base change this branch's version replaced, which stays true until
|
|
710
|
+
// the branch merges (see recordConflicts). A lingering rebaseFailure
|
|
711
|
+
// is cleared, or it sticks as a stale warning forever: nothing else
|
|
712
|
+
// touches a caught-up branch, so no other save site clears it.
|
|
694
713
|
const currentMeta = await BranchMetadataFileManager.loadOnly(branchPath);
|
|
695
|
-
|
|
696
|
-
const conflictFiles = currentMeta?.branch.conflictFiles;
|
|
697
|
-
const conflictAlreadyClean = (conflictStatus === undefined || conflictStatus === 'clean') &&
|
|
698
|
-
(conflictFiles === undefined || conflictFiles.length === 0);
|
|
699
|
-
// A lingering rebaseFailure must also be cleared once the branch
|
|
700
|
-
// catches up clean, or it sticks as a stale warning forever: nothing
|
|
701
|
-
// else touches a caught-up branch, so no other save site clears it.
|
|
702
|
-
const alreadyClean = conflictAlreadyClean && currentMeta?.branch.rebaseFailure === undefined;
|
|
703
|
-
if (alreadyClean) {
|
|
714
|
+
if (currentMeta?.branch.rebaseFailure === undefined) {
|
|
704
715
|
return { kind: 'none' };
|
|
705
716
|
}
|
|
706
|
-
await meta.save({
|
|
707
|
-
branch: {
|
|
708
|
-
name: branchDir,
|
|
709
|
-
conflictStatus: 'clean',
|
|
710
|
-
conflictFiles: [],
|
|
711
|
-
rebaseFailure: undefined,
|
|
712
|
-
},
|
|
713
|
-
});
|
|
717
|
+
await meta.save({ branch: { name: branchDir, rebaseFailure: undefined } });
|
|
714
718
|
return { kind: 'none' };
|
|
715
719
|
}
|
|
716
720
|
workerLog(`Rebasing ${branchDir} (${behindCount} commits behind)...`);
|
|
@@ -720,6 +724,7 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
720
724
|
// the rebase makes unreadable.
|
|
721
725
|
const preRebaseHead = (await branchGit.revparse(['HEAD'])).trim();
|
|
722
726
|
const publishedSha = canPublish ? await readPublishedSha(ctx, branchRef) : null;
|
|
727
|
+
await assertNoIncomingSubmodules(branchGit, branchPath, preRebaseHead, fetchedBaseTip);
|
|
723
728
|
const { completed, conflictedFiles, failureReason } = await runRebaseRounds(ctx, branchGit, branchDir, fetchedBaseTip, lockCompromised);
|
|
724
729
|
// Outside the round loop's try/catch, so a throwing test hook can
|
|
725
730
|
// never be misread as a rebase error.
|
|
@@ -785,16 +790,15 @@ async function rebaseOneBranch(ctx, branchDir, branchPath, isProvisioningLockCom
|
|
|
785
790
|
workerLog(hadConflicts
|
|
786
791
|
? ` Rebased ${branchDir} (kept branch version for ${conflictIdsDeduped.length} conflicting file(s))`
|
|
787
792
|
: ` Rebased ${branchDir} successfully`);
|
|
788
|
-
await meta.
|
|
793
|
+
await meta.update((existing) => ({
|
|
789
794
|
branch: {
|
|
790
795
|
name: branchDir,
|
|
791
|
-
|
|
792
|
-
conflictFiles: conflictIdsDeduped,
|
|
796
|
+
...recordConflicts(existing?.branch.conflictFiles, conflictIdsDeduped),
|
|
793
797
|
// The cycle completed, so clear any prior failure record regardless
|
|
794
798
|
// of the conflict outcome.
|
|
795
799
|
rebaseFailure: undefined,
|
|
796
800
|
},
|
|
797
|
-
});
|
|
801
|
+
}));
|
|
798
802
|
didRebase = true;
|
|
799
803
|
await carryForwardRewrittenHistory(ctx, {
|
|
800
804
|
branchPath,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { repackBareRemoteIfNeeded } from '../git-manager.js';
|
|
2
|
+
import { workerLog } from './log.js';
|
|
3
|
+
import { sharedRepoGit } from './shared-repo-git.js';
|
|
4
|
+
/** The worker's per-cycle repack of `remote.git` (git-manager.ts owns the rule), logged. */
|
|
5
|
+
export async function maintainRemoteGit(gitDir) {
|
|
6
|
+
const result = await repackBareRemoteIfNeeded(gitDir, sharedRepoGit(gitDir, 'bare'));
|
|
7
|
+
if (result.repacked) {
|
|
8
|
+
workerLog(`remote.git maintenance: loose ${result.before.loose}→${result.after.loose} ` +
|
|
9
|
+
`packs ${result.before.packs}→${result.after.packs} in ${result.ms} ms`);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { simpleGit } from 'simple-git';
|
|
2
|
+
import type { BaseSchemaHold } from '../types.js';
|
|
3
|
+
/** How long the base branch may wait for its oldest missing schema before the worker advances it anyway. */
|
|
4
|
+
export declare const DEFAULT_SCHEMA_HOLD_MAX_MS: number;
|
|
5
|
+
type Git = ReturnType<typeof simpleGit>;
|
|
6
|
+
export type SchemaGateDecision = {
|
|
7
|
+
kind: 'advance';
|
|
8
|
+
}
|
|
9
|
+
/** Advance anyway: the oldest missing schema has waited its bound. `hold` names them all. */
|
|
10
|
+
| {
|
|
11
|
+
kind: 'advance-expired';
|
|
12
|
+
hold: BaseSchemaHold;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'hold';
|
|
15
|
+
hold: BaseSchemaHold;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The hold in the status file a previous worker process left, so a restart or a lock takeover
|
|
19
|
+
* keeps each schema's first-seen time and cannot reset the bound. Tolerant: an unreadable or
|
|
20
|
+
* malformed file carries nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readCarriedBaseHold(taskDir: string): Promise<BaseSchemaHold | undefined>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the base branch may fast-forward from `currentSha` to `incomingSha` while the serving
|
|
25
|
+
* editor runs the registry in the schema-registry record (schema-registry-record.ts).
|
|
26
|
+
*
|
|
27
|
+
* Holds only for a name that a `.collection.json` changed between the two tips references at the
|
|
28
|
+
* incoming one, that the record lacks, and that the current base does not already reference:
|
|
29
|
+
* holding cannot repair a reference already live, and a merge that changes no collection meta
|
|
30
|
+
* reads no meta blob but those of symlinks.
|
|
31
|
+
*
|
|
32
|
+
* Fails open: with no readable record (an editor too old to write one, one not yet started, a
|
|
33
|
+
* failed write), or when the trees cannot be read, the base advances as if there were no gate.
|
|
34
|
+
* When each name was first seen missing is carried in `previous.firstSeen` across cycles and
|
|
35
|
+
* worker restarts; once the oldest missing name has waited `maxHoldMs` the base advances with an
|
|
36
|
+
* error naming them, since a name no deploy will ever supply would otherwise freeze every content
|
|
37
|
+
* update.
|
|
38
|
+
*/
|
|
39
|
+
export declare function decideBaseAdvance(input: {
|
|
40
|
+
git: Git;
|
|
41
|
+
contentBranchesPath: string;
|
|
42
|
+
baseBranch: string;
|
|
43
|
+
currentSha: string;
|
|
44
|
+
incomingSha: string;
|
|
45
|
+
previous: BaseSchemaHold | undefined;
|
|
46
|
+
maxHoldMs: number;
|
|
47
|
+
now?: Date;
|
|
48
|
+
}): Promise<SchemaGateDecision>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readSchemaRegistryRecord } from '../schema-registry-record.js';
|
|
4
|
+
import { WORKER_STATUS_FILE } from '../task-queue/worker-status.js';
|
|
5
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
6
|
+
import { MAX_REPORTED_PATHS } from './canopy-state.js';
|
|
7
|
+
import { workerLogError, workerLogWarn } from './log.js';
|
|
8
|
+
/** How long the base branch may wait for its oldest missing schema before the worker advances it anyway. */
|
|
9
|
+
export const DEFAULT_SCHEMA_HOLD_MAX_MS = 30 * 60_000;
|
|
10
|
+
const COLLECTION_META_FILE = '.collection.json';
|
|
11
|
+
const SYMLINK_MODE = '120000';
|
|
12
|
+
/**
|
|
13
|
+
* `contentRoot` as a literal pathspec: repo-relative with no `./` or trailing slash, or null for
|
|
14
|
+
* the repository root. Literal, so a directory named like pathspec magic (`:x`) reads as itself.
|
|
15
|
+
*/
|
|
16
|
+
function contentPathspec(contentRoot) {
|
|
17
|
+
const normalized = path.posix.normalize(contentRoot.replace(/\\/g, '/')).replace(/\/+$/, '');
|
|
18
|
+
return normalized === '.' || normalized === '' ? null : `:(literal)${normalized}`;
|
|
19
|
+
}
|
|
20
|
+
function withPathspec(args, pathspec) {
|
|
21
|
+
return pathspec ? [...args, '--', pathspec] : args;
|
|
22
|
+
}
|
|
23
|
+
/** Schema names a `.collection.json` blob's entry types reference; none when it does not parse. */
|
|
24
|
+
function referencedSchemas(raw) {
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
if (typeof parsed !== 'object' || parsed === null || !('entries' in parsed))
|
|
33
|
+
return [];
|
|
34
|
+
const { entries } = parsed;
|
|
35
|
+
if (!Array.isArray(entries))
|
|
36
|
+
return [];
|
|
37
|
+
const names = [];
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (typeof entry !== 'object' || entry === null || !('schema' in entry))
|
|
40
|
+
continue;
|
|
41
|
+
if (typeof entry.schema === 'string' && entry.schema.length > 0)
|
|
42
|
+
names.push(entry.schema);
|
|
43
|
+
}
|
|
44
|
+
return names;
|
|
45
|
+
}
|
|
46
|
+
/** Every `.collection.json` under `pathspec` at `commit`, by repo-relative path. */
|
|
47
|
+
async function listCollectionMeta(git, commit, pathspec) {
|
|
48
|
+
const listing = await git.raw(withPathspec(['ls-tree', '-r', '-z', '--full-tree', commit], pathspec));
|
|
49
|
+
const entries = new Map();
|
|
50
|
+
for (const line of listing.split('\0')) {
|
|
51
|
+
// `<mode> <type> <oid>\t<path>`
|
|
52
|
+
const tab = line.indexOf('\t');
|
|
53
|
+
if (tab < 0)
|
|
54
|
+
continue;
|
|
55
|
+
const filePath = line.slice(tab + 1);
|
|
56
|
+
if (path.posix.basename(filePath) !== COLLECTION_META_FILE)
|
|
57
|
+
continue;
|
|
58
|
+
const [mode, type, oid] = line.slice(0, tab).split(' ');
|
|
59
|
+
if (type === 'blob' && mode && oid)
|
|
60
|
+
entries.set(filePath, { mode, oid });
|
|
61
|
+
}
|
|
62
|
+
return entries;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The schema names one meta file references at `commit`. A symlink is followed one hop, to a
|
|
66
|
+
* relative target inside the repository; the editor's `fs.readFile` follows every hop, so a
|
|
67
|
+
* longer chain or an absolute target reads as no references and fails open.
|
|
68
|
+
*/
|
|
69
|
+
async function metaReferences(git, commit, filePath, entry, blobCache) {
|
|
70
|
+
if (entry.mode !== SYMLINK_MODE) {
|
|
71
|
+
const cached = blobCache.get(entry.oid);
|
|
72
|
+
if (cached)
|
|
73
|
+
return cached;
|
|
74
|
+
const names = referencedSchemas(await git.raw(['cat-file', 'blob', entry.oid]));
|
|
75
|
+
blobCache.set(entry.oid, names);
|
|
76
|
+
return names;
|
|
77
|
+
}
|
|
78
|
+
const linkText = await git.raw(['cat-file', 'blob', entry.oid]);
|
|
79
|
+
const target = path.posix.normalize(path.posix.join(path.posix.dirname(filePath), linkText));
|
|
80
|
+
if (target.startsWith('../') || path.posix.isAbsolute(target))
|
|
81
|
+
return [];
|
|
82
|
+
try {
|
|
83
|
+
return referencedSchemas(await git.raw(['cat-file', 'blob', `${commit}:${target}`]));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const isString = (value) => typeof value === 'string';
|
|
90
|
+
const isStringArray = (value) => Array.isArray(value) && value.every(isString);
|
|
91
|
+
function isBaseSchemaHold(value) {
|
|
92
|
+
if (typeof value !== 'object' || value === null)
|
|
93
|
+
return false;
|
|
94
|
+
const hold = value;
|
|
95
|
+
const { firstSeen, editorBuild } = hold;
|
|
96
|
+
return (isString(hold.since) &&
|
|
97
|
+
typeof firstSeen === 'object' &&
|
|
98
|
+
firstSeen !== null &&
|
|
99
|
+
Object.values(firstSeen).every(isString) &&
|
|
100
|
+
isString(hold.incomingSha) &&
|
|
101
|
+
isStringArray(hold.missingSchemas) &&
|
|
102
|
+
isStringArray(hold.files) &&
|
|
103
|
+
typeof hold.fileCount === 'number' &&
|
|
104
|
+
typeof editorBuild === 'object' &&
|
|
105
|
+
editorBuild !== null &&
|
|
106
|
+
'canopycmsVersion' in editorBuild &&
|
|
107
|
+
isString(editorBuild.canopycmsVersion) &&
|
|
108
|
+
isString(hold.editorRecordedAt) &&
|
|
109
|
+
(hold.expired === undefined || hold.expired === true));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The hold in the status file a previous worker process left, so a restart or a lock takeover
|
|
113
|
+
* keeps each schema's first-seen time and cannot reset the bound. Tolerant: an unreadable or
|
|
114
|
+
* malformed file carries nothing.
|
|
115
|
+
*/
|
|
116
|
+
export async function readCarriedBaseHold(taskDir) {
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(await fs.readFile(path.join(taskDir, WORKER_STATUS_FILE), 'utf-8'));
|
|
119
|
+
if (typeof parsed !== 'object' || parsed === null || !('baseHold' in parsed))
|
|
120
|
+
return undefined;
|
|
121
|
+
return isBaseSchemaHold(parsed.baseHold) ? parsed.baseHold : undefined;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether the base branch may fast-forward from `currentSha` to `incomingSha` while the serving
|
|
129
|
+
* editor runs the registry in the schema-registry record (schema-registry-record.ts).
|
|
130
|
+
*
|
|
131
|
+
* Holds only for a name that a `.collection.json` changed between the two tips references at the
|
|
132
|
+
* incoming one, that the record lacks, and that the current base does not already reference:
|
|
133
|
+
* holding cannot repair a reference already live, and a merge that changes no collection meta
|
|
134
|
+
* reads no meta blob but those of symlinks.
|
|
135
|
+
*
|
|
136
|
+
* Fails open: with no readable record (an editor too old to write one, one not yet started, a
|
|
137
|
+
* failed write), or when the trees cannot be read, the base advances as if there were no gate.
|
|
138
|
+
* When each name was first seen missing is carried in `previous.firstSeen` across cycles and
|
|
139
|
+
* worker restarts; once the oldest missing name has waited `maxHoldMs` the base advances with an
|
|
140
|
+
* error naming them, since a name no deploy will ever supply would otherwise freeze every content
|
|
141
|
+
* update.
|
|
142
|
+
*/
|
|
143
|
+
export async function decideBaseAdvance(input) {
|
|
144
|
+
const { git, baseBranch, currentSha, incomingSha, previous, maxHoldMs } = input;
|
|
145
|
+
const now = input.now ?? new Date();
|
|
146
|
+
let record;
|
|
147
|
+
try {
|
|
148
|
+
record = await readSchemaRegistryRecord(input.contentBranchesPath);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
workerLogWarn(`Schema gate (${baseBranch}): could not read the schema-registry record, not holding: ${getErrorMessage(err)}`);
|
|
152
|
+
return { kind: 'advance' };
|
|
153
|
+
}
|
|
154
|
+
if (!record)
|
|
155
|
+
return { kind: 'advance' };
|
|
156
|
+
// Name -> the incoming meta files referencing it, for names the record lacks.
|
|
157
|
+
const missing = new Map();
|
|
158
|
+
try {
|
|
159
|
+
const pathspec = contentPathspec(record.contentRoot);
|
|
160
|
+
const diff = await git.raw(withPathspec(['diff-tree', '-r', '-z', '--name-only', '--no-renames', currentSha, incomingSha], pathspec));
|
|
161
|
+
const changed = new Set(diff.split('\0').filter((file) => path.posix.basename(file) === COLLECTION_META_FILE));
|
|
162
|
+
const incomingMeta = await listCollectionMeta(git, incomingSha, pathspec);
|
|
163
|
+
// A symlink's target can change without the link itself changing.
|
|
164
|
+
for (const [file, entry] of incomingMeta)
|
|
165
|
+
if (entry.mode === SYMLINK_MODE)
|
|
166
|
+
changed.add(file);
|
|
167
|
+
const known = new Set(record.schemas);
|
|
168
|
+
const blobCache = new Map();
|
|
169
|
+
for (const file of changed) {
|
|
170
|
+
const entry = incomingMeta.get(file);
|
|
171
|
+
if (!entry)
|
|
172
|
+
continue;
|
|
173
|
+
for (const name of await metaReferences(git, incomingSha, file, entry, blobCache)) {
|
|
174
|
+
if (!known.has(name))
|
|
175
|
+
missing.set(name, [...(missing.get(name) ?? []), file]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (missing.size > 0) {
|
|
179
|
+
for (const [file, entry] of await listCollectionMeta(git, currentSha, pathspec)) {
|
|
180
|
+
for (const name of await metaReferences(git, currentSha, file, entry, blobCache)) {
|
|
181
|
+
missing.delete(name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
workerLogWarn(`Schema gate (${baseBranch}): could not read collection meta, not holding: ${getErrorMessage(err)}`);
|
|
188
|
+
return { kind: 'advance' };
|
|
189
|
+
}
|
|
190
|
+
if (missing.size === 0)
|
|
191
|
+
return { kind: 'advance' };
|
|
192
|
+
const missingSchemas = [...missing.keys()].sort();
|
|
193
|
+
const carried = previous?.firstSeen ?? {};
|
|
194
|
+
const firstSeenAt = missingSchemas.map((name) => {
|
|
195
|
+
const at = Object.prototype.hasOwnProperty.call(carried, name)
|
|
196
|
+
? Date.parse(carried[name])
|
|
197
|
+
: Number.NaN;
|
|
198
|
+
// An unparseable or future carried time restarts that name's wait, never freezes it.
|
|
199
|
+
return [name, Number.isFinite(at) && at <= now.getTime() ? at : now.getTime()];
|
|
200
|
+
});
|
|
201
|
+
// The oldest missing name bounds the hold: past its bound, holding cannot help it, and
|
|
202
|
+
// waiting on newer names as well would let a stream of schema merges hold the base for good.
|
|
203
|
+
const sinceMs = Math.min(...firstSeenAt.map(([, at]) => at));
|
|
204
|
+
const waiting = now.getTime() - sinceMs < maxHoldMs;
|
|
205
|
+
const files = [...new Set([...missing.values()].flat())].sort();
|
|
206
|
+
const hold = {
|
|
207
|
+
since: new Date(sinceMs).toISOString(),
|
|
208
|
+
// fromEntries defines own properties, so a name like `__proto__` is recorded as itself.
|
|
209
|
+
firstSeen: Object.fromEntries(firstSeenAt.map(([name, at]) => [name, new Date(at).toISOString()])),
|
|
210
|
+
incomingSha,
|
|
211
|
+
missingSchemas,
|
|
212
|
+
files: files.slice(0, MAX_REPORTED_PATHS),
|
|
213
|
+
fileCount: files.length,
|
|
214
|
+
editorBuild: record.build,
|
|
215
|
+
editorRecordedAt: record.recordedAt,
|
|
216
|
+
};
|
|
217
|
+
if (waiting)
|
|
218
|
+
return { kind: 'hold', hold };
|
|
219
|
+
workerLogError(`Schema gate (${baseBranch}): held since ${hold.since} for ${missingSchemas.join(', ')} ` +
|
|
220
|
+
`(referenced by ${hold.files.join(', ')}${files.length > hold.files.length ? ', …' : ''}), ` +
|
|
221
|
+
`which the serving editor` +
|
|
222
|
+
`${record.build.sourceRevision ? ` (built from ${record.build.sourceRevision})` : ''} ` +
|
|
223
|
+
`does not define. Advancing anyway; content types using them are unavailable until an editor ` +
|
|
224
|
+
`image defining them is deployed.`);
|
|
225
|
+
return { kind: 'advance-expired', hold: { ...hold, expired: true } };
|
|
226
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The boot self-checks of the worker process split (.claude/future-tasks/resolved/
|
|
3
|
+
* worker-shared-repo-git-process-split.md §5): each proves a sandbox layer the split rests on took
|
|
4
|
+
* effect, because systemd applies some directives silently or not at all (`IPAddressDeny` without
|
|
5
|
+
* cgroup-BPF). Every check fails closed: what it cannot read counts against it. They only report;
|
|
6
|
+
* the entrypoint that runs them decides to stop (D3: they are fatal).
|
|
7
|
+
*
|
|
8
|
+
* Every path is read under `root` (`/` on an instance), so a test can stand in a fake tree.
|
|
9
|
+
*/
|
|
10
|
+
/** One check's outcome. `detail` names what was seen, never a secret's value. */
|
|
11
|
+
export interface SelfCheckResult {
|
|
12
|
+
check: SelfCheckName;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
detail: string;
|
|
15
|
+
}
|
|
16
|
+
export type SelfCheckName = 'imds' | 'network-namespace' | 'proc' | 'home' | 'secrets-in-env' | 'efs-unreadable' | 'core-pattern' | 'core-limit' | 'socket-access';
|
|
17
|
+
/**
|
|
18
|
+
* How a TCP connect ended: it connected, it failed with an errno code, or it timed out.
|
|
19
|
+
* @internal Exported for tests.
|
|
20
|
+
*/
|
|
21
|
+
export type ConnectOutcome = 'connected' | 'timeout' | {
|
|
22
|
+
code: string;
|
|
23
|
+
};
|
|
24
|
+
export interface SelfCheckEnvironment {
|
|
25
|
+
/** Where `/proc`, `/home` and `/mnt/efs` are read from. Default `/`. */
|
|
26
|
+
root?: string;
|
|
27
|
+
/** The process environment the secret check reads. Default `process.env`. */
|
|
28
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
29
|
+
/** The uid every visible process must have. Default this process's. */
|
|
30
|
+
uid?: number;
|
|
31
|
+
/** Test seam: the TCP connect the IMDS check makes. */
|
|
32
|
+
connect?: (host: string, port: number, timeoutMs: number) => Promise<ConnectOutcome>;
|
|
33
|
+
/**
|
|
34
|
+
* The gateway's listening descriptor when systemd passed it (socket activation), which the
|
|
35
|
+
* `socket-access` check reads. Unset, that check is skipped: a gateway that binds its own path
|
|
36
|
+
* owns the socket by construction (development).
|
|
37
|
+
*/
|
|
38
|
+
listenFd?: number;
|
|
39
|
+
}
|
|
40
|
+
/** The worker unit's checks, in the plan's order. */
|
|
41
|
+
export declare function workerSelfChecks(environment?: SelfCheckEnvironment): Promise<SelfCheckResult[]>;
|
|
42
|
+
/** The gateway unit's checks. */
|
|
43
|
+
export declare function gatewaySelfChecks(environment?: SelfCheckEnvironment): Promise<SelfCheckResult[]>;
|
|
44
|
+
/** Throw one error naming every failed check, or return. */
|
|
45
|
+
export declare function assertSelfChecks(results: readonly SelfCheckResult[]): void;
|
|
46
|
+
/**
|
|
47
|
+
* A TCP connect to IMDS fails with no route (`ENETUNREACH`, or `EAFNOSUPPORT` under
|
|
48
|
+
* `RestrictAddressFamilies=AF_UNIX`) or times out. Any other outcome, a refusal included, means a
|
|
49
|
+
* route exists.
|
|
50
|
+
* @internal Exported for tests.
|
|
51
|
+
*/
|
|
52
|
+
export declare function imdsUnreachable(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
53
|
+
/**
|
|
54
|
+
* `/proc/self/net/dev` lists only `lo`: `PrivateNetwork=` took effect. Not `/sys/class/net`, which
|
|
55
|
+
* `PrivateNetwork=` does not remount, so it shows the host's interfaces even inside the cut.
|
|
56
|
+
* @internal Exported for tests.
|
|
57
|
+
*/
|
|
58
|
+
export declare function onlyLoopback(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Every process `/proc` lists is this uid's: `ProtectProc=invisible` took effect.
|
|
61
|
+
* @internal Exported for tests.
|
|
62
|
+
*/
|
|
63
|
+
export declare function procShowsOnlyOwnProcesses(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
64
|
+
/**
|
|
65
|
+
* `/home` is empty: `ProtectHome=tmpfs`'s always-observable effect. `/run/user`, which it also
|
|
66
|
+
* hides, is empty anyway whenever nobody is logged in.
|
|
67
|
+
* @internal Exported for tests.
|
|
68
|
+
*/
|
|
69
|
+
export declare function homeIsEmpty(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
70
|
+
/**
|
|
71
|
+
* No GitHub or Clerk secret, nor the ARN or JSON field naming one, is in the worker's environment:
|
|
72
|
+
* they belong to the gateway's env file. Nor is a conventional credential variable (a GitHub token,
|
|
73
|
+
* the AWS SDK's static credentials). Names only are reported.
|
|
74
|
+
* @internal Exported for tests.
|
|
75
|
+
*/
|
|
76
|
+
export declare function noSecretsInEnv(environment?: SelfCheckEnvironment): SelfCheckResult;
|
|
77
|
+
/**
|
|
78
|
+
* The gateway cannot list `/mnt/efs`: `InaccessiblePaths=/mnt/efs` took effect.
|
|
79
|
+
* @internal Exported for tests.
|
|
80
|
+
*/
|
|
81
|
+
export declare function efsUnreadable(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
82
|
+
/**
|
|
83
|
+
* `kernel.core_pattern` does not begin with `|`: a piped pattern would hand a crashing gateway
|
|
84
|
+
* git's memory (the credential file's contents) to a helper.
|
|
85
|
+
* @internal Exported for tests.
|
|
86
|
+
*/
|
|
87
|
+
export declare function corePatternNotPiped(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
88
|
+
/**
|
|
89
|
+
* `/proc/self/limits` shows a zero core limit, soft and hard: `LimitCORE=0` took effect.
|
|
90
|
+
* @internal Exported for tests.
|
|
91
|
+
*/
|
|
92
|
+
export declare function coreLimitZero(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
93
|
+
/**
|
|
94
|
+
* The socket systemd passed is one only another user (and root) can connect to: its node is not
|
|
95
|
+
* this process's uid, and grants no group or other access. Whoever owns it is the worker, so a
|
|
96
|
+
* gateway running as that user (a unit without `User=canopy-github`) would hand the worker its
|
|
97
|
+
* own environment and memory. A socket's own descriptor reports the creating process (systemd,
|
|
98
|
+
* root), not the node's owner, so the node is found by the socket's inode in
|
|
99
|
+
* `/proc/self/net/unix`.
|
|
100
|
+
* @internal Exported for tests.
|
|
101
|
+
*/
|
|
102
|
+
export declare function socketAccessIsOthers(environment?: SelfCheckEnvironment): Promise<SelfCheckResult>;
|
|
103
|
+
/**
|
|
104
|
+
* One TCP connect, as the IMDS check makes it.
|
|
105
|
+
* @internal Exported for tests.
|
|
106
|
+
*/
|
|
107
|
+
export declare function tcpConnect(host: string, port: number, timeoutMs: number): Promise<ConnectOutcome>;
|