docs: preserve reviewed NetKingdom session artifacts
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Has been cancelled

This commit is contained in:
codex 2026-11-01 23:20:24 +01:00
parent d9a74861ba
commit cb60cd3bbe
45 changed files with 2502 additions and 0 deletions

View file

@ -0,0 +1,22 @@
Source draft continuation, 2026-10-17 (Custodian preparation; no Apps assent):
`tools/forgejo_configuration_prepare.py` now supplies offline frozen-source
checks, exact proposed case-only OAuth/group-reference public projection,
metadata-only upgrade/inverse command templates and original-before/post/current
inverse guards. `tests/test_forgejo_configuration_prepare.py` exercises ten
synthetic offline cases. The preparer does not authenticate an acceptance or
observation, execute the helper/Helm, persist a transaction, or grant authority.
`activate` always refuses. It does not change the existing delivery/hand-over
receipt-only rollback or preexisting NOCHANGE paths.
Remaining T05 implementation is concrete: bind chart 12.5.0's actual public and
private configuration projections; implement the owner committed-evidence
journal's intent/post/inverse-attempt/outcome publication and recovery; bind a
current attended owner operation session, with an independent inverse operation
and disposition/revocation checks; enforce exclusion across every Forgejo
release writer for check+mutation+readback. Existing Warden SQLite durability and
process locking alone do not establish owner authentication or all-writer
exclusion. Keep source dispatch disabled until these bindings exist. Preserve
pending and publication-ambiguous outcomes for owner reconciliation. T02/T03
still owe the four real login/group observations and T05 the real created-only
objects/receipt backend. Existing needs-change history, human acceptance and
first-case tasks remain open; this preparation does not close them.

View file

@ -0,0 +1,97 @@
# Apps T05 source preparation, r1
This packet adds real offline source mechanics on frozen Apps main
`944b30ad63ab054cc24617e374e177d34cd572da`, without claiming an operational
configuration backend. The isolated source is `/tmp/apps-execution-backend-r1/source`.
`apps-source.patch` contains one module, ten synthetic tests and an existing
RAPPS-WP-0016-T05 continuation. No canonical Apps source or status was edited,
no commit was made and no owner assent was produced. NK-WP-0048-T34 remains open.
The module compares explicitly supplied public JSON projections against exact
proposed acceptance digests. It permits differences only in the named
`netkingdom` OAuth entry and `ENABLE_AUTO_REGISTRATION`/`ACCOUNT_LINKING` policy
keys. It preserves other OAuth sources and all unrelated public settings,
requires disabled registration and retained local sign-in, and rejects
value-bearing key shapes. It pins the actual Makefile, public values, registry
values and helper bytes. Output contains only metadata, hashes and command
templates, never OAuth/group contents or helper output. The group reference
bytes are exact acceptance-bound data; this does not establish chart key
compatibility or the runtime any-of group behavior.
The upgrade template uses the actual Forgejo chart/version/namespace/release
and private helper stream seam, excluding unrelated Make prerequisites and
`--install`/namespace creation. The module imports no command/runtime API,
opens no files, and has no dispatch runner or capability callbacks. The private
helper is named but cannot execute. `activate()` always raises `Refused`, even
when caller-supplied session, journal or exclusion flags are supplied. A source
input is not an authenticated acceptance, observation or authority record.
Inverse preparation binds the exact transaction scope, immutable original
before, confirmed post, current post equality and later restored revision with
the original configuration hash. Pending, unknown and already-restored records
require owner reconciliation rather than unguarded replay. Preexisting matched
configuration yields no rollback command. All inverse templates remain
non-dispatchable and require a fresh current inverse operation session; the
revoked hand-over parent and stored provenance confer no permission. The
separate control-flow candidate's `inverse_session` placement is unchanged.
The original receipt-only rollback seam and preexisting NOCHANGE behavior are
untouched: this patch does not edit or replace any adapter or kernel step. It
does not introduce an eighth step or switch configuration ownership to a
Forgejo API mutation.
## Actual facilities selected for owner continuation
These are source implementation proposals within T05, not authority grants or
an assertion that the missing implementations exist.
| Need | Selected existing owner surface | Exact remaining implementation |
| --- | --- | --- |
| Accepted case configuration | Apps `helm/forgejo-values.yaml`, registry values and existing `tools/forgejo-helm-secrets.sh`; chart 12.5.0 | Owner must publish the exact accepted public OAuth/group-reference projection and chart-compatible secret reference. A confidential projection must prove DB/admin/helper values unchanged without disclosing or emitting them. The preparer never consumes `helm get values` output. |
| Authenticated durable configuration history | Existing owner choice of committed `docs/evidence/<date>-forgejo-netkingdom-handover.json` identified by path@commit, extended within the same repository evidence surface | Retain original intent before mutation; bind separate confirmed post, inverse attempt and verified outcome records to that immutable intent and transaction. Authenticate owner custody and main publication/readback. Stop on pending/publication ambiguity. A local file or git commit alone is not authenticated execution permission. No remote publication or new service is selected here. |
| Current operation/inverse session | Existing attended Apps owner option C (`apps-consumer-handover`), using Warden's actual `Invocation`/exact-source evidence-verification seam where applicable | Implement the actual owner provenance verifier, current disposition and revocation source. Bind principal/owner/case/acceptance/source/cluster/namespace/release/client/descriptor/transaction and distinct inverse operation. Revoke each operation attempt on success/refusal/failure. The same operator may perform the fresh inverse; no new standing agent identity is proposed. |
| Release-wide serialization | Existing Apps owner-operated release tooling capability, with attended release-freeze/reconciliation as the proposed mechanism | Owner must bind effective exclusion of manual Helm, Make and every external automation/reconciler across current-check, command and readback. No implemented enforcement was found. Helm has no CAS. Local locks, Warden journal locks and revision equality are insufficient; dispatch must stay disabled. |
Warden `case_run.Executor` explicitly injects an evidence verifier and fixed
adapters; none ship by default. Its `_admission` rechecks current session
evidence, but that does not implement a separate Apps configuration inverse
session. `Journal` provides private SQLite, synchronous FULL and a process
lock for its own case receipts/intents; it is neither this configuration journal
nor exclusion of other Helm writers. Its code is inspected, not instantiated.
The Apps private-reader runtime-proof wrapper authenticates a different read
operation and is not reused as release-write or inverse permission.
## Bounded writer inventory
The archive's `Makefile`, all `tools/` source and all three workflow source
files were searched, together with the Forgejo/operator runbooks. Paths/hashes
are in `source-inventory.json`. No credentials/config from HOME were inspected.
The direct Forgejo Helm writer is `Makefile:483` (`forgejo-deploy`), with
`Makefile:465` its server dry-run variant. The former depends on mailer ESO,
OpenBao store/operator installation and hook-template deployment; those are
outside this OAuth-only transaction. Runbook deployment commands invoke this
Make target. No independent Forgejo release writer was found in the archived
tools or workflows. Other release writers exist for inter-hub/core-hub/vergabe/
coulomb/reuse; their configurable Make release variables and arbitrary manual
Helm access cannot be assumed excluded from Forgejo. External writers and
cluster reconciler permissions are not enumerable or enforceable from this
bounded source inventory. There is no claim of exhaustive estate writer
discovery or production writer freeze.
## Validation and limits
Ten tests pass under an empty HOME and allowlisted environment. The test code
blocks subprocess creation and sockets before exercising preparation. Checks
cover exact scope/source mismatch, unrelated release and OAuth policy changes,
duplicate sources, unsafe registration, value-bearing keys, case-only templates,
original-before/post/current/inverse guards, ambiguous states and preexisting
NOCHANGE. No native tool, credential read, external network, browser/MFA or
owner session was exercised. This proves offline validation mechanics only.
Synthetic public projections intentionally do not assert the chart's actual
OAuth schema. No production accepted projection was available in the bounded
owner source. Actual chart rendering, authoritative private/public readback,
durable publication failure/recovery and all four consumer login/group probes
remain real T02/T03/T05 work. The existing T05 workplan's needs-change history
and task status remain retained in the source draft.

View file

@ -0,0 +1,10 @@
{
"RAPPS-WP-0016-T05-source-draft.md": "faf5b7e38cdfd7778a6ff98d19f1ce7a3a163cd11ec321dff397e480ad99a8ad",
"README.md": "3caaa38b16e9016fe0b2f7b478e87d383c5b1bef546282727cf14d695c05b8e4",
"apps-source.patch": "f2363df928db278b0c5303e37efa53ed8397bc40725b240e69d78e3d0ad7e362",
"source-inventory.json": "b57fc904ae6baf2d73cc5c5465ee5463590a966eebe3d30b91aaeb6e8432bcd2",
"tests/test_forgejo_configuration_prepare.py": "7ca372cb5b72b8e16af4b3a277fd04613afdc90ebf77d38a223687b72fb91491",
"tests.log": "2f9d4d78cae19f752436fec5ff770e3a80e81dae9ce5caa6e048efeb28668873",
"tools/forgejo_configuration_prepare.py": "414ecf813db17ef1dd767626065e652b715c1db1f37f61b1f81303b0de7ca921",
"validation.json": "acd2ef5226c7f04396676804b4de29e6db12682739659971e0040ca0cbb494a7"
}

View file

@ -0,0 +1,192 @@
{
"apps_head": "944b30ad63ab054cc24617e374e177d34cd572da",
"scope": "bounded public source only; no secrets, runtime, native calls or network",
"files": [
{
"path": ".forgejo/workflows/ci-smoke.yaml",
"sha256": "c17729b2144648fa92871e1c94bcb6bd8f64a4b533577ede9457593758e2b7ab"
},
{
"path": ".gitea/workflows/inter-hub-production-deploy.yaml",
"sha256": "b8d7a8c7de94d449a9d1846f5336f606dc72cc0d3c64136be0e7e5feb48d61b9"
},
{
"path": ".gitea/workflows/manifest-server-dry-run.yaml",
"sha256": "2b0f0a536c8aec797705b4d7329eb748734597d8593a697e628a5cae693fe3fa"
},
{
"path": "AGENTS.md",
"sha256": "2a366a5fbb69ba235df97ee117153cbfa1af11ab579372751b4fb5dbb30c31a7"
},
{
"path": "INTENT.md",
"sha256": "31060dc1738aa4540f78735019359a276ece39dad8afbf1be740713bebbe65e5"
},
{
"path": "Makefile",
"sha256": "a319e264e6f04378f6b3d2c7781860fc4bac1abdb248cffa8c26065e08f2f290"
},
{
"path": "docs/assessments/cust81-private-reader/phase-b-r1/native_runtime_proof_stdin.py",
"sha256": "1edc317e8563a120b62a6830eebb2875c144b45c57a491eb7da849095c5d1933"
},
{
"path": "docs/evidence/nk48-t16-owner-review/owner-review-railiance-apps.json",
"sha256": "67e9ded8d37462ba04a0a224bac03a840d0c94326235474e1d3371c8ff61865d"
},
{
"path": "docs/forgejo-on-railiance01.md",
"sha256": "a71670f0241d4bb76ea457f2120a332d3a53ec3f3098859982fb0b283f97370e"
},
{
"path": "docs/operator-recipes.md",
"sha256": "d0c8c4b4ea0ea4d148153724e11e8396249d14609dc3095bdab3adec0132694e"
},
{
"path": "docs/proposals/2026-10-11-forgejo-consumer-eso/owner-design.md",
"sha256": "c523949454e4c6eb58e8caee382380a561f561704136de62bf5783c592f962f2"
},
{
"path": "helm/forgejo-registry-values.yaml",
"sha256": "a38f551734843c6367448f267c7376b1aeee010927a49690fdd7c1471e0ae450"
},
{
"path": "helm/forgejo-values.yaml",
"sha256": "cf87a3dccd666bf8870619755afac4af64fc0bee77b0f0ea54718d6384b70d4f"
},
{
"path": "registry/capabilities/capability.railiance.workload-deployment-tooling.md",
"sha256": "ad24b7656512f93698af3182a3187c2e32efd5e74ae69007cb86eb7cd889528f"
},
{
"path": "tools/apps-pg-backup-dry-run.sh",
"sha256": "a468d6c80fb13e4bc5e67acbd51f11ae08d2543fe1366e846e940e7356674160"
},
{
"path": "tools/apps-pg-restore-drill.sh",
"sha256": "ba94c3f56c54e6c899718ed3b89dc18308d2e334cfa68c04828df9f616ce9c3d"
},
{
"path": "tools/build-database-url-secret.sh",
"sha256": "5d11320093170dbf77d0d4a390714a04491126f85703d701374c797f686ea2bd"
},
{
"path": "tools/check-backup-lane-auth.sh",
"sha256": "1a0ee0d0d4646e92c654567a9c7304a80d4b33b4aadd513a326af0d8a2a68266"
},
{
"path": "tools/check-oci-image.sh",
"sha256": "064b2a84fb3adfb45bbd704c718e8f0cf2fe48965d78b08c7b8be629eefeb5fe"
},
{
"path": "tools/check-policy-nexus-binding.py",
"sha256": "0db04d2d88038bc8079c559c8406d825fc6966968e27a801feefefb263bcc244"
},
{
"path": "tools/check-sops.sh",
"sha256": "e6ddd6c4ef78a7608d94e5ca3b36a4d25816f6e0c04cd34bb9e08eee441f4d74"
},
{
"path": "tools/check-tools.sh",
"sha256": "50a94cd27c694731407e3ee10f44e64d20b5d44d9af21163eb00c8d685f1542d"
},
{
"path": "tools/cnpg-backup-offsite-secret-apply.sh",
"sha256": "18a24ea2206a0775a4764779c96c7f93a2c36130790721a78c1806b8b6ac510a"
},
{
"path": "tools/cnpg-backup-status.sh",
"sha256": "3e5b474fc81a394b6cf4e91434194f1df88e1958d15326bbfb1160a60c67bc8b"
},
{
"path": "tools/cnpg-logical-backup.sh",
"sha256": "1ccbb608827af036fbbd5c132284227fe8a651e75e3a1abf66eb54382599cb25"
},
{
"path": "tools/core-hub-helm-adopt.sh",
"sha256": "047d793b9e763b62980df4ab0d2cef5aa1b115a3d6510bac6d96cf6adf5856e8"
},
{
"path": "tools/core-hub-smoke.sh",
"sha256": "5492aa72c6ecf5e3c22ac6c2b57385c594f52b216c525c531aec18a870e2c84f"
},
{
"path": "tools/create-coulomb-social-env-secret.sh",
"sha256": "a741de560918b54166e818a8302338546dbcd93572624436c779cb0518003bc8"
},
{
"path": "tools/forgejo-breakglass-seal.sh",
"sha256": "aef89b19f1f82acfbd565e07580295dd66037af27f0f3c4703327a2fe77fac33"
},
{
"path": "tools/forgejo-breakglass-verify.sh",
"sha256": "e0144114468015730c415a503e906154313ee79acaeeba5c5ddf0a0b79f9b599"
},
{
"path": "tools/forgejo-helm-secrets.sh",
"sha256": "ebc5425af168763923db9b120c4ac59e2b15f6aae4c3481946d8ccbc9c62412e"
},
{
"path": "tools/forgejo-npm-smoke.sh",
"sha256": "eb13107d933d109302916c4663bf95f5f49e9d2d1cc7272f96e229e179b92f66"
},
{
"path": "tools/forgejo-openbao-eso-token-apply.sh",
"sha256": "2e37b355631ef1d0f8a5741276f7c17f8c99e2d2dcf855e4e4273b1105d2309a"
},
{
"path": "tools/forgejo-operator-bootstrap.sh",
"sha256": "77f27b016fd772d112c9f4ab1ff7dce569d28bb60291ad6ef04f5896c0465960"
},
{
"path": "tools/forgejo-runner-registration-sops-bootstrap.sh",
"sha256": "3b5242696616dbb169e6315e9a70c7fd942724427fa24a60d820b556425782e5"
},
{
"path": "tools/forgejo-smoke.sh",
"sha256": "f6146412cbe29f4abba9d81b3bc7a0652cf281b30a56fd7054e72f1574553869"
},
{
"path": "tools/forgejo-verify.sh",
"sha256": "799571da79db58be2e6eee533ec20765aa9961bb74ea3865d69f48273a86f242"
},
{
"path": "tools/inter-hub-smoke.sh",
"sha256": "068fa77a76bb3047e581398560d02d0bb517614abbb935655fe849082b4666ee"
},
{
"path": "tools/k8s-server-dry-run.sh",
"sha256": "a6ff8018a777fd4a95a34609ca3efa22e5af314710522ca6fc02e852be5bb439"
},
{
"path": "tools/reuse-forgejo-webhook.sh",
"sha256": "bf9dfe0be5ca3033e20495dbd8914e59cedc7092254c544c1436f6f4f850a041"
},
{
"path": "tools/reuse-openbao-eso-token-apply.sh",
"sha256": "0ea9cf2b3767dac5cd43d51e15af86bfee366cbd55a2f9bf0e8e8b71714052d2"
},
{
"path": "tools/reuse-webhook-smoke.sh",
"sha256": "be7b076970d02e357f2216935d257b44a880dec8005860003448ea8c69270a51"
},
{
"path": "tools/smoke-service.sh",
"sha256": "0de60ff9e32d820ea60e3b197715a4a99167f929b69516e0b632019c046ea1a4"
}
],
"owner_runtime_backend_found": false,
"all_writer_exclusion_found": false,
"other_public_source": [
{
"repo": "ops-warden",
"path": "src/warden/case_run.py",
"sha256": "1bc73e30ac718fd75bbaff2a739495fc051aeaec73802ac71f0583cb2d53ab0a"
},
{
"repo": "ops-warden",
"path": "src/warden/case_receipt_state.py",
"sha256": "a2cda7f187e77bd76d4d209505638de44a587fae0bc033ec75c23ee9ea9f73bc"
}
]
}

View file

@ -0,0 +1,167 @@
"""Synthetic offline tests; never acquire owner credentials or execute native tools."""
import copy
import importlib.util
import json
from pathlib import Path
import socket
import subprocess
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location('prepare', ROOT / 'tools/forgejo_configuration_prepare.py')
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
class PreparationTests(unittest.TestCase):
def setUp(self):
self.guards = [patch.object(subprocess, 'Popen', side_effect=AssertionError('native call')),
patch.object(socket, 'socket', side_effect=AssertionError('network call'))]
for guard in self.guards:
guard.start()
self.addCleanup(guard.stop)
self.binding = dict(principal='attended-owner', owner='railiance-apps', case='synthetic-case',
acceptance='synthetic-only', source=m.BASE, cluster='railiance01',
namespace='forgejo', release='forgejo', client='forgejo',
handover_descriptor='synthetic-descriptor')
self.before = {'image': {'tag': 'unchanged'}, 'gitea': {
'oauth': [{'name': 'preexisting', 'provider': 'other'}], 'config': {
'service': {'DISABLE_REGISTRATION': True},
'oauth2_client': {'UNRELATED': 'keep'}}}}
self.after = copy.deepcopy(self.before)
self.after['gitea']['oauth'].append({'name': 'netkingdom', 'provider': 'openidConnect',
'existingSecret': 'synthetic-client-reference',
'groups': ['synthetic-access-group']})
self.after['gitea']['config']['oauth2_client'].update(
ENABLE_AUTO_REGISTRATION=False, ACCOUNT_LINKING='login')
self.contents = {p: (ROOT / p).read_bytes() for p in m.SOURCE_PATHS}
def accepted(self, before=None, after=None):
return {'binding_sha256': m.digest(self.binding),
'before_sha256': m.digest(self.before if before is None else before),
'after_sha256': m.digest(self.after if after is None else after),
'source_sha256': m.SOURCE_HASHES.copy()}
def prepare(self, before=None, after=None):
return m.prepare(self.binding, self.before if before is None else before,
self.after if after is None else after,
self.accepted(before, after), self.contents)
def observation(self, revision, config):
return dict(binding_sha256=m.digest(self.binding), revision=revision,
configuration_sha256=m.digest(config), status='deployed')
def transaction(self):
return dict(id='synthetic-trial', binding_sha256=m.digest(self.binding),
state='confirmed', changed=True,
before=self.observation(7, self.before), after=self.observation(8, self.after))
def test_exact_projection_and_command_without_unrelated_prerequisites(self):
result = self.prepare()
self.assertFalse(result['dispatchable'])
self.assertEqual(result['command_template'][:6],
['helm', 'upgrade', 'forgejo', 'gitea-charts/gitea', '--version', '12.5.0'])
self.assertNotIn('--install', result['command_template'])
self.assertNotIn('make', result['command_template'])
encoded = json.dumps(result)
self.assertNotIn('synthetic-access-group', encoded)
self.assertNotIn('synthetic-client-reference', encoded)
def test_unrelated_changes_even_if_proposed_acceptance_contains_them(self):
candidates = []
for path, value in [(('image', 'tag'), 'changed'),
(('gitea', 'config', 'oauth2_client', 'UNRELATED'), 'changed'),
(('gitea', 'config', 'service', 'ENABLE_INTERNAL_SIGNIN'), False)]:
after = copy.deepcopy(self.after)
parent = after
for key in path[:-1]:
parent = parent[key]
parent[path[-1]] = value
candidates.append(after)
after = copy.deepcopy(self.after)
after['gitea']['oauth'][0]['provider'] = 'changed'
candidates.append(after)
for after in candidates:
with self.subTest(after=m.digest(after)), self.assertRaises(m.Refused):
self.prepare(after=after)
def test_case_scope_source_and_acceptance_mismatch(self):
for key in self.binding:
accepted = self.accepted()
original = self.binding[key]
self.binding[key] = original + '-different'
with self.subTest(key=key), self.assertRaises(m.Refused):
m.prepare(self.binding, self.before, self.after, accepted, self.contents)
self.binding[key] = original
for path in m.SOURCE_PATHS:
contents = self.contents.copy()
contents[path] += b'changed'
with self.subTest(path=path), self.assertRaises(m.Refused):
m.prepare(self.binding, self.before, self.after, self.accepted(), contents)
def test_value_bearing_input_is_not_echoed(self):
for key in ('clientSecret', 'password', 'token', 'cookie', 'secret'):
after = copy.deepcopy(self.after)
after['gitea']['oauth'][-1][key] = 'do-not-echo'
with self.subTest(key=key), self.assertRaises(m.Refused) as caught:
self.prepare(after=after)
self.assertNotIn('do-not-echo', str(caught.exception))
def test_duplicate_auth_source_and_registration_refused(self):
after = copy.deepcopy(self.after)
after['gitea']['oauth'].append(after['gitea']['oauth'][-1].copy())
with self.assertRaises(m.Refused):
self.prepare(after=after)
after = copy.deepcopy(self.after)
after['gitea']['config']['oauth2_client']['ENABLE_AUTO_REGISTRATION'] = True
with self.assertRaises(m.Refused):
self.prepare(after=after)
def test_exact_preexisting_nochange_has_no_rollback(self):
plan = self.prepare(before=self.after)
self.assertFalse(plan['projection']['changed'])
tx = self.transaction()
tx.update(changed=False, before=tx['after'])
inverse = m.prepare_inverse(self.binding, tx, tx['after'])
self.assertEqual(inverse['action'], 'preserve-preexisting')
self.assertEqual(inverse['command_template'], [])
def test_inverse_uses_original_revision_and_checks_current(self):
tx = self.transaction()
plan = m.prepare_inverse(self.binding, tx, tx['after'])
self.assertEqual(plan['command_template'][:4], ['helm', 'rollback', 'forgejo', '7'])
self.assertFalse(plan['dispatchable'])
for current in (self.observation(9, self.after), self.observation(8, self.before)):
with self.assertRaises(m.Refused):
m.prepare_inverse(self.binding, tx, current)
def test_pending_or_ambiguous_transaction_requires_reconciliation(self):
for state in ('pending', 'inverse-started', 'restored', 'publication-unknown'):
tx = self.transaction()
tx['state'] = state
with self.subTest(state=state), self.assertRaises(m.Refused):
m.prepare_inverse(self.binding, tx, tx['after'])
for field in ('revision', 'configuration_sha256'):
tx = self.transaction()
tx['after'][field] = tx['before'][field]
with self.subTest(field=field), self.assertRaises(m.Refused):
m.prepare_inverse(self.binding, tx, tx['after'])
def test_inverse_readback_requires_original_config_and_new_revision(self):
tx = self.transaction()
self.assertIn('durable-owner-confirmation-required',
m.verify_inverse_readback(tx, self.observation(9, self.before)))
for restored in (self.observation(7, self.before), self.observation(9, self.after)):
with self.assertRaises(m.Refused):
m.verify_inverse_readback(tx, restored)
def test_no_fabricated_facility_can_activate(self):
for operation in ('configure', 'inverse'):
with self.assertRaises(m.Refused):
m.activate(operation=operation, authenticated=True,
journal={}, lock=True, inverse_session='synthetic')
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,43 @@
{
"base": "944b30ad63ab054cc24617e374e177d34cd572da",
"command": [
"/usr/bin/python3",
"-m",
"unittest",
"discover",
"-s",
"tests",
"-p",
"test_forgejo_configuration_prepare.py",
"-v"
],
"cwd": "/tmp/apps-execution-backend-r1/source",
"environment": {
"HOME": "/tmp/apps-execution-backend-r1/empty-home",
"PATH": "/usr/bin:/bin",
"PYTHONDONTWRITEBYTECODE": "1"
},
"exit_code": 0,
"tests_run": 10,
"synthetic_only": true,
"test_network_and_native_processes": "socket.socket and subprocess.Popen blocked inside each synthetic test",
"native_or_credential_calls": false,
"external_network": false,
"operational_backend": false,
"first_case_acceptance": false,
"canonical_apps_edits": false,
"existing_task": "RAPPS-WP-0016-T05",
"existing_netkingdom_task": "NK-WP-0048-T34",
"patch_sha256": "f2363df928db278b0c5303e37efa53ed8397bc40725b240e69d78e3d0ad7e362",
"patch_apply_check": {
"command": [
"git",
"apply",
"--check",
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/apps-execution-backend-r1/apps-source.patch"
],
"cwd": "/tmp/apps-execution-backend-r1/apply-check",
"exit_code": 0,
"baseline_workplan": "archived from frozen Apps commit; no canonical checkout touched"
}
}

View file

@ -0,0 +1,62 @@
# Separate source proposal: private client-secret file, never secret argv
The reviewed seven-path extension and this release build do **not** add a new
secret input mechanism. Current Forgejo 11.0.3's `secret` CLI flag has no private
file/descriptor/environment input configured; the chart's automatic quoted
`${GITEA_OAUTH_SECRET_<index>}` becomes a value-bearing `--secret` argument at
runtime. There is no admitted existing alternate path in the bounded source.
No actual secret or auth operation was used during this build.
A concrete owner source continuation is:
1. Add a distinct `--secret-file` StringFlag to Forgejo add/update OAuth CLI.
Resolve it only inside the existing owner workload after operation admission.
Reject simultaneous `--secret`/`--secret-file` and reject empty file paths.
For update, omission preserves the existing stored secret, while an explicitly
selected private input must succeed completely before any DB source update.
Errors contain only fixed reasons, never secret bytes or serialized config.
2. Read a bounded regular file with a non-following open and file-descriptor
metadata checks; reject symlinks/FIFO/device/oversize/short or failed reads,
ownership/mode mismatch, writable-by-others paths and empty values. Parent
directories and the exact mount identity must be owner-controlled. Preserve
exact secret bytes; do not trim or log them. The accepted secret encoding and
maximum length must match the existing minted-client contract. Keep the
transient string out of command tracing, diagnostics and public receipts.
The process already holds the secret for the existing DB configuration; this
proposal changes the input boundary, not its custody authority.
3. Use the existing `forgejo/forgejo-netkingdom-oidc-client` Secret,
key `CLIENT_SECRET`, through a **reviewed read-only file subPath mount** into
the existing configure init container. A file subPath avoids the projected
Secret symlink leaf and permits non-following file checks. The exact mount,
UID/GID/mode and allowed reader set require owner/runtime admission; no file
has been mounted or permission asserted here. Do not add an alias key or a
new remote read/ACL. Rotation/restart behavior of a subPath mount needs the
existing owner refresh contract; it does not update in place.
4. The current chart helper would automatically add `--secret` even when a new
`secretFile` key is supplied. Therefore a **separately reviewed chart-source
patch** is also required. In chart 12.5.0 `_helpers.tpl`'s
`gitea.oauth_settings`, change the default-secret insertion condition from
`if not (hasKey $values "secret")` to
`if and (not (hasKey $values "secret")) (not (hasKey $values "secretFile"))`.
Reject both keys together. The existing generic key formatter then emits the
implemented `--secret-file` flag containing only the fixed private path.
Omit `existingSecret`, the indexed secret environment entry and the legacy
`secret` key for this case. Do not put the secret itself in any public value,
Make variable, agent argument, Helm override or receipt.
This is a source proposal, not an implemented/accepted flag or chart feature.
It must be implemented and independently tested as a new exact source revision,
with CLI add/update preservation, source serialization unchanged except intended
secret replacement, mutual exclusion, symlink/permission/file-race/error cases,
no argv/env/log/receipt value leakage and actual chart command rendering. A
secret-file path flag alone is insufficient if the helper still injects
`--secret`. The source/chart archives and owner descriptors must then be
reviewed/re-frozen. Existing type 1.3 does not admit this mechanism implicitly.
The agent can prepare public source, references and readback schema; the actual
workload reads the value only after the authenticated access-engine decision,
current operation session and private-runtime admission. No standing agent
credential or value-bearing agent argv is proposed. The T34 owner backend still
owes exact application before/post readback, durable journal, all-writer
exclusion, current operation/inverse sessions and the application-state inverse.
This source proposal supplies none of those grants or implementations.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,24 @@
{
"argv": [
"docker",
"buildx",
"build",
"--load",
"--platform",
"linux/amd64",
"--pull=false",
"--network=none",
"--provenance=false",
"--sbom=false",
"--iidfile",
"/tmp/apps-forgejo-release-repro-r1/image-id",
"--metadata-file",
"/tmp/apps-forgejo-release-repro-r1/image-build-metadata.json",
"--build-arg",
"SOURCE_DATE_EPOCH=1752105237",
"/tmp/apps-forgejo-release-repro-r1/container-context"
],
"phase": "container-build",
"returncode": 0,
"seconds": 13.55
}

View file

@ -0,0 +1,13 @@
{
"argv": [
"docker",
"image",
"save",
"--output",
"/tmp/apps-forgejo-release-repro-r1/image.tar",
"sha256:ffae6c666403ab2f995dc017069bcb1a9aea9c6e9d21a55b2ae5a7df5d71ba64"
],
"phase": "container-save",
"returncode": 1,
"seconds": 0.05
}

View file

@ -0,0 +1,61 @@
{
"argv": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--dir",
"/app",
"--ro-bind",
"/tmp/apps-forgejo-release-repro-r1/source/gitea",
"/app/forgejo",
"--bind",
"/tmp/apps-forgejo-release-repro-r1/embedded-extracted",
"/results",
"--chdir",
"/tmp",
"--clearenv",
"--setenv",
"PATH",
"/nonexistent",
"--setenv",
"HOME",
"/tmp/home",
"--setenv",
"USER",
"builder",
"--setenv",
"USERNAME",
"builder",
"--setenv",
"GITEA_WORK_DIR",
"/tmp/work",
"--setenv",
"GITEA_CUSTOM",
"/tmp/custom",
"--setenv",
"GITEA_APP_INI",
"/tmp/absent-app.ini",
"--setenv",
"TZ",
"UTC",
"--setenv",
"LC_ALL",
"C",
"--",
"/app/forgejo",
"embedded",
"extract",
"--include-vendored",
"--destination",
"/results"
],
"phase": "embedded-extract",
"returncode": 1,
"seconds": 0.15
}

View file

@ -0,0 +1,67 @@
# Build reproducibility
## Reproducing the pinned dependencies
Reproducing the pinned dependencies is supported since BuildKit v0.11.
e.g.,
```bash
buildctl build --frontend dockerfile.v0 --local dockerfile=. --local context=. --source-policy-file policy.json
```
An example `policy.json`:
```json
{
"rules": [
{
"action": "CONVERT",
"selector": {
"identifier": "docker-image://docker.io/library/alpine:latest"
},
"updates": {
"identifier": "docker-image://docker.io/library/alpine:latest@sha256:4edbd2beb5f78b1014028f4fbb99f3237d9561100b6881aabbf5acce2c4f9454"
}
},
{
"action": "CONVERT",
"selector": {
"identifier": "https://raw.githubusercontent.com/moby/buildkit/v0.10.1/README.md"
},
"updates": {
"attrs": {"http.checksum": "sha256:6e4b94fc270e708e1068be28bd3551dc6917a4fc5a61293d51bb36e6b75c4b53"}
}
}
]
}
```
Any source type is supported, but how to pin a source depends on the type.
## `SOURCE_DATE_EPOCH`
[`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/) is the convention for pinning timestamps to a specific value.
The Dockerfile frontend supports consuming the `SOURCE_DATE_EPOCH` value as a special build arg, since BuildKit 0.11.
Minimal support is also available on older BuildKit when using Dockerfile 1.5 frontend.
```console
buildctl build --frontend dockerfile.v0 --opt build-arg:SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) ...
```
The `buildctl` CLI (>= v0.13) and Docker Buildx (>= 0.10) automatically propagate the `$SOURCE_DATE_EPOCH` environment value from the client host to the `SOURCE_DATE_EPOCH` build arg.
The build arg value is used for:
- the `created` timestamp in the [OCI Image Config](https://github.com/opencontainers/image-spec/blob/main/config.md#properties)
- the `created` timestamp in the `history` objects in the [OCI Image Config](https://github.com/opencontainers/image-spec/blob/main/config.md#properties)
- the `org.opencontainers.image.created` annotation in the [OCI Image Index](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys)
- the timestamp of the files exported with the `local` exporter
- the timestamp of the files exported with the `tar` exporter
To apply the build arg value to the timestamps of the files inside the image, specify `rewrite-timestamp=true` as an image exporter option:
```
--output type=image,name=docker.io/username/image,push=true,rewrite-timestamp=true
```
The `rewrite-timestamp` option is available since BuildKit v0.13.
See [v0.12 documentation](https://github.com/moby/buildkit/blob/v0.12/docs/build-repro.md#caveats) for dealing with timestamps
in BuildKit v0.12 and v0.11.
See also the [documentation](/frontend/dockerfile/docs/reference.md#buildkit-built-in-build-args) of the Dockerfile frontend.

View file

@ -0,0 +1,5 @@
{
"url": "https://raw.githubusercontent.com/moby/buildkit/v0.27.1/docs/build-repro.md",
"sha256": "1e503e37e994311600745f61ed0173a7ae4217ce307cf72513230e534f5833ca",
"primary_source": true
}

View file

@ -0,0 +1,24 @@
{
"argv": [
"docker",
"buildx",
"build",
"--output",
"type=oci,dest=/tmp/apps-forgejo-release-repro-r1/image.oci.tar,rewrite-timestamp=true",
"--no-cache",
"--platform",
"linux/amd64",
"--pull=false",
"--network=none",
"--provenance=false",
"--sbom=false",
"--metadata-file",
"/tmp/apps-forgejo-release-repro-r1/image-build-metadata.json",
"--build-arg",
"SOURCE_DATE_EPOCH=1752120742",
"/tmp/apps-forgejo-release-repro-r1/container-context"
],
"phase": "container-build",
"returncode": 0,
"seconds": 22.18
}

View file

@ -0,0 +1 @@
{"source_epoch": 1752105237, "base_created": "2025-07-10T04:12:21.034425519Z", "container_epoch": 1752120742, "derivation": "max(source epoch,ceil immutable base created timestamp)"}

View file

@ -0,0 +1,9 @@
{
"image_build_succeeded": true,
"failed_phase": "image export only",
"reason": "Docker29 containerd store addresses untagged image by manifest digest, not iid config digest",
"config_digest": "sha256:ffae6c666403ab2f995dc017069bcb1a9aea9c6e9d21a55b2ae5a7df5d71ba64",
"manifest_digest": "sha256:7cee81d2fce0b5a189de9702f3e800684dd4155176110469a911f0d7ac188f10",
"resume_exact_existing_image_no_rebuild": true,
"same_trial": true
}

View file

@ -0,0 +1,52 @@
{
"source_binary_bytes_identical": true,
"layer_headers": {
"image.tar": [
{
"path": "app",
"mtime": 1752120740,
"uid": 0,
"gid": 0,
"mode": 493
},
{
"path": "app/gitea",
"mtime": 1792273215,
"uid": 0,
"gid": 0,
"mode": 493
},
{
"path": "app/gitea/gitea",
"mtime": 1752105237,
"uid": 0,
"gid": 0,
"mode": 493
}
],
"image-repeat.tar": [
{
"path": "app",
"mtime": 1752120740,
"uid": 0,
"gid": 0,
"mode": 493
},
{
"path": "app/gitea",
"mtime": 1792273359,
"uid": 0,
"gid": 0,
"mode": 493
},
{
"path": "app/gitea/gitea",
"mtime": 1752105237,
"uid": 0,
"gid": 0,
"mode": 493
}
]
},
"finding": "app/gitea parent directory mtime changes on COPY despite fixed input file mtime; explicit layer timestamp rewriting needed"
}

View file

@ -0,0 +1,12 @@
{
"no_cache_repeat": true,
"separate_context_from_independent_binary": true,
"returncode": 0,
"seconds": 11.14,
"identical_output_digests": false,
"digests": {
"containerimage.digest": "sha256:c24e0097f53f31308312ca2f8e411869a36860a7e54c24587e4cde346bbbd1f2",
"containerimage.config.digest": "sha256:f91ba4f17fbac1f563ccaef4f30aa4d9e2f59a950f75df388d78b57f73b3bf1f"
},
"registry_publication": false
}

View file

@ -0,0 +1,17 @@
{
"all_base_layers_and_rootless_runtime_config_preserved": true,
"all_oci_blob_digests_verified": true,
"archive_sha256": "e0e74653321383e133485191f0ac5af53aafb065fb5809e20ede2808cec91539",
"artifact_path": "/tmp/apps-forgejo-release-repro-r1/image.oci.tar",
"config_digest": "sha256:e9e81da43c5ea6895a95b6304337a40874e3275833be5f21fc6ffc104f79a2af",
"container_epoch": 1752120742,
"deployment_tag": null,
"final_layer_binary_exact": true,
"host_or_secret_mounts": false,
"image_manifest_digest": "sha256:9d12779f0936b9ccdbde402a9869eaad8bb91ba724d579b6c2983e8e58dabf96",
"local_runtime_image_loaded": false,
"oci_index_sha256": "24bd0e22e815dbc3470cad5a9dda509e68ad6bc751f2bfae85d02d95f8bc8cf5",
"privileged_containers": false,
"registry_publication": false,
"source_epoch": 1752105237
}

View file

@ -0,0 +1,24 @@
{
"argv": [
"docker",
"buildx",
"build",
"--load",
"--platform",
"linux/amd64",
"--pull=false",
"--network=none",
"--provenance=false",
"--sbom=false",
"--iidfile",
"/tmp/apps-forgejo-release-repro-r1/image-id",
"--metadata-file",
"/tmp/apps-forgejo-release-repro-r1/image-build-metadata.json",
"--build-arg",
"SOURCE_DATE_EPOCH=1752105237",
"/tmp/apps-forgejo-release-repro-r1/container-context"
],
"phase": "container-build",
"returncode": 0,
"seconds": 13.55
}

View file

@ -0,0 +1,12 @@
{
"all_base_layers_and_rootless_runtime_config_preserved": true,
"archive_sha256": "dca200a1aae8c62c4003e9d1817548b4c6dea11cee19a7687e92dbca4859d9f9",
"config_digest": "sha256:ffae6c666403ab2f995dc017069bcb1a9aea9c6e9d21a55b2ae5a7df5d71ba64",
"deployment_tag": null,
"final_layer_binary_exact": true,
"host_or_secret_mounts": false,
"local_export_reference": "sha256:7cee81d2fce0b5a189de9702f3e800684dd4155176110469a911f0d7ac188f10",
"local_image_id": "sha256:7cee81d2fce0b5a189de9702f3e800684dd4155176110469a911f0d7ac188f10",
"privileged_containers": false,
"registry_publication": false
}

View file

@ -0,0 +1,52 @@
{
"buildx.build.provenance": {
"builder": {
"id": ""
},
"buildType": "https://mobyproject.org/buildkit@v1",
"materials": [
{
"uri": "pkg:docker/code.forgejo.org/forgejo/forgejo?digest=sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b\u0026platform=linux%2Famd64",
"digest": {
"sha256": "41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b"
}
}
],
"invocation": {
"configSource": {
"entryPoint": "Dockerfile"
},
"parameters": {
"frontend": "dockerfile.v0",
"args": {
"build-arg:SOURCE_DATE_EPOCH": "1752105237",
"force-network-mode": "none"
},
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"environment": {
"platform": "linux/amd64"
}
}
},
"buildx.build.ref": "default/default/uhbf0ilfuaklbkxp6x34k9ci4",
"containerimage.config.digest": "sha256:ffae6c666403ab2f995dc017069bcb1a9aea9c6e9d21a55b2ae5a7df5d71ba64",
"containerimage.descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:7cee81d2fce0b5a189de9702f3e800684dd4155176110469a911f0d7ac188f10",
"size": 2850,
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
"containerimage.digest": "sha256:7cee81d2fce0b5a189de9702f3e800684dd4155176110469a911f0d7ac188f10",
"image.name": "moby-dangling@sha256:7cee81d2fce0b5a189de9702f3e800684dd4155176110469a911f0d7ac188f10"
}

View file

@ -0,0 +1,10 @@
{
"initial_observation": "Frozen original source-inventory hash did not match current public metadata; retained as an adverse comparison, not source drift.",
"root_provenance_commit": "16236ef",
"resolution": "Exact per-record reconstruction verifies original bytes. Only public identifier notation changed; frozen SHA map unmodified.",
"original_sha256": "77aabbe413aae6ac31c342304afa200d09ff3c74e3fc9f18d2af6919a816ab94",
"current_sha256": "866641169b73aa6e56d4f8f3d497eda28229bdf2aafe98daf5fefd09258338a1",
"reconstructor_sha256": "ec20b3378ece29dcf7faf4ad5e610e1e85bfa260121f2b8c537665f9530466e0",
"published_provenance_sha256": "770a5831999df8a8dc1b66dc65b738f58bedd4eb0dab56127f8d37b2b10107b2",
"reconstruction_verified": true
}

View file

@ -0,0 +1,16 @@
{
"argv": [
"make",
"GOFLAGS=-trimpath -buildvcs=false -p=4",
"RELEASE_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION_API=11.0.3+nk48.f2788b3f",
"TAGS=bindata timetzdata sqlite sqlite_unlock_notify",
"go-check",
"generate-backend",
"static-executable"
],
"phase": "embedded-and-static-backend",
"returncode": 0,
"seconds": 288.85
}

View file

@ -0,0 +1,16 @@
{
"controls": [
{
"case": "tampered-source",
"refused": true,
"exitcode": 1
},
{
"case": "existing-workspace",
"refused": true,
"exitcode": 1
}
],
"tampered_source_workspace_not_created": true,
"existing_trial_not_overwritten": true
}

View file

@ -0,0 +1,7 @@
{
"initial_patch_harness": "Python difflib omitted no-newline marker for immutable base-config JSON; git apply refused. Corrected generator emits standard marker without changing postimage bytes.",
"second_harness": "Dry-run assertion expected python3 prefix but owner target correctly uses absolute /usr/bin/python3 -I. Assertion corrected to exact emitted command.",
"result": "Final patch git apply --check and isolated apply pass; all six owner postimages exact; make dry run emits exactly two isolated build/container commands.",
"embedded_patch_whitespace": "git apply warned about literal context whitespace inside newly added immutable patch file; --whitespace=nowarn preserves byte-exact reviewed patch. No whitespace fix or source change.",
"build_restart": false
}

View file

@ -0,0 +1,81 @@
{
"argv": [
"bwrap",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/apps-forgejo-release-build-r1",
"/tmp/apps-forgejo-release-build-r1",
"--bind",
"/tmp/apps-forgejo-release-repro-r1/gocache",
"/tmp/apps-forgejo-release-repro-r1/gocache",
"--ro-bind",
"/tmp/forgejo-independent-r1/go",
"/tmp/forgejo-independent-r1/go",
"--ro-bind",
"/tmp/forgejo-independent-r1/gomodcache",
"/tmp/forgejo-independent-r1/gomodcache",
"--chdir",
"/tmp/apps-forgejo-release-build-r1/source",
"--",
"env",
"-i",
"PATH=/tmp/apps-forgejo-release-build-r1/node-v22.15.0-linux-x64/bin:/tmp/forgejo-independent-r1/go/bin:/usr/bin:/bin",
"HOME=/tmp/apps-forgejo-release-build-r1/home",
"NPM_CONFIG_USERCONFIG=/tmp/apps-forgejo-release-build-r1/empty.npmrc",
"NPM_CONFIG_GLOBALCONFIG=/tmp/apps-forgejo-release-build-r1/empty-global.npmrc",
"NPM_CONFIG_CACHE=/tmp/apps-forgejo-release-build-r1/npm-cache",
"NPM_CONFIG_REGISTRY=https://registry.npmjs.org",
"NPM_CONFIG_AUDIT=false",
"NPM_CONFIG_FUND=false",
"NPM_CONFIG_UPDATE_NOTIFIER=false",
"PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1",
"GOTOOLCHAIN=local",
"GOPROXY=off",
"GOSUMDB=off",
"GOVCS=*:off",
"GOPATH=/tmp/apps-forgejo-release-build-r1/gopath",
"GOMODCACHE=/tmp/forgejo-independent-r1/gomodcache",
"GOCACHE=/tmp/apps-forgejo-release-repro-r1/gocache",
"CGO_ENABLED=1",
"GOMAXPROCS=4",
"GOFLAGS=-trimpath -buildvcs=false -p=4",
"SOURCE_DATE_EPOCH=1752105237",
"TZ=UTC",
"LC_ALL=C",
"NPM_CONFIG_OFFLINE=true",
"NODE_OPTIONS=--max-old-space-size=4096",
"GIT_CONFIG_NOSYSTEM=1",
"GIT_CONFIG_GLOBAL=/dev/null",
"GIT_TERMINAL_PROMPT=0",
"make",
"GOFLAGS=-trimpath -buildvcs=false -p=4",
"RELEASE_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION_API=11.0.3+nk48.f2788b3f",
"TAGS=bindata timetzdata sqlite sqlite_unlock_notify",
"go-check",
"generate-backend",
"static-executable"
],
"phase": "normalized-embedded-and-static-backend",
"returncode": 0,
"seconds": 17.39
}

View file

@ -0,0 +1,9 @@
{
"binary_sha256": "4df429454b0289c6df76d7fd95d41394ebc286191809349b2c1e655b6be494e4",
"explicit_make_GOFLAGS": true,
"file_and_directory_times_normalized": true,
"owner_approval": false,
"predecessor_sha256": "bc34e12a8794e556764ab2325c31615f6ddbcc7a314aabf106b580e470189911",
"refinement_same_trial": true,
"source_locks_preserved": true
}

View file

@ -0,0 +1,6 @@
{
"two_independent_workspace_frontend_builds": true,
"asset_files": 628,
"byte_identical": true,
"different_paths": []
}

View file

@ -0,0 +1,13 @@
{
"argv": [
"make",
"GOFLAGS=-trimpath -buildvcs=false -p=4",
"RELEASE_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION_API=11.0.3+nk48.f2788b3f",
"frontend"
],
"phase": "frontend",
"returncode": 0,
"seconds": 108.61
}

View file

@ -0,0 +1,55 @@
{
"buildx.build.provenance": {
"builder": {
"id": ""
},
"buildType": "https://mobyproject.org/buildkit@v1",
"materials": [
{
"uri": "pkg:docker/code.forgejo.org/forgejo/forgejo?digest=sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b\u0026platform=linux%2Famd64",
"digest": {
"sha256": "41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b"
}
}
],
"invocation": {
"configSource": {
"entryPoint": "Dockerfile"
},
"parameters": {
"frontend": "dockerfile.v0",
"args": {
"build-arg:SOURCE_DATE_EPOCH": "1752120742",
"force-network-mode": "none",
"no-cache": ""
},
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"environment": {
"platform": "linux/amd64"
}
}
},
"buildx.build.ref": "default/default/n1p4kafj9m6flpod8a9dbtxkm",
"containerimage.config.digest": "sha256:e9e81da43c5ea6895a95b6304337a40874e3275833be5f21fc6ffc104f79a2af",
"containerimage.descriptor": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:9d12779f0936b9ccdbde402a9869eaad8bb91ba724d579b6c2983e8e58dabf96",
"size": 2841,
"annotations": {
"org.opencontainers.image.created": "2025-07-10T04:12:22Z"
},
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
"containerimage.digest": "sha256:9d12779f0936b9ccdbde402a9869eaad8bb91ba724d579b6c2983e8e58dabf96"
}

View file

@ -0,0 +1,2 @@
/usr/bin/python3 -I tools/forgejo-release-build.py build --workspace "/tmp/railiance-apps-forgejo-release"
/usr/bin/python3 -I tools/forgejo-release-build.py container --workspace "/tmp/railiance-apps-forgejo-release"

View file

@ -0,0 +1,41 @@
049718cc0ce4f58de90f7b752b82a78868a21fe7685af1c603ba1d22ae8a4d67 node-v22.15.0-aix-ppc64.tar.gz
1013d381e8a4ebe64484eed07db2637a570090fdef87924bf0beb92d9ba7cc92 node-v22.15.0-arm64.msi
92eb58f54d172ed9dee320b8450f1390db629d4262c936d5c074b25a110fed02 node-v22.15.0-darwin-arm64.tar.gz
6e278a107d50da24b644dd26810a639a5f8ca67b55086e6b693caabcbb759912 node-v22.15.0-darwin-arm64.tar.xz
f7f42bee60d602783d3a842f0a02a2ecd9cb9d7f6f3088686c79295b0222facf node-v22.15.0-darwin-x64.tar.gz
7dab3f93551d88f1e63db6b32bae6d4858e16740e9849ebbeac1d43f5055d8f0 node-v22.15.0-darwin-x64.tar.xz
cda8bbbfb4f7fb19b65efd5faabc97ccea1e94422e7066b9a1e70280c1ca6453 node-v22.15.0-headers.tar.gz
1f0377a9f3dbb85959d38a6ebc6e7e189f520f509488c401be4970d8718a14e9 node-v22.15.0-headers.tar.xz
c3582722db988ed1eaefd590b877b86aaace65f68746726c1f8c79d26e5cc7de node-v22.15.0-linux-arm64.tar.gz
d68adf72c531f1118bee75b20ffbc5911accfda5e73454a798625464b40a4adf node-v22.15.0-linux-arm64.tar.xz
639a3ee3217049ba20e6f05651c7281a9d007111196387eb177081af8851e52f node-v22.15.0-linux-armv7l.tar.gz
998da599863b8104992472ca4e738842b5e9b76b3665ea8bc13bd1ca52019426 node-v22.15.0-linux-armv7l.tar.xz
a744107732546a3b112630cde1cc2db681559668cea36bee851348ed036c101c node-v22.15.0-linux-ppc64le.tar.gz
e12384dd6d8e80ee1cdb8cf0f38545d3cc2a5b99f7dd2c6ae05067222d39ff7a node-v22.15.0-linux-ppc64le.tar.xz
600afa22128e5d1b7d472109b8f9f93ace161dab3c1a8a642895675471916d86 node-v22.15.0-linux-s390x.tar.gz
308e14b2b20445e03fbec920921b6b7f579832516b3e826b0d67be8acf0ac6f6 node-v22.15.0-linux-s390x.tar.xz
29d1c60c5b64ccdb0bc4e5495135e68e08a872e0ae91f45d9ec34fc135a17981 node-v22.15.0-linux-x64.tar.gz
dafe2e8f82cb97de1bd10db9e2ec4c07bbf53389b0799b1e095a918951e78fd4 node-v22.15.0-linux-x64.tar.xz
0bc096a279cd7cbc57bf3a6c6570f3ca03b3ab684d1878a1425919e9b6b76317 node-v22.15.0.pkg
8e64edbc70a7188873e0f4f07c812fa747c27c477d558b23dcb135bdf5e32298 node-v22.15.0.tar.gz
e7c4226d1d92f33ad854d6da4f7e519e77690b8e73f93496881f8c539174d9df node-v22.15.0.tar.xz
d0615f16a280f4660a6c83d472a0b2e72122e47f4c82754a5ed950d54d0cbe3d node-v22.15.0-win-arm64.7z
737cd7046d96575c2d6cd36e0355afba54b79296b0f403ef4b3b1b5852b10ab6 node-v22.15.0-win-arm64.zip
45471a4f77cdaacce971ab7a6698659ed872606f7b4b0aecdfe5689627134cb2 node-v22.15.0-win-x64.7z
06067d4f0d463f90ed803d5eca5b039a05dec5d70fc7b7cc254803a59bd0e27c node-v22.15.0-win-x64.zip
f9534de57244052cb245ca3af29ac40e947b5735b05b37a28efac7bc82809a8a node-v22.15.0-win-x86.7z
961e362568b91340b0229d27a64e3925ce5d807898a86074d61cab578f151843 node-v22.15.0-win-x86.zip
499d4a47b9db6db47114a31ceab69c69a7265900a29a5da53746c00d13dbad88 node-v22.15.0-x64.msi
1d884cdc5517126c2da00e23b445050a2f1e8b7bcaa57e0feb2c2481d347fb0a node-v22.15.0-x86.msi
ebdab2adc9ff5fb09f852b665875af35138b9ee5725a3927bb85d7cfd36e059f win-arm64/node.exe
6c43922c747e0c07fcc4ceb64f08e98854a5c1b31665764076f5783556973d16 win-arm64/node.lib
fb437ae33e9e5fff6792ba7a1e81d1a1dab5e346b57328e9a0d2ec7b9bdb309d win-arm64/node_pdb.7z
7785c2aec7a86efce7707a1abc7dee58bd11e610f748f74e243789f9daff1e13 win-arm64/node_pdb.zip
77bdff912b1c569b3e693fe126f619337c3e9d73dafbc4d0bf1d4f1f6a145761 win-x64/node.exe
e731ad9af05eabe6bd941442b19dd6037f559a19a2fa8df2a8297ccb46ed7577 win-x64/node.lib
8c886b364ac8c65bef429d9d127f457e5c5755d19c4df294e9d187763dc6c3ea win-x64/node_pdb.7z
132d18a1f522d20bc63d66b0eabe8089a2643ac61e1585d0269013f01ef1baef win-x64/node_pdb.zip
0c20613ca23c8b8e7ca3d1ee5da9173dd9289f9833cff18649c192e4ce42c9b4 win-x86/node.exe
5f66397542e27adb288e1742720cedbeaba8a2034ebd58397306b2905f4acd2d win-x86/node.lib
93d5377d08c9c5edf71f0c52e801c1ae55ace3e45e107fdac61ec7e04e0e5ff5 win-x86/node_pdb.7z
7601c2ca6c1a64b834af03ac2cb78b8485dc501e8b5b980ac4702ff8d6ad0cfb win-x86/node_pdb.zip

View file

@ -0,0 +1,9 @@
{
"binary_sha256": "4df429454b0289c6df76d7fd95d41394ebc286191809349b2c1e655b6be494e4",
"explicit_make_GOFLAGS": true,
"file_and_directory_times_normalized": true,
"owner_approval": false,
"predecessor_sha256": "4df429454b0289c6df76d7fd95d41394ebc286191809349b2c1e655b6be494e4",
"refinement_same_trial": true,
"source_locks_preserved": true
}

View file

@ -0,0 +1,16 @@
{
"argv": [
"make",
"GOFLAGS=-trimpath -buildvcs=false -p=4",
"RELEASE_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION_API=11.0.3+nk48.f2788b3f",
"TAGS=bindata timetzdata sqlite sqlite_unlock_notify",
"go-check",
"generate-backend",
"static-executable"
],
"phase": "normalized-embedded-and-static-backend",
"returncode": 0,
"seconds": 7.4
}

View file

@ -0,0 +1,13 @@
{
"argv": [
"npm",
"ci",
"--offline",
"--ignore-scripts",
"--no-audit",
"--no-fund"
],
"phase": "npm-offline-ci",
"returncode": 0,
"seconds": 72.94
}

View file

@ -0,0 +1,55 @@
{
"buildx.build.provenance": {
"builder": {
"id": ""
},
"buildType": "https://mobyproject.org/buildkit@v1",
"materials": [
{
"uri": "pkg:docker/code.forgejo.org/forgejo/forgejo?digest=sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b\u0026platform=linux%2Famd64",
"digest": {
"sha256": "41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b"
}
}
],
"invocation": {
"configSource": {
"entryPoint": "Dockerfile"
},
"parameters": {
"frontend": "dockerfile.v0",
"args": {
"build-arg:SOURCE_DATE_EPOCH": "1752120742",
"force-network-mode": "none",
"no-cache": ""
},
"locals": [
{
"name": "context"
},
{
"name": "dockerfile"
}
]
},
"environment": {
"platform": "linux/amd64"
}
}
},
"buildx.build.ref": "default/default/jm2m1kzz79ebeomnmao91kvkq",
"containerimage.config.digest": "sha256:e9e81da43c5ea6895a95b6304337a40874e3275833be5f21fc6ffc104f79a2af",
"containerimage.descriptor": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:9d12779f0936b9ccdbde402a9869eaad8bb91ba724d579b6c2983e8e58dabf96",
"size": 2841,
"annotations": {
"org.opencontainers.image.created": "2025-07-10T04:12:22Z"
},
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
"containerimage.digest": "sha256:9d12779f0936b9ccdbde402a9869eaad8bb91ba724d579b6c2983e8e58dabf96"
}

View file

@ -0,0 +1,22 @@
import subprocess,pathlib,json,time,os
r=pathlib.Path('/tmp/apps-forgejo-release-build-r1');review=pathlib.Path('/tmp/forgejo-independent-r1');env=json.loads((r/'build-env.json').read_text());src=r/'source'
# Normalise immutable source metadata before webpack and bindata generation.
for root,dirs,files in os.walk(src):
dirs[:]=[d for d in dirs if d not in ('node_modules','.git')]
for name in files:
p=pathlib.Path(root)/name
if not p.is_symlink():os.utime(p,(1752105237,1752105237))
base=['bwrap','--unshare-net','--ro-bind','/usr','/usr','--ro-bind','/lib','/lib','--ro-bind','/lib64','/lib64','--symlink','usr/bin','/bin','--proc','/proc','--dev','/dev','--tmpfs','/tmp','--bind',str(r),str(r),'--ro-bind',str(review/'go'),str(review/'go'),'--ro-bind',str(review/'gomodcache'),str(review/'gomodcache'),'--chdir',str(src),'--','env','-i']+[k+'='+v for k,v in env.items()]
results=[]
commands=[('verify-go-modules',['go','mod','verify']),('frontend',['make','frontend']),('embedded-and-static-backend',['make','RELEASE_VERSION=11.0.3+nk48.f2788b3f','FORGEJO_VERSION=11.0.3+nk48.f2788b3f','FORGEJO_VERSION_API=11.0.3+nk48.f2788b3f','TAGS=bindata timetzdata sqlite sqlite_unlock_notify','go-check','generate-backend','static-executable'])]
for name,args in commands:
if name=='embedded-and-static-backend':
for folder in ['public','options','templates','modules/migration/schemas']:
for root,dirs,files in os.walk(src/folder):
for n in files:
p=pathlib.Path(root)/n
if not p.is_symlink():os.utime(p,(1752105237,1752105237))
start=time.time()
with (r/(name+'.log')).open('w') as out:result=subprocess.run(base+args,stdout=out,stderr=subprocess.STDOUT)
item={'phase':name,'argv':args,'returncode':result.returncode,'seconds':round(time.time()-start,2)};results.append(item);(r/'source-build-results.json').write_text(json.dumps(results,indent=2));print(json.dumps(item),flush=True)
if result.returncode:print((r/(name+'.log')).read_text()[-5000:],flush=True);raise SystemExit(result.returncode)

View file

@ -0,0 +1,36 @@
[
{
"phase": "verify-go-modules",
"argv": [
"go",
"mod",
"verify"
],
"returncode": 0,
"seconds": 6.88
},
{
"phase": "frontend",
"argv": [
"make",
"frontend"
],
"returncode": 0,
"seconds": 103.46
},
{
"phase": "embedded-and-static-backend",
"argv": [
"make",
"RELEASE_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION=11.0.3+nk48.f2788b3f",
"FORGEJO_VERSION_API=11.0.3+nk48.f2788b3f",
"TAGS=bindata timetzdata sqlite sqlite_unlock_notify",
"go-check",
"generate-backend",
"static-executable"
],
"returncode": 0,
"seconds": 350.05
}
]

View file

@ -0,0 +1,8 @@
{
"binary_sha256": "bc34e12a8794e556764ab2325c31615f6ddbcc7a314aabf106b580e470189911",
"bytes": 111162624,
"path": "/tmp/apps-forgejo-release-build-r1/forgejo-predecessor-no-trimpath",
"source_build_complete": true,
"reproducibility_accepted": false,
"reason": "upstream Make overrides environment GOFLAGS; input directories were not yet normalised"
}

View file

@ -0,0 +1,5 @@
{
"lock_sha256": "29f329b204a2a5f72084b50d59e8445486ff4799450097c6f3908c74e87a233a",
"module_cache": "/tmp/forgejo-independent-r1/gomodcache",
"public_archives_verified": true
}

View file

@ -0,0 +1,9 @@
{
"node_archive_reused_existing_download": true,
"official_checksum_url": "https://nodejs.org/dist/v22.15.0/SHASUMS256.txt",
"archive_sha256": "dafe2e8f82cb97de1bd10db9e2ec4c07bbf53389b0799b1e095a918951e78fd4",
"official_checksum_refetched_empty_environment": true,
"proxy_environment_disabled": true,
"HOME_isolated": true,
"private_credential_or_config_access": false
}

View file

@ -0,0 +1 @@
{"returncode": 0, "seconds": 46.13}

View file

@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""Build-only public Forgejo owner recipe; no deployment or publication code."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tarfile
import time
OWNER=Path(__file__).resolve().parents[1]
INPUTS=OWNER/'build/forgejo'
LOCK=json.loads((INPUTS/'inputs.lock.json').read_text())
def digest(path):
h=hashlib.sha256()
with Path(path).open('rb') as f:
for block in iter(lambda:f.read(1024*1024),b''):h.update(block)
return h.hexdigest()
def save(path,data):
Path(path).write_text(json.dumps(data,indent=2,sort_keys=True)+'\n')
def check(path,expected):
if digest(path)!=expected:raise ValueError('frozen public input digest differs: '+str(path))
def base_env(work):
return {'PATH':str(work/'node-v22.15.0-linux-x64/bin')+':'+str(work/'go/bin')+':/usr/bin:/bin',
'HOME':str(work/'home'),'GOTOOLCHAIN':'local','GOPROXY':'off','GOSUMDB':'off','GOVCS':'*:off',
'GOPATH':str(work/'gopath'),'GOMODCACHE':json.loads((work/'prepared.json').read_text())['module_cache'],
'GOCACHE':str(work/'gocache'),'CGO_ENABLED':'1','GOMAXPROCS':'4',
'GOFLAGS':'-trimpath -buildvcs=false -p=4','SOURCE_DATE_EPOCH':str(LOCK['source_date_epoch']),
'TZ':'UTC','LC_ALL':'C','NODE_OPTIONS':'--max-old-space-size=4096',
'GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_TERMINAL_PROMPT':'0',
'NPM_CONFIG_USERCONFIG':str(work/'empty.npmrc'),'NPM_CONFIG_GLOBALCONFIG':str(work/'empty-global.npmrc'),
'NPM_CONFIG_CACHE':str(work/'npm-cache'),'NPM_CONFIG_REGISTRY':'https://registry.npmjs.org',
'NPM_CONFIG_OFFLINE':'true','NPM_CONFIG_AUDIT':'false','NPM_CONFIG_FUND':'false',
'NPM_CONFIG_UPDATE_NOTIFIER':'false','PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD':'1'}
def sandbox(work,env):
cache=Path(env['GOMODCACHE'])
return ['bwrap','--unshare-net','--ro-bind','/usr','/usr','--ro-bind','/lib','/lib',
'--ro-bind','/lib64','/lib64','--symlink','usr/bin','/bin','--proc','/proc','--dev','/dev',
'--tmpfs','/tmp','--bind',str(work),str(work),'--ro-bind',str(cache),str(cache),
'--chdir',str(work/'source'),'--','env','-i']+[k+'='+v for k,v in env.items()]
def run(work,name,args,env=None,offline=False,cwd=None):
actual=sandbox(work,env)+args if offline else args
start=time.time()
with (work/(name+'.log')).open('w') as out:
proc=subprocess.Popen(actual,cwd=cwd or work,env={"PATH":"/usr/bin:/bin","HOME":str(work/"home")} if offline else env,stdout=out,stderr=subprocess.STDOUT)
save(work/'process.json',{'phase':name,'pid':proc.pid,'driver_pid':os.getpid(),'argv':args,'state':'running'})
code=proc.wait()
result={'phase':name,'argv':args,'returncode':code,'seconds':round(time.time()-start,2)}
save(work/(name+'-result.json'),result);save(work/'process.json',dict(result,state='complete',pid=None,driver_pid=None))
print(json.dumps(result),flush=True)
if code:raise RuntimeError('phase failed; retained log: '+str(work/(name+'.log')))
return result
def checked_inputs(work):
for n,h in LOCK['host_tools'].items():check(n,h)
for n,h in LOCK['lockfiles'].items():check(work/'source'/n,h)
for x in LOCK['reviewed_outputs']:check(work/'source'/x['path'],x['sha256'])
def prepare(a,work):
if work.exists():raise ValueError('workspace must be new; existing trial is never restarted or overwritten')
for n,key in [('source_archive','source_archive_sha256'),('go_archive','go_archive_sha256'),('node_archive','node_archive_sha256')]:
if not getattr(a,n):raise ValueError('explicit '+n+' required')
check(getattr(a,n),LOCK[key])
if not a.module_cache:raise ValueError('explicit public module cache required')
cache=Path(a.module_cache).resolve();assert cache.is_dir()
check(INPUTS/'required-claim-values.patch',LOCK['patch_sha256'])
for n,h in LOCK['host_tools'].items():check(n,h)
work.mkdir(parents=True);(work/'home').mkdir()
with tarfile.open(a.source_archive) as t:
# The official archive is digest-bound before any member is extracted.
parent=work/'unpacked';parent.mkdir();t.extractall(parent,filter='data')
folders=list(parent.iterdir());assert len(folders)==1 and folders[0].is_dir();folders[0].rename(work/'source');parent.rmdir()
for n in ['go_archive','node_archive']:
with tarfile.open(getattr(a,n)) as t:t.extractall(work,filter='data')
for n in ['empty.npmrc','empty-global.npmrc']:(work/n).write_text('')
if a.go_cache_seed:shutil.copytree(Path(a.go_cache_seed).resolve(),work/'gocache')
if a.npm_cache_seed:shutil.copytree(Path(a.npm_cache_seed).resolve(),work/'npm-cache')
subprocess.run(['/usr/bin/git','apply','--check',str(INPUTS/'required-claim-values.patch')],cwd=work/'source',check=True,env={'PATH':'/usr/bin:/bin','HOME':str(work/'home'),'GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null'})
subprocess.run(['/usr/bin/git','apply',str(INPUTS/'required-claim-values.patch')],cwd=work/'source',check=True,env={'PATH':'/usr/bin:/bin','HOME':str(work/'home'),'GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null'})
save(work/'prepared.json',{'module_cache':str(cache),'lock_sha256':digest(INPUTS/'inputs.lock.json'),'public_archives_verified':True})
checked_inputs(work)
packages=json.loads((work/'source/package-lock.json').read_text())['packages']
for name,package in packages.items():
if not name:continue
if not package.get('resolved','').startswith('https://registry.npmjs.org/') or not package.get('integrity','').startswith('sha512-'):
raise ValueError('non-public/non-integrity-bound npm package: '+name)
print(json.dumps({'prepared':True,'lockfile_npm_packages':len(packages)-1}))
def normalize(folder):
epoch=LOCK['source_date_epoch']
for root,dirs,files in os.walk(folder):
dirs[:]=[x for x in dirs if x not in ('node_modules','.git')]
for n in files:
p=Path(root)/n
if not p.is_symlink():os.utime(p,(epoch,epoch))
def build(work):
checked_inputs(work);env=base_env(work)
run(work,'verify-go-modules',['go','mod','verify'],env,True)
run(work,'npm-offline-ci',['npm','ci','--offline','--ignore-scripts','--no-audit','--no-fund'],env,True)
normalize(work/'source')
version=['GOFLAGS=-trimpath -buildvcs=false -p=4','RELEASE_VERSION='+LOCK['release_version'],'FORGEJO_VERSION='+LOCK['release_version'],'FORGEJO_VERSION_API='+LOCK['release_version']]
run(work,'frontend',['make']+version+['frontend'],env,True)
for n in ['public','options','templates','modules/migration/schemas']:normalize(work/'source'/n)
run(work,'embedded-and-static-backend',['make']+version+['TAGS='+LOCK['tags'],'go-check','generate-backend','static-executable'],env,True)
checked_inputs(work)
save(work/'build.json',{'binary_sha256':digest(work/'source/gitea'),'source_locks_preserved':True,'frontend_built':True,'tags':LOCK['tags'],'image_deployment_selected':False})
def verify(work):
env=base_env(work);binary=str(work/'source/gitea')
env.update(GITEA_WORK_DIR=str(work/'verify-work'),GITEA_CUSTOM=str(work/'verify-custom'),GITEA_APP_INI=str(work/'absent-app.ini'))
(work/'verify-work').mkdir(exist_ok=True)
run(work,'binary-version',[binary,'--version'],env,True)
run(work,'new-cli-help',[binary,'admin','auth','add-oauth','--help'],env,True)
assert '--required-claim-values' in (work/'new-cli-help.log').read_text()
target=work/'embedded-extracted'
if target.exists():raise ValueError('retain prior verification; extraction target already exists')
run(work,'embedded-extract',[binary,'embedded','extract','--include-vendored','--destination',str(target)],env,True)
records={}
for folder in ['public','options','templates']:
for f in sorted((work/'source'/folder).rglob('*')):
if not f.is_file():continue
n=str(f.relative_to(work/'source'));q=target/n
if not q.is_file() or digest(f)!=digest(q):raise ValueError('embedded asset missing/different: '+n)
records[n]=digest(f)
assert 'public/assets/js/index.js' in records and 'public/assets/css/index.css' in records
assert 'public/assets/licenses.txt' in records
save(work/'embedded-files.json',records)
readelf=subprocess.run(['/usr/bin/readelf','-l',binary],capture_output=True,text=True,check=True,env={'PATH':'/usr/bin:/bin'})
assert 'INTERP' not in readelf.stdout
save(work/'verification.json',{'binary_sha256':digest(binary),'embedded_files':len(records),
'embedded_file_manifest_sha256':digest(work/'embedded-files.json'),'all_public_options_templates_exact':True,
'no_dynamic_interpreter':True,'new_cli_flag_present':True,'network':'bwrap --unshare-net',
'source_locks_preserved':True,'architecture_approval':False,'native_admission':False})
print((work/'verification.json').read_text())
def container(work):
verified=json.loads((work/'verification.json').read_text());check(work/'source/gitea',verified['binary_sha256'])
context=work/'container-context'
if context.exists():raise ValueError('container trial already exists; retain evidence')
context.mkdir();shutil.copyfile(work/'source/gitea',context/'gitea')
os.utime(context/'gitea',(LOCK['source_date_epoch'],)*2)
(context/'Dockerfile').write_text('FROM '+LOCK['runtime_base']+'\nARG SOURCE_DATE_EPOCH\nCOPY --chown=0:0 --chmod=755 gitea /app/gitea/gitea\n')
os.utime(context/'Dockerfile',(LOCK['source_date_epoch'],)*2)
config=work/'docker-config';config.mkdir(exist_ok=True)
env={'PATH':'/usr/bin:/bin','HOME':str(work/'home'),'DOCKER_CONFIG':str(config),'DOCKER_HOST':'unix:///var/run/docker.sock'}
run(work,'container-build',['docker','buildx','build','--load','--platform',LOCK['platform'],'--pull=false',
'--network=none','--provenance=false','--sbom=false','--iidfile',str(work/'image-id'),
'--metadata-file',str(work/'image-build-metadata.json'),'--build-arg','SOURCE_DATE_EPOCH='+str(LOCK['source_date_epoch']),str(context)],env)
image=(work/'image-id').read_text().strip();assert image.startswith('sha256:')
run(work,'container-save',['docker','image','save','--output',str(work/'image.tar'),image],env)
with tarfile.open(work/'image.tar') as archive:
manifest=json.load(archive.extractfile('manifest.json'));assert len(manifest)==1
item=manifest[0];config_bytes=archive.extractfile(item['Config']).read()
assert 'sha256:'+hashlib.sha256(config_bytes).hexdigest()==image
config=json.loads(config_bytes)
base_bytes=(INPUTS/'runtime-base-config.json').read_bytes();assert hashlib.sha256(base_bytes).hexdigest()==LOCK['runtime_base_config_sha256']
original=json.loads(base_bytes)
assert config['rootfs']['diff_ids'][:-1]==original['rootfs']['diff_ids']
for key in ['User','Entrypoint','Cmd','Env','WorkingDir','Volumes']:
assert config['config'].get(key)==original['config'].get(key),key
assert config['config']['User']=='1000:1000'
found=False
with tarfile.open(fileobj=archive.extractfile(item['Layers'][-1]),mode='r|*') as layer:
for member in layer:
if member.name.lstrip('./')=='app/gitea/gitea':
h=hashlib.sha256();stream=layer.extractfile(member)
for block in iter(lambda:stream.read(1024*1024),b''):h.update(block)
assert h.hexdigest()==verified['binary_sha256'];found=True
assert found
save(work/'container-result.json',{'local_image_id':image,'archive_sha256':digest(work/'image.tar'),
'all_base_layers_and_rootless_runtime_config_preserved':True,'final_layer_binary_exact':True,
'registry_publication':False,'deployment_tag':None,'host_or_secret_mounts':False,'privileged_containers':False})
print((work/'container-result.json').read_text())
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('phase',choices=['prepare','acquire-npm','build','verify','container'])
parser.add_argument('--workspace',required=True)
for n in ['source-archive','go-archive','node-archive','module-cache','go-cache-seed','npm-cache-seed']:parser.add_argument('--'+n)
a=parser.parse_args();work=Path(a.workspace).resolve()
if not str(work).startswith('/tmp/'):raise ValueError('isolated build workspace must be under /tmp')
if a.phase=='prepare':return prepare(a,work)
assert json.loads((work/'prepared.json').read_text())['lock_sha256']==digest(INPUTS/'inputs.lock.json')
if a.phase=='acquire-npm':
checked_inputs(work);env=base_env(work);env['NPM_CONFIG_OFFLINE']='false'
return run(work,'npm-public-acquire',['npm','ci','--ignore-scripts','--no-audit','--no-fund'],env,cwd=work/'source')
{'build':build,'verify':verify,'container':container}[a.phase](work)
if __name__=='__main__':main()