theurian 0.1.0.dev6__tar.gz → 0.1.0.dev7__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.
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/CHANGELOG.md +39 -3
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/PKG-INFO +1 -1
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/pyproject.toml +1 -1
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/__init__.py +1 -1
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/proposal_service.py +20 -4
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/propose_commands.py +180 -6
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/migration.py +12 -4
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/migration_loader.py +6 -4
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_proposal_service.py +4 -2
- theurian-0.1.0.dev7/tests/integration/test_propose_cli.py +989 -0
- theurian-0.1.0.dev6/tests/integration/test_propose_cli.py +0 -386
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/.gitignore +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/LICENSE +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/README.md +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/hatch_build.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/forest_builder.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/index_builder.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/ingestion_service.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/migration_alias_guards.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/migration_body_guards.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/migration_engine.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/project_service.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/retrieval_service.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_context.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_service.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_steps.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_withholding.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/visibility.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/withdrawal_purge.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/auth_commands.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/commands.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/context.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/index_commands.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/main.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/setup_commands.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/instance.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/runner.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/server.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/chunking.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/compatibility.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/context.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/enums.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/errors.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/extras.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/identifiers.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ingestion.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/knowledge.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/authorization.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/canonical_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/daemon_manager.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/determinism.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/embedding.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/index_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/mcp_client_config.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/object_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/reranking.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/review_provider.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/secret_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/source_parser.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/specification_provider.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/summarization.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/vector_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/project.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/proposal.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ranking.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/raptor.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/retrieval.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/review.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/setup.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/specification.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/state.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/values.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/indexing/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/claude/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/claude/mcp_config.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/determinism.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/embedding/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/embedding/hashing.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/markdown.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/openapi.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/registry.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/structured.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/git/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/github/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/raptor/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/raptor/extractive.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/secrets/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/secrets/file_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/launchagent.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/runner.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/systemd_user.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/connection.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_forest.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_purge.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_query.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_scan.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_schema.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/schema.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/vector/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/ingestion/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/results.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/search.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/tools.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/migrations/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/normalization/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/normalization/projection.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/observability/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/retrieval/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/review/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/env_file.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/paths.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/tokens.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/yaml_loading.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/specification/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/traceability/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/command_extraction.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/command_population.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/conftest.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/__init__.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/clock.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/ids.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/pages.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/setup.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_absence_proof.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_alias_item_id_collision.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_auth_rotate.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_bare_install.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_canonical_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_canonical_store_corruption.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_claude_mcp_config.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_cli_commands.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_cli_help_without_rich.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_daemon.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_builder.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_builder_scale.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_node_scope.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_purge_equality.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_purge_recompute.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_retrieval.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_store_retrieval.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_gc_during_a_search.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_fallback.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_gc_cli.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_purge.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_purge_differential.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_purge_nodes.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_scan_fold.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_schema_v4.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_store.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_ingestion.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_init_gitignore_block.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_mcp_tools.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_project_registry.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_retrieval_service.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_revision_id_reuse.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_scan_exhaustion.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_service_adapters.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_session_start_hook.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_cli.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_env_file.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_journal.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_partial_failure.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_probe_assertions.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_report_withholding.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_service.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_short_query_retrieval.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_state_provenance.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_unreadable_registry_surface.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_wire_contract.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_withdrawal_purge.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_writer_contract.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_artifact_integrity_claim.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_candidate_cut.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_candidate_depth.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_chunking.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_cli_help_rendering.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_command_extraction.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_compatibility.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_config_key_call_sites.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_daemon_extra.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_documented_commands.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_domain_invariants.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_env_file_merge.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_examples.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_extractive_summarizer.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_forest_derivation.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_forest_fanout.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_gate_call_sites.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_hashing_embedding.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ingest_manifest_path.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_install_claims.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_layering.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_alias_guards.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_engine.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_loader_errors.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_loader_identity.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_network_call_sites.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_parsers.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_path_security.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_plugin_boundary.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ports.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_project_and_traceability.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_project_registry_errors.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_projection.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_proposal.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ranking.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_raptor_scope.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ref_recording.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_resolve_context_call_sites.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_result_gate_session.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_result_payload.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_retrieval_depth.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_schemas.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_scope_isolation.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_secret_detector.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_setup_changed_paths.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_setup_claims.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_setup_domain.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_state_hash.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_test_fixtures.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_tokens.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_yaml_loading.py +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/README.md +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/cli/version.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/config/project-config.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/knowledge/retrieval-result.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/knowledge-search-response.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/knowledge-status-response.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/project-list-response.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/retrieval-metadata.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/tool-context.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/migrations/migration.schema.json +0 -0
- {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/protocol/compatibility.schema.json +0 -0
|
@@ -12,13 +12,48 @@ Pre-1.0, a MINOR bump may change the protocol. Post-1.0, only a MAJOR may.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.1.0.dev7] - 2026-08-19
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`theurian propose` can now set `--scope-path` and `--label`**
|
|
20
|
+
([#249](https://github.com/theurian/theurian/issues/249)). Each is repeatable
|
|
21
|
+
and keeps the order it was given: `--scope-path` writes `metadata.scope.paths`
|
|
22
|
+
(the globs Milestone 8's drift detection will read), and `--label` writes
|
|
23
|
+
`metadata.labels`. A `--label` given alongside `--authored-here` is
|
|
24
|
+
de-duplicated against the implied `authored-in-theurian` label — the schema
|
|
25
|
+
declares `labels` `uniqueItems`, so a duplicate would otherwise turn a legal
|
|
26
|
+
invocation into a refusal. An enhancement rather than a fix: before this, these
|
|
27
|
+
fields were simply unreachable from the propose flow, and their absence
|
|
28
|
+
published nothing false.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **A maintainer-reviewed, public ADR accepted through `propose` → `accept`
|
|
33
|
+
published `trustLevel: unverified` and `sensitivity: internal` on every result**
|
|
34
|
+
([#249](https://github.com/theurian/theurian/issues/249)). `theurian propose`
|
|
35
|
+
could express no `trustLevel` or `sensitivity`, so a corpus built by the shipped
|
|
36
|
+
flow could only ever carry the schema defaults; the loader completed the omitted
|
|
37
|
+
fields silently, and both reach every `knowledge.search` and `knowledge.get`
|
|
38
|
+
result. Measured on the first dogfooding slice: three publicly readable ADRs a
|
|
39
|
+
maintainer had merged were published as `unverified`/`internal` beside
|
|
40
|
+
`status: approved`, and hand-editing the generated YAML was the only remedy.
|
|
41
|
+
`propose` now takes `--trust-level {unverified|inferred|reviewed|authoritative}`
|
|
42
|
+
and `--sensitivity {public|internal|confidential|restricted}`, on both the
|
|
43
|
+
create and update flows, so a false value is correctable rather than permanent.
|
|
44
|
+
When either is omitted the default is not stamped into the migration — that
|
|
45
|
+
would assert a judgement the caller never made — but is named in the draft's
|
|
46
|
+
`--json` next steps, so an omission is a surfaced choice rather than a silent
|
|
47
|
+
false positive.
|
|
48
|
+
|
|
15
49
|
## [0.1.0.dev6] - 2026-08-19
|
|
16
50
|
|
|
17
51
|
### Fixed
|
|
18
52
|
|
|
19
53
|
- **A rejected item's relation note reached an approved item's `knowledge.get`
|
|
20
|
-
through an alias-id collision**
|
|
21
|
-
|
|
54
|
+
through an alias-id collision**
|
|
55
|
+
([GHSA-vx8x-rjfj-9x54](https://github.com/theurian/theurian/security/advisories/GHSA-vx8x-rjfj-9x54)).
|
|
56
|
+
Critical; affected 0.1.0.dev0–0.1.0.dev5. An `addAlias` whose key
|
|
22
57
|
equalled the id of a live `rejected` item, while the alias pointed at an
|
|
23
58
|
approved item, defeated the relation-visibility gate: `get_item` resolves an
|
|
24
59
|
alias before it looks up a status, so a lookup for the retired id resolved to
|
|
@@ -42,7 +77,8 @@ Pre-1.0, a MINOR bump may change the protocol. Post-1.0, only a MAJOR may.
|
|
|
42
77
|
### Changed
|
|
43
78
|
|
|
44
79
|
- **BREAKING — an alias key may not collide with a non-deprecated item id across a
|
|
45
|
-
migration set**
|
|
80
|
+
migration set**
|
|
81
|
+
([GHSA-vx8x-rjfj-9x54](https://github.com/theurian/theurian/security/advisories/GHSA-vx8x-rjfj-9x54)).
|
|
46
82
|
|
|
47
83
|
**Old shape:** a set whose `addAlias` key equalled a live item's id applied
|
|
48
84
|
silently. With that item `rejected` and the alias pointing at an approved item,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: theurian
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev7
|
|
4
4
|
Summary: Git-native engineering knowledge platform: versioned, traceable context for AI agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/theurian/theurian
|
|
6
6
|
Project-URL: Documentation, https://github.com/theurian/theurian/tree/main/docs
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "theurian"
|
|
7
|
-
version = "0.1.0.
|
|
7
|
+
version = "0.1.0.dev7"
|
|
8
8
|
description = "Git-native engineering knowledge platform: versioned, traceable context for AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -8,7 +8,7 @@ plugin must never import them (ADR-0001, CP-2).
|
|
|
8
8
|
|
|
9
9
|
from theurian.domain.compatibility import CURRENT_PROTOCOL_VERSION
|
|
10
10
|
|
|
11
|
-
__version__ = "0.1.0.
|
|
11
|
+
__version__ = "0.1.0.dev7"
|
|
12
12
|
__protocol_version__ = CURRENT_PROTOCOL_VERSION
|
|
13
13
|
|
|
14
14
|
__all__ = ["__protocol_version__", "__version__"]
|
|
@@ -46,7 +46,7 @@ from typing import Final
|
|
|
46
46
|
import yaml
|
|
47
47
|
|
|
48
48
|
from theurian.application.project_service import ProjectPaths
|
|
49
|
-
from theurian.domain.enums import KnowledgeKind, KnowledgeStatus
|
|
49
|
+
from theurian.domain.enums import KnowledgeKind, KnowledgeStatus, Sensitivity, TrustLevel
|
|
50
50
|
from theurian.domain.errors import PathEscapeError, TheurianError
|
|
51
51
|
from theurian.domain.identifiers import ItemId, MigrationId, ProposalId, RevisionId
|
|
52
52
|
from theurian.domain.knowledge import AUTHORED_IN_THEURIAN, SourceAnchor
|
|
@@ -131,6 +131,13 @@ class ProposalRequest:
|
|
|
131
131
|
evidence: Evidence
|
|
132
132
|
source_anchors: tuple[SourceAnchor, ...] = ()
|
|
133
133
|
labels: tuple[str, ...] = ()
|
|
134
|
+
scope_paths: tuple[str, ...] = ()
|
|
135
|
+
#: Absent means "not stated": the field is left out of the migration and the
|
|
136
|
+
#: loader applies the schema default. A value is never invented here, because
|
|
137
|
+
#: `unverified`/`internal` written into every draft would assert a judgement
|
|
138
|
+
#: the caller did not make (#249).
|
|
139
|
+
trust_level: TrustLevel | None = None
|
|
140
|
+
sensitivity: Sensitivity | None = None
|
|
134
141
|
namespace: str | None = None
|
|
135
142
|
expected_revision: RevisionId | None = None
|
|
136
143
|
|
|
@@ -840,9 +847,12 @@ def _migration_document( # noqa: PLR0913 -- the fields a migration has; all key
|
|
|
840
847
|
``status: approved`` is right even though nobody has approved it yet: the
|
|
841
848
|
file applies only once a human has merged it, and ``draft`` would land
|
|
842
849
|
knowledge that ``theurian index build`` leaves out. ``trustLevel`` and
|
|
843
|
-
``sensitivity`` are
|
|
844
|
-
|
|
845
|
-
|
|
850
|
+
``sensitivity`` are written only when the caller set them (``--trust-level``,
|
|
851
|
+
``--sensitivity``): left unset they are absent from the file and the loader
|
|
852
|
+
applies its defaults, ``unverified`` and ``internal``. Stamping those
|
|
853
|
+
defaults in unasked-for would assert a judgement the caller never made, so
|
|
854
|
+
the omission is surfaced in the CLI's next steps instead of written here
|
|
855
|
+
(#249).
|
|
846
856
|
"""
|
|
847
857
|
metadata: dict[str, object] = {
|
|
848
858
|
"title": request.title,
|
|
@@ -852,8 +862,14 @@ def _migration_document( # noqa: PLR0913 -- the fields a migration has; all key
|
|
|
852
862
|
"status": KnowledgeStatus.APPROVED.value,
|
|
853
863
|
"owner": request.owner,
|
|
854
864
|
}
|
|
865
|
+
if request.trust_level is not None:
|
|
866
|
+
metadata["trustLevel"] = request.trust_level.value
|
|
867
|
+
if request.sensitivity is not None:
|
|
868
|
+
metadata["sensitivity"] = request.sensitivity.value
|
|
855
869
|
if request.labels:
|
|
856
870
|
metadata["labels"] = list(request.labels)
|
|
871
|
+
if request.scope_paths:
|
|
872
|
+
metadata["scope"] = {"paths": list(request.scope_paths)}
|
|
857
873
|
if request.source_anchors:
|
|
858
874
|
metadata["sourceAnchors"] = [_anchor_document(a) for a in request.source_anchors]
|
|
859
875
|
|
|
@@ -34,11 +34,15 @@ from theurian.application.proposal_service import (
|
|
|
34
34
|
ProposalService,
|
|
35
35
|
)
|
|
36
36
|
from theurian.cli.context import CommandContext, schema_root
|
|
37
|
-
from theurian.domain.enums import KnowledgeKind
|
|
37
|
+
from theurian.domain.enums import KnowledgeKind, Sensitivity, TrustLevel
|
|
38
38
|
from theurian.domain.errors import TheurianError
|
|
39
39
|
from theurian.domain.identifiers import AgentId, ItemId, ProposalId, RevisionId, TaskId
|
|
40
40
|
from theurian.domain.knowledge import AUTHORED_IN_THEURIAN, SourceAnchor
|
|
41
|
-
from theurian.domain.migration import
|
|
41
|
+
from theurian.domain.migration import (
|
|
42
|
+
DEFAULT_SENSITIVITY,
|
|
43
|
+
DEFAULT_TRUST_LEVEL,
|
|
44
|
+
current_revision_in,
|
|
45
|
+
)
|
|
42
46
|
from theurian.domain.proposal import Evidence
|
|
43
47
|
from theurian.domain.values import JSON, MARKDOWN, YAML, MediaType
|
|
44
48
|
from theurian.infrastructure.filesystem.migration_loader import validate_migration_document
|
|
@@ -50,6 +54,14 @@ from theurian.security.paths import MAX_SOURCE_FILE_BYTES
|
|
|
50
54
|
#: had the group shape allowed the options to be declared required.
|
|
51
55
|
EXIT_INVALID_INPUT: Final = 2
|
|
52
56
|
|
|
57
|
+
#: The C0 control block (below U+0020) and DEL (U+007F). A label carrying one of
|
|
58
|
+
#: these corrupts the reviewed migration text -- a newline splits it across
|
|
59
|
+
#: lines, a NUL truncates it -- and the schema's ``labels.items`` does not forbid
|
|
60
|
+
#: them the way ``title``/``owner`` do, so ``propose`` refuses them at draft
|
|
61
|
+
#: time (#249).
|
|
62
|
+
_C0_CEILING: Final = 0x20
|
|
63
|
+
_DEL: Final = 0x7F
|
|
64
|
+
|
|
53
65
|
#: Body formats a proposal may carry, keyed by the extension the caller wrote.
|
|
54
66
|
#: Read from the file name rather than taken as its own option so the extension
|
|
55
67
|
#: and the declared ``contentType`` cannot disagree -- a body written as ``.md``
|
|
@@ -107,6 +119,10 @@ class _Inputs:
|
|
|
107
119
|
expected_revision: str | None
|
|
108
120
|
anchor: SourceAnchor | None
|
|
109
121
|
authored_here: bool
|
|
122
|
+
trust_level: TrustLevel | None
|
|
123
|
+
sensitivity: Sensitivity | None
|
|
124
|
+
scope_paths: tuple[str, ...]
|
|
125
|
+
labels: tuple[str, ...]
|
|
110
126
|
agent_id: str
|
|
111
127
|
task_id: str
|
|
112
128
|
model: str
|
|
@@ -176,6 +192,35 @@ def propose_draft( # noqa: PLR0913 -- one option per migration field, all keywo
|
|
|
176
192
|
help="Declare that this knowledge originates in Theurian and has no external source.",
|
|
177
193
|
),
|
|
178
194
|
] = False,
|
|
195
|
+
trust_level: Annotated[
|
|
196
|
+
TrustLevel | None,
|
|
197
|
+
typer.Option(
|
|
198
|
+
"--trust-level",
|
|
199
|
+
help="How much scrutiny this content has had. Omitted, the revision loads as "
|
|
200
|
+
"'unverified' -- honest for an agent draft, wrong for reviewed knowledge.",
|
|
201
|
+
),
|
|
202
|
+
] = None,
|
|
203
|
+
sensitivity: Annotated[
|
|
204
|
+
Sensitivity | None,
|
|
205
|
+
typer.Option(
|
|
206
|
+
"--sensitivity",
|
|
207
|
+
help="Disclosure class. Omitted, the revision loads as 'internal'.",
|
|
208
|
+
),
|
|
209
|
+
] = None,
|
|
210
|
+
scope_path: Annotated[
|
|
211
|
+
list[str] | None,
|
|
212
|
+
typer.Option(
|
|
213
|
+
"--scope-path",
|
|
214
|
+
help="Glob this knowledge governs, e.g. src/**. Repeatable; the order is kept.",
|
|
215
|
+
),
|
|
216
|
+
] = None,
|
|
217
|
+
label: Annotated[
|
|
218
|
+
list[str] | None,
|
|
219
|
+
typer.Option(
|
|
220
|
+
"--label",
|
|
221
|
+
help="Free-form label to group this knowledge by. Repeatable; the order is kept.",
|
|
222
|
+
),
|
|
223
|
+
] = None,
|
|
179
224
|
agent_id: Annotated[
|
|
180
225
|
str | None, typer.Option("--agent-id", help="Which agent drafted this (required).")
|
|
181
226
|
] = None,
|
|
@@ -226,6 +271,13 @@ def propose_draft( # noqa: PLR0913 -- one option per migration field, all keywo
|
|
|
226
271
|
"--source-uri": source_uri,
|
|
227
272
|
"--source-commit": source_commit,
|
|
228
273
|
"--source-path": source_path,
|
|
274
|
+
"--trust-level": trust_level,
|
|
275
|
+
"--sensitivity": sensitivity,
|
|
276
|
+
# Repeatable options arrive as ``None`` when omitted and a list when
|
|
277
|
+
# given, so an empty draft leaves them out of the stray check while a
|
|
278
|
+
# populated one is caught -- the same "was it passed" test the scalars use.
|
|
279
|
+
"--scope-path": scope_path,
|
|
280
|
+
"--label": label,
|
|
229
281
|
"--agent-id": agent_id,
|
|
230
282
|
"--task-id": task_id,
|
|
231
283
|
"--model": model,
|
|
@@ -249,6 +301,11 @@ def propose_draft( # noqa: PLR0913 -- one option per migration field, all keywo
|
|
|
249
301
|
as_json=as_json,
|
|
250
302
|
)
|
|
251
303
|
|
|
304
|
+
scope_paths = tuple(scope_path or ())
|
|
305
|
+
labels = tuple(label or ())
|
|
306
|
+
_refuse_blank_scope_paths(scope_paths, as_json=as_json)
|
|
307
|
+
_refuse_unusable_labels(labels, as_json=as_json)
|
|
308
|
+
|
|
252
309
|
_draft(
|
|
253
310
|
_Inputs(
|
|
254
311
|
item_id=_present(item_id),
|
|
@@ -262,6 +319,10 @@ def propose_draft( # noqa: PLR0913 -- one option per migration field, all keywo
|
|
|
262
319
|
expected_revision=expected_revision,
|
|
263
320
|
anchor=_anchor(source_provider, source_uri, source_commit, source_path),
|
|
264
321
|
authored_here=authored_here,
|
|
322
|
+
trust_level=trust_level,
|
|
323
|
+
sensitivity=sensitivity,
|
|
324
|
+
scope_paths=scope_paths,
|
|
325
|
+
labels=labels,
|
|
265
326
|
agent_id=_present(agent_id),
|
|
266
327
|
task_id=_present(task_id),
|
|
267
328
|
model=_present(model),
|
|
@@ -372,6 +433,48 @@ _DRAFT_STEPS: Final = (
|
|
|
372
433
|
)
|
|
373
434
|
|
|
374
435
|
|
|
436
|
+
def _refuse_blank_scope_paths(scope_paths: tuple[str, ...], *, as_json: bool) -> None:
|
|
437
|
+
"""Refuse a ``--scope-path`` that names no path.
|
|
438
|
+
|
|
439
|
+
``revisionMetadata.scope.paths`` items carry no ``minLength``, so ``""`` or a
|
|
440
|
+
whitespace-only value stages a scope entry that matches nothing and reads in
|
|
441
|
+
review as an authoring slip. Refuse it here -- naming the option, staging
|
|
442
|
+
nothing -- rather than packaging a glob that can never apply (#249).
|
|
443
|
+
"""
|
|
444
|
+
if any(not path.strip() for path in scope_paths):
|
|
445
|
+
_refuse(
|
|
446
|
+
"--scope-path was given an empty path.",
|
|
447
|
+
remedy="Pass a path glob such as docs/**, or drop --scope-path.",
|
|
448
|
+
as_json=as_json,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _refuse_unusable_labels(labels: tuple[str, ...], *, as_json: bool) -> None:
|
|
453
|
+
"""Refuse an empty ``--label`` or one bearing a control character.
|
|
454
|
+
|
|
455
|
+
``revisionMetadata.labels.items`` carry only ``maxLength``, unlike ``title``
|
|
456
|
+
and ``owner`` whose ``pattern`` forbids control characters. #249 first opened
|
|
457
|
+
this arbitrary-label path, so it closes the two values that corrupt the
|
|
458
|
+
reviewed migration text: an empty label groups by nothing, and a C0 or DEL
|
|
459
|
+
control character -- a newline splits the label across lines, a NUL truncates
|
|
460
|
+
it -- makes the YAML read as something other than what was passed. The public
|
|
461
|
+
schema is unchanged here; this is the CLI refusing before it stages (#249).
|
|
462
|
+
"""
|
|
463
|
+
for label in labels:
|
|
464
|
+
if not label:
|
|
465
|
+
_refuse(
|
|
466
|
+
"--label was given an empty value.",
|
|
467
|
+
remedy="Pass a non-empty label, or drop --label.",
|
|
468
|
+
as_json=as_json,
|
|
469
|
+
)
|
|
470
|
+
if any(ord(char) < _C0_CEILING or ord(char) == _DEL for char in label):
|
|
471
|
+
_refuse(
|
|
472
|
+
"--label contains a control character.",
|
|
473
|
+
remedy="Pass a label of printable text, or drop --label.",
|
|
474
|
+
as_json=as_json,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
|
|
375
478
|
def _draft(inputs: _Inputs, *, as_json: bool) -> None:
|
|
376
479
|
"""Read the body, build the request, and report what was written."""
|
|
377
480
|
from theurian.cli.commands import _emit, _fail, _require_project # noqa: PLC0415 - cycle
|
|
@@ -396,7 +499,7 @@ def _draft(inputs: _Inputs, *, as_json: bool) -> None:
|
|
|
396
499
|
)
|
|
397
500
|
return
|
|
398
501
|
|
|
399
|
-
_emit(_drafted_payload(drafted, context), as_json=as_json)
|
|
502
|
+
_emit(_drafted_payload(drafted, context, inputs), as_json=as_json)
|
|
400
503
|
|
|
401
504
|
|
|
402
505
|
def _request(inputs: _Inputs, *, body: str, content_type: MediaType) -> ProposalRequest:
|
|
@@ -432,7 +535,10 @@ def _request(inputs: _Inputs, *, body: str, content_type: MediaType) -> Proposal
|
|
|
432
535
|
content_type=content_type,
|
|
433
536
|
evidence=evidence,
|
|
434
537
|
source_anchors=anchors,
|
|
435
|
-
labels=(
|
|
538
|
+
labels=_merge_labels(inputs.labels, authored_here=inputs.authored_here),
|
|
539
|
+
scope_paths=inputs.scope_paths,
|
|
540
|
+
trust_level=inputs.trust_level,
|
|
541
|
+
sensitivity=inputs.sensitivity,
|
|
436
542
|
namespace=inputs.namespace,
|
|
437
543
|
expected_revision=(
|
|
438
544
|
None if inputs.expected_revision is None else RevisionId.parse(inputs.expected_revision)
|
|
@@ -440,6 +546,21 @@ def _request(inputs: _Inputs, *, body: str, content_type: MediaType) -> Proposal
|
|
|
440
546
|
)
|
|
441
547
|
|
|
442
548
|
|
|
549
|
+
def _merge_labels(labels: tuple[str, ...], *, authored_here: bool) -> tuple[str, ...]:
|
|
550
|
+
"""The caller's labels and the authored-here label, deduplicated in order.
|
|
551
|
+
|
|
552
|
+
``revisionMetadata.labels`` is ``uniqueItems``, so a caller who passes
|
|
553
|
+
``--label authored-in-theurian`` beside ``--authored-here`` must not produce a
|
|
554
|
+
document the generator's own validation then rejects. ``--authored-here`` is
|
|
555
|
+
INV-8's declaration for source-less knowledge, not a synonym for the label, so
|
|
556
|
+
it is added rather than assumed and never dropped. First-seen order is kept
|
|
557
|
+
because the migration is reviewed as text. ``dict.fromkeys`` is the dedupe:
|
|
558
|
+
it preserves insertion order and collapses the repeat.
|
|
559
|
+
"""
|
|
560
|
+
ordered = (*labels, AUTHORED_IN_THEURIAN) if authored_here else labels
|
|
561
|
+
return tuple(dict.fromkeys(ordered))
|
|
562
|
+
|
|
563
|
+
|
|
443
564
|
def _anchor(
|
|
444
565
|
provider: str, uri: str | None, commit: str | None, file_path: str | None
|
|
445
566
|
) -> SourceAnchor | None:
|
|
@@ -513,7 +634,9 @@ def _service(context: CommandContext) -> ProposalService:
|
|
|
513
634
|
)
|
|
514
635
|
|
|
515
636
|
|
|
516
|
-
def _drafted_payload(
|
|
637
|
+
def _drafted_payload(
|
|
638
|
+
drafted: DraftedProposal, context: CommandContext, inputs: _Inputs
|
|
639
|
+
) -> dict[str, object]:
|
|
517
640
|
root = context.paths.root
|
|
518
641
|
return {
|
|
519
642
|
"proposalId": drafted.proposal_id.value,
|
|
@@ -529,10 +652,61 @@ def _drafted_payload(drafted: DraftedProposal, context: CommandContext) -> dict[
|
|
|
529
652
|
"contentFile": drafted.content_file,
|
|
530
653
|
"contentSha256": drafted.content_sha256.value,
|
|
531
654
|
"bodyDestination": _relative(drafted.body_destination, root),
|
|
532
|
-
"nextSteps":
|
|
655
|
+
"nextSteps": _draft_steps(inputs),
|
|
533
656
|
}
|
|
534
657
|
|
|
535
658
|
|
|
659
|
+
def _draft_steps(inputs: _Inputs) -> list[str]:
|
|
660
|
+
"""The next-steps list, with the governed-defaults warning first when owed.
|
|
661
|
+
|
|
662
|
+
#249: an omitted ``--trust-level`` or ``--sensitivity`` is not an error --
|
|
663
|
+
the schema defaults are the honest answer for an agent's unreviewed draft --
|
|
664
|
+
but the loader fills ``unverified``/``internal`` silently, and both are
|
|
665
|
+
published on every retrieval result. The migration is deliberately left
|
|
666
|
+
without those keys (writing them would state a judgement the caller never
|
|
667
|
+
made, and would break the compatibility pin), so the surfacing lives here:
|
|
668
|
+
naming the default the revision will publish, and how to set it, is what
|
|
669
|
+
keeps a reviewed, public ADR from acquiring ``unverified``/``internal`` with
|
|
670
|
+
nothing telling the caller.
|
|
671
|
+
"""
|
|
672
|
+
note = _governed_defaults_note(inputs.trust_level, inputs.sensitivity)
|
|
673
|
+
return [note, *_DRAFT_STEPS] if note is not None else list(_DRAFT_STEPS)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _governed_defaults_note(
|
|
677
|
+
trust_level: TrustLevel | None, sensitivity: Sensitivity | None
|
|
678
|
+
) -> str | None:
|
|
679
|
+
"""Name the governed fields left unset and the default each will publish.
|
|
680
|
+
|
|
681
|
+
``None`` when both were given: there is then no default to warn about. The
|
|
682
|
+
defaults are read from :data:`DEFAULT_TRUST_LEVEL` and
|
|
683
|
+
:data:`DEFAULT_SENSITIVITY` -- the same constants the loader's ``.get(...)``
|
|
684
|
+
fallbacks apply when a migration omits these keys -- so the value named here
|
|
685
|
+
and the value the revision will publish are one definition, not two that
|
|
686
|
+
happen to agree.
|
|
687
|
+
"""
|
|
688
|
+
omitted = [
|
|
689
|
+
(field, default, option)
|
|
690
|
+
for value, field, default, option in (
|
|
691
|
+
(trust_level, "trustLevel", DEFAULT_TRUST_LEVEL.value, "--trust-level"),
|
|
692
|
+
(sensitivity, "sensitivity", DEFAULT_SENSITIVITY.value, "--sensitivity"),
|
|
693
|
+
)
|
|
694
|
+
if value is None
|
|
695
|
+
]
|
|
696
|
+
if not omitted:
|
|
697
|
+
return None
|
|
698
|
+
plural = len(omitted) > 1
|
|
699
|
+
fields = " and ".join(field for field, _, _ in omitted)
|
|
700
|
+
defaults = " and ".join(f"{field}: {default}" for field, default, _ in omitted)
|
|
701
|
+
options = " and ".join(option for _, _, option in omitted)
|
|
702
|
+
return (
|
|
703
|
+
f"{fields} {'were' if plural else 'was'} not set, so this revision will publish "
|
|
704
|
+
f"{defaults} -- the schema default{'s' if plural else ''} -- on every knowledge.search "
|
|
705
|
+
f"and knowledge.get result. If that is not right for this knowledge, re-draft with "
|
|
706
|
+
f"{options}."
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
|
|
536
710
|
def _relative(path: Path, root: Path) -> str:
|
|
537
711
|
try:
|
|
538
712
|
return path.relative_to(root).as_posix()
|
|
@@ -39,6 +39,14 @@ MIGRATION_API_VERSION: Final = "theurian.dev/v1"
|
|
|
39
39
|
#: reinterpreting it (ADR-0007).
|
|
40
40
|
MIGRATION_ENGINE_VERSION: Final = 1
|
|
41
41
|
|
|
42
|
+
#: The governed metadata a revision acquires when its migration omits it. These
|
|
43
|
+
#: are the single source of truth: the dataclass defaults below, the loader's
|
|
44
|
+
#: ``.get(...)`` fallbacks, and the ``propose`` note that warns an author which
|
|
45
|
+
#: default an omission will publish all read *these* constants, so the warning
|
|
46
|
+
#: cannot claim one value while the loader fills another (#249).
|
|
47
|
+
DEFAULT_TRUST_LEVEL: Final = TrustLevel.UNVERIFIED
|
|
48
|
+
DEFAULT_SENSITIVITY: Final = Sensitivity.INTERNAL
|
|
49
|
+
|
|
42
50
|
|
|
43
51
|
class OperationKind(StrEnum):
|
|
44
52
|
"""The closed operation set. Adding one bumps ``apiVersion`` (ADR-0005)."""
|
|
@@ -84,8 +92,8 @@ class CreateItem(Operation):
|
|
|
84
92
|
kind_: KnowledgeKind
|
|
85
93
|
namespace: str
|
|
86
94
|
owner: str
|
|
87
|
-
sensitivity: Sensitivity =
|
|
88
|
-
trust_level: TrustLevel =
|
|
95
|
+
sensitivity: Sensitivity = DEFAULT_SENSITIVITY
|
|
96
|
+
trust_level: TrustLevel = DEFAULT_TRUST_LEVEL
|
|
89
97
|
|
|
90
98
|
@override
|
|
91
99
|
@property
|
|
@@ -103,8 +111,8 @@ class RevisionMetadataSpec:
|
|
|
103
111
|
namespace: str
|
|
104
112
|
status: KnowledgeStatus
|
|
105
113
|
owner: str
|
|
106
|
-
trust_level: TrustLevel =
|
|
107
|
-
sensitivity: Sensitivity =
|
|
114
|
+
trust_level: TrustLevel = DEFAULT_TRUST_LEVEL
|
|
115
|
+
sensitivity: Sensitivity = DEFAULT_SENSITIVITY
|
|
108
116
|
tenant_id: str = "local"
|
|
109
117
|
acl_group: str = "default"
|
|
110
118
|
valid_from: datetime | None = None
|
|
@@ -39,6 +39,8 @@ from theurian.domain.errors import (
|
|
|
39
39
|
from theurian.domain.identifiers import ItemId, MigrationId, RevisionId, SpecId
|
|
40
40
|
from theurian.domain.knowledge import SourceAnchor
|
|
41
41
|
from theurian.domain.migration import (
|
|
42
|
+
DEFAULT_SENSITIVITY,
|
|
43
|
+
DEFAULT_TRUST_LEVEL,
|
|
42
44
|
MIGRATION_API_VERSION,
|
|
43
45
|
AddAlias,
|
|
44
46
|
AddEvidence,
|
|
@@ -608,8 +610,8 @@ def _parse_operation( # noqa: PLR0911, PLR0912 -- a flat dispatch over 14 opera
|
|
|
608
610
|
kind_=KnowledgeKind(payload["kind"]),
|
|
609
611
|
namespace=payload["namespace"],
|
|
610
612
|
owner=payload["owner"],
|
|
611
|
-
sensitivity=Sensitivity(payload.get("sensitivity",
|
|
612
|
-
trust_level=TrustLevel(payload.get("trustLevel",
|
|
613
|
+
sensitivity=Sensitivity(payload.get("sensitivity", DEFAULT_SENSITIVITY.value)),
|
|
614
|
+
trust_level=TrustLevel(payload.get("trustLevel", DEFAULT_TRUST_LEVEL.value)),
|
|
613
615
|
)
|
|
614
616
|
case "upsertRevision":
|
|
615
617
|
return _parse_upsert(payload, path, project_root, migrations_dir, content_by_hash)
|
|
@@ -785,8 +787,8 @@ def _parse_upsert(
|
|
|
785
787
|
namespace=metadata["namespace"],
|
|
786
788
|
status=KnowledgeStatus(metadata["status"]),
|
|
787
789
|
owner=metadata["owner"],
|
|
788
|
-
trust_level=TrustLevel(metadata.get("trustLevel",
|
|
789
|
-
sensitivity=Sensitivity(metadata.get("sensitivity",
|
|
790
|
+
trust_level=TrustLevel(metadata.get("trustLevel", DEFAULT_TRUST_LEVEL.value)),
|
|
791
|
+
sensitivity=Sensitivity(metadata.get("sensitivity", DEFAULT_SENSITIVITY.value)),
|
|
790
792
|
tenant_id=metadata.get("tenantId", "local"),
|
|
791
793
|
acl_group=metadata.get("aclGroup", "default"),
|
|
792
794
|
valid_from=_optional_datetime(metadata.get("validFrom"), path),
|
|
@@ -223,13 +223,15 @@ def test_generation_modifies_no_file_outside_the_proposal_directory(
|
|
|
223
223
|
def test_a_generated_revision_is_status_approved_and_carries_no_trust_level(
|
|
224
224
|
service: ProposalService,
|
|
225
225
|
) -> None:
|
|
226
|
-
"""m07/b24: ``status: approved`` is fixed, and ``trustLevel`` is never
|
|
226
|
+
"""m07/b24: ``status: approved`` is fixed, and ``trustLevel`` is never invented.
|
|
227
227
|
|
|
228
228
|
``status: approved`` is right even though nobody has approved it: the file
|
|
229
229
|
applies only after a human has merged it, and ``draft`` would keep the
|
|
230
230
|
knowledge out of the default index. ``trustLevel: reviewed`` on an agent's
|
|
231
231
|
draft, by contrast, would claim a review that has not happened -- so the
|
|
232
|
-
generator
|
|
232
|
+
generator writes ``trustLevel`` only when the caller states one (#249's
|
|
233
|
+
``--trust-level``); ``_request`` here states none, so it stays out of the
|
|
234
|
+
file and the loader's ``unverified`` default applies.
|
|
233
235
|
"""
|
|
234
236
|
metadata = _upsert(service.draft(_request()).migration_file)["metadata"]
|
|
235
237
|
assert isinstance(metadata, dict)
|