Repository navigation
Releases
v0.5.0
Added
- the narrative skills that did not exist —
linkedin,x, and one shared
two-axis statement every platform skill cites (#462, part B) — the two
channels that shippedpost.md/posting.yamlwith noSKILL.mdare exactly
the two whose narrative a session overwrote “for consistency”. They now carry
one — reader, angle, how it deliberately differs fromgithub-readme, the whole
channel flow, and contract pointers (every limit cited fromposting.yaml,
never re-typed). The two-axis rule (skills/FACTS-VS-NARRATIVE.md) has ONE
home; every platform skill links it, and no skill's own golden rule may
contradict it (order is narrative, not a fact), so the norm cannot drift
between skills;skills/README.mdrecords thatSKILL.mdis required.pasco refine's task template names the target platform's skill as
the source of its reader/angle and states that a variant must not restate
another platform's headline, bio or body. - «facts are unique · narrative is per surface» — the norm and its advisory
check (#462, SPEC §21.8) — the rule lived only in one platform skill (as a
golden rule) and as a product goal, so a workspace could unify one headline
across its GitHub README, LinkedIn and X “for consistency” andpasco check
stayed green. SPEC §21.8 now states it normatively: every FACT in
content/*.yaml(includingidentity.definition) is one and identical across
surfaces and a contradiction is a defect, not an adaptation, while the
per-platformheadlines/biosrows are the narrative text; narrative
(reader, emphasis, order, subset viaselection.views, composition via the
variant body) is per surface and is never propagated.pasco checkgains the advisory
text/same-across-platforms— a warn naming both platforms and the text
kind when two different platforms carry a byte-identicalheadlineor abio
in the samelengthband; exempt: the same platform across langs and
identity.definition. No schema change, no hard gate. The skills half
(linkedin/xSKILL.md+ the shared two-axis statement) lands in the
chained PR. - per-view
selection.views.<v>.orderKeys— the narrative order the featured
sort was missing (#458, SPEC §21.2/§21.3) —github-readme's featured list is
fully derived (statusrank → skill weight → name), so a workspace that wanted
"these two first" for one surface had only false claims (changestatus) or
invisible tiebreak tuning (changeskills[].keywords); the measured case kept
the order as hand-edited text, outside the engine. A view now declares
orderKeysby §19.2 key: the listed entries render first, in declared order,
per block; the remaining entries keep the renderer's comparator. It is
workspace policy, never an SSOT field — order is narrative and two surfaces may
legitimately disagree. The pinning rule lives once in core (pinnedRanks),
used by the slice and by the sorting adapters throughorderedForSurface;
pasco status --selectionprints the resolved order. A non-string key is a
config error in.pascorcand a warn at the pure core seam; a key the view does
not emit warns (selection/order-key-unknown) instead of no-op'ing silently.
NoorderKeys⇒ byte-identical artifact and report. doctor --contract-delta— the upgrade names the fields, not only the
versions (#439, SPEC §7.6) —doctorreportedengine 0.4.0 · schema v1.16 · requires engine >= 0.4.0 (met)and never WHAT changed between the two: a real
0.3.0 → 0.3.7 upgrade (contract v1.9 → v1.15) moved six adoptable fields, and
finding them took a supervision session across the CHANGELOG, the schema
descriptionand the geo report.pasco initnow writes a"contract"floor
into.pascorcat birth — beside the #434"engine"floor, from the same
generatedPROFILE_SCHEMA_VERSION, never a literal — anddoctor --contract-deltadiffs that floor against the installed contract: one entry per
minor with the fields it added and the surface that reads them, in five states
(delta·none·absentwith the remedy ·INVALID·ahead).--json
addsdata.contractDeltaonly when the flag is passed, rows of exactly
{ version, fields, buys }. The exit matrix is unchanged: the floor DECLARES
and is never enforced (v1), sodoctorreports without inventing a verdict the
gate does not back. The rows are a projection of the schema's own narration,
pinned in both directions byconsumer-contract.test.ts— a schema bump that
forgets a row, an invented minor, or a row that omits a field its minor narrates
failspnpm test; anotFieldslabel is a review judgement, as SPEC §7.6
states.
Fixed
orderKeysfollow-ups from the #458 dogfood — the pure core seam now warns
(selection/order-key-unknown) whenorderKeysis a non-array value
instead of ignoring it silently;.pascorcalready rejected it as a config
error, so only directselectFacts/compilecallers see the new warning. The
active-variant interaction the dogfood measured is now reported by the writer
and documented: a variant body is a complete artifact written verbatim, so
orderKeysorders the render and does not rewrite it. When the active variant
wins and the slice carries a resolved order,buildemits a permanent,
informational note naming the variant and the layer (the body is
authoritative; the order applied to the render) — not a staleness verdict and
not a silent no-op. SPEC §16.2 step 4 owns the mechanical sequence — remove
that platform'sactive.yamlentry (the other platforms stay), build the
ordered render as the base, let the executing agent compose the body per the
platform skill (the engine models no composition, #476), and re-record with
refine --choose <id> --lang <l>— cited from §21.3 rule 11 and §21.6, with an
end-to-end test that runs it and asserts the other platform's choice survives.
Internal
- The core seam goes 0.1.3 → 0.1.4 (#458, #462) — two additions to
@profile-as-code/core's public surface:orderedForSurface(the one
precedence home forselection.views.<v>.orderKeys) and
sameTextAcrossPlatformswith itsTextFinding/TextKindtypes (the
§21.8 advisory detector). The CLI bundles core and is unaffected; the
cohort step skips a version already live on the registry, so an unchanged
0.1.3 would leave the published core describing the previous tree.
v0.4.0
Added
release plan --jsoncarriesorderon every row (#421, SPEC §7.8) — the
human plan prints a 1-based ORDER column, but the machine payload had no
equivalent: a consumer had to count array indices to reproduce it, an implicit
contract it could get wrong. Eachsurfaces[]row now carriesorder
(1..n, the array position), and the human column reads the same field instead
of recomputingindex + 1. Additive: the array order is unchanged and no
consumer breaks.discover --observations— the human reads the surface the machine cannot
(#412, SPEC §7.10) —discoverreads only schema.orgPerson/Organization
JSON-LD, which a prospect's real surfaces do not publish (measured at4560ae1:
LinkedInhttp-999, ResearchGatehttp-403, ORCID/X/Instagram noPerson—
0 of 5 usable), so the audit's classes had no input for the person it sells an
audit to.--observations <file>takes YAML rows read by hand —surface,
field(name·link·experience),value,url,retrieved_at— and
turns each into the SAMESurfaceObservationthe transport produces, so
provisionalProfile, provenance,absent[]anddiscoveryFindingsrun
unchanged. Rows make NO request (theurlis a provenance pointer) and enter
first, so a declared name beats a machine-read one.--jsonis payload v2
(via: fetch|manual; a manual surface'sstatusisnull— no request, and a
fabricated 200 would be a claim). A name declared differently is a
contradictionatidentity.namewith both sources cited: the profile holds
one name, and dropping the second hid the disagreement the audit exists to find.
A malformed row is refused with its number: an impossible date (2026-02-31,
2020-13,2026-02-00), a non-http URL, an unknown key, and a second
experiencerow for the assembly's own (surface, org) key (experienceId, so
ACME/Acmecollide andÉcole/Coledo not) fail with the row. That key
holds ONE row by design, so a second role at one org is refused rather than
dropped, and a surface the transport also reads resolves to the machine row's
key: supply one or the other.achievements[]— a career impact log with metrics, targets and capture-first
evidence (#446, D24, schema v1.16) — the contract recorded what a person DID
(experience[].highlights, free text: no value, unit, baseline, target or link)
and what can be PROVEN (projects[].evidence[], project-scoped and requiring a
publicsource, so an internal outcome had no home), but not what CHANGED — so
the effect was not captured when it happened and could not be reconstructed
later. One ROOT block, never per-rowoutcomes[]: each row states astatement,
is ametric(value/target/baseline/unit/direction) or amilestone,
points at the units it belongs to (refs,block:id, resolved by core) and
declares how the result is known (basis:measured|estimated|reported
|attested) plus a REQUIREDvisibility, so an internal outcome never becomes
public by omission. Atargetwithout avalueis a planned goal, and the
status (planned|measured|achieved|partial) is DERIVED by the one
achievementStatus()function, never stored — a stored status could disagree
with the numbers it describes.loadfails on duplicate ids, a dangling
refs/artifactId/credentialId, akind↔metricmismatch and a
public→private registry leak (§18.5), including throughrefs[].
pasco achievements [--targets] [--json]is the read-only log view (grouped by
the first ref, with the derived status, the formatted metric, thebasisand the
pointer;scopecounts the rows the view returned);pasco auditreports a
publicmeasured/attestedrow with no pointer asunsourced-claim(WARN) and
never as a finding forreported. Additive: a workspace without the block
validates and renders byte-identically —pasco export profileemits
achievements: []for it, the same loaded-shape rule every list root follows,
which is why the root carries nominItems: an explicitly empty array stays
schema-valid, and "declare the block only when you have rows" is an authoring
convention the engine does not enforce.
SPEC §5 D24 + §6 + §7 + §23,schema/README.md,docs/SCHEMA-CONTRACT.mdand
schema/consumer-contract.json(v1.16, unpublished) document the change, and
completenesscountsachievements[].statementin language coverage.pasco editreaches the answer-engine surface — and--createseeds a
missing root (#435) — two addresses join the contract-path grammar with the
same rules as the existing ones (exactly one row,--checkpreview, cap
pre-flight):identity.definition.<lang>and
faq[id=<slug>,lang=<l>].q|.a/faq[id=<slug>].source.faq[]rows are
selected by their stableid(a repeated id is ambiguous and refused, never
index-addressed) andq/aare langMaps, so theirlangselector is
required; an optional value the row does not carry is a 0-row address. The
preview is the field's real published surface:identity.definitionshows the
entity JSON-LD line thegithub-readme/github-orgartifacts embed, and
faq[]shows theFAQPagenodepasco entity --jsonldemits (no
pasco buildartifact carries it). Both are pre-flighted against the
contract's own caps (160 / 90 / 320 chars per value, v1.10 #288): unlike a
platform's soft cap, over one is a schema failure, so the pre-flight warns and
the write is refused by validation. The byte-range splice cannot invent a
root, so--create <root>is a SEPARATE operation: it writes a schema-valid
template for a root the workspace does not declare yet (faq: 3 placeholder
pairs, the schema minimum — never invented facts), refuses an existing root
under any extension case (idempotent, nothing written), takes the same write
lock and validation gate over a temp copy, runs the same cap pre-flight, and
--checkpreviews the seed.--listgainsidentity.definitionandfaq
scopes.--help, the CLI README, the user guide and SPEC §7 document the new
addresses and the seeded root.
Fixed
checkandpreviewname the catalog they actually read (dogfood
2026-11-04) — on a real workspace with nocontent/checks.yaml
(profile-data),checkprinteduser checks (content/checks.yaml, schema-validated catalog)over the four rows ofDEFAULT_CHECKS, and
previewcaptioned the same rowsdeclared limits (content/checks.yaml): two
published surfaces attributing the caps to a file the engine never read — the
provenance class #455 closed in the docs, still live in runtime output.
load()merges everycontent/*.y{a,}mlby root property and reports no
source, so a filename is a claim the engine cannot make (false for an empty
checks.yaml, false for a workspace usingchecks.yml). The rule "the
workspace's catalog, else the package's conservative caps (#240)" now has ONE
home (check-catalog.ts) instead of four copied ternaries, and every surface
derives its wording fromcatalogSource():checksaysworkspace checks (declared in content/, …)orworkspace checks: none declared — the conservative caps shipped with pasco apply (#240);preview's table captions
declared limits (content/)ordeclared limits (caps shipped with pasco, #240). Tests cover the four states — declared, absent, an empty file, and rows
inchecks.yml— and assert that no surface printscontent/checks.yaml.
Values,check --jsonand every exit code are unchanged; the only payload
movement ispreview --json'sbytes(the caption's own length).post --helpnames which channels ship short rules and which are long-form
(#422) —pasco post task <id> --channel devtoexited2naming the file it
wanted, so a reader learned the split by provoking the error. The help and
docs/USER-GUIDE.md§Posts now name the short-rule channels (linkedin,x)
and the long-form set (devto,ghost,hashnode,listmonk,medium,
substack,wordpress); no flag and no runtime discovery. A test reads both
sets fromskills/, so a channel added later fails CI instead of drifting.- The engine floor is declared at birth, and an old engine's failure names the
upgrade (#434) —pasco initnow writes"engine": "<installed CLI version>"
into.pascorc(in the gate'sMAJOR.MINOR.PATCHgrammar), so a scaffolded
workspace can no longer leave the gate off by default; a build whose version
carries a pre-release suffix declares no floor at all (reducing it to the
numeric triple would pin the scaffold to a version@latestmay not publish).
When schema validation fails on anUnrecognized key,pasco validateadds
ONE hint line naming the likely cause (data newer than the engine) and the
remedy (npm i -g @profile-as-code/cli@latest, thenpasco doctor) — as
error.hintin--jsontoo — unless the engine gate already named the
mismatch (installed older than the declared floor). The verdict is unchanged: exit1, same violations.init's
version comes from one shared read (packages/cli/src/version.ts), never a
literal, anddocs/USER-GUIDE.md§3/§8.2.1 now state that the floor is
declared at birth, not by hand. pasco doctornames the engine-requirement state instead of letting
"absent" look met (#436) — the line now readsengine requirement absent — no .pascorc declares onefor a workspace with no declaration, alongsidemet,
NOT METandINVALID, and--jsoncarries the same value as
requirement.state(absent|met|NOT MET|INVALID). The exit code is
unchanged: an absent declaration remains a healthy workspace, but the premise it
actually read is now stated, never implied by a green exit.- **The completeness label names the policy's langs, no...
v0.3.7
Added
-
pasco entityemits theFAQPagenode — the answer surface's consumer
(#360) —--jsonldnow appends the separateFAQPageblock after the entity
block when the workspace declaresfaq[](never an empty node, no invented
@id;faq[].sourcetravels asacceptedAnswer.citation), and--json
exposes the same assembler node asfaqJsonLd(nullwhen undeclared).--bare
stays the entity object alone when there is no FAQPage, and is a JSON array of
the emitted nodes otherwise. The renderers still embedjsonLdonly: the
published-page consumer remains M5 (surface.kind, D23). SPEC §7.2/§15.4
updated, and the emitted block has a committed Code-input fixture for the Rich
Results release step. -
Bilingual landing
/es/(#308) — the project page mirrors the EN landing
in Spanish at/es/, with reciprocalhreflang(en·es·x-default) on
both pages and the same alternates insitemap.xml. The mirror carries no
version claim — a second place to forget on every release — and
version-checknow enforces "claim-free surfaces" from theLANDINGSlist in
site-config.mjs, which is also the one home the sitemap reads. Docs stay EN
(decision #280-B; the roadmap remains the declared ES document);site-checkgates the new page
like every other. -
pasco discover— a provisional Profile from public surfaces (#329) —
pasco auditneeds aProfile; a prospect has none. Discovery reads only what
a surface itself declares (schema.org JSON-LD + its links), attaches per-fact
provenance (surface·url·retrieved·attested: false), listsabsent
fields instead of defaulting them, and runsdriftCheckover the result plus
the two classes discovery owns:dangling-ref(a same-host link that did not
resolve) andunsourced-claim(a numeric claim with no source on the surface).
One row per surface is kept on purpose, so a cross-surface date disagreement
surfaces asdrift/duplicate-experienceinstead of being averaged away.
Transport is §19.5's: closed host allowlist checked before any request
(registry platform hosts +--allow-host), sequential, no retries, no
credentials, redirects refused. It writes nothing, and the provisional shape is
not a schema version (SPEC §7.10). -
Answer-engine surface, consumer half —
structuredData()projects
identity.definition+faq[](#316) — the v1.10 contract shipped in v0.3.6
without an emitter;structuredData()now reads the fields. Person:
fields.description+jsonLd.descriptionfromidentity.definition
([lang]falling toen), emitted only when declared — never invented — and
descriptionjoins the Personconsideredlist, whose denominator is now fixed
at 8 (counted as populated only when declared, so a person declaring the seven
pre-v1.10 fields and no definition moves to 7/8: the declared, honestly-accounted
delta). Brand:definitionwins overmissionin the same description slot —
a definition-less brand keeps the v1.9 mission output byte-identical. The
FAQPageJSON-LD node (StructuredDataReadiness.faqJsonLd) exists iff
rootfaq[]exists — never an empty one — and carries no@id(the engine never
invents an anchor); afaq[].sourcetravels asAnswer.citation. No consumer
embedsfaqJsonLdyet (renderers embedjsonLdonly): the emitted node is the
readiness surface the M5 web renderer will consume. -
pasco preview— see the data without publishing it (#282) — a private
workspace had no way to look at the rendered result; the only options were to
publish it or to read the base texts one by one, and Pages is not the missing
piece (on personal plans the published site is public, so a private fork with
Pages on publishes the data). The new command renders one self-contained HTML
file — inline CSS, no<script>, no external asset, no HTTP server — whose
sections are the surfacespasco buildwould write (#github-readme,
#linkedin, …), each with the check state, plus every surface this language
does not publish (named with its reason, never dropped). The default writes
<workspace>/.pasco/preview/index.htmland opens it;--out <dir>writes and
opens nothing, which is the CI-artifact transport (the workflow itself lives in
the workspace template, not in the engine). One artifact, not a second
implementation: the text isrenderArtifact's decision tree (active variant →
renderer → shared paste source, withbuild's selection gate) and the checks are
the reportpasco checkprints — that report is now abuildCheckReport()
function instead of a private body insiderunCheck, so the two commands cannot
drift. A privacy gate refuses the write (exit 1, and no file is left behind)
on an absolute machine path, a credential shape, an external asset, active
content, or a source that resolves outside the workspace: evidence-root media is
never inlined, a remote image becomes a text note instead of a fetch, and raw
HTML in the data is escaped rather than executed.--now <iso8601>pins the
generated <date>stamp, so the page is byte-identical across runs. Contract in
SPEC §7.9 (command row in §7);--help, the package README and the User Guide
document it, and the privacy gate has its own seeded tests. -
pasco export profile --json— the normalized profile for programmatic
consumers (#348) — the engine exposedbuild <platform>(text slices) and
validate, but
no stable object a consumer could read without re-implementingload()+
normalize(). Both real consumers paid for that copy and drifted: the CV syncer
read 9 of 14 roots (education,languages,credentials,artifacts
hardcoded to[]), dropped an explicitpublications[].urlin favour of a
DOI-derived link, and treated the invariantexperience[].orgas a langMap.
The new command emits
{ schemaVersion, engine, source: { commit, generatedAt }, profile }, where
profileis exactly the normalizedcore.load()object: every root the
workspace declares, plus every list root defaulted to[](faq[]joins
normalizeProfile's defaults, so the shape no longer depends on whether answers
exist); an optional object root (newsletter,podcast,audiences,
active) appears only when declared, because the contract has no valid empty
value for it.artifactRefsstay ids that resolve inprofile.artifacts[],
publications[].urlas declared,deploymentsincluded — never a CLI-local
builder and nevercompile(profile, platform)'s platform slice (§15.4).
schemaVersion("1") is the frozen payload version a consumer refuses if it
does not know (the §7.1/§7.2/§7.3 key and precedent). Deterministic by
construction: no clock is read —generatedAtisnullunless--now <iso8601>pins it verbatim after a real-calendar check — andsource.commitis
the workspace gitHEAD(nulloutside a repository), so two runs over one
workspace are byte-identical and a committed golden fixture asserts the exact
bytes. Contract in SPEC §7.5 (command row in §7); the--helpoutput and the
package README document it. Separately, criterion 3 — validator parity is
now pinned by a test:validateandbuildboth load throughcore.load(), so
they resolve the same schema and the sameadditionalPropertiespolicy; the
fixture carryingprojects[].artifactRefs(the v1.12/#333 witness) is accepted
by both, and an injected unknown row field is refused by both with identical
violations (export profileshares that load path and is asserted with them). -
The
.pascorcengine gate andpasco doctor(#348) — a workspace can now
declare the minimum engine it requires, and the commands that do work refuse
early instead of failing on a contract they do not know..pascorcgains an
optional"engine": "MAJOR.MINOR.PATCH"field (a lower bound, exactly three
numeric components — nov, range operator or wildcard; the repo stays
dependency-light, so there is no semver range engine).validate,buildand
export profilecheck it before loading or rendering: an older install exits
1with the required version, the installed version and the upgrade command
(npm i -g @profile-as-code/cli@latest); a malformed value exits1naming the
field, the value and the accepted syntax. Absent means no requirement, so every
existing workspace keeps working. Every.pascorcthe resolution consulted is
read — the cwd.pascorcthat pointed at the workspace AND the workspace's own
file — and the stricter declaration wins (a malformed one in either file is
refused); an explicit--workspaceuses only the workspace's file.pasco doctor [--json]is the reading surface — chosen over overloading--version,
which describes the binary and must work with no workspace, whiledoctor
describes the pair: one line with the installed engine, the profile-contract
version this build validates (derived from the schema$idatpnpm gen:zod,
never a literal), the resolved workspace and whether it loads, plus the
effective requirement and its.pascorc(met/NOT MET/INVALID). Exit0
healthy ·1unhealthy ·2usage; offline, no clock, no writes. It reports
the same truth the gate enforces; it does not replace the gate. Contract in SPEC
§7.6; the.pascorcfield in USER-GUIDE and the package README. -
The published schema contract for consumers without the engine (#348) — a
consumer that only validates a profile no longer has to install the CLI.
schema/consumer-contract.jsonrecords the contract's URL + sha256: the
immutable copy at a released tag
(https://raw.githubusercontent.com/ChristianLuciani/profile-as-code/v0.3.6/schema/profile.schema.json,
schema v1.13, sha256275340ff…) and the committed...
v0.3.6
Added
identity.nationality— a citable nationality fact (#334) — the ISO 3166-1
alpha-2 code, uppercase (VE), as a fact distinct fromidentity.location
(residence). The schema pattern^[A-Z]{2}$rejectsve,VenezuelaandVENat
the boundary.structuredData()emits schema.orgnationalityas
{ "@type": "Country", identifier: "<code>" }on the Person entity JSON-LD —
Person-only, since a brand has no nationality. Optional and outside the
structured-datareadygate and completeness ratio: a workspace that omits it
validates, emits, and scores exactly as before. Schema contract v1.13 (additive,
pre-v1.13 workspaces validate unchanged);@profile-as-code/core0.1.1.- Schema v1.12 — a project names its artifact (#333) —
projects[].artifactRefs
referencesartifacts[].id, the third reference site afterpublications[]and
press[]. It stores the edge only: published-vs-in-development is derived per
artifact (visibility: public+locator.url), never a stored flag (§15.3/§20.1).
load()fails on a dangling ref, andpasco checkfails when the target is not
public (§18.5 — the leak gate now covers every reference site). pasco edit— a contract edit surface, S1–S5 (#248–#252; epic #247) — address
one contract field by contract path (never line/offset): read its current value
(S1),--setwrites it with a comment-preserving byte-range splice — validated and
atomic, one-line diff (S2) —--set --checkpreviews the published lines the
edit changes (S3), and writing text pre-flights the platform's published cap at
edit time (S4;content/checks.yamlwhen declared, else the shared limits table).
S3 also reports the unit's per-surface staleness from the §19 ledger. S5 freezes
the machine-readable contract and names the#238variant, so an agent consumes
pasco editwithout parsing prose. Writer concurrency is covered under Fixed
below.- Answer-engine surface —
identity.definition+faq[](schema v1.10, #288) —
the two things an answer engine can quote: a canonical per-language definition
(≤ 160 chars per value, must contain the entity name) and 3–20 citable Q&A pairs
(q≤ 90 and unique per workspace,a≤ 320, optional http(s)source). This
release ships the contract; thestructuredData()projection of these fields
(Persondescription+ theFAQPageJSON-LD node) is tracked in #316 and is not
emitted yet. - Funding rails — the project is now easy to fund —
fundingfield in the CLI
manifest (GitHub Sponsors · Ko-fi · Patreon;npm fundand the npmjs.com package
page surface it natively), a--helpfooter tip line, a Support section on the
package README, the three badge rails in the root sponsor section, the vendored GitHub
mark on the landing and docs chrome, and theko_fi:/patreon:rails in
.github/FUNDING.yml.
Deliberately not: no post-install script, no donation nag on every command — the
ask lives in--helpand the READMEs only.
Changed
- GEO scores move: geoCheck v2 (#289) — five citability codes join the report
(geo/canonical-definition,geo/faq-coverage,geo/freshness,
geo/sameas-coverage,geo/name-collision), funded by diluting the nine pre-v1.10
checks ×0.8 (the weights still sum to 1, asserted). Reported GEO scores move for
most workspaces — the five codes enter the weighted report; a workspace already at
the ceiling on all 14 stays at 100. The newGeoCheck.severitytier is additive and
infonever changes the pass/fail story. - The release publishes the seam cohort in dependency order (#301) — core →
markdown → paste → artifact → github viapnpm publish, which rewrites the
runtimeworkspace:*dependency, then the CLI vianpm publishover OIDC; a
package whose version is already live is skipped. - Deterministic ordering is ICU-free everywhere (#305) — every text sort delegates
to core'sbyteOrder(UTF-16 code units), so output is byte-stable across locales.
Fixed
pasco editwriter hardening (#332) — the preview refuses what the write
refuses, anchored/aliased scalars are refused, the writer re-reads immediately
before the atomic rename and refuses on drift, and an advisory lock closes the
last-writer-wins window; concurrent edits on one workspace keep both values.
v0.3.5
Added
docs/START-HERE.md— the map each reader was missing (#260) — the dogfood run
(2026-10-19/20) broke at five points the guide never covered: editing the wrong YAML file,
editing without an editor, being cut by a platform cap a greenvalidatenever warned about,
deploying by PR from the wrong directory, andlinks[].role: identity(the one line worth
+20 structured-data points, documented nowhere). Three routes (person · contributor · agent),
each with the mistake that route actually makes, plus the USER-GUIDE sections they read next:
the seven-step cycle of one change (§0.5), the first edit without an editor, thelinks[]
role rule, platform caps at edit time (§6), and deploy by PR with the.pascorcdiscovery
trap (§7). The issue's proposed §4.1/§8.3 were already taken (golden rule, multi-workspace),
so the new material sits at §4.4 and inside §7 — an addressed deviation, not a silent one.
Published as a page (site-config.mjs), so the same Markdown is the only copy, and CONTRIBUTING
ground rule 8 makes "the doc line" part of every dogfood finding.- Derived publication truth —
pasco statusnow says whether the artifact is the one
the SSOT produces (SPEC §20.1, #242) — the 2026-10-19 dogfood found every gate green
while the published profile was not verified in the terms a user cares about: a green
score over an unpublished artifact, a greencheckover a field the platform truncates,
and a declared-but-ignored variant. Each surface with a ledger record or an artifact on
disk now gets three orthogonal axes —sync(§19, unchanged),artifact
(matches-SSOT/diverges/absent, offline, against the SSOT's current render),
andlimits(ok/over(cap), the #240 catalog). Derived on every read and stored
nowhere (§20's own rule), folded into--completenessas coverage of publication
(the weighted percentage never moves because a surface drifted) and into the generated
CHECKLIST.md. Additive in--json(publication), and the one cap executor moved to
packages/cli/src/limits.tssocheckandstatusshare it — and the planned
editslices (#248–#252) consume it instead of a second cap table. - The platform skills ship with
@profile-as-code/cli(#253) —skills/is copied
into the package at build/pack time (files: ["dist","skills"]) and resolved from the
package root, sopasco refineprints a realSKILL.mdpath on an npm install instead
of the repo-dev layout a user does not have. The output now distinguishes no skill for
<platform>from skills not shipped with this package;postreads the same tree.
Changed
- The workspace has three top-level grains, and the scaffold now says so (#267) —
content/is identity (the SSOT),profiles/is generated output,posts/is the stream
(seeds + per-channel projections, #264) — never identity facts.pasco init's README
templates state the boundary for both flavors, SPEC §9's tree carriesposts/, and
docs/post-mvp.mdnames the rejected nestings (content/posts,content/identities,
content/profiles). No rename, no schema change, no move of existing workspaces. - The recommended on-ramp now resolves for a stranger (#273) —
profile-data-template
went public (verified 2026-10-23:gh repo view ChristianLuciani/profile-data-template --json visibility→PUBLIC, HTTP 200), so its removal trigger fired: the link-check
declaration inscripts/link-check/allowlist.jsonis gone (the registry is empty, the
W-ALLOWLIST-UNUSEDwarning keeps it honest). The surfaces reconciled here —README.md,
docs/USER-GUIDE.md,docs/SPEC.md§9.2,docs/STANDARDS.md(community items 4, 5, 10
and the publication section),docs/ci-cd.md,site/index.html— state the template as
public and cite the verification where they make the claim;check.mjs's header documents
the mechanism. The wider class (surfaces still calling the engine repo private) is
tracked in #293, not silently dropped. No duplication:templates/profile-data/
stays a pointer; re-scoping to a second in-repo template would have created a second source
of truth for zero reader benefit. pasco skills:install <agent>→pasco skills:export <dir> [--force] [--list](#254, C1) —
the CLI no longer prints a projection command for a private studio. It copies the
skill channels it ships into a directory the user names (a non-empty destination needs
--force, which overlays and never deletes), and no published surface references a
path/repo/tooling the reader cannot open (rule now in AGENTS/CONTRIBUTING). The tarball
is asserted byverify:pack.- The hosted on-ramp is a declared v1 non-goal (#221), and the surfaces that still called it
a plan are reconciled (#222) — §9.2's three on-ramps (A template repo · Bpasco init·
C bring your own storage) all require a terminal, and A also an account, so a visitor with
neither has no path; in particular none reaches a profile hosted by us.
docs/SPEC.md§2 declares that path a v1 non-goal: the service it would need is
multi-tenant (accounts, storage, secrets, privacy, cost) and inverts the folder-first model
(D14), so it reopens only on its trigger — the first hosted use case, or M5 + web consumption
live — and as a milestone of its own, not an extension of M5 (§12 keeps M5 a consumption
renderer).docs/USER-GUIDE.md,README.mdanddocs/ROADMAP.mdare reconciled to that
state; the#122tracker is closed and is no longer a deliverable.
v0.3.0
Added
-
Profile completeness — engine-computed checklist + percentage (SPEC §20, issue #102) —
the schema is deliberately permissive (identityis the only required block), so a
workspace at 5% and one at 95% produced the same greenpasco validate: a passing check
with no direction. New pure, deterministic, offlinecompleteness(profile, policy)in
packages/core/(besidegeoCheck, SPEC §7) scores five weighted items over signals
already in the engine —presence(flavor-aware expected blocks),langCoverage(en/es/fr
over every langMap),geoAnchors(associations reinforced elsewhere),evidence(sourced
claims + registry) anddeployment(share of trackable units with a ledger record) — with
COMPLETENESS_CATALOG+DEFAULT_COMPLETENESS_POLICY. The percentage is never stored:
only the policy is, read from.pascorc"completeness"(target/langs/items; a
weightof 0 drops an item from the denominator), andcompile()attaches the report to
the slice so the renderer emits it verbatim rather than recomputing it.pasco status --completeness [--json]prints the checklist + percentage;--write-checklistfolds the
line into the generatedCHECKLIST.md(D18 — the view is where the user looks);
github-readmeemits one deterministic line (Profile completeness: <pct>% — <n>/<m>)
only when the slice carries a report — a hand-built slice renders byte-identical to
before. No new required schema field: completeness is advisory (D12 preserved). -
Publishable-surface gate — only
packages/climay omitprivate: true(#190; the
surface set by #189) — the 11 bundle-only workspace packages are marked"private": true(they ship inside the CLI bundle, not as standalone npm packages), leaving
@profile-as-code/clias the only publishable manifest — but after #189 that invariant was
convention-only.scripts/publishable-gate/(thescripts/dep-gate/
scripts/typecheck-gateshape: a fail-loud--checkplus a fixture self-test that runs
first) asserts every workspace manifest outside the explicit allowlist declares"private": true, so a new package (e.g. the plannedpackages/server) cannot silently rejoin the
accidental-publish surface with every gate green. It reuses dep-gate's exported workspace
walk instead of re-implementing it and runs in the pre-install block ofci.yml(fail
fast). The gate verifies, never fixes, and itsE-GATEvocabulary refuses the pathological
shapes — non-object manifest, parser refusal — instead of crashing with aTypeError
(#195, #199). -
CLI — the documented
pasco diffno-matchtagPatternfallback is pinned by tests
(#197) —docs/USER-GUIDE.md§5 documented that a workspace whosediff.tagPattern
matches no tag falls back to the root commit, but that was verified only by hand: a change
topackages/cli/src/commands/diff.tscould silently invalidate the doc. Two vitest cases
now lock it with a non-defaultdiff.tagPattern: "rel-*"fixture (so the.pascorc
read is load-bearing, not the engine default): no matching tag →tag: null, base = the
root commit, the post-root commits andhasChanges: true; addingrel-1→tag: "rel-1",
base = the tagged commit, no commits,hasChanges: false. No behaviour change.
Changed
-
SPEC §9 — the workspace is a folder; the three on-ramps are declared as peers (#200) —
the contract and the guides treated this repository as the shape of the workspace, and
every surface assumed the reader was standing in it. But the local path already works
(pasco init), some users want no repo at all, and a prospective user can arrive from the
npm catalog, a search engine, an agent or a shared link. §9 is retitled "the profile-data
workspace (folder-first; a repo is recommended)": the only hard requirement is a folder
pascocan read by configured path whosecontent/*.yamlsatisfies the schema, and a repo
(GitHub, another git host, or none at all) is the recommended home. New §9.2
declares three on-ramps as peers with their capability differences up front: A template
(automation + history; requires account/repo) · B localpasco init(no account, no repo;
loses automation and history) · C the samepasco initscaffold written at a path you
choose. D14 is amended ("separate repo" → "separate workspace; a repo is the
recommended home, not a requirement") and §3/§4/§10/§13 are reconciled;docs/USER-GUIDE.md,
packages/cli/README.md(the npm page),README.md,templates/profile-data/README.mdand
docs/STANDARDS.mdcarry the same framing. No engine behaviour changes. -
Release docs reconciled against the real v0.2.0 publish (#192) — the post-release
surfaces still claimed the package was unpublished. Corrected against the actual sources
(tag, GitHub Release, npm registry, a running bin):docs/STANDARDS.mditem 3 (Tagged release + changelog)❌ blocker → ✅,docs/USER-GUIDE.md,docs/ci-cd.mdand
packages/cli/README.md; andpackages/cli/package.json'sbin.pascowas normalized with
npm pkg fix("./dist/index.js"→"dist/index.js"), removing the single npm manifest
auto-correction the v0.2.0 publish warned about.
Fixed
dep-gateandtypecheck-gaterefuse a non-object manifest instead of crashing (#198) —
apackage.jsoncontaining the JSON literalnullparses without throwing, then crashed
both gates onmanifest.namewith a rawTypeErrorinstead of their declaredE-GATE
vocabulary (fail-loud, but the wrong failure shape — and the refusal class had no committed
fixture). Both now guard the manifest shape right after the parse, pinned by a
null-manifest fixture in each sibling self-test. The same pass closed two adjacent
escapes found in review:tsconfig.test.json=null, and apnpm-workspace.yamlwhose
quoted glob the reused parser refuses (whoseGateErrorclass did not match
typecheck-gate's own catch).
v0.2.0
First public release. This section covers what changed since the 2026-10-06
prep; the untagged ## [0.1.0] section below (schema v1, the core engine, the
pac → pasco rename, brand flavor, the evidence registry) is the project's
pre-release history and was never tagged or published.
Added
-
CI — offline, self-tested link gate for the Markdown corpus (#149) —
scripts/link-check/(checker + fixture self-test first, thescripts/dep-gate
shape): every relative link/path in the tracked, non-hidden Markdown doc areas
must resolve to a file or directory, every in-page anchor must match its
destination's headings under GitHub's slug rules (the expected anchors are
committed asgithub-slugger@2.0.0output and asserted by the self-test), and
private targets go through an allowlist that requires a reason + removal
trigger. Offline and deterministic — external URLs are never fetched — so the
required check cannot depend on third-party uptime. Runs after the pinned Node
setup and beforepnpm install(fail fast). -
CI — every package type-checks its tests, and the wiring is gated (#181) —
each package gains atsconfig.test.jsonthat extends its own tsconfig (single
source for compiler options) and addstests/**, the root*.config.tsand
scripts/**;pnpm -r typechecknow covers the suites, so a mistyped shared
contract (e.g.@profile-as-code/paste/conformance) fails the gate. A node-only
scripts/typecheck-gateruns first and refuses a package whosetypecheck
script omits the test program — without it,pnpm -r typechecksilently skips
the package and CI stays green. -
Platform registry — per-platform metadata now has one machine-readable home (#154):
platforms/registry.yamlcarries the metadata that used to live hand-written across ≥7
surfaces (family, deploy kind, hosts, subdomain hosts, handle hosts, status, since, issue,
sources).pnpm gen:platforms(scripts/gen-platforms/, the D3 drift-check shape) projects
every derived surface:packages/core/src/generated/platforms.gen.ts(the host maps + slug
list consumed bydrift.ts/types.ts, plus the derived test enumerations), the SPEC §6
family table + count arithmetic and the README status counts between explicit
<!-- gen:… -->…<!-- /gen -->markers. The reviewed contract is unchanged — the schema
keeps$defs.platform.enum(D3) — and CI asserts registry ↔ enum ↔ generated agree
(scripts/schema-check/check.py+node scripts/gen-platforms/gen.mjs --check), including
the reverse assert: every enum member has a declared renderer or an explicit
renderer: false("not implemented yet"), and each renderer'sdeploySpecequals the
registrydeploy(D9). -
CONTRIBUTING — escalation path and measured response latency (#148) — the
published latency is measured, not a promise: a table over the three strata
(n=103 merged PRs, [MI], 2026-10-08), the regenerablegh+python3
one-liner, and the honest caveat that no external PRs exist yet so the figure
describes maintenance cadence. Stalled = no maintainer response after 7 days;
the escalation channel and the possible outcomes are named, and silence is not
one of them. -
Package READMEs + complete publication metadata (#143) — 9 new READMEs
(D8, English:packages/markdown+ the 8 renderers) with one uniform skeleton,
since npm always packsREADME.mdand publishing would otherwise show an empty
package page; the 10 libraries + the CLI gainrepository(+directory),
bugs,homepageandkeywords, and the pure libraries gain
sideEffects: false. -
hotmartplatform — schema additive v1.9, D16 operator extra #15 (49 total;
issue #137) — Hotmart: digital-products storefront + memberships (the Payment Policy's
recurring billing) + affiliate program, serving the Spanish/Portuguese-speaking markets
(offices in Belo Horizonte, Madrid, Bogotá, Ciudad de México — [OFICIAL] footer, fetched
2026-10-05), in thecreator-monetizationfamily. Enum
entries in$defs.platformand the derivedcheck.platform, ledger surface, drift host
hotmart.com(+HANDLE_HOSTS; the Hotmart Space profile URL shape stays[?]— no
PLATFORM_SUBDOMAIN_HOSTS entry without a verified shape), Zod regenerated (D3). Paste
platform (D9 — no management API documented for the profile body; developers.hotmart.com
covers sales/products [?]):pasco build hotmart→ paste →pasco status --mark.
Fees/eligibility sourced from the official legal texts, fetched 2026-10-05: no
monthly/annual fees ([OFICIAL] pricing); fixed fee BRL 2.49 / USD 1.00 / EUR 1.00 +
service fee 9.9% (20% micro-transactions) ([OFICIAL] Payment Policy §5); withdrawal
minimum BRL 2.00 + BRL 1.99 fee, 30/15-day deadlines ([OFICIAL] root FAQ #8); worldwide
eligibility, sanctions-based exclusions only, bank account in own name in the country of
residence after the first sale ([OFICIAL] ToU §A/§H/§2.8). EC payout rail remains
[?]— carried per the #133 precedent (kofi/payhipPayPal-EC), verification item
in the venture instance. The market-leadership framing in issue #137 was NOT carried into
this contract text (no independent source; Hotmart's own "world's largest / Latin
America's leader" wording lives in the skill research as attributed vendor marketing). -
Renderer
@profile-as-code/renderers-hotmart(v1.9, #137) — deterministic
plain-text profile paste-source: exactly ONE bios row (long > medium > short), byte-stable,
empty slice → empty artifact (skipped/unbuilt bypasco build, §15.1). Deploy spec is
§7'smanual(D9 paste platform). -
Skill
skills/hotmart/(#137) — SKILL.md (storefront frame — "esto resuelve X", not
tip-jar prose; no-results-promised rule per ToU §1.4) + playbook (paste flow with the
first-paste surface-verification step) + checks.yaml (soft-cap curacional — no documented
bio limit) + research/SOTA-2026.md (all sources fetched 2026-10-05; the fee table and
eligibility from the official legal texts; the Space URL shape and EC payout carried
[?]with sourcing leads). -
M9 floor — private-first, consent-gated AI-crawler posture (SPEC §12/§14, issue #141) —
the training-time corpus floor inverts:robots.txton published surfaces blocks
AI-training crawlers (CCBot / vendor AI crawlers) and user data ships without an open
license; entering corpora or open-licensing data requires the profile owner's explicit opt-in
(consent field — schema work deferred with Q10). Rationale: the engine is multi-tenant
(SPEC §9), so an allow-posture decides privacy + licensing on behalf of profile owners
(identity data entering training corpora without consent — GDPR-adjacent exposure when
productized); the operator's own profiles may opt in — that consent is theirs to give. Inputs -
Platform families
freelance+job-boards— SPEC docs-first declaration
(issue #138) — the platform families table in the SPEC (D16 block):freelance
covers the pre-existing v1upworkretroactively and names its backlog slice
(freelancer,workana,fiverr— filed with the gig-storefront boundary note);
job-boardsis a backlog-only slice (infojobs,computrabajo,indeed—
recruiter-facing CV surfaces; GEO hook = name-reputation in search results). No
enum/schema/core/CLI changes (48 unchanged); each backlog member follows the #133
member chain per slug when real operator demand lands, with an official eligibility
page before any claim. Boundary notes:linkedinstays un-bucketed (hybrid),
github-readme/github-org/researchgatestay un-declared (YAGNI),toptalis
recorded as a reject candidate (invite-only network,[MI]). -
Creator-monetization platform family — schema additive v1.8, D16 operator extras
#11–#14 (48 total; issue #133) —buymeacoffee,liberapay,gumroad,payhip: the
platforms where creators receive money (memberships / tips / recurrent donations /
storefronts) alongsidepatreon(v1) andkofi(v1.7). The family is declared docs-first
ascreator-monetization("Creator monetization platforms" — canonical industry term)
in SPEC (D16/§9) and the schema descriptions — no hard schema field (zero consumer
breakage). Enum entries in$defs.platformand the derivedcheck.platform, ledger
surfaces, drift hosts (buymeacoffee.com,liberapay.com,gumroad.com,payhip.com,
with Gumroad/Liberapay subdomain profile URLs), Zod regenerated (D3). Paste platforms
(D9 — no management API for the profile body):pasco build <platform>→ paste →
pasco status --mark. Per-platformgofundmedeferred (no EC payout as organizer,
operator decision in #133); per-platform skills remain separate follow-ups (D16 parity
caveat) and each carries its own sourced research when authored. -
Renderers
@profile-as-code/renderers-*for the family (v1.8, #133) —
renderers-buymeacoffee,renderers-liberapay,renderers-gumroad,renderers-payhip:
deterministic plain-text paste-sources (exactly ONE bios row, long > medium > short),
byte-stable, empty slice → empty artifact (skipped/unbuilt bypasco build, §15.1).
Core fixtureminimal-creator-monetization+schema-checkfixture
valid-creator-monetization.json. -
kofiplatform — schema additive v1.7, D16 operator extra #10 (44 total; issue #130) —
Ko-fi creator page (same family aspatreon): enum entries in$defs.platformand the
derivedcheck.platform, ledger surface, drift hostko-fi.com(+HANDLE_HOSTS), Zod
regenerated (D3). Paste platform (D9 — no API deploy; Ko-fi ships payment webhooks only):
pasco build kofi→ paste → `pasco status -...