Skip to content

refactor(work-items): compose whole-Goal task maps in TypeScript - #6602

Merged
loopx-agent merged 7 commits into
loopx-project:mainfrom
hhyykk:codex/ts-goal-map-composition-20261016
Oct 17, 2026
Merged

loopx-agent merged 7 commits into
loopx-project:mainfrom
hhyykk:codex/ts-goal-map-composition-20261016

Conversation

@hhyykk

@hhyykk hhyykk commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

The whole-Goal work map split composition between native topology and Python map policy. This change moves map kind, metadata visibility, edge and header composition into the existing TypeScript owner, preserves the shared codecs that still have selected-work consumers, and completes the required source-envelope integration through the actual frontend and export.

Before: a successful delivery read exposed only its assembly time; the map and outer response lacked source freshness/coverage. After: the same readonly entry carries the existing kernel envelopes, discloses old/unreadable/partial or unknown sources, and offers same-context refresh. Existing task identities, relationships and authorization remain unchanged.

Related: #3225. Intended base: main. This completes the bounded map/read-consumer increment, not the whole migration RFC or Agent transfer.

Author Declaration

  • Original author declaration retained: model_agent — OpenAI Codex, GPT-6 family. Maintainer repairs: model_agent — OpenAI GPT-6.1-sol, runtime-reported.
  • Implemented against: docs/architecture/rfcs/typescript-control-plane-migration-v0.md at 83faa5bd9f7667ae104943feacdc1536a3d57f74, plus the existing graph and projection-envelope contracts.
Criterion Disposition Implementation / evidence
T3 implemented Existing projectGoalTaskMap composes the map; replaced Python map policy retired
§2.6 implemented Map seals source facts in-process; outer HTTP inherits status/map sources; actual UI/export consumes both
§5 implemented Shared codec callers/retirement condition and one additional coarse HTTP seal crossing documented
§6 implemented Current real File/SQLite, full-response parity, normal installs, state negatives, browser recovery and paired HTTP cost

Scope And Continuation

  • Reuse the existing topology/relation and envelope owners. Python retains actual IO and common public-safe source/text/state/identity codecs while other graph consumers still use them.
  • The executable request scope pins a code revision only; it caches no authority/provider state. The map stays one request. The complete HTTP read changes from 36 to 37 crossings because it now seals one outer aggregate.
  • Map completeness covers its declared Todo source/relations. Outer coverage covers the three delivered components and upstream declared scope; existing graph limits remain visible. Neither is an atomic cross-source snapshot, execution permission or Goal acceptance.
  • No new provider, service, setting, persistence or default-provider switch. Ordinary status does not fetch the cold map. Existing source-open and effect authorization owners remain unchanged.
  • Other selected-work sources, provider promotion and full Agent migration continue under their existing owners; no new prerequisite task is created here.

Validation

  • Tested head: 8d8f3c004f4ee9570ca218e0d3942aca5d6e2852.
  • Run state: finished. Input classes: synthetic, public_fixture.
  • 51 Python cases and 30 native map/envelope cases; frontend data/export/identity smoke; whole control-plane and frontend typechecks; semantic advisory/full smoke, bilingual RFC and public/private boundary checks pass.
  • Original integrated head 9a41c713a330fbd65e85d2fb001fbdf5ba415bcd returns HTTP200 and fails the independent missing-envelope assertion; the repaired head passes it. Stale, unreadable, unknown/truncated scope and replay are tested through the real producer/kernel; the packaged UI verifies visible recovery and downloaded metadata.
  • 1,056 complete HTTP observations: real File/SQLite × display present/missing × immutable Python baseline/current source/normal wheel/normal sdist, including 64 alternating timed samples per receiver and first/restart readback. Old domain fields and identities match; only the original root assembly clock and new envelopes are compared separately. Canonical Todo/provider revision, registry and display remain unchanged.
  • Normal wheel/sdist builds and fresh installs: six runtime files and 37 compiled UI files match source/archive/installed bytes. Generated bundles and private raw evidence are not committed.
  • Exact-scope CQR and native premerge pass: 6 direct + 19 selected checks, no failures or manual holds. The initial stale-CQR overall failure is retained; the unchanged source head was requalified after recording the current receipt. All seven contribution/merge commits have DCO.
Whole HTTP p95 (ms) Pinned Python baseline Source Normal wheel Normal sdist
File / display present 165.602 91.299 101.824 91.325
File / display missing 172.778 102.341 94.901 91.470
SQLite / display present 158.397 90.284 90.475 81.120
SQLite / display missing 206.022 103.404 107.023 104.910

Baseline: 83faa5bd9f7667ae104943feacdc1536a3d57f74; relevant map/status/runtime paths are unchanged through integrated main 90932447eb5ed38a0d3fec8c57e21a62f57f849c. Gains include executable-revision scope and are not attributed to language movement alone. Source native map p95 is 0.509–0.684 ms; this is separate from full HTTP evidence. Original 5%/25 ms requirements are retained.

CI was not queried or awaited under the configured policy. Windows, PostgreSQL, live models, installed-App adoption, sustained fleet behavior and new global cold-start/RSS qualification are not claimed. This read-model increment changes no PostgreSQL store or writer transaction. The existing 59 unproven semantic producer sites remain explicitly unproven.

Frontend / Visual Evidence

Before: the existing view retained a successful-looking assembly timestamp, and an unavailable map had no same-context retry. No normal viewport layout or navigation is redesigned. After, on the same existing entry: native source validity is visible, refresh recovers the map, and downloaded Markdown preserves old source time and both envelopes.

These are current packaged-source views at the tested implementation, using disposable synthetic File authority. The IO-failure/old-clock probes are explicit fixture hooks, not live outages. Keyboard recovery and 390px overflow were checked; no model call or user App upgrade occurred. The warning uses the existing notice style; the retry is essential recovery, with no extra setup or confirmation.

Current packaged desktop map

Unavailable source with same-context retry

Mobile stale-source disclosure

Shared-authority Fixture Impact

Read projection only: no authority field, transition, capacity/retention or provider promotion change. Existing production-scale native graph fixture remains; complete 12,000-row map admission and isolated canonical File/SQLite receiver arms cover the touched projection. PostgreSQL promotion and the legacy/file/PostgreSQL three-arm rehearsal are not claimed or required by this readonly composition boundary.

中文:地图组装归同一 TS owner,补齐真实来源封套、前端披露与原位恢复;原字段、身份与授权保持。正常包和四组完整 HTTP 对照通过,多一次外层封装调用已披露;这不等于完整 Agent 迁移或整个 RFC 完成。

Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com>
Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com>
@hhyykk

hhyykk commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Author validation — exact head 0441f5e600d8934a4e59d8a1b21a422510bade54

Baseline e11e859e8bc70bb8292b90c7523f05f8f604c16e → candidate 0441f5e600d8934a4e59d8a1b21a422510bade54. All reported final tests, packaging and timings follow the last rebase. This is a validation comment, not merge approval.

Execution receipts

Command for every source arm: uv run --extra test python qualify.py HEAD_CHECKOUT OUTPUT source-variants.json 64 using the two scripts below. The production entrypoint is actual HTTP GET /api/chat/delivery-review?goal_id=release-demo; the receiver asserts its imported distribution prefix before serving. Each fixture is initialized once through real File or SQLite authority and is shared by the two processes. Every response is compared as a full object; only root observed_at is removed. Stored timestamps and source/provider values are never normalized. Each fresh replay gets its own timestamped synthetic fixture, so its hashes may differ from this receipt; within that run base/head must match exactly.

Backend / display Exit base/head Fixture SHA-256 Normalized full HTTP SHA-256
file / current 0 / 0 2ba8aa5c097234dfc163b6c8bed54b3c56a6831756714ff855c7030db169820f 9eaa7b8ef57c41513e0972f787cfa4f83827a73772989e573b0110f85b3410e7
file / missing 0 / 0 fff75ac388ccbae7e12ed676d977c4e8264473a49b47c9a8232db6390101fe8a a6159f39e7904e342d75c7b6e4681304ac620e34abaf0118e462096b78150293
sqlite / current 0 / 0 150949190d5323cb3c6f1cb8f73c55b1511d89849a53640637b2c738d7e2d497 c4f2ab484859169e7f4c4e294099d9052233b573d8c029e611edc1c97d6f2f98
sqlite / missing 0 / 0 a32b13d9ee89d0cf4222993deaafbc372d9e53a8ecbb93d91d8a533d16dd9a5e a090c9b8d51155b99cff13bcf5f1bf1b4bedb844ebd38592e2879368a47d0d5d

Each arm: 64 pairs; one map request per HTTP on both revisions; 36 total runtime requests per HTTP on both; restart parity and unchanged logical canonical Todos/provider revision, registry and available Markdown bytes. Source, ordinary installed wheel and installed sdist also passed the same four-arm full-object comparison at samples=0; all six changed production files match source/archive/install bytes. The deliberate physical TS mutation gate ? "gate" → gate ? "deliverable" in a copy of this exact candidate failed the same HTTP harness: exit 1, AssertionError: file-current. Unmodified baseline/head exit 0. Environment startup failures were not counted as mutation sensitivity.

Migration economics (same final merge-base classification)

Field Final merge-base receipt
Canonical owner Before: TS owned Goal topology; Python owned map kind, metadata visibility and map assembly. After: projectGoalTaskMap composes those rules with the existing topology owner in-process. Python has no map-policy fallback. Shared source/text/state/SHA identity codecs remain single Python owners.
Legacy semantic code deleted task_graph.py: 66 gross deleted product lines, including adapter reshaping/moves; this is not 66 retired rule LOC. The removed rule/header blocks total 20 lines: 2 schema/cap constants, gate classification (1), identity-prefix choice (1), kind selection (3), metadata visibility (2), edge descriptors (3), public map return/header (8). Shared codec implementations are not counted as retired.
Bridge code added Conservative gross classification: 65 added product lines: 49 Python adapter lines in build_goal_task_map and imports, 13 native wire/identity descriptor lines (request/result tags, request validation, index and compatibility descriptors), 2 snapshot allowlist entries, 1 existing dispatcher replacement. This retains one compatibility adapter; it adds no effect name, daemon or per-field RPC.
Cross-runtime calls Actual GET /api/chat/delivery-review?goal_id=release-demo: map composition 1→1; 36→36 total runtime requests per HTTP in all four fixture arms. Empty/repeated/restarted map reads still use one composer request. Restart/readback parity passes; no write/replay transaction is introduced.
Product-code net change Six production files: +100/−68 = +32 lines. Validation separately: +353/−1 = +352. Docs separately: +24/−5 = +19. No generated file/dependency change. The temporary net increase is disclosed; policy deletion, not total LOC, is the payoff.
Migration scaffolding No product dual-run or retained Python oracle. Three test files pin durable public semantics. Disposable baseline, distribution and mutation harnesses are outside product code; the public replay recipe below is evidence, not a runtime dependency.
Facade exit build_goal_task_map remains for Chat delivery-review's source/renderability, public-safe text, shared _task_graph_todo_state and _task_graph_node_id. Delete it with the remaining build_task_graph_projection/shared Todo-source consumers once their exact text/state/stable-ID contracts have one native owner. Do not duplicate those codecs in this increment.
Correctness and performance Full real HTTP parity against immutable baseline, File/SQLite × present/missing Markdown, repeated reads, managed-runtime restart, unchanged canonical Todos/revision and source bytes; same harness rejects a physical gate→deliverable native mutation. Normal wheel/sdist and installed files match all six product sources. Matched HTTP and cold/warm/memory measurements below.

Performance receipt

Backend / display Baseline p50 / p95 ms Candidate p50 / p95 ms
file / current 188.317 / 219.908 66.660 / 91.262
file / missing 189.681 / 211.080 67.189 / 80.468
sqlite / current 213.423 / 268.769 88.232 / 111.476
sqlite / missing 209.084 / 247.175 86.764 / 106.523

Warm typed p50/p95 0.477/0.998 ms (200); complete map 0.631/1.165 ms; cold complete adapter 262.477/352.485 ms (12 starts). Daemon RSS 102.10→103.66 MiB after 200 requests. Full HTTP improvement includes one existing request-scoped code fingerprint; all provider requests remain. No full-CLI claim for this HTTP-only consumer. No budget was raised.

Final checks / scope

206 targeted Python tests; 23 native topology/snapshot tests; full control-plane typecheck; full semantic vocabulary smoke; Ruff/diff checks; normal wheel/sdist; frontend build and raw-response parser; independent clean-code/test/docs guards passed. No entire repository suite, Windows/macOS, PostgreSQL, real-model journey or merged-main combination qualification. GitHub CI is queued/in progress at posting; it is not included in these local results. Coverage is sufficient for this read-only map increment; it does not complete graph/source retirement, provider promotion or the parent migration. No new mutation authority or default.

Bounded public-safe source replay recipe

Prerequisites: Git, uv, CPython 3.13 and Node.js 22.22.3 on PATH. From the repository, create disposable checkouts. Save the two code blocks below as http_receiver.py and qualify.py in the generated replay directory. They use synthetic state only.

replay_root=$(mktemp -d)
export LOOPX_GOAL_MAP_REPLAY="$replay_root"
git worktree add --detach "$replay_root/base" e11e859e8bc70bb8292b90c7523f05f8f604c16e
git worktree add --detach "$replay_root/head" 0441f5e600d8934a4e59d8a1b21a422510bade54
uv sync --project "$replay_root/head" --extra test

Generate the process descriptors after saving the scripts:

import json, os
from pathlib import Path
root = Path(os.environ["LOOPX_GOAL_MAP_REPLAY"])
python = str(root / "head/.venv/bin/python")
variants = {label: {"python": python, "pythonpath": str(root / label),
                    "expected": str(root / label / "loopx")}
            for label in ("base", "head")}
(root / "source-variants.json").write_text(json.dumps(variants))
cd "$replay_root/head"
uv run --extra test python "$replay_root/qualify.py" "$replay_root/head" "$replay_root" "$replay_root/source-variants.json" 64
uv run --extra test python -c 'from loopx.control_plane.effect_runtime import restart_effect_runtime; restart_effect_runtime(wait_for_exit=True)'

Receiver (http_receiver.py):

"""Disposable real API receiver with observation at the existing runtime boundary."""
from collections import Counter
import json
import os
from pathlib import Path
import sys
import threading
import time

import loopx
from loopx.chat_server import ChatHTTPServer, ChatRequestHandler
from loopx import chat_status_api
from loopx.control_plane import effect_runtime

registry, runtime, expected = map(Path, sys.argv[1:4])
assert Path(loopx.__file__).resolve().is_relative_to(expected.resolve()), loopx.__file__
counts = Counter()
native_ms, projection_ms, sizes = [], [], []
actual_request = effect_runtime.effect_runtime_request
actual_map = chat_status_api.build_goal_task_map

def observed_request(method, params, **kwargs):
    counts[method] += 1
    started = time.perf_counter()
    result = actual_request(method, params, **kwargs)
    if method == "work_item.task_graph.goal_topology":
        native_ms.append((time.perf_counter() - started) * 1000)
        sizes.append(len(json.dumps(params, separators=(",", ":")).encode()))
    return result

def observed_map(**kwargs):
    started = time.perf_counter()
    result = actual_map(**kwargs)
    projection_ms.append((time.perf_counter() - started) * 1000)
    return result

effect_runtime.effect_runtime_request = observed_request
chat_status_api.build_goal_task_map = observed_map
server = ChatHTTPServer(("127.0.0.1", 0), ChatRequestHandler)
server.registry_path = registry
server.runtime_root_override = str(runtime)
server.runtime_root = runtime
server.scan_roots = []
server.limit = 20
server.selected_goal_id = None
server.verbose = False
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
print(json.dumps({"port": server.server_address[1], "module": loopx.__file__}), flush=True)
try:
    for command in sys.stdin:
        operation = command.strip()
        if operation == "stop":
            break
        if operation == "reset":
            counts.clear(); native_ms.clear(); projection_ms.clear(); sizes.clear()
            print("{}", flush=True)
        elif operation == "stats":
            print(json.dumps({"counts": counts, "native_ms": native_ms,
                "projection_ms": projection_ms, "request_bytes": sizes}), flush=True)
        elif operation == "restart":
            print(json.dumps(effect_runtime.restart_effect_runtime(wait_for_exit=True)), flush=True)
        else:
            raise ValueError("unsupported qualification control")
finally:
    server.shutdown()
    thread.join(timeout=5)
    server.server_close()
    effect_runtime.restart_effect_runtime(wait_for_exit=True)

Driver (qualify.py):

"""Disposable baseline/source/distribution HTTP qualification; keep raw output local."""
import hashlib
import http.client
import importlib.util
import json
import os
from pathlib import Path
import select
import statistics
import subprocess
import sys
import time

source, output = map(Path, sys.argv[1:3])
sys.path.insert(0, str(source))
variants = json.loads(Path(sys.argv[3]).read_text())
samples = int(sys.argv[4])

def module(name, path):
    spec = importlib.util.spec_from_file_location(name, path)
    loaded = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(loaded)
    return loaded

fixture_api = module("goal_map_fixture", source / "tests/test_delivery_review.py")
canonical = module("goal_map_canonical", source / "tests/control_plane/canonical_authority_fixture.py")
from loopx.control_plane.todos.active_state_todo_parser import parse_todo_source
from loopx.control_plane.todos.todo_summary import canonical_todo_read_record, structured_todo_item
from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection
from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted

class Receiver:
    def __init__(self, name, variant, registry, runtime):
        temp = output / "receiver-runtime" / name
        temp.mkdir(parents=True, exist_ok=True)
        self.log = (output / (name + ".stderr")).open("w")
        env = {**os.environ, "PYTHONPATH": variant.get("pythonpath", ""), "TMPDIR": str(temp),
               "LOOPX_USAGE_PING": "0", "NODE_OPTIONS": "--experimental-sqlite"}
        self.process = subprocess.Popen([variant["python"], str(output / "http_receiver.py"),
            str(registry), str(runtime), variant["expected"]], cwd=output, env=env,
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=self.log, text=True, bufsize=1)
        self.port = self.receive()["port"]
    def receive(self):
        assert select.select([self.process.stdout], [], [], 90)[0], "receiver timed out"
        line = self.process.stdout.readline()
        assert line, "receiver exited before its response"
        return json.loads(line)
    def control(self, command):
        self.process.stdin.write(command + "\n"); self.process.stdin.flush()
        return self.receive()
    def get(self):
        started = time.perf_counter()
        connection = http.client.HTTPConnection("127.0.0.1", self.port, timeout=90)
        connection.request("GET", "/api/chat/delivery-review?goal_id=release-demo")
        response = connection.getresponse()
        payload = json.loads(response.read())
        connection.close()
        elapsed = (time.perf_counter() - started) * 1000
        assert response.status == 200, (response.status, payload)
        return payload, elapsed
    def close(self):
        self.process.stdin.write("stop\n"); self.process.stdin.flush()
        assert self.process.wait(timeout=30) == 0
        self.log.close()

def summary(values):
    if not values: return None
    return {"p50_ms": round(statistics.median(values), 3),
            "p95_ms": round(sorted(values)[min(len(values)-1, int(len(values)*.95))], 3)}

records = []
for provider in ("file", "sqlite"):
    for display in ("current", "missing"):
        name = provider + "-" + display
        directory = output / name
        if not directory.exists():
            registry, runtime = fixture_api.make_project(directory)
            state = registry.parent / "ACTIVE_GOAL_STATE.md"
            groups, _, _ = parse_todo_source(state.read_text())
            todos = [canonical_todo_read_record(structured_todo_item(row, role=role,
                source_section=row.get("source_section"))) for role, rows in groups.items() for row in rows]
            canonical.initialize_canonical_authority(runtime, "release-demo",
                build_todo_runtime_shadow_projection(goal_id="release-demo", todos=todos),
                state_path=state, provider=provider)
            if display == "missing": state.unlink()
        registry, runtime = directory / "project/registry.json", directory / "runtime"
        state = registry.parent / "ACTIVE_GOAL_STATE.md"
        before = (registry.read_bytes(), state.read_bytes() if state.exists() else None,
                  read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="release-demo"))
        receivers = {}
        try:
            packets = {}
            for label, variant in variants.items():
                receiver = Receiver(name + "-" + label, variant, registry, runtime)
                receivers[label] = receiver
                packet, _ = receiver.get()
                (output / f"{name}-{label}-raw.json").write_text(json.dumps(packet, indent=2))
                # This field is this HTTP observation's clock, never a persisted source timestamp.
                packet.pop("observed_at")
                packets[label] = packet
                (output / f"{name}-{label}-packet.json").write_text(json.dumps(packet, indent=2))
            expected = next(iter(packets.values()))
            assert all(packet == expected for packet in packets.values()), name
            by_todo = {node["refs"]["todo_ids"][0]: node for node in expected["goal_map"]["nodes"]}
            assert len(by_todo) == 5 and by_todo["todo_review"]["kind"] == "gate"
            assert "owner_agent" not in by_todo["todo_review"]
            assert by_todo["todo_design"]["state"] == "done" and by_todo["todo_integrate"]["depth"] == 1
            assert len(expected["goal_map"]["edges"]) == 2 and expected["goal_map"]["limits"]["topology_complete"]
            durations = {label: [] for label in receivers}
            for receiver in receivers.values(): receiver.control("reset")
            for iteration in range(samples):
                order = list(receivers) if iteration % 2 == 0 else list(reversed(receivers))
                for label in order:
                    packet, elapsed = receivers[label].get()
                    packet.pop("observed_at")
                    assert packet == expected, (name, label)
                    durations[label].append(elapsed)
            stats = {label: receiver.control("stats") for label, receiver in receivers.items()}
            if samples:
                for report in stats.values():
                    assert report["counts"]["work_item.task_graph.goal_topology"] == samples
                    assert len(report["projection_ms"]) == len(report["native_ms"]) == samples
            for label, receiver in receivers.items():
                receiver.control("restart")
                packet, _ = receiver.get(); packet.pop("observed_at")
                assert packet == expected, (name, label, "restart")
            after = (registry.read_bytes(), state.read_bytes() if state.exists() else None,
                     read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="release-demo"))
            assert after == before, name
            records.append({"provider": provider, "display": display, "full_http_parity": True,
                "normalization": ["observed_at"], "restart_parity": True, "provider_unchanged": True,
                "observation_sha256": hashlib.sha256(json.dumps(expected,sort_keys=True).encode()).hexdigest(),
                "fixture_sha256": hashlib.sha256(json.dumps(before[2],sort_keys=True).encode()).hexdigest(),
                "samples": samples, "http_ms": {k: summary(v) for k,v in durations.items()},
                "native_ms": {k: summary(v["native_ms"]) for k,v in stats.items()},
                "projection_ms": {k: summary(v["projection_ms"]) for k,v in stats.items()},
                "stats": stats, "http_samples": durations})
            (output / "http-receipts.json").write_text(json.dumps(records,indent=2))
        finally:
            for receiver in receivers.values(): receiver.close()
print(json.dumps(records, indent=2))

For the durable literal contract independently of the parity oracle, run the candidate's tests/test_delivery_review.py::test_canonical_http_goal_map_survives_repeated_reads_and_runtime_restart on both checkouts using the same candidate test file. To repeat mutation sensitivity, create a third disposable baseline checkout, copy only the candidate's six production files into it, change the single gate ? "gate" branch to gate ? "deliverable", and point a mutant receiver at that checkout; expected failure is the full HTTP mismatch above. Retain separate TMPDIRs as the receiver does; remove only the disposable worktrees created for this replay.

@mergify

mergify Bot commented Oct 16, 2026

Copy link
Copy Markdown

This pull request has merge conflicts with main and cannot be merged
until they are resolved. Please rebase or merge the base branch, @hhyykk.

Choose the remote for the base repository, not an out-of-date fork.
For a fork clone, first inspect git remote -v; upstream must point
to https://github.com/loopx-project/loopx.git. If it is absent, add it
with git remote add upstream https://github.com/loopx-project/loopx.git.
Then run:

git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEAD

For a same-repository clone whose origin points to
https://github.com/loopx-project/loopx.git, use origin instead of
upstream for fetch/rebase. If you prefer merging the base, use
git merge <base-remote>/main and push normally.

Keep the DCO Signed-off-by trailer on every commit when you rebase.
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Oct 16, 2026

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=max

请求修改:[P2] 本次 TypeScript 读模型迁移缺少 Accepted RFC 要求的 projection envelope。

动机

查看一个 Goal 全部任务及依赖关系的用户,需要在同一个只读入口得到稳定、可解释的任务地图。

原来 TypeScript 算拓扑、Python 再决定节点类型和地图结构;本提案把组装合到现有 TypeScript owner,重复读取和重启仍应得到同一张地图。

File/SQLite、存在或缺失 Markdown 的真实 HTTP 对比保持完整输出一致,源码和两种正常安装也一致;新读模型仍未携带规范要求的新鲜度与覆盖封套。

本次不认证 provider 晋升、完整控制面迁移、真实模型采用、其它平台或长期运行收益。

先补本次迁移的 projection_envelope;共用文本、状态和稳定 ID codec 的最终退役仍由原 selected-work graph/source 消费者迁移承担。

改动思路

沿用现有 work_items 拓扑 owner 和私有快照传输,保留仍被选中任务图使用的共用 codec,有明确收益;新增封套应复用既有 projection_envelope.ts,避免另建新鲜度规则。 当前 PR 是一个完整只读地图组装切片;必须在同一切片接上既有封套 owner 和实际消费者,不能用旧输出字节相同替代 Accepted RFC 的迁移晋升门。

正常链路是 HTTP delivery-review 读取原 canonical Todo 来源,Python 仅准备经过公共安全处理的事实,既有 TS 拓扑 owner 组合节点类型、元数据可见性、边和地图头,再由共用 codec 生成原稳定身份与显示文本。新的 request scope 固定的是一次 HTTP 的代码版本,provider、来源和权限检查仍实际执行,不能把它解释成数据缓存。元数据只在节点准入后读取;超出普通帧的完整输入走既有私有、摘要绑定的快照通道。

具体改动

head 0441f5e600d8934a4e59d8a1b21a422510bade54,实际 merge base e11e859e8bc70bb8292b90c7523f05f8f604c16e。spec_ref: docs/architecture/rfcs/typescript-control-plane-migration-v0.md;spec_revision: e11e859e8bc70bb8292b90c7523f05f8f604c16e。独立规范为该 Accepted RFC和既有任务图协议。本 PR 对 RFC 实施事实的编辑没有改变 §2.6/§6 晋升条款;fetched main 也保留它们。

原规范条款 当前判定
Canonical owner implemented:地图组装规则交给既有 TS owner,Python 不保留地图规则 fallback。
Cross-runtime calls implemented:四组各64对真实 HTTP,地图调用1→1,全部 runtime 请求36→36,重启读回一致。
Facade exit implemented:保留实际仍被 selected-work graph 使用的文本、状态、SHA 身份 codec;随其余消费者迁移退役。
Caller-observable semantic parity implemented:完整对象、实际 backend、正常安装及物理语义 mutation 已核。
§2.6 not_met:新 TS 读模型及外层 HTTP 都未携带 projection_envelope。
§6 not_met:打包通过,producer 尚无封套可验证 stale/unreadable/incomplete/replay 四种必需状态。

关键代码讲解

  1. build_goal_task_map 删除地图策略,保留共享来源/身份适配;第一条可展示重复记录仍优先,丢弃或未准入记录的 opaque 元数据不被求值,真实大输入测试保留120节点上限与11880省略计数。
  2. projectGoalTaskMap 复用已解码的拓扑、现有 relation catalog 与120节点公开上限,决定 gate/monitor/deliverable、可见字段及边;原公开 ID 与 schema 保持。当前返回处遗漏封套。
  3. _delivery_review 复用已有代码版本 request scope;两端 snapshot allowlist 同步扩展,未增加 daemon、每字段 RPC 或写入权威。

全提案12路径 +477/-74:产品六文件净+32、测试三文件净+352、文档三文件净+19。已核删除的是地图策略,不把所有搬动行都计作退役收益。仍有实际共用 codec 消费者,整体复制到 TS 反而产生第二份规则;这个有界保留是合理的。

对主干的风险

[P2] 新读模型需要通过已接受的封套晋升门。 真实 GET /api/chat/delivery-review?goal_id=release-demo 在 File/SQLite、显示文件存在/缺失、源码/wheel/sdist 都返回200;外层只有 ok/goal_id/observed_at/graph/goal_map/acceptance,地图内也没有 projection_envelope。实际前端 schema 和时间展示只读组装时的 observed_at。它不能代替来源读取时间、覆盖遗漏、继承最旧来源和 replay 的区分。复用既有 projection.envelope.seal,传递真实来源读取与覆盖事实并接到实际消费者;补 stale、unreadable、incomplete scope、replay 四种 producer 用例。

这是当前迁移资格缺口。基线也没有封套,已明确作为 legacy rollout 事实保留,没有把这个既存字段缺失称为新 runtime 回归;但 Accepted RFC 明确要求新迁移读模型在同一 PR 接入该 owner。仅保住旧字节不满足这条新增迁移时必须履行的条件。

独立验证:206 Python、23 TS、全 control-plane typecheck、Ruff、完整语义 smoke、前端生产构建及现有 delivery-review smoke 通过。六个产品文件在源码、正常 wheel/sdist 及两份全新安装中逐字节一致;16份真实 HTTP 响应通过实际前端 parser,错 Goal 被拒绝。四组各64对完整对象比较仅去掉 root observed_at;canonical Todos/revision、registry、显示文件保持,重启读回一致。安装副本的 gate→deliverable 物理 mutation 使同一真实 HTTP 比较明确失败,非启动失败冒充敏感性。

本机配对 p95(base→head,ms):File/current252.783→119.139,File/missing398.870→206.185,SQLite/current221.508→121.608,SQLite/missing243.671→156.303。12次冷完整适配器 p50/p95=137.772/211.681ms;200次单代码作用域 warm map p95=.550ms;RSS82.547→85.016MiB。HTTP 与微测边界分开,收益包含 fingerprint scope,不归因于 TS 搬动本身;不推导其它宿主或持续性能。

语义与 CI 对齐

复用既有公共地图/关系词汇,仅在现有 owner 内新增私有 request/result 标签;snapshot 方法名单两端同步扩展。advisory 只发现该名单扩展,其支持语法有界;full semantic smoke 通过不能证明封套义务已履行。原生 premerge 的5 direct+18 selected可执行检查通过,整体仍因本地 quality_receipt_missing 为失败状态;没有生成凭据、豁免或弱化预算。未查询或等待 CI。现有 merge conflicts 及最新 main 组合资格仍独立保留。

我的整体评价

REQUEST_CHANGES。long_horizon=preserved、user_experience=preserved 限于已实测的只读重复/重启和现有入口,零新增必要交互轮次;它们不认证长期模型使用或整个迁移完成。这个切片能集中地图策略,桥接增量有明确边界,共用 codec 暂留合理,未来改动检查已落在同一 owner 的合并 seam;当前需要完成现有封套接入。当前 PR 是一个完整只读地图组装切片;必须在同一切片接上既有封套 owner 和实际消费者,不能用旧输出字节相同替代 Accepted RFC 的迁移晋升门。

重新评审应在新精确 head 复核完整提案、来源封套四态、真实 HTTP/正常安装及原语义反例;不要求本 PR 完成无关的 parent roadmap,也不把本地 CQR/冲突 hold 代替本次具体发现。

English verdict: REQUEST_CHANGES - 0441f5e; real source/wheel/sdist HTTP parity and local costs pass, but the migrated user-facing Goal map lacks the projection envelope required by the accepted RFC §2.6/§6. Complete the existing owner/consumer integration and four producer-state regressions.

Comment thread loopx/control_plane/work_items/task_graph.ts
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>

# Conflicts:
#	loopx/control_plane/effect_runtime.py
#	loopx/control_plane/effect_runtime_server.ts
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
@loopx-agent
loopx-agent requested a review from maxliux5 as a code owner October 17, 2026 14:25
@mergify mergify Bot removed the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Oct 17, 2026

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max

动机

查看一个 Goal 的全部工作、关系及交付依据的用户和 Agent。

原来 TS 只给拓扑,Python 再组地图;迁移后的地图与 HTTP 又缺少来源封套,组装时间无法说明来源是否仍新鲜。

同一个既有页面返回稳定原任务/边及当前来源范围,过期或不可读可见、可原位刷新,导出保留原始读取时间。

不改变任务执行、claim/lease、provider 默认或验收授权;不认证完整 Agent 迁移、整个 T3 或生产采用。

其他 selected-work graph/source 消费者与用户 Agent 迁移继续由既有路线验收;本次地图切片没有新增后续任务前提。

本轮在 hhyykk 原分支补齐缺口并复核完整提案;此评审包含维护者补丁的自审,不冒称另一名独立 reviewer。

改动思路

地图规则和来源判定复用既有 TS owner,Python 保留仍有调用者的 IO 与共享 codec;外层只增加一次整批封装调用。

本次收敛既有 Goal 地图组装及来源读回,完整 Agent 迁移仍由原产品路径验收。

正常路径:Goal 概览/交付依据 → 原 canonical Todo/status 读取 → 原 TS topology/map owner → 既有 envelope owner → 已交付前端与导出。代码版本 request scope 只固定一次 HTTP 的可执行代码身份,provider 和来源仍实时读取。共享文本、状态和稳定 ID codec 仍有 selected-work graph 调用者,暂留一个 owner,并非复制一份新规则。

来源不可读时保留其他组件与原任务,地图为 null 并告警;旧来源时间不被组装或重放刷新。原刷新入口能恢复读取,不增加配置、确认或执行授权。

具体改动

精确 head:8d8f3c004f4ee9570ca218e0d3942aca5d6e2852。spec_ref=docs/architecture/rfcs/typescript-control-plane-migration-v0.md;spec_revision=83faa5bd9f7667ae104943feacdc1536a3d57f74。依据是改动前 Accepted RFC与原图协议、封套契约。候选 checkpoint 不反过来定义验收。

原条款 当前判断
T3 implemented:地图 kind、元数据可见性、边及形状归原 TS owner;重复 Python 地图策略退役
§2.6 implemented:地图原位 seal;外层继承 status/map 来源;前端与导出消费实际封套
§5 implemented:保留 codec 的实际消费者/退出条件、删除及桥接代价明确
§6 implemented:完整原字段对照、真实 backend/HTTP、正常分发包、四态反例与实际入口成本已执行

关键代码:

  1. build_goal_task_map 保留真实共享来源/身份适配,重复或未准入的 opaque 元数据不求值;仅向 native 传事实,普通帧之外仍走原私有快照,完整来源不截断。
  2. projectGoalTaskMap 复用原关系与120节点准入,并用已有 seal owner 处理读取时间、未知分母、无法展示/上限/缺失关系;没有第二套新鲜度或生命周期决定。
  3. _delivery_review 保留 origin、Goal 和 registry drift 边界;root 的 declared coverage 是三个交付组件及其上游范围,图内 limits 继续独立披露。fresh/complete 都不是跨来源原子快照或整 Goal 验收。
  4. 实际前端 校验身份、披露 native flags,保留旧响应“未知”状态与原来源打开权限;真实下载包含两层封套和原读取时间。

完整提案23路径:产品9文件 +202/−79,测试/fixture7文件 +499/−9,文档/视觉7文件 +54/−5(另含三张合成截图)。没有新生产模块、依赖、配置、持久化状态或服务。当前 main 组合冲突保留两边的 snapshot 方法,最终 diff 不夹带无关 main 工作,7个贡献/merge 提交均有 DCO。

对主干的风险

原阻塞已有实际反例:整合原提案 9a41c713a330fbd65e85d2fb001fbdf5ba415bcd 成功返回 HTTP200 后,在独立“必须有封套”断言报 KeyError;同一断言在当前头通过。旧评审的正常包和微测只作为历史,以下为当前头重新执行的结果。

File/SQLite × 展示文件存在/缺失 × baseline/source/正常 wheel/正常 sdist,共 1056 次完整 HTTP 观察,每 receiver64个交替计时样本并含首次/重启读回。完整原 JSON 相等;只把既有 root observed_at 和有意新增的两层封套单列检查,没有删除领域字段、身份或拒绝细节。canonical Todo/revision、registry 和展示文件保持。六个产品运行文件及37个编译 UI 文件在源码、归档和新安装中逐字节一致。

完整 HTTP p95,ms Python baseline 当前 source 正常 wheel 正常 sdist
File / present 165.602 91.299 101.824 91.325
File / missing 172.778 102.341 94.901 91.470
SQLite / present 158.397 90.284 90.475 81.120
SQLite / missing 206.022 103.404 107.023 104.910

地图仍1次请求;完整 HTTP 36→37,新增的是一次明确的整批外层 seal。收益含代码版本作用域,不归因于语言搬动本身。source native map p95为0.509–0.684ms;不把微测当成完整入口证明,也没有改动原5%/25ms要求。baseline固定 83faa5bd9f7667ae104943feacdc1536a3d57f74;它与组合 main 90932447eb5ed38a0d3fec8c57e21a62f57f849c 的相关 map/status/runtime 路径无改动,比较不借无关 main 历史改变基线。

当前51个 Python、30个 native、前端 data/export/identity smoke、两端完整 typecheck、正常 bundle/分发包、双语 RFC 及公共边界检查通过。原生 premerge 6 direct +19 selected 通过,零 hold;CQR 当前精确 scope 通过。先前 stale-CQR 的整体失败记录保留,后续执行已在同一未改源码头闭合该门。

实际打包页面使用隔离合成 File authority:正常地图读取、明确的 IO 故障注入、原位键盘刷新恢复、390px 无横向溢出、过期警告及真实 Markdown 下载均完成。故障与旧时钟是 fixture hook,不是生产故障或模型验收;未运行真实模型或升级用户 App。正常桌面、不可读/重试、手机过期来源为本实现的合成视图;普通入口不增加必要交互轮次。

语义与 CI 对齐

完整 PR advisory 识别到既有 LOCAL_SNAPSHOT_METHODS 的一个方法扩展,Python/TS 两端一致,复用原传输 owner;两层载体/范围与局部 omission reason 已记录在原协议中。full semantic 通过,原59处未证明 producer 仍保留,advisory 不是语义等价证明。无文本黑名单、benchmark 特定核心义务、新激活开关或静默 provider/default 改动。

warning 是来源披露,不是新的 must_attempt_work、额度或执行权限。普通 status 仍不触发冷地图读取,实际原 origin/Goal/身份/registry drift 拒绝保留。依 no-CI 策略未查询、等待或触发 hosted CI。Windows、PostgreSQL、实时模型、已安装 App 采用、长期 fleet 和全局冷启动/RSS 新资格未测;本次不改 PostgreSQL store/事务,不能外推这些结果。

我的整体评价

APPROVE 当前精确头。 原地图与封套缺口已补齐,原身份、角色、关系、共享 codec、只读边界及正常安装行为有实际证据;HTTP 多一次 seal 的代价已披露且完整入口成本通过。

long_horizon=preserved 限于可重复/重启找回同一原任务;user_experience=improved 限于实际来源告警、导出和原位恢复。有限未来改动检查已经应用在一个 TS 地图 owner、共享 codec 不复制和现有 showcase fixture 的小 helper 上;这不是完整 Agent 迁移或整 RFC 完成。批准与合并授权/readiness仍分开,按当前同一 head 执行最后检查。

English verdict: APPROVE - 8d8f3c0. The original native map proposal now includes the accepted source envelopes and actual UI/export/recovery consumers. Real File/SQLite source and normal wheel/sdist HTTP parity, all four paired cost arms, current risk-based premerge and exact-scope quality pass. One extra coarse seal crossing is disclosed; authority and original task identity remain unchanged. This includes self-review of maintainer repairs and does not certify broader Agent migration or production adoption.

@loopx-agent
loopx-agent merged commit cb06759 into loopx-project:main Oct 17, 2026
13 of 34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants