@qijenchen/design-system 0.1.0-beta.13 → 0.1.0-beta.131
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/AGENTS.md +8 -0
- package/CLAUDE.md +3 -199
- package/README.md +6 -4
- package/cli-init.mjs +30 -2
- package/dist/components/Accordion/accordion.d.ts +3 -3
- package/dist/components/Accordion/accordion.d.ts.map +1 -1
- package/dist/components/Accordion/accordion.js +6 -3
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Accordion/index.d.ts.map +1 -0
- package/dist/components/Accordion/index.js +9 -0
- package/dist/components/Accordion/index.js.map +1 -0
- package/dist/components/AccountMenu/account-menu.d.ts +93 -0
- package/dist/components/AccountMenu/account-menu.d.ts.map +1 -0
- package/dist/components/AccountMenu/account-menu.js +73 -0
- package/dist/components/AccountMenu/account-menu.js.map +1 -0
- package/dist/components/AccountMenu/index.d.ts +2 -0
- package/dist/components/AccountMenu/index.d.ts.map +1 -0
- package/dist/components/AccountMenu/index.js +6 -0
- package/dist/components/AccountMenu/index.js.map +1 -0
- package/dist/components/AgentPanel/agent-fab.d.ts +24 -0
- package/dist/components/AgentPanel/agent-fab.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-logo.d.ts +75 -0
- package/dist/components/AgentPanel/agent-logo.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-logo.js +371 -0
- package/dist/components/AgentPanel/agent-logo.js.map +1 -0
- package/dist/components/AgentPanel/agent-panel.d.ts +183 -0
- package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -0
- package/dist/components/AgentPanel/agent-panel.js +900 -0
- package/dist/components/AgentPanel/agent-panel.js.map +1 -0
- package/dist/components/AgentPanel/index.d.ts +2 -0
- package/dist/components/AgentPanel/index.d.ts.map +1 -0
- package/dist/components/AgentPanel/index.js +16 -0
- package/dist/components/AgentPanel/index.js.map +1 -0
- package/dist/components/Alert/alert.d.ts +7 -2
- package/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +12 -8
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/Alert/index.d.ts +2 -0
- package/dist/components/Alert/index.d.ts.map +1 -0
- package/dist/components/Alert/index.js +7 -0
- package/dist/components/Alert/index.js.map +1 -0
- package/dist/components/AppShell/_demo-helpers.d.ts +3 -1
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +19 -4
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +48 -11
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/AppShell/index.d.ts +2 -0
- package/dist/components/AppShell/index.d.ts.map +1 -0
- package/dist/components/AppShell/index.js +8 -0
- package/dist/components/AppShell/index.js.map +1 -0
- package/dist/components/AspectRatio/aspect-ratio.d.ts +3 -2
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js +4 -2
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
- package/dist/components/AspectRatio/index.d.ts +2 -0
- package/dist/components/AspectRatio/index.d.ts.map +1 -0
- package/dist/components/AspectRatio/index.js +6 -0
- package/dist/components/AspectRatio/index.js.map +1 -0
- package/dist/components/Avatar/avatar.d.ts +14 -7
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +24 -30
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.d.ts.map +1 -0
- package/dist/components/Avatar/index.js +6 -0
- package/dist/components/Avatar/index.js.map +1 -0
- package/dist/components/Badge/badge.d.ts +21 -9
- package/dist/components/Badge/badge.d.ts.map +1 -1
- package/dist/components/Badge/badge.js +9 -6
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Badge/index.d.ts.map +1 -0
- package/dist/components/Badge/index.js +7 -0
- package/dist/components/Badge/index.js.map +1 -0
- package/dist/components/Breadcrumb/breadcrumb.d.ts +16 -3
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +21 -53
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/Breadcrumb/index.d.ts +2 -0
- package/dist/components/Breadcrumb/index.d.ts.map +1 -0
- package/dist/components/Breadcrumb/index.js +12 -0
- package/dist/components/Breadcrumb/index.js.map +1 -0
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +7 -4
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +8 -8
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/BulkActionBar/index.d.ts +2 -0
- package/dist/components/BulkActionBar/index.d.ts.map +1 -0
- package/dist/components/BulkActionBar/index.js +7 -0
- package/dist/components/BulkActionBar/index.js.map +1 -0
- package/dist/components/Button/button-group.d.ts +31 -5
- package/dist/components/Button/button-group.d.ts.map +1 -1
- package/dist/components/Button/button-group.js +31 -14
- package/dist/components/Button/button-group.js.map +1 -1
- package/dist/components/Button/button.d.ts +15 -14
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +94 -52
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Button/index.d.ts +3 -0
- package/dist/components/Button/index.d.ts.map +1 -0
- package/dist/components/Button/index.js +11 -0
- package/dist/components/Button/index.js.map +1 -0
- package/dist/components/Calendar/calendar.d.ts +22 -19
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +180 -135
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Calendar/index.d.ts +2 -0
- package/dist/components/Calendar/index.d.ts.map +1 -0
- package/dist/components/Calendar/index.js +6 -0
- package/dist/components/Calendar/index.js.map +1 -0
- package/dist/components/Carousel/carousel.d.ts +15 -9
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +91 -50
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Carousel/index.d.ts +2 -0
- package/dist/components/Carousel/index.d.ts.map +1 -0
- package/dist/components/Carousel/index.js +11 -0
- package/dist/components/Carousel/index.js.map +1 -0
- package/dist/components/Chart/chart.d.ts +2 -2
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +5 -2
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Chart/index.d.ts +2 -0
- package/dist/components/Chart/index.d.ts.map +1 -0
- package/dist/components/Chart/index.js +11 -0
- package/dist/components/Chart/index.js.map +1 -0
- package/dist/components/Checkbox/checkbox-group.d.ts +2 -1
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox-group.js +21 -14
- package/dist/components/Checkbox/checkbox-group.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +20 -11
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +117 -18
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Checkbox/index.d.ts +3 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -0
- package/dist/components/Checkbox/index.js +10 -0
- package/dist/components/Checkbox/index.js.map +1 -0
- package/dist/components/Chip/chip.d.ts +30 -11
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +83 -33
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Chip/index.d.ts.map +1 -0
- package/dist/components/Chip/index.js +8 -0
- package/dist/components/Chip/index.js.map +1 -0
- package/dist/components/CircularProgress/circular-progress.d.ts +2 -3
- package/dist/components/CircularProgress/circular-progress.d.ts.map +1 -1
- package/dist/components/CircularProgress/circular-progress.js +5 -4
- package/dist/components/CircularProgress/circular-progress.js.map +1 -1
- package/dist/components/CircularProgress/index.d.ts +2 -0
- package/dist/components/CircularProgress/index.d.ts.map +1 -0
- package/dist/components/CircularProgress/index.js +6 -0
- package/dist/components/CircularProgress/index.js.map +1 -0
- package/dist/components/Coachmark/coachmark-story-helpers.d.ts +22 -0
- package/dist/components/Coachmark/coachmark-story-helpers.d.ts.map +1 -0
- package/dist/components/Coachmark/coachmark.d.ts +15 -6
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js +12 -4
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Coachmark/index.d.ts +2 -0
- package/dist/components/Coachmark/index.d.ts.map +1 -0
- package/dist/components/Coachmark/index.js +6 -0
- package/dist/components/Coachmark/index.js.map +1 -0
- package/dist/components/Combobox/combobox.d.ts +39 -15
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +169 -65
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Combobox/index.d.ts +2 -0
- package/dist/components/Combobox/index.d.ts.map +1 -0
- package/dist/components/Combobox/index.js +6 -0
- package/dist/components/Combobox/index.js.map +1 -0
- package/dist/components/Command/command.d.ts +7 -3
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +23 -10
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/Command/index.d.ts +2 -0
- package/dist/components/Command/index.d.ts.map +1 -0
- package/dist/components/Command/index.js +14 -0
- package/dist/components/Command/index.js.map +1 -0
- package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts +5 -5
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +89 -95
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +11 -21
- package/dist/components/DataTable/column-types.d.ts.map +1 -1
- package/dist/components/DataTable/column-types.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +4 -3
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +193 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts +51 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.js +159 -0
- package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-labels.d.ts +44 -0
- package/dist/components/DataTable/data-table-filter-labels.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-labels.js +55 -0
- package/dist/components/DataTable/data-table-filter-labels.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts +12 -3
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +316 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +32 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js +205 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +17 -20
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.d.ts +5 -5
- package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +150 -0
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +66 -15
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +367 -185
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-operators.d.ts +1 -1
- package/dist/components/DataTable/filter-operators.js +140 -0
- package/dist/components/DataTable/filter-operators.js.map +1 -0
- package/dist/components/DataTable/filter-tree.d.ts +6 -1
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
- package/dist/components/DataTable/filter-tree.js +318 -0
- package/dist/components/DataTable/filter-tree.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +5 -0
- package/dist/components/DataTable/index.d.ts.map +1 -0
- package/dist/components/DataTable/index.js +21 -0
- package/dist/components/DataTable/index.js.map +1 -0
- package/dist/components/DataTable/lib/column-meta.js +25 -0
- package/dist/components/DataTable/lib/column-meta.js.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts +23 -19
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +60 -12
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DateGrid/index.d.ts +2 -0
- package/dist/components/DateGrid/index.d.ts.map +1 -0
- package/dist/components/DateGrid/index.js +6 -0
- package/dist/components/DateGrid/index.js.map +1 -0
- package/dist/components/DatePicker/date-picker.d.ts +12 -12
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +418 -309
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DatePicker/index.d.ts +2 -0
- package/dist/components/DatePicker/index.d.ts.map +1 -0
- package/dist/components/DatePicker/index.js +8 -0
- package/dist/components/DatePicker/index.js.map +1 -0
- package/dist/components/DescriptionList/description-list.d.ts +6 -6
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +11 -10
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/DescriptionList/index.d.ts +2 -0
- package/dist/components/DescriptionList/index.d.ts.map +1 -0
- package/dist/components/DescriptionList/index.js +7 -0
- package/dist/components/DescriptionList/index.js.map +1 -0
- package/dist/components/Dialog/dialog.d.ts +22 -5
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +44 -23
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/Dialog/index.d.ts +2 -0
- package/dist/components/Dialog/index.d.ts.map +1 -0
- package/dist/components/Dialog/index.js +16 -0
- package/dist/components/Dialog/index.js.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +19 -10
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +79 -28
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/DropdownMenu/index.d.ts +2 -0
- package/dist/components/DropdownMenu/index.d.ts.map +1 -0
- package/dist/components/DropdownMenu/index.js +21 -0
- package/dist/components/DropdownMenu/index.js.map +1 -0
- package/dist/components/Empty/empty.d.ts +14 -6
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js +7 -7
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Empty/index.d.ts +2 -0
- package/dist/components/Empty/index.d.ts.map +1 -0
- package/dist/components/Empty/index.js +6 -0
- package/dist/components/Empty/index.js.map +1 -0
- package/dist/components/Field/field-context.d.ts +111 -4
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +70 -0
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +21 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
- package/dist/components/Field/field-edit-keys.js +19 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +22 -3
- package/dist/components/Field/field-types.d.ts.map +1 -1
- package/dist/components/Field/field-types.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +24 -6
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +98 -74
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +7 -4
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +39 -6
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/index.d.ts +3 -0
- package/dist/components/Field/index.d.ts.map +1 -0
- package/dist/components/Field/index.js +14 -0
- package/dist/components/Field/index.js.map +1 -0
- package/dist/components/Field/use-form-validation.d.ts +92 -0
- package/dist/components/Field/use-form-validation.d.ts.map +1 -0
- package/dist/components/Field/use-form-validation.js +137 -0
- package/dist/components/Field/use-form-validation.js.map +1 -0
- package/dist/components/FieldControlGroup/field-control-group.d.ts +8 -11
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js +2 -2
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FieldControlGroup/index.d.ts +2 -0
- package/dist/components/FieldControlGroup/index.d.ts.map +1 -0
- package/dist/components/FieldControlGroup/index.js +6 -0
- package/dist/components/FieldControlGroup/index.js.map +1 -0
- package/dist/components/FileItem/file-item.d.ts +14 -2
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +45 -16
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileItem/index.d.ts +2 -0
- package/dist/components/FileItem/index.d.ts.map +1 -0
- package/dist/components/FileItem/index.js +6 -0
- package/dist/components/FileItem/index.js.map +1 -0
- package/dist/components/FileUpload/file-upload.d.ts +39 -19
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +102 -32
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileUpload/index.d.ts +2 -0
- package/dist/components/FileUpload/index.d.ts.map +1 -0
- package/dist/components/FileUpload/index.js +6 -0
- package/dist/components/FileUpload/index.js.map +1 -0
- package/dist/components/FileViewer/can-render-image.d.ts +6 -0
- package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
- package/dist/components/FileViewer/can-render-image.js +11 -0
- package/dist/components/FileViewer/can-render-image.js.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +4 -4
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +88 -50
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts +0 -5
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +23 -10
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/FileViewer/index.d.ts +2 -0
- package/dist/components/FileViewer/index.d.ts.map +1 -0
- package/dist/components/FileViewer/index.js +7 -0
- package/dist/components/FileViewer/index.js.map +1 -0
- package/dist/components/HoverCard/hover-card.d.ts +9 -5
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +12 -5
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/HoverCard/index.d.ts +2 -0
- package/dist/components/HoverCard/index.d.ts.map +1 -0
- package/dist/components/HoverCard/index.js +8 -0
- package/dist/components/HoverCard/index.js.map +1 -0
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.d.ts.map +1 -0
- package/dist/components/InlineEdit/index.js +6 -0
- package/dist/components/InlineEdit/index.js.map +1 -0
- package/dist/components/InlineEdit/inline-edit.d.ts +86 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
- package/dist/components/InlineEdit/inline-edit.js +254 -0
- package/dist/components/InlineEdit/inline-edit.js.map +1 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.d.ts.map +1 -0
- package/dist/components/Input/index.js +6 -0
- package/dist/components/Input/index.js.map +1 -0
- package/dist/components/Input/input.d.ts +31 -10
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +67 -30
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/index.d.ts +2 -0
- package/dist/components/LinkInput/index.d.ts.map +1 -0
- package/dist/components/LinkInput/index.js +6 -0
- package/dist/components/LinkInput/index.js.map +1 -0
- package/dist/components/LinkInput/link-input.d.ts +9 -9
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +40 -37
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/index.d.ts +2 -0
- package/dist/components/Menu/index.d.ts.map +1 -0
- package/dist/components/Menu/index.js +9 -0
- package/dist/components/Menu/index.js.map +1 -0
- package/dist/components/Menu/menu-item.d.ts +20 -4
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +37 -9
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/index.d.ts +2 -0
- package/dist/components/Notice/index.d.ts.map +1 -0
- package/dist/components/Notice/index.js +8 -0
- package/dist/components/Notice/index.js.map +1 -0
- package/dist/components/Notice/notice.d.ts +35 -7
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +34 -10
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/index.d.ts +2 -0
- package/dist/components/NumberInput/index.d.ts.map +1 -0
- package/dist/components/NumberInput/index.js +7 -0
- package/dist/components/NumberInput/index.js.map +1 -0
- package/dist/components/NumberInput/number-input.d.ts +5 -5
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +40 -18
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/index.d.ts +2 -0
- package/dist/components/OverflowIndicator/index.d.ts.map +1 -0
- package/dist/components/OverflowIndicator/index.js +6 -0
- package/dist/components/OverflowIndicator/index.js.map +1 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +6 -2
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +11 -7
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +6 -0
- package/dist/components/Pagination/index.js.map +1 -0
- package/dist/components/Pagination/pagination.d.ts +49 -0
- package/dist/components/Pagination/pagination.d.ts.map +1 -0
- package/dist/components/Pagination/pagination.js +182 -0
- package/dist/components/Pagination/pagination.js.map +1 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
- package/dist/components/PeoplePicker/index.d.ts +2 -0
- package/dist/components/PeoplePicker/index.d.ts.map +1 -0
- package/dist/components/PeoplePicker/index.js +15 -0
- package/dist/components/PeoplePicker/index.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.js +2 -2
- package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +26 -10
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +97 -65
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +19 -11
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +39 -23
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/index.d.ts +2 -0
- package/dist/components/Popover/index.d.ts.map +1 -0
- package/dist/components/Popover/index.js +14 -0
- package/dist/components/Popover/index.js.map +1 -0
- package/dist/components/Popover/popover.d.ts +2 -2
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +72 -42
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/index.d.ts +2 -0
- package/dist/components/ProfileCard/index.d.ts.map +1 -0
- package/dist/components/ProfileCard/index.js +8 -0
- package/dist/components/ProfileCard/index.js.map +1 -0
- package/dist/components/{NameCard/name-card.d.ts → ProfileCard/profile-card.d.ts} +34 -20
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -0
- package/dist/components/{NameCard/name-card.js → ProfileCard/profile-card.js} +27 -16
- package/dist/components/ProfileCard/profile-card.js.map +1 -0
- package/dist/components/ProgressBar/index.d.ts +2 -0
- package/dist/components/ProgressBar/index.d.ts.map +1 -0
- package/dist/components/ProgressBar/index.js +6 -0
- package/dist/components/ProgressBar/index.js.map +1 -0
- package/dist/components/ProgressBar/progress-bar.d.ts +2 -14
- package/dist/components/ProgressBar/progress-bar.d.ts.map +1 -1
- package/dist/components/ProgressBar/progress-bar.js +5 -9
- package/dist/components/ProgressBar/progress-bar.js.map +1 -1
- package/dist/components/RadioGroup/index.d.ts +2 -0
- package/dist/components/RadioGroup/index.d.ts.map +1 -0
- package/dist/components/RadioGroup/index.js +8 -0
- package/dist/components/RadioGroup/index.js.map +1 -0
- package/dist/components/RadioGroup/radio-group.d.ts +18 -12
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +131 -40
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/index.d.ts +2 -0
- package/dist/components/Rating/index.d.ts.map +1 -0
- package/dist/components/Rating/index.js +6 -0
- package/dist/components/Rating/index.js.map +1 -0
- package/dist/components/Rating/rating.d.ts +6 -2
- package/dist/components/Rating/rating.d.ts.map +1 -1
- package/dist/components/Rating/rating.js +17 -10
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/index.d.ts +2 -0
- package/dist/components/ScrollArea/index.d.ts.map +1 -0
- package/dist/components/ScrollArea/index.js +7 -0
- package/dist/components/ScrollArea/index.js.map +1 -0
- package/dist/components/ScrollArea/scroll-area.d.ts +16 -4
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +9 -6
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/index.d.ts +2 -0
- package/dist/components/SegmentedControl/index.d.ts.map +1 -0
- package/dist/components/SegmentedControl/index.js +9 -0
- package/dist/components/SegmentedControl/index.js.map +1 -0
- package/dist/components/SegmentedControl/segmented-control.d.ts +8 -7
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +31 -20
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/index.d.ts +2 -0
- package/dist/components/Select/index.d.ts.map +1 -0
- package/dist/components/Select/index.js +6 -0
- package/dist/components/Select/index.js.map +1 -0
- package/dist/components/Select/select.d.ts +70 -13
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +271 -83
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.d.ts +2 -0
- package/dist/components/SelectMenu/index.d.ts.map +1 -0
- package/dist/components/SelectMenu/index.js +8 -0
- package/dist/components/SelectMenu/index.js.map +1 -0
- package/dist/components/SelectMenu/select-menu.d.ts +61 -10
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +216 -93
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
- package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
- package/dist/components/SelectionControl/boolean-value.js +23 -0
- package/dist/components/SelectionControl/boolean-value.js.map +1 -0
- package/dist/components/SelectionControl/index.d.ts +3 -0
- package/dist/components/SelectionControl/index.d.ts.map +1 -0
- package/dist/components/SelectionControl/index.js +9 -0
- package/dist/components/SelectionControl/index.js.map +1 -0
- package/dist/components/SelectionControl/selection-item.d.ts +5 -1
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
- package/dist/components/SelectionControl/selection-item.js +3 -1
- package/dist/components/SelectionControl/selection-item.js.map +1 -1
- package/dist/components/Separator/index.d.ts +2 -0
- package/dist/components/Separator/index.d.ts.map +1 -0
- package/dist/components/Separator/index.js +6 -0
- package/dist/components/Separator/index.js.map +1 -0
- package/dist/components/Separator/separator.d.ts +2 -2
- package/dist/components/Separator/separator.js +4 -3
- package/dist/components/Separator/separator.js.map +1 -1
- package/dist/components/Sheet/index.d.ts +2 -0
- package/dist/components/Sheet/index.d.ts.map +1 -0
- package/dist/components/Sheet/index.js +18 -0
- package/dist/components/Sheet/index.js.map +1 -0
- package/dist/components/Sheet/sheet.d.ts +15 -8
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +9 -5
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/index.d.ts +2 -0
- package/dist/components/Sidebar/index.d.ts.map +1 -0
- package/dist/components/Sidebar/index.js +24 -0
- package/dist/components/Sidebar/index.js.map +1 -0
- package/dist/components/Sidebar/sidebar.d.ts +17 -11
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +78 -35
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Skeleton/index.d.ts.map +1 -0
- package/dist/components/Skeleton/index.js +6 -0
- package/dist/components/Skeleton/index.js.map +1 -0
- package/dist/components/Skeleton/skeleton.d.ts +2 -2
- package/dist/components/Skeleton/skeleton.js +4 -3
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Slider/index.d.ts.map +1 -0
- package/dist/components/Slider/index.js +7 -0
- package/dist/components/Slider/index.js.map +1 -0
- package/dist/components/Slider/slider.d.ts +7 -7
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +39 -17
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/index.d.ts +2 -0
- package/dist/components/Steps/index.d.ts.map +1 -0
- package/dist/components/Steps/index.js +12 -0
- package/dist/components/Steps/index.js.map +1 -0
- package/dist/components/Steps/steps.d.ts +8 -6
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +74 -32
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.d.ts.map +1 -0
- package/dist/components/Switch/index.js +7 -0
- package/dist/components/Switch/index.js.map +1 -0
- package/dist/components/Switch/switch.d.ts +20 -18
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +133 -26
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/index.d.ts +2 -0
- package/dist/components/Tabs/index.d.ts.map +1 -0
- package/dist/components/Tabs/index.js +10 -0
- package/dist/components/Tabs/index.js.map +1 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.d.ts +31 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.d.ts.map +1 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.js +43 -0
- package/dist/components/Tabs/tabs-inline-action-geometry.js.map +1 -0
- package/dist/components/Tabs/tabs.d.ts +14 -11
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +184 -69
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/index.d.ts +2 -0
- package/dist/components/Tag/index.d.ts.map +1 -0
- package/dist/components/Tag/index.js +7 -0
- package/dist/components/Tag/index.js.map +1 -0
- package/dist/components/Tag/tag.d.ts +39 -23
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +71 -63
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/index.d.ts +2 -0
- package/dist/components/Textarea/index.d.ts.map +1 -0
- package/dist/components/Textarea/index.js +7 -0
- package/dist/components/Textarea/index.js.map +1 -0
- package/dist/components/Textarea/textarea.d.ts +24 -17
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +110 -72
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/index.d.ts +2 -0
- package/dist/components/TimePicker/index.d.ts.map +1 -0
- package/dist/components/TimePicker/index.js +6 -0
- package/dist/components/TimePicker/index.js.map +1 -0
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
- package/dist/components/TimePicker/time-columns.js +44 -36
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +8 -7
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +71 -53
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/index.d.ts +2 -0
- package/dist/components/Toast/index.d.ts.map +1 -0
- package/dist/components/Toast/index.js +7 -0
- package/dist/components/Toast/index.js.map +1 -0
- package/dist/components/Toast/toast.d.ts +2 -2
- package/dist/components/Toast/toast.d.ts.map +1 -1
- package/dist/components/Toast/toast.js +82 -20
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/index.d.ts +2 -0
- package/dist/components/Tooltip/index.d.ts.map +1 -0
- package/dist/components/Tooltip/index.js +9 -0
- package/dist/components/Tooltip/index.js.map +1 -0
- package/dist/components/Tooltip/tooltip.d.ts +2 -2
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +57 -25
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/index.d.ts +2 -0
- package/dist/components/TreeView/index.d.ts.map +1 -0
- package/dist/components/TreeView/index.js +8 -0
- package/dist/components/TreeView/index.js.map +1 -0
- package/dist/components/TreeView/tree-view.d.ts +71 -14
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +313 -34
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-controllable.d.ts.map +1 -1
- package/dist/hooks/use-controllable.js +4 -2
- package/dist/hooks/use-controllable.js.map +1 -1
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
- package/dist/hooks/use-is-touch-device.js +3 -1
- package/dist/hooks/use-is-touch-device.js.map +1 -1
- package/dist/hooks/use-overflow-items.d.ts +10 -6
- package/dist/hooks/use-overflow-items.d.ts.map +1 -1
- package/dist/hooks/use-overflow-items.js.map +1 -1
- package/dist/hooks/use-truncated.d.ts +27 -0
- package/dist/hooks/use-truncated.d.ts.map +1 -0
- package/dist/hooks/use-truncated.js +57 -0
- package/dist/hooks/use-truncated.js.map +1 -0
- package/dist/{react-day-picker.css → index.css} +59 -0
- package/dist/index.d.ts +121 -67
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +143 -160
- package/dist/index.js.map +1 -1
- package/dist/lib/drag-visual.d.ts +2 -2
- package/dist/lib/drag-visual.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +24 -2
- package/dist/lib/utils.js.map +1 -1
- package/dist/patterns/element-anatomy/index.d.ts +3 -0
- package/dist/patterns/element-anatomy/index.d.ts.map +1 -0
- package/dist/patterns/element-anatomy/index.js +22 -0
- package/dist/patterns/element-anatomy/index.js.map +1 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +32 -16
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +83 -35
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/element-anatomy/truncated-text.d.ts +27 -0
- package/dist/patterns/element-anatomy/truncated-text.d.ts.map +1 -0
- package/dist/patterns/element-anatomy/truncated-text.js +16 -0
- package/dist/patterns/element-anatomy/truncated-text.js.map +1 -0
- package/dist/patterns/header-canonical/chrome-header.d.ts +20 -4
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.js +4 -2
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
- package/dist/patterns/header-canonical/index.d.ts +2 -0
- package/dist/patterns/header-canonical/index.d.ts.map +1 -0
- package/dist/patterns/header-canonical/index.js +6 -0
- package/dist/patterns/header-canonical/index.js.map +1 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts +1 -1
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
- package/dist/patterns/horizontal-overflow/index.d.ts +2 -0
- package/dist/patterns/horizontal-overflow/index.d.ts.map +1 -0
- package/dist/patterns/horizontal-overflow/index.js +14 -0
- package/dist/patterns/horizontal-overflow/index.js.map +1 -0
- package/dist/patterns/overlay-surface/index.d.ts +2 -0
- package/dist/patterns/overlay-surface/index.d.ts.map +1 -0
- package/dist/patterns/overlay-surface/index.js +8 -0
- package/dist/patterns/overlay-surface/index.js.map +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +12 -2
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js +6 -2
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/patterns/resize-handle/index.d.ts +2 -0
- package/dist/patterns/resize-handle/index.d.ts.map +1 -0
- package/dist/patterns/resize-handle/index.js +5 -0
- package/dist/patterns/resize-handle/index.js.map +1 -0
- package/dist/patterns/resize-handle/resize-handle.d.ts +9 -11
- package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -1
- package/dist/patterns/resize-handle/resize-handle.js +3 -6
- package/dist/patterns/resize-handle/resize-handle.js.map +1 -1
- package/dist/story-governance/category-matrix.d.ts +35 -0
- package/dist/story-governance/category-matrix.d.ts.map +1 -0
- package/dist/tokens/categorical-color.d.ts +60 -0
- package/dist/tokens/categorical-color.d.ts.map +1 -0
- package/dist/tokens/categorical-color.js +125 -0
- package/dist/tokens/categorical-color.js.map +1 -0
- package/dist/tokens/motion/motion.d.ts +8 -7
- package/dist/tokens/motion/motion.d.ts.map +1 -1
- package/dist/tokens/motion/motion.js +6 -6
- package/dist/tokens/motion/motion.js.map +1 -1
- package/dist/tokens/motion/overlay-motion.d.ts +19 -0
- package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
- package/dist/tokens/motion/overlay-motion.js +9 -0
- package/dist/tokens/motion/overlay-motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.d.ts +3 -4
- package/dist/tokens/uiSize/icon-size.d.ts.map +1 -1
- package/dist/tokens/uiSize/icon-size.js +5 -0
- package/dist/tokens/uiSize/icon-size.js.map +1 -0
- package/ds-canonical/adapters/claude/agents/README.md +58 -0
- package/ds-canonical/adapters/claude/agents/canonical-reviewer.md +14 -0
- package/ds-canonical/adapters/claude-root-instructions.md +24 -0
- package/ds-canonical/adapters/claude-settings-base.json +169 -0
- package/ds-canonical/commands/README.md +9 -14
- package/ds-canonical/fork/AGENTS.md +110 -0
- package/ds-canonical/fork/CLAUDE.md +54 -0
- package/ds-canonical/fork/codex/agents/skills/bug-fix-rhythm/SKILL.md +207 -0
- package/ds-canonical/fork/codex/agents/skills/code-quality-audit/SKILL.md +71 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/SKILL.md +244 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/codex/agents/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +43 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +114 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/SKILL.md +252 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/codex/agents/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/codex/agents/skills/propose-options/SKILL.md +190 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/SKILL.md +246 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/codex/agents/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/codex/agents/skills/scan-similar-bugs/SKILL.md +203 -0
- package/ds-canonical/fork/codex/agents/skills/ux-audit/SKILL.md +135 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/SKILL.md +258 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/codex/agents/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/fork/codex/hooks.json +57 -0
- package/ds-canonical/fork/common/closed-tool-execution.mjs +1005 -0
- package/ds-canonical/fork/common/instruction.md +110 -0
- package/ds-canonical/fork/consumer/governance-check.mjs +3163 -0
- package/ds-canonical/fork/consumer/lib/closed-tool-execution.mjs +1005 -0
- package/ds-canonical/fork/consumer/lock.json +189 -0
- package/ds-canonical/fork/consumer/lock.schema.json +338 -0
- package/ds-canonical/fork/consumer/managed-files/0235cf6b3fd402a3717881dc205b5bf77aed7632e7d7d6f1811a40e3033bd816.blob +145 -0
- package/ds-canonical/fork/consumer/managed-files/145aab4b4fc470fc9ba7679b4a1d87d4f6a6d604940bd40f8d675c8af696498f.blob +53 -0
- package/ds-canonical/fork/consumer/managed-files/189e551431181e4a88783c02130d2787b801ef731c86e35bcde89a7313aa020e.blob +149 -0
- package/ds-canonical/fork/consumer/managed-files/24ad71c8613ddcf6fd23818cb3bb477a1fb6d83af4550b0bad43099813088686.blob +28 -0
- package/ds-canonical/fork/consumer/managed-files/2d1ef5617a5ce3c39ad9aae1c42af7d199f9270c0a0ccb490b63d62dbc6913db.blob +1005 -0
- package/ds-canonical/fork/consumer/managed-files/3625e02235cf166a50649a4d704cfb68fead69d0e168c481df013ebae9f6be25.blob +1412 -0
- package/ds-canonical/fork/consumer/managed-files/3ddedc67b464bdfe3dde9803a2d917920c96d633678b802eee638a44cd47366a.blob +8 -0
- package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +348 -0
- package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +549 -0
- package/ds-canonical/fork/consumer/managed-files/475fba657eaeb8abd88f60635f3eadab27a9d0d3114ba4fffc65f43a2a0fe6e2.blob +124 -0
- package/ds-canonical/fork/consumer/managed-files/5aeda1df235815f40dd755951f2b868d4e02035653d4536a8dde209cdc75b493.blob +268 -0
- package/ds-canonical/fork/consumer/managed-files/61c6aca6d7f04bedbf556eaa9d3043681fe48cbe0e93a8ccb1d69a7c5e23fdcf.blob +113 -0
- package/ds-canonical/fork/consumer/managed-files/63061e05969667005020166ea2ae7df3c96663f7a928e7dd3eebc1471022df56.blob +2454 -0
- package/ds-canonical/fork/consumer/managed-files/65410b66e8799a560e07b71779c186fed2a0ef50575ac44745df7a0938a9c2b4.blob +321 -0
- package/ds-canonical/fork/consumer/managed-files/6b746628ac11fcccbe277ef6fea94cc2a7b90ea2822e83536eae80d5ee4b3fa5.blob +167 -0
- package/ds-canonical/fork/consumer/managed-files/76d485470d30ff014afacf7ce643763de9f4f956d9fc8128dbc1814774cf2e63.blob +671 -0
- package/ds-canonical/fork/consumer/managed-files/7937321877e5c34c27dae36c0d507d21cd42fa3f46a8f11a284b8b4a52b6faa4.blob +84 -0
- package/ds-canonical/fork/consumer/managed-files/794bb911ba8ebef58ab0d44a55b9968a19d2c62bcfb310b1ee3a84166f087f21.blob +924 -0
- package/ds-canonical/fork/consumer/managed-files/7ab15a8aeca39485d7b9b57234bbc4a69e0b9e70b4a851dfe10f6447127cee78.blob +51 -0
- package/ds-canonical/fork/consumer/managed-files/7b17835d0ceb17cdcdcaf930540a3e5d0943a34a747ac43c12ca8cd485c11581.blob +44 -0
- package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +900 -0
- package/ds-canonical/fork/consumer/managed-files/81037efa5b3b9f4a875704005fb268acdc9233bb901f44902e4e37e1da92b8e3.blob +46 -0
- package/ds-canonical/fork/consumer/managed-files/884721550b56308812b20917adac7a12f36fdb4ecda6b02460a7f5a219c0c966.blob +82 -0
- package/ds-canonical/fork/consumer/managed-files/91f414b9462ddc1e4be1d4c6ca4ac7a7340a6eec9e98ee109cbc049d3b354b6a.blob +36 -0
- package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +121 -0
- package/ds-canonical/fork/consumer/managed-files/93a89f16f6717d1e4e4e27ec20ef916e3355c7aa890ca74cd9b12db30d8a899f.blob +36 -0
- package/ds-canonical/fork/consumer/managed-files/974521d6b8fe796ea4c58b3c78b0c2b8a1be262b97100d77aaf4cc9c7724bcab.blob +45 -0
- package/ds-canonical/fork/consumer/managed-files/9791c85388d2e2e2bd564e2550fadc3351c4c960a47ce838098afcba7544eb90.blob +973 -0
- package/ds-canonical/fork/consumer/managed-files/a448d2222133335c30e6fda5116e26a40db3283a19d62ccc0663c30d272262a7.blob +174 -0
- package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +2639 -0
- package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +620 -0
- package/ds-canonical/fork/consumer/managed-files/c4aa767f11714467de0038a2149cf78074baddc17fc6a9e074b6aed7544c6f3c.blob +80 -0
- package/ds-canonical/fork/consumer/managed-files/c893e9b734b162f4c631fef871128c16a9ab565f796a0e76d7f320f821f220f5.blob +41 -0
- package/ds-canonical/fork/consumer/managed-files/cd9c61f8ff24fb9bcc3663e346e2cfa419cfb84e02aae18d3dc66dd192681696.blob +9 -0
- package/ds-canonical/fork/consumer/managed-files/da2ad2737315d25a5300905ddefd62f1695056a8a1af155c5b34170ad9923bce.blob +38 -0
- package/ds-canonical/fork/consumer/managed-files/e26057408661597aac4a57cdf7e30e2f9d0afb1b9a2dbbb692c3805cd9028ca5.blob +250 -0
- package/ds-canonical/fork/consumer/managed-files/e68ae1915965d8e17d4b2441c00c2b97fe062ea308ae8535c9eebb2fc16fa245.blob +20 -0
- package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +182 -0
- package/ds-canonical/fork/consumer/managed-files/e813096d69c49812e40e00be9ac8fa14d77f0ec1f97ab4c45cb096b3bedd35de.blob +3 -0
- package/ds-canonical/fork/consumer/managed-files/e930ebca6fd8eeb30802aac0930efae7bcc44cc0996f24de07c11ca6a081fedf.blob +277 -0
- package/ds-canonical/fork/consumer/managed-files/ea94e914d5c8acb84ad7660e5bda0f01b6ccc8cf96b0f6b1ce048f8938d0f5b5.blob +47 -0
- package/ds-canonical/fork/consumer/managed-files/f4db6e616d5229aa7e700c6a3ace022cb9d90baae74e38ca65fce617f18e0d9b.blob +110 -0
- package/ds-canonical/fork/consumer/managed-files/faa128c26102415dc5345e68698efbbd28a395c35ee278dceae044cf430692b2.blob +11 -0
- package/ds-canonical/fork/governance.lock +1696 -0
- package/ds-canonical/fork/hooks/_log-fire.sh +143 -0
- package/ds-canonical/fork/hooks/block_prototype_imports.py +241 -0
- package/ds-canonical/fork/hooks/check_chrome_header_avatar_canonical.sh +106 -0
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +731 -0
- package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +173 -0
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +219 -0
- package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +57 -0
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +213 -0
- package/ds-canonical/fork/hooks/check_field_family_invariants.sh +376 -0
- package/ds-canonical/fork/hooks/check_fork_product_quality.sh +38 -0
- package/ds-canonical/fork/hooks/check_item_list_gap.sh +56 -0
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +160 -0
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +319 -0
- package/ds-canonical/fork/hooks/check_pattern_invariants.sh +339 -0
- package/ds-canonical/fork/hooks/check_sidebar_menu_button_implicit_wrap.sh +99 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +39 -0
- package/ds-canonical/fork/hooks/check_story_invariants.sh +400 -0
- package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +241 -0
- package/ds-canonical/fork/hooks/check_tailwind_wildcard_in_docs.sh +86 -0
- package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +333 -0
- package/ds-canonical/fork/hooks/lib/_hook_integrity.sh +158 -0
- package/ds-canonical/fork/hooks/lib/_micro_geometry.sh +291 -0
- package/ds-canonical/fork/hooks/lib/_provider_paths.sh +162 -0
- package/ds-canonical/fork/launchers/approval-evidence.mjs +1321 -0
- package/ds-canonical/fork/launchers/authority-decision-evidence.mjs +413 -0
- package/ds-canonical/fork/launchers/canonical-order.mjs +8 -0
- package/ds-canonical/fork/launchers/fork-governance-dispatcher.sh +154 -0
- package/ds-canonical/fork/launchers/format-provider-hook-output.mjs +25 -0
- package/ds-canonical/fork/launchers/independent-review.mjs +490 -0
- package/ds-canonical/fork/launchers/inject_fork_governance_preamble.sh +99 -0
- package/ds-canonical/fork/launchers/normalize-provider-event.mjs +355 -0
- package/ds-canonical/fork/launchers/product-hook-dispatch-runtime.mjs +1067 -0
- package/ds-canonical/fork/launchers/provider-hook-normalization.mjs +1640 -0
- package/ds-canonical/fork/launchers/provider-hook-output-transport.mjs +540 -0
- package/ds-canonical/fork/launchers/settings-hooks.json +35 -0
- package/ds-canonical/fork/manifest.json +1984 -0
- package/ds-canonical/fork/providers/claude/hook-config.json +327 -0
- package/ds-canonical/fork/providers/claude/instruction.md +54 -0
- package/ds-canonical/fork/providers/claude/skills/bug-fix-rhythm/SKILL.md +207 -0
- package/ds-canonical/fork/providers/claude/skills/code-quality-audit/SKILL.md +71 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/SKILL.md +244 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/providers/claude/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/providers/claude/skills/independent-review/SKILL.md +43 -0
- package/ds-canonical/fork/providers/claude/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/providers/claude/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +114 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/SKILL.md +252 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/providers/claude/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/providers/claude/skills/propose-options/SKILL.md +190 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/SKILL.md +246 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/providers/claude/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/providers/claude/skills/scan-similar-bugs/SKILL.md +203 -0
- package/ds-canonical/fork/providers/claude/skills/ux-audit/SKILL.md +135 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/SKILL.md +258 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/providers/claude/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/fork/providers/codex/hook-config.json +57 -0
- package/ds-canonical/fork/providers/codex/skills/bug-fix-rhythm/SKILL.md +207 -0
- package/ds-canonical/fork/providers/codex/skills/code-quality-audit/SKILL.md +71 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/SKILL.md +244 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/providers/codex/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/providers/codex/skills/independent-review/SKILL.md +43 -0
- package/ds-canonical/fork/providers/codex/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/providers/codex/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +114 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/SKILL.md +252 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/providers/codex/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/providers/codex/skills/propose-options/SKILL.md +190 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/SKILL.md +246 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/providers/codex/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/providers/codex/skills/scan-similar-bugs/SKILL.md +203 -0
- package/ds-canonical/fork/providers/codex/skills/ux-audit/SKILL.md +135 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/SKILL.md +258 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/providers/codex/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/fork/references/story-baseline-registry.json +123 -0
- package/ds-canonical/fork/references/story-baseline-registry.schema.json +145 -0
- package/ds-canonical/fork/references/utility-registry.json +426 -0
- package/ds-canonical/fork/references/utility-registry.schema.json +409 -0
- package/ds-canonical/fork/review/canonical-order.mjs +8 -0
- package/ds-canonical/fork/review/provider-review-binding.mjs +2377 -0
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +196 -0
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +69 -0
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +242 -0
- package/ds-canonical/fork/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/fork/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/fork/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/fork/skills/independent-review/SKILL.md +37 -0
- package/ds-canonical/fork/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/fork/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/fork/skills/performance-audit/SKILL.md +112 -0
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +250 -0
- package/ds-canonical/fork/skills/product-ui-audit/references/audit-checks.md +250 -0
- package/ds-canonical/fork/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/fork/skills/product-ui-audit/references/report-template.md +163 -0
- package/ds-canonical/fork/skills/propose-options/SKILL.md +179 -0
- package/ds-canonical/fork/skills/prototype/SKILL.md +244 -0
- package/ds-canonical/fork/skills/prototype/references/audit-checks.md +39 -0
- package/ds-canonical/fork/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/fork/skills/prototype/references/checkpoints.md +200 -0
- package/ds-canonical/fork/skills/prototype/references/evaluation-matrix.md +142 -0
- package/ds-canonical/fork/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/fork/skills/prototype/references/proposal-template.md +230 -0
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +201 -0
- package/ds-canonical/fork/skills/ux-audit/SKILL.md +133 -0
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +248 -0
- package/ds-canonical/fork/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/fork/skills/visual-audit/references/audit-architecture.md +101 -0
- package/ds-canonical/fork/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/fork/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/ds-canonical/hooks/README.md +39 -25
- package/ds-canonical/hooks/_log-fire.sh +111 -12
- package/ds-canonical/hooks/auto_regen_ds_barrel.sh +90 -0
- package/ds-canonical/hooks/block_prototype_imports.py +156 -26
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +328 -32
- package/ds-canonical/hooks/check_audit_sample_escape.sh +10 -7
- package/ds-canonical/hooks/check_benchmark_citation.sh +41 -8
- package/ds-canonical/hooks/check_canonical_propagation.sh +85 -24
- package/ds-canonical/hooks/check_chrome_header_avatar_canonical.sh +106 -0
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +138 -29
- package/ds-canonical/hooks/check_codex_collab_5step.sh +31 -25
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +731 -0
- package/ds-canonical/hooks/check_consumer_code_quality.sh +173 -0
- package/ds-canonical/hooks/check_datatable_invariants.sh +173 -29
- package/ds-canonical/hooks/check_dim_count_drift.sh +8 -7
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +219 -0
- package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +57 -0
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +213 -0
- package/ds-canonical/hooks/check_field_controls_contracts.sh +37 -8
- package/ds-canonical/hooks/check_field_family_invariants.sh +195 -24
- package/ds-canonical/hooks/check_file_size_budget.sh +32 -14
- package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +111 -0
- package/ds-canonical/hooks/check_item_list_gap.sh +56 -0
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +160 -0
- package/ds-canonical/hooks/check_main_branch_workbench.sh +27 -68
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +21 -10
- package/ds-canonical/hooks/check_opacity_token_usage.sh +238 -68
- package/ds-canonical/hooks/check_orphan_ds_css.sh +96 -0
- package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +110 -0
- package/ds-canonical/hooks/check_pattern_invariants.sh +160 -15
- package/ds-canonical/hooks/check_peoplepicker_ssot_drift.sh +4 -4
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +6 -4
- package/ds-canonical/hooks/check_plugin_fork_health.sh +282 -0
- package/ds-canonical/hooks/check_propose_discipline.sh +255 -0
- package/ds-canonical/hooks/check_propose_pre_grep_verify.sh +6 -4
- package/ds-canonical/hooks/check_propose_without_benchmark.sh +71 -0
- package/ds-canonical/hooks/check_select_all_canonical.sh +1 -1
- package/ds-canonical/hooks/check_sidebar_menu_button_implicit_wrap.sh +99 -0
- package/ds-canonical/hooks/check_solo_workflow.sh +62 -168
- package/ds-canonical/hooks/check_spec_class_drift.sh +33 -19
- package/ds-canonical/hooks/check_ssot_header_declaration.sh +56 -0
- package/ds-canonical/hooks/check_story_determinism.sh +39 -0
- package/ds-canonical/hooks/check_story_invariants.sh +722 -222
- package/ds-canonical/hooks/check_storybook_addon_packaging.sh +201 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +143 -64
- package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +241 -0
- package/ds-canonical/hooks/check_tailwind_wildcard_in_docs.sh +86 -0
- package/ds-canonical/hooks/check_wrapper_primitive_schema_drift.sh +15 -12
- package/ds-canonical/hooks/chrome_header_dispatcher.sh +105 -0
- package/ds-canonical/hooks/enforce_home_charter.sh +31 -10
- package/ds-canonical/hooks/governance_control_plane.sh +15 -0
- package/ds-canonical/hooks/inject_deploy_url_after_push.sh +333 -0
- package/ds-canonical/hooks/inject_pending_self_audit.sh +68 -21
- package/ds-canonical/hooks/lib/_app_shell_primary_header_consistency.sh +148 -0
- package/ds-canonical/hooks/lib/_chrome_header_handcraft.sh +121 -0
- package/ds-canonical/hooks/lib/_code_quality.sh +9 -9
- package/ds-canonical/hooks/lib/_cva_default_sync.sh +11 -13
- package/ds-canonical/hooks/lib/_governance_coverage_check.sh +48 -21
- package/ds-canonical/hooks/lib/_hardcoded_strings.sh +18 -10
- package/ds-canonical/hooks/lib/_header_with_tabs_border.sh +107 -0
- package/ds-canonical/hooks/lib/_hook_integrity.sh +158 -0
- package/ds-canonical/hooks/lib/_layout_space_canonical.sh +5 -5
- package/ds-canonical/hooks/lib/_micro_geometry.sh +291 -0
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +42 -23
- package/ds-canonical/hooks/lib/_person_data_richness.sh +5 -5
- package/ds-canonical/hooks/lib/_provider_paths.sh +162 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +5 -5
- package/ds-canonical/hooks/lib/_tab_lg_chrome_header_equal.sh +290 -0
- package/ds-canonical/hooks/lib/_token_hygiene.sh +55 -20
- package/ds-canonical/hooks/lib/approval-evidence.mjs +1321 -0
- package/ds-canonical/hooks/lib/audit-report-validator.mjs +122 -0
- package/ds-canonical/hooks/lib/provider-marker-regex.mjs +23 -0
- package/ds-canonical/hooks/lib/tsx-governance-analysis.mjs +410 -0
- package/ds-canonical/hooks/log_governance_fires.sh +45 -13
- package/ds-canonical/hooks/log_skill_invokes.sh +8 -7
- package/ds-canonical/hooks/post_edit_dispatcher.sh +106 -28
- package/ds-canonical/hooks/registrations.json +488 -0
- package/ds-canonical/hooks/registrations.schema.json +58 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_addon_subdir_ship.sh +76 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_app_story_title.sh +81 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_ds_primitive_misuse.sh +120 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_no_ds_catalog.sh +101 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_consumer_story_baseline.sh +76 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_data_table_size_num_to_meta_width.sh +68 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_fork_user_plugin_install.sh +79 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_plugin_freshness.sh +68 -0
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_propose_cite_required.sh +88 -0
- package/ds-canonical/hooks/{check_propose_plain_chinese.sh → retired/2026-06-11-prune-merge/check_propose_plain_chinese.sh} +2 -2
- package/ds-canonical/hooks/retired/2026-06-11-prune-merge/check_storybook_addon_preset_cjs.sh +87 -0
- package/ds-canonical/hooks/{lib/_approval_re.sh → retired/_approval_re.sh.retired-2026-08-18} +6 -7
- package/ds-canonical/hooks/retired/check_container_breathing.sh +1 -1
- package/ds-canonical/hooks/retired/check_governance_compliance.sh +1 -1
- package/ds-canonical/hooks/session_start_governance_check.sh +157 -69
- package/ds-canonical/hooks/stop_audit_content_quality.sh +20 -0
- package/ds-canonical/hooks/stop_extract_canonical_rules.sh +20 -0
- package/ds-canonical/hooks/stop_passive_logging.sh +133 -59
- package/ds-canonical/hooks/stop_self_audit.sh +472 -178
- package/ds-canonical/hooks/tests/fixtures/transcripts/claude-transcript-jsonl-v1.jsonl +3 -0
- package/ds-canonical/hooks/tests/fixtures/transcripts/claude-transcript-jsonl-v2.jsonl +3 -0
- package/ds-canonical/hooks/tests/registration-output-contracts.mjs +151 -0
- package/ds-canonical/hooks/tests/run-all.sh +35 -5
- package/ds-canonical/hooks/tests/test_auto_regen_ds_barrel.sh +224 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +181 -9
- package/ds-canonical/hooks/tests/test_check_addon_subdir_ship.sh +171 -0
- package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +275 -81
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +292 -14
- package/ds-canonical/hooks/tests/test_check_audit_sample_escape.sh +0 -0
- package/ds-canonical/hooks/tests/test_check_benchmark_citation.sh +41 -9
- package/ds-canonical/hooks/tests/test_check_canonical_propagation.sh +56 -5
- package/ds-canonical/hooks/tests/test_check_chrome_header_avatar_canonical.sh +198 -0
- package/ds-canonical/hooks/tests/test_check_chrome_header_handcraft.sh +197 -81
- package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +375 -0
- package/ds-canonical/hooks/tests/test_check_codex_collab_5step.sh +49 -5
- package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +140 -0
- package/ds-canonical/hooks/tests/test_check_consumer_app_story_title.sh +164 -0
- package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +86 -0
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +417 -0
- package/ds-canonical/hooks/tests/test_check_consumer_no_ds_catalog.sh +214 -0
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +209 -0
- package/ds-canonical/hooks/tests/test_check_data_table_size_num_to_meta_width.sh +71 -0
- package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +126 -12
- package/ds-canonical/hooks/tests/test_check_dim_count_drift.sh +13 -8
- package/ds-canonical/hooks/tests/test_check_ds_anchor_preflight.sh +150 -0
- package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +278 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +108 -18
- package/ds-canonical/hooks/tests/test_check_file_size_budget.sh +67 -5
- package/ds-canonical/hooks/tests/test_check_fork_user_plugin_install.sh +151 -0
- package/ds-canonical/hooks/tests/test_check_full_story_visual_interaction_sweep.sh +200 -0
- package/ds-canonical/hooks/tests/test_check_header_with_tabs_border.sh +178 -68
- package/ds-canonical/hooks/tests/test_check_item_list_gap.sh +44 -0
- package/ds-canonical/hooks/tests/test_check_layout_space_magic_numbers.sh +264 -0
- package/ds-canonical/hooks/tests/test_check_main_branch_workbench.sh +45 -110
- package/ds-canonical/hooks/tests/test_check_naming_and_abstraction.sh +19 -2
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +151 -6
- package/ds-canonical/hooks/tests/test_check_orphan_ds_css.sh +228 -0
- package/ds-canonical/hooks/tests/test_check_overlay_open_focus_escape_probe.sh +191 -0
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +103 -7
- package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +132 -0
- package/ds-canonical/hooks/tests/test_check_propose_cite_required.sh +181 -0
- package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +69 -0
- package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +10 -7
- package/ds-canonical/hooks/tests/test_check_propose_pre_grep_verify.sh +5 -5
- package/ds-canonical/hooks/tests/test_check_propose_without_benchmark.sh +100 -0
- package/ds-canonical/hooks/tests/test_check_sidebar_menu_button_implicit_wrap.sh +224 -0
- package/ds-canonical/hooks/tests/test_check_solo_workflow.sh +86 -105
- package/ds-canonical/hooks/tests/test_check_spec_class_drift.sh +35 -7
- package/ds-canonical/hooks/tests/test_check_ssot_header_declaration.sh +72 -0
- package/ds-canonical/hooks/tests/{test_check_story_anatomy.sh.broken → test_check_story_anatomy.sh} +73 -4
- package/ds-canonical/hooks/tests/test_check_story_category.sh +411 -0
- package/ds-canonical/hooks/tests/test_check_story_determinism.sh +39 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +734 -43
- package/ds-canonical/hooks/tests/{test_check_story_name_jargon.sh.broken → test_check_story_name_jargon.sh} +22 -1
- package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +89 -0
- package/ds-canonical/hooks/tests/test_check_storybook_addon_preset_cjs.sh +200 -0
- package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +672 -28
- package/ds-canonical/hooks/tests/test_check_tab_lg_chrome_header_equal.sh +182 -97
- package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_tailwind_wildcard_in_docs.sh +182 -0
- package/ds-canonical/hooks/tests/test_check_token_hygiene.sh +63 -15
- package/ds-canonical/hooks/tests/test_check_wrapper_primitive_schema_drift.sh +12 -6
- package/ds-canonical/hooks/tests/test_chrome_header_dispatcher.sh +125 -0
- package/ds-canonical/hooks/tests/test_enforce_home_charter.sh +58 -4
- package/ds-canonical/hooks/tests/test_governance_control_plane.sh +66 -0
- package/ds-canonical/hooks/tests/test_governance_coverage_check.sh +88 -0
- package/ds-canonical/hooks/tests/test_hook_integrity_classification.sh +217 -0
- package/ds-canonical/hooks/tests/test_inject_deploy_url_after_push.sh +276 -0
- package/ds-canonical/hooks/tests/test_inject_pending_self_audit.sh +116 -11
- package/ds-canonical/hooks/tests/test_log_governance_fires.sh +117 -1
- package/ds-canonical/hooks/tests/test_log_skill_invokes.sh +74 -5
- package/ds-canonical/hooks/tests/test_post_edit_dispatcher.sh +234 -77
- package/ds-canonical/hooks/tests/test_provider_peer_symmetry.sh +76 -0
- package/ds-canonical/hooks/tests/test_registration_output_contracts.sh +20 -0
- package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +145 -22
- package/ds-canonical/hooks/tests/test_stop_audit_content_quality.sh +75 -0
- package/ds-canonical/hooks/tests/test_stop_capture_metrics.sh +101 -23
- package/ds-canonical/hooks/tests/test_stop_extract_canonical_rules.sh +75 -0
- package/ds-canonical/hooks/tests/{test_stop_governance_drift_check.sh.broken → test_stop_governance_drift_check.sh} +22 -12
- package/ds-canonical/hooks/tests/test_stop_passive_logging.sh +102 -1
- package/ds-canonical/hooks/tests/test_stop_repository_command_context.sh +38 -0
- package/ds-canonical/hooks/tests/test_stop_self_audit.sh +427 -13
- package/ds-canonical/references/README.md +19 -11
- package/ds-canonical/references/build-ui-canonicals.md +23 -6
- package/ds-canonical/references/certified-surfaces.md +46 -0
- package/ds-canonical/references/composition-fidelity.md +47 -0
- package/ds-canonical/references/cva-patterns.md +2 -2
- package/ds-canonical/references/drag-canonical.md +67 -311
- package/ds-canonical/references/failure-class-registry.json +160 -0
- package/ds-canonical/references/governance-audit-coverage.md +34 -0
- package/ds-canonical/references/item-anatomy-recipe.md +3 -3
- package/ds-canonical/references/naming-conventions.md +2 -1
- package/ds-canonical/references/principle-dim-map.json +136 -9
- package/ds-canonical/references/props-naming.md +18 -3
- package/ds-canonical/references/repository-hygiene-policy.json +143 -0
- package/ds-canonical/references/repository-hygiene.md +106 -0
- package/ds-canonical/references/runtime-evidence-retention.md +88 -0
- package/ds-canonical/references/scenario-definition.md +146 -0
- package/ds-canonical/references/spec-rules.md +1 -1
- package/ds-canonical/references/ssot-consultation.md +5 -5
- package/ds-canonical/references/ssot-index.md +11 -10
- package/ds-canonical/references/story-baseline-registry.json +55 -11
- package/ds-canonical/references/story-baseline-registry.schema.json +145 -0
- package/ds-canonical/references/structural-token-retention.md +7 -10
- package/ds-canonical/references/tailwind-gotchas.md +4 -4
- package/ds-canonical/references/ui-dev-rules.md +22 -1
- package/ds-canonical/rules/README.md +15 -10
- package/ds-canonical/rules/meta-patterns.md +30 -30
- package/ds-canonical/rules/self-verify.md +22 -12
- package/ds-canonical/rules/spec-rules.md +2 -2
- package/ds-canonical/rules/story-rules.md +36 -9
- package/ds-canonical/rules/ui-development.md +23 -15
- package/ds-canonical/skills/README.md +29 -17
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +26 -13
- package/ds-canonical/skills/canonical-reviewer/SKILL.md +29 -0
- package/ds-canonical/skills/code-quality-audit/SKILL.md +15 -11
- package/ds-canonical/skills/codex-collab/SKILL.md +22 -235
- package/ds-canonical/skills/codex-collab/references/brief-template.md +37 -43
- package/ds-canonical/skills/codex-collab/references/transport.md +38 -57
- package/ds-canonical/skills/codify-corrections/SKILL.md +37 -36
- package/ds-canonical/skills/codify-principle/SKILL.md +35 -34
- package/ds-canonical/skills/component-quality-gate/SKILL.md +17 -17
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +8 -8
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +47 -224
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +36 -102
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +33 -147
- package/ds-canonical/skills/deep-audit-cross-codex/references/skill-relationships-antipatterns-benchmarks.md +28 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +15 -80
- package/ds-canonical/skills/deep-audit-cross-codex/references/upstream-directives-r-mapping.md +34 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +19 -8
- package/ds-canonical/skills/design-system-audit/SKILL.md +130 -76
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +207 -105
- package/ds-canonical/skills/design-system-audit/references/checkpoints.md +59 -50
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +33 -9
- package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +51 -32
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +57 -53
- package/ds-canonical/skills/ensure-canonical/SKILL.md +37 -35
- package/ds-canonical/skills/governance-health/SKILL.md +31 -25
- package/ds-canonical/skills/governance-status/SKILL.md +30 -0
- package/ds-canonical/skills/independent-review/SKILL.md +37 -0
- package/ds-canonical/skills/independent-review/agents/openai.yaml +4 -0
- package/ds-canonical/skills/independent-review/references/evidence-contract.md +23 -0
- package/ds-canonical/skills/knowledge-prune/SKILL.md +57 -114
- package/ds-canonical/skills/knowledge-prune/references/phase-z-cross-repo-ssot-propagation.md +15 -0
- package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +57 -0
- package/ds-canonical/skills/knowledge-prune/references/world-class-prune-alignment.md +7 -0
- package/ds-canonical/skills/new-component/SKILL.md +33 -25
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +12 -11
- package/ds-canonical/skills/performance-audit/SKILL.md +15 -12
- package/ds-canonical/skills/product-ui-audit/SKILL.md +52 -34
- package/ds-canonical/skills/product-ui-audit/references/audit-checks.md +10 -7
- package/ds-canonical/skills/product-ui-audit/references/report-template.md +30 -26
- package/ds-canonical/skills/propose-options/SKILL.md +23 -23
- package/ds-canonical/skills/prototype/SKILL.md +19 -21
- package/ds-canonical/skills/prototype/references/audit-checks.md +6 -5
- package/ds-canonical/skills/prototype/references/checkpoints.md +26 -17
- package/ds-canonical/skills/prototype/references/evaluation-matrix.md +2 -2
- package/ds-canonical/skills/prototype/references/ooux-template.md +1 -1
- package/ds-canonical/skills/prototype/references/proposal-template.md +5 -5
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +29 -28
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +26 -25
- package/ds-canonical/skills/story-writing/SKILL.md +25 -14
- package/ds-canonical/skills/story-writing/references/anatomy-standard.md +2 -2
- package/ds-canonical/skills/story-writing/references/category-templates.md +15 -15
- package/ds-canonical/skills/ux-audit/SKILL.md +13 -10
- package/ds-canonical/skills/visual-audit/SKILL.md +47 -46
- package/ds-canonical/skills/visual-audit/references/audit-architecture.md +7 -7
- package/ds-canonical/skills/visual-audit/references/visual-checklist.md +14 -14
- package/ds-canonical/skills/visual-audit/references/world-class-benchmarks.md +3 -3
- package/ds-canonical/templates/_README.md +22 -0
- package/ds-canonical/templates/consumer-governance.md +82 -0
- package/ds-canonical/templates/dashboard-app.tsx +186 -0
- package/ds-canonical/templates/product-launchers/fork-governance-dispatcher.sh +154 -0
- package/ds-canonical/templates/product-launchers/format-provider-hook-output.mjs +25 -0
- package/ds-canonical/templates/product-launchers/independent-review.mjs +490 -0
- package/ds-canonical/templates/product-launchers/inject_fork_governance_preamble.sh +99 -0
- package/ds-canonical/templates/product-launchers/normalize-provider-event.mjs +355 -0
- package/ds-canonical/templates/product-launchers/product-hook-dispatch-runtime.mjs +1067 -0
- package/ds-canonical/templates/product-provider-wiring.json +82 -0
- package/ds-canonical/templates/product-provider-wiring.schema.json +153 -0
- package/ds-story-manifest.json +1791 -0
- package/llms-full.txt +450 -0
- package/llms.txt +74 -0
- package/package.json +43 -10
- package/src/README.md +1 -1
- package/src/components/Accordion/accordion.anatomy.stories.tsx +496 -0
- package/src/components/Accordion/accordion.principles.stories.tsx +365 -0
- package/src/components/Accordion/accordion.spec.md +31 -5
- package/src/components/Accordion/accordion.stories.tsx +192 -0
- package/src/components/Accordion/accordion.tsx +13 -5
- package/src/components/Accordion/index.ts +5 -0
- package/src/components/AccountMenu/account-menu.anatomy.stories.tsx +117 -0
- package/src/components/AccountMenu/account-menu.principles.stories.tsx +99 -0
- package/src/components/AccountMenu/account-menu.spec.md +127 -0
- package/src/components/AccountMenu/account-menu.stories.tsx +65 -0
- package/src/components/AccountMenu/account-menu.tsx +194 -0
- package/src/components/AccountMenu/index.ts +5 -0
- package/src/components/AgentPanel/agent-fab.tsx +126 -0
- package/src/components/AgentPanel/agent-logo.tsx +491 -0
- package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +127 -0
- package/src/components/AgentPanel/agent-panel.css +59 -0
- package/src/components/AgentPanel/agent-panel.principles.stories.tsx +57 -0
- package/src/components/AgentPanel/agent-panel.spec.md +319 -0
- package/src/components/AgentPanel/agent-panel.stories.tsx +360 -0
- package/src/components/AgentPanel/agent-panel.tsx +1241 -0
- package/src/components/AgentPanel/index.ts +5 -0
- package/src/components/Alert/alert.anatomy.stories.tsx +241 -0
- package/src/components/Alert/alert.principles.stories.tsx +214 -0
- package/src/components/Alert/alert.spec.md +28 -26
- package/src/components/Alert/alert.stories.tsx +136 -0
- package/src/components/Alert/alert.tsx +19 -8
- package/src/components/Alert/index.ts +5 -0
- package/src/components/AppShell/_demo-helpers.tsx +107 -28
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +185 -0
- package/src/components/AppShell/app-shell.principles.stories.tsx +140 -0
- package/src/components/AppShell/app-shell.spec.md +84 -48
- package/src/components/AppShell/app-shell.stories.tsx +445 -0
- package/src/components/AppShell/app-shell.tsx +77 -24
- package/src/components/AppShell/index.ts +5 -0
- package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +208 -0
- package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +303 -0
- package/src/components/AspectRatio/aspect-ratio.spec.md +24 -12
- package/src/components/AspectRatio/aspect-ratio.stories.tsx +91 -0
- package/src/components/AspectRatio/aspect-ratio.tsx +6 -3
- package/src/components/AspectRatio/index.ts +5 -0
- package/src/components/Avatar/avatar.anatomy.stories.tsx +705 -0
- package/src/components/Avatar/avatar.principles.stories.tsx +249 -0
- package/src/components/Avatar/avatar.spec.md +66 -103
- package/src/components/Avatar/avatar.stories.tsx +156 -0
- package/src/components/Avatar/avatar.tsx +85 -64
- package/src/components/Avatar/index.ts +5 -0
- package/src/components/Badge/badge.anatomy.stories.tsx +591 -0
- package/src/components/Badge/badge.principles.stories.tsx +391 -0
- package/src/components/Badge/badge.spec.md +43 -23
- package/src/components/Badge/badge.stories.tsx +82 -0
- package/src/components/Badge/badge.tsx +38 -14
- package/src/components/Badge/index.ts +5 -0
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +462 -0
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +353 -0
- package/src/components/Breadcrumb/breadcrumb.spec.md +55 -43
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +381 -0
- package/src/components/Breadcrumb/breadcrumb.tsx +43 -86
- package/src/components/Breadcrumb/index.ts +5 -0
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +225 -0
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +255 -0
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +64 -36
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +124 -0
- package/src/components/BulkActionBar/bulk-action-bar.tsx +26 -18
- package/src/components/BulkActionBar/index.ts +5 -0
- package/src/components/Button/button-group.tsx +64 -21
- package/src/components/Button/button.anatomy.stories.tsx +729 -0
- package/src/components/Button/button.principles.stories.tsx +369 -0
- package/src/components/Button/button.spec.md +42 -39
- package/src/components/Button/button.stories.tsx +368 -0
- package/src/components/Button/button.tsx +109 -55
- package/src/components/Button/index.ts +6 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +237 -0
- package/src/components/Calendar/calendar.principles.stories.tsx +152 -0
- package/src/components/Calendar/calendar.spec.md +80 -55
- package/src/components/Calendar/calendar.stories.tsx +108 -0
- package/src/components/Calendar/calendar.tsx +200 -134
- package/src/components/Calendar/index.ts +5 -0
- package/src/components/Carousel/carousel.anatomy.stories.tsx +517 -0
- package/src/components/Carousel/carousel.principles.stories.tsx +334 -0
- package/src/components/Carousel/carousel.spec.md +51 -15
- package/src/components/Carousel/carousel.stories.tsx +125 -0
- package/src/components/Carousel/carousel.tsx +139 -56
- package/src/components/Carousel/index.ts +5 -0
- package/src/components/Chart/chart.anatomy.stories.tsx +310 -0
- package/src/components/Chart/chart.principles.stories.tsx +437 -0
- package/src/components/Chart/chart.spec.md +24 -5
- package/src/components/Chart/chart.stories.tsx +263 -0
- package/src/components/Chart/chart.tsx +10 -3
- package/src/components/Chart/index.ts +5 -0
- package/src/components/Checkbox/checkbox-group.tsx +28 -16
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +614 -0
- package/src/components/Checkbox/checkbox.principles.stories.tsx +216 -0
- package/src/components/Checkbox/checkbox.spec.md +65 -42
- package/src/components/Checkbox/checkbox.stories.tsx +119 -0
- package/src/components/Checkbox/checkbox.tsx +189 -32
- package/src/components/Checkbox/index.ts +6 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +442 -0
- package/src/components/Chip/chip.principles.stories.tsx +228 -0
- package/src/components/Chip/chip.spec.md +52 -40
- package/src/components/Chip/chip.stories.tsx +111 -0
- package/src/components/Chip/chip.tsx +130 -45
- package/src/components/Chip/index.ts +5 -0
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +177 -0
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +265 -0
- package/src/components/CircularProgress/circular-progress.spec.md +32 -32
- package/src/components/CircularProgress/circular-progress.stories.tsx +149 -0
- package/src/components/CircularProgress/circular-progress.tsx +5 -6
- package/src/components/CircularProgress/index.ts +5 -0
- package/src/components/Coachmark/coachmark-story-helpers.tsx +39 -0
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +356 -0
- package/src/components/Coachmark/coachmark.principles.stories.tsx +356 -0
- package/src/components/Coachmark/coachmark.spec.md +36 -29
- package/src/components/Coachmark/coachmark.stories.tsx +220 -0
- package/src/components/Coachmark/coachmark.tsx +36 -10
- package/src/components/Coachmark/index.ts +5 -0
- package/src/components/Combobox/combobox.anatomy.stories.tsx +926 -0
- package/src/components/Combobox/combobox.principles.stories.tsx +238 -0
- package/src/components/Combobox/combobox.spec.md +45 -16
- package/src/components/Combobox/combobox.stories.tsx +194 -0
- package/src/components/Combobox/combobox.tsx +278 -97
- package/src/components/Combobox/index.ts +5 -0
- package/src/components/Command/command.anatomy.stories.tsx +107 -0
- package/src/components/Command/command.principles.stories.tsx +86 -0
- package/src/components/Command/command.spec.md +33 -23
- package/src/components/Command/command.stories.tsx +312 -0
- package/src/components/Command/command.tsx +28 -12
- package/src/components/Command/index.ts +5 -0
- package/src/components/DataTable/active-editor-controller.ts +4 -3
- package/src/components/DataTable/cell-registry.tsx +161 -120
- package/src/components/DataTable/column-types.ts +14 -18
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +28 -12
- package/src/components/DataTable/data-table-filter-group.tsx +248 -0
- package/src/components/DataTable/data-table-filter-labels.ts +100 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +85 -448
- package/src/components/DataTable/data-table-filter-value-picker.tsx +254 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +26 -29
- package/src/components/DataTable/data-table-sort-manager.tsx +19 -11
- package/src/components/DataTable/data-table.anatomy.stories.tsx +522 -0
- package/src/components/DataTable/data-table.css +34 -8
- package/src/components/DataTable/data-table.principles.stories.tsx +270 -0
- package/src/components/DataTable/data-table.spec.md +186 -172
- package/src/components/DataTable/data-table.stories.tsx +2311 -0
- package/src/components/DataTable/data-table.tsx +678 -278
- package/src/components/DataTable/filter-operators.spec.md +269 -0
- package/src/components/DataTable/filter-operators.ts +1 -1
- package/src/components/DataTable/filter-tree.ts +194 -44
- package/src/components/DataTable/index.ts +8 -0
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +587 -0
- package/src/components/DateGrid/date-grid.principles.stories.tsx +344 -0
- package/src/components/DateGrid/date-grid.spec.md +46 -41
- package/src/components/DateGrid/date-grid.stories.tsx +204 -0
- package/src/components/DateGrid/date-grid.tsx +89 -28
- package/src/components/DateGrid/index.ts +5 -0
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +946 -0
- package/src/components/DatePicker/date-picker.principles.stories.tsx +205 -0
- package/src/components/DatePicker/date-picker.spec.md +61 -57
- package/src/components/DatePicker/date-picker.stories.tsx +364 -0
- package/src/components/DatePicker/date-picker.tsx +382 -198
- package/src/components/DatePicker/index.ts +5 -0
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +254 -0
- package/src/components/DescriptionList/description-list.principles.stories.tsx +309 -0
- package/src/components/DescriptionList/description-list.spec.md +35 -30
- package/src/components/DescriptionList/description-list.stories.tsx +113 -0
- package/src/components/DescriptionList/description-list.tsx +16 -14
- package/src/components/DescriptionList/index.ts +5 -0
- package/src/components/Dialog/dialog.anatomy.stories.tsx +485 -0
- package/src/components/Dialog/dialog.principles.stories.tsx +261 -0
- package/src/components/Dialog/dialog.spec.md +58 -32
- package/src/components/Dialog/dialog.stories.tsx +548 -0
- package/src/components/Dialog/dialog.tsx +81 -36
- package/src/components/Dialog/index.ts +5 -0
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +971 -0
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +341 -0
- package/src/components/DropdownMenu/dropdown-menu.spec.md +40 -22
- package/src/components/DropdownMenu/dropdown-menu.stories.tsx +276 -0
- package/src/components/DropdownMenu/dropdown-menu.tsx +153 -40
- package/src/components/DropdownMenu/index.ts +5 -0
- package/src/components/Empty/empty.anatomy.stories.tsx +275 -0
- package/src/components/Empty/empty.principles.stories.tsx +314 -0
- package/src/components/Empty/empty.spec.md +42 -42
- package/src/components/Empty/empty.stories.tsx +69 -0
- package/src/components/Empty/empty.tsx +23 -11
- package/src/components/Empty/index.ts +5 -0
- package/src/components/Field/field-context.ts +196 -4
- package/src/components/Field/field-controls.spec.md +161 -54
- package/src/components/Field/field-edit-keys.ts +66 -0
- package/src/components/Field/field-types.ts +71 -20
- package/src/components/Field/field-wrapper.tsx +139 -89
- package/src/components/Field/field.anatomy.stories.tsx +547 -0
- package/src/components/Field/field.principles.stories.tsx +255 -0
- package/src/components/Field/field.spec.md +76 -34
- package/src/components/Field/field.stories.tsx +751 -0
- package/src/components/Field/field.tsx +65 -12
- package/src/components/Field/form-validation.spec.md +60 -34
- package/src/components/Field/index.ts +6 -0
- package/src/components/Field/use-form-validation.ts +270 -0
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +119 -0
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +121 -0
- package/src/components/FieldControlGroup/field-control-group.spec.md +41 -19
- package/src/components/FieldControlGroup/field-control-group.stories.tsx +162 -0
- package/src/components/FieldControlGroup/field-control-group.tsx +13 -15
- package/src/components/FieldControlGroup/index.ts +5 -0
- package/src/components/FileItem/file-item.anatomy.stories.tsx +346 -0
- package/src/components/FileItem/file-item.principles.stories.tsx +245 -0
- package/src/components/FileItem/file-item.spec.md +133 -104
- package/src/components/FileItem/file-item.stories.tsx +289 -0
- package/src/components/FileItem/file-item.tsx +85 -39
- package/src/components/FileItem/index.ts +5 -0
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +446 -0
- package/src/components/FileUpload/file-upload.principles.stories.tsx +312 -0
- package/src/components/FileUpload/file-upload.spec.md +66 -24
- package/src/components/FileUpload/file-upload.stories.tsx +219 -0
- package/src/components/FileUpload/file-upload.tsx +138 -43
- package/src/components/FileUpload/index.ts +5 -0
- package/src/components/FileViewer/can-render-image.ts +15 -0
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1130 -0
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +636 -0
- package/src/components/FileViewer/file-viewer.spec.md +70 -58
- package/src/components/FileViewer/file-viewer.stories.tsx +417 -0
- package/src/components/FileViewer/file-viewer.tsx +157 -72
- package/src/components/FileViewer/image-renderer.tsx +38 -15
- package/src/components/FileViewer/index.ts +5 -0
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +151 -0
- package/src/components/HoverCard/hover-card.principles.stories.tsx +211 -0
- package/src/components/HoverCard/hover-card.spec.md +38 -28
- package/src/components/HoverCard/hover-card.stories.tsx +304 -0
- package/src/components/HoverCard/hover-card.tsx +23 -6
- package/src/components/HoverCard/index.ts +5 -0
- package/src/components/InlineEdit/index.ts +5 -0
- package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +250 -0
- package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
- package/src/components/InlineEdit/inline-edit.spec.md +186 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +261 -0
- package/src/components/InlineEdit/inline-edit.tsx +455 -0
- package/src/components/Input/index.ts +5 -0
- package/src/components/Input/input.anatomy.stories.tsx +780 -0
- package/src/components/Input/input.principles.stories.tsx +260 -0
- package/src/components/Input/input.spec.md +34 -26
- package/src/components/Input/input.stories.tsx +150 -0
- package/src/components/Input/input.tsx +102 -45
- package/src/components/LinkInput/index.ts +5 -0
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +748 -0
- package/src/components/LinkInput/link-input.principles.stories.tsx +182 -0
- package/src/components/LinkInput/link-input.spec.md +25 -7
- package/src/components/LinkInput/link-input.stories.tsx +133 -0
- package/src/components/LinkInput/link-input.tsx +89 -66
- package/src/components/Menu/index.ts +5 -0
- package/src/components/Menu/menu-item.anatomy.stories.tsx +780 -0
- package/src/components/Menu/menu-item.principles.stories.tsx +81 -0
- package/src/components/Menu/menu-item.spec.md +55 -32
- package/src/components/Menu/menu-item.stories.tsx +242 -0
- package/src/components/Menu/menu-item.tsx +71 -12
- package/src/components/Notice/index.ts +5 -0
- package/src/components/Notice/notice.anatomy.stories.tsx +614 -0
- package/src/components/Notice/notice.principles.stories.tsx +102 -0
- package/src/components/Notice/notice.spec.md +39 -18
- package/src/components/Notice/notice.stories.tsx +257 -0
- package/src/components/Notice/notice.tsx +62 -16
- package/src/components/NumberInput/index.ts +5 -0
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +822 -0
- package/src/components/NumberInput/number-input.principles.stories.tsx +206 -0
- package/src/components/NumberInput/number-input.spec.md +35 -16
- package/src/components/NumberInput/number-input.stories.tsx +167 -0
- package/src/components/NumberInput/number-input.tsx +65 -31
- package/src/components/OverflowIndicator/index.ts +5 -0
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +550 -0
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +76 -0
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +36 -26
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +222 -0
- package/src/components/OverflowIndicator/overflow-indicator.tsx +22 -7
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Pagination/pagination.anatomy.stories.tsx +144 -0
- package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
- package/src/components/Pagination/pagination.spec.md +150 -0
- package/src/components/Pagination/pagination.stories.tsx +97 -0
- package/src/components/Pagination/pagination.tsx +263 -0
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
- package/src/components/PeoplePicker/index.ts +5 -0
- package/src/components/PeoplePicker/people-picker-helpers.ts +5 -5
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +437 -0
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +196 -0
- package/src/components/PeoplePicker/people-picker.spec.md +97 -40
- package/src/components/PeoplePicker/people-picker.stories.tsx +245 -0
- package/src/components/PeoplePicker/people-picker.tsx +225 -108
- package/src/components/PeoplePicker/person-display.tsx +83 -47
- package/src/components/Popover/index.ts +5 -0
- package/src/components/Popover/popover.anatomy.stories.tsx +282 -0
- package/src/components/Popover/popover.principles.stories.tsx +316 -0
- package/src/components/Popover/popover.spec.md +41 -31
- package/src/components/Popover/popover.stories.tsx +120 -0
- package/src/components/Popover/popover.tsx +106 -47
- package/src/components/ProfileCard/index.ts +5 -0
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +493 -0
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +225 -0
- package/src/components/ProfileCard/profile-card.spec.md +189 -0
- package/src/components/ProfileCard/profile-card.stories.tsx +57 -0
- package/src/components/{NameCard/name-card.tsx → ProfileCard/profile-card.tsx} +66 -39
- package/src/components/ProgressBar/index.ts +5 -0
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +441 -0
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +326 -0
- package/src/components/ProgressBar/progress-bar.spec.md +25 -21
- package/src/components/ProgressBar/progress-bar.stories.tsx +133 -0
- package/src/components/ProgressBar/progress-bar.tsx +6 -23
- package/src/components/README.md +5 -4
- package/src/components/RadioGroup/index.ts +5 -0
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +672 -0
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +163 -0
- package/src/components/RadioGroup/radio-group.spec.md +15 -9
- package/src/components/RadioGroup/radio-group.stories.tsx +204 -0
- package/src/components/RadioGroup/radio-group.tsx +219 -67
- package/src/components/Rating/index.ts +5 -0
- package/src/components/Rating/rating.anatomy.stories.tsx +498 -0
- package/src/components/Rating/rating.principles.stories.tsx +296 -0
- package/src/components/Rating/rating.spec.md +41 -29
- package/src/components/Rating/rating.stories.tsx +119 -0
- package/src/components/Rating/rating.tsx +37 -18
- package/src/components/ScrollArea/index.ts +5 -0
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +451 -0
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +331 -0
- package/src/components/ScrollArea/scroll-area.spec.md +24 -22
- package/src/components/ScrollArea/scroll-area.stories.tsx +247 -0
- package/src/components/ScrollArea/scroll-area.tsx +24 -7
- package/src/components/SegmentedControl/index.ts +5 -0
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +295 -0
- package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +284 -0
- package/src/components/SegmentedControl/segmented-control.spec.md +38 -39
- package/src/components/SegmentedControl/segmented-control.stories.tsx +37 -0
- package/src/components/SegmentedControl/segmented-control.tsx +44 -24
- package/src/components/Select/index.ts +5 -0
- package/src/components/Select/select.anatomy.stories.tsx +834 -0
- package/src/components/Select/select.principles.stories.tsx +312 -0
- package/src/components/Select/select.spec.md +99 -39
- package/src/components/Select/select.stories.tsx +273 -0
- package/src/components/Select/select.tsx +389 -97
- package/src/components/SelectMenu/index.ts +5 -0
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +830 -0
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +106 -0
- package/src/components/SelectMenu/select-menu.spec.md +51 -32
- package/src/components/SelectMenu/select-menu.stories.tsx +143 -0
- package/src/components/SelectMenu/select-menu.tsx +276 -33
- package/src/components/SelectionControl/boolean-value.tsx +39 -0
- package/src/components/SelectionControl/index.ts +6 -0
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +580 -0
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +77 -0
- package/src/components/SelectionControl/selection-item.spec.md +30 -16
- package/src/components/SelectionControl/selection-item.stories.tsx +137 -0
- package/src/components/SelectionControl/selection-item.tsx +8 -2
- package/src/components/Separator/index.ts +5 -0
- package/src/components/Separator/separator.anatomy.stories.tsx +125 -0
- package/src/components/Separator/separator.principles.stories.tsx +141 -0
- package/src/components/Separator/separator.spec.md +15 -9
- package/src/components/Separator/separator.stories.tsx +106 -0
- package/src/components/Separator/separator.tsx +2 -2
- package/src/components/Sheet/index.ts +5 -0
- package/src/components/Sheet/sheet.anatomy.stories.tsx +269 -0
- package/src/components/Sheet/sheet.principles.stories.tsx +353 -0
- package/src/components/Sheet/sheet.spec.md +16 -17
- package/src/components/Sheet/sheet.stories.tsx +172 -0
- package/src/components/Sheet/sheet.tsx +24 -14
- package/src/components/Sidebar/index.ts +5 -0
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +776 -0
- package/src/components/Sidebar/sidebar.principles.stories.tsx +507 -0
- package/src/components/Sidebar/sidebar.spec.md +65 -62
- package/src/components/Sidebar/sidebar.stories.tsx +535 -0
- package/src/components/Sidebar/sidebar.tsx +134 -57
- package/src/components/Skeleton/index.ts +5 -0
- package/src/components/Skeleton/skeleton.anatomy.stories.tsx +145 -0
- package/src/components/Skeleton/skeleton.principles.stories.tsx +234 -0
- package/src/components/Skeleton/skeleton.spec.md +15 -14
- package/src/components/Skeleton/skeleton.stories.tsx +134 -0
- package/src/components/Skeleton/skeleton.tsx +2 -2
- package/src/components/Slider/index.ts +5 -0
- package/src/components/Slider/slider.anatomy.stories.tsx +237 -0
- package/src/components/Slider/slider.principles.stories.tsx +250 -0
- package/src/components/Slider/slider.spec.md +66 -118
- package/src/components/Slider/slider.stories.tsx +115 -0
- package/src/components/Slider/slider.tsx +83 -26
- package/src/components/Steps/index.ts +5 -0
- package/src/components/Steps/steps.anatomy.stories.tsx +448 -0
- package/src/components/Steps/steps.principles.stories.tsx +300 -0
- package/src/components/Steps/steps.spec.md +91 -74
- package/src/components/Steps/steps.stories.tsx +306 -0
- package/src/components/Steps/steps.tsx +99 -37
- package/src/components/Switch/index.ts +5 -0
- package/src/components/Switch/switch.anatomy.stories.tsx +227 -0
- package/src/components/Switch/switch.principles.stories.tsx +177 -0
- package/src/components/Switch/switch.spec.md +53 -24
- package/src/components/Switch/switch.stories.tsx +95 -0
- package/src/components/Switch/switch.tsx +176 -43
- package/src/components/Tabs/index.ts +5 -0
- package/src/components/Tabs/tabs-inline-action-geometry.ts +94 -0
- package/src/components/Tabs/tabs.anatomy.stories.tsx +424 -0
- package/src/components/Tabs/tabs.principles.stories.tsx +291 -0
- package/src/components/Tabs/tabs.spec.md +58 -69
- package/src/components/Tabs/tabs.stories.tsx +257 -0
- package/src/components/Tabs/tabs.tsx +262 -82
- package/src/components/Tag/index.ts +5 -0
- package/src/components/Tag/tag.anatomy.stories.tsx +667 -0
- package/src/components/Tag/tag.principles.stories.tsx +270 -0
- package/src/components/Tag/tag.spec.md +112 -100
- package/src/components/Tag/tag.stories.tsx +88 -0
- package/src/components/Tag/tag.tsx +120 -80
- package/src/components/Textarea/index.ts +5 -0
- package/src/components/Textarea/textarea.anatomy.stories.tsx +310 -0
- package/src/components/Textarea/textarea.principles.stories.tsx +192 -0
- package/src/components/Textarea/textarea.spec.md +35 -16
- package/src/components/Textarea/textarea.stories.tsx +57 -0
- package/src/components/Textarea/textarea.tsx +154 -83
- package/src/components/TimePicker/index.ts +5 -0
- package/src/components/TimePicker/time-columns.tsx +15 -3
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +283 -0
- package/src/components/TimePicker/time-picker.principles.stories.tsx +133 -0
- package/src/components/TimePicker/time-picker.spec.md +58 -52
- package/src/components/TimePicker/time-picker.stories.tsx +193 -0
- package/src/components/TimePicker/time-picker.tsx +119 -60
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/toast.anatomy.stories.tsx +328 -0
- package/src/components/Toast/toast.principles.stories.tsx +216 -0
- package/src/components/Toast/toast.spec.md +26 -16
- package/src/components/Toast/toast.stories.tsx +241 -0
- package/src/components/Toast/toast.tsx +101 -23
- package/src/components/Tooltip/index.ts +5 -0
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +422 -0
- package/src/components/Tooltip/tooltip.principles.stories.tsx +204 -0
- package/src/components/Tooltip/tooltip.spec.md +37 -23
- package/src/components/Tooltip/tooltip.stories.tsx +82 -0
- package/src/components/Tooltip/tooltip.tsx +58 -12
- package/src/components/TreeView/index.ts +5 -0
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +436 -0
- package/src/components/TreeView/tree-view.principles.stories.tsx +223 -0
- package/src/components/TreeView/tree-view.spec.md +103 -54
- package/src/components/TreeView/tree-view.stories.tsx +365 -0
- package/src/components/TreeView/tree-view.tsx +501 -58
- package/src/hooks/use-controllable.ts +7 -2
- package/src/hooks/use-is-touch-device.ts +8 -1
- package/src/hooks/use-overflow-items.ts +10 -6
- package/src/hooks/use-truncated.ts +111 -0
- package/src/index.ts +651 -71
- package/src/lib/drag-visual.ts +2 -2
- package/src/lib/utils.ts +21 -1
- package/src/patterns/README.md +20 -16
- package/src/patterns/action-bar/action-bar.spec.md +63 -45
- package/src/patterns/action-bar/action-bar.stories.tsx +619 -0
- package/src/patterns/element-anatomy/element-anatomy.spec.md +17 -13
- package/src/patterns/element-anatomy/index.ts +6 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +33 -22
- package/src/patterns/element-anatomy/item-anatomy.spec.md +108 -79
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1648 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +147 -56
- package/src/patterns/element-anatomy/truncated-text.spec.md +68 -0
- package/src/patterns/element-anatomy/truncated-text.tsx +61 -0
- package/src/patterns/header-canonical/chrome-header.tsx +26 -6
- package/src/patterns/header-canonical/header-canonical.css +3 -4
- package/src/patterns/header-canonical/header-canonical.spec.md +96 -42
- package/src/patterns/header-canonical/header-canonical.stories.tsx +257 -0
- package/src/patterns/header-canonical/index.ts +5 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +36 -15
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +139 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +1 -1
- package/src/patterns/horizontal-overflow/index.ts +5 -0
- package/src/patterns/overlay-surface/index.ts +5 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +94 -58
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +132 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +37 -18
- package/src/patterns/resize-handle/index.ts +5 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +22 -13
- package/src/patterns/resize-handle/resize-handle.stories.tsx +73 -0
- package/src/patterns/resize-handle/resize-handle.tsx +13 -18
- package/src/story-governance/category-matrix.json +132 -0
- package/src/story-governance/category-matrix.ts +55 -0
- package/src/styles/base.css +99 -0
- package/src/styles/tokens.css +14 -0
- package/src/tokens/README.md +10 -5
- package/src/tokens/categorical-color.ts +164 -0
- package/src/tokens/color/color.spec.md +95 -72
- package/src/tokens/color/color.stories.tsx +403 -0
- package/src/tokens/color/primitives.css +23 -7
- package/src/tokens/color/semantic.css +75 -21
- package/src/tokens/density/density.spec.md +33 -30
- package/src/tokens/density/density.stories.tsx +254 -0
- package/src/tokens/elevation/elevation.spec.md +11 -14
- package/src/tokens/elevation/elevation.stories.tsx +99 -0
- package/src/tokens/layoutSpace/layoutSpace.css +1 -1
- package/src/tokens/layoutSpace/layoutSpace.spec.md +44 -31
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +76 -0
- package/src/tokens/motion/motion.css +49 -20
- package/src/tokens/motion/motion.spec.md +45 -21
- package/src/tokens/motion/motion.stories.tsx +196 -0
- package/src/tokens/motion/motion.ts +8 -7
- package/src/tokens/motion/overlay-motion.ts +26 -0
- package/src/tokens/opacity/opacity.spec.md +15 -20
- package/src/tokens/opacity/opacity.stories.tsx +66 -0
- package/src/tokens/orphan-tokens.spec.md +22 -11
- package/src/tokens/radius/radius.spec.md +14 -11
- package/src/tokens/radius/radius.stories.tsx +87 -0
- package/src/tokens/token-system.spec.md +241 -0
- package/src/tokens/typography/typography.css +1 -1
- package/src/tokens/typography/typography.spec.md +12 -8
- package/src/tokens/typography/typography.stories.tsx +134 -0
- package/src/tokens/uiSize/icon-size.ts +3 -4
- package/src/tokens/uiSize/uiSize.css +60 -1
- package/src/tokens/uiSize/uiSize.spec.md +61 -32
- package/src/tokens/uiSize/uiSize.stories.tsx +106 -0
- package/src/tokens/utility-registry.json +426 -0
- package/src/tokens/utility-registry.schema.json +409 -0
- package/tools/README.md +9 -0
- package/tools/codemods/cli.mjs +93 -0
- package/tools/codemods/index.mjs +582 -0
- package/tools/shared/safe-filesystem.mjs +371 -0
- package/tools/visual-baseline/cli.mjs +97 -0
- package/tools/visual-baseline/index.mjs +522 -0
- package/dist/components/NameCard/name-card.d.ts.map +0 -1
- package/dist/components/NameCard/name-card.js.map +0 -1
- package/ds-canonical/commands/gov-status.md +0 -79
- package/ds-canonical/hooks/check_app_shell_primary_header_consistency.sh +0 -68
- package/ds-canonical/hooks/check_chrome_header_handcraft.sh +0 -70
- package/ds-canonical/hooks/check_header_with_tabs_border.sh +0 -87
- package/ds-canonical/hooks/check_tab_lg_chrome_header_equal.sh +0 -66
- package/ds-canonical/hooks/tests/KNOWN-BROKEN.md +0 -15
- package/ds-canonical/hooks/tests/test_check_story_category.sh.broken +0 -187
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +0 -74
- package/ds-canonical/skills/design-system-audit/references/spec-template.md +0 -66
- package/src/components/NameCard/name-card.spec.md +0 -171
|
@@ -0,0 +1,3163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Provider-neutral consumer hard gate. Read-only by construction: no install, repair, refresh,
|
|
3
|
+
// registry lookup, or generated-file write. Native Claude/Codex hooks are optional accelerators;
|
|
4
|
+
// this check binds all provider views to the same immutable release BOM and npm payload.
|
|
5
|
+
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
7
|
+
import { spawnSync } from 'node:child_process'
|
|
8
|
+
import {
|
|
9
|
+
chmodSync,
|
|
10
|
+
closeSync,
|
|
11
|
+
constants as fsConstants,
|
|
12
|
+
existsSync,
|
|
13
|
+
fstatSync,
|
|
14
|
+
fsyncSync,
|
|
15
|
+
lstatSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
mkdtempSync,
|
|
18
|
+
openSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
readlinkSync,
|
|
22
|
+
realpathSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from 'node:fs'
|
|
26
|
+
import { dirname, resolve, join, relative, isAbsolute, sep } from 'node:path'
|
|
27
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
28
|
+
import { TextDecoder } from 'node:util'
|
|
29
|
+
import {
|
|
30
|
+
assertClosedGitLocalConfiguration,
|
|
31
|
+
materializeClosedHookToolProfile,
|
|
32
|
+
runClosedGit,
|
|
33
|
+
} from './lib/closed-tool-execution.mjs'
|
|
34
|
+
// This checker is also shipped as the standalone `consumer/governance-check.mjs` corpus entry,
|
|
35
|
+
// where repository managed files have not necessarily been materialized yet. Keep the exact
|
|
36
|
+
// comparator dependency-free at this bootstrap boundary.
|
|
37
|
+
const compareUtf8Bytes = (left, right) => Buffer.compare(
|
|
38
|
+
Buffer.from(String(left), 'utf8'),
|
|
39
|
+
Buffer.from(String(right), 'utf8'),
|
|
40
|
+
)
|
|
41
|
+
const strictUtf8 = new TextDecoder('utf-8', { fatal: true })
|
|
42
|
+
const maxReplaySourceBytes = 16 * 1024 * 1024
|
|
43
|
+
const maxReplayChildOutputBytes = 1024 * 1024
|
|
44
|
+
const replayChildTimeoutMs = 30000
|
|
45
|
+
const replayChildMaxAttempts = 2
|
|
46
|
+
|
|
47
|
+
const args = process.argv.slice(2)
|
|
48
|
+
const valueOf = (flag) => {
|
|
49
|
+
const i = args.indexOf(flag)
|
|
50
|
+
return i >= 0 ? args[i + 1] : undefined
|
|
51
|
+
}
|
|
52
|
+
const repo = resolve(valueOf('--repo') || process.cwd())
|
|
53
|
+
const asJson = args.includes('--json')
|
|
54
|
+
const canonicalOutputRoots = new Set()
|
|
55
|
+
const canonicalReplayExcludedRoots = new Set()
|
|
56
|
+
const canonicalReplayExcludedFiles = new Set()
|
|
57
|
+
const discoveryTraversalExcludedRoots = new Set()
|
|
58
|
+
const exactSemver = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$/
|
|
59
|
+
const stableVersionAtLeast = (value, minimum) => {
|
|
60
|
+
const parse = version => typeof version === 'string'
|
|
61
|
+
? version.match(/^(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/)?.slice(1, 4).map(Number)
|
|
62
|
+
: null
|
|
63
|
+
const observed = parse(value)
|
|
64
|
+
const required = parse(minimum)
|
|
65
|
+
if (!observed || !required) return false
|
|
66
|
+
for (let index = 0; index < 3; index += 1) {
|
|
67
|
+
if (observed[index] !== required[index]) return observed[index] > required[index]
|
|
68
|
+
}
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
const pathsOverlap = (left, right) => left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)
|
|
72
|
+
const canonicalManagedRepositoryPath = (value) => (
|
|
73
|
+
typeof value === 'string'
|
|
74
|
+
&& value.length > 0
|
|
75
|
+
&& value === value.normalize('NFC')
|
|
76
|
+
&& !value.includes('\\')
|
|
77
|
+
&& !value.includes('\0')
|
|
78
|
+
&& !isAbsolute(value)
|
|
79
|
+
&& value.split('/').every((segment) => segment && segment !== '.' && segment !== '..')
|
|
80
|
+
&& value.split('/')[0] !== '.git'
|
|
81
|
+
)
|
|
82
|
+
const portablePathKey = (value) => String(value).split('/')
|
|
83
|
+
.map((segment) => segment.normalize('NFC').toLocaleLowerCase('en-US').normalize('NFC'))
|
|
84
|
+
.join('/')
|
|
85
|
+
const diagnostics = []
|
|
86
|
+
const evidence = {}
|
|
87
|
+
const add = (ruleId, severity, message, detail = undefined) => diagnostics.push({ ruleId, severity, message, ...(detail ? { evidence: detail } : {}) })
|
|
88
|
+
const sha256 = (content) => createHash('sha256').update(content).digest('hex')
|
|
89
|
+
const fileSha = (path) => sha256(readFileSync(path))
|
|
90
|
+
const sameStableFileIdentity = (left, right) => (
|
|
91
|
+
left.dev === right.dev
|
|
92
|
+
&& left.ino === right.ino
|
|
93
|
+
&& left.mode === right.mode
|
|
94
|
+
&& left.nlink === right.nlink
|
|
95
|
+
&& left.size === right.size
|
|
96
|
+
&& left.mtimeNs === right.mtimeNs
|
|
97
|
+
&& left.ctimeNs === right.ctimeNs
|
|
98
|
+
)
|
|
99
|
+
const captureStableRegularFile = (path, maxBytes, label) => {
|
|
100
|
+
let descriptor
|
|
101
|
+
try {
|
|
102
|
+
const beforePath = lstatSync(path, { bigint: true })
|
|
103
|
+
if (
|
|
104
|
+
!beforePath.isFile()
|
|
105
|
+
|| beforePath.isSymbolicLink()
|
|
106
|
+
|| beforePath.nlink !== 1n
|
|
107
|
+
|| beforePath.size > BigInt(maxBytes)
|
|
108
|
+
|| realpathSync(path) !== resolve(path)
|
|
109
|
+
) throw new Error(`${label} is not one bounded single-link regular file`)
|
|
110
|
+
descriptor = openSync(
|
|
111
|
+
path,
|
|
112
|
+
fsConstants.O_RDONLY
|
|
113
|
+
| (fsConstants.O_CLOEXEC ?? 0)
|
|
114
|
+
| (fsConstants.O_NOFOLLOW ?? 0),
|
|
115
|
+
)
|
|
116
|
+
const before = fstatSync(descriptor, { bigint: true })
|
|
117
|
+
if (!sameStableFileIdentity(beforePath, before)) throw new Error(`${label} changed before open`)
|
|
118
|
+
const bytes = readFileSync(descriptor)
|
|
119
|
+
const after = fstatSync(descriptor, { bigint: true })
|
|
120
|
+
const afterPath = lstatSync(path, { bigint: true })
|
|
121
|
+
if (
|
|
122
|
+
!sameStableFileIdentity(before, after)
|
|
123
|
+
|| !sameStableFileIdentity(after, afterPath)
|
|
124
|
+
|| BigInt(bytes.length) !== before.size
|
|
125
|
+
) throw new Error(`${label} changed while captured`)
|
|
126
|
+
return Object.freeze({ bytes, info: after, sha256: sha256(bytes) })
|
|
127
|
+
} finally {
|
|
128
|
+
if (descriptor !== undefined) closeSync(descriptor)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const pathEntryExists = (path) => {
|
|
132
|
+
try { lstatSync(path); return true }
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error?.code === 'ENOENT') return false
|
|
135
|
+
throw error
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const permissionDenied = (error) => error?.code === 'EPERM' || error?.code === 'EACCES'
|
|
139
|
+
// Local provider sandboxes legitimately deny credential-class paths (for example ./.env.*) that
|
|
140
|
+
// the repository may track as inert templates (.env.example). A denied metadata/content read is
|
|
141
|
+
// evidence about the runtime, not about the repository: record it as a deterministic WARNING and
|
|
142
|
+
// a distinct inventory row so a degraded local attestation can never collide with the fully
|
|
143
|
+
// readable CI hard-gate attestation, instead of crashing the whole check.
|
|
144
|
+
const sandboxDeniedPaths = new Set()
|
|
145
|
+
const noteSandboxDenied = (path) => {
|
|
146
|
+
const rel = relative(repo, resolve(path)).replaceAll('\\', '/')
|
|
147
|
+
if (!sandboxDeniedPaths.has(rel)) {
|
|
148
|
+
sandboxDeniedPaths.add(rel)
|
|
149
|
+
add('GOV-SANDBOX-001', 'WARNING', `path is unreadable in this sandboxed runtime; full verification defers to the unsandboxed CI hard gate:${rel}`)
|
|
150
|
+
}
|
|
151
|
+
return rel
|
|
152
|
+
}
|
|
153
|
+
const lstatIfReadable = (path) => {
|
|
154
|
+
try { return lstatSync(path) }
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (!permissionDenied(error)) throw error
|
|
157
|
+
noteSandboxDenied(path)
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const readdirIfReadable = (path, options) => {
|
|
162
|
+
try { return readdirSync(path, options) }
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (!permissionDenied(error)) throw error
|
|
165
|
+
noteSandboxDenied(path)
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const unsafePathReports = new Set()
|
|
170
|
+
const repositoryRootSafe = pathEntryExists(repo) && !lstatSync(repo).isSymbolicLink() && lstatSync(repo).isDirectory()
|
|
171
|
+
if (pathEntryExists(join(repo, 'npm-shrinkwrap.json'))) {
|
|
172
|
+
add(
|
|
173
|
+
'GOV-LOCK-001',
|
|
174
|
+
'BLOCKER',
|
|
175
|
+
'root npm-shrinkwrap.json is forbidden; authenticated package-lock.json is the sole dependency lock authority',
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
const repositoryPathIsSafe = (path) => {
|
|
179
|
+
if (!repositoryRootSafe) return false
|
|
180
|
+
const absolute = resolve(path)
|
|
181
|
+
const rel = relative(repo, absolute)
|
|
182
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
183
|
+
if (!unsafePathReports.has(absolute)) add('GOV-SYMLINK-001', 'BLOCKER', `governance path escapes repository:${absolute}`)
|
|
184
|
+
unsafePathReports.add(absolute)
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
let cursor = repo
|
|
188
|
+
for (const part of rel.split(sep).filter(Boolean)) {
|
|
189
|
+
cursor = join(cursor, part)
|
|
190
|
+
if (!pathEntryExists(cursor)) return true
|
|
191
|
+
if (lstatSync(cursor).isSymbolicLink()) {
|
|
192
|
+
if (!unsafePathReports.has(cursor)) add('GOV-SYMLINK-001', 'BLOCKER', `governance path contains symlink component:${relative(repo, cursor)}`)
|
|
193
|
+
unsafePathReports.add(cursor)
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return true
|
|
198
|
+
}
|
|
199
|
+
if (!repositoryRootSafe) {
|
|
200
|
+
add('GOV-SYMLINK-001', 'BLOCKER', 'repository root must be a real non-symlink directory')
|
|
201
|
+
}
|
|
202
|
+
let closedToolProfile = null
|
|
203
|
+
if (repositoryRootSafe) {
|
|
204
|
+
try {
|
|
205
|
+
closedToolProfile = materializeClosedHookToolProfile({ repoRoot: repo })
|
|
206
|
+
closedToolProfile.verify()
|
|
207
|
+
} catch (error) {
|
|
208
|
+
add('GOV-RUNTIME-001', 'BLOCKER', 'closed governance tool profile is unavailable', error.message)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let closedRepositoryGitReady = false
|
|
212
|
+
if (repositoryRootSafe) {
|
|
213
|
+
try {
|
|
214
|
+
const topLevel = runClosedGit(['rev-parse', '--show-toplevel'], {
|
|
215
|
+
cwd: repo,
|
|
216
|
+
maxOutputBytes: 1024 * 1024,
|
|
217
|
+
timeoutMs: 3000,
|
|
218
|
+
})
|
|
219
|
+
if (topLevel.status === 0 && typeof topLevel.stdout === 'string' && topLevel.stdout.trim()) {
|
|
220
|
+
const observedTopLevel = realpathSync(resolve(topLevel.stdout.trim()))
|
|
221
|
+
if (observedTopLevel !== repo) {
|
|
222
|
+
add('GOV-INVENTORY-001', 'BLOCKER', 'consumer repository must be the exact closed Git top-level')
|
|
223
|
+
} else {
|
|
224
|
+
assertClosedGitLocalConfiguration(repo)
|
|
225
|
+
closedRepositoryGitReady = true
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
add('GOV-INVENTORY-001', 'BLOCKER', 'consumer repository Git configuration is unsafe', error.message)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
let externalGovernanceRoot = null
|
|
233
|
+
const externalGovernancePathIsSafe = (path) => {
|
|
234
|
+
if (!externalGovernanceRoot) return false
|
|
235
|
+
const absolute = resolve(path)
|
|
236
|
+
const rel = relative(externalGovernanceRoot, absolute)
|
|
237
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return false
|
|
238
|
+
let cursor = externalGovernanceRoot
|
|
239
|
+
for (const part of rel.split(sep).filter(Boolean)) {
|
|
240
|
+
cursor = join(cursor, part)
|
|
241
|
+
if (!pathEntryExists(cursor)) return true
|
|
242
|
+
if (lstatSync(cursor).isSymbolicLink()) return false
|
|
243
|
+
}
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
246
|
+
const governancePathIsSafe = path => {
|
|
247
|
+
const absolute = resolve(path)
|
|
248
|
+
const rel = relative(repo, absolute)
|
|
249
|
+
const contained = rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
|
|
250
|
+
return contained ? repositoryPathIsSafe(absolute) : externalGovernancePathIsSafe(absolute)
|
|
251
|
+
}
|
|
252
|
+
const isRegularFile = (path) => {
|
|
253
|
+
if (!governancePathIsSafe(path) || !pathEntryExists(path)) return false
|
|
254
|
+
const info = lstatSync(path)
|
|
255
|
+
return info.isFile() && !info.isSymbolicLink() && info.nlink === 1
|
|
256
|
+
}
|
|
257
|
+
const readJson = (path, ruleId) => {
|
|
258
|
+
if (!isRegularFile(path)) { add(ruleId, 'BLOCKER', `${relative(repo, path)} missing or not a regular non-symlink file`); return null }
|
|
259
|
+
try { return JSON.parse(readFileSync(path, 'utf8')) }
|
|
260
|
+
catch (error) { add(ruleId, 'BLOCKER', `${relative(repo, path)} invalid JSON`, error.message); return null }
|
|
261
|
+
}
|
|
262
|
+
const treeInventory = (root, { frozenCanonicalSnapshot = false } = {}) => {
|
|
263
|
+
if (!existsSync(root)) return null
|
|
264
|
+
const rows = []
|
|
265
|
+
const visit = (absolute, rel = '') => {
|
|
266
|
+
const stat = lstatSync(absolute)
|
|
267
|
+
const observedMode = stat.mode & 0o777
|
|
268
|
+
let projectedMode = observedMode
|
|
269
|
+
if (frozenCanonicalSnapshot) {
|
|
270
|
+
if (stat.isDirectory() && observedMode === 0o500) projectedMode = 0o755
|
|
271
|
+
else if (stat.isFile() && observedMode === 0o400) projectedMode = 0o644
|
|
272
|
+
else if (stat.isFile() && observedMode === 0o500) projectedMode = 0o755
|
|
273
|
+
else projectedMode = -1
|
|
274
|
+
}
|
|
275
|
+
const mode = projectedMode < 0
|
|
276
|
+
? `invalid-frozen-${observedMode.toString(8).padStart(4, '0')}`
|
|
277
|
+
: projectedMode.toString(8).padStart(4, '0')
|
|
278
|
+
if (stat.isSymbolicLink()) { rows.push(`link:${rel}:${mode}:${readlinkSync(absolute)}`); return }
|
|
279
|
+
if (stat.isDirectory()) {
|
|
280
|
+
rows.push(`dir:${rel || '.'}:${mode}`)
|
|
281
|
+
for (const name of readdirSync(absolute).sort()) visit(join(absolute, name), rel ? `${rel}/${name}` : name)
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if (stat.isFile()) rows.push(`file:${rel}:${mode}:links=${stat.nlink}:${fileSha(absolute)}`)
|
|
285
|
+
else rows.push(`unsupported:${rel}:${mode}`)
|
|
286
|
+
}
|
|
287
|
+
visit(root)
|
|
288
|
+
return rows
|
|
289
|
+
}
|
|
290
|
+
const assertTreeEqual = (ruleId, label, actual, canonical) => {
|
|
291
|
+
if (!governancePathIsSafe(actual) || !governancePathIsSafe(canonical)) {
|
|
292
|
+
add(ruleId, 'BLOCKER', `${label} contains an unsafe symlink path component`)
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
const isFrozenCanonicalPath = path => entrypointIsPrivateSnapshot
|
|
296
|
+
&& (path === forkRoot || path.startsWith(`${forkRoot}${sep}`))
|
|
297
|
+
const actualRows = treeInventory(actual, {
|
|
298
|
+
frozenCanonicalSnapshot: isFrozenCanonicalPath(actual),
|
|
299
|
+
})
|
|
300
|
+
const canonicalRows = treeInventory(canonical, {
|
|
301
|
+
frozenCanonicalSnapshot: isFrozenCanonicalPath(canonical),
|
|
302
|
+
})
|
|
303
|
+
if (!actualRows || !canonicalRows || JSON.stringify(actualRows) !== JSON.stringify(canonicalRows)) {
|
|
304
|
+
add(ruleId, 'BLOCKER', `${label} is not a recursive path/type/mode/hash match of the installed canonical projection`)
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
return true
|
|
308
|
+
}
|
|
309
|
+
const collectSourceFiles = (root) => {
|
|
310
|
+
const files = []
|
|
311
|
+
if (!repositoryRootSafe) return files
|
|
312
|
+
// Provider/generated control surfaces contain rule examples and are hash-verified separately.
|
|
313
|
+
// Everything else in the consumer repository is part of the exhaustive hooks-off replay.
|
|
314
|
+
const ignoredFiles = new Set(['scripts/governance-check.mjs'])
|
|
315
|
+
const visit = (dir) => {
|
|
316
|
+
if (!pathEntryExists(dir)) return
|
|
317
|
+
const entries = readdirIfReadable(dir, { withFileTypes: true })
|
|
318
|
+
if (!entries) return
|
|
319
|
+
for (const entry of entries) {
|
|
320
|
+
const absolute = join(dir, entry.name)
|
|
321
|
+
const rel = relative(root, absolute).replaceAll('\\', '/')
|
|
322
|
+
const info = lstatIfReadable(absolute)
|
|
323
|
+
if (!info) {
|
|
324
|
+
// A denied replay-class source would silently skip hook replay: that stays fail-closed.
|
|
325
|
+
if (/\.(?:[cm]?[jt]sx?|css|mdx?|html|json|sh|py)$/.test(entry.name)) {
|
|
326
|
+
add('GOV-CONTENT-002', 'BLOCKER', `replay-class source is unreadable in this runtime and cannot be attested:${rel}`)
|
|
327
|
+
}
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
if (info.isSymbolicLink()) {
|
|
331
|
+
add('GOV-SYMLINK-001', 'BLOCKER', `repository symlink is outside the exhaustive replay contract: ${rel}`)
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
if (info.isDirectory() && canonicalReplayExcludedRoots.has(resolve(absolute))) continue
|
|
335
|
+
if (info.isDirectory()) visit(absolute)
|
|
336
|
+
else if (
|
|
337
|
+
info.isFile()
|
|
338
|
+
&& !canonicalReplayExcludedFiles.has(resolve(absolute))
|
|
339
|
+
&& /\.(?:[cm]?[jt]sx?|css|mdx?|html|json|sh|py)$/.test(entry.name)
|
|
340
|
+
&& !ignoredFiles.has(rel)
|
|
341
|
+
) {
|
|
342
|
+
if (info.nlink !== 1) add('GOV-ALIAS-001', 'BLOCKER', `repository source file has a hard-link alias:${rel}`)
|
|
343
|
+
else files.push(absolute)
|
|
344
|
+
} else if (!info.isFile()) add('GOV-SPECIAL-001', 'BLOCKER', `repository contains an unsupported filesystem entry:${rel}`)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
visit(root)
|
|
348
|
+
return files.sort()
|
|
349
|
+
}
|
|
350
|
+
const repositoryInventory = (root) => {
|
|
351
|
+
const rows = []
|
|
352
|
+
if (!repositoryRootSafe) return { basis: 'unsafe-repository-root', rows }
|
|
353
|
+
const tracked = closedRepositoryGitReady
|
|
354
|
+
? runClosedGit(['ls-files', '-z', '--cached'], {
|
|
355
|
+
cwd: resolve(root),
|
|
356
|
+
maxOutputBytes: 32 * 1024 * 1024,
|
|
357
|
+
output: 'buffer',
|
|
358
|
+
timeoutMs: 5000,
|
|
359
|
+
})
|
|
360
|
+
: null
|
|
361
|
+
if (tracked?.status === 0) {
|
|
362
|
+
for (const rel of tracked.stdout.toString('utf8').split('\0').filter(Boolean).sort()) {
|
|
363
|
+
const absolute = join(root, rel)
|
|
364
|
+
let info
|
|
365
|
+
try { info = lstatSync(absolute) }
|
|
366
|
+
catch (error) {
|
|
367
|
+
if (error?.code === 'ENOENT') {
|
|
368
|
+
add('GOV-INVENTORY-001', 'BLOCKER', `tracked repository path missing:${rel}`)
|
|
369
|
+
} else if (permissionDenied(error)) {
|
|
370
|
+
// Tracked but sandbox-denied (for example .env.example under the canonical ./.env.*
|
|
371
|
+
// credential deny): present per the Git index, so this is never GOV-INVENTORY-001.
|
|
372
|
+
noteSandboxDenied(absolute)
|
|
373
|
+
rows.push(`${rel}:sandbox-unreadable`)
|
|
374
|
+
} else throw error
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
if (info.isSymbolicLink()) {
|
|
378
|
+
add('GOV-SYMLINK-001', 'BLOCKER', `tracked symlink cannot be governance-attested:${rel}`)
|
|
379
|
+
rows.push(`${rel}:symlink:${readlinkSync(absolute)}`)
|
|
380
|
+
continue
|
|
381
|
+
}
|
|
382
|
+
if (!info.isFile()) {
|
|
383
|
+
add('GOV-SPECIAL-001', 'BLOCKER', `tracked repository path is not a regular file:${rel}`)
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
if (info.nlink !== 1) {
|
|
387
|
+
add('GOV-ALIAS-001', 'BLOCKER', `tracked repository file has a hard-link alias:${rel}`)
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
rows.push(`${rel}:${(info.mode & 0o777).toString(8).padStart(4, '0')}:${fileSha(absolute)}`)
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (!permissionDenied(error)) throw error
|
|
394
|
+
noteSandboxDenied(absolute)
|
|
395
|
+
rows.push(`${rel}:sandbox-unreadable`)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return { basis: 'git-index', rows }
|
|
399
|
+
}
|
|
400
|
+
const visit = (dir) => {
|
|
401
|
+
const entries = readdirIfReadable(dir, { withFileTypes: true })
|
|
402
|
+
if (!entries) return
|
|
403
|
+
for (const entry of entries) {
|
|
404
|
+
const absolute = join(dir, entry.name)
|
|
405
|
+
const rel = relative(root, absolute).replaceAll('\\', '/')
|
|
406
|
+
const info = lstatIfReadable(absolute)
|
|
407
|
+
if (!info) {
|
|
408
|
+
rows.push(`${rel}:sandbox-unreadable`)
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
if (info.isSymbolicLink()) {
|
|
412
|
+
add('GOV-SYMLINK-001', 'BLOCKER', `filesystem symlink cannot be governance-attested:${rel}`)
|
|
413
|
+
rows.push(`${rel}:symlink:${readlinkSync(absolute)}`)
|
|
414
|
+
continue
|
|
415
|
+
}
|
|
416
|
+
if (info.isDirectory() && canonicalOutputRoots.has(resolve(absolute))) continue
|
|
417
|
+
if (info.isDirectory()) visit(absolute)
|
|
418
|
+
else if (info.isFile()) {
|
|
419
|
+
if (info.nlink !== 1) {
|
|
420
|
+
add('GOV-ALIAS-001', 'BLOCKER', `filesystem repository file has a hard-link alias:${rel}`)
|
|
421
|
+
continue
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
rows.push(`${rel}:${(info.mode & 0o777).toString(8).padStart(4, '0')}:${fileSha(absolute)}`)
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (!permissionDenied(error)) throw error
|
|
427
|
+
noteSandboxDenied(absolute)
|
|
428
|
+
rows.push(`${rel}:sandbox-unreadable`)
|
|
429
|
+
}
|
|
430
|
+
} else add('GOV-SPECIAL-001', 'BLOCKER', `filesystem repository contains an unsupported entry:${rel}`)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
visit(root)
|
|
434
|
+
rows.sort()
|
|
435
|
+
return { basis: 'filesystem-clean-room', rows }
|
|
436
|
+
}
|
|
437
|
+
const commandAvailable = (command) => Boolean(closedToolProfile?.executables?.[command])
|
|
438
|
+
|
|
439
|
+
const pkgPath = join(repo, 'package.json')
|
|
440
|
+
const lockPath = join(repo, 'package-lock.json')
|
|
441
|
+
const bomPath = join(repo, 'governance/lock.json')
|
|
442
|
+
const pkg = readJson(pkgPath, 'GOV-PACKAGE-001')
|
|
443
|
+
const packageLock = readJson(lockPath, 'GOV-PACKAGE-LOCK-001')
|
|
444
|
+
const bom = readJson(bomPath, 'GOV-BOM-001')
|
|
445
|
+
const bomManagedFileNames = Object.keys(bom?.payload?.managedFiles || {}).sort()
|
|
446
|
+
const bomManagedPortableKeys = bomManagedFileNames.map(portablePathKey)
|
|
447
|
+
if (
|
|
448
|
+
bomManagedFileNames.some((path) => !canonicalManagedRepositoryPath(path))
|
|
449
|
+
|| new Set(bomManagedPortableKeys).size !== bomManagedPortableKeys.length
|
|
450
|
+
) {
|
|
451
|
+
add(
|
|
452
|
+
'GOV-MANAGED-FILE-002',
|
|
453
|
+
'BLOCKER',
|
|
454
|
+
'immutable BOM managed-file paths must be canonical NFC repository paths and portable-case unique',
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Scan exclusions are absolute, machine-derived canonical roots rather than basename matches.
|
|
459
|
+
// Otherwise source-controlled paths such as apps/template/src/build/evil.ts or a nested
|
|
460
|
+
// node_modules/.codex/config.toml could evade both replay and discovery merely by choosing a
|
|
461
|
+
// generated-looking directory name. The only workspace output roots trusted here are direct
|
|
462
|
+
// children of the protected apps/* workspaces used by the base-owned consumer source harness.
|
|
463
|
+
const canonicalWorkspacePatterns = ['apps/*']
|
|
464
|
+
if (JSON.stringify(pkg?.workspaces) !== JSON.stringify(canonicalWorkspacePatterns)) {
|
|
465
|
+
add('GOV-CONTENT-001', 'BLOCKER', `consumer workspaces must equal the protected roots:${canonicalWorkspacePatterns.join(',')}`)
|
|
466
|
+
}
|
|
467
|
+
const canonicalScanRoots = [repo]
|
|
468
|
+
const appsRoot = join(repo, 'apps')
|
|
469
|
+
if (!repositoryPathIsSafe(appsRoot) || !pathEntryExists(appsRoot) || !lstatSync(appsRoot).isDirectory()) {
|
|
470
|
+
add('GOV-CONTENT-001', 'BLOCKER', 'canonical apps/* workspace parent is missing or unsafe')
|
|
471
|
+
} else {
|
|
472
|
+
for (const name of readdirSync(appsRoot).sort()) {
|
|
473
|
+
const workspace = join(appsRoot, name)
|
|
474
|
+
const info = lstatSync(workspace)
|
|
475
|
+
if (info.isSymbolicLink() || (!info.isDirectory() && !info.isFile())) {
|
|
476
|
+
add('GOV-CONTENT-001', 'BLOCKER', `canonical workspace parent contains an unsafe entry:apps/${name}`)
|
|
477
|
+
continue
|
|
478
|
+
}
|
|
479
|
+
if (info.isFile()) continue
|
|
480
|
+
if (!isRegularFile(join(workspace, 'package.json'))) {
|
|
481
|
+
add('GOV-CONTENT-001', 'BLOCKER', `canonical workspace directory has no regular package.json:apps/${name}`)
|
|
482
|
+
continue
|
|
483
|
+
}
|
|
484
|
+
canonicalScanRoots.push(workspace)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const generatedOutputNames = ['node_modules', 'dist', 'build', 'coverage', 'storybook-static', '.next', '.turbo', '.cache', '.vite']
|
|
488
|
+
canonicalOutputRoots.add(resolve(repo, '.git'))
|
|
489
|
+
discoveryTraversalExcludedRoots.add(resolve(repo, '.git'))
|
|
490
|
+
for (const scanRoot of canonicalScanRoots) {
|
|
491
|
+
for (const name of generatedOutputNames) {
|
|
492
|
+
const output = resolve(scanRoot, name)
|
|
493
|
+
canonicalOutputRoots.add(output)
|
|
494
|
+
if (name === 'node_modules') discoveryTraversalExcludedRoots.add(output)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
for (const path of canonicalOutputRoots) canonicalReplayExcludedRoots.add(path)
|
|
498
|
+
// Provider control surfaces are excluded only after their signed manifest records have been
|
|
499
|
+
// closed-shape validated below. Keeping a fixed provider-name list here would silently scan a
|
|
500
|
+
// future adapter as product source (or silently trust an unregistered adapter root).
|
|
501
|
+
canonicalReplayExcludedFiles.add(resolve(repo, 'governance/lock.json'))
|
|
502
|
+
canonicalReplayExcludedFiles.add(resolve(repo, 'governance/lock.schema.json'))
|
|
503
|
+
|
|
504
|
+
if (bom) {
|
|
505
|
+
if (bom.schemaVersion !== 1 || bom.role !== 'template-consumer' || bom.generatedBy !== 'scripts/build-fork-governance.mjs') {
|
|
506
|
+
add('GOV-BOM-002', 'BLOCKER', 'governance lock identity/schema is not canonical')
|
|
507
|
+
}
|
|
508
|
+
evidence.bomSha256 = fileSha(bomPath)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) }
|
|
512
|
+
for (const [name, lockKey] of [['@qijenchen/design-system', 'designSystem'], ['@qijenchen/storybook-config', 'storybookConfig']]) {
|
|
513
|
+
const declared = deps[name]
|
|
514
|
+
const expected = bom?.release?.[lockKey]
|
|
515
|
+
if (!exactSemver.test(declared || '')) add('GOV-VERSION-001', 'BLOCKER', `${name} must be exact semver; got ${declared || 'missing'}`)
|
|
516
|
+
if (expected && declared !== expected) add('GOV-VERSION-002', 'BLOCKER', `${name} declaration ${declared} != governance lock ${expected}`)
|
|
517
|
+
const lockEntry = packageLock?.packages?.[`node_modules/${name}`]
|
|
518
|
+
const locked = lockEntry?.version
|
|
519
|
+
if (declared && locked !== declared) add('GOV-PACKAGE-LOCK-002', 'BLOCKER', `${name} package-lock version ${locked || 'missing'} != ${declared}`)
|
|
520
|
+
const unscoped = name.split('/').at(-1)
|
|
521
|
+
const canonicalTarball = declared ? `https://registry.npmjs.org/${name}/-/${unscoped}-${declared}.tgz` : null
|
|
522
|
+
if (declared && lockEntry?.resolved !== canonicalTarball) add('GOV-SUPPLY-001', 'BLOCKER', `${name} resolved URL is not the canonical exact npm registry tarball`)
|
|
523
|
+
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(lockEntry?.integrity || '')) add('GOV-SUPPLY-001', 'BLOCKER', `${name} package-lock entry lacks npm sha512 integrity`)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for (const [name, command] of Object.entries(bom?.payload?.requiredScripts || {})) {
|
|
527
|
+
if (pkg?.scripts?.[name] !== command) add('GOV-SCRIPT-001', 'BLOCKER', `required package script ${name} drifted from immutable governance BOM`)
|
|
528
|
+
}
|
|
529
|
+
for (const [file, digest] of Object.entries(bom?.payload?.managedFiles || {})) {
|
|
530
|
+
const absolute = join(repo, file)
|
|
531
|
+
if (!isRegularFile(absolute) || fileSha(absolute) !== digest) add('GOV-MANAGED-FILE-001', 'BLOCKER', `${file} drifted from immutable governance BOM or is not a regular file`)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const dsRoot = join(repo, 'node_modules/@qijenchen/design-system')
|
|
535
|
+
const sbRoot = join(repo, 'node_modules/@qijenchen/storybook-config')
|
|
536
|
+
const utilityRegistryPath = join(dsRoot, 'src/tokens/utility-registry.json')
|
|
537
|
+
const dsPkg = readJson(join(dsRoot, 'package.json'), 'GOV-DEPS-001')
|
|
538
|
+
const sbPkg = readJson(join(sbRoot, 'package.json'), 'GOV-DEPS-001')
|
|
539
|
+
if (dsPkg?.version !== deps['@qijenchen/design-system']) add('GOV-DEPS-002', 'BLOCKER', `installed design-system ${dsPkg?.version || 'missing'} != declaration ${deps['@qijenchen/design-system'] || 'missing'}`)
|
|
540
|
+
if (sbPkg?.version !== deps['@qijenchen/storybook-config']) add('GOV-DEPS-002', 'BLOCKER', `installed storybook-config ${sbPkg?.version || 'missing'} != declaration ${deps['@qijenchen/storybook-config'] || 'missing'}`)
|
|
541
|
+
|
|
542
|
+
const installedForkRoot = join(dsRoot, 'ds-canonical/fork')
|
|
543
|
+
const entrypoint = realpathSync(fileURLToPath(import.meta.url))
|
|
544
|
+
const entrypointForkRoot = resolve(dirname(entrypoint), '..')
|
|
545
|
+
const entrypointForkRelative = relative(repo, entrypointForkRoot)
|
|
546
|
+
const entrypointIsPrivateSnapshot = (
|
|
547
|
+
entrypoint === join(entrypointForkRoot, 'consumer/governance-check.mjs')
|
|
548
|
+
&& entrypointForkRelative !== ''
|
|
549
|
+
&& (entrypointForkRelative === '..' || entrypointForkRelative.startsWith(`..${sep}`))
|
|
550
|
+
&& pathEntryExists(entrypointForkRoot)
|
|
551
|
+
&& lstatSync(entrypointForkRoot).isDirectory()
|
|
552
|
+
&& !lstatSync(entrypointForkRoot).isSymbolicLink()
|
|
553
|
+
&& realpathSync(entrypointForkRoot) === entrypointForkRoot
|
|
554
|
+
)
|
|
555
|
+
const forkRoot = entrypointIsPrivateSnapshot ? entrypointForkRoot : installedForkRoot
|
|
556
|
+
if (entrypointIsPrivateSnapshot) externalGovernanceRoot = forkRoot
|
|
557
|
+
const corpusLockPath = join(forkRoot, 'governance.lock')
|
|
558
|
+
const corpusLock = readJson(corpusLockPath, 'GOV-CORPUS-001')
|
|
559
|
+
const manifest = readJson(join(forkRoot, 'manifest.json'), 'GOV-CORPUS-001')
|
|
560
|
+
const closedKeys = (value, keys) => Boolean(
|
|
561
|
+
value
|
|
562
|
+
&& typeof value === 'object'
|
|
563
|
+
&& !Array.isArray(value)
|
|
564
|
+
&& JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort())
|
|
565
|
+
)
|
|
566
|
+
// Structural/tamper mirror only. Canonical selection and certification semantics remain owned by
|
|
567
|
+
// packages/governance/src/provider-review-binding.mjs. This standalone shipped checker cannot
|
|
568
|
+
// import a repository-relative module before its corpus lock has been authenticated.
|
|
569
|
+
const stableProjectionValue = (value) => {
|
|
570
|
+
if (Array.isArray(value)) return value.map(stableProjectionValue)
|
|
571
|
+
if (value && typeof value === 'object') {
|
|
572
|
+
return Object.fromEntries(
|
|
573
|
+
Object.keys(value).sort(compareUtf8Bytes).map((key) => [key, stableProjectionValue(value[key])]),
|
|
574
|
+
)
|
|
575
|
+
}
|
|
576
|
+
return value
|
|
577
|
+
}
|
|
578
|
+
const validReviewInvocation = (value) => (
|
|
579
|
+
(closedKeys(value, ['strategy']) && value.strategy === 'main-context')
|
|
580
|
+
|| (
|
|
581
|
+
closedKeys(value, ['agent', 'context', 'strategy'])
|
|
582
|
+
&& value.strategy === 'native-context-fork'
|
|
583
|
+
&& value.context === 'fork'
|
|
584
|
+
&& /^[a-z][a-z0-9-]*$/.test(value.agent || '')
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
const validIndependentReviewProjection = (value) => {
|
|
588
|
+
if (
|
|
589
|
+
!closedKeys(value, [
|
|
590
|
+
'schemaVersion', 'kind', 'skill', 'repositoryRole', 'certificationContract',
|
|
591
|
+
'bindings', 'providerCompatibility', 'certifications', 'projectionDigest',
|
|
592
|
+
])
|
|
593
|
+
|| value.schemaVersion !== 2
|
|
594
|
+
|| value.kind !== 'provider-neutral-independent-review-projection'
|
|
595
|
+
|| value.skill !== 'independent-review'
|
|
596
|
+
|| value.repositoryRole !== 'product-consumer'
|
|
597
|
+
|| value.certificationContract !== 'exact-provider-runtime-surface-role-target-v1'
|
|
598
|
+
|| !Array.isArray(value.bindings)
|
|
599
|
+
|| value.bindings.length === 0
|
|
600
|
+
|| !Array.isArray(value.providerCompatibility)
|
|
601
|
+
|| value.providerCompatibility.length === 0
|
|
602
|
+
|| !Array.isArray(value.certifications)
|
|
603
|
+
|| !/^[a-f0-9]{64}$/.test(value.projectionDigest || '')
|
|
604
|
+
) return false
|
|
605
|
+
const bindingIds = []
|
|
606
|
+
for (const binding of value.bindings) {
|
|
607
|
+
if (
|
|
608
|
+
!closedKeys(binding, [
|
|
609
|
+
'selfProviderId', 'selectionPolicy', 'reviewClass', 'requiredAssuranceTier',
|
|
610
|
+
'requiredReasoningTier', 'requiredComputeTier', 'transport', 'invocation',
|
|
611
|
+
'certificationContract', 'bindingDigest',
|
|
612
|
+
])
|
|
613
|
+
|| !/^[a-z][a-z0-9-]*$/.test(binding.selfProviderId || '')
|
|
614
|
+
|| !/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(binding.selectionPolicy || '')
|
|
615
|
+
|| binding.reviewClass !== 'tier-0-governance'
|
|
616
|
+
|| binding.requiredAssuranceTier !== 'maximum'
|
|
617
|
+
|| binding.requiredReasoningTier !== 'maximum'
|
|
618
|
+
|| binding.requiredComputeTier !== 'maximum'
|
|
619
|
+
|| binding.transport !== 'content-addressed-model-broker-exchange-v1'
|
|
620
|
+
|| !validReviewInvocation(binding.invocation)
|
|
621
|
+
|| binding.certificationContract !== 'exact-provider-runtime-surface-role-target-v1'
|
|
622
|
+
|| !/^[a-f0-9]{64}$/.test(binding.bindingDigest || '')
|
|
623
|
+
) return false
|
|
624
|
+
bindingIds.push(binding.selfProviderId)
|
|
625
|
+
}
|
|
626
|
+
const compatibilityProviderIds = []
|
|
627
|
+
const compatibilityRuntimeIds = []
|
|
628
|
+
for (const compatibility of value.providerCompatibility) {
|
|
629
|
+
if (
|
|
630
|
+
!closedKeys(compatibility, ['compatibilityProviderId', 'providerId'])
|
|
631
|
+
|| !/^[a-z][a-z0-9-]*$/.test(compatibility.providerId || '')
|
|
632
|
+
|| !/^[a-z][a-z0-9-]*$/.test(compatibility.compatibilityProviderId || '')
|
|
633
|
+
) return false
|
|
634
|
+
compatibilityProviderIds.push(compatibility.providerId)
|
|
635
|
+
compatibilityRuntimeIds.push(compatibility.compatibilityProviderId)
|
|
636
|
+
}
|
|
637
|
+
if (
|
|
638
|
+
JSON.stringify(bindingIds) !== JSON.stringify([...new Set(bindingIds)].sort(compareUtf8Bytes))
|
|
639
|
+
|| JSON.stringify(compatibilityProviderIds) !== JSON.stringify([...new Set(compatibilityProviderIds)].sort(compareUtf8Bytes))
|
|
640
|
+
|| new Set(compatibilityRuntimeIds).size !== compatibilityRuntimeIds.length
|
|
641
|
+
|| bindingIds.some((providerId) => !compatibilityProviderIds.includes(providerId))
|
|
642
|
+
) return false
|
|
643
|
+
const expectedDigest = sha256(JSON.stringify(stableProjectionValue({
|
|
644
|
+
skill: value.skill,
|
|
645
|
+
repositoryRole: value.repositoryRole,
|
|
646
|
+
bindings: value.bindings,
|
|
647
|
+
providerCompatibility: value.providerCompatibility,
|
|
648
|
+
certifications: value.certifications,
|
|
649
|
+
})))
|
|
650
|
+
return value.projectionDigest === expectedDigest
|
|
651
|
+
}
|
|
652
|
+
const expectedManifestKeys = [
|
|
653
|
+
'_generated', 'schemaVersion', 'kind', 'providerRegistrySchemaVersion', 'materializers', 'discoveryPolicy',
|
|
654
|
+
'providerLifecycle', 'providerAdapters', 'authorityDecision', 'independentReview', 'launchers', 'hooks', 'ciReplay', 'skills', 'commands',
|
|
655
|
+
'agents', 'providerSurfaces', 'codex', 'consumer',
|
|
656
|
+
]
|
|
657
|
+
if (
|
|
658
|
+
!closedKeys(manifest, expectedManifestKeys)
|
|
659
|
+
|| manifest?.schemaVersion !== 2
|
|
660
|
+
|| manifest?.kind !== 'provider-neutral-fork-manifest'
|
|
661
|
+
|| manifest?.providerRegistrySchemaVersion !== 9
|
|
662
|
+
) add('GOV-CORPUS-001', 'BLOCKER', 'installed fork manifest is open-shaped, unknown-versioned, or has the wrong identity')
|
|
663
|
+
const authorityDecision = manifest?.authorityDecision
|
|
664
|
+
const authorityDecisionArtifactPaths = [
|
|
665
|
+
authorityDecision?.classifierSource,
|
|
666
|
+
authorityDecision?.receiptContractSource,
|
|
667
|
+
authorityDecision?.runner,
|
|
668
|
+
]
|
|
669
|
+
if (
|
|
670
|
+
!closedKeys(authorityDecision, [
|
|
671
|
+
'schemaVersion', 'policySource', 'classifierSource', 'receiptContractSource', 'runner',
|
|
672
|
+
'prepareArgv', 'verifyArgv', 'certificationClaim',
|
|
673
|
+
])
|
|
674
|
+
|| authorityDecision.schemaVersion !== 1
|
|
675
|
+
|| authorityDecision.policySource !== 'AGENTS.md#canonical-decision-authority'
|
|
676
|
+
|| authorityDecision.classifierSource !== 'launchers/approval-evidence.mjs'
|
|
677
|
+
|| authorityDecision.receiptContractSource !== 'launchers/authority-decision-evidence.mjs'
|
|
678
|
+
|| authorityDecision.runner !== 'launchers/product-hook-dispatch-runtime.mjs'
|
|
679
|
+
|| JSON.stringify(authorityDecision.prepareArgv) !== JSON.stringify(['--authority-decision', 'prepare'])
|
|
680
|
+
|| JSON.stringify(authorityDecision.verifyArgv) !== JSON.stringify(['--authority-decision', 'verify'])
|
|
681
|
+
|| authorityDecision.certificationClaim !== 'structural-only-not-runtime-certified'
|
|
682
|
+
) add('GOV-CORPUS-001', 'BLOCKER', 'installed authority-decision projection is open-shaped, incomplete, tampered, or has the wrong identity')
|
|
683
|
+
const independentReview = manifest?.independentReview
|
|
684
|
+
if (!validIndependentReviewProjection(independentReview)) {
|
|
685
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed independent-review projection is open-shaped, incomplete, tampered, or has the wrong identity')
|
|
686
|
+
}
|
|
687
|
+
const expectedConsumerKeys = [
|
|
688
|
+
'claudeMd', 'commonInstruction', 'compatibilityCategories', 'executionRuntime', 'governanceCheck', 'governanceLock',
|
|
689
|
+
'governanceLockSchema', 'launcherDestination', 'managedFiles', 'materialization',
|
|
690
|
+
]
|
|
691
|
+
if (!closedKeys(manifest?.consumer, expectedConsumerKeys)) {
|
|
692
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed fork manifest consumer record is open-shaped or incomplete')
|
|
693
|
+
}
|
|
694
|
+
if (manifest?.consumer?.claudeMd !== null && typeof manifest?.consumer?.claudeMd !== 'string') {
|
|
695
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'legacy Claude instruction compatibility alias must be a path or null')
|
|
696
|
+
}
|
|
697
|
+
const commonInstruction = manifest?.consumer?.commonInstruction
|
|
698
|
+
if (
|
|
699
|
+
!closedKeys(commonInstruction, ['artifact', 'destination', 'materializerId', 'schemaVersion'])
|
|
700
|
+
|| commonInstruction.schemaVersion !== 1
|
|
701
|
+
|| commonInstruction.artifact !== 'common/instruction.md'
|
|
702
|
+
|| typeof commonInstruction.destination !== 'string'
|
|
703
|
+
|| !commonInstruction.destination
|
|
704
|
+
|| typeof commonInstruction.materializerId !== 'string'
|
|
705
|
+
|| !commonInstruction.materializerId
|
|
706
|
+
) add('GOV-ADAPTER-003', 'BLOCKER', 'installed common instruction record is not the closed consumer authority')
|
|
707
|
+
if (manifest?.codex !== null && (
|
|
708
|
+
!closedKeys(manifest?.codex, [
|
|
709
|
+
'agentsMd', 'agentsSkills', 'compatibilityOnly', 'hookArtifact', 'hookDestination', 'hooksJson',
|
|
710
|
+
'instructionDestination', 'skillArtifactRoot', 'skillDestination',
|
|
711
|
+
])
|
|
712
|
+
|| manifest.codex.compatibilityOnly !== true
|
|
713
|
+
|| typeof manifest.codex.agentsMd !== 'string'
|
|
714
|
+
|| typeof manifest.codex.instructionDestination !== 'string'
|
|
715
|
+
|| !Array.isArray(manifest.codex.agentsSkills)
|
|
716
|
+
|| !Array.isArray(manifest.codex.hooksJson)
|
|
717
|
+
|| (manifest.codex.hookArtifact !== null && typeof manifest.codex.hookArtifact !== 'string')
|
|
718
|
+
|| (manifest.codex.hookDestination !== null && typeof manifest.codex.hookDestination !== 'string')
|
|
719
|
+
|| typeof manifest.codex.skillArtifactRoot !== 'string'
|
|
720
|
+
|| typeof manifest.codex.skillDestination !== 'string'
|
|
721
|
+
)) {
|
|
722
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'legacy Codex compatibility record must be a closed active-surface projection or null')
|
|
723
|
+
}
|
|
724
|
+
const providerMaterializers = manifest?.materializers
|
|
725
|
+
const materializerGroups = ['instructionViews', 'hookBaseTemplateContracts', 'hookViews', 'skillViews', 'treeViews']
|
|
726
|
+
let providerMaterializersValid = closedKeys(providerMaterializers, materializerGroups)
|
|
727
|
+
&& materializerGroups.every((group) => Object.keys(providerMaterializers?.[group] || {}).length > 0)
|
|
728
|
+
if (providerMaterializersValid) {
|
|
729
|
+
for (const [id, materializer] of Object.entries(providerMaterializers.instructionViews)) {
|
|
730
|
+
const expectedOwnership = materializer?.engine === 'shared-instruction-v1'
|
|
731
|
+
? 'common-authority-consumer'
|
|
732
|
+
: materializer?.engine === 'markdown-relative-at-import-v1'
|
|
733
|
+
? 'provider-generated-projection'
|
|
734
|
+
: null
|
|
735
|
+
if (
|
|
736
|
+
!/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(id)
|
|
737
|
+
|| !closedKeys(materializer, ['engine', 'ownershipPolicy', 'supplementPolicy'])
|
|
738
|
+
|| !expectedOwnership
|
|
739
|
+
|| materializer.ownershipPolicy !== expectedOwnership
|
|
740
|
+
|| !['forbidden', 'optional', 'required'].includes(materializer.supplementPolicy)
|
|
741
|
+
|| (materializer.engine === 'shared-instruction-v1' && materializer.supplementPolicy !== 'forbidden')
|
|
742
|
+
) providerMaterializersValid = false
|
|
743
|
+
}
|
|
744
|
+
for (const [id, contract] of Object.entries(providerMaterializers.hookBaseTemplateContracts)) {
|
|
745
|
+
const valid = contract?.engine === 'claude-permission-policy-v1'
|
|
746
|
+
? closedKeys(contract, ['engine', 'schema', 'source']) && typeof contract.source === 'string' && typeof contract.schema === 'string'
|
|
747
|
+
: contract?.engine === 'static-json-object-v1' && closedKeys(contract, ['engine'])
|
|
748
|
+
if (!/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(id) || !valid) providerMaterializersValid = false
|
|
749
|
+
}
|
|
750
|
+
for (const [id, materializer] of Object.entries(providerMaterializers.hookViews)) {
|
|
751
|
+
const idValid = /^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(id)
|
|
752
|
+
const shapeValid = materializer?.engine === 'json-hook-map-v1'
|
|
753
|
+
? closedKeys(materializer, ['baseTemplateContract', 'descriptionField', 'engine', 'hooksField', 'mergeStrategy'])
|
|
754
|
+
&& typeof materializer.hooksField === 'string' && materializer.hooksField.length > 0
|
|
755
|
+
: materializer?.engine === 'json-hook-event-list-v1'
|
|
756
|
+
? closedKeys(materializer, ['baseTemplateContract', 'descriptionField', 'engine', 'eventField', 'eventsField', 'groupsField', 'handlerArgvField', 'handlersField', 'matcherField', 'mergeStrategy'])
|
|
757
|
+
&& ['eventField', 'eventsField', 'groupsField', 'handlerArgvField', 'handlersField', 'matcherField'].every((field) => typeof materializer[field] === 'string' && materializer[field].length > 0)
|
|
758
|
+
: false
|
|
759
|
+
const descriptionValid = materializer?.engine === 'json-hook-event-list-v1'
|
|
760
|
+
? typeof materializer.descriptionField === 'string' && materializer.descriptionField.length > 0
|
|
761
|
+
: materializer?.descriptionField === null || (typeof materializer?.descriptionField === 'string' && materializer.descriptionField.length > 0)
|
|
762
|
+
const commonValid = descriptionValid
|
|
763
|
+
&& ['merge-hook-map-into-base-v1', 'replace-document-v1'].includes(materializer?.mergeStrategy)
|
|
764
|
+
&& (materializer.mergeStrategy === 'merge-hook-map-into-base-v1'
|
|
765
|
+
? typeof materializer.baseTemplateContract === 'string' && Boolean(providerMaterializers.hookBaseTemplateContracts[materializer.baseTemplateContract])
|
|
766
|
+
: materializer.baseTemplateContract === null)
|
|
767
|
+
if (!idValid || !shapeValid || !commonValid) providerMaterializersValid = false
|
|
768
|
+
}
|
|
769
|
+
for (const [id, materializer] of Object.entries(providerMaterializers.skillViews)) {
|
|
770
|
+
if (
|
|
771
|
+
!/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(id)
|
|
772
|
+
|| !closedKeys(materializer, ['engine', 'entryFile'])
|
|
773
|
+
|| materializer.engine !== 'skill-directory-v1'
|
|
774
|
+
|| typeof materializer.entryFile !== 'string'
|
|
775
|
+
|| !/^[A-Za-z0-9._-]+\.md$/.test(materializer.entryFile)
|
|
776
|
+
) providerMaterializersValid = false
|
|
777
|
+
}
|
|
778
|
+
for (const [id, materializer] of Object.entries(providerMaterializers.treeViews)) {
|
|
779
|
+
if (
|
|
780
|
+
!/^[a-z][a-z0-9-]*-v[1-9][0-9]*$/.test(id)
|
|
781
|
+
|| !closedKeys(materializer, ['engine', 'transformPolicies'])
|
|
782
|
+
|| materializer.engine !== 'closed-tree-copy-v1'
|
|
783
|
+
|| JSON.stringify(materializer.transformPolicies) !== JSON.stringify(['byte-exact'])
|
|
784
|
+
) providerMaterializersValid = false
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
const commonAuthorityMaterializerIds = Object.entries(providerMaterializers?.instructionViews || {})
|
|
788
|
+
.filter(([, materializer]) => (
|
|
789
|
+
materializer?.engine === 'shared-instruction-v1'
|
|
790
|
+
&& materializer?.ownershipPolicy === 'common-authority-consumer'
|
|
791
|
+
))
|
|
792
|
+
.map(([id]) => id)
|
|
793
|
+
if (commonAuthorityMaterializerIds.length !== 1) providerMaterializersValid = false
|
|
794
|
+
if (!providerMaterializersValid) add('GOV-PROVIDER-001', 'BLOCKER', 'installed provider materializer catalog is missing, open-shaped, or uses an unsupported engine')
|
|
795
|
+
const resolveSurfaceMaterializer = (group, id, label) => {
|
|
796
|
+
const materializer = typeof id === 'string' ? providerMaterializers?.[group]?.[id] : null
|
|
797
|
+
if (!materializer) add('GOV-PROVIDER-002', 'BLOCKER', `${label} references an unknown ${group} materializer:${id || '<missing>'}`)
|
|
798
|
+
return materializer || null
|
|
799
|
+
}
|
|
800
|
+
const discoveryPolicy = manifest?.discoveryPolicy
|
|
801
|
+
const discoveryPolicyKeys = [
|
|
802
|
+
'schemaVersion', 'providerRootNames', 'instructionNames', 'instructionOverrideNames', 'configPaths', 'pluginRootNames',
|
|
803
|
+
].sort()
|
|
804
|
+
const discoveryArrayKeys = discoveryPolicyKeys.filter((key) => key !== 'schemaVersion')
|
|
805
|
+
const discoveryPolicyShapeValid = Boolean(
|
|
806
|
+
discoveryPolicy
|
|
807
|
+
&& typeof discoveryPolicy === 'object'
|
|
808
|
+
&& !Array.isArray(discoveryPolicy)
|
|
809
|
+
&& discoveryPolicy.schemaVersion === 1
|
|
810
|
+
&& JSON.stringify(Object.keys(discoveryPolicy).sort()) === JSON.stringify(discoveryPolicyKeys)
|
|
811
|
+
)
|
|
812
|
+
if (!discoveryPolicyShapeValid) {
|
|
813
|
+
add('GOV-PROVIDER-001', 'BLOCKER', 'installed manifest discoveryPolicy is missing, unknown-versioned, or open-shaped')
|
|
814
|
+
}
|
|
815
|
+
const discoveryInventory = Object.fromEntries(discoveryArrayKeys.map((key) => {
|
|
816
|
+
const values = discoveryPolicyShapeValid && Array.isArray(discoveryPolicy[key]) ? discoveryPolicy[key] : []
|
|
817
|
+
const expected = [...new Set(values.filter((value) => typeof value === 'string'))].sort()
|
|
818
|
+
if (!values.length || JSON.stringify(values) !== JSON.stringify(expected)) {
|
|
819
|
+
add('GOV-PROVIDER-001', 'BLOCKER', `installed discoveryPolicy.${key} must be a nonempty sorted unique string array`)
|
|
820
|
+
}
|
|
821
|
+
return [key, expected]
|
|
822
|
+
}))
|
|
823
|
+
const discoveryRootPattern = /^\.(?!git(?:hub)?$)[A-Za-z0-9][A-Za-z0-9_-]*$/
|
|
824
|
+
const discoveryInstructionPattern = /^[A-Za-z0-9][A-Za-z0-9_.@-]*\.md$/
|
|
825
|
+
const discoveryConfigPattern = /^\.(?:[A-Za-z0-9][A-Za-z0-9_.@-]*|[A-Za-z0-9][A-Za-z0-9_-]*\/[A-Za-z0-9_.@/-]+)$/
|
|
826
|
+
for (const root of [...discoveryInventory.providerRootNames, ...discoveryInventory.pluginRootNames]) {
|
|
827
|
+
if (!discoveryRootPattern.test(root)) add('GOV-PROVIDER-001', 'BLOCKER', `installed discovery policy has an unsafe provider/plugin root:${root}`)
|
|
828
|
+
}
|
|
829
|
+
for (const name of [...discoveryInventory.instructionNames, ...discoveryInventory.instructionOverrideNames]) {
|
|
830
|
+
if (!discoveryInstructionPattern.test(name)) add('GOV-PROVIDER-001', 'BLOCKER', `installed discovery policy has an unsafe instruction name:${name}`)
|
|
831
|
+
}
|
|
832
|
+
for (const path of discoveryInventory.configPaths) {
|
|
833
|
+
if (!discoveryConfigPattern.test(path) || path.split('/').some((segment) => !segment || segment === '.' || segment === '..')) {
|
|
834
|
+
add('GOV-PROVIDER-001', 'BLOCKER', `installed discovery policy has an unsafe config path:${path}`)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (discoveryPolicyShapeValid && bom?.payload?.discoveryPolicySha256 !== sha256(JSON.stringify(discoveryPolicy))) {
|
|
838
|
+
add('GOV-BOM-004', 'BLOCKER', 'installed discovery policy digest differs from the immutable governance BOM')
|
|
839
|
+
}
|
|
840
|
+
const providerLifecycle = manifest?.providerLifecycle
|
|
841
|
+
const lifecycleSha256 = /^[a-f0-9]{64}$/
|
|
842
|
+
const lifecycleProviderId = /^[a-z][a-z0-9-]*$/
|
|
843
|
+
const lifecyclePathKey = (value) => String(value).split('/')
|
|
844
|
+
.map((segment) => segment.normalize('NFC').toLocaleLowerCase('en-US').normalize('NFC'))
|
|
845
|
+
.join('/')
|
|
846
|
+
const lifecyclePathsOverlap = (left, right) => {
|
|
847
|
+
const leftKey = lifecyclePathKey(left)
|
|
848
|
+
const rightKey = lifecyclePathKey(right)
|
|
849
|
+
return leftKey === rightKey || leftKey.startsWith(`${rightKey}/`) || rightKey.startsWith(`${leftKey}/`)
|
|
850
|
+
}
|
|
851
|
+
const lifecyclePathValid = (value) => (
|
|
852
|
+
typeof value === 'string'
|
|
853
|
+
&& value.length > 0
|
|
854
|
+
&& !value.includes('\\')
|
|
855
|
+
&& !value.includes('\0')
|
|
856
|
+
&& !isAbsolute(value)
|
|
857
|
+
&& value.split('/').every((segment) => segment && segment !== '.' && segment !== '..')
|
|
858
|
+
&& !['.git', '.github'].includes(lifecyclePathKey(value.split('/')[0]))
|
|
859
|
+
)
|
|
860
|
+
const lifecycleSemverParts = (value) => {
|
|
861
|
+
const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/)
|
|
862
|
+
return match ? { core: match.slice(1, 4).map(Number), pre: match[4]?.split('.') ?? null } : null
|
|
863
|
+
}
|
|
864
|
+
const compareLifecycleSemver = (leftValue, rightValue) => {
|
|
865
|
+
const left = lifecycleSemverParts(leftValue)
|
|
866
|
+
const right = lifecycleSemverParts(rightValue)
|
|
867
|
+
if (!left || !right) return null
|
|
868
|
+
for (let index = 0; index < 3; index += 1) if (left.core[index] !== right.core[index]) return left.core[index] - right.core[index]
|
|
869
|
+
if (left.pre === null || right.pre === null) return left.pre === right.pre ? 0 : left.pre === null ? 1 : -1
|
|
870
|
+
for (let index = 0; index < Math.max(left.pre.length, right.pre.length); index += 1) {
|
|
871
|
+
if (left.pre[index] === undefined || right.pre[index] === undefined) return left.pre[index] === undefined ? -1 : 1
|
|
872
|
+
if (left.pre[index] === right.pre[index]) continue
|
|
873
|
+
const leftNumeric = /^\d+$/.test(left.pre[index])
|
|
874
|
+
const rightNumeric = /^\d+$/.test(right.pre[index])
|
|
875
|
+
if (leftNumeric && rightNumeric) return Number(left.pre[index]) - Number(right.pre[index])
|
|
876
|
+
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1
|
|
877
|
+
return compareUtf8Bytes(left.pre[index], right.pre[index])
|
|
878
|
+
}
|
|
879
|
+
return 0
|
|
880
|
+
}
|
|
881
|
+
const lifecycleProblem = (message) => add('GOV-LIFECYCLE-001', 'BLOCKER', message)
|
|
882
|
+
const lifecycleDiscoveryReservesPath = (discovery, value) => {
|
|
883
|
+
const key = lifecyclePathKey(value)
|
|
884
|
+
const roots = [...(discovery?.providerRootNames || []), ...(discovery?.pluginRootNames || [])]
|
|
885
|
+
.map(lifecyclePathKey)
|
|
886
|
+
if (roots.some((root) => key === root || key.startsWith(`${root}/`))) return true
|
|
887
|
+
return [
|
|
888
|
+
...(discovery?.instructionNames || []),
|
|
889
|
+
...(discovery?.instructionOverrideNames || []),
|
|
890
|
+
...(discovery?.configPaths || []),
|
|
891
|
+
].some((entry) => lifecyclePathKey(entry) === key)
|
|
892
|
+
}
|
|
893
|
+
const validateLifecycleDiscovery = (value, label) => {
|
|
894
|
+
let valid = closedKeys(value, discoveryArrayKeys)
|
|
895
|
+
if (!valid) {
|
|
896
|
+
lifecycleProblem(`${label} discovery is open-shaped`)
|
|
897
|
+
return false
|
|
898
|
+
}
|
|
899
|
+
for (const key of discoveryArrayKeys) {
|
|
900
|
+
const entries = value[key]
|
|
901
|
+
const pattern = key === 'configPaths'
|
|
902
|
+
? discoveryConfigPattern
|
|
903
|
+
: ['instructionNames', 'instructionOverrideNames'].includes(key)
|
|
904
|
+
? discoveryInstructionPattern
|
|
905
|
+
: discoveryRootPattern
|
|
906
|
+
const canonical = Array.isArray(entries)
|
|
907
|
+
? [...new Set(entries.filter((entry) => typeof entry === 'string'))].sort()
|
|
908
|
+
: []
|
|
909
|
+
const portableEntries = Array.isArray(entries) ? entries.map(lifecyclePathKey) : []
|
|
910
|
+
if (
|
|
911
|
+
!Array.isArray(entries)
|
|
912
|
+
|| JSON.stringify(entries) !== JSON.stringify(canonical)
|
|
913
|
+
|| portableEntries.length !== new Set(portableEntries).size
|
|
914
|
+
|| entries.some((entry) => !pattern.test(entry))
|
|
915
|
+
) {
|
|
916
|
+
lifecycleProblem(`${label} discovery ${key} must be a safe sorted unique string array`)
|
|
917
|
+
valid = false
|
|
918
|
+
continue
|
|
919
|
+
}
|
|
920
|
+
if (entries.some((entry) => !discoveryInventory[key].includes(entry))) {
|
|
921
|
+
lifecycleProblem(`${label} discovery ${key} exceeds the signed discovery policy`)
|
|
922
|
+
valid = false
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return valid
|
|
926
|
+
}
|
|
927
|
+
const validateLifecycleSurface = (surface, label, retired = false) => {
|
|
928
|
+
const keys = retired ? ['kind', 'path', 'sha256'] : ['kind', 'path']
|
|
929
|
+
const valid = closedKeys(surface, keys)
|
|
930
|
+
&& ['file', 'tree'].includes(surface.kind)
|
|
931
|
+
&& lifecyclePathValid(surface.path)
|
|
932
|
+
&& (!retired || lifecycleSha256.test(surface.sha256 || ''))
|
|
933
|
+
if (!valid) lifecycleProblem(`${label} is open-shaped or invalid`)
|
|
934
|
+
return valid
|
|
935
|
+
}
|
|
936
|
+
const validateSnapshotRetirement = (retirement, label) => {
|
|
937
|
+
const transfersPresent = Object.hasOwn(retirement || {}, 'transfers')
|
|
938
|
+
const keys = [
|
|
939
|
+
'discovery', 'id', 'reasonCode', 'replacementProviderId', 'retiredInVersion', 'surfaces',
|
|
940
|
+
...(transfersPresent ? ['transfers'] : []),
|
|
941
|
+
]
|
|
942
|
+
let valid = closedKeys(retirement, keys)
|
|
943
|
+
&& lifecycleProviderId.test(retirement.id || '')
|
|
944
|
+
&& (retirement.replacementProviderId === null || lifecycleProviderId.test(retirement.replacementProviderId || ''))
|
|
945
|
+
&& retirement.replacementProviderId !== retirement.id
|
|
946
|
+
&& exactSemver.test(retirement.retiredInVersion || '')
|
|
947
|
+
&& /^[A-Z][A-Z0-9_]*$/.test(retirement.reasonCode || '')
|
|
948
|
+
&& Array.isArray(retirement.surfaces)
|
|
949
|
+
&& (!transfersPresent || (Array.isArray(retirement.transfers) && retirement.transfers.length > 0))
|
|
950
|
+
if (!valid) {
|
|
951
|
+
lifecycleProblem(`${label} is open-shaped or invalid`)
|
|
952
|
+
return false
|
|
953
|
+
}
|
|
954
|
+
valid = validateLifecycleDiscovery(retirement.discovery, `${label}.${retirement.id}`) && valid
|
|
955
|
+
const transfers = retirement.transfers || []
|
|
956
|
+
for (const [index, surface] of retirement.surfaces.entries()) {
|
|
957
|
+
valid = validateLifecycleSurface(surface, `${label}.surfaces[${index}]`, true) && valid
|
|
958
|
+
}
|
|
959
|
+
for (const [index, surface] of transfers.entries()) {
|
|
960
|
+
valid = validateLifecycleSurface(surface, `${label}.transfers[${index}]`) && valid
|
|
961
|
+
}
|
|
962
|
+
if (!retirement.surfaces.length && !transfers.length) {
|
|
963
|
+
lifecycleProblem(`${label} must delete or transfer at least one surface`)
|
|
964
|
+
valid = false
|
|
965
|
+
}
|
|
966
|
+
if (retirement.replacementProviderId === null && transfers.length) {
|
|
967
|
+
lifecycleProblem(`${label} cannot transfer surfaces without a replacement provider`)
|
|
968
|
+
valid = false
|
|
969
|
+
}
|
|
970
|
+
const sortedRetired = [...retirement.surfaces].sort((left, right) => compareUtf8Bytes(left?.path, right?.path))
|
|
971
|
+
const sortedTransfers = [...transfers].sort((left, right) => compareUtf8Bytes(left?.path, right?.path))
|
|
972
|
+
if (JSON.stringify(retirement.surfaces) !== JSON.stringify(sortedRetired) || JSON.stringify(transfers) !== JSON.stringify(sortedTransfers)) {
|
|
973
|
+
lifecycleProblem(`${label} deletion/transfer surfaces must be sorted by path`)
|
|
974
|
+
valid = false
|
|
975
|
+
}
|
|
976
|
+
const partition = [...retirement.surfaces, ...transfers]
|
|
977
|
+
if (partition.length !== new Set(partition.map((surface) => lifecyclePathKey(surface?.path))).size) {
|
|
978
|
+
lifecycleProblem(`${label} deletion/transfer partition contains duplicate paths`)
|
|
979
|
+
valid = false
|
|
980
|
+
}
|
|
981
|
+
return valid
|
|
982
|
+
}
|
|
983
|
+
const validateLifecycleSnapshot = (snapshot, label) => {
|
|
984
|
+
let valid = closedKeys(snapshot, ['previousSnapshotSha256', 'providers', 'releaseVersion', 'retiredProviders'])
|
|
985
|
+
&& exactSemver.test(snapshot?.releaseVersion || '')
|
|
986
|
+
&& (snapshot?.previousSnapshotSha256 === null || lifecycleSha256.test(snapshot?.previousSnapshotSha256 || ''))
|
|
987
|
+
&& Array.isArray(snapshot?.providers)
|
|
988
|
+
&& Array.isArray(snapshot?.retiredProviders)
|
|
989
|
+
if (!valid) {
|
|
990
|
+
lifecycleProblem(`${label} is missing, open-shaped, or invalid`)
|
|
991
|
+
return false
|
|
992
|
+
}
|
|
993
|
+
const providerIds = []
|
|
994
|
+
const globalClaims = []
|
|
995
|
+
for (const [providerIndex, provider] of snapshot.providers.entries()) {
|
|
996
|
+
const providerLabel = `${label}.providers[${providerIndex}]`
|
|
997
|
+
let providerValid = closedKeys(provider, ['discovery', 'id', 'surfaces'])
|
|
998
|
+
&& lifecycleProviderId.test(provider?.id || '')
|
|
999
|
+
&& Array.isArray(provider?.surfaces)
|
|
1000
|
+
&& provider.surfaces.length > 0
|
|
1001
|
+
if (!providerValid) {
|
|
1002
|
+
lifecycleProblem(`${providerLabel} is open-shaped or invalid`)
|
|
1003
|
+
valid = false
|
|
1004
|
+
continue
|
|
1005
|
+
}
|
|
1006
|
+
providerValid = validateLifecycleDiscovery(provider.discovery, `${providerLabel}.${provider.id}`) && providerValid
|
|
1007
|
+
for (const [surfaceIndex, surface] of provider.surfaces.entries()) {
|
|
1008
|
+
providerValid = validateLifecycleSurface(surface, `${providerLabel}.surfaces[${surfaceIndex}]`) && providerValid
|
|
1009
|
+
if (surface?.path) globalClaims.push({ ...surface, providerId: provider.id })
|
|
1010
|
+
}
|
|
1011
|
+
const sortedSurfaces = [...provider.surfaces].sort((left, right) => compareUtf8Bytes(left?.path, right?.path))
|
|
1012
|
+
if (JSON.stringify(provider.surfaces) !== JSON.stringify(sortedSurfaces)) {
|
|
1013
|
+
lifecycleProblem(`${providerLabel}.surfaces must be sorted by path`)
|
|
1014
|
+
providerValid = false
|
|
1015
|
+
}
|
|
1016
|
+
for (let left = 0; left < provider.surfaces.length; left += 1) for (let right = left + 1; right < provider.surfaces.length; right += 1) {
|
|
1017
|
+
if (lifecyclePathsOverlap(provider.surfaces[left]?.path, provider.surfaces[right]?.path)) {
|
|
1018
|
+
lifecycleProblem(`${providerLabel} has overlapping managed surfaces`)
|
|
1019
|
+
providerValid = false
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
providerIds.push(provider.id)
|
|
1023
|
+
valid = providerValid && valid
|
|
1024
|
+
}
|
|
1025
|
+
if (JSON.stringify(providerIds) !== JSON.stringify([...new Set(providerIds)].sort())) {
|
|
1026
|
+
lifecycleProblem(`${label} provider ids must be sorted and unique`)
|
|
1027
|
+
valid = false
|
|
1028
|
+
}
|
|
1029
|
+
for (let left = 0; left < globalClaims.length; left += 1) for (let right = left + 1; right < globalClaims.length; right += 1) {
|
|
1030
|
+
if (globalClaims[left].providerId !== globalClaims[right].providerId && lifecyclePathsOverlap(globalClaims[left].path, globalClaims[right].path)) {
|
|
1031
|
+
lifecycleProblem(`${label} has cross-provider managed surface overlap`)
|
|
1032
|
+
valid = false
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
for (const [index, retirement] of snapshot.retiredProviders.entries()) {
|
|
1036
|
+
valid = validateSnapshotRetirement(retirement, `${label}.retiredProviders[${index}]`) && valid
|
|
1037
|
+
}
|
|
1038
|
+
const retiredIds = snapshot.retiredProviders.map((retirement) => retirement?.id)
|
|
1039
|
+
if (
|
|
1040
|
+
JSON.stringify(retiredIds) !== JSON.stringify([...new Set(retiredIds)].sort())
|
|
1041
|
+
|| providerIds.some((providerId) => retiredIds.includes(providerId))
|
|
1042
|
+
) {
|
|
1043
|
+
lifecycleProblem(`${label} retired provider ids must be sorted, unique, and inactive`)
|
|
1044
|
+
valid = false
|
|
1045
|
+
}
|
|
1046
|
+
return valid
|
|
1047
|
+
}
|
|
1048
|
+
const lifecycleShapeValid = Boolean(
|
|
1049
|
+
closedKeys(providerLifecycle, [
|
|
1050
|
+
'schemaVersion', 'releaseVersion', 'immutableHead', 'immutableHeadSnapshot', 'ledgerSha256', 'previousSnapshotSha256',
|
|
1051
|
+
'currentSnapshot', 'currentSnapshotSha256', 'currentProviderInventorySha256', 'retiredProviders',
|
|
1052
|
+
])
|
|
1053
|
+
&& providerLifecycle.schemaVersion === 4
|
|
1054
|
+
&& providerLifecycle.releaseVersion === bom?.release?.designSystem
|
|
1055
|
+
&& closedKeys(providerLifecycle.immutableHead, ['releaseVersion', 'snapshotSha256', 'providerInventorySha256'])
|
|
1056
|
+
&& exactSemver.test(providerLifecycle.immutableHead.releaseVersion || '')
|
|
1057
|
+
&& lifecycleSha256.test(providerLifecycle.immutableHead.snapshotSha256 || '')
|
|
1058
|
+
&& lifecycleSha256.test(providerLifecycle.immutableHead.providerInventorySha256 || '')
|
|
1059
|
+
&& lifecycleSha256.test(providerLifecycle.ledgerSha256 || '')
|
|
1060
|
+
&& lifecycleSha256.test(providerLifecycle.currentSnapshotSha256 || '')
|
|
1061
|
+
&& lifecycleSha256.test(providerLifecycle.currentProviderInventorySha256 || '')
|
|
1062
|
+
&& (providerLifecycle.previousSnapshotSha256 === null || lifecycleSha256.test(providerLifecycle.previousSnapshotSha256 || ''))
|
|
1063
|
+
&& Array.isArray(providerLifecycle.retiredProviders)
|
|
1064
|
+
)
|
|
1065
|
+
if (!lifecycleShapeValid) lifecycleProblem('installed provider lifecycle is missing, open-shaped, or unknown-versioned')
|
|
1066
|
+
const currentSnapshotValid = validateLifecycleSnapshot(providerLifecycle?.currentSnapshot, 'installed current provider snapshot')
|
|
1067
|
+
const immutableHeadSnapshotValid = validateLifecycleSnapshot(providerLifecycle?.immutableHeadSnapshot, 'installed immutable-head provider snapshot')
|
|
1068
|
+
const currentSnapshot = currentSnapshotValid ? providerLifecycle.currentSnapshot : null
|
|
1069
|
+
const immutableHeadSnapshot = immutableHeadSnapshotValid ? providerLifecycle.immutableHeadSnapshot : null
|
|
1070
|
+
if (lifecycleShapeValid && currentSnapshot && immutableHeadSnapshot) {
|
|
1071
|
+
const currentSnapshotSha256 = sha256(JSON.stringify(currentSnapshot))
|
|
1072
|
+
const currentProviderInventorySha256 = sha256(JSON.stringify(currentSnapshot.providers))
|
|
1073
|
+
const immutableSnapshotSha256 = sha256(JSON.stringify(immutableHeadSnapshot))
|
|
1074
|
+
const immutableProviderInventorySha256 = sha256(JSON.stringify(immutableHeadSnapshot.providers))
|
|
1075
|
+
if (
|
|
1076
|
+
currentSnapshotSha256 !== providerLifecycle.currentSnapshotSha256
|
|
1077
|
+
|| currentProviderInventorySha256 !== providerLifecycle.currentProviderInventorySha256
|
|
1078
|
+
) lifecycleProblem('installed current provider snapshot body does not match its authenticated hashes')
|
|
1079
|
+
if (
|
|
1080
|
+
currentSnapshot.releaseVersion !== providerLifecycle.releaseVersion
|
|
1081
|
+
|| currentSnapshot.previousSnapshotSha256 !== providerLifecycle.previousSnapshotSha256
|
|
1082
|
+
) lifecycleProblem('installed current provider snapshot release/previous pointer differs from its lifecycle envelope')
|
|
1083
|
+
if (
|
|
1084
|
+
immutableSnapshotSha256 !== providerLifecycle.immutableHead.snapshotSha256
|
|
1085
|
+
|| immutableProviderInventorySha256 !== providerLifecycle.immutableHead.providerInventorySha256
|
|
1086
|
+
|| immutableHeadSnapshot.releaseVersion !== providerLifecycle.immutableHead.releaseVersion
|
|
1087
|
+
) lifecycleProblem('installed immutable-head snapshot body does not match its authenticated head')
|
|
1088
|
+
|
|
1089
|
+
const projectedRetirements = providerLifecycle.retiredProviders.map((retirement) => {
|
|
1090
|
+
if (!retirement || typeof retirement !== 'object' || Array.isArray(retirement)) return retirement
|
|
1091
|
+
const {
|
|
1092
|
+
priorProviderSha256: _priorProviderSha256,
|
|
1093
|
+
priorReleaseVersion: _priorReleaseVersion,
|
|
1094
|
+
priorSnapshotSha256: _priorSnapshotSha256,
|
|
1095
|
+
...projection
|
|
1096
|
+
} = retirement
|
|
1097
|
+
return projection
|
|
1098
|
+
})
|
|
1099
|
+
if (JSON.stringify(projectedRetirements) !== JSON.stringify(currentSnapshot.retiredProviders)) {
|
|
1100
|
+
lifecycleProblem('installed authenticated retirement proofs do not exactly project the current snapshot retirement ledger')
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (currentSnapshot.previousSnapshotSha256 === null) {
|
|
1104
|
+
if (
|
|
1105
|
+
JSON.stringify(currentSnapshot) !== JSON.stringify(immutableHeadSnapshot)
|
|
1106
|
+
|| currentSnapshotSha256 !== immutableSnapshotSha256
|
|
1107
|
+
|| currentSnapshot.retiredProviders.length !== 0
|
|
1108
|
+
) lifecycleProblem('provider lifecycle genesis must use one identical current/immutable snapshot without retirements')
|
|
1109
|
+
} else {
|
|
1110
|
+
if (currentSnapshot.previousSnapshotSha256 !== immutableSnapshotSha256) {
|
|
1111
|
+
lifecycleProblem('current provider snapshot does not bind the embedded immutable-head snapshot')
|
|
1112
|
+
}
|
|
1113
|
+
if ((compareLifecycleSemver(currentSnapshot.releaseVersion, immutableHeadSnapshot.releaseVersion) ?? 0) <= 0) {
|
|
1114
|
+
lifecycleProblem('current provider snapshot release must increase after the immutable-head release')
|
|
1115
|
+
}
|
|
1116
|
+
const priorRetirements = immutableHeadSnapshot.retiredProviders
|
|
1117
|
+
if (
|
|
1118
|
+
currentSnapshot.retiredProviders.length < priorRetirements.length
|
|
1119
|
+
|| JSON.stringify(currentSnapshot.retiredProviders.slice(0, priorRetirements.length)) !== JSON.stringify(priorRetirements)
|
|
1120
|
+
) lifecycleProblem('provider snapshot retirement history is not append-only')
|
|
1121
|
+
const previousById = new Map(immutableHeadSnapshot.providers.map((provider) => [provider.id, provider]))
|
|
1122
|
+
const currentById = new Map(currentSnapshot.providers.map((provider) => [provider.id, provider]))
|
|
1123
|
+
for (const [providerId, previousProvider] of previousById) {
|
|
1124
|
+
const nextProvider = currentById.get(providerId)
|
|
1125
|
+
if (!nextProvider) continue
|
|
1126
|
+
const nextSurfaceKinds = new Map(nextProvider.surfaces.map((surface) => [surface.path, surface.kind]))
|
|
1127
|
+
if (previousProvider.surfaces.some((surface) => nextSurfaceKinds.get(surface.path) !== surface.kind)) {
|
|
1128
|
+
lifecycleProblem(`${currentSnapshot.releaseVersion}:${providerId} removed or changed a managed surface without retirement`)
|
|
1129
|
+
}
|
|
1130
|
+
const previousSurfacePaths = new Set(previousProvider.surfaces.map((surface) => lifecyclePathKey(surface.path)))
|
|
1131
|
+
if (nextProvider.surfaces.some((surface) => (
|
|
1132
|
+
!previousSurfacePaths.has(lifecyclePathKey(surface.path))
|
|
1133
|
+
&& !lifecycleDiscoveryReservesPath(previousProvider.discovery, surface.path)
|
|
1134
|
+
))) lifecycleProblem(`${currentSnapshot.releaseVersion}:${providerId} added a managed surface outside its immutable discovery authority`)
|
|
1135
|
+
for (const key of discoveryArrayKeys) {
|
|
1136
|
+
const nextDiscovery = new Set(nextProvider.discovery[key].map(lifecyclePathKey))
|
|
1137
|
+
if (previousProvider.discovery[key].some((entry) => !nextDiscovery.has(lifecyclePathKey(entry)))) {
|
|
1138
|
+
lifecycleProblem(`${currentSnapshot.releaseVersion}:${providerId} removed immutable discovery authority ${key}`)
|
|
1139
|
+
}
|
|
1140
|
+
const previousDiscovery = new Set(previousProvider.discovery[key].map(lifecyclePathKey))
|
|
1141
|
+
if (nextProvider.discovery[key].some((entry) => (
|
|
1142
|
+
!previousDiscovery.has(lifecyclePathKey(entry))
|
|
1143
|
+
&& !lifecycleDiscoveryReservesPath(previousProvider.discovery, entry)
|
|
1144
|
+
))) lifecycleProblem(`${currentSnapshot.releaseVersion}:${providerId} added discovery authority outside its immutable namespace ${key}`)
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const removedProviderIds = [...previousById.keys()].filter((providerId) => !currentById.has(providerId)).sort()
|
|
1148
|
+
const appendedRetirements = currentSnapshot.retiredProviders.slice(priorRetirements.length)
|
|
1149
|
+
if (JSON.stringify(appendedRetirements.map((retirement) => retirement.id).sort()) !== JSON.stringify(removedProviderIds)) {
|
|
1150
|
+
lifecycleProblem('provider removals do not exactly match appended snapshot retirements')
|
|
1151
|
+
}
|
|
1152
|
+
const authenticatedById = new Map(providerLifecycle.retiredProviders.map((retirement) => [retirement?.id, retirement]))
|
|
1153
|
+
const currentActiveClaims = currentSnapshot.providers.flatMap((provider) => provider.surfaces.map((surface) => ({
|
|
1154
|
+
...surface,
|
|
1155
|
+
providerId: provider.id,
|
|
1156
|
+
})))
|
|
1157
|
+
for (const retirement of appendedRetirements) {
|
|
1158
|
+
const priorProvider = previousById.get(retirement.id)
|
|
1159
|
+
const authenticated = authenticatedById.get(retirement.id)
|
|
1160
|
+
const partition = [
|
|
1161
|
+
...retirement.surfaces.map(({ path, kind }) => ({ path, kind })),
|
|
1162
|
+
...(retirement.transfers || []).map(({ path, kind }) => ({ path, kind })),
|
|
1163
|
+
].sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
1164
|
+
if (
|
|
1165
|
+
!priorProvider
|
|
1166
|
+
|| JSON.stringify(retirement.discovery) !== JSON.stringify(priorProvider.discovery)
|
|
1167
|
+
|| JSON.stringify(partition) !== JSON.stringify(priorProvider.surfaces)
|
|
1168
|
+
|| retirement.retiredInVersion !== currentSnapshot.releaseVersion
|
|
1169
|
+
) lifecycleProblem(`${retirement.id}:retirement does not exactly project its immutable prior provider`)
|
|
1170
|
+
if (
|
|
1171
|
+
!authenticated
|
|
1172
|
+
|| authenticated.priorReleaseVersion !== immutableHeadSnapshot.releaseVersion
|
|
1173
|
+
|| authenticated.priorSnapshotSha256 !== immutableSnapshotSha256
|
|
1174
|
+
|| !priorProvider
|
|
1175
|
+
|| authenticated.priorProviderSha256 !== sha256(JSON.stringify(priorProvider))
|
|
1176
|
+
) lifecycleProblem(`${retirement.id}:authenticated retirement origin does not bind the embedded immutable head`)
|
|
1177
|
+
const replacement = retirement.replacementProviderId === null
|
|
1178
|
+
? null
|
|
1179
|
+
: currentById.get(retirement.replacementProviderId)
|
|
1180
|
+
if (retirement.replacementProviderId !== null && (!replacement || replacement.id === retirement.id)) {
|
|
1181
|
+
lifecycleProblem(`${retirement.id}:replacement provider is not a distinct active current provider`)
|
|
1182
|
+
}
|
|
1183
|
+
for (const surface of retirement.surfaces) {
|
|
1184
|
+
if (currentActiveClaims.some((candidate) => lifecyclePathsOverlap(candidate.path, surface.path))) {
|
|
1185
|
+
lifecycleProblem(`${retirement.id}:deletion tombstone remains owned by an active provider`)
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
for (const surface of retirement.transfers || []) {
|
|
1189
|
+
if (!replacement?.surfaces.some((candidate) => candidate.path === surface.path && candidate.kind === surface.kind)) {
|
|
1190
|
+
lifecycleProblem(`${retirement.id}:transferred surface is not exactly owned by its replacement provider`)
|
|
1191
|
+
}
|
|
1192
|
+
if (currentActiveClaims.some((candidate) => candidate.providerId !== replacement?.id && lifecyclePathsOverlap(candidate.path, surface.path))) {
|
|
1193
|
+
lifecycleProblem(`${retirement.id}:transferred surface is also owned by a non-replacement provider`)
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if (lifecycleShapeValid && bom?.payload?.providerLifecycleSha256 !== sha256(JSON.stringify(providerLifecycle))) {
|
|
1200
|
+
add('GOV-BOM-004', 'BLOCKER', 'installed provider lifecycle digest differs from the immutable governance BOM')
|
|
1201
|
+
}
|
|
1202
|
+
const shippedManagedFiles = manifest?.consumer?.managedFiles || {}
|
|
1203
|
+
const expectedManagedNames = bomManagedFileNames
|
|
1204
|
+
if (JSON.stringify(Object.keys(shippedManagedFiles).sort()) !== JSON.stringify(expectedManagedNames)) {
|
|
1205
|
+
add('GOV-MANAGED-FILE-002', 'BLOCKER', 'installed managed-file body map does not exactly match the immutable BOM')
|
|
1206
|
+
}
|
|
1207
|
+
for (const [destination, digest] of Object.entries(bom?.payload?.managedFiles || {})) {
|
|
1208
|
+
const artifact = shippedManagedFiles[destination]
|
|
1209
|
+
const absolute = typeof artifact === 'string' ? resolve(forkRoot, artifact) : ''
|
|
1210
|
+
const artifactRelative = absolute ? relative(forkRoot, absolute).replaceAll('\\', '/') : ''
|
|
1211
|
+
if (
|
|
1212
|
+
!artifact
|
|
1213
|
+
|| artifactRelative.startsWith('../')
|
|
1214
|
+
|| !artifactRelative.startsWith('consumer/managed-files/')
|
|
1215
|
+
|| !isRegularFile(absolute)
|
|
1216
|
+
|| fileSha(absolute) !== digest
|
|
1217
|
+
) add('GOV-MANAGED-FILE-002', 'BLOCKER', `installed managed body missing or unbound: ${destination}`)
|
|
1218
|
+
}
|
|
1219
|
+
const shippedBomPath = join(forkRoot, manifest?.consumer?.governanceLock || 'consumer/lock.json')
|
|
1220
|
+
const shippedBomSchemaPath = join(forkRoot, manifest?.consumer?.governanceLockSchema || 'consumer/lock.schema.json')
|
|
1221
|
+
const committedBomSchemaPath = join(repo, 'governance/lock.schema.json')
|
|
1222
|
+
if (!existsSync(shippedBomPath) || !existsSync(bomPath) || fileSha(shippedBomPath) !== fileSha(bomPath)) {
|
|
1223
|
+
add('GOV-BOM-003', 'BLOCKER', 'committed governance lock is not byte-identical to the installed immutable release lock')
|
|
1224
|
+
}
|
|
1225
|
+
if (!existsSync(shippedBomSchemaPath) || !existsSync(committedBomSchemaPath) || fileSha(shippedBomSchemaPath) !== fileSha(committedBomSchemaPath)) {
|
|
1226
|
+
add('GOV-BOM-003', 'BLOCKER', 'committed governance lock schema is not byte-identical to the installed immutable schema')
|
|
1227
|
+
}
|
|
1228
|
+
const expectedBomKeys = ['$schema', 'authority', 'generatedBy', 'payload', 'providers', 'release', 'role', 'schemaVersion', 'upgradeProtocol', 'upgradeTrust']
|
|
1229
|
+
const expectedPayloadKeys = [
|
|
1230
|
+
'commonInstructionSha256', 'mergeSurfaceDigests', 'providerArtifactDigests',
|
|
1231
|
+
'discoveryPolicySha256', 'providerLifecycleSha256', 'providerInventorySha256', 'providerMaterializersSha256',
|
|
1232
|
+
'executionRuntimeSha256',
|
|
1233
|
+
'forkCorpusLockSha256', 'governanceCheckSha256', 'managedFiles', 'requiredScripts',
|
|
1234
|
+
'sharedSkillCount', 'sharedSkillsSha256', 'utilityRegistrySha256',
|
|
1235
|
+
].sort()
|
|
1236
|
+
if (
|
|
1237
|
+
!bom || JSON.stringify(Object.keys(bom).sort()) !== JSON.stringify(expectedBomKeys.sort())
|
|
1238
|
+
|| JSON.stringify(Object.keys(bom?.payload || {}).sort()) !== JSON.stringify(expectedPayloadKeys)
|
|
1239
|
+
) add('GOV-BOM-004', 'BLOCKER', 'governance lock does not satisfy the closed canonical shape')
|
|
1240
|
+
if (!isRegularFile(utilityRegistryPath) || bom?.payload?.utilityRegistrySha256 !== fileSha(utilityRegistryPath)) {
|
|
1241
|
+
add('GOV-UTILITY-REGISTRY-001', 'BLOCKER', 'installed canonical utility registry is missing, aliased, or differs from the immutable governance BOM')
|
|
1242
|
+
}
|
|
1243
|
+
const executionRuntime = manifest?.consumer?.executionRuntime
|
|
1244
|
+
const expectedExecutionRuntimeKeys = [
|
|
1245
|
+
'engine', 'exactNpmVersion', 'minimumNodeVersion', 'nativeWindowsPolicy', 'requiredExecutables', 'requiredPathClasses', 'schemaVersion',
|
|
1246
|
+
'supportedHostPlatforms', 'windowsCompatibilityEnvironments',
|
|
1247
|
+
].sort()
|
|
1248
|
+
const executionRuntimeValid = Boolean(
|
|
1249
|
+
closedKeys(executionRuntime, expectedExecutionRuntimeKeys)
|
|
1250
|
+
&& executionRuntime.schemaVersion === 2
|
|
1251
|
+
&& executionRuntime.engine === 'node-posix-toolchain-v2'
|
|
1252
|
+
&& /^\d+\.\d+\.\d+$/.test(executionRuntime.minimumNodeVersion || '')
|
|
1253
|
+
&& /^\d+\.\d+\.\d+$/.test(executionRuntime.exactNpmVersion || '')
|
|
1254
|
+
&& executionRuntime.nativeWindowsPolicy === 'unsupported-fail-closed'
|
|
1255
|
+
&& JSON.stringify(executionRuntime.supportedHostPlatforms) === JSON.stringify(['darwin', 'linux'])
|
|
1256
|
+
&& JSON.stringify(executionRuntime.windowsCompatibilityEnvironments) === JSON.stringify(['devcontainer-linux', 'wsl2-linux'])
|
|
1257
|
+
&& JSON.stringify(executionRuntime.requiredExecutables) === JSON.stringify(['bash', 'git', 'jq', 'node', 'perl', 'python3'])
|
|
1258
|
+
&& JSON.stringify(executionRuntime.requiredPathClasses) === JSON.stringify(['plain', 'space-containing'])
|
|
1259
|
+
)
|
|
1260
|
+
if (!executionRuntimeValid) add('GOV-RUNTIME-001', 'BLOCKER', 'installed consumer execution runtime contract is missing, open-shaped, or unsupported')
|
|
1261
|
+
if (executionRuntimeValid && bom?.payload?.executionRuntimeSha256 !== sha256(JSON.stringify(executionRuntime))) {
|
|
1262
|
+
add('GOV-BOM-004', 'BLOCKER', 'installed execution runtime digest differs from the immutable governance BOM')
|
|
1263
|
+
}
|
|
1264
|
+
if (executionRuntimeValid && !executionRuntime.supportedHostPlatforms.includes(process.platform)) {
|
|
1265
|
+
const compatibility = process.platform === 'win32'
|
|
1266
|
+
? `; use one of the declared Linux execution environments: ${executionRuntime.windowsCompatibilityEnvironments.join(', ')}`
|
|
1267
|
+
: ''
|
|
1268
|
+
add('GOV-RUNTIME-002', 'BLOCKER', `native host platform is unsupported:${process.platform}${compatibility}`)
|
|
1269
|
+
}
|
|
1270
|
+
if (executionRuntimeValid && !stableVersionAtLeast(process.versions.node, executionRuntime.minimumNodeVersion)) {
|
|
1271
|
+
add('GOV-RUNTIME-002', 'BLOCKER', `Node.js ${executionRuntime.minimumNodeVersion} or newer is required; received ${process.versions.node}`)
|
|
1272
|
+
}
|
|
1273
|
+
if (executionRuntimeValid) {
|
|
1274
|
+
const requiredNodeRange = `>=${executionRuntime.minimumNodeVersion}`
|
|
1275
|
+
const lockedRoot = packageLock?.packages?.['']
|
|
1276
|
+
const lockedNpm = packageLock?.packages?.['node_modules/npm']
|
|
1277
|
+
if (
|
|
1278
|
+
pkg?.engines?.node !== requiredNodeRange
|
|
1279
|
+
|| pkg?.devDependencies?.npm !== executionRuntime.exactNpmVersion
|
|
1280
|
+
|| packageLock?.lockfileVersion !== 3
|
|
1281
|
+
|| lockedRoot?.engines?.node !== requiredNodeRange
|
|
1282
|
+
|| lockedRoot?.devDependencies?.npm !== executionRuntime.exactNpmVersion
|
|
1283
|
+
|| lockedNpm?.version !== executionRuntime.exactNpmVersion
|
|
1284
|
+
|| lockedNpm?.resolved !== `https://registry.npmjs.org/npm/-/npm-${executionRuntime.exactNpmVersion}.tgz`
|
|
1285
|
+
|| !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(lockedNpm?.integrity || '')
|
|
1286
|
+
) add('GOV-RUNTIME-001', 'BLOCKER', 'package engines and exact npm lock must equal the installed execution runtime contract')
|
|
1287
|
+
}
|
|
1288
|
+
const expectedUpgradeTrust = {
|
|
1289
|
+
schemaVersion: 1,
|
|
1290
|
+
repository: 'ajenchen/design-system',
|
|
1291
|
+
workflow: '.github/workflows/release.yml',
|
|
1292
|
+
workflowRef: 'refs/heads/main',
|
|
1293
|
+
provenancePredicate: 'https://slsa.dev/provenance/v1',
|
|
1294
|
+
builderId: 'https://github.com/actions/runner/github-hosted',
|
|
1295
|
+
immutableRelease: true,
|
|
1296
|
+
releaseBomAsset: 'release-bom.json',
|
|
1297
|
+
upgradePackages: ['@qijenchen/design-system', '@qijenchen/storybook-config'],
|
|
1298
|
+
releasePackages: ['@qijenchen/design-system', '@qijenchen/governance', '@qijenchen/storybook-config'],
|
|
1299
|
+
}
|
|
1300
|
+
if (JSON.stringify(bom?.upgradeTrust) !== JSON.stringify(expectedUpgradeTrust)) {
|
|
1301
|
+
add('GOV-BOM-004', 'BLOCKER', 'governance lock upgradeTrust is not the closed repository/workflow/release/package policy')
|
|
1302
|
+
}
|
|
1303
|
+
const expectedUpgradeProtocolKeys = [
|
|
1304
|
+
'bootstrapEvidenceContract', 'candidateCodeExecutionAllowed', 'controlPlaneUpdateEvidenceContract',
|
|
1305
|
+
'controlPlaneUpdateMode', 'controlPlaneUpdatePlanner', 'implementationSha256', 'legacyEntryMode',
|
|
1306
|
+
'ordinaryUpgradeMode', 'physicalPowerLossDurabilityClaimed', 'protectedBaseVerifier',
|
|
1307
|
+
'protocolId', 'schemaSha256', 'schemaVersion', 'specificationSha256',
|
|
1308
|
+
]
|
|
1309
|
+
const upgradeProtocolShapeValid = Boolean(
|
|
1310
|
+
closedKeys(bom?.upgradeProtocol, expectedUpgradeProtocolKeys)
|
|
1311
|
+
&& bom.upgradeProtocol.schemaVersion === 2
|
|
1312
|
+
&& bom.upgradeProtocol.protocolId === 'protected-base-reconstruction-v2'
|
|
1313
|
+
&& bom.upgradeProtocol.legacyEntryMode === 'one-time-reviewed-full-snapshot-pr'
|
|
1314
|
+
&& bom.upgradeProtocol.bootstrapEvidenceContract === 'consumer-governance-bootstrap-readback-v1'
|
|
1315
|
+
&& bom.upgradeProtocol.controlPlaneUpdateMode === 'recurring-reviewed-full-snapshot-pr'
|
|
1316
|
+
&& bom.upgradeProtocol.controlPlaneUpdateEvidenceContract === 'consumer-governance-control-plane-update-readback-v1'
|
|
1317
|
+
&& bom.upgradeProtocol.controlPlaneUpdatePlanner === 'infra/governance/bin/consumerctl.mjs'
|
|
1318
|
+
&& bom.upgradeProtocol.ordinaryUpgradeMode === 'protected-base-disposable-reconstruction'
|
|
1319
|
+
&& bom.upgradeProtocol.protectedBaseVerifier === 'scripts/verify-upgrade-evidence.mjs'
|
|
1320
|
+
&& bom.upgradeProtocol.candidateCodeExecutionAllowed === false
|
|
1321
|
+
&& bom.upgradeProtocol.physicalPowerLossDurabilityClaimed === false
|
|
1322
|
+
&& /^[a-f0-9]{64}$/.test(bom.upgradeProtocol.specificationSha256 || '')
|
|
1323
|
+
&& /^[a-f0-9]{64}$/.test(bom.upgradeProtocol.schemaSha256 || '')
|
|
1324
|
+
&& /^[a-f0-9]{64}$/.test(bom.upgradeProtocol.implementationSha256 || '')
|
|
1325
|
+
)
|
|
1326
|
+
if (!upgradeProtocolShapeValid) {
|
|
1327
|
+
add('GOV-BOM-004', 'BLOCKER', 'governance lock upgradeProtocol is not the closed protected-base reconstruction v2 specification/schema/implementation binding')
|
|
1328
|
+
}
|
|
1329
|
+
if (corpusLock && bom?.payload?.forkCorpusLockSha256 !== fileSha(corpusLockPath)) {
|
|
1330
|
+
add('GOV-CORPUS-002', 'BLOCKER', 'installed fork corpus lock digest does not match governance BOM')
|
|
1331
|
+
}
|
|
1332
|
+
const corpusLockShapeValid = Boolean(
|
|
1333
|
+
closedKeys(corpusLock, ['schemaVersion', 'kind', 'hashAlgorithm', '_purpose', 'entries'])
|
|
1334
|
+
&& corpusLock.schemaVersion === 1
|
|
1335
|
+
&& corpusLock.kind === 'fork-governance-corpus-lock'
|
|
1336
|
+
&& corpusLock.hashAlgorithm === 'sha256'
|
|
1337
|
+
&& Array.isArray(corpusLock.entries)
|
|
1338
|
+
)
|
|
1339
|
+
if (!corpusLockShapeValid) add('GOV-CORPUS-001', 'BLOCKER', 'installed fork corpus lock is open-shaped, unknown-versioned, or has the wrong identity')
|
|
1340
|
+
const corpusEntryFiles = []
|
|
1341
|
+
for (const entry of corpusLock?.entries || []) {
|
|
1342
|
+
if (
|
|
1343
|
+
!closedKeys(entry, ['schemaVersion', 'file', 'sha256', 'source', 'classification', 'destination'])
|
|
1344
|
+
|| entry.schemaVersion !== 1
|
|
1345
|
+
|| typeof entry.file !== 'string'
|
|
1346
|
+
|| entry.file.includes('\\')
|
|
1347
|
+
|| entry.file.split('/').some((segment) => !segment || segment === '.' || segment === '..')
|
|
1348
|
+
|| !/^[a-f0-9]{64}$/.test(entry.sha256 || '')
|
|
1349
|
+
|| ![null, 'SHIP_AS_IS', 'SHIP_REWRITTEN', 'REPLACE', 'HELPER'].includes(entry.classification)
|
|
1350
|
+
|| (entry.source !== null && typeof entry.source !== 'string')
|
|
1351
|
+
|| (entry.destination !== null && typeof entry.destination !== 'string')
|
|
1352
|
+
) {
|
|
1353
|
+
add('GOV-CORPUS-001', 'BLOCKER', `installed corpus entry is open-shaped or invalid:${entry?.file || '<unknown>'}`)
|
|
1354
|
+
continue
|
|
1355
|
+
}
|
|
1356
|
+
corpusEntryFiles.push(entry.file)
|
|
1357
|
+
const file = join(forkRoot, entry.file)
|
|
1358
|
+
if (!isRegularFile(file)) add('GOV-CORPUS-003', 'BLOCKER', `corpus entry missing or not a regular file: ${entry.file}`)
|
|
1359
|
+
else if (fileSha(file) !== entry.sha256) add('GOV-CORPUS-003', 'BLOCKER', `corpus entry tampered: ${entry.file}`)
|
|
1360
|
+
}
|
|
1361
|
+
if (JSON.stringify(corpusEntryFiles) !== JSON.stringify([...new Set(corpusEntryFiles)].sort(compareUtf8Bytes))) {
|
|
1362
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed corpus entries must be unique and sorted by file')
|
|
1363
|
+
}
|
|
1364
|
+
if (!corpusEntryFiles.includes('review/provider-review-binding.mjs')) {
|
|
1365
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed corpus lacks the locked exact provider review resolver')
|
|
1366
|
+
}
|
|
1367
|
+
for (const artifactPath of authorityDecisionArtifactPaths) {
|
|
1368
|
+
const launcherName = typeof artifactPath === 'string' && artifactPath.startsWith('launchers/')
|
|
1369
|
+
? artifactPath.slice('launchers/'.length)
|
|
1370
|
+
: null
|
|
1371
|
+
if (
|
|
1372
|
+
!launcherName
|
|
1373
|
+
|| !Array.isArray(manifest?.launchers)
|
|
1374
|
+
|| !manifest.launchers.includes(launcherName)
|
|
1375
|
+
|| !corpusEntryFiles.includes(artifactPath)
|
|
1376
|
+
) add('GOV-CORPUS-001', 'BLOCKER', `installed authority-decision artifact is absent from the authenticated launcher corpus:${artifactPath || '<missing>'}`)
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Required CI executes this checker from the exact installed payload. The committed copy remains a
|
|
1380
|
+
// reviewed mirror, but can never become the authority by editing itself in the same pull request.
|
|
1381
|
+
const runningChecker = fileURLToPath(import.meta.url)
|
|
1382
|
+
const shippedChecker = join(forkRoot, manifest?.consumer?.governanceCheck || 'consumer/governance-check.mjs')
|
|
1383
|
+
const committedChecker = join(repo, 'scripts/governance-check.mjs')
|
|
1384
|
+
if (!isRegularFile(shippedChecker) || fileSha(runningChecker) !== fileSha(shippedChecker)) add('GOV-CHECKER-001', 'BLOCKER', 'executed checker is not the exact installed regular-file governance payload')
|
|
1385
|
+
if (!isRegularFile(committedChecker) || !isRegularFile(shippedChecker) || fileSha(committedChecker) !== fileSha(shippedChecker)) add('GOV-CHECKER-001', 'BLOCKER', 'committed governance checker drifted from exact installed payload')
|
|
1386
|
+
if (bom?.payload?.governanceCheckSha256 && isRegularFile(shippedChecker) && bom.payload.governanceCheckSha256 !== fileSha(shippedChecker)) add('GOV-CHECKER-001', 'BLOCKER', 'governance checker digest != immutable consumer BOM')
|
|
1387
|
+
|
|
1388
|
+
const providerSurfaces = manifest?.providerSurfaces
|
|
1389
|
+
if (!providerSurfaces || typeof providerSurfaces !== 'object' || Array.isArray(providerSurfaces)) {
|
|
1390
|
+
add('GOV-PROVIDER-001', 'BLOCKER', 'installed manifest has no provider-neutral providerSurfaces inventory')
|
|
1391
|
+
}
|
|
1392
|
+
if (providerSurfaces && bom?.payload?.providerInventorySha256 !== sha256(JSON.stringify(providerSurfaces))) {
|
|
1393
|
+
add('GOV-BOM-004', 'BLOCKER', 'installed provider surface inventory digest differs from the immutable governance BOM')
|
|
1394
|
+
}
|
|
1395
|
+
if (providerMaterializersValid && bom?.payload?.providerMaterializersSha256 !== sha256(JSON.stringify(providerMaterializers))) {
|
|
1396
|
+
add('GOV-BOM-004', 'BLOCKER', 'installed provider materializer catalog digest differs from the immutable governance BOM')
|
|
1397
|
+
}
|
|
1398
|
+
const providerAdapters = manifest?.providerAdapters
|
|
1399
|
+
if (!providerAdapters || typeof providerAdapters !== 'object' || Array.isArray(providerAdapters)) {
|
|
1400
|
+
add('GOV-PROVIDER-001', 'BLOCKER', 'installed manifest has no provider adapter inventory')
|
|
1401
|
+
}
|
|
1402
|
+
const expectedAdapterKeys = ['schemaVersion', 'capabilities', 'environmentMap', 'normalization', 'blockingExitCode', 'contextOutputTransport', 'stopDecisionTransport']
|
|
1403
|
+
for (const [providerId, adapter] of Object.entries(providerAdapters || {})) {
|
|
1404
|
+
if (
|
|
1405
|
+
!closedKeys(adapter, expectedAdapterKeys)
|
|
1406
|
+
|| adapter.schemaVersion !== 1
|
|
1407
|
+
|| !Number.isInteger(adapter.blockingExitCode)
|
|
1408
|
+
|| adapter.blockingExitCode < 1
|
|
1409
|
+
|| adapter.blockingExitCode > 255
|
|
1410
|
+
|| !closedKeys(adapter.capabilities, ['nativeHooks', 'hookEnforcement', 'hardGate'])
|
|
1411
|
+
|| adapter.capabilities.hardGate !== 'provider-neutral-ci-required'
|
|
1412
|
+
|| !['native-feedback-plus-ci', 'ci-only'].includes(adapter.capabilities.hookEnforcement)
|
|
1413
|
+
) add('GOV-PROVIDER-001', 'BLOCKER', `${providerId} provider adapter record is open-shaped or unknown-versioned`)
|
|
1414
|
+
}
|
|
1415
|
+
const manifestPath = (root, value, label) => {
|
|
1416
|
+
if (typeof value !== 'string' || !value || value.includes('\0') || isAbsolute(value)) {
|
|
1417
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${label} is not a repository-relative path`)
|
|
1418
|
+
return null
|
|
1419
|
+
}
|
|
1420
|
+
const absolute = resolve(root, value)
|
|
1421
|
+
const rel = relative(root, absolute)
|
|
1422
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
1423
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${label} escapes its declared root:${value}`)
|
|
1424
|
+
return null
|
|
1425
|
+
}
|
|
1426
|
+
return absolute
|
|
1427
|
+
}
|
|
1428
|
+
const commonInstructionMaterializer = resolveSurfaceMaterializer(
|
|
1429
|
+
'instructionViews',
|
|
1430
|
+
commonInstruction?.materializerId,
|
|
1431
|
+
'consumer.commonInstruction.materializerId',
|
|
1432
|
+
)
|
|
1433
|
+
if (
|
|
1434
|
+
commonInstructionMaterializer?.engine !== 'shared-instruction-v1'
|
|
1435
|
+
|| commonInstructionMaterializer?.ownershipPolicy !== 'common-authority-consumer'
|
|
1436
|
+
|| commonInstruction?.materializerId !== commonAuthorityMaterializerIds[0]
|
|
1437
|
+
) add('GOV-ADAPTER-003', 'BLOCKER', 'common instruction materializer is not the registered common-authority consumer')
|
|
1438
|
+
const commonInstructionArtifact = manifestPath(forkRoot, commonInstruction?.artifact, 'common instruction artifact')
|
|
1439
|
+
const commonInstructionDestination = manifestPath(repo, commonInstruction?.destination, 'common instruction destination')
|
|
1440
|
+
if (
|
|
1441
|
+
!commonInstructionArtifact
|
|
1442
|
+
|| !commonInstructionDestination
|
|
1443
|
+
|| !isRegularFile(commonInstructionArtifact)
|
|
1444
|
+
|| !isRegularFile(commonInstructionDestination)
|
|
1445
|
+
|| fileSha(commonInstructionArtifact) !== fileSha(commonInstructionDestination)
|
|
1446
|
+
|| bom?.payload?.commonInstructionSha256 !== fileSha(commonInstructionArtifact)
|
|
1447
|
+
) add('GOV-ADAPTER-003', 'BLOCKER', 'common instruction is not the exact locked consumer projection')
|
|
1448
|
+
// Provider-native discovery roots are closed surfaces. Exact hook files and recursively verified
|
|
1449
|
+
// skill/launcher trees are the only leaves allowed below each generated dot-directory; otherwise a
|
|
1450
|
+
// sibling settings.local.json/command/skill can become an unsigned executable policy source even
|
|
1451
|
+
// when every canonical destination still matches its digest.
|
|
1452
|
+
const providerRootPlans = new Map()
|
|
1453
|
+
const generatedProviderDestinations = new Set()
|
|
1454
|
+
if (commonInstructionDestination) {
|
|
1455
|
+
generatedProviderDestinations.add(resolve(commonInstructionDestination))
|
|
1456
|
+
canonicalReplayExcludedFiles.add(resolve(commonInstructionDestination))
|
|
1457
|
+
}
|
|
1458
|
+
const providerRootFor = (absolute) => {
|
|
1459
|
+
const rel = relative(repo, absolute)
|
|
1460
|
+
if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null
|
|
1461
|
+
const first = rel.split(sep)[0]
|
|
1462
|
+
return first.startsWith('.') && first !== '.' && first !== '..' ? join(repo, first) : null
|
|
1463
|
+
}
|
|
1464
|
+
const registerProviderPath = (absolute, kind) => {
|
|
1465
|
+
if (!absolute) return
|
|
1466
|
+
const root = providerRootFor(absolute)
|
|
1467
|
+
if (!root) return
|
|
1468
|
+
const plan = providerRootPlans.get(root) || { files: new Set(), trees: new Set() }
|
|
1469
|
+
plan[kind === 'tree' ? 'trees' : 'files'].add(resolve(absolute))
|
|
1470
|
+
providerRootPlans.set(root, plan)
|
|
1471
|
+
}
|
|
1472
|
+
const registerProviderRootOnly = (absolute) => {
|
|
1473
|
+
const root = providerRootFor(absolute)
|
|
1474
|
+
if (root && !providerRootPlans.has(root)) providerRootPlans.set(root, { files: new Set(), trees: new Set() })
|
|
1475
|
+
}
|
|
1476
|
+
for (const root of [...discoveryInventory.providerRootNames, ...discoveryInventory.pluginRootNames]) {
|
|
1477
|
+
if (discoveryRootPattern.test(root)) registerProviderRootOnly(join(repo, root))
|
|
1478
|
+
}
|
|
1479
|
+
const surfaceHookConfigs = new Map()
|
|
1480
|
+
const installedSurfaceHookConfigs = new Map()
|
|
1481
|
+
const surfaceHookMaterializers = new Map()
|
|
1482
|
+
const surfaceSkillMaterializers = new Map()
|
|
1483
|
+
const surfaceNativeEventBindings = new Map()
|
|
1484
|
+
const generatedSurfaces = []
|
|
1485
|
+
const managedSurfacesByProvider = new Map()
|
|
1486
|
+
const expectedSurfaceKeys = [
|
|
1487
|
+
'schemaVersion', 'generated', 'capabilities', 'instructionArtifact', 'instructionDestination',
|
|
1488
|
+
'instructionMaterializerId', 'hookArtifact', 'hookDestination', 'hookMaterializerId', 'skillArtifactRoot', 'skillDestination',
|
|
1489
|
+
'skillEntryFile', 'skillMaterializerId', 'skills', 'trees', 'managedSurfaces', 'nativeHookCoverage', 'environmentMap', 'exclusions',
|
|
1490
|
+
]
|
|
1491
|
+
for (const [providerId, surface] of Object.entries(providerSurfaces || {}).sort(([a], [b]) => compareUtf8Bytes(a, b))) {
|
|
1492
|
+
if (
|
|
1493
|
+
!closedKeys(surface, expectedSurfaceKeys)
|
|
1494
|
+
|| surface.schemaVersion !== 3
|
|
1495
|
+
|| !closedKeys(surface.capabilities, ['nativeHooks', 'hookEnforcement', 'hardGate'])
|
|
1496
|
+
|| surface.capabilities.hardGate !== 'provider-neutral-ci-required'
|
|
1497
|
+
|| !Array.isArray(surface.skills)
|
|
1498
|
+
|| !Array.isArray(surface.trees)
|
|
1499
|
+
|| !Array.isArray(surface.managedSurfaces)
|
|
1500
|
+
|| !Array.isArray(surface.exclusions)
|
|
1501
|
+
) {
|
|
1502
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} provider surface record is open-shaped or unknown-versioned`)
|
|
1503
|
+
continue
|
|
1504
|
+
}
|
|
1505
|
+
if (!surface?.generated) {
|
|
1506
|
+
if (surface.managedSurfaces.length) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} disabled surface claims managed destinations`)
|
|
1507
|
+
if (surface.trees.length) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} disabled surface claims product managed trees`)
|
|
1508
|
+
if (!Array.isArray(surface?.exclusions) || !surface.exclusions.length) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} disabled surface has no machine-readable exclusion`)
|
|
1509
|
+
if (surface.instructionMaterializerId !== null || surface.hookMaterializerId !== null || surface.skillMaterializerId !== null || surface.skillEntryFile !== null) {
|
|
1510
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} disabled surface claims provider materializers`)
|
|
1511
|
+
}
|
|
1512
|
+
if (surface.nativeHookCoverage !== null) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} disabled surface claims native hook coverage`)
|
|
1513
|
+
continue
|
|
1514
|
+
}
|
|
1515
|
+
const nativeHooks = surface.capabilities.nativeHooks === true
|
|
1516
|
+
const nativeHookCoverage = surface.nativeHookCoverage
|
|
1517
|
+
const coverageCanonicalEvents = nativeHookCoverage?.canonicalEvents
|
|
1518
|
+
const coverageProjectedEvents = nativeHookCoverage?.projectedEvents
|
|
1519
|
+
const coverageProjectedNativeEvents = nativeHookCoverage?.projectedNativeEvents
|
|
1520
|
+
const coverageExcludedEvents = nativeHookCoverage?.excludedEvents
|
|
1521
|
+
const sortedUniqueStrings = (values) => Array.isArray(values)
|
|
1522
|
+
&& values.every((value) => typeof value === 'string' && value.length > 0)
|
|
1523
|
+
&& JSON.stringify(values) === JSON.stringify([...new Set(values)].sort())
|
|
1524
|
+
const coverageExcludedValid = Array.isArray(coverageExcludedEvents)
|
|
1525
|
+
&& coverageExcludedEvents.every((record) => (
|
|
1526
|
+
closedKeys(record, ['authoritativeFallback', 'event', 'reasonCode'])
|
|
1527
|
+
&& typeof record.event === 'string'
|
|
1528
|
+
&& /^[A-Z][A-Z0-9_]*$/.test(record.reasonCode || '')
|
|
1529
|
+
&& record.authoritativeFallback === 'immutable-governance-check-and-protected-ci'
|
|
1530
|
+
))
|
|
1531
|
+
&& JSON.stringify(coverageExcludedEvents.map((record) => record.event))
|
|
1532
|
+
=== JSON.stringify(coverageExcludedEvents.map((record) => record.event).sort())
|
|
1533
|
+
const excludedCoverageEvents = coverageExcludedValid ? coverageExcludedEvents.map((record) => record.event) : []
|
|
1534
|
+
const coverageProjectedNativeValid = Array.isArray(coverageProjectedNativeEvents)
|
|
1535
|
+
&& coverageProjectedNativeEvents.every((record) => (
|
|
1536
|
+
closedKeys(record, ['canonicalEvent', 'nativeEvent'])
|
|
1537
|
+
&& typeof record.canonicalEvent === 'string'
|
|
1538
|
+
&& record.canonicalEvent.length > 0
|
|
1539
|
+
&& typeof record.nativeEvent === 'string'
|
|
1540
|
+
&& /^[A-Za-z][A-Za-z0-9_.:@/-]*$/.test(record.nativeEvent)
|
|
1541
|
+
))
|
|
1542
|
+
&& JSON.stringify(coverageProjectedNativeEvents.map((record) => record.canonicalEvent))
|
|
1543
|
+
=== JSON.stringify(coverageProjectedNativeEvents.map((record) => record.canonicalEvent).sort())
|
|
1544
|
+
const projectedNativeCanonicalEvents = coverageProjectedNativeValid ? coverageProjectedNativeEvents.map((record) => record.canonicalEvent) : []
|
|
1545
|
+
const projectedNativeEvents = coverageProjectedNativeValid ? coverageProjectedNativeEvents.map((record) => record.nativeEvent) : []
|
|
1546
|
+
if (
|
|
1547
|
+
!closedKeys(nativeHookCoverage, ['canonicalEvents', 'excludedEvents', 'projectedEvents', 'projectedNativeEvents', 'schemaVersion'])
|
|
1548
|
+
|| nativeHookCoverage.schemaVersion !== 2
|
|
1549
|
+
|| !sortedUniqueStrings(coverageCanonicalEvents)
|
|
1550
|
+
|| !coverageCanonicalEvents.length
|
|
1551
|
+
|| !sortedUniqueStrings(coverageProjectedEvents)
|
|
1552
|
+
|| !coverageExcludedValid
|
|
1553
|
+
|| !coverageProjectedNativeValid
|
|
1554
|
+
|| JSON.stringify(projectedNativeCanonicalEvents) !== JSON.stringify(coverageProjectedEvents)
|
|
1555
|
+
|| new Set(projectedNativeEvents).size !== projectedNativeEvents.length
|
|
1556
|
+
|| new Set(excludedCoverageEvents).size !== excludedCoverageEvents.length
|
|
1557
|
+
|| coverageProjectedEvents.some((event) => excludedCoverageEvents.includes(event))
|
|
1558
|
+
|| JSON.stringify([...coverageProjectedEvents, ...excludedCoverageEvents].sort()) !== JSON.stringify(coverageCanonicalEvents)
|
|
1559
|
+
|| (!nativeHooks && (coverageProjectedEvents.length > 0 || coverageExcludedEvents.some((record) => record.reasonCode !== 'NATIVE_HOOK_API_UNAVAILABLE')))
|
|
1560
|
+
|| (nativeHooks && coverageExcludedEvents.some((record) => record.reasonCode !== 'EVENT_NOT_SUPPORTED'))
|
|
1561
|
+
) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} native hook event coverage is open, incomplete, or lacks its authoritative fallback`)
|
|
1562
|
+
surfaceNativeEventBindings.set(providerId, coverageProjectedNativeValid ? coverageProjectedNativeEvents : [])
|
|
1563
|
+
const instructionMaterializer = resolveSurfaceMaterializer('instructionViews', surface.instructionMaterializerId, `${providerId}.instructionMaterializerId`)
|
|
1564
|
+
const hookMaterializer = nativeHooks
|
|
1565
|
+
? resolveSurfaceMaterializer('hookViews', surface.hookMaterializerId, `${providerId}.hookMaterializerId`)
|
|
1566
|
+
: null
|
|
1567
|
+
const skillMaterializer = resolveSurfaceMaterializer('skillViews', surface.skillMaterializerId, `${providerId}.skillMaterializerId`)
|
|
1568
|
+
surfaceHookMaterializers.set(providerId, hookMaterializer)
|
|
1569
|
+
surfaceSkillMaterializers.set(providerId, skillMaterializer)
|
|
1570
|
+
if (skillMaterializer && surface.skillEntryFile !== skillMaterializer.entryFile) {
|
|
1571
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} skill entry file differs from its registered materializer`)
|
|
1572
|
+
}
|
|
1573
|
+
const canonicalManagedSurfaces = [...surface.managedSurfaces].sort((left, right) => compareUtf8Bytes(left?.path, right?.path))
|
|
1574
|
+
if (!surface.managedSurfaces.length || JSON.stringify(surface.managedSurfaces) !== JSON.stringify(canonicalManagedSurfaces)) {
|
|
1575
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} managed surfaces must be non-empty and sorted`)
|
|
1576
|
+
}
|
|
1577
|
+
const managedSurfaceKeys = new Set()
|
|
1578
|
+
const ownedManagedSurfaces = []
|
|
1579
|
+
for (const managed of surface.managedSurfaces) {
|
|
1580
|
+
const absolute = closedKeys(managed, ['path', 'kind']) && ['file', 'tree'].includes(managed.kind)
|
|
1581
|
+
? manifestPath(repo, managed.path, `${providerId} managed surface`)
|
|
1582
|
+
: null
|
|
1583
|
+
if (!absolute || managedSurfaceKeys.has(managed.path)) {
|
|
1584
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} managed surface is invalid or duplicated:${managed?.path || '<unknown>'}`)
|
|
1585
|
+
continue
|
|
1586
|
+
}
|
|
1587
|
+
managedSurfaceKeys.add(managed.path)
|
|
1588
|
+
ownedManagedSurfaces.push({ path: managed.path, kind: managed.kind })
|
|
1589
|
+
registerProviderPath(absolute, managed.kind)
|
|
1590
|
+
if (managed.kind === 'tree') canonicalReplayExcludedRoots.add(resolve(absolute))
|
|
1591
|
+
else canonicalReplayExcludedFiles.add(resolve(absolute))
|
|
1592
|
+
}
|
|
1593
|
+
managedSurfacesByProvider.set(providerId, ownedManagedSurfaces)
|
|
1594
|
+
generatedSurfaces.push([providerId, surface])
|
|
1595
|
+
if (instructionMaterializer?.engine === 'shared-instruction-v1') {
|
|
1596
|
+
if (
|
|
1597
|
+
surface.instructionArtifact !== commonInstruction?.artifact
|
|
1598
|
+
|| surface.instructionDestination !== commonInstruction?.destination
|
|
1599
|
+
|| surface.instructionMaterializerId !== commonInstruction?.materializerId
|
|
1600
|
+
|| ownedManagedSurfaces.some((managed) => commonInstruction?.destination && pathsOverlap(managed.path, commonInstruction.destination))
|
|
1601
|
+
) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} shared instruction does not reference the common non-provider authority`)
|
|
1602
|
+
} else if (!ownedManagedSurfaces.some((managed) => managed.path === surface.instructionDestination && managed.kind === 'file')) {
|
|
1603
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} provider instruction projection is absent from its managed surfaces`)
|
|
1604
|
+
}
|
|
1605
|
+
const instructionArtifact = manifestPath(forkRoot, surface.instructionArtifact, `${providerId} instruction artifact`)
|
|
1606
|
+
const instructionDestination = manifestPath(repo, surface.instructionDestination, `${providerId} instruction destination`)
|
|
1607
|
+
if (instructionDestination) {
|
|
1608
|
+
generatedProviderDestinations.add(resolve(instructionDestination))
|
|
1609
|
+
registerProviderPath(instructionDestination, 'file')
|
|
1610
|
+
}
|
|
1611
|
+
if (!instructionArtifact || !instructionDestination || !isRegularFile(instructionArtifact) || !isRegularFile(instructionDestination) || fileSha(instructionArtifact) !== fileSha(instructionDestination)) {
|
|
1612
|
+
add('GOV-ADAPTER-002', 'BLOCKER', `${providerId} instruction is not the exact installed regular-file projection`)
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (surface.capabilities.hookEnforcement !== (nativeHooks ? 'native-feedback-plus-ci' : 'ci-only')) {
|
|
1616
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} hook capability does not match its provider-neutral enforcement mode`)
|
|
1617
|
+
}
|
|
1618
|
+
const hookArtifact = nativeHooks ? manifestPath(forkRoot, surface.hookArtifact, `${providerId} hook artifact`) : null
|
|
1619
|
+
const hookDestination = nativeHooks ? manifestPath(repo, surface.hookDestination, `${providerId} hook destination`) : null
|
|
1620
|
+
if (nativeHooks && hookDestination) {
|
|
1621
|
+
generatedProviderDestinations.add(resolve(hookDestination))
|
|
1622
|
+
registerProviderPath(hookDestination, 'file')
|
|
1623
|
+
}
|
|
1624
|
+
if (!nativeHooks && (surface.hookArtifact !== null || surface.hookDestination !== null || surface.hookMaterializerId !== null)) {
|
|
1625
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} ci-only provider declares a native hook surface`)
|
|
1626
|
+
}
|
|
1627
|
+
if (!nativeHooks && !surface.exclusions.some((item) => item?.scope === 'native-hooks')) {
|
|
1628
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} ci-only provider has no explicit native-hooks exclusion`)
|
|
1629
|
+
}
|
|
1630
|
+
const canonicalHookConfig = nativeHooks && hookArtifact ? readJson(hookArtifact, 'GOV-ADAPTER-001') : null
|
|
1631
|
+
const installedHookConfig = nativeHooks && hookDestination ? readJson(hookDestination, 'GOV-ADAPTER-001') : null
|
|
1632
|
+
surfaceHookConfigs.set(providerId, canonicalHookConfig)
|
|
1633
|
+
installedSurfaceHookConfigs.set(providerId, installedHookConfig)
|
|
1634
|
+
if (nativeHooks && hookMaterializer?.mergeStrategy !== 'merge-hook-map-into-base-v1') {
|
|
1635
|
+
if (!hookArtifact || !hookDestination || !isRegularFile(hookArtifact) || !isRegularFile(hookDestination) || fileSha(hookArtifact) !== fileSha(hookDestination)) {
|
|
1636
|
+
add('GOV-ADAPTER-005', 'BLOCKER', `${providerId} hook config is not the exact installed canonical projection`)
|
|
1637
|
+
}
|
|
1638
|
+
const expectedConfigKeys = hookMaterializer?.engine === 'json-hook-map-v1'
|
|
1639
|
+
? [hookMaterializer.hooksField, ...(hookMaterializer.descriptionField === null ? [] : [hookMaterializer.descriptionField])]
|
|
1640
|
+
: hookMaterializer?.engine === 'json-hook-event-list-v1'
|
|
1641
|
+
? [hookMaterializer.eventsField, hookMaterializer.descriptionField]
|
|
1642
|
+
: []
|
|
1643
|
+
if (installedHookConfig && (!expectedConfigKeys.length || !closedKeys(installedHookConfig, expectedConfigKeys))) {
|
|
1644
|
+
add('GOV-ADAPTER-004', 'BLOCKER', `${providerId} hook config does not satisfy its registered closed materializer shape`)
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
const skillDestinationRoot = manifestPath(repo, surface.skillDestination, `${providerId} skill destination`)
|
|
1649
|
+
if (skillDestinationRoot) {
|
|
1650
|
+
generatedProviderDestinations.add(resolve(skillDestinationRoot))
|
|
1651
|
+
registerProviderPath(skillDestinationRoot, 'tree')
|
|
1652
|
+
}
|
|
1653
|
+
for (const name of surface.skills || []) {
|
|
1654
|
+
const source = manifestPath(forkRoot, `${surface.skillArtifactRoot}/${name}`, `${providerId} skill artifact ${name}`)
|
|
1655
|
+
const destination = manifestPath(repo, `${surface.skillDestination}/${name}`, `${providerId} skill destination ${name}`)
|
|
1656
|
+
if (!source || !destination) continue
|
|
1657
|
+
if (!isRegularFile(join(source, surface.skillEntryFile || ''))) {
|
|
1658
|
+
add('GOV-SKILL-003', 'BLOCKER', `${providerId} managed skill ${name} lacks its registered entry file`)
|
|
1659
|
+
}
|
|
1660
|
+
assertTreeEqual('GOV-SKILL-003', `${providerId} managed skill ${name}`, destination, source)
|
|
1661
|
+
}
|
|
1662
|
+
const validTrees = []
|
|
1663
|
+
for (const [index, tree] of surface.trees.entries()) {
|
|
1664
|
+
const label = `${providerId} product managed tree[${index}]`
|
|
1665
|
+
if (
|
|
1666
|
+
!closedKeys(tree, ['artifactRoot', 'destination', 'materializerId', 'name', 'schemaVersion', 'transformPolicy'])
|
|
1667
|
+
|| tree.schemaVersion !== 1
|
|
1668
|
+
|| !/^[a-z][a-z0-9-]*$/.test(tree.name || '')
|
|
1669
|
+
|| tree.artifactRoot !== `providers/${providerId}/trees/${tree.name}`
|
|
1670
|
+
|| typeof tree.destination !== 'string'
|
|
1671
|
+
|| !tree.destination
|
|
1672
|
+
|| tree.destination.includes('\\')
|
|
1673
|
+
|| tree.destination.includes('\0')
|
|
1674
|
+
|| isAbsolute(tree.destination)
|
|
1675
|
+
|| tree.destination.split('/').some((segment) => !segment || segment === '.' || segment === '..')
|
|
1676
|
+
) {
|
|
1677
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${label} record is open-shaped or invalid`)
|
|
1678
|
+
continue
|
|
1679
|
+
}
|
|
1680
|
+
const materializer = resolveSurfaceMaterializer('treeViews', tree.materializerId, `${label}.materializerId`)
|
|
1681
|
+
if (
|
|
1682
|
+
materializer?.engine !== 'closed-tree-copy-v1'
|
|
1683
|
+
|| tree.transformPolicy !== 'byte-exact'
|
|
1684
|
+
|| !materializer?.transformPolicies?.includes(tree.transformPolicy)
|
|
1685
|
+
) {
|
|
1686
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${label} has an unsupported deterministic materializer`)
|
|
1687
|
+
continue
|
|
1688
|
+
}
|
|
1689
|
+
const source = manifestPath(forkRoot, tree.artifactRoot, `${label} artifact`)
|
|
1690
|
+
const destination = manifestPath(repo, tree.destination, `${label} destination`)
|
|
1691
|
+
if (!source || !destination) continue
|
|
1692
|
+
generatedProviderDestinations.add(resolve(destination))
|
|
1693
|
+
registerProviderPath(destination, 'tree')
|
|
1694
|
+
const lockedFiles = (corpusLock?.entries || [])
|
|
1695
|
+
.map((entry) => entry?.file)
|
|
1696
|
+
.filter((file) => typeof file === 'string' && file.startsWith(`${tree.artifactRoot}/`))
|
|
1697
|
+
.sort()
|
|
1698
|
+
const actualFiles = []
|
|
1699
|
+
const visitArtifact = (directory) => {
|
|
1700
|
+
if (!governancePathIsSafe(directory) || !pathEntryExists(directory)) return
|
|
1701
|
+
for (const name of readdirSync(directory).sort()) {
|
|
1702
|
+
const absolute = join(directory, name)
|
|
1703
|
+
const info = lstatSync(absolute)
|
|
1704
|
+
if (info.isDirectory() && !info.isSymbolicLink()) visitArtifact(absolute)
|
|
1705
|
+
else if (info.isFile() && !info.isSymbolicLink() && info.nlink === 1) actualFiles.push(relative(forkRoot, absolute).replaceAll('\\', '/'))
|
|
1706
|
+
else add('GOV-ADAPTER-005', 'BLOCKER', `${label} artifact contains an unsafe filesystem entry`)
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
if (!pathEntryExists(source) || !lstatSync(source).isDirectory() || lstatSync(source).isSymbolicLink()) {
|
|
1710
|
+
add('GOV-ADAPTER-005', 'BLOCKER', `${label} artifact is missing or not a real directory`)
|
|
1711
|
+
} else visitArtifact(source)
|
|
1712
|
+
if (!actualFiles.length || JSON.stringify(actualFiles) !== JSON.stringify(lockedFiles)) {
|
|
1713
|
+
add('GOV-CORPUS-003', 'BLOCKER', `${label} artifact does not exactly match its corpus-lock inventory`)
|
|
1714
|
+
}
|
|
1715
|
+
assertTreeEqual('GOV-ADAPTER-005', label, destination, source)
|
|
1716
|
+
validTrees.push(tree)
|
|
1717
|
+
}
|
|
1718
|
+
if (
|
|
1719
|
+
JSON.stringify(surface.trees) !== JSON.stringify([...surface.trees].sort((left, right) => compareUtf8Bytes(left?.name, right?.name)))
|
|
1720
|
+
|| new Set(surface.trees.map((tree) => tree?.name)).size !== surface.trees.length
|
|
1721
|
+
|| new Set(surface.trees.map((tree) => tree?.destination)).size !== surface.trees.length
|
|
1722
|
+
) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} product managed trees must be sorted and unique by name/destination`)
|
|
1723
|
+
const expectedManagedTrees = [surface.skillDestination, ...validTrees.map((tree) => tree.destination)].sort()
|
|
1724
|
+
const actualManagedTrees = ownedManagedSurfaces.filter((managed) => managed.kind === 'tree').map((managed) => managed.path).sort()
|
|
1725
|
+
if (JSON.stringify(actualManagedTrees) !== JSON.stringify(expectedManagedTrees)) {
|
|
1726
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} managed tree ownership does not exactly match skill plus product-tree artifacts`)
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
if (manifest?.codex != null) {
|
|
1730
|
+
const compatibility = manifest.codex
|
|
1731
|
+
const matches = generatedSurfaces.filter(([, surface]) => (
|
|
1732
|
+
surface.instructionArtifact === compatibility.agentsMd
|
|
1733
|
+
&& surface.instructionDestination === compatibility.instructionDestination
|
|
1734
|
+
&& surface.hookDestination === compatibility.hookDestination
|
|
1735
|
+
&& surface.skillDestination === compatibility.skillDestination
|
|
1736
|
+
&& JSON.stringify(surface.skills) === JSON.stringify(compatibility.agentsSkills)
|
|
1737
|
+
))
|
|
1738
|
+
if (matches.length !== 1) {
|
|
1739
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'legacy Codex compatibility metadata does not bind exactly one active provider surface')
|
|
1740
|
+
} else {
|
|
1741
|
+
const [, sourceSurface] = matches[0]
|
|
1742
|
+
const compatibilityHook = compatibility.hookArtifact
|
|
1743
|
+
? manifestPath(forkRoot, compatibility.hookArtifact, 'legacy Codex hook alias')
|
|
1744
|
+
: null
|
|
1745
|
+
const sourceHook = sourceSurface.hookArtifact
|
|
1746
|
+
? manifestPath(forkRoot, sourceSurface.hookArtifact, 'legacy Codex hook source')
|
|
1747
|
+
: null
|
|
1748
|
+
if ((compatibilityHook || sourceHook) && (
|
|
1749
|
+
!compatibilityHook
|
|
1750
|
+
|| !sourceHook
|
|
1751
|
+
|| !isRegularFile(compatibilityHook)
|
|
1752
|
+
|| !isRegularFile(sourceHook)
|
|
1753
|
+
|| fileSha(compatibilityHook) !== fileSha(sourceHook)
|
|
1754
|
+
)) add('GOV-CORPUS-003', 'BLOCKER', 'legacy Codex hook alias differs from its active provider artifact')
|
|
1755
|
+
for (const name of compatibility.agentsSkills) {
|
|
1756
|
+
const alias = manifestPath(forkRoot, `${compatibility.skillArtifactRoot}/${name}`, `legacy Codex skill alias ${name}`)
|
|
1757
|
+
const source = manifestPath(forkRoot, `${sourceSurface.skillArtifactRoot}/${name}`, `legacy Codex skill source ${name}`)
|
|
1758
|
+
if (alias && source) assertTreeEqual('GOV-CORPUS-003', `legacy Codex skill alias ${name}`, alias, source)
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
const surfaceProviderIds = Object.keys(providerSurfaces || {}).sort()
|
|
1763
|
+
const bomProviderIds = Object.keys(bom?.providers || {}).sort()
|
|
1764
|
+
if (JSON.stringify(surfaceProviderIds) !== JSON.stringify(bomProviderIds)) {
|
|
1765
|
+
add('GOV-PROVIDER-002', 'BLOCKER', 'providerSurfaces inventory does not exactly match the immutable BOM provider inventory')
|
|
1766
|
+
}
|
|
1767
|
+
if (JSON.stringify(surfaceProviderIds) !== JSON.stringify(Object.keys(providerAdapters || {}).sort())) {
|
|
1768
|
+
add('GOV-PROVIDER-002', 'BLOCKER', 'providerSurfaces inventory does not exactly match the provider adapter inventory')
|
|
1769
|
+
}
|
|
1770
|
+
const providerArtifactDigests = bom?.payload?.providerArtifactDigests
|
|
1771
|
+
if (
|
|
1772
|
+
!providerArtifactDigests
|
|
1773
|
+
|| typeof providerArtifactDigests !== 'object'
|
|
1774
|
+
|| Array.isArray(providerArtifactDigests)
|
|
1775
|
+
|| JSON.stringify(Object.keys(providerArtifactDigests).sort()) !== JSON.stringify(surfaceProviderIds)
|
|
1776
|
+
) add('GOV-BOM-004', 'BLOCKER', 'provider artifact digest map does not exactly match the signed provider inventory')
|
|
1777
|
+
const lockedTreeDigest = (prefix) => sha256((corpusLock?.entries || [])
|
|
1778
|
+
.filter((entry) => typeof entry?.file === 'string' && entry.file.startsWith(`${prefix}/`))
|
|
1779
|
+
.map((entry) => `${entry.file}:${entry.sha256}`)
|
|
1780
|
+
.join('\n') + '\n')
|
|
1781
|
+
for (const [providerId, surface] of Object.entries(providerSurfaces || {})) {
|
|
1782
|
+
const binding = providerArtifactDigests?.[providerId]
|
|
1783
|
+
if (
|
|
1784
|
+
!closedKeys(binding, ['hookSha256', 'instructionSha256', 'skillsSha256', 'treeDigests'])
|
|
1785
|
+
|| !closedKeys(binding?.treeDigests, (surface.trees || []).map((tree) => tree.name))
|
|
1786
|
+
) {
|
|
1787
|
+
add('GOV-BOM-004', 'BLOCKER', `${providerId} provider artifact digest binding is open-shaped or missing`)
|
|
1788
|
+
continue
|
|
1789
|
+
}
|
|
1790
|
+
const expected = {
|
|
1791
|
+
instructionSha256: surface.instructionArtifact && isRegularFile(join(forkRoot, surface.instructionArtifact))
|
|
1792
|
+
? fileSha(join(forkRoot, surface.instructionArtifact))
|
|
1793
|
+
: null,
|
|
1794
|
+
hookSha256: surface.hookArtifact && isRegularFile(join(forkRoot, surface.hookArtifact))
|
|
1795
|
+
? fileSha(join(forkRoot, surface.hookArtifact))
|
|
1796
|
+
: null,
|
|
1797
|
+
skillsSha256: surface.skillArtifactRoot ? lockedTreeDigest(surface.skillArtifactRoot) : null,
|
|
1798
|
+
treeDigests: Object.fromEntries((surface.trees || []).map((tree) => [tree.name, lockedTreeDigest(tree.artifactRoot)])),
|
|
1799
|
+
}
|
|
1800
|
+
if (JSON.stringify(binding) !== JSON.stringify(expected)) {
|
|
1801
|
+
add('GOV-BOM-004', 'BLOCKER', `${providerId} provider artifact digests differ from the installed locked corpus`)
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
const retiredProviderIds = []
|
|
1805
|
+
const retiredSurfacePaths = []
|
|
1806
|
+
const activeOwnedSurfaces = [...managedSurfacesByProvider].flatMap(([providerId, surfaces]) => (
|
|
1807
|
+
surfaces.map((surface) => ({ ...surface, providerId }))
|
|
1808
|
+
))
|
|
1809
|
+
for (const retired of providerLifecycle?.retiredProviders || []) {
|
|
1810
|
+
const discovery = retired?.discovery
|
|
1811
|
+
const surfaces = retired?.surfaces
|
|
1812
|
+
const transfers = retired?.transfers || []
|
|
1813
|
+
if (
|
|
1814
|
+
!closedKeys(retired, [
|
|
1815
|
+
'id', 'replacementProviderId', 'retiredInVersion', 'reasonCode', 'discovery', 'surfaces',
|
|
1816
|
+
'priorReleaseVersion', 'priorSnapshotSha256', 'priorProviderSha256',
|
|
1817
|
+
...(Object.hasOwn(retired || {}, 'transfers') ? ['transfers'] : []),
|
|
1818
|
+
])
|
|
1819
|
+
|| !/^[a-z][a-z0-9-]*$/.test(retired.id || '')
|
|
1820
|
+
|| !(retired.replacementProviderId === null || /^[a-z][a-z0-9-]*$/.test(retired.replacementProviderId || ''))
|
|
1821
|
+
|| !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$/.test(retired.retiredInVersion || '')
|
|
1822
|
+
|| !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$/.test(retired.priorReleaseVersion || '')
|
|
1823
|
+
|| !/^[a-f0-9]{64}$/.test(retired.priorSnapshotSha256 || '')
|
|
1824
|
+
|| !/^[a-f0-9]{64}$/.test(retired.priorProviderSha256 || '')
|
|
1825
|
+
|| (compareLifecycleSemver(retired.retiredInVersion, retired.priorReleaseVersion) ?? 0) <= 0
|
|
1826
|
+
|| (compareLifecycleSemver(retired.retiredInVersion, providerLifecycle?.releaseVersion) ?? 1) > 0
|
|
1827
|
+
|| !/^[A-Z][A-Z0-9_]*$/.test(retired.reasonCode || '')
|
|
1828
|
+
|| !closedKeys(discovery, discoveryArrayKeys)
|
|
1829
|
+
|| !Array.isArray(surfaces)
|
|
1830
|
+
|| (Object.hasOwn(retired || {}, 'transfers') && (!Array.isArray(transfers) || !transfers.length))
|
|
1831
|
+
|| (!surfaces?.length && !transfers.length)
|
|
1832
|
+
) {
|
|
1833
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `retired provider record is open-shaped or invalid:${retired?.id || '<unknown>'}`)
|
|
1834
|
+
continue
|
|
1835
|
+
}
|
|
1836
|
+
retiredProviderIds.push(retired.id)
|
|
1837
|
+
const priorSurfaces = [
|
|
1838
|
+
...retired.surfaces.map((surface) => ({ path: surface?.path, kind: surface?.kind })),
|
|
1839
|
+
...transfers.map((surface) => ({ path: surface?.path, kind: surface?.kind })),
|
|
1840
|
+
].sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
1841
|
+
if (sha256(JSON.stringify({
|
|
1842
|
+
id: retired.id,
|
|
1843
|
+
discovery: retired.discovery,
|
|
1844
|
+
surfaces: priorSurfaces,
|
|
1845
|
+
})) !== retired.priorProviderSha256) {
|
|
1846
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} retirement proof does not bind its previous managed provider inventory`)
|
|
1847
|
+
}
|
|
1848
|
+
if (surfaceProviderIds.includes(retired.id)) add('GOV-LIFECYCLE-001', 'BLOCKER', `retired provider remains active:${retired.id}`)
|
|
1849
|
+
const replacementSurfaces = retired.replacementProviderId === null
|
|
1850
|
+
? null
|
|
1851
|
+
: managedSurfacesByProvider.get(retired.replacementProviderId)
|
|
1852
|
+
if (retired.replacementProviderId !== null && (!replacementSurfaces || retired.replacementProviderId === retired.id)) {
|
|
1853
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `replacementProviderId must name a distinct active current provider:${retired.id}:${retired.replacementProviderId}`)
|
|
1854
|
+
}
|
|
1855
|
+
if (retired.replacementProviderId === null && transfers.length) {
|
|
1856
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} null replacementProviderId cannot transfer surfaces`)
|
|
1857
|
+
}
|
|
1858
|
+
for (const key of discoveryArrayKeys) {
|
|
1859
|
+
const values = discovery[key]
|
|
1860
|
+
if (!Array.isArray(values) || JSON.stringify(values) !== JSON.stringify([...new Set(values || [])].sort())) {
|
|
1861
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} retired discovery ${key} must be sorted and unique`)
|
|
1862
|
+
continue
|
|
1863
|
+
}
|
|
1864
|
+
if (values.some((value) => !discoveryInventory[key].includes(value))) {
|
|
1865
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} retired discovery ${key} is not reserved by the signed discovery policy`)
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
const canonicalSurfaces = [...surfaces].sort((left, right) => compareUtf8Bytes(left?.path, right?.path))
|
|
1869
|
+
if (JSON.stringify(surfaces) !== JSON.stringify(canonicalSurfaces)) {
|
|
1870
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} retired surfaces must be sorted by path`)
|
|
1871
|
+
}
|
|
1872
|
+
const canonicalTransfers = [...transfers].sort((left, right) => compareUtf8Bytes(left?.path, right?.path))
|
|
1873
|
+
if (JSON.stringify(transfers) !== JSON.stringify(canonicalTransfers)) {
|
|
1874
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} transferred surfaces must be sorted by path`)
|
|
1875
|
+
}
|
|
1876
|
+
const partitionPaths = [...surfaces, ...transfers].map((surface) => surface?.path)
|
|
1877
|
+
if (partitionPaths.length !== new Set(partitionPaths).size) {
|
|
1878
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} deletion/transfer partition contains a duplicate surface path`)
|
|
1879
|
+
}
|
|
1880
|
+
for (const surface of surfaces) {
|
|
1881
|
+
if (!closedKeys(surface, ['path', 'kind', 'sha256']) || !['file', 'tree'].includes(surface.kind) || !/^[a-f0-9]{64}$/.test(surface.sha256 || '')) {
|
|
1882
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} retired surface is open-shaped or invalid`)
|
|
1883
|
+
continue
|
|
1884
|
+
}
|
|
1885
|
+
const destination = manifestPath(repo, surface.path, `${retired.id} retired surface`)
|
|
1886
|
+
if (!destination) continue
|
|
1887
|
+
if (['README.md', 'CONTRIBUTING.md', 'SECURITY.md', 'CODE_OF_CONDUCT.md', 'CHANGELOG.md', 'LICENSE.md'].includes(surface.path)) {
|
|
1888
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} tombstone targets a protected product document:${surface.path}`)
|
|
1889
|
+
}
|
|
1890
|
+
if (commonInstruction?.destination && pathsOverlap(surface.path, commonInstruction.destination)) {
|
|
1891
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} provider tombstone targets the common instruction authority:${surface.path}`)
|
|
1892
|
+
}
|
|
1893
|
+
retiredSurfacePaths.push(surface.path)
|
|
1894
|
+
if (activeOwnedSurfaces.some((candidate) => pathsOverlap(candidate.path, surface.path))) {
|
|
1895
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} deletion tombstone remains owned by an active provider:${surface.path}`)
|
|
1896
|
+
}
|
|
1897
|
+
if (pathEntryExists(destination)) add('GOV-LIFECYCLE-001', 'BLOCKER', `retired provider surface has not converged to absent:${surface.path}`)
|
|
1898
|
+
}
|
|
1899
|
+
for (const surface of transfers) {
|
|
1900
|
+
if (!closedKeys(surface, ['path', 'kind']) || !['file', 'tree'].includes(surface.kind)) {
|
|
1901
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} transferred surface is open-shaped or invalid`)
|
|
1902
|
+
continue
|
|
1903
|
+
}
|
|
1904
|
+
const destination = manifestPath(repo, surface.path, `${retired.id} transferred surface`)
|
|
1905
|
+
if (!destination) continue
|
|
1906
|
+
if (['README.md', 'CONTRIBUTING.md', 'SECURITY.md', 'CODE_OF_CONDUCT.md', 'CHANGELOG.md', 'LICENSE.md'].includes(surface.path)) {
|
|
1907
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} transfer targets a protected product document:${surface.path}`)
|
|
1908
|
+
}
|
|
1909
|
+
if (!replacementSurfaces?.some((candidate) => candidate.path === surface.path && candidate.kind === surface.kind)) {
|
|
1910
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} transfer is not exactly owned by replacement ${retired.replacementProviderId}:${surface.path}`)
|
|
1911
|
+
}
|
|
1912
|
+
if (activeOwnedSurfaces.some((candidate) => candidate.providerId !== retired.replacementProviderId && pathsOverlap(candidate.path, surface.path))) {
|
|
1913
|
+
add('GOV-LIFECYCLE-001', 'BLOCKER', `${retired.id} transfer is also owned by a non-replacement provider:${surface.path}`)
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (
|
|
1918
|
+
JSON.stringify(retiredProviderIds) !== JSON.stringify([...new Set(retiredProviderIds)].sort())
|
|
1919
|
+
|| retiredSurfacePaths.length !== new Set(retiredSurfacePaths).size
|
|
1920
|
+
) add('GOV-LIFECYCLE-001', 'BLOCKER', 'retired provider ids and tombstone paths must be globally unique and sorted')
|
|
1921
|
+
for (const [providerId, surface] of Object.entries(providerSurfaces || {})) {
|
|
1922
|
+
const declaration = bom?.providers?.[providerId]
|
|
1923
|
+
if (
|
|
1924
|
+
!closedKeys(declaration, ['schemaVersion', 'generated', 'instruction', 'hooks', 'skills', 'capabilities', 'exclusions'])
|
|
1925
|
+
|| declaration.schemaVersion !== 1
|
|
1926
|
+
|| !closedKeys(declaration.capabilities, ['nativeHooks', 'hookEnforcement', 'hardGate'])
|
|
1927
|
+
) {
|
|
1928
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} immutable BOM provider record is open-shaped or unknown-versioned`)
|
|
1929
|
+
continue
|
|
1930
|
+
}
|
|
1931
|
+
if (declaration.generated !== Boolean(surface?.generated)) {
|
|
1932
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} generated/disabled state differs from the immutable BOM`)
|
|
1933
|
+
continue
|
|
1934
|
+
}
|
|
1935
|
+
if (JSON.stringify(declaration.capabilities) !== JSON.stringify(surface.capabilities)) {
|
|
1936
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} provider capability differs from the immutable BOM`)
|
|
1937
|
+
}
|
|
1938
|
+
const instructionName = typeof declaration.instruction === 'string' ? declaration.instruction.replaceAll('\\', '/').split('/').at(-1) : null
|
|
1939
|
+
if (!instructionName || !discoveryInventory.instructionNames.includes(instructionName)) {
|
|
1940
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} instruction is not reserved by the signed discovery policy`)
|
|
1941
|
+
}
|
|
1942
|
+
if (typeof declaration.hooks === 'string' && !discoveryInventory.configPaths.includes(declaration.hooks)) {
|
|
1943
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} hook config is not reserved by the signed discovery policy`)
|
|
1944
|
+
}
|
|
1945
|
+
for (const [kind, value] of [['instruction', declaration.instruction], ['hooks', declaration.hooks], ['skills', declaration.skills]]) {
|
|
1946
|
+
if (typeof value !== 'string') continue
|
|
1947
|
+
const root = value.replaceAll('\\', '/').split('/')[0]
|
|
1948
|
+
if (root.startsWith('.') && !discoveryInventory.providerRootNames.includes(root)) {
|
|
1949
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} ${kind} root ${root} is not reserved by the signed discovery policy`)
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (surface.generated) {
|
|
1953
|
+
if (
|
|
1954
|
+
declaration.instruction !== surface.instructionDestination
|
|
1955
|
+
|| declaration.hooks !== surface.hookDestination
|
|
1956
|
+
|| declaration.skills !== surface.skillDestination
|
|
1957
|
+
) add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} destination binding differs from the immutable BOM`)
|
|
1958
|
+
continue
|
|
1959
|
+
}
|
|
1960
|
+
if (JSON.stringify(declaration.exclusions || []) !== JSON.stringify(surface.exclusions || [])) {
|
|
1961
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `${providerId} disabled-provider exclusions differ from the immutable BOM`)
|
|
1962
|
+
}
|
|
1963
|
+
for (const [kind, value] of [['instruction', declaration.instruction], ['hook', declaration.hooks], ['skill', declaration.skills]]) {
|
|
1964
|
+
if (typeof value !== 'string' || !value) continue
|
|
1965
|
+
const destination = manifestPath(repo, value, `${providerId} disabled ${kind} destination`)
|
|
1966
|
+
if (!destination || generatedProviderDestinations.has(resolve(destination))) continue
|
|
1967
|
+
registerProviderRootOnly(destination)
|
|
1968
|
+
if (pathEntryExists(destination)) {
|
|
1969
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `${providerId} is disabled but its undeclared ${kind} surface exists at ${value}`)
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
if (!generatedSurfaces.length) add('GOV-PROVIDER-001', 'BLOCKER', 'providerSurfaces has no generated provider adapter')
|
|
1974
|
+
|
|
1975
|
+
const rawManagedLaunchers = manifest?.launchers || manifest?.codex?.hooksJson || []
|
|
1976
|
+
const managedLaunchers = Array.isArray(rawManagedLaunchers)
|
|
1977
|
+
? rawManagedLaunchers.filter((name) => typeof name === 'string' && /^[A-Za-z0-9._-]+\.(?:sh|mjs|py)$/.test(name))
|
|
1978
|
+
: []
|
|
1979
|
+
if (
|
|
1980
|
+
JSON.stringify(rawManagedLaunchers) !== JSON.stringify([...new Set(managedLaunchers)].sort())
|
|
1981
|
+
|| managedLaunchers.length === 0
|
|
1982
|
+
) add('GOV-CORPUS-001', 'BLOCKER', 'installed manifest launcher inventory must be non-empty, safe, unique, and sorted')
|
|
1983
|
+
// Claude commands and subagent definitions are package-owned discovery leaves too. Keep the
|
|
1984
|
+
// directory closed even while today's classified sets are empty: a later release may add an exact
|
|
1985
|
+
// file without requiring the checker to open the entire directory to consumer-owned content.
|
|
1986
|
+
const materialization = manifest?.consumer?.materialization
|
|
1987
|
+
const materializationExactPaths = materialization?.exactPaths
|
|
1988
|
+
const materializationPathPrefixes = materialization?.pathPrefixes
|
|
1989
|
+
if (
|
|
1990
|
+
!closedKeys(materialization, ['exactPaths', 'pathPrefixes'])
|
|
1991
|
+
|| !Array.isArray(materializationExactPaths)
|
|
1992
|
+
|| !Array.isArray(materializationPathPrefixes)
|
|
1993
|
+
) add('GOV-CORPUS-001', 'BLOCKER', 'installed manifest has no closed consumer materialization inventory')
|
|
1994
|
+
const canonicalMaterializationPath = (value, prefix) => {
|
|
1995
|
+
if (
|
|
1996
|
+
typeof value !== 'string'
|
|
1997
|
+
|| !value
|
|
1998
|
+
|| value.includes('\\')
|
|
1999
|
+
|| value.includes('\0')
|
|
2000
|
+
|| isAbsolute(value)
|
|
2001
|
+
|| (prefix ? !value.endsWith('/') : value.endsWith('/'))
|
|
2002
|
+
) return null
|
|
2003
|
+
const path = prefix ? value.slice(0, -1) : value
|
|
2004
|
+
const segments = path.split('/')
|
|
2005
|
+
if (segments.some((segment) => !segment || segment === '.' || segment === '..') || segments[0] === '.git') return null
|
|
2006
|
+
return value
|
|
2007
|
+
}
|
|
2008
|
+
const materializationInventory = (values, prefix, label) => {
|
|
2009
|
+
const valid = (values || []).map((value) => canonicalMaterializationPath(value, prefix)).filter(Boolean)
|
|
2010
|
+
const canonical = [...new Set(valid)].sort()
|
|
2011
|
+
if (JSON.stringify(values || []) !== JSON.stringify(canonical)) {
|
|
2012
|
+
add('GOV-CORPUS-001', 'BLOCKER', `consumer materialization ${label} must be safe, unique, and sorted`)
|
|
2013
|
+
}
|
|
2014
|
+
return canonical
|
|
2015
|
+
}
|
|
2016
|
+
const exactMaterializationPaths = materializationInventory(materializationExactPaths, false, 'exactPaths')
|
|
2017
|
+
const prefixMaterializationPaths = materializationInventory(materializationPathPrefixes, true, 'pathPrefixes')
|
|
2018
|
+
const exactMaterializationSet = new Set(exactMaterializationPaths)
|
|
2019
|
+
const expectedProviderPrefixes = generatedSurfaces.flatMap(([, surface]) => [
|
|
2020
|
+
...(surface.skills || []).map((name) => `${surface.skillDestination}/${name}/`),
|
|
2021
|
+
...(surface.trees || []).map((tree) => `${tree.destination}/`),
|
|
2022
|
+
]).sort()
|
|
2023
|
+
if (JSON.stringify(prefixMaterializationPaths) !== JSON.stringify(expectedProviderPrefixes)) {
|
|
2024
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'consumer path-prefix materialization inventory does not exactly match provider skill and product-tree surfaces')
|
|
2025
|
+
}
|
|
2026
|
+
if (
|
|
2027
|
+
!commonInstruction?.destination
|
|
2028
|
+
|| exactMaterializationPaths.filter((path) => path === commonInstruction.destination).length !== 1
|
|
2029
|
+
|| !(corpusLock?.entries || []).some((entry) => entry?.file === commonInstruction.artifact)
|
|
2030
|
+
) add('GOV-CORPUS-001', 'BLOCKER', 'common instruction must have exactly one locked artifact and one consumer destination')
|
|
2031
|
+
const materializationAllows = (relativePath, directory) => {
|
|
2032
|
+
if (exactMaterializationSet.has(relativePath) || prefixMaterializationPaths.some((prefix) => relativePath.startsWith(prefix))) return true
|
|
2033
|
+
if (!directory) return false
|
|
2034
|
+
const descendantPrefix = `${relativePath}/`
|
|
2035
|
+
return exactMaterializationPaths.some((path) => path.startsWith(descendantPrefix))
|
|
2036
|
+
|| prefixMaterializationPaths.some((path) => path.startsWith(descendantPrefix))
|
|
2037
|
+
}
|
|
2038
|
+
const compatibilityCategories = manifest?.consumer?.compatibilityCategories
|
|
2039
|
+
const compatibilityFileSurfacesByProvider = new Map()
|
|
2040
|
+
if (!Array.isArray(compatibilityCategories)) {
|
|
2041
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed compatibility category inventory must be an array')
|
|
2042
|
+
} else {
|
|
2043
|
+
const categoryNames = compatibilityCategories.map((category) => category?.name)
|
|
2044
|
+
if (JSON.stringify(categoryNames) !== JSON.stringify([...new Set(categoryNames)].sort())) {
|
|
2045
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed compatibility categories must be sorted and unique by name')
|
|
2046
|
+
}
|
|
2047
|
+
for (const category of compatibilityCategories) {
|
|
2048
|
+
if (
|
|
2049
|
+
!closedKeys(category, ['artifactRoot', 'destinationRoot', 'entries', 'name', 'providerId', 'schemaVersion'])
|
|
2050
|
+
|| category.schemaVersion !== 1
|
|
2051
|
+
|| !['agents', 'commands'].includes(category.name)
|
|
2052
|
+
|| !/^[a-z][a-z0-9-]*$/.test(category.providerId || '')
|
|
2053
|
+
|| !Array.isArray(category.entries)
|
|
2054
|
+
) {
|
|
2055
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'installed compatibility category record is open-shaped or invalid')
|
|
2056
|
+
continue
|
|
2057
|
+
}
|
|
2058
|
+
const artifactRoot = canonicalMaterializationPath(category.artifactRoot, false)
|
|
2059
|
+
const destinationRoot = canonicalMaterializationPath(category.destinationRoot, false)
|
|
2060
|
+
const entries = category.entries.filter((name) => /^[A-Za-z0-9][A-Za-z0-9_.@-]*\.md$/.test(name))
|
|
2061
|
+
if (
|
|
2062
|
+
!artifactRoot
|
|
2063
|
+
|| !destinationRoot
|
|
2064
|
+
|| JSON.stringify(category.entries) !== JSON.stringify([...new Set(entries)].sort())
|
|
2065
|
+
|| JSON.stringify(manifest?.[category.name]) !== JSON.stringify(category.entries)
|
|
2066
|
+
) {
|
|
2067
|
+
add('GOV-CORPUS-001', 'BLOCKER', `compatibility category ${category.name} has an unsafe or divergent inventory`)
|
|
2068
|
+
continue
|
|
2069
|
+
}
|
|
2070
|
+
const ownerSurfaces = managedSurfacesByProvider.get(category.providerId)
|
|
2071
|
+
if (!ownerSurfaces) add('GOV-PROVIDER-002', 'BLOCKER', `compatibility category ${category.name} has no active provider owner`)
|
|
2072
|
+
for (const name of entries) {
|
|
2073
|
+
const artifactPath = `${artifactRoot}/${name}`
|
|
2074
|
+
const destinationPath = `${destinationRoot}/${name}`
|
|
2075
|
+
const providerFiles = compatibilityFileSurfacesByProvider.get(category.providerId) || []
|
|
2076
|
+
providerFiles.push({ path: destinationPath, kind: 'file' })
|
|
2077
|
+
compatibilityFileSurfacesByProvider.set(category.providerId, providerFiles)
|
|
2078
|
+
if (!(corpusLock?.entries || []).some((entry) => entry?.file === artifactPath)) {
|
|
2079
|
+
add('GOV-CORPUS-003', 'BLOCKER', `compatibility category artifact is absent from corpus lock:${artifactPath}`)
|
|
2080
|
+
}
|
|
2081
|
+
if (!exactMaterializationSet.has(destinationPath)) {
|
|
2082
|
+
add('GOV-CORPUS-001', 'BLOCKER', `compatibility category destination is absent from exact materialization:${destinationPath}`)
|
|
2083
|
+
}
|
|
2084
|
+
if (!ownerSurfaces?.some((surface) => surface.path === destinationPath && surface.kind === 'file')) {
|
|
2085
|
+
add('GOV-PROVIDER-002', 'BLOCKER', `compatibility category destination is absent from provider lifecycle ownership:${destinationPath}`)
|
|
2086
|
+
}
|
|
2087
|
+
const artifact = manifestPath(forkRoot, artifactPath, `compatibility category ${category.name} artifact ${name}`)
|
|
2088
|
+
const destination = manifestPath(repo, destinationPath, `compatibility category ${category.name} destination ${name}`)
|
|
2089
|
+
if (!artifact || !destination) continue
|
|
2090
|
+
generatedProviderDestinations.add(resolve(destination))
|
|
2091
|
+
registerProviderPath(destination, 'file')
|
|
2092
|
+
assertTreeEqual('GOV-ADAPTER-005', `compatibility category ${category.name} ${name}`, destination, artifact)
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
for (const name of ['agents', 'commands']) if (
|
|
2096
|
+
!compatibilityCategories.some((category) => category.name === name)
|
|
2097
|
+
&& (manifest?.[name] || []).length
|
|
2098
|
+
) add('GOV-CORPUS-001', 'BLOCKER', `legacy ${name} inventory has no active compatibility category owner`)
|
|
2099
|
+
}
|
|
2100
|
+
const activeLifecycleProviderIds = generatedSurfaces.map(([providerId]) => providerId).sort()
|
|
2101
|
+
const currentSnapshotProviderIds = currentSnapshot?.providers?.map((provider) => provider.id) || []
|
|
2102
|
+
if (currentSnapshot && JSON.stringify(currentSnapshotProviderIds) !== JSON.stringify(activeLifecycleProviderIds)) {
|
|
2103
|
+
lifecycleProblem('current provider snapshot ids do not exactly match active generated provider surfaces')
|
|
2104
|
+
}
|
|
2105
|
+
const currentSnapshotProvidersById = new Map((currentSnapshot?.providers || []).map((provider) => [provider.id, provider]))
|
|
2106
|
+
for (const [providerId, surface] of generatedSurfaces) {
|
|
2107
|
+
const currentProvider = currentSnapshotProvidersById.get(providerId)
|
|
2108
|
+
const actualManagedSurfaces = managedSurfacesByProvider.get(providerId) || []
|
|
2109
|
+
if (!currentProvider || JSON.stringify(currentProvider.surfaces) !== JSON.stringify(actualManagedSurfaces)) {
|
|
2110
|
+
lifecycleProblem(`${providerId} current snapshot managed surfaces do not exactly match the installed provider surface inventory`)
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const instructionEngine = providerMaterializers?.instructionViews?.[surface.instructionMaterializerId]?.engine
|
|
2114
|
+
const expectedManagedFiles = [
|
|
2115
|
+
...(instructionEngine === 'shared-instruction-v1' ? [] : [{ path: surface.instructionDestination, kind: 'file' }]),
|
|
2116
|
+
...(typeof surface.hookDestination === 'string' ? [{ path: surface.hookDestination, kind: 'file' }] : []),
|
|
2117
|
+
...(compatibilityFileSurfacesByProvider.get(providerId) || []),
|
|
2118
|
+
].sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
2119
|
+
const actualManagedFiles = actualManagedSurfaces
|
|
2120
|
+
.filter((managed) => managed.kind === 'file')
|
|
2121
|
+
.sort((left, right) => compareUtf8Bytes(left.path, right.path))
|
|
2122
|
+
if (JSON.stringify(actualManagedFiles) !== JSON.stringify(expectedManagedFiles)) {
|
|
2123
|
+
add(
|
|
2124
|
+
'GOV-PROVIDER-002',
|
|
2125
|
+
'BLOCKER',
|
|
2126
|
+
`${providerId} managed file ownership must exactly equal its non-shared instruction, hook config, and compatibility-category leaves`,
|
|
2127
|
+
)
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
const mergeSurfaces = generatedSurfaces.filter(([providerId]) => surfaceHookMaterializers.get(providerId)?.mergeStrategy === 'merge-hook-map-into-base-v1')
|
|
2131
|
+
const mergePolicy = readJson(manifestPath(forkRoot, 'launchers/settings-hooks.json', 'provider hook merge policy'), 'GOV-ADAPTER-001')
|
|
2132
|
+
const mergeProviderIds = mergeSurfaces.map(([providerId]) => providerId).sort()
|
|
2133
|
+
const mergePolicyProviderIds = Object.keys(mergePolicy?.surfaces || {}).sort()
|
|
2134
|
+
const mergeDigestProviderIds = Object.keys(bom?.payload?.mergeSurfaceDigests || {}).sort()
|
|
2135
|
+
if (
|
|
2136
|
+
!closedKeys(mergePolicy, ['_generated', 'kind', 'schemaVersion', 'sourceDigest', 'surfaces'])
|
|
2137
|
+
|| mergePolicy?._generated !== 'build-fork-governance.mjs'
|
|
2138
|
+
|| mergePolicy?.kind !== 'provider-hook-merge-policies'
|
|
2139
|
+
|| mergePolicy?.schemaVersion !== 1
|
|
2140
|
+
|| !/^[a-f0-9]{64}$/.test(mergePolicy?.sourceDigest || '')
|
|
2141
|
+
|| JSON.stringify(mergePolicyProviderIds) !== JSON.stringify(mergeProviderIds)
|
|
2142
|
+
|| JSON.stringify(mergeDigestProviderIds) !== JSON.stringify(mergeProviderIds)
|
|
2143
|
+
) add('GOV-ADAPTER-001', 'BLOCKER', 'provider hook merge policy/BOM inventory does not exactly match every merge-capable provider')
|
|
2144
|
+
for (const [providerId, surface] of mergeSurfaces) {
|
|
2145
|
+
const materializer = surfaceHookMaterializers.get(providerId)
|
|
2146
|
+
const baseTemplate = providerMaterializers?.hookBaseTemplateContracts?.[materializer?.baseTemplateContract]
|
|
2147
|
+
const policy = mergePolicy?.surfaces?.[providerId]
|
|
2148
|
+
if (
|
|
2149
|
+
materializer?.engine !== 'json-hook-map-v1'
|
|
2150
|
+
|| !closedKeys(policy, [
|
|
2151
|
+
'baseTemplateContract', 'hookArtifact', 'hookDestination', 'hookMaterializerId', 'mergeStrategy',
|
|
2152
|
+
'permissionPolicy', 'retiredHookRegistrations', 'schemaVersion',
|
|
2153
|
+
])
|
|
2154
|
+
|| policy.schemaVersion !== 1
|
|
2155
|
+
|| policy.hookArtifact !== surface.hookArtifact
|
|
2156
|
+
|| policy.hookDestination !== surface.hookDestination
|
|
2157
|
+
|| policy.hookMaterializerId !== surface.hookMaterializerId
|
|
2158
|
+
|| policy.mergeStrategy !== materializer.mergeStrategy
|
|
2159
|
+
|| policy.baseTemplateContract !== materializer.baseTemplateContract
|
|
2160
|
+
|| !baseTemplate
|
|
2161
|
+
) {
|
|
2162
|
+
add('GOV-ADAPTER-004', 'BLOCKER', `${providerId} template-backed hook materializer is unsupported by the closed merge runtime`)
|
|
2163
|
+
continue
|
|
2164
|
+
}
|
|
2165
|
+
const settings = installedSurfaceHookConfigs.get(providerId)
|
|
2166
|
+
const canonicalConfig = surfaceHookConfigs.get(providerId)
|
|
2167
|
+
const hookArtifact = manifestPath(forkRoot, surface.hookArtifact, `${providerId} canonical hook artifact`)
|
|
2168
|
+
if (!hookArtifact || bom?.payload?.mergeSurfaceDigests?.[providerId] !== fileSha(hookArtifact)) {
|
|
2169
|
+
add('GOV-ADAPTER-003', 'BLOCKER', `${providerId} canonical merge surface digest != immutable governance BOM`)
|
|
2170
|
+
}
|
|
2171
|
+
if (JSON.stringify(settings?.[materializer.hooksField] || {}) !== JSON.stringify(canonicalConfig?.[materializer.hooksField] || {})) {
|
|
2172
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `${providerId} native hook wiring contains an undeclared executable extension; product policy belongs in governance/overlay.md`)
|
|
2173
|
+
}
|
|
2174
|
+
if (!Array.isArray(policy.retiredHookRegistrations)) {
|
|
2175
|
+
add('GOV-ADAPTER-004', 'BLOCKER', `${providerId} retired hook registration policy is invalid`)
|
|
2176
|
+
} else {
|
|
2177
|
+
const ids = []
|
|
2178
|
+
const paths = []
|
|
2179
|
+
for (const record of policy.retiredHookRegistrations) {
|
|
2180
|
+
if (
|
|
2181
|
+
!closedKeys(record, ['id', 'path', 'provider', 'reasonCode'])
|
|
2182
|
+
|| record.provider !== providerId
|
|
2183
|
+
|| !/^[a-z][a-z0-9-]*$/.test(record.id || '')
|
|
2184
|
+
|| !/^[A-Z][A-Z0-9_]*$/.test(record.reasonCode || '')
|
|
2185
|
+
|| !canonicalMaterializationPath(record.path, false)
|
|
2186
|
+
) add('GOV-ADAPTER-004', 'BLOCKER', `${providerId} retired hook registration policy is invalid`)
|
|
2187
|
+
ids.push(record.id)
|
|
2188
|
+
paths.push(record.path)
|
|
2189
|
+
if (JSON.stringify(settings || {}).includes(JSON.stringify(record.path).slice(1, -1))) {
|
|
2190
|
+
add('GOV-ADAPTER-005', 'BLOCKER', `${providerId} settings still reference retired hook registration ${record.id}`)
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
if (JSON.stringify(ids) !== JSON.stringify([...ids].sort()) || new Set(paths).size !== paths.length) {
|
|
2194
|
+
add('GOV-ADAPTER-004', 'BLOCKER', `${providerId} retired hook registrations must be sorted and path-unique`)
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
const specializedBaseFields = baseTemplate.engine === 'claude-permission-policy-v1'
|
|
2198
|
+
? ['permissions']
|
|
2199
|
+
: []
|
|
2200
|
+
for (const [key, value] of Object.entries(canonicalConfig || {})) {
|
|
2201
|
+
if ([materializer.hooksField, ...specializedBaseFields].includes(key)) continue
|
|
2202
|
+
if (JSON.stringify(settings?.[key]) !== JSON.stringify(value)) {
|
|
2203
|
+
add('GOV-ADAPTER-005', 'BLOCKER', `${providerId} base-template field ${key} differs from its canonical projection`)
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
if (baseTemplate.engine === 'claude-permission-policy-v1') {
|
|
2207
|
+
const permissionPolicy = policy.permissionPolicy
|
|
2208
|
+
const canonicalPermissions = canonicalConfig?.permissions
|
|
2209
|
+
const installedPermissions = settings?.permissions
|
|
2210
|
+
if (
|
|
2211
|
+
!closedKeys(permissionPolicy, ['retiredPermissionAllow', 'sha256', 'source'])
|
|
2212
|
+
|| permissionPolicy.source !== baseTemplate.source
|
|
2213
|
+
|| !/^[a-f0-9]{64}$/.test(permissionPolicy.sha256 || '')
|
|
2214
|
+
|| !Array.isArray(permissionPolicy.retiredPermissionAllow)
|
|
2215
|
+
|| !permissionPolicy.retiredPermissionAllow.length
|
|
2216
|
+
|| permissionPolicy.retiredPermissionAllow.some((rule) => typeof rule !== 'string' || !/^Bash\(.+\)$/.test(rule))
|
|
2217
|
+
|| !canonicalPermissions
|
|
2218
|
+
|| !installedPermissions
|
|
2219
|
+
|| Object.hasOwn(canonicalConfig, 'disableAutoMode')
|
|
2220
|
+
|| Object.hasOwn(settings || {}, 'disableAutoMode')
|
|
2221
|
+
|| canonicalPermissions.defaultMode !== installedPermissions.defaultMode
|
|
2222
|
+
|| canonicalPermissions.disableBypassPermissionsMode !== installedPermissions.disableBypassPermissionsMode
|
|
2223
|
+
|| !Array.isArray(installedPermissions.allow)
|
|
2224
|
+
|| !Array.isArray(installedPermissions.ask)
|
|
2225
|
+
|| !Array.isArray(installedPermissions.deny)
|
|
2226
|
+
|| installedPermissions.allow.some((rule) => (
|
|
2227
|
+
typeof rule !== 'string'
|
|
2228
|
+
|| installedPermissions.ask.includes(rule)
|
|
2229
|
+
|| installedPermissions.deny.includes(rule)
|
|
2230
|
+
))
|
|
2231
|
+
|| installedPermissions.ask.some((rule) => typeof rule !== 'string')
|
|
2232
|
+
|| installedPermissions.deny.some((rule) => typeof rule !== 'string')
|
|
2233
|
+
|| (canonicalPermissions.allow || []).some((rule) => !installedPermissions.allow.includes(rule))
|
|
2234
|
+
|| (canonicalPermissions.ask || []).some((rule) => !installedPermissions.ask.includes(rule))
|
|
2235
|
+
|| (canonicalPermissions.deny || []).some((rule) => !installedPermissions.deny.includes(rule))
|
|
2236
|
+
|| permissionPolicy.retiredPermissionAllow.some((rule) => installedPermissions.allow.includes(rule))
|
|
2237
|
+
|| Object.hasOwn(installedPermissions, 'disableAutoMode')
|
|
2238
|
+
|| Object.hasOwn(settings || {}, 'defaultMode')
|
|
2239
|
+
) add('GOV-ADAPTER-005', 'BLOCKER', `${providerId} permission base was not safely materialized from its registered contract`)
|
|
2240
|
+
} else if (policy.permissionPolicy !== null) {
|
|
2241
|
+
add('GOV-ADAPTER-004', 'BLOCKER', `${providerId} non-permission base template claims a permission policy`)
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
const canonicalEventSurfaces = generatedSurfaces
|
|
2246
|
+
.map(([providerId, surface]) => {
|
|
2247
|
+
const config = surfaceHookConfigs.get(providerId)
|
|
2248
|
+
const materializer = surfaceHookMaterializers.get(providerId)
|
|
2249
|
+
const rawNativeEvents = !surface.capabilities.nativeHooks
|
|
2250
|
+
? []
|
|
2251
|
+
: materializer?.engine === 'json-hook-map-v1'
|
|
2252
|
+
? Object.keys(config?.[materializer.hooksField] || {})
|
|
2253
|
+
: materializer?.engine === 'json-hook-event-list-v1'
|
|
2254
|
+
? (config?.[materializer.eventsField] || []).map((record) => record?.[materializer.eventField]).filter((event) => typeof event === 'string')
|
|
2255
|
+
: []
|
|
2256
|
+
const bindings = surfaceNativeEventBindings.get(providerId) || []
|
|
2257
|
+
const canonicalEvents = bindings.map((record) => record.canonicalEvent)
|
|
2258
|
+
return [providerId, canonicalEvents, [...new Set(rawNativeEvents)].sort(), rawNativeEvents.length !== new Set(rawNativeEvents).size, surface.nativeHookCoverage]
|
|
2259
|
+
})
|
|
2260
|
+
const canonicalEventsByProvider = new Map(canonicalEventSurfaces.map(([providerId, events]) => [providerId, events]))
|
|
2261
|
+
const nativeEventsByProvider = new Map(canonicalEventSurfaces.map(([providerId, , nativeEvents]) => [providerId, nativeEvents]))
|
|
2262
|
+
const productEventBaseline = canonicalEventSurfaces[0]?.[4]?.canonicalEvents || []
|
|
2263
|
+
for (const [providerId, events, nativeEvents, duplicateNativeEvents, coverage] of canonicalEventSurfaces) {
|
|
2264
|
+
if (JSON.stringify(coverage?.canonicalEvents || []) !== JSON.stringify(productEventBaseline)) {
|
|
2265
|
+
add('GOV-PROVIDER-001', 'BLOCKER', `${providerId} product hook event authority differs from the signed provider baseline`)
|
|
2266
|
+
}
|
|
2267
|
+
if (JSON.stringify(events) !== JSON.stringify(coverage?.projectedEvents || [])) {
|
|
2268
|
+
add('GOV-PROVIDER-001', 'BLOCKER', `${providerId} projected native hook events differ from its machine-readable coverage record`)
|
|
2269
|
+
}
|
|
2270
|
+
if (duplicateNativeEvents || JSON.stringify(nativeEvents) !== JSON.stringify((coverage?.projectedNativeEvents || []).map((record) => record.nativeEvent).sort())) {
|
|
2271
|
+
add('GOV-PROVIDER-001', 'BLOCKER', `${providerId} native hook configuration event identifiers differ from its canonical-to-native bindings`)
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
const launcherDestination = manifest?.consumer?.launcherDestination
|
|
2276
|
+
const launcherRootName = typeof launcherDestination === 'string' ? launcherDestination.replaceAll('\\', '/').split('/')[0] : null
|
|
2277
|
+
if (
|
|
2278
|
+
typeof launcherDestination !== 'string'
|
|
2279
|
+
|| !launcherDestination
|
|
2280
|
+
|| discoveryInventory.providerRootNames.includes(launcherRootName)
|
|
2281
|
+
|| discoveryInventory.pluginRootNames.includes(launcherRootName)
|
|
2282
|
+
) add('GOV-ADAPTER-005', 'BLOCKER', 'shared launcher destination must be provider-neutral and outside every provider discovery root')
|
|
2283
|
+
const committedLauncherRoot = manifestPath(repo, launcherDestination, 'shared launcher destination')
|
|
2284
|
+
if (committedLauncherRoot) canonicalReplayExcludedRoots.add(resolve(committedLauncherRoot))
|
|
2285
|
+
const launcherLifecycleOwners = generatedSurfaces.filter(([, surface]) => (surface.managedSurfaces || []).some((managed) => (
|
|
2286
|
+
managed.path === launcherDestination
|
|
2287
|
+
|| managed.path.startsWith(`${launcherDestination}/`)
|
|
2288
|
+
|| launcherDestination?.startsWith(`${managed.path}/`)
|
|
2289
|
+
)))
|
|
2290
|
+
if (launcherLifecycleOwners.length) {
|
|
2291
|
+
add('GOV-ADAPTER-005', 'BLOCKER', `shared launcher substrate is incorrectly owned by provider lifecycle:${launcherLifecycleOwners.map(([id]) => id).join(',')}`)
|
|
2292
|
+
}
|
|
2293
|
+
const expectedLauncherDestinations = managedLaunchers.map((launcher) => `${launcherDestination}/${launcher}`).sort()
|
|
2294
|
+
const actualLauncherDestinations = exactMaterializationPaths
|
|
2295
|
+
.filter((path) => managedLaunchers.some((launcher) => path.endsWith(`/${launcher}`)))
|
|
2296
|
+
.sort()
|
|
2297
|
+
if (JSON.stringify(actualLauncherDestinations) !== JSON.stringify(expectedLauncherDestinations)) {
|
|
2298
|
+
add('GOV-CORPUS-001', 'BLOCKER', 'shared launchers must have exactly one provider-neutral materialization destination')
|
|
2299
|
+
}
|
|
2300
|
+
for (const launcher of managedLaunchers) {
|
|
2301
|
+
const committed = committedLauncherRoot ? join(committedLauncherRoot, launcher) : null
|
|
2302
|
+
const shipped = join(forkRoot, 'launchers', launcher)
|
|
2303
|
+
if (!committed || !isRegularFile(committed) || !isRegularFile(shipped) || fileSha(committed) !== fileSha(shipped)) add('GOV-ADAPTER-005', 'BLOCKER', `shared thin adapter drift: ${launcher}`)
|
|
2304
|
+
if (!exactMaterializationSet.has(`${launcherDestination}/${launcher}`)) {
|
|
2305
|
+
add('GOV-CORPUS-001', 'BLOCKER', `shared launcher is absent from consumer materialization inventory:${launcher}`)
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
if (!committedLauncherRoot || !repositoryPathIsSafe(committedLauncherRoot) || !pathEntryExists(committedLauncherRoot) || !lstatSync(committedLauncherRoot).isDirectory()) {
|
|
2309
|
+
add('GOV-EXTENSION-001', 'BLOCKER', 'provider-neutral shared launcher directory is missing or unsafe')
|
|
2310
|
+
} else {
|
|
2311
|
+
const actualLaunchers = readdirSync(committedLauncherRoot).sort()
|
|
2312
|
+
const expectedLaunchers = [...managedLaunchers].sort()
|
|
2313
|
+
if (JSON.stringify(actualLaunchers) !== JSON.stringify(expectedLaunchers)) {
|
|
2314
|
+
add('GOV-EXTENSION-001', 'BLOCKER', 'undeclared files are forbidden in the shared launcher directory; executable extensions must be promoted through the provider-neutral package SSOT')
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
const managedSharedSkills = manifest?.skills || []
|
|
2319
|
+
for (const [providerId, surface] of generatedSurfaces) {
|
|
2320
|
+
const skillRoot = manifestPath(repo, surface.skillDestination, `${providerId} skill destination`)
|
|
2321
|
+
if (!skillRoot || !repositoryPathIsSafe(skillRoot) || !pathEntryExists(skillRoot) || !lstatSync(skillRoot).isDirectory()) {
|
|
2322
|
+
add('GOV-SKILL-004', 'BLOCKER', `${providerId} skill directory is missing or unsafe`)
|
|
2323
|
+
continue
|
|
2324
|
+
}
|
|
2325
|
+
const actualEntries = readdirSync(skillRoot).sort()
|
|
2326
|
+
const expectedEntries = [...new Set(surface.skills || [])].sort()
|
|
2327
|
+
if (JSON.stringify(actualEntries) !== JSON.stringify(expectedEntries)) {
|
|
2328
|
+
add('GOV-SKILL-004', 'BLOCKER', `${providerId} skill directory contains undeclared executable content; extensions must be promoted through the provider-neutral package SSOT`)
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (bom?.payload?.sharedSkillCount !== managedSharedSkills.length) add('GOV-SKILL-003', 'BLOCKER', `managed shared skill count ${managedSharedSkills.length} != BOM ${bom?.payload?.sharedSkillCount}`)
|
|
2332
|
+
const lockedSharedSkills = (corpusLock?.entries || []).filter((entry) => entry.file.startsWith('skills/')).map((entry) => `${entry.file}:${entry.sha256}`).sort()
|
|
2333
|
+
if (bom?.payload?.sharedSkillsSha256 !== sha256(lockedSharedSkills.join('\n') + '\n')) add('GOV-SKILL-003', 'BLOCKER', 'shared skill aggregate digest != governance BOM')
|
|
2334
|
+
|
|
2335
|
+
// Provider runtimes create local, git-ignored state directly below their discovery root:
|
|
2336
|
+
// Claude Code's .cc-writes write journal and the governance dispatcher's telemetry logs
|
|
2337
|
+
// (template/ds-product-template/.gitignore already declares .claude/logs/ as runtime noise;
|
|
2338
|
+
// both are empirically present in live consumer repos). They are not instruction/config/skill/
|
|
2339
|
+
// command/agent/hook discovery surfaces, and every executable/discovery class above stays
|
|
2340
|
+
// digest-closed, so skipping these exact direct children of the Claude root cannot open an
|
|
2341
|
+
// unsigned policy or execution surface. Symlink and plain-file impostors still fail below.
|
|
2342
|
+
const claudeRuntimeStateNames = new Set(['.cc-writes', 'logs'])
|
|
2343
|
+
for (const [providerRoot, plan] of providerRootPlans) {
|
|
2344
|
+
if (!pathEntryExists(providerRoot)) continue
|
|
2345
|
+
if (!repositoryPathIsSafe(providerRoot) || lstatSync(providerRoot).isSymbolicLink() || !lstatSync(providerRoot).isDirectory()) {
|
|
2346
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `provider-native root must be a real directory:${relative(repo, providerRoot)}`)
|
|
2347
|
+
continue
|
|
2348
|
+
}
|
|
2349
|
+
const providerRootIsClaude = relative(repo, providerRoot) === '.claude'
|
|
2350
|
+
const leaves = [...plan.files, ...plan.trees]
|
|
2351
|
+
const visit = (directory) => {
|
|
2352
|
+
for (const name of readdirSync(directory).sort()) {
|
|
2353
|
+
const absolute = resolve(directory, name)
|
|
2354
|
+
const info = lstatSync(absolute)
|
|
2355
|
+
const rel = relative(repo, absolute).replaceAll('\\', '/')
|
|
2356
|
+
if (info.isSymbolicLink()) {
|
|
2357
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `provider-native surface contains an undeclared symlink:${rel}`)
|
|
2358
|
+
continue
|
|
2359
|
+
}
|
|
2360
|
+
if (providerRootIsClaude && directory === providerRoot && info.isDirectory() && claudeRuntimeStateNames.has(name)) continue
|
|
2361
|
+
if (plan.files.has(absolute)) {
|
|
2362
|
+
if (!info.isFile()) add('GOV-EXTENSION-001', 'BLOCKER', `provider-native managed file has the wrong type:${rel}`)
|
|
2363
|
+
continue
|
|
2364
|
+
}
|
|
2365
|
+
if (plan.trees.has(absolute)) {
|
|
2366
|
+
if (!info.isDirectory()) add('GOV-EXTENSION-001', 'BLOCKER', `provider-native managed tree has the wrong type:${rel}`)
|
|
2367
|
+
else visit(absolute)
|
|
2368
|
+
continue
|
|
2369
|
+
}
|
|
2370
|
+
const isManagedAncestor = leaves.some((leaf) => leaf.startsWith(`${absolute}${sep}`))
|
|
2371
|
+
if (isManagedAncestor && info.isDirectory()) visit(absolute)
|
|
2372
|
+
else if (materializationAllows(rel, info.isDirectory())) {
|
|
2373
|
+
if (info.isDirectory()) visit(absolute)
|
|
2374
|
+
else if (!info.isFile() || info.nlink !== 1) add('GOV-EXTENSION-001', 'BLOCKER', `provider-native materialization is not one regular file:${rel}`)
|
|
2375
|
+
} else add('GOV-EXTENSION-001', 'BLOCKER', `undeclared provider-native executable/discovery surface:${rel}`)
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
visit(providerRoot)
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// Repository-level provider discovery is also closed. Codex and Claude can discover instruction,
|
|
2382
|
+
// config and MCP files below the repository root, so hashing only the root projections would still
|
|
2383
|
+
// leave a second policy/tool SSOT in an application subdirectory. Provider roots already verified
|
|
2384
|
+
// above (including authenticated skill trees) are skipped; generated/build dependency trees are
|
|
2385
|
+
// non-source and intentionally excluded from this discovery walk.
|
|
2386
|
+
const managedProviderRoots = new Set([...providerRootPlans.keys()].map((path) => resolve(path)))
|
|
2387
|
+
const reservedProviderRootNames = new Set([
|
|
2388
|
+
...discoveryInventory.providerRootNames,
|
|
2389
|
+
...discoveryInventory.pluginRootNames,
|
|
2390
|
+
])
|
|
2391
|
+
const canonicalInstructionDestinations = new Set(generatedSurfaces
|
|
2392
|
+
.map(([, surface]) => surface?.instructionDestination)
|
|
2393
|
+
.filter((path) => typeof path === 'string' && path)
|
|
2394
|
+
.map((path) => resolve(repo, path)))
|
|
2395
|
+
const reservedInstructionNames = new Set(discoveryInventory.instructionNames)
|
|
2396
|
+
const overrideInstructionNames = new Set(discoveryInventory.instructionOverrideNames)
|
|
2397
|
+
const reservedConfigPaths = [...discoveryInventory.configPaths]
|
|
2398
|
+
const canonicalConfigDestinations = new Set(generatedSurfaces
|
|
2399
|
+
.map(([, surface]) => surface?.hookDestination)
|
|
2400
|
+
.filter((path) => typeof path === 'string' && path)
|
|
2401
|
+
.map((path) => resolve(repo, path)))
|
|
2402
|
+
const matchedDiscoveryConfig = (rel, name) => reservedConfigPaths.find((path) => path.includes('/')
|
|
2403
|
+
? rel === path || rel.endsWith(`/${path}`)
|
|
2404
|
+
: name === path)
|
|
2405
|
+
const reservedDiscoveryLeaf = (rel, name) => (
|
|
2406
|
+
reservedInstructionNames.has(name)
|
|
2407
|
+
|| overrideInstructionNames.has(name)
|
|
2408
|
+
|| Boolean(matchedDiscoveryConfig(rel, name))
|
|
2409
|
+
)
|
|
2410
|
+
const visitDiscoverySurfaces = (directory) => {
|
|
2411
|
+
const names = readdirIfReadable(directory)
|
|
2412
|
+
if (!names) return
|
|
2413
|
+
for (const name of names.sort()) {
|
|
2414
|
+
const absolute = resolve(directory, name)
|
|
2415
|
+
const rel = relative(repo, absolute).replaceAll('\\', '/')
|
|
2416
|
+
const info = lstatIfReadable(absolute)
|
|
2417
|
+
if (!info) {
|
|
2418
|
+
// Reserved provider discovery surfaces must stay verifiable; only non-reserved names degrade.
|
|
2419
|
+
if (reservedProviderRootNames.has(name) || reservedDiscoveryLeaf(rel, name)) {
|
|
2420
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `provider discovery surface is unreadable and cannot be verified:${rel}`)
|
|
2421
|
+
}
|
|
2422
|
+
continue
|
|
2423
|
+
}
|
|
2424
|
+
if (info.isSymbolicLink()) {
|
|
2425
|
+
if (reservedProviderRootNames.has(name) || reservedDiscoveryLeaf(rel, name)) {
|
|
2426
|
+
add('GOV-SYMLINK-001', 'BLOCKER', `provider discovery surface is a symbolic-link alias:${rel}`)
|
|
2427
|
+
}
|
|
2428
|
+
continue
|
|
2429
|
+
}
|
|
2430
|
+
if (info.isDirectory()) {
|
|
2431
|
+
if (managedProviderRoots.has(absolute) || discoveryTraversalExcludedRoots.has(absolute)) continue
|
|
2432
|
+
if (reservedProviderRootNames.has(name)) {
|
|
2433
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `nested provider-native discovery root is undeclared:${rel}`)
|
|
2434
|
+
continue
|
|
2435
|
+
}
|
|
2436
|
+
visitDiscoverySurfaces(absolute)
|
|
2437
|
+
continue
|
|
2438
|
+
}
|
|
2439
|
+
if (!info.isFile()) {
|
|
2440
|
+
if (reservedDiscoveryLeaf(rel, name)) add('GOV-SPECIAL-001', 'BLOCKER', `provider discovery surface is not a regular file:${rel}`)
|
|
2441
|
+
continue
|
|
2442
|
+
}
|
|
2443
|
+
if (info.nlink !== 1 && reservedDiscoveryLeaf(rel, name)) {
|
|
2444
|
+
add('GOV-ALIAS-001', 'BLOCKER', `provider discovery surface has a hard-link alias:${rel}`)
|
|
2445
|
+
continue
|
|
2446
|
+
}
|
|
2447
|
+
const matchedConfig = matchedDiscoveryConfig(rel, name)
|
|
2448
|
+
if (matchedConfig && !canonicalConfigDestinations.has(absolute)) {
|
|
2449
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `provider config/MCP discovery surface is undeclared:${rel} (policy:${matchedConfig})`)
|
|
2450
|
+
continue
|
|
2451
|
+
}
|
|
2452
|
+
if (overrideInstructionNames.has(name)) {
|
|
2453
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `provider instruction override/local file is undeclared:${rel}`)
|
|
2454
|
+
continue
|
|
2455
|
+
}
|
|
2456
|
+
if (reservedInstructionNames.has(name) && !canonicalInstructionDestinations.has(absolute)) {
|
|
2457
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `nested provider instruction is undeclared:${rel}`)
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
if (repositoryRootSafe) visitDiscoverySurfaces(repo)
|
|
2462
|
+
|
|
2463
|
+
const governanceRoot = join(repo, 'governance')
|
|
2464
|
+
if (!repositoryPathIsSafe(governanceRoot) || !pathEntryExists(governanceRoot) || lstatSync(governanceRoot).isSymbolicLink() || !lstatSync(governanceRoot).isDirectory()) {
|
|
2465
|
+
add('GOV-EXTENSION-001', 'BLOCKER', 'governance must be a real directory containing only immutable locks, authenticated managed data, the declared launcher substrate, and optional overlay.md')
|
|
2466
|
+
} else {
|
|
2467
|
+
// Governance data files are allowed only when the immutable consumer BOM declares their exact
|
|
2468
|
+
// repository path and digest. Deriving both files and parent directories from that authenticated
|
|
2469
|
+
// authority lets a release add non-executable data (for example trust registries and schemas)
|
|
2470
|
+
// without turning governance/ into an open local policy or executable extension home.
|
|
2471
|
+
// Filesystem execute bits are not a data/code boundary: `node file.mjs` and `bash file.sh`
|
|
2472
|
+
// execute mode-0644 files. Dynamic direct leaves therefore use a closed lowercase JSON data
|
|
2473
|
+
// class. Fixed Markdown/lock/overlay homes retain their separate authority below.
|
|
2474
|
+
const requiredManagedGuide = 'governance/consumer-governance.md'
|
|
2475
|
+
if (!expectedManagedNames.includes(requiredManagedGuide)) {
|
|
2476
|
+
add('GOV-MANAGED-FILE-002', 'BLOCKER', `${requiredManagedGuide} must remain bound to the immutable managed-file BOM`)
|
|
2477
|
+
}
|
|
2478
|
+
const fixedGovernanceEntryNames = new Set([
|
|
2479
|
+
'consumer-governance.md', 'lock.json', 'lock.schema.json', 'overlay.md',
|
|
2480
|
+
])
|
|
2481
|
+
const directGovernanceDataName = /^[a-z0-9][a-z0-9._-]*\.json$/
|
|
2482
|
+
const authenticatedManagedGovernanceEntries = []
|
|
2483
|
+
const authenticatedNestedGovernanceFiles = new Set()
|
|
2484
|
+
const authenticatedNestedGovernanceDirectories = new Set()
|
|
2485
|
+
const governanceDirectoryName = /^[a-z0-9][a-z0-9._-]*$/
|
|
2486
|
+
for (const path of expectedManagedNames) {
|
|
2487
|
+
const segments = path.split('/')
|
|
2488
|
+
if (segments.length < 2 || segments[0] !== 'governance') continue
|
|
2489
|
+
const name = segments.at(-1)
|
|
2490
|
+
if (segments.length === 2 && name === 'consumer-governance.md') continue
|
|
2491
|
+
if (fixedGovernanceEntryNames.has(name) || !directGovernanceDataName.test(name)) {
|
|
2492
|
+
add(
|
|
2493
|
+
'GOV-EXTENSION-001',
|
|
2494
|
+
'BLOCKER',
|
|
2495
|
+
`BOM-managed governance data must use a non-reserved canonical lowercase .json name:${path}`,
|
|
2496
|
+
)
|
|
2497
|
+
continue
|
|
2498
|
+
}
|
|
2499
|
+
if (segments.length === 2) {
|
|
2500
|
+
authenticatedManagedGovernanceEntries.push(name)
|
|
2501
|
+
continue
|
|
2502
|
+
}
|
|
2503
|
+
const directorySegments = segments.slice(1, -1)
|
|
2504
|
+
if (directorySegments.some((segment) => !governanceDirectoryName.test(segment))) {
|
|
2505
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `BOM-managed governance data has an unsafe directory path:${path}`)
|
|
2506
|
+
continue
|
|
2507
|
+
}
|
|
2508
|
+
authenticatedNestedGovernanceFiles.add(segments.slice(1).join('/'))
|
|
2509
|
+
for (let index = 1; index <= directorySegments.length; index += 1) {
|
|
2510
|
+
authenticatedNestedGovernanceDirectories.add(directorySegments.slice(0, index).join('/'))
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
const allowedGovernanceEntries = new Set([
|
|
2514
|
+
...fixedGovernanceEntryNames,
|
|
2515
|
+
...authenticatedManagedGovernanceEntries,
|
|
2516
|
+
...[...authenticatedNestedGovernanceDirectories].filter((path) => !path.includes('/')),
|
|
2517
|
+
])
|
|
2518
|
+
const launcherSegments = typeof launcherDestination === 'string' ? launcherDestination.replaceAll('\\', '/').split('/') : []
|
|
2519
|
+
if (launcherSegments[0] === 'governance' && launcherSegments.length > 1) allowedGovernanceEntries.add(launcherSegments[1])
|
|
2520
|
+
const unexpected = readdirSync(governanceRoot).filter((name) => !allowedGovernanceEntries.has(name)).sort()
|
|
2521
|
+
if (unexpected.length) add('GOV-EXTENSION-001', 'BLOCKER', `governance contains undeclared executable/policy homes:${unexpected.join(',')}`)
|
|
2522
|
+
for (const name of authenticatedManagedGovernanceEntries) {
|
|
2523
|
+
const managedData = join(governanceRoot, name)
|
|
2524
|
+
if (!isRegularFile(managedData) || (lstatSync(managedData).mode & 0o111) !== 0) {
|
|
2525
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `managed governance data must be one regular non-executable file:governance/${name}`)
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
const nestedGovernanceFiles = [...authenticatedNestedGovernanceFiles].sort()
|
|
2529
|
+
const nestedGovernanceDirectories = [...authenticatedNestedGovernanceDirectories].sort()
|
|
2530
|
+
if (launcherSegments[0] === 'governance' && nestedGovernanceDirectories.some((path) => (
|
|
2531
|
+
path === launcherSegments[1] || path.startsWith(`${launcherSegments[1]}/`)
|
|
2532
|
+
))) {
|
|
2533
|
+
add('GOV-EXTENSION-001', 'BLOCKER', 'BOM-managed governance data overlaps the declared executable launcher substrate')
|
|
2534
|
+
}
|
|
2535
|
+
for (const directory of nestedGovernanceDirectories) {
|
|
2536
|
+
const absolute = join(governanceRoot, directory)
|
|
2537
|
+
if (!repositoryPathIsSafe(absolute) || !pathEntryExists(absolute) || lstatSync(absolute).isSymbolicLink() || !lstatSync(absolute).isDirectory()) {
|
|
2538
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `managed governance data directory must be one real directory:governance/${directory}`)
|
|
2539
|
+
continue
|
|
2540
|
+
}
|
|
2541
|
+
const expectedChildren = new Set()
|
|
2542
|
+
for (const childDirectory of nestedGovernanceDirectories) {
|
|
2543
|
+
const parts = childDirectory.split('/')
|
|
2544
|
+
if (parts.slice(0, -1).join('/') === directory) expectedChildren.add(parts.at(-1))
|
|
2545
|
+
}
|
|
2546
|
+
for (const childFile of nestedGovernanceFiles) {
|
|
2547
|
+
const parts = childFile.split('/')
|
|
2548
|
+
if (parts.slice(0, -1).join('/') === directory) expectedChildren.add(parts.at(-1))
|
|
2549
|
+
}
|
|
2550
|
+
const actualChildren = readdirSync(absolute).sort()
|
|
2551
|
+
const expected = [...expectedChildren].sort()
|
|
2552
|
+
if (JSON.stringify(actualChildren) !== JSON.stringify(expected)) {
|
|
2553
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `managed governance data directory differs from its exact BOM-derived inventory:governance/${directory}`)
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
for (const file of nestedGovernanceFiles) {
|
|
2557
|
+
const managedData = join(governanceRoot, file)
|
|
2558
|
+
if (!isRegularFile(managedData) || (lstatSync(managedData).mode & 0o111) !== 0) {
|
|
2559
|
+
add('GOV-EXTENSION-001', 'BLOCKER', `managed governance data must be one regular non-executable file:governance/${file}`)
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
const overlay = join(governanceRoot, 'overlay.md')
|
|
2563
|
+
if (pathEntryExists(overlay) && (!isRegularFile(overlay) || (lstatSync(overlay).mode & 0o111) !== 0)) {
|
|
2564
|
+
add('GOV-EXTENSION-001', 'BLOCKER', 'governance/overlay.md must be one regular non-executable Markdown file')
|
|
2565
|
+
}
|
|
2566
|
+
const consumerGuide = join(governanceRoot, 'consumer-governance.md')
|
|
2567
|
+
if (!isRegularFile(consumerGuide) || (lstatSync(consumerGuide).mode & 0o111) !== 0) {
|
|
2568
|
+
add('GOV-EXTENSION-001', 'BLOCKER', 'governance/consumer-governance.md must be the managed regular non-executable Markdown guide')
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// Replay every enforceable shared hook directly over every consumer-controlled source/config/doc.
|
|
2573
|
+
// Direct invocation avoids dispatcher event fan-out/timeouts and means hook crashes are blockers,
|
|
2574
|
+
// while preserving one predicate implementation (the exact immutable npm corpus) as the SSOT.
|
|
2575
|
+
const sourceFiles = collectSourceFiles(repo)
|
|
2576
|
+
const replayHooks = Array.isArray(manifest?.ciReplay) ? manifest.ciReplay : []
|
|
2577
|
+
let replayExecutionCount = 0
|
|
2578
|
+
const replayHookKeys = [
|
|
2579
|
+
'event',
|
|
2580
|
+
'file',
|
|
2581
|
+
'sourceHook',
|
|
2582
|
+
'interpreter',
|
|
2583
|
+
'matcher',
|
|
2584
|
+
'outputMode',
|
|
2585
|
+
'inputContract',
|
|
2586
|
+
'transcriptRequirement',
|
|
2587
|
+
]
|
|
2588
|
+
const nativeHookKeys = [
|
|
2589
|
+
'file',
|
|
2590
|
+
'sourceHook',
|
|
2591
|
+
'bucket',
|
|
2592
|
+
'matcher',
|
|
2593
|
+
'runtime',
|
|
2594
|
+
'outputMode',
|
|
2595
|
+
'inputContract',
|
|
2596
|
+
'transcriptRequirement',
|
|
2597
|
+
]
|
|
2598
|
+
const nativeHookEvents = new Set(['PreToolUse', 'PostToolUse', 'SessionStart', 'Stop', 'UserPromptSubmit'])
|
|
2599
|
+
const replayEvents = new Set(['PreToolUse', 'PostToolUse'])
|
|
2600
|
+
const replayInterpreters = new Set(['bash', 'node', 'python3'])
|
|
2601
|
+
const replayOutputModes = new Set([
|
|
2602
|
+
'diagnostic',
|
|
2603
|
+
'blocking',
|
|
2604
|
+
'governance-context',
|
|
2605
|
+
'governance-decision',
|
|
2606
|
+
'governance-context-or-block',
|
|
2607
|
+
])
|
|
2608
|
+
const replayInputContracts = new Set(['event-v1', 'proposed-text-v1'])
|
|
2609
|
+
const replayTranscriptRequirements = new Set(['none', 'optional', 'stable-required'])
|
|
2610
|
+
const replayWriteTools = ['Write', 'Edit', 'MultiEdit']
|
|
2611
|
+
const replayToolByHook = new Map()
|
|
2612
|
+
const replayHookIdentities = new Set()
|
|
2613
|
+
const nativeHookByIdentity = new Map()
|
|
2614
|
+
const expectedReplayIdentities = new Set()
|
|
2615
|
+
if (!manifest?.hooks || typeof manifest.hooks !== 'object' || Array.isArray(manifest.hooks)) {
|
|
2616
|
+
add('GOV-CONTENT-002', 'BLOCKER', 'installed native hook manifest is missing or invalid')
|
|
2617
|
+
} else {
|
|
2618
|
+
for (const [event, hooks] of Object.entries(manifest.hooks)) {
|
|
2619
|
+
if (!nativeHookEvents.has(event) || !Array.isArray(hooks)) {
|
|
2620
|
+
add('GOV-CONTENT-002', 'BLOCKER', `installed native hook event contract is invalid:${event}`)
|
|
2621
|
+
continue
|
|
2622
|
+
}
|
|
2623
|
+
for (const [index, hook] of hooks.entries()) {
|
|
2624
|
+
const matcherTools = typeof hook?.matcher === 'string'
|
|
2625
|
+
? hook.matcher.split('|').filter(Boolean)
|
|
2626
|
+
: []
|
|
2627
|
+
const identity = `${event}\0${hook?.file || ''}\0${hook?.sourceHook || ''}\0${hook?.matcher || ''}`
|
|
2628
|
+
const valid = (
|
|
2629
|
+
closedKeys(hook, nativeHookKeys)
|
|
2630
|
+
&& canonicalManagedRepositoryPath(hook.file)
|
|
2631
|
+
&& hook.file.startsWith('hooks/')
|
|
2632
|
+
&& typeof hook.sourceHook === 'string'
|
|
2633
|
+
&& hook.sourceHook.length > 0
|
|
2634
|
+
&& ['SHIP_AS_IS', 'SHIP_REWRITTEN', 'REPLACE'].includes(hook.bucket)
|
|
2635
|
+
&& typeof hook.matcher === 'string'
|
|
2636
|
+
&& matcherTools.every((tool) => /^[A-Za-z][A-Za-z0-9]*$/.test(tool))
|
|
2637
|
+
&& replayInterpreters.has(hook.runtime)
|
|
2638
|
+
&& replayOutputModes.has(hook.outputMode)
|
|
2639
|
+
&& replayInputContracts.has(hook.inputContract)
|
|
2640
|
+
&& replayTranscriptRequirements.has(hook.transcriptRequirement)
|
|
2641
|
+
&& (
|
|
2642
|
+
hook.inputContract !== 'proposed-text-v1'
|
|
2643
|
+
|| (event === 'PreToolUse' && replayWriteTools.some((tool) => matcherTools.includes(tool)))
|
|
2644
|
+
)
|
|
2645
|
+
&& !nativeHookByIdentity.has(identity)
|
|
2646
|
+
)
|
|
2647
|
+
if (!valid) {
|
|
2648
|
+
add('GOV-CONTENT-002', 'BLOCKER', `installed native hook contract is invalid or duplicated:${event}:${index}`)
|
|
2649
|
+
continue
|
|
2650
|
+
}
|
|
2651
|
+
nativeHookByIdentity.set(identity, hook)
|
|
2652
|
+
if (hook.bucket !== 'REPLACE' && hook.sourceHook !== 'inject_deploy_url_after_push.sh') {
|
|
2653
|
+
expectedReplayIdentities.add(identity)
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
for (const [index, hook] of replayHooks.entries()) {
|
|
2659
|
+
const matcherTools = typeof hook?.matcher === 'string'
|
|
2660
|
+
? hook.matcher.split('|').filter(Boolean)
|
|
2661
|
+
: []
|
|
2662
|
+
const replayTool = replayWriteTools.find((tool) => matcherTools.includes(tool))
|
|
2663
|
+
const identity = `${hook?.event || ''}\0${hook?.file || ''}\0${hook?.sourceHook || ''}\0${hook?.matcher || ''}`
|
|
2664
|
+
const nativeHook = nativeHookByIdentity.get(identity)
|
|
2665
|
+
const valid = (
|
|
2666
|
+
closedKeys(hook, replayHookKeys)
|
|
2667
|
+
&& replayEvents.has(hook.event)
|
|
2668
|
+
&& canonicalManagedRepositoryPath(hook.file)
|
|
2669
|
+
&& hook.file.startsWith('hooks/')
|
|
2670
|
+
&& typeof hook.sourceHook === 'string'
|
|
2671
|
+
&& hook.sourceHook.length > 0
|
|
2672
|
+
&& replayInterpreters.has(hook.interpreter)
|
|
2673
|
+
&& matcherTools.length > 0
|
|
2674
|
+
&& matcherTools.every((tool) => /^[A-Za-z][A-Za-z0-9]*$/.test(tool))
|
|
2675
|
+
&& Boolean(replayTool)
|
|
2676
|
+
&& replayOutputModes.has(hook.outputMode)
|
|
2677
|
+
&& replayInputContracts.has(hook.inputContract)
|
|
2678
|
+
&& replayTranscriptRequirements.has(hook.transcriptRequirement)
|
|
2679
|
+
&& (hook.inputContract !== 'proposed-text-v1' || hook.event === 'PreToolUse')
|
|
2680
|
+
&& nativeHook?.runtime === hook.interpreter
|
|
2681
|
+
&& nativeHook?.outputMode === hook.outputMode
|
|
2682
|
+
&& nativeHook?.inputContract === hook.inputContract
|
|
2683
|
+
&& nativeHook?.transcriptRequirement === hook.transcriptRequirement
|
|
2684
|
+
&& expectedReplayIdentities.has(identity)
|
|
2685
|
+
&& !replayHookIdentities.has(identity)
|
|
2686
|
+
)
|
|
2687
|
+
if (!valid) {
|
|
2688
|
+
add('GOV-CONTENT-002', 'BLOCKER', `installed CI replay hook contract is invalid or duplicated at index ${index}`)
|
|
2689
|
+
continue
|
|
2690
|
+
}
|
|
2691
|
+
replayHookIdentities.add(identity)
|
|
2692
|
+
replayToolByHook.set(hook, replayTool)
|
|
2693
|
+
}
|
|
2694
|
+
if (
|
|
2695
|
+
JSON.stringify([...replayHookIdentities].sort())
|
|
2696
|
+
!== JSON.stringify([...expectedReplayIdentities].sort())
|
|
2697
|
+
) {
|
|
2698
|
+
add('GOV-CONTENT-002', 'BLOCKER', 'installed CI replay inventory is not an exact projection of eligible native hook contracts')
|
|
2699
|
+
}
|
|
2700
|
+
const requiredTools = new Set(executionRuntimeValid ? executionRuntime.requiredExecutables : [])
|
|
2701
|
+
for (const hook of replayHooks) {
|
|
2702
|
+
if (replayToolByHook.has(hook)) requiredTools.add(hook.interpreter)
|
|
2703
|
+
}
|
|
2704
|
+
const missingTools = [...requiredTools].filter((tool) => !commandAvailable(tool)).sort()
|
|
2705
|
+
if (!replayHooks.length) add('GOV-CONTENT-001', 'BLOCKER', 'installed manifest has no fail-closed CI replay hooks')
|
|
2706
|
+
if (missingTools.length) add('GOV-CONTENT-002', 'BLOCKER', `required governance replay tools missing: ${missingTools.join(', ')}`)
|
|
2707
|
+
const replayBaseEnvironment = closedToolProfile
|
|
2708
|
+
? Object.freeze({
|
|
2709
|
+
HOME: closedToolProfile.homeDirectory,
|
|
2710
|
+
LANG: 'C',
|
|
2711
|
+
LC_ALL: 'C',
|
|
2712
|
+
NO_COLOR: '1',
|
|
2713
|
+
PATH: closedToolProfile.executablePath,
|
|
2714
|
+
PYTHONDONTWRITEBYTECODE: '1',
|
|
2715
|
+
PYTHONNOUSERSITE: '1',
|
|
2716
|
+
TMPDIR: closedToolProfile.tempDirectory,
|
|
2717
|
+
XDG_CONFIG_HOME: closedToolProfile.homeDirectory,
|
|
2718
|
+
})
|
|
2719
|
+
: Object.freeze({})
|
|
2720
|
+
|
|
2721
|
+
const materializeVerifiedReplayCorpus = () => {
|
|
2722
|
+
if (!closedToolProfile) throw new Error('closed replay tool profile is unavailable')
|
|
2723
|
+
const snapshotRoot = realpathSync(mkdtempSync(join(closedToolProfile.tempDirectory, 'verified-replay-corpus-')))
|
|
2724
|
+
const snapshotForkRoot = join(snapshotRoot, 'fork')
|
|
2725
|
+
chmodSync(snapshotRoot, 0o700)
|
|
2726
|
+
mkdirSync(snapshotForkRoot, { mode: 0o700 })
|
|
2727
|
+
const captured = new Map()
|
|
2728
|
+
let totalBytes = 0
|
|
2729
|
+
const captureAndWrite = (path, expectedDigest = null) => {
|
|
2730
|
+
const source = join(forkRoot, ...path.split('/'))
|
|
2731
|
+
const record = captureStableRegularFile(source, maxReplaySourceBytes, `replay corpus ${path}`)
|
|
2732
|
+
if (expectedDigest !== null && record.sha256 !== expectedDigest) {
|
|
2733
|
+
throw new Error(`replay corpus digest differs from its authenticated lock:${path}`)
|
|
2734
|
+
}
|
|
2735
|
+
totalBytes += record.bytes.length
|
|
2736
|
+
if (totalBytes > 256 * 1024 * 1024) throw new Error('replay corpus exceeds the closed 256 MiB snapshot budget')
|
|
2737
|
+
const destination = join(snapshotForkRoot, ...path.split('/'))
|
|
2738
|
+
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 })
|
|
2739
|
+
let descriptor
|
|
2740
|
+
try {
|
|
2741
|
+
descriptor = openSync(destination, 'wx', 0o400)
|
|
2742
|
+
writeFileSync(descriptor, record.bytes)
|
|
2743
|
+
fsyncSync(descriptor)
|
|
2744
|
+
} finally {
|
|
2745
|
+
if (descriptor !== undefined) closeSync(descriptor)
|
|
2746
|
+
}
|
|
2747
|
+
const written = captureStableRegularFile(destination, maxReplaySourceBytes, `private replay corpus ${path}`)
|
|
2748
|
+
if (written.sha256 !== record.sha256) throw new Error(`private replay corpus write changed authenticated bytes:${path}`)
|
|
2749
|
+
captured.set(path, record)
|
|
2750
|
+
}
|
|
2751
|
+
const thawAndRemove = () => {
|
|
2752
|
+
if (!existsSync(snapshotRoot)) return
|
|
2753
|
+
const thaw = directory => {
|
|
2754
|
+
chmodSync(directory, 0o700)
|
|
2755
|
+
for (const name of readdirSync(directory)) {
|
|
2756
|
+
const path = join(directory, name)
|
|
2757
|
+
const info = lstatSync(path)
|
|
2758
|
+
if (info.isDirectory() && !info.isSymbolicLink()) thaw(path)
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
thaw(snapshotRoot)
|
|
2762
|
+
rmSync(snapshotRoot, { recursive: true, force: true })
|
|
2763
|
+
}
|
|
2764
|
+
try {
|
|
2765
|
+
for (const entry of corpusLock.entries) captureAndWrite(entry.file, entry.sha256)
|
|
2766
|
+
for (const special of ['governance.lock', 'consumer/lock.json', 'consumer/lock.schema.json']) {
|
|
2767
|
+
if (!captured.has(special)) captureAndWrite(special)
|
|
2768
|
+
}
|
|
2769
|
+
const directories = []
|
|
2770
|
+
const freeze = directory => {
|
|
2771
|
+
directories.push(directory)
|
|
2772
|
+
for (const name of readdirSync(directory)) {
|
|
2773
|
+
const path = join(directory, name)
|
|
2774
|
+
if (lstatSync(path).isDirectory()) freeze(path)
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
freeze(snapshotRoot)
|
|
2778
|
+
for (const directory of directories.reverse()) chmodSync(directory, 0o500)
|
|
2779
|
+
return {
|
|
2780
|
+
forkRoot: snapshotForkRoot,
|
|
2781
|
+
outputTransport: join(snapshotForkRoot, 'launchers/provider-hook-output-transport.mjs'),
|
|
2782
|
+
has(path) {
|
|
2783
|
+
return captured.has(path)
|
|
2784
|
+
},
|
|
2785
|
+
verify() {
|
|
2786
|
+
for (const [path, expected] of captured) {
|
|
2787
|
+
const current = captureStableRegularFile(
|
|
2788
|
+
join(forkRoot, ...path.split('/')),
|
|
2789
|
+
maxReplaySourceBytes,
|
|
2790
|
+
`live replay corpus CAS ${path}`,
|
|
2791
|
+
)
|
|
2792
|
+
if (
|
|
2793
|
+
current.sha256 !== expected.sha256
|
|
2794
|
+
|| !sameStableFileIdentity(current.info, expected.info)
|
|
2795
|
+
) throw new Error(`live replay corpus changed after authenticated snapshot:${path}`)
|
|
2796
|
+
}
|
|
2797
|
+
return true
|
|
2798
|
+
},
|
|
2799
|
+
cleanup: thawAndRemove,
|
|
2800
|
+
corpusLockSha256: captured.get('governance.lock')?.sha256 || null,
|
|
2801
|
+
}
|
|
2802
|
+
} catch (error) {
|
|
2803
|
+
thawAndRemove()
|
|
2804
|
+
throw error
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
let interpretProviderHookChildResult = null
|
|
2809
|
+
const replayIntegrity = (detail) => ({ kind: 'integrity', detail })
|
|
2810
|
+
const interpretReplayResult = ({ hook, status, stdout, stderr }) => {
|
|
2811
|
+
if (status === 70) {
|
|
2812
|
+
return replayIntegrity(
|
|
2813
|
+
stderr.trim() || stdout.trim() || 'child reported the reserved integrity exit code:70',
|
|
2814
|
+
)
|
|
2815
|
+
}
|
|
2816
|
+
if (!Number.isInteger(status) || ![0, 2].includes(status)) {
|
|
2817
|
+
return replayIntegrity(`unsupported child exit code:${status ?? '<missing>'}`)
|
|
2818
|
+
}
|
|
2819
|
+
if (stderr.includes('GOVERNANCE_INTEGRITY:')) {
|
|
2820
|
+
return replayIntegrity('child output contains the reserved governance integrity marker')
|
|
2821
|
+
}
|
|
2822
|
+
const hasStdout = stdout.length > 0
|
|
2823
|
+
const hasStderr = stderr.length > 0
|
|
2824
|
+
if (hasStdout && hasStderr) return replayIntegrity('child mixed stdout and stderr channels')
|
|
2825
|
+
|
|
2826
|
+
if (hook.outputMode === 'diagnostic') {
|
|
2827
|
+
if (status !== 0) return replayIntegrity('diagnostic hook returned a blocking exit')
|
|
2828
|
+
if (hasStdout) return replayIntegrity('diagnostic hook wrote forbidden success stdout')
|
|
2829
|
+
return hasStderr
|
|
2830
|
+
? { kind: 'content-violation', detail: stderr.trim() }
|
|
2831
|
+
: { kind: 'clean' }
|
|
2832
|
+
}
|
|
2833
|
+
try {
|
|
2834
|
+
const interpreted = interpretProviderHookChildResult({
|
|
2835
|
+
status,
|
|
2836
|
+
outputMode: hook.outputMode,
|
|
2837
|
+
event: hook.event,
|
|
2838
|
+
stdout,
|
|
2839
|
+
stderr,
|
|
2840
|
+
})
|
|
2841
|
+
if (interpreted.kind === 'clean') return interpreted
|
|
2842
|
+
if (interpreted.kind === 'policy-block') {
|
|
2843
|
+
return { kind: 'content-violation', detail: interpreted.reason }
|
|
2844
|
+
}
|
|
2845
|
+
if (interpreted.kind === 'governance-context') {
|
|
2846
|
+
return { kind: 'content-violation', detail: interpreted.message }
|
|
2847
|
+
}
|
|
2848
|
+
if (interpreted.kind === 'governance-decision') {
|
|
2849
|
+
return { kind: 'content-violation', detail: interpreted.reason }
|
|
2850
|
+
}
|
|
2851
|
+
return replayIntegrity(`strict child-result interpreter returned an unknown result:${interpreted.kind || '<missing>'}`)
|
|
2852
|
+
} catch (error) {
|
|
2853
|
+
return replayIntegrity(error?.message || 'strict child-result interpretation failed')
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
// A repository that already failed a closed structural/authenticity rule cannot become compliant
|
|
2857
|
+
// through content replay. Avoid executing the expensive hook matrix in that terminal state while
|
|
2858
|
+
// preserving the complete replay for every otherwise-eligible repository and every content-only
|
|
2859
|
+
// violation. The report remains a hard failure and records zero hook executions, so this is an
|
|
2860
|
+
// explicit fail-closed short circuit rather than a hidden PASS-path optimization.
|
|
2861
|
+
const replaySkippedForPreexistingBlocker = diagnostics.some((diagnostic) => diagnostic.severity === 'BLOCKER')
|
|
2862
|
+
let verifiedReplayCorpus = null
|
|
2863
|
+
const replaySourceCaptures = new Map()
|
|
2864
|
+
const replaySourceChangeDiagnostics = new Set()
|
|
2865
|
+
const addReplaySourceChange = (file, message, evidence = undefined) => {
|
|
2866
|
+
const repositoryFile = relative(repo, file).replaceAll('\\', '/')
|
|
2867
|
+
if (replaySourceChangeDiagnostics.has(repositoryFile)) return
|
|
2868
|
+
replaySourceChangeDiagnostics.add(repositoryFile)
|
|
2869
|
+
add('GOV-CONTENT-002', 'BLOCKER', `${repositoryFile} ${message}`, evidence)
|
|
2870
|
+
}
|
|
2871
|
+
const verifyReplaySourceCaptures = () => {
|
|
2872
|
+
for (const [file, replayed] of replaySourceCaptures) {
|
|
2873
|
+
const repositoryFile = relative(repo, file).replaceAll('\\', '/')
|
|
2874
|
+
try {
|
|
2875
|
+
const current = captureStableRegularFile(
|
|
2876
|
+
file,
|
|
2877
|
+
maxReplaySourceBytes,
|
|
2878
|
+
`final replay source CAS ${repositoryFile}`,
|
|
2879
|
+
)
|
|
2880
|
+
if (
|
|
2881
|
+
current.sha256 !== replayed.sha256
|
|
2882
|
+
|| !sameStableFileIdentity(current.info, replayed.info)
|
|
2883
|
+
) addReplaySourceChange(file, 'changed after its authenticated replay')
|
|
2884
|
+
} catch (error) {
|
|
2885
|
+
addReplaySourceChange(file, 'cannot be stably attested after replay', error.message)
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (replayHooks.length && !missingTools.length && !replaySkippedForPreexistingBlocker) {
|
|
2890
|
+
try {
|
|
2891
|
+
verifiedReplayCorpus = materializeVerifiedReplayCorpus()
|
|
2892
|
+
;({ interpretProviderHookChildResult } = await import(pathToFileURL(verifiedReplayCorpus.outputTransport).href))
|
|
2893
|
+
if (typeof interpretProviderHookChildResult !== 'function') {
|
|
2894
|
+
throw new Error('strict child-result interpreter export is missing')
|
|
2895
|
+
}
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
verifiedReplayCorpus?.cleanup()
|
|
2898
|
+
verifiedReplayCorpus = null
|
|
2899
|
+
add('GOV-CONTENT-002', 'BLOCKER', 'verified immutable CI replay corpus cannot be materialized or loaded', error.message)
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
if (
|
|
2903
|
+
replayHooks.length
|
|
2904
|
+
&& !missingTools.length
|
|
2905
|
+
&& !replaySkippedForPreexistingBlocker
|
|
2906
|
+
&& verifiedReplayCorpus
|
|
2907
|
+
&& typeof interpretProviderHookChildResult === 'function'
|
|
2908
|
+
) {
|
|
2909
|
+
outer: for (const file of sourceFiles) {
|
|
2910
|
+
let body
|
|
2911
|
+
try {
|
|
2912
|
+
const captured = captureStableRegularFile(file, maxReplaySourceBytes, `replay source ${relative(repo, file)}`)
|
|
2913
|
+
replaySourceCaptures.set(file, captured)
|
|
2914
|
+
body = strictUtf8.decode(captured.bytes)
|
|
2915
|
+
} catch (error) {
|
|
2916
|
+
add('GOV-CONTENT-002', 'BLOCKER', `${relative(repo, file)} cannot be replayed as bounded strict UTF-8`, error.message)
|
|
2917
|
+
continue
|
|
2918
|
+
}
|
|
2919
|
+
for (const hook of replayHooks) {
|
|
2920
|
+
const hookPath = join(verifiedReplayCorpus.forkRoot, hook.file || '')
|
|
2921
|
+
const replayTool = replayToolByHook.get(hook)
|
|
2922
|
+
if (!replayTool) {
|
|
2923
|
+
add('GOV-CONTENT-002', 'BLOCKER', `CI replay hook has no validated write contract: ${hook.file || 'unnamed'}`)
|
|
2924
|
+
break outer
|
|
2925
|
+
}
|
|
2926
|
+
if (!verifiedReplayCorpus.has(hook.file || '')) {
|
|
2927
|
+
add('GOV-CONTENT-002', 'BLOCKER', `CI replay hook missing or unsafe: ${hook.file || 'unnamed'}`)
|
|
2928
|
+
break outer
|
|
2929
|
+
}
|
|
2930
|
+
// The shared hook corpus has a provider-neutral canonical payload contract. Native adapters
|
|
2931
|
+
// translate provider tool names to this contract; CI deliberately replays the canonical form.
|
|
2932
|
+
const absoluteFile = resolve(file)
|
|
2933
|
+
const repositoryFile = relative(repo, file).replaceAll('\\', '/')
|
|
2934
|
+
const proposedState = hook.inputContract === 'proposed-text-v1'
|
|
2935
|
+
? { schemaVersion: 1, path: repositoryFile, kind: 'text', content: body }
|
|
2936
|
+
: null
|
|
2937
|
+
const materializedFile = {
|
|
2938
|
+
file_path: absoluteFile,
|
|
2939
|
+
path: absoluteFile,
|
|
2940
|
+
content: body,
|
|
2941
|
+
new_string: body,
|
|
2942
|
+
...(proposedState ? { governance_write_state: proposedState } : {}),
|
|
2943
|
+
}
|
|
2944
|
+
const payload = JSON.stringify({
|
|
2945
|
+
hook_event_name: hook.event,
|
|
2946
|
+
event: hook.event,
|
|
2947
|
+
tool_name: replayTool,
|
|
2948
|
+
tool: 'write_file',
|
|
2949
|
+
operation: 'write',
|
|
2950
|
+
tool_input: {
|
|
2951
|
+
...materializedFile,
|
|
2952
|
+
edits: [materializedFile],
|
|
2953
|
+
changed_paths: [absoluteFile],
|
|
2954
|
+
},
|
|
2955
|
+
...(proposedState ? { governance_write_state: proposedState } : {}),
|
|
2956
|
+
changed_paths: [absoluteFile],
|
|
2957
|
+
})
|
|
2958
|
+
const replayRuntime = closedToolProfile?.executables?.[hook.interpreter]
|
|
2959
|
+
if (!replayRuntime) {
|
|
2960
|
+
add('GOV-CONTENT-002', 'BLOCKER', `CI replay runtime is unavailable:${hook.interpreter}`)
|
|
2961
|
+
break outer
|
|
2962
|
+
}
|
|
2963
|
+
const replayOptions = {
|
|
2964
|
+
cwd: repo,
|
|
2965
|
+
input: Buffer.from(payload),
|
|
2966
|
+
encoding: null,
|
|
2967
|
+
timeout: replayChildTimeoutMs,
|
|
2968
|
+
maxBuffer: maxReplayChildOutputBytes,
|
|
2969
|
+
killSignal: 'SIGKILL',
|
|
2970
|
+
env: {
|
|
2971
|
+
...replayBaseEnvironment,
|
|
2972
|
+
CLAUDE_PROJECT_DIR: repo,
|
|
2973
|
+
GOVERNANCE_PROJECT_DIR: repo,
|
|
2974
|
+
// Direct hooks-off CI replay must use the same already-verified immutable corpus as the
|
|
2975
|
+
// native product runner. Never inherit or rediscover a mutable project/package root.
|
|
2976
|
+
GOVERNANCE_CORPUS_ROOT: verifiedReplayCorpus.forkRoot,
|
|
2977
|
+
GOVERNANCE_PROVIDER: 'ci-replay',
|
|
2978
|
+
GOVERNANCE_SELF_PROVIDER: 'ci-replay',
|
|
2979
|
+
GOVERNANCE_READ_ONLY: '1',
|
|
2980
|
+
GOVERNANCE_TELEMETRY_OPT_IN: '0',
|
|
2981
|
+
GOVERNANCE_STRICT_REPLAY: '1',
|
|
2982
|
+
GOVERNANCE_STATIC_REPLAY: '1',
|
|
2983
|
+
GOVERNANCE_RUNTIME_ROOT: '',
|
|
2984
|
+
GOVERNANCE_STATE_DIR: '',
|
|
2985
|
+
GOVERNANCE_TRANSCRIPT_PATH: '',
|
|
2986
|
+
GOVERNANCE_TRANSCRIPT_TRUST: 'unavailable',
|
|
2987
|
+
GOVERNANCE_WRITE_STATE_TRUST: proposedState ? 'runner-v1' : 'unavailable',
|
|
2988
|
+
},
|
|
2989
|
+
}
|
|
2990
|
+
let replay
|
|
2991
|
+
let replayAttempt = 0
|
|
2992
|
+
do {
|
|
2993
|
+
replayAttempt += 1
|
|
2994
|
+
closedToolProfile.verify()
|
|
2995
|
+
replay = spawnSync(replayRuntime, [hookPath], replayOptions)
|
|
2996
|
+
closedToolProfile.verify()
|
|
2997
|
+
} while (
|
|
2998
|
+
replay.error?.code === 'ETIMEDOUT'
|
|
2999
|
+
&& replayAttempt < replayChildMaxAttempts
|
|
3000
|
+
)
|
|
3001
|
+
replayExecutionCount += 1
|
|
3002
|
+
if (replay.error || replay.status === null || replay.signal) {
|
|
3003
|
+
const cause = [
|
|
3004
|
+
`file=${repositoryFile}`,
|
|
3005
|
+
`interpreter=${hook.interpreter}`,
|
|
3006
|
+
`attempt=${replayAttempt}/${replayChildMaxAttempts}`,
|
|
3007
|
+
`timeoutMs=${replayChildTimeoutMs}`,
|
|
3008
|
+
`error=${replay.error?.code || 'none'}`,
|
|
3009
|
+
`signal=${replay.signal || 'none'}`,
|
|
3010
|
+
`executionIndex=${replayExecutionCount}`,
|
|
3011
|
+
].join(';')
|
|
3012
|
+
add(
|
|
3013
|
+
'GOV-CONTENT-002',
|
|
3014
|
+
'BLOCKER',
|
|
3015
|
+
`CI replay child execution failed for ${hook.sourceHook || hook.file}`,
|
|
3016
|
+
cause,
|
|
3017
|
+
)
|
|
3018
|
+
break outer
|
|
3019
|
+
}
|
|
3020
|
+
let stdout
|
|
3021
|
+
let stderr
|
|
3022
|
+
try {
|
|
3023
|
+
stdout = strictUtf8.decode(replay.stdout || Buffer.alloc(0))
|
|
3024
|
+
stderr = strictUtf8.decode(replay.stderr || Buffer.alloc(0))
|
|
3025
|
+
} catch {
|
|
3026
|
+
add('GOV-CONTENT-002', 'BLOCKER', `CI replay child emitted invalid UTF-8 for ${hook.sourceHook || hook.file}`)
|
|
3027
|
+
break outer
|
|
3028
|
+
}
|
|
3029
|
+
const interpreted = interpretReplayResult({
|
|
3030
|
+
hook,
|
|
3031
|
+
status: replay.status,
|
|
3032
|
+
stdout,
|
|
3033
|
+
stderr,
|
|
3034
|
+
})
|
|
3035
|
+
const detail = String(interpreted.detail || '').split(/\r?\n/).slice(0, 8).join(' | ')
|
|
3036
|
+
if (interpreted.kind === 'integrity') {
|
|
3037
|
+
add(
|
|
3038
|
+
'GOV-CONTENT-002',
|
|
3039
|
+
'BLOCKER',
|
|
3040
|
+
`CI replay integrity failure from ${hook.sourceHook || hook.file} (${hook.event})`,
|
|
3041
|
+
detail,
|
|
3042
|
+
)
|
|
3043
|
+
break outer
|
|
3044
|
+
}
|
|
3045
|
+
if (interpreted.kind === 'content-violation') {
|
|
3046
|
+
const ruleId = hook.outputMode === 'diagnostic' ? 'GOV-CONTENT-003' : 'GOV-CONTENT-001'
|
|
3047
|
+
add(ruleId, 'BLOCKER', `${relative(repo, file)} violates ${hook.sourceHook || hook.file} (${hook.event})`, detail)
|
|
3048
|
+
break
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
if (verifiedReplayCorpus) {
|
|
3054
|
+
try {
|
|
3055
|
+
verifiedReplayCorpus.verify()
|
|
3056
|
+
} catch (error) {
|
|
3057
|
+
add('GOV-CONTENT-002', 'BLOCKER', 'live replay corpus failed final compare-and-swap verification', error.message)
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
evidence.providers = Object.fromEntries(generatedSurfaces.map(([providerId, surface]) => [providerId, {
|
|
3062
|
+
events: canonicalEventsByProvider.get(providerId) || [],
|
|
3063
|
+
nativeEvents: nativeEventsByProvider.get(providerId) || [],
|
|
3064
|
+
skills: (surface.skills || []).length,
|
|
3065
|
+
instruction: surface.instructionDestination,
|
|
3066
|
+
hooks: surface.hookDestination,
|
|
3067
|
+
hookEnforcement: surface.capabilities.hookEnforcement,
|
|
3068
|
+
}]))
|
|
3069
|
+
evidence.release = bom?.release || null
|
|
3070
|
+
evidence.checkerSha256 = isRegularFile(shippedChecker) ? fileSha(shippedChecker) : null
|
|
3071
|
+
evidence.corpusLockSha256 = verifiedReplayCorpus?.corpusLockSha256
|
|
3072
|
+
|| (existsSync(corpusLockPath) ? fileSha(corpusLockPath) : null)
|
|
3073
|
+
const sourceInventoryRows = sourceFiles.map((file) => {
|
|
3074
|
+
const repositoryFile = relative(repo, file).replaceAll('\\', '/')
|
|
3075
|
+
try {
|
|
3076
|
+
const current = captureStableRegularFile(file, maxReplaySourceBytes, `source inventory ${repositoryFile}`)
|
|
3077
|
+
const replayed = replaySourceCaptures.get(file)
|
|
3078
|
+
if (
|
|
3079
|
+
replayed
|
|
3080
|
+
&& (
|
|
3081
|
+
current.sha256 !== replayed.sha256
|
|
3082
|
+
|| !sameStableFileIdentity(current.info, replayed.info)
|
|
3083
|
+
)
|
|
3084
|
+
) {
|
|
3085
|
+
addReplaySourceChange(file, 'changed after its authenticated replay')
|
|
3086
|
+
return `${repositoryFile}:${replayed.sha256}`
|
|
3087
|
+
}
|
|
3088
|
+
return `${repositoryFile}:${current.sha256}`
|
|
3089
|
+
} catch (error) {
|
|
3090
|
+
addReplaySourceChange(file, 'cannot be stably attested after replay', error.message)
|
|
3091
|
+
return `${repositoryFile}:unavailable`
|
|
3092
|
+
}
|
|
3093
|
+
})
|
|
3094
|
+
evidence.sourceInventory = {
|
|
3095
|
+
files: sourceInventoryRows.length,
|
|
3096
|
+
sha256: sha256(sourceInventoryRows.join('\n') + '\n'),
|
|
3097
|
+
}
|
|
3098
|
+
const repositoryRows = repositoryInventory(repo)
|
|
3099
|
+
evidence.repositoryInventory = {
|
|
3100
|
+
basis: repositoryRows.basis,
|
|
3101
|
+
files: repositoryRows.rows.length,
|
|
3102
|
+
sha256: sha256(repositoryRows.rows.join('\n') + '\n'),
|
|
3103
|
+
}
|
|
3104
|
+
const gitIdentity = closedRepositoryGitReady
|
|
3105
|
+
? runClosedGit(['rev-parse', 'HEAD', 'HEAD^{tree}'], {
|
|
3106
|
+
cwd: repo,
|
|
3107
|
+
timeoutMs: 3000,
|
|
3108
|
+
})
|
|
3109
|
+
: null
|
|
3110
|
+
const [gitHead = null, gitTree = null] = gitIdentity?.status === 0
|
|
3111
|
+
? gitIdentity.stdout.trim().split(/\r?\n/)
|
|
3112
|
+
: []
|
|
3113
|
+
evidence.git = {
|
|
3114
|
+
head: /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(gitHead || '') ? gitHead : null,
|
|
3115
|
+
tree: /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(gitTree || '') ? gitTree : null,
|
|
3116
|
+
}
|
|
3117
|
+
evidence.staticReplay = {
|
|
3118
|
+
sourceFiles: sourceFiles.length,
|
|
3119
|
+
hookExecutions: replayExecutionCount,
|
|
3120
|
+
skippedForPreexistingBlocker: replaySkippedForPreexistingBlocker,
|
|
3121
|
+
hooks: replayHooks.map((hook) => `${hook.event}:${hook.sourceHook || hook.file}`),
|
|
3122
|
+
requiredTools: [...requiredTools].sort(),
|
|
3123
|
+
nativeHooks: false,
|
|
3124
|
+
}
|
|
3125
|
+
evidence.sandboxDeniedPaths = [...sandboxDeniedPaths].sort()
|
|
3126
|
+
verifyReplaySourceCaptures()
|
|
3127
|
+
if (verifiedReplayCorpus) {
|
|
3128
|
+
try {
|
|
3129
|
+
verifiedReplayCorpus.verify()
|
|
3130
|
+
} catch (error) {
|
|
3131
|
+
add('GOV-CONTENT-002', 'BLOCKER', 'live replay corpus changed before final attestation', error.message)
|
|
3132
|
+
} finally {
|
|
3133
|
+
verifiedReplayCorpus.cleanup()
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
const ok = !diagnostics.some((diagnostic) => diagnostic.severity === 'BLOCKER')
|
|
3137
|
+
// Content-address the complete compliance input, not merely the package version. Two products at
|
|
3138
|
+
// the same release therefore cannot share an attestation digest unless their source snapshot,
|
|
3139
|
+
// checker/BOM/corpus, provider projections, and replay plan are all identical.
|
|
3140
|
+
const attestationDigest = sha256(JSON.stringify({
|
|
3141
|
+
role: bom?.role || null,
|
|
3142
|
+
ok,
|
|
3143
|
+
bomSha256: evidence.bomSha256 || null,
|
|
3144
|
+
checkerSha256: evidence.checkerSha256,
|
|
3145
|
+
corpusLockSha256: evidence.corpusLockSha256,
|
|
3146
|
+
release: evidence.release,
|
|
3147
|
+
providers: evidence.providers,
|
|
3148
|
+
sourceInventory: evidence.sourceInventory,
|
|
3149
|
+
repositoryInventory: evidence.repositoryInventory,
|
|
3150
|
+
git: evidence.git,
|
|
3151
|
+
staticReplay: evidence.staticReplay,
|
|
3152
|
+
sandboxDeniedPaths: evidence.sandboxDeniedPaths,
|
|
3153
|
+
blockerRuleIds: diagnostics.filter((item) => item.severity === 'BLOCKER').map((item) => item.ruleId).sort(),
|
|
3154
|
+
}))
|
|
3155
|
+
const report = { schemaVersion: 1, ok, role: bom?.role || null, hooksRequired: false, attestationDigest, evidence, diagnostics }
|
|
3156
|
+
|
|
3157
|
+
if (asJson) process.stdout.write(JSON.stringify(report, null, 2) + '\n')
|
|
3158
|
+
else {
|
|
3159
|
+
console.log(`${ok ? '✅' : '❌'} provider-neutral governance check: ${ok ? 'PASS' : 'FAIL'} (${diagnostics.length} diagnostics)`)
|
|
3160
|
+
console.log(` attestationDigest: ${attestationDigest}`)
|
|
3161
|
+
for (const item of diagnostics) console.log(` ${item.severity} ${item.ruleId}: ${item.message}${item.evidence ? ` (${item.evidence})` : ''}`)
|
|
3162
|
+
}
|
|
3163
|
+
process.exit(ok ? 0 : 1)
|