The workstation

Real files, annotated.

No fragments written for the occasion. Every block below comes from a repository that runs: the atlasmuseum wiki, and this platform itself. Next to each block: what it does, which pattern it follows, what the trade-off was and what it delivered.

15,929lines of PHP, own work in atlasmuseum
5,415lines of JavaScript, own work in atlasmuseum
22PHP files in AMAPI, the own API layer
4languages on this page: PHP, TypeScript, SQL, and the text beside them

Counted in the atlasmuseum repository on 12 September 2026, without the vendored jQuery and without the import script from before the engagement. The most recently changed file of my own dates from 11 September 2026. The atlasmuseum files are kept in this repository as verbatim copies, path for path, so the line numbers here are the line numbers in the wiki.

atlasmuseum

The wiki that had been given up on: migrated from MediaWiki 1.26.4 to 1.39.10, with its own API layer, own extensions and own skin. Four files from the own work.

The map query, with bound parameters

amapi/generators/map.phpPHP · line 347 to 371 · 25 lines · verbatim copy from the wiki

      try {
        $mysqli = new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT, DB_SOCKET);
      } catch (mysqli_sql_exception $e) {
        error_log('[AMAPI Map] database connection failed: ' . $e->getMessage());
        return [];
      }
      $mysqli->set_charset('utf8mb4');

      // Only the columns actually used below, instead of SELECT *.
      $columns = 'article, title, artist, lat, lon, nature, wikidata';

      try {
        if (is_array($nature) && sizeof($nature) > 0) {
          $placeholders = implode(', ', array_fill(0, sizeof($nature), '?'));
          $stmt = $mysqli->prepare(
            'SELECT ' . $columns . ' FROM ' . DATABASE_MAP . ' WHERE nature IN (' . $placeholders . ')'
          );
          // All bound values are strings; str_repeat builds the type string.
          $stmt->bind_param(str_repeat('s', sizeof($nature)), ...array_values($nature));
          $stmt->execute();
          $result = $stmt->get_result();
        } else {
          $result = $mysqli->query('SELECT ' . $columns . ' FROM ' . DATABASE_MAP);
        }
      } catch (mysqli_sql_exception $e) {
Role
The only place where AMAPI reads SQL directly. Every other endpoint goes through the MediaWiki action API, so an upgrade of the wiki cannot break the backend. A map with thousands of points is the exception: it comes from a denormalised map table.
Pattern
Prepared statement with one placeholder per value; the type string is built from the length of the list. The connection sits in a try/catch because mysqli reports errors as exceptions since PHP 8.1, and the old error branch was never reached any more.
Trade-off
Only the columns the map uses instead of SELECT *, at the cost of a longer query in the code. A failed connection yields an empty map rather than an empty response with a fatal error.
What it delivered
This replaced a hand-written str_replace that escaped the quote but not the backslash: an SQL injection reachable from outside through the nature parameter. Fixed with exactly this block.

The map rebuilt in one transaction

amapi/rebuildMap.phpPHP · line 143 to 207 · 65 lines · verbatim copy from the wiki

// 3) Rewrite am_map inside a transaction (atomic; the map is never empty
//    mid-rebuild). DELETE + INSERT rather than TRUNCATE so it can roll back.
$mysqli = @new mysqli( DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT, DB_SOCKET );
if ( $mysqli->connect_errno ) {
    rm_log( 'Database connection failed: ' . $mysqli->connect_error );
    flock( $lock, LOCK_UN );
    exit( 1 );
}
$mysqli->set_charset( 'utf8' );

$mysqli->begin_transaction();
try {
    $mysqli->query( 'DELETE FROM ' . DATABASE_MAP . ';' );

    $batchSize = 100;
    for ( $j = 0; $j < $total; $j += $batchSize ) {
        $values = [];
        for ( $i = $j; $i < $j + $batchSize && $i < $total; $i++ ) {
            $row = $dataAM[ $i ];

            // Keep each value within its column width so a stray oversized value
            // can never abort the whole rebuild. Wikidata must be a Q-id.
            $wd = (string)( $row['wikidata'] ?? '' );
            if ( !preg_match( '/^Q[0-9]{1,14}$/', $wd ) ) {
                $wd = '';
            }
            $article = mb_substr( (string)( $row['article'] ?? '' ), 0, 255 );
            $title   = mb_substr( (string)( $row['title'] ?? '' ), 0, 255 );
            $artist  = mb_substr( (string)( $row['artist'] ?? '' ), 0, 255 );
            $nature  = mb_substr( (string)( $row['nature'] ?? '' ), 0, 20 );
            $image   = mb_substr( (string)( $row['image'] ?? '' ), 0, 255 );

            $values[] = sprintf(
                '(%d,"%s","%s","%s",%f,%f,"%s","%s","%s")',
                $i + 1,
                $mysqli->real_escape_string( $article ),
                $mysqli->real_escape_string( $title ),
                $mysqli->real_escape_string( $artist ),
                isset( $row['lat'] ) ? (float)$row['lat'] : 0,
                isset( $row['lon'] ) ? (float)$row['lon'] : 0,
                $mysqli->real_escape_string( $nature ),
                $mysqli->real_escape_string( $wd ),
                $mysqli->real_escape_string( $image )
            );
        }
        if ( $values ) {
            $sql = 'INSERT INTO ' . DATABASE_MAP
                . ' (id,article,title,artist,lat,lon,nature,wikidata,image) VALUES '
                . implode( ',', $values );
            if ( !$mysqli->query( $sql ) ) {
                throw new RuntimeException( $mysqli->error );
            }
        }
    }

    $mysqli->commit();
} catch ( \Throwable $e ) {
    $mysqli->rollback();
    rm_log( 'Rebuild failed, rolled back: ' . $e->getMessage() );
    flock( $lock, LOCK_UN );
    exit( 1 );
}

$ms = round( ( microtime( true ) - $t0 ) * 1000 );
rm_log( "Map rebuilt: $total points written to " . DATABASE_MAP . " in {$ms} ms." );
Role
The command line script that refills the map table from Semantic MediaWiki and Wikidata. It runs from the command line, so no HTTP call is nested inside another.
Pattern
DELETE and INSERT inside one transaction, in batches of a hundred, with a rollback on any error. No TRUNCATE, because that cannot roll back. So the map is never empty while it is being rebuilt.
Trade-off
A transaction over thousands of rows holds a lock longer than a TRUNCATE would. That is the price of a map that still works halfway through a failed rebuild.
What it delivered
Above this block sit two safeties: an empty layer is never written, and neither is an incomplete one. The second came after a batch with a timeout was once read as "no more results" and the map fell from 4,537 to 988 points.

Labels from Wikidata, in chunks of fifty

extensions/AtlasRepair/includes/Repair.phpPHP · line 142 to 177 · 36 lines · verbatim copy from the wiki

	public static function resolveLabels( array $qids ): array {
		$out = [];
		$fetch = [];
		$dir = sys_get_temp_dir();
		foreach ( $qids as $q ) {
			$cf = $dir . '/amrepair-' . $q . '.txt';
			if ( is_file( $cf ) && ( time() - filemtime( $cf ) < 2592000 ) ) {
				$v = @file_get_contents( $cf );
				if ( $v !== false && $v !== '' ) { $out[ $q ] = $v; continue; }
			}
			$fetch[] = $q;
		}
		// Wikidata allows up to 50 ids per call.
		foreach ( array_chunk( $fetch, 50 ) as $chunk ) {
			$url = 'https://www.wikidata.org/w/api.php?action=wbgetentities&props=labels'
				. '&languages=fr%7Cen&format=json&ids=' . implode( '%7C', $chunk );
			$raw = self::httpGet( $url );
			$json = $raw ? json_decode( $raw ) : null;
			if ( !$json || !isset( $json->entities ) ) { continue; }
			foreach ( $chunk as $q ) {
				$label = '';
				if ( isset( $json->entities->{$q}->labels->fr->value ) ) {
					$label = $json->entities->{$q}->labels->fr->value;
				} elseif ( isset( $json->entities->{$q}->labels->en->value ) ) {
					$label = $json->entities->{$q}->labels->en->value;
				}
				if ( $label !== '' ) {
					$out[ $q ] = $label;
					@file_put_contents( $dir . '/amrepair-' . $q . '.txt', $label );
				}
			}
		}
		return $out;
	}

	/** Simple GET with a descriptive User-Agent (Wikidata requires one). */
Role
AtlasRepair replaces Q-numbers that an old import left in artist and work notices with the name Wikidata knows for them. This is the function that fetches those names.
Pattern
A cache per Q-number on disk, valid for thirty days, then one call per fifty numbers: the maximum the Wikidata API allows per request. French first, English as the fallback.
Trade-off
A file cache rather than a table: no migration needed in a wiki that had just been rescued, and a repair you can repeat without loading Wikidata again. The file can disappear on a server restart; then it is simply fetched again.
What it delivered
The repair first runs as a plan and only with --apply as a change, with a report in between. Four files, 488 lines, and the notices were readable again.

The quality index, one score per notice

extensions/AtlasQualite/includes/QualityIndex.phpPHP · line 86 to 133 · 48 lines · verbatim copy from the wiki

	public static function rebuild( ?callable $progress = null ): array {
		$config   = MediaWikiServices::getInstance()->getMainConfig();
		$category = $config->get( 'AtlasQualiteCategory' );
		$checks   = self::checks();

		$rows        = self::querySmw( $category, $checks );
		$records     = [];
		$issueCounts = [];
		foreach ( $checks as $c ) {
			$issueCounts[ $c['key'] ] = 0;
		}
		$complete = 0;
		$done     = 0;

		foreach ( $rows as $page => $values ) {
			$missing    = [];
			$maxWeight  = 0;
			$gotWeight  = 0;

			foreach ( $checks as $c ) {
				$maxWeight += $c['weight'];
				$val = $values[ $c['key'] ] ?? '';
				$ok  = self::passes( $c, $val );
				if ( $ok ) {
					$gotWeight += $c['weight'];
				} else {
					$missing[] = $c['key'];
					$issueCounts[ $c['key'] ]++;
				}
			}

			$score = $maxWeight > 0 ? (int) round( 100 * $gotWeight / $maxWeight ) : 100;
			if ( $score === 100 ) {
				$complete++;
			}

			$records[] = [
				'page'    => $page,
				'score'   => $score,
				'missing' => $missing,
			];

			$done++;
			if ( $progress && ( $done % 200 === 0 ) ) {
				$progress( $done );
			}
		}

Role
AtlasQualité gives every work notice a score from a list of weighted checks and puts the most incomplete notices first: a curator's worklist.
Pattern
One pass over the results of a semantic query, a weighted sum per page, and an index as JSON on disk. The build runs only from the command line, never on a page view.
Trade-off
An index that can be as old as the last rebuild when the dashboard opens, in exchange for a dashboard that opens instantly on a wiki with tens of thousands of pages.
What it delivered
Four files and one stylesheet, 640 lines, and an association that could see for the first time which notices still needed attention.

This platform

The site you are reading measures itself and keeps every measurement in a chain that cannot be changed unnoticed. Three files from the core.

The same rule, once more, in the database

packages/db/migrations/0001_targets_runs_measurements.sqlSQL · line 89 to 116 · 28 lines · in this repository

CREATE TABLE measurements (
    id          uuid        PRIMARY KEY,
    run_id      uuid        NOT NULL UNIQUE REFERENCES runs (id) ON DELETE RESTRICT,
    target_id   uuid        NOT NULL REFERENCES targets (id) ON DELETE RESTRICT,

    -- The chain. `payload` is the canonical-JSON body; `hash` is
    -- sha256(prev_hash || canonical_json(body)) as defined in packages/core.
    chain_id    uuid        NOT NULL,
    sequence    bigint      NOT NULL,
    recorded_at timestamptz NOT NULL,
    payload     jsonb       NOT NULL,
    prev_hash   char(64)    NOT NULL,
    hash        char(64)    NOT NULL,

    created_at  timestamptz NOT NULL DEFAULT now(),

    CONSTRAINT measurements_sequence_non_negative CHECK (sequence >= 0),
    CONSTRAINT measurements_hash_hex      CHECK (hash ~ '^[0-9a-f]{64}$'),
    CONSTRAINT measurements_prev_hash_hex CHECK (prev_hash ~ '^[0-9a-f]{64}$'),
    -- The genesis record is the only one that may point at the zero hash.
    CONSTRAINT measurements_genesis_is_first CHECK (
        (sequence = 0) = (prev_hash = repeat('0', 64))
    ),
    -- A chain never forks: one record per position, and one successor per record.
    CONSTRAINT measurements_one_per_position UNIQUE (chain_id, sequence),
    CONSTRAINT measurements_one_successor    UNIQUE (chain_id, prev_hash),
    CONSTRAINT measurements_hash_unique      UNIQUE (hash)
);
Role
Defence in depth. Every invariant the core enforces in TypeScript stands here again as a constraint: a hash is 64 hexadecimal characters, only the first record points at the zero hash, and a chain never forks.
Pattern
CHECK constraints for the shape, UNIQUE constraints for the structure: one record per position and one successor per record. It is the chain as schema, not as agreement.
Trade-off
The same rule twice is not repetition. The code gives the developer a guarantee at compile and test time; the database gives the operator a guarantee that also holds against a migration script or a manual psql session.
What it delivered
A chain that stays intact only as long as the application writes carefully is not a chain. These constraints are what makes "cannot be changed unnoticed" true.

Claiming a job without running it twice

packages/db/src/jobs.tsTypeScript · line 102 to 134 · 33 lines · in this repository

export async function claimJobs(
  sql: Sql,
  workerId: string,
  limit = 1,
  leaseMs: number = DEFAULT_LEASE_MS,
): Promise<ClaimedJob[]> {
  const rows = await sql<JobRow[]>`
    UPDATE jobs AS j
    SET state = 'claimed',
        claimed_by = ${workerId},
        claimed_at = now(),
        lease_expires_at = now() + make_interval(secs => ${leaseMs / 1000}),
        attempts = j.attempts + 1,
        updated_at = now()
    WHERE j.id IN (
      SELECT c.id
      FROM jobs c
      WHERE c.state = 'pending'
        AND c.run_at <= now()
        -- A job with no attempts left must never be picked up: the claim
        -- increments attempts, and jobs_attempts_bounded would reject it.
        -- Reaching that constraint means the queue tried to run something it
        -- had already given up on.
        AND c.attempts < c.max_attempts
      ORDER BY c.run_at
      FOR UPDATE SKIP LOCKED
      LIMIT ${limit}
    )
    RETURNING j.id, j.target_id, j.state, j.run_at, j.attempts, j.max_attempts,
              j.claimed_by, j.lease_expires_at, j.last_error, j.dead_lettered_at
  `;
  return rows.map(toClaimed);
}
Role
The measurement workers' queue runs on Postgres, without a separate message broker. This is the one statement with which a worker takes a job for itself.
Pattern
UPDATE with a nested SELECT ... FOR UPDATE SKIP LOCKED. The inner select locks candidates and skips rows another transaction already holds. Two workers at the same instant therefore get disjoint sets, and the UPDATE flips the state before the lock is released.
Trade-off
Postgres as a queue means one system fewer to operate, with a measured throughput that is ample at this scale. The decision and the figures are in the architecture decisions of the repository.
What it delivered
Without SKIP LOCKED this is the classic queue bug: two workers read the same oldest row and run the same job. A row lock alone is not enough either, it lines the workers up in a queue of one.

More of this platform, with the measurements it keeps on itself, is on the work page.

To the work