CommunityScale / Code
← The format

CS-Local 1.0

A markup standard for the codes of United States local government. This is the namespace document for https://code.communityscale.io/ns/local/1.0, and the contract our converter emits against and our renderer reads.

A markup standard for the codes of United States local government. Cities, towns, counties, townships, boroughs, villages, authorities and tribal governments all publish codes, and they do not publish them the same way. CS-Local starts from Akoma Ntoso 3.0 and extends it to cover that range.

Namespace   https://code.communityscale.io/ns/local/1.0    prefix  local:
Inherited   http://docs.oasis-open.org/legaldocml/ns/akn/3.0   prefix  akn:
Status      Draft. Supersedes schema/cs-municipal-profile.md.
Evidence    738 complete codes in 25 states; see survey/findings-corpus.md.
            1,618 zoning instruments for § 9; see survey/findings-schedules.md.
            157 codes from a second publisher; see survey/findings-second-publisher.md.

Relationship to Akoma Ntoso

Akoma Ntoso supplies the parts of this that are the same everywhere: a way to identify a provision, a way to record an amendment against it, and a hierarchy to hang both on. CS-Local keeps those and adds what AKN has no vocabulary for, because AKN assumes a national legislative tradition and United States local government has none. It has fifty state enabling frameworks, sixteen kinds of government publishing codes, and two codifier house styles.

Added: an instrument declaration, six typed notes where AKN has one, five container rungs AKN has no element for, rungs below the fifth (§ 3.1), a provision that stands for a range of numbers (§ 6.1), a per-document declaration of citation and enumeration grammar, and a normative schedule vocabulary for the matrices a code states rules with, bound to a declared entity registry (§ 9).

There is precedent at the federal level. USLM, the schema the Government Publishing Office and the House of Representatives use for United States law, is a derivative of Akoma Ntoso that forks the same way, adding <uscDoc>, <cfrDoc> and <frDoc> as separate document roots and typing its notes as <sourceCredit>, <statutoryNote> and <editorialNote> rather than using one <note> for everything.

Inherited from AKN, which is what makes round-tripping possible: the FRBR identity model, the eId naming convention, the hierarchical container model with <num>, <heading>, <intro>, <content> and <wrapUp>, the content-xor-children rule, <ref> and <def> and the TLC entity vocabulary, <lifecycle> and <passiveModifications>, and <proprietary> for source identifiers. A CS-Local document with its extensions stripped is a valid AKN document, with less information but no changed meaning. convert/downgrade_akn.py performs that reduction and reports what it drops.

Added by CS-Local is everything below marked local:.

1. Document identity

One <local:code> per government, holding one or more <local:instrument>. An instrument is a body of text adopted as a whole by one body. In a published code it is usually a chapter.

<local:code xmlns:local="https://code.communityscale.io/ns/local/1.0"
           xmlns="http://docs.oasis-open.org/legaldocml/ns/akn/3.0">
  <local:jurisdiction>
    <local:place gnis="0619426" state="MA" type="town">Sherborn</local:place>
    <local:enablingFramework>/us-ma/act/mgl</local:enablingFramework>
  </local:jurisdiction>
  <local:profile citation="chapter-article-section" enumeration="eastern-bracket" containers="chapter-article"/>
  <local:instrument type="bylaw" eId="chp_240"> … </local:instrument>
</local:code>

<local:place>/@type is the kind of government, which decides what its legislature is called, which instrument types are available to it, and what state framework it acts under. The sixteen types in the corpus, by share of the 3,825 codes surveyed:

@type Share @type Share
city 32% county 2.5%
town 21% authority 0.4%
township 16% tribal 0.3%
borough 15% municipality 0.2%
village 12% district, metropolitan, other <0.2%

A county is not a large city: its legislature may be a Board of Supervisors, a County Council or a Board of Commissioners, it may have no charter, and in many states it can only legislate on subjects the state has delegated. @type is what lets the same document model carry both without either being a special case.

FRBR runs unchanged from AKN, per instrument, and the expression date is the code date rather than the capture date:

FRBRWork        /us-ma/sherborn/bylaw/zoning
FRBRExpression  /us-ma/sherborn/bylaw/zoning/eng@2026-04-23          adopted
FRBRExpression  /us-ma/sherborn/bylaw/zoning/eng@proposed-2027-atm   proposed

2. Instruments

19% of codes in the corpus contain at least one chapter that a legislature never voted on. Planning Board subdivision rules, Board of Health regulations, Conservation Commission wetlands regulations, cable franchise agreements, fee schedules, acceptances of state statutes, and the municipal charter itself all sit in the same undifferentiated stack of chapters, distinguished only by a sentence of English at the top.

The distinction determines what it takes to change a provision. A zoning bylaw takes a two-thirds Town Meeting vote at an annual meeting. Subdivision rules take a Planning Board vote after a public hearing, which the Board can hold at any time.

<local:instrument type="regulation" eId="chp_a676">
  <local:adoption>
    <local:body role="administrative" refersTo="#org_planning_board">Planning Board</local:body>
    <local:authority href="/us-ma/act/mgl/41/81Q">MGL c. 41, § 81Q</local:authority>
    <local:procedure amendedBy="board-vote" hearing="required" majority="simple"/>
    <local:date date="1970-06-25" type="adoption"/>
  </local:adoption>
  …
</local:instrument>

@type is required and closed:

@type What it is Who changes it
ordinance Legislation of a city, county, township, borough or village Council, board or commission vote
bylaw Legislation of a New England town Town Meeting vote, and in Massachusetts an Attorney General review
regulation Rules made by a board under delegated authority That board's own vote
charter The instrument the government itself is made under State legislature or charter commission and ballot
specialAct State legislation for this government alone State legislature
acceptance A local vote to adopt a state statute The accepting body, and it is revocable the same way
franchise A contract with a utility or operator Contract term and renegotiation
policy Internal administrative policy The adopting officer
ruleOfProcedure A body's rules for its own conduct That body

<local:body>/@role is legislative, executive, administrative, or judicial. It is declared, not inferred. Classifying adopting bodies by name leaves 5,070 of the corpus's 20,308 adoption statements unresolved, because a Board of Selectmen legislates in some towns and administers in others. The role is curated per government at onboarding, from that government's charter.

<local:authority> is required on every instrument whose @type is not ordinance, bylaw, or charter. This follows the CFR, which requires an AUTH element naming the enabling statute on every part: for a regulation, the authority is the basis of its validity. In local practice the enabling statute is almost never stated anywhere a machine can reach it, so this field is populated at onboarding rather than extracted.

<local:procedure> is what the platform renders as "what it takes to change this", and what the comment workflow reads to name the meeting a comment should go to.

3. Containers

AKN has chapter, article, part, section, subsection, paragraph, subparagraph, point, indent. The corpus needs title (44% of codes), division (29%), subchapter, subdivision, and an appendix series that is a container rather than an attachment (appendix, 26%).

CS-Local declares the ladder and uses AKN's <hcontainer> with a @name for the rungs AKN lacks, rather than adding five elements:

<local:profile containers="title-chapter-article-division"/>
…
<hcontainer name="title" eId="ttl_17"><num>17</num><heading>Zoning</heading>
  <chapter eId="ttl_17__chp_17.10"> … </chapter>
</hcontainer>

The declared ladder is what the converter and the renderer both read. A Title/Chapter/Article/Division code and a Part/Chapter/Article code round-trip through the same code path because neither is hardcoded.

Depth, not marker shape, picks the element below section level. 34 distinct enumeration ladders are in use across the corpus, so the marker cannot be the signal. The second publisher's corpus settles this the other way and confirms it: there the depth is carried explicitly by the export, and of 43,964 descents 99.4% move exactly one rung, so the ladder is real and it is the marker that varies over it.

3.1 Below the fifth rung

AKN offers five rungs below section: subsection, paragraph, subparagraph, point, indent. Codes go deeper. 26 of 157 codes in the second corpus (17%) descend to a sixth or seventh rung, and the first corpus holds a nine-rung ladder.

Repeating indent at the bottom is not an answer, because it makes two different depths into the same element and the document then misstates its own shape. The rungs past the fifth take the same treatment as the container rungs AKN lacks, an <hcontainer> with a name, and carry their depth so nothing is inferred from position alone:

<point eId="sec_5-1__subsec_a__para_1__subpara_a__point_1">
  <num>1.</num>
  <indent eId="…__point_1__indent_a">
    <num>a.</num>
    <hcontainer name="level" local:depth="6" eId="…__indent_a__level_i">
      <num>i.</num>
      <content><p>…</p></content>
    </hcontainer>
  </indent>
</point>

@local:depth is required on <hcontainer name="level"> and counts from the section, so the first rung below a section is 1 and the overflow rungs are 6 and beyond. It is not written on the five AKN rungs, whose element name already fixes their depth. On downgrade the hcontainer survives as an hcontainer and only @local:depth is reported as lost, so the nesting a reader sees is unchanged.

3.2 An illustration is addressed like a provision

A zoning code draws. Spencer's § 4.9.2 settles what "increased or intensified" means for a nonconforming structure with three plan diagrams and a sentence apiece; Lowell measures the width of an irregular lot the same way. The drawing carries part of the rule, and a reader who disagrees with it, cites it or asks a question about it is talking about the drawing rather than about the sentence beside it.

An illustration is therefore a container with an eId of its own, not an image inside a paragraph:

<blockContainer name="illustration" eId="sec_4.9__subsec_2__para_a__fig_1">
  <img src="illustrations/image1.png" alt="Plan of a lot drawn with a dash-dot parcel line and,
       inside it, a dashed zoning setback line …"/>
  <p class="caption">Addition that does not encroach setbacks</p>
</blockContainer>

@alt is required and is not the caption. A caption names the figure for a reader who can see it; alternative text has to say what the drawing shows, because the drawing is the rule. It has no machine source (no exporter carries it), so it is written by hand, and the converter reports every illustration that reaches the document without it.

The renderer carries the eId onto the page, so a diagram anchors a link and takes a comment in its own right.

4. Citation grammar and eIds

The citation is the URL, so its grammar is declared, not guessed. Three grammars cover 96% of the corpus, and schema/profiles.yaml carries all six with their patterns and their support:

@citation Form Example Share
chapter-section <chapter>-<section> § 1-14 62%
dotted-decimal <title>.<chapter>.<section> § 14.05.030 31%
chapter-article-section <chapter>-<article>.<section> § 240-4.5 4%
dashed-triple <title>-<chapter>-<section> § 1-04-010 2%

eIds follow AKN's convention with CS-Local prefixes, derived from the declared grammar: sec_240-4.5 under chapter-article-section, sec_14.05.030 under dotted-decimal.

Level eId
Instrument chp_240, or the container word the jurisdiction uses
Container ttl_17, art_IV, div_3, pt_I
Section sec_<citation>
Below section …__subsec_a__para_1__subpara_a__point_1__indent_a
Below the fifth rung …__indent_a__level_i, with @local:depth (§ 3.1)
Range of numbers sec_94-1_thru_94-30 (§ 6.1)
Definition sec_<citation>__def_<slug>
Term entity term_<citation>_<slug>
Named entity <kind>_<slug>, e.g. dist_ra, wshed_charles, org_planning_board

5. Notes are typed

AKN has one <note>. CS-Local types notes as USLM does. The corpus carries this material in 90% of codes and 41,320 paragraphs.

Element What it carries Prevalence
<local:editorialNote> The codifier speaking: renumbering, supersession, moves 90% of codes
<local:sourceCredit> The provenance note as published, verbatim, plus parsed events 100%
<local:generalReferences> The cross-reference block at the head of a chapter 45%
<local:disposition> The ordinance-to-location audit trail 66%
<local:derivation> Current numbering mapped to the prior codification 27%
<local:effectiveNote> Effective dates that differ from adoption, after the CFR's EFFDNOT

<local:sourceCredit> keeps the published string and the parsed events side by side. Seven written forms exist in the corpus and the parse is lossy, so the string is authoritative:

<local:sourceCredit>
  <local:asPublished>[Amended 4-25-2023 ATM by Art. 23]</local:asPublished>
  <local:event date="2023-04-25" type="amendment" by="#org_town_meeting" instrument="Art. 23"/>
</local:sourceCredit>

6. In force, reserved, repealed

73% of codes publish Reserved placeholders, and every code publishes repealed provisions with a note pointing elsewhere. Both are published rather than dropped: a reserved number asserts that nothing is missing, and a repealed section is where a reader following an old citation lands.

CS-Local follows USLM's @inEffect rather than AKN's @status, which conflates the dispositions below:

<section eId="sec_240-1.4__subsec_e" local:inForce="false" local:disposition="deleted">
  <num>E.</num>
  <content><p>[Deleted 1994; see <ref href="#sec_240-5.5__subsec_b__para_2">§ 240-5.5B(2)</ref>.]</p></content>
</section>

@local:disposition is current, reserved, deleted, repealed, superseded, or renumbered. @local:inForce is the boolean the renderer keys on.

6.1 A provision may stand for a range of numbers

One publisher reserves numbers one at a time. The other reserves them in blocks, and publishes the block as a single heading: Secs. 94-1—94-30. Reserved. This is not a minor variant. In that corpus there are 1,603 single reserved sections against 7,884 ranges, those ranges stand for 152,931 section numbers at a median of 19 each, and 70% of codes publish at least one. There are more numbers behind the ranges than there are real sections in the whole corpus.

Neither thing the schema could already do is acceptable. Expanding a range into thirty section elements invents thirty provisions the code never published, and multiplies the document by the size of its own gaps. Collapsing it to a single section numbered 94-1 throws away twenty-nine numbers, so a reader following a citation to § 94-17 lands nowhere, which is the exact failure the rest of § 6 exists to prevent.

So a provision may declare the span of numbers it stands for:

<section eId="sec_94-1_thru_94-30" local:inForce="false" local:disposition="reserved"
         local:coversFrom="94-1" local:coversTo="94-30">
  <num>94-1—94-30</num>
  <heading>Reserved</heading>
</section>

@local:coversFrom and @local:coversTo are citations in the document's declared grammar, written as the code writes them. <num> keeps the published string, which is what a reader sees. The eId joins the endpoints with _thru_ rather than AKN's __, because __ means containment and a range contains nothing; the separator has to be one no citation grammar produces.

A citation to any number within the span resolves to this provision. That is the whole purpose: the range is how a reader chasing § 94-17 is told that the number is deliberately empty rather than missing. A resolver that cannot interpret the grammar well enough to test membership should fall back to matching the endpoints exactly rather than reporting the number unknown.

The two attributes are declared together or not at all, and only on a provision that also declares a disposition, since a range of numbers with no disposition asserts nothing. They are not restricted to reserved: a block repealed at one stroke is the same shape. On downgrade both are reported as lost, along with the disposition they qualify, and the section survives carrying its published <num>.

7. Definitions stay section-scoped

The corpus holds 30,327 definitions sections across 738 codes, a median of 31 per code. A term is routinely defined more than once in one code with different meanings: Sherborn defines STRUCTURE three times, generally and again for floodplain and historic district purposes. A global term entity would resolve a floodplain provision to the general definition, changing what the provision means.

The term entity is therefore scoped to its defining section. Resolving a use of a term to the nearest in-scope definition is the renderer's job: section, then container, then instrument, then code.

Three lead-in forms are in use and a parser must accept all of them: all-caps then a dash (68% of codes), a quoted term then "means" (42%), sentence case then "means" (50%). The XML is the same shape for each:

<hcontainer name="definition" eId="sec_240-5.5__def_structure">
  <content><p><def refersTo="#term_240-5.5_structure">STRUCTURE</def> — Means, for floodplain
    management purposes, a walled and roofed building …</p></content>
</hcontainer>

8. Named entities

A zoning chapter regulates districts, a stormwater chapter regulates watersheds, a licensing chapter regulates premises, a scenic road bylaw regulates named ways. Each is the same pattern: an identified thing that provisions refer to and a map can show. Districts are one case of it, not the general rule.

<TLCConcept eId="dist_ra"        href="/ontology/district/ra"      showAs="Residence District A"/>
<TLCConcept eId="wshed_charles"  href="/ontology/watershed/charles" showAs="Charles River Watershed"/>
<TLCOrganization eId="org_planning_board" href="/ontology/organization/planning-board" showAs="Planning Board"/>

Provisions carry refersTo. Because the entity is identified rather than named in prose, renumbering a section cannot break the binding, and the map layer is a lookup from entity eId to geometry rather than a string match.

An entity that a schedule keys on, or that a map draws, needs more than a TLCConcept can hold: the abbreviation the code writes in table headers, the key the geometry layer uses, and the provisions that govern it. <local:entities kind="…"> in § 9.1 is that declaration, and it is written for any kind, not for districts.

9. Normative schedules

A table that lays text out stays an AKN <table> and nothing more is claimed about it; a table that states rules additionally gets a <local:schedule> whose axes bind to declared entities. The two most consulted parts of a zoning code are matrices: use × district → permission, and district × dimension → value. The survey (survey/findings-schedules.md) shows why the schedule has to be structure of its own rather than markup on the table: only 25% of zoning instruments publish these matrices as inline tables at all. 22% publish them as PDF attachments the code text merely points at, and 17% publish them as per-district prose. The rules exist in every case; the table is only one of three carriers. A schedule therefore lives beside its source, whatever the source is, and declares where it came from: it is a child of the provision that publishes the rule, next to the AKN <table> when one is inline, and the registry of § 9.1 sits with the document's <references>.

9.1 The entity registry

The join between a schedule column header (RA), the prose name ("Residence District A"), and the zoning map's polygon attribute is declared once. 87% of zoning instruments reference their zoning map and 52% have overlay districts, so the map key and the overlay relation are first-class, and an unmapped district is stated rather than silent (Sherborn's Flood Plain District is established by the code and absent from the map; that absence is information).

The registry is a registry of entities with a declared kind, not of districts, because § 8's argument applies here: a stormwater chapter's schedule keys on watersheds and a licensing chapter's on premises, and those need the same declaration rather than a parallel one. Zoning is the first @kind, not the only one.

<local:entities kind="district" established="#sec_240-2.1">
  <local:entity eId="dist_ra"  code="RA"  class="residential" map="RA"
                governedBy="#sec_240-3.2 #sec_240-4.2" showAs="Residence District A"/>
  <local:entity eId="dist_b-g" code="B-G" class="business"    map="B-G"
                governedBy="#sec_240-3.2 #sec_240-4.2 #sec_240-5.3" showAs="Business District G"/>

  <!-- An overlay says what it does and where its rules are written. -->
  <local:entity eId="dist_fp"  code="FP"  class="overlay" relation="restricts"
                over="#dist_ra #dist_rb #dist_rc" governedBy="#sec_240-5.5"
                showAs="Flood Plain District"/>   <!-- no @map: not on the map -->
  <local:entity eId="dist_mod" code="MOD" class="overlay" relation="supplements"
                map="MODSherborn" governedBy="#sec_240-5.7"
                showAs="Multi-Family Overlay District"/>
</local:entities>

@code is the abbreviation the code itself uses in schedules and prose. @map is the value of the geometry layer's attribute, present only when a layer exists; the two differ whenever the map speaks its own dialect (Sherborn's map says M+EA where the schedule says REA). @class is residential, business, commercial, industrial, mixed, overlay, or special, as recommended values. @established, on the group or overridden per entity, points at the provision that creates it.

@governedBy is the entity's substantive text, a whitespace-separated list of the provisions that regulate it, and it is what makes an overlay usable. Establishing text and governing text are different things: § 240-2.1 creates the Flood Plain District in one line, and § 240-5.5 is what it actually requires. Without the link, a reader who lands on an overlaid parcel is told a district name and nothing else. 52% of zoning instruments have overlays, with a median of 11 mentions each, and 19% of the registries in the corpus enumerate overlays in the same table as base districts, which is why one registry with a class carries both.

@relation says what an overlay does to the districts beneath it, and @over names them (absent @over means it applies wherever it is mapped):

@relation What the overlay does
supplements Adds uses or allowances the underlying district does not give (the "additional uses allowed" overlay)
restricts Adds constraints on top of the underlying district; the underlying rules still apply (a flood plain or watershed protection district)
replaces Supersedes the underlying district's rules within its area, for the subjects its text covers

A parcel is in an overlay and its base district, so resolving "what can I build here" means resolving the base district and then applying every overlay mapped to that parcel in the declared relation. A consumer that cannot resolve an overlay must say so rather than answer from the base district alone. On downgrade each entity becomes a plain TLCConcept, keeping eId and showAs.

9.2 Use schedules

The corpus shape: districts across one axis (median 6, max 23), uses down the other, one symbol per cell, and three kinds of content a flat grid drops. 73% of codes with a use matrix group rows into category bands, 45% footnote individual cells, and 29% attach a per-use reference to the section that regulates the use further. All three are structure here.

<local:schedule eId="schd_240-3.2" type="use" source="#sec_240-3.2">
  <local:legend>
    <local:symbol code="P" outcome="permitted">Permitted</local:symbol>
    <local:symbol code="A" outcome="approval" approver="#org_board_of_appeals"
                  procedure="special-permit">Allowable on special permit</local:symbol>
    <local:symbol code="X" outcome="prohibited">Prohibited</local:symbol>
  </local:legend>
  <local:group heading="Residential uses">
    <local:row eId="schd_240-3.2__use_1" num="1" subject="Single-family home"
               refersTo="#term_240-1.5_dwelling_sf" source="#sec_240-3.2__subsec_1">
      <local:cell entity="#dist_ra" symbol="P"/>
      <local:cell entity="#dist_b-g" symbol="A" footnote="*" see="#sec_240-5.3"/>
    </local:row>
  </local:group>
  <local:footnote marker="*" effect="conditions">Subject to site plan review under § 240-5.3.</local:footnote>
</local:schedule>

A row binds to the term it regulates, and the binding is by identity rather than by string. @subject stays exactly as the schedule prints it and @refersTo names the defined term, because the two are routinely worded differently: the schedule says "Single-family home" where the definitions section says DWELLING, SINGLE-FAMILY. Matching them on text is the same mistake the entity registry exists to prevent on the district axis, and it fails silently, which is worse. The corpus makes the stakes concrete: 30,327 definitions sections, a median of 31 definitions per code, and § 7's rule that a term is scoped to its defining section and resolves outward (section, then container, then instrument, then code) is what a row's refersTo follows.

@refersTo is optional, because plenty of use rows name things the code never defines, and a row without it is a legitimate state rather than an error. It is worth reporting, though: the share of rows that bind is a direct measure of how well a code's schedule and its glossary agree, and the ones that do not bind are where a drafter should look.

The legend is declared per schedule and is semantically typed. The corpus forbids anything else: 331 codes with a use matrix run 200 distinct symbol systems, the same glyph means "allowed", "accessory" or "agriculture permit" in different codes' own legends, the dash family fills 24,960 cells without one recoverable legend entry, and only 17% of codes publish a legend a machine can read. @code is therefore an opaque string whose meaning is given entirely by @outcome, a closed vocabulary:

@outcome Meaning
permitted Allowed by right
accessory Allowed as an accessory use
approval Allowed on a discretionary approval; @approver names the body, @procedure the instrument (special-permit, conditional-use, site-plan, variance)
prohibited Not allowed
not-applicable The combination cannot occur
see The cell defers to a provision; the cell's @see says which

@approver resolves to a declared organization, which connects the schedule to the adoption and procedure model of § 2: the platform can answer "can I build this here, and if not by right, who says yes" as a lookup from a map click.

9.3 Dimensional schedules

Orientation in the corpus runs four-to-one districts-as-columns over districts-as-rows, and it is presentation: the content is (entity, dimension, value) triples, stored one row per district, rendered on whichever axis fits. About twenty concepts cover the dimension vocabulary of every inline dimensional matrix found, so the concept is normalized while the label stays as published.

<local:schedule eId="schd_240-4.2" type="dimensional" source="#sec_240-4.2">
  <local:provenance>
    <local:transcribedFrom
        href="/attachment/333628/SH4020-240a Schedule of Dimensional Requirements.pdf"
        supplement="Supp 3, Aug 2025" verified="2026-08-05"/>
  </local:provenance>
  <local:dimensions>
    <local:dimension key="lot_area"      concept="lot-area" bound="min" unit="acre">Minimum Lot Size</local:dimension>
    <local:dimension key="setback_front" concept="setback-front" bound="min" unit="ft"
                     footnote="**">Minimum Front Setback</local:dimension>
    <local:dimension key="height_st"     concept="height" bound="max" unit="story">Maximum Height</local:dimension>
  </local:dimensions>
  <local:row entity="#dist_ra">
    <local:cell dim="lot_area" value="1"/>
    <local:cell dim="setback_front" value="60"/>
    <local:cell dim="height_st" value="2.5"/>
  </local:row>
  <local:footnote marker="**" effect="clarifies">Measured from the center line of the abutting way.</local:footnote>
</local:schedule>

Recommended @concept values: lot-area, lot-width, lot-depth, frontage, setback-front, setback-side, setback-rear, height, coverage-lot, coverage-building, far, open-space, density, impervious. The list is open; the census in survey/corpus-schedules.md is its source.

Cells are uniform everywhere in § 9: @entity, @value (a number), @text (published words like "Not applicable" or "—"), @symbol (use schedules), @footnote, @see. A renderer never branches on cell shape, which is the flatten rule convert/matrices.py already enforces, promoted to the content model. An absent cell means the source published nothing, which is distinct from not-applicable and preserved as such.

@entity takes a whitespace-separated list, because a source cell merged across several columns ("P in all residential districts") is common, and spelling it entity="#dist_ra #dist_rb #dist_rc" keeps one published cell as one cell instead of duplicating it three times or falling back to the ragged-row escape hatch matrices.py uses today.

9.4 Footnotes alter the cell they mark

A footnoted symbol is not the bare symbol. P* where * reads "subject to site plan review" is a different permission from P, and a consumer that resolves the cell's outcome while treating the marker as decoration misreports the law. 45% of codes with a use matrix footnote individual cells, so this is the common case, not the corner.

The bloat pressure is real: one footnote routinely marks dozens of cells (Sherborn's front-setback note applies to every district's row; in a 33-use by 23-district table a general condition can mark hundreds). Inlining the condition per cell would multiply the text by the cell count; fusing it into the symbol would explode the legend. So footnotes get the same treatment symbols got: declared once, typed once, referenced by marker, and the per-cell cost stays one short attribute.

<local:footnote marker="*" effect="conditions">Subject to site plan review under § 240-5.3.</local:footnote>
<local:footnote marker="**" effect="clarifies">Measured from the center line of the abutting way.</local:footnote>
<local:footnote marker="†" effect="varies" see="#sec_240-4.3">Reduced to 100 feet on a
  cul-de-sac turnaround, per § 240-4.3.</local:footnote>

@effect is required and closed, and it is what makes the marker computable:

@effect What it does to the marked cell
clarifies Defines a term or a measurement datum; the outcome and value stand
conditions The outcome applies only subject to the stated circumstance; a permitted cell so marked must never be presented as an unconditional yes
varies The value or outcome is different in stated circumstances; the text says how, @see says where

A cell's outcome is provisional until its footnotes are applied: clarifies is the only effect that leaves it untouched, and a consumer that encounters an effect it does not recognize treats it as conditions, so an under-curated footnote degrades to a warning rather than a false yes.

Two rules keep the mechanism small. Markers attach at any scope: @footnote is allowed on <local:cell>, <local:row>, <local:group>, <local:dimension>, <local:column>, and the schedule itself, and a marker at an outer scope applies to every cell within it, so Sherborn's measurement note attaches to the setback_front dimension once rather than to six rows, and a whole-schedule note is one attribute. Multiple markers are whitespace-separated (footnote="* †"), AKN's idiom for reference lists. And a marker that is really a permission class belongs in the legend: the corpus's P1 through P5 families, where the digit gives P a distinct standing meaning across the whole table, are declared as legend symbols with their own @outcome (<local:symbol code="P1" outcome="approval" ...>), not as footnotes. Footnotes are for conditions that cut across symbols and values; the legend is for the permission vocabulary. The boundary test is whether the marker's meaning depends on which symbol it touches.

A marker means nothing outside its own schedule. <local:footnote> is a child of the <local:schedule> it belongs to, and resolving @footnote is a lookup in that schedule and nowhere else. This is not a convenience: codes reuse markers across their tables as a matter of course. Of the codes in the corpus carrying two or more footnoted matrices, 93% use the same marker on more than one of them (108 of 116), and the marker is almost always * or a low integer or letter restarted per table. Santa Clarita is the extreme, running b on 91 of its 117 footnoted matrices, and Rancho Cordova restarts its numbering on each of 21. A document-wide footnote namespace would therefore have to renumber the published markers, which changes what the page shows a reader, or collide. Schedule-local resolution is the same rule § 7 applies to definitions, for the same reason: Sherborn defines STRUCTURE three times and a global term would resolve a floodplain provision to the general meaning.

Document-unique identity is separate from the marker and is carried by the eId, derived from the schedule and a slug of the marker, so the two * notes below never collide as identifiers even though both print as *:

schd_240-3.2__fn_ast          *    in the use schedule
schd_240-4.2__fn_ast          *    in the dimensional schedule
schd_240-4.2__fn_ast2         **
schd_240-4.2__fn_dag          †
schd_240-4.2__fn_3            3

Slugs: *ast, **ast2, dag, ddag; digits and letters stand for themselves, and enclosing parentheses are dropped ((4)4). eIds are derived from the published marker rather than from position so that inserting a footnote does not renumber its neighbours, which matters because comments anchor to eIds.

Within one schedule a marker must be unique, and that is a validation error rather than a resolution rule. A few codes restart markers partway down one long printed table; a curator either keeps the source's distinct markers or splits the table into the two schedules it really is. Two schedules that genuinely share a note each declare it, since duplicating one sentence is cheaper than a cross-schedule reference that a reader of either table cannot see.

9.5 Provenance, expressions, other schedules

<local:provenance> is required whenever @source does not point at an inline AKN table: <local:transcribedFrom> for the attachment-only mode (the PDF, the supplement it was verified against, the date), <local:curatedFrom> for the prose mode, where each row's @source names the provision it was read from.

A schedule is versioned whole, and the redline is computed rather than stored. The schedule carries a <local:sourceCredit> of its own, the same element § 5 defines, recording the amendment that produced the state it is in:

<local:schedule eId="schd_240-3.2" type="use" source="#sec_240-3.2">
  <local:sourceCredit>
    <local:asPublished>[Amended 4-29-2025 ATM by Art. 17]</local:asPublished>
    <local:event date="2025-04-29" type="amendment" by="#org_town_meeting" instrument="Art. 17"/>
  </local:sourceCredit>
  …

Schedules are expression-level content like everything else in FRBR, so the adopted expression holds one schedule and the proposed expression holds another, and the cell-wise difference between them is derived by comparing the two, keyed by (row, entity) or (entity, dimension). That is what the platform's proposed-schedule page already does.

Cells therefore carry no amendment history of their own. Recording per cell when each permission last moved would multiply the document by its amendment count (Sherborn's use schedule has two dozen amendment events against a table of several hundred cells), and the stored history would drift out of agreement with the cells themselves the first time a transcription was corrected. A diff between two expressions cannot drift, because it is recomputed from the thing it describes.

Use and dimensional schedules are the two typed cases of a general mechanism. Parking tables appear in 825 zoning instruments, more than twice as many as carry a use matrix, with sign and landscaping tables behind them; these are usually use × requirement rather than anything × district. type="parking", "sign", "landscaping" or "other" with declared generic columns covers them:

<local:schedule eId="schd_240-4.6" type="parking" source="#sec_240-4.6">
  <local:columns>
    <local:column key="spaces" label="Minimum off-street spaces"/>
  </local:columns>
  <local:row subject="Dwelling, single-family">
    <local:cell col="spaces" text="2 per dwelling unit"/>
  </local:row>
</local:schedule>

On downgrade, each <local:entity> reduces to a plain TLCConcept (eId and showAs survive; the map join, the classification and an overlay's relation to what lies under it are reported as lost), and <local:schedule> is dropped and reported with its cell count; the AKN table, where one exists, remains, so no meaning changes. The full element-by-element reference is published at /ns/local/1.0/reference/.

10. <proprietary>

Provenance and source identifiers only.

<proprietary source="#communityscale">
  <local:sourceSystem customer="SH4020" id="38073564" codeDate="2026-04-23"
                      captured="2026-08-05"/>
  <local:attachment href="/attachment/333628/SH4020-240b Zoning Map.pdf"/>
</proprietary>

Never drafting comments, reviewer names, or internal notes. The working DOCX carries 28 of them for Sherborn alone and they must not travel with a published file. The build fails if one appears in rendered output.

11. Validation

Three layers, in the order they should run:

  1. Structural. Every hierarchical element has an eId; the eId matches the declared citation grammar; content-xor-children; no duplicate eIds. Every <hcontainer name="level"> declares @local:depth, and the depth is greater than five, since the first five rungs have AKN elements of their own.
  2. Referential. Every <ref> target resolves; every refersTo names a declared TLC entity; every <local:authority> href parses as a citation. In schedules: every @entity on a cell or row, singly or in a list, resolves to a declared <local:entity>, every @symbol to the schedule's own legend, every @dim to a declared dimension, every @footnote marker, at whatever scope it sits, to a footnote declared in the same schedule, every @see, @source, @governedBy, @over and row @refersTo to a provision or declared entity. Marker uniqueness is checked per schedule, never per document; footnote eIds are checked per document, like every other eId.
  3. Instrumental. Every instrument declares a type; every non-primary instrument declares an authority; every <local:body> has a role and resolves to a declared organization. Every schedule whose @source is not an inline table declares provenance; every legend @approver resolves to a declared organization; every footnote declares an @effect; @local:coversFrom and @local:coversTo appear together and only on a provision that declares a disposition, and the span runs forwards; every class="overlay" entity declares a @relation and a @governedBy, since an overlay whose effect and text are both unstated cannot be applied to a parcel.

Layer 3 cannot be inferred from a source document and must be curated.

Open questions

  • Whether <local:code> should be a document collection in AKN terms or a manifest of separate documents. The corpus argues for separate documents, since instruments are adopted, amended and repealed independently, but a reader wants one code.
  • Whether a varies footnote should be able to hold a structured predicate or provisions rather than a paragraph, so that "except 100 feet where abutting a residential district" becomes computable rather than quoted. § 9.4 types the footnote's effect on the cell; it does not yet structure the circumstance that triggers it.
  • How a dimensional cell says "same as the least restrictive adjacent residential district" (Beverly) or otherwise defines a value by reference to another district's, which @value/@text can record but not resolve.
  • How to model an instrument adopted jointly, which the corpus shows for combined planning and zoning commissions.
  • Whether acceptance instruments should carry the accepted state statute by reference and render it inline, since the text is not the municipality's.
  • Amendment procedure varies by state and by home-rule status, so <local:procedure> may need a state-level vocabulary rather than free attributes.

Differences from cs-municipal-profile.md

Superseded in these respects: it declared a profile rather than a fork; it assumed one citation grammar, one enumeration ladder and a Chapter/Article spine; it had no instrument, adopting body, or enabling authority; it had one note type; and it omitted reserved and repealed provisions.

Carried forward unchanged: section-scoped definitions, depth rather than marker shape choosing the element, and @GUID as the back-reference to the source of record.