Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ jobs:
- name: Validate declared convergence evidence and scope
run: >-
python3 scripts/validate_convergence.py
blueprints/convergence-practice/ci-security/experiment.json
blueprints/convergence-practice/native-worker/experiment.json --root . --json
--all-recorded --root . --json
- name: Check offline ecosystem explorer rebuild
run: python3 scripts/build_ecosystem.py --check
- name: Test validation failure modes
Expand Down
3 changes: 2 additions & 1 deletion adoption/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@
"fastapi": "blueprints/convergence-practice/application-delivery/README.md",
"postgresql": "blueprints/convergence-practice/application-delivery/README.md",
"poppler": "blueprints/convergence-practice/document-ingestion/README.md",
"apple-container": "blueprints/convergence-practice/container-storage/README.md"
"apple-container": "blueprints/convergence-practice/container-storage/README.md",
"playwright-test": "blueprints/convergence-practice/wsl-application/README.md"
},
"profiles": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ postgres-install:
printf '%s %s\n' 555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f .runtime/postgresql-source/postgresql-18.6.tar.bz2 | shasum -a 256 -c -
tar -xjf .runtime/postgresql-source/postgresql-18.6.tar.bz2 -C .runtime/postgresql-source
cd .runtime/postgresql-build && ../postgresql-source/postgresql-18.6/configure --prefix="$$(pwd)/../postgresql/18.6" --without-icu --without-readline --without-zlib
$(MAKE) -C .runtime/postgresql-build -j 4
$(MAKE) -C .runtime/postgresql-build install
env MAKELEVEL=0 $(MAKE) -C .runtime/postgresql-build -j 4
env MAKELEVEL=0 $(MAKE) -C .runtime/postgresql-build install
cp .runtime/postgresql-source/postgresql-18.6/COPYRIGHT .runtime/postgresql/18.6/COPYRIGHT

postgres-init:
Expand Down
25 changes: 25 additions & 0 deletions blueprints/convergence-practice/application-delivery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,31 @@ The nested [project contract](project-contract.json) supplies actual setup, run,
test and verify commands for native Codex/Claude integration. The enclosing
repository's global actions were not modified by this bounded contribution.

## Recipe portability follow-up

The current PostgreSQL build/install entrypoints explicitly reset `MAKELEVEL=0`
at the external source-tree boundary. PostgreSQL's own recursive make calls still
advance normally. This prevents the enclosing application Makefile from causing
PostgreSQL to skip its top-level generated-header prerequisites. The application
port probe now uses `SO_REUSEADDR`, allowing normal restart after TIME_WAIT while
still refusing a live listener. It does not enable `SO_REUSEPORT` or stop any
unknown process.

Five focused tests exercise both actual make command boundaries, reproduce the
old recursion failure, create real loopback TIME_WAIT and live-listener sockets,
and check historical byte identity. They pass on the Mac without rebuilding the
application or PostgreSQL:

```sh
python3 -m unittest discover -s blueprints/convergence-practice/application-delivery -p 'test_portability.py' -v
```

Run that command from the repository root. The [recipe revision mapping](history/recipe-revisions.json)
preserves the original Makefile and launcher bytes under their recorded hashes.
The original receipt and experiment are unchanged and still describe that earlier
recipe. These portability checks qualify only the two changes; later WSL full-stack
acceptance is recorded separately.

## Qualification and limits

[acceptance-plan.json](acceptance-plan.json) fixed the acceptance criteria;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.PHONY: setup schema migrate migrate-test run test verify browser postgres-install postgres-init postgres-start postgres-stop postgres-databases

setup:
UV_CACHE_DIR=.runtime/uv-cache uv sync --frozen --no-build
pnpm install --frozen-lockfile --ignore-scripts --store-dir .runtime/pnpm-store

schema:
uv run --frozen python -m backend.export_openapi
pnpm types

migrate:
uv run --frozen alembic upgrade head

migrate-test:
DATABASE_URL=postgresql://postgres@127.0.0.1:15432/ledger_test uv run --frozen alembic upgrade head

run:
python3 serve.py

test:
DATABASE_URL=postgresql://postgres@127.0.0.1:15432/ledger_test uv run --frozen pytest -q

browser:
pnpm test:e2e

verify:
pnpm peers check
uv run --frozen python verify_schema.py
pnpm typecheck
NEXT_TELEMETRY_DISABLED=1 pnpm build
DATABASE_URL=postgresql://postgres@127.0.0.1:15432/ledger_test uv run --frozen pytest -q
pnpm test:e2e

# One-time native source build. Refuse to overwrite an existing installation.
postgres-install:
test ! -e .runtime/postgresql/18.6
mkdir -p .runtime/postgresql-source .runtime/postgresql-build
curl --fail --location --proto '=https' --tlsv1.2 https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2 --output .runtime/postgresql-source/postgresql-18.6.tar.bz2
printf '%s %s\n' 555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f .runtime/postgresql-source/postgresql-18.6.tar.bz2 | shasum -a 256 -c -
tar -xjf .runtime/postgresql-source/postgresql-18.6.tar.bz2 -C .runtime/postgresql-source
cd .runtime/postgresql-build && ../postgresql-source/postgresql-18.6/configure --prefix="$$(pwd)/../postgresql/18.6" --without-icu --without-readline --without-zlib
$(MAKE) -C .runtime/postgresql-build -j 4
$(MAKE) -C .runtime/postgresql-build install
cp .runtime/postgresql-source/postgresql-18.6/COPYRIGHT .runtime/postgresql/18.6/COPYRIGHT

postgres-init:
test ! -e .runtime/native-pgdata
.runtime/postgresql/18.6/bin/initdb --pgdata=.runtime/native-pgdata --username=postgres --auth=trust --no-locale --encoding=UTF8
printf "\nlisten_addresses = '127.0.0.1'\nport = 15432\nunix_socket_directories = ''\n" >> .runtime/native-pgdata/postgresql.conf

postgres-start:
.runtime/postgresql/18.6/bin/pg_ctl -D .runtime/native-pgdata -l .runtime/postgres.log -w start

postgres-stop:
.runtime/postgresql/18.6/bin/pg_ctl -D .runtime/native-pgdata -m fast -w stop

postgres-databases:
.runtime/postgresql/18.6/bin/createdb --host=127.0.0.1 --port=15432 --username=postgres ledger
.runtime/postgresql/18.6/bin/createdb --host=127.0.0.1 --port=15432 --username=postgres ledger_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"schema_version": 1,
"scope": "Separate native make and loopback socket portability regressions; no full application/PostgreSQL build or model rerun.",
"historical_acceptance": "Original receipt and experiment retain their original hashes. Resolve original Makefile/serve.py bytes through this explicit hash-to-retained-file mapping; original full-stack acceptance does not qualify the changed recipe.",
"recipes": [
{
"current_path": "Makefile",
"original": {
"revision": "cabbe2c96e39096b510f739f07a6e4132da082de",
"sha256": "dbab0b62a259b26a86a8e174b2ab17ba7ce28ac5d19f8412fb41c016f75bc011",
"retained_path": "history/Makefile.cabbe2c"
},
"current_sha256": "799c61469b98dddbc20870b6166737312968a97ddf16aa6ed3e7d50104278e80",
"change": "Reset MAKELEVEL only at both external PostgreSQL build/install entrypoints; internal recursive make still advances normally."
},
{
"current_path": "serve.py",
"original": {
"revision": "cabbe2c96e39096b510f739f07a6e4132da082de",
"sha256": "e3e59b5315d5f2b3c2fe863cdecabfd9f7ffd1977a83d332331a10e33a42a14e",
"retained_path": "history/serve.cabbe2c.py"
},
"current_sha256": "bba1f9655cce0b412257fbd0c40680b6b0ccc0ecffb91a2f3605323491655aed",
"change": "Set SO_REUSEADDR on the preflight bind so TIME_WAIT does not mimic a live listener; keep live listener refusal and never enable SO_REUSEPORT."
}
],
"unchanged_historical_evidence": {
"receipt.json": "3c7fb4ec3b477b90f47b3765a0122a89aa39fb6c0fe3529818f6c4b4145e1cd7",
"experiment.json": "1d6df4651976293bef2a18867430912db51496ecb3fd60224b44061f31098556",
"freeze.json": "333198ba238dd35bea47882dea9e15fcadf1ab5b8609ed2bfa93aa21a1911720"
},
"qualification": {
"command": "python3 -m unittest discover -s blueprints/convergence-practice/application-delivery -p 'test_portability.py' -v",
"status": "passed",
"tests": 5,
"scope": "Native synthetic make recursion plus actual owned loopback TIME_WAIT/live-listener sockets; no unknown process stopped.",
"exit_code": 0,
"host": {
"system": "Darwin",
"macos": "26.5.1",
"architecture": "arm64",
"python": "3.14.7",
"make": "GNU Make 3.81"
},
"observed_make_levels": {
"original_external": [
"all:1"
],
"fixed_build_install": [
"all:0",
"internal:1",
"install:0",
"internal:1"
]
},
"observed_socket_results": {
"ordinary_bind_after_TIME_WAIT": "EADDRINUSE",
"reuseaddr_probe_after_TIME_WAIT": "passed",
"reuseaddr_probe_with_live_listener": "EADDRINUSE; listener remained usable"
},
"lint": {
"command": "ruff check --ignore EXE001 serve.py test_portability.py",
"status": "passed",
"retained_baseline_note": "serve.py already had a shebang with non-executable mode at cabbe2c and is invoked explicitly with python3; its mode is unchanged. EXE001 is excluded only from this focused lint invocation."
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Run only the two owned loopback application processes in the foreground."""
import os
from pathlib import Path
import signal
import socket
import subprocess
import time

HERE = Path(__file__).resolve().parent


def main():
for port in (18080, 18081):
with socket.socket() as probe:
probe.bind(("127.0.0.1", port))
logs = HERE / ".runtime"
logs.mkdir(exist_ok=True)
children, handles = [], []

def interrupted(_number, _frame):
raise KeyboardInterrupt

signal.signal(signal.SIGTERM, interrupted)
try:
for name, command in [
("api", [str(HERE / ".venv/bin/python"), "-m", "uvicorn", "backend.app:app", "--host", "127.0.0.1", "--port", "18081"]),
("web", ["pnpm", "start"]),
]:
handle = (logs / f"{name}.log").open("a")
handles.append(handle)
children.append(subprocess.Popen(command, cwd=HERE, stdout=handle, stderr=subprocess.STDOUT,
env={**os.environ, "NEXT_TELEMETRY_DISABLED": "1"}, start_new_session=True))
print("Run ledger: http://127.0.0.1:18080 (Ctrl-C stops both owned application processes)", flush=True)
while all(child.poll() is None for child in children):
time.sleep(0.2)
return 1
except KeyboardInterrupt:
return 0
finally:
for child in children:
if child.poll() is None:
os.killpg(child.pid, signal.SIGTERM)
try:
child.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(child.pid, signal.SIGKILL)
child.wait(timeout=5)
for handle in handles:
handle.close()


if __name__ == "__main__":
raise SystemExit(main())
12 changes: 9 additions & 3 deletions blueprints/convergence-practice/application-delivery/serve.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
#!/usr/bin/env python3
"""Run only the two owned loopback application processes in the foreground."""
import os
from pathlib import Path
import signal
import socket
import subprocess
import time
from pathlib import Path

HERE = Path(__file__).resolve().parent


def main():
for port in (18080, 18081):
def probe_ports(ports):
"""Allow ordinary restart reuse while refusing a live loopback listener."""
for port in ports:
with socket.socket() as probe:
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
probe.bind(("127.0.0.1", port))


def main():
probe_ports((18080, 18081))
logs = HERE / ".runtime"
logs.mkdir(exist_ok=True)
children, handles = [], []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""Small native make/socket regressions; no application or PostgreSQL build."""
import errno
import hashlib
import importlib.util
import json
import os
import shutil
import socket
import subprocess
import tempfile
import unittest
from pathlib import Path

HERE = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("ledger_serve", HERE / "serve.py")
serve = importlib.util.module_from_spec(spec)
spec.loader.exec_module(serve)


class MakeBoundaryTests(unittest.TestCase):
def exercise(self, source):
make = shutil.which("make")
self.assertIsNotNone(make, "native make is required for this regression")
commands = [line for line in source.read_text().splitlines()
if "$(MAKE)" in line and "-C .runtime/postgresql-build" in line]
self.assertEqual(len(commands), 2, "exercise both actual build/install boundaries")
with tempfile.TemporaryDirectory(prefix="ledger-make-boundary-") as folder:
root = Path(folder)
external = root / ".runtime/postgresql-build"
external.mkdir(parents=True)
(root / "Makefile").write_text(".PHONY: all\nall:\n" + "\n".join(commands) + "\n")
(external / "Makefile").write_text(
".PHONY: all install internal\n"
"all install:\n"
"\t@printf '%s:%s\\n' '$@' '$(MAKELEVEL)' >> levels.txt\n"
"\t@test '$(MAKELEVEL)' = '0'\n"
"\t@$(MAKE) --no-print-directory internal\n"
"internal:\n"
"\t@printf 'internal:%s\\n' '$(MAKELEVEL)' >> levels.txt\n"
"\t@test '$(MAKELEVEL)' = '1'\n"
)
result = subprocess.run([make, "--no-print-directory"], cwd=root,
env={"PATH": os.defpath, "LC_ALL": "C"}, capture_output=True, text=True,
timeout=10, check=False)
levels = (external / "levels.txt").read_text().splitlines()
return result, levels

def test_original_recipe_reproduces_external_recursion_bug(self):
result, levels = self.exercise(HERE / "history/Makefile.cabbe2c")
self.assertNotEqual(result.returncode, 0)
self.assertEqual(levels, ["all:1"])

def test_build_and_install_reset_only_external_boundary(self):
result, levels = self.exercise(HERE / "Makefile")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(levels, ["all:0", "internal:1", "install:0", "internal:1"])


class RestartPortTests(unittest.TestCase):
def test_time_wait_restart_is_allowed(self):
# The accepted server socket actively closes, leaving its local port in
# TIME_WAIT. Both listener and accepted socket inherit SO_REUSEADDR,
# matching ordinary native application server behavior.
with socket.socket() as listener:
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(("127.0.0.1", 0))
listener.listen(1)
address = listener.getsockname()
with socket.create_connection(address, timeout=2) as client:
accepted, _ = listener.accept()
with accepted:
accepted.settimeout(2)
accepted.shutdown(socket.SHUT_WR)
self.assertEqual(client.recv(1), b"")
client.shutdown(socket.SHUT_WR)
self.assertEqual(accepted.recv(1), b"")
with socket.socket() as old_probe:
with self.assertRaises(OSError) as raised:
old_probe.bind(address)
self.assertEqual(raised.exception.errno, errno.EADDRINUSE)
serve.probe_ports([address[1]])

def test_live_listener_is_refused_and_remains_usable(self):
with socket.socket() as listener:
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(("127.0.0.1", 0))
listener.listen(1)
listener.settimeout(2)
address = listener.getsockname()
with self.assertRaises(OSError) as raised:
serve.probe_ports([address[1]])
self.assertEqual(raised.exception.errno, errno.EADDRINUSE)
with socket.create_connection(address, timeout=2) as client:
accepted, _ = listener.accept()
with accepted:
accepted.sendall(b"owned listener remains alive")
self.assertEqual(client.recv(128), b"owned listener remains alive")


class RecipeHistoryTests(unittest.TestCase):
def test_retained_originals_and_current_mapping(self):
history = json.loads((HERE / "history/recipe-revisions.json").read_text())
original_receipt = json.loads((HERE / "receipt.json").read_text())
accepted_hashes = {row["artifact"]: row["sha256"]
for row in original_receipt["artifact_sha256"]}
for row in history["recipes"]:
with self.subTest(recipe=row["current_path"]):
old = HERE / row["original"]["retained_path"]
current = HERE / row["current_path"]
self.assertEqual(hashlib.sha256(old.read_bytes()).hexdigest(), row["original"]["sha256"])
self.assertEqual(accepted_hashes[row["current_path"]], row["original"]["sha256"])
self.assertEqual(hashlib.sha256(current.read_bytes()).hexdigest(), row["current_sha256"])
self.assertNotEqual(old.read_bytes(), current.read_bytes())
for name, expected in history["unchanged_historical_evidence"].items():
self.assertEqual(hashlib.sha256((HERE / name).read_bytes()).hexdigest(), expected)


if __name__ == "__main__":
unittest.main()
Loading