30 KiB
ENVIRONMENT.md
Specification for a Mechanical Compiler instance.
| Revision | 5.1 (2026-08-17) |
| Supersedes | Revisions 1 through 4 |
| Basis | Revision 4, reconciled against the proven staging build, then work order 002 |
| Scope | Host-agnostic. Applies to any instance. |
| Instance state | STAGING-STATE.md, and later PRODUCTION-STATE.md |
| Failure evidence | FAILURES.md |
| Project sequence | ROADMAP.md |
0. How to use this document
This describes what an instance must be. It does not describe what any particular host currently is — that lives in the corresponding state file.
Revision 4 mixed the two, which is why it aged badly the moment a real host
disagreed with it. Everything specific to srv-b has been removed.
Authority order
- The instance state file — factual, wins on any question of what is true.
FAILURES.md— evidence from contact with hosts. Read this before writing automation, not after.- This document — the specification, corrected whenever proven facts invalidate it.
ROADMAP.md— product sequence. Environment work never invents application code.
Markers
| Marker | Meaning |
|---|---|
| REQ | Required. The work is blocked or wrong without it. |
| PREF | Preferred. Substitute freely, but record the substitution. |
| PROVEN | Established by a real build. Do not re-derive; see the cited failure. |
| ASSUMED | Decided without confirmation. Listed in §16. |
| DEFERRED | Deliberately postponed. Not a defect, not a gap. |
| INSTANCE | A value supplied per instance, not fixed here. |
Provisioning method
REQ — manual first. One command group at a time. Failures are recorded before they are corrected. The smallest corrective experiment is preferred over the one that also fixes three adjacent worries.
Automation is derived from a proven manual procedure, never written ahead of
one. Revision 4 said the opposite. FAILURES.md is why it changed: three of
this document's assumptions were wrong in ways only a real host revealed, and
one of them (F-003) produced a container that booted, reported success, and had
four broken services.
1. Five decisions that shape everything below
1.1 The 2D path and the 3D path are separable
The catalogue's product is a 2D cross-section rendered to SVG. Shapely plus our own code covers that completely. CadQuery/OCCT is needed only for STEP and mesh export.
They stay separate — separate requirements files, separate code paths, and a CI job running the suite with the CAD dependencies absent. Three reasons, none of which is packaging politics:
- Part II §16 requires the control-plane / execution-plane separation regardless of distribution.
- A slow OCCT import must never land in the request path for a page that only draws a cross-section.
- Test-suite speed — the difference between running 123 fixtures on every save and only before commit.
CadQuery is a default-installed dependency. Revision 1 restricted it as a defence against YunoHost's "resource-hungry" criterion; that was overcalibrated. The criterion reads "compared to their features" and is aimed at marginal apps. The boundary stays; the apology is gone.
1.2 YunoHost and Docker are parallel targets, not sequential
YunoHost apps install natively — apt, venv, systemd, nginx — and the project does not want Docker inside YunoHost. Neither blocks the other, and neither should be built "in order to" reach the other.
1.3 Every instance mirrors production topology, not production exposure
An instance runs two containers: an application container that never
terminates TLS, and a reverse proxy that does. A single container serving
directly would never exercise the X-Forwarded-Proto path.
Staging is not publicly reachable and does not use the public FQDN. That forces the promotion path to be exercised rather than assumed.
1.4 Directory layout mirrors YunoHost conventions
install_dir, data_dir, a dedicated system user, a port treated as data. The
eventual manifest.toml resource block then describes what already exists.
1.5 Configuration comes from the environment, never the filesystem
No path, port, URL or secret is hardcoded or discovered by convention. One env
file; the same variables become ENV in a Dockerfile and ynh_add_config
substitutions in a package.
The check: moving an instance to a different hostname is a one-variable change.
2. Host requirements
REQ — Proxmox VE 8.x, Debian 12 base.
REQ — Two bridges (§3). REQ — Directory storage for backups and templates; LVM-thin or equivalent for container volumes.
PREF — At least 12 CPU threads and 24 GiB RAM if the instance is also a development host. The geometry solvers are single-threaded, so single-thread performance governs fixture regeneration while thread count governs test parallelism. An older CPU without AVX is acceptable; all compiled wheels target an SSE2 baseline.
REQ — Time zone and NTP configured; clock synchronised.
INSTANCE — Hostname, addressing, storage pool names, template path.
3. Network
3.1 Two bridges
REQ — A management bridge with a physical port, carrying the host's own address. The host uses it for egress and administration.
REQ — A service bridge with no physical port, carrying the host at
.1. This is the only network the containers sit on.
A portless bridge needs no cable, no switch configuration and no spare NIC. It exists so the containers cannot be reached from the physical network at all.
3.2 Containers have exactly one interface
PROVEN (F-017) — Each container has a single interface on the service bridge only, with the host as gateway. No management-network interface.
Revision 4 gave each container an address on both bridges, on the unstated assumption that a workstation on the physical LAN would browse the instance. That assumption was wrong and put both containers on the operator's home network.
PROVEN (F-021) — After any interface change, the guest's
/etc/network/interfaces must be inspected. pct set --delete netN removes the
LXC interface but leaves the stanza in the guest, and ifup -a then fails at
boot on a device that no longer exists. See §15 gate 1 for the assertion that
catches this.
3.3 Isolation is routing, not interfaces
PROVEN (F-018) — Removing an interface removes an address, not a route. With forwarding enabled and a masquerade for internet access, containers reach the physical LAN through the host, translated to its address.
REQ — These rules, in this order:
nat POSTROUTING
-s <SVC_NET> -d <LAN_NET> -j RETURN # before any masquerade
-s <SVC_NET> -o <WG_IF> -j MASQUERADE
-s <SVC_NET> -o <MGMT> -j MASQUERADE
filter FORWARD
-s <SVC_NET> -d <SVC_NET> -j ACCEPT
-s <SVC_NET> -d <LAN_NET> -j DROP
REQ — Rule order is load-bearing. RETURN must precede both masquerades.
Rules must be persisted, and the persisted set verified to match the live set.
REQ — The isolation must be asserted negatively: the LAN gateway is unreachable from both containers. Asserting that the interface is gone is not sufficient — that is exactly the mistake F-018 records.
Expected and correct: containers can reach the host itself. A container addressing its own gateway takes the INPUT path and never enters FORWARD. This is required — the host is their router and their SSH entry point — and is not a leak.
3.4 Names
REQ — Names resolve through /etc/hosts on the host and both containers.
REQ — do not stand up an internal DNS server. Two containers and one host do not justify one, and it would create a second source of truth for names that production will not share.
REQ — The service FQDN names the proxy, which has exactly one address. Guest hostnames are separate entries.
<SERVICE_FQDN> -> <proxy service address>
<app hostname> -> <app service address>
<proxy hostname> -> <proxy service address>
PROVEN (F-020) — This value tracked topology through three states before settling. The reasoning matters more than the value.
3.5 No DHCP
REQ — Static addressing. Two containers on a portless bridge is the entire address space; a DHCP server would add a daemon, a lease database and a failure mode in exchange for nothing.
3.6 Firewall
ASSUMED — The Proxmox firewall may remain disabled where the service bridge is portless and the application has no listener outside it. REQ — Production must revisit this; see §15 gate 5.
4. Containers
4.1 Roles and features
| Application | Proxy | |
|---|---|---|
| Role | application, worker | reverse proxy, TLS |
| Unprivileged | yes | yes |
| Features | nesting=1,keyctl=1 |
nesting=1 |
PROVEN (F-003) — Every Debian 12 unprivileged container requires
nesting=1, including one running nothing but nginx. Without it,
systemd-logind, systemd-networkd, systemd-timedated and
systemd-networkd.socket fail with 226/NAMESPACE. Revision 4 said the proxy
needed no features.
keyctl=1 is required only where Docker runs. Tested separately rather than
copied across; it is genuinely unnecessary on the proxy.
4.2 Sizing
INSTANCE. Sizing is a host capacity decision, not a specification value.
REQ — The application container's data volume is a real mount point with
backup=0. Backup of application data is a separate tier (§10), and including a
large data volume in a container snapshot is how a backup store fills.
REQ — Do not over-commit a thin pool. Thin-pool exhaustion is an ugly failure mode.
4.3 Base OS
REQ — Debian 12 bookworm, both containers. It matches YunoHost 12 stable and ships Python 3.11, which has the most reliable OCP/CadQuery wheel coverage.
REQ (F-005) — apt full-upgrade immediately after first boot, before
anything else is installed. Templates are not current, and discovering a
52-package backlog midway through provisioning is avoidable.
REQ (F-004) — Locale must be generated, not merely configured. Install
locales, enable the locale, run locale-gen, then update-locale. Setting
LANG alone leaves the system with only C and POSIX, silently, until
something depends on collation.
REQ — Time zone matching the host.
5. Names and TLS
5.1 Three names, never conflated
| Name | Scope |
|---|---|
| Service FQDN | What users type. Differs per instance. |
| Infrastructure token | System user, paths, units, YunoHost app id. Same everywhere. |
| Project name | Documents, README, catalog entry |
YunoHost app ids accept lowercase alphanumerics and underscores only, so a hyphenated FQDN cannot serve as the token.
5.2 Non-production instances
REQ — An internal service FQDN. REQ — No public DNS record and no publicly-trusted certificate: it consumes rate limit against a name production needs clean, and puts a development host on the internet.
PROVEN — A locally generated CA is sufficient and is the fallback whenever a site CA has no discoverable issuance path. The point of the TLS step is exercising termination and header forwarding, not the trust chain. Replacing the leaf later is two file copies and a reload.
REQ — The CA root is installed into the trust store of the host, both containers, and any client used to browse the instance.
REQ — Record the leaf's expiry somewhere that gets read. Nothing renews a hand-made certificate.
5.3 Production
| Value | |
|---|---|
| FQDN | mechanical-compiler.manufacturing.kane-il.us |
| Zone | Labels within kane-il.us. Not delegated. No new DS record. |
| Certificate | Let's Encrypt, per-name, no wildcard |
| Challenge | ASSUMED HTTP-01. Match existing practice if it is DNS-01; never run both. |
manufacturing.kane-il.us is a namespace, not a service. kane-il.us stays
reserved for cross-cutting infrastructure and must not acquire project records.
The leaf names the service, not the discipline, because the parent already carries the discipline and the namespace must hold siblings:
mechanical-compiler.manufacturing.kane-il.us this application
field-metrology.manufacturing.kane-il.us Utility One, later
stock-processing.manufacturing.kane-il.us Utility Two, later
6. Reverse proxy
REQ — nginx in the proxy container. Terminates TLS, proxies to the application container's service address.
REQ — Debian's default site removed. Only the project vhost enabled.
REQ — X-Forwarded-Proto is mandatory. Getting this wrong is what broke
Hubzilla sessions.
REQ — default_server declared explicitly on both TLS listeners. With one
vhost the implicit default is correct today; the moment a second is added, which
block catches unmatched requests becomes a function of file ordering. This is
independent hardening and does not close F-012.
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name <SERVICE_FQDN>;
ssl_certificate <cert>;
ssl_certificate_key <key>;
location / {
proxy_pass http://<APP_SVC_ADDR>:<PORT>;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
client_max_body_size 8m;
}
}
server {
listen 80;
listen [::]:80;
server_name <SERVICE_FQDN>;
return 301 https://$host$request_uri;
}
REQ — This vhost differs between instances in exactly two places:
server_name and the certificate paths. Any further divergence means the
non-production instance has stopped testing production.
PREF — proxy_read_timeout 300s. Compile jobs are asynchronous, but a cold
synchronous render can take tens of seconds and the 60s default leaves no
headroom.
7. Users, directories, permissions
REQ — Dedicated system user, no login shell:
user:group mechcomp:mechcomp
shell /usr/sbin/nologin
home <install_dir>
REQ (F-016 context) — The service user's home is the install directory, not
a nonexistent /home/<user>. Tooling run as that user writes to $HOME;
ProtectHome=true makes it moot at runtime but not during provisioning.
REQ — Layout, matching what a YunoHost package would provision:
| Path | Owner | Mode | YunoHost equivalent |
|---|---|---|---|
/var/www/mechcomp |
mechcomp:mechcomp |
0750 |
install_dir |
/var/lib/mechcomp |
mechcomp:mechcomp |
0750 |
data_dir |
/var/log/mechcomp |
mechcomp:mechcomp |
0750 |
— |
/etc/mechcomp/mechcomp.env |
root:mechcomp |
0640 |
ynh_add_config |
/var/lib/mechcomp/
├── db/ SQLite database
├── artifacts/ generated SVG, STL, STEP, manifests
├── fixtures/ frozen acceptance oracles, read-mostly
└── cache/ safe to delete at any time
The cache/ guarantee matters: anything not reproducible from db/ plus
artifacts/ must not live there.
REQ (F-007) — Never chown -R a mount point root. data_dir is a
filesystem root containing lost+found, owned by nobody:nogroup at 0700,
created by mkfs and not ours to manage. Enumerate the application directories
explicitly.
REQ (F-008) — All repository operations run as the owning service user. Do
not add a root safe.directory exception — it would mask every future instance
of the same mistake.
REQ — The admin account exists in both containers with an authorized key. REQ (F-014) — If the admin is placed in the service group, that group must be created explicitly in the proxy container, where no service user creates it as a side effect.
8. Software
8.1 Application container
REQ
python3 python3-venv python3-dev
build-essential pkg-config
git curl ca-certificates
libgeos-dev
sqlite3
REQ (F-015) — The proxy container needs its own minimal toolset, at least
curl and ca-certificates. Do not assume packages installed in one container
exist in the other.
REQ — do not install openscad, openscad-nightly, or any Qt or X11
package. The running application never invokes OpenSCAD, and §8.2 confines it.
8.2 The pinned reference toolchain
REQ — One Docker image with the exact toolchain the fixture oracle was frozen against, and nothing else:
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
openscad git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/BelfrySCAD/BOSL2.git /BOSL2 \
&& git -C /BOSL2 checkout 92d697c2856de2fed93a33e858068589cefc2898
Tag mechcomp/reference-toolchain:8.0.0. Its only job is to run
make_fixtures.py and prove the oracle reproduces. The container has no
OpenSCAD, so its version cannot drift.
Acceptance — must reproduce exactly:
sha256 strap-beam-fixtures-8.0.0.json
= ddd0f1548379205dd0c652ec07285b0dae331e52ff0a0437005dfc6cddcc2cb2
A drifting oracle is worse than no oracle.
8.3 Python
REQ — A virtualenv inside install_dir. Debian 12 enforces PEP 668; do not
use --break-system-packages.
REQ — venv/ is in .gitignore. It is a legitimate artifact in
install_dir and simply should not be tracked.
REQ — Pinned, hash-checked dependencies in two files, both installed:
| File | Contents |
|---|---|
requirements-base.txt |
shapely, fastapi, uvicorn, pydantic, sqlalchemy, jinja2, pytest, pytest-xdist |
requirements-cad.txt |
cadquery / build123d |
CI runs the suite twice — with both, then with base alone — and the second run must pass. That is the mechanism keeping §1.1 honest.
9. Services
REQ — Two units, split along the control-plane / execution-plane boundary:
| Unit | Role |
|---|---|
mechcomp.service |
FastAPI/uvicorn. Serves the catalogue, accepts jobs, returns cached artifacts. Never runs geometry. |
mechcomp-worker.service |
Consumes the queue, runs generators, writes artifacts. |
REQ — Both:
[Service]
User=mechcomp
Group=mechcomp
EnvironmentFile=/etc/mechcomp/mechcomp.env
WorkingDirectory=/var/www/mechcomp
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/mechcomp /var/log/mechcomp
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true
[Install]
WantedBy=multi-user.target
REQ — Worker only: a MemoryMax bound, a CPUQuota, TimeoutStopSec=30.
A runaway geometry job must become a clean OOM-kill of one worker, not a dead
container.
REQ — Logging to journald. No application-managed rotation.
REQ — Job queue is SQLite-backed and in-process. No Redis, no Celery, no RabbitMQ. A table with a status column and a claim query is correct at this scale and adds no services to either packaging target.
REQ (F-019) — Anything a proof depends on must be supervised. This includes temporary scaffolding. A placeholder backend used to prove the proxy chain before the application exists is a systemd unit, obviously named and obviously temporary — otherwise the proof expires at the next reboot and the following session diagnoses a proxy fault that does not exist.
REQ (F-011) — Do not use /tmp for state shared across users.
fs.protected_regular makes that fail in ways that look like permission bugs.
Moot under PrivateTmp=true.
10. Backup
DEFERRED — 2026-08-16, operator decision. The backup strategy may change. This section is retained as a design sketch and is not authoritative; no acceptance gate depends on it until a strategy is chosen.
The sketch, for whoever revisits it:
| Tier | What | Retention |
|---|---|---|
| Routine | container snapshot, rootfs only | measured, not guessed |
| Application | tar stream of db/, artifacts/, fixtures/, and the env file |
short, rolling |
| Gold | routine + application + manifest, on removable encrypted media | indefinite, offline |
Three constraints that survive any strategy change:
- Data volumes carry
backup=0. A container snapshot store on the root filesystem is how a Proxmox host fills up. - Pull, never push. The container has no access to any backup destination. The alternative hands a network-facing container write access to the last line of defence, and it avoids idmap ownership problems.
- Encrypt removable media. The backup set includes the env file, which includes the secret key. Media that travels is media that gets lost.
Standing recommendation regardless of strategy: take one manual snapshot of both containers before any significant change. It presupposes nothing and yields the archive size that retention decisions need.
11. Configuration contract
REQ — One env file configures the application:
MECHCOMP_ENV= # staging | production
MECHCOMP_BIND= # single address — see below
MECHCOMP_PORT=
MECHCOMP_BASE_URL=
MECHCOMP_DATA_DIR=/var/lib/mechcomp
MECHCOMP_LOG_LEVEL=info
MECHCOMP_DB_URL=sqlite:////var/lib/mechcomp/db/mechcomp.sqlite3
MECHCOMP_WORKER_CONCURRENCY=
MECHCOMP_CAD_BACKEND=none # none | cadquery
MECHCOMP_ARTIFACT_RETENTION_DAYS=30
MECHCOMP_SECRET_KEY= # generated on first provision, never committed
REQ — MECHCOMP_BIND is a single address. Loopback is not additionally
bound.
Revision 4 said "the service-network address, plus 127.0.0.1," which a scalar
cannot express. Resolved toward one address: uvicorn takes one --host, and
two sockets would mean either 0.0.0.0 — defeating the isolation — or
multi-socket setup for no benefit. Local checks inside the container can use the
service address. The scalar also generalises correctly: under YunoHost, where
nginx and the application share a host, the same variable takes 127.0.0.1.
REQ — The application fails loudly at startup if a required variable is missing, and never falls back to a compiled-in default path.
REQ — The secret is generated on first provision and never overwritten by a re-run. It is never committed and never logged.
Promotion changes MECHCOMP_ENV, MECHCOMP_BIND and MECHCOMP_BASE_URL.
Nothing else.
12. Mail and monitoring
REQ — SMTP 250 from a relay and an empty local queue prove handoff, not delivery. Alerting acceptance requires demonstrated end-to-end receipt, twice, with full headers captured. This is the whole content of F-023: a message can be accepted by every hop and still be rejected at the last one.
REQ — Diagnose relay problems with RCPT-only probes, over every address
family, before and after any change, with no message body sent. A 554
before and a 250 after proves the change caused the fix rather than
coinciding with it.
REQ (F-024) — On a Proxmox host, inspect mail logs with
journalctl -u postfix@-. There is no rsyslog and no /var/log/mail.log.
Automation that greps a logfile path finds nothing silently, which is worse than
failing.
REQ — Disk monitoring must be configured explicitly per device where the
controller does not present members to automatic scanning. An active
smartmontools.service monitoring zero devices is not monitoring, and is easy
to mistake for success. Acceptance is a delivered test alert, not an active
unit.
12.1 Downstream infrastructure is out of bounds
REQ — An instance may configure its own relay client. It may not alter the mail infrastructure it relays through. Where a downstream change is genuinely required, it is escalated with evidence, not made locally.
Two standing constraints apply to this estate:
| Host | Constraint |
|---|---|
| The final mailbox host | Runs YunoHost. Its mail configuration must not be altered — which is the reason a separate relay exists. |
| The relay MTA | Uses DANE. Its TLS and certificate configuration must not be broken. Client-trust changes such as mynetworks do not interact with DANE; certificate changes do. |
REQ (F-025) — Record what a trust change grants, not only what it
repairs. permit_mynetworks is destination-agnostic: authorising a client to
fix one rejected recipient authorises it for every destination. Where an
instance's containers have no reason to originate mail, block SMTP egress at the
instance boundary rather than relying on downstream policy to refuse it.
13. Instance parameters
INSTANCE — Every value below is supplied per instance and recorded in that instance's state file, not here.
host hostname, storage pools, bridges, template
management network host address, gateway
service network network, host address, container addresses
wireguard interface, address, allowed networks
containers ids, hostnames, sizing
service FQDN per instance
TLS CA and leaf source
admin username, authorized key
mail relay endpoint, root alias
REQ — These live in exactly one file per instance, sourced by every provisioning step. No literals scattered through scripts. The file is not committed; an example with empty values is.
14. Constraints the application code will follow
- No hardcoded absolute paths. Everything derives from
MECHCOMP_DATA_DIR. - No writes outside
MECHCOMP_DATA_DIR. Enforced byProtectSystem=strict. - No dependency on systemd from application code. Signal handling only.
- The app listens on one TCP address, never
0.0.0.0. - No OpenSCAD, Qt or X11 dependency in the running application.
- The 3D backend is reached only through an interface in
src/mechcomp/cad/. No other module imports CadQuery or OCP directly. - Schema migrations are explicit and forward-only.
- Artifacts are addressed by content hash of inputs — parameter set plus generator revision — never by hash of output bytes. Mesh output is not reproducible across toolchain versions; input hashing keeps a qualification valid across an upgrade that did not change the geometry.
- Every generated artifact carries the generator revision that produced it.
- The test suite passes with
requirements-cad.txtuninstalled. - AGPL-3.0 §13 requires network users be offered the source. The web tier carries a visible source link to the repository. A licence obligation.
15. Acceptance gates
REQ — Five independent gates. Revision 4 ran them as one list, which meant an honest infrastructure build appeared to fail because application code did not exist yet.
Gate 1 — Infrastructure
Host and both containers running with zero failed units, asserted after a
reboot. Locale generated. Data volume is a real mount. Ownership correct with
lost+found untouched. Forbidden packages absent. Container egress works;
the LAN gateway is unreachable from both containers; the host remains
reachable from them; the management console remains reachable from the LAN. NAT
and FORWARD rules present live and persisted, in order. Proxy serves the service
FQDN over TLS without an insecure bypass, redirects HTTP, and
X-Forwarded-Proto: https is observed at the backend. Any supervised
scaffolding survives a reboot.
PROVEN (F-021) — the reboot is not optional. Connectivity can be perfect while boot is degraded.
Gate 2 — Operational services
Mail delivered end to end and received twice, with full headers captured, an empty queue and no deferred entries. Disk monitoring reporting a non-zero device count and a delivered test alert. Container SMTP egress blocked where the containers have no reason to originate mail. Not gated on gate 1.
Gate 3 — Application runtime
Dependencies installed from committed manifests. Real service and worker units
active. Reference toolchain image reproduces ddd0f154…. Scaffolding removed.
Gate 4 — Backup
Undefined pending a strategy decision.
Gate 5 — Production automation
Idempotent scripts derived from a proven manual procedure. Firewall posture revisited. Publicly-trusted certificate issued and renewing.
16. Assumptions
| § | Assumption | If wrong |
|---|---|---|
| 3.6 | Firewall may stay disabled on a non-production instance | Enable per-container; production revisits regardless |
| 5.3 | Production uses HTTP-01 | Switch to DNS-01 if that is existing practice; never both |
| 11 | MECHCOMP_BIND is a single address |
Revisit only if a real requirement for two sockets appears |
| 15 | Five gates are the right split | Merge or split further as evidence warrants |
17. Provenance disclosure
This project's documents, code and roadmap are LLM-generated under human direction. Stated plainly here, in the README, and in any catalog submission.
The YunoHost policy is a quality bar with a disclosure requirement, not a
ban. The catalog rejects generated packages that do not follow the
example_ynh template, citing verbose code, hallucinated helpers and
non-standard directory architectures — then explicitly permits AI use where the
maintainer is transparent and can explain every line. The failure mode guarded
against is sprawl, not provenance.
Package provenance is not upstream provenance. The policy concerns the
_ynh repository — a few hundred lines of shell and one TOML file.
Disclosure is right; headlining it is a tactical mistake. If provenance becomes the pitch, the project is judged on that axis rather than on whether it makes distributed manufacturing capacity legible. State it in the README; keep the pitch about manufacturing.
18. Packaging, when it comes
DISCOVER — Unknown territory, documented as it is walked: manifest.toml v2
with helpers 2.1, the scripts/ set, conf/ templates, tests.toml,
package_check levels.
The useful comparison for acceptable weight is paperless-ngx_ynh or
fab-manager_ynh, not a hello-world. The package is drafted against
example_ynh and reviewed by CIVICVS as his own work.
19. First application work item
Port sb-geom to Shapely; pytest -n auto green against the 123 frozen cases in
strap-beam-fixtures-8.0.0.json. The ten rejected cases are part of the
contract: a port that accepts them is wrong.