context-motilis 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- context_motilis-0.0.1/.gitignore +28 -0
- context_motilis-0.0.1/LICENSE +21 -0
- context_motilis-0.0.1/PKG-INFO +150 -0
- context_motilis-0.0.1/README.md +126 -0
- context_motilis-0.0.1/docs/adr/0001-mit-licence.md +50 -0
- context_motilis-0.0.1/docs/adr/0002-as-of-is-a-position.md +47 -0
- context_motilis-0.0.1/docs/adr/0003-follow-up-lag-follows-its-definition.md +52 -0
- context_motilis-0.0.1/docs/adr/0004-scheduling-inputs-are-read-when-the-occurrence-is-created.md +56 -0
- context_motilis-0.0.1/docs/adr/0005-urgent-is-a-flag-on-the-two-occurrence-sources.md +74 -0
- context_motilis-0.0.1/docs/adr/0006-the-brief-overlays-pending-confirmation-on-effective-state.md +92 -0
- context_motilis-0.0.1/docs/adr/0007-authority-takes-two-inputs-and-origin-is-not-one.md +71 -0
- context_motilis-0.0.1/docs/adr/0008-the-fallback-algorithm-and-what-late-confirmation-matches.md +81 -0
- context_motilis-0.0.1/docs/adr/0009-the-clock-is-a-clamped-wall-clock.md +68 -0
- context_motilis-0.0.1/docs/adr/0010-a-model-returns-operations-and-the-runtime-builds-the-envelope.md +91 -0
- context_motilis-0.0.1/docs/adr/0011-the-model-input-contract-discovery-disclosure-projection.md +106 -0
- context_motilis-0.0.1/docs/adr/0012-serve-waits-through-the-clock.md +136 -0
- context_motilis-0.0.1/docs/adr/0013-a-diverged-interpretation-is-recorded-bounded.md +96 -0
- context_motilis-0.0.1/docs/adr/0014-say-records-and-the-dispatcher-applies.md +76 -0
- context_motilis-0.0.1/docs/adr/0015-section-9s-script-follows-section-4-10.md +63 -0
- context_motilis-0.0.1/docs/adr/0016-the-listing-is-a-tool-as-well-as-a-resource.md +74 -0
- context_motilis-0.0.1/docs/adr/README.md +50 -0
- context_motilis-0.0.1/docs/audits/2026-09-12-baseline.md +148 -0
- context_motilis-0.0.1/docs/audits/2026-09-12-documentation-system-audit.md +750 -0
- context_motilis-0.0.1/docs/audits/2026-09-12-post-task-3.md +94 -0
- context_motilis-0.0.1/docs/audits/2026-09-12-task-2-store.md +82 -0
- context_motilis-0.0.1/docs/brief-v7.md +794 -0
- context_motilis-0.0.1/docs/hosts.md +66 -0
- context_motilis-0.0.1/docs/matching.md +249 -0
- context_motilis-0.0.1/docs/notes/README.md +17 -0
- context_motilis-0.0.1/docs/notes/lexical-ambiguity-is-not-capture-retry-divergence.md +63 -0
- context_motilis-0.0.1/docs/roadmap.md +233 -0
- context_motilis-0.0.1/docs/walkthrough.md +288 -0
- context_motilis-0.0.1/docs/walkthrough.sh +88 -0
- context_motilis-0.0.1/fixtures/README.md +7 -0
- context_motilis-0.0.1/pyproject.toml +123 -0
- context_motilis-0.0.1/src/context_motilis/__init__.py +10 -0
- context_motilis-0.0.1/src/context_motilis/canon.py +187 -0
- context_motilis-0.0.1/src/context_motilis/capture/__init__.py +0 -0
- context_motilis-0.0.1/src/context_motilis/capture/envelope.py +174 -0
- context_motilis-0.0.1/src/context_motilis/capture/fallback.py +152 -0
- context_motilis-0.0.1/src/context_motilis/capture/filing.py +162 -0
- context_motilis-0.0.1/src/context_motilis/capture/intake.py +320 -0
- context_motilis-0.0.1/src/context_motilis/capture/interpret.py +463 -0
- context_motilis-0.0.1/src/context_motilis/capture/process.py +439 -0
- context_motilis-0.0.1/src/context_motilis/capture/validator.py +305 -0
- context_motilis-0.0.1/src/context_motilis/cli.py +859 -0
- context_motilis-0.0.1/src/context_motilis/clock.py +236 -0
- context_motilis-0.0.1/src/context_motilis/contexts/__init__.py +1 -0
- context_motilis-0.0.1/src/context_motilis/contexts/acceptance.py +108 -0
- context_motilis-0.0.1/src/context_motilis/contexts/apply.py +780 -0
- context_motilis-0.0.1/src/context_motilis/contexts/brief.py +263 -0
- context_motilis-0.0.1/src/context_motilis/contexts/confirmations.py +871 -0
- context_motilis-0.0.1/src/context_motilis/contexts/cursors.py +257 -0
- context_motilis-0.0.1/src/context_motilis/contexts/derivation.py +84 -0
- context_motilis-0.0.1/src/context_motilis/contexts/header.py +202 -0
- context_motilis-0.0.1/src/context_motilis/contexts/kinds.py +257 -0
- context_motilis-0.0.1/src/context_motilis/contexts/operations.py +133 -0
- context_motilis-0.0.1/src/context_motilis/contexts/parties.py +208 -0
- context_motilis-0.0.1/src/context_motilis/contexts/projection.py +481 -0
- context_motilis-0.0.1/src/context_motilis/contexts/reading.py +136 -0
- context_motilis-0.0.1/src/context_motilis/contexts/suspension.py +170 -0
- context_motilis-0.0.1/src/context_motilis/contexts/templates.py +71 -0
- context_motilis-0.0.1/src/context_motilis/contexts/versions.py +216 -0
- context_motilis-0.0.1/src/context_motilis/contexts/why.py +200 -0
- context_motilis-0.0.1/src/context_motilis/demo.py +380 -0
- context_motilis-0.0.1/src/context_motilis/echoes.py +93 -0
- context_motilis-0.0.1/src/context_motilis/host.py +771 -0
- context_motilis-0.0.1/src/context_motilis/ids.py +31 -0
- context_motilis-0.0.1/src/context_motilis/mcp_host.py +893 -0
- context_motilis-0.0.1/src/context_motilis/models.py +108 -0
- context_motilis-0.0.1/src/context_motilis/ports.py +375 -0
- context_motilis-0.0.1/src/context_motilis/py.typed +0 -0
- context_motilis-0.0.1/src/context_motilis/render.py +620 -0
- context_motilis-0.0.1/src/context_motilis/runtime.py +36 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/__init__.py +1 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/matching.py +283 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/occurrences.py +560 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/rules.py +200 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/serve.py +256 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/temperature.py +204 -0
- context_motilis-0.0.1/src/context_motilis/scheduler/tick.py +262 -0
- context_motilis-0.0.1/src/context_motilis/sinks.py +142 -0
- context_motilis-0.0.1/src/context_motilis/store/__init__.py +1 -0
- context_motilis-0.0.1/src/context_motilis/store/artifacts.py +96 -0
- context_motilis-0.0.1/src/context_motilis/store/backup.py +472 -0
- context_motilis-0.0.1/src/context_motilis/store/database.py +237 -0
- context_motilis-0.0.1/src/context_motilis/store/events.py +245 -0
- context_motilis-0.0.1/src/context_motilis/store/hashes.py +88 -0
- context_motilis-0.0.1/src/context_motilis/store/ingestion.py +243 -0
- context_motilis-0.0.1/src/context_motilis/store/lease.py +153 -0
- context_motilis-0.0.1/src/context_motilis/store/materialize.py +194 -0
- context_motilis-0.0.1/src/context_motilis/store/outbox.py +354 -0
- context_motilis-0.0.1/src/context_motilis/store/rebuild.py +105 -0
- context_motilis-0.0.1/src/context_motilis/store/schema.py +552 -0
- context_motilis-0.0.1/tests/__init__.py +0 -0
- context_motilis-0.0.1/tests/acceptance/__init__.py +0 -0
- context_motilis-0.0.1/tests/acceptance/naming.py +8 -0
- context_motilis-0.0.1/tests/acceptance/registry.py +81 -0
- context_motilis-0.0.1/tests/acceptance/restart_segment.py +59 -0
- context_motilis-0.0.1/tests/acceptance/test_brief.py +308 -0
- context_motilis-0.0.1/tests/acceptance/test_brief_traceability.py +65 -0
- context_motilis-0.0.1/tests/acceptance/test_capture.py +454 -0
- context_motilis-0.0.1/tests/acceptance/test_cli.py +209 -0
- context_motilis-0.0.1/tests/acceptance/test_delivery_crash.py +141 -0
- context_motilis-0.0.1/tests/acceptance/test_fencing.py +190 -0
- context_motilis-0.0.1/tests/acceptance/test_mcp.py +422 -0
- context_motilis-0.0.1/tests/acceptance/test_models.py +170 -0
- context_motilis-0.0.1/tests/acceptance/test_pending.py +44 -0
- context_motilis-0.0.1/tests/acceptance/test_portability.py +232 -0
- context_motilis-0.0.1/tests/acceptance/test_registry.py +135 -0
- context_motilis-0.0.1/tests/acceptance/test_scheduler.py +418 -0
- context_motilis-0.0.1/tests/acceptance/test_stale_confirmation.py +154 -0
- context_motilis-0.0.1/tests/capture/__init__.py +0 -0
- context_motilis-0.0.1/tests/capture/conftest.py +95 -0
- context_motilis-0.0.1/tests/capture/test_envelope.py +199 -0
- context_motilis-0.0.1/tests/capture/test_fallback.py +107 -0
- context_motilis-0.0.1/tests/capture/test_filing.py +522 -0
- context_motilis-0.0.1/tests/capture/test_intake.py +298 -0
- context_motilis-0.0.1/tests/capture/test_interpret.py +475 -0
- context_motilis-0.0.1/tests/capture/test_process.py +420 -0
- context_motilis-0.0.1/tests/capture/test_validator.py +171 -0
- context_motilis-0.0.1/tests/conftest.py +160 -0
- context_motilis-0.0.1/tests/contexts/__init__.py +0 -0
- context_motilis-0.0.1/tests/contexts/test_brief.py +416 -0
- context_motilis-0.0.1/tests/contexts/test_confirmations.py +409 -0
- context_motilis-0.0.1/tests/contexts/test_cursors.py +429 -0
- context_motilis-0.0.1/tests/contexts/test_header.py +320 -0
- context_motilis-0.0.1/tests/contexts/test_kinds.py +96 -0
- context_motilis-0.0.1/tests/contexts/test_late_confirmation.py +497 -0
- context_motilis-0.0.1/tests/contexts/test_operations.py +397 -0
- context_motilis-0.0.1/tests/contexts/test_parties.py +216 -0
- context_motilis-0.0.1/tests/contexts/test_reading.py +185 -0
- context_motilis-0.0.1/tests/contexts/test_suspension.py +117 -0
- context_motilis-0.0.1/tests/contexts/test_templates.py +312 -0
- context_motilis-0.0.1/tests/contexts/test_why.py +118 -0
- context_motilis-0.0.1/tests/hosts/__init__.py +0 -0
- context_motilis-0.0.1/tests/hosts/test_mcp.py +706 -0
- context_motilis-0.0.1/tests/invariants/README.md +24 -0
- context_motilis-0.0.1/tests/invariants/__init__.py +0 -0
- context_motilis-0.0.1/tests/invariants/test_acceptance.py +182 -0
- context_motilis-0.0.1/tests/invariants/test_attention_independence.py +98 -0
- context_motilis-0.0.1/tests/invariants/test_authority.py +257 -0
- context_motilis-0.0.1/tests/invariants/test_brief_completeness.py +166 -0
- context_motilis-0.0.1/tests/invariants/test_canonical_record.py +471 -0
- context_motilis-0.0.1/tests/invariants/test_capture.py +234 -0
- context_motilis-0.0.1/tests/invariants/test_dormancy.py +61 -0
- context_motilis-0.0.1/tests/invariants/test_ingestion.py +416 -0
- context_motilis-0.0.1/tests/invariants/test_occurrence.py +198 -0
- context_motilis-0.0.1/tests/invariants/test_provenance.py +198 -0
- context_motilis-0.0.1/tests/invariants/test_stated.py +118 -0
- context_motilis-0.0.1/tests/invariants/test_time.py +156 -0
- context_motilis-0.0.1/tests/scheduler/__init__.py +0 -0
- context_motilis-0.0.1/tests/scheduler/conftest.py +97 -0
- context_motilis-0.0.1/tests/scheduler/test_matching.py +173 -0
- context_motilis-0.0.1/tests/scheduler/test_occurrences.py +668 -0
- context_motilis-0.0.1/tests/scheduler/test_rules.py +239 -0
- context_motilis-0.0.1/tests/scheduler/test_serve.py +198 -0
- context_motilis-0.0.1/tests/scheduler/test_temperature.py +241 -0
- context_motilis-0.0.1/tests/scheduler/test_tick.py +310 -0
- context_motilis-0.0.1/tests/store/__init__.py +0 -0
- context_motilis-0.0.1/tests/store/test_artifacts.py +64 -0
- context_motilis-0.0.1/tests/store/test_backup.py +287 -0
- context_motilis-0.0.1/tests/store/test_canon.py +141 -0
- context_motilis-0.0.1/tests/store/test_events.py +207 -0
- context_motilis-0.0.1/tests/store/test_lease.py +119 -0
- context_motilis-0.0.1/tests/store/test_migration.py +184 -0
- context_motilis-0.0.1/tests/store/test_outbox.py +183 -0
- context_motilis-0.0.1/tests/store/test_rebuild.py +75 -0
- context_motilis-0.0.1/tests/test_cli_display.py +152 -0
- context_motilis-0.0.1/tests/test_cli_say.py +476 -0
- context_motilis-0.0.1/tests/test_clock.py +206 -0
- context_motilis-0.0.1/tests/test_demo.py +258 -0
- context_motilis-0.0.1/tests/test_host.py +193 -0
- context_motilis-0.0.1/tests/test_models.py +120 -0
- context_motilis-0.0.1/tests/test_network_guard.py +92 -0
- context_motilis-0.0.1/tests/test_package.py +41 -0
- context_motilis-0.0.1/tests/test_process_chain.py +203 -0
- context_motilis-0.0.1/tests/test_release_tag.py +172 -0
- context_motilis-0.0.1/tests/test_render.py +510 -0
- context_motilis-0.0.1/tests/test_repo_hygiene.py +149 -0
- context_motilis-0.0.1/tests/test_time_to_return.py +169 -0
- context_motilis-0.0.1/tools/__init__.py +1 -0
- context_motilis-0.0.1/tools/release_tag.py +178 -0
- context_motilis-0.0.1/tools/repo_hygiene.py +223 -0
- context_motilis-0.0.1/tools/time_to_return.py +403 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Never committed (§1.2): household data and store files.
|
|
2
|
+
cultures/
|
|
3
|
+
artifacts/
|
|
4
|
+
*.sqlite*
|
|
5
|
+
.env
|
|
6
|
+
.env.*
|
|
7
|
+
|
|
8
|
+
# Python
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
|
|
17
|
+
# Tooling caches
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.hypothesis/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# Editors / OS
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
.DS_Store
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cetatec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: context-motilis
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A local-first runtime for resumable context.
|
|
5
|
+
Project-URL: Homepage, https://github.com/cetatec/context-motilis
|
|
6
|
+
Project-URL: Source, https://github.com/cetatec/context-motilis
|
|
7
|
+
Author: cetatec
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: context,local-first,mcp,provenance,runtime
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: mcp==2.2.0
|
|
21
|
+
Requires-Dist: pydantic<3,>=2.12
|
|
22
|
+
Requires-Dist: typer<1,>=0.16
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# context-motilis
|
|
26
|
+
|
|
27
|
+
A local-first runtime for resumable context.
|
|
28
|
+
|
|
29
|
+
A **context** is a durable unit — a matter, later an entity or an archive — that
|
|
30
|
+
can be suspended for months and resumed by a person or by any model, with its
|
|
31
|
+
evidence, history, accepted state, commitments in both directions, and dates
|
|
32
|
+
intact. The runtime captures inputs durably before it processes them, keeps time
|
|
33
|
+
and fires reminders without a model, authorizes changes by their consequences,
|
|
34
|
+
records every version with its provenance, and produces a deterministic **brief**
|
|
35
|
+
that answers "where were we?" without rereading history.
|
|
36
|
+
|
|
37
|
+
The model interprets and proposes; the runtime preserves, schedules, and
|
|
38
|
+
authorizes. A dormant context owns no running process; the shared runtime reacts
|
|
39
|
+
to indexed dates and events on its behalf.
|
|
40
|
+
|
|
41
|
+
Tools provide capabilities. context-motilis provides continuity.
|
|
42
|
+
|
|
43
|
+
## Status
|
|
44
|
+
|
|
45
|
+
Stage 1, tasks 1 through 7 of §10 are merged: the store, contexts, the scheduler,
|
|
46
|
+
capture, the brief, export and import, and the CLI host with `motilis demo`
|
|
47
|
+
replaying the stage-1 script of §9. Twenty-three of the twenty-nine stage-1
|
|
48
|
+
acceptance cases are real tests; the six still pending are all MCP, and land
|
|
49
|
+
with task 8.
|
|
50
|
+
|
|
51
|
+
What is absent: the MCP host (`motilis serve` as a server), and with it
|
|
52
|
+
elicitation over MCP and `context://inbox`. Under the default fake clock `serve`
|
|
53
|
+
runs a bounded number of passes; staying up is [ADR 0012](docs/adr/0012-serve-waits-through-the-clock.md)
|
|
54
|
+
and task 8.
|
|
55
|
+
|
|
56
|
+
Two things a new user should know before typing. The CLI is the human's host:
|
|
57
|
+
`note`, `commit`, `remind`, `open`, `ask`, `why`, `due` and `tick` work on a
|
|
58
|
+
clean checkout with no model anywhere. `say` needs a model, this repository
|
|
59
|
+
holds none (§11.1), and in stage 1 free text is interpreted through MCP, where
|
|
60
|
+
the model is the client; on the CLI `say` replays a recorded reading and
|
|
61
|
+
refuses plainly without one. The demo is the way to see the whole loop:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
uvx --from git+https://github.com/cetatec/context-motilis motilis demo
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The remaining gate is checkpoint 1 (§1.3), run by the owner.
|
|
68
|
+
|
|
69
|
+
## The brief
|
|
70
|
+
|
|
71
|
+
[`docs/brief-v7.md`](docs/brief-v7.md) is the controlling specification, committed
|
|
72
|
+
verbatim. Every `§` reference in this repository, in its tests and in its commit
|
|
73
|
+
messages points into it. It is authoritative: where it and any other document in
|
|
74
|
+
this repository disagree, it governs. Revisions supersede one another by number,
|
|
75
|
+
and v7 is the one in force.
|
|
76
|
+
|
|
77
|
+
[`docs/roadmap.md`](docs/roadmap.md) indexes where the work stands against §7 and
|
|
78
|
+
§10; it decides nothing.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
Before the first release:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
uvx --from git+https://github.com/cetatec/context-motilis motilis version
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Once released:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
uvx --from context-motilis motilis version
|
|
92
|
+
# or
|
|
93
|
+
uv tool install context-motilis && motilis version
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The package name and the executable name differ; `--from` is not optional.
|
|
97
|
+
|
|
98
|
+
## Develop
|
|
99
|
+
|
|
100
|
+
Python ≥ 3.12 and [uv](https://docs.astral.sh/uv/).
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
uv sync --group dev
|
|
104
|
+
uv run ruff check . && uv run ruff format --check .
|
|
105
|
+
uv run mypy
|
|
106
|
+
uv run pytest
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Enable the hygiene hook once per clone, so household data and store files cannot
|
|
110
|
+
be committed:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
git config core.hooksPath .githooks
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The same check runs in CI, so a bypassed hook still fails the build.
|
|
117
|
+
|
|
118
|
+
## What is never in this repository
|
|
119
|
+
|
|
120
|
+
Household data, real names, real documents, `.sqlite` files, `artifacts/` and
|
|
121
|
+
`cultures/`. CI runs with no model, no network and no secrets.
|
|
122
|
+
|
|
123
|
+
## Layout
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
src/context_motilis/ the runtime and its hosts
|
|
127
|
+
tests/invariants/ one property test per invariant of §3
|
|
128
|
+
tests/acceptance/ the §8 acceptance suite, by stable name
|
|
129
|
+
docs/brief-v7.md the controlling specification; every § points here
|
|
130
|
+
docs/adr/ decisions the brief does not settle, and why
|
|
131
|
+
docs/hosts.md pinned protocol revision, SDK, and tested client builds
|
|
132
|
+
docs/roadmap.md where the work stands against §7 and §10
|
|
133
|
+
fixtures/ generated fixtures only
|
|
134
|
+
tools/ repository tooling, not part of the package
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Licence
|
|
138
|
+
|
|
139
|
+
MIT. See [`LICENSE`](LICENSE), and [ADR 0001](docs/adr/0001-mit-licence.md) for
|
|
140
|
+
why. Reuse is meant to be straightforward, commercial reuse included.
|
|
141
|
+
|
|
142
|
+
## Two sentences
|
|
143
|
+
|
|
144
|
+
*Runtime:* A context is a durable boundary that can be suspended and
|
|
145
|
+
independently resumed. It retains evidence and history, may carry commitments
|
|
146
|
+
and deterministic behavior, and exposes a bounded brief to whichever human or
|
|
147
|
+
model attends to it next.
|
|
148
|
+
|
|
149
|
+
*Development principle:* What exactly must survive this kill? A feature that
|
|
150
|
+
cannot name what survives its kill is not added.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# context-motilis
|
|
2
|
+
|
|
3
|
+
A local-first runtime for resumable context.
|
|
4
|
+
|
|
5
|
+
A **context** is a durable unit — a matter, later an entity or an archive — that
|
|
6
|
+
can be suspended for months and resumed by a person or by any model, with its
|
|
7
|
+
evidence, history, accepted state, commitments in both directions, and dates
|
|
8
|
+
intact. The runtime captures inputs durably before it processes them, keeps time
|
|
9
|
+
and fires reminders without a model, authorizes changes by their consequences,
|
|
10
|
+
records every version with its provenance, and produces a deterministic **brief**
|
|
11
|
+
that answers "where were we?" without rereading history.
|
|
12
|
+
|
|
13
|
+
The model interprets and proposes; the runtime preserves, schedules, and
|
|
14
|
+
authorizes. A dormant context owns no running process; the shared runtime reacts
|
|
15
|
+
to indexed dates and events on its behalf.
|
|
16
|
+
|
|
17
|
+
Tools provide capabilities. context-motilis provides continuity.
|
|
18
|
+
|
|
19
|
+
## Status
|
|
20
|
+
|
|
21
|
+
Stage 1, tasks 1 through 7 of §10 are merged: the store, contexts, the scheduler,
|
|
22
|
+
capture, the brief, export and import, and the CLI host with `motilis demo`
|
|
23
|
+
replaying the stage-1 script of §9. Twenty-three of the twenty-nine stage-1
|
|
24
|
+
acceptance cases are real tests; the six still pending are all MCP, and land
|
|
25
|
+
with task 8.
|
|
26
|
+
|
|
27
|
+
What is absent: the MCP host (`motilis serve` as a server), and with it
|
|
28
|
+
elicitation over MCP and `context://inbox`. Under the default fake clock `serve`
|
|
29
|
+
runs a bounded number of passes; staying up is [ADR 0012](docs/adr/0012-serve-waits-through-the-clock.md)
|
|
30
|
+
and task 8.
|
|
31
|
+
|
|
32
|
+
Two things a new user should know before typing. The CLI is the human's host:
|
|
33
|
+
`note`, `commit`, `remind`, `open`, `ask`, `why`, `due` and `tick` work on a
|
|
34
|
+
clean checkout with no model anywhere. `say` needs a model, this repository
|
|
35
|
+
holds none (§11.1), and in stage 1 free text is interpreted through MCP, where
|
|
36
|
+
the model is the client; on the CLI `say` replays a recorded reading and
|
|
37
|
+
refuses plainly without one. The demo is the way to see the whole loop:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
uvx --from git+https://github.com/cetatec/context-motilis motilis demo
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The remaining gate is checkpoint 1 (§1.3), run by the owner.
|
|
44
|
+
|
|
45
|
+
## The brief
|
|
46
|
+
|
|
47
|
+
[`docs/brief-v7.md`](docs/brief-v7.md) is the controlling specification, committed
|
|
48
|
+
verbatim. Every `§` reference in this repository, in its tests and in its commit
|
|
49
|
+
messages points into it. It is authoritative: where it and any other document in
|
|
50
|
+
this repository disagree, it governs. Revisions supersede one another by number,
|
|
51
|
+
and v7 is the one in force.
|
|
52
|
+
|
|
53
|
+
[`docs/roadmap.md`](docs/roadmap.md) indexes where the work stands against §7 and
|
|
54
|
+
§10; it decides nothing.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
Before the first release:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
uvx --from git+https://github.com/cetatec/context-motilis motilis version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Once released:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
uvx --from context-motilis motilis version
|
|
68
|
+
# or
|
|
69
|
+
uv tool install context-motilis && motilis version
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The package name and the executable name differ; `--from` is not optional.
|
|
73
|
+
|
|
74
|
+
## Develop
|
|
75
|
+
|
|
76
|
+
Python ≥ 3.12 and [uv](https://docs.astral.sh/uv/).
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
uv sync --group dev
|
|
80
|
+
uv run ruff check . && uv run ruff format --check .
|
|
81
|
+
uv run mypy
|
|
82
|
+
uv run pytest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Enable the hygiene hook once per clone, so household data and store files cannot
|
|
86
|
+
be committed:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
git config core.hooksPath .githooks
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The same check runs in CI, so a bypassed hook still fails the build.
|
|
93
|
+
|
|
94
|
+
## What is never in this repository
|
|
95
|
+
|
|
96
|
+
Household data, real names, real documents, `.sqlite` files, `artifacts/` and
|
|
97
|
+
`cultures/`. CI runs with no model, no network and no secrets.
|
|
98
|
+
|
|
99
|
+
## Layout
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
src/context_motilis/ the runtime and its hosts
|
|
103
|
+
tests/invariants/ one property test per invariant of §3
|
|
104
|
+
tests/acceptance/ the §8 acceptance suite, by stable name
|
|
105
|
+
docs/brief-v7.md the controlling specification; every § points here
|
|
106
|
+
docs/adr/ decisions the brief does not settle, and why
|
|
107
|
+
docs/hosts.md pinned protocol revision, SDK, and tested client builds
|
|
108
|
+
docs/roadmap.md where the work stands against §7 and §10
|
|
109
|
+
fixtures/ generated fixtures only
|
|
110
|
+
tools/ repository tooling, not part of the package
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Licence
|
|
114
|
+
|
|
115
|
+
MIT. See [`LICENSE`](LICENSE), and [ADR 0001](docs/adr/0001-mit-licence.md) for
|
|
116
|
+
why. Reuse is meant to be straightforward, commercial reuse included.
|
|
117
|
+
|
|
118
|
+
## Two sentences
|
|
119
|
+
|
|
120
|
+
*Runtime:* A context is a durable boundary that can be suspended and
|
|
121
|
+
independently resumed. It retains evidence and history, may carry commitments
|
|
122
|
+
and deterministic behavior, and exposes a bounded brief to whichever human or
|
|
123
|
+
model attends to it next.
|
|
124
|
+
|
|
125
|
+
*Development principle:* What exactly must survive this kill? A feature that
|
|
126
|
+
cannot name what survives its kill is not added.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 0001. The project is licensed MIT
|
|
2
|
+
|
|
3
|
+
**Status:** accepted
|
|
4
|
+
**Date:** 2026-09-12
|
|
5
|
+
**Question:** [#1](https://github.com/cetatec/context-motilis/issues/1), labelled `question`
|
|
6
|
+
**Decided by:** the project owner, answering that issue
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The brief requires publishing `0.0.x` early, to PyPI by trusted publishing
|
|
11
|
+
(§1.1). It does not name a licence. A licence is not a question an agent may
|
|
12
|
+
settle on the owner's behalf, so `pyproject.toml` carried no `license` field
|
|
13
|
+
and the repository had no `LICENSE` file. The first release was blocked on it,
|
|
14
|
+
and nothing else was.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
MIT.
|
|
19
|
+
|
|
20
|
+
## Rationale
|
|
21
|
+
|
|
22
|
+
In the owner's words:
|
|
23
|
+
|
|
24
|
+
> Motilis uses MIT to favor straightforward reuse and broad adoption, including
|
|
25
|
+
> commercial reuse. We accept that others may build competing products. Our
|
|
26
|
+
> commercial experiments will focus on additional value, not on charging for
|
|
27
|
+
> permission already granted by the open-source license.
|
|
28
|
+
|
|
29
|
+
## Consequences
|
|
30
|
+
|
|
31
|
+
- Anyone may use, modify, redistribute and sell this software, including inside
|
|
32
|
+
a closed-source product and including in direct competition. This is accepted,
|
|
33
|
+
not a side effect to be mitigated later by relicensing.
|
|
34
|
+
- No patent grant is made or received. Apache-2.0 would have given one. This was
|
|
35
|
+
the trade taken in exchange for the simplest possible terms.
|
|
36
|
+
- Contributions arrive under the same terms, so no contributor licence agreement
|
|
37
|
+
is needed and none should be introduced without a superseding record.
|
|
38
|
+
- Any future commercial offering has to be worth paying for on its own, because
|
|
39
|
+
the licence already grants permission. Relicensing the existing code away from
|
|
40
|
+
MIT would not retract what is already granted.
|
|
41
|
+
- `LICENSE` is included in the built distributions, so the terms travel with the
|
|
42
|
+
package rather than living only in the repository.
|
|
43
|
+
|
|
44
|
+
## Implementation
|
|
45
|
+
|
|
46
|
+
`LICENSE` at the repository root; `license = "MIT"` and
|
|
47
|
+
`license-files = ["LICENSE"]` in `pyproject.toml`, as an SPDX expression rather
|
|
48
|
+
than a classifier. The copyright line names `cetatec`, matching the GitHub owner
|
|
49
|
+
and the package author. If a legal entity should hold the copyright instead,
|
|
50
|
+
that is an amendment to this record, not a new decision.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 0002. `as_of` is a position in the committed order
|
|
2
|
+
|
|
3
|
+
**Status:** accepted
|
|
4
|
+
**Date:** 2026-09-12
|
|
5
|
+
**Question:** [#8](https://github.com/cetatec/context-motilis/issues/8), labelled `question`
|
|
6
|
+
**Decided by:** the project owner, accepting the answer on that issue
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
§5.1 makes `(epoch, sequence)` the committed order and says timestamps are data,
|
|
11
|
+
never order. §3 `canonical_record` asks for a `canon/1` state-hash rebuild "at
|
|
12
|
+
`as_of`", and the brief does not say in so many words what `as_of` is made of.
|
|
13
|
+
Two readings were available: a position in that order, or a wall-clock instant.
|
|
14
|
+
|
|
15
|
+
The reading matters because `as_of` is the boundary a replay stops at and the
|
|
16
|
+
thing two state hashes must share before they can be compared at all.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
`as_of` is a position in the committed event order, `(epoch, sequence)`. Never a
|
|
21
|
+
time.
|
|
22
|
+
|
|
23
|
+
## Rationale
|
|
24
|
+
|
|
25
|
+
A timestamp cutoff does not identify one boundary. Events can share a timestamp,
|
|
26
|
+
and nothing in the brief makes event timestamps monotonic — §5.1 says the
|
|
27
|
+
opposite, that timestamps are data. So "every event at or before time T" can
|
|
28
|
+
name several different sets of events, and a hash taken at one of them is not
|
|
29
|
+
comparable with a hash taken at another. A position names exactly one prefix of
|
|
30
|
+
the record.
|
|
31
|
+
|
|
32
|
+
It also keeps one rule rather than two. If `as_of` were a time, the store would
|
|
33
|
+
order by `(epoch, sequence)` and cut by timestamp, and the two would disagree the
|
|
34
|
+
first time a clock moved.
|
|
35
|
+
|
|
36
|
+
## Consequences
|
|
37
|
+
|
|
38
|
+
- `store.events.Position` is the value `as_of` names, and `hashes.state_hashes`
|
|
39
|
+
serializes its `epoch` and `sequence` into the overall hash.
|
|
40
|
+
- An export manifest carries two different things: the `as_of` through which the
|
|
41
|
+
materialization was rebuilt, and the position of the event recording that the
|
|
42
|
+
export happened. They are not the same number and neither substitutes.
|
|
43
|
+
- State hashes are comparable only at the same `as_of`, which the brief already
|
|
44
|
+
required and this makes checkable.
|
|
45
|
+
|
|
46
|
+
No code changed: task 2 implemented this reading, and the decision records why
|
|
47
|
+
it was the only one available rather than a choice that happened to be made.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 0003. `follow_up_lag` follows its definition, not the demo figure
|
|
2
|
+
|
|
3
|
+
**Status:** accepted
|
|
4
|
+
**Date:** 2026-09-12
|
|
5
|
+
**Question:** [#15](https://github.com/cetatec/context-motilis/issues/15), labelled `question`
|
|
6
|
+
**Decided by:** the project owner, accepting the answer on that issue
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
§4.10 defines the first overdue follow-up twice, and the two do not agree.
|
|
11
|
+
|
|
12
|
+
The rule: "for an open commitment owed to us, at `D + follow_up_lag`, shifted to
|
|
13
|
+
`quiet_hours_end` if inside quiet hours".
|
|
14
|
+
|
|
15
|
+
The constant: "`follow_up_lag` = next `quiet_hours_end` after D (demo: Sat
|
|
16
|
+
09:00)".
|
|
17
|
+
|
|
18
|
+
With the default quiet hours — 21:00–08:00 Europe/Paris — the next
|
|
19
|
+
`quiet_hours_end` after a Friday 23:59:59 deadline is Saturday **08:00**, not
|
|
20
|
+
the 09:00 the parenthesis shows.
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
The definition governs. `follow_up_lag(D) = quiet_hours_end(D)`, and the demo's
|
|
25
|
+
"Sat 09:00" is a documentation error that should read 08:00.
|
|
26
|
+
|
|
27
|
+
The trailing "shifted to `quiet_hours_end` if inside quiet hours" is already
|
|
28
|
+
what the lag *is*, so it is not applied a second time. Repeats are different:
|
|
29
|
+
they are `previous + follow_up_cooldown` and only then shifted, because a
|
|
30
|
+
cooldown is not defined in terms of quiet hours.
|
|
31
|
+
|
|
32
|
+
## Rationale
|
|
33
|
+
|
|
34
|
+
A definition and an example that disagree are not two requirements; one of them
|
|
35
|
+
is wrong. The definition is the one written as a rule, it composes with the
|
|
36
|
+
quiet-hours constant the brief also states, and it is the one the algorithm can
|
|
37
|
+
be derived from. The parenthesis is an illustration, and an illustration that
|
|
38
|
+
contradicts its own defaults illustrates nothing.
|
|
39
|
+
|
|
40
|
+
Applying the shift twice would be worse than redundant. At exactly 08:00 the
|
|
41
|
+
boundary rule says the moment is not inside quiet hours, so a second application
|
|
42
|
+
is a no-op today — but a later change to that boundary would silently start
|
|
43
|
+
advancing every first follow-up by a day.
|
|
44
|
+
|
|
45
|
+
## Consequences
|
|
46
|
+
|
|
47
|
+
- A Friday 23:59:59 Paris deadline produces its first follow-up at Saturday
|
|
48
|
+
08:00 Paris, `2026-09-12T06:00:00Z`.
|
|
49
|
+
- `ACC-clock-cases` asserts that, and `scheduler/rules.follow_up_lag_surface` is
|
|
50
|
+
one line because of it.
|
|
51
|
+
- The brief's own parenthesis still says 09:00. Correcting the specification is
|
|
52
|
+
the owner's to make; this record is why the implementation does not match it.
|
context_motilis-0.0.1/docs/adr/0004-scheduling-inputs-are-read-when-the-occurrence-is-created.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 0004. Scheduling inputs are read when the occurrence is created
|
|
2
|
+
|
|
3
|
+
**Status:** accepted
|
|
4
|
+
**Date:** 2026-09-12
|
|
5
|
+
**Question:** [#16](https://github.com/cetatec/context-motilis/issues/16), labelled `question`
|
|
6
|
+
**Decided by:** the project owner, accepting the answer on that issue
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
§4.10 computes a pre-deadline reminder from the deadline `D` and the act `A`,
|
|
11
|
+
and `A` comes from the context's `next`. So stating a `next` after a date, or
|
|
12
|
+
changing its act later, changes what §4.10 would compute for that date.
|
|
13
|
+
|
|
14
|
+
§3 `occurrence` says the opposite thing about what may move: at most one logical
|
|
15
|
+
occurrence per effect key, "with its original scheduled time preserved". The key
|
|
16
|
+
carries the node and the deadline, not the act.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
The effective `due`, `next.act` and confirmed `next.remind_at` are read when the
|
|
21
|
+
logical occurrence is **first scheduled**. A later change to any of them does
|
|
22
|
+
not move an occurrence that already exists.
|
|
23
|
+
|
|
24
|
+
## Rationale
|
|
25
|
+
|
|
26
|
+
§3's sentence is the more specific of the two and the one written as an
|
|
27
|
+
invariant. §4.10 says how a surface time is computed; it does not say the
|
|
28
|
+
computation is repeated against an occurrence that has already been placed.
|
|
29
|
+
|
|
30
|
+
Putting the act in the effect key was the alternative and is worse in a way that
|
|
31
|
+
is easy to miss: an act edited after the reminder has already fired would mint a
|
|
32
|
+
second key, and for a deadline in the past that second occurrence surfaces
|
|
33
|
+
immediately as a catch-up. A metadata correction would ring a bell.
|
|
34
|
+
|
|
35
|
+
## Consequences
|
|
36
|
+
|
|
37
|
+
- `desired` recomputes from current state and so reports a time the table does
|
|
38
|
+
not hold, for any occurrence whose inputs changed after it was scheduled. The
|
|
39
|
+
disagreement is real and is recorded by
|
|
40
|
+
`test_an_act_stated_after_a_date_does_not_move_its_reminder`.
|
|
41
|
+
- **There is no way to move a scheduled reminder.** The original issue text
|
|
42
|
+
offered a confirmed `remind_at` as the owner's escape hatch. That was wrong:
|
|
43
|
+
the key does not carry `remind_at` either, so a later one is the same spent
|
|
44
|
+
key and reconciliation skips it. Pinned by
|
|
45
|
+
`test_a_later_confirmed_remind_at_does_not_move_it_either`. Moving an
|
|
46
|
+
occurrence that exists would need an explicit reschedule, which the brief does
|
|
47
|
+
not describe and which is not in stage 1.
|
|
48
|
+
- Urgency is the one input that reaches an occurrence after the fact, and it
|
|
49
|
+
does so by minting a different key rather than by re-timing one. See
|
|
50
|
+
[0005](0005-urgent-is-a-flag-on-the-two-occurrence-sources.md).
|
|
51
|
+
|
|
52
|
+
A follow-up worth taking when the model is next touched: `desired` could report
|
|
53
|
+
the stored time for an occurrence that exists and the computed time only for one
|
|
54
|
+
that does not, so it stops claiming a time reconciliation will never apply. That
|
|
55
|
+
is behaviour-neutral and was deliberately left out of the change that answered
|
|
56
|
+
this question, because it presumes the answer.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 0005. `urgent` is a flag on `commitment` and `date`
|
|
2
|
+
|
|
3
|
+
**Status:** accepted
|
|
4
|
+
**Date:** 2026-09-12
|
|
5
|
+
**Question:** [#18](https://github.com/cetatec/context-motilis/issues/18), labelled `question`
|
|
6
|
+
**Decided by:** the project owner, accepting the answer on that issue
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
§4.10 ends its precedence paragraph with one clause:
|
|
11
|
+
|
|
12
|
+
> `urgent` sets `surface = min(surface, now)`.
|
|
13
|
+
|
|
14
|
+
That is the only occurrence of the word in the brief. No §4.4 kind carries the
|
|
15
|
+
flag, no §6.1 operation sets it, no §5 table stores it, and §4.8's authority
|
|
16
|
+
table says nothing about who may declare something urgent. The rule could not be
|
|
17
|
+
implemented without first deciding where urgency lives, which §11.4 makes an
|
|
18
|
+
approval twice over: a field on a kind §4.4 enumerates, and a change to §4.10's
|
|
19
|
+
precedence.
|
|
20
|
+
|
|
21
|
+
## Decision
|
|
22
|
+
|
|
23
|
+
A boolean `urgent`, defaulting to `false`, on `commitment` and `date` — the two
|
|
24
|
+
kinds §4.10 names as occurrence sources. Effective only from an **accepted**
|
|
25
|
+
version. It clamps the **pre-deadline reminder** only, at the reconciliation
|
|
26
|
+
instant, and it is part of that occurrence's effect key.
|
|
27
|
+
|
|
28
|
+
## Rationale
|
|
29
|
+
|
|
30
|
+
**On the node, not the occurrence.** Urgency is a property of the thing, not of
|
|
31
|
+
the reminder about it. On an occurrence it would be unreachable from a mutation
|
|
32
|
+
set and would not survive the node being superseded.
|
|
33
|
+
|
|
34
|
+
**Not on `next`.** A `next` raises no occurrence of its own — §4.10 says "for a
|
|
35
|
+
commitment or date with due D and act A" — so an `urgent` there would have
|
|
36
|
+
nothing to act on.
|
|
37
|
+
|
|
38
|
+
**Accepted only**, the rule `remind_at` already follows (§4.5). A model that
|
|
39
|
+
could mark its own extraction urgent could make the runtime interrupt the owner
|
|
40
|
+
on the strength of an untrusted document, which is the shape §4.8 exists to
|
|
41
|
+
prevent. A fallback-derived node is not schedulable at all, so it never reaches
|
|
42
|
+
the question.
|
|
43
|
+
|
|
44
|
+
**Pre-deadline only.** An overdue follow-up exists *because* the deadline
|
|
45
|
+
passed, so "surface it sooner" has nothing to mean for it, and an urgent overdue
|
|
46
|
+
commitment would otherwise fire twice at once. §4.10 fixes both expiry times to
|
|
47
|
+
the expiry itself, and a thing does not lapse sooner for being urgent.
|
|
48
|
+
|
|
49
|
+
**In the effect key**, which is the part that took implementing to see.
|
|
50
|
+
Acceptance lands *after* the mutation carrying it — `apply_set` reconciles, then
|
|
51
|
+
`confirm` accepts and reconciles again — so by the moment urgency is effective
|
|
52
|
+
the ordinary reminder has already been scheduled and its key is spent. Without
|
|
53
|
+
urgency in the key the rule could never fire on the case it exists for: a thing
|
|
54
|
+
created urgent. With it, the base key drops out of `desired` as the urgent one
|
|
55
|
+
appears, so reconciliation cancels it in the same pass and one occurrence stands.
|
|
56
|
+
§3 is satisfied per key, each keeping its own original time.
|
|
57
|
+
|
|
58
|
+
**Only the time depends on the instant, never the key.** `tick.stale_reason`
|
|
59
|
+
asks whether an occurrence's key is still in `desired`, and asks without a
|
|
60
|
+
reconciliation instant. When urgency lived in both, every urgent occurrence was
|
|
61
|
+
cancelled as "no longer implied" moments before delivery — the tick destroying
|
|
62
|
+
the thing urgency is for. The key is a fact about state; `now` only clamps.
|
|
63
|
+
|
|
64
|
+
## Consequences
|
|
65
|
+
|
|
66
|
+
- Something created urgent surfaces at the instant its acceptance is
|
|
67
|
+
reconciled, and is delivered by the next tick like anything else.
|
|
68
|
+
- `min`, not "set to now": a reminder already due keeps its earlier time.
|
|
69
|
+
- Marking an existing commitment urgent cancels its ordinary reminder and
|
|
70
|
+
schedules an urgent one; the cancelled row still records when it was going to
|
|
71
|
+
surface.
|
|
72
|
+
- **Urgency cannot be taken back.** Un-marking it finds the base key spent and
|
|
73
|
+
schedules nothing. That is the right way round — the owner has already been
|
|
74
|
+
told — but it is a one-way door and is stated here rather than discovered.
|