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
75 changes: 75 additions & 0 deletions .github/workflows/recovery-safety.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Recovery safety
on:
push:
branches: [main, fix/issues-68-70-recovery-safety]
pull_request:
permissions:
contents: read
concurrency:
group: recovery-safety-${{ github.ref }}
cancel-in-progress: true
jobs:
recovery:
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: msgtest
POSTGRES_PASSWORD: msgtest
POSTGRES_DB: postgres
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U msgtest -d postgres"
--health-interval 5s --health-timeout 5s --health-retries 10
valkey:
image: valkey/valkey:9.0.6
ports: ['6379:6379']
options: >-
--health-cmd "valkey-cli ping"
--health-interval 5s --health-timeout 5s --health-retries 10
env:
MSG_TEST_POSTGRES_URL_TEMPLATE: postgresql://msgtest:msgtest@127.0.0.1:5432/{database}
MSG_TEST_VALKEY_URL: redis://127.0.0.1:6379/0
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-python@v7
with:
python-version: '3.15'
allow-prereleases: true
cache: pip
- name: Install real cryptographic and database tools
run: |
sudo apt-get update
sudo apt-get install -y age postgresql-client
python -m pip install -e '.[dev]'
- name: Compile and collect the complete suite
run: |
python -m compileall -q src
mkdir -p artifacts
python -m pytest --collect-only -q tests >artifacts/recovery-collection.txt 2>&1 || { cat artifacts/recovery-collection.txt; exit 1; }
tail -n 3 artifacts/recovery-collection.txt
- name: Verify recovery against real PostgreSQL and age
run: |
python -m pytest -q \
tests/test_recovery_safety_units.py \
tests/test_custody_exit_evidence.py \
tests/test_custodial_upgrade.py \
tests/test_custodial_historical_migration.py \
tests/test_custodial_inventory_ack.py \
tests/test_custodial_rewrap.py \
tests/test_root_rotation.py \
tests/test_root_rotation_resume.py \
tests/test_root_storage_layout.py \
tests/test_authority_snapshot_drift.py \
tests/test_backup_drill.py \
tests/test_backup_daemon.py \
--junitxml=artifacts/recovery-safety.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: recovery-safety-verification
path: artifacts/
55 changes: 55 additions & 0 deletions docs/ARCHITECTURE_REUSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Architecture reuse

The authoritative design approves fewer independent mechanisms, not fewer
visible capabilities. Keep published operation versions, short codes, signed
bytes, IDs and historical receipts compatible. Existing completed-contract
regressions remain binding.

## Implemented slice: order notifications

`market/delivery_targets.py` owns shared account-email lookup, address validation,
mail enablement and authenticated pickup-link construction. Legacy checkout and
v3 keep their endpoint encoding and message formats; sharing a helper does not
reinterpret their signed contracts. Endpoint generation, owner, current key and
notification opt-in are still revalidated before sending. Control characters,
URL credentials, query/fragment secrets and malformed origins fail closed.

A Delivery says what was provided to which buyer. A notification EffectJob says
whether SMTP work was queued, accepted, failed or uncertain. Neither proves buyer
acceptance or permits settlement. V3 order reads derive notification state from
the current endpoint's job and result, without editing order rows or trusting a
stale cached success. Missing jobs cannot prove SMTP delivery. Failed jobs stay
failed; disabled endpoints and uncertain attempts are not reactivated by reads.

The shared effect completion/retry/lease fence writes job facts, not market
presentation fields. Its attempt, state, deadline and current-authority checks
remain unchanged. The notification read path checks the linked job's order,
endpoint, kind and principal before displaying it. Seller views still exclude
buyer email addresses.

## Remaining convergence

ServerOffer should become a Listing compatibility view, Purchase an Order,
and subject/order/bounty accounts use one protected ledger implementation.
Retain typed account purposes, server entitlement authority, atomic accounting
and explicit migrations. This slice does not implement those model migrations.

ConsignmentPackage is immutable content; Delivery is a recipient-bound fact.
Recovery and delivery envelopes can share codecs, not authority or secret-release
policies. Notes and similar content share Resource/Revision, while membership,
certificates, grants and money keep their controlled operations and constraints.

OperationSpec is the source of operation metadata; routes/help derive from it.
Specific business functions use the existing executor transaction. They do not
need another Engine/Manager/Repository hierarchy. Worker scheduling is shared;
channel executors and secret/network permissions remain isolated.

New default explicit acceptance requires a new operation or policy version.
Never change orders.buy@1/@2/@3 in place. Keep automatic-delivery/settlement
regressions and distinguish their policy settlement from signed buyer claims.
Money held before fulfillment may be refunded from escrow; already-settled root
funds still require the local root command. Same request and payload replay the
original non-secret result, not another payout or another one-time secret.

Refs #85, #71, #72 and #81. No production deployment or financial operation is
part of this refactor.
61 changes: 61 additions & 0 deletions docs/AUTHORIZATION_SOURCES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Current authorization sources (issue #76)

Every adapter uses OperationExecutor and the current Authorizer. A signature,
resource ID, cursor, historical revision or role name is never a grant by itself.
Credential ceilings, entry restrictions, live security-parent traversal and
special type/state gates apply before an alternative source can authorize access.

| Source | Live conditions | Losing the source |
|---|---|---|
| Owner/mode | Current owner, mode, active security-parent chain | Owner/mode/state change takes effect immediately |
| Organization/group | Active organization and matching active membership; `public` remains virtual | Leave, removal, archived/missing organization or mismatched restored row denies that source |
| Topic governance | Current membership policy; bans on every ancestor topic constrain writes | A nested topic cannot escape a parent ban |
| Certificate | Current issuer chain, grants, authority sources, scope and credential ceiling | Revocation, expiry, source loss or narrower scope is rechecked |
| ShareGrant@2 | Ordinary shareable resource; current owner at root, active user/group, exact read operation and empty constraints | Expiry/revocation/owner change removes this source, not an independent one |
| Reshare | Same resource, fixed source chain, each parent currently allows reshare; child expiry no later than current parent | Cycles, missing parents, shortened expiry, unknown operations/constraints fail closed |
| ShareLink | Disabled by default; live owner, source credential and resource checks | Token is not public access; expiry/revocation/owner or credential changes deny it |

The first supported ShareGrant@2 contract remains **read only, empty constraints**.
Unknown constraints and future operation names are rejected, not silently ignored.
This is not a general delegation evaluator. Containers, controlled/system facts,
DMs, SOUL/AGENTS personal roots, Todo and preview resources are not shareable.
Grant creation, source-chain validation and shared/link reads use one boundary
predicate. Sharing a child does not open its parent or another attachment.

Ordinary read/write capabilities replace only their named checks. A group or
Topic role never implies CA, system, root, purge, chown or credential-management
rights. Topic bans constrain writes even when an outer source permits them;
independent read sources remain independent. Losing a group role does not remove
an otherwise active membership. Owner transfer and leaving are separate facts.

## Regression evidence

`tests/test_authorization_matrix.py` uses one nine-source table: owner, mode,
inherited traversal, organization member/maintainer/owner, scoped certificate,
user ShareGrant and group ShareGrant. Each source is established and removed by
real operations (with one explicit current-parent relocation fixture). It checks
current content, history and pinned revisions through HTTP, PathGET, GraphQL and
MCP, plus the actual CLI parser/dispatcher with its real signed client. It also
checks Search, Sync, Inbox and Outbox after revocation. Reused adapters may cache
transport descriptions, never positive resource authorization.

`tests/test_authorization_sources.py` covers archived organizations with stale
membership rows, independent source preservation, restored unknown operations
and constraints, contracted parent expiry, newly protected previews and ancestor
topic bans. `tests/test_authorization_recovery_boundaries.py` proves that a shared
post does not open an attachment; concurrent reads cannot retain access after a
completed revocation; ShareLinks work only through supported body transports;
and a real pg_dump/restore preserves denial of content/history/old revisions and
filters notifications. Restored workers remain disabled in a recovery drill.

These 20 new cases passed locally with real PostgreSQL 14.24, Git and signatures
on Python 3.13.5. That evidence does not replace the final commit's Python 3.15,
PostgreSQL 16, Valkey, full core suite, conformance and build CI.

The new matrix complements `test_share_grants*`, `test_share_links`,
`test_organization_governance`, `test_topic_governance`, `test_authorization`,
`test_linkset_diff` and `test_search_source_relation_filters`, including explicit
role boundaries, organization policy changes, Topic virtual-event privacy and
old search cursor revalidation. Reads do not repair stale facts or append
business events, jobs, audit records or revisions. No production data, Root
credentials, deployment configuration or signed CA ceilings are changed.
14 changes: 13 additions & 1 deletion docs/DESIGN_GAP_REPORT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# 项目设计差距报告

## 当前工作树进度与验收边界
## #73–75:版本化市场实现(2026-09-28)

订单快照、自动交付、独立 checkout 邮箱验证和确定性仲裁已实现;接口、最终性、默认无仲裁员、旧 `@1` 兼容及运维边界集中见 [MARKET_CONTRACTS.md](MARKET_CONTRACTS.md)。下方 `135a190` 及更早测试数字属于历史提交,不能用于本批验收。

| Issue | 实现与测试入口 | 保留的部署边界 |
| --- | --- | --- |
| #73 | `market/orders.py`、`escrow.py`;不可变签名快照、显式状态表、单一资金释放、原子分账;`test_market_lifecycle.py` | 不改已签名 `orders.buy@1` 的含义;生产库迁移单独审查 |
| #74 | `market/delivery.py`、`targets.py`、`email.py`;managed 自动结算但不 claimed,sealed/service 显式验收,真实 Transfer 和买家 endpoint 复核;`test_market_delivery.py` | SMTP 只发最小通知;本地 fake sender 不代表真实 SMTP 验收 |
| #75 | `market/policy.py`、`arbitration.py`;固定候选/epoch/panel/quorum、一次申诉、私有证据、有效签名 Decision 由 escrow 原子消费;`test_market_arbitration.py` | 默认候选为空;撤权/缺 quorum 持款,不临时挑人或让 AI 自由裁量 |

`market_lifecycle` 隔离 selftest 覆盖 20 MSG 注资、10 MSG PoP 奖励、5 MSG 固定 bundle 自动交易及签名仲裁退款;只读 doctor 检查市场不变量。`test_market_recovery.py` 验证真实备份恢复、CAS 证据及恢复演练副作用隔离。验证结果以本批 PR 的具体 head 和 CI 为准,不能把这些模块扩写为整个项目或生产交易已验收。

## 历史:135a190 时点的工作树进度与验收边界

本次重新读取[ChatGPT权威设计](https://docs.google.com/document/d/1EM5Qr5qdg6tAFi2wvY0EBm6zxMj6DTBMc_dybU5qkz0/edit),当前191个非空段;[已完成部分](https://docs.google.com/document/d/1FtTdF5uhBPAsi-so-jOfpsiVI19RWKgx6bzIEFvpR2E/edit)当前71个非空段,含A19/A20等归档条款。历史段数和旧CI仅作沿革;归档不取消未迁出需求及回归要求。

Expand Down
14 changes: 13 additions & 1 deletion docs/IMPLEMENTATION_STATUS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# 实现范围与需求差异

## 当前工作树进度与验收边界
## #73–75:版本化市场实现(2026-09-28)

订单快照、自动交付、独立 checkout 邮箱验证和确定性仲裁已实现;接口、最终性、默认无仲裁员、旧 `@1` 兼容及运维边界集中见 [MARKET_CONTRACTS.md](MARKET_CONTRACTS.md)。下方 `135a190` 及更早测试数字属于历史提交,不能用于本批验收。

| Issue | 实现与测试入口 | 保留的部署边界 |
| --- | --- | --- |
| #73 | `market/orders.py`、`escrow.py`;不可变签名快照、显式状态表、单一资金释放、原子分账;`test_market_lifecycle.py` | 不改已签名 `orders.buy@1` 的含义;生产库迁移单独审查 |
| #74 | `market/delivery.py`、`targets.py`、`email.py`;managed 自动结算但不 claimed,sealed/service 显式验收,真实 Transfer 和买家 endpoint 复核;`test_market_delivery.py` | SMTP 只发最小通知;本地 fake sender 不代表真实 SMTP 验收 |
| #75 | `market/policy.py`、`arbitration.py`;固定候选/epoch/panel/quorum、一次申诉、私有证据、有效签名 Decision 由 escrow 原子消费;`test_market_arbitration.py` | 默认候选为空;撤权/缺 quorum 持款,不临时挑人或让 AI 自由裁量 |

`market_lifecycle` 隔离 selftest 覆盖 20 MSG 注资、10 MSG PoP 奖励、5 MSG 固定 bundle 自动交易及签名仲裁退款;只读 doctor 检查市场不变量。`test_market_recovery.py` 验证真实备份恢复、CAS 证据及恢复演练副作用隔离。验证结果以本批 PR 的具体 head 和 CI 为准,不能把这些模块扩写为整个项目或生产交易已验收。

## 历史:135a190 时点的工作树进度与验收边界

本次实时读取[ChatGPT权威设计](https://docs.google.com/document/d/1EM5Qr5qdg6tAFi2wvY0EBm6zxMj6DTBMc_dybU5qkz0/edit),修订 `ANLCKQmHD9c-ju8OTbJ2QIem9CjzQKIpNwVNIxKrhZ47O54VGNYCNnqIaJ8czWILEXFuaBxzxZ8_A9oocGySQwmmVmTnXAubcqo05Xpa6o8`;[已完成部分](https://docs.google.com/document/d/1FtTdF5uhBPAsi-so-jOfpsiVI19RWKgx6bzIEFvpR2E/edit)修订`ANLCKQmjW4TG7EcyxL6DyAXC86ij36Ex01x5DzJ1NjrlYcJOn4GJcC_12tos9Q12O-Vv5NXH6SBjID-dAdVA13vnqhISDbYzDf_1QJXUKE4`。归档文件明确:首次迁出由e126539/CI36306888836支撑,后续条款按各自提交和CI;已迁入条款的完成范围不延伸到整章。

Expand Down
Loading
Loading