@astrale-os/cli 1.0.0-beta.0 → 1.0.0-beta.10
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/README.md +23 -12
- package/dist/astrale.js +51494 -56307
- package/dist/public/connect-core.js +2028 -26846
- package/dist/public/keys/index.js +1755 -40694
- package/dist/public/paths/index.js +859 -39518
- package/dist/types/admin/binding.d.ts +19 -0
- package/dist/types/admin/instance/client.d.ts +22 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +5 -12
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +6 -2
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +7 -0
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +10 -2
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/meta.d.ts +2 -2
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +131 -0
- package/package.json +8 -10
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +31 -0
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +98 -0
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +36 -32
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +40 -163
- package/src/admin/instance/client.ts +35 -137
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +11 -20
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
- package/src/commands/__tests__/domain-list.test.ts +63 -4
- package/src/commands/__tests__/install-identity-override.test.ts +19 -19
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +95 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +43 -56
- package/src/commands/domain/install.ts +13 -17
- package/src/commands/domain/list.ts +18 -13
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +9 -16
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/studio.ts +20 -0
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +68 -22
- package/src/commands/view.ts +7 -6
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +20 -1
- package/src/connection/__tests__/errors.test.ts +43 -32
- package/src/connection/__tests__/exchange.test.ts +36 -6
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/__tests__/target.test.ts +1 -0
- package/src/connection/auth.ts +13 -0
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +52 -27
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +19 -8
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- package/src/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/domain-publication.test.ts +138 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +36 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +113 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +1 -1
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/domain-publication.ts +104 -0
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +39 -4
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +22 -12
- package/src/lib/log.ts +16 -9
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/update.ts +124 -30
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/program/__tests__/program.test.ts +20 -4
- package/src/program/build.ts +2 -2
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
- package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
- package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
- package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +5 -10
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- package/studio/server/agent/prompts/anchors.test.ts +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +6 -5
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +43 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +122 -0
- package/studio/server/cache.ts +162 -11
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +50 -40
- package/studio/server/domain.ts +83 -52
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/routes.ts +114 -0
- package/studio/server/introspect/anatomy/views.ts +53 -0
- package/studio/server/introspect/anatomy-extras.test.ts +106 -35
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +52 -28
- package/studio/server/introspect/bundle.ts +15 -9
- package/studio/server/introspect/canonical-schema.test.ts +93 -309
- package/studio/server/introspect/canonical-schema.ts +257 -425
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +18 -18
- package/studio/server/introspect/diff.ts +44 -90
- package/studio/server/introspect/extractor.ts +23 -116
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +83 -143
- package/studio/server/introspect/runtime.ts +40 -11
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +151 -0
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +161 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +330 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +223 -7
- package/studio/server/state/baseline.ts +148 -34
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -11
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/views/target.test.ts +78 -0
- package/studio/server/views/target.ts +222 -0
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +9 -5
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +393 -0
- package/studio/shared/contracts/surface.test.ts +317 -0
- package/studio/shared/contracts/workspace.ts +265 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +34 -0
- package/studio/shared/schema/identity.ts +68 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +40 -61
- package/.check-workspace.cjs +0 -40
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.test.ts +0 -286
- package/studio/server/state/views.ts +0 -535
|
@@ -3,7 +3,17 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import type { SchemaIR } from '../../shared/types'
|
|
7
|
+
|
|
8
|
+
import { STUDIO_SCHEMA_PROJECTION_VERSION } from '../../shared/types'
|
|
9
|
+
import {
|
|
10
|
+
BASELINE_FORMAT_VERSION,
|
|
11
|
+
captureBaseline,
|
|
12
|
+
computeChanges,
|
|
13
|
+
hashAnatomyFiles,
|
|
14
|
+
loadBaseline,
|
|
15
|
+
} from './baseline'
|
|
16
|
+
import { writeJson, writeState } from './store'
|
|
7
17
|
|
|
8
18
|
const roots: string[] = []
|
|
9
19
|
|
|
@@ -11,13 +21,13 @@ afterEach(() => {
|
|
|
11
21
|
while (roots.length) rmSync(roots.pop()!, { recursive: true, force: true })
|
|
12
22
|
})
|
|
13
23
|
|
|
14
|
-
test('hashes current
|
|
24
|
+
test('hashes current Application and vertical authoring files', () => {
|
|
15
25
|
const root = mkdtempSync(join(tmpdir(), 'studio-current-baseline-'))
|
|
16
26
|
roots.push(root)
|
|
17
27
|
for (const dir of [
|
|
18
28
|
'schema',
|
|
19
29
|
'actions/risk',
|
|
20
|
-
'
|
|
30
|
+
'providers/mail',
|
|
21
31
|
'queries/risk',
|
|
22
32
|
'ui/risk',
|
|
23
33
|
'views/risk',
|
|
@@ -26,9 +36,10 @@ test('hashes current implementation and semantic layer files alongside legacy an
|
|
|
26
36
|
}
|
|
27
37
|
for (const file of [
|
|
28
38
|
'schema/index.ts',
|
|
29
|
-
'
|
|
39
|
+
'application.ts',
|
|
40
|
+
'runtime.ts',
|
|
30
41
|
'actions/risk/index.ts',
|
|
31
|
-
'
|
|
42
|
+
'providers/mail/index.ts',
|
|
32
43
|
'queries/risk/index.ts',
|
|
33
44
|
'ui/risk/screen.tsx',
|
|
34
45
|
'views/risk/index.ts',
|
|
@@ -40,12 +51,217 @@ test('hashes current implementation and semantic layer files alongside legacy an
|
|
|
40
51
|
|
|
41
52
|
expect(Object.keys(hashAnatomyFiles(root, 'schema')).sort()).toEqual([
|
|
42
53
|
'actions/risk/index.ts',
|
|
43
|
-
'
|
|
44
|
-
'implementation.ts',
|
|
54
|
+
'application.ts',
|
|
45
55
|
'package.json',
|
|
56
|
+
'providers/mail/index.ts',
|
|
46
57
|
'queries/risk/index.ts',
|
|
58
|
+
'runtime.ts',
|
|
47
59
|
'schema/index.ts',
|
|
48
60
|
'ui/risk/screen.tsx',
|
|
49
61
|
'views/risk/index.ts',
|
|
50
62
|
])
|
|
51
63
|
})
|
|
64
|
+
|
|
65
|
+
const schema = (domain: string): SchemaIR => ({
|
|
66
|
+
format: 'astrale.dsl',
|
|
67
|
+
version: 'v1',
|
|
68
|
+
domain,
|
|
69
|
+
classes: {},
|
|
70
|
+
importsByKey: {},
|
|
71
|
+
importedClassesByKey: {},
|
|
72
|
+
functions: {},
|
|
73
|
+
views: {},
|
|
74
|
+
policies: {},
|
|
75
|
+
dependencies: [],
|
|
76
|
+
core: {},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const canonicalRoot = {
|
|
80
|
+
format: 'astrale.dsl',
|
|
81
|
+
version: 'v1',
|
|
82
|
+
origin: 'notes.example.dev',
|
|
83
|
+
} as const
|
|
84
|
+
|
|
85
|
+
test('persists a versioned canonical baseline with revision and raw root', () => {
|
|
86
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-versioned-baseline-'))
|
|
87
|
+
roots.push(root)
|
|
88
|
+
const revision = `sha256:${'b'.repeat(64)}` as const
|
|
89
|
+
|
|
90
|
+
captureBaseline(
|
|
91
|
+
root,
|
|
92
|
+
{ ir: schema('notes.example.dev'), root: canonicalRoot, revision },
|
|
93
|
+
{ source: 'hash' },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
expect(loadBaseline(root)).toMatchObject({
|
|
97
|
+
formatVersion: BASELINE_FORMAT_VERSION,
|
|
98
|
+
revision,
|
|
99
|
+
root: canonicalRoot,
|
|
100
|
+
files: { source: 'hash' },
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('invalidates an unversioned baseline instead of diffing it through a new projection', () => {
|
|
105
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-legacy-baseline-'))
|
|
106
|
+
roots.push(root)
|
|
107
|
+
writeJson(root, '.cache/baseline/meta.json', { capturedAt: '2026-01-01T00:00:00.000Z' })
|
|
108
|
+
writeJson(root, '.cache/baseline/ir.json', schema('notes.example.dev'))
|
|
109
|
+
writeJson(root, '.cache/baseline/files.json', {})
|
|
110
|
+
|
|
111
|
+
expect(loadBaseline(root)).toBeNull()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const corruptBaselineCases = [
|
|
115
|
+
{
|
|
116
|
+
name: 'missing meta',
|
|
117
|
+
mutate: (root: string) => rmSync(join(root, '.domain-studio/.cache/baseline/meta.json')),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'syntactically invalid meta',
|
|
121
|
+
mutate: (root: string) => writeState(root, '.cache/baseline/meta.json', '{'),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'structurally invalid meta',
|
|
125
|
+
mutate: (root: string) =>
|
|
126
|
+
writeJson(root, '.cache/baseline/meta.json', {
|
|
127
|
+
formatVersion: BASELINE_FORMAT_VERSION,
|
|
128
|
+
projectionVersion: STUDIO_SCHEMA_PROJECTION_VERSION,
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'future baseline format',
|
|
133
|
+
mutate: (root: string) =>
|
|
134
|
+
writeJson(root, '.cache/baseline/meta.json', {
|
|
135
|
+
formatVersion: BASELINE_FORMAT_VERSION + 1,
|
|
136
|
+
projectionVersion: STUDIO_SCHEMA_PROJECTION_VERSION,
|
|
137
|
+
revision: `sha256:${'d'.repeat(64)}`,
|
|
138
|
+
}),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'future projection format',
|
|
142
|
+
mutate: (root: string) =>
|
|
143
|
+
writeJson(root, '.cache/baseline/meta.json', {
|
|
144
|
+
formatVersion: BASELINE_FORMAT_VERSION,
|
|
145
|
+
projectionVersion: STUDIO_SCHEMA_PROJECTION_VERSION + 1,
|
|
146
|
+
revision: `sha256:${'d'.repeat(64)}`,
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'missing IR',
|
|
151
|
+
mutate: (root: string) => rmSync(join(root, '.domain-studio/.cache/baseline/ir.json')),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'syntactically invalid IR',
|
|
155
|
+
mutate: (root: string) => writeState(root, '.cache/baseline/ir.json', '{'),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'structurally invalid IR',
|
|
159
|
+
mutate: (root: string) =>
|
|
160
|
+
writeJson(root, '.cache/baseline/ir.json', {
|
|
161
|
+
...schema('notes.example.dev'),
|
|
162
|
+
classes: { Broken: null },
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'missing canonical root',
|
|
167
|
+
mutate: (root: string) => rmSync(join(root, '.domain-studio/.cache/baseline/schema-root.json')),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'syntactically invalid canonical root',
|
|
171
|
+
mutate: (root: string) => writeState(root, '.cache/baseline/schema-root.json', '{'),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'structurally invalid canonical root',
|
|
175
|
+
mutate: (root: string) =>
|
|
176
|
+
writeJson(root, '.cache/baseline/schema-root.json', { canonical: true }),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'missing file hashes',
|
|
180
|
+
mutate: (root: string) => rmSync(join(root, '.domain-studio/.cache/baseline/files.json')),
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'syntactically invalid file hashes',
|
|
184
|
+
mutate: (root: string) => writeState(root, '.cache/baseline/files.json', '{'),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'structurally invalid file hashes',
|
|
188
|
+
mutate: (root: string) => writeJson(root, '.cache/baseline/files.json', { source: 42 }),
|
|
189
|
+
},
|
|
190
|
+
] as const
|
|
191
|
+
|
|
192
|
+
for (const scenario of corruptBaselineCases) {
|
|
193
|
+
test(`invalidates a baseline with ${scenario.name}`, () => {
|
|
194
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-corrupt-baseline-'))
|
|
195
|
+
roots.push(root)
|
|
196
|
+
const revision = `sha256:${'d'.repeat(64)}` as const
|
|
197
|
+
captureBaseline(root, { ir: schema('notes.example.dev'), root: canonicalRoot, revision }, {})
|
|
198
|
+
|
|
199
|
+
scenario.mutate(root)
|
|
200
|
+
|
|
201
|
+
expect(loadBaseline(root)).toBeNull()
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
test('keeps a valid current-format legacy baseline without a canonical root or revision', () => {
|
|
206
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-legacy-v2-baseline-'))
|
|
207
|
+
roots.push(root)
|
|
208
|
+
const legacyIr = { ...schema('notes.example.dev'), version: 'legacy' }
|
|
209
|
+
|
|
210
|
+
captureBaseline(root, { ir: legacyIr, root: null, revision: null }, { source: 'hash' })
|
|
211
|
+
|
|
212
|
+
expect(loadBaseline(root)).toMatchObject({
|
|
213
|
+
formatVersion: BASELINE_FORMAT_VERSION,
|
|
214
|
+
ir: legacyIr,
|
|
215
|
+
root: null,
|
|
216
|
+
revision: null,
|
|
217
|
+
files: { source: 'hash' },
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('uses equal admitted revisions to suppress projection-only schema churn', () => {
|
|
222
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-revision-baseline-'))
|
|
223
|
+
roots.push(root)
|
|
224
|
+
const revision = `sha256:${'c'.repeat(64)}` as const
|
|
225
|
+
captureBaseline(
|
|
226
|
+
root,
|
|
227
|
+
{
|
|
228
|
+
ir: schema('old-projection.example.dev'),
|
|
229
|
+
root: { ...canonicalRoot, origin: 'old-projection.example.dev' },
|
|
230
|
+
revision,
|
|
231
|
+
},
|
|
232
|
+
{},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
const changes = computeChanges(
|
|
236
|
+
root,
|
|
237
|
+
schema('new-projection.example.dev'),
|
|
238
|
+
{},
|
|
239
|
+
{
|
|
240
|
+
currentRevision: revision,
|
|
241
|
+
git: { hasGit: false },
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
expect(changes.schemaChanges).toEqual([])
|
|
245
|
+
expect(changes.structuralStatus).toBe('none')
|
|
246
|
+
expect(changes.baselineRevision).toBe(revision)
|
|
247
|
+
expect(changes.currentRevision).toBe(revision)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('uses caller-provided Git enrichment without importing workspace effects', () => {
|
|
251
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-enriched-baseline-'))
|
|
252
|
+
roots.push(root)
|
|
253
|
+
captureBaseline(root, { ir: schema('notes.example.dev'), root: null, revision: null }, {})
|
|
254
|
+
|
|
255
|
+
const changes = computeChanges(
|
|
256
|
+
root,
|
|
257
|
+
schema('notes.example.dev'),
|
|
258
|
+
{},
|
|
259
|
+
{
|
|
260
|
+
git: { hasGit: true, diffText: 'diff --git a/schema/index.ts b/schema/index.ts' },
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(changes.source).toBe('git')
|
|
265
|
+
expect(changes.hasGit).toBe(true)
|
|
266
|
+
expect(changes.schemaDiffText).toBe('diff --git a/schema/index.ts b/schema/index.ts')
|
|
267
|
+
})
|
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* baseline.ts — the PRIMARY change tracker. On first launch we capture a
|
|
3
|
-
* snapshot (
|
|
3
|
+
* versioned snapshot (canonical revision/root, render IR, and a content hash of
|
|
4
|
+
* the "anatomy + schema fileset")
|
|
4
5
|
* under `.domain-studio/.cache/baseline/`. Subsequent runs diff the live state
|
|
5
6
|
* against that baseline to compute a ChangeSet. Git (git.ts) only enriches when
|
|
6
7
|
* a real repo is present; the fixtures are not repos, so baseline stands alone.
|
|
7
8
|
*
|
|
8
9
|
* Layout under `.cache/baseline/`:
|
|
9
|
-
* ir.json
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* ir.json — the captured render SchemaIR (or null)
|
|
11
|
+
* schema-root.json — the admitted portable V1 root (or null)
|
|
12
|
+
* files.json — { [relpathFromRoot]: sha256 }
|
|
13
|
+
* meta.json — format/projection versions, revision, capturedAt
|
|
12
14
|
*/
|
|
13
15
|
import { createHash } from 'node:crypto'
|
|
14
16
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
15
17
|
import { join, relative, resolve } from 'node:path'
|
|
16
18
|
|
|
17
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
ChangeSet,
|
|
21
|
+
FileChange,
|
|
22
|
+
SchemaChange,
|
|
23
|
+
SchemaIR,
|
|
24
|
+
SchemaRevision,
|
|
25
|
+
} from '../../shared/types'
|
|
18
26
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
27
|
+
import { isSchemaRevision, STUDIO_SCHEMA_PROJECTION_VERSION } from '../../shared/types'
|
|
28
|
+
import { isCanonicalDomainSchemaV1 } from '../introspect/canonical-schema'
|
|
29
|
+
import { diffSchemas, structuralStatusOf } from '../introspect/diff'
|
|
30
|
+
import { decodeSchemaIR } from '../introspect/schema-ir-json'
|
|
31
|
+
import { asFiniteNumber, asJsonRecord, asStringRecord, parseJson } from '../json'
|
|
32
|
+
import { readState, writeJson } from './store'
|
|
22
33
|
|
|
23
34
|
const BASE = '.cache/baseline'
|
|
35
|
+
export const BASELINE_FORMAT_VERSION = 2
|
|
24
36
|
|
|
25
37
|
/** Directories to never descend into when hashing the fileset. */
|
|
26
38
|
const SKIP_DIRS = new Set(['node_modules', '.dist', 'dist', '.domain-studio', '.git'])
|
|
@@ -33,12 +45,10 @@ const SKIP_DIRS = new Set(['node_modules', '.dist', 'dist', '.domain-studio', '.
|
|
|
33
45
|
export const ANATOMY_GLOBS = {
|
|
34
46
|
dirs: [
|
|
35
47
|
'actions',
|
|
36
|
-
'capabilities',
|
|
37
48
|
'core',
|
|
49
|
+
'providers',
|
|
38
50
|
'runtime',
|
|
39
51
|
'views',
|
|
40
|
-
'functions',
|
|
41
|
-
'handlers',
|
|
42
52
|
'integrations',
|
|
43
53
|
'migrations',
|
|
44
54
|
'mutations',
|
|
@@ -52,8 +62,8 @@ export const ANATOMY_GLOBS = {
|
|
|
52
62
|
'client/src',
|
|
53
63
|
],
|
|
54
64
|
files: [
|
|
55
|
-
'
|
|
56
|
-
'
|
|
65
|
+
'application.ts',
|
|
66
|
+
'runtime.ts',
|
|
57
67
|
'index.ts',
|
|
58
68
|
'core.ts',
|
|
59
69
|
'deps.ts',
|
|
@@ -140,30 +150,128 @@ export function hashAnatomyFiles(root: string, schemaDirName: string): Record<st
|
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
export interface Baseline {
|
|
153
|
+
formatVersion: typeof BASELINE_FORMAT_VERSION
|
|
154
|
+
projectionVersion: number
|
|
143
155
|
ir: SchemaIR | null
|
|
156
|
+
/** Admitted canonical root only; null for legacy/preview snapshots. */
|
|
157
|
+
root: unknown | null
|
|
158
|
+
revision: SchemaRevision | null
|
|
144
159
|
files: Record<string, string>
|
|
145
160
|
capturedAt?: string
|
|
146
161
|
}
|
|
147
162
|
|
|
163
|
+
export interface BaselineSchemaSnapshot {
|
|
164
|
+
ir: SchemaIR | null
|
|
165
|
+
/** Admitted canonical root only; preview roots must be passed as null. */
|
|
166
|
+
root: unknown | null
|
|
167
|
+
revision: SchemaRevision | null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface BaselineMeta {
|
|
171
|
+
formatVersion: number
|
|
172
|
+
projectionVersion: number
|
|
173
|
+
revision: SchemaRevision | null
|
|
174
|
+
capturedAt?: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function decodeBaselineMeta(value: unknown): BaselineMeta | undefined {
|
|
178
|
+
const record = asJsonRecord(value)
|
|
179
|
+
if (!record) return undefined
|
|
180
|
+
const formatVersion = asFiniteNumber(record.formatVersion)
|
|
181
|
+
const projectionVersion = asFiniteNumber(record.projectionVersion)
|
|
182
|
+
const revision =
|
|
183
|
+
record.revision === null
|
|
184
|
+
? null
|
|
185
|
+
: isSchemaRevision(record.revision)
|
|
186
|
+
? record.revision
|
|
187
|
+
: undefined
|
|
188
|
+
const capturedAt = record.capturedAt
|
|
189
|
+
if (
|
|
190
|
+
formatVersion === undefined ||
|
|
191
|
+
projectionVersion === undefined ||
|
|
192
|
+
revision === undefined ||
|
|
193
|
+
(capturedAt !== undefined && typeof capturedAt !== 'string')
|
|
194
|
+
) {
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
formatVersion,
|
|
199
|
+
projectionVersion,
|
|
200
|
+
revision,
|
|
201
|
+
...(capturedAt === undefined ? {} : { capturedAt }),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function decodeNullableSchemaIR(value: unknown): SchemaIR | null | undefined {
|
|
206
|
+
return value === null ? null : decodeSchemaIR(value)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function decodeNullableSchemaRoot(value: unknown): unknown | null | undefined {
|
|
210
|
+
return value === null ? null : isCanonicalDomainSchemaV1(value) ? value : undefined
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function readBaselineJson<T>(
|
|
214
|
+
root: string,
|
|
215
|
+
subpath: string,
|
|
216
|
+
decode: (value: unknown) => T | undefined,
|
|
217
|
+
): T | undefined {
|
|
218
|
+
const raw = readState(root, subpath)
|
|
219
|
+
if (raw === null) return undefined
|
|
220
|
+
const parsed = parseJson(raw)
|
|
221
|
+
return parsed === undefined ? undefined : decode(parsed)
|
|
222
|
+
}
|
|
223
|
+
|
|
148
224
|
/** Persist a baseline snapshot under `.cache/baseline/`. All writes go through the store. */
|
|
149
225
|
export function captureBaseline(
|
|
150
226
|
root: string,
|
|
151
|
-
|
|
227
|
+
snapshot: BaselineSchemaSnapshot,
|
|
152
228
|
fileHashes: Record<string, string>,
|
|
153
229
|
): void {
|
|
154
|
-
writeJson(root, `${BASE}/ir.json`, ir)
|
|
230
|
+
writeJson(root, `${BASE}/ir.json`, snapshot.ir)
|
|
231
|
+
writeJson(root, `${BASE}/schema-root.json`, snapshot.root)
|
|
155
232
|
writeJson(root, `${BASE}/files.json`, fileHashes)
|
|
156
|
-
writeJson(root, `${BASE}/meta.json`, {
|
|
233
|
+
writeJson(root, `${BASE}/meta.json`, {
|
|
234
|
+
formatVersion: BASELINE_FORMAT_VERSION,
|
|
235
|
+
projectionVersion: STUDIO_SCHEMA_PROJECTION_VERSION,
|
|
236
|
+
revision: snapshot.revision,
|
|
237
|
+
capturedAt: new Date().toISOString(),
|
|
238
|
+
} satisfies BaselineMeta)
|
|
157
239
|
}
|
|
158
240
|
|
|
159
|
-
/**
|
|
241
|
+
/**
|
|
242
|
+
* Load a current baseline. Older/unrecognised formats and render-projection
|
|
243
|
+
* versions are explicitly invalidated so adapter changes cannot masquerade as
|
|
244
|
+
* authored schema changes. The lifecycle will seed a replacement snapshot.
|
|
245
|
+
*/
|
|
160
246
|
export function loadBaseline(root: string): Baseline | null {
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
247
|
+
const meta = readBaselineJson(root, `${BASE}/meta.json`, decodeBaselineMeta)
|
|
248
|
+
if (
|
|
249
|
+
!meta ||
|
|
250
|
+
meta.formatVersion !== BASELINE_FORMAT_VERSION ||
|
|
251
|
+
meta.projectionVersion !== STUDIO_SCHEMA_PROJECTION_VERSION ||
|
|
252
|
+
!isSchemaRevisionOrNull(meta.revision)
|
|
253
|
+
) {
|
|
254
|
+
return null
|
|
255
|
+
}
|
|
256
|
+
const ir = readBaselineJson(root, `${BASE}/ir.json`, decodeNullableSchemaIR)
|
|
257
|
+
const schemaRoot = readBaselineJson(root, `${BASE}/schema-root.json`, decodeNullableSchemaRoot)
|
|
258
|
+
const files = readBaselineJson(root, `${BASE}/files.json`, asStringRecord)
|
|
259
|
+
if (ir === undefined || schemaRoot === undefined || files === undefined) return null
|
|
260
|
+
if (meta.revision !== null && (schemaRoot === null || ir === null)) return null
|
|
261
|
+
if (meta.revision === null && schemaRoot !== null) return null
|
|
262
|
+
return {
|
|
263
|
+
formatVersion: BASELINE_FORMAT_VERSION,
|
|
264
|
+
projectionVersion: STUDIO_SCHEMA_PROJECTION_VERSION,
|
|
265
|
+
ir,
|
|
266
|
+
root: schemaRoot,
|
|
267
|
+
revision: meta.revision ?? null,
|
|
268
|
+
files,
|
|
269
|
+
capturedAt: meta.capturedAt,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isSchemaRevisionOrNull(value: unknown): value is SchemaRevision | null {
|
|
274
|
+
return value === null || isSchemaRevision(value)
|
|
167
275
|
}
|
|
168
276
|
|
|
169
277
|
/** Compare two file-hash maps into added/modified/removed FileChange[]. */
|
|
@@ -185,9 +293,8 @@ function summarizeSchemaChanges(changes: SchemaChange[]): string {
|
|
|
185
293
|
if (changes.length === 0) return 'No schema changes.'
|
|
186
294
|
return changes
|
|
187
295
|
.map((c) => {
|
|
188
|
-
const flag = c.breaking ? 'breaking' : 'additive'
|
|
189
296
|
const detail = c.detail ? `: ${c.detail}` : ''
|
|
190
|
-
return `${c.kind} ${c.target}${detail}
|
|
297
|
+
return `${c.kind} ${c.target}${detail}`
|
|
191
298
|
})
|
|
192
299
|
.join('\n')
|
|
193
300
|
}
|
|
@@ -202,9 +309,12 @@ export function computeChanges(
|
|
|
202
309
|
root: string,
|
|
203
310
|
currentIr: SchemaIR | null,
|
|
204
311
|
currentFiles: Record<string, string>,
|
|
205
|
-
opts: {
|
|
312
|
+
opts: {
|
|
313
|
+
currentRevision?: SchemaRevision | null
|
|
314
|
+
git: { hasGit: boolean; diffText?: string }
|
|
315
|
+
},
|
|
206
316
|
): ChangeSet {
|
|
207
|
-
const { hasGit } =
|
|
317
|
+
const { hasGit } = opts.git
|
|
208
318
|
const source: ChangeSet['source'] = hasGit ? 'git' : 'baseline'
|
|
209
319
|
const baseline = loadBaseline(root)
|
|
210
320
|
|
|
@@ -215,17 +325,19 @@ export function computeChanges(
|
|
|
215
325
|
hasBaseline: false,
|
|
216
326
|
schemaChanges: [],
|
|
217
327
|
fileChanges: [],
|
|
218
|
-
|
|
328
|
+
structuralStatus: 'none',
|
|
329
|
+
...(opts.currentRevision ? { currentRevision: opts.currentRevision } : {}),
|
|
219
330
|
}
|
|
220
331
|
}
|
|
221
332
|
|
|
222
|
-
const schemaChanges =
|
|
223
|
-
|
|
333
|
+
const schemaChanges =
|
|
334
|
+
baseline.revision && opts.currentRevision === baseline.revision
|
|
335
|
+
? []
|
|
336
|
+
: diffSchemas(baseline.ir ?? null, currentIr)
|
|
337
|
+
const structuralStatus = structuralStatusOf(schemaChanges)
|
|
224
338
|
const fileChanges = diffFiles(baseline.files ?? {}, currentFiles)
|
|
225
339
|
|
|
226
|
-
const schemaDiffText = hasGit
|
|
227
|
-
? (gitDiff(root, opts.schemaDirName) ?? undefined)
|
|
228
|
-
: summarizeSchemaChanges(schemaChanges)
|
|
340
|
+
const schemaDiffText = hasGit ? opts.git.diffText : summarizeSchemaChanges(schemaChanges)
|
|
229
341
|
|
|
230
342
|
return {
|
|
231
343
|
source,
|
|
@@ -234,7 +346,9 @@ export function computeChanges(
|
|
|
234
346
|
schemaChanges,
|
|
235
347
|
fileChanges,
|
|
236
348
|
schemaDiffText,
|
|
237
|
-
|
|
349
|
+
structuralStatus,
|
|
350
|
+
...(baseline.revision ? { baselineRevision: baseline.revision } : {}),
|
|
351
|
+
...(opts.currentRevision ? { currentRevision: opts.currentRevision } : {}),
|
|
238
352
|
baselineCapturedAt: baseline.capturedAt,
|
|
239
353
|
}
|
|
240
354
|
}
|
|
@@ -3,7 +3,8 @@ import { mkdtempSync, rmSync } from 'node:fs'
|
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
|
-
import { readComments, upsertComment } from './comments'
|
|
6
|
+
import { mergeReply, readComments, upsertComment } from './comments'
|
|
7
|
+
import { writeJson } from './store'
|
|
7
8
|
|
|
8
9
|
const roots: string[] = []
|
|
9
10
|
|
|
@@ -27,3 +28,67 @@ test('missing comment stores do not share entries across domains', () => {
|
|
|
27
28
|
])
|
|
28
29
|
expect(readComments(rootB).comments).toEqual([])
|
|
29
30
|
})
|
|
31
|
+
|
|
32
|
+
test('normalizes malformed nested comment data and ignores future fields', () => {
|
|
33
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-comments-corrupt-'))
|
|
34
|
+
roots.push(root)
|
|
35
|
+
writeJson(root, 'comments.json', {
|
|
36
|
+
schemaVersion: 'render-fingerprint',
|
|
37
|
+
futureStoreField: { version: 2 },
|
|
38
|
+
comments: [
|
|
39
|
+
{
|
|
40
|
+
id: 'kept',
|
|
41
|
+
anchors: ['Thing'],
|
|
42
|
+
anchorRefs: [
|
|
43
|
+
{ ref: 'class.Thing', kind: 'schema', futureAnchorField: true },
|
|
44
|
+
{ ref: 42, kind: 'schema' },
|
|
45
|
+
],
|
|
46
|
+
status: 'open',
|
|
47
|
+
thread: [
|
|
48
|
+
{
|
|
49
|
+
id: 'entry',
|
|
50
|
+
role: 'author',
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: 'A valid question',
|
|
53
|
+
futureEntryField: true,
|
|
54
|
+
},
|
|
55
|
+
{ id: 'broken', role: 'nobody', type: 'text', text: 'ignored' },
|
|
56
|
+
],
|
|
57
|
+
createdAt: '2026-08-20T00:00:00.000Z',
|
|
58
|
+
futureCommentField: true,
|
|
59
|
+
},
|
|
60
|
+
{ id: 42, thread: [] },
|
|
61
|
+
],
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(readComments(root)).toEqual({
|
|
65
|
+
schemaVersion: 'render-fingerprint',
|
|
66
|
+
comments: [
|
|
67
|
+
{
|
|
68
|
+
id: 'kept',
|
|
69
|
+
anchors: ['Thing'],
|
|
70
|
+
anchorRefs: [{ ref: 'class.Thing', kind: 'schema' }],
|
|
71
|
+
status: 'open',
|
|
72
|
+
thread: [
|
|
73
|
+
{
|
|
74
|
+
id: 'entry',
|
|
75
|
+
role: 'author',
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: 'A valid question',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
createdAt: '2026-08-20T00:00:00.000Z',
|
|
81
|
+
kind: 'question',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('rejects a syntactically valid machine-state block with the wrong root shape', () => {
|
|
88
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-comments-paste-'))
|
|
89
|
+
roots.push(root)
|
|
90
|
+
|
|
91
|
+
expect(() => mergeReply(root, 'render-fingerprint', '```json\n[]\n```')).toThrow(
|
|
92
|
+
'invalid machine-state json block',
|
|
93
|
+
)
|
|
94
|
+
})
|