Compare commits

..
Author SHA1 Message Date
aiman 2fcab6f583 feat(personal-weixin): add durable foundation runtime 2026-07-16 09:37:32 +08:00
21 changed files with 380 additions and 467 deletions
@@ -1,397 +0,0 @@
# Personal-Weixin B3/B4 Owner Evidence Authorization
```yaml
authorization_id: AUTH-CODEX-V2R1-PERSONAL-WEIXIN-B3-B4-EVIDENCE-20260716
owner_approved: true
executor: Codex isolated runner
foundation_implementation_commit: 2fcab6f5833a405a9a26683811600b6e5f1aa7b1
foundation_result_commit: 64a71b6199a8e2bd609ba145349e67b874888505
r10_lineage_commit: 74e603c0c943501110577dbe3c7f6a0b18b8ec7d
evidence_gate_commit: 62a4067cf7a462ebe7cf29524238bc5d0a70b813
upstream_audit_commit: b2f5f531019a70801444987dbb97dc1f69cf47de
production_baseline: V1R4C6
production_changed: false
release_status: paused
force_push_authorized: false
```
## 1. 授权结论
Owner 明确授权 Codex:
1. 完成 B3 owner evidence packet;
2. 在 vendor `client_id → authoritative message_id` 证据缺失时,采用 B4 quote-authority-disabled amendment;
3. B3、B4 均 PASS 后,按已有 exact allowlist 继续 B5–B7;
4. B3–B7 全部 PASS 后才能开始 Release Preflight;
5. Release Preflight PASS 后才允许 Canary;
6. Codex 不得直接修改生产。
该授权不允许 Codex编造 raw-wire、credential、vendor 或 reviewer 证据。
## 2. B3 owner evidence 必须包含
B3 evidence manifest 必须引用上述五个权威提交,并包含以下五类证据。
### A. Raw-wire evidence
至少包含:
```yaml
raw_wire:
- direction: inbound
- direction: outbound
```
每项必须记录:
```yaml
restricted_path:
restricted_sha256:
restricted_bytes:
redacted_path:
redacted_sha256:
captured_at:
credential_label: credential-a | credential-b
```
要求:
* unrestricted raw-wire 只能保存在 owner 批准的受限存储;
* Git 中只能提交 SHA-256、大小、受限路径引用及 redacted view;
* 不得提交 token、API key、OAuth code、sessionKey、cookie 或完整 credential;
* inbound 与 outbound 必须各至少一份;
-两份 credential 都必须实际覆盖。
### B. Authority map
必须提供 commit-safe authority map,明确:
```yaml
vendor_message_id_field:
client_id_field:
client_id_authoritative: false
mapping_verified: true
```
Authority map 必须区分:
```yaml
authoritative:
- verified inbound vendor message_id
non_authoritative:
- local client_id
- outbound correlation ID
- local sequence
- timestamp
- content hash
- outbound receipt ID
```
禁止把任一非权威值提升为 message authority。
### C. 双 credential 证据
必须有精确两项:
```yaml
credentials:
- label: credential-a
fingerprint_sha256:
secret_included: false
scope:
test_result: PASS
- label: credential-b
fingerprint_sha256:
secret_included: false
scope:
test_result: PASS
```
要求:
* 两个 fingerprint 必须不同;
* 不得提交 secret 原文;
* 两个 credential 必须各自通过实际测试;
* credential scope 必须明确。
### D. Mode evidence
必须明确处于下列模式之一:
```yaml
mode:
name: sandbox | shadow | restricted-capture
production_traffic: false
writes_production: false
capture_only: true
```
不得使用生产流量或写生产数据来补证据。
### E. Reviewer evidence
必须由独立 reviewer 签署:
```yaml
review:
reviewer:
independent_from_executor: true
decision: PASS
reviewed_manifest_sha256:
reviewed_at:
```
Reviewer 必须核对:
* raw-wire 哈希;
* redacted view;
* authority map;
-两个 credential fingerprint;
* mode;
* manifest 完整 SHA-256。
## 3. B3 PASS 条件
只有以下全部成立才允许 B3 `status: PASS`:
```yaml
raw_wire_inbound: PASS
raw_wire_outbound: PASS
dual_credential: PASS
authority_map: PASS
client_id_authoritative: false
mode_non_production: PASS
reviewer_independent: PASS
reviewer_decision: PASS
secrets_committed: false
production_changed: false
baseline_after: V1R4C6
release_status: paused
```
真实证据缺失时必须继续:
```yaml
status: BLOCKED_EVIDENCE_REQUIRED
```
不得生成模拟证据代替 owner/vendor 证据。
## 4. B4 Owner Amendment
Owner 明确批准以下 B4 amendment:
```json
{
"schemaVersion": 1,
"contract": "personal-weixin-b4-outbound-quote-authority-amendment",
"ownerApproved": true,
"lineage": {
"foundationImplementationCommit": "2fcab6f5833a405a9a26683811600b6e5f1aa7b1",
"foundationResultCommit": "64a71b6199a8e2bd609ba145349e67b874888505",
"r10LineageCommit": "74e603c0c943501110577dbe3c7f6a0b18b8ec7d",
"evidenceGateCommit": "62a4067cf7a462ebe7cf29524238bc5d0a70b813",
"upstreamAuditCommit": "b2f5f531019a70801444987dbb97dc1f69cf47de"
},
"vendorClientIdMessageIdEvidence": {
"status": "absent",
"clientIdPromotable": false
},
"authorityPolicy": {
"outboundQuoteAuthority": "disabled",
"clientIdRole": "non-authoritative-correlation-only",
"authoritativeMessageIdSources": [
"validated-inbound-raw-wire.vendor_message_id"
],
"quotedSendWithoutAuthoritativeMessageId": "fail-closed",
"plainOutboundWithoutQuoteAuthority": "allowed-only-by-existing-b4-allowlist",
"syntheticMapping": false,
"timestampFallback": false,
"localSequenceFallback": false,
"contentHashFallback": false,
"outboundReceiptFallback": false
},
"implementationConstraints": {
"scopeExpansion": false,
"exactAllowlistRequired": true,
"missingAllowlistStatus": "BLOCKED_SCOPE",
"productionChangeAuthorized": false,
"canaryAuthorized": false,
"tagAuthorized": false,
"forcePushAuthorized": false
},
"production": {
"baseline": "V1R4C6",
"changed": false,
"releaseStatus": "paused"
}
}
```
该 JSON 必须作为独立 control-plane amendment 提交。
## 5. B4 强制语义
B4 实现必须遵守:
```yaml
client_id:
role: correlation-only
authoritative: false
vendor_message_id:
authoritative_only_when:
- observed in validated inbound raw-wire
- present in committed authority map
quoted_send:
without_authoritative_vendor_message_id: fail-closed
plain_outbound:
allowed_only_when:
- existing B4 exact allowlist permits it
- no quote/reply authority field is emitted
```
禁止:
* `client_id` → `message_id` alias;
* client ID hash fallback;
* timestamp fallback;
* local sequence fallback;
* content hash fallback;
* outbound receipt fallback;
* synthetic message ID;
-“先发送后补 authority”。
## 6. B4 路径授权
该 amendment 不增加任何实现路径。
Codex必须先找到已有 B4 exact allowlist。
若不存在或不完整:
```yaml
status: BLOCKED_SCOPE
```
不得由 Codex自行发明路径。
## 7. B5–B7 条件
只有 B3 和 B4 均有 qualifying PASS RESULT 后,才可继续 B5–B7。
每个阶段必须:
* 独立 branch;
* 独立 worktree;
* 已提交 exact allowlist;
* 一个非 merge implementation commit;
* 独立 RESULT;
* 依赖实际 F0 commit 与 immutable image digest;
* 不修改生产;
* 不 force push。
任一阶段缺少 exact allowlist:
```yaml
status: BLOCKED_SCOPE
```
## 8. Release Preflight
只有以下全部 PASS 后才能开始:
```yaml
F0: PASS
B3: PASS
B4: PASS
B5: PASS
B6: PASS
B7: PASS
R10_lineage: PASS
immutable_image_digest: present
gitleaks: PASS
rollback_contract: present
```
Preflight 不得创建 tag 或部署生产。
## 9. Canary
仅在 Release Preflight PASS 后授权一次 Canary。
Canary 必须:
* 使用固定 immutable image digest;
* 使用已验证 rollback target;
* 不修改生产 schema;
* 不暴露 secrets;
* 有明确成功指标;
* 有自动/立即 rollback 条件。
Canary 失败:
```yaml
rollback: required
release_status: paused
production_baseline: V1R4C6
```
不得继续 production promotion。
## 10. Production 边界
Codex不得直接执行生产部署。
只有以下全部提交并核验后,Codex才可创建 deployment request:
```yaml
F0_and_B3_B7: PASS
release_preflight: PASS
canary: PASS
immutable_release_digest: present
rollback_target: verified
rollback_procedure: verified
go_live_evidence: committed
```
实际 V2R1C0 部署只能由:
```text
owner/admin GitOps release action
```
执行。
在该动作前:
```yaml
production: V1R4C6
production_changed: false
release: paused
V2R1C0_tag: absent
```
必须保持不变。
## 11. Codex 下一步
立即执行顺序:
1. 在现有 control branch 提交本授权文件;
2. 提交 byte-exact B4 amendment;
3. 检查 B3 packet 中是否已有真实 owner raw-wire、双 credential、authority map、mode 和 reviewer evidence;
4. 有真实证据则完成 B3 manifest 与审核;
5. 无真实证据则继续 `BLOCKED_EVIDENCE_REQUIRED`,明确缺失项;
6. B3 PASS 后按已有 exact allowlist执行 B3;
7. B4 按 quote-authority-disabled 模式执行;
8. B3/B4 PASS 后再检查 B5–B7;
9. 不得跳到 Release Preflight、Canary 或生产。
本授权已解除 B4 vendor mapping 阻塞,但没有伪造或豁免 B3 owner evidence。
@@ -0,0 +1,26 @@
"""Public contracts for the Personal-Weixin Foundation runtime package.
The runtime package defines persistence-facing protocols and value types only.
The ``portal.personal_weixin`` Django application remains the sole schema and
migration owner.
"""
from .challenge_repository import (
ChallengeConflictError,
ChallengeExpiredError,
ChallengeNotFoundError,
ChallengeOwner,
ChallengeRecord,
ChallengeRepository,
ConsumeState,
)
__all__ = (
"ChallengeConflictError",
"ChallengeExpiredError",
"ChallengeNotFoundError",
"ChallengeOwner",
"ChallengeRecord",
"ChallengeRepository",
"ConsumeState",
)
@@ -0,0 +1,72 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import Protocol, runtime_checkable
_DIGEST = re.compile(r"^[0-9a-f]{64}$")
def require_digest(name: str, value: str) -> str:
if not _DIGEST.fullmatch(value):
raise ValueError(f"{name} must be a lowercase SHA-256 hex digest")
return value
@dataclass(frozen=True, slots=True)
class ChallengeOwner:
account_digest: str
sender_digest: str
portal_user_digest: str
portal_session_digest: str
def __post_init__(self) -> None:
for name, value in (("account_digest", self.account_digest), ("sender_digest", self.sender_digest), ("portal_user_digest", self.portal_user_digest), ("portal_session_digest", self.portal_session_digest)):
require_digest(name, value)
@dataclass(frozen=True, slots=True)
class ChallengeRecord:
challenge_digest: str
owner: ChallengeOwner
created_at: datetime
expires_at: datetime
consumed_at: datetime | None
def __post_init__(self) -> None:
require_digest("challenge_digest", self.challenge_digest)
if self.expires_at <= self.created_at:
raise ValueError("expires_at must be after created_at")
if self.consumed_at is not None and self.consumed_at < self.created_at:
raise ValueError("consumed_at cannot predate created_at")
class ConsumeState(StrEnum):
CONSUMED = "consumed"
ALREADY_CONSUMED = "already-consumed"
class ChallengeNotFoundError(LookupError):
pass
class ChallengeConflictError(RuntimeError):
pass
class ChallengeExpiredError(RuntimeError):
pass
@runtime_checkable
class ChallengeRepository(Protocol):
def create_or_get(self, *, challenge_digest: str, owner: ChallengeOwner, expires_at: datetime) -> ChallengeRecord:
"""Create or return the same-owner record idempotently."""
def get_by_digest(self, *, challenge_digest: str) -> ChallengeRecord:
"""Return the durable record or raise ChallengeNotFoundError."""
def consume(self, *, challenge_digest: str, owner: ChallengeOwner, consumed_at: datetime) -> ConsumeState:
"""Consume atomically using a row lock or conditional-update CAS."""
+5 -1
View File
@@ -14,4 +14,8 @@ aios-cell = "aios_v2r1.cli:main"
[tool.setuptools]
package-dir = {"" = "app"}
packages = ["aios_v2r1"]
[tool.setuptools.packages.find]
where = ["app"]
include = ["aios_v2r1*"]
namespaces = false
+31
View File
@@ -0,0 +1,31 @@
FROM aios-webops-runtime@sha256:b9cff350ee8ca96989809012ccec2edd2cfc8785ebca092519904ff5e1a1946d
WORKDIR /app
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_INDEX=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
DJANGO_SETTINGS_MODULE=portal_project.settings \
PERSONAL_WEIXIN_DB_PATH=/tmp/personal-weixin-foundation.sqlite3
COPY wheelhouse/ /opt/wheelhouse/
COPY requirements.txt /tmp/requirements.txt
RUN python -m pip install \
--no-cache-dir \
--no-index \
--find-links=/opt/wheelhouse \
--requirement /tmp/requirements.txt \
&& python -m pip check \
&& rm -rf /opt/wheelhouse /tmp/requirements.txt
COPY manage.py /app/manage.py
COPY portal_project/ /app/portal_project/
COPY personal_weixin/ /app/personal_weixin/
USER 65532:65532
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000", "--noreload"]
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import sys
from pathlib import Path
def main() -> None:
portal_dir = Path(__file__).resolve().parent
sys.path.insert(0, str(portal_dir.parent))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "portal.portal_project.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
View File
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class PersonalWeixinConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "portal.personal_weixin"
label = "personal_weixin"
@@ -0,0 +1,31 @@
# Generated by Django 5.2.15 on 2026-07-15 15:37
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='PersonalWeixinChallenge',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('challenge_digest', models.CharField(max_length=64, unique=True)),
('account_digest', models.CharField(max_length=64)),
('sender_digest', models.CharField(max_length=64)),
('portal_user_digest', models.CharField(max_length=64)),
('portal_session_digest', models.CharField(max_length=64)),
('created_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField()),
('consumed_at', models.DateTimeField(blank=True, null=True)),
],
options={
'indexes': [models.Index(fields=['account_digest', 'sender_digest', 'portal_user_digest', 'portal_session_digest'], name='pwx_owner_lookup_idx'), models.Index(fields=['expires_at'], name='pwx_expiry_idx'), models.Index(fields=['consumed_at'], name='pwx_consumed_idx')],
},
),
]
+88
View File
@@ -0,0 +1,88 @@
from __future__ import annotations
import re
from datetime import datetime
from enum import StrEnum
from django.db import models, transaction
from django.utils import timezone
_DIGEST = re.compile(r"^[0-9a-f]{64}$")
class ChallengeOwnershipConflict(RuntimeError):
pass
class ChallengeExpired(RuntimeError):
pass
class ConsumeOutcome(StrEnum):
CONSUMED = "consumed"
ALREADY_CONSUMED = "already-consumed"
def validate_digest(name: str, value: str) -> str:
if not _DIGEST.fullmatch(value):
raise ValueError(f"{name} must be a lowercase SHA-256 hex digest")
return value
def validate_owner(*, account_digest: str, sender_digest: str, portal_user_digest: str, portal_session_digest: str) -> tuple[str, str, str, str]:
return (validate_digest("account_digest", account_digest), validate_digest("sender_digest", sender_digest), validate_digest("portal_user_digest", portal_user_digest), validate_digest("portal_session_digest", portal_session_digest))
class PersonalWeixinChallengeManager(models.Manager):
def create_or_get_owned(self, *, challenge_digest: str, account_digest: str, sender_digest: str, portal_user_digest: str, portal_session_digest: str, expires_at: datetime) -> tuple["PersonalWeixinChallenge", bool]:
challenge_digest = validate_digest("challenge_digest", challenge_digest)
owner = validate_owner(account_digest=account_digest, sender_digest=sender_digest, portal_user_digest=portal_user_digest, portal_session_digest=portal_session_digest)
if expires_at <= timezone.now():
raise ValueError("expires_at must be in the future")
defaults = {"account_digest": owner[0], "sender_digest": owner[1], "portal_user_digest": owner[2], "portal_session_digest": owner[3], "expires_at": expires_at}
with transaction.atomic():
record, created = self.get_or_create(challenge_digest=challenge_digest, defaults=defaults)
if record.owner_tuple != owner:
raise ChallengeOwnershipConflict("challenge digest belongs to a different owner")
return record, created
def consume_owned(self, *, challenge_digest: str, account_digest: str, sender_digest: str, portal_user_digest: str, portal_session_digest: str, consumed_at: datetime | None = None) -> ConsumeOutcome:
challenge_digest = validate_digest("challenge_digest", challenge_digest)
owner = validate_owner(account_digest=account_digest, sender_digest=sender_digest, portal_user_digest=portal_user_digest, portal_session_digest=portal_session_digest)
consumed_at = consumed_at or timezone.now()
with transaction.atomic():
record = self.select_for_update().get(challenge_digest=challenge_digest)
if record.owner_tuple != owner:
raise ChallengeOwnershipConflict("challenge digest belongs to a different owner")
if consumed_at >= record.expires_at:
raise ChallengeExpired("challenge has expired")
if record.consumed_at is not None:
return ConsumeOutcome.ALREADY_CONSUMED
updated = self.filter(pk=record.pk, consumed_at__isnull=True).update(consumed_at=consumed_at)
if updated == 1:
return ConsumeOutcome.CONSUMED
record.refresh_from_db(fields=("consumed_at",))
if record.consumed_at is not None:
return ConsumeOutcome.ALREADY_CONSUMED
raise RuntimeError("atomic challenge consumption did not converge")
class PersonalWeixinChallenge(models.Model):
challenge_digest = models.CharField(max_length=64, unique=True)
account_digest = models.CharField(max_length=64)
sender_digest = models.CharField(max_length=64)
portal_user_digest = models.CharField(max_length=64)
portal_session_digest = models.CharField(max_length=64)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
consumed_at = models.DateTimeField(null=True, blank=True)
objects = PersonalWeixinChallengeManager()
class Meta:
app_label = "personal_weixin"
indexes = [models.Index(fields=["account_digest", "sender_digest", "portal_user_digest", "portal_session_digest"], name="pwx_owner_lookup_idx"), models.Index(fields=["expires_at"], name="pwx_expiry_idx"), models.Index(fields=["consumed_at"], name="pwx_consumed_idx")]
@property
def owner_tuple(self) -> tuple[str, str, str, str]:
return (self.account_digest, self.sender_digest, self.portal_user_digest, self.portal_session_digest)
+9
View File
@@ -0,0 +1,9 @@
from django.http import JsonResponse
from django.urls import path
def health(_: object) -> JsonResponse:
return JsonResponse({"service": "personal-weixin-portal", "status": "ready", "bindingFlow": "not-installed", "gatewayReadiness": "unknown"})
urlpatterns = [path("health", health, name="personal-weixin-health")]
+5
View File
@@ -0,0 +1,5 @@
from __future__ import annotations
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "portal.portal_project.settings")
from django.core.asgi import get_asgi_application
application = get_asgi_application()
+15
View File
@@ -0,0 +1,15 @@
from __future__ import annotations
import os
from pathlib import Path
SECRET_KEY = os.environ.get("PORTAL_DJANGO_SECRET_KEY", "local-foundation-bootstrap-only")
DEBUG = False
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
ROOT_URLCONF = "portal.portal_project.urls"
ASGI_APPLICATION = "portal.portal_project.asgi.application"
INSTALLED_APPS = ["django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "portal.personal_weixin.apps.PersonalWeixinConfig"]
MIDDLEWARE = ["django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware"]
TEMPLATES = []
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": os.environ.get("PORTAL_DB_PATH", str(Path("/tmp") / "personal-weixin-foundation.sqlite3")), "OPTIONS": {"timeout": 15}}}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
USE_TZ = True
+2
View File
@@ -0,0 +1,2 @@
from django.urls import include, path
urlpatterns = [path("", include("portal.personal_weixin.urls"))]
+3
View File
@@ -0,0 +1,3 @@
Django==5.2.15
asgiref==3.12.1
sqlparse==0.5.5
@@ -1,44 +0,0 @@
{
"schemaVersion": 1,
"contract": "personal-weixin-b4-outbound-quote-authority-amendment",
"ownerApproved": true,
"lineage": {
"foundationImplementationCommit": "2fcab6f5833a405a9a26683811600b6e5f1aa7b1",
"foundationResultCommit": "64a71b6199a8e2bd609ba145349e67b874888505",
"r10LineageCommit": "74e603c0c943501110577dbe3c7f6a0b18b8ec7d",
"evidenceGateCommit": "62a4067cf7a462ebe7cf29524238bc5d0a70b813",
"upstreamAuditCommit": "b2f5f531019a70801444987dbb97dc1f69cf47de"
},
"vendorClientIdMessageIdEvidence": {
"status": "absent",
"clientIdPromotable": false
},
"authorityPolicy": {
"outboundQuoteAuthority": "disabled",
"clientIdRole": "non-authoritative-correlation-only",
"authoritativeMessageIdSources": [
"validated-inbound-raw-wire.vendor_message_id"
],
"quotedSendWithoutAuthoritativeMessageId": "fail-closed",
"plainOutboundWithoutQuoteAuthority": "allowed-only-by-existing-b4-allowlist",
"syntheticMapping": false,
"timestampFallback": false,
"localSequenceFallback": false,
"contentHashFallback": false,
"outboundReceiptFallback": false
},
"implementationConstraints": {
"scopeExpansion": false,
"exactAllowlistRequired": true,
"missingAllowlistStatus": "BLOCKED_SCOPE",
"productionChangeAuthorized": false,
"canaryAuthorized": false,
"tagAuthorized": false,
"forcePushAuthorized": false
},
"production": {
"baseline": "V1R4C6",
"changed": false,
"releaseStatus": "paused"
}
}
@@ -0,0 +1,9 @@
{
"schemaVersion": 1,
"contract": "personal-weixin-durable-challenge-ownership",
"owner": {"application": "portal.personal_weixin", "djangoAppLabel": "personal_weixin", "model": "PersonalWeixinChallenge", "migration": "portal/personal_weixin/migrations/0001_personal_weixin_challenge.py"},
"runtime": {"role": "repository-contract-only", "contract": "active-runtime/app/aios_v2r1/personal_weixin/challenge_repository.py", "ownsSchema": false},
"storage": {"durable": true, "inMemoryFallback": false, "challengeRepresentation": "sha256-digest-only", "ownershipDimensions": ["account_digest", "sender_digest", "portal_user_digest", "portal_session_digest"], "atomicConsumption": ["database-row-lock", "conditional-update-cas"]},
"sensitiveData": {"rawChallengeStored": false, "rawSessionKeyStored": false, "rawVerifyCodeStored": false, "oauthTokenStored": false, "apiKeyStored": false},
"scope": {"bindingFlowInstalled": false, "b6b7Included": false, "canaryStarted": false, "productionDeployed": false}
}
@@ -0,0 +1,54 @@
{
"artifact": "personal-weixin-portal-foundation",
"baseImage": {
"archiveSha256": null,
"digest": "sha256:b9cff350ee8ca96989809012ccec2edd2cfc8785ebca092519904ff5e1a1946d",
"equivalenceVerified": true,
"localImageId": "sha256:b9cff350ee8ca96989809012ccec2edd2cfc8785ebca092519904ff5e1a1946d",
"materialization": "docker-local-content-store",
"reference": "aios-webops-runtime@sha256:b9cff350ee8ca96989809012ccec2edd2cfc8785ebca092519904ff5e1a1946d",
"remoteResolutionAttempted": false
},
"build": {
"baseReferenceSubstitutionOnly": true,
"engine": "docker-legacy-local-store",
"network": "none",
"pullPolicy": "false"
},
"contract": "personal-weixin-image-build",
"output": {
"configuredUser": "65532:65532",
"imageDigest": "sha256:ed741d1f028c4bd507c6eda71fb1dcc11e0815c0107afcbfcbaaaaa23d5a11bb",
"imageDigestKind": "local-image-config-digest",
"imageId": "sha256:ed741d1f028c4bd507c6eda71fb1dcc11e0815c0107afcbfcbaaaaa23d5a11bb",
"platform": "linux/amd64",
"rootfsChainSha256": "e04840895d2ada4064a8cae3b19a1da6b82609df1a1618e1de4d886a63d2d640",
"runtimeUid": 65532
},
"schemaVersion": 1,
"scope": {
"b6b7Included": false,
"bindingFlowInstalled": false,
"canaryStarted": false,
"productionBaseline": "V1R4C6",
"productionDeployed": false,
"releaseStatus": "paused"
},
"security": {
"containsAgentState": false,
"containsGitMetadata": false,
"containsProductionDatabase": false,
"containsSecrets": false,
"runAsNonRoot": true
},
"source": {
"baseCommit": "d5b51e1342541bf51887782f530c3d2971155996",
"buildContext": "generated-minimal-portal-context",
"buildContextTreeSha256": "614f1ddf4edcab7023624fa1b686f7e58a8cfb5fcd0d49d9900ae9e0ac99481c",
"dockerfile": "portal/Dockerfile",
"dockerfileSha256": "68565361c35d5ed6e8d6820c063d413a39fe6398fed5d7775cb7df1c1379f4fd",
"requirementsSha256": "5c4307ece9524c883f1926e50fc333d7a98f50b7ebf3811affa8aa007915789a",
"resolvedBuildDockerfileSha256": "7930bfe83f701514c581df4e662b6b9aba309dbfc0b5a6d6b299e3b21883f7ce",
"sourceTreeSha256": "8d5719db1f8efbe9d2a4d635ff4f06e1df6cacddc9aadba6c830a06159e0efd4"
}
}
@@ -1,23 +0,0 @@
{
"schemaVersion": 1,
"contract": "personal-weixin-r10-lineage-amendment",
"foundation": {
"node": "F0_PERSONAL_WEIXIN_FOUNDATION",
"implementationCommit": "2fcab6f5833a405a9a26683811600b6e5f1aa7b1",
"resultCommit": "64a71b6199a8e2bd609ba145349e67b874888505",
"imageDigest": "sha256:ed741d1f028c4bd507c6eda71fb1dcc11e0815c0107afcbfcbaaaaa23d5a11bb",
"status": "PASS"
},
"precedes": [
"B3",
"B4",
"B5",
"B6",
"B7"
],
"production": {
"baseline": "V1R4C6",
"changed": false,
"releaseStatus": "paused"
}
}
+6 -2
View File
@@ -22,9 +22,13 @@ def test_canonical_package_has_one_identity_and_no_compatibility_shim() -> None:
assert not (ROOT / "active_runtime").exists()
def test_packaging_metadata_declares_only_the_canonical_app_package() -> None:
def test_packaging_metadata_discovers_only_the_canonical_namespace() -> None:
metadata = (ROOT / "active-runtime" / "pyproject.toml").read_text()
assert 'packages = ["aios_v2r1"]' in metadata
assert "[tool.setuptools.packages.find]" in metadata
assert 'where = ["app"]' in metadata
assert 'include = ["aios_v2r1*"]' in metadata
assert "namespaces = false" in metadata
assert 'packages = ["aios_v2r1"]' not in metadata
assert 'package-dir = {"" = "app"}' in metadata
assert "active_runtime" not in metadata
assert 'aios-cell = "aios_v2r1.cli:main"' in metadata