ltcai 11.5.2 → 11.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +54 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Rebuild the OpenAPI contract from committed fragments and prove it exact.
|
|
3
|
+
|
|
4
|
+
README — what this is, and the one step that flips it on
|
|
5
|
+
========================================================
|
|
6
|
+
|
|
7
|
+
``frontend/openapi.json`` is the single committed contract and stays that way.
|
|
8
|
+
v11.6.0 moves most of the surface it describes out of Python, so the spec of
|
|
9
|
+
each migrated family lives beside the Rust crates as a committed fragment
|
|
10
|
+
(``rust/fixtures/openapi/<family>.json``, written by
|
|
11
|
+
``scripts/gen_openapi_fragments.py``). This script runs that split backwards:
|
|
12
|
+
|
|
13
|
+
envelope + every fragment == frontend/openapi.json, byte for byte
|
|
14
|
+
|
|
15
|
+
Byte-for-byte is the whole point. It is compared as *bytes*, not as parsed
|
|
16
|
+
JSON, so a re-ordered key, a changed indent or an ``int`` that became a
|
|
17
|
+
``float`` all fail here instead of surfacing as client drift later.
|
|
18
|
+
|
|
19
|
+
**Cutover (Wave 3 / WP-P1) — one step, deliberately not taken yet.**
|
|
20
|
+
Today ``scripts/export_openapi.py`` builds the whole FastAPI app and dumps its
|
|
21
|
+
schema; this script only *verifies*. Once the Python app is reduced to the
|
|
22
|
+
worker surface, its schema no longer describes the product, and the pipeline
|
|
23
|
+
flips:
|
|
24
|
+
|
|
25
|
+
1. Keep ``scripts/export_openapi.py`` exporting the (now worker-only) app, but
|
|
26
|
+
to a scratch path — it becomes an input, not the contract.
|
|
27
|
+
2. Regenerate ``rust/fixtures/openapi/worker_keep.json`` from that worker spec
|
|
28
|
+
(``gen_openapi_fragments.py`` still owns the fragment shape); the other
|
|
29
|
+
fragments are already committed and stop depending on Python entirely.
|
|
30
|
+
``info.version`` lives in ``_envelope.json``, so the fragments are
|
|
31
|
+
regenerated **after** the version bump, never before — the composer refuses
|
|
32
|
+
a worker spec whose version disagrees with theirs.
|
|
33
|
+
3. Point ``npm run frontend:openapi`` at
|
|
34
|
+
``scripts/compose_openapi.py --worker-spec <scratch> --output frontend/openapi.json``
|
|
35
|
+
followed by the existing ``openapi-typescript`` step, so the committed
|
|
36
|
+
contract is the composition rather than a Python dump.
|
|
37
|
+
4. Leave ``scripts/check_openapi_drift.mjs`` exactly as it is: it re-runs the
|
|
38
|
+
generator into a temp dir and byte-compares. Composition is deterministic,
|
|
39
|
+
so the gate keeps biting — it now bites on fragment drift too.
|
|
40
|
+
|
|
41
|
+
Until step 3 lands, ``--worker-spec`` is verification only: it asserts the
|
|
42
|
+
worker app's operations are a **subset** of the ``worker_keep`` fragment, i.e.
|
|
43
|
+
that Python has not grown a route the gateway would have to proxy without a
|
|
44
|
+
contract. That is the cutover's precondition, checkable today. The exception is
|
|
45
|
+
``EXEMPT_WORKER_PATHS``: WP-I6's internal ``/worker/*`` seams are loopback-only
|
|
46
|
+
and stay out of the public contract by design, and FastAPI's own docs
|
|
47
|
+
endpoints describe the schema rather than being surface. Both are named
|
|
48
|
+
explicitly below so nothing else slips through with them.
|
|
49
|
+
|
|
50
|
+
Usage::
|
|
51
|
+
|
|
52
|
+
.venv/bin/python scripts/compose_openapi.py
|
|
53
|
+
.venv/bin/python scripts/compose_openapi.py --worker-spec /tmp/worker.json
|
|
54
|
+
.venv/bin/python scripts/compose_openapi.py --output /tmp/composed.json
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
import argparse
|
|
60
|
+
import copy
|
|
61
|
+
import difflib
|
|
62
|
+
import importlib.util
|
|
63
|
+
import json
|
|
64
|
+
import sys
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
from typing import Any, Dict, List, Mapping, Tuple
|
|
67
|
+
|
|
68
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
69
|
+
|
|
70
|
+
# ``scripts`` is not a package, so the generator is loaded by path — the same
|
|
71
|
+
# trick the parity generators and their tests use. Importing it rather than
|
|
72
|
+
# retyping its constants is what keeps *one* serialiser in the pipeline: if the
|
|
73
|
+
# two halves could disagree about ``sort_keys`` or ``indent``, byte-exactness
|
|
74
|
+
# would be a coincidence instead of a property.
|
|
75
|
+
def _load_module(name: str, path: Path) -> Any:
|
|
76
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
77
|
+
if spec is None or spec.loader is None: # pragma: no cover - packaging accident
|
|
78
|
+
raise ImportError(f"cannot load {path}")
|
|
79
|
+
module = importlib.util.module_from_spec(spec)
|
|
80
|
+
spec.loader.exec_module(module)
|
|
81
|
+
return module
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
fragments_module = _load_module(
|
|
85
|
+
"gen_openapi_fragments",
|
|
86
|
+
Path(__file__).resolve().parent / "gen_openapi_fragments.py",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
SPEC_PATH = fragments_module.SPEC_PATH
|
|
90
|
+
FRAGMENT_DIR = fragments_module.FRAGMENT_DIR
|
|
91
|
+
ENVELOPE_NAME = fragments_module.ENVELOPE_NAME
|
|
92
|
+
INDEX_NAME = fragments_module.INDEX_NAME
|
|
93
|
+
WORKER_FAMILY = fragments_module.WORKER_FAMILY
|
|
94
|
+
canonical_json = fragments_module.canonical_json
|
|
95
|
+
sha256_text = fragments_module.sha256_text
|
|
96
|
+
load_json = fragments_module.load_json
|
|
97
|
+
operation_key = fragments_module.operation_key
|
|
98
|
+
spec_operations = fragments_module.spec_operations
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
#: WP-I6's internal worker seams. The gateway calls them over loopback behind
|
|
102
|
+
#: the seam gate; no client ever does, so they are deliberately absent from the
|
|
103
|
+
#: committed public contract and must not be filed into the ``worker_keep``
|
|
104
|
+
#: fragment. They are exempt from the subset check — and only they are.
|
|
105
|
+
INTERNAL_WORKER_PATHS = frozenset(
|
|
106
|
+
{
|
|
107
|
+
# ``/worker/chat/record-turn`` was the third state seam. WP-W3a moved
|
|
108
|
+
# the history chain into lattice-chat and WP-P1 deleted the handler.
|
|
109
|
+
"/worker/sysinfo",
|
|
110
|
+
"/worker/llm/stream",
|
|
111
|
+
# Wave 2.5 §W2 — the pure-compute seams. Same reasoning: the gateway
|
|
112
|
+
# calls them over loopback behind the seam gate, and the shapes they
|
|
113
|
+
# exchange (vectors, document bytes, parsed text) are an internal
|
|
114
|
+
# protocol between lattice-host and its own worker, not client surface.
|
|
115
|
+
"/worker/embed",
|
|
116
|
+
"/worker/parse",
|
|
117
|
+
"/worker/render/docx",
|
|
118
|
+
"/worker/render/xlsx",
|
|
119
|
+
"/worker/render/pptx",
|
|
120
|
+
"/worker/render/pdf",
|
|
121
|
+
"/worker/asr",
|
|
122
|
+
"/worker/multimodal/describe",
|
|
123
|
+
"/worker/extract",
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
#: FastAPI's own documentation endpoints. They are normally ``include_in_schema
|
|
128
|
+
#: = False`` and never reach ``paths`` (the committed contract has none of
|
|
129
|
+
#: them), but a worker app that turns docs on should not fail this check either
|
|
130
|
+
#: — they describe the schema, they are not product surface.
|
|
131
|
+
DOC_PATHS = frozenset({"/openapi.json", "/docs", "/docs/oauth2-redirect", "/redoc"})
|
|
132
|
+
|
|
133
|
+
#: Everything a worker spec may serve without a committed contract entry.
|
|
134
|
+
EXEMPT_WORKER_PATHS = INTERNAL_WORKER_PATHS | DOC_PATHS
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CompositionError(Exception):
|
|
138
|
+
"""A fragment set that cannot be reassembled into one coherent spec."""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def read_fragment_set(
|
|
142
|
+
directory: Path,
|
|
143
|
+
) -> Tuple[Dict[str, Any], Dict[str, Dict[str, Any]], List[str]]:
|
|
144
|
+
"""Load the envelope and every fragment ``_index.json`` lists.
|
|
145
|
+
|
|
146
|
+
Returns ``(envelope, fragments_by_family, problems)``. Recorded sha256 values
|
|
147
|
+
are checked here: a hand-edited fragment is reported as stale rather than
|
|
148
|
+
silently composed, because the index is what the Rust tests and CI read.
|
|
149
|
+
"""
|
|
150
|
+
index_path = directory / INDEX_NAME
|
|
151
|
+
if not index_path.is_file():
|
|
152
|
+
raise CompositionError(
|
|
153
|
+
f"{index_path} is missing — run scripts/gen_openapi_fragments.py"
|
|
154
|
+
)
|
|
155
|
+
index = load_json(index_path)
|
|
156
|
+
problems: List[str] = []
|
|
157
|
+
|
|
158
|
+
envelope_path = directory / index["envelope"]["file"]
|
|
159
|
+
if not envelope_path.is_file():
|
|
160
|
+
raise CompositionError(f"{envelope_path} is missing — regenerate the fragments")
|
|
161
|
+
envelope_text = envelope_path.read_text(encoding="utf-8")
|
|
162
|
+
if sha256_text(envelope_text) != index["envelope"]["sha256"]:
|
|
163
|
+
problems.append(f"{envelope_path.name}: sha256 does not match {INDEX_NAME}")
|
|
164
|
+
envelope = load_json(envelope_path)
|
|
165
|
+
|
|
166
|
+
fragments: Dict[str, Dict[str, Any]] = {}
|
|
167
|
+
for entry in index["fragments"]:
|
|
168
|
+
path = directory / entry["file"]
|
|
169
|
+
if not path.is_file():
|
|
170
|
+
raise CompositionError(f"{path} is missing — regenerate the fragments")
|
|
171
|
+
text = path.read_text(encoding="utf-8")
|
|
172
|
+
if sha256_text(text) != entry["sha256"]:
|
|
173
|
+
problems.append(f"{entry['file']}: sha256 does not match {INDEX_NAME}")
|
|
174
|
+
fragments[entry["family"]] = load_json(path)
|
|
175
|
+
return envelope, fragments, problems
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def merge_fragments(
|
|
179
|
+
envelope: Mapping[str, Any],
|
|
180
|
+
fragments: Mapping[str, Dict[str, Any]],
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
"""Reassemble one spec. Raises on a collision rather than picking a winner."""
|
|
183
|
+
spec: Dict[str, Any] = copy.deepcopy(dict(envelope["spec"]))
|
|
184
|
+
paths: Dict[str, Any] = {}
|
|
185
|
+
owner: Dict[str, str] = {}
|
|
186
|
+
schemas: Dict[str, Any] = dict(spec["components"]["schemas"])
|
|
187
|
+
|
|
188
|
+
for family in sorted(fragments):
|
|
189
|
+
fragment = fragments[family]
|
|
190
|
+
for path, item in fragment["paths"].items():
|
|
191
|
+
for method, operation in item.items():
|
|
192
|
+
key = operation_key(method, path)
|
|
193
|
+
if key in owner:
|
|
194
|
+
raise CompositionError(
|
|
195
|
+
f"{key} is claimed by both {owner[key]} and {family} — "
|
|
196
|
+
"one operation, one family"
|
|
197
|
+
)
|
|
198
|
+
owner[key] = family
|
|
199
|
+
paths.setdefault(path, {})[method] = operation
|
|
200
|
+
for name, schema in fragment["components"]["schemas"].items():
|
|
201
|
+
existing = schemas.get(name)
|
|
202
|
+
if existing is not None and canonical_json(existing) != canonical_json(schema):
|
|
203
|
+
raise CompositionError(
|
|
204
|
+
f"schema {name} differs between fragments (seen again in {family}) — "
|
|
205
|
+
"shared schemas must be byte-identical copies"
|
|
206
|
+
)
|
|
207
|
+
schemas[name] = schema
|
|
208
|
+
|
|
209
|
+
spec["paths"] = paths
|
|
210
|
+
spec["components"]["schemas"] = schemas
|
|
211
|
+
return spec
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def diff_report(composed_text: str, committed_text: str, limit: int = 40) -> List[str]:
|
|
215
|
+
"""A readable account of how the composition misses the committed bytes."""
|
|
216
|
+
composed = load_json_text(composed_text)
|
|
217
|
+
committed = load_json_text(committed_text)
|
|
218
|
+
lines: List[str] = []
|
|
219
|
+
|
|
220
|
+
composed_ops = {key for key, _, _ in spec_operations(composed)}
|
|
221
|
+
committed_ops = {key for key, _, _ in spec_operations(committed)}
|
|
222
|
+
for label, missing in (
|
|
223
|
+
("missing from the composition", sorted(committed_ops - composed_ops)),
|
|
224
|
+
("present only in the composition", sorted(composed_ops - committed_ops)),
|
|
225
|
+
):
|
|
226
|
+
if missing:
|
|
227
|
+
lines.append(f"{len(missing)} operation(s) {label}:")
|
|
228
|
+
lines.extend(f" {key}" for key in missing[:limit])
|
|
229
|
+
if len(missing) > limit:
|
|
230
|
+
lines.append(f" … {len(missing) - limit} more")
|
|
231
|
+
|
|
232
|
+
composed_schemas = set(composed["components"]["schemas"])
|
|
233
|
+
committed_schemas = set(committed["components"]["schemas"])
|
|
234
|
+
for label, names in (
|
|
235
|
+
("missing from the composition", sorted(committed_schemas - composed_schemas)),
|
|
236
|
+
("present only in the composition", sorted(composed_schemas - committed_schemas)),
|
|
237
|
+
):
|
|
238
|
+
if names:
|
|
239
|
+
lines.append(f"{len(names)} schema(s) {label}: {', '.join(names[:limit])}")
|
|
240
|
+
|
|
241
|
+
delta = list(
|
|
242
|
+
difflib.unified_diff(
|
|
243
|
+
committed_text.splitlines(),
|
|
244
|
+
composed_text.splitlines(),
|
|
245
|
+
fromfile="frontend/openapi.json (committed)",
|
|
246
|
+
tofile="composed from rust/fixtures/openapi/",
|
|
247
|
+
lineterm="",
|
|
248
|
+
n=1,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
if delta:
|
|
252
|
+
lines.append(f"first {min(limit, len(delta))} diff line(s) of {len(delta)}:")
|
|
253
|
+
lines.extend(f" {line}" for line in delta[:limit])
|
|
254
|
+
return lines
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def load_json_text(text: str) -> Dict[str, Any]:
|
|
258
|
+
parsed: Dict[str, Any] = json.loads(text)
|
|
259
|
+
return parsed
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def worker_spec_problems(
|
|
263
|
+
worker_spec: Mapping[str, Any],
|
|
264
|
+
worker_fragment: Mapping[str, Any],
|
|
265
|
+
envelope: Mapping[str, Any],
|
|
266
|
+
) -> List[str]:
|
|
267
|
+
"""Check the worker app against the ``worker_keep`` fragment.
|
|
268
|
+
|
|
269
|
+
Two things are asserted. First the subset: every operation the worker app
|
|
270
|
+
still serves must have a committed contract in the fragment, or the gateway
|
|
271
|
+
would be proxying a route nothing describes. Second the version: the
|
|
272
|
+
fragments embed ``info.version``, so a bump that regenerated the worker spec
|
|
273
|
+
without regenerating the fragments is drift — the same trap that reddens the
|
|
274
|
+
drift gate when ``npm run frontend:openapi`` runs before the bump.
|
|
275
|
+
|
|
276
|
+
``EXEMPT_WORKER_PATHS`` is the one hole, and it is a named list rather than
|
|
277
|
+
a pattern: WP-I6's internal seams are loopback-only and seam-gated, so they
|
|
278
|
+
are product surface for the gateway alone and never enter the committed
|
|
279
|
+
public contract. Anything else the worker grows still fails here.
|
|
280
|
+
"""
|
|
281
|
+
problems: List[str] = []
|
|
282
|
+
kept = {
|
|
283
|
+
operation_key(method, path)
|
|
284
|
+
for path, item in worker_fragment["paths"].items()
|
|
285
|
+
for method in item
|
|
286
|
+
}
|
|
287
|
+
served = {
|
|
288
|
+
key for key, path, _ in spec_operations(worker_spec) if path not in EXEMPT_WORKER_PATHS
|
|
289
|
+
}
|
|
290
|
+
extra = sorted(served - kept)
|
|
291
|
+
if extra:
|
|
292
|
+
problems.append(
|
|
293
|
+
f"{len(extra)} worker operation(s) are not in the {WORKER_FAMILY} fragment — "
|
|
294
|
+
"the gateway would have to proxy a route with no committed contract:"
|
|
295
|
+
)
|
|
296
|
+
problems.extend(f" {key}" for key in extra)
|
|
297
|
+
|
|
298
|
+
worker_version = worker_spec.get("info", {}).get("version")
|
|
299
|
+
fragment_version = envelope["spec"]["info"]["version"]
|
|
300
|
+
if worker_version != fragment_version:
|
|
301
|
+
problems.append(
|
|
302
|
+
f"worker spec is version {worker_version!r} but the fragments carry "
|
|
303
|
+
f"{fragment_version!r} — regenerate the fragments after the bump"
|
|
304
|
+
)
|
|
305
|
+
return problems
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def compose(
|
|
309
|
+
directory: Path = FRAGMENT_DIR,
|
|
310
|
+
spec_path: Path = SPEC_PATH,
|
|
311
|
+
worker_spec_path: Path | None = None,
|
|
312
|
+
) -> Tuple[str, List[str]]:
|
|
313
|
+
"""Compose, verify, and return ``(composed_text, problems)``."""
|
|
314
|
+
envelope, fragments, problems = read_fragment_set(directory)
|
|
315
|
+
composed_text = canonical_json(merge_fragments(envelope, fragments))
|
|
316
|
+
committed_text = spec_path.read_text(encoding="utf-8")
|
|
317
|
+
|
|
318
|
+
if composed_text != committed_text:
|
|
319
|
+
problems.append(
|
|
320
|
+
f"composition does not reproduce {spec_path.name} byte for byte "
|
|
321
|
+
f"({len(composed_text)} vs {len(committed_text)} bytes)"
|
|
322
|
+
)
|
|
323
|
+
problems.extend(diff_report(composed_text, committed_text))
|
|
324
|
+
|
|
325
|
+
if worker_spec_path is not None:
|
|
326
|
+
worker_spec = load_json(worker_spec_path)
|
|
327
|
+
problems.extend(worker_spec_problems(worker_spec, fragments[WORKER_FAMILY], envelope))
|
|
328
|
+
return composed_text, problems
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def main(argv: List[str] | None = None) -> int:
|
|
332
|
+
parser = argparse.ArgumentParser(description="Compose the OpenAPI contract from fragments.")
|
|
333
|
+
parser.add_argument("--fragments", type=Path, default=FRAGMENT_DIR, help="fragment directory")
|
|
334
|
+
parser.add_argument("--spec", type=Path, default=SPEC_PATH, help="committed contract")
|
|
335
|
+
parser.add_argument(
|
|
336
|
+
"--worker-spec",
|
|
337
|
+
type=Path,
|
|
338
|
+
default=None,
|
|
339
|
+
help="OpenAPI schema of the Python worker app; asserted ⊆ the worker_keep fragment",
|
|
340
|
+
)
|
|
341
|
+
parser.add_argument(
|
|
342
|
+
"--output",
|
|
343
|
+
type=Path,
|
|
344
|
+
default=None,
|
|
345
|
+
help="write the composed spec here (the P1 cutover writes frontend/openapi.json)",
|
|
346
|
+
)
|
|
347
|
+
args = parser.parse_args(argv)
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
composed_text, problems = compose(args.fragments, args.spec, args.worker_spec)
|
|
351
|
+
except CompositionError as error:
|
|
352
|
+
print(f"openapi composition failed: {error}")
|
|
353
|
+
return 1
|
|
354
|
+
|
|
355
|
+
if problems:
|
|
356
|
+
print("openapi composition failed:")
|
|
357
|
+
for problem in problems:
|
|
358
|
+
print(f" {problem}")
|
|
359
|
+
print(" fix: rerun scripts/gen_openapi_fragments.py and commit the fragments")
|
|
360
|
+
return 1
|
|
361
|
+
|
|
362
|
+
if args.output is not None:
|
|
363
|
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
364
|
+
args.output.write_text(composed_text, encoding="utf-8")
|
|
365
|
+
print(f"wrote {args.output}")
|
|
366
|
+
|
|
367
|
+
spec = load_json_text(composed_text)
|
|
368
|
+
checked = f" (+worker spec ⊆ {WORKER_FAMILY})" if args.worker_spec else ""
|
|
369
|
+
print(
|
|
370
|
+
f"composed {len(spec['paths'])} paths / {len(spec_operations(spec))} operations "
|
|
371
|
+
f"from {args.fragments} — byte-identical to {args.spec}{checked}"
|
|
372
|
+
)
|
|
373
|
+
return 0
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
if __name__ == "__main__":
|
|
377
|
+
sys.exit(main())
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Export the
|
|
2
|
+
"""Export the AI-Worker's OpenAPI schema — one input to the composed contract.
|
|
3
|
+
|
|
4
|
+
Until v11.6.0 this *was* the contract: it built the 464-route product app and
|
|
5
|
+
wrote ``frontend/openapi.json``. WP-P1 deleted that app. The committed contract
|
|
6
|
+
is unchanged (421 paths / 463 operations — clients still call all of them) but
|
|
7
|
+
its source is now ``scripts/compose_openapi.py``, which reassembles the
|
|
8
|
+
per-crate fragments in ``rust/fixtures/openapi/``.
|
|
9
|
+
|
|
10
|
+
What this script exports is the worker half, to a **scratch** path, so the
|
|
11
|
+
composer can check it: every operation the worker serves must already be in
|
|
12
|
+
``worker_keep.json`` (the gateway will not proxy a route with no contract), and
|
|
13
|
+
its ``info.version`` must agree with the fragments. It is an input, not the
|
|
14
|
+
output — see WP-I5 §"P1 cutover".
|
|
15
|
+
|
|
16
|
+
node scripts/run_python.mjs scripts/export_openapi.py <scratch>/worker.json
|
|
17
|
+
node scripts/run_python.mjs scripts/compose_openapi.py \
|
|
18
|
+
--worker-spec <scratch>/worker.json --output frontend/openapi.json
|
|
19
|
+
"""
|
|
3
20
|
|
|
4
21
|
from __future__ import annotations
|
|
5
22
|
|
|
@@ -72,15 +89,26 @@ def isolated_runtime_environment(root: Path) -> Iterator[Mapping[str, str]]:
|
|
|
72
89
|
|
|
73
90
|
|
|
74
91
|
def main() -> int:
|
|
75
|
-
|
|
92
|
+
# No default of ``frontend/openapi.json``: that file is the composer's
|
|
93
|
+
# output now, and silently overwriting it with the 28-route worker spec is
|
|
94
|
+
# exactly the mistake this argument exists to prevent.
|
|
95
|
+
if len(sys.argv) < 2:
|
|
96
|
+
print(
|
|
97
|
+
"usage: export_openapi.py <output.json>\n"
|
|
98
|
+
" the committed contract is written by scripts/compose_openapi.py;\n"
|
|
99
|
+
" this exports the worker half to a scratch path it consumes.",
|
|
100
|
+
file=sys.stderr,
|
|
101
|
+
)
|
|
102
|
+
return 2
|
|
103
|
+
target = Path(sys.argv[1])
|
|
76
104
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
77
105
|
with tempfile.TemporaryDirectory(prefix="ltcai-openapi-") as temp_dir:
|
|
78
106
|
with isolated_runtime_environment(Path(temp_dir)):
|
|
79
107
|
# Import only after isolation is active in case a future dependency
|
|
80
108
|
# starts reading environment or user paths during module import.
|
|
81
|
-
from latticeai.
|
|
109
|
+
from latticeai.worker_app import create_worker_app
|
|
82
110
|
|
|
83
|
-
app =
|
|
111
|
+
app = create_worker_app()
|
|
84
112
|
schema = app.openapi()
|
|
85
113
|
target.write_text(json.dumps(schema, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
|
86
114
|
print(f"wrote {target} with {len(schema.get('paths', {}))} paths")
|