@stigmer/react 3.2.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/steps/ReviewStep.d.ts.map +1 -1
- package/agent/steps/ReviewStep.js +2 -3
- package/agent/steps/ReviewStep.js.map +1 -1
- package/billing/BaselineEditor.d.ts +9 -0
- package/billing/BaselineEditor.d.ts.map +1 -0
- package/billing/BaselineEditor.js +253 -0
- package/billing/BaselineEditor.js.map +1 -0
- package/billing/ModelCatalogPanel.d.ts +28 -0
- package/billing/ModelCatalogPanel.d.ts.map +1 -0
- package/billing/ModelCatalogPanel.js +102 -0
- package/billing/ModelCatalogPanel.js.map +1 -0
- package/billing/ModelGovernanceDetail.d.ts +8 -0
- package/billing/ModelGovernanceDetail.d.ts.map +1 -0
- package/billing/ModelGovernanceDetail.js +64 -0
- package/billing/ModelGovernanceDetail.js.map +1 -0
- package/billing/OperatorAccessNotice.d.ts +12 -0
- package/billing/OperatorAccessNotice.d.ts.map +1 -0
- package/billing/OperatorAccessNotice.js +15 -0
- package/billing/OperatorAccessNotice.js.map +1 -0
- package/billing/PricingGovernanceConsole.d.ts +57 -0
- package/billing/PricingGovernanceConsole.d.ts.map +1 -0
- package/billing/PricingGovernanceConsole.js +164 -0
- package/billing/PricingGovernanceConsole.js.map +1 -0
- package/billing/PricingGovernancePanel.d.ts +26 -0
- package/billing/PricingGovernancePanel.d.ts.map +1 -0
- package/billing/PricingGovernancePanel.js +64 -0
- package/billing/PricingGovernancePanel.js.map +1 -0
- package/billing/RetireConfirm.d.ts +9 -0
- package/billing/RetireConfirm.d.ts.map +1 -0
- package/billing/RetireConfirm.js +19 -0
- package/billing/RetireConfirm.js.map +1 -0
- package/billing/form-primitives.d.ts +18 -0
- package/billing/form-primitives.d.ts.map +1 -0
- package/billing/form-primitives.js +19 -0
- package/billing/form-primitives.js.map +1 -0
- package/billing/governance-primitives.d.ts +33 -0
- package/billing/governance-primitives.d.ts.map +1 -0
- package/billing/governance-primitives.js +43 -0
- package/billing/governance-primitives.js.map +1 -0
- package/billing/index.d.ts +18 -0
- package/billing/index.d.ts.map +1 -1
- package/billing/index.js +9 -0
- package/billing/index.js.map +1 -1
- package/billing/pricing-format.d.ts +12 -0
- package/billing/pricing-format.d.ts.map +1 -0
- package/billing/pricing-format.js +50 -0
- package/billing/pricing-format.js.map +1 -0
- package/billing/useDecidePricingOverride.d.ts +46 -0
- package/billing/useDecidePricingOverride.d.ts.map +1 -0
- package/billing/useDecidePricingOverride.js +44 -0
- package/billing/useDecidePricingOverride.js.map +1 -0
- package/billing/useModelGovernanceView.d.ts +103 -0
- package/billing/useModelGovernanceView.d.ts.map +1 -0
- package/billing/useModelGovernanceView.js +144 -0
- package/billing/useModelGovernanceView.js.map +1 -0
- package/billing/useModelPricingBaselines.d.ts +40 -0
- package/billing/useModelPricingBaselines.d.ts.map +1 -0
- package/billing/useModelPricingBaselines.js +32 -0
- package/billing/useModelPricingBaselines.js.map +1 -0
- package/billing/usePricingGovernance.d.ts +39 -0
- package/billing/usePricingGovernance.d.ts.map +1 -0
- package/billing/usePricingGovernance.js +31 -0
- package/billing/usePricingGovernance.js.map +1 -0
- package/billing/useRetireModelPricingBaseline.d.ts +31 -0
- package/billing/useRetireModelPricingBaseline.d.ts.map +1 -0
- package/billing/useRetireModelPricingBaseline.js +36 -0
- package/billing/useRetireModelPricingBaseline.js.map +1 -0
- package/billing/useUpsertModelPricingBaseline.d.ts +38 -0
- package/billing/useUpsertModelPricingBaseline.d.ts.map +1 -0
- package/billing/useUpsertModelPricingBaseline.js +37 -0
- package/billing/useUpsertModelPricingBaseline.js.map +1 -0
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +11 -5
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/composer/SessionComposer.d.ts +1 -1
- package/cursor-accounts/CursorAccountEditor.d.ts +28 -0
- package/cursor-accounts/CursorAccountEditor.d.ts.map +1 -0
- package/cursor-accounts/CursorAccountEditor.js +51 -0
- package/cursor-accounts/CursorAccountEditor.js.map +1 -0
- package/cursor-accounts/CursorAccountsAccessNotice.d.ts +11 -0
- package/cursor-accounts/CursorAccountsAccessNotice.d.ts.map +1 -0
- package/cursor-accounts/CursorAccountsAccessNotice.js +14 -0
- package/cursor-accounts/CursorAccountsAccessNotice.js.map +1 -0
- package/cursor-accounts/CursorAccountsConsole.d.ts +28 -0
- package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -0
- package/cursor-accounts/CursorAccountsConsole.js +275 -0
- package/cursor-accounts/CursorAccountsConsole.js.map +1 -0
- package/cursor-accounts/cursor-account-format.d.ts +10 -0
- package/cursor-accounts/cursor-account-format.d.ts.map +1 -0
- package/cursor-accounts/cursor-account-format.js +26 -0
- package/cursor-accounts/cursor-account-format.js.map +1 -0
- package/cursor-accounts/index.d.ts +18 -0
- package/cursor-accounts/index.d.ts.map +1 -0
- package/cursor-accounts/index.js +13 -0
- package/cursor-accounts/index.js.map +1 -0
- package/cursor-accounts/useCursorAccountView.d.ts +30 -0
- package/cursor-accounts/useCursorAccountView.d.ts.map +1 -0
- package/cursor-accounts/useCursorAccountView.js +25 -0
- package/cursor-accounts/useCursorAccountView.js.map +1 -0
- package/cursor-accounts/useCursorAccounts.d.ts +33 -0
- package/cursor-accounts/useCursorAccounts.d.ts.map +1 -0
- package/cursor-accounts/useCursorAccounts.js +25 -0
- package/cursor-accounts/useCursorAccounts.js.map +1 -0
- package/cursor-accounts/useCursorMemberKeyActions.d.ts +49 -0
- package/cursor-accounts/useCursorMemberKeyActions.d.ts.map +1 -0
- package/cursor-accounts/useCursorMemberKeyActions.js +38 -0
- package/cursor-accounts/useCursorMemberKeyActions.js.map +1 -0
- package/cursor-accounts/useDeleteCursorAccount.d.ts +30 -0
- package/cursor-accounts/useDeleteCursorAccount.d.ts.map +1 -0
- package/cursor-accounts/useDeleteCursorAccount.js +32 -0
- package/cursor-accounts/useDeleteCursorAccount.js.map +1 -0
- package/cursor-accounts/useSyncCursorAccount.d.ts +23 -0
- package/cursor-accounts/useSyncCursorAccount.d.ts.map +1 -0
- package/cursor-accounts/useSyncCursorAccount.js +31 -0
- package/cursor-accounts/useSyncCursorAccount.js.map +1 -0
- package/cursor-accounts/useUpsertCursorAccount.d.ts +26 -0
- package/cursor-accounts/useUpsertCursorAccount.d.ts.map +1 -0
- package/cursor-accounts/useUpsertCursorAccount.js +34 -0
- package/cursor-accounts/useUpsertCursorAccount.js.map +1 -0
- package/datastore/CollectionRecordsBrowser.d.ts +34 -0
- package/datastore/CollectionRecordsBrowser.d.ts.map +1 -0
- package/datastore/CollectionRecordsBrowser.js +209 -0
- package/datastore/CollectionRecordsBrowser.js.map +1 -0
- package/datastore/CollectionSchemaView.d.ts +29 -0
- package/datastore/CollectionSchemaView.d.ts.map +1 -0
- package/datastore/CollectionSchemaView.js +93 -0
- package/datastore/CollectionSchemaView.js.map +1 -0
- package/datastore/DatastoreDetailView.d.ts +44 -0
- package/datastore/DatastoreDetailView.d.ts.map +1 -0
- package/datastore/DatastoreDetailView.js +76 -0
- package/datastore/DatastoreDetailView.js.map +1 -0
- package/datastore/DatastoreSyncReport.d.ts +19 -0
- package/datastore/DatastoreSyncReport.d.ts.map +1 -0
- package/datastore/DatastoreSyncReport.js +45 -0
- package/datastore/DatastoreSyncReport.js.map +1 -0
- package/datastore/DeleteDatastoreDialog.d.ts +35 -0
- package/datastore/DeleteDatastoreDialog.d.ts.map +1 -0
- package/datastore/DeleteDatastoreDialog.js +90 -0
- package/datastore/DeleteDatastoreDialog.js.map +1 -0
- package/datastore/FieldValueControl.d.ts +55 -0
- package/datastore/FieldValueControl.d.ts.map +1 -0
- package/datastore/FieldValueControl.js +138 -0
- package/datastore/FieldValueControl.js.map +1 -0
- package/datastore/RecordFilterBuilder.d.ts +30 -0
- package/datastore/RecordFilterBuilder.d.ts.map +1 -0
- package/datastore/RecordFilterBuilder.js +122 -0
- package/datastore/RecordFilterBuilder.js.map +1 -0
- package/datastore/RecordFormPanel.d.ts +45 -0
- package/datastore/RecordFormPanel.d.ts.map +1 -0
- package/datastore/RecordFormPanel.js +215 -0
- package/datastore/RecordFormPanel.js.map +1 -0
- package/datastore/index.d.ts +43 -0
- package/datastore/index.d.ts.map +1 -0
- package/datastore/index.js +22 -0
- package/datastore/index.js.map +1 -0
- package/datastore/recordFilter.d.ts +54 -0
- package/datastore/recordFilter.d.ts.map +1 -0
- package/datastore/recordFilter.js +93 -0
- package/datastore/recordFilter.js.map +1 -0
- package/datastore/recordValues.d.ts +85 -0
- package/datastore/recordValues.d.ts.map +1 -0
- package/datastore/recordValues.js +282 -0
- package/datastore/recordValues.js.map +1 -0
- package/datastore/useDatastore.d.ts +39 -0
- package/datastore/useDatastore.d.ts.map +1 -0
- package/datastore/useDatastore.js +44 -0
- package/datastore/useDatastore.js.map +1 -0
- package/datastore/useDatastoreCount.d.ts +48 -0
- package/datastore/useDatastoreCount.d.ts.map +1 -0
- package/datastore/useDatastoreCount.js +27 -0
- package/datastore/useDatastoreCount.js.map +1 -0
- package/datastore/useDatastoreDescription.d.ts +54 -0
- package/datastore/useDatastoreDescription.d.ts.map +1 -0
- package/datastore/useDatastoreDescription.js +50 -0
- package/datastore/useDatastoreDescription.js.map +1 -0
- package/datastore/useDatastoreList.d.ts +57 -0
- package/datastore/useDatastoreList.d.ts.map +1 -0
- package/datastore/useDatastoreList.js +29 -0
- package/datastore/useDatastoreList.js.map +1 -0
- package/datastore/useDatastoreSearch.d.ts +19 -0
- package/datastore/useDatastoreSearch.d.ts.map +1 -0
- package/datastore/useDatastoreSearch.js +22 -0
- package/datastore/useDatastoreSearch.js.map +1 -0
- package/datastore/useDeleteDatastore.d.ts +53 -0
- package/datastore/useDeleteDatastore.d.ts.map +1 -0
- package/datastore/useDeleteDatastore.js +40 -0
- package/datastore/useDeleteDatastore.js.map +1 -0
- package/datastore/useDeleteRecord.d.ts +34 -0
- package/datastore/useDeleteRecord.d.ts.map +1 -0
- package/datastore/useDeleteRecord.js +43 -0
- package/datastore/useDeleteRecord.js.map +1 -0
- package/datastore/useInsertRecord.d.ts +50 -0
- package/datastore/useInsertRecord.d.ts.map +1 -0
- package/datastore/useInsertRecord.js +55 -0
- package/datastore/useInsertRecord.js.map +1 -0
- package/datastore/useRecordCollection.d.ts +104 -0
- package/datastore/useRecordCollection.d.ts.map +1 -0
- package/datastore/useRecordCollection.js +136 -0
- package/datastore/useRecordCollection.js.map +1 -0
- package/datastore/useRecordList.d.ts +66 -0
- package/datastore/useRecordList.d.ts.map +1 -0
- package/datastore/useRecordList.js +58 -0
- package/datastore/useRecordList.js.map +1 -0
- package/datastore/useUpdateRecord.d.ts +52 -0
- package/datastore/useUpdateRecord.d.ts.map +1 -0
- package/datastore/useUpdateRecord.js +55 -0
- package/datastore/useUpdateRecord.js.map +1 -0
- package/environment/EnvironmentListPanel.d.ts.map +1 -1
- package/environment/EnvironmentListPanel.js +12 -7
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/iam-policy/index.d.ts +1 -1
- package/iam-policy/index.d.ts.map +1 -1
- package/iam-policy/index.js.map +1 -1
- package/iam-policy/useCheckPermission.d.ts +32 -11
- package/iam-policy/useCheckPermission.d.ts.map +1 -1
- package/iam-policy/useCheckPermission.js +24 -19
- package/iam-policy/useCheckPermission.js.map +1 -1
- package/index.d.ts +12 -6
- package/index.d.ts.map +1 -1
- package/index.js +14 -3
- package/index.js.map +1 -1
- package/library/index.d.ts +0 -7
- package/library/index.d.ts.map +1 -1
- package/library/index.js +0 -4
- package/library/index.js.map +1 -1
- package/library/useApplyResource.d.ts +12 -12
- package/library/useApplyResource.d.ts.map +1 -1
- package/library/useApplyResource.js +16 -25
- package/library/useApplyResource.js.map +1 -1
- package/library/useExportResource.d.ts +13 -9
- package/library/useExportResource.d.ts.map +1 -1
- package/library/useExportResource.js +18 -36
- package/library/useExportResource.js.map +1 -1
- package/manifest/ApplyManifestDialog.d.ts +48 -0
- package/manifest/ApplyManifestDialog.d.ts.map +1 -0
- package/manifest/ApplyManifestDialog.js +135 -0
- package/manifest/ApplyManifestDialog.js.map +1 -0
- package/manifest/EditResourceYamlDialog.d.ts +51 -0
- package/manifest/EditResourceYamlDialog.d.ts.map +1 -0
- package/manifest/EditResourceYamlDialog.js +104 -0
- package/manifest/EditResourceYamlDialog.js.map +1 -0
- package/manifest/RedactedSecretsNotice.d.ts +14 -0
- package/manifest/RedactedSecretsNotice.d.ts.map +1 -0
- package/manifest/RedactedSecretsNotice.js +14 -0
- package/manifest/RedactedSecretsNotice.js.map +1 -0
- package/manifest/YamlEditor.d.ts +44 -0
- package/manifest/YamlEditor.d.ts.map +1 -0
- package/manifest/YamlEditor.js +216 -0
- package/manifest/YamlEditor.js.map +1 -0
- package/manifest/index.d.ts +13 -0
- package/manifest/index.d.ts.map +1 -0
- package/manifest/index.js +13 -0
- package/manifest/index.js.map +1 -0
- package/manifest/useApplyManifest.d.ts +82 -0
- package/manifest/useApplyManifest.d.ts.map +1 -0
- package/manifest/useApplyManifest.js +180 -0
- package/manifest/useApplyManifest.js.map +1 -0
- package/manifest/useEditResourceYaml.d.ts +106 -0
- package/manifest/useEditResourceYaml.d.ts.map +1 -0
- package/manifest/useEditResourceYaml.js +168 -0
- package/manifest/useEditResourceYaml.js.map +1 -0
- package/mcp-server/steps/ReviewStep.d.ts.map +1 -1
- package/mcp-server/steps/ReviewStep.js +2 -3
- package/mcp-server/steps/ReviewStep.js.map +1 -1
- package/package.json +4 -4
- package/resource-creation/templates/workflow-templates.js +4 -4
- package/session/useEditSessionPrep.js +3 -3
- package/session/useEditSessionPrep.js.map +1 -1
- package/settings/index.d.ts +2 -1
- package/settings/index.d.ts.map +1 -1
- package/settings/index.js +2 -1
- package/settings/index.js.map +1 -1
- package/settings/settings-nav.d.ts +22 -0
- package/settings/settings-nav.d.ts.map +1 -1
- package/settings/settings-nav.js +32 -1
- package/settings/settings-nav.js.map +1 -1
- package/settings/useSettingsNavGroups.d.ts +29 -0
- package/settings/useSettingsNavGroups.d.ts.map +1 -0
- package/settings/useSettingsNavGroups.js +61 -0
- package/settings/useSettingsNavGroups.js.map +1 -0
- package/src/agent/steps/ReviewStep.tsx +2 -3
- package/src/billing/BaselineEditor.tsx +619 -0
- package/src/billing/ModelCatalogPanel.tsx +283 -0
- package/src/billing/ModelGovernanceDetail.tsx +289 -0
- package/src/billing/OperatorAccessNotice.tsx +32 -0
- package/src/billing/PricingGovernanceConsole.tsx +465 -0
- package/src/billing/PricingGovernancePanel.tsx +166 -0
- package/src/billing/RetireConfirm.tsx +82 -0
- package/src/billing/__tests__/ModelCatalogPanel.test.tsx +253 -0
- package/src/billing/__tests__/PricingGovernanceConsole.test.tsx +410 -0
- package/src/billing/__tests__/useModelGovernanceView.test.tsx +308 -0
- package/src/billing/form-primitives.tsx +68 -0
- package/src/billing/governance-primitives.tsx +133 -0
- package/src/billing/index.ts +37 -0
- package/src/billing/pricing-format.ts +53 -0
- package/src/billing/useDecidePricingOverride.ts +78 -0
- package/src/billing/useModelGovernanceView.ts +290 -0
- package/src/billing/useModelPricingBaselines.ts +69 -0
- package/src/billing/usePricingGovernance.ts +57 -0
- package/src/billing/useRetireModelPricingBaseline.ts +65 -0
- package/src/billing/useUpsertModelPricingBaseline.ts +72 -0
- package/src/channel/AgentChannelsPanel.tsx +27 -1
- package/src/composer/SessionComposer.tsx +1 -1
- package/src/cursor-accounts/CursorAccountEditor.tsx +181 -0
- package/src/cursor-accounts/CursorAccountsAccessNotice.tsx +36 -0
- package/src/cursor-accounts/CursorAccountsConsole.tsx +810 -0
- package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +384 -0
- package/src/cursor-accounts/cursor-account-format.ts +29 -0
- package/src/cursor-accounts/index.ts +29 -0
- package/src/cursor-accounts/useCursorAccountView.ts +49 -0
- package/src/cursor-accounts/useCursorAccounts.ts +49 -0
- package/src/cursor-accounts/useCursorMemberKeyActions.ts +106 -0
- package/src/cursor-accounts/useDeleteCursorAccount.ts +62 -0
- package/src/cursor-accounts/useSyncCursorAccount.ts +54 -0
- package/src/cursor-accounts/useUpsertCursorAccount.ts +57 -0
- package/src/datastore/CollectionRecordsBrowser.tsx +544 -0
- package/src/datastore/CollectionSchemaView.tsx +247 -0
- package/src/datastore/DatastoreDetailView.tsx +201 -0
- package/src/datastore/DatastoreSyncReport.tsx +105 -0
- package/src/datastore/DeleteDatastoreDialog.tsx +202 -0
- package/src/datastore/FieldValueControl.tsx +289 -0
- package/src/datastore/RecordFilterBuilder.tsx +411 -0
- package/src/datastore/RecordFormPanel.tsx +432 -0
- package/src/datastore/__tests__/CollectionRecordsBrowser.test.tsx +207 -0
- package/src/datastore/__tests__/DeleteDatastoreDialog.test.tsx +133 -0
- package/src/datastore/__tests__/FieldValueControl.test.tsx +160 -0
- package/src/datastore/__tests__/RecordFilterBuilder.test.tsx +141 -0
- package/src/datastore/__tests__/RecordFormPanel.test.tsx +227 -0
- package/src/datastore/__tests__/recordFilter.test.ts +156 -0
- package/src/datastore/__tests__/recordMutations.test.tsx +195 -0
- package/src/datastore/__tests__/recordValues.test.ts +359 -0
- package/src/datastore/__tests__/schemaAndSyncViews.test.tsx +142 -0
- package/src/datastore/__tests__/useDatastoreDescription.test.tsx +143 -0
- package/src/datastore/__tests__/useRecordCollection.test.tsx +250 -0
- package/src/datastore/__tests__/useRecordList.test.tsx +85 -0
- package/src/datastore/index.ts +111 -0
- package/src/datastore/recordFilter.ts +140 -0
- package/src/datastore/recordValues.ts +327 -0
- package/src/datastore/useDatastore.ts +67 -0
- package/src/datastore/useDatastoreCount.ts +67 -0
- package/src/datastore/useDatastoreDescription.ts +90 -0
- package/src/datastore/useDatastoreList.ts +79 -0
- package/src/datastore/useDatastoreSearch.ts +39 -0
- package/src/datastore/useDeleteDatastore.ts +87 -0
- package/src/datastore/useDeleteRecord.ts +77 -0
- package/src/datastore/useInsertRecord.ts +92 -0
- package/src/datastore/useRecordCollection.ts +256 -0
- package/src/datastore/useRecordList.ts +131 -0
- package/src/datastore/useUpdateRecord.ts +95 -0
- package/src/environment/EnvironmentListPanel.tsx +24 -0
- package/src/execution/__tests__/useArtifactInspection.test.tsx +12 -2
- package/src/iam-policy/__tests__/useCheckPermission.test.tsx +221 -0
- package/src/iam-policy/index.ts +1 -0
- package/src/iam-policy/useCheckPermission.ts +45 -19
- package/src/index.ts +163 -13
- package/src/library/index.ts +0 -20
- package/src/library/useApplyResource.ts +27 -34
- package/src/library/useExportResource.ts +21 -40
- package/src/manifest/ApplyManifestDialog.tsx +315 -0
- package/src/manifest/EditResourceYamlDialog.tsx +265 -0
- package/src/manifest/RedactedSecretsNotice.tsx +30 -0
- package/src/manifest/YamlEditor.tsx +272 -0
- package/src/manifest/__tests__/EditResourceYamlDialog.test.tsx +118 -0
- package/src/manifest/__tests__/useApplyManifest.test.tsx +237 -0
- package/src/manifest/__tests__/useEditResourceYaml.test.tsx +252 -0
- package/src/manifest/index.ts +33 -0
- package/src/manifest/useApplyManifest.ts +270 -0
- package/src/manifest/useEditResourceYaml.ts +273 -0
- package/src/mcp-server/steps/ReviewStep.tsx +2 -3
- package/src/resource-creation/templates/workflow-templates.ts +4 -4
- package/src/session/useEditSessionPrep.ts +3 -3
- package/src/settings/__tests__/useSettingsNavGroups.test.tsx +138 -0
- package/src/settings/index.ts +5 -1
- package/src/settings/settings-nav.ts +43 -0
- package/src/settings/useSettingsNavGroups.ts +81 -0
- package/src/workflow/WorkflowYamlEditor.tsx +6 -270
- package/styles.css +1 -1
- package/workflow/CanvasTransitionEdge.d.ts +1 -1
- package/workflow/WorkflowYamlEditor.d.ts +2 -42
- package/workflow/WorkflowYamlEditor.d.ts.map +1 -1
- package/workflow/WorkflowYamlEditor.js +5 -214
- package/workflow/WorkflowYamlEditor.js.map +1 -1
- package/library/ImportResourceDialog.d.ts +0 -43
- package/library/ImportResourceDialog.d.ts.map +0 -1
- package/library/ImportResourceDialog.js +0 -90
- package/library/ImportResourceDialog.js.map +0 -1
- package/library/parse-resource-yaml.d.ts +0 -63
- package/library/parse-resource-yaml.d.ts.map +0 -1
- package/library/parse-resource-yaml.js +0 -407
- package/library/parse-resource-yaml.js.map +0 -1
- package/library/serialize-resource-yaml.d.ts +0 -103
- package/library/serialize-resource-yaml.d.ts.map +0 -1
- package/library/serialize-resource-yaml.js +0 -565
- package/library/serialize-resource-yaml.js.map +0 -1
- package/library/useImportResource.d.ts +0 -84
- package/library/useImportResource.d.ts.map +0 -1
- package/library/useImportResource.js +0 -188
- package/library/useImportResource.js.map +0 -1
- package/src/library/ImportResourceDialog.tsx +0 -227
- package/src/library/parse-resource-yaml.ts +0 -630
- package/src/library/serialize-resource-yaml.ts +0 -756
- package/src/library/useImportResource.ts +0 -253
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { create, type JsonObject } from "@bufbuild/protobuf";
|
|
5
|
+
import {
|
|
6
|
+
UpdateRecordRequestSchema,
|
|
7
|
+
type RecordEnvelope,
|
|
8
|
+
} from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/record_io_pb";
|
|
9
|
+
import { useStigmer } from "../hooks.js";
|
|
10
|
+
import { toError } from "../internal/toError.js";
|
|
11
|
+
import type { RecordScope } from "./useRecordList.js";
|
|
12
|
+
|
|
13
|
+
/** Arguments for a single record update. */
|
|
14
|
+
export interface UpdateRecordArgs extends RecordScope {
|
|
15
|
+
/** Record id (`dsr_…`). */
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/**
|
|
18
|
+
* Partial merge (DD-005): only the supplied fields change; an
|
|
19
|
+
* explicit `null` clears a field; absent fields keep their stored
|
|
20
|
+
* values. Constraints evaluate on the merged result. Build this from
|
|
21
|
+
* per-field edits with `buildUpdateFields`.
|
|
22
|
+
*/
|
|
23
|
+
readonly fields: JsonObject;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Return value of {@link useUpdateRecord}. */
|
|
27
|
+
export interface UseUpdateRecordReturn {
|
|
28
|
+
/**
|
|
29
|
+
* Update one record by id. Resolves with the merged envelope. Rejects
|
|
30
|
+
* — and sets `error` — on denial or constraint violation; the error's
|
|
31
|
+
* message is the server's relayable text, byte-for-byte.
|
|
32
|
+
*/
|
|
33
|
+
readonly updateRecord: (args: UpdateRecordArgs) => Promise<RecordEnvelope>;
|
|
34
|
+
/** `true` while the update is in flight. */
|
|
35
|
+
readonly isUpdating: boolean;
|
|
36
|
+
/** Error from the last failed update, or `null` when healthy. */
|
|
37
|
+
readonly error: Error | null;
|
|
38
|
+
/** Reset `error` to `null`. */
|
|
39
|
+
readonly clearError: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Behavior hook that wraps `datastore.updateRecord()` with loading and
|
|
44
|
+
* error state.
|
|
45
|
+
*
|
|
46
|
+
* The RPC is an honest tri-state partial merge — the hook adds no
|
|
47
|
+
* merge semantics of its own. A record in another partition is
|
|
48
|
+
* NOT_FOUND (DD-010: partition scoping is structural).
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* const { updateRecord } = useUpdateRecord();
|
|
53
|
+
* await updateRecord({
|
|
54
|
+
* org: "acme",
|
|
55
|
+
* datastore: "clinic",
|
|
56
|
+
* collection: "bookings",
|
|
57
|
+
* id: "dsr_01j9...",
|
|
58
|
+
* fields: { patient_phone: "+15550100", notes: null }, // notes cleared
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function useUpdateRecord(): UseUpdateRecordReturn {
|
|
63
|
+
const stigmer = useStigmer();
|
|
64
|
+
const [isUpdating, setIsUpdating] = useState(false);
|
|
65
|
+
const [error, setError] = useState<Error | null>(null);
|
|
66
|
+
|
|
67
|
+
const clearError = useCallback(() => setError(null), []);
|
|
68
|
+
|
|
69
|
+
const updateRecord = useCallback(
|
|
70
|
+
async (args: UpdateRecordArgs): Promise<RecordEnvelope> => {
|
|
71
|
+
setIsUpdating(true);
|
|
72
|
+
setError(null);
|
|
73
|
+
try {
|
|
74
|
+
return await stigmer.datastore.updateRecord(
|
|
75
|
+
create(UpdateRecordRequestSchema, {
|
|
76
|
+
org: args.org,
|
|
77
|
+
datastore: args.datastore,
|
|
78
|
+
collection: args.collection,
|
|
79
|
+
partition: args.partition ?? "",
|
|
80
|
+
id: args.id,
|
|
81
|
+
fields: args.fields,
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
setError(toError(err));
|
|
86
|
+
throw err;
|
|
87
|
+
} finally {
|
|
88
|
+
setIsUpdating(false);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[stigmer],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return { updateRecord, isUpdating, error, clearError };
|
|
95
|
+
}
|
|
@@ -6,6 +6,7 @@ import { getUserMessage } from "@stigmer/sdk";
|
|
|
6
6
|
import type { Environment } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
7
7
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
8
8
|
import { ResourceVisibilityControl } from "../library/ResourceVisibilityControl.js";
|
|
9
|
+
import { EditResourceYamlDialog } from "../manifest/EditResourceYamlDialog.js";
|
|
9
10
|
import { useEnvironmentList } from "./useEnvironmentList.js";
|
|
10
11
|
import { EnvironmentVariableEditor } from "./EnvironmentVariableEditor.js";
|
|
11
12
|
import { isShareRestrictedEnvironment } from "./shareRestriction.js";
|
|
@@ -94,6 +95,7 @@ export function EnvironmentListPanel({
|
|
|
94
95
|
labels,
|
|
95
96
|
);
|
|
96
97
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|
98
|
+
const [yamlEnvironment, setYamlEnvironment] = useState<Environment | null>(null);
|
|
97
99
|
|
|
98
100
|
if (onRefetchRef) {
|
|
99
101
|
onRefetchRef(refetch);
|
|
@@ -179,9 +181,18 @@ export function EnvironmentListPanel({
|
|
|
179
181
|
onToggle={() => handleToggle(env)}
|
|
180
182
|
readOnly={readOnly}
|
|
181
183
|
onVisibilityChanged={refetch}
|
|
184
|
+
onEditYaml={() => setYamlEnvironment(env)}
|
|
182
185
|
/>
|
|
183
186
|
);
|
|
184
187
|
})}
|
|
188
|
+
<EditResourceYamlDialog
|
|
189
|
+
open={yamlEnvironment !== null}
|
|
190
|
+
onOpenChange={(open) => {
|
|
191
|
+
if (!open) setYamlEnvironment(null);
|
|
192
|
+
}}
|
|
193
|
+
resource={yamlEnvironment}
|
|
194
|
+
onApplied={refetch}
|
|
195
|
+
/>
|
|
185
196
|
</div>
|
|
186
197
|
);
|
|
187
198
|
}
|
|
@@ -196,12 +207,14 @@ function EnvironmentCard({
|
|
|
196
207
|
onToggle,
|
|
197
208
|
readOnly,
|
|
198
209
|
onVisibilityChanged,
|
|
210
|
+
onEditYaml,
|
|
199
211
|
}: {
|
|
200
212
|
environment: Environment;
|
|
201
213
|
isExpanded: boolean;
|
|
202
214
|
onToggle: () => void;
|
|
203
215
|
readOnly: boolean;
|
|
204
216
|
onVisibilityChanged: () => void;
|
|
217
|
+
onEditYaml: () => void;
|
|
205
218
|
}) {
|
|
206
219
|
const name =
|
|
207
220
|
environment.metadata?.name || environment.metadata?.slug || "Unnamed";
|
|
@@ -249,6 +262,17 @@ function EnvironmentCard({
|
|
|
249
262
|
</div>
|
|
250
263
|
</button>
|
|
251
264
|
|
|
265
|
+
{!readOnly && (
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
onClick={onEditYaml}
|
|
269
|
+
aria-label={`Edit ${name} as YAML`}
|
|
270
|
+
className="shrink-0 rounded px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
271
|
+
>
|
|
272
|
+
Edit YAML
|
|
273
|
+
</button>
|
|
274
|
+
)}
|
|
275
|
+
|
|
252
276
|
{showVisibilityControl && (
|
|
253
277
|
<ResourceVisibilityControl
|
|
254
278
|
kind="environment"
|
|
@@ -119,12 +119,20 @@ describe("useArtifactInspection — detection + apply", () => {
|
|
|
119
119
|
|
|
120
120
|
it("applies a detected Agent and fires onApplied with the result", async () => {
|
|
121
121
|
const getArtifactContent = vi.fn().mockResolvedValue(contentResult(AGENT_YAML));
|
|
122
|
+
// The apply path routes through the kind-agnostic manifest engine:
|
|
123
|
+
// the hook parses the YAML into a ManifestDocument and hands it to
|
|
124
|
+
// stigmer.manifest.apply.
|
|
122
125
|
const apply = vi.fn().mockResolvedValue({
|
|
123
|
-
|
|
126
|
+
yamlKind: "Agent",
|
|
127
|
+
displayName: "Agent",
|
|
128
|
+
name: "my-agent",
|
|
129
|
+
org: "acme",
|
|
130
|
+
slug: "my-agent",
|
|
131
|
+
id: "agt_01",
|
|
124
132
|
});
|
|
125
133
|
const stigmer = {
|
|
126
134
|
agentExecution: { getArtifactContent },
|
|
127
|
-
|
|
135
|
+
manifest: { apply },
|
|
128
136
|
} as unknown as Stigmer;
|
|
129
137
|
const onApplied = vi.fn();
|
|
130
138
|
|
|
@@ -144,6 +152,8 @@ describe("useArtifactInspection — detection + apply", () => {
|
|
|
144
152
|
|
|
145
153
|
expect(apply).toHaveBeenCalledTimes(1);
|
|
146
154
|
expect(apply.mock.calls[0][0].name).toBe("my-agent");
|
|
155
|
+
expect(apply.mock.calls[0][0].handler.yamlKind).toBe("Agent");
|
|
156
|
+
expect(apply.mock.calls[0][0].org).toBe("acme");
|
|
147
157
|
await waitFor(() => expect(result.current.applyResult?.kind).toBe("Agent"));
|
|
148
158
|
expect(onApplied).toHaveBeenCalledTimes(1);
|
|
149
159
|
expect(onApplied.mock.calls[0][0].name).toBe("my-agent");
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { StigmerContext } from "../../context";
|
|
5
|
+
import {
|
|
6
|
+
useCheckPermission,
|
|
7
|
+
type CheckPermissionOptions,
|
|
8
|
+
type PermissionCheckResource,
|
|
9
|
+
} from "../useCheckPermission";
|
|
10
|
+
|
|
11
|
+
function createMockStigmer(checkMyPermission: ReturnType<typeof vi.fn>) {
|
|
12
|
+
return { iamPolicy: { checkMyPermission } } as never;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function wrapper(client: unknown) {
|
|
16
|
+
return function Wrapper({ children }: { children: ReactNode }) {
|
|
17
|
+
return (
|
|
18
|
+
<StigmerContext.Provider value={client as never}>
|
|
19
|
+
{children}
|
|
20
|
+
</StigmerContext.Provider>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Renders the hook and exposes its state via data attributes. */
|
|
26
|
+
function Probe({
|
|
27
|
+
resource,
|
|
28
|
+
relation = "can_edit",
|
|
29
|
+
options,
|
|
30
|
+
}: {
|
|
31
|
+
resource: PermissionCheckResource | null;
|
|
32
|
+
relation?: string;
|
|
33
|
+
options?: CheckPermissionOptions;
|
|
34
|
+
}) {
|
|
35
|
+
const { allowed, isLoading, error } = useCheckPermission(
|
|
36
|
+
resource,
|
|
37
|
+
relation,
|
|
38
|
+
options,
|
|
39
|
+
);
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
data-testid="probe"
|
|
43
|
+
data-allowed={String(allowed)}
|
|
44
|
+
data-loading={String(isLoading)}
|
|
45
|
+
data-error={String(error !== null)}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const AGENT: PermissionCheckResource = { kind: "agent", id: "agt_1" };
|
|
51
|
+
|
|
52
|
+
/** A checkMyPermission stub that stays pending until resolved manually. */
|
|
53
|
+
function pendingCheck() {
|
|
54
|
+
let resolve!: (value: { isAuthorized: boolean }) => void;
|
|
55
|
+
let reject!: (err: unknown) => void;
|
|
56
|
+
const check = vi.fn().mockReturnValue(
|
|
57
|
+
new Promise((res, rej) => {
|
|
58
|
+
resolve = res;
|
|
59
|
+
reject = rej;
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
return { check, resolve: (v: boolean) => resolve({ isAuthorized: v }), reject: (e: unknown) => reject(e) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
afterEach(cleanup);
|
|
66
|
+
|
|
67
|
+
describe("useCheckPermission — fail-open (default)", () => {
|
|
68
|
+
it("is allowed while the check is in flight", async () => {
|
|
69
|
+
const { check } = pendingCheck();
|
|
70
|
+
render(<Probe resource={AGENT} />, {
|
|
71
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const probe = screen.getByTestId("probe");
|
|
75
|
+
expect(probe.getAttribute("data-allowed")).toBe("true");
|
|
76
|
+
await waitFor(() =>
|
|
77
|
+
expect(probe.getAttribute("data-loading")).toBe("true"),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("respects an explicit server denial", async () => {
|
|
82
|
+
const check = vi.fn().mockResolvedValue({ isAuthorized: false });
|
|
83
|
+
render(<Probe resource={AGENT} />, {
|
|
84
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await waitFor(() =>
|
|
88
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("false"),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("respects an explicit server grant", async () => {
|
|
93
|
+
const check = vi.fn().mockResolvedValue({ isAuthorized: true });
|
|
94
|
+
render(<Probe resource={AGENT} />, {
|
|
95
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const probe = screen.getByTestId("probe");
|
|
99
|
+
await waitFor(() => expect(probe.getAttribute("data-loading")).toBe("false"));
|
|
100
|
+
expect(probe.getAttribute("data-allowed")).toBe("true");
|
|
101
|
+
expect(probe.getAttribute("data-error")).toBe("false");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("fails open on RPC rejection (OSS single-user mode)", async () => {
|
|
105
|
+
const check = vi.fn().mockRejectedValue(new Error("unimplemented"));
|
|
106
|
+
render(<Probe resource={AGENT} />, {
|
|
107
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const probe = screen.getByTestId("probe");
|
|
111
|
+
await waitFor(() => expect(probe.getAttribute("data-error")).toBe("true"));
|
|
112
|
+
expect(probe.getAttribute("data-allowed")).toBe("true");
|
|
113
|
+
expect(probe.getAttribute("data-loading")).toBe("false");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("is allowed when resource is null (check skipped, no RPC)", () => {
|
|
117
|
+
const check = vi.fn();
|
|
118
|
+
render(<Probe resource={null} />, {
|
|
119
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("true");
|
|
123
|
+
expect(check).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("useCheckPermission — fail-closed", () => {
|
|
128
|
+
const CLOSED: CheckPermissionOptions = { fail: "closed" };
|
|
129
|
+
|
|
130
|
+
it("is denied while the check is in flight (no flash of gated UI)", async () => {
|
|
131
|
+
const { check } = pendingCheck();
|
|
132
|
+
render(<Probe resource={AGENT} options={CLOSED} />, {
|
|
133
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const probe = screen.getByTestId("probe");
|
|
137
|
+
expect(probe.getAttribute("data-allowed")).toBe("false");
|
|
138
|
+
await waitFor(() =>
|
|
139
|
+
expect(probe.getAttribute("data-loading")).toBe("true"),
|
|
140
|
+
);
|
|
141
|
+
expect(probe.getAttribute("data-allowed")).toBe("false");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("is allowed only on an explicit server grant", async () => {
|
|
145
|
+
const check = vi.fn().mockResolvedValue({ isAuthorized: true });
|
|
146
|
+
render(<Probe resource={AGENT} options={CLOSED} />, {
|
|
147
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await waitFor(() =>
|
|
151
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("true"),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("fails closed on RPC rejection", async () => {
|
|
156
|
+
const check = vi.fn().mockRejectedValue(new Error("unimplemented"));
|
|
157
|
+
render(<Probe resource={AGENT} options={CLOSED} />, {
|
|
158
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const probe = screen.getByTestId("probe");
|
|
162
|
+
await waitFor(() => expect(probe.getAttribute("data-error")).toBe("true"));
|
|
163
|
+
expect(probe.getAttribute("data-allowed")).toBe("false");
|
|
164
|
+
expect(probe.getAttribute("data-loading")).toBe("false");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("is denied when resource is null (check skipped)", () => {
|
|
168
|
+
const check = vi.fn();
|
|
169
|
+
render(<Probe resource={null} options={CLOSED} />, {
|
|
170
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("false");
|
|
174
|
+
expect(check).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("useCheckPermission — caching", () => {
|
|
179
|
+
it("serves repeat checks of the same triple from the mount cache", async () => {
|
|
180
|
+
const check = vi.fn().mockResolvedValue({ isAuthorized: true });
|
|
181
|
+
const { rerender } = render(<Probe resource={AGENT} />, {
|
|
182
|
+
wrapper: wrapper(createMockStigmer(check)),
|
|
183
|
+
});
|
|
184
|
+
await waitFor(() =>
|
|
185
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("true"),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Switch relation (new triple → one more RPC), then switch back
|
|
189
|
+
// (cached triple → served without an RPC).
|
|
190
|
+
rerender(<Probe resource={AGENT} relation="can_delete" />);
|
|
191
|
+
await waitFor(() => expect(check).toHaveBeenCalledTimes(2));
|
|
192
|
+
rerender(<Probe resource={AGENT} relation="can_edit" />);
|
|
193
|
+
await waitFor(() =>
|
|
194
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("true"),
|
|
195
|
+
);
|
|
196
|
+
expect(check).toHaveBeenCalledTimes(2);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("does not cache a failed check as a verdict", async () => {
|
|
200
|
+
// First mount errors; the error must not be pinned as `true` — a
|
|
201
|
+
// remount (fresh cache) with a healthy server gets the real verdict.
|
|
202
|
+
const failing = vi.fn().mockRejectedValue(new Error("blip"));
|
|
203
|
+
const { unmount } = render(
|
|
204
|
+
<Probe resource={AGENT} options={{ fail: "closed" }} />,
|
|
205
|
+
{ wrapper: wrapper(createMockStigmer(failing)) },
|
|
206
|
+
);
|
|
207
|
+
await waitFor(() =>
|
|
208
|
+
expect(screen.getByTestId("probe").getAttribute("data-error")).toBe("true"),
|
|
209
|
+
);
|
|
210
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("false");
|
|
211
|
+
unmount();
|
|
212
|
+
|
|
213
|
+
const healthy = vi.fn().mockResolvedValue({ isAuthorized: true });
|
|
214
|
+
render(<Probe resource={AGENT} options={{ fail: "closed" }} />, {
|
|
215
|
+
wrapper: wrapper(createMockStigmer(healthy)),
|
|
216
|
+
});
|
|
217
|
+
await waitFor(() =>
|
|
218
|
+
expect(screen.getByTestId("probe").getAttribute("data-allowed")).toBe("true"),
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
package/src/iam-policy/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect,
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { create } from "@bufbuild/protobuf";
|
|
5
5
|
import {
|
|
6
6
|
CheckMyPermissionInputSchema,
|
|
@@ -18,6 +18,25 @@ export interface PermissionCheckResource {
|
|
|
18
18
|
readonly id: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Options for {@link useCheckPermission}. */
|
|
22
|
+
export interface CheckPermissionOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Behavior when authorization cannot be confirmed — the check is in
|
|
25
|
+
* flight, the RPC failed, the resource is `null`, or the server does
|
|
26
|
+
* not implement authorization checks (OSS edition).
|
|
27
|
+
*
|
|
28
|
+
* - `"open"` (default): `allowed` is `true`. Right for gating
|
|
29
|
+
* *capabilities* (buttons, actions) — the server re-checks every
|
|
30
|
+
* request anyway, and all UI stays visible in single-user local
|
|
31
|
+
* mode.
|
|
32
|
+
* - `"closed"`: `allowed` is `false` until the server explicitly
|
|
33
|
+
* authorizes. Right for *discoverability* surfaces (navigation to
|
|
34
|
+
* operator-only areas) that must not appear on deployments where
|
|
35
|
+
* the feature does not exist.
|
|
36
|
+
*/
|
|
37
|
+
readonly fail?: "open" | "closed";
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
/** Return value of {@link useCheckPermission}. */
|
|
22
41
|
export interface UseCheckPermissionReturn {
|
|
23
42
|
/** Whether the current user has the specified permission. */
|
|
@@ -34,21 +53,24 @@ export interface UseCheckPermissionReturn {
|
|
|
34
53
|
*
|
|
35
54
|
* Wraps `iamPolicy.checkMyPermission()` — the dedicated self-check RPC
|
|
36
55
|
* where the server derives the principal from the authenticated token
|
|
37
|
-
* (the client never names a principal) — with caching and
|
|
38
|
-
* degradation.
|
|
39
|
-
* (OSS edition where the IAM service
|
|
40
|
-
* returns `allowed: true`
|
|
41
|
-
* local mode.
|
|
56
|
+
* (the client never names a principal) — with caching and configurable
|
|
57
|
+
* degradation. By default the hook *fails open*: when the server does
|
|
58
|
+
* not support authorization checks (OSS edition where the IAM service
|
|
59
|
+
* is not registered), it returns `allowed: true` so all UI remains
|
|
60
|
+
* visible in single-user local mode. Pass `{ fail: "closed" }` for
|
|
61
|
+
* surfaces that must stay hidden until authorization is confirmed
|
|
62
|
+
* (see {@link CheckPermissionOptions}).
|
|
42
63
|
*
|
|
43
|
-
* Pass `null` as `resource` to skip the check
|
|
44
|
-
*
|
|
45
|
-
* flashing hidden UI).
|
|
64
|
+
* Pass `null` as `resource` to skip the check — the result resolves
|
|
65
|
+
* through the fail mode (`allowed: true` under the default fail-open).
|
|
46
66
|
*
|
|
47
|
-
*
|
|
48
|
-
* of the component mount.
|
|
67
|
+
* Only genuine server verdicts are cached (per (kind, id, relation)
|
|
68
|
+
* triple, for the lifetime of the component mount). A failed check is
|
|
69
|
+
* never cached, so a transient error does not pin a wrong answer.
|
|
49
70
|
*
|
|
50
71
|
* @param resource - The resource to check, or `null` to skip.
|
|
51
72
|
* @param relation - The permission to check (e.g. "can_edit", "can_grant_access").
|
|
73
|
+
* @param options - Fail-mode configuration; see {@link CheckPermissionOptions}.
|
|
52
74
|
*
|
|
53
75
|
* @example
|
|
54
76
|
* ```tsx
|
|
@@ -63,9 +85,13 @@ export interface UseCheckPermissionReturn {
|
|
|
63
85
|
export function useCheckPermission(
|
|
64
86
|
resource: PermissionCheckResource | null,
|
|
65
87
|
relation: string,
|
|
88
|
+
options?: CheckPermissionOptions,
|
|
66
89
|
): UseCheckPermissionReturn {
|
|
67
90
|
const stigmer = useStigmer();
|
|
68
|
-
const
|
|
91
|
+
const failMode = options?.fail ?? "open";
|
|
92
|
+
const failValue = failMode === "open";
|
|
93
|
+
|
|
94
|
+
const [allowed, setAllowed] = useState(failValue);
|
|
69
95
|
const [isLoading, setIsLoading] = useState(false);
|
|
70
96
|
const [error, setError] = useState<Error | null>(null);
|
|
71
97
|
|
|
@@ -74,7 +100,7 @@ export function useCheckPermission(
|
|
|
74
100
|
|
|
75
101
|
useEffect(() => {
|
|
76
102
|
if (!resource || !cacheKey) {
|
|
77
|
-
setAllowed(
|
|
103
|
+
setAllowed(failValue);
|
|
78
104
|
setIsLoading(false);
|
|
79
105
|
return;
|
|
80
106
|
}
|
|
@@ -87,6 +113,7 @@ export function useCheckPermission(
|
|
|
87
113
|
}
|
|
88
114
|
|
|
89
115
|
let cancelled = false;
|
|
116
|
+
setAllowed(failValue);
|
|
90
117
|
setIsLoading(true);
|
|
91
118
|
|
|
92
119
|
const input = create(CheckMyPermissionInputSchema, {
|
|
@@ -108,11 +135,10 @@ export function useCheckPermission(
|
|
|
108
135
|
})
|
|
109
136
|
.catch((err: unknown) => {
|
|
110
137
|
if (cancelled) return;
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
setAllowed(true);
|
|
138
|
+
// Degradation is fail-mode-resolved and deliberately NOT cached:
|
|
139
|
+
// an error is not an authorization verdict, and caching it would
|
|
140
|
+
// pin a possibly-wrong answer for the mount's lifetime.
|
|
141
|
+
setAllowed(failValue);
|
|
116
142
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
117
143
|
})
|
|
118
144
|
.finally(() => {
|
|
@@ -122,7 +148,7 @@ export function useCheckPermission(
|
|
|
122
148
|
return () => {
|
|
123
149
|
cancelled = true;
|
|
124
150
|
};
|
|
125
|
-
}, [cacheKey, resource?.kind, resource?.id, relation, stigmer]);
|
|
151
|
+
}, [cacheKey, resource?.kind, resource?.id, relation, failValue, stigmer]);
|
|
126
152
|
|
|
127
153
|
return { allowed, isLoading, error };
|
|
128
154
|
}
|