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.
Files changed (247) hide show
  1. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/CHANGELOG.md +39 -3
  2. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/PKG-INFO +1 -1
  3. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/pyproject.toml +1 -1
  4. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/__init__.py +1 -1
  5. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/proposal_service.py +20 -4
  6. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/propose_commands.py +180 -6
  7. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/migration.py +12 -4
  8. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/migration_loader.py +6 -4
  9. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_proposal_service.py +4 -2
  10. theurian-0.1.0.dev7/tests/integration/test_propose_cli.py +989 -0
  11. theurian-0.1.0.dev6/tests/integration/test_propose_cli.py +0 -386
  12. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/.gitignore +0 -0
  13. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/LICENSE +0 -0
  14. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/README.md +0 -0
  15. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/hatch_build.py +0 -0
  16. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/__init__.py +0 -0
  17. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/forest_builder.py +0 -0
  18. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/index_builder.py +0 -0
  19. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/ingestion_service.py +0 -0
  20. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/migration_alias_guards.py +0 -0
  21. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/migration_body_guards.py +0 -0
  22. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/migration_engine.py +0 -0
  23. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/project_service.py +0 -0
  24. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/retrieval_service.py +0 -0
  25. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_context.py +0 -0
  26. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_service.py +0 -0
  27. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_steps.py +0 -0
  28. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/setup_withholding.py +0 -0
  29. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/visibility.py +0 -0
  30. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/application/withdrawal_purge.py +0 -0
  31. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/__init__.py +0 -0
  32. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/auth_commands.py +0 -0
  33. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/commands.py +0 -0
  34. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/context.py +0 -0
  35. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/index_commands.py +0 -0
  36. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/main.py +0 -0
  37. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/cli/setup_commands.py +0 -0
  38. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/__init__.py +0 -0
  39. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/instance.py +0 -0
  40. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/runner.py +0 -0
  41. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/daemon/server.py +0 -0
  42. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/__init__.py +0 -0
  43. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/chunking.py +0 -0
  44. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/compatibility.py +0 -0
  45. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/context.py +0 -0
  46. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/enums.py +0 -0
  47. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/errors.py +0 -0
  48. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/extras.py +0 -0
  49. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/identifiers.py +0 -0
  50. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ingestion.py +0 -0
  51. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/knowledge.py +0 -0
  52. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/__init__.py +0 -0
  53. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/authorization.py +0 -0
  54. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/canonical_store.py +0 -0
  55. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/daemon_manager.py +0 -0
  56. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/determinism.py +0 -0
  57. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/embedding.py +0 -0
  58. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/index_store.py +0 -0
  59. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/mcp_client_config.py +0 -0
  60. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/object_store.py +0 -0
  61. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/reranking.py +0 -0
  62. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/review_provider.py +0 -0
  63. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/secret_store.py +0 -0
  64. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/source_parser.py +0 -0
  65. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/specification_provider.py +0 -0
  66. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/summarization.py +0 -0
  67. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ports/vector_store.py +0 -0
  68. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/project.py +0 -0
  69. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/proposal.py +0 -0
  70. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/ranking.py +0 -0
  71. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/raptor.py +0 -0
  72. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/retrieval.py +0 -0
  73. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/review.py +0 -0
  74. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/setup.py +0 -0
  75. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/specification.py +0 -0
  76. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/state.py +0 -0
  77. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/domain/values.py +0 -0
  78. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/indexing/__init__.py +0 -0
  79. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/__init__.py +0 -0
  80. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/claude/__init__.py +0 -0
  81. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/claude/mcp_config.py +0 -0
  82. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/determinism.py +0 -0
  83. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/embedding/__init__.py +0 -0
  84. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/embedding/hashing.py +0 -0
  85. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/__init__.py +0 -0
  86. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/markdown.py +0 -0
  87. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/openapi.py +0 -0
  88. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/registry.py +0 -0
  89. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/filesystem/parsers/structured.py +0 -0
  90. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/git/__init__.py +0 -0
  91. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/github/__init__.py +0 -0
  92. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/raptor/__init__.py +0 -0
  93. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/raptor/extractive.py +0 -0
  94. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/secrets/__init__.py +0 -0
  95. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/secrets/file_store.py +0 -0
  96. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/__init__.py +0 -0
  97. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/launchagent.py +0 -0
  98. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/runner.py +0 -0
  99. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/services/systemd_user.py +0 -0
  100. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/__init__.py +0 -0
  101. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/connection.py +0 -0
  102. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_forest.py +0 -0
  103. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_purge.py +0 -0
  104. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_query.py +0 -0
  105. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_scan.py +0 -0
  106. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_schema.py +0 -0
  107. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/index_store.py +0 -0
  108. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/schema.py +0 -0
  109. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/sqlite/store.py +0 -0
  110. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/infrastructure/vector/__init__.py +0 -0
  111. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/ingestion/__init__.py +0 -0
  112. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/__init__.py +0 -0
  113. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/results.py +0 -0
  114. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/search.py +0 -0
  115. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/mcp/tools.py +0 -0
  116. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/migrations/__init__.py +0 -0
  117. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/normalization/__init__.py +0 -0
  118. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/normalization/projection.py +0 -0
  119. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/observability/__init__.py +0 -0
  120. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/retrieval/__init__.py +0 -0
  121. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/review/__init__.py +0 -0
  122. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/__init__.py +0 -0
  123. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/env_file.py +0 -0
  124. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/paths.py +0 -0
  125. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/tokens.py +0 -0
  126. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/security/yaml_loading.py +0 -0
  127. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/specification/__init__.py +0 -0
  128. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/src/theurian/traceability/__init__.py +0 -0
  129. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/command_extraction.py +0 -0
  130. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/command_population.py +0 -0
  131. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/conftest.py +0 -0
  132. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/__init__.py +0 -0
  133. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/clock.py +0 -0
  134. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/ids.py +0 -0
  135. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/pages.py +0 -0
  136. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/setup.py +0 -0
  137. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/fakes/store.py +0 -0
  138. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_absence_proof.py +0 -0
  139. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_alias_item_id_collision.py +0 -0
  140. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_auth_rotate.py +0 -0
  141. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_bare_install.py +0 -0
  142. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_canonical_store.py +0 -0
  143. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_canonical_store_corruption.py +0 -0
  144. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_claude_mcp_config.py +0 -0
  145. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_cli_commands.py +0 -0
  146. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_cli_help_without_rich.py +0 -0
  147. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_daemon.py +0 -0
  148. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_builder.py +0 -0
  149. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_builder_scale.py +0 -0
  150. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_node_scope.py +0 -0
  151. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_purge_equality.py +0 -0
  152. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_purge_recompute.py +0 -0
  153. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_retrieval.py +0 -0
  154. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_forest_store_retrieval.py +0 -0
  155. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_gc_during_a_search.py +0 -0
  156. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_fallback.py +0 -0
  157. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_gc_cli.py +0 -0
  158. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_purge.py +0 -0
  159. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_purge_differential.py +0 -0
  160. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_purge_nodes.py +0 -0
  161. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_scan_fold.py +0 -0
  162. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_schema_v4.py +0 -0
  163. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_index_store.py +0 -0
  164. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_ingestion.py +0 -0
  165. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_init_gitignore_block.py +0 -0
  166. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_mcp_tools.py +0 -0
  167. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_project_registry.py +0 -0
  168. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_retrieval_service.py +0 -0
  169. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_revision_id_reuse.py +0 -0
  170. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_scan_exhaustion.py +0 -0
  171. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_service_adapters.py +0 -0
  172. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_session_start_hook.py +0 -0
  173. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_cli.py +0 -0
  174. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_env_file.py +0 -0
  175. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_journal.py +0 -0
  176. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_partial_failure.py +0 -0
  177. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_probe_assertions.py +0 -0
  178. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_report_withholding.py +0 -0
  179. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_setup_service.py +0 -0
  180. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_short_query_retrieval.py +0 -0
  181. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_state_provenance.py +0 -0
  182. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_unreadable_registry_surface.py +0 -0
  183. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_wire_contract.py +0 -0
  184. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_withdrawal_purge.py +0 -0
  185. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/integration/test_writer_contract.py +0 -0
  186. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_artifact_integrity_claim.py +0 -0
  187. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_candidate_cut.py +0 -0
  188. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_candidate_depth.py +0 -0
  189. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_chunking.py +0 -0
  190. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_cli_help_rendering.py +0 -0
  191. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_command_extraction.py +0 -0
  192. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_compatibility.py +0 -0
  193. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_config_key_call_sites.py +0 -0
  194. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_daemon_extra.py +0 -0
  195. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_documented_commands.py +0 -0
  196. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_domain_invariants.py +0 -0
  197. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_env_file_merge.py +0 -0
  198. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_examples.py +0 -0
  199. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_extractive_summarizer.py +0 -0
  200. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_forest_derivation.py +0 -0
  201. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_forest_fanout.py +0 -0
  202. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_gate_call_sites.py +0 -0
  203. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_hashing_embedding.py +0 -0
  204. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ingest_manifest_path.py +0 -0
  205. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_install_claims.py +0 -0
  206. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_layering.py +0 -0
  207. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_alias_guards.py +0 -0
  208. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_engine.py +0 -0
  209. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_loader_errors.py +0 -0
  210. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_migration_loader_identity.py +0 -0
  211. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_network_call_sites.py +0 -0
  212. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_parsers.py +0 -0
  213. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_path_security.py +0 -0
  214. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_plugin_boundary.py +0 -0
  215. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ports.py +0 -0
  216. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_project_and_traceability.py +0 -0
  217. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_project_registry_errors.py +0 -0
  218. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_projection.py +0 -0
  219. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_proposal.py +0 -0
  220. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ranking.py +0 -0
  221. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_raptor_scope.py +0 -0
  222. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_ref_recording.py +0 -0
  223. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_resolve_context_call_sites.py +0 -0
  224. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_result_gate_session.py +0 -0
  225. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_result_payload.py +0 -0
  226. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_retrieval_depth.py +0 -0
  227. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_schemas.py +0 -0
  228. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_scope_isolation.py +0 -0
  229. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_secret_detector.py +0 -0
  230. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_setup_changed_paths.py +0 -0
  231. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_setup_claims.py +0 -0
  232. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_setup_domain.py +0 -0
  233. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_state_hash.py +0 -0
  234. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_test_fixtures.py +0 -0
  235. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_tokens.py +0 -0
  236. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/tests/unit/test_yaml_loading.py +0 -0
  237. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/README.md +0 -0
  238. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/cli/version.schema.json +0 -0
  239. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/config/project-config.schema.json +0 -0
  240. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/knowledge/retrieval-result.schema.json +0 -0
  241. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/knowledge-search-response.schema.json +0 -0
  242. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/knowledge-status-response.schema.json +0 -0
  243. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/project-list-response.schema.json +0 -0
  244. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/retrieval-metadata.schema.json +0 -0
  245. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/mcp/tool-context.schema.json +0 -0
  246. {theurian-0.1.0.dev6 → theurian-0.1.0.dev7}/theurian/schemas/migrations/migration.schema.json +0 -0
  247. {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** (GHSA-\<pending\>, assigned when the advisory is
21
- published). Critical; affected 0.1.0.dev0–0.1.0.dev5. An `addAlias` whose key
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** (GHSA-\<pending\>, assigned when the advisory is published).
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.dev6
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.dev6"
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.dev6"
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 deliberately absent -- claiming ``reviewed`` on
844
- something an agent drafted asserts a review that has not happened, and a
845
- reviewer can add either.
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 current_revision_in
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=(AUTHORED_IN_THEURIAN,) if inputs.authored_here else (),
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(drafted: DraftedProposal, context: CommandContext) -> dict[str, object]:
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": list(_DRAFT_STEPS),
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 = Sensitivity.INTERNAL
88
- trust_level: TrustLevel = TrustLevel.UNVERIFIED
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 = TrustLevel.UNVERIFIED
107
- sensitivity: Sensitivity = Sensitivity.INTERNAL
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", "internal")),
612
- trust_level=TrustLevel(payload.get("trustLevel", "unverified")),
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", "unverified")),
789
- sensitivity=Sensitivity(metadata.get("sensitivity", "internal")),
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 smuggled in.
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 omits ``trustLevel`` entirely and leaves it to a reviewer.
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)