Rest api + docs + namespaces - #6
Merged
Merged
Conversation
Shekar V (vshekar)
commented
Sep 16, 2026
Collaborator
- Created infrastructure for single user REST API
- Updated docs to use Great docs
- Use namespaces similar to Tiled
Replace global template and run identities with namespace-scoped constraints. Keep Campaign linkage for process runs until Task 8.
Remove Campaign domain APIs after validating migrated namespace ownership. Rebuild process_run on SQLite, preserve existing IDs and assignments, then drop campaign data structures.
Require exact Apikey credentials and compare them in constant time. Redact configured secrets from representations and authentication failures.
Generate trusted request IDs and sanitize failure responses so caller input and internal details cannot cross API boundaries. Restrict mutation audit records to identifiers and closed reason codes.
Persist actor-scoped command results and sanitized mutation audits in the same transaction as domain changes. Add revision CAS support and command infrastructure migration.
There was a problem hiding this comment.
🟡 Changes recommended
Critical and moderate authorization, lifecycle, migration, and REST error-handling issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a single-user REST API foundation with authentication, namespace-aware storage and authorization, lifecycle/revision support, migrations, tests, and Great Docs updates.
Changes:
- Added REST server, client, query, command, and authentication infrastructure.
- Added namespaces, authorization, auditing, idempotency, lifecycle handling, and process-run lineage.
- Expanded tests and migrated documentation tooling and content.
File summaries
| File | Reviewed changes |
|---|---|
recap/utils/namespace.py |
Namespace path and ancestry helpers |
recap/utils/migrations.py |
Migration configuration |
recap/utils/general.py |
General utility exports and conversions |
recap/tests/transport_factories.py |
Transport test factories |
recap/tests/test_strawberry_types.py |
Strawberry type tests |
recap/tests/test_set_campaign_perf.py |
Campaign performance tests |
recap/tests/test_server_config.py |
Server configuration tests |
recap/tests/test_schema_registry.py |
Schema registry tests |
recap/tests/test_revisions.py |
Revision behavior tests |
recap/tests/test_rest_resource_templates.py |
Resource-template REST tests |
recap/tests/test_rest_process_templates.py |
Process-template REST tests |
recap/tests/test_rest_lifecycle.py |
REST lifecycle tests |
recap/tests/test_resource_tree_perf.py |
Resource-tree performance tests |
recap/tests/test_resource_freeze.py |
Resource freeze tests |
recap/tests/test_resolvers.py |
Resolver tests |
recap/tests/test_request_backend_scope.py |
Request backend scope tests |
recap/tests/test_query_loaders.py |
Query loader tests |
recap/tests/test_public_errors.py |
Public error tests |
recap/tests/test_public_api.py |
Public API tests |
recap/tests/test_protocol_split.py |
Protocol separation tests |
recap/tests/test_process_run_commands.py |
Process-run command tests |
recap/tests/test_platemate_parity.py |
PlateMate parity tests |
recap/tests/test_namespace_path.py |
Namespace path tests |
recap/tests/test_namespace_models.py |
Namespace model tests |
recap/tests/test_lifecycle.py |
Lifecycle tests |
recap/tests/test_include_properties_perf.py |
Property-loading performance tests |
recap/tests/test_graphql_integration.py |
GraphQL integration tests |
recap/tests/test_get_resource_perf.py |
Resource retrieval performance tests |
recap/tests/test_field_predicates.py |
Field predicate tests |
recap/tests/test_experiment.py |
Experiment tests |
recap/tests/test_exceptions.py |
Exception tests |
recap/tests/test_end_to_end.py |
End-to-end tests |
recap/tests/test_database_config.py |
Database configuration tests |
recap/tests/test_container.py |
Container tests |
recap/tests/test_command_models.py |
Command model tests |
recap/tests/test_command_errors.py |
Command error tests |
recap/tests/test_canonical_models.py |
Canonical model tests |
recap/tests/test_campaign_update.py |
Campaign update tests |
recap/tests/test_builder_identity.py |
Builder identity tests |
recap/tests/test_builder_command_count.py |
Builder command-count tests |
recap/tests/test_authentication_models.py |
Authentication model tests |
recap/tests/test_audit.py |
Audit tests |
recap/tests/test_attribute_value.py |
Attribute value tests |
recap/tests/test_array_default_value.py |
Array default-value tests |
recap/tests/test_actions.py |
Action tests |
recap/tests/fixtures/authorization.yml |
Authorization fixture data |
recap/server/strawberry_types.py |
Strawberry API types |
recap/server/strawberry_schema.py |
Strawberry schema assembly |
recap/server/security.py |
API-key security |
recap/server/rest_models.py |
REST request and response models |
recap/server/query_models.py |
Query models |
recap/server/errors.py |
Server errors |
recap/server/error_handlers.py |
Error handlers |
recap/server/dependencies.py |
Server dependencies |
recap/server/config.py |
Server configuration |
recap/server/audit.py |
Server auditing |
recap/server/__main__.py |
Server entry point |
recap/server/__init__.py |
Server package exports |
recap/schemas/namespace.py |
Namespace schemas |
recap/schemas/attribute.py |
Attribute schemas |
recap/schemas/__init__.py |
Schema exports |
recap/lifecycle.py |
Lifecycle definitions |
recap/exporters/registry.py |
Exporter registry |
recap/exporters/protocol.py |
Exporter protocol |
recap/exporters/__init__.py |
Exporter exports |
recap/exceptions.py |
Package exceptions |
recap/dsl/builder_state.py |
DSL builder state |
recap/dsl/attribute_builder.py |
Attribute builder |
recap/dsl/__init__.py |
DSL exports |
recap/db/step.py |
Database step models |
recap/db/namespace.py |
Namespace persistence and hierarchy |
recap/db/migrations/versions/c9d2e8f4a1b7_add_process_run_lineage.py |
Process-run lineage migration |
recap/db/migrations/versions/b47f5e2a9c10_add_command_infrastructure.py |
Command infrastructure migration |
recap/db/migrations/env.py |
Migration environment |
recap/db/engine.py |
Database engine setup |
recap/db/campaign.py |
Campaign persistence |
recap/db/base.py |
Database base definitions |
recap/db/audit.py |
Database auditing |
recap/db/__init__.py |
Database package exports |
recap/commands/models.py |
Command models |
recap/commands/idempotency.py |
Idempotency handling |
recap/commands/errors.py |
Command errors |
recap/commands/context.py |
Command context |
recap/commands/audit.py |
Command auditing |
recap/commands/__init__.py |
Command package exports |
recap/client/permissions.py |
Client permissions |
recap/client/connection_state.py |
Client connection state |
recap/client/__init__.py |
Client package exports |
recap/authorization/scopes.py |
Authorization scopes |
recap/authorization/query.py |
Authorized query filtering |
recap/authorization/__init__.py |
Authorization exports |
recap/authentication/protocols.py |
Authentication protocols |
recap/authentication/models.py |
Authentication models |
recap/authentication/errors.py |
Authentication errors |
recap/authentication/api_key.py |
API-key authentication |
recap/authentication/actors.py |
Authentication actors |
recap/authentication/__init__.py |
Authentication exports |
recap/adapter/query_loaders.py |
Adapter query loading |
pyproject.toml |
Project and documentation configuration |
mkdocs.yml |
Documentation site configuration |
docs/tutorials/resource-examples.qmd |
Resource examples tutorial |
docs/tutorials/index.qmd |
Tutorials index |
docs/tutorials/complete-local-provenance.qmd |
Local provenance tutorial |
docs/resource_examples.md |
Resource examples |
docs/reference/server-configuration.qmd |
Server configuration reference |
docs/index.md |
Documentation index |
docs/how-to/use-revisions-and-idempotency.qmd |
Revisions and idempotency guide |
docs/how-to/use-remote-authentication.qmd |
Remote authentication guide |
docs/how-to/update-and-retire-templates.qmd |
Template update and retirement guide |
docs/how-to/resource-template.qmd |
Resource-template guide |
docs/how-to/quick-start-create-and-store-data-locally.qmd |
Local data quick start |
docs/how-to/query-templates-and-namespaces.qmd |
Template and namespace queries |
docs/how-to/query-resources.qmd |
Resource queries |
docs/how-to/query-process-runs.qmd |
Process-run queries |
docs/how-to/provenance-queries.qmd |
Provenance queries |
docs/how-to/process-template.qmd |
Process-template guide |
docs/how-to/performance.qmd |
Performance guide |
docs/how-to/nested-resource-templates.qmd |
Nested resource-template guide |
docs/how-to/manage-resource-lifecycle.qmd |
Resource lifecycle guide |
docs/how-to/index.qmd |
How-to index |
docs/how-to/handle-errors-and-request-ids.qmd |
Errors and request IDs guide |
docs/how-to/edit-resource-properties.qmd |
Resource property editing guide |
docs/how-to/choose-query-shape-and-loading.qmd |
Query shape and loading guide |
docs/how-to/build-process-runs.qmd |
Process-run construction guide |
docs/how-to/add-runtime-children.qmd |
Runtime children guide |
docs/getting_started/03-process-workflow.qmd |
Process workflow guide |
docs/getting_started/00-orientation.qmd |
Getting started orientation |
docs/explanation/rest-query-architecture.qmd |
REST query architecture |
docs/explanation/resource-loading.qmd |
Resource loading explanation |
docs/explanation/query-model.qmd |
Query model explanation |
docs/explanation/index.qmd |
Explanations index |
docs/explanation/data-organization.qmd |
Data organization explanation |
docs/explanation/core-concepts.qmd |
Core concepts explanation |
docs/explanation/authentication-authorization.qmd |
Authentication and authorization explanation |
docs/explanation/audit-and-errors.qmd |
Auditing and errors explanation |
CONTRIBUTING.rst |
Contribution guidance |
.gitignore |
Ignore rules |
.github/workflows/docs.yml |
Documentation workflow |
Review details
Suppressed comments (8)
recap/commands/service.py:1323
- Unlike
copy_resource, this canonicalization is outside thetryblock. An invalid destination path therefore escapes as a rawValueErrorand the REST endpoint returns a generic 500 instead of the command validation 422 used by the other copy API.
recap/commands/service.py:1759 - All denied scope checks are recorded with
resource_type="resource", including denied process-template, process-run, and resource-template operations. This misclassifies authorization audit records and prevents consumers from identifying which object type was denied; derive the type from the scope or pass it from each caller.
recap/commands/service.py:468 - Suppressing denial auditing for the destination check means a copy rejected for insufficient destination write access produces no denied record;
_emit_failureexplicitly skipsAuthorizationDenied, so only source-side denials are recorded. Do not disable the denial audit for this check.
recap/commands/service.py:275 CreateResourceaccepts a client-suppliedid, but that field is omitted from_CreateResourceFingerprinthere. Reusing an idempotency key with the same other fields and a different requested ID therefore replays the first resource instead of reporting a fingerprint conflict, silently ignoring the new identity. Include the requested ID in the fingerprint.
recap/commands/service.py:877- Updating a process template is also authorized through
_authorize, which checksnamespace:writerather than the separately definedprocess-template:writescope. This lets namespace-only writers mutate process-template definitions in multi-user mode.
recap/commands/service.py:953 - Resource-template creation is guarded by
namespace:writethrough_authorize, even though scopes are separate for namespace and resource-template mutations. A namespace-only writer can therefore create resource templates withoutresource-template:write. Use the operation-specific scope.
recap/commands/service.py:1046 - This update path checks only
namespace:write, not the distinctresource-template:writescope. It grants namespace-only principals mutation access to resource-template definitions in multi-user deployments.
recap/db/migrations/versions/c9d2e8f4a1b7_add_process_run_lineage.py:35 - The downgrade uses
op.create_unique_constraintdirectly after dropping the index. SQLite does not supportALTER TABLE ADD CONSTRAINT, so downgrading this revision on the SQLite databases used by the project raisesNotImplementedErrorinstead of restoring the prior uniqueness constraint. Usebatch_alter_tablefor the downgrade as well.
- Files reviewed: 106/243 changed files
- Comments generated: 14
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+754
to
757
| stmt = select(Resource).where(Resource.id.in_(subtree_ids)).options( | ||
| *self._resource_subtree_loaders() | ||
| ) | ||
| return list(session.scalars(stmt).unique()) |
Comment on lines
+198
to
+204
| if target_status is LifecycleStatus.ACTIVE: | ||
| if isinstance(obj, ProcessRun): | ||
| obj.finalize(bump_revision=False) | ||
| else: | ||
| obj.activate(bump_revision=False) | ||
| else: | ||
| obj.archive(bump_revision=False) |
| ) | ||
| if namespace is None: | ||
| raise CommandNotFoundError("Namespace not found") | ||
| template = session.get(ResourceTemplate, template_id) |
| canonical_path = canonicalize_namespace_path(namespace_path) | ||
| except ValueError as error: | ||
| raise CommandValidationError(str(error)) from error | ||
| self._authorize(context, canonical_path, mutation="create_process_template") |
Comment on lines
+1146
to
+1150
| self._authorize_scope( | ||
| context, | ||
| template.namespace.path, | ||
| Scope.PROCESS_TEMPLATE_READ, | ||
| "create_process_run", |
| "process_run", | ||
| ["namespace_id", "name"], | ||
| unique=True, | ||
| sqlite_where=sa.text("copied_from_id IS NULL"), |
Comment on lines
+48
to
+50
| ancestor_paths = namespace_ancestors(path)[:-1] | ||
| parent_path = ancestor_paths[-1] | ||
| parent = self._session.scalar( |
| "namespace_id", | ||
| "name", | ||
| unique=True, | ||
| sqlite_where=(copied_from_id.is_(None)), |
| actor: Annotated[RequestActor, Depends(authenticate_request)], | ||
| namespace_path: str = "", | ||
| ) -> list[str]: | ||
| path = canonicalize_namespace_path(namespace_path) |
Comment on lines
+71
to
72
| is required; environment variables and direct kwargs override YAML. | ||
| Raises FileNotFoundError if the config file does not exist. |
This branch is being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.