Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
91adfe9
docs(sdd): specify issue contract in specs/recurrence-lifecycle-calen…
github-actions[bot] Nov 3, 2026
1bf29de
test: establish the genuine recurrence lifecycle baseline
efraespada Nov 3, 2026
2046b40
docs: prioritize usable task workflows within M2
efraespada Nov 3, 2026
a954ce0
Merge develop into feature/23-complete-relative-recurrence-lifecycle-…
vypbot Nov 3, 2026
2ee3ff4
feat: add the household task workspace
efraespada Nov 3, 2026
7d28926
build: package the task workspace in Edge.8
efraespada Nov 3, 2026
93d7d77
fix: preserve task recovery and enforce Unicode form limits
efraespada Nov 3, 2026
2b2da5f
build: publish the corrected task workspace in Edge.9
efraespada Nov 4, 2026
280cdd1
fix: require a reason before rejecting or reopening tasks
efraespada Nov 4, 2026
b2729f3
feat: define recurrence cycle closure and successor review policy
efraespada Nov 4, 2026
e687211
docs: prioritize shared components and Domus-specific household UX
efraespada Nov 4, 2026
fc90ba3
feat: read bounded live and historical recurrence source facts
efraespada Nov 4, 2026
a6715d8
docs: define shared UI repository and Storybook ownership
efraespada Nov 4, 2026
992f96b
docs: retain recurrence capability readiness blockers
efraespada Nov 4, 2026
ef09331
test: capture reopening response before click auto-wait completes
efraespada Nov 4, 2026
8d2742e
feat: materialize initial relative work atomically with original proof
efraespada Nov 4, 2026
8654b8a
test: retain real reopening responses and per-CPU browser evidence
efraespada Nov 4, 2026
c6c2924
feat: encode closed relative lifecycle snapshots without implicit def…
efraespada Nov 4, 2026
8c0a36c
docs: clarify the current recurrence implementation status
efraespada Nov 4, 2026
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
19 changes: 15 additions & 4 deletions .github/workflows/ci_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,22 +290,33 @@ jobs:
cache: false
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @domus/frontend exec playwright install --with-deps chromium
- name: Execute administration in the actual isolated image from Chromium
- name: Execute household workflows in the actual isolated image from Chromium
env:
DOMUS_BROWSER_IMAGE: domus-app-ci
DOMUS_TEST_SOURCE_COMMIT: ${{ github.sha }}
DOMUS_TEST_ARCH: ${{ matrix.arch }}
run: >-
pnpm --filter @domus/frontend exec playwright test
-c playwright.entry.config.ts tests/e2e/administration.spec.ts
- name: Require all thirty-four actual-image administration journeys
-c playwright.entry.config.ts tests/e2e/administration.spec.ts tests/e2e/tasks.spec.ts
- name: Require all forty-four actual-image household journeys
run: >-
uv run python -c "import json; from pathlib import Path;
result=json.loads(Path('frontend/test-results/entry-results.json').read_text());
assert result['stats']['expected']==34 and result['stats']['unexpected']==0
assert result['stats']['expected']==44 and result['stats']['unexpected']==0
and result['stats']['skipped']==0 and result['stats']['flaky']==0"

- name: Retain actual-image browser evidence
if: ${{ always() }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: image-browser-${{ matrix.arch }}-${{ github.sha }}
path: frontend/test-results/
include-hidden-files: true
if-no-files-found: warn
retention-days: 7

- name: Require no surviving image browser fixtures
if: ${{ always() }}
run: >-
uv run python -c "import subprocess;
assert not subprocess.check_output('docker ps --all --quiet --filter label=domus.browser-fixture=true'.split()).strip()"
Expand Down
21 changes: 21 additions & 0 deletions addon-edge/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.0.0-edge.9

- Recover the caller's original task action using its current recorded task authority, even when
general operation browsing is disabled; retain the separate browsing permission boundary.
- Enforce shared Unicode code-point limits in task forms, preserve overlong drafts and show the
field that needs correction; accept the exact limits for astral characters.
- Exercise restricted lost-response recovery and exact Unicode limits in both deployment modes,
increasing the actual-image campaign to 44 household/task journeys per CPU.
- Replace the earlier Edge.8 candidate with a distinct immutable reviewed runtime.
- Retain schema 9, cold backups and the existing Core API permission scope.

## 0.0.0-edge.8

- Make Tasks the default household screen, with English/Spanish creation, list and detail.
- Claim and complete work as the current participant; approve, reject or reopen the current cycle.
- Review adjustable linked-material quantities without inventing stock or downstream delivery.
- Recover an uncertain original task request through an authorized read, without another command.
- Keep household administration available and move technical task details behind optional disclosure.
- Retain schema 9, cold backups, both CPU families and the existing Core API permission scope.
- Add eight task journeys to the actual-image browser campaign; owner adoption remains separate.

## 0.0.0-edge.7

- Require cold Supervisor backups so the app is stopped while its private SQLite data is copied.
Expand Down
26 changes: 24 additions & 2 deletions addon-edge/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ it preserves later operation evidence without reapplying rolled-away effects. St
restore preparation/activation. Inspect and resume the same activation ID after an uncertain result.
The retained source-checkpoint supports deliberate rollback reconciled against the latest history.
Do not switch root pointers or run an older image manually. See the
[restoration guide](https://github.com/vypdev/domus/blob/3200bd9278f57f142ed03b54843a38a4138fc0af/docs/restoration.md)
[restoration guide](https://github.com/vypdev/domus/blob/93d7d77b98a196640bf0049bca36a3d90b7a5857/docs/restoration.md)
for the exact console commands. Restore is not available from the household entry interface.
App removal with data
deletion removes the app's private records; use deliberate operator backup procedures before removal.
Expand Down Expand Up @@ -69,7 +69,7 @@ The [artifact declaration](artifact.json) fixes the reviewed source/archive and
file fingerprints. Application CI builds the actual pinned installer image on amd64 and aarch64,
checks those bytes, exercises its native restart/authority boundary and standalone UI, and runs
the installed Edge.5 upgrade/replay/restore cycle. The separate current-source image retains its
complete administration browser campaign. These fixtures use isolated owned data; actual owner
complete household administration and task browser campaign. These fixtures use isolated owned data; actual owner
adoption and required human UX are additional acceptance steps.

Edge.7 retains the Edge.6 runtime/schema and declares `backup: cold`. Supervisor stops Domus while
Expand All @@ -91,3 +91,25 @@ Stopping does not sign out your HA frontend. There is no Domus OAuth
grant to revoke for this native mode. Remove the development app/repository after its owned test;
do not migrate production household data into this preview. A stable package will be published
separately when its acceptance gates pass.


Edge.8 makes Tasks the default screen. Create a task with a title, optional description and priority;
open it to claim and mark your own work done. Current authorized reviewers can approve/reject the
current cycle or reopen it with a reason. Linked material quantities respect their unit precision;
a recorded completion never fabricates stock or downstream point delivery. Uncertain requests
remain inspectable by their original key without automatically sending another command. Technical
identifiers are under optional details. Household settings remain in the Household tab.

This increment keeps schema 9 and the existing installation options. It adds no breaking-version
boundary, host exposure or Core API permission. The immutable artifact fixes the published runtime;
both CPU jobs exercise all 44 household/task browser journeys in the current-source image. The
calendar, full recurrence editors, points/rewards and supplies screens are still being developed.
Actual owner installation and usability review are separate from isolated browser fixtures.


Edge.9 corrects task-specific original recovery so it checks current task-read and the recorded
command grants without requiring operations.read for general browsing. The general operation-by-ID
query keeps that separate permission. Form-level title, description and reason limits count Unicode
code points, retain invalid input with a visible error and prevent submission until corrected. Both
deployments exercise restricted lost-response recovery and exact astral-character bounds. This is
a distinct immutable preview from Edge.8; it retains the same schema and permission scope.
8 changes: 4 additions & 4 deletions addon-edge/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# The Supervisor builds this folder. Fetch only the reviewed immutable runtime and UI.
FROM ghcr.io/astral-sh/uv:0.11.29@sha256:eb2843a1e56fd9e30c7276ce1a52cba86e64c7b385f5e3279a0e08e02dd058fc AS uv
FROM python:3.14.6-slim-trixie@sha256:7bec7ddcddeff7975d6ba9b4be7dd6f6b2f55e7491539145e2978f7f97ce9144 AS source
ARG DOMUS_SOURCE_COMMIT=3200bd9278f57f142ed03b54843a38a4138fc0af
ARG DOMUS_SOURCE_SHA256=ca991d7b3560dbdb7a6a86a34eea19708f01f10c15da502e49ed8ab740a8533b
ARG DOMUS_SOURCE_COMMIT=93d7d77b98a196640bf0049bca36a3d90b7a5857
ARG DOMUS_SOURCE_SHA256=97dc46699ad7eab54c487386ffaffc28624e8e50f9d3a2f1ffc6b525fb7b8f0e
ADD https://codeload.github.com/vypdev/domus/tar.gz/${DOMUS_SOURCE_COMMIT} /tmp/source.tar.gz
RUN printf '%s /tmp/source.tar.gz\n' "$DOMUS_SOURCE_SHA256" | sha256sum --check - \
&& mkdir /app \
Expand All @@ -24,9 +24,9 @@ RUN uv sync --frozen --no-dev --package domus-server --no-editable
RUN .venv/bin/python -c "from pathlib import Path; import domus; from domus.entrypoints.ui_bundle import load_bundle; assert load_bundle(Path(domus.__file__).parent / 'ui') is not None"

FROM python:3.14.6-slim-trixie@sha256:7bec7ddcddeff7975d6ba9b4be7dd6f6b2f55e7491539145e2978f7f97ce9144
ARG BUILD_VERSION=0.0.0-edge.7
ARG BUILD_VERSION=0.0.0-edge.9
ARG BUILD_ARCH
ARG DOMUS_SOURCE_COMMIT=3200bd9278f57f142ed03b54843a38a4138fc0af
ARG DOMUS_SOURCE_COMMIT=93d7d77b98a196640bf0049bca36a3d90b7a5857
LABEL io.hass.type="app" \
io.hass.version="${BUILD_VERSION}" \
io.hass.arch="${BUILD_ARCH}" \
Expand Down
11 changes: 9 additions & 2 deletions addon-edge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
Development preview of Domus installed as a Home Assistant app. Open it through HA's Open Web UI;
it uses your existing HA sign-in without another login.

Edge 0.0.0-edge.7 includes the English/Spanish household administration workspace: participant roster,
Edge 0.0.0-edge.9 includes the Tasks workspace: create, claim, complete, approve/reject and reopen work
in English or Spanish. Tasks open by default; household administration remains available from the
Household tab. Material quantities and original-request recovery preserve the existing server
authorization and exact operation identity. Calendar, recurrence controls, points and supplies
remain in progress. Task forms retain invalid drafts and enforce exact Unicode character limits;
original task recovery uses the current task grants independently of general operation browsing.

It retains the English/Spanish household administration workspace: participant roster,
household settings, HA administrator mapping, member roles/ordinary permissions, public aliases
and owner-private preferences. Reviewed changes retain exact revisions and original operation
identity; a changed verified household retires private drafts. Saved preferences describe stored
Expand All @@ -18,7 +25,7 @@ household records, history and original-operation evidence are retained. The ins
recipe is preserved separately as an immutable compatibility fixture.

Set your Home Assistant browser origins in Configuration before starting. Edge remains experimental;
the remaining domain screens, required human UX/owner adoption and complete Stable product are
the remaining domain screens, required human UX review and complete Stable product are
still in progress.

See [installation and permissions](DOCS.md) and the [reviewed artifact declaration](artifact.json).
20 changes: 10 additions & 10 deletions addon-edge/artifact.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"schemaVersion": 1,
"version": "0.0.0-edge.7",
"sourceCommit": "3200bd9278f57f142ed03b54843a38a4138fc0af",
"sourceArchiveSHA256": "ca991d7b3560dbdb7a6a86a34eea19708f01f10c15da502e49ed8ab740a8533b",
"version": "0.0.0-edge.9",
"sourceCommit": "93d7d77b98a196640bf0049bca36a3d90b7a5857",
"sourceArchiveSHA256": "97dc46699ad7eab54c487386ffaffc28624e8e50f9d3a2f1ffc6b525fb7b8f0e",
"storageSchema": 9,
"wheelSHA256": "6578a595350e00b0df2fb7e12a436a100a6cf8a661ad4147a5fc074d6de05351",
"wheelSHA256": "7c3e1d6b7078a2ab7177407c027df10afdacb2ef66f63b790782987538d3e0c4",
"pythonFiles": {
"domus/__init__.py": "7182c88b92234b4328f94497f4e70222d4390d8093e4b62893dfa78ac5530ac6",
"domus/application/__init__.py": "1e8c0c87bf2a8ca947112543070583c0cc55d9d40b56eab0d86ed59a4ec1e82c",
Expand Down Expand Up @@ -49,7 +49,7 @@
"domus/application/task_material_views.py": "1f4e1b52c17064b73e3c6502b38fc578a7589d68044279312c783b656e5aa5bf",
"domus/application/task_materials.py": "caa132a7b16fca293770deba3da149cf9bda2e24d4719abe9de39470d686698e",
"domus/application/task_ports.py": "67ede6022534205e35878c695c0fee4354ccfa207140e8c4043888fc133c5076",
"domus/application/task_queries.py": "e4e8256e49acd19af6d850606168e649ea00813712b0f3bae1d8cf57e0a3ba97",
"domus/application/task_queries.py": "4a36d6712634ed778755d52a7203af91132c5532ad05d3300e374ec337f50c83",
"domus/application/task_requests.py": "ca196b2fdfecd1110d03caff6121be0ca50c505a4f7713f97a8f62d2449556ec",
"domus/application/task_reviews.py": "dcc288cda6cdcf5073bad4f24133b9d3496c0b0cf1272e8328e6df38129f3328",
"domus/composition/__init__.py": "a902d8faf7dd19da6829fb730d81e32d34ec38ee6cfafd676966a95d0ad31366",
Expand Down Expand Up @@ -106,7 +106,7 @@
"domus/entrypoints/native_http.py": "5a92ac6094dea64b06e13af9e05453a2f302bd6261ab46195bed0198b3d69482",
"domus/entrypoints/openapi.py": "01a3fab9e5890ebcc1fb9c70bce9ced43d45b9190afd134db7dd45e0c5eb28f1",
"domus/entrypoints/task_dto.py": "7df14d696be6290f4f3f412dc5ec6ffe1682750f410ad38260552d5e1b770c9f",
"domus/entrypoints/task_http.py": "bb646552a3a54a538219470c0e160f281b9926c553630036a7a569f87eb3e84a",
"domus/entrypoints/task_http.py": "7096eba00065fcdaccef04126b465919281b9efe06acaf655ee5ae2041ccf63a",
"domus/entrypoints/task_response.py": "33cb58fba8e45dfaf46474905bb8bef0145c392ee4ed3155bff7958677742435",
"domus/entrypoints/ui_bundle.py": "beae3d4a7a57375ee195cca81efccdbc899d9db4286029d53eabaf30de6e14f7",
"domus/entrypoints/ui_http.py": "b6c47baa742f3862173a93e4121dc9914fd972eb6287ffffb9ffccecbf2ebe4a",
Expand Down Expand Up @@ -159,9 +159,9 @@
"domus/infrastructure/task_materials.py": "d5d2934e36f290c7bf44b473392f014eab8749f1f3b3d26a3402079e65d2818f"
},
"uiFiles": {
"index.html": "fb4f250c231b0f0b07fd4d99fa00925610f0573b01f033a4bf75178f860887fd",
".vite/manifest.json": "07f6f6a7f49b0c8691b16dd3e687e17698b4e540e6f3cd2577fcfc187514f159",
"assets/index-CS8EClQ3.js": "c3103fcc8d107cff163ab5a98c8a587ef5203367624dd1f23fea56b96227dedd",
"assets/index-CjJnHZzV.css": "0f2c8c2e31576c0b64fe60373150f09390dafb896df6c07777b2a220ad430e48"
".vite/manifest.json": "31afa1ff0c4629340409460e52b6d2901e2a53154efb7c4a12fede871bde200c",
"assets/index-CjJnHZzV.css": "0f2c8c2e31576c0b64fe60373150f09390dafb896df6c07777b2a220ad430e48",
"assets/index-Cu0tEo5S.js": "d2a7ce802d45bbbf6cc7fcea110e51bd0339f6279a0a7d4ee9df66b0785feee8",
"index.html": "1a9d70fdea01faba7f0ea2e5e7a9d3bfdaa2e4a2bd0209c66d85f1d6d1c36048"
}
}
2 changes: 1 addition & 1 deletion addon-edge/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Domus Edge
version: "0.0.0-edge.7"
version: "0.0.0-edge.9"
slug: domus_edge
description: Development preview of the local Domus household application
url: https://github.com/vypdev/domus
Expand Down
65 changes: 65 additions & 0 deletions backend/src/domus/application/recurrence_sources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""A composition-scoped source reads original current/history facts without granting effects."""

from dataclasses import dataclass

from domus.application.fixed_materialization import MaintenanceScope
from domus.application.ports import Transactions
from domus.application.task_authorization import recorded_request
from domus.domain.errors import InvalidInput, StorageFailure
from domus.domain.restoration import RetainedOperation
from domus.domain.task_events import TaskLifecycleEvent, TaskSourceRecord


@dataclass(frozen=True, slots=True)
class RecurrenceObservation:
original: RetainedOperation
event: TaskLifecycleEvent


@dataclass(frozen=True, slots=True)
class RecurrenceSourcePage:
items: tuple[RecurrenceObservation, ...]
after_position: int
complete: bool
source_rows_examined: int


@dataclass(frozen=True, slots=True)
class RecurrenceSourceObservations:
transactions: Transactions
scope: MaintenanceScope

def read(self, *, limit: int = 100, after: int = 0) -> RecurrenceSourcePage:
if type(limit) is not int or not 1 <= limit <= 100:
raise InvalidInput("invalid_page_size")
if type(after) is not int or not 0 <= after <= 2**63 - 1:
raise InvalidInput("invalid_task_position")
with self.transactions.read() as unit:
self.scope.require(unit)
records = unit.tasks.lifecycle_sources(
self.scope.household_id, limit=limit, after=after
)
if type(records) is not tuple or len(records) > limit:
raise StorageFailure("invalid_recurrence_source")
items = []
previous = after
for record in records:
if not isinstance(record, TaskSourceRecord):
raise StorageFailure("invalid_recurrence_source")
recorded_request(record.original.evidence)
try:
event = record.event(self.scope.installation)
except InvalidInput, TypeError, ValueError:
Comment thread
vypbot marked this conversation as resolved.
Resolved
raise StorageFailure("invalid_recurrence_source") from None
if (
event.cycle.household_id != self.scope.household_id
or event.position <= previous
):
raise StorageFailure("invalid_recurrence_source")
items.append(RecurrenceObservation(record.original, event))
previous = event.position
# No lookahead is fetched or skipped. An exact full page is conservatively incomplete.
# Even an empty scan consumes one unit of the independent source currency.
return RecurrenceSourcePage(
tuple(items), previous, len(records) < limit, max(1, len(records))
)
Loading
Loading