A software portfolio can become difficult to manage long before it becomes large. One app lives behind an old subdomain. Another uses a shared account whose owner has changed. A prototype still has a scheduled job. A customer has bookmarked a route that the team considers abandoned. The repositories may all be visible, yet the operating obligations are scattered across memory, provider dashboards, and old messages.
An app registry gives those obligations a home. It is a record of products and services the business operates, including their owners, public surfaces, dependencies, costs, data responsibilities, release state, and retirement conditions. The record should answer a practical question: if this app changes or fails today, who can explain what is affected and what must happen next?
The registry does not prevent sprawl merely by existing. A table can become stale as easily as a repository. Its value comes from being connected to creation, release, recurring review, and retirement. Each of those events either confirms the current record or exposes a missing decision.
This chapter in The AI Software Factory, part of the AI collection, defines that operating record. The component catalog inventories reusable building blocks. The app registry inventories the products that use them and the business obligations those products carry.
Register the operating obligation, not just the repository
A repository is a source container. An app is a service people rely on. The relationship is not always one to one. A single repository can deploy several apps, and one app can depend on multiple repositories. An inventory that treats repository names as the complete portfolio will miss those relationships.
Start by defining the unit the registry tracks. For a small business, an app might be a distinct customer workflow with an accountable owner and a release lifecycle. Two interfaces can belong to one app if they serve the same promise and change together. A scheduled worker with no public interface may deserve its own record if it has separate data, cost, and failure responsibilities.
Use a stable app identifier that survives changes in display name, repository, or domain. Human-friendly names are useful, but they are not safe keys for operational relationships. A rebrand should not make logs, deployment records, and customer support history appear to refer to unrelated products.
Register experiments too, with a different state and a smaller required record. A prototype can create obligations through a live integration, a paid provider account, a retained dataset, or a public route. Calling it an experiment does not make those obligations disappear. The registry can acknowledge limited scope without pretending that an early product needs the same paperwork as a mature one.
Distinguish an app from a reusable capability. A billing adapter may appear in the component catalog and be referenced by several app records. The adapter’s maintenance owner is responsible for the shared contract. Each app owner remains responsible for how that app uses it. A shared dependency does not transfer the entire customer promise to the platform team.
Make ownership specific enough to act
“Owned by engineering” is rarely a sufficient answer when a renewal fails or a customer asks for deletion. The registry should identify a person or a current accountable role that can accept the obligation, plus a way to reach the appropriate operational channel. The record should say when that ownership was last confirmed.
Different responsibilities may have different owners. Product purpose, technical maintenance, commercial account management, and data handling can be separate roles. In a small company one person may hold all four, but the distinction still helps. If the company later delegates billing operations, it can transfer that responsibility without implying that architecture or customer support moved with it.
Ownership should include authority boundaries. A maintainer who can prepare a deployment may not be authorized to change a price or delete customer records. A support operator may need to restore access without modifying the entitlement rules. Record where a consequential decision is approved, especially when the app is maintained by coding agents.
An owner departure is a registry event. It should trigger reassignment and review of credentials, provider access, unresolved incidents, and upcoming renewals. Merely replacing a name in the row leaves unanswered whether the new owner can actually operate the product. Transfer is complete when the receiving owner can locate the necessary evidence and knows the pending decisions.
A useful ownership check asks someone other than the original builder to follow the links. Can they find the current release, operational procedure, and support responsibility? This is a proposed check of recoverability, not a claim that the registry has reduced incidents. It tests whether the record can serve its purpose when the builder is unavailable.
Record surfaces customers and machines can reach
List the app’s public domains, route prefixes, APIs, webhooks, and scheduled entry points. A home page is only one surface. A forgotten webhook can continue accepting events after the interface has been removed. A background job can continue exporting data after the product is considered inactive.
For each surface, record the environment and intended audience. A staging route, a public production route, and an internal operations endpoint should not be indistinguishable. Include the relationship to access controls and the source of routing configuration. The registry should link to the authoritative configuration, not maintain a competing copy of every routing rule.
Public routes also have content obligations. If an app is linked from a series hub, a directory, documentation, or an external campaign, retirement affects more than a deployment. The app record can list important inbound references and the proposed destination for redirects or replacement guidance. It should distinguish a planned redirect from one that has been deployed and checked.
An API contract needs its own version and consumer relationship. A route can exist while its response breaks a downstream client. Record known consumers, compatibility policy, and the owner of the contract. Unknown consumers should remain an uncertainty, not be silently treated as an empty set.
A plausible counterexample is an internal script invoked manually from a developer’s machine. It may have no public URL, yet it could access production data or spend money through an API. Its registry surface is the invocation procedure and authority boundary. A public-route-only registry would miss exactly the kind of quiet obligation it is meant to expose.
Link dependencies at two levels
The registry needs technical dependencies and operational dependencies. Technical dependencies include repositories, deployable services, shared packages, runtime assumptions, and databases. Operational dependencies include provider accounts, support procedures, data feeds, commercial agreements, and people who supply necessary inputs.
Do not attempt to reproduce a package lockfile by hand in the app record. GitHub’s dependency graph can derive supported dependency information from manifests, lockfiles, and submissions; its coverage depends on supported ecosystems and available input. Use the appropriate technical inventory and link it from the app. A manually maintained registry has a different job: explaining which dependency relationships matter to operating the product. GitHub’s dependency graph documentation describes that tooling boundary.
For a shared service, record the interface the app uses and the consequence of failure. An optional analytics integration and a required entitlement service have different effects. Listing both as “dependencies” without the consequence does little to help an incident response or retirement decision.
A dependency record should state whether the app can operate in a degraded mode. For example, a hypothetical report tool might accept new jobs while an optional notification provider is unavailable, then deliver notices later. It might refuse a new job if the required data source cannot be verified. Those are product policies requiring review; the registry makes them discoverable.
Also record dependencies in the other direction. If three apps use a shared export worker, the worker’s record should identify those consumers. Removing a service because its own dashboard looks quiet can break activity elsewhere. A registry should support the question “what depends on this?” as well as “what does this depend on?”
Cost records need definitions and dates
A useful cost field says more than a monthly number. It identifies the period, source, scope, and basis of attribution. A provider bill may include several apps. A shared database may have a fixed base charge and variable usage. An app’s apparent cost changes when the allocation rule changes even if the underlying bill does not.
Record directly attributable charges separately from allocated shared charges. If a proposed internal method allocates a shared service by measured usage, state that method. If the allocation is a rough planning estimate, label it as such. The registry should not present a guess as a precise economic fact.
Human obligations belong beside infrastructure charges. A small app can have a low hosting bill and a high support burden. The registry can link to support records and maintenance estimates rather than forcing them into one misleading total. The broader portfolio discipline chapter addresses investment decisions; the registry supplies dated inputs for those decisions.
Include renewal dates, minimum commitments, spending controls, and the owner of each provider account. A zero-traffic app can still renew a contract. A usage spike can still exceed a budget even if the product appears small. These facts are often more actionable than a single average cost number.
For illustration, imagine an app with $40 of direct monthly hosting and an estimated $30 allocation from a shared service. The record should show $40 observed from a dated bill and $30 estimated under a named method, not “cost: $70” without qualification. No real Salars cost is implied by this example. The distinction preserves the evidence needed to challenge or update the estimate.
Data responsibility survives inactivity
List the important classes of data the app collects, stores, sends, and derives. Identify where they reside and which provider or shared service handles them. A registry does not need to contain the data itself. In fact, copying sensitive content into the inventory would create an additional handling obligation.
Record the purpose of retention and the applicable deletion or export procedure. If a rule is unresolved, write “unresolved” with an owner and decision date. Do not let an empty field imply that no rule is needed. A prototype containing test data and a live product containing customer documents have different responsibilities even when their code is similar.
Data flows matter because removing an app’s primary database may leave copies elsewhere: backups, exports, logs, support attachments, or downstream integrations. The record should link to the relevant map or procedure. It should not imply that clicking a delete button in one console settles every obligation.
Access responsibilities need the same clarity. Who can read production data, through which account, and for which operational purpose? Where are access reviews recorded? The registry can point to the authoritative access system rather than hold credential details. It should never become a convenient place to paste secrets.
An inactive app may still hold data for an agreed purpose or required period. That means “no current users” and “ready to remove” are different states. The shutdown chapter develops execution of retirement decisions. The registry preserves the conditions that must be resolved before those decisions are complete.
Give lifecycle states observable meanings
A small state vocabulary is easier to use than an elaborate taxonomy. One proposed sequence is proposed, experiment, active, constrained, retiring, and retired. The names can vary. What matters is that each state has entry conditions and operational consequences.
An experiment might have a named owner, a limited audience, an expiry date, and a defined data boundary. An active app might require a supported deployment, current operational links, and a confirmed support owner. A constrained app might remain available to existing customers while accepting no new ones. Retiring means a shutdown plan is underway, not that everything has already been removed.
Retired should mean specified conditions have been checked. Public surfaces are closed or redirected as intended. Scheduled activity has stopped. Provider obligations have been resolved or transferred. Data has been handled according to the reviewed plan. Remaining records explain what was done and where any continuing obligations live.
Avoid letting a successful build automatically mark an app active. A build is a technical event. Active service also requires the relevant commercial, operational, and data decisions. Conversely, an app can remain active while a new release is being prepared. Lifecycle state and release state should be separate fields.
Release state can link a source revision, build artifact, environment, deployment identifier, and verification record. This makes “current release” an evidence-backed pointer rather than a manually typed version name. A planned release should not replace the record of what customers currently receive. The distinction becomes crucial when a deployment fails or a rollback is needed.
Maintain the registry through ordinary work
The best time to update an app record is when the fact changes. Creation supplies the initial owner and purpose. A release confirms the deployed artifact and surfaces. A provider change updates dependency and cost relationships. A retirement decision adds the plan and unresolved conditions. A periodic review catches facts that escaped those events.
Keep automation narrow and source-aware. Deployment systems can supply artifact identifiers. Infrastructure inventory can supply observed resources. A package tool can supply dependency metadata. None of those systems can reliably infer why the customer needs the app or who is accountable for its promise. Those fields require an explicit decision.
Every automatically supplied field should identify its source and freshness. If synchronization fails, preserve the last known value with a stale indicator instead of quietly replacing it with blank data. Absence from one provider’s inventory is not proof that an app no longer exists elsewhere.
A recurring reconciliation can compare registry records with observed domains, deployments, jobs, and provider resources. Differences become a review queue: unregistered resource, missing expected resource, changed owner, expired review. The queue should have a responsible person and bounded resolution process. Generating an ever-growing exception list without action simply creates another form of sprawl.
The app template system can require an initial record when a repository is created. That is useful but insufficient. A repository may never deploy, and a product may later change shape. The registry should tolerate those paths rather than assume creation metadata remains true forever.
Keep the first implementation small
A small portfolio can begin with a versioned structured file or a modest internal table. The essential properties are stable identifiers, clear field definitions, change history, useful links, and a way to query obligations. Buying a large catalog platform before understanding the records can substitute configuration work for the decisions the business actually needs.
Start with the questions most likely to expose a costly gap. Which apps have no confirmed owner? Which active apps have an unsupported runtime? Which experiments have passed their expiry date? Which retired apps still have paid resources or retained data? Which public surfaces are missing from the record? Those questions create a reason to maintain the fields.
Do not demand exhaustive precision before the first entry. Mark uncertain values and resolve the most consequential ones first. A partially complete record with honest unknowns is more useful than no record, provided those unknowns have owners and are not mistaken for clearance. The initial goal is visibility of obligations, not cosmetic completeness.
Nor should the registry become a surveillance system for every developer action. Record product-relevant decisions and operational relationships. Excessive detail makes the record harder to maintain and can obscure the few facts needed during a release or incident. The test is whether a field supports a concrete decision.
What Would We Do at Salars?
We would propose a registry seeded from the apps and services we can actually identify, then reconcile it against known deployment, domain, scheduled-job, and provider records. We would not describe that inventory as complete until the sources and exceptions had been reviewed. This is a proposed operating method, not a measured claim about Salars’ current portfolio.
The initial record would include stable identity, purpose, lifecycle state, accountable owner, public and machine surfaces, current release evidence, important dependencies, dated cost inputs, data responsibilities, and the next review date. Template origin would be a link rather than the app’s identity. Shared capabilities would remain in their own catalog with explicit consumer relationships.
We would choose a protected check before judging the registry useful: give a reviewer a hypothetical owner-departure or app-retirement case and ask them to locate the affected surfaces, accounts, consumers, and data obligations. The baseline would be the existing documented discovery process. Success would require correct identification of the obligations, not just a faster search or a fuller-looking table.
A counterexample would be a quiet background worker with no public page. If the registry missed its schedule or provider cost, the method would need revision. We would stop expanding automation if it generated records without accountable owners or if synchronization erased uncertainty. A meaningful provider migration, incident, or retirement failure would trigger revalidation.
A registry is useful when an app’s continued existence is a conscious decision. It makes the responsibility visible enough to maintain, transfer, constrain, or retire. That is how a growing software portfolio stays understandable without requiring the original builder to remember every promise it has ever made.
Sources
- GitHub: About the dependency graph — supported technical dependency discovery from manifests, lockfiles, and submitted dependency information.
The registry fields, state vocabulary, reconciliation process, and Salars evaluation are proposed designs. The cited technical inventory capability does not establish that a registry prevents incidents or that the proposed portfolio method has been validated.
Loading comments…