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
32 changes: 20 additions & 12 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,37 @@

## 本轮基线与完成事项

- 已核对 16 个开放 issue(#64、#65、#68、#69、#70、#72、#76–#85)与唯一开放 PR #186。
- 已核对 16 个开放 issue(#64、#65、#68、#69、#70、#72、#76–#85);
#186、#187、#188 已审查并按 expected head 依次合并。
- 已读取当前权威设计及有效归档;现有功能和历史协议保持原义。
- #186 已审查并合并为 `dfb89fe989eaef66c99f14ae6a953b07204b7170`。
主线 tree `e1d65b624266527bef5f66d2843fa1a40c4ddc5f` 与实际通过测试的 tree 相同。
- [核心验收 36632579472](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36632579472):
八份 ZIP 摘要、manifest 和 JUnit 真实 node-ID 已核验,2541 core + 8 conformance,
- 当前基线 main `c8ed2610ab774dba3085972683c1d2c8a9232222`,
tree `89f8e4119e625cb421b75be468392b2d7b187930` 与 #188 实际验收树一致。
- [核心验收 36644965337](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36644965337):
八份 ZIP 摘要、manifest 和 JUnit 真实 node-ID 已核验,2569 core + 8 conformance,
无失败、错误、跳过、重复或遗漏。
- [质量 36632578929](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36632578929):
Ruff JSON 零诊断、stderr 为空、585 文件格式、Python 3.15.0rc2 编译通过。
- [部署演练 36632578807](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36632578807):
- [质量 36644965239](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36644965239):
Ruff JSON 零诊断、stderr 为空、588 文件格式、Python 3.15.0rc2 编译通过。
- [部署演练 36644965142](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36644965142):
同一 wheel 的锁定 server/client 安装、真实启动/重启、四传输、只读 doctor、
禁外发 worker、真实 OpenSSH/Git 撤权及正式隔离 selftest 42 项均通过。
wheel/sdist 与核心构建产物逐字节一致;精确摘要见 PR #186。
wheel/sdist 与核心构建产物逐字节一致;全部十份 ZIP、摘要与测量精确证据见
[PR #188](https://github.com/TokenNotIncluded/msg.lmm.best/pull/188#issuecomment-5906443148)。
合并后独立主线部署/质量/恢复专项已 success,发布四文件与已验 #188 逐字节一致;
主线 Rewrite 完整门槛仍排队,不能提前算通过。

## 当前推进

- 增加安装包的有界并发写入/读取测量、真实 SIGKILL 重启与全表/sequence 不变量检查。
- 增加安装包的真实 PostgreSQL/Git/CAS 备份恢复计时和全表值比较;只允许恢复流程
- 已完成安装包的有界并发写入/读取测量、真实 SIGKILL 重启与全表/sequence 不变量检查。
- 已完成安装包的真实 PostgreSQL/Git/CAS 备份恢复计时和全表值比较;只允许恢复流程
明确规定的 runtime_config/quarantine 变化,验证删 marker 后仍隔离业务与幂等重放。
- 首次新演练 [36643217304](https://github.com/TokenNotIncluded/msg.lmm.best/actions/runs/36643217304)
在真实恢复时失败:私有 Git 打包引用后,ZIP 丢失空 refs 目录,Git 无法识别仓库。
本轮修复私有/用户仓库统一结构目录重建,保持只读 proof 校验不修改磁盘,
并新增 packed-ref 恢复、只读拒绝和符号链接拒绝回归。新最终 tree 仍须重新完整验收。
并新增 packed-ref 恢复、只读拒绝和符号链接拒绝回归;#187 修复后的完整验收已通过。
- #188 新增真实 COMMIT 故障回滚、主体奖励限额/版本拒绝,以及 R1–R5 各轮
nonce/question/单轮 TTL/答案、跨主体与并发授勋回归,均已在完整门槛执行。
- 当前补核对 #72:六种公开搜索/真实 CLI/历史签署内容状态、CLI PoP 真实重启重试;
`docs/BOUNTY_ACCEPTANCE.md` 给出原清单逐断言映射,新回归须云端实际通过后收口。
- 逐条核对 issue 中尚未刷新到当前源码的描述,保留原验收范围和历史证据。

## issue 交接
Expand Down
53 changes: 53 additions & 0 deletions docs/BOUNTY_ACCEPTANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Bounty / issue #72 acceptance

Scope: the prefunded PoP market contract and its isolated official fixture.
The source base is main `c8ed2610ab774dba3085972683c1d2c8a9232222`.
CI provenance belongs to the exact tested tree; final results are recorded in
the PR and issue, rather than inferred from test names or this document.

## Requirement to assertion map

All node IDs below are relative to `tests/`. Parameterized nodes must all run.
The tests use actual PostgreSQL and the registered executor unless a row
explicitly describes a CLI parsing or pure-policy test.

| Requirement | Test node or installed check | Actual assertion |
| --- | --- | --- |
| Integer, protected prefunding; publisher can be offline | `test_bounty.py::test_prefunded_bounty_pays_offline_publisher_and_stops_at_budget` | BountyEscrow is a LedgerAccount, has no Subject or credential; one funding leg, one payout, exact recipient/escrow balances and total supply; no publisher call needed to claim |
| Current catalog and immutable historical terms | `test_bounty.py::test_bounty_catalog_current_projection_and_historical_revision` | Underfunded, top-up, paid, closed projections; historical budget/state and terms Revision remain original while current fields report live values |
| Search and real CLI across all lifecycle states | `test_bounty_public_lifecycle.py::test_search_and_real_cli_preserve_terms_and_current_accounting` | Six transitions: underfunded/top-up/publisher pause/claims exhausted/inclusive expiry/close. Both public search APIs locate the exact Revision; real CLI get/store get agree on state, reason, budget, escrow and claims; historical signed Revision and content bytes are unchanged; all public table values and effect entrypoints prove reads cause no writes |
| Search semantics | Same six-transition test | Search returns Resource metadata/references, not accounting balances. Resource publication state is distinct from Bounty payout state; dereferencing through bounty.get/store.listing_get yields the current accounting projection. Existing published schemas are unchanged |
| Pause/resume/top-up/close conservation | `test_market_72.py::test_pause_preserves_budget_topup_does_not_resume_and_close_returns_remainder` | Publisher pause survives top-up; pending claim denied while paused, succeeds after resume; close refunds exact remainder; balances sum to supply |
| Claim cap cannot reopen through top-up | `test_market_72.py::test_exhausted_claim_limit_is_consistent_in_both_views_and_topup_cannot_reopen` | Current views remain claims_exhausted with positive escrow; resume denied; claimant sees own claim and outsider sees none |
| Expiry does not write during GET | `test_market_72.py::test_expired_listing_projection_does_not_mutate_state_or_emit_events` | Both views report expired; stored state and Event count remain unchanged; six-transition test additionally checks exact deadline and every table value |
| Current key/revocation and subject binding | `test_bounty.py::test_bad_proof_wrong_subject_expiry_and_top_up_do_not_spend`; `test_market_contracts.py::test_revoked_or_replaced_identity_key_cannot_spend_pending_challenge` | Wrong proof key/foreign subject/expired nonce denied; old revoked key and replacement non-primary key denied; no payout or consumed nonce |
| Canonical nonce fields and verifier version | `test_market_72.py::test_stored_challenge_fields_must_match_canonical_signed_payload`; `test_bounty_commit_boundary.py::test_unsupported_challenge_verifier_cannot_consume_budget_or_nonce` | Tampered stored nonce or unsupported version cannot spend; full row values unchanged in the version case |
| Eligibility and per-subject limit | `test_bounty.py::test_bad_proof_wrong_subject_expiry_and_top_up_do_not_spend`; `test_bounty.py::test_unknown_eligibility_constraint_fails_closed`; `test_bounty_commit_boundary.py::test_subject_limit_rejects_fresh_challenge_while_budget_remains` | Allowlist excludes other subject; unknown constraint rejected before funding; fresh valid challenge denied at subject cap even with remaining budget |
| Last reward concurrency and close race | `test_bounty.py::test_concurrent_last_reward_only_one_paid_claim`; `test_market_contracts.py::test_claim_close_race_and_event_failure_never_lose_or_duplicate_budget` | Exactly one payout at last reward; close always succeeds and escrow ends zero; all remaining funds belong to publisher/claimant, no lost supply |
| Claim/ledger/receipt/Event/Inbox/results transaction | `test_bounty_commit_boundary.py::test_deferred_claim_commit_failure_preserves_all_rows_and_retry`; event-failure node above | Real deferred PostgreSQL trigger fails at COMMIT after handler preparation; every public row value rolls back, then same request succeeds exactly once and replay changes no rows; injected Event failure also rolls back payout/nonce/minimal notice |
| CLI proof retries and actual restart | `test_bounty_public_lifecycle.py::test_real_cli_prove_replays_after_restart_without_second_nonce_or_reward` | Real signer/Registry/executor/PostgreSQL through CLI; new Application returns identical payout/receipt; one challenge/claim, every row value unchanged on retry |
| Mixed mutable/immutable terms fail closed | All nodes in `test_bounty_snapshot_integrity.py` | Reward/cap/subject cap/eligibility mismatch rejects claim and reads; doctor does not repair; management cannot move ledger funds |
| Real backup and persistent restore quarantine | `test_market_72.py::test_backup_restores_claim_nonce_inbox_receipt_and_replay_together` | Real backup/empty PostgreSQL restore retains exact claim, nonce, result, minimal Inbox and ledger rows; saved-result replay stays writes_paused, including after marker deletion; private reads remain quarantined |
| Official mint/fund entrypoints | `test_market_71.py::test_bank_fund_is_a_formal_command_without_unattended_flags`; `test_market_71.py::test_bank_fund_two_console_approvals_before_pin_or_writes`; installed `market_e2e` | Real daemon parser and MoneyAdmin through isolated TestRoot/TestConsoleIO; mint20 and bank fund20 require explicit approvals. Refusal precedes PIN/writes; no unattended flag |
| Official 20→10→5 fixture | `msg.admin.market_check::check_market_e2e`, run by installed official selftest | Bank prefunds10; current buyer IdentityKey PoP earns10; fixed text `msg.lmm.best store selftest` and hello.txt `delivery-ok\n` cost5; V4 Delivery waits for buyer's signed exact digest, then buyer5/bank15/all escrow0/supply20 |
| Published V3 automatic settlement preserved | `test_market_lifecycle.py::test_instant_delivery_settlement_and_claim_are_distinct`; `test_market_cli_bindings.py::test_explicit_buy_version_preserves_old_inputs_and_settlement_semantics` | Original published policies remain automatic; settled does not manufacture claimed or accepted. Explicit old versions 1/2/3 retain signed input and behavior |
| Current default V4 explicit acceptance | All nodes in `test_market_explicit_acceptance.py` | CLI selects V4; current buyer and exact delivered digest required; wrong/revoked signatures cannot settle or replay; one protected posting; restart preserves delivered escrow |
| Disabled/failed/unverified mail does not gate trade | `test_market_integration.py::test_explicitly_disabled_mail_never_calls_sender`; `test_market_delivery.py::test_verified_checkout_mail_is_minimal_and_not_claimed`; `test_market_delivery.py::test_unverified_checkout_has_no_order_information_until_owner_verifies`; installed `market_e2e` | Disabled sender never called; failed sender leaves settled business fact intact; unverified receives only minimal verification; verified notice has order reference/name snapshot/authenticated pickup, no product secret; local sink only |
| Send-time endpoint/current authorization | `test_market_delivery.py::test_send_time_revalidation_prevents_stale_or_cross_subject_delivery` | Disabled, unverified and foreign-subject endpoint variants cannot leak order facts |
| Bank has no special business privileges | `test_market_redemption.py::test_clearing_policy_is_pure_and_has_no_bank_or_role_exceptions`; `test_market_71.py::test_bank_fund_role_and_payment_rollback_together`; `test_market_cli_bindings.py::test_package_shortcut_binds_actual_id_and_preserves_seller_privacy` | Pure policy without bank/role override; failed bank funding preserves role/payment; another subject cannot inspect private package |
| Default/doctor/bootstrap/selftest guard | `test_market_contracts.py::test_default_doctor_is_read_only_and_selftest_cannot_seed_an_installed_server`; `test_market_recovery.py::test_market_doctor_cannot_mutate_or_synthesize_a_repair`; installed selftest | Default publishes no fake budget; doctor retains all table values; production selftest refuses seeding an installed server; official selftest is isolated |

## Version and field boundary

The original issue's automatic-settlement wording belongs to published V3.
The authoritative current design and default V4 require explicit signed buyer
acceptance. Both are tested; reverting V4 to automatic settlement would alter
the current contract. Delivery, claimed, accepted and settled are separate facts.

PoP proves possession of the current IdentityKey for one subject/challenge.
It does not prove a human, trustworthiness or resistance to Sybil identities.
The bank remains an ordinary Subject for catalog, verification and permissions.

This code acceptance does not authorize production mint/funding, Root/PIN,
SMTP/Webhook delivery or deployment. Real target-host/old-snapshot/current-pin
and cutover evidence remain separately tracked in #64–70 and #84.
126 changes: 126 additions & 0 deletions tests/test_bounty_public_lifecycle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Public discovery locates immutable terms; market reads report live accounting."""

from datetime import timedelta
from types import SimpleNamespace

import pytest
from read_only_evidence import business_snapshot, readonly_evidence
from test_market_72 import fixture, proof
from test_market_cli_bindings import ExecutorClient, command
from test_service import NOW, call

from msg.application import Application
from msg.core.codec import canonical, wire


@pytest.mark.asyncio
@pytest.mark.parametrize(
'transition,state,reason,budget,balance,claims',
[
('underfunded', 'paused', 'out_of_budget', 5, 5, 0),
('top_up', 'active', None, 20, 20, 0),
('pause', 'paused', 'publisher_paused', 20, 20, 0),
('exhausted', 'paused', 'claims_exhausted', 20, 10, 1),
('expire', 'paused', 'expired', 20, 20, 0),
('close', 'closed', None, 20, 0, 0),
],
)
async def test_search_and_real_cli_preserve_terms_and_current_accounting(
installed, monkeypatch, transition, state, reason, budget, balance, claims
):
app, root = installed
changes = {'budget_minor': 5} if transition in {'underfunded', 'top_up'} else {}
if transition == 'expire':
changes['expires_at'] = wire(NOW + timedelta(seconds=1))
(key, bank), identity, created = await fixture(app, root, **changes)
lid = created.data['bounty']['listing_id']
ref = created.resources[0]
original = dict(created.data['bounty'])
async with app.metadata.transaction(write=False) as tx:
revision_before = wire(await tx.revision(ref))
content_before = await app.contents.read_bytes((await tx.revision(ref)).content)
publisher = ExecutorClient(app, key, bank)
if transition == 'top_up':
result = await command(
publisher,
'bounty',
'top-up',
canonical({'listing_id': lid, 'amount_minor': 15}).decode(),
)
assert result.status == 'ok', wire(result)
elif transition in {'pause', 'close'}:
result = await command(publisher, 'bounty', transition, lid)
assert result.status == 'ok', wire(result)
elif transition == 'exhausted':
claimant_key, claimant = identity
result = await call(
app,
'bounty.claim',
await proof(app, identity, lid),
key=claimant_key,
subject=claimant,
)
assert result.status == 'ok', wire(result)
elif transition == 'expire':
app.clock = lambda: NOW + timedelta(seconds=1)
await app.load()

anonymous = ExecutorClient(app, None, None)
async with readonly_evidence(app, monkeypatch):
for operation, parameters in (
('discovery.search', {'query': 'pop', 'type': 'listing'}),
('discovery.lexical_search', {'scope': '/store', 'terms': 'pop', 'field': 'name'}),
):
found = await call(app, operation, parameters)
assert found.status == 'ok', wire(found)
assert [item['id'] for item in found.data['items']] == [lid]
# Resource.state describes publication, not Bounty accounting.
# Search returns a reference, never fabricated budget/award facts.
assert found.data['items'][0]['revision'] == ref.revision
assert 'budget_minor' not in found.data['items'][0]
bounty = await command(anonymous, 'bounty', 'get', lid)
catalog = await command(anonymous, 'store', 'get', lid)
assert bounty.status == catalog.status == 'ok', (wire(bounty), wire(catalog))
for shown in (bounty.data['bounty'], catalog.data['listing']):
assert (shown['state'], shown['pause_reason']) == (state, reason)
assert shown['budget_minor'] == budget
assert shown['escrow_balance_minor'] == balance
assert shown['paid_claims'] == claims
history = await call(app, 'store.listing_get', {'id': lid, 'revision': ref.revision})
assert history.status == 'ok', wire(history)
shown = history.data['listing']
assert shown['state'] == original['state']
assert shown['budget_minor'] == original['budget_minor']
assert shown['current_state'] == state and shown['current_budget_minor'] == budget
assert shown['terms_revision'] == ref.revision
async with app.metadata.transaction(write=False) as tx:
revision = await tx.revision(ref)
assert wire(revision) == revision_before
assert await app.contents.read_bytes(revision.content) == content_before


@pytest.mark.asyncio
async def test_real_cli_prove_replays_after_restart_without_second_nonce_or_reward(installed):
app, root = installed
_, (key, claimant), created = await fixture(app, root)
lid = created.data['bounty']['listing_id']
client = ExecutorClient(app, key, claimant)
client.state = SimpleNamespace(signer=key, subject=claimant)
argv = ('bounty', 'prove', lid, '--request-id', 'cli-pop-restart')
paid = await command(client, *argv)
assert paid.status == 'ok' and not paid.replayed, wire(paid)
before = await business_snapshot(app)
restarted = Application(app.settings, clock=lambda: NOW)
await restarted.load()
try:
client = ExecutorClient(restarted, key, claimant)
client.state = SimpleNamespace(signer=key, subject=claimant)
replay = await command(client, *argv)
assert replay.status == 'ok' and replay.replayed, wire(replay)
assert replay.data == paid.data and replay.receipt == paid.receipt
assert await business_snapshot(restarted) == before
async with restarted.metadata.transaction(write=False) as tx:
assert tx.one('SELECT COUNT(*) FROM bounty_challenges')[0] == 1
assert tx.one('SELECT COUNT(*) FROM bounty_claims')[0] == 1
finally:
await restarted.close()
Loading