Jump to content

ICT:FinalConfig - Asset - v2 - new

From Costa Sano MediaWiki

Final Configuration for the Asset Entity – v2

(Automatic Identification, Provenance Capture, File Normalization)

Document revision: 2026-02-20 Status: Authoritative

This document defines the complete and final configuration for the Asset entity (version 2).

It supersedes all previous drafts and experimental implementations.


0. Runtime Baseline (LOCKED)

The following environment is assumed and MUST NOT change during Asset v2 implementation:

  • OS: AlmaLinux 10.1
  • PHP: 8.3
  • MediaWiki: 1.45.x
  • Page Forms: 6.x
  • Cargo
  • Scribunto (Lua 5.1)

Lua 5.4 is explicitly excluded.


1. Asset Concept (Normative)

An Asset represents the historical metadata of exactly one digital file.

Rules:

  • One Asset = one File
  • The Asset identifier and the File name are identical (except extension)
  • The Asset/File pair is immutable after creation
  • Assets may reference parent Assets (derivatives, OCR, AI output, annexes)

The Asset identifier is the archival reference and MUST NEVER change once created.


2. Identifier Scheme (Normative)

Asset identifiers follow this pattern:

<ChapterCode>-<ContextCode>-<SequenceNumber>

Where:

  • ChapterCode comes from Chapter.Code
  • ContextCode comes from either Place.Code OR Organisation.Code
  • SequenceNumber is a 4-digit integer

Example:

CH03-BER-0007

Rules:

  • Sequence numbers are computed automatically
  • Gaps are allowed
  • Numbers are never reused
  • Contiguity is NOT guaranteed

3. Draft Asset Strategy (Mandatory)

Asset creation uses a single fixed draft page.

3.1 Draft Page

The following page MUST exist:

Asset:DRAFT

Rules:

  • All new Assets are initially created via this page
  • The page itself is never queried or displayed in dashboards
  • The page is moved to its final identifier on save

4. Cargo Table and Template

4.1 Template:Asset

<noinclude>
Asset data template (v2)

{{#cargo_declare:
 |_table=Assets
 |Code=Page
 |Label=String
 |Chapter=Page
 |Place=Page
 |Organisation=Page
 |Sequence=Integer
 |File=String
 |OriginalFilename=String
 |Parent=Page
 |AssetType=Page
 |Description=Text
 |Notes=Text
}}
</noinclude>

{{#cargo_store:
 |Code={{FULLPAGENAME}}
 |Label={{{Label|}}}
 |Chapter={{{Chapter|}}}
 |Place={{{Place|}}}
 |Organisation={{{Organisation|}}}
 |Sequence={{{Sequence|}}}
 |File={{{File|}}}
 |OriginalFilename={{{OriginalFilename|}}}
 |Parent={{{Parent|}}}
 |AssetType={{{AssetType|}}}
 |Description={{{Description|}}}
 |Notes={{{Notes|}}}
}}

== {{{Label|}}} ==
{{DISPLAYTITLE:{{{Label}}}}}

{{#if:{{{File|}}}|'''File:''' [[{{{File}}}]]}}

{{#if:{{{OriginalFilename|}}}|'''Original filename:''' {{{OriginalFilename}}}}}

{{#if:{{{Parent|}}}|'''Parent asset:''' [[{{{Parent}}}]]}}

'''Description:'''
{{{Description|}}}

'''Notes:'''
{{{Notes|}}}

4.2 Cargo Setup

After saving the template:

  • Go to Special:CargoTables
  • Manually create the table Assets

5. Page Form

5.1 Form:Asset

<noinclude>
Form for creating and editing Asset pages (v2)
</noinclude>

{{{info
|no summary
|no preview
|no minor edit
|no watch
|no footer
}}}

{{{for template|Asset}}}

{| class="formtable"

! Chapter (*)
| {{{field|Chapter
 |input type=combobox
 |values from namespace=Chapter
 |existing values only
}}}
|-

! Place
| {{{field|Place
 |input type=combobox
 |values from namespace=Place
 |existing values only
 |placeholder=Use Organisation instead
}}}
|-

! Organisation
| {{{field|Organisation
 |input type=combobox
 |values from namespace=Organisation
 |existing values only
 |placeholder=Use Place instead
}}}
|-

! Identifier
| {{{field|Label|readonly}}}
|-

! Sequence
| {{{field|Sequence|readonly}}}
|-

! File
| {{{field|File
 |input type=page
 |namespace=File
 |uploadable=yes
}}}
|-

! Original filename
| {{{field|OriginalFilename|readonly}}}
|-

! Asset type
| {{{field|AssetType
 |input type=combobox
 |values from namespace=AssetType
 |existing values only
}}}
|-

! Parent asset
| {{{field|Parent
 |input type=combobox
 |values from namespace=Asset
 |existing values only
 |placeholder=Top level
}}}
|-

! Description
| {{{field|Description|input type=textarea}}}
|-

! Notes
| {{{field|Notes|input type=textarea}}}

|}

{{{standard input|save}}}

{{{end template}}}

{{#tag:html|
<script>
mw.loader.load(
  mw.util.getUrl(
    'MediaWiki:Asset.js',
    { action: 'raw', ctype: 'text/javascript' }
  )
);
</script>
|}}

6. Dashboard

6.1 Dashboard:Asset

= 🗂️ Asset Dashboard =

{| class="wikitable sortable"
! Code !! Chapter !! Place !! Organisation !! File !! Type
{{#cargo_query:
 tables=Assets
 |fields=_pageName,_pageTitle,Chapter,Place,Organisation,File,AssetType
 |where=_pageName!='Asset:DRAFT'
 |order by=_pageTitle
 |format=template
 |template=AssetRow
 |named args=yes
 |cache=no
}}
|}

{{#forminput:
 form=Asset
 |namespace=Asset
 |default value=DRAFT
 |size=24
 |button text=➕ New Asset
 |returnto=Dashboard:Asset
}}

6.2 Template:AssetRow

<includeonly>
|-
| {{#formlink:
   form=Asset
   |target={{{_pageName}}}
   |link text={{{_pageTitle}}}
   |returnto=Dashboard:Asset
 }}
| {{{Chapter}}}
| {{{Place}}}
| {{{Organisation}}}
| {{{File}}}
| {{{AssetType}}}
</includeonly>

7. Lua Numbering Module

7.1 Module:AssetID

local p = {}
local cargo = mw.ext.cargo

local function pad(n)
    return string.format("%04d", tonumber(n))
end

function p.generate(frame)
    local a = frame.args
    local chapter = a.Chapter or ""
    local place = a.Place or ""
    local org = a.Organisation or ""

    if (place ~= "" and org ~= "") then
        return mw.text.jsonEncode({ error = "Choose Place OR Organisation." })
    end

    if (place == "" and org == "") then
        return mw.text.jsonEncode({ error = "Place or Organisation required." })
    end

    local ctxField = place ~= "" and "Place" or "Organisation"
    local ctxValue = place ~= "" and place or org

    local res = cargo.query(
        "Assets",
        "MAX(Sequence)=max",
        {
            where = string.format(
                "Chapter='%s' AND %s='%s'",
                chapter, ctxField, ctxValue
            )
        }
    )

    local nextSeq = (res[1] and res[1].max or 0) + 1

    local code = string.format(
        "%s-%s-%s",
        mw.title.new(chapter).text,
        mw.title.new(ctxValue).text,
        pad(nextSeq)
    )

    return mw.text.jsonEncode({
        identifier = code,
        sequence = nextSeq
    })
end

return p

8. JavaScript (Form Assistance Only)

8.1 MediaWiki:Asset.js

$(document).ready(function () {

    function f(name) {
        return $("[name='Asset[" + name + "]']");
    }

    function isCreateMode() {
        return $("input[name='pfFormPageName']").val() === "Asset:DRAFT";
    }

    function generate() {
        if (!isCreateMode()) return;

        const ch = f("Chapter").val();
        const pl = f("Place").val();
        const org = f("Organisation").val();

        if (!ch || (pl && org) || (!pl && !org)) return;

        new mw.Api().get({
            action: "scribunto-console",
            title: "Module:AssetID",
            question:
                "return require('Module:AssetID').generate{" +
                "Chapter='" + ch + "'," +
                "Place='" + pl + "'," +
                "Organisation='" + org + "'}"
        }).done(function (data) {
            const r = JSON.parse(data.return);
            if (r.error) {
                alert(r.error);
                return;
            }

            f("Label").val(r.identifier);
            f("Sequence").val(r.sequence);
            $("input[name='pfFormPageName']").val(r.identifier);
        });
    }

    f("Chapter").change(generate);
    f("Place").change(generate);
    f("Organisation").change(generate);

    $("input[type='file']").on("change", function () {
        if (this.files[0]) {
            f("OriginalFilename").val(this.files[0].name);
        }
    });
});

9. File Normalization (Server-Side)

Automatic file renaming to match the Asset identifier is mandatory but implemented via a private extension.

Rules:

  • Rename occurs after successful save
  • File is moved to:
File:<AssetIdentifier>.<extension>
  • No redirect is created
  • File history is preserved
  • Failure does not invalidate the Asset

10. Immutability Rules

After a successful save, the following fields MUST NOT change:

  • Asset identifier (page name)
  • Label
  • Chapter
  • Place / Organisation
  • File
  • OriginalFilename

Other metadata MAY change.

Deletion of an Asset/File pair is sysop-only.


11. Status

Asset v2 is fully specified.

Implementation may proceed by recreating each page defined above, one at a time.