Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b83121d
refactor: remove dead code and harden review-revision signing
Aug 14, 2026
32b405b
refactor: unify content-sensitivity detection into app.sensitivity
Aug 14, 2026
f011486
refactor: unify review-signal predicates and thresholds
Aug 14, 2026
6741639
refactor: unify cosine, line helpers, and sqlite connection
Aug 14, 2026
630f2a1
refactor: extract admin context and commit helpers in service.py
Aug 14, 2026
d7b8a3c
refactor: extract daemon process management into process.py
Aug 14, 2026
a61ce75
refactor: move chat side-effect machinery out of the store facade
Aug 14, 2026
e8369a1
feat: Windows PowerShell installer and runtime compatibility
Aug 14, 2026
588ebb5
refactor: fix knowledge agent async glue and consolidate schema ensures
Aug 14, 2026
a5b56be
refactor: remove dead code and unify diverged secret-ref helper
Aug 14, 2026
497eb6b
refactor(ui): drop dead components and share data-loading hooks
Aug 14, 2026
e42cac8
refactor(deploy): extract legacy volume migration into standalone cut…
Aug 14, 2026
d5395f5
refactor(memory): invert store submodule dependencies and slim the fa…
Aug 14, 2026
42c343e
refactor: simplify memory runtime and centralize usage
Aug 15, 2026
0abe6cf
refactor(ui): remove dead clients and share provider helpers
Aug 15, 2026
8d2983e
refactor(memory): split internals and preserve durable turn idempotency
Aug 15, 2026
0052446
refactor(model): simplify gateway internals and harden test isolation
Aug 15, 2026
271e2fb
refactor(deploy): align setup installers and backup tooling
Aug 15, 2026
1e0b1f4
fix: harden finalize and model routing invariants
Aug 15, 2026
0290971
refactor(memory): keep optional analysis off hot paths
Aug 15, 2026
2ffbd28
refactor(model): unify upstream execution and control plane
Aug 15, 2026
a957684
refactor(memory): compose repositories and isolate evaluation
Aug 15, 2026
ced0ce6
refactor(deploy): converge installer state machines
Aug 15, 2026
f6ae357
refactor(platform): isolate shared contracts and runtimes
Aug 15, 2026
ebef096
test: expand gateway boundary coverage
Aug 16, 2026
826b76d
fix(deploy): brace installer variables adjacent to CJK punctuation
Aug 16, 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
67 changes: 52 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,45 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Install both services with dev extras
- name: Install contracts and both services with dev extras
run: >-
python -m pip install
-c constraints.txt
-e packages/model-gateway-contracts
-e 'services/memory-gateway[dev]'
-e 'services/model-gateway[dev]'

- name: Check Python dependency consistency
run: python -m pip check

- name: Check installer syntax
- name: Check POSIX installer syntax
if: matrix.python-version == '3.12'
shell: pwsh
run: sh -n deploy/install.sh

- name: Test Memory Gateway
working-directory: services/memory-gateway
run: python -m pytest -ra

- name: Test Model Gateway
working-directory: services/model-gateway
run: python -m pytest -ra

windows-installer:
name: Windows installer dual-engine contract
runs-on: windows-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Set up Python 3.12
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Install installer test dependency
run: python -m pip install -c constraints.txt pytest

- name: PowerShell 5.1 parser, parity, Windows installer and journal corpus
shell: powershell
run: |
$tokens = $null
$errors = $null
Expand All @@ -44,20 +70,31 @@ jobs:
$errors | ForEach-Object { Write-Error $_ }
exit 1
}
sh -n deploy/install.sh
python -m pytest -q `
services/memory-gateway/tests/test_installer_parity.py `
services/memory-gateway/tests/test_windows_docker_install_script.py
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& ./services/memory-gateway/tests/windows_installer_journal.ps1

- name: Exercise Windows cutover journal crash recovery
if: matrix.python-version == '3.12'
- name: PowerShell 7 parser, parity, Windows installer and journal corpus
shell: pwsh
run: '& ./services/memory-gateway/tests/windows_installer_journal.ps1'

- name: Test Memory Gateway
working-directory: services/memory-gateway
run: python -m pytest -ra

- name: Test Model Gateway
working-directory: services/model-gateway
run: python -m pytest -ra
run: |
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
"$PWD/deploy/install.ps1",
[ref]$tokens,
[ref]$errors
) | Out-Null
if ($errors.Count -gt 0) {
$errors | ForEach-Object { Write-Error $_ }
exit 1
}
python -m pytest -q `
services/memory-gateway/tests/test_installer_parity.py `
services/memory-gateway/tests/test_windows_docker_install_script.py
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& ./services/memory-gateway/tests/windows_installer_journal.ps1

ui-build:
name: UI tests and production build
Expand Down
23 changes: 18 additions & 5 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:
echo "HOST_UID=$(id -u)" >> "$GITHUB_ENV"
echo "HOST_GID=$(id -g)" >> "$GITHUB_ENV"

# Installers no longer validate the Compose topology contract on every
# install; the release compose is gated here instead.
# Installers validate public/internal rendered topology with the same
# candidate init image before cutover; CI also gates the shipped compose.
- name: Validate release Compose isolation contract
run: |
export MEMORY_PLATFORM_INIT_IMAGE="ghcr.io/sparkhello/memory-platform-init@sha256:$(printf 'a%.0s' $(seq 64))"
Expand Down Expand Up @@ -60,14 +60,26 @@ jobs:
run: |
docker compose -p memory-platform-ci -f deploy/docker-compose.yml \
exec -T memory-gateway sh -ec \
'test "$(id -u)" = 10001; test ! -e /secrets/secrets.env; test ! -e /model-data'
'test "$(id -u)" = 10001; test ! -e /secrets/secrets.env; test ! -e /model-data; test -f /opt/venv/.pip-check-ok'
docker compose -p memory-platform-ci -f deploy/docker-compose.yml \
exec -T model-gateway sh -ec \
'test "$(id -u)" = 10002; test ! -e /secrets/settings.env; test ! -e /memory-data'
'test "$(id -u)" = 10002; test ! -e /secrets/settings.env; test ! -e /memory-data; test -f /opt/venv/.pip-check-ok'
test -z "$(docker port memory-platform-ci-model-gateway-1 2030/tcp)"
test "$(stat -c %a "$MEMORY_CREDENTIAL_DIR/gateway.txt")" = 600
test "$(stat -c %a "$MEMORY_CREDENTIAL_DIR/admin.txt")" = 600

- name: Verify runtime package and import boundaries
run: |
docker compose -p memory-platform-ci -f deploy/docker-compose.yml \
exec -T memory-gateway python -c \
'import importlib.metadata as m,importlib.util as u; names={d.metadata["Name"].lower() for d in m.distributions()}; assert {"memory-gateway","model-gateway-contracts","mcp","pypdf"} <= names; assert "local-model-gateway" not in names; assert u.find_spec("app") is not None; assert u.find_spec("model_gateway_contracts") is not None; assert u.find_spec("model_gateway") is None'
docker compose -p memory-platform-ci -f deploy/docker-compose.yml \
exec -T model-gateway python -c \
'import importlib.metadata as m,importlib.util as u; names={d.metadata["Name"].lower() for d in m.distributions()}; assert {"local-model-gateway","model-gateway-contracts"} <= names; assert {"memory-gateway","mcp","pypdf","pydantic-settings"}.isdisjoint(names); assert u.find_spec("model_gateway") is not None; assert u.find_spec("model_gateway_contracts") is not None; assert u.find_spec("app") is None'
docker compose -p memory-platform-ci -f deploy/docker-compose.yml \
run --rm --no-deps --entrypoint python stack-init -c \
'import importlib.metadata as m,importlib.util as u,runpy; from pathlib import Path; names={d.metadata["Name"].lower() for d in m.distributions()}; assert {"memory-gateway","local-model-gateway","model-gateway-contracts"} <= names; assert u.find_spec("app") is not None; assert u.find_spec("model_gateway") is not None; assert u.find_spec("model_gateway_contracts") is not None; assert Path("/opt/venv/.pip-check-ok").is_file(); root="/usr/local/libexec/memory-platform"; [runpy.run_path(f"{root}/{name}",run_name="_image_smoke") for name in ("init_stack.py","migrate_legacy.py","backup_legacy.py","restore_split.py","validate_compose.py","plan_install.py","verify_backup.py")]'

- name: Verify split network topology
run: |
docker compose -p memory-platform-ci -f deploy/docker-compose.yml \
Expand Down Expand Up @@ -120,10 +132,11 @@ jobs:
with:
python-version: "3.12"

- name: Install both services with dev extras
- name: Install contracts and both services with dev extras
run: >-
python -m pip install
-c constraints.txt
-e packages/model-gateway-contracts
-e 'services/memory-gateway[dev]'
-e 'services/model-gateway[dev]'

Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

- `services/memory-gateway`:长期记忆、知识库、MCP、OpenAI-compatible 代理和 Web Console。
- `services/model-gateway`:模型连接、deployment、route、pricing、usage 和管理接口。
- `packages/model-gateway-contracts`:只含纯配置/schema、稳定 route、归因 Header 和错误枚举;不得依赖 HTTP、CLI、settings 或任一服务启动代码。
- `scripts/bootstrap.sh`:创建统一开发环境并安装两个服务。
- `scripts/memgw`:根目录统一运行栈入口。
- `scripts/test.sh`:两个后端测试集和前端构建。
- `Dockerfile` 与 `deploy/`:单容器一体化镜像、首启接线入口脚本、compose 文件和 `deploy/install.sh` Docker 一键安装脚本;`.github/workflows/docker.yml` 在 tag 时推送 GHCR。
- `Dockerfile` 与 `deploy/`:Memory、Model、离线 init/maintenance 三套隔离运行镜像、首启接线入口脚本、compose 文件和 `deploy/install.sh` Docker 一键安装脚本;长期镜像使用各自独立 Python 环境,只共享窄协议包。`.github/workflows/docker.yml` 在 tag 时推送 GHCR。旧单卷(legacy)布局的一次性迁移已从安装器拆出为独立工具 `deploy/legacy_cutover.py`(容器内仍复用 `backup_legacy.py` / `migrate_legacy.py`),安装器检测到 legacy 布局时只报错并指向该工具。`install.sh` 与 `install.ps1` 是同一安装器的双实现:改动必须两边同步,默认版本号等共享常量由 `services/memory-gateway/tests/test_installer_parity.py` 钉住

## 开发边界

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## Unreleased

### Changed

- 旧单卷(legacy all-in-one)布局迁移从 Docker 一键安装器拆分为独立一次性工具 `deploy/legacy_cutover.py`;`install.sh` / `install.ps1` 检测到 legacy 布局时 fail-closed 并指向该工具,split 布局的 journal 状态机语义不变。删除未使用的 `stack-maintenance` Dockerfile target 与开发 compose 中的对应 service(发布路径 `docker-compose.user.yml` 的 maintenance 服务继续复用 init 镜像)。

## 0.5.1 - 2026-08-13

### Fixed
Expand Down
94 changes: 67 additions & 27 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,89 @@ RUN npm ci --ignore-scripts --no-audit --no-fund
COPY services/memory-gateway/ui/ ./
RUN npm run build

FROM python:3.12.13-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 AS python-build
FROM python:3.12.13-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 AS python-wheelhouse
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1 \
PYTHONDONTWRITEBYTECODE=1
WORKDIR /build

# Every third-party runtime/build artifact is hash-locked. Project packages
# are then built as ordinary wheels; release containers never use editable
# installs or import the repository checkout as their primary code path.
# Download the union dependency graph once, with hashes enforced, into an
# offline wheelhouse. The three runtime environments below resolve only their
# own project wheels from this trusted artifact set; they do not clone a shared
# union venv into every image.
COPY requirements-runtime.lock ./requirements-runtime.lock
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install --require-hashes -r requirements-runtime.lock
RUN mkdir -p /wheelhouse \
&& python -m pip download \
--require-hashes --only-binary=:all: \
--dest /wheelhouse \
-r requirements-runtime.lock

COPY packages/model-gateway-contracts/pyproject.toml ./model-gateway-contracts/pyproject.toml
COPY packages/model-gateway-contracts/model_gateway_contracts ./model-gateway-contracts/model_gateway_contracts
COPY services/memory-gateway/pyproject.toml services/memory-gateway/README.md ./memory-gateway/
COPY services/memory-gateway/app ./memory-gateway/app
COPY services/model-gateway/pyproject.toml services/model-gateway/README.md ./model-gateway/
COPY services/model-gateway/model_gateway ./model-gateway/model_gateway
RUN mkdir -p /wheels \
&& /opt/venv/bin/pip wheel \
RUN python -m venv /opt/build-venv \
&& /opt/build-venv/bin/pip install \
--no-index --find-links=/wheelhouse \
setuptools==83.0.0 wheel==0.46.3 \
&& mkdir -p /wheels \
&& /opt/build-venv/bin/pip wheel \
--no-deps --no-build-isolation --wheel-dir /wheels \
./memory-gateway ./model-gateway \
&& /opt/venv/bin/pip install --no-deps /wheels/*.whl \
./model-gateway-contracts ./memory-gateway ./model-gateway \
&& cp /wheels/*.whl /wheelhouse/

# Each target starts from a fresh venv. pip resolves against only the
# hash-verified offline wheelhouse, so Model cannot accidentally inherit MCP,
# pypdf, or other Memory-only distributions.
FROM python-wheelhouse AS memory-python-build
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install \
--no-index --find-links=/wheelhouse \
/wheelhouse/memory_gateway-*.whl \
/wheelhouse/model_gateway_contracts-*.whl \
&& /opt/venv/bin/pip check \
&& touch /opt/venv/.pip-check-ok \
&& /opt/venv/bin/pip uninstall -y pip setuptools wheel

FROM python-wheelhouse AS model-python-build
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install \
--no-index --find-links=/wheelhouse \
/wheelhouse/local_model_gateway-*.whl \
/wheelhouse/model_gateway_contracts-*.whl \
&& /opt/venv/bin/pip check \
&& touch /opt/venv/.pip-check-ok \
&& /opt/venv/bin/pip uninstall -y pip setuptools wheel

FROM python-wheelhouse AS init-python-build
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install \
--no-index --find-links=/wheelhouse \
/wheelhouse/memory_gateway-*.whl \
/wheelhouse/local_model_gateway-*.whl \
/wheelhouse/model_gateway_contracts-*.whl \
&& /opt/venv/bin/pip check \
&& touch /opt/venv/.pip-check-ok \
&& /opt/venv/bin/pip uninstall -y pip setuptools wheel

FROM python:3.12.13-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 AS runtime-common
FROM python:3.12.13-slim-bookworm@sha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2 AS runtime-base
ENV PATH=/opt/venv/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY --from=python-build /opt/venv /opt/venv

# Keep a validated CLI project root without making it an editable install.
# `memgw` uses this path for portable backup/doctor operations in one-shot
# maintenance containers; the installed wheel remains the runtime import.
COPY services/memory-gateway/pyproject.toml /app/services/memory-gateway/pyproject.toml
COPY services/memory-gateway/app /app/services/memory-gateway/app

FROM runtime-common AS memory-runtime
FROM runtime-base AS memory-runtime
ENV MEMGW_HOME=/data/config \
MEMGW_SETTINGS_PATH=/secrets/settings.env \
MEMGW_PROJECT_ROOT=/app/services/memory-gateway \
UI_DIST_DIR=/app/ui/dist
COPY --from=memory-python-build /opt/venv /opt/venv
# Keep a validated CLI project root without making it an editable install.
# The installed wheel remains the runtime import; Model source is absent.
COPY services/memory-gateway/pyproject.toml /app/services/memory-gateway/pyproject.toml
COPY services/memory-gateway/app /app/services/memory-gateway/app
COPY --from=ui-build /build/ui/dist /app/ui/dist
COPY deploy/memory-entrypoint.sh /usr/local/bin/memory-gateway-entrypoint
RUN groupadd --gid 10001 memory-gateway \
Expand All @@ -64,9 +104,10 @@ VOLUME ["/data", "/secrets"]
EXPOSE 2026
ENTRYPOINT ["memory-gateway-entrypoint"]

FROM runtime-common AS model-runtime
FROM runtime-base AS model-runtime
ENV MODEL_GATEWAY_HOME=/data \
MODEL_GATEWAY_SECRETS_PATH=/secrets/secrets.env
COPY --from=model-python-build /opt/venv /opt/venv
COPY deploy/model-entrypoint.sh /usr/local/bin/model-gateway-entrypoint
RUN groupadd --gid 10002 model-gateway \
&& useradd --uid 10002 --gid 10002 --no-create-home \
Expand All @@ -82,21 +123,20 @@ ENTRYPOINT ["model-gateway-entrypoint"]
# One-shot root image. It is run with networking disabled, initializes or
# migrates only explicitly mounted volumes, drops credentials into a host bind
# as 0600 files, and exits before either long-lived service starts.
FROM runtime-common AS stack-init
FROM runtime-base AS stack-init
ENV MEMGW_HOME=/memory-data/config \
MEMGW_SETTINGS_PATH=/memory-secrets/settings.env \
MEMGW_PROJECT_ROOT=/app/services/memory-gateway \
MODEL_GATEWAY_HOME=/model-data \
MODEL_GATEWAY_SECRETS_PATH=/model-secrets/secrets.env
COPY --from=init-python-build /opt/venv /opt/venv
COPY services/memory-gateway/pyproject.toml /app/services/memory-gateway/pyproject.toml
COPY services/memory-gateway/app /app/services/memory-gateway/app
COPY deploy/init_stack.py /usr/local/libexec/memory-platform/init_stack.py
COPY deploy/migrate_legacy.py /usr/local/libexec/memory-platform/migrate_legacy.py
COPY deploy/backup_legacy.py /usr/local/libexec/memory-platform/backup_legacy.py
COPY deploy/restore_split.py /usr/local/libexec/memory-platform/restore_split.py
COPY deploy/validate_compose.py /usr/local/libexec/memory-platform/validate_compose.py
COPY deploy/plan_install.py /usr/local/libexec/memory-platform/plan_install.py
COPY deploy/verify_backup.py /usr/local/libexec/memory-platform/verify_backup.py
ENTRYPOINT ["python", "/usr/local/libexec/memory-platform/init_stack.py"]

# Maintenance deliberately has no default secret mounts. Compose grants only
# the paths required by an explicitly requested backup/restore operation.
FROM runtime-common AS stack-maintenance
ENV MEMGW_PROJECT_ROOT=/app/services/memory-gateway
ENTRYPOINT ["memgw"]
Loading
Loading