viewspec 0.3.0b2__tar.gz → 0.3.0b4__tar.gz

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.
Files changed (89) hide show
  1. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/.gitignore +2 -0
  2. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/PKG-INFO +25 -4
  3. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/README.md +21 -2
  4. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/pyproject.toml +4 -2
  5. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/__init__.py +140 -0
  6. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/_version.py +1 -1
  7. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_react_verify.py +47 -7
  8. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/cli.py +64 -0
  9. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/compiler.py +64 -13
  10. viewspec-0.3.0b4/src/viewspec/conformance.py +285 -0
  11. viewspec-0.3.0b4/src/viewspec/convergence.py +227 -0
  12. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/error_codes.py +12 -0
  13. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify.py +249 -23
  14. viewspec-0.3.0b4/src/viewspec/host_verify_template/playwright.config.ts +52 -0
  15. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/tests/host-verify.spec.ts +264 -14
  16. viewspec-0.3.0b4/src/viewspec/hosted_app_bundles.py +310 -0
  17. viewspec-0.3.0b4/src/viewspec/hosted_artifacts.py +283 -0
  18. viewspec-0.3.0b4/src/viewspec/hosted_receipts.py +84 -0
  19. viewspec-0.3.0b4/src/viewspec/hosted_verification.py +453 -0
  20. viewspec-0.3.0b4/src/viewspec/intent_contract.py +62 -0
  21. viewspec-0.3.0b4/src/viewspec/local_verify.py +249 -0
  22. viewspec-0.3.0b4/src/viewspec/node_runtime.py +27 -0
  23. viewspec-0.3.0b4/src/viewspec/repair.py +358 -0
  24. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/types.py +7 -13
  25. viewspec-0.3.0b4/src/viewspec/verification.py +452 -0
  26. viewspec-0.3.0b2/src/viewspec/host_verify_template/playwright.config.ts +0 -25
  27. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/LICENSE +0 -0
  28. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/__main__.py +0 -0
  29. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/a11y.py +0 -0
  30. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/aesthetics.py +0 -0
  31. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/agent.py +0 -0
  32. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/agent_assets.py +0 -0
  33. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_agent_schema.py +0 -0
  34. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_bundle.py +0 -0
  35. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_diff.py +0 -0
  36. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_errors.py +0 -0
  37. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_paths.py +0 -0
  38. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_pipeline.py +0 -0
  39. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_prepared.py +0 -0
  40. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_react.py +0 -0
  41. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_reports.py +0 -0
  42. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_resource_binding.py +0 -0
  43. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_screens.py +0 -0
  44. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_shell.py +0 -0
  45. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_shell_writer.py +0 -0
  46. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_starters.py +0 -0
  47. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_state_artifacts.py +0 -0
  48. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_tools.py +0 -0
  49. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_validation.py +0 -0
  50. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/app_visibility.py +0 -0
  51. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/compiler_benchmarks.py +0 -0
  52. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/compiler_refs.py +0 -0
  53. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/design_md.py +0 -0
  54. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/__init__.py +0 -0
  55. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/base.py +0 -0
  56. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/html_tailwind/__init__.py +0 -0
  57. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/react_tailwind_tsx/__init__.py +0 -0
  58. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/react_tailwind_tsx/a11y.py +0 -0
  59. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/react_tailwind_tsx/recipes.py +0 -0
  60. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/emitters/react_tsx/__init__.py +0 -0
  61. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/__init__.py +0 -0
  62. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/index.html +0 -0
  63. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/package-lock.json +0 -0
  64. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/package.json +0 -0
  65. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/src/App.tsx +0 -0
  66. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/src/index.css +0 -0
  67. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/src/main.tsx +0 -0
  68. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/tsconfig.json +0 -0
  69. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/host_verify_template/vite.config.ts +0 -0
  70. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/intent_tools.py +0 -0
  71. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/local_tools.py +0 -0
  72. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/local_tools_constants.py +0 -0
  73. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/local_tools_hash.py +0 -0
  74. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/local_tools_io.py +0 -0
  75. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/local_tools_response.py +0 -0
  76. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/local_tools_validators.py +0 -0
  77. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/manifest_summary.py +0 -0
  78. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/mcp_server.py +0 -0
  79. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/motif_compilers.py +0 -0
  80. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/motif_plugins.py +0 -0
  81. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/native_agents.py +0 -0
  82. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/prove.py +0 -0
  83. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/py.typed +0 -0
  84. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/raw_html.py +0 -0
  85. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/schema/__init__.py +0 -0
  86. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/schema/viewspec_pb2.py +0 -0
  87. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/sdk/__init__.py +0 -0
  88. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/sdk/builder.py +0 -0
  89. {viewspec-0.3.0b2 → viewspec-0.3.0b4}/src/viewspec/state_ir.py +0 -0
@@ -16,4 +16,6 @@ build/
16
16
  output/
17
17
  viewspec_output/
18
18
  .venv/
19
+ .env
20
+ node_modules/
19
21
  demos/server.*.log
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: viewspec
3
- Version: 0.3.0b2
3
+ Version: 0.3.0b4
4
4
  Summary: Agent-native UI compiler. Agents describe intent; ViewSpec compiles concrete UI.
5
5
  Project-URL: Homepage, https://github.com/nxrobins/viewspec
6
6
  Project-URL: Repository, https://github.com/nxrobins/viewspec
@@ -9,7 +9,7 @@ Author: Nigel Robinson
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
11
11
  Keywords: agents,compiler,composition-ir,declarative,protobuf,semantic,ui,viewspec
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
@@ -28,10 +28,12 @@ Requires-Dist: mcp>=1.9.0; extra == 'agents'
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: build>=1.2; extra == 'dev'
30
30
  Requires-Dist: httpx>=0.27.0; extra == 'dev'
31
+ Requires-Dist: hypothesis<7,>=6.100; extra == 'dev'
31
32
  Requires-Dist: jsonschema>=4.18; extra == 'dev'
32
33
  Requires-Dist: pytest>=8.0; extra == 'dev'
33
34
  Requires-Dist: ruff>=0.4; extra == 'dev'
34
35
  Provides-Extra: remote
36
+ Requires-Dist: cryptography<49,>=48.0.1; extra == 'remote'
35
37
  Requires-Dist: httpx>=0.27.0; extra == 'remote'
36
38
  Description-Content-Type: text/markdown
37
39
 
@@ -39,7 +41,7 @@ Description-Content-Type: text/markdown
39
41
 
40
42
  **Stop asking agents to write React, DOM, or State.**
41
43
 
42
- ViewSpec is an agent-native UI compiler. It acts as a universal Intermediate Representation (IR) for AI Software Engineering. Agents describe UI and state intent as `IntentBundle` or `AppBundle` JSON; ViewSpec compiles that intent into deterministic HTML and React surfaces with the free local compiler, and SwiftUI or Flutter surfaces via the hosted compiler.
44
+ ViewSpec is an agent-native UI compiler. It acts as a universal Intermediate Representation (IR) for AI Software Engineering. Agents describe UI and state intent as `IntentBundle` or `AppBundle` JSON; ViewSpec compiles that intent into deterministic HTML and React surfaces with the free local compiler. The paid hosted API adds Level 2 derivation, DESIGN.md context, projections, rules, custom motifs, signed usage receipts, integrity-checked single-screen artifacts, and verified runnable AppBundle projects.
43
45
 
44
46
  By totally decoupling intent from implementation, ViewSpec eliminates the "visual context window" problem. Agents no longer need to see pixels to build complex, perfectly styled, and deeply interactive applications.
45
47
 
@@ -219,7 +221,20 @@ viewspec mcp
219
221
  ```
220
222
  The MCP server exposes all intent-first local tools without requiring shell commands, including `validate_intent_bundle_file`, `compile_intent_bundle_file`, `verify_host`, `prove`, `validate_app_file`, `diff_app_files`, `compile_app`, and `prove_app`.
221
223
 
222
- For bounded React Tailwind runtime proof, run `viewspec prove --target react-tailwind-tsx --install --out .viewspec-proof --json` or `viewspec verify-host react-tailwind-output/ --target react-tailwind-tsx --install --json`. The host proof checks grid column/span counts, profiled aesthetic marker/layout assertions, and action payload behavior; JSON reports include `assertion_requirements` with `dom_count`, `style_assertion_count`, `aesthetic_layout_assertion_count`, `aesthetic_profile_assertion_count`, and `grid_span_assertion_count`.
224
+ For rendered conformance, compile React/Tailwind TSX and run:
225
+
226
+ ```bash
227
+ viewspec verify react-tailwind-output/ --install --json
228
+ ```
229
+
230
+ The verifier renders canonical mobile, tablet, and desktop viewports and writes integrity-addressed
231
+ screenshots, DOM snapshots, accessibility evidence, `result.json`, and a deterministic `repair.json`.
232
+ Exit codes distinguish conformant (`0`), nonconformant (`1`), and indeterminate (`2`) results.
233
+ `viewspec verify-host` remains the lower-level bounded host assertion proof for
234
+ grid column/span counts, profiled aesthetic markers/layout, and action payload behavior.
235
+ Its JSON `assertion_requirements` records `dom_count`, `style_assertion_count`, and
236
+ `aesthetic_layout_assertion_count` expectations from the checked manifest.
237
+ `viewspec prove` combines compilation checks with that proof.
223
238
 
224
239
  ## viewspec.dev
225
240
 
@@ -300,6 +315,12 @@ ast = compile_auto(builder.build_bundle())
300
315
 
301
316
  The hosted fallback requires the `remote` extra: `python -m pip install "viewspec[remote]"` (adds `httpx`). Without it, `compile_auto` runs locally and raises `ImportError` only if a hosted fallback is actually needed.
302
317
 
318
+ Paid agents can submit a complete AppBundle to `submit_verification_remote(...)` for compiled
319
+ route/state proof plus per-screen browser evidence and a signed receipt. For bounded autonomous
320
+ repair, `compile_until_conformant_remote(...)` repeats that paid compile-and-verify step while a
321
+ caller-owned repair callback edits the semantic AppBundle. ViewSpec enforces lineage, plan
322
+ stability, attempt limits, and no-progress termination.
323
+
303
324
  ### Theming with DESIGN.md
304
325
  The local SDK uses a strict YAML-front-matter `DESIGN.md` for offline HTML and IntentBundle compilation. The API requires exact sRGB hex values (e.g., `#FFFFFF`), enforcing strict design token discipline.
305
326
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Stop asking agents to write React, DOM, or State.**
4
4
 
5
- ViewSpec is an agent-native UI compiler. It acts as a universal Intermediate Representation (IR) for AI Software Engineering. Agents describe UI and state intent as `IntentBundle` or `AppBundle` JSON; ViewSpec compiles that intent into deterministic HTML and React surfaces with the free local compiler, and SwiftUI or Flutter surfaces via the hosted compiler.
5
+ ViewSpec is an agent-native UI compiler. It acts as a universal Intermediate Representation (IR) for AI Software Engineering. Agents describe UI and state intent as `IntentBundle` or `AppBundle` JSON; ViewSpec compiles that intent into deterministic HTML and React surfaces with the free local compiler. The paid hosted API adds Level 2 derivation, DESIGN.md context, projections, rules, custom motifs, signed usage receipts, integrity-checked single-screen artifacts, and verified runnable AppBundle projects.
6
6
 
7
7
  By totally decoupling intent from implementation, ViewSpec eliminates the "visual context window" problem. Agents no longer need to see pixels to build complex, perfectly styled, and deeply interactive applications.
8
8
 
@@ -182,7 +182,20 @@ viewspec mcp
182
182
  ```
183
183
  The MCP server exposes all intent-first local tools without requiring shell commands, including `validate_intent_bundle_file`, `compile_intent_bundle_file`, `verify_host`, `prove`, `validate_app_file`, `diff_app_files`, `compile_app`, and `prove_app`.
184
184
 
185
- For bounded React Tailwind runtime proof, run `viewspec prove --target react-tailwind-tsx --install --out .viewspec-proof --json` or `viewspec verify-host react-tailwind-output/ --target react-tailwind-tsx --install --json`. The host proof checks grid column/span counts, profiled aesthetic marker/layout assertions, and action payload behavior; JSON reports include `assertion_requirements` with `dom_count`, `style_assertion_count`, `aesthetic_layout_assertion_count`, `aesthetic_profile_assertion_count`, and `grid_span_assertion_count`.
185
+ For rendered conformance, compile React/Tailwind TSX and run:
186
+
187
+ ```bash
188
+ viewspec verify react-tailwind-output/ --install --json
189
+ ```
190
+
191
+ The verifier renders canonical mobile, tablet, and desktop viewports and writes integrity-addressed
192
+ screenshots, DOM snapshots, accessibility evidence, `result.json`, and a deterministic `repair.json`.
193
+ Exit codes distinguish conformant (`0`), nonconformant (`1`), and indeterminate (`2`) results.
194
+ `viewspec verify-host` remains the lower-level bounded host assertion proof for
195
+ grid column/span counts, profiled aesthetic markers/layout, and action payload behavior.
196
+ Its JSON `assertion_requirements` records `dom_count`, `style_assertion_count`, and
197
+ `aesthetic_layout_assertion_count` expectations from the checked manifest.
198
+ `viewspec prove` combines compilation checks with that proof.
186
199
 
187
200
  ## viewspec.dev
188
201
 
@@ -263,6 +276,12 @@ ast = compile_auto(builder.build_bundle())
263
276
 
264
277
  The hosted fallback requires the `remote` extra: `python -m pip install "viewspec[remote]"` (adds `httpx`). Without it, `compile_auto` runs locally and raises `ImportError` only if a hosted fallback is actually needed.
265
278
 
279
+ Paid agents can submit a complete AppBundle to `submit_verification_remote(...)` for compiled
280
+ route/state proof plus per-screen browser evidence and a signed receipt. For bounded autonomous
281
+ repair, `compile_until_conformant_remote(...)` repeats that paid compile-and-verify step while a
282
+ caller-owned repair callback edits the semantic AppBundle. ViewSpec enforces lineage, plan
283
+ stability, attempt limits, and no-progress termination.
284
+
266
285
  ### Theming with DESIGN.md
267
286
  The local SDK uses a strict YAML-front-matter `DESIGN.md` for offline HTML and IntentBundle compilation. The API requires exact sRGB hex values (e.g., `#FFFFFF`), enforcing strict design token discipline.
268
287
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "viewspec"
7
- version = "0.3.0b2"
7
+ version = "0.3.0b4"
8
8
  description = "Agent-native UI compiler. Agents describe intent; ViewSpec compiles concrete UI."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -14,7 +14,7 @@ authors = [
14
14
  ]
15
15
  keywords = ["ui", "compiler", "semantic", "viewspec", "composition-ir", "protobuf", "declarative", "agents"]
16
16
  classifiers = [
17
- "Development Status :: 3 - Alpha",
17
+ "Development Status :: 4 - Beta",
18
18
  "Intended Audience :: Developers",
19
19
  "License :: OSI Approved :: MIT License",
20
20
  "Programming Language :: Python :: 3",
@@ -38,12 +38,14 @@ viewspec = "viewspec.cli:main"
38
38
  dev = [
39
39
  "build>=1.2",
40
40
  "pytest>=8.0",
41
+ "hypothesis>=6.100,<7",
41
42
  "ruff>=0.4",
42
43
  "httpx>=0.27.0",
43
44
  "jsonschema>=4.18",
44
45
  ]
45
46
  remote = [
46
47
  "httpx>=0.27.0",
48
+ "cryptography>=48.0.1,<49",
47
49
  ]
48
50
  agents = [
49
51
  "mcp>=1.9.0",
@@ -46,6 +46,64 @@ from viewspec.compiler import (
46
46
  CompilerInputError,
47
47
  UnsupportedMotifError,
48
48
  )
49
+ from viewspec.conformance import (
50
+ CONFORMANCE_CORPUS_SCHEMA_VERSION,
51
+ ConformanceCase,
52
+ ConformanceCorpus,
53
+ load_conformance_corpus,
54
+ run_conformance_corpus,
55
+ )
56
+ from viewspec.convergence import (
57
+ CONVERGENCE_SCHEMA_VERSION,
58
+ ConvergenceAttempt,
59
+ ConvergenceRun,
60
+ MAX_CONVERGENCE_ATTEMPTS,
61
+ run_until_conformant,
62
+ )
63
+ from viewspec.hosted_artifacts import (
64
+ ARTIFACT_SCHEMA_VERSION,
65
+ ARTIFACT_TARGETS,
66
+ ArtifactContractError,
67
+ ArtifactCompilerIdentity,
68
+ ArtifactFile,
69
+ ArtifactProvenance,
70
+ ArtifactResponse,
71
+ ArtifactUsage,
72
+ compile_artifact_remote,
73
+ )
74
+ from viewspec.hosted_receipts import RECEIPT_ALGORITHM, ReceiptPublicKey, verify_usage_receipt
75
+ from viewspec.hosted_receipts import verify_signed_receipt
76
+ from viewspec.hosted_app_bundles import (
77
+ APP_BUILD_MANIFEST,
78
+ APP_BUILD_SCHEMA_VERSION,
79
+ APP_BUILD_TARGET,
80
+ AppBuildFile,
81
+ AppBuildProvenance,
82
+ AppBuildUsage,
83
+ AppBundleBuildContractError,
84
+ AppBundleBuildResponse,
85
+ compile_app_remote,
86
+ )
87
+ from viewspec.hosted_verification import (
88
+ HOSTED_VERIFICATION_SCHEMA_VERSION,
89
+ HOSTED_VERIFICATION_TARGET,
90
+ HostedEvidenceArtifact,
91
+ HostedVerificationContractError,
92
+ HostedVerificationError,
93
+ HostedVerificationJob,
94
+ HostedVerificationUsage,
95
+ build_verification_job_id,
96
+ compile_until_conformant_remote,
97
+ get_verification_remote,
98
+ submit_verification_remote,
99
+ verification_request_payload,
100
+ wait_verification_remote,
101
+ )
102
+ from viewspec.intent_contract import (
103
+ IntentBundleContractError,
104
+ normalize_intent_bundle_payload,
105
+ validate_intent_bundle_schema_version,
106
+ )
49
107
  from viewspec.motif_plugins import (
50
108
  MOTIF_PLUGIN_ABI_VERSION,
51
109
  MotifCompileContext,
@@ -171,6 +229,24 @@ from viewspec.host_verify import (
171
229
  verify_host_intent_file,
172
230
  verify_host_tool,
173
231
  )
232
+ from viewspec.local_verify import BrowserEvidence, BrowserPlanOutcome, verify_local_artifact
233
+ from viewspec.repair import (
234
+ REPAIR_SCHEMA_VERSION,
235
+ RepairDirective,
236
+ SourceNodePath,
237
+ VerificationRepairPlan,
238
+ )
239
+ from viewspec.verification import (
240
+ DEFAULT_VIEWPORTS,
241
+ EvidenceFile,
242
+ RetryLineage,
243
+ VERIFICATION_SCHEMA_VERSION,
244
+ VerificationDiagnostic,
245
+ VerificationPlan,
246
+ VerificationResult,
247
+ VerificationViewport,
248
+ build_verification_id,
249
+ )
174
250
  from viewspec.prove import prove, prove_tool
175
251
 
176
252
  __all__ = [
@@ -184,6 +260,12 @@ __all__ = [
184
260
  "CompileResponseMeta",
185
261
  "CompilerDiagnostic",
186
262
  "CompilerResult",
263
+ "CONFORMANCE_CORPUS_SCHEMA_VERSION",
264
+ "CONVERGENCE_SCHEMA_VERSION",
265
+ "ConformanceCase",
266
+ "ConformanceCorpus",
267
+ "ConvergenceAttempt",
268
+ "ConvergenceRun",
187
269
  "CompositionIR",
188
270
  "DesignFinding",
189
271
  "DesignMetadata",
@@ -214,6 +296,44 @@ __all__ = [
214
296
  "CompilerAPIError",
215
297
  "CompilerInputError",
216
298
  "UnsupportedMotifError",
299
+ "ARTIFACT_SCHEMA_VERSION",
300
+ "ARTIFACT_TARGETS",
301
+ "ArtifactContractError",
302
+ "ArtifactCompilerIdentity",
303
+ "ArtifactFile",
304
+ "ArtifactProvenance",
305
+ "ArtifactResponse",
306
+ "ArtifactUsage",
307
+ "compile_artifact_remote",
308
+ "RECEIPT_ALGORITHM",
309
+ "ReceiptPublicKey",
310
+ "verify_usage_receipt",
311
+ "verify_signed_receipt",
312
+ "APP_BUILD_MANIFEST",
313
+ "APP_BUILD_SCHEMA_VERSION",
314
+ "APP_BUILD_TARGET",
315
+ "AppBuildFile",
316
+ "AppBuildProvenance",
317
+ "AppBuildUsage",
318
+ "AppBundleBuildContractError",
319
+ "AppBundleBuildResponse",
320
+ "compile_app_remote",
321
+ "HOSTED_VERIFICATION_SCHEMA_VERSION",
322
+ "HOSTED_VERIFICATION_TARGET",
323
+ "HostedEvidenceArtifact",
324
+ "HostedVerificationContractError",
325
+ "HostedVerificationError",
326
+ "HostedVerificationJob",
327
+ "HostedVerificationUsage",
328
+ "build_verification_job_id",
329
+ "compile_until_conformant_remote",
330
+ "get_verification_remote",
331
+ "submit_verification_remote",
332
+ "verification_request_payload",
333
+ "wait_verification_remote",
334
+ "IntentBundleContractError",
335
+ "normalize_intent_bundle_payload",
336
+ "validate_intent_bundle_schema_version",
217
337
  "MOTIF_PLUGIN_ABI_VERSION",
218
338
  "MotifCompileContext",
219
339
  "MotifCompileResult",
@@ -325,11 +445,31 @@ __all__ = [
325
445
  "verify_host_artifact_dir",
326
446
  "verify_host_intent_file",
327
447
  "verify_host_tool",
448
+ "BrowserEvidence",
449
+ "BrowserPlanOutcome",
450
+ "verify_local_artifact",
451
+ "DEFAULT_VIEWPORTS",
452
+ "EvidenceFile",
453
+ "RetryLineage",
454
+ "REPAIR_SCHEMA_VERSION",
455
+ "RepairDirective",
456
+ "SourceNodePath",
457
+ "VERIFICATION_SCHEMA_VERSION",
458
+ "VerificationDiagnostic",
459
+ "VerificationPlan",
460
+ "VerificationResult",
461
+ "VerificationRepairPlan",
462
+ "VerificationViewport",
463
+ "build_verification_id",
328
464
  "prove",
329
465
  "prove_tool",
330
466
  # Utilities
331
467
  "build_address_index",
332
468
  "normalize_compiler_result",
469
+ "load_conformance_corpus",
470
+ "MAX_CONVERGENCE_ATTEMPTS",
333
471
  "parse_canonical_address",
334
472
  "resolve_address",
473
+ "run_conformance_corpus",
474
+ "run_until_conformant",
335
475
  ]
@@ -1,3 +1,3 @@
1
1
  """Package version for local source and installed entrypoints."""
2
2
 
3
- __version__ = "0.3.0b2"
3
+ __version__ = "0.3.0b4"
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import os
6
7
  import shutil
7
8
  import subprocess
8
9
  import tempfile
@@ -12,6 +13,7 @@ from typing import Any
12
13
 
13
14
  from viewspec.app_react import REACT_APP_MANIFEST, REACT_APP_TARGET
14
15
  from viewspec.local_tools import file_hash
16
+ from viewspec.node_runtime import materialize_prebuilt_node_modules
15
17
 
16
18
 
17
19
  APP_REACT_VERIFY_SCHEMA_VERSION = 1
@@ -43,12 +45,20 @@ def verify_react_app_artifact_dir(
43
45
  host_dir = Path(temp_name) / "app"
44
46
  _copy_artifact(artifact_path, host_dir)
45
47
  if install:
46
- install_result = _run_process(
47
- ["npm", "ci", "--ignore-scripts"],
48
- cwd=host_dir,
49
- timeout=APP_REACT_VERIFY_INSTALL_TIMEOUT_SECONDS,
50
- )
51
- _require_success(install_result, "APP_REACT_VERIFY_INSTALL_FAILED", "npm ci --ignore-scripts")
48
+ seed = os.environ.get("VIEWSPEC_HOST_VERIFY_NODE_MODULES_DIR")
49
+ if seed:
50
+ _link_prebuilt_node_modules(host_dir, Path(seed))
51
+ else:
52
+ install_result = _run_process(
53
+ ["npm", "ci", "--ignore-scripts"],
54
+ cwd=host_dir,
55
+ timeout=APP_REACT_VERIFY_INSTALL_TIMEOUT_SECONDS,
56
+ )
57
+ _require_success(
58
+ install_result,
59
+ "APP_REACT_VERIFY_INSTALL_FAILED",
60
+ "npm ci --ignore-scripts",
61
+ )
52
62
  else:
53
63
  source_modules = artifact_path / "node_modules"
54
64
  if not source_modules.is_dir():
@@ -87,7 +97,13 @@ def verify_react_app_artifact_dir(
87
97
  "install": bool(install),
88
98
  "assertions": assertions,
89
99
  "policy": {
90
- "install_command": "npm ci --ignore-scripts" if install else "none",
100
+ "install_command": (
101
+ "prebuilt_node_modules"
102
+ if install and os.environ.get("VIEWSPEC_HOST_VERIFY_NODE_MODULES_DIR")
103
+ else "npm ci --ignore-scripts"
104
+ if install
105
+ else "none"
106
+ ),
91
107
  "build_command": "npm run build",
92
108
  "browser_command": "npm run viewspec:verify",
93
109
  },
@@ -122,6 +138,30 @@ class ReactAppVerifyFailure(ValueError):
122
138
  self.fix = fix
123
139
 
124
140
 
141
+ def _link_prebuilt_node_modules(host_dir: Path, configured: Path) -> None:
142
+ if not configured.is_absolute():
143
+ raise ReactAppVerifyFailure(
144
+ "APP_REACT_VERIFY_DEPENDENCIES_MISSING",
145
+ "VIEWSPEC_HOST_VERIFY_NODE_MODULES_DIR must be an absolute path.",
146
+ "Configure an absolute prebuilt node_modules path and retry.",
147
+ )
148
+ seed = configured.resolve()
149
+ if not seed.is_dir():
150
+ raise ReactAppVerifyFailure(
151
+ "APP_REACT_VERIFY_DEPENDENCIES_MISSING",
152
+ "Configured prebuilt node_modules directory does not exist.",
153
+ "Build the configured dependency bundle and retry.",
154
+ )
155
+ destination = host_dir / "node_modules"
156
+ if destination.exists() or destination.is_symlink():
157
+ raise ReactAppVerifyFailure(
158
+ "APP_REACT_VERIFY_DEPENDENCIES_MISSING",
159
+ "Host node_modules destination must be empty before linking dependencies.",
160
+ "Remove the existing host dependency directory and retry.",
161
+ )
162
+ materialize_prebuilt_node_modules(destination, seed)
163
+
164
+
125
165
  def _preflight(artifact_dir: Path, manifest_path: Path) -> dict[str, Any]:
126
166
  if not artifact_dir.is_dir() or not manifest_path.is_file():
127
167
  raise ReactAppVerifyFailure(
@@ -55,6 +55,8 @@ from viewspec.local_tools import (
55
55
  init_design_file,
56
56
  source_hash,
57
57
  )
58
+ from viewspec.local_verify import verify_local_artifact
59
+ from viewspec.verification import VerificationPlan
58
60
  from viewspec.prove import PROVE_DEFAULT_OUT, prove
59
61
  from viewspec.mcp_server import MCP_INSTALL_HINT, MissingMCPDependency, mcp_dependency_available, run_mcp_server
60
62
  from viewspec.native_agents import NativeAgentError, VALID_TARGETS, init_agent_instructions
@@ -275,6 +277,27 @@ def _build_parser() -> argparse.ArgumentParser:
275
277
  verify_host_parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
276
278
  verify_host_parser.set_defaults(func=_verify_host_command)
277
279
 
280
+ verify_parser = subparsers.add_parser("verify", help="Verify rendered UI conformance across canonical viewports.")
281
+ verify_parser.add_argument("artifact_dir", help="Checked react-tailwind-tsx artifact directory.")
282
+ verify_parser.add_argument("--plan", help="Optional verification plan JSON file.")
283
+ verify_parser.add_argument(
284
+ "--evidence-out",
285
+ default=".viewspec-verification/evidence",
286
+ help="Empty directory where browser evidence will be written.",
287
+ )
288
+ verify_parser.add_argument(
289
+ "--report-out",
290
+ default=".viewspec-verification/result.json",
291
+ help="Canonical verification result JSON path.",
292
+ )
293
+ verify_parser.add_argument(
294
+ "--repair-out",
295
+ help="Repair plan JSON path. Defaults to repair.json beside --report-out.",
296
+ )
297
+ verify_parser.add_argument("--install", action="store_true", help="Allow npm ci in the isolated browser host.")
298
+ verify_parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
299
+ verify_parser.set_defaults(func=_verify_command)
300
+
278
301
  init_agent_parser = subparsers.add_parser("init-agent", help="Install managed ViewSpec instructions for coding agents.")
279
302
  init_agent_parser.add_argument("--target", required=True, choices=VALID_TARGETS, help="Agent instruction target to update.")
280
303
  init_agent_parser.add_argument("--root", default=".", help="Repository root for instruction files.")
@@ -1076,6 +1099,47 @@ def _verify_host_command(args: argparse.Namespace) -> int:
1076
1099
  return 0 if result["ok"] else 2
1077
1100
 
1078
1101
 
1102
+ def _verify_command(args: argparse.Namespace) -> int:
1103
+ plan = VerificationPlan.default()
1104
+ if args.plan:
1105
+ plan = VerificationPlan.from_json(json.loads(Path(args.plan).read_text(encoding="utf-8")))
1106
+ result = verify_local_artifact(
1107
+ args.artifact_dir,
1108
+ plan=plan,
1109
+ evidence_dir=args.evidence_out,
1110
+ report_out=args.report_out,
1111
+ install=bool(args.install),
1112
+ )
1113
+ from viewspec.repair import VerificationRepairPlan
1114
+
1115
+ repair = VerificationRepairPlan.from_result(result)
1116
+ repair_path = (
1117
+ Path(args.repair_out)
1118
+ if args.repair_out
1119
+ else Path(args.report_out).with_name("repair.json")
1120
+ )
1121
+ atomic_write(
1122
+ repair_path,
1123
+ json.dumps(repair.to_json(), indent=2, sort_keys=True) + "\n",
1124
+ )
1125
+ payload = {**result.to_json(), "repair_plan": repair.to_json()}
1126
+ if args.json:
1127
+ print(json.dumps(payload, indent=2, sort_keys=True))
1128
+ else:
1129
+ print(result.status)
1130
+ print(f"verification_id: {result.verification_id}")
1131
+ print(f"evidence_files: {len(result.evidence)}")
1132
+ print(f"next: {repair.disposition}")
1133
+ for diagnostic in result.diagnostics:
1134
+ location = f" [{diagnostic.viewport}]" if diagnostic.viewport else ""
1135
+ source = f" {diagnostic.source_ref}" if diagnostic.source_ref else ""
1136
+ print(f"{diagnostic.severity}: {diagnostic.code}{location}{source}: {diagnostic.message}")
1137
+ for directive in repair.directives:
1138
+ source = f" {directive.source_path.to_text()}" if directive.source_path else ""
1139
+ print(f"repair: {directive.repair_id}{source}: {directive.instruction}")
1140
+ return {"conformant": 0, "nonconformant": 1, "indeterminate": 2}[result.status]
1141
+
1142
+
1079
1143
  def _host_assertion_summary_lines(assertions: object, requirements: object = None) -> list[str]:
1080
1144
  if not isinstance(assertions, dict):
1081
1145
  return []
@@ -1803,8 +1803,66 @@ VIEWSPEC_API_URL = "https://api.viewspec.dev"
1803
1803
 
1804
1804
 
1805
1805
  class CompilerAPIError(Exception):
1806
- """Raised when the hosted compiler API returns an error."""
1807
- pass
1806
+ """Structured failure returned by the hosted compiler API."""
1807
+
1808
+ def __init__(
1809
+ self,
1810
+ message: str,
1811
+ *,
1812
+ code: str = "remote_error",
1813
+ path: str | None = None,
1814
+ status_code: int | None = None,
1815
+ request_id: str | None = None,
1816
+ retry_after: str | None = None,
1817
+ ) -> None:
1818
+ super().__init__(message)
1819
+ self.code = code
1820
+ self.path = path
1821
+ self.status_code = status_code
1822
+ self.request_id = request_id
1823
+ self.retry_after = retry_after
1824
+
1825
+
1826
+ def _response_header(response: Any, name: str) -> str | None:
1827
+ headers = getattr(response, "headers", {})
1828
+ value = headers.get(name) if hasattr(headers, "get") else None
1829
+ if value is None and hasattr(headers, "items"):
1830
+ value = next((item for key, item in headers.items() if str(key).lower() == name.lower()), None)
1831
+ return None if value is None else str(value)
1832
+
1833
+
1834
+ def _compiler_api_error(response: Any, data: Any = None) -> CompilerAPIError:
1835
+ status_code = int(getattr(response, "status_code", 0)) or None
1836
+ if data is None:
1837
+ try:
1838
+ data = response.json()
1839
+ except ValueError:
1840
+ data = None
1841
+ error = data.get("error") if isinstance(data, dict) else None
1842
+ structured = error if isinstance(error, dict) else {}
1843
+ message = str(structured.get("message") or "")
1844
+ code = str(structured.get("code") or "remote_error")
1845
+ path = structured.get("path") if isinstance(structured.get("path"), str) else None
1846
+ if not message and isinstance(data, dict) and isinstance(data.get("message"), str):
1847
+ message = data["message"]
1848
+ if status_code == 401 and not structured:
1849
+ message = "Invalid API key"
1850
+ code = "invalid_api_key"
1851
+ elif status_code == 429 and not structured:
1852
+ message = f"Rate limit exceeded: {message or 'Upgrade at viewspec.dev'}"
1853
+ code = "rate_limit_exceeded"
1854
+ elif not message:
1855
+ message = f"Compilation failed (HTTP {status_code})"
1856
+ request_id = data.get("request_id") if isinstance(data, dict) and isinstance(data.get("request_id"), str) else None
1857
+ request_id = request_id or _response_header(response, "X-Request-ID")
1858
+ return CompilerAPIError(
1859
+ message,
1860
+ code=code,
1861
+ path=path,
1862
+ status_code=status_code,
1863
+ request_id=request_id,
1864
+ retry_after=_response_header(response, "Retry-After"),
1865
+ )
1808
1866
 
1809
1867
 
1810
1868
  def _compile_request_payload(request: IntentBundle | CompileRequestPayload) -> CompileRequestPayload:
@@ -1859,7 +1917,9 @@ def compile_remote_response(
1859
1917
  timeout=30.0,
1860
1918
  )
1861
1919
  except httpx.HTTPError as exc:
1862
- raise CompilerAPIError(f"Remote compilation request failed: {exc}") from exc
1920
+ raise CompilerAPIError(
1921
+ f"Remote compilation request failed: {exc}", code="network_error"
1922
+ ) from exc
1863
1923
 
1864
1924
  def _response_json() -> dict[str, Any]:
1865
1925
  try:
@@ -1870,17 +1930,8 @@ def compile_remote_response(
1870
1930
  raise CompilerAPIError("Compilation failed: response JSON was not an object")
1871
1931
  return data
1872
1932
 
1873
- if response.status_code == 429:
1874
- data = _response_json()
1875
- raise CompilerAPIError(
1876
- f"Rate limit exceeded: {data.get('message', 'Upgrade at viewspec.dev')}"
1877
- )
1878
- if response.status_code == 401:
1879
- raise CompilerAPIError("Invalid API key")
1880
1933
  if response.status_code != 200:
1881
- raise CompilerAPIError(
1882
- f"Compilation failed (HTTP {response.status_code}): {response.text}"
1883
- )
1934
+ raise _compiler_api_error(response, _response_json())
1884
1935
 
1885
1936
  data = _response_json()
1886
1937
  if "ast" not in data: