Skip to content

RFC-0002: publish workbench public API contract - #16

Merged
EstandarMustaq merged 2 commits into
rfc-0002-tenant-isolation-rlsfrom
rfc-0002-openapi-contract
Jul 16, 2026
Merged

EstandarMustaq merged 2 commits into
rfc-0002-tenant-isolation-rlsfrom
rfc-0002-openapi-contract

Conversation

@EstandarMustaq

@EstandarMustaq EstandarMustaq commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Publishes the versioned Workbench OpenAPI 3.1 contract
  • Covers authenticated job submission and tenant-safe status surfaces
  • Excludes health, metrics, and internal worker callbacks
  • Enforces route coverage through the repository guardian

Validation

  • Guardian and OpenAPI checks pass
  • 21 unit tests and 3 e2e tests pass
  • TypeScript build passes

Stacked on the RFC-0002 tenant isolation branch.

* feat: orchestrate legacy batch processing

* docs: expand public workbench API contract (#18)
@EstandarMustaq
EstandarMustaq marked this pull request as ready for review July 16, 2026 20:27
@EstandarMustaq
EstandarMustaq merged commit 86f2e24 into rfc-0002-tenant-isolation-rls Jul 16, 2026
1 check passed
@EstandarMustaq
EstandarMustaq deleted the rfc-0002-openapi-contract branch July 16, 2026 20:28
EstandarMustaq added a commit that referenced this pull request Jul 16, 2026
* fix: reject cross-tenant domain jobs

* RFC-0002: publish workbench public API contract (#16)

* feat: publish versioned public api contract

* RFC-0002: orchestrate legacy batch processing (#17)

* feat: orchestrate legacy batch processing

* docs: expand public workbench API contract (#18)
EstandarMustaq added a commit that referenced this pull request Jul 16, 2026
* feat: secure worker runtime with OIDC

* RFC-0002: reject cross-tenant domain jobs (#15)

* fix: reject cross-tenant domain jobs

* RFC-0002: publish workbench public API contract (#16)

* feat: publish versioned public api contract

* RFC-0002: orchestrate legacy batch processing (#17)

* feat: orchestrate legacy batch processing

* docs: expand public workbench API contract (#18)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98c3a56ea0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

@Get('metrics')
@Public()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep metrics behind the observability permission

With @Public() here, both AccessTokenGuard and PermissionsGuard short-circuit on the public-route metadata, so GET /api/metrics no longer requires a bearer token or observability.read despite the following decorator. In deployments where metrics are intended to be permissioned as before this change, this exposes worker, queue, and legacy batch metrics to unauthenticated callers.

Useful? React with 👍 / 👎.

Comment thread src/types.ts
Comment on lines +14 to +15
'LEGACY_EXPORT',
'LEGACY_IMPORT',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject legacy batch jobs through public submissions

CreateJobV1Dto and JobsController.validateCreateJob both accept anything in JOB_TYPES, so adding LEGACY_EXPORT/LEGACY_IMPORT here lets callers with only workbench.jobs.write submit internal legacy batch worker jobs through /api/jobs. In tenants where job submitters are not compliance managers, they can trigger processing of an existing queued legacy batch by knowing its batch_id, bypassing the compliance.manage gate on LegacyBatchesController.

Useful? React with 👍 / 👎.

Comment on lines +106 to +109
return this.runtime.getManager().markDelivered(
req.tenantId, batchId, body.authority_reference,
body.delivered_at ? new Date(body.delivered_at) : undefined,
).then((receipt) => this.present(receipt));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor delivery idempotency keys

For POST /api/regulatory-exports/:batchId/delivery, the handler requires Idempotency-Key but then calls markDelivered without passing the key or a request hash into persistence. When a client retries a timed-out delivery request, or reuses the same key with a different delivery body, the legacy batch store has no way to replay or reject based on that key, unlike the export/import creation paths that persist idempotency inputs.

Useful? React with 👍 / 👎.


private async enqueue(batchId: string, tenantId: string, type: 'LEGACY_EXPORT' | 'LEGACY_IMPORT'): Promise<void> {
await this.jobs.enqueue({
job_id: `legacy-${batchId}`, queue: 'legacy', type, tenant_id: tenantId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not reuse batch IDs as global job IDs

This makes the legacy worker job ID legacy-${batchId}. I checked src/controllers/jobs.controller.ts, and GET /api/jobs/:jobId reads store.get(jobId) without comparing the job's tenant to the requester, so any principal with workbench.read and a leaked or known batch UUID can call /api/jobs/legacy-<uuid> and read status/result/error details for another tenant's compliance batch. Keeping these job IDs opaque or scoping job reads by tenant avoids turning the batch ID into a global lookup key.

Useful? React with 👍 / 👎.

catch (error) {
if (error instanceof BadRequestException) throw error;
if (error instanceof LegacyBatchConflictError || error instanceof LegacyBatchStateError) throw new ConflictException(error.message);
throw new BadRequestException((error as Error).message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve 5xx failures from legacy operations

When the legacy store or connector layer fails unexpectedly in production, this catch-all maps every non-conflict exception to 400 Bad Request. That makes server-side outages or database errors look like client contract errors, which can suppress retries and alerting and leaves callers unable to distinguish malformed input from an unavailable service; unknown exceptions should be allowed to surface as 5xx while only validation errors become 400s.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant