diff --git a/Dockerfile b/Dockerfile index c29b2bd1..c4002ec2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Observogram — studio + API in one Express server (server/index.mjs). # -# Build: npm run build:stamp && docker build -t observogram:0.5.0 . -# Run: docker run --rm -p 8000:8000 -e OBSERVOGRAM_ADMIN_PASSWORD= observogram:0.5.0 +# Build: npm run build:stamp && docker build -t observogram:0.6.0 . +# Run: docker run --rm -p 8000:8000 -e OBSERVOGRAM_ADMIN_PASSWORD= observogram:0.6.0 # (the image binds 0.0.0.0, and the server refuses to start off # loopback without a seeded sign-in or OBSERVOGRAM_API_TOKEN; the # workspace lives at /app/.observogram — mount a volume there or @@ -18,10 +18,10 @@ FROM node:22-alpine # package.json. server/build-info.mjs then answers the studio footer, # GET /api/version and /healthz from that file (source 'file'); without it # they say "build unknown" — never a guess. -# npm run build:stamp && docker build -t observogram:0.5.0 . +# npm run build:stamp && docker build -t observogram:0.6.0 . # OBSERVOGRAM_BUILD, when given, overrides the composite build string on # /healthz only (a CI run number or tag); /api/version stays the stamp. -# docker build --build-arg OBSERVOGRAM_BUILD=ci-412 -t observogram:0.5.0 . +# docker build --build-arg OBSERVOGRAM_BUILD=ci-412 -t observogram:0.6.0 . ARG OBSERVOGRAM_BUILD= ENV OBSERVOGRAM_BUILD=$OBSERVOGRAM_BUILD diff --git a/README.md b/README.md index d4cff73b..9d8293a9 100644 --- a/README.md +++ b/README.md @@ -1219,8 +1219,8 @@ The whole app is one Express process, so the container story is one image: ```bash npm run build:stamp # build.json: the commit the image is built from (the image has no .git) -docker build -t observogram:0.5.0 . -docker run --rm -p 8000:8000 -e OBSERVOGRAM_ADMIN_PASSWORD= observogram:0.5.0 +docker build -t observogram:0.6.0 . +docker run --rm -p 8000:8000 -e OBSERVOGRAM_ADMIN_PASSWORD= observogram:0.6.0 ``` The image binds `0.0.0.0`, so it needs a seeded sign-in (or diff --git a/VENDOR-MANIFEST.json b/VENDOR-MANIFEST.json index 3c79a394..8e34a15c 100644 --- a/VENDOR-MANIFEST.json +++ b/VENDOR-MANIFEST.json @@ -2,7 +2,7 @@ "$comment": "Generated by tools/gen-vendor-manifest.mjs. Do not edit: run `npm run vendor-manifest`. docs/DOWNSTREAM.md explains the fields.", "manifestVersion": 1, "package": "observogram", - "version": "0.5.0", + "version": "0.6.0", "root": "tools/lib", "modules": { "tools/lib/adapter.mjs": { @@ -21,12 +21,7 @@ "tools/lib/validator.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "adapt", - "applyEnvironmentOverlay", - "listEnvironments" - ] + "changedSinceRelease": false }, "tools/lib/alert-routes.mjs": { "sha256": "ab5be6d93f293c69a0896f4805e0dd4fd0836654b52a5afae9b859e881a0c1ef", @@ -75,8 +70,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/artefact-model.mjs": { "sha256": "28f9f2669390b02432f3a85343f336b2c4afd995c05d581d1aee0c66b146a8b6", @@ -158,8 +152,7 @@ "tools/lib/remediation-flow.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/backend-products.mjs": { "sha256": "f63099877b804e06c90520df63afce5e6ca72ee938b6fdda9da2f1ae7631783e", @@ -209,8 +202,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/burn-rules.mjs": { "sha256": "4ae27bf6b1805fea24fb1cee2b71a00e02da6fbcb54417677b79a88c4021664b", @@ -332,12 +324,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "RUBRIC", - "TIER_RANK", - "evaluateConformance" - ] + "changedSinceRelease": false }, "tools/lib/contracts/mcp-capabilities.mjs": { "sha256": "f1d3065a32fb237b6a6602cebc0bd8c7e22c60a5921b24899e55c225e9890e4e", @@ -356,18 +343,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "BUILD_INFO_PROBES", - "CAPABILITIES", - "allKnownToolNames", - "candidateTool", - "capability", - "capabilityTool", - "deprecatedAliases", - "probeCandidates", - "productAttestedByTool" - ] + "changedSinceRelease": false }, "tools/lib/contracts/response-shapes.mjs": { "sha256": "ca83c7f917b48825355222fbfae82ac28a2c5fca7a064dd5b5834e6bae967a1c", @@ -426,18 +402,7 @@ "tools/lib/slug.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "SCAN_EXT", - "SCAN_IGNORE_DIRS", - "SCAN_MAX_FILE_BYTES", - "crawlFiles", - "crawlToYaml", - "detectArtefactKind", - "scanReadsPath", - "scanSkipsName", - "walkScanFolder" - ] + "changedSinceRelease": false }, "tools/lib/dashboards/generic.mjs": { "sha256": "f02f24c9f0d967afa4a0f9dfd0846cceb960b065c772f78eaf21c0cbf151a959", @@ -581,8 +546,7 @@ "tools/lib/artefact-model.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/inventory-coverage.mjs": { "sha256": "075cf2a9c79e98432a043c94596179c52882926ebbf828a5ffb584d07054f476", @@ -657,11 +621,7 @@ "tools/lib/pack-conformance.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "isLegacyLayeredPack", - "upconvertLegacyPack" - ] + "changedSinceRelease": false }, "tools/lib/library.mjs": { "sha256": "90ed9b24bdaba4f8d2174ebabcc07a944b69e6c1ea6b785d27fd279a90139c03", @@ -731,8 +691,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/mcp-client.mjs": { "sha256": "4d3b8c6a2004ea32fb0b8d80102d705641be5db89e844f496de797c57df57c61", @@ -750,8 +709,7 @@ "tools/lib/mcp-url-safety.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/mcp-server-settings.mjs": { "sha256": "02b49b86fdb2baad64d346c298d5bc86f2cc1f03d748537f3c7c0ec54cbe8d27", @@ -779,8 +737,7 @@ "tools/lib/mcp-url-safety.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/mcp-url-safety.mjs": { "sha256": "0869a761a7856f7c851205f127b53c37874cc17741e36b0649bfd9cac7ff0d69", @@ -800,14 +757,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "credentialParamName", - "droppedNote", - "mcpUrlOrigin", - "safeMcpUrl", - "stripMcpUrl" - ] + "changedSinceRelease": false }, "tools/lib/mini-yaml.mjs": { "sha256": "c4a903325dd1119f859fbeac1fa3e17b4104ebc2af08f721387dc7a6be7862fd", @@ -878,8 +828,7 @@ "tools/lib/waivers.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/profiles.mjs": { "sha256": "027f2d9004a0858641baab79192a2a4019f58e608fb7c08380ddbace4de3141f", @@ -978,8 +927,7 @@ "tools/lib/artefact-model.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/schedule-snippets.mjs": { "sha256": "a7eac2632b83b511100d37b1c80801ff9cc68880a0458c212e2e54ed1dc6e4ce", @@ -1036,17 +984,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "catalogEntryOf", - "isLiveAggregatePack", - "normalizeServiceKey", - "primaryServiceName", - "serviceKeyForPack", - "serviceMetadata", - "serviceNamesForPack", - "servicesForPack" - ] + "changedSinceRelease": false }, "tools/lib/site/derive.mjs": { "sha256": "afc9f35d08440637eca96c26ef21ed943e659c1daa3a0e0b4ef40f4b96cf4ab6", @@ -1170,23 +1108,7 @@ "tools/lib/slug.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "alertRuleExpr", - "alertRuleWindows", - "burnAlertsFromAlertRules", - "burnCandidateFromAlertRule", - "canonicalRuleDuration", - "defaultBurnWindows", - "inferSlisFromRecordingRules", - "isGrafanaManagedRule", - "mergeBurnAlertsBySlo", - "operationalAlertRule", - "operationalAlertRules", - "recordedSloForExpr", - "ruleNameToSliId", - "ruleNameToSloId" - ] + "changedSinceRelease": false }, "tools/lib/slug.mjs": { "sha256": "bf9f4c4816363d65c66203e4f7fdbc061b9a144f909545278b7192b4ec903c51", @@ -1200,13 +1122,7 @@ ], "imports": [], "npm": [], - "changedSinceRelease": true, - "releasedExports": [ - "fileSlug", - "metricPrefix", - "serviceSlug", - "symbolSlug" - ] + "changedSinceRelease": false }, "tools/lib/stack-evidence.mjs": { "sha256": "c49cf35064c8e3cefc72ce19ce4a667deb126294d543adc8e8d8978acf0e048a", @@ -1326,8 +1242,7 @@ "tools/lib/conformance.mjs" ], "npm": [], - "changedSinceRelease": true, - "releasedExports": null + "changedSinceRelease": false }, "tools/lib/zip.mjs": { "sha256": "c5277647671a51503d112ea6203e5e0691d1aed55853ce2ed19322f891bb4d5b", diff --git a/deploy/k8s-journeys/kustomization.yaml b/deploy/k8s-journeys/kustomization.yaml index 55217afe..297881cf 100644 --- a/deploy/k8s-journeys/kustomization.yaml +++ b/deploy/k8s-journeys/kustomization.yaml @@ -30,7 +30,7 @@ components: images: - name: observogram - newTag: 0.5.0 + newTag: 0.6.0 labels: - pairs: diff --git a/deploy/k8s/README.md b/deploy/k8s/README.md index d0b67a1f..9b09ef15 100644 --- a/deploy/k8s/README.md +++ b/deploy/k8s/README.md @@ -7,14 +7,14 @@ and one PersistentVolumeClaim that holds the studio's state. ```bash # 1. Stamp the checkout (build.json says which commit the image is), then build it from the repo root. -npm run build:stamp && docker build -t observogram:0.5.0 . +npm run build:stamp && docker build -t observogram:0.6.0 . # 2. Make it visible to your cluster. # docker-desktop: nothing to do. -# kind: kind load docker-image observogram:0.5.0 -# remote: docker tag observogram:0.5.0 /observogram:0.5.0 -# docker push /observogram:0.5.0 -# cd deploy/k8s && kustomize edit set image observogram=/observogram:0.5.0 +# kind: kind load docker-image observogram:0.6.0 +# remote: docker tag observogram:0.6.0 /observogram:0.6.0 +# docker push /observogram:0.6.0 +# cd deploy/k8s && kustomize edit set image observogram=/observogram:0.6.0 # 3. Check that the cluster can provision the store volume (see "Storage class" below). kubectl get storageclass # one line should say (default) @@ -111,7 +111,7 @@ spec: fsGroupChangePolicy: OnRootMismatch containers: - name: restore - image: observogram:0.5.0 # the studio's image and tag + image: observogram:0.6.0 # the studio's image and tag workingDir: /app command: ["node", "tools/cli.mjs", "store", "restore", "/data/db/$BACKUP"] env: @@ -154,8 +154,9 @@ the studio at 0, from a one-off pod of the **store** image (the old one has no `store export`) that mounts both subPaths of the `store` claim at the studio's two paths: -The pre-store release is the git tag `v0.4.0`. The store build is `0.5.0`, -but builds of `develop` between the two also said `0.4.0` in `package.json` +The pre-store release is the git tag `v0.4.0`. The store build is `0.6.0` +(`0.5.0` was the first store release), but builds of `develop` between +`v0.4.0` and `0.5.0` also said `0.4.0` in `package.json` and `kustomization.yaml`, so an image called `observogram:0.4.0` may be either: one built from a pre-0.5.0 checkout is a store build and replaced any older image of that name. Build the old one under a tag of its own @@ -499,7 +500,7 @@ spec: fsGroupChangePolicy: OnRootMismatch containers: - name: copy - image: observogram:0.5.0 # the studio's image and tag + image: observogram:0.6.0 # the studio's image and tag command: ["sh", "-c", "cp -a /data/workspace/. /workspace/ && ls -la /workspace"] volumeMounts: - { name: store, mountPath: /data/workspace, subPath: workspace } diff --git a/deploy/k8s/components/journeys/cronjob-journeys.yaml b/deploy/k8s/components/journeys/cronjob-journeys.yaml index b1c0f77c..b558f309 100644 --- a/deploy/k8s/components/journeys/cronjob-journeys.yaml +++ b/deploy/k8s/components/journeys/cronjob-journeys.yaml @@ -71,7 +71,7 @@ spec: # applies to the kustomization that declares it), so # deploy/k8s-journeys/kustomization.yaml repeats the `images:` # entry; tools/test-deploy-manifests.mjs pins it to the base's. - image: observogram:0.5.0 + image: observogram:0.6.0 imagePullPolicy: IfNotPresent workingDir: /app command: ["node", "tools/cli.mjs", "journey", "run", "--all"] diff --git a/deploy/k8s/deployment-studio.yaml b/deploy/k8s/deployment-studio.yaml index 201e2ab6..ec97f97a 100644 --- a/deploy/k8s/deployment-studio.yaml +++ b/deploy/k8s/deployment-studio.yaml @@ -59,7 +59,7 @@ spec: fsGroupChangePolicy: OnRootMismatch containers: - name: studio - image: observogram:0.5.0 + image: observogram:0.6.0 imagePullPolicy: IfNotPresent ports: - name: http diff --git a/deploy/k8s/kustomization.yaml b/deploy/k8s/kustomization.yaml index b99afd4e..e4d1443c 100644 --- a/deploy/k8s/kustomization.yaml +++ b/deploy/k8s/kustomization.yaml @@ -7,10 +7,10 @@ # assets all ship inside the image. # # Build + make the image visible to the cluster, then apply: -# npm run build:stamp && docker build -t observogram:0.5.0 . -# # docker-desktop: nothing more to do. kind: `kind load docker-image observogram:0.5.0` +# npm run build:stamp && docker build -t observogram:0.6.0 . +# # docker-desktop: nothing more to do. kind: `kind load docker-image observogram:0.6.0` # # remote cluster: push to a registry and override below, e.g. -# # kustomize edit set image observogram=ghcr.io/moebiusx/observogram:0.5.0 +# # kustomize edit set image observogram=ghcr.io/moebiusx/observogram:0.6.0 # kubectl apply -k deploy/k8s # # Needs a default StorageClass, or a storageClassName in pvc-store.yaml: the @@ -33,7 +33,7 @@ resources: images: - name: observogram - newTag: 0.5.0 + newTag: 0.6.0 labels: - pairs: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d525a124..b1508c5d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 0.6.0 — 2026-10-15 + +**The store-and-seams release.** The store now holds the studio's state, not only its identities: the pack registry moved into the database; services, their environments and an org's MCP endpoints are records managed over HTTP; every change a principal makes through the server is an audit row that can be read; the home is the org's services table with a page per service; and Settings is the org's administration as a studio mode (STORE_PLAN slices 4 to 6). A downstream can vendor and rebadge Observogram without forking it: a manifest of the vendorable modules, the MCP transport hook, the artefact taxonomy classifier, reverse-proxy identity passthrough, one brand config and an embeddable static bundle, which now runs Compare and loads no font over the network. The rebadge batches built on those seams: a reviewer's verdicts, waivers and an exportable service audit report; canonical pack production, where a fresh crawl is a valid pack; a true-snapshot live pack behind a connection test; comparison identity modes; the MCP server's settings in the page. Spec 1.4 keeps a repository's operational alert rules and pairs them with the live ones. Windows has a CI leg, and a path there is compared by what it names, not by how it is spelled: a journey's org scope holds, and `packc store purge-org` decides the users file by what deleting the org's root takes. Also: a malformed JSON body is answered as JSON on every route, a server-run journey's notification reads only its org's hooks, the settings-policy deadline is per rule, and the id clocks time the pattern, not the machine. + ### Follow-up `paths-volume-through-handle`: on Windows the store's path comparison reads a path's volume through a handle - **On Node 22.16.x the store's offline checks place a path through a share again** (a fix; `server/store/paths.mjs`; Linux and macOS compare exactly as before). `comparedPath` placed a path reached through a share by a file id it shares with a directory of the path it is checked against, and judged one nothing places by its volume (the serial's low 32 bits), both read with `statSync`. libuv answers a stat by the path's name (`GetFileInformationByName`, Windows 11 24H2 and Server 2025) unless the path is reached through a share or its last name is a reparse point, which it opens and reads the volume's serial from (`FILE_FS_VOLUME_INFORMATION`); libuv 1.49.0 to 1.50.x (Node 22.16.x ships 1.49.2) declared `FILE_STAT_BASIC_INFORMATION` with `FileId128` before `VolumeSerialNumber`, swapped against Windows' layout (fixed in 1.51.0), so a stat by name reported the upper half of the file's 128-bit id as the device, 0 on NTFS. There a local path and a share of it had no file id in common, and a local volume never matched a share's: with the workspace at `C:\ws`, a directory export to `\\myhost\ws\exports\new` (which is `C:\ws\exports\new`), or to `C:\ws\exports\new` with the workspace named `\\myhost\ws`, compared as outside the workspace and was written into it, and so was one through a share of a directory under it (`\\myhost\exports`, which no file id places and which was refused as lying on the workspace's volume); the in-place detection and backup's own-file check missed the workspace and the database's `-wal` through a share alike, and purge-org refused a users file a share placed outside the root (a false refusal). On Windows a file id and a volume are now read through a handle opened on the path (`openSync`, `fstatSync`, `closeSync`; libuv opens a directory too), which reports the serial on every libuv, every handle closed before the comparison goes on; a path that does not open is passed over as one that did not stat was. `comparedPath` takes `openSync`, `fstatSync` and `closeSync` in place of `statSync`. `server/fixtures/ntfs.mjs` models both libuvs (`fastDev`: the device a stat by name reports — `null`, all 64 bits of the serial as 1.51.0 reads it, or `0n` as 1.49.2 does; a path through a share, or whose last name is a link or another reparse point, is opened, with the serial's low 32 bits) and returns `openSync`, `fstatSync`, `closeSync` and `opened()`; its header no longer says a local stat may report all 64 bits on every libuv. One more test in `server/test-store-paths.mjs` (1519 → 1520 tests): on libuv 1.51.0 and 1.49.2, a directory export through `\\myhost\ws` into `C:\ws`, and one on the drive into the workspace named through it, refused as inside it, the workspace through its share refused as the in-place export, a backup over the database's `-wal` through `\\myhost\dbdir` refused, purge-org refusing a users file placed under the root and purging with one placed beside it, a share nothing places on the workspace's volume refused, one on another volume written, and every handle closed; at the parent commit it fails on 1.49.2 (the export through `\\myhost\ws` was written). Every comparison the suite builds injects the fixture's handles. diff --git a/package-lock.json b/package-lock.json index dff11cdb..420aa9b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "observogram", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "observogram", - "version": "0.5.0", + "version": "0.6.0", "license": "MIT", "dependencies": { "@lezer/highlight": "^1.2.3", diff --git a/package.json b/package.json index 2313d823..200e7db0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "observogram", - "version": "0.5.0", + "version": "0.6.0", "description": "Observogram — the Observability Compiler. Compiles, scans, and scores ObservabilityPack spec v1.4 manifests. Express server + thin client.", "type": "module", "engines": { diff --git a/studio/index.html b/studio/index.html index 7437f2e0..65131b31 100644 --- a/studio/index.html +++ b/studio/index.html @@ -555,7 +555,7 @@

3 · Progress and resu