From 6d490a0080b1f1b353ba1aa197004cf0cac6df21 Mon Sep 17 00:00:00 2001 From: mattamior Date: Sun, 20 Sep 2026 23:35:35 +0800 Subject: [PATCH 1/5] ci: capture pinned bootstrap source for isolated validation --- .github/workflows/bootstrap-source-once.yml | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/bootstrap-source-once.yml diff --git a/.github/workflows/bootstrap-source-once.yml b/.github/workflows/bootstrap-source-once.yml new file mode 100644 index 0000000..e900f12 --- /dev/null +++ b/.github/workflows/bootstrap-source-once.yml @@ -0,0 +1,27 @@ +name: Bootstrap source capture once +on: + push: + branches: [fix/bootstrap-acceptance] + paths: [.github/workflows/bootstrap-source-once.yml] +permissions: + contents: read +jobs: + source: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + ref: 9501fd7ed0843f46882ee63ed9c19cd2759a7cb2 + persist-credentials: false + - name: Capture tracked source only + run: | + mkdir -p "$RUNNER_TEMP/source" + git rev-parse HEAD HEAD^{tree} > "$RUNNER_TEMP/source/identity.txt" + git ls-tree -r HEAD > "$RUNNER_TEMP/source/tree.txt" + git archive --format=tar HEAD > "$RUNNER_TEMP/source/source.tar" + - uses: actions/upload-artifact@v4 + with: + name: svif-bootstrap-pinned-source + path: ${{ runner.temp }}/source/ + retention-days: 3 + if-no-files-found: error From a523e3e5f7c5a3b7f09cc54492ff2763124911f8 Mon Sep 17 00:00:00 2001 From: mattamior Date: Sun, 20 Sep 2026 23:44:39 +0800 Subject: [PATCH 2/5] ci: verify exact bootstrap patch and live stable lookup once --- .github/workflows/bootstrap-source-once.yml | 78 ++++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bootstrap-source-once.yml b/.github/workflows/bootstrap-source-once.yml index e900f12..c1af32a 100644 --- a/.github/workflows/bootstrap-source-once.yml +++ b/.github/workflows/bootstrap-source-once.yml @@ -4,7 +4,7 @@ on: branches: [fix/bootstrap-acceptance] paths: [.github/workflows/bootstrap-source-once.yml] permissions: - contents: read + contents: write jobs: source: runs-on: ubuntu-24.04 @@ -13,15 +13,75 @@ jobs: with: ref: 9501fd7ed0843f46882ee63ed9c19cd2759a7cb2 persist-credentials: false - - name: Capture tracked source only + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Verify authored patch, test, and materialize Git objects only + env: + GH_TOKEN: ${{ github.token }} + PYTHONPATH: src run: | - mkdir -p "$RUNNER_TEMP/source" - git rev-parse HEAD HEAD^{tree} > "$RUNNER_TEMP/source/identity.txt" - git ls-tree -r HEAD > "$RUNNER_TEMP/source/tree.txt" - git archive --format=tar HEAD > "$RUNNER_TEMP/source/source.tar" + python - <<'PY' + import base64, hashlib, importlib.util, json, os, subprocess, urllib.request + from pathlib import Path + out = Path(os.environ['RUNNER_TEMP']) / 'bootstrap-result' + out.mkdir() + def api(path, data=None): + request = urllib.request.Request('https://api.github.com/repos/iorLab/svif/' + path, + data=None if data is None else json.dumps(data).encode(), + headers={'Authorization': 'Bearer ' + os.environ['GH_TOKEN'], + 'Accept': 'application/vnd.github+json', 'Content-Type': 'application/json'}) + with urllib.request.urlopen(request, timeout=30) as response: + return json.load(response) + def git(*args): + return subprocess.check_output(['git', *args], text=True).strip() + assert git('rev-parse', 'HEAD') == '9501fd7ed0843f46882ee63ed9c19cd2759a7cb2' + blob = api('git/blobs/f0b1b5d26060f9256fa3e81d9209c39bc3ae759f') + patch = base64.b64decode(blob['content']) + assert hashlib.sha256(patch).hexdigest() == '830fa876982df4fd4b8aa26bb47e77abf9c5c3596db389f7de98dfa0e318950c' + subprocess.run(['git', 'apply', '--unidiff-zero', '-'], input=patch, check=True) + allowed = {'README.md', 'README.zh-CN.md', 'REPOSITORY_TREE.md', + 'checks/check_local_install.py', 'checks/check_repository.py', + 'conformance/RELEASE_READINESS.md', 'plugin/skills/svif/SKILL.md', + 'tests/test_local_acceptance_harness.py', 'tests/test_plugin_first_use_bootstrap.py'} + changed = git('diff', '--name-only').splitlines() + assert set(changed) == allowed + subprocess.run(['git', 'diff', '--check'], check=True) + subprocess.run(['python', 'checks/check_repository.py'], check=True) + subprocess.run(['python', 'conformance/check_contracts.py'], check=True) + with (out / 'tests.txt').open('w') as log: + subprocess.run(['python', '-B', '-m', 'unittest', 'discover', '-s', 'tests', '-v'], + stdout=log, stderr=subprocess.STDOUT, check=True) + subprocess.run(['git', 'add', '-u'], check=True) + expected = '3c053b2371f0fe916379ac7ea2f74ce1eb4bad3f' + assert git('write-tree') == expected + spec = importlib.util.spec_from_file_location('acceptance', 'checks/check_local_install.py') + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + receipt, files = module.resolve_latest_agnir() + (out / 'agnir-live-resolution.json').write_text(json.dumps(receipt, indent=2) + '\n') + print('LIVE_AGNIR', json.dumps(receipt)) + subprocess.run(['python', 'checks/build_submission_bundle.py', '--output', str(out / 'svif-0.2.0.zip')], check=True) + archive_hash = hashlib.sha256((out / 'svif-0.2.0.zip').read_bytes()).hexdigest() + (out / 'svif-0.2.0.zip.sha256').write_text(archive_hash + ' svif-0.2.0.zip\n') + elements = [] + for path in changed: + raw = Path(path).read_bytes() + mode = git('ls-files', '--stage', '--', path).split()[0] + result = api('git/blobs', {'content': base64.b64encode(raw).decode(), 'encoding': 'base64'}) + assert result['sha'] == git('hash-object', path) + elements.append({'path': path, 'mode': mode, 'type': 'blob', 'sha': result['sha']}) + tree = api('git/trees', {'base_tree': '6c430926bf8f80b57c1497f3230a5008c42a09de', 'tree': elements}) + assert tree['sha'] == expected + summary = {'tree': tree['sha'], 'plugin_tree': git('rev-parse', ':plugin'), + 'zip_sha256': archive_hash, 'files': elements} + (out / 'candidate.json').write_text(json.dumps(summary, indent=2) + '\n') + print('VERIFIED_CANDIDATE', json.dumps(summary)) + PY - uses: actions/upload-artifact@v4 + if: always() with: - name: svif-bootstrap-pinned-source - path: ${{ runner.temp }}/source/ - retention-days: 3 + name: svif-bootstrap-validation + path: ${{ runner.temp }}/bootstrap-result/ + retention-days: 14 if-no-files-found: error From ed0ae6924aabfa65e10be13ecaeda8564acae604 Mon Sep 17 00:00:00 2001 From: mattamior Date: Sun, 20 Sep 2026 23:46:13 +0800 Subject: [PATCH 3/5] ci: correct one-shot candidate tree receipt --- .github/workflows/bootstrap-source-once.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bootstrap-source-once.yml b/.github/workflows/bootstrap-source-once.yml index c1af32a..b603559 100644 --- a/.github/workflows/bootstrap-source-once.yml +++ b/.github/workflows/bootstrap-source-once.yml @@ -73,7 +73,7 @@ jobs: elements.append({'path': path, 'mode': mode, 'type': 'blob', 'sha': result['sha']}) tree = api('git/trees', {'base_tree': '6c430926bf8f80b57c1497f3230a5008c42a09de', 'tree': elements}) assert tree['sha'] == expected - summary = {'tree': tree['sha'], 'plugin_tree': git('rev-parse', ':plugin'), + summary = {'tree': tree['sha'], 'plugin_tree': git('rev-parse', expected + ':plugin'), 'zip_sha256': archive_hash, 'files': elements} (out / 'candidate.json').write_text(json.dumps(summary, indent=2) + '\n') print('VERIFIED_CANDIDATE', json.dumps(summary)) From 08f2c7c7581edb8845cea161b971a0c4dca2de4d Mon Sep 17 00:00:00 2001 From: mattamior Date: Sun, 20 Sep 2026 23:49:20 +0800 Subject: [PATCH 4/5] test: verify Project-aware Agnir bootstrap and exact stable provenance Use a fresh canonical release/contract receipt only for new Projects; keep existing version and offline resume. Add executable checker regressions and align bilingual onboarding diagrams. Remove the one-shot source/materialization workflow from the product tree. Local and isolated CI checks: 141 tests plus repository integrity and portable conformance pass. Live latest lookup succeeded, then independently failed closed on upstream rate limit; neither result substitutes for authenticated model acceptance. --- .github/workflows/bootstrap-source-once.yml | 87 ----------- README.md | 8 +- README.zh-CN.md | 8 +- REPOSITORY_TREE.md | 2 +- checks/check_local_install.py | 155 ++++++++++++++++++- checks/check_repository.py | 4 +- conformance/RELEASE_READINESS.md | 19 ++- plugin/skills/svif/SKILL.md | 5 +- tests/test_local_acceptance_harness.py | 162 ++++++++++++++++++++ tests/test_plugin_first_use_bootstrap.py | 12 ++ 10 files changed, 358 insertions(+), 104 deletions(-) delete mode 100644 .github/workflows/bootstrap-source-once.yml diff --git a/.github/workflows/bootstrap-source-once.yml b/.github/workflows/bootstrap-source-once.yml deleted file mode 100644 index b603559..0000000 --- a/.github/workflows/bootstrap-source-once.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Bootstrap source capture once -on: - push: - branches: [fix/bootstrap-acceptance] - paths: [.github/workflows/bootstrap-source-once.yml] -permissions: - contents: write -jobs: - source: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - ref: 9501fd7ed0843f46882ee63ed9c19cd2759a7cb2 - persist-credentials: false - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Verify authored patch, test, and materialize Git objects only - env: - GH_TOKEN: ${{ github.token }} - PYTHONPATH: src - run: | - python - <<'PY' - import base64, hashlib, importlib.util, json, os, subprocess, urllib.request - from pathlib import Path - out = Path(os.environ['RUNNER_TEMP']) / 'bootstrap-result' - out.mkdir() - def api(path, data=None): - request = urllib.request.Request('https://api.github.com/repos/iorLab/svif/' + path, - data=None if data is None else json.dumps(data).encode(), - headers={'Authorization': 'Bearer ' + os.environ['GH_TOKEN'], - 'Accept': 'application/vnd.github+json', 'Content-Type': 'application/json'}) - with urllib.request.urlopen(request, timeout=30) as response: - return json.load(response) - def git(*args): - return subprocess.check_output(['git', *args], text=True).strip() - assert git('rev-parse', 'HEAD') == '9501fd7ed0843f46882ee63ed9c19cd2759a7cb2' - blob = api('git/blobs/f0b1b5d26060f9256fa3e81d9209c39bc3ae759f') - patch = base64.b64decode(blob['content']) - assert hashlib.sha256(patch).hexdigest() == '830fa876982df4fd4b8aa26bb47e77abf9c5c3596db389f7de98dfa0e318950c' - subprocess.run(['git', 'apply', '--unidiff-zero', '-'], input=patch, check=True) - allowed = {'README.md', 'README.zh-CN.md', 'REPOSITORY_TREE.md', - 'checks/check_local_install.py', 'checks/check_repository.py', - 'conformance/RELEASE_READINESS.md', 'plugin/skills/svif/SKILL.md', - 'tests/test_local_acceptance_harness.py', 'tests/test_plugin_first_use_bootstrap.py'} - changed = git('diff', '--name-only').splitlines() - assert set(changed) == allowed - subprocess.run(['git', 'diff', '--check'], check=True) - subprocess.run(['python', 'checks/check_repository.py'], check=True) - subprocess.run(['python', 'conformance/check_contracts.py'], check=True) - with (out / 'tests.txt').open('w') as log: - subprocess.run(['python', '-B', '-m', 'unittest', 'discover', '-s', 'tests', '-v'], - stdout=log, stderr=subprocess.STDOUT, check=True) - subprocess.run(['git', 'add', '-u'], check=True) - expected = '3c053b2371f0fe916379ac7ea2f74ce1eb4bad3f' - assert git('write-tree') == expected - spec = importlib.util.spec_from_file_location('acceptance', 'checks/check_local_install.py') - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - receipt, files = module.resolve_latest_agnir() - (out / 'agnir-live-resolution.json').write_text(json.dumps(receipt, indent=2) + '\n') - print('LIVE_AGNIR', json.dumps(receipt)) - subprocess.run(['python', 'checks/build_submission_bundle.py', '--output', str(out / 'svif-0.2.0.zip')], check=True) - archive_hash = hashlib.sha256((out / 'svif-0.2.0.zip').read_bytes()).hexdigest() - (out / 'svif-0.2.0.zip.sha256').write_text(archive_hash + ' svif-0.2.0.zip\n') - elements = [] - for path in changed: - raw = Path(path).read_bytes() - mode = git('ls-files', '--stage', '--', path).split()[0] - result = api('git/blobs', {'content': base64.b64encode(raw).decode(), 'encoding': 'base64'}) - assert result['sha'] == git('hash-object', path) - elements.append({'path': path, 'mode': mode, 'type': 'blob', 'sha': result['sha']}) - tree = api('git/trees', {'base_tree': '6c430926bf8f80b57c1497f3230a5008c42a09de', 'tree': elements}) - assert tree['sha'] == expected - summary = {'tree': tree['sha'], 'plugin_tree': git('rev-parse', expected + ':plugin'), - 'zip_sha256': archive_hash, 'files': elements} - (out / 'candidate.json').write_text(json.dumps(summary, indent=2) + '\n') - print('VERIFIED_CANDIDATE', json.dumps(summary)) - PY - - uses: actions/upload-artifact@v4 - if: always() - with: - name: svif-bootstrap-validation - path: ${{ runner.temp }}/bootstrap-result/ - retention-days: 14 - if-no-files-found: error diff --git a/README.md b/README.md index 79ac4f1..98e2d32 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ If an activation locator, identity, required memory locator, or compatibility ch ## What Svif Adds to a Project +The following is the current unpublished `0.2.0` behavior. The immutable `v0.2.0-preview.1` package retains its historical onboarding procedure. + On first use, Svif first checks whether the Project already has Agnir. If it does, Svif preserves that Project's declared Agnir Core/profile compatibility exactly and creates/validates the Svif binding against the same line; installing Svif is not permission to migrate Agnir. Only a genuinely uninitialized repository/filesystem Project resolves the canonical **latest published stable Agnir** and initializes using the Core/profile and activation contract declared by that stable release. **Svif does not take over existing Project files.** Existing activation/documentation surfaces receive only the entry required by the selected Agnir contract, while unrelated content is preserved. ```text @@ -87,10 +89,12 @@ flowchart TB subgraph T["Target Project surface — first use"] G["AGENTS.md
EDIT: add activation locator only"] - H["README.md
EDIT: add Agnir instructions only"] + H["README.md
EDIT: add compatibility locator only"] + A0["AGNIR.md
ADD: activation procedure when selected release requires it"] Q["AGNIR.yaml + .agnir/
ADD: founding continuity"] B["SVIF.yaml
ADD: Project binding"] - G --> H --> Q --> B + G --> A0 --> Q --> B + H --> A0 end D -. "non-destructive first-use setup" .-> G diff --git a/README.zh-CN.md b/README.zh-CN.md index 860c89b..23bbf04 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -58,6 +58,8 @@ Install and enable Svif for this Project: https://github.com/iorLab/svif ## Svif 会给 Project 增加什么 +以下描述当前尚未发布的 `0.2.0` 行为;不可变的 `v0.2.0-preview.1` 包仍保留其历史初始化流程。 + 首次使用时,Svif 会先判断 Project 是否已经使用 Agnir。若已经存在 Agnir,就原样保留该 Project 声明的 Agnir Core/profile compatibility,并让 Svif binding 与这一条兼容线保持一致;安装 Svif 不等于获得迁移 Agnir 的授权。只有真正没有 Agnir / 其他 continuity binding 的 repository/filesystem Project,才解析 canonical **最新已发布 stable Agnir**,并按该 stable release 声明的 Core/profile 与 activation contract 初始化。**Svif 不会接管已有 Project 文件。** 已有 activation / 文档表面只增加所选 Agnir contract 需要的入口,并保留无关内容。 ```text @@ -87,10 +89,12 @@ flowchart TB subgraph T["目标 Project surface — 首次使用"] G["AGENTS.md
编辑:仅添加 activation locator"] - H["README.md
编辑:仅添加 Agnir instructions"] + H["README.md
编辑:仅添加兼容入口"] + A0["AGNIR.md
新增:所选版本要求的项目激活流程"] Q["AGNIR.yaml + .agnir/
新增:founding continuity"] B["SVIF.yaml
新增:Project binding"] - G --> H --> Q --> B + G --> A0 --> Q --> B + H --> A0 end D -. "非破坏性 first-use setup" .-> G diff --git a/REPOSITORY_TREE.md b/REPOSITORY_TREE.md index 5c2169d..be12576 100644 --- a/REPOSITORY_TREE.md +++ b/REPOSITORY_TREE.md @@ -144,7 +144,7 @@ svif/ # Svif 产品主仓库 │ └── workspace-scm.json # workspace / source-control capability fixture │ ├── checks/ # 仓库与产品结构完整性检查 -│ ├── check_local_install.py # 隔离原生 Codex 安装/发现与显式 opt-in 真实模型验收 +│ ├── check_local_install.py # 原生安装/发现;同操作 latest-stable 源核验、精确 bootstrap binding 与 opt-in 模型验收 │ ├── build_submission_bundle.py # 从 accepted plugin/ tree 构建 deterministic Skills-only portal ZIP 并输出 SHA-256 │ └── check_repository.py # 防止关键模块、README、Plugin packaging、Agnir activation、canonical topology 漂移 │ diff --git a/checks/check_local_install.py b/checks/check_local_install.py index 18406cf..974dde9 100644 --- a/checks/check_local_install.py +++ b/checks/check_local_install.py @@ -10,17 +10,22 @@ from __future__ import annotations import argparse +import base64 import hashlib import json import os import platform import queue +import re import shutil import subprocess import sys import threading import time import uuid +import urllib.parse +import urllib.request +from datetime import datetime, timezone from pathlib import Path from typing import Any @@ -171,6 +176,134 @@ def discovered_skill(response: dict, expected: Path) -> dict: return matches[0] +def agnir_adapter(): + # This checker uses the repository's actual parser and compatibility support. + source = str(ROOT / "src") + if source not in sys.path: + sys.path.insert(0, source) + from svif.continuity.agnir import AgnirFilesystemContinuityProvider + return AgnirFilesystemContinuityProvider + + +def canonical_agnir_json(path: str) -> Any: + """Read public canonical metadata only; never send credentials or change hosts.""" + class NoRedirect(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + raise RuntimeError("canonical Agnir lookup redirected; resolve explicitly") + + request = urllib.request.Request( + "https://api.github.com/repos/iorLab/agnir/" + path, + headers={"Accept": "application/vnd.github+json", "User-Agent": "svif-local-acceptance"}, + ) + with urllib.request.build_opener(NoRedirect).open(request, timeout=30) as response: + data = response.read(4 * 1024 * 1024 + 1) + require(len(data) <= 4 * 1024 * 1024, "canonical Agnir response exceeds limit") + return json.loads(data) + + +def resolve_latest_agnir(fetch=None) -> tuple[dict, dict[str, str]]: + """Resolve once per NEW bootstrap, not per existing Project or normal resume. + + The injected reader is for unit tests; those fixtures are NOT live receipts. + Return verified release files to the sandbox without granting it network access. + """ + fetch = canonical_agnir_json if fetch is None else fetch + release = fetch("releases/latest") + require(isinstance(release, dict), "invalid latest-stable release metadata") + tag = release.get("tag_name", "") + require(isinstance(tag, str) and re.fullmatch(r"v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)", tag) is not None, + "latest Agnir must have a stable SemVer tag, not a branch or prerelease") + require(release.get("draft") is False and release.get("prerelease") is False, + "latest Agnir is not a published stable release") + published = release.get("published_at") + require(isinstance(published, str) and bool(published), "latest Agnir is not published") + timestamp = datetime.fromisoformat(published.replace("Z", "+00:00")) + require(timestamp.tzinfo is not None and timestamp <= datetime.now(timezone.utc), + "invalid Agnir publication time") + commit = fetch("commits/" + urllib.parse.quote(tag, safe="")) + require(isinstance(commit, dict), "invalid stable tag resolution") + revision = commit.get("sha", "") + require(isinstance(revision, str) and re.fullmatch(r"[0-9a-f]{40}", revision) is not None, + "stable tag did not resolve to an exact commit") + files: dict[str, str] = {} + hashes: dict[str, str] = {} + + def read(path: str) -> str: + item = fetch("contents/" + path + "?ref=" + revision) + require(isinstance(item, dict) and item.get("type") == "file" + and item.get("encoding") == "base64", "missing canonical release file: " + path) + content = item.get("content") + require(isinstance(content, str), "invalid release file content: " + path) + raw = base64.b64decode("".join(content.split()), validate=True) + blob = hashlib.sha1(b"blob " + str(len(raw)).encode() + b"\0" + raw).hexdigest() + require(blob == item.get("sha"), "release file Git identity mismatch: " + path) + files[path] = raw.decode("utf-8") + hashes[path] = hashlib.sha256(raw).hexdigest() + return files[path] + + package = read("VERSION").strip() + require(package == tag.removeprefix("v"), "Agnir tag and package VERSION disagree") + values = agnir_adapter()._parse_discovery(read("AGNIR.yaml")) + core, profile = values.get(("agnir", "version")), values.get(("agnir", "discovery_profile")) + require(core in agnir_adapter()._SUPPORTED_PROFILES + and profile == agnir_adapter()._SUPPORTED_PROFILES[core], + "latest stable Core/profile is unsupported; do not downgrade or initialize") + # Paths belong to the supported contract lines, not the package patch number. + suffix = {"0.1": "", "0.2": "_0_2", "1.0": "_1_0"}[core] + read("SKILL.md") + read("RELEASE.md") + read("spec/AGNIR_CORE" + suffix + ".md") + read("profiles/REPOSITORY_FILESYSTEM" + suffix + ".md") + schema_suffix = "" if core == "0.1" else "-" + core + read("schemas/agnir-manifest" + schema_suffix + ".schema.json") + entries = fetch("contents?ref=" + revision) + require(isinstance(entries, list), "cannot inspect canonical activation files") + activation = "AGNIR.md" if any(e.get("path") == "AGNIR.md" and e.get("type") == "file" + for e in entries if isinstance(e, dict)) else "README.md" + read(activation) + receipt = {"source": "iorLab/agnir", "release": package, "tag": tag, + "revision": revision, "release_id": release.get("id"), + "published_at": published, "resolved_at": datetime.now(timezone.utc).isoformat(), + "core": core, "profile": profile, "activation": activation, + "files_sha256": hashes} + return receipt, files + + +def validate_agnir_binding(project: Path, *, stable: dict | None = None): + """Read-only assertion: bind to existing truth, or independently resolved stable. + + No latest lookup is made for an existing Project. Structural assertions here + do not replace the installed Skill's actual activation/behavior observations. + """ + adapter = agnir_adapter() + values = adapter._parse_discovery((project / "AGNIR.yaml").read_text(encoding="utf-8")) + binding = adapter._parse_discovery((project / "SVIF.yaml").read_text(encoding="utf-8")) + identity = values.get(("project", "identity")) + core, profile = values.get(("agnir", "version")), values.get(("agnir", "discovery_profile")) + require(isinstance(identity, str) and bool(identity.strip()), "missing Agnir Project identity") + require(core in adapter._SUPPORTED_PROFILES and profile == adapter._SUPPORTED_PROFILES[core], + "unsupported or inconsistent existing Agnir Core/profile; preserve it and stop") + require(binding.get(("svif", "manifest")) == "project-binding/0.2" + and binding.get(("project", "identity")) == identity + and binding.get(("bindings", "continuity", "provider")) == "agnir" + and binding.get(("bindings", "continuity", "compatibility")) == core + and binding.get(("bindings", "continuity", "profile")) == profile + and binding.get(("bindings", "continuity", "config", "discovery")) == "AGNIR.yaml", + "Svif/Agnir Project binding does not match exactly") + if stable is not None: + require((core, profile) == (stable["core"], stable["profile"]), + "new Project did not use independently resolved latest stable Core/profile") + for key, expected in (("source", "iorLab/agnir"), ("release", stable["release"]), + ("applied_revision", stable["revision"])): + require(values.get(("extensions", "agnir/operations", key)) == expected, + "new Project lacks exact applied Agnir release provenance: " + key) + activation = stable["activation"] + require((project / activation).is_file() + and activation in (project / "AGENTS.md").read_text(encoding="utf-8"), + "new Project has no selected-release activation locator") + return adapter(project, expected_core_version=core, expected_profile=profile), identity + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, required=True, help="isolated acceptance directory outside the source repo") @@ -233,6 +366,16 @@ def main() -> int: return 0 report["model_exercise"] = "in-progress" + # Same-operation trusted lookup; no preinitialized Project or stale cache. + stable, release_files = resolve_latest_agnir() + report["agnir_bootstrap_target"] = stable + release_root = session_dir / "agnir-release" + release_root.mkdir() + for name, data in release_files.items(): + destination = release_root / name + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(data.encode("utf-8")) + (release_root / "resolved-release.json").write_text(json.dumps(stable, indent=2), encoding="utf-8") token = "svif-local-" + uuid.uuid4().hex content = token + "\n" digest = hashlib.sha256(content.encode()).hexdigest() @@ -242,20 +385,16 @@ def main() -> int: thread1, _ = client.exercise(project, f"Use Svif for this selected ordinary Project. Create RESULT.md containing exactly {token!r} followed by one newline. " "Verify the real file and checkpoint completion with no remaining task. Preserve existing README and AGENTS instructions. " + f"A trusted latest-stable Agnir lookup for this bootstrap operation is available at {release_root}. " + "Read its resolved-release.json, SKILL.md and selected contracts. These are installer source, " + "not this Project's identity or memory. Use them without making network requests. " "Do not access network providers or publish anything.", skill, model=args.model) require((project / "RESULT.md").read_bytes() == content.encode(), "native task result bytes do not match") - sys.path.insert(0, str(ROOT / "src")) - from svif.continuity.agnir import AgnirFilesystemContinuityProvider - provider = AgnirFilesystemContinuityProvider(project) - values = provider._parse_discovery((project / "AGNIR.yaml").read_text(encoding="utf-8")) - identity = values.get(("project", "identity")) - require(isinstance(identity, str) and bool(identity), "native bootstrap did not establish Project identity") + provider, identity = validate_agnir_binding(project, stable=stable) snapshot = provider.load(identity) require(bool(snapshot.state) and bool(snapshot.next_actions) and bool(snapshot.evidence), "native checkpoint is incomplete") require(readme.strip() in (project / "README.md").read_text(encoding="utf-8"), "README original content was destroyed") require(agents.strip() in (project / "AGENTS.md").read_text(encoding="utf-8"), "AGENTS original content was destroyed") - svif = provider._parse_discovery((project / "SVIF.yaml").read_text(encoding="utf-8")) - require(svif.get(("project", "identity")) == identity, "Svif/Agnir identity mismatch") before = file_map(project) phase2 = session_dir / "cold-resume" phase2.mkdir() diff --git a/checks/check_repository.py b/checks/check_repository.py index a866147..d099890 100755 --- a/checks/check_repository.py +++ b/checks/check_repository.py @@ -350,7 +350,7 @@ def main() -> None: ("## Architecture Diagram", "## Runtime / Operation Flow"), architecture_markers=( "non-destructive first-use setup", "EDIT: add activation locator only", - "EDIT: add Agnir instructions only", "ADD: founding continuity", "ADD: Project binding", + "EDIT: add compatibility locator only", "AGNIR.md", "ADD: founding continuity", "ADD: Project binding", "Svif Orchestrator", "Continuity Provider", "Execution integration", "Capability Providers", ), runtime_forbidden_markers=("EDIT: add", "ADD: founding", "ADD: Project binding"), @@ -360,7 +360,7 @@ def main() -> None: ("## 架构图", "## 运行流程"), architecture_markers=( "非破坏性 first-use setup", "编辑:仅添加 activation locator", - "编辑:仅添加 Agnir instructions", "新增:founding continuity", "新增:Project binding", + "编辑:仅添加兼容入口", "AGNIR.md", "新增:founding continuity", "新增:Project binding", "Svif 编排器", "项目连续性提供者", "执行环境适配层", "能力提供层", ), runtime_forbidden_markers=("编辑:仅添加", "新增:founding", "新增:Project binding"), diff --git a/conformance/RELEASE_READINESS.md b/conformance/RELEASE_READINESS.md index 723f5ab..00c175b 100644 --- a/conformance/RELEASE_READINESS.md +++ b/conformance/RELEASE_READINESS.md @@ -30,7 +30,7 @@ Evidence in `.agnir/` records the actually observed candidate, host and results. | Portable distribution and exact package bytes | package/component tests; builder + negative archive tests | Invalid output paths, links, limits and I/O failure do not corrupt source or prior ZIP. Source/installed file identities must match. | | Native install + enabled + Skill discovery | `checks/check_local_install.py`; real Codex CLI/app-server | Requires actual native `plugin add`, `plugin list`, `skills/list` and installed-file hash equality. Unit mocks do not count. | | Ordinary Project first-use, actual work, checkpoint and fresh-context resume | installed `plugin/skills/svif/SKILL.md`; native `--exercise` | Existing Agnir Projects must preserve their declared compatibility; genuinely new Projects must resolve the latest published stable Agnir and use that release's declared Core/profile. Requires model authentication in isolated home and independent result-file checks. Not proven by a Skill-text assertion. | -| Existing Project idempotency and instruction preservation | installed Skill; native `--exercise` | Separate fresh thread preserves identity, original instructions and unchanged completed memory. | +| Existing Project idempotency and instruction preservation | installed Skill; native `--exercise` | Separate fresh thread preserves identity, original instructions and unchanged completed memory. Also exercise existing 0.1/0.2/1.0 Projects independently, with no implicit package upgrade or latest lookup. | | Broken discovery, another provider and failure-path host behavior | negative scenarios below | Must be observed with the same installed candidate; kernel unit tests are not a substitute for Skill adherence. | ## Reproducible native acceptance @@ -70,6 +70,23 @@ On PowerShell set `$env:CODEX_HOME` to that directory for the login command. The itself sets CODEX_HOME explicitly. `--exercise` uses the signed-in account's quota. It creates only isolated dummy Projects and does not authorize deployment/publication. +For a new Project, the trusted harness now resolves `iorLab/agnir/releases/latest` +once in this operation, rejects unpublished/prerelease or unsupported targets, resolves +the tag to an exact commit, and verifies each retrieved contract file's Git blob identity. +It reads package `VERSION` separately from the release-declared Core/profile. Required +installer/contracts/schema and a timestamped `resolved-release.json` are staged outside +the target Project. The model reads that freshly resolved source via local tools; the +sandbox remains network-restricted and no target continuity is preinitialized by the +harness. Failed upstream lookup is a blocker, never an old-version fallback. + +The independent post-bootstrap checker requires exact Agnir/Svif compatibility/profile, +Project identity, selected-release activation locator and applied package/revision +provenance. Existing Project validation does not query latest or demand missing optional +historical provenance. Unit fixtures cover existing 0.1/0.2/1.0 Projects, version/binding +conflicts, wrong latest/fallback/provenance and unavailable upstream; these are checker +regressions, not evidence that a model has executed those cases. The no-auth install +path does not fetch Agnir and continues to leave model acceptance unpassed. + The positive exercise uses three distinct native app-server processes and thread IDs: ordinary-Project bootstrap and concrete file task; read-only cold reconstruction with no old transcript, expected file contents or expected digest supplied to that session; diff --git a/plugin/skills/svif/SKILL.md b/plugin/skills/svif/SKILL.md index d1d964d..65b8aa8 100644 --- a/plugin/skills/svif/SKILL.md +++ b/plugin/skills/svif/SKILL.md @@ -23,7 +23,7 @@ If `AGNIR.yaml` exists or durable Project artifacts clearly establish an Agnir b 2. Dispatch according to the compatibility identifiers the Project actually declares. A valid Core/profile `0.1` Project remains `0.1`; a valid `0.2` Project remains `0.2`; a valid `1.0` Project remains `1.0`. The same rule applies to future supported `x.y` lines. Installing or discovering a newer Agnir distribution is **not** authorization to migrate or relabel compatibility. 3. If `SVIF.yaml` is absent and Svif setup is authorized, create a minimal `project-binding/0.2` binding that reuses the exact Agnir Project identity and binds `continuity.provider: "agnir"`, `compatibility` equal to the existing `agnir.version`, `profile` equal to the existing `agnir.discovery_profile`, and discovery `"AGNIR.yaml"`. 4. If `SVIF.yaml` already exists, require its Agnir compatibility/profile/identity to agree with the existing Agnir Project. A mismatch is a repair/binding case, not permission to choose whichever version is newer. -5. An operational Agnir package upgrade and a Core/profile compatibility migration are different operations. Neither is implied by installing or invoking Svif. +5. An operational Agnir package upgrade and a Core/profile compatibility migration are different operations. Neither is implied by installing or invoking Svif. Normal binding/resume of an existing Project does not require a latest-release lookup. Preserve its recorded operational release/provenance as well; absent optional package provenance is not evidence of an uninitialized Project. Do not treat partial or contradictory Agnir/Svif artifacts as a clean first-use bootstrap. If durable surfaces show that the Project already intends to use Agnir but activation/discovery is incomplete, broken, unsupported, or inconsistent, enter repair and preserve the applicable Agnir failure class. Do not silently replace that Project with a newly initialized latest-version Project. @@ -45,6 +45,8 @@ For a genuinely new repository/filesystem Project: 6. Run the fresh activation route required by the resolved Agnir release, then validate that `SVIF.yaml` identifies the same Project and exact continuity compatibility/profile. 7. Once bootstrap passes, continue the user's original Project task in the same operation. Do not make the user issue a separate Agnir initialization prompt and do not stop merely because the Project started without Agnir. +A trusted integration may supply a fresh, same-operation canonical release-resolution receipt and exact source files through authorized local tools. Verify their tag/revision and contract provenance; this is not permission to substitute an old cached version. Before writing any Project files, confirm the selected adapter supports the resolved latest Core/profile. If unsupported, stop without initializing or selecting an older release. + The released `v0.2.0-preview.1` artifact is immutable historical evidence and still contains its original Core/profile `0.1` first-use procedure. Do not move or rewrite that tag. The current unpublished `0.2.0` line deliberately supersedes that historical bootstrap rule: existing Projects preserve their declared Agnir compatibility, while genuinely new Projects resolve the latest published stable Agnir at bootstrap time. This bootstrap behavior MUST NOT require a preinstalled Agnir Skill, a previous Agnir installation conversation, or predecessor-private memory. Resolving latest stable may read canonical Agnir release and contract artifacts through available authorized tools. If that resolution is unavailable, surface the blocker instead of inventing a version. A compatible surface may delegate installation to an available Agnir installer, but Svif remains responsible for selecting the correct existing-vs-new branch and verifying the resulting Project binding. @@ -64,6 +66,7 @@ Whichever canonical instruction surface applies, it must state that the Project The fact that the current Agent can directly open `AGNIR.yaml` MUST NOT be used to bypass a missing, stale, contradictory, or predecessor-private activation route or to claim that a fresh Agent can resume from the Project root. A non-Agent Executor or trusted adapter already given the applicable profile implementation may begin discovery at `AGNIR.yaml` as the profile permits; that exception does not silently redefine Agent activation. `AGENTS.md` remains locator-only. When installing or repairing its Agnir locator, make the smallest locator-only merge: do not delete, reorder, normalize, summarize, or otherwise rewrite unrelated Project-owned instructions merely to install or repair Agnir. If an equivalent Agnir locator already exists, keep the operation idempotent rather than adding another copy. If resolving a material conflict would require deleting, overriding, or reinterpreting an existing Project instruction, surface the conflict to the Principal and do not report Agent activation healthy until it is explicitly resolved and a fresh activation test passes. The selected canonical activation surface—legacy README procedure or current `AGNIR.md` procedure—owns the full Agnir Project instructions. + Before following that route, require the authorized Project Entry Point or trusted binding context to select exactly one Project root. If multiple candidate Project roots exist and no authority rule selects one, surface `AGNIR_DISCOVERY_AMBIGUOUS` rather than choosing the most convenient candidate. Once one root is authoritatively selected, a parent or child Project with its own `AGNIR.yaml` does not make that selected root ambiguous and MUST NOT be searched as a replacement. Before resolving a Discovery Record, select the discovery profile/adapter convention applicable to the authorized Project Entry Point from trusted integration or binding context. The Discovery Record may declare its profile for compatibility checking, but it MUST NOT bootstrap authority by choosing the adapter/convention used to discover or interpret itself. If no applicable profile can be selected safely, surface the discovery/compatibility blocker rather than guessing from nearby files or model memory. diff --git a/tests/test_local_acceptance_harness.py b/tests/test_local_acceptance_harness.py index cd466a2..fa01e82 100644 --- a/tests/test_local_acceptance_harness.py +++ b/tests/test_local_acceptance_harness.py @@ -35,3 +35,165 @@ def test_file_map_detects_changed_bytes_and_rejects_symlinks(self): self.skipTest("symlinks unavailable") with self.assertRaises(RuntimeError): module.file_map(root) + + +class BootstrapVersionAcceptanceTests(unittest.TestCase): + """Executable checker regressions, not fabricated native-model observations.""" + @staticmethod + def release_fixture(package="1.0.2", core="1.0"): + import base64 + import hashlib + import json + sha = "a" * 40 + files = { + "VERSION": package + "\n", + "AGNIR.yaml": f'agnir:\n version: "{core}"\n discovery_profile: "repository-filesystem/{core}"\n', + "SKILL.md": "# Agnir fixture installer\n", + "RELEASE.md": "# Fixture release\n", + "AGNIR.md": "# Fixture activation\n", + "spec/AGNIR_CORE_1_0.md": "# Core fixture\n", + "profiles/REPOSITORY_FILESYSTEM_1_0.md": "# Profile fixture\n", + "schemas/agnir-manifest-1.0.schema.json": "{}\n", + } + replies = { + "releases/latest": {"id": 123, "tag_name": "v" + package, "draft": False, + "prerelease": False, "published_at": "2026-01-01T00:00:00Z"}, + "commits/v" + package: {"sha": sha}, + "contents?ref=" + sha: [{"path": "AGNIR.md", "type": "file"}], + } + for name, text in files.items(): + raw = text.encode() + replies["contents/" + name + "?ref=" + sha] = { + "type": "file", "encoding": "base64", "content": base64.b64encode(raw).decode(), + "sha": hashlib.sha1(b"blob " + str(len(raw)).encode() + b"\0" + raw).hexdigest(), + } + calls = [] + def fetch(path): + calls.append(path) + return json.loads(json.dumps(replies[path])) + return replies, calls, fetch + + @staticmethod + def project_fixture(root, core="1.0", provenance=True): + from test_agnir_continuity import write_project, PROJECT + write_project(root, version=core) + (root / "SVIF.yaml").write_text( + 'svif:\n manifest: "project-binding/0.2"\n' + f'project:\n identity: "{PROJECT}"\n' + 'bindings:\n continuity:\n provider: "agnir"\n' + f' compatibility: "{core}"\n profile: "repository-filesystem/{core}"\n' + ' config:\n discovery: "AGNIR.yaml"\n', encoding="utf-8") + (root / "AGNIR.md").write_text("# Installed activation\n", encoding="utf-8") + (root / "AGENTS.md").write_text("Read AGNIR.md.\n", encoding="utf-8") + if provenance: + with (root / "AGNIR.yaml").open("a", encoding="utf-8") as file: + file.write('extensions:\n agnir/operations:\n source: "iorLab/agnir"\n' + ' release: "1.0.2"\n applied_revision: "' + "a" * 40 + '"\n') + + def test_latest_resolution_pins_all_contract_files_and_separates_version_layers(self): + for package in ("1.0.2", "1.1.7"): + with self.subTest(package=package): + _, calls, fetch = self.release_fixture(package=package) + receipt, files = module.resolve_latest_agnir(fetch) + self.assertEqual((receipt["release"], receipt["core"]), (package, "1.0")) + self.assertEqual(receipt["profile"], "repository-filesystem/1.0") + self.assertEqual(receipt["activation"], "AGNIR.md") + self.assertEqual(set(receipt["files_sha256"]), set(files)) + self.assertTrue(all(call.endswith("?ref=" + "a" * 40) + for call in calls if call.startswith("contents"))) + + def test_latest_rejects_prerelease_branch_unpublished_and_missing_metadata(self): + for mutation in ({"prerelease": True}, {"draft": True}, {"published_at": None}, + {"tag_name": "main"}, {"tag_name": "v1.1.0-rc.1"}, + {"published_at": "2999-01-01T00:00:00Z"}): + with self.subTest(mutation=mutation): + replies, calls, fetch = self.release_fixture() + replies["releases/latest"].update(mutation) + with self.assertRaises((RuntimeError, ValueError)): + module.resolve_latest_agnir(fetch) + self.assertEqual(calls, ["releases/latest"]) + + def test_resolution_failure_never_uses_a_cached_or_older_baseline(self): + from unittest.mock import Mock + fetch = Mock(side_effect=OSError("no upstream access")) + with self.assertRaises(OSError): + module.resolve_latest_agnir(fetch) + fetch.assert_called_once_with("releases/latest") + + def test_latest_unsupported_line_fails_before_install_contract_or_project_writes(self): + _, calls, fetch = self.release_fixture(core="9.9") + with self.assertRaisesRegex(RuntimeError, "unsupported"): + module.resolve_latest_agnir(fetch) + self.assertFalse(any("SKILL.md" in call for call in calls)) + + def test_changed_tag_package_or_content_identity_cannot_pass(self): + for kind in ("commit", "version", "blob"): + with self.subTest(kind=kind): + replies, _, fetch = self.release_fixture() + if kind == "commit": + replies["commits/v1.0.2"]["sha"] = "main" + elif kind == "version": + replies["releases/latest"]["tag_name"] = "v1.0.3" + replies["commits/v1.0.3"] = {"sha": "a" * 40} + else: + replies["contents/SKILL.md?ref=" + "a" * 40]["sha"] = "b" * 40 + with self.assertRaises(RuntimeError): + module.resolve_latest_agnir(fetch) + + def test_existing_projects_preserve_all_supported_lines_without_latest_lookup(self): + from unittest.mock import patch + for core in ("0.1", "0.2", "1.0"): + with self.subTest(core=core), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.project_fixture(root, core=core, provenance=False) + before = module.file_map(root) + with patch.object(module, "canonical_agnir_json", side_effect=AssertionError("unexpected lookup")): + provider, identity = module.validate_agnir_binding(root) + self.assertEqual(module.file_map(root), before) + self.assertEqual(provider.expected_core_version, core) + self.assertEqual(provider.load(identity).project_identity, identity) + + def test_existing_binding_conflicts_block_without_reinitialization(self): + for old, new in (('provider: "agnir"', 'provider: "other"'), + ('compatibility: "1.0"', 'compatibility: "0.1"'), + ('identity: "urn:test:svif-project"', 'identity: "foreign"'), + ('profile: "repository-filesystem/1.0"', 'profile: "repository-filesystem/0.1"')): + with self.subTest(old=old), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.project_fixture(root) + path = root / "SVIF.yaml" + path.write_text(path.read_text().replace(old, new), encoding="utf-8") + before = module.file_map(root) + with self.assertRaises(RuntimeError): + module.validate_agnir_binding(root) + self.assertEqual(module.file_map(root), before) + + def test_fresh_project_must_match_resolved_stable_and_exact_provenance(self): + _, _, fetch = self.release_fixture() + stable, _ = module.resolve_latest_agnir(fetch) + for core, provenance, passed in (("1.0", True, True), ("1.0", False, False), + ("0.1", True, False), ("0.2", True, False)): + with self.subTest(core=core, provenance=provenance), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.project_fixture(root, core=core, provenance=provenance) + before = module.file_map(root) + if passed: + module.validate_agnir_binding(root, stable=stable) + else: + with self.assertRaises(RuntimeError): + module.validate_agnir_binding(root, stable=stable) + self.assertEqual(module.file_map(root), before) + + def test_fresh_activation_or_release_provenance_mismatch_fails(self): + _, _, fetch = self.release_fixture() + stable, _ = module.resolve_latest_agnir(fetch) + for path, old, new in (("AGENTS.md", "AGNIR.md", "README.md"), + ("AGNIR.yaml", 'release: "1.0.2"', 'release: "1.0.1"'), + ("AGNIR.yaml", "a" * 40, "b" * 40)): + with self.subTest(path=path, old=old), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.project_fixture(root) + file = root / path + file.write_text(file.read_text().replace(old, new), encoding="utf-8") + with self.assertRaises(RuntimeError): + module.validate_agnir_binding(root, stable=stable) diff --git a/tests/test_plugin_first_use_bootstrap.py b/tests/test_plugin_first_use_bootstrap.py index 59b2b23..6a24d67 100644 --- a/tests/test_plugin_first_use_bootstrap.py +++ b/tests/test_plugin_first_use_bootstrap.py @@ -72,6 +72,18 @@ def test_activation_dispatch_preserves_installed_contract(self) -> None: ): self.assertIn(marker, text) + def test_existing_resume_is_offline_and_new_unsupported_latest_does_not_downgrade(self) -> None: + text = SKILL.read_text(encoding="utf-8") + for marker in ( + "does not require a latest-release lookup", + "Preserve its recorded operational release/provenance", + "absent optional package provenance is not evidence of an uninitialized Project", + "fresh, same-operation canonical release-resolution receipt", + "Before writing any Project files", + "If unsupported, stop without initializing or selecting an older release", + ): + self.assertIn(marker, text) + def test_preview_history_does_not_pin_current_bootstrap(self) -> None: text = SKILL.read_text(encoding="utf-8") self.assertIn("released `v0.2.0-preview.1` artifact is immutable historical evidence", text) From 7a62052994815213a39f8077a0a325ea5580dd92 Mon Sep 17 00:00:00 2001 From: mattamior Date: Sun, 20 Sep 2026 23:53:56 +0800 Subject: [PATCH 5/5] checkpoint: accept bootstrap selection checks and exact native receipts Record the new Plugin identity, 141 passing tests, green eight-job PR CI, downloaded Linux/macOS native results, actual upstream resolution and non-conflated auxiliary failures. Preserve pending authenticated model acceptance and paused publication. No product or Plugin bytes changed from the tested candidate. --- .../2026-09-20-bootstrap-version-selection.md | 86 ++++++++++++++++++- .agnir/next-actions.md | 41 ++++++--- .agnir/state.md | 52 ++++++----- 3 files changed, 144 insertions(+), 35 deletions(-) diff --git a/.agnir/evidence/2026-09-20-bootstrap-version-selection.md b/.agnir/evidence/2026-09-20-bootstrap-version-selection.md index 7dc62d8..3d7837e 100644 --- a/.agnir/evidence/2026-09-20-bootstrap-version-selection.md +++ b/.agnir/evidence/2026-09-20-bootstrap-version-selection.md @@ -1,6 +1,6 @@ # Svif first-use Agnir version-selection repair — 2026-09-20 -Status: implementation candidate on `fix/bootstrap-agnir-version-selection`; OpenAI Platform publication remains paused. +Status: version-selection implementation plus executable checker and native installation gates accepted for the PR #13 candidate; authenticated model behavior and whole-release sign-off remain pending. OpenAI Platform publication remains paused. Principal correction: Svif must not pin all new/current Projects to Agnir Core/profile `0.1`. Existing Project truth wins. A valid existing Agnir Project keeps the Core/profile it declares; adding Svif creates/validates a matching continuity binding and does not authorize compatibility migration. Partial or contradictory Agnir artifacts remain repair cases. @@ -9,3 +9,87 @@ Only a genuinely uninitialized Project resolves the canonical latest published s Current external observation on 2026-09-20: `iorLab/agnir` latest published stable is repository release `v1.0.2` at revision `b5626394ec40a5cb7a28c01892acde07cc0adc8e`; that release still declares Core `1.0` and `repository-filesystem/1.0`. This receipt is evidence of today's resolved stable, not a hard-coded future bootstrap constant. Future first-use operations must resolve latest stable again. The immutable Svif `v0.2.0-preview.1` release is not modified. It remains historical evidence with its original `0.1` bootstrap behavior. The current unpublished `0.2.0` Skill is the first Svif line to supersede that rule. + +## Completed acceptance implementation + +The initial selection rule reached main at `9501fd7ed0843f46882ee63ed9c19cd2759a7cb2` +(run `35519581944`, success). Follow-up inspection found that the native exercise still +prohibited model network lookup and only compared Project identity after bootstrap; +it did not verify that the new Project matched an independently resolved latest stable. +The actual Agnir adapter already supports 0.1/0.2/1.0; no runtime default change was needed. + +The completed candidate is `08f2c7c7581edb8845cea161b971a0c4dca2de4d` on temporary +`fix/bootstrap-acceptance`, PR #13. Exact tree: +`3c053b2371f0fe916379ac7ea2f74ce1eb4bad3f`. +Exact Plugin tree: `77953ba954b2c0f3ff7dcc6f9c7814df5fa05e12`. + +`checks/check_local_install.py` now resolves latest stable only for a new-Project model +exercise, verifies release/tag/commit and each source file's Git blob hash, obtains the +matching installer/Core/profile/schema/activation files and provides the model a fresh +same-operation source snapshot outside the still-uninitialized Project. It does not +relax the model sandbox or copy the source repository's Project identity/memory. The +independent result checker enforces Agnir/Svif identity and compatibility/profile, +applied package/source/revision provenance and selected-release activation locator. +The no-auth install path performs no Agnir lookup. Existing-Project validation is +read-only, supports 0.1/0.2/1.0 and does not require optional historical package provenance. +Unavailable or unsupported latest stops; it is never replaced with an older release. + +The shared Skill, bilingual first-use diagrams, repository map and acceptance matrix +agree with these rules. This repository's existing Agnir activation, compatibility, +identity, lineage, selector, memory locators and applied v1.0.0 provenance are preserved. + +## Verification receipts and boundaries + +- Local full suite: 141 tests, success. Repository integrity, portable contracts and + diff checks pass. New executable fixtures cover stable/package version separation, + old-Project preservation, binding/provenance mismatches, RC/unpublished targets, + unsupported compatibility, corrupt source and unavailable latest. Fixtures are + checker regression evidence, not native-model behavior. +- PR run `35520792408`: all eight jobs succeeded for the candidate, including runtime + on Linux (Python 3.12/3.13), macOS (3.12) and Windows (3.12), plus repository integrity, + portable contracts and native Codex installation/discovery on Linux/macOS. +- Native synthetic merge checkout: `ddbc9b1d51a8701f51ee83c9cde6152d0e559384`. + Linux artifact `10608285647`, outer digest + `5102d99af3518ce903092aaf5ff4f6da428dd6ac1582a5dd49b7f2351919dea1`; + macOS artifact `10607959357`, outer digest + `024b0ac82e0df4416b608c55ee347af145460fac557c9d46d1e0cc0efe4b8efc`. + Both were downloaded and compared to ALL candidate Plugin source file hashes. +- Actual host: Codex CLI 0.155.1 on Linux and macOS. Installation, enabled state and + installed Skill discovery passed. Package-file-map SHA-256: + `4e6ccce8405606f260d010d058622b1f32eeafdb59501658ea75d971a621ed81`. + Both reports retain `model_exercise: not-run`, `complete_release_acceptance: false`. +- Exact generated inner `svif-0.2.0.zip` SHA-256: + `08ad8bb13657c359d799d66c9adafcd552f8d6afdd27ed1ad96d4e34e093911b`. + Its regular-file map was independently compared byte-for-byte to this Plugin tree. + +## Actual upstream observation and auxiliary workflow failures + +One-shot validation run `35520533567` passed 141 tests and both structural checks, +resolved actual canonical Agnir v1.0.2, built the exact ZIP and materialized the tested +Git tree. Its final summary command incorrectly used `git rev-parse :plugin` and failed. +The run as a whole is a failure, NOT a green pipeline claim. Artifact `10607369559` +retains the real tests, live-resolution receipt, ZIP and checksum. Outer artifact digest: +`35970da37830902b13034c56060ba3bd4af62eaa91df78bd9e7e33d9c8f83857`. + +The live resolver observed at `2026-09-20T15:44:51.613206+00:00`: +release `v1.0.2`, publication `2026-09-18T14:14:18Z`, release ID `391529372`, exact commit +`b5626394ec40a5cb7a28c01892acde07cc0adc8e`, Core `1.0`, +profile `repository-filesystem/1.0`, activation `AGNIR.md`. Eight source files were +retrieved from that exact commit and verified. This is today's source observation, +not a pinned future default and not proof of a model executing the installer. + +After correcting the summary command, run `35520615978` passed tests but encountered +GitHub public API HTTP 403 rate limiting at latest lookup. It stopped as designed, +without fallback or target initialization. This auxiliary run also remains a failure. +The completed tree was independently read back through the authorized GitHub connector +and validated by the separate fully green PR suite. The one-shot transfer/verification +workflow is absent from the accepted product tree; it is not a new runtime dependency. + +## Outstanding release gate + +Authenticated positive/negative native behavior on this exact changed Skill still +needs actual observations. Existing-Project versions must remain unchanged in those +exercises; new Projects must match stable resolved in that operation. Passing source, +checker, archive or no-auth install tests cannot replace these observations. +No v0.2.0 tag, public Release, Platform submission/Publish or live Cloudflare effect is +created or authorized. Released Preview history remains immutable. diff --git a/.agnir/next-actions.md b/.agnir/next-actions.md index aef6cc8..fc42905 100644 --- a/.agnir/next-actions.md +++ b/.agnir/next-actions.md @@ -7,24 +7,37 @@ and native install/discovery now have passing evidence; do not repeat the old cl that the four audit findings are unrepaired or that publisher prerequisites are the P0. Full current-version Skill effectiveness and whole-release sign-off remain open. -1. **Accept the new bootstrap-version-selection Plugin subject before authenticated behavior.** The current Skill now preserves an existing Project's declared Agnir Core/profile and resolves latest published stable Agnir only for a genuinely uninitialized Project. This changes Plugin bytes, so tree `7cc90517013306181a4df2238f849b85cf716665` / run `35509417968` remains historical for the prior candidate. Run full CI and `checks/check_local_install.py --output ` on the exact new candidate; require installed+enabled status, exact installed bytes and native Skill discovery on supported hosts before using `--exercise`. Then use the operator's authorized Codex login in the isolated CODEX_HOME and perform ordinary-Project bootstrap/task/checkpoint, fresh process + new conversation recovery, and unchanged existing-Project reuse. Do not copy, request or commit secret token values. +1. **Complete authenticated native behavior on the accepted current candidate.** + Use a fixed reviewed checkout whose Plugin tree is + `77953ba954b2c0f3ff7dcc6f9c7814df5fa05e12`. PR #13 candidate + `08f2c7c7581edb8845cea161b971a0c4dca2de4d` passed all eight checks in + `35520792408`; downloaded Linux/macOS Codex 0.155.1 reports confirm exact installed + bytes, enabled state and Skill discovery. This new no-auth gate is no longer pending. + With the operator's authorized login in isolated CODEX_HOME, run + `checks/check_local_install.py --output --exercise`. + New-Project testing resolves the latest published stable Agnir in that operation, + stages verified installer/contracts outside the Project, and independently checks + matching Core/profile, Svif binding and operational provenance. No preinitialized + continuity, cached fallback or relaxed model sandbox is allowed. Existing Projects + keep their actual version and valid activation; normal resume needs no latest lookup. + Exercise first-use task/checkpoint, fresh process + new conversation recovery and + unchanged reuse; also exercise existing 0.1/0.2/1.0 Projects independently. + Do not copy, request or commit secret token values. 2. **Exercise all same-candidate negative host scenarios.** Follow `conformance/RELEASE_READINESS.md`: broken discovery, other Continuity Provider, identity/version mismatch, failing required checks, absent authority, missing observation and interrupted write markers. Record prompts, actual tool behavior, - initial/resulting file hashes, host/version and classification. Kernel tests do not - substitute for installed-Skill adherence. Repair any observed failure before sign-off. -3. **Preserve the integrated engineering baseline and release limits.** PR #11 was - squash-merged to authoritative `main` as `9e8f602b3c9a9d4a1a3c674979fcbc6f5483f27a`. - The merge tree is `3b6d026b8d3b6b8281b9cc9f7d8e62fefc741361`, exactly the reviewed - candidate tree. Final PR run `35509842215` and authoritative-main run `35509894716` - passed all eight checks. The latter includes native Codex installation/discovery on - Linux and macOS, plus runtime checks on Linux/macOS/Windows. Plugin tree remains - `7cc90517013306181a4df2238f849b85cf716665`. Integration verification is complete; - the remaining P0 is real authenticated native positive/negative behavior, not merging - the already-integrated repair. Keep `spec/RUNTIME_SAFETY.md` limitations explicit. - Implementation evidence: `.agnir/evidence/2026-09-20-runtime-readiness-repair.md`. - Full release acceptance must stay unpassed until the remaining native gates close. + initial/resulting file hashes, host/version and classification. The 141 passing tests + include version-checker regressions, not fabricated model observations. Kernel tests + and native installation cannot substitute for installed-Skill adherence. +3. **Preserve current evidence and integration identity.** The bootstrap correction + and native receipts are in `.agnir/evidence/2026-09-20-bootstrap-version-selection.md`. + Candidate tree `3c053b2371f0fe916379ac7ea2f74ce1eb4bad3f` contains no temporary + workflow. Require the continuity-only PR checkpoint CI before integration and read + back authoritative main afterward. Keep `spec/RUNTIME_SAFETY.md` limitations explicit. + PR #11's older integrated runtime and Plugin receipt remain historical; they must not + replace the current Plugin identity. Full release acceptance stays unpassed until + authenticated positive and negative native behavior gates close. ## Deferred public/personal ChatGPT path diff --git a/.agnir/state.md b/.agnir/state.md index 7f24d5d..507a887 100644 --- a/.agnir/state.md +++ b/.agnir/state.md @@ -9,29 +9,41 @@ Agnir continuity on `main` remains canonical; staging is not a second authority. The Principal requested release-standard functional repair and local effectiveness; OpenAI Platform submission/Publish remains paused until an explicit new instruction. -**The four reproduced runtime blockers are repaired and cross-platform regression -checks pass. A new first-use version-selection repair is now under validation: existing -Agnir Projects preserve their declared Core/profile, while genuinely uninitialized -Projects resolve the canonical latest published stable Agnir. Because this changes Skill -bytes, the prior native install/discovery receipt remains historical for the preceding -candidate and must be rerun for the new candidate before model-driven release acceptance.** +**The four reproduced runtime blockers remain repaired. Project-aware bootstrap selection +and its executable acceptance checks now pass: existing Agnir Projects preserve their +own version; only genuinely uninitialized Projects resolve the latest published stable. +The changed Plugin has fresh Linux/macOS native installation/discovery receipts. +Authenticated model-driven effectiveness and whole-product release sign-off remain open.** -## Verified repair subject +## Verified current candidate - Product/package version: unpublished `0.2.0`; product and portable contracts remain `0.2`. -- Baseline: `960526544da308ea8b0eb1d325b26609487ab856`, retaining audited product code - `fe7788bd53d3a240f663860133b741799d0470e3`. -- Reviewed repair source: `225e535e32a18bf8db2bfc7d76efe6bed9378e97`, tree - `12542123c9ab47e5e938f8db8fd8a7d36f28a513`, PR #11. -- Prior repaired Plugin tree `7cc90517013306181a4df2238f849b85cf716665` passed native install/discovery and remains historical evidence. The bootstrap-version-selection change creates a new Plugin subject that requires its own exact tree and native receipt before acceptance. -- Local full suite: 128 tests pass on Linux/Python 3.13.5; repository integrity and - portable contracts pass. Tests execute real code, including process termination. -- PR candidate run `35509417968`: all eight jobs pass, including runtime on Linux - (Python 3.12/3.13), macOS (3.12), Windows (3.12), and native installation on Linux/macOS. -- Native Codex 0.155.1 reports installed+enabled and discovers `svif:svif` at the exact - installed cache path. All installed package file hashes equal the selected source. -- Native no-auth receipts remain distinct from model execution. No current-version - real model task/checkpoint/fresh-LLM-session or desktop GUI acceptance is claimed. +- Initial version-selection implementation reached main at + `9501fd7ed0843f46882ee63ed9c19cd2759a7cb2`; run `35519581944` passed. +- Completed checker/Skill candidate: `08f2c7c7581edb8845cea161b971a0c4dca2de4d`, + tree `3c053b2371f0fe916379ac7ea2f74ce1eb4bad3f`, PR #13. +- Exact current Plugin tree: `77953ba954b2c0f3ff7dcc6f9c7814df5fa05e12`. +- Local complete suite: 141 tests pass; repository integrity and portable contracts pass. +- PR run `35520792408` passed all eight checks: Linux Python 3.12/3.13, macOS 3.12, + Windows 3.12, repository integrity, portable contracts, native installation on Linux/macOS. +- Downloaded native reports from artifacts `10608285647` and `10607959357` confirm + Codex 0.155.1 installation, enabled state and exact installed Skill discovery. Every + installed file equals the candidate source; package-file-map SHA-256 is + `4e6ccce8405606f260d010d058622b1f32eeafdb59501658ea75d971a621ed81`. +- Native reports explicitly retain `model_exercise: not-run` and + `complete_release_acceptance: false`. No authenticated current-version task, + checkpoint/fresh-model-session, negative-host or desktop GUI acceptance is claimed. +- Actual stable lookup observed Agnir `v1.0.2` at + `b5626394ec40a5cb7a28c01892acde07cc0adc8e`, Core/profile `1.0`, on 2026-09-20. + This is a dated receipt, never a hard-coded future default. A subsequent HTTP 403 + lookup stopped safely; no old-version fallback was used. +- The one-shot transfer/verification workflow is absent from the product candidate. + +The earlier runtime repair was integrated by PR #11 at +`9e8f602b3c9a9d4a1a3c674979fcbc6f5483f27a`, with authoritative run `35509894716`. +Its Plugin tree `7cc90517013306181a4df2238f849b85cf716665` and 128-test/native receipts +remain historical and are not the identity of this changed candidate. Runtime code and +its safety contract were not changed by the bootstrap acceptance work. ## Implemented behavior