Repository navigation
Conversation
…r and Client The ready-to-run Server and the primary Python Client did not enforce the network safety policy already applied by the Agent integrations, allowing two unsafe transports: - ServerSettings accepted a non-loopback bind (e.g. 0.0.0.0) while bearer authentication was disabled, silently exposing an unauthenticated Server. - ClientSettings / PowerContextClient accepted a non-loopback http:// URL and would send a bearer token over plaintext. Introduce a single shared transport-policy module (powercontext.transport) and consume it everywhere: - ClientSettings rejects non-loopback plaintext http:// Server URLs. - PowerContextClient refuses to send a bearer token over an unencrypted non-loopback connection. - ServerSettings rejects an unauthenticated non-loopback bind unless the operator opts in via POWERCONTEXT_SERVER_ALLOW_UNAUTHENTICATED_NON_LOOPBACK (for TLS-terminated / controlled-network deployments). - The `server run` CLI applies the same guard, since model_copy bypasses the settings validator on --host overrides. Add a cross-surface contract test suite and update the EN/ZH configuration reference. Closes oceanbase#1319
# Conflicts: # docs/en/docs/reference/configuration.md # docs/zh/docs/reference/configuration.md # src/powercontext/server/settings.py
Load the Codex plugin's settings module by path and assert its private loopback host set and _http_base_url behavior match the shared transport contract, so the isolated plugin copy cannot silently drift. Tighten the configuration docs to note the plaintext-URL rule applies to configured Server URLs.
The loopback check only matched the three named hosts 127.0.0.1, ::1 and localhost, so plaintext HTTP to any other 127.0.0.0/8 address (e.g. 127.0.0.2) was wrongly rejected as non-loopback. Detect loopback via ipaddress.ip_address(...).is_loopback, which covers the whole IPv4 loopback block, and route the Client CLI through the shared helper. Both agent plugins ship isolated and cannot import powercontext, so each vendors its own copy of the check; parametrised drift-guard tests pin both copies to the shared contract. Surface ServerSettings validation errors as a typer.BadParameter instead of a raw traceback.
…ports The bearer-token-over-plaintext guard is only meaningful for the transport the client opens itself, where base_url's scheme reflects what crosses the wire. A caller-supplied http_client owns its own transport (ASGI in-process, a Unix socket, a TLS-terminating proxy -- all carrying an http:// label), so the scheme is no longer a reliable signal and enforcing it only produced false positives, breaking the authenticated in-memory e2e flow. Enforce the guard only when the client creates the transport.
The Docker image binds POWERCONTEXT_SERVER_HTTP_HOST=0.0.0.0 so the port is reachable from the harness container, but the unauthenticated-non-loopback guard now refuses that bind and the Server fails to start. The Compose network is isolated, so opt in via POWERCONTEXT_SERVER_ALLOW_UNAUTHENTICATED_NON_LOOPBACK to restore startup.
review - client: gate the plaintext-token guard on an explicit trust_transport_security opt-in; supplying an http_client is no longer treated as evidence the transport is safe, so a bearer token is still refused over unencrypted non-loopback HTTP. - docker: declare POWERCONTEXT_SERVER_ALLOW_UNAUTHENTICATED_NON_LOOPBACK in the image itself so the documented `docker run` starts out of the box, document the network-exposure trade-off, and drop the masking env from the e2e compose so it exercises the real image contract; add a Dockerfile smoke test. - server cli: merge --host/--port before validation so a safe override can repair an unsafe environment bind, and translate the actionable settings failures (unauthenticated non-loopback bind, missing bearer token) into friendly CLI errors recognised by exception identity rather than raw validation text.
- cli: unpack the ServerSettings kwargs so the partial ``http`` mapping is not type-checked against the full HttpConfig annotation (ty invalid-argument-type). - tests: pin a wide COLUMNS in the friendly-error tests so the rich panel does not hard-wrap the asserted --host / env-var tokens on a narrow CI terminal.
…ntract Pi's isLoopback only accepted localhost, 127.0.0.1 and [::1], so its production resolveConfig rejected http://127.0.0.2:8000 as "must use HTTPS outside loopback" while the shared policy and the Python plugins already trust the whole 127.0.0.0/8 block. Rewrite it to mirror is_loopback_host (strip brackets, lowercase, accept 127.0.0.0/8, localhost and ::1). Add a Pi Vitest drift guard and a JSON fixture of loopback/non-loopback host vectors that both the Python drift guard and the Pi suite consume, so a future divergence from the shared contract fails in at least one language.
…error assertions pretty-format-json rewrites the fixture with one array element per line; apply that formatting so the quality hook passes in CI. The friendly-error CLI tests asserted tokens (--host, the opt-in env var) against typer's rich error panel. Under GitHub Actions typer forces force_terminal=True and the runner's TERM=dumb pins rich to 80 columns while ignoring COLUMNS, so the panel hyphen-breaks --host and force-splits the long env var. Replace the COLUMNS override with a fixture that neutralises the forced terminal and sets an explicit wide width, making the assertions independent of the terminal environment.
…hes for the transport The loopback guard only tripped when a bearer token was present, so an unauthenticated client would still ship Memory content -- carried in the request body -- over unencrypted non-loopback HTTP. Broaden the guard to reject *any* plaintext non-loopback request, and let a caller that knows its transport is secure opt in explicitly with `trust_transport_security=True`; supplying an `http_client` is not itself evidence of safety. Thread that opt-in through the LangGraph adapter's `shared_http_client` / `open_client`: a plain pooling client stays untrusted, while in-process ASGI harnesses declare trust. Update the in-process test harnesses accordingly. Also stop the remote-access guides from documenting a bare `server run --host 0.0.0.0`, which the bind policy now rejects; show the authenticated bind and the explicit unauthenticated opt-in instead, and add a command-level contract test so a copy-pasteable command that fails to start fails in CI rather than only for a reader.
Resolve tests/e2e/test_runtime_server.py: upstream dropped the explicit handoff-report project/workstream setup in the delegation-loop e2e; keep that refactor and re-apply this branch's `trust_transport_security=True` on the in-process ASGI client.
The hardened client guard now refuses plaintext non-loopback URLs unconditionally, which broke the e2e harness where agent containers reach the server over the private Compose bridge via host-gateway. Add an explicit POWERCONTEXT_BUB_TRUST_TRANSPORT_SECURITY opt-in (default false) that routes through the client's endorsed bypass: a caller-supplied httpx transport plus trust_transport_security=True. Enable it only in the e2e Compose harness, mirror the server-side ALLOW_UNAUTHENTICATED_NON_LOOPBACK opt-in shape, document the switch, and cover both the refusal default and the trusted construction.
…t-policy # Conflicts: # e2e/bub/uv.lock # integrations/bub/pyproject.toml # integrations/bub/src/powercontext_bub/plugin.py
…t policy The LangChain middleware and pydantic-ai tests merged from master were written before the client started refusing all plaintext non-loopback URLs, so their in-process ASGI transports now hit the guard. Port the langgraph-style trust plumbing to the LangChain shared HTTP client and let the affected tests vouch for their ASGI transports with trust_transport_security=True.
The plugin hooks honoured POWERCONTEXT_BUB_TRUST_TRANSPORT_SECURITY, but the powercontext.search/remember/context tools still constructed bare clients, so the acceptance harness failed on the plaintext host-gateway hop. Hoist the vouched construction into a module-level open_client helper, carry the flag in the per-turn tool settings, and cover both tool paths in the trust tests.
…k requests The reference still described the pre-hardening guard, which only refused to attach a bearer token. Align both language variants with the shipped behaviour: every request over unencrypted non-loopback HTTP is refused, and callers whose http:// base URL merely labels a secure transport must supply their own http_client with an explicit trust_transport_security=True.
ea1688e to
fc1521a
Compare
fc1521a to
3c0e6fb
Compare
…e-export-backup-restore
…e-export-backup-restore
…e-export-backup-restore # Conflicts: # docs/en/docs/operate/meta.json # docs/en/docs/operate/troubleshoot.md # docs/zh/docs/operate/meta.json # docs/zh/docs/operate/troubleshoot.md # src/powercontext/builtin/persistence/artifacts.py # src/powercontext/builtin/runtime/relational.py
| "artifact_head": _RecordSpec( | ||
| ARTIFACT_HEADS_TABLE, | ||
| ("scope_id", "family", "artifact_id"), | ||
| ("revision", "lifecycle_state", "replacement_artifact_id", "governance_generation"), |
There was a problem hiding this comment.
[P1] 导出 artifact_head 时保留 merged_into_id。该字段虽已在 pc_artifact_heads 中持久化,并被修订路径用作冻结 merge 输入的判断(artifacts.py:336),但此处的 payload 未包含它。恢复时 _row_values() 因而写入默认 NULL:已合并输入会从“deprecated 且冻结”变成普通 deprecated Artifact,之后可以被修订;同一 revision 但合并拓扑不同的目标也会因 record digest 未覆盖该字段而被误判为一致。请让 bundle 的 schema、校验和冲突比较完整保留该治理字段,并覆盖 merge -> export -> restore 后输入仍不可修订的回归。
There was a problem hiding this comment.
Independently reproduced this on fd56c05 using the public Atomic Memory API and a real SQLite database: create two memories, merge them, export the scope, and restore it into a clean target. An input memory changes from state=merged with a non-null merged_into_id to state=forgotten with merged_into_id=None, while restore reports status=ready. Please preserve this authoritative field in the bundle encoding, dependency validation, and conflict comparison so the restored merge topology and restoration behavior remain intact.
hidb4ai
left a comment
There was a problem hiding this comment.
Two correctness issues remain: the merge relationship loss discussed in the existing P1 thread, and the Candidate proposal validation gap below. Both were reproduced on SQLite at this commit.
| "reason", | ||
| nullable=("target_family", "target_artifact_id", "reason"), | ||
| ) | ||
| bytes_values("proposal", "source_refs", "artifact_refs") |
There was a problem hiding this comment.
[P2] Validate Candidate proposals with the target proposal decoder before writing
This only checks that proposal is encoded bytes; the target content validation handles artifact_revision records and skips candidate_version. A bundle with valid record and aggregate checksums, an experience Candidate proposal of {}, and a matching pending head passes validate with compatible=True and restores with status=ready. Reading that Candidate then raises InvalidStoredPayloadError because the proposal does not match ExperienceContent; listing an inbox page containing it also cannot decode the record. Please validate each Candidate proposal against the target family proposal contract before any restore writes, and reject unsupported or malformed proposals instead of committing records the supported read path cannot use.
Closes #1421
Rationale for this change
PowerContext needs a backend-neutral logical archive for disaster recovery, offline transfer, customer-controlled backup, and SQLite/OceanBase migration without losing immutable history, lineage, review decisions, or Handoff evidence. Database-native backup remains a separate operational recovery mechanism.
What changes are included in this PR?
.pcbcontract with deterministic manifests, producer/schema versions, per-record SHA-256 digests, an aggregate checksum, deterministic ZIP output, and optional compression.Are there any user-facing changes?
Adds
powercontext archive export,powercontext archive inspect, andpowercontext archive restore. Restore writes require--yes;--dry-runperforms target compatibility and conflict checks without domain writes. This introduces portable bundle format version 1 and thepc_portable_restore_receiptstable. No existing API or bundle format is changed.How was this change tested?
make check: lock consistency, pre-commit hooks, type checks, and integration manifest checks passed.POWERCONTEXT_TEST_OCEANBASE_URL.AI usage statement
AI assistance was used for implementation, tests, documentation, and review. The changes were reviewed and validated locally.