《独立开发者的商业指南·资本与退出》6.3 技术与知识产权尽调

技术 IP 尽调查五件事:代码归属、开源许可证、第三方依赖、域名账号与数据合规。LedgerLite 的 AGPL 合规成本 85,672、重写 20,400;Tidecast 的美术重制 30,240、回购最高 60,480。本节把修复成本换算成价格折让与 escrow 缺口,说明标准 10% 托管为什么不够。

以下情境为教学假设,人物与数字不代表经核实的真实案例。

技术尽调不是代码评审。代码写得好不好,买方可以自己看;尽调要回答的是另一组问题:这些代码是谁的、能不能转给买方、用了什么许可证、依赖谁、账号在谁手里、数据能不能合法带走。五个问题里只要有一个答案不清楚,买方买到的就不是一项可以自由使用的资产,而是一份需要补签字、补授权或补钱的权利。

Nord Studio 是本节的主案例。它是英国 Ltd,两位合伙人各持 50%,代码、域名、云账号和客户数据都可能分散在两个人名下;买方按 3.0× SDE 给出的锚是 102,960 美元。LedgerLite 做对照:一人公司,260 家订阅客户,核心服务里有一段 AGPL 组件,合规成本是 85,672 美元,重写替代是 20,400 美元。Tidecast 提供资产型案例:美术外包占资产 30%,重制预算 25,200 美元,音乐是买断授权且不得转授权。

五个检查:从权属到可转移性

五个检查按「能不能拥有、能不能用、能不能带走」排列。代码归属回答拥有;开源许可证和第三方依赖回答使用;域名账号和数据合规回答带走。买方不会把五项平均看待:权属不清可能让交易直接中止,许可证问题通常可以用钱解决,依赖和账号问题决定交割后能不能继续运营。

检查买方要看典型瑕疵量化后果
代码归属创始人、员工、外包的转让协议代码在个人名下、外包未转让补转让或重制;Tidecast 美术 30,240
开源许可证SBOM、依赖清单、许可证扫描AGPL 进入 SaaS合规 85,672 或重写 20,400
第三方依赖云、支付、平台的合同与退出条款支付 25%、云 60%、Steam 80%依赖中断期的收入暴露
域名与账号WHOIS、注册商、云根账号、平台账号账号在个人或前员工名下交割无法完成,可能中止交易
数据合规DPA、数据地图、子处理者、跨境安排无 DPA、跨境无依据首年 DPA/SCC 5,400;更高是客户流失

五项里,代码归属和账号是「开关型」问题:有就是有,没有就是没有,不能用价格完全补偿。许可证和依赖是「成本型」问题:可以算出一个修复金额,再决定折价还是托管。数据合规介于两者之间:补 DPA 有明确成本,但客户是否接受、监管是否追问,往往要交割后才知道。

代码归属与开源许可证

代码归属的第一个问题是「谁写的」。Nord 的两位合伙人在公司成立前后都写过代码;如果早期代码没有通过书面转让进入 Ltd,公司拥有的可能只是一份使用许可,而不是所有权。买方收购的是公司资产,代码不在公司名下,资产表就缺一块。补一份转让协议的成本不高,但它是交割条件;如果某位合伙人已经离开或不愿意签,买方只能要求重写或把价格压到能覆盖重写。

外包代码和美术是第二个问题。Tidecast 的美术外包占资产工作量 30%,按 84,000 美元的资产重置基数计算,重制预算是 84,000 × 30% = 25,200 美元;按教学假设加上 20% 的管理加成是 30,240 美元。如果外包方已经持有权利,回购还要付溢价,教学假设 50% 到 100%,区间是 45,360 到 60,480 美元。合同里加一句转让条款的成本接近于零,事后回购的成本是五位数美元,而且还要看外包方是否愿意配合。

开源许可证是第三个问题,也是 LedgerLite 的主要风险。AGPL 的触发点不是「有没有修改」,而是「有没有向网络用户提供服务」;订阅 SaaS 正好落在这个触发点上。卷二已经算过三条路径:继续合规的成本是 85,672 美元,重写替代组件是 20,400 美元,购买商业许可的教学报价是 30,000 美元。合规与重写的差额是 65,272 美元,相当于 768,768 美元价格锚的 8.5%。许可证是否触发取决于版本、修改方式和提供服务的形式,本节只做商业口径的保守估算,不是法律结论。

路径成本占 768,768说明
继续用 AGPL,合规85,67211.1%工程 13,600 + 三年竞争损失 72,072
重写替代组件20,4002.7%240 小时 × 85 美元,一次性为主
购买商业许可30,0003.9%教学假设报价,可能含年度支持
合规与重写差额65,2728.5%谈判区间,不是必然折价

依赖、账号与数据合规

第三方依赖不是「用了谁的 API」这么简单。买方要知道合同期限、终止条件、数据导出能力、价格调整机制和替代方案。LedgerLite 的支付依赖占 25%,按 240,240 美元计算是 60,060 美元收入;如果支付方终止服务且短期无法替换,按 3.2× ARR 计算的价格暴露是 60,060 × 3.2 = 192,192 美元,正好是 768,768 美元的 25%。云依赖占 60%,但云的替代性通常比支付高;Tidecast 的 Steam 占销量 80%,平台协议决定结算、退款和账号转让;Nord 的转介绍占获客 55%,依赖的是合伙人的关系,不是一纸合同。

域名与账号是最容易被当成行政细节的一项。域名注册商、DNS、云根账号、代码仓库、应用商店、支付后台、企业邮箱,任何一项在创始人个人名下,交割时都要多一次转移;如果在前员工或外包方名下,买方可能直接失去控制权。Nord 的两位合伙人如果一人控制云账号、另一人控制域名,任何一方不配合都能让交割停摆。买方的处理方式通常不是折价,而是把账号转移写成交割条件,或者在托管里留一笔直到转移完成。

数据合规要看三件事:有没有 DPA、数据流向哪里、客户能不能要求删除或导出。LedgerLite 的 DPA 年维护成本是 3,600 美元,首年 SCC 是 1,800 美元,合计 5,400 美元,占 768,768 美元的 0.7%;这笔钱不大,但如果买方继承的是没有 DPA 的数据处理关系,修复成本只是起点,客户流失和监管问询才是大头。Nord 的咨询项目要确认交付物和客户数据归谁、项目结束后是否删除;Tidecast 要确认玩家数据、分析工具和年龄分级。数据合规的边界是:本节不判断是否违法,只把可见的修复成本放进价格模型。

修复成本如何变成价格与托管

IP 瑕疵的价格处理有两条路:卖方在交割前修好,或者买方在价格里扣掉修复成本。前者不改价格,但会增加卖方的现金支出和时间;后者把修复责任转移给买方,通常按修复成本直接扣减。如果修复结果在交割时还不能确认,双方会把钱放进托管:先按修复成本加一个缓冲留存,修复完成或观察期结束后释放。下面的 10% 标准托管是案例设定的教学比例,不是行业统一标准。

案例瑕疵与路径修复成本占价格标准 10% 托管所需托管(+20%)缺口
LedgerLiteAGPL,重写20,4002.7%76,87724,4800
LedgerLiteAGPL,继续合规85,67211.1%76,877102,80625,930
Tidecast美术重制30,24012.2%24,87136,28811,417
Tidecast美术回购上限60,48024.3%24,87172,57647,705

这张表最重要的一行是 Tidecast 的美术重制:修复成本 30,240 美元,占 248,708 美元价格的 12.2%,而标准 10% 托管只有 24,871 美元,缺口 11,417 美元。也就是说,10% 托管覆盖不了这项瑕疵;买方要么把托管提高到 36,288 美元(14.6%),要么在价格里先扣 30,240 美元。LedgerLite 的 AGPL 重写只占 2.7%,10% 托管足够覆盖;但如果卖方选择继续合规,成本 85,672 美元占 11.1%,托管需要提高到 102,806 美元(13.4%),标准 10% 不够。

修复成本和托管金额的对应关系可以写成一句话:所需托管 = 修复成本 ×(1 + 缓冲比例),再和标准托管比较;差额就是买方要多留的钱,或者要从价格里先扣的钱。缓冲比例是教学假设的 20%,用来覆盖修复超支和观察期风险;真实交易可能用 10% 到 30%,取决于瑕疵的可验证程度。托管不是折价:如果修复完成、没有索赔,钱会退回卖方;折价不会退回。卖方在选择「自己修」还是「让买方扣」时,要比较的是现金时间和托管释放条件,而不只是金额。

把修复写成代码:tech-ip-dd.ts

下面的 tech-ip-dd.ts 把两条路径写成函数。compareRemediation 接收每个路径的一次性成本、年度成本和年数,按总成本排序,返回最便宜路径和最贵路径的差额;escrowPlan 接收价格、标准托管比例、修复成本和缓冲比例,返回标准托管、所需托管、缺口和所需托管占价格的基点。金额用整数美分,比例用基点。

// file: src/capital/tech-ip-dd.ts
// tier: L1
// verify: node --test test/capital/tech-ip-dd.test.ts

import { type Cents } from "../core/money.ts";
import { type Bps, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";

export type RemediationPath =
  | "rewrite"
  | "commercial-license"
  | "compliance"
  | "rebuy"
  | "rebuy-premium"
  | "fix";

export type RemediationOption = {
  path: RemediationPath;
  oneTimeCents: Cents;
  annualCents: Cents;
  years: number;
};

export type PathTotal = {
  path: RemediationPath;
  totalCents: Cents;
};

export type PathComparison = {
  rows: PathTotal[];
  cheapest: RemediationPath;
  cheapestCents: Cents;
  spreadCents: Cents;
};

export type EscrowInput = {
  priceCents: Cents;
  standardEscrowBps: Bps;
  remediationCents: Cents;
  bufferBps: Bps;
};

export type EscrowPlan = {
  standardEscrowCents: Cents;
  requiredEscrowCents: Cents;
  shortfallCents: Cents;
  requiredBps: Bps;
  covered: boolean;
};

function isPath(value: string): value is RemediationPath {
  return (
    value === "rewrite" ||
    value === "commercial-license" ||
    value === "compliance" ||
    value === "rebuy" ||
    value === "rebuy-premium" ||
    value === "fix"
  );
}

function validCost(value: Cents): boolean {
  return Number.isInteger(value) && value >= 0;
}

function validBps(value: Bps): boolean {
  return Number.isInteger(value) && value >= 0 && value <= 10000;
}

function validYears(value: number): boolean {
  return Number.isInteger(value) && value >= 0;
}

/**
 * Paths are compared on total cost, not on headline quote. A rewrite is
 * one-time; compliance usually carries an annual competition loss. Sorting is
 * ascending, and ties keep the input order so the seller can present the
 * preferred path first.
 */
export function compareRemediation(
  options: RemediationOption[],
): Result<PathComparison> {
  if (options.length === 0) {
    return fail("at least one remediation path is required");
  }

  const seen = new Set<RemediationPath>();
  const rows: { row: PathTotal; index: number }[] = [];
  for (const option of options) {
    if (!isPath(option.path)) {
      return fail(`unknown remediation path ${option.path}`);
    }
    if (seen.has(option.path)) {
      return fail(`duplicate remediation path ${option.path}`);
    }
    if (!validCost(option.oneTimeCents)) {
      return fail(`${option.path} one-time cost must be a non-negative integer`);
    }
    if (!validCost(option.annualCents)) {
      return fail(`${option.path} annual cost must be a non-negative integer`);
    }
    if (!validYears(option.years)) {
      return fail(`${option.path} years must be a non-negative integer`);
    }
    seen.add(option.path);
    rows.push({
      row: {
        path: option.path,
        totalCents: option.oneTimeCents + option.annualCents * option.years,
      },
      index: rows.length,
    });
  }

  const ordered = rows
    .sort(
      (a, b) => a.row.totalCents - b.row.totalCents || a.index - b.index,
    )
    .map((entry) => entry.row);
  const cheapest = ordered[0];
  const mostExpensive = ordered[ordered.length - 1];

  return ok({
    rows: ordered,
    cheapest: cheapest.path,
    cheapestCents: cheapest.totalCents,
    spreadCents: mostExpensive.totalCents - cheapest.totalCents,
  });
}

/**
 * The standard escrow is a percentage of price. Cure cost can exceed it, so
 * the required escrow is the cure cost plus a buffer. The return value shows
 * both the shortfall and the escrow as a share of price, which is the number
 * that belongs in the term sheet.
 */
export function escrowPlan(input: EscrowInput): Result<EscrowPlan> {
  if (!Number.isInteger(input.priceCents) || input.priceCents <= 0) {
    return fail("price must be a positive integer amount");
  }
  if (!validBps(input.standardEscrowBps)) {
    return fail("standard escrow must be between 0 and 100%");
  }
  if (!validCost(input.remediationCents)) {
    return fail("remediation cost must be a non-negative integer");
  }
  if (!validBps(input.bufferBps)) {
    return fail("escrow buffer must be between 0 and 100%");
  }

  const standardEscrowCents = Math.round(
    (input.priceCents * input.standardEscrowBps) / 10000,
  );
  const requiredEscrowCents = Math.round(
    (input.remediationCents * (10000 + input.bufferBps)) / 10000,
  );

  return ok({
    standardEscrowCents,
    requiredEscrowCents,
    shortfallCents: Math.max(0, requiredEscrowCents - standardEscrowCents),
    requiredBps: divide(requiredEscrowCents, input.priceCents),
    covered: requiredEscrowCents <= standardEscrowCents,
  });
}

把 LedgerLite 代进去:合规路径是 1,360,000 + 2,402,400 × 3 = 8,567,200 美分,重写是 2,040,000 美分,商业许可是 3,000,000 美分;最便宜是重写,差额 6,527,200 美分,对应 65,272 美元。Tidecast 的美术重制是 3,024,000 美分,回购下限 4,536,000 美分,上限 6,048,000 美分;最便宜是重制,差额 3,024,000 美分,对应 30,240 美元。

托管函数对 LedgerLite 重写返回标准 7,687,680 美分、所需 2,448,000 美分、缺口 0、所需 318 个基点;对合规路径返回所需 10,280,640 美分、缺口 2,592,960 美分、所需 1337 个基点;对 Tidecast 重制返回标准 2,487,078 美分、所需 3,628,800 美分、缺口 1,141,722 美分、所需 1459 个基点。这些数字和上面的表逐行对应。

代码的边界是「已识别的修复路径」。它不判断许可证是否触发,不替代转让协议,也不计算客户流失;它只把修复成本、缓冲和托管放在同一张表上,让卖方知道「修」和「扣」哪一个更贵。权属、账号和数据合规里那些不能用钱完全补偿的部分,仍然要写进交割条件。

小结

  • 技术 IP 尽调做五个检查:代码归属、开源许可证、第三方依赖、域名与账号、数据合规。
  • 代码归属是开关型问题:Nord 的早期代码如果没有转让给 Ltd,补协议是交割条件,不是折价项。
  • LedgerLite 的 AGPL 合规成本是 85,672 美元(11.1%),重写是 20,400 美元(2.7%),差额 65,272 美元。
  • Tidecast 的美术重制是 30,240 美元(12.2%),回购区间 45,360 到 60,480 美元(18.2% 到 24.3%)。
  • 标准 10% 托管覆盖不了 Tidecast 的美术重制:缺口 11,417 美元,所需托管 36,288 美元(14.6%)。
  • AGPL 继续合规的所需托管是 102,806 美元(13.4%),标准 10% 托管缺口 25,930 美元。
  • 所需托管 = 修复成本 ×(1 + 缓冲比例);托管可能退回,折价不会。
  • 许可证是否触发、数据是否合规,都要按实际架构和当期法规核对;案例数字只用于演示结构。

阅读导航:上一节:6.2 客户与合同尽调 · 下一节:7.1 资产收购与股权收购 。

常见错误与边界

  1. 把技术尽调当成代码评审。 代码质量影响未来成本,权属和许可证影响交易能不能成立;两者不是一回事。
  2. 以为代码是谁写的就是谁的。 在很多法域,创始人个人写的代码如果没有转让,公司可能只有使用权;买方买到的资产表里就没有这块。
  3. 把外包付款当成权利转让。 付款买的是服务,不是著作权;Tidecast 的美术重制 30,240 美元就是缺口的价格。
  4. 忽略 AGPL 的触发点。 订阅 SaaS 向网络用户提供服务,可能直接触发 AGPL;合规成本 85,672 美元不是许可证费。
  5. 只用标准 10% 托管。 Tidecast 的美术修复占价格 12.2%,10% 托管不够;所需托管要按修复成本加缓冲算。
  6. 把托管当成折价。 托管完成修复后会退回;折价不会。两者对卖方的现金流影响完全不同。
  7. 把账号当成行政细节。 域名、云账号、平台账号不在公司名下,交割可能直接停摆;这不是钱能完全解决的问题。
  8. 用案例税率或法律结论。 许可证是否触发、数据跨境是否合规,取决于实际架构和当期法规;本节只做商业估算。

本节的边界是「技术与 IP 瑕疵如何变成价格与托管」:不展开客户集中度、财务尽调和交割迁移,也不对许可证或数据合规给法律意见。开源许可证的商业边界见 7.2 开源许可证的商业边界 ,IP 归属与转让见 7.3 IP 归属、转让与员工发明 ,账号与合同迁移见 8.1 交割清单与资金流 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《移动游戏产品的架构·商业化与发行工程》附录 C 商业化与发行评审清单
  2. 《移动游戏产品的架构·商业化与发行工程》附录 B 双案例商业化事实表
  3. 《移动游戏产品的架构·商业化与发行工程》附录 A 商业化与发行术语速查