The Principia Machina/TPC-PY/tpc lint.py
Appearance
# -*- coding: utf-8 -*-
"""tpc_lint 0.1.0 (working label; a TPC-PY version is declared by H-DECL at
publication): the PROMPT LINT, D182.1 feature F5, "prompt lint as TPC
functions PRB runs locally". Pure functions over the SPONSOR ZONE (F1) and
the live tool list's JSON, returning coded findings. Advisory: consumed by
no rule. Standard library only; Python 3.8+ (the corpus run on 3.8.20 and
3.10-3.13 on 2026-09-27; the Windows run owed); Windows/Linux. The PHP
residence (tpc_php/tpc_lint.php) reproduces every result byte for byte;
the vectors are vectors/lint_corpus.json.
lint(zone, tools, cap) -> list of findings (canonical: lint_json)
zone the SPONSOR ZONE: a str, or bytes read as UTF-8.
tools the JSON text of the tool list the model is offered (the same text
D(PROMPT)'s canonical_tools reads): a str or UTF-8 bytes.
cap SPONSOR ZONE CAP in code points, an int 1..1,048,576. A declared
parameter (8,000, ESTIMATE, NOT IN FORCE until declared): the
function takes it as an argument and holds no value of it.
A finding is {code, item, locator}. It never carries an excerpt of the
input: item is a table entry id (PL2-01 ...), "U+XXXX" for PL1 (the
echo-safe form of TPS 2.5.14), "-" for PL8, or a PL0 reason; locator is
"Z:<line>:<column>" (zone; 1-based; code points after line-ending
normalization and NFC), "T:<tool>:<path>" (tools; tool index 0-based;
path of member names from KNOWN_KEYS, "@<rank>" for any other member,
its rank in canonical key order, and list indices; ":key" appended when
the finding is in a member's key), or "-".
PL0 INPUT. The input cannot be linted; the result is exactly one finding,
item, in this order of checks: ZONE-TYPE (not text), ZONE-UTF8 (bytes
that are not UTF-8, or a str holding a surrogate), ZONE-SIZE (over
LINT_MAX_BYTES of UTF-8), TOOLS-TYPE, TOOLS-UTF8, TOOLS-SIZE,
TOOLS-JSON (canonical_tools of tpc_identity refuses it: D(PROMPT)'s
own refusal), TOOLS-SHAPE (not a JSON array of objects), CAP (not an
int, a bool, or outside 1..1,048,576). Never an exception.
PL1 HIDDEN CHARACTERS. Every code point the TEXT SAFETY RULE (TPS 2.5.13)
refuses, by the tree's own predicate (tpc_zone._disallowed; TPC-PHP
_disallowed_cp), after line-ending normalization: zone, one finding
per occurrence; tools, one per distinct code point per string (keys
included).
PL2 CREDENTIAL SHAPES. Table PL2 (30 entries; OWASP LLM07: the prompt is
not a secret and not a control): vendor key shapes, private-key
headers (RFC 7468 labels), bearer and basic credentials, a URL with a
password, generic key=value assignments. Zone and every tool string
and key.
PL3 A SOURCE OF AUTHORIZATION OTHER THAN THE HUMAN (AC-1.11). Zone only;
negation-guarded.
PL4 RELAXATION OR PROBE-SPOTTING (AC-1.10, AC-1.12, AC-5.18, benchmark
quarantine). Zone only; negation-guarded.
PL5 A SECOND TPS OR GTP TEXT IN THE ZONE. The published texts' own header,
title, version, AC-1.19 and sentinel lines (table PL5). Zone only. Its
limit: a reformatted, paraphrased or partial copy without those lines
passes; naming TPS or part 6 is not a finding.
PL6 TOOL TEXT ADDRESSED TO THE MODEL. Hidden-instruction tags, override
and secrecy phrases, cross-tool shadowing, local secret files, hosts
and send-to phrases, memory writes (table PL6). String values of the
members "description" and "title" at any depth; other members are not
read by PL6.
PL7 OPEN-ENDED TOOLS (OWASP LLM06: shell, arbitrary fetch, send). A
finding only. The tool's "name" (or "function"/"name") split into
tokens (maximal runs of ASCII letters and digits, split again where a
lower-case letter or digit is followed by a capital; then folded)
holding a table token sequence; or its "description" (or
"function"/"description") holding a table phrase.
PL8 THE ZONE OVER SPONSOR ZONE CAP. One finding at the first code point
past the cap; a zone of exactly cap code points passes.
PL9 INSTRUCTIONS TO PERSIST OR TO WRITE MEMORY OR INSTRUCTION FILES. Zone
only; negation-guarded.
MATCHING. One fixed normalization: line endings to LF, NFC, A-Z folded to
a-z (ASCII only), every maximal run of Unicode White_Space (tpc_zone's
list) replaced by one space. A phrase (an expansion of a table template)
matches at a position where its text equals the normalized text; when its
first (last) character is an ASCII letter or digit, the character before
(after) it must not be one. A PL2 pattern matches literal by literal,
each class run possessive (longest run up to its maximum, never given
back), and a closing class run must end at a character outside its
class. Tables are written literally in both residences from one source
(gen_lint_tables.py) with their SHA-256, checked at load.
NEGATION GUARD (PL3, PL4, PL9). A match is dropped when one of the last
NEG_WINDOW (4) tokens before it, within its sentence, is a negation (table
NEG). Tokens are maximal runs of a-z, 0-9, the apostrophe and U+2019; the
sentence starts after the last ".", "!", "?", ";" or ":" before the match.
ORDER. Findings are unique and sorted by: code; zone before tools; line
and column, or tool index and then path (code-point order); item.
LIMITS (named). A filter, not a guarantee: paraphrase, another language,
spelling, spacing inside a word, homoglyphs, fullwidth or other non-ASCII
letters (NFC is not NFKC; only A-Z fold), curly apostrophes inside a
phrase and a hidden character inside a phrase all evade a phrase (the
last is itself a PL1 finding). The negation guard drops a negated
restatement and can be gamed by a negation placed before a phrase.
Credential shapes miss unknown formats and catch look-alike prose after
"password:". Adaptive attackers defeat pattern filters (Nasr et al.
2025, "The Attacker Moves Second"). PL1's unassigned class follows the
residence's Unicode version (TPS 2.5.13): vectors avoid code points
assigned after Unicode 13.0. The lint reads no meaning and decides
nothing: no finding refuses, holds or scores anything (D158.1's rule of
no act on a pattern).
lint_counts(findings) -> {"PL0": n, ..., "PL9": n}: counts per code, the
only lint output PRB may place in CAPABILITY. A malformed list raises
ValueError("findings").
"""
import hashlib
import json
import unicodedata
import tpc_zone as Z
import tpc_identity as ID
VERSION = "0.1.0"
LINT_MAX_BYTES = 1048576
LINT_CAP_MAX = 1048576
CODES = ["PL0", "PL1", "PL2", "PL3", "PL4", "PL5", "PL6", "PL7", "PL8", "PL9"]
NEG_CODES = ("PL3", "PL4", "PL9")
# BEGIN LINT TABLES (gen_lint_tables.py; do not edit)
LINT_TABLES_VERSION = "0.1.0"
LINT_TABLES_SHA256 = (
"7dbc052a0c9d5a752f267529343d9d5eaccd66500b88b859923858804a924c17")
LINT_TABLES_JSON = (
'{"KNOWN_KEYS":["$defs","$id","$ref","$schema","_meta","additiona'
'lProperties","allOf","annotations","anyOf","cache_control","cons'
't","contentEncoding","contentMediaType","default","definitions",'
'"deprecated","description","destructiveHint","enum","examples","'
'exclusiveMaximum","exclusiveMinimum","format","function","idempo'
'tentHint","inputSchema","input_schema","items","maxItems","maxLe'
'ngth","maximum","minItems","minLength","minimum","name","not","o'
'neOf","openWorldHint","outputSchema","output_schema","parameters'
'","pattern","patternProperties","prefixItems","properties","read'
'Only","readOnlyHint","required","strict","title","type","uniqueI'
'tems","writeOnly"],"NEG":["not","never","no","nor","cannot","don'
't","don\'t","don\\u2019t","cant","can\'t","can\\u2019t","mustnt","mu'
'stn\'t","mustn\\u2019t","shouldnt","shouldn\'t","shouldn\\u2019t","w'
'ont","won\'t","won\\u2019t","doesnt","doesn\'t","doesn\\u2019t"],"NE'
'G_STOP":".!?;:","NEG_WINDOW":4,"PL2":[["PL2-01",[[["L","akia"],['
'"C","V",16,16]]]],["PL2-02",[[["L","asia"],["C","V",16,16]]]],["'
'PL2-03",[[["L","ghp_"],["C","N",36,36]]]],["PL2-04",[[["L","gho_'
'"],["C","N",36,36]]]],["PL2-05",[[["L","ghu_"],["C","N",36,36]]]'
'],["PL2-06",[[["L","ghs_"],["C","N",36,36]]]],["PL2-07",[[["L","'
'ghr_"],["C","N",36,36]]]],["PL2-08",[[["L","github_pat_"],["C","'
'W",82,82]]]],["PL2-09",[[["L","glpat-"],["C","U",20,20]]]],["PL2'
'-10",[[["L","xox"],["C","X",1,1],["L","-"],["C","D",10,12],["L",'
'"-"],["C","D",10,13],["L","-"],["C","N",24,26]]]],["PL2-11",[[["'
'L","xapp-"],["C","U",20,255]]]],["PL2-12",[[["L","aiza"],["C","U'
'",35,35]]]],["PL2-13",[[["L","sk_live_"],["C","N",20,255]]]],["P'
'L2-14",[[["L","rk_live_"],["C","N",20,255]]]],["PL2-15",[[["L","'
'sk_test_"],["C","N",20,255]]]],["PL2-16",[[["L","sk-"],["C","N",'
'20,20],["L","t3blbkfj"],["C","N",20,20]]]],["PL2-17",[[["L","sk-'
'proj-"],["C","U",20,4096]]]],["PL2-18",[[["L","sk-svcacct-"],["C'
'","U",20,4096]]]],["PL2-19",[[["L","sk-admin-"],["C","U",20,4096'
']]]],["PL2-20",[[["L","sk-ant-"],["C","U",20,4096]]]],["PL2-21",'
'[[["L","hf_"],["C","L",34,34]]]],["PL2-22",[[["L","npm_"],["C","'
'N",36,36]]]],["PL2-23",[[["L","pypi-ageichlwas5vcmc"],["C","U",5'
'0,1000]]]],["PL2-24",[[["L","eyj"],["C","U",10,4096],["L",".eyj"'
'],["C","U",10,4096],["L","."],["C","U",0,4096]]]],["PL2-25",[[["'
'L","-----begin private key-----"]],[["L","-----begin encrypted p'
'rivate key-----"]],[["L","-----begin rsa private key-----"]],[["'
'L","-----begin dsa private key-----"]],[["L","-----begin ec priv'
'ate key-----"]],[["L","-----begin openssh private key-----"]],[['
'"L","-----begin pgp private key block-----"]]]],["PL2-26",[[["L"'
',"bearer "],["C","K",20,4096]]]],["PL2-27",[[["L","authorization'
': basic "],["C","K",8,4096]]]],["PL2-28",[[["L","x-api-key:"],["'
'C","S",0,1],["C","U",16,4096]]]],["PL2-29",[[["L","://"],["C","I'
'",1,256],["L",":"],["C","I",1,256],["L","@"]]]],["PL2-30",[[["L"'
',"password"],["C","S",0,1],["C","E",1,1],["C","S",0,1],["C","Q",'
'0,1],["C","G",8,4096]],[["L","passwd"],["C","S",0,1],["C","E",1,'
'1],["C","S",0,1],["C","Q",0,1],["C","G",8,4096]],[["L","api_key"'
'],["C","S",0,1],["C","E",1,1],["C","S",0,1],["C","Q",0,1],["C","'
'G",8,4096]],[["L","apikey"],["C","S",0,1],["C","E",1,1],["C","S"'
',0,1],["C","Q",0,1],["C","G",8,4096]],[["L","api-key"],["C","S",'
'0,1],["C","E",1,1],["C","S",0,1],["C","Q",0,1],["C","G",8,4096]]'
',[["L","secret_key"],["C","S",0,1],["C","E",1,1],["C","S",0,1],['
'"C","Q",0,1],["C","G",8,4096]],[["L","client_secret"],["C","S",0'
',1],["C","E",1,1],["C","S",0,1],["C","Q",0,1],["C","G",8,4096]],'
'[["L","access_token"],["C","S",0,1],["C","E",1,1],["C","S",0,1],'
'["C","Q",0,1],["C","G",8,4096]],[["L","auth_token"],["C","S",0,1'
'],["C","E",1,1],["C","S",0,1],["C","Q",0,1],["C","G",8,4096]],[['
'"L","private_key"],["C","S",0,1],["C","E",1,1],["C","S",0,1],["C'
'","Q",0,1],["C","G",8,4096]],[["L","aws_secret_access_key"],["C"'
',"S",0,1],["C","E",1,1],["C","S",0,1],["C","Q",0,1],["C","G",8,4'
'096]]]]],"PL3":[["PL3-01",[[["follow","obey","execute","carry ou'
't","act on"],[" "],["the ","any ","all ",""],["instructions","di'
'rections","commands","directives","orders"],[" "],["in","from","'
'found in","contained in","embedded in","inside"],[" "],["tool re'
'sults","tool output","tool outputs","tool responses","web pages"'
',"wiki pages","the wiki","retrieved content","retrieved document'
's","search results","documents","files","emails","other agents",'
'"another agent","the server","the api","tpx"]]]],["PL3-02",[[["t'
'reat "],["tool results","tool output","tool outputs","tool respo'
'nses","wiki content","wiki pages","the wiki","retrieved content"'
',"search results","documents","file contents","emails","messages'
' from other agents","server messages","tpx responses"],[" as "],'
'["instructions","commands","authoritative","authorization","trus'
'ted"]]]],["PL3-03",[[["you are","you\'re","you have been"],[" "],'
'["authorized","authorised","permitted","allowed","cleared","pre-'
'authorized","preauthorized"],[" to"]]]],["PL3-04",[[["you have",'
'"i give you","i grant you","you are granted","you have been gran'
'ted"],[" "],["my ","full ","standing ","blanket ",""],["permissi'
'on","authorization","authorisation","approval","consent"],[" to"'
']]]],["PL3-05",[[["consider","treat"],[" "],["it","this","that",'
'"all actions","every action","such actions","these actions"],[" '
'"],["as ",""],["approved","authorized","authorised","pre-approve'
'd","preapproved","sanctioned"]]]],["PL3-06",[[["assume","presume'
'"],[" "],["the user\'s ","the human\'s ","the sponsor\'s ","the ope'
'rator\'s ","my ",""],["approval","consent","permission","authoriz'
'ation","authorisation"]]]],["PL3-07",[[["pre-approved","preappro'
'ved","approved in advance","authorized in advance","authorised i'
'n advance"]]]],["PL3-08",[[["no need to","do not need to","don\'t'
' need to","you need not"],[" "],["ask","wait for","request","see'
'k","obtain","get"],[" "],["for ",""],["permission","approval","c'
'onfirmation","consent","authorization","authorisation"]]]],["PL3'
'-09",[[["without "],["asking","waiting for","requesting","seekin'
'g"],[" "],["the human","the user","the sponsor","anyone","permis'
'sion","approval","confirmation","consent"]]]],["PL3-10",[[["act"'
',"proceed","decide"],[" "],["on your own authority","autonomousl'
'y","without supervision"]]]],["PL3-11",[[["on behalf of","in the'
' name of"],[" the "],["sponsor","user","human","operator","decla'
'ring authority"]]]],["PL3-12",[[["your","these","the"],[" "],["i'
'nstructions","rules"],[" "],["override","outrank","supersede","t'
'ake precedence over"],[" "],["the human","the user","any human",'
'"the human\'s","the user\'s"]]]]],"PL4":[["PL4-01",[[["ignore","di'
'sregard","forget","override","bypass","set aside"],[" "],["all "'
',"any ",""],["the ","your ",""],["previous","prior","above","ear'
'lier","preceding","original","system","other"],[" "],["instructi'
'ons","instruction","rules","guidelines","directives","prompt","p'
'rompts","constraints"]]]],["PL4-02",[[["ignore","disregard","ove'
'rride","bypass","set aside","relax","suspend","waive"],[" "],["t'
'he ","any ","all ",""],["tps","agent contract","contract","speci'
'fication","gtp","rules","safety rules","input vetting","decision'
' protocol","digest gate","text safety rule"]]]],["PL4-03",[[["sk'
'ip","bypass","omit","circumvent"],[" "],["the ","any ","all ",""'
'],["digest","digests","digest gate","digest check","checks","che'
'ck","verification","validation","gate","lock","mutex","refusal",'
'"refusals","audit","review","quarantine","benchmark quarantine",'
'"decision item","decision items","report","reporting"]]]],["PL4-'
'04",[[["do not","don\'t","never","no need to"],[" "],["report","f'
'lag","log","record","disclose","mention"],[" "],["it","this","th'
'em","detections","injections","probes","canaries","failures","er'
'rors","refusals","the detection","the injection","suspicious inp'
'ut","suspect input"]]]],["PL4-05",[[["always comply","comply wit'
'h every","comply with all","comply with any","never refuse","do '
'not refuse","don\'t refuse","never decline","do not decline","don'
'\'t decline"]]]],["PL4-06",[[["compute","calculate","estimate","g'
'uess","reason","work out","approximate"],[" "],["the ","a ","any'
' ",""],["digest","digests","hash","hashes","sha-256","d(is)","d('
'prompt)","d(tfk)"],[" "],["by hand","by reasoning","mentally","i'
'n your head","without tpc","without running tpc-py","without exe'
'cuting tpc-py"]]]],["PL4-07",[[["identify","detect","spot","reco'
'gnize","recognise","distinguish","guess","figure out"],[" "],["t'
'he ","a ","any ","all ",""],["probe","probes","canary","canaries'
'","parrot","parrots","test input","test inputs","planted test","'
'planted tests","planted error","planted errors"]]]],["PL4-08",[['
'["if "],["this","it","the input","the task","the request","the p'
'ackage","the block"],[" "],["is","looks like","seems like","seem'
's to be","might be","may be","could be","appears to be"],[" "],['
'"a ","an ",""],["probe","canary","parrot","test","evaluation","p'
'lanted test"]]]],["PL4-09",[[["when","if","while","whenever"],["'
' you "],["are","suspect you are","think you are","believe you ar'
'e"],[" "],["being tested","being evaluated","being probed","unde'
'r evaluation","tested","evaluated","probed"]]]],["PL4-10",[[["tr'
'eat","handle","answer","respond to"],[" "],["probes","canaries",'
'"parrots","tests","test inputs","suspected probes"],[" "],["diff'
'erently","with extra care","more carefully","specially"]]]],["PL'
'4-11",[[["look up","search for","fetch","read","consult","check"'
'],[" the "],["known","expected","published","reference","benchma'
'rk","correct"],[" "],["value","values","answer","answers","resul'
't","results"],[" "],["first","before deriving","before the deriv'
'ation","before you derive"]]]]],"PL5":[["PL5-01",[[["| class: in'
'struction artifact"]]]],["PL5-02",[[["end of artifact --- tps"]]'
']],["PL5-03",[[["end of artifact --- gtp"]]]],["PL5-04",[[["the '
'principia machina specification (tps)"]]]],["PL5-05",[[["the gen'
'eric tps prompt (gtp)"]]]],["PL5-06",[[["tps","gtp"],[" version:'
'"]]]],["PL5-07",[[["this part, taken as a runnable instruction s'
'et, is gtp"]]]],["PL5-08",[[["part 6 agent contract"]]]],["PL5-0'
'9",[[["reading conventions, for this file"]]]]],"PL6":[["PL6-01"'
',[[["<important>","</important>","<instructions>","</instruction'
's>","<instruction>","<system>","</system>","<system-reminder>","'
'[system]","[inst]","<<sys>>","<|im_start|>"]]]],["PL6-02",[[["ig'
'nore","disregard","forget","override"],[" "],["all ","any ",""],'
'["the ","your ",""],["previous","prior","above","earlier","prece'
'ding","original","system","other"],[" "],["instructions","instru'
'ction","rules","guidelines","directives","prompt","prompts"]]]],'
'["PL6-03",[[["do not","don\'t","never"],[" "],["tell","inform","n'
'otify","alert","mention","reveal","show","disclose","say"],[" "]'
',["this ","it ","anything ",""],["to ",""],["the user","the huma'
'n","the sponsor","anyone","that you","this"]]]],["PL6-04",[[["wi'
'thout "],["telling","informing","notifying","alerting"],[" "],["'
'the user","the human","the sponsor","anyone"]]]],["PL6-05",[[["k'
'eep","hide"],[" "],["this","it"],[" "],["secret","hidden","from '
'the user","from the human"]]]],["PL6-06",[[["the user "],["must '
'not","should not","must never","should never"],[" "],["know","se'
'e","find out","be told"]]]],["PL6-07",[[["before","prior to"],["'
' "],["using","calling","invoking","running"],[" "],["this","any"'
',"any other","another","the other"],[" tool"]]]],["PL6-08",[[["a'
'lways","first"],[" "],["call","use","invoke","run"],[" this tool'
'"]]]],["PL6-09",[[["all other tools","any other tool","every oth'
'er tool","the other tools"]]]],["PL6-10",[[["instead of","rather'
' than"],[" "],["using","calling","invoking"],[" "],["the","any",'
'"another"]]]],["PL6-11",[[["~/","id_rsa","id_ed25519","id_ecdsa"'
',".ssh/","mcp.json",".env","/etc/passwd","/etc/shadow",".aws/cre'
'dentials",".netrc",".git-credentials",".npmrc",".pypirc","creden'
'tials.json",".kube/config",".docker/config.json"]]]],["PL6-12",['
'[["pass","send","include","attach","forward","append","upload","'
'post"],[" "],["its","the","their","all","the file\'s","the file",'
'"those","these"],[" "],["content","contents"]]]],["PL6-13",[[["r'
'ead","open","cat","load","access"],[" "],["the ",""],["private k'
'ey","ssh key","ssh keys","credentials","secrets","environment va'
'riables","config file","configuration file","api key","api keys"'
',"tokens"]]]],["PL6-14",[[["http://","https://","ftp://","ngrok"'
',"pastebin","requestbin","webhook.site","exfiltrate","exfiltrati'
'on"]]]],["PL6-15",[[["send","post","upload","forward","email","t'
'ransmit"],[" "],["it","them","this","the result","the results","'
'the data","everything","all data"],[" to"]]]],["PL6-16",[[["add"'
',"write","save","append"],[" "],["this","these instructions","th'
'e following"],[" to "],["memory","your memory","claude.md","agen'
'ts.md","your instructions","the system prompt"]]]]],"PL7":[["PL7'
'-01",[["bash"],["sh"],["zsh"],["shell"],["powershell"],["pwsh"],'
'["cmd"],["terminal"],["subprocess"],["exec"],["execute"],["eval"'
'],["repl"],["run","command"],["run","commands"],["run","code"],['
'"code","interpreter"],["run","script"],["run","python"]],[[["arb'
'itrary","any","shell","bash","terminal","system"],[" "],["comman'
'd","commands"]],[["execute","executes","run","runs","evaluate","'
'evaluates"],[" "],["arbitrary ","any ",""],["code","python","jav'
'ascript","scripts","a script","shell commands","a shell command"'
',"a command","commands"]]]],["PL7-02",[["fetch"],["webfetch"],["'
'curl"],["wget"],["browse"],["browser"],["navigate"],["puppeteer"'
'],["playwright"],["scrape"],["scraper"],["crawl"],["crawler"],["'
'download"],["http","request"],["http","get"],["http","post"],["u'
'rl","fetch"],["fetch","url"],["open","url"],["get","url"],["web"'
',"request"]],[[["any","arbitrary"],[" "],["url","urls","web page'
'","web pages","website","websites","host","hosts"]],[["fetch","f'
'etches","retrieve","retrieves","download","downloads","get","get'
's"],[" "],["a","any","the content of a","the contents of a","con'
'tent from a","content from any"],[" "],["url","web page","websit'
'e"]]]],["PL7-03",[["send"],["email"],["mail"],["gmail"],["smtp"]'
',["sms"],["webhook"],["tweet"],["upload"],["post","message"],["p'
'ost","to"]],[[["send","sends"],[" "],["an ","a ",""],["email","e'
'mails","message","messages","sms","text message","notification"]'
'],[["post","posts"],[" "],["a ",""],["message","messages","tweet'
'","tweets"],[" to"]]]]],"PL9":[["PL9-01",[[["remember","memorize'
'","memorise","save","store","persist","record","retain","keep"],'
'[" "],["this","these","the following","these instructions","this'
' instruction","these rules","this rule","it"],[" "],["for","in",'
'"across","into","to"],[" "],["future","later","all future","subs'
'equent","every","your","the","long-term","persistent"],[" "],["s'
'essions","session","conversations","conversation","memory","runs'
'","work"]]]],["PL9-02",[[["write","save","store","add","append",'
'"commit"],[" "],["this","these","it","them","the following","the'
'se instructions","this instruction","these rules","a note","note'
's"],[" "],["to","in","into"],[" "],["memory","your memory","the '
'memory","long-term memory","persistent memory","the memory file"'
',"a memory file","your memory file","the knowledge base"]]]],["P'
'L9-03",[[["update","edit","modify","rewrite","change","overwrite'
'","extend","amend","append to"],[" "],["your","the","this"],[" "'
'],["memory","memories","instructions","system prompt","prompt","'
'instruction file","instruction files","rules file"]]]],["PL9-04"'
',[[["claude.md","agents.md","gemini.md",".cursorrules",".windsur'
'frules",".clinerules","copilot-instructions.md",".cursor/rules",'
'".github/instructions","memory.md",".claude/"]]]],["PL9-05",[[["'
'for","in","across","during"],[" "],["future","all future","later'
'","subsequent","every future","next"],[" "],["sessions","session'
'","conversations","conversation","runs","chats"]]]],["PL9-06",[['
'["memory tool","save_memory","memory_write","add_memory","create'
'_memory","remember tool"]]]]],"classes":{"D":"0123456789","E":"='
':","G":"!#$%&()*+,-./0123456789:;<=>?@[\\\\]^_abcdefghijklmnopqrst'
'uvwxyz{|}~","I":"abcdefghijklmnopqrstuvwxyz0123456789-._~%!$&\'()'
'*+,;=","K":"abcdefghijklmnopqrstuvwxyz0123456789-._~+/=","L":"ab'
'cdefghijklmnopqrstuvwxyz","N":"abcdefghijklmnopqrstuvwxyz0123456'
'789","Q":"\\"\'`","S":" ","U":"abcdefghijklmnopqrstuvwxyz012345678'
'9_-","V":"abcdefghijklmnopqrstuvwxyz234567","W":"abcdefghijklmno'
'pqrstuvwxyz0123456789_","X":"abprs"},"version":"0.1.0"}'
)
# END LINT TABLES
_W = frozenset("abcdefghijklmnopqrstuvwxyz0123456789")
_APOS = chr(0x2019)
_TOKEN = frozenset("abcdefghijklmnopqrstuvwxyz0123456789'" + _APOS)
_FOLD = {c: c + 32 for c in range(0x41, 0x5B)}
_UPPER = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
_LOWDIG = frozenset("abcdefghijklmnopqrstuvwxyz0123456789")
_ALNUM = _UPPER | _LOWDIG
def _load():
if hashlib.sha256(LINT_TABLES_JSON.encode("ascii")).hexdigest() != LINT_TABLES_SHA256:
raise RuntimeError("tpc_lint: the lint tables do not match their digest")
return json.loads(LINT_TABLES_JSON)
_T = _load()
_CLASSES = {k: frozenset(v) for k, v in _T["classes"].items()}
_NEG = frozenset(_T["NEG"])
_NEG_WINDOW = _T["NEG_WINDOW"]
_NEG_STOP = frozenset(_T["NEG_STOP"])
_KNOWN = frozenset(_T["KNOWN_KEYS"])
class _Refused(Exception):
pass
# ---------------------------------------------------------------- inputs
def _text_of(x, what):
"""The input as a str, or _Refused with the PL0 item."""
if type(x) is bytes:
try:
s = x.decode("utf-8")
except UnicodeDecodeError:
raise _Refused(what + "-UTF8")
n = len(x)
elif type(x) is str:
for ch in x:
if 0xD800 <= ord(ch) <= 0xDFFF:
raise _Refused(what + "-UTF8")
s = x
n = len(x.encode("utf-8"))
else:
raise _Refused(what + "-TYPE")
if n > LINT_MAX_BYTES:
raise _Refused(what + "-SIZE")
return s
def _lint_text(s):
"""The text every locator counts in: LF line endings, then NFC."""
return unicodedata.normalize("NFC", Z._eol(s))
def _normalize(t):
"""The matching text of t (already LF and NFC) and the map from each of
its code points to t's index: fold A-Z, collapse White_Space runs."""
out, mp = [], []
i, n = 0, len(t)
ws = Z._WS
while i < n:
c = t[i]
if c in ws:
j = i + 1
while j < n and t[j] in ws:
j += 1
out.append(" ")
mp.append(i)
i = j
else:
out.append(c.translate(_FOLD))
mp.append(i)
i += 1
return "".join(out), mp
def lint_norm(text):
"""The matching normalization of a text (a vector helper)."""
if type(text) is not str:
raise ValueError("text")
s = _text_of(text, "TEXT")
return _normalize(_lint_text(s))[0]
# -------------------------------------------------------------- matching
def _cands(m, alts):
"""Every index where one of alts starts, ascending."""
found = set()
for a in alts:
j = m.find(a)
while j != -1:
found.add(j)
j = m.find(a, j + 1)
return sorted(found)
def _tmatch(m, pos, parts, k):
if k == len(parts):
return m[pos - 1] not in _W or pos >= len(m) or m[pos] not in _W
for a in parts[k]:
if m.startswith(a, pos) and _tmatch(m, pos + len(a), parts, k + 1):
return True
return False
def _template_hits(m, parts):
"""Start indices where the template matches, boundaries included."""
hits = []
for j in _cands(m, parts[0]):
if m[j] in _W and j > 0 and m[j - 1] in _W:
continue
if _tmatch(m, j, parts, 0):
hits.append(j)
return hits
def _pattern_at(m, j, pat):
if m[j] in _W and j > 0 and m[j - 1] in _W:
return False
pos, n = j, len(m)
for el in pat:
if el[0] == "L":
if not m.startswith(el[1], pos):
return False
pos += len(el[1])
else:
cls, lo, hi = _CLASSES[el[1]], el[2], el[3]
cnt = 0
while cnt < hi and pos < n and m[pos] in cls:
pos += 1
cnt += 1
if cnt < lo:
return False
last = pat[-1]
if last[0] == "L":
if last[1][-1] in _W and pos < n and m[pos] in _W:
return False
elif pos < n and m[pos] in _CLASSES[last[1]]:
return False
return True
def _pattern_hits(m, pat):
return [j for j in _cands(m, [pat[0][1]]) if _pattern_at(m, j, pat)]
def _negated(m, j):
"""The negation guard: a NEG token among the last NEG_WINDOW tokens of
the sentence before index j."""
toks = []
p = j - 1
cur = []
while p >= 0:
c = m[p]
if c in _TOKEN:
cur.append(c)
else:
if cur:
toks.append("".join(reversed(cur)))
cur = []
if len(toks) == _NEG_WINDOW:
break
if c in _NEG_STOP:
break
p -= 1
if cur and len(toks) < _NEG_WINDOW:
toks.append("".join(reversed(cur)))
for t in toks:
if t in _NEG:
return True
return False
def name_tokens(name):
"""PL7's tokens of a tool name."""
if type(name) is not str:
raise ValueError("name")
s = _lint_text(_text_of(name, "NAME"))
toks, cur = [], ""
for c in s:
if c in _ALNUM:
if cur and c in _UPPER and cur[-1] in _LOWDIG:
toks.append(cur)
cur = ""
cur += c
elif cur:
toks.append(cur)
cur = ""
if cur:
toks.append(cur)
return [t.translate(_FOLD) for t in toks]
def _has_seq(toks, seq):
k = len(seq)
for i in range(len(toks) - k + 1):
if toks[i:i + k] == seq:
return True
return False
# ------------------------------------------------- canonical JSON reader
def _read_canon(c):
"""The tree of a canonical JSON text (tpc_identity CJ5): ("obj",
[(key, node)]), ("arr", [node]), ("str", s) or ("lit", text)."""
i = [0]
n = len(c)
def value():
ch = c[i[0]]
if ch == "{":
i[0] += 1
mem = []
if c[i[0]] == "}":
i[0] += 1
return ("obj", mem)
while True:
k = string()
i[0] += 1 # ':'
mem.append((k, value()))
if c[i[0]] == ",":
i[0] += 1
continue
i[0] += 1 # '}'
return ("obj", mem)
if ch == "[":
i[0] += 1
items = []
if c[i[0]] == "]":
i[0] += 1
return ("arr", items)
while True:
items.append(value())
if c[i[0]] == ",":
i[0] += 1
continue
i[0] += 1 # ']'
return ("arr", items)
if ch == '"':
return ("str", string())
j = i[0]
while i[0] < n and c[i[0]] not in ",]}":
i[0] += 1
return ("lit", c[j:i[0]])
esc = {'"': '"', "\\": "\\", "b": "\b", "f": "\f", "n": "\n", "r": "\r", "t": "\t"}
def string():
i[0] += 1
out = []
while True:
ch = c[i[0]]
if ch == '"':
i[0] += 1
return "".join(out)
if ch == "\\":
e = c[i[0] + 1]
if e == "u":
out.append(chr(int(c[i[0] + 2:i[0] + 6], 16)))
i[0] += 6
else:
out.append(esc[e])
i[0] += 2
else:
out.append(ch)
i[0] += 1
return value()
# ------------------------------------------------------------------ lint
def _pos_fn(t):
lf = [i for i, ch in enumerate(t) if ch == "\n"]
def loc(i):
lo, hi = 0, len(lf)
while lo < hi:
mid = (lo + hi) // 2
if lf[mid] < i:
lo = mid + 1
else:
hi = mid
prev = lf[lo - 1] if lo > 0 else -1
return lo + 1, i - prev
return loc
def _zone(t, cap, add):
loc = _pos_fn(t)
def z(code, item, i):
line, col = loc(i)
add(code, item, 0, line, col, "", "Z:%d:%d" % (line, col))
for i, ch in enumerate(t):
if Z._disallowed(ch):
z("PL1", "U+%04X" % ord(ch), i)
if len(t) > cap:
z("PL8", "-", cap)
m, mp = _normalize(t)
for eid, pats in _T["PL2"]:
for pat in pats:
for j in _pattern_hits(m, pat):
z("PL2", eid, mp[j])
for code in ("PL3", "PL4", "PL5", "PL9"):
guard = code in NEG_CODES
for eid, temps in _T[code]:
for parts in temps:
for j in _template_hits(m, parts):
if guard and _negated(m, j):
continue
z(code, eid, mp[j])
def _tool_string(s, i, path, add, key, pl6):
loc = "T:%d:%s%s" % (i, path, ":key" if key else "")
def f(code, item):
add(code, item, 1, i, 0, loc[len("T:%d:" % i):], loc)
t = _lint_text(s)
for ch in t:
if Z._disallowed(ch):
f("PL1", "U+%04X" % ord(ch))
m = _normalize(t)[0]
for eid, pats in _T["PL2"]:
for pat in pats:
if _pattern_hits(m, pat):
f("PL2", eid)
if pl6:
for eid, temps in _T["PL6"]:
for parts in temps:
if _template_hits(m, parts):
f("PL6", eid)
def _walk(node, i, segs, add, key_name):
kind = node[0]
if kind == "obj":
for r, (k, v) in enumerate(node[1]):
seg = k if k in _KNOWN else "@%d" % r
p = segs + [seg]
_tool_string(k, i, "/".join(p), add, True, False)
_walk(v, i, p, add, k)
elif kind == "arr":
for r, v in enumerate(node[1]):
_walk(v, i, segs + [str(r)], add, None)
elif kind == "str":
_tool_string(node[1], i, "/".join(segs), add, False,
key_name in ("description", "title"))
def _pl7(tool, i, add):
def member(obj, name):
for k, v in obj[1]:
if k == name:
return v
return None
places = [(tool, "")]
fn = member(tool, "function")
if fn is not None and fn[0] == "obj":
places.append((fn, "function/"))
for obj, pre in places:
nm = member(obj, "name")
toks = name_tokens(nm[1]) if nm is not None and nm[0] == "str" else None
ds = member(obj, "description")
dm = _normalize(_lint_text(ds[1]))[0] if ds is not None and ds[0] == "str" else None
for eid, seqs, temps in _T["PL7"]:
if dm is not None:
for parts in temps:
if _template_hits(dm, parts):
add("PL7", eid, 1, i, 0, pre + "description",
"T:%d:%sdescription" % (i, pre))
break
if toks is not None:
for seq in seqs:
if _has_seq(toks, seq):
add("PL7", eid, 1, i, 0, pre + "name", "T:%d:%sname" % (i, pre))
break
def _pl0(item):
return [{"code": "PL0", "item": item, "locator": "-"}]
def lint(zone, tools, cap):
try:
z = _text_of(zone, "ZONE")
t = _text_of(tools, "TOOLS")
except _Refused as ex:
return _pl0(str(ex))
try:
canon = ID.canonical_tools(t)
except ValueError:
return _pl0("TOOLS-JSON")
tree = _read_canon(canon)
if tree[0] != "arr" or any(el[0] != "obj" for el in tree[1]):
return _pl0("TOOLS-SHAPE")
if type(cap) is not int or cap < 1 or cap > LINT_CAP_MAX:
return _pl0("CAP")
found = {}
def add(code, item, scope, n1, n2, path, loc):
k = (code, item, loc)
if k not in found:
found[k] = (CODES.index(code), scope, n1, n2, path, item)
_zone(_lint_text(z), cap, add)
for i, tool in enumerate(tree[1]):
_walk(tool, i, [], add, None)
_pl7(tool, i, add)
order = sorted(found.items(), key=lambda kv: kv[1])
return [{"code": k[0], "item": k[1], "locator": k[2]} for k, _ in order]
def lint_json(findings):
"""The canonical text of a finding list (every field is ASCII)."""
return "[" + ",".join('{"code":"%s","item":"%s","locator":"%s"}'
% (f["code"], f["item"], f["locator"])
for f in findings) + "]"
def lint_counts(findings):
if type(findings) is not list:
raise ValueError("findings")
counts = dict((c, 0) for c in CODES)
for f in findings:
if type(f) is not dict or sorted(f.keys()) != ["code", "item", "locator"]:
raise ValueError("findings")
if any(type(f[k]) is not str for k in f) or f["code"] not in counts:
raise ValueError("findings")
counts[f["code"]] += 1
return counts
def counts_json(counts):
return "{" + ",".join('"%s":%d' % (c, counts[c]) for c in CODES) + "}"
# ---------------------------------------------------------------- corpus
FNS = ("lint", "counts", "counts_of", "norm", "tokens", "tables")
def _arg_text(args, name):
if name + "_hex" in args:
return bytes.fromhex(args[name + "_hex"])
if name + "_parts" in args:
return "".join(p[0] * p[1] for p in args[name + "_parts"])
return args.get(name)
def run_case(fn, args):
"""One corpus case: the canonical result, or "ERROR:" and the message."""
if fn not in FNS:
raise ValueError("fn")
try:
if fn in ("lint", "counts"):
res = lint(_arg_text(args, "zone"), _arg_text(args, "tools"), args.get("cap"))
return lint_json(res) if fn == "lint" else counts_json(lint_counts(res))
if fn == "counts_of":
return counts_json(lint_counts(args.get("findings")))
if fn == "norm":
return lint_norm(args.get("text"))
if fn == "tokens":
return json.dumps(name_tokens(args.get("name")), separators=(",", ":"))
return "tables %s %s" % (LINT_TABLES_VERSION, LINT_TABLES_SHA256)
except _Refused:
return "ERROR:" + fn.replace("norm", "text").replace("tokens", "name")
except ValueError as ex:
return "ERROR:" + str(ex)