<?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%3ADocumentation%3ALua_Numbering_Module_AssetID</id>
	<title>ICT:Documentation:Lua Numbering Module AssetID - 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%3ADocumentation%3ALua_Numbering_Module_AssetID"/>
	<link rel="alternate" type="text/html" href="https://mwiki.costasano.club/index.php?title=ICT:Documentation:Lua_Numbering_Module_AssetID&amp;action=history"/>
	<updated>2026-07-23T00:11:48Z</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:Documentation:Lua_Numbering_Module_AssetID&amp;diff=1218&amp;oldid=prev</id>
		<title>Mngr: Created page with &quot;= Explanation of the Lua Numbering Module (Module:AssetID) =  This page documents the purpose, design, and inner logic of the Lua module &#039;&#039;&#039;Module:AssetID&#039;&#039;&#039; used in Asset v2.  It is intended as long-term reference documentation and is written to be understandable years later without re-reading the original code discussion.  ----  == Purpose of This Document ==  Lua modules in MediaWiki often appear opaque because:  * they run server-side * they interact with Cargo * the...&quot;</title>
		<link rel="alternate" type="text/html" href="https://mwiki.costasano.club/index.php?title=ICT:Documentation:Lua_Numbering_Module_AssetID&amp;diff=1218&amp;oldid=prev"/>
		<updated>2026-02-20T13:21:03Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;= Explanation of the Lua Numbering Module (Module:AssetID) =  This page documents the purpose, design, and inner logic of the Lua module &amp;#039;&amp;#039;&amp;#039;Module:AssetID&amp;#039;&amp;#039;&amp;#039; used in Asset v2.  It is intended as long-term reference documentation and is written to be understandable years later without re-reading the original code discussion.  ----  == Purpose of This Document ==  Lua modules in MediaWiki often appear opaque because:  * they run server-side * they interact with Cargo * the...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Explanation of the Lua Numbering Module (Module:AssetID) =&lt;br /&gt;
&lt;br /&gt;
This page documents the purpose, design, and inner logic of the Lua module&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Module:AssetID&amp;#039;&amp;#039;&amp;#039; used in Asset v2.&lt;br /&gt;
&lt;br /&gt;
It is intended as long-term reference documentation and is written&lt;br /&gt;
to be understandable years later without re-reading the original code discussion.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Purpose of This Document ==&lt;br /&gt;
&lt;br /&gt;
Lua modules in MediaWiki often appear opaque because:&lt;br /&gt;
&lt;br /&gt;
* they run server-side&lt;br /&gt;
* they interact with Cargo&lt;br /&gt;
* they return data in non-obvious formats (JSON)&lt;br /&gt;
* they are usually called indirectly (via JavaScript)&lt;br /&gt;
&lt;br /&gt;
This document explains &amp;#039;&amp;#039;&amp;#039;why&amp;#039;&amp;#039;&amp;#039; the module exists,&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;what&amp;#039;&amp;#039;&amp;#039; it does,&lt;br /&gt;
and &amp;#039;&amp;#039;&amp;#039;why it is intentionally limited&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Role of the Lua Module in Asset v2 ==&lt;br /&gt;
&lt;br /&gt;
The Lua module has exactly one responsibility:&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Compute the next Asset identifier and sequence number.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
It does NOT:&lt;br /&gt;
&lt;br /&gt;
* create pages&lt;br /&gt;
* reserve identifiers&lt;br /&gt;
* write to Cargo&lt;br /&gt;
* upload or rename files&lt;br /&gt;
* enforce immutability&lt;br /&gt;
* validate user permissions&lt;br /&gt;
&lt;br /&gt;
The module is purely computational.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Why Lua Is Used at All ==&lt;br /&gt;
&lt;br /&gt;
Lua is used instead of JavaScript or PHP because:&lt;br /&gt;
&lt;br /&gt;
* it runs server-side&lt;br /&gt;
* it can safely query Cargo&lt;br /&gt;
* it is deterministic&lt;br /&gt;
* it does not depend on the browser&lt;br /&gt;
* it does not require a custom extension&lt;br /&gt;
&lt;br /&gt;
Lua is the correct layer for “read-only system logic”.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Where the Module Lives ==&lt;br /&gt;
&lt;br /&gt;
The module is defined at:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Module:AssetID&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is a standard Scribunto Lua module.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Full Module Code ==&lt;br /&gt;
&lt;br /&gt;
For reference, the module code is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local p = {}&lt;br /&gt;
local cargo = mw.ext.cargo&lt;br /&gt;
&lt;br /&gt;
local function pad(n)&lt;br /&gt;
    return string.format(&amp;quot;%04d&amp;quot;, tonumber(n))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.generate(frame)&lt;br /&gt;
    local a = frame.args&lt;br /&gt;
    local chapter = a.Chapter or &amp;quot;&amp;quot;&lt;br /&gt;
    local place = a.Place or &amp;quot;&amp;quot;&lt;br /&gt;
    local org = a.Organisation or &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if (place ~= &amp;quot;&amp;quot; and org ~= &amp;quot;&amp;quot;) then&lt;br /&gt;
        return mw.text.jsonEncode({ error = &amp;quot;Choose Place OR Organisation.&amp;quot; })&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if (place == &amp;quot;&amp;quot; and org == &amp;quot;&amp;quot;) then&lt;br /&gt;
        return mw.text.jsonEncode({ error = &amp;quot;Place or Organisation required.&amp;quot; })&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local ctxField = place ~= &amp;quot;&amp;quot; and &amp;quot;Place&amp;quot; or &amp;quot;Organisation&amp;quot;&lt;br /&gt;
    local ctxValue = place ~= &amp;quot;&amp;quot; and place or org&lt;br /&gt;
&lt;br /&gt;
    local res = cargo.query(&lt;br /&gt;
        &amp;quot;Assets&amp;quot;,&lt;br /&gt;
        &amp;quot;MAX(Sequence)=max&amp;quot;,&lt;br /&gt;
        {&lt;br /&gt;
            where = string.format(&lt;br /&gt;
                &amp;quot;Chapter=&amp;#039;%s&amp;#039; AND %s=&amp;#039;%s&amp;#039;&amp;quot;,&lt;br /&gt;
                chapter, ctxField, ctxValue&lt;br /&gt;
            )&lt;br /&gt;
        }&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    local nextSeq = (res[1] and res[1].max or 0) + 1&lt;br /&gt;
&lt;br /&gt;
    local code = string.format(&lt;br /&gt;
        &amp;quot;%s-%s-%s&amp;quot;,&lt;br /&gt;
        mw.title.new(chapter).text,&lt;br /&gt;
        mw.title.new(ctxValue).text,&lt;br /&gt;
        pad(nextSeq)&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    return mw.text.jsonEncode({&lt;br /&gt;
        identifier = code,&lt;br /&gt;
        sequence = nextSeq&lt;br /&gt;
    })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each part is explained below.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Module Structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local p = {}&lt;br /&gt;
return p&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Defines a module table&lt;br /&gt;
* Exposes functions via that table&lt;br /&gt;
&lt;br /&gt;
This is standard Scribunto structure.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Access to Cargo ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local cargo = mw.ext.cargo&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Imports the Cargo Lua interface&lt;br /&gt;
* Allows querying Cargo tables&lt;br /&gt;
&lt;br /&gt;
Important:&lt;br /&gt;
The module uses Cargo only for reading, never for writing.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Padding Function ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local function pad(n)&lt;br /&gt;
    return string.format(&amp;quot;%04d&amp;quot;, tonumber(n))&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What it does:&lt;br /&gt;
&lt;br /&gt;
* Converts a number into a 4-digit string&lt;br /&gt;
* Adds leading zeros if necessary&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
* 1 → 0001&lt;br /&gt;
* 27 → 0027&lt;br /&gt;
* 305 → 0305&lt;br /&gt;
&lt;br /&gt;
Why this exists:&lt;br /&gt;
&lt;br /&gt;
* Enforces fixed-width identifiers&lt;br /&gt;
* Keeps identifiers sortable and readable&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Entry Point: generate() ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
function p.generate(frame)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is the only public function.&lt;br /&gt;
&lt;br /&gt;
It is called by JavaScript using Scribunto.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Reading Input Arguments ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local a = frame.args&lt;br /&gt;
local chapter = a.Chapter or &amp;quot;&amp;quot;&lt;br /&gt;
local place = a.Place or &amp;quot;&amp;quot;&lt;br /&gt;
local org = a.Organisation or &amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Reads values passed from JavaScript&lt;br /&gt;
* Defaults missing values to empty strings&lt;br /&gt;
&lt;br /&gt;
Why this matters:&lt;br /&gt;
&lt;br /&gt;
* Prevents nil errors&lt;br /&gt;
* Allows explicit validation&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Mutual Exclusivity Check ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if (place ~= &amp;quot;&amp;quot; and org ~= &amp;quot;&amp;quot;) then&lt;br /&gt;
    return mw.text.jsonEncode({ error = &amp;quot;Choose Place OR Organisation.&amp;quot; })&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Rejects invalid input&lt;br /&gt;
* Returns a JSON error message&lt;br /&gt;
&lt;br /&gt;
Important:&lt;br /&gt;
The module does NOT throw errors.&lt;br /&gt;
It returns structured information to the caller.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Required Context Check ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if (place == &amp;quot;&amp;quot; and org == &amp;quot;&amp;quot;) then&lt;br /&gt;
    return mw.text.jsonEncode({ error = &amp;quot;Place or Organisation required.&amp;quot; })&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Enforces the rule that exactly one context must exist&lt;br /&gt;
&lt;br /&gt;
Again:&lt;br /&gt;
The module does not decide what happens next.&lt;br /&gt;
It only reports the problem.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Determining the Context Field ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local ctxField = place ~= &amp;quot;&amp;quot; and &amp;quot;Place&amp;quot; or &amp;quot;Organisation&amp;quot;&lt;br /&gt;
local ctxValue = place ~= &amp;quot;&amp;quot; and place or org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Determines which Cargo field to query&lt;br /&gt;
* Keeps logic compact and explicit&lt;br /&gt;
&lt;br /&gt;
This is a standard Lua idiom.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Cargo Query for the Maximum Sequence ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local res = cargo.query(&lt;br /&gt;
    &amp;quot;Assets&amp;quot;,&lt;br /&gt;
    &amp;quot;MAX(Sequence)=max&amp;quot;,&lt;br /&gt;
    {&lt;br /&gt;
        where = string.format(&lt;br /&gt;
            &amp;quot;Chapter=&amp;#039;%s&amp;#039; AND %s=&amp;#039;%s&amp;#039;&amp;quot;,&lt;br /&gt;
            chapter, ctxField, ctxValue&lt;br /&gt;
        )&lt;br /&gt;
    }&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Queries the Assets table&lt;br /&gt;
* Finds the highest existing Sequence number&lt;br /&gt;
* Restricts by Chapter and Context&lt;br /&gt;
&lt;br /&gt;
Why MAX() is used:&lt;br /&gt;
&lt;br /&gt;
* Fast&lt;br /&gt;
* Deterministic&lt;br /&gt;
* Does not require locks&lt;br /&gt;
* Accepts gaps naturally&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Why Gaps Are Accepted ==&lt;br /&gt;
&lt;br /&gt;
The module intentionally allows gaps because:&lt;br /&gt;
&lt;br /&gt;
* Asset creation may fail after number generation&lt;br /&gt;
* Users may abandon forms&lt;br /&gt;
* Concurrent users may collide&lt;br /&gt;
&lt;br /&gt;
Trying to prevent gaps would require:&lt;br /&gt;
* locks&lt;br /&gt;
* reservations&lt;br /&gt;
* cleanup logic&lt;br /&gt;
&lt;br /&gt;
This is explicitly avoided.&lt;br /&gt;
&lt;br /&gt;
Gaps are acceptable in archival identifiers.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Computing the Next Sequence ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local nextSeq = (res[1] and res[1].max or 0) + 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* If records exist, increment the max&lt;br /&gt;
* If none exist, start at 1&lt;br /&gt;
&lt;br /&gt;
This line handles both cases safely.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Building the Identifier ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local code = string.format(&lt;br /&gt;
    &amp;quot;%s-%s-%s&amp;quot;,&lt;br /&gt;
    mw.title.new(chapter).text,&lt;br /&gt;
    mw.title.new(ctxValue).text,&lt;br /&gt;
    pad(nextSeq)&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Extracts page titles&lt;br /&gt;
* Builds the final identifier string&lt;br /&gt;
&lt;br /&gt;
Assumption:&lt;br /&gt;
Page title equals the Code of Chapter / Place / Organisation.&lt;br /&gt;
&lt;br /&gt;
This is a documented system invariant.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Returning Data as JSON ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
return mw.text.jsonEncode({&lt;br /&gt;
    identifier = code,&lt;br /&gt;
    sequence = nextSeq&lt;br /&gt;
})&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What this does:&lt;br /&gt;
&lt;br /&gt;
* Returns structured data&lt;br /&gt;
* Allows JavaScript to parse it reliably&lt;br /&gt;
&lt;br /&gt;
Why JSON is used:&lt;br /&gt;
&lt;br /&gt;
* Safe transport format&lt;br /&gt;
* Extensible&lt;br /&gt;
* Error and success responses use same mechanism&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Important Design Limits ==&lt;br /&gt;
&lt;br /&gt;
This module intentionally does NOT:&lt;br /&gt;
&lt;br /&gt;
* reserve identifiers&lt;br /&gt;
* check for page existence&lt;br /&gt;
* detect concurrent conflicts&lt;br /&gt;
* write anything to Cargo&lt;br /&gt;
* modify pages or files&lt;br /&gt;
&lt;br /&gt;
All of that is handled elsewhere or not at all.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Mental Model to Keep ==&lt;br /&gt;
&lt;br /&gt;
You can safely remember:&lt;br /&gt;
&lt;br /&gt;
* Lua module = calculator&lt;br /&gt;
* Input = Chapter + Context&lt;br /&gt;
* Output = identifier + sequence&lt;br /&gt;
* No side effects&lt;br /&gt;
* No state&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
&lt;br /&gt;
This document is stable reference documentation.&lt;br /&gt;
&lt;br /&gt;
It exists to support long-term understanding and memory retention.&lt;/div&gt;</summary>
		<author><name>Mngr</name></author>
	</entry>
</feed>