Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .changeset/sharing-execution-context-full-envelope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
"@objectstack/spec": minor
---

feat(spec): sharing / approval / report enforcement takes the full `ExecutionContext`; the six-field context is migration residue (#6523, #6206 ruling default)

`ISharingService`, `ISharingRuleService`, `IApprovalService` and
`IReportService` now declare their context parameter as the complete
`ExecutionContext` envelope instead of the six-field
`SharingExecutionContext` — 36 signatures across the three contract files.
Every one of those methods ADJUDICATES access (the read-filter contribution,
both write gates and their tri-state forms, share management, rule definition
and evaluation, approval decisions and recalls, report runs and schedules), so
each needs the whole `resolveAuthzContext` result: `accessible_org_ids` (the
`group`-posture Layer 0 wall, ADR-0105 D2), `org_user_ids`, `systemPermissions`,
`posture` (ADR-0095 D2 — resolved once, carried, never re-derived at the
enforcement site) and `tabPermissions` included.

`SharingExecutionContext` was the fourth and widest twin of the family #6206
ruled on (converge to the full envelope, keep no per-site subset contracts);
that ruling's sweep had reached only the share-link site (#6430).

The damage ran in the MIRROR direction of the share-link case, which is worth
stating because it is the direction a reviewer does not expect. Nothing here
trimmed a value: `plugin-sharing`'s engine middleware passes its whole
execution context down (`buildReadFilter(ctx.object, exec ?? {})`), so the
values always arrived complete. It was the declared TYPE that was narrow, so
the receiving implementation could not read what it had been handed without
casting out of its own contract — measurably, `plugin-approvals`'
privileged-override gate reaching for the resolved posture as
`(context as any).posture`.

`SharingExecutionContext` is retained and unchanged in shape, now documented as
migration residue: nothing in `packages/spec` takes it any more, and the three
plugin implementations that still annotate their own parameters with it are the
consumer half, separated exactly as #6430's contract and plugin halves were.
Widening it field by field is explicitly refused — that would rebuild the
per-site subset the ruling removed.

Contract-only, no runtime behaviour change and no acceptance-surface change
(these are TypeScript interfaces, not Zod schemas — nothing authorable moves).
Existing implementations keep compiling: the two types are mutually assignable
(all fields optional, all six present in the wider type), so neither direction
of the parameter change breaks them.
33 changes: 20 additions & 13 deletions packages/spec/src/contracts/approval-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
* authoring type, submit, or step machinery anymore.
*/

import type { SharingExecutionContext } from './sharing-service.js';
// [#6523 / #6206 ruling default] Every method below ADJUDICATES access, so each
// takes the complete `resolveAuthzContext` envelope rather than the six-field
// `SharingExecutionContext` this contract used to borrow from `sharing-service`.
// That narrow type omitted `accessible_org_ids` (the `group`-posture Layer 0
// wall, ADR-0105 D2), `org_user_ids`, `posture` (ADR-0095 D2) and
// `tabPermissions` — see `SharingExecutionContext` in `./sharing-service.js`
// for the boundary and the measured consequence.
import type { ExecutionContext } from '../kernel/execution-context.zod.js';

/**
* Lifecycle states of an approval request, in the order the
Expand Down Expand Up @@ -515,7 +522,7 @@ export interface IApprovalService {
limit?: number;
offset?: number;
} | undefined,
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<ApprovalRequestRow[]>;

/**
Expand All @@ -524,17 +531,17 @@ export interface IApprovalService {
*/
countRequests(
filter: Parameters<IApprovalService['listRequests']>[0],
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<number>;

getRequest(requestId: string, context: SharingExecutionContext): Promise<ApprovalRequestRow | null>;
getRequest(requestId: string, context: ExecutionContext): Promise<ApprovalRequestRow | null>;

/**
* Record a decision on a node-driven request. Honours the node's
* `unanimous` behaviour, finalises the request when satisfied, and resumes
* the owning flow run down the matching `approve` / `reject` edge.
*/
decide(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult>;
decide(requestId: string, input: ApprovalDecisionInput, context: ExecutionContext): Promise<ApprovalDecisionResult>;

/**
* Withdraw a pending request. Only the submitter (or a system context) may
Expand All @@ -546,7 +553,7 @@ export interface IApprovalService {
* request flips `returned → recalled` and the run resumes down `reject` the
* same way.
*/
recall(requestId: string, input: ApprovalRecallInput, context: SharingExecutionContext): Promise<ApprovalRecallResult>;
recall(requestId: string, input: ApprovalRecallInput, context: ExecutionContext): Promise<ApprovalRecallResult>;

/**
* ADR-0044 send back for revision. Finalises the pending request as
Expand All @@ -559,7 +566,7 @@ export interface IApprovalService {
sendBack(
requestId: string,
input: ApprovalSendBackInput,
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<ApprovalSendBackResult>;

/**
Expand All @@ -572,7 +579,7 @@ export interface IApprovalService {
resubmit(
requestId: string,
input: ApprovalResubmitInput,
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<ApprovalResubmitResult>;

/**
Expand All @@ -584,7 +591,7 @@ export interface IApprovalService {
reassign(
requestId: string,
input: { actorId: string; to: string; from?: string; comment?: string },
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<{ request: ApprovalRequestRow }>;

/**
Expand All @@ -595,7 +602,7 @@ export interface IApprovalService {
remind(
requestId: string,
input: { actorId: string; comment?: string },
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<{ request: ApprovalRequestRow; notified: number }>;

/**
Expand All @@ -606,7 +613,7 @@ export interface IApprovalService {
requestInfo(
requestId: string,
input: { actorId: string; comment: string },
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<{ request: ApprovalRequestRow }>;

/**
Expand All @@ -616,9 +623,9 @@ export interface IApprovalService {
comment(
requestId: string,
input: { actorId: string; comment: string },
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<{ request: ApprovalRequestRow }>;

/** Audit trail for a request. */
listActions(requestId: string, context: SharingExecutionContext): Promise<ApprovalActionRow[]>;
listActions(requestId: string, context: ExecutionContext): Promise<ApprovalActionRow[]>;
}
26 changes: 16 additions & 10 deletions packages/spec/src/contracts/report-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
* pivots, charts) layers on top of these primitives.
*/

import type { SharingExecutionContext } from './sharing-service.js';
// [#6523 / #6206 ruling default] Reports are read UNDER the caller's context —
// row visibility, the saved-report gate and the schedule owner check all read
// it — so every method takes the complete `resolveAuthzContext` envelope rather
// than the six-field `SharingExecutionContext` this contract used to borrow
// from `sharing-service`. See `SharingExecutionContext` in
// `./sharing-service.js` for the boundary.
import type { ExecutionContext } from '../kernel/execution-context.zod.js';

/** Render format supported by `IReportService.run()`. */
export type ReportFormat = 'csv' | 'json' | 'html_table';
Expand Down Expand Up @@ -117,36 +123,36 @@ export interface ScheduleReportInput {
*/
export interface IReportService {
/** Execute a report by id. */
run(reportId: string, context: SharingExecutionContext): Promise<ReportRunResult>;
run(reportId: string, context: ExecutionContext): Promise<ReportRunResult>;

/** Execute an ad-hoc report from an in-memory definition. */
runAdHoc(input: SaveReportInput, context: SharingExecutionContext): Promise<ReportRunResult>;
runAdHoc(input: SaveReportInput, context: ExecutionContext): Promise<ReportRunResult>;

/** Upsert a saved report. Returns the persisted row. */
saveReport(input: SaveReportInput, context: SharingExecutionContext): Promise<SavedReport>;
saveReport(input: SaveReportInput, context: ExecutionContext): Promise<SavedReport>;

/** List saved reports — optionally filtered by object. */
listReports(
filter: { object?: string; ownerId?: string } | undefined,
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<SavedReport[]>;

/** Get a saved report by id. */
getReport(reportId: string, context: SharingExecutionContext): Promise<SavedReport | null>;
getReport(reportId: string, context: ExecutionContext): Promise<SavedReport | null>;

/** Delete a saved report by id (and any attached schedules). */
deleteReport(reportId: string, context: SharingExecutionContext): Promise<void>;
deleteReport(reportId: string, context: ExecutionContext): Promise<void>;

/** Create or update a schedule. */
scheduleReport(input: ScheduleReportInput, context: SharingExecutionContext): Promise<ReportSchedule>;
scheduleReport(input: ScheduleReportInput, context: ExecutionContext): Promise<ReportSchedule>;

/** Remove a schedule by id. */
unscheduleReport(scheduleId: string, context: SharingExecutionContext): Promise<void>;
unscheduleReport(scheduleId: string, context: ExecutionContext): Promise<void>;

/** List schedules — optionally filtered by report. */
listSchedules(
filter: { reportId?: string } | undefined,
context: SharingExecutionContext,
context: ExecutionContext,
): Promise<ReportSchedule[]>;

/**
Expand Down
Loading
Loading