V2 architecture
Ownership and stored artifacts
The branches contain different publication inputs and outputs:
gitascii.json is the default profile configuration, including widgets, global styles, slug, and revision metadata. Named profiles use gitascii_[slug].json. The gitascii branch is initially created from the default branch’s current commit, so it can also contain inherited files; it is not created as an orphan branch. See Configuration for the model and Multi-Profile for profile management.
Editing, onboarding, and publication
- Edit locally. The editor store updates the design and saves a browser draft. This does not itself publish a GitHub commit. Publish to GitHub sends the current widgets, styles, template, and slug as
exportDatatoPOST /api/github/commit. - Authenticate and authorize. GitHub OAuth exchanges an authorization code, reads the GitHub user, and establishes the GitAscii session. Repository publication separately uses a GitHub App installation token. The backend derives the owner from the session, checks profile write access, and stamps a revision. An absent installation returns
not_installedwith an installation URL; the editor redirects there and the setup callback returns to the app. That callback does not publish by itself. - Prepare the repository. The backend accesses
username/usernameand discovers its default branch. On a 404, it attempts to create the profile repository using the installation token. A rejected creation request is returned as an error; successful repository creation is not guaranteed by this code path. - Render and commit now. GitAscii fetches fresh profile data and calls
bootstrapGitasciiBranch. It createsgitasciiif needed, renders both themes with the shared engine, processes external assets, and writes the configuration plus SVG pair through GitHub’s blobs/tree/commit/ref APIs. The branch ref update usesforce: false. - Write integration files. The backend generates the README embed and
.github/workflows/gitascii.ymlon the default branch, updating each only when its content differs. These writes are separate from the SVG/config commit. - Request a refresh and persist app state. It sends
workflow_dispatchforgitascii.ymlwithrefset to the default branch, then attempts to save the configuration in GitAscii storage. Dispatch and storage failures are logged without failing an otherwise completed publication. The editor marks its draft clean only after a successful response.
Pro profile mutations
Creating, duplicating, or restoring a Pro profile saves app state and then calls the V2 profile publisher to bootstrap its configuration and SVGs and request a refresh. This helper assumes onboarding has already installed the workflow; it does not install it or rewrite the README. Promotion writes the canonical default and the old-default backup together in one Git commit before updating app metadata. Deletion removes the named profile files and its marked README block in separate operations. These management paths are not a transaction spanning GitHub and app storage. See profile publisher and multi-profile management.What the backend and database retain
saveProfileConfig writes profiles metadata and JSON in profile_configurations when PostgreSQL is configured, updates the process memory cache, and invalidates the HTTP SVG cache. Without a configured database, it uses Redis for configuration persistence. Normal storage reads use memory, then PostgreSQL (or Redis when no database is configured), then GitHub as a fallback. The public configuration endpoint requests preferGitHub: true.
GitHub configuration lookup tries gitascii first, then legacy root and .github/ locations on main and master. These copies can diverge: the Action reads configuration from GitHub, and its SVG refresh does not synchronize configuration back into PostgreSQL. The backend/database remain important for account state, entitlements, profile management, migration tracking, and optional Pro services.
Sources: profile storage, database repository, and configuration endpoint.
Workflow and Action refreshes
The generated workflow runs onubuntu-latest, grants contents: write, has a five-minute timeout, and cancels an earlier run in the same concurrency group. It calls Igorcbraz/GitAscii/action@<ref> with secrets.GITHUB_TOKEN. The ref is GITASCII_ACTION_SHA when configured, otherwise main; deployments are not necessarily pinned to an immutable Action revision. The Action metadata selects Node 20 and the bundled action/dist/index.js, which includes the shared SVG engine.
At execution time, the Action:
- Reads the
gitasciibranch and discoversgitascii.json/gitascii_[slug].jsonconfigurations through GitHub’s tree/blob APIs. It does not load profile JSON from GitAscii’s database. Missing branches or no usable configurations fail the run; onboarding must create them first. - Checks the publication policy. Only scheduled runs apply the elapsed-time guard, measured against the latest branch commit date. The effective interval is the larger of
refresh_minutesand the policy minimum. Without a successful OIDC policy response, the minimum falls back to 1,440 minutes; Pro’s supported minimum is 60. Manual dispatch bypasses this elapsed-time guard. - Fetches fresh GitHub data using REST and GraphQL, renders both themes for all discovered profiles (or the optional
profile_slugselection), and inlines permitted external assets. See Rendering and the ASCII Pipeline for engine internals. - Checks that each result contains SVG markup and submits the generated SVGs together through
publishAtomic. Matching Git blob hashes producesvg_unchanged; a changed branch head or applicable configuration revision producesskipped_stale. Otherwise it creates one commit and advances the branch withforce: false. - Optionally reports profile-scoped health results with GitHub OIDC. Pro workflows grant
id-token: write; see Health Monitoring and Telemetry & Privacy.
How the SVG reaches the README
The generated<picture> references stable URLs such as:
<img> fallback. GitHub serves the committed files; GitHub image proxy/cache behavior can delay visibility of newer bytes at the same URL. No render runs because a reader opens the README. A failed refresh can leave the previous committed image available, but this is not a guarantee against repository deletion, access changes, or GitHub outages.
Use GitHub README Integration for copyable embeds. The optional Pro badge makes a separate GitAscii request; it does not turn the raw profile image into an exact view counter. An enabled Pro dynamic rule for the default profile instead generates an API URL with ?dynamic=1 and follows the HTTP path.
Failure and consistency boundaries
Sources: publication route, Action data fetcher, external asset inliner, and atomic publication.
V1: verified historical rendering path
The available pre-V2 snapshot is 4e6b497, the parent of the first available GitHub-native publishing commit,085de1f. This describes that late V1 implementation, not every earlier deployment.
- Save: the publisher wrote configuration JSON to the profile repository’s default branch and saved an app-side copy. README embeds pointed to GitAscii HTTP SVG endpoints such as
/api/[username], with theme/slug variants and cache-busting URLs in some paths. A separate contribution-snake workflow existed; it did not render the whole GitAscii profile. - Render and serve: on an uncached HTTP request, GitAscii loaded configuration (memory/Redis/PostgreSQL, then legacy GitHub locations), fetched GitHub data, ran
renderSvg, inlined external images, and returnedimage/svg+xml. Missing configuration could fall back to a template. - Cache/proxy: the service had an optional Cloudflare Cache API layer, memory and compressed Redis SVG caches, in-flight request deduplication, ETags/304 responses, and browser/CDN cache headers. Normal responses requested a 300-second cache lifetime and CDN stale-while-revalidate; degraded results used shorter lifetimes. GitHub Camo added another cache/proxy boundary for README embeds.
- Limitation: cold generation still depended on the GitAscii runtime, cache/storage availability, GitHub requests, and external assets before the proxy received an image. The historical service explicitly records that awaiting an edge-cache write caused cold renders to reach Camo 504 timeouts. Layered invalidation and cache lifetimes also complicated update visibility.
V1 versus V2
V2 moves expensive rendering out of the usual image request and makes the published result independently retrievable from the user’s repository. The tradeoff is eventually refreshed data and a publishing pipeline that can fail or partially complete. It does not remove GitAscii storage, every API request, GitHub caching, or the need to monitor refresh failures.
V1 to V2 migration and backfill
Existing users can publish through the editor, which creates the V2 artifacts directly. That route intentionally leaves legacy configuration files in place. There is also a separate migration campaign implementation:- A secret-protected
POST /api/internal/migrationreads eligible rows from PostgreSQL’smigration_installationstable, mints installation tokens, checks rate limits, and records outcomes. Eligible states arepending,failed,permissions_missing, andpost_merge_pending, with fewer than five attempts. - For an unmigrated repository, the engine loads the default profile through existing storage/fallbacks and fetches fresh GitHub data. If no configuration is found, it constructs a minimal stats configuration. It bootstraps
gitasciiand the initial SVG pair before proposing integration changes. - It opens a PR from
gitascii-v2-migrationto the default branch with the workflow and generated README. The PR also removes rootgitascii.json/gitascii_default.jsonfrom the default branch when present. This migration handles the default profile; it is not an all-slug backfill. - When a workflow is already present, the engine checks the branch, both default SVGs, and the README raw URL. It may rewrite the README to repair the embed or dispatch a refresh. It returns
completedonly when those checks pass; otherwise it returnspost_merge_pending. Existing open or closed-unmerged PRs have separate states.
Repository inspection found no production caller of
upsertMigrationInstallation and no scheduled
caller of the migration endpoint. Also, the batch query excludes pr_opened, so opening a PR does
not by itself establish automatic post-merge verification. Queue population, scheduling, and
requeueing after merge require orchestration that cannot be verified here.runRedisToPostgresBackfill utility copies legacy app data (users, settings, profiles/configurations, versions, rules, email logs, and errors) under locks. It does not publish GitHub SVGs. No invocation of that utility was found in the current application or scripts, so it is documented as available migration code rather than an automatic startup step.
Sources: migration endpoint, migration engine, queue repository, and Redis-to-PostgreSQL backfill.
Verification limits
Source inspection establishes the implemented paths, not their production execution history. The repository does not establish migration rollout completion, externally configured schedulers, actual GitHub App installation permissions, deployedGITASCII_ACTION_SHA, or exact raw/Camo cache retention. Repository creation is an attempted API call, not a verified onboarding outcome. The V1 snapshot supplies concrete engineering evidence but no measured before/after reliability or cost data.