<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mwiki.costasano.club/index.php?action=history&amp;feed=atom&amp;title=ICT%3AFinalConfig_-_AssetFileNormakizer_extension</id>
	<title>ICT:FinalConfig - AssetFileNormakizer extension - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mwiki.costasano.club/index.php?action=history&amp;feed=atom&amp;title=ICT%3AFinalConfig_-_AssetFileNormakizer_extension"/>
	<link rel="alternate" type="text/html" href="https://mwiki.costasano.club/index.php?title=ICT:FinalConfig_-_AssetFileNormakizer_extension&amp;action=history"/>
	<updated>2026-07-23T02:45:26Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://mwiki.costasano.club/index.php?title=ICT:FinalConfig_-_AssetFileNormakizer_extension&amp;diff=1050&amp;oldid=prev</id>
		<title>Mngr at 14:49, 18 February 2026</title>
		<link rel="alternate" type="text/html" href="https://mwiki.costasano.club/index.php?title=ICT:FinalConfig_-_AssetFileNormakizer_extension&amp;diff=1050&amp;oldid=prev"/>
		<updated>2026-02-18T14:49:07Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://mwiki.costasano.club/index.php?title=ICT:FinalConfig_-_AssetFileNormakizer_extension&amp;amp;diff=1050&amp;amp;oldid=1048&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Mngr</name></author>
	</entry>
	<entry>
		<id>https://mwiki.costasano.club/index.php?title=ICT:FinalConfig_-_AssetFileNormakizer_extension&amp;diff=1048&amp;oldid=prev</id>
		<title>Mngr: Created page with &quot;= Final Configuration for Extension: AssetFileNormalizer =  Document revision: {{#time:Y-m-d|{{REVISIONTIMESTAMP}}}} by {{REVISIONUSER}}  This document defines the private MediaWiki extension &#039;&#039;&#039;AssetFileNormalizer&#039;&#039;&#039;.  The extension enforces the archival invariant:  &lt;blockquote&gt; &#039;&#039;&#039;One Asset = one File, both sharing the same immutable identifier.&#039;&#039;&#039; &lt;/blockquote&gt;  The extension is part of the Asset v2 architecture and must be considered normative infrastructure.  Cate...&quot;</title>
		<link rel="alternate" type="text/html" href="https://mwiki.costasano.club/index.php?title=ICT:FinalConfig_-_AssetFileNormakizer_extension&amp;diff=1048&amp;oldid=prev"/>
		<updated>2026-02-18T14:35:11Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;= Final Configuration for Extension: AssetFileNormalizer =  Document revision: {{#time:Y-m-d|{{REVISIONTIMESTAMP}}}} by {{REVISIONUSER}}  This document defines the private MediaWiki extension &amp;#039;&amp;#039;&amp;#039;AssetFileNormalizer&amp;#039;&amp;#039;&amp;#039;.  The extension enforces the archival invariant:  &amp;lt;blockquote&amp;gt; &amp;#039;&amp;#039;&amp;#039;One Asset = one File, both sharing the same immutable identifier.&amp;#039;&amp;#039;&amp;#039; &amp;lt;/blockquote&amp;gt;  The extension is part of the Asset v2 architecture and must be considered normative infrastructure.  Cate...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Final Configuration for Extension: AssetFileNormalizer =&lt;br /&gt;
&lt;br /&gt;
Document revision: {{#time:Y-m-d|{{REVISIONTIMESTAMP}}}} by {{REVISIONUSER}}&lt;br /&gt;
&lt;br /&gt;
This document defines the private MediaWiki extension &amp;#039;&amp;#039;&amp;#039;AssetFileNormalizer&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
The extension enforces the archival invariant:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;One Asset = one File, both sharing the same immutable identifier.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The extension is part of the Asset v2 architecture and must be considered&lt;br /&gt;
normative infrastructure.&lt;br /&gt;
&lt;br /&gt;
[[Category:FinalConfig]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 1. Purpose and Scope =&lt;br /&gt;
&lt;br /&gt;
The AssetFileNormalizer extension automatically renames uploaded files&lt;br /&gt;
associated with Asset pages so that:&lt;br /&gt;
&lt;br /&gt;
* The file name matches the Asset identifier&lt;br /&gt;
* The original upload filename is removed&lt;br /&gt;
* No redirect is created&lt;br /&gt;
* No user intervention is required&lt;br /&gt;
&lt;br /&gt;
This extension applies only to:&lt;br /&gt;
* newly created Asset pages&lt;br /&gt;
* files uploaded through the Asset form&lt;br /&gt;
&lt;br /&gt;
It does not affect:&lt;br /&gt;
* existing files&lt;br /&gt;
* non-Asset pages&lt;br /&gt;
* manual file moves&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 2. Design Principles =&lt;br /&gt;
&lt;br /&gt;
The extension is designed according to the following principles:&lt;br /&gt;
&lt;br /&gt;
* Minimal responsibility (one job only)&lt;br /&gt;
* No dependency on Cargo internals&lt;br /&gt;
* No dependency on Page Forms internals&lt;br /&gt;
* Server-side authority (no JavaScript enforcement)&lt;br /&gt;
* Upgrade-safe MediaWiki APIs&lt;br /&gt;
* Non-blocking behavior on failure&lt;br /&gt;
&lt;br /&gt;
The extension intentionally performs no validation of identifiers.&lt;br /&gt;
It assumes that Asset v2 numbering logic has already produced a valid identifier.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 3. Functional Behavior =&lt;br /&gt;
&lt;br /&gt;
== 3.1 Trigger ==&lt;br /&gt;
&lt;br /&gt;
The extension runs when:&lt;br /&gt;
* a page is saved&lt;br /&gt;
* the page is newly created&lt;br /&gt;
* the page is in the Asset namespace&lt;br /&gt;
&lt;br /&gt;
== 3.2 Conditions ==&lt;br /&gt;
&lt;br /&gt;
The extension proceeds only if:&lt;br /&gt;
* the page contains a File field&lt;br /&gt;
* the referenced file exists&lt;br /&gt;
* the file name does not already match the Asset identifier&lt;br /&gt;
&lt;br /&gt;
== 3.3 Action ==&lt;br /&gt;
&lt;br /&gt;
If all conditions are met:&lt;br /&gt;
* the file is moved to &amp;lt;AssetIdentifier&amp;gt;.&amp;lt;extension&amp;gt;&lt;br /&gt;
* file history is preserved&lt;br /&gt;
* redirect creation is suppressed&lt;br /&gt;
&lt;br /&gt;
== 3.4 Failure Policy ==&lt;br /&gt;
&lt;br /&gt;
If the file move fails:&lt;br /&gt;
* the Asset page remains valid&lt;br /&gt;
* the file remains under its original name&lt;br /&gt;
* a debug log entry is written&lt;br /&gt;
&lt;br /&gt;
The extension never blocks page saving.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 4. Directory Structure =&lt;br /&gt;
&lt;br /&gt;
The extension is installed as a private extension under:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
extensions/AssetFileNormalizer/&lt;br /&gt;
 ├─ extension.json&lt;br /&gt;
 └─ AssetFileNormalizer.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 5. Extension Registration =&lt;br /&gt;
&lt;br /&gt;
== 5.1 extension.json ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;AssetFileNormalizer&amp;quot;,&lt;br /&gt;
  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;,&lt;br /&gt;
  &amp;quot;author&amp;quot;: &amp;quot;Costa Sano Research&amp;quot;,&lt;br /&gt;
  &amp;quot;description&amp;quot;: &amp;quot;Automatically renames Asset files to match Asset identifiers&amp;quot;,&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;extension&amp;quot;,&lt;br /&gt;
  &amp;quot;Hooks&amp;quot;: {&lt;br /&gt;
    &amp;quot;PageSaveComplete&amp;quot;: &amp;quot;AssetFileNormalizer::onPageSaveComplete&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;AutoloadClasses&amp;quot;: {&lt;br /&gt;
    &amp;quot;AssetFileNormalizer&amp;quot;: &amp;quot;AssetFileNormalizer.php&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;ManifestVersion&amp;quot;: 2&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 6. Core Implementation =&lt;br /&gt;
&lt;br /&gt;
== 6.1 AssetFileNormalizer.php ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
use MediaWiki\MediaWikiServices;&lt;br /&gt;
&lt;br /&gt;
class AssetFileNormalizer {&lt;br /&gt;
&lt;br /&gt;
    public static function onPageSaveComplete(&lt;br /&gt;
        WikiPage $wikiPage,&lt;br /&gt;
        UserIdentity $user,&lt;br /&gt;
        string $summary,&lt;br /&gt;
        int $flags,&lt;br /&gt;
        RevisionRecord $revisionRecord,&lt;br /&gt;
        EditResult $editResult&lt;br /&gt;
    ) {&lt;br /&gt;
        $title = $wikiPage-&amp;gt;getTitle();&lt;br /&gt;
&lt;br /&gt;
        // Only Asset namespace (ID must match local configuration)&lt;br /&gt;
        if ( $title-&amp;gt;getNamespace() !== 3014 ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Only act on page creation&lt;br /&gt;
        if ( !$editResult-&amp;gt;isNewPage() ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Extract wikitext content&lt;br /&gt;
        $content = $revisionRecord-&amp;gt;getContent( SlotRecord::MAIN );&lt;br /&gt;
        if ( !$content instanceof WikitextContent ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text = $content-&amp;gt;getText();&lt;br /&gt;
&lt;br /&gt;
        // Extract File field&lt;br /&gt;
        if ( !preg_match( &amp;#039;/\|\s*File\s*=\s*(File:[^\|\n]+)/&amp;#039;, $text, $m ) ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $fileTitle = Title::newFromText( trim( $m[1] ) );&lt;br /&gt;
        if ( !$fileTitle || !$fileTitle-&amp;gt;inNamespace( NS_FILE ) ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $repo = MediaWikiServices::getInstance()&lt;br /&gt;
            -&amp;gt;getRepoGroup()&lt;br /&gt;
            -&amp;gt;getLocalRepo();&lt;br /&gt;
&lt;br /&gt;
        $file = $repo-&amp;gt;newFile( $fileTitle );&lt;br /&gt;
        if ( !$file-&amp;gt;exists() ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Compute target filename&lt;br /&gt;
        $assetCode = $title-&amp;gt;getText();&lt;br /&gt;
        $extension = $file-&amp;gt;getExtension();&lt;br /&gt;
&lt;br /&gt;
        $targetTitle = Title::makeTitle(&lt;br /&gt;
            NS_FILE,&lt;br /&gt;
            $assetCode . &amp;#039;.&amp;#039; . $extension&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        // Already normalized&lt;br /&gt;
        if ( $fileTitle-&amp;gt;equals( $targetTitle ) ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Move file without redirect&lt;br /&gt;
        $status = $file-&amp;gt;move(&lt;br /&gt;
            $targetTitle,&lt;br /&gt;
            &amp;#039;Automatic Asset file normalization&amp;#039;,&lt;br /&gt;
            /* suppressRedirect */ true,&lt;br /&gt;
            $user&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if ( !$status-&amp;gt;isOK() ) {&lt;br /&gt;
            wfDebugLog(&lt;br /&gt;
                &amp;#039;AssetFileNormalizer&amp;#039;,&lt;br /&gt;
                &amp;#039;File move failed for &amp;#039; . $fileTitle-&amp;gt;getPrefixedText()&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 7. Enabling the Extension =&lt;br /&gt;
&lt;br /&gt;
Add the following line to &amp;#039;&amp;#039;&amp;#039;LocalSettings.php&amp;#039;&amp;#039;&amp;#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wfLoadExtension( &amp;#039;AssetFileNormalizer&amp;#039; );&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally, the extension may be wrapped in a feature flag for emergency disablement.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 8. Security and Permissions =&lt;br /&gt;
&lt;br /&gt;
* The file move is executed under the saving user context&lt;br /&gt;
* Users creating Assets must have:&lt;br /&gt;
** upload rights&lt;br /&gt;
** movefile rights (or equivalent)&lt;br /&gt;
* The extension does not escalate privileges&lt;br /&gt;
&lt;br /&gt;
Deletion of Asset/File couples remains a sysop-only operation.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 9. Relationship to Asset v2 =&lt;br /&gt;
&lt;br /&gt;
This extension is a mandatory component of Asset v2.&lt;br /&gt;
&lt;br /&gt;
Without it:&lt;br /&gt;
* Asset creation still works&lt;br /&gt;
* File upload still works&lt;br /&gt;
* The archival invariant (name equality) is not enforced&lt;br /&gt;
&lt;br /&gt;
With it:&lt;br /&gt;
* Asset and File identity are strictly aligned&lt;br /&gt;
* The Asset–File couple is sealed at creation time&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= 10. Status =&lt;br /&gt;
&lt;br /&gt;
AssetFileNormalizer is complete and ready for deployment.&lt;br /&gt;
&lt;br /&gt;
No database changes are required.&lt;br /&gt;
No migration of existing files is performed.&lt;br /&gt;
&lt;br /&gt;
[[Category:FinalConfig]]&lt;/div&gt;</summary>
		<author><name>Mngr</name></author>
	</entry>
</feed>