Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> before assuming something isn't built. `@api` = stable to build on; `@internal` = may change.
> Grouped by **capability** (across layers), not by directory.

**229 classes** across **33 capabilities** · **25 modules**. Full prose: [FEATURES.md](FEATURES.md) (what) · [ARCHITECTURE.md](ARCHITECTURE.md) (why). Not-yet-built: [BACKLOG.md](BACKLOG.md).
**244 classes** across **33 capabilities** · **26 modules**. Full prose: [FEATURES.md](FEATURES.md) (what) · [ARCHITECTURE.md](ARCHITECTURE.md) (why). Not-yet-built: [BACKLOG.md](BACKLOG.md).

## Capabilities (`library/Tiger`)

Expand Down Expand Up @@ -120,6 +120,8 @@
- **Tiger_License_Store_Option** `@api` — the default license store, backed by the lazy `option` tier. · `library/Tiger/License/Store/Option.php`
- **Tiger_License_Vendor** `@api` — CONNECT + pin a paid module's vendor (its `[owner]/TigerVendor` repo). · `library/Tiger/License/Vendor.php`
- **Tiger_Model_Module** `@api` — the module lifecycle registry (see migration 0023). · `library/Tiger/Model/Module.php`
- **Tiger_Model_ModuleInstallPin** `@api` — a consumer install's version policy + rollback anchor (TIGER-334, 0054). · `library/Tiger/Model/ModuleInstallPin.php`
- **Tiger_Model_ModuleRelease** `@api` — a module release moving through QA (TIGER-334, migration 0054). · `library/Tiger/Model/ModuleRelease.php`
- **Tiger_Model_UpdateHistory** `@api` — the durable log of one-click update runs (table `update_history`). · `library/Tiger/Model/UpdateHistory.php`
- **Tiger_Module_Compat** `@api` — advisory "which Tiger versions was this module tested for?" metadata. · `library/Tiger/Module/Compat.php`
- **Tiger_Module_Dependency** `@api` — lightweight, lazy inter-module dependency alerts. · `library/Tiger/Module/Dependency.php`
Expand Down Expand Up @@ -306,6 +308,9 @@
- **Tiger_Model_AgentRun** `@api` — one turn's execution + control record (see migration 0036). · `library/Tiger/Model/AgentRun.php`
- **Tiger_Model_Comment** `@api` — the one comment/review store (migration 0045). · `library/Tiger/Model/Comment.php`
- **Tiger_Model_CommentAggregate** `@api` — the denormalized per-subject rollup (migration 0046). · `library/Tiger/Model/CommentAggregate.php`
- **Tiger_Model_GitCredential** `@api` — a least-privilege, encrypted-at-rest credential (TIGER-334, migration 0054). · `library/Tiger/Model/GitCredential.php`
- **Tiger_Model_GitOperationLog** `@api` — the append-only git audit trail (TIGER-334, migration 0054). · `library/Tiger/Model/GitOperationLog.php`
- **Tiger_Model_GitRepoConnection** `@api` — a module's connection to its GitHub repo (TIGER-334, migration 0054). · `library/Tiger/Model/GitRepoConnection.php`
- **Tiger_Model_Menu** `@api` — Menu — custom navigation menus (see migration 0017). · `library/Tiger/Model/Menu.php`
- **Tiger_Model_Message** `@api` — the message body store (migration 0047, TIGER-114). · `library/Tiger/Model/Message.php`
- **Tiger_Model_MessageBlock** `@api` — one user muting another, within an org (migration 0049). · `library/Tiger/Model/MessageBlock.php`
Expand All @@ -319,6 +324,16 @@

- **Tiger_Comment** `@api` — the subject registry and policy gate for comments, ratings and reviews. · `library/Tiger/Comment.php`
- **Tiger_Comment_Spam** `@api` — the spam-check registry, and the first checker: the in-platform AI agent. · `library/Tiger/Comment/Spam.php`
- **Tiger_Git** `@api` — the facade + shared constants for the platform's one git/GitHub layer (TIGER-334). · `library/Tiger/Git.php`
- **Tiger_Git_Credential** `@api` — the write-only credential manager (TIGER-334 / TIGER-365). · `library/Tiger/Git/Credential.php`
- **Tiger_Git_Environment** `@api` — detect which transport this instance can use (TIGER-334). · `library/Tiger/Git/Environment.php`
- **Tiger_Git_Exception** `@api` — a structured, agent-readable, SECRET-FREE git failure (TIGER-334). · `library/Tiger/Git/Exception.php`
- **Tiger_Git_Provider_GitHub** `@api` — the GitHub adapter: WHICH host, composing the two transports (TIGER-334). · `library/Tiger/Git/Provider/GitHub.php`
- **Tiger_Git_Provider_Interface** `@api` — WHICH host a connection talks to (TIGER-334). · `library/Tiger/Git/Provider/Interface.php`
- **Tiger_Git_Transport_Api** `@api` — git operations through the GitHub API, with NO `.git` directory (TIGER-364). · `library/Tiger/Git/Transport/Api.php`
- **Tiger_Git_Transport_Interface** `@api` — HOW a git operation is carried out (TIGER-334). · `library/Tiger/Git/Transport/Interface.php`
- **Tiger_Git_Transport_Shell** `@api` — git operations driven by the `git` binary (TIGER-364). · `library/Tiger/Git/Transport/Shell.php`
- **Tiger_Git_WorkingTree** `@api` — one module's git working tree (TIGER-334). · `library/Tiger/Git/WorkingTree.php`
- **Tiger_Message** `@api` — the platform's way of telling people things (TIGER-114). · `library/Tiger/Message.php`
- **Tiger_Secret** `@api` — provider-neutral resolution of secrets and sensitive config values. · `library/Tiger/Secret.php`
- **Tiger_Secret_Adapter_Chain** `@api` — try several backends in order; the first hit wins. · `library/Tiger/Secret/Adapter/Chain.php`
Expand Down Expand Up @@ -348,6 +363,7 @@
- **CMS** (`cms`, app) · services: Menu, Page, Paths, Settings · `modules/cms`
- **Code** (`code`, developer) · services: Code · `modules/code`
- **Comments** (`comment`, app) · services: Comment, Render, Settings, Subjects · `modules/comment`
- **TigerGit** (`git`, app) · services: Base, Release, Repo, Update · `modules/git`
- **Identity** (`identity`, plugin) · services: Identity · `modules/identity`
- **Matomo** (`matomo`, app) · services: Matomo · `modules/matomo`
- **Mcp** (`mcp`, module) · services: Settings · `modules/mcp`
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@ All notable changes to **Tiger Core** (`webtigers/tiger-core`). Format follows

## [Unreleased]

## [1.38.0] — 2026-10-21

### Added
- **TigerGit (`modules/git` + `Tiger_Git_*`) — first-class git/GitHub for a Tiger install** (TIGER-334).
Version a custom module in a private repo, cut a release through QA, and receive approved updates —
with no local machine, no terminal, and (on shared hosting with `exec` disabled) no git binary. No
other module talks to GitHub directly.
- **Two interchangeable transports behind one interface** (`Tiger_Git_Transport_Interface`): a
`ShellTransport` (the `git` binary via `proc_open`, host-agnostic) and an `ApiTransport` that commits
through the GitHub Git Data API with **no `.git` directory** — blob → tree → commit → fast-forward
ref — the path that makes shared hosting work. `Tiger_Git_Environment` detects which the host can use.
- **`Tiger_Git_Provider_GitHub`** composes them: it picks the working transport per connection and
routes the provider-API operations (releases, archives) to the API even on a shell connection. The
provider/transport split (`Tiger_Git_Provider_Interface`) keeps the door open for other hosts.
- **The no-`.git` reconciler** — `status` / `diff` / `pull` / `switchBranch` over the API, using a
`.tiger-git/head` merge-base marker so an exec-disabled host can tell local edits from the remote
advancing; `pull` fast-forwards while preserving non-conflicting local work.
- **Write-only credentials** (`Tiger_Git_Credential`, encrypted at rest; a single `resolve()` read
path for transports), an **active-only-unique** data model so reconnect-after-disconnect never
collides, and an append-only operation log.
- **`/api` services** — `Git_Service_Repo` (connect / status / recheck / list / log / disconnect),
`Git_Service_Release` (tag / submit / decide / list; tag + submit are owner-only via the
`Tiger_Git::RESOURCE_RELEASE` capability, not a role compare), and `Git_Service_Update` (check /
apply / setPolicy / rollback — an update only ever installs an **approved** release at its exact
tag). An admin dashboard at `/git/admin`, and the whole surface is runnable over **MCP** (the git
tools reflect into `tools/list`; `git` is in the curated default token set).

## [1.37.0] — 2026-10-21

### Removed
Expand Down
112 changes: 112 additions & 0 deletions library/Tiger/Git.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 WebTigers. Tiger™ and WebTigers™ are trademarks of WebTigers.
/**
* Tiger_Git — the facade + shared constants for the platform's one git/GitHub layer (TIGER-334).
*
* This is the single trusted entry point for every git interaction in the platform: versioning a
* custom module in a private repo, cutting a release through QA, and receiving approved updates — with
* no local machine, no terminal, and (on a shared-hosting account with `exec` disabled) no git binary.
* No other module talks to GitHub directly.
*
* The design is two interchangeable TRANSPORTS behind one interface
* (Tiger_Git_Transport_Interface): a ShellTransport that drives the `git` binary where the host allows
* it, and an ApiTransport that creates commits through the GitHub API with no working `.git` directory
* at all. Callers never know which is active; capability detection (Tiger_Git_Environment) chooses.
*
* This class holds only cross-cutting constants + small pure helpers. The work lives in the services
* (Tiger_Git_Service_*), the transports, and the models. Authorization is expressed as ACL privileges
* — never a role-string compare (TIGER-335): the sharp owner-only operations (tag, release submit) are
* a CAPABILITY resource, RESOURCE_RELEASE, checked with `_isAllowed('tag', Tiger_Git::RESOURCE_RELEASE)`.
*
* @api
* @since 1.38.0
*/
class Tiger_Git
{
/** Transport names (Tiger_Git_Environment picks one per instance; stored on the connection). */
const TRANSPORT_SHELL = 'shell';
const TRANSPORT_API = 'api';

/**
* The release CAPABILITY resource (ACL). The owner-only operations — creating a semver tag and
* submitting a release to QA — must be STRICTER than the service's own grant (editors/admins reach
* the service, but only an owner may tag/submit). Expressing that as a privilege on a capability
* resource — `_isAllowed('tag', Tiger_Git::RESOURCE_RELEASE)` — is the proven Tiger_Message_Compose
* / Tiger_Agent_Forge shape; a resource named after a role tier would just be a role compare with
* one level of indirection. Granted to the module-owner role in modules/git/configs/acl.ini.
*/
const RESOURCE_RELEASE = 'Tiger_Git_Release';

/** Release-capability privileges. */
const PRIV_TAG = 'tag';
const PRIV_SUBMIT = 'submit';

/** A connection's lifecycle status. */
const STATUS_CONNECTED = 'connected'; // verified, read (and write when enabled) work
const STATUS_DEGRADED = 'degraded'; // credential revoked/expired at GitHub; local reads still OK
const STATUS_REVOKED = 'revoked'; // disconnected by the owner

/** Update policy per installed module. */
const POLICY_AUTO = 'auto';
const POLICY_NOTIFY = 'notify'; // default
const POLICY_PINNED = 'pinned';

/** The only hosts Tiger_Git ever reaches (outbound allowlist). */
const ALLOWED_HOSTS = ['github.com', 'api.github.com', 'raw.githubusercontent.com', 'codeload.github.com'];

/**
* Is a string a valid semantic version (optionally `v`-prefixed, optional pre-release/build)?
* Tag creation and the manifest version are both validated against this.
*
* @param string $version e.g. "1.0.0", "v2.3.1-beta.1"
* @return bool
*/
public static function isSemver($version)
{
return (bool) preg_match(
'/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/',
(string) $version
);
}

/**
* Normalize a version for comparison: strip a leading `v`. (Not a comparator — just canonicalizes
* what a tag and a manifest might each write differently.)
*
* @param string $version
* @return string
*/
public static function normalizeVersion($version)
{
return ltrim((string) $version, 'vV');
}

/**
* Is $host one of the hosts Tiger_Git is permitted to reach? Enforced before any outbound request;
* a transport must refuse anything else rather than follow a redirect off-allowlist.
*
* @param string $host
* @return bool
*/
public static function isAllowedHost($host)
{
return in_array(strtolower((string) $host), self::ALLOWED_HOSTS, true);
}

/**
* Validate an `owner/repo` pair to GitHub's own character rules (so it can never be bent into a
* path-traversal or a second path segment). Returns [owner, repo] or null.
*
* @param string $owner
* @param string $repo
* @return array{0:string,1:string}|null
*/
public static function repoSlug($owner, $repo)
{
$owner = (string) $owner; $repo = (string) $repo;
$ok = '/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/';
if (!preg_match($ok, $owner) || !preg_match($ok, $repo)) { return null; }
return [$owner, $repo];
}
}
Loading
Loading