Skip to content
Draft
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
4 changes: 4 additions & 0 deletions BunkFy.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<File Path="docs/planning/guest-data-rights-restriction-task.md" />
<File Path="docs/planning/guest-record-retention-task.md" />
<File Path="docs/planning/guest-records-module-task.md" />
<File Path="docs/planning/guests-deployed-stay-history-proof-task.md" />
<File Path="docs/planning/guests-management-create-idempotency-task.md" />
<File Path="docs/planning/guests-management-update-archive-idempotency-task.md" />
<File Path="docs/planning/guests-operational-surface-hardening-task.md" />
Expand Down Expand Up @@ -113,6 +114,7 @@
<File Path="docs/planning/international-market-gate-task.md" />
<File Path="docs/planning/inventory-allocation-mutation-serialization-task.md" />
<File Path="docs/planning/inventory-data-rights-owner-capability-task.md" />
<File Path="docs/planning/inventory-manual-block-group-convergence-task.md" />
<File Path="docs/planning/inventory-manual-block-idempotency-task.md" />
<File Path="docs/planning/inventory-module-task.md" />
<File Path="docs/planning/inventory-operational-surface-hardening-task.md" />
Expand Down Expand Up @@ -143,6 +145,7 @@
<File Path="docs/planning/production-authentication-admission-task.md" />
<File Path="docs/planning/production-file-surface-lockdown-task.md" />
<File Path="docs/planning/properties-bed-mutation-idempotency-task.md" />
<File Path="docs/planning/properties-deployed-topology-lifecycle-proof-task.md" />
<File Path="docs/planning/properties-details-update-idempotency-task.md" />
<File Path="docs/planning/properties-follow-up-notes.md" />
<File Path="docs/planning/properties-lifecycle-idempotency-task.md" />
Expand Down Expand Up @@ -187,6 +190,7 @@
<File Path="docs/planning/staff-data-rights-correction-task.md" />
<File Path="docs/planning/staff-data-rights-owner-capability-task.md" />
<File Path="docs/planning/staff-data-rights-restriction-task.md" />
<File Path="docs/planning/staff-deployed-employment-lifecycle-proof-task.md" />
<File Path="docs/planning/staff-employment-lifecycle-idempotency-task.md" />
<File Path="docs/planning/staff-identity-bootstrap-replay-envelope-task.md" />
<File Path="docs/planning/staff-immediate-effective-date-integrity-task.md" />
Expand Down
4 changes: 4 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Useful entry points:
- [Properties Follow-Up Notes](planning/properties-follow-up-notes.md)
- [GMA Access Control and Properties Alignment](planning/gma-access-control-properties-alignment.md)
- [Properties Topology Lifecycle Task](planning/properties-topology-lifecycle-task.md)
- [Properties Deployed Topology Lifecycle Proof Task](planning/properties-deployed-topology-lifecycle-proof-task.md)
- [Properties Mutation Serialization Task](planning/properties-mutation-serialization-task.md)
- [Properties Management Create Idempotency Task](planning/properties-management-create-idempotency-task.md)
- [Properties Details Update Idempotency Task](planning/properties-details-update-idempotency-task.md)
Expand All @@ -53,6 +54,7 @@ Useful entry points:
- [Inventory Allocation Mutation Serialization Task](planning/inventory-allocation-mutation-serialization-task.md)
- [Inventory Room Sales Mode Idempotency Task](planning/inventory-room-sales-mode-idempotency-task.md)
- [Inventory Manual Block Idempotency Task](planning/inventory-manual-block-idempotency-task.md)
- [Inventory Manual Block Group Convergence Task](planning/inventory-manual-block-group-convergence-task.md)
- [Inventory Retirement Idempotency Task](planning/inventory-retirement-idempotency-task.md)
- [Inventory Retirement Outcome Convergence Task](planning/inventory-retirement-outcome-convergence-task.md)
- [Inventory Retirement Cancellation Recovery Task](planning/inventory-retirement-cancellation-recovery-task.md)
Expand All @@ -68,6 +70,7 @@ Useful entry points:
- [Staff Record Retention Task](planning/staff-record-retention-task.md)
- [Staff Retention Prerequisite Transaction Boundary Task](planning/staff-retention-prerequisite-transaction-boundary-task.md)
- [Guest Records Module Task](planning/guest-records-module-task.md)
- [Guests Deployed Stay-History Proof Task](planning/guests-deployed-stay-history-proof-task.md)
- [Guests Management Create Idempotency Task](planning/guests-management-create-idempotency-task.md)
- [Guests Management Update And Archive Idempotency Task](planning/guests-management-update-archive-idempotency-task.md)
- [Guests Read Surface Hardening Task](planning/guests-read-surface-hardening-task.md)
Expand All @@ -85,6 +88,7 @@ Useful entry points:
- [Staff Manual-Create Account-Link Authority Task](planning/staff-manual-create-account-link-authority-task.md)
- [Staff Account-Link Authorization Task](planning/staff-account-link-authorization-task.md)
- [Staff Employment Lifecycle Idempotency Task](planning/staff-employment-lifecycle-idempotency-task.md)
- [Staff Deployed Employment Lifecycle Proof Task](planning/staff-deployed-employment-lifecycle-proof-task.md)
- [Staff Immediate Effective-Date Integrity Task](planning/staff-immediate-effective-date-integrity-task.md)
- [Staff Identity Bootstrap Replay Envelope Task](planning/staff-identity-bootstrap-replay-envelope-task.md)
- [Staff Property Assignment Idempotency Task](planning/staff-property-assignment-idempotency-task.md)
Expand Down
116 changes: 116 additions & 0 deletions docs/planning/inventory-manual-block-group-convergence-task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Inventory Manual Block Group Convergence Task

Status: implementation and local verification complete; draft publication pending
Date: 2026-08-12

## Goal

Make broad manual Inventory blocks a bounded, previewable, confirmable, and
recoverable operator workflow without weakening the existing atomic
availability contract.

## Ownership

- Inventory owns the block-group definition, resolved membership, maximum
impact, selection digest, mutation versions, operation receipts, actor
provenance, and tenant lifecycle.
- Properties remains authoritative for physical topology. Inventory previews
and commits against its local versioned topology projection.
- Reservations continues to consume the existing per-child block events. This
slice does not redefine a committed Inventory mutation as downstream
projection convergence.
- The workflow is BunkFy-specific. GMA receives no Inventory block concepts.
- Failed transactional results rely on the provider-neutral rollback-reset hook
from GMA Framework PR #24. The backend pins that exact framework commit so a
later command in the same scope cannot persist entities or domain events left
tracked by a rolled-back attempt; this is generic unit-of-work hygiene, not an
Inventory concept in the framework.

## Frozen Contract

- A synchronous block group contains at most 500 child blocks. `500` succeeds;
`501` returns a stable impact-limit error and creates no group, child,
operation receipt, outbox message, or Inventory revision.
- For targets within the limit, preview returns the normalized target, exact
affected count, maximum, bounded member evidence, and a deterministic digest
of the selection and selection-affecting versions. Above the limit it returns
`TooLarge`, a bounded lower count of at least 501, and no exact count or
confirmation digest; it does not perform an unbounded count scan. A count
alone never confirms membership.
- Create requires the preview digest, expected count, and explicit
confirmation. The server resolves the target again under the mutation
fences and rejects a stale preview atomically.
- A block group is a first-class, versioned parent. Its read model reports the
original target, active/released/total counts, status, predecessor/successor
relationship, timestamps, and actor provenance. Legacy parents identify
their original target as unknown instead of inferring intent from mutable
topology.
- Group definitions are immutable. Replace atomically releases the predecessor
and creates a successor with a new group id, linked in both read models and
in the receipt. A semantic no-op may retain the same group id and version.
- Replace and release bind the caller-observed group version and require
explicit confirmation. Receipts report the observed predecessor and actual
result, including released-now, already-released, remaining, and affected
counts where applicable.
- Exact operation replay returns its committed receipt before mutable topology
or group-state checks. Changed operation-id reuse is a conflict. Failed
preview, confirmation, impact-limit, stale-version, or availability checks do
not bind the operation id.

## Operator Surfaces

Public and Admin API expose the same Inventory contracts:

- `POST /properties/{propertyId}/block-groups/preview`;
- `GET /properties/{propertyId}/block-groups`;
- `GET /properties/{propertyId}/block-groups/{blockGroupId}`;
- `GET /properties/{propertyId}/block-groups/{blockGroupId}/members`;
- `POST /properties/{propertyId}/block-groups`;
- `PUT /properties/{propertyId}/block-groups/{blockGroupId}`;
- `POST /properties/{propertyId}/block-groups/{blockGroupId}/release`;
- `GET /properties/{propertyId}/block-group-create-operations/{operationId}`;
- `GET /properties/{propertyId}/block-groups/{blockGroupId}/operations/{operationId}`.

Group and member directories use opaque keyset cursors and bounded page sizes;
they never return an unbounded nested child collection. The create-operation
read is property-scoped because a client does not know the generated group id
after a lost response. With a success-only receipt journal, `404` means no
committed outcome is visible and does not assert that the request failed; the
safe recovery action is an exact retry with the same operation id and payload.

Every public and Admin response, including errors, remains `no-store`. Stable
problem codes distinguish invalid input/confirmation, not found, stale preview
or version, impact over 500, closed workspace, and unavailable admission. Human
messages are display text; clients branch and localize on the code.

Admin API and Admin CLI use distinct `inventory.block-groups.*` audit operation
names and propagate the authenticated actor into Inventory's durable evidence.
The new property-scoped, sensitive `inventory.block-groups.manage` permission is
granted to the Manager seed and is delegable with its prerequisites. It is not
silently added to legacy Front desk or Housekeeping access. CLI mutations
require `--yes` and support the same preview, reads, keyset traversal, create,
replace, release, and operation-recovery workflow as HTTP.

## Scale And Failure Semantics

The service does not silently split an over-limit request into active batches.
That would expose partially blocked availability and permit allocations to race
between batches. Operators narrow the target and preview again.

An asynchronous staged workflow is deferred. It would require inactive
prepared children, explicit progress/retry/cancel states, conflict revalidation,
an atomic activation gate, and downstream convergence semantics. Those are not
implicit in this synchronous contract.

## Completion Criteria

- unit and provider tests prove `0`, `500`, and `501`, deterministic digest and
stale-selection behavior, atomic replace/release, exact replay, lost-response
recovery, keyset traversal, tenant isolation, and no partial persistence;
- public, Admin API, and Admin CLI authorization tests prove the new permission,
property scope, confirmation, actor attribution, stable errors, and no-store;
- generated OpenAPI and TypeScript contracts match the runtime endpoints;
- Inventory tenant export, destruction, and personal-data metadata include the
first-class group and persisted actor evidence;
- the implementation is repository-verified; deployment and production
readiness remain separate evidence gates.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ Make Inventory's ordinary operator surfaces truthful, bounded, cache-safe, and i

## Deferred

- A first-class persisted manual-block-group aggregate and group-paged history. The current storage model records one block per unit, so changing history pagination semantics safely requires preserving the original target intent rather than inferring it from mutable topology.
- A first-class persisted manual-block-group aggregate and group-paged history
were deferred by this historical slice and are addressed by
`inventory-manual-block-group-convergence-task.md`, which preserves original
target intent and marks legacy intent unknown rather than inferring it.
- Server-side searchable block-target discovery for unusually large properties.
- Partial availability paging or search. That requires a reservation-selection UX and consistency contract of its own.
- Provider-specific query tuning or partitioning before measurements justify it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ public static class InventoryAdminOperationNames
public const string BlocksList = "inventory.blocks.list";
public const string BlocksCreate = "inventory.blocks.create";
public const string BlocksRelease = "inventory.blocks.release";
public const string BlockGroupsPreview = "inventory.block-groups.preview";
public const string BlockGroupsList = "inventory.block-groups.list";
public const string BlockGroupsGet = "inventory.block-groups.get";
public const string BlockGroupMembersList = "inventory.block-groups.members.list";
public const string BlockGroupsCreate = "inventory.block-groups.create";
public const string BlockGroupsReplace = "inventory.block-groups.replace";
public const string BlockGroupsRelease = "inventory.block-groups.release";
public const string BlockGroupCreateOperationsGet = "inventory.block-group-create-operations.get";
public const string BlockGroupOperationsGet = "inventory.block-group-operations.get";
public const string BedRetirementsGet = "inventory.bed-retirements.get";
public const string BedRetirementsRequest = "inventory.bed-retirements.request";
public const string BedRetirementsRetry = "inventory.bed-retirements.retry";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public static class InventoryAdminPermissions
public static readonly AdminPermission Read = AdminPermission.Create(InventoryAdminPermissionCodes.Read);
public static readonly AdminPermission Configure = AdminPermission.Create(InventoryAdminPermissionCodes.Configure);
public static readonly AdminPermission BlocksManage = AdminPermission.Create(InventoryAdminPermissionCodes.BlocksManage);
public static readonly AdminPermission BlockGroupsManage = AdminPermission.Create(InventoryAdminPermissionCodes.BlockGroupsManage);
public static readonly AdminPermission Retire = AdminPermission.Create(InventoryAdminPermissionCodes.Retire);
}
Loading
Loading