generated from coulomb/repo-seed
feat: add vsm hub metadata
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
This commit is contained in:
21
Application/Migration/1744588800-vsm-hub-metadata.sql
Normal file
21
Application/Migration/1744588800-vsm-hub-metadata.sql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-- IHUB-WP-0019 T03 - first-class VSM hub metadata
|
||||||
|
|
||||||
|
ALTER TABLE hubs
|
||||||
|
ADD COLUMN hub_family TEXT,
|
||||||
|
ADD COLUMN vsm_function TEXT,
|
||||||
|
ADD COLUMN vsm_system TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE hubs
|
||||||
|
ADD CONSTRAINT hubs_vsm_metadata_consistency CHECK (
|
||||||
|
(hub_family IS NULL AND vsm_function IS NULL AND vsm_system IS NULL)
|
||||||
|
OR (
|
||||||
|
hub_family = 'vsm'
|
||||||
|
AND vsm_function IS NOT NULL
|
||||||
|
AND vsm_function <> ''
|
||||||
|
AND vsm_system IN ('1', '2', '3', '3*', '4', '5', 'environment')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX hubs_hub_family_idx ON hubs (hub_family);
|
||||||
|
CREATE INDEX hubs_vsm_system_idx ON hubs (vsm_system)
|
||||||
|
WHERE vsm_system IS NOT NULL;
|
||||||
@@ -25,7 +25,19 @@ CREATE TABLE hubs (
|
|||||||
domain TEXT NOT NULL,
|
domain TEXT NOT NULL,
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||||||
api_key TEXT,
|
api_key TEXT,
|
||||||
hub_kind TEXT NOT NULL DEFAULT 'domain'
|
hub_kind TEXT NOT NULL DEFAULT 'domain',
|
||||||
|
hub_family TEXT,
|
||||||
|
vsm_function TEXT,
|
||||||
|
vsm_system TEXT,
|
||||||
|
CONSTRAINT hubs_vsm_metadata_consistency CHECK (
|
||||||
|
(hub_family IS NULL AND vsm_function IS NULL AND vsm_system IS NULL)
|
||||||
|
OR (
|
||||||
|
hub_family = 'vsm'
|
||||||
|
AND vsm_function IS NOT NULL
|
||||||
|
AND vsm_function <> ''
|
||||||
|
AND vsm_system IN ('1', '2', '3', '3*', '4', '5', 'environment')
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Widgets — smallest semantically governable interaction units
|
-- Widgets — smallest semantically governable interaction units
|
||||||
@@ -557,6 +569,11 @@ CREATE INDEX hubs_hub_kind_idx ON hubs (hub_kind);
|
|||||||
CREATE UNIQUE INDEX hubs_one_framework_idx ON hubs (hub_kind)
|
CREATE UNIQUE INDEX hubs_one_framework_idx ON hubs (hub_kind)
|
||||||
WHERE hub_kind = 'framework';
|
WHERE hub_kind = 'framework';
|
||||||
|
|
||||||
|
-- IHUB-WP-0019 T03 — first-class VSM hub metadata
|
||||||
|
CREATE INDEX hubs_hub_family_idx ON hubs (hub_family);
|
||||||
|
CREATE INDEX hubs_vsm_system_idx ON hubs (vsm_system)
|
||||||
|
WHERE vsm_system IS NOT NULL;
|
||||||
|
|
||||||
-- T03 — Type registries
|
-- T03 — Type registries
|
||||||
|
|
||||||
CREATE TABLE widget_type_registry (
|
CREATE TABLE widget_type_registry (
|
||||||
|
|||||||
16
Test/Main.hs
16
Test/Main.hs
@@ -8,7 +8,9 @@ import Web.Controller.Api.V2.InteractionEvents
|
|||||||
( declaredEventTypeNames, manifestAllowsEvent, metadataFromJsonBody
|
( declaredEventTypeNames, manifestAllowsEvent, metadataFromJsonBody
|
||||||
, metadataParamOrEmpty
|
, metadataParamOrEmpty
|
||||||
)
|
)
|
||||||
import Web.Controller.Api.V2.Hubs (missingRequiredFields, validCreateHubKind)
|
import Web.Controller.Api.V2.Hubs
|
||||||
|
( missingRequiredFields, validCreateHubKind, validVsmMetadata
|
||||||
|
, validVsmSystem )
|
||||||
import Web.Controller.Api.V2.HubCapabilityManifests
|
import Web.Controller.Api.V2.HubCapabilityManifests
|
||||||
( jsonArrayTexts, textArrayFieldFromJsonBody )
|
( jsonArrayTexts, textArrayFieldFromJsonBody )
|
||||||
import Web.Controller.Api.V2.ApiConsumers (positiveLimit)
|
import Web.Controller.Api.V2.ApiConsumers (positiveLimit)
|
||||||
@@ -63,6 +65,18 @@ main = hspec do
|
|||||||
]
|
]
|
||||||
`shouldBe` ["slug", "name"]
|
`shouldBe` ["slug", "name"]
|
||||||
|
|
||||||
|
it "accepts complete VSM hub classification for ops-hub" do
|
||||||
|
validVsmMetadata (Just "vsm") (Just "operations") (Just "1")
|
||||||
|
`shouldBe` True
|
||||||
|
validVsmSystem "1" `shouldBe` True
|
||||||
|
validVsmSystem "6" `shouldBe` False
|
||||||
|
|
||||||
|
it "rejects partial VSM metadata" do
|
||||||
|
validVsmMetadata (Just "vsm") (Just "operations") Nothing
|
||||||
|
`shouldBe` False
|
||||||
|
validVsmMetadata Nothing (Just "operations") (Just "1")
|
||||||
|
`shouldBe` False
|
||||||
|
|
||||||
it "accepts widget statuses supported by the UI create flow" do
|
it "accepts widget statuses supported by the UI create flow" do
|
||||||
validWidgetStatus "active" `shouldBe` True
|
validWidgetStatus "active" `shouldBe` True
|
||||||
validWidgetStatus "deprecated" `shouldBe` True
|
validWidgetStatus "deprecated" `shouldBe` True
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ hubDetailJson hub mManifest mSnapshot =
|
|||||||
, "slug" .= hub.slug
|
, "slug" .= hub.slug
|
||||||
, "domain" .= hub.domain
|
, "domain" .= hub.domain
|
||||||
, "hubKind" .= hub.hubKind
|
, "hubKind" .= hub.hubKind
|
||||||
|
, "hubFamily" .= hub.hubFamily
|
||||||
|
, "vsmFunction" .= hub.vsmFunction
|
||||||
|
, "vsmSystem" .= hub.vsmSystem
|
||||||
, "gaafStatus" .= gaafIndicator
|
, "gaafStatus" .= gaafIndicator
|
||||||
, "manifest" .= fmap manifestSummary mManifest
|
, "manifest" .= fmap manifestSummary mManifest
|
||||||
, "healthScore" .= fmap (.healthScore) mSnapshot
|
, "healthScore" .= fmap (.healthScore) mSnapshot
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ createHub = do
|
|||||||
name = paramOrNothing @Text "name"
|
name = paramOrNothing @Text "name"
|
||||||
domain = paramOrNothing @Text "domain"
|
domain = paramOrNothing @Text "domain"
|
||||||
kind = fromMaybe "domain" (nonEmptyText =<< paramOrNothing @Text "hubKind")
|
kind = fromMaybe "domain" (nonEmptyText =<< paramOrNothing @Text "hubKind")
|
||||||
|
hubFamily = nonEmptyText =<< paramOrNothing @Text "hubFamily"
|
||||||
|
vsmFunction = nonEmptyText =<< paramOrNothing @Text "vsmFunction"
|
||||||
|
vsmSystem = nonEmptyText =<< paramOrNothing @Text "vsmSystem"
|
||||||
|
|
||||||
let missing = missingRequiredFields
|
let missing = missingRequiredFields
|
||||||
[ ("slug", slug)
|
[ ("slug", slug)
|
||||||
@@ -65,6 +68,14 @@ createHub = do
|
|||||||
, "valid" .= validCreateHubKinds
|
, "valid" .= validCreateHubKinds
|
||||||
]
|
]
|
||||||
|
|
||||||
|
unless (validVsmMetadata hubFamily vsmFunction vsmSystem) do
|
||||||
|
respondWithStatus 422 $ object
|
||||||
|
[ "error" .= ("Invalid VSM hub metadata" :: Text)
|
||||||
|
, "code" .= ("invalid_vsm_metadata" :: Text)
|
||||||
|
, "hint" .= ("Use no VSM fields, or set hubFamily=vsm with vsmFunction and vsmSystem." :: Text)
|
||||||
|
, "validVsmSystems" .= validVsmSystems
|
||||||
|
]
|
||||||
|
|
||||||
let Just slugText = slug
|
let Just slugText = slug
|
||||||
Just nameText = name
|
Just nameText = name
|
||||||
Just domainText = domain
|
Just domainText = domain
|
||||||
@@ -84,6 +95,9 @@ createHub = do
|
|||||||
|> set #name nameText
|
|> set #name nameText
|
||||||
|> set #domain domainText
|
|> set #domain domainText
|
||||||
|> set #hubKind kind
|
|> set #hubKind kind
|
||||||
|
|> set #hubFamily hubFamily
|
||||||
|
|> set #vsmFunction vsmFunction
|
||||||
|
|> set #vsmSystem vsmSystem
|
||||||
|> createRecord
|
|> createRecord
|
||||||
respondWithStatus 201 (hubToJson hub)
|
respondWithStatus 201 (hubToJson hub)
|
||||||
|
|
||||||
@@ -94,6 +108,9 @@ hubToJson hub = object
|
|||||||
, "name" .= hub.name
|
, "name" .= hub.name
|
||||||
, "domain" .= hub.domain
|
, "domain" .= hub.domain
|
||||||
, "hubKind" .= hub.hubKind
|
, "hubKind" .= hub.hubKind
|
||||||
|
, "hubFamily" .= hub.hubFamily
|
||||||
|
, "vsmFunction" .= hub.vsmFunction
|
||||||
|
, "vsmSystem" .= hub.vsmSystem
|
||||||
, "createdAt" .= hub.createdAt
|
, "createdAt" .= hub.createdAt
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -103,6 +120,18 @@ validCreateHubKinds = ["domain", "shared"]
|
|||||||
validCreateHubKind :: Text -> Bool
|
validCreateHubKind :: Text -> Bool
|
||||||
validCreateHubKind kind = kind `elem` validCreateHubKinds
|
validCreateHubKind kind = kind `elem` validCreateHubKinds
|
||||||
|
|
||||||
|
validVsmSystems :: [Text]
|
||||||
|
validVsmSystems = ["1", "2", "3", "3*", "4", "5", "environment"]
|
||||||
|
|
||||||
|
validVsmSystem :: Text -> Bool
|
||||||
|
validVsmSystem systemName = systemName `elem` validVsmSystems
|
||||||
|
|
||||||
|
validVsmMetadata :: Maybe Text -> Maybe Text -> Maybe Text -> Bool
|
||||||
|
validVsmMetadata Nothing Nothing Nothing = True
|
||||||
|
validVsmMetadata (Just "vsm") (Just functionName) (Just systemName) =
|
||||||
|
functionName /= "" && validVsmSystem systemName
|
||||||
|
validVsmMetadata _ _ _ = False
|
||||||
|
|
||||||
missingRequiredFields :: [(Text, Maybe Text)] -> [Text]
|
missingRequiredFields :: [(Text, Maybe Text)] -> [Text]
|
||||||
missingRequiredFields fields =
|
missingRequiredFields fields =
|
||||||
[ name | (name, value) <- fields, maybe True (== "") value ]
|
[ name | (name, value) <- fields, maybe True (== "") value ]
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ buildOpenApiSpec = do
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, "Hub" .= hubSchema
|
, "Hub" .= hubSchema
|
||||||
|
, "CreateHubRequest" .= createHubRequestSchema
|
||||||
, "HubCapabilityManifest" .= manifestSchema
|
, "HubCapabilityManifest" .= manifestSchema
|
||||||
, "ApiConsumer" .= apiConsumerSchema
|
, "ApiConsumer" .= apiConsumerSchema
|
||||||
, "ApiKey" .= apiKeySchema
|
, "ApiKey" .= apiKeySchema
|
||||||
@@ -320,10 +321,28 @@ hubSchema = object
|
|||||||
, "name" .= strProp
|
, "name" .= strProp
|
||||||
, "domain" .= strProp
|
, "domain" .= strProp
|
||||||
, "hubKind" .= object ["type" .= ("string" :: Text), "enum" .= ["domain" :: Text, "shared"]]
|
, "hubKind" .= object ["type" .= ("string" :: Text), "enum" .= ["domain" :: Text, "shared"]]
|
||||||
|
, "hubFamily" .= object ["type" .= ("string" :: Text), "enum" .= ["vsm" :: Text]]
|
||||||
|
, "vsmFunction" .= strProp
|
||||||
|
, "vsmSystem" .= object ["type" .= ("string" :: Text), "enum" .= ["1" :: Text, "2", "3", "3*", "4", "5", "environment"]]
|
||||||
, "createdAt" .= object ["type" .= ("string" :: Text), "format" .= ("date-time" :: Text)]
|
, "createdAt" .= object ["type" .= ("string" :: Text), "format" .= ("date-time" :: Text)]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
createHubRequestSchema :: Value
|
||||||
|
createHubRequestSchema = object
|
||||||
|
[ "type" .= ("object" :: Text)
|
||||||
|
, "required" .= (["slug", "name", "domain"] :: [Text])
|
||||||
|
, "properties" .= object
|
||||||
|
[ "slug" .= strProp
|
||||||
|
, "name" .= strProp
|
||||||
|
, "domain" .= strProp
|
||||||
|
, "hubKind" .= object ["type" .= ("string" :: Text), "enum" .= ["domain" :: Text, "shared"]]
|
||||||
|
, "hubFamily" .= object ["type" .= ("string" :: Text), "enum" .= ["vsm" :: Text]]
|
||||||
|
, "vsmFunction" .= strProp
|
||||||
|
, "vsmSystem" .= object ["type" .= ("string" :: Text), "enum" .= ["1" :: Text, "2", "3", "3*", "4", "5", "environment"]]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
widgetSchema :: Value
|
widgetSchema :: Value
|
||||||
widgetSchema = object
|
widgetSchema = object
|
||||||
[ "type" .= ("object" :: Text)
|
[ "type" .= ("object" :: Text)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ tsSdkClientClass = T.unlines
|
|||||||
, " });"
|
, " });"
|
||||||
, " }"
|
, " }"
|
||||||
, ""
|
, ""
|
||||||
, " async createHub(body: { slug: string; name: string; domain: string; hubKind?: 'domain' | 'shared' }) {"
|
, " async createHub(body: { slug: string; name: string; domain: string; hubKind?: 'domain' | 'shared'; hubFamily?: 'vsm'; vsmFunction?: string; vsmSystem?: '1' | '2' | '3' | '3*' | '4' | '5' | 'environment' }) {"
|
||||||
, " return this.fetch('/hubs', 'POST', body).then(r => r.json());"
|
, " return this.fetch('/hubs', 'POST', body).then(r => r.json());"
|
||||||
, " }"
|
, " }"
|
||||||
, ""
|
, ""
|
||||||
@@ -177,8 +177,12 @@ pyClientClass = T.unlines
|
|||||||
, " with urllib.request.urlopen(req) as resp:"
|
, " with urllib.request.urlopen(req) as resp:"
|
||||||
, " return json.loads(resp.read())"
|
, " return json.loads(resp.read())"
|
||||||
, ""
|
, ""
|
||||||
, " def create_hub(self, slug: str, name: str, domain: str, hub_kind: str = 'domain') -> dict:"
|
, " def create_hub(self, slug: str, name: str, domain: str, hub_kind: str = 'domain', hub_family: Optional[str] = None, vsm_function: Optional[str] = None, vsm_system: Optional[str] = None) -> dict:"
|
||||||
, " return self._request('/hubs', 'POST', {'slug': slug, 'name': name, 'domain': domain, 'hubKind': hub_kind})"
|
, " body: dict = {'slug': slug, 'name': name, 'domain': domain, 'hubKind': hub_kind}"
|
||||||
|
, " if hub_family: body['hubFamily'] = hub_family"
|
||||||
|
, " if vsm_function: body['vsmFunction'] = vsm_function"
|
||||||
|
, " if vsm_system: body['vsmSystem'] = vsm_system"
|
||||||
|
, " return self._request('/hubs', 'POST', body)"
|
||||||
, ""
|
, ""
|
||||||
, " def create_hub_capability_manifest(self, body: dict) -> dict:"
|
, " def create_hub_capability_manifest(self, body: dict) -> dict:"
|
||||||
, " return self._request('/hub-capability-manifests', 'POST', body)"
|
, " return self._request('/hub-capability-manifests', 'POST', body)"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ renderRow row@HubRegistryRow { hub, mManifest, mLatestSnapshot } =
|
|||||||
{hub.name}
|
{hub.name}
|
||||||
</a>
|
</a>
|
||||||
<span class="text-xs text-gray-400 font-mono">{hub.hubKind}</span>
|
<span class="text-xs text-gray-400 font-mono">{hub.hubKind}</span>
|
||||||
|
{classificationBadge hub}
|
||||||
{gaafBadge gs}
|
{gaafBadge gs}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 text-xs text-gray-500">
|
<div class="flex items-center gap-4 text-xs text-gray-500">
|
||||||
@@ -74,6 +75,17 @@ gaafBadge GaafDraftOnly =
|
|||||||
gaafBadge GaafNoManifest =
|
gaafBadge GaafNoManifest =
|
||||||
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-red-100 text-red-700">no manifest</span>|]
|
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-red-100 text-red-700">no manifest</span>|]
|
||||||
|
|
||||||
|
classificationBadge :: Hub -> Html
|
||||||
|
classificationBadge hub =
|
||||||
|
case (hub.hubFamily, hub.vsmFunction, hub.vsmSystem) of
|
||||||
|
(Just "vsm", Just functionName, Just systemName) ->
|
||||||
|
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-800">VSM {functionName} / {vsmSystemLabel systemName}</span>|]
|
||||||
|
_ -> mempty
|
||||||
|
|
||||||
|
vsmSystemLabel :: Text -> Text
|
||||||
|
vsmSystemLabel "environment" = "Environment"
|
||||||
|
vsmSystemLabel systemName = "System " <> systemName
|
||||||
|
|
||||||
healthScoreBadge :: Int -> Html
|
healthScoreBadge :: Int -> Html
|
||||||
healthScoreBadge s =
|
healthScoreBadge s =
|
||||||
let cls :: Text
|
let cls :: Text
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ instance View IndexView where
|
|||||||
<th class="text-left px-4 py-3 font-medium text-gray-700">Slug</th>
|
<th class="text-left px-4 py-3 font-medium text-gray-700">Slug</th>
|
||||||
<th class="text-left px-4 py-3 font-medium text-gray-700">Domain</th>
|
<th class="text-left px-4 py-3 font-medium text-gray-700">Domain</th>
|
||||||
<th class="text-left px-4 py-3 font-medium text-gray-700">Kind</th>
|
<th class="text-left px-4 py-3 font-medium text-gray-700">Kind</th>
|
||||||
|
<th class="text-left px-4 py-3 font-medium text-gray-700">Family</th>
|
||||||
<th class="px-4 py-3"></th>
|
<th class="px-4 py-3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -41,6 +42,17 @@ kindBadge "framework" = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-purple-
|
|||||||
kindBadge "shared" = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-teal-100 text-teal-800">shared</span>|]
|
kindBadge "shared" = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-teal-100 text-teal-800">shared</span>|]
|
||||||
kindBadge _ = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-blue-100 text-blue-800">domain</span>|]
|
kindBadge _ = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-blue-100 text-blue-800">domain</span>|]
|
||||||
|
|
||||||
|
classificationBadge :: Hub -> Html
|
||||||
|
classificationBadge hub =
|
||||||
|
case (hub.hubFamily, hub.vsmFunction, hub.vsmSystem) of
|
||||||
|
(Just "vsm", Just functionName, Just systemName) ->
|
||||||
|
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-800">VSM {functionName} / {vsmSystemLabel systemName}</span>|]
|
||||||
|
_ -> [hsx|<span class="text-xs text-gray-400">-</span>|]
|
||||||
|
|
||||||
|
vsmSystemLabel :: Text -> Text
|
||||||
|
vsmSystemLabel "environment" = "Environment"
|
||||||
|
vsmSystemLabel systemName = "System " <> systemName
|
||||||
|
|
||||||
renderHub :: Hub -> Html
|
renderHub :: Hub -> Html
|
||||||
renderHub hub = [hsx|
|
renderHub hub = [hsx|
|
||||||
<tr class="border-b border-gray-100 hover:bg-gray-50">
|
<tr class="border-b border-gray-100 hover:bg-gray-50">
|
||||||
@@ -53,6 +65,7 @@ renderHub hub = [hsx|
|
|||||||
<td class="px-4 py-3 text-gray-500 font-mono text-xs">{hub.slug}</td>
|
<td class="px-4 py-3 text-gray-500 font-mono text-xs">{hub.slug}</td>
|
||||||
<td class="px-4 py-3 text-gray-500">{hub.domain}</td>
|
<td class="px-4 py-3 text-gray-500">{hub.domain}</td>
|
||||||
<td class="px-4 py-3">{kindBadge hub.hubKind}</td>
|
<td class="px-4 py-3">{kindBadge hub.hubKind}</td>
|
||||||
|
<td class="px-4 py-3">{classificationBadge hub}</td>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right">
|
||||||
<a href={EditHubAction (hub.id)}
|
<a href={EditHubAction (hub.id)}
|
||||||
class="text-gray-500 hover:text-gray-700 text-xs mr-3">Edit</a>
|
class="text-gray-500 hover:text-gray-700 text-xs mr-3">Edit</a>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ instance View ShowView where
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1 class="text-2xl font-semibold">{hub.name}</h1>
|
<h1 class="text-2xl font-semibold">{hub.name}</h1>
|
||||||
{kindBadge hub.hubKind}
|
{kindBadge hub.hubKind}
|
||||||
|
{classificationBadge hub}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 mt-1">
|
<p class="text-sm text-gray-500 mt-1">
|
||||||
<span class="font-mono bg-gray-100 px-1 rounded">{hub.slug}</span>
|
<span class="font-mono bg-gray-100 px-1 rounded">{hub.slug}</span>
|
||||||
@@ -223,6 +224,17 @@ kindBadge "framework" = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-purple-
|
|||||||
kindBadge "shared" = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-teal-100 text-teal-800">shared</span>|]
|
kindBadge "shared" = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-teal-100 text-teal-800">shared</span>|]
|
||||||
kindBadge _ = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-blue-100 text-blue-800">domain</span>|]
|
kindBadge _ = [hsx|<span class="px-2 py-0.5 rounded text-xs bg-blue-100 text-blue-800">domain</span>|]
|
||||||
|
|
||||||
|
classificationBadge :: Hub -> Html
|
||||||
|
classificationBadge hub =
|
||||||
|
case (hub.hubFamily, hub.vsmFunction, hub.vsmSystem) of
|
||||||
|
(Just "vsm", Just functionName, Just systemName) ->
|
||||||
|
[hsx|<span class="px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-800">VSM {functionName} / {vsmSystemLabel systemName}</span>|]
|
||||||
|
_ -> mempty
|
||||||
|
|
||||||
|
vsmSystemLabel :: Text -> Text
|
||||||
|
vsmSystemLabel "environment" = "Environment"
|
||||||
|
vsmSystemLabel systemName = "System " <> systemName
|
||||||
|
|
||||||
maybeText :: Maybe Text -> [Text]
|
maybeText :: Maybe Text -> [Text]
|
||||||
maybeText Nothing = []
|
maybeText Nothing = []
|
||||||
maybeText (Just t) = [t]
|
maybeText (Just t) = [t]
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ shell does not have `IHP_LIB`/the IHP dev environment loaded.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: IHUB-WP-0019-T03
|
id: IHUB-WP-0019-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "a90a0220-3d02-4b97-9fbf-a6bbbfa5019c"
|
state_hub_task_id: "a90a0220-3d02-4b97-9fbf-a6bbbfa5019c"
|
||||||
```
|
```
|
||||||
@@ -168,6 +168,17 @@ Candidate placement:
|
|||||||
Done when: `ops-hub` can be represented as the VSM Operations / System 1 hub
|
Done when: `ops-hub` can be represented as the VSM Operations / System 1 hub
|
||||||
without hiding that classification inside prose.
|
without hiding that classification inside prose.
|
||||||
|
|
||||||
|
Implementation note (2026-05-19): chose new nullable columns on `hubs`
|
||||||
|
(`hub_family`, `vsm_function`, `vsm_system`) because the VSM role is hub
|
||||||
|
identity/classification metadata, not manifest vocabulary. Added migration
|
||||||
|
`1744588800-vsm-hub-metadata.sql`, schema constraints, v2 hub create/list/show
|
||||||
|
JSON, hub registry JSON, compact registry/UI badges, OpenAPI request/response
|
||||||
|
fields, SDK parameters, and validation tests. API validation now accepts either
|
||||||
|
no VSM fields or `hubFamily=vsm` with non-empty `vsmFunction` and a supported
|
||||||
|
`vsmSystem` (`1`, `2`, `3`, `3*`, `4`, `5`, or `environment`). `git diff
|
||||||
|
--check` passed; `scripts/compile-check` is still blocked in this shell because
|
||||||
|
`IHP_LIB` is not set.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T04 — Add API consumer and API key bootstrap support
|
### T04 — Add API consumer and API key bootstrap support
|
||||||
|
|||||||
Reference in New Issue
Block a user