Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a71b6199 |
+110
@@ -0,0 +1,110 @@
|
||||
# Personal-Weixin Foundation Retry 10 Result
|
||||
|
||||
```yaml
|
||||
status: PASS
|
||||
task: Personal-Weixin Foundation Retry 10
|
||||
timestamp_utc8: 20260716T092623UTC8
|
||||
|
||||
previous_result_commit: da776bad5234fcb0c3c18ce07cec6eb63bd669b6
|
||||
owner_patch_blocked_result_commit: ed329b57fdf1503c7a9d481a29555c0a266e053f
|
||||
authorization_commit: 720ff846a2a7be526c1c8d79bed6a48adbbe884e
|
||||
|
||||
owner_bundle_sha256: 305beb860ea404e0762aa1f3171b992fb5a841cb3d211e294d85fe9d36764d91
|
||||
owner_materializer_sha256: b90533429dc386b8044b8c01d2d493e9b6f82329154c84a1663b5a0c5525714e
|
||||
owner_context_script_sha256: 303a562a978cb5c823bfe1f49afde06b106b34ee3592141d2925fc98e2be7b1d
|
||||
owner_authorization_sha256: 717b95c1799223d124d7197a04c4a14285fe539619aac7c6db5cbd8b4d77a76e
|
||||
owner_lineage_schema_sha256: 1d23897412a0c566588e7bf9ac18803c53ee9698f296778cd4639c940c2553fb
|
||||
archive_member_count: 10
|
||||
archive_member_safety: PASS
|
||||
owner_member_sha256_gate: PASS
|
||||
owner_files_modified: false
|
||||
|
||||
marker_environment_sha256: b583947c74eff4872d782b415bbcb4e671cd84443e409922525d8aa3f87f46e8
|
||||
metadata_fingerprints:
|
||||
django: 2bc0174c10a1f3528cb276a163975f77f991334455f725242744f979fce21d5a
|
||||
asgiref: a5a59f037b3899475e95f520d16255d48cba551da2cd6fbdb27f7cd5eaf8dff8
|
||||
sqlparse: e9a2699b6af81349cdae372215a906b599eea07b33738660f13350a7e243da59
|
||||
offline_wheelhouse_manifest_sha256: b2d99d2670497b64a0af803c78d623b2255af62a137637fd0ce9e817ad50f820
|
||||
active_requirements:
|
||||
- asgiref>=3.8.1
|
||||
- sqlparse>=0.3.1
|
||||
inactive_requirements:
|
||||
- 'tzdata; sys_platform == "win32"'
|
||||
- 'argon2-cffi>=19.1.0; extra == "argon2"'
|
||||
- 'bcrypt; extra == "bcrypt"'
|
||||
- 'typing_extensions>=4; python_version < "3.11"'
|
||||
- 'pytest; extra == "tests"'
|
||||
- 'pytest-asyncio; extra == "tests"'
|
||||
- 'mypy>=1.14.0; extra == "mypy"'
|
||||
- "build; extra == 'dev'"
|
||||
- "sphinx; extra == 'doc'"
|
||||
|
||||
implementation_branch: patch/v2r1-personal-weixin-foundation-codex-retry10-20260716T092623UTC8
|
||||
implementation_commit: 2fcab6f5833a405a9a26683811600b6e5f1aa7b1
|
||||
implementation_parent: d5b51e1342541bf51887782f530c3d2971155996
|
||||
implementation_commits: 1
|
||||
merge_commits: 0
|
||||
worktree_oid: 2fcab6f5833a405a9a26683811600b6e5f1aa7b1
|
||||
bare_oid: 2fcab6f5833a405a9a26683811600b6e5f1aa7b1
|
||||
gitea_oid: 2fcab6f5833a405a9a26683811600b6e5f1aa7b1
|
||||
implementation_oids_all_equal: true
|
||||
|
||||
allowlist_count: 18
|
||||
staging_manifest_valid: PASS
|
||||
runtime_import_gate: PASS
|
||||
runtime_static_export_gate: PASS
|
||||
runtime_wheel: PASS
|
||||
runtime_wheel_sha256: 41b467f9a26e640b92c6004ef9562319b74047505fa09287204dc2bd5f78c2af
|
||||
fresh_venv_install: PASS
|
||||
external_import: PASS
|
||||
installed_wheel_export_gate: PASS
|
||||
package_identity_tests: PASS
|
||||
package_identity_test_count: 4
|
||||
portal_check: PASS
|
||||
migration_dry_run: PASS
|
||||
temporary_migration_apply: PASS
|
||||
model_security: PASS
|
||||
repository_single_consume: PASS
|
||||
repository_idempotency: PASS
|
||||
repository_conflict_fail_closed: PASS
|
||||
repository_concurrency_contract: PASS
|
||||
record_path_policy_self_test: PASS
|
||||
frozen_pep508_policy_self_test: PASS
|
||||
frozen_marker_metadata_fingerprint_gate: PASS
|
||||
offline_wheelhouse: PASS
|
||||
offline_install: PASS
|
||||
offline_import: PASS
|
||||
offline_pip_check: PASS
|
||||
|
||||
local_base_digest_equivalence: PASS
|
||||
base_image_digest: sha256:b9cff350ee8ca96989809012ccec2edd2cfc8785ebca092519904ff5e1a1946d
|
||||
build_engine: docker-legacy-local-store
|
||||
build_network: none
|
||||
remote_resolution_attempted: false
|
||||
image_build: PASS
|
||||
image_id: sha256:ed741d1f028c4bd507c6eda71fb1dcc11e0815c0107afcbfcbaaaaa23d5a11bb
|
||||
image_digest: sha256:ed741d1f028c4bd507c6eda71fb1dcc11e0815c0107afcbfcbaaaaa23d5a11bb
|
||||
image_digest_kind: local-image-config-digest
|
||||
container_offline_dependency_gate: PASS
|
||||
container_non_root: true
|
||||
runtime_uid: 65532
|
||||
image_contract: PASS
|
||||
|
||||
changed_paths_exact_18: PASS
|
||||
gitleaks: PASS
|
||||
worktree_artifact_pollution: PASS
|
||||
worktree_clean: true
|
||||
|
||||
production_changed: false
|
||||
baseline_after: V1R4C6
|
||||
release_status: paused
|
||||
b6_b7_started: false
|
||||
canary_started: false
|
||||
```
|
||||
|
||||
## Scope evidence
|
||||
|
||||
- Authorization was committed only to the takeover/control branch and was not inherited by the Foundation implementation branch.
|
||||
- The Foundation implementation contains exactly one non-merge commit and exactly the 18 owner-authorized paths.
|
||||
- The image was built from the locally present approved base digest with network disabled and pull disabled.
|
||||
- No production, canary, B3, B4, B5, B6, or B7 action was performed during Retry 10.
|
||||
@@ -1,26 +0,0 @@
|
||||
"""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",
|
||||
)
|
||||
@@ -1,72 +0,0 @@
|
||||
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."""
|
||||
@@ -14,8 +14,4 @@ aios-cell = "aios_v2r1.cli:main"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "app"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["app"]
|
||||
include = ["aios_v2r1*"]
|
||||
namespaces = false
|
||||
packages = ["aios_v2r1"]
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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"]
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/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()
|
||||
@@ -1,7 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PersonalWeixinConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "portal.personal_weixin"
|
||||
label = "personal_weixin"
|
||||
@@ -1,31 +0,0 @@
|
||||
# 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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,88 +0,0 @@
|
||||
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)
|
||||
@@ -1,9 +0,0 @@
|
||||
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")]
|
||||
@@ -1,5 +0,0 @@
|
||||
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()
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,2 +0,0 @@
|
||||
from django.urls import include, path
|
||||
urlpatterns = [path("", include("portal.personal_weixin.urls"))]
|
||||
@@ -1,3 +0,0 @@
|
||||
Django==5.2.15
|
||||
asgiref==3.12.1
|
||||
sqlparse==0.5.5
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"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}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,9 @@ def test_canonical_package_has_one_identity_and_no_compatibility_shim() -> None:
|
||||
assert not (ROOT / "active_runtime").exists()
|
||||
|
||||
|
||||
def test_packaging_metadata_discovers_only_the_canonical_namespace() -> None:
|
||||
def test_packaging_metadata_declares_only_the_canonical_app_package() -> None:
|
||||
metadata = (ROOT / "active-runtime" / "pyproject.toml").read_text()
|
||||
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 'packages = ["aios_v2r1"]' in metadata
|
||||
assert 'package-dir = {"" = "app"}' in metadata
|
||||
assert "active_runtime" not in metadata
|
||||
assert 'aios-cell = "aios_v2r1.cli:main"' in metadata
|
||||
|
||||
Reference in New Issue
Block a user